refactoring part 2, use source generators for GameData

This commit is contained in:
Mikhail
2025-05-03 11:52:09 -04:00
parent 4265c43b4f
commit 0b936fa6e5
15 changed files with 346 additions and 358 deletions

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<IsRoslynComponent>true</IsRoslynComponent>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,104 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Immutable;
using System.Text;
namespace EpinelPS.Analyzers;
[Generator]
public class LoadRecordInitializerGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Step 1: Filter for field declarations with attributes
var fieldDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (node, _) => node is FieldDeclarationSyntax fds && fds.AttributeLists.Count > 0,
transform: static (ctx, _) => GetTargetFieldInfo(ctx)
)
.Where(static m => m is not null)
.Collect();
// Step 2: Generate the code
context.RegisterSourceOutput(fieldDeclarations, (spc, fieldInfos) =>
{
var source = GenerateInitializerCode(fieldInfos!);
spc.AddSource("GameDataInitializer.g.cs", SourceText.From(source, Encoding.UTF8));
});
}
private static LoadFieldInfo? GetTargetFieldInfo(GeneratorSyntaxContext context)
{
if (context.Node is not FieldDeclarationSyntax fieldDecl)
return null;
var variable = fieldDecl.Declaration.Variables.FirstOrDefault();
if (variable == null)
return null;
if (context.SemanticModel.GetDeclaredSymbol(variable) is not IFieldSymbol symbol)
return null;
foreach (var attr in symbol.GetAttributes())
{
if (attr.ConstructorArguments.Length == 3)
{
if (attr.ConstructorArguments[0].Value is not string fileName || attr.ConstructorArguments[1].Value is not string key || attr.ConstructorArguments[2].Value is not INamedTypeSymbol recordType)
return null;
return new LoadFieldInfo
{
ContainingClass = symbol.ContainingType.ToDisplayString(),
FieldName = symbol.Name,
FileName = fileName,
Key = key,
RecordTypeName = recordType.ToDisplayString()
};
}
}
return null;
}
private static string GenerateInitializerCode(ImmutableArray<LoadFieldInfo> fieldInfos)
{
var sb = new StringBuilder();
sb.AppendLine("using System.Collections.Generic;");
sb.AppendLine("using System.Threading.Tasks;");
sb.AppendLine();
sb.AppendLine("namespace EpinelPS.Data;");
sb.AppendLine("public static class GameDataInitializer");
sb.AppendLine("{");
sb.AppendFormat($"public static int TotalFiles = {fieldInfos.Length};");
sb.AppendLine(" public static async Task InitializeGameData(IProgress<double> progress = null)");
sb.AppendLine(" {");
foreach (var info in fieldInfos)
{
var tempVar = $"data_{info.FieldName}";
sb.AppendLine($" var {tempVar} = await {info.ContainingClass}.Instance.LoadZip<{info.RecordTypeName}>(\"{info.FileName}\", progress);");
sb.AppendLine($" foreach (var obj in {tempVar}.records)");
sb.AppendLine(" {");
sb.AppendLine($" {info.ContainingClass}.Instance.{info.FieldName}.Add(obj.{info.Key}, obj);");
sb.AppendLine(" }");
}
sb.AppendLine(" }");
sb.AppendLine("}");
return sb.ToString();
}
private class LoadFieldInfo
{
public string ContainingClass = "";
public string FieldName = "";
public string FileName = "";
public string Key = "";
public string RecordTypeName = "";
}
}

View File

@@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerSelector.Desktop", "S
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{4BB2E77F-84A6-4644-9FB3-38E2A7DCDEC0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EpinelPS.Analyzers", "EpinelPS.Analyzers\EpinelPS.Analyzers.csproj", "{E3B18A3D-B20B-447D-BCBE-12931E18B41E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -99,6 +101,30 @@ Global
{01D0A73A-A881-439D-9318-54DB5B00D6F5}.ReleaseDLL|x64.Build.0 = Release|Any CPU
{01D0A73A-A881-439D-9318-54DB5B00D6F5}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
{01D0A73A-A881-439D-9318-54DB5B00D6F5}.ReleaseDLL|x86.Build.0 = Release|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|x64.ActiveCfg = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|x64.Build.0 = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|x86.ActiveCfg = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|x86.Build.0 = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|x64.Build.0 = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|x86.Build.0 = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|Any CPU.Build.0 = Release|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|x64.ActiveCfg = Release|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|x64.Build.0 = Release|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|x86.ActiveCfg = Release|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|x86.Build.0 = Release|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|Any CPU.ActiveCfg = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|Any CPU.Build.0 = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|x64.ActiveCfg = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|x64.Build.0 = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|x86.ActiveCfg = Debug|Any CPU
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|x86.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -23,60 +23,147 @@ namespace EpinelPS.Data
}
}
private ZipFile MainZip;
private MemoryStream ZipStream;
private readonly Dictionary<int, MainQuestCompletionRecord> questDataRecords = [];
private readonly Dictionary<int, CampaignStageRecord> stageDataRecords = [];
private readonly Dictionary<int, RewardTableRecord> rewardDataRecords = [];
private readonly Dictionary<int, UserExpRecord> userExpDataRecords = [];
private readonly Dictionary<int, CampaignChapterRecord> chapterCampaignData = [];
private readonly Dictionary<int, CharacterCostumeRecord> characterCostumeTable = [];
public readonly Dictionary<int, CharacterRecord> characterTable = [];
public readonly Dictionary<int, ClearedTutorialData> tutorialTable = [];
public readonly Dictionary<int, ItemEquipRecord> itemEquipTable = [];
public readonly Dictionary<int, ItemMaterialRecord> itemMaterialTable = [];
public readonly Dictionary<int, ItemEquipExpRecord> itemEquipExpTable = [];
public readonly Dictionary<int, ItemEquipGradeExpRecord> ItemEquipGradeExpTable = [];
private readonly Dictionary<string, JArray> FieldMapData = [];
private readonly Dictionary<int, CharacterLevelData> LevelData = [];
private readonly Dictionary<int, TacticAcademyLessonRecord> TacticAcademyLessons = [];
public readonly Dictionary<int, SideStoryStageRecord> SidestoryRewardTable = [];
public readonly Dictionary<string, int> PositionReward = [];
public readonly Dictionary<int, FieldItemRecord> FieldItems = [];
public readonly Dictionary<int, OutpostBattleTableRecord> OutpostBattle = [];
public readonly Dictionary<int, JukeboxListRecord> jukeboxListDataRecords = [];
private readonly Dictionary<int, JukeboxThemeRecord> jukeboxThemeDataRecords = [];
public readonly Dictionary<int, GachaType> gachaTypes = [];
public readonly Dictionary<int, EventManager> eventManagers = [];
public readonly Dictionary<int, LiveWallpaperRecord> lwptablemgrs = [];
public readonly Dictionary<int, AlbumResourceRecord> albumResourceRecords = [];
public readonly Dictionary<int, UserFrameTableRecord> userFrameTable = [];
public readonly Dictionary<int, ArchiveRecordManagerRecord> archiveRecordManagerTable = [];
public readonly Dictionary<int, ArchiveEventStoryRecord> archiveEventStoryRecords = [];
public readonly Dictionary<int, ArchiveEventQuestRecord> archiveEventQuestRecords = [];
public readonly Dictionary<int, ArchiveEventDungeonStageRecord> archiveEventDungeonStageRecords = [];
public readonly Dictionary<int, UserTitleRecord> userTitleRecords = [];
public readonly Dictionary<int, ArchiveMessengerConditionRecord> archiveMessengerConditionRecords = [];
public readonly Dictionary<int, CharacterStatRecord> characterStatTable = [];
public readonly Dictionary<int, SkillInfoRecord> skillInfoTable = [];
public readonly Dictionary<int, CostRecord> costTable = [];
public readonly Dictionary<string, MidasProductRecord> mediasProductTable = [];
public readonly Dictionary<int, TowerRecord> towerTable = [];
public readonly Dictionary<int, TriggerRecord> TriggerTable = [];
public readonly Dictionary<int, InfracoreRecord> InfracoreTable = [];
public readonly Dictionary<int, AttractiveCounselCharacterRecord> AttractiveCounselCharacterTable = [];
public readonly Dictionary<int, AttractiveLevelRewardRecord> AttractiveLevelReward = [];
public readonly Dictionary<int, SubquestRecord> Subquests = [];
public readonly Dictionary<string, MessengerDialogRecord> Messages = [];
public readonly Dictionary<int, MessengerMsgConditionRecord> MessageConditions = [];
public readonly Dictionary<string, ScenarioRewardRecord> ScenarioRewards = [];
public byte[] Sha256Hash;
public int Size;
private ZipFile MainZip;
private MemoryStream ZipStream;
private int totalFiles = 1;
private int currentFile = 0;
[LoadRecord("MainQuestTable.json", "id", typeof(MainQuestCompletionTable))]
public readonly Dictionary<int, MainQuestCompletionRecord> QuestDataRecords = [];
[LoadRecord("CampaignStageTable.json", "id", typeof(CampaignStageTable))]
public readonly Dictionary<int, CampaignStageRecord> StageDataRecords = [];
[LoadRecord("RewardTable.json", "id", typeof(RewardTable))]
public readonly Dictionary<int, RewardTableRecord> RewardDataRecords = [];
[LoadRecord("UserExpTable.json", "level", typeof(UserExpTable))]
public readonly Dictionary<int, UserExpRecord> UserExpDataRecords = [];
[LoadRecord("CampaignChapterTable.json", "chapter", typeof(CampaignChapterTable))]
public readonly Dictionary<int, CampaignChapterRecord> ChapterCampaignData = [];
[LoadRecord("CharacterCostumeTable.json", "id", typeof(CharacterCostumeTable))]
public readonly Dictionary<int, CharacterCostumeRecord> CharacterCostumeTable = [];
[LoadRecord("CharacterTable.json", "id", typeof(CharacterTable))]
public readonly Dictionary<int, CharacterRecord> CharacterTable = [];
[LoadRecord("ContentsTutorialTable.json", "id", typeof(TutorialTable))]
public readonly Dictionary<int, ClearedTutorialData> TutorialTable = [];
[LoadRecord("ItemEquipTable.json", "id", typeof(ItemEquipTable))]
public readonly Dictionary<int, ItemEquipRecord> ItemEquipTable = [];
[LoadRecord("ItemMaterialTable.json", "id", typeof(ItemMaterialTable))]
public readonly Dictionary<int, ItemMaterialRecord> itemMaterialTable = [];
[LoadRecord("ItemEquipExpTable.json", "id", typeof(ItemEquipExpTable))]
public readonly Dictionary<int, ItemEquipExpRecord> itemEquipExpTable = [];
[LoadRecord("ItemEquipGradeExpTable.json", "id", typeof(ItemEquipGradeExpTable))]
public readonly Dictionary<int, ItemEquipGradeExpRecord> ItemEquipGradeExpTable = [];
[LoadRecord("CharacterLevelTable.json", "level", typeof(CharacterLevelTable))]
public readonly Dictionary<int, CharacterLevelData> LevelData = [];
[LoadRecord("TacticAcademyFunctionTable.json", "id", typeof(TacticAcademyLessonTable))]
public readonly Dictionary<int, TacticAcademyLessonRecord> TacticAcademyLessons = [];
[LoadRecord("SideStoryStageTable.json", "id", typeof(SideStoryStageTable))]
public readonly Dictionary<int, SideStoryStageRecord> SidestoryRewardTable = [];
public readonly Dictionary<string, int> PositionReward = [];
[LoadRecord("FieldItemTable.json", "id", typeof(FieldItemTable))]
public readonly Dictionary<int, FieldItemRecord> FieldItems = [];
[LoadRecord("OutpostBattleTable.json", "id", typeof(OutpostBattleTable))]
public readonly Dictionary<int, OutpostBattleTableRecord> OutpostBattle = [];
[LoadRecord("JukeboxListTable.json", "id", typeof(JukeboxListTable))]
public readonly Dictionary<int, JukeboxListRecord> jukeboxListDataRecords = [];
[LoadRecord("JukeboxThemeTable.json", "id", typeof(JukeboxThemeTable))]
public readonly Dictionary<int, JukeboxThemeRecord> jukeboxThemeDataRecords = [];
[LoadRecord("GachaTypeTable.json", "id", typeof(GachaTypeTable))]
public readonly Dictionary<int, GachaType> gachaTypes = [];
[LoadRecord("EventManagerTable.json", "id", typeof(EventManagerTable))]
public readonly Dictionary<int, EventManager> eventManagers = [];
[LoadRecord("LiveWallpaperTable.json", "id", typeof(LiveWallpaperTable))]
public readonly Dictionary<int, LiveWallpaperRecord> lwptablemgrs = [];
[LoadRecord("AlbumResourceTable.json", "id", typeof(AlbumResourceTable))]
public readonly Dictionary<int, AlbumResourceRecord> albumResourceRecords = [];
[LoadRecord("UserFrameTable.json", "id", typeof(UserFrameTable))]
public readonly Dictionary<int, UserFrameTableRecord> userFrameTable = [];
[LoadRecord("ArchiveRecordManagerTable.json", "id", typeof(ArchiveRecordManagerTable))]
public readonly Dictionary<int, ArchiveRecordManagerRecord> archiveRecordManagerTable = [];
[LoadRecord("ArchiveEventStoryTable.json", "id", typeof(ArchiveEventStoryTable))]
public readonly Dictionary<int, ArchiveEventStoryRecord> archiveEventStoryRecords = [];
[LoadRecord("ArchiveEventQuestTable.json", "id", typeof(ArchiveEventQuestTable))]
public readonly Dictionary<int, ArchiveEventQuestRecord> archiveEventQuestRecords = [];
[LoadRecord("ArchiveEventDungeonStageTable.json", "id", typeof(ArchiveEventDungeonStageTable))]
public readonly Dictionary<int, ArchiveEventDungeonStageRecord> archiveEventDungeonStageRecords = [];
[LoadRecord("UserTitleTable.json", "id", typeof(UserTitleTable))]
public readonly Dictionary<int, UserTitleRecord> userTitleRecords = [];
[LoadRecord("ArchiveMessengerConditionTable.json", "id", typeof(ArchiveMessengerConditionTable))]
public readonly Dictionary<int, ArchiveMessengerConditionRecord> archiveMessengerConditionRecords = [];
[LoadRecord("CharacterStatTable.json", "id", typeof(CharacterStatTable))]
public readonly Dictionary<int, CharacterStatRecord> characterStatTable = [];
[LoadRecord("SkillInfoTable.json", "id", typeof(SkillInfoTable))]
public readonly Dictionary<int, SkillInfoRecord> skillInfoTable = [];
[LoadRecord("CostTable.json", "id", typeof(CostTable))]
public readonly Dictionary<int, CostRecord> costTable = [];
[LoadRecord("MidasProductTable.json", "midas_product_id_proximabeta", typeof(MidasProductTable))]
public readonly Dictionary<string, MidasProductRecord> mediasProductTable = [];
[LoadRecord("TowerTable.json", "id", typeof(TowerTable))]
public readonly Dictionary<int, TowerRecord> towerTable = [];
[LoadRecord("TriggerTable.json", "id", typeof(TriggerTable))]
public readonly Dictionary<int, TriggerRecord> TriggerTable = [];
[LoadRecord("InfraCoreGradeTable.json", "id", typeof(InfracoreTable))]
public readonly Dictionary<int, InfracoreRecord> InfracoreTable = [];
[LoadRecord("AttractiveCounselCharacterTable.json", "name_code", typeof(AttractiveCounselCharacterTable))]
public readonly Dictionary<int, AttractiveCounselCharacterRecord> AttractiveCounselCharacterTable = [];
[LoadRecord("AttractiveLevelRewardTable.json", "id", typeof(AttractiveLevelRewardTable))]
public readonly Dictionary<int, AttractiveLevelRewardRecord> AttractiveLevelReward = [];
[LoadRecord("SubQuestTable.json", "id", typeof(SubquestTable))]
public readonly Dictionary<int, SubquestRecord> Subquests = [];
[LoadRecord("MessengerDialogTable.json", "id", typeof(MessengerDialogTable))]
public readonly Dictionary<string, MessengerDialogRecord> Messages = [];
[LoadRecord("MessengerConditionTriggerTable.json", "id", typeof(MessengerMsgConditionTable))]
public readonly Dictionary<int, MessengerMsgConditionRecord> MessageConditions = [];
[LoadRecord("ScenarioRewardsTable.json", "condition_id", typeof(ScenarioRewardTable))]
public readonly Dictionary<string, ScenarioRewardRecord> ScenarioRewards = [];
static async Task<GameData> BuildAsync()
{
await Load();
@@ -232,7 +319,7 @@ namespace EpinelPS.Data
}
#endregion
private async Task<T> LoadZip<T>(string entry, ProgressBar bar)
public async Task<T> LoadZip<T>(string entry, IProgress<double> bar)
{
var mainQuestData = MainZip.GetEntry(entry) ?? throw new Exception(entry + " does not exist in static data");
using StreamReader mainQuestReader = new(MainZip.GetInputStream(mainQuestData));
@@ -262,102 +349,13 @@ namespace EpinelPS.Data
return records;
}
int totalFiles = 90;
int currentFile = 0;
public async Task Parse()
{
using var progress = new ProgressBar();
var questDataRecords = await LoadZip<MainQuestCompletionTable>("MainQuestTable.json", progress);
foreach (var obj in questDataRecords.records)
{
this.questDataRecords.Add(obj.id, obj);
}
totalFiles = GameDataInitializer.TotalFiles;
var stageDataRecords = await LoadZip<CampaignStageTable>("CampaignStageTable.json", progress);
foreach (var obj in stageDataRecords.records)
{
this.stageDataRecords.Add(obj.id, obj);
}
var rewardDataRecords = await LoadZip<RewardTable>("RewardTable.json", progress);
foreach (var obj in rewardDataRecords.records)
{
this.rewardDataRecords.Add(obj.id, obj);
}
var chapterCampaignData = await LoadZip<CampaignChapterTable>("CampaignChapterTable.json", progress);
foreach (var obj in chapterCampaignData.records)
{
this.chapterCampaignData.Add(obj.chapter, obj);
}
var userExpDataRecords = await LoadZip<UserExpTable>("UserExpTable.json", progress);
foreach (var obj in userExpDataRecords.records)
{
this.userExpDataRecords.Add(obj.level, obj);
}
var characterCostumeTable = await LoadZip<CharacterCostumeTable>("CharacterCostumeTable.json", progress);
foreach (var obj in characterCostumeTable.records)
{
this.characterCostumeTable.Add(obj.id, obj);
}
var characterTable = await LoadZip<CharacterTable>("CharacterTable.json", progress);
foreach (var obj in characterTable.records)
{
this.characterTable.Add(obj.id, obj);
}
var tutorialTable = await LoadZip<TutorialTable>("ContentsTutorialTable.json", progress);
foreach (var obj in tutorialTable.records)
{
this.tutorialTable.Add(obj.id, obj);
}
var itemEquipTable = await LoadZip<ItemEquipTable>("ItemEquipTable.json", progress);
foreach (var obj in itemEquipTable.records)
{
this.itemEquipTable.Add(obj.id, obj);
}
var itemMaterialTable = await LoadZip<ItemMaterialTable>("ItemMaterialTable.json", progress);
foreach (var obj in itemMaterialTable.records)
{
this.itemMaterialTable.Add(obj.id, obj);
}
var itemEquipExpTable = await LoadZip<ItemEquipExpTable>("ItemEquipExpTable.json", progress);
foreach (var obj in itemEquipExpTable.records)
{
this.itemEquipExpTable.Add(obj.id, obj);
}
var ItemEquipGradeExpTable = await LoadZip<ItemEquipGradeExpTable>("ItemEquipGradeExpTable.json", progress);
foreach (var obj in ItemEquipGradeExpTable.records)
{
this.ItemEquipGradeExpTable.Add(obj.id, obj);
}
var characterLevelTable = await LoadZip<CharacterLevelTable>("CharacterLevelTable.json", progress);
foreach (var obj in characterLevelTable.records)
{
LevelData.Add(obj.level, obj);
}
var tacticLessonTable = await LoadZip<TacticAcademyLessonTable>("TacticAcademyFunctionTable.json", progress);
foreach (var obj in tacticLessonTable.records)
{
TacticAcademyLessons.Add(obj.id, obj);
}
var sidestoryTable = await LoadZip<SideStoryStageTable>("SideStoryStageTable.json", progress);
foreach (var obj in sidestoryTable.records)
{
SidestoryRewardTable.Add(obj.id, obj);
}
await GameDataInitializer.InitializeGameData(progress);
foreach (ZipEntry item in MainZip)
{
@@ -382,184 +380,12 @@ namespace EpinelPS.Data
}
}
}
var fieldItems = await LoadZip<FieldItemTable>("FieldItemTable.json", progress);
foreach (var obj in fieldItems.records)
{
FieldItems.Add(obj.id, obj);
}
var battleOutpostTable = await LoadZip<OutpostBattleTable>("OutpostBattleTable.json", progress);
foreach (var obj in battleOutpostTable.records)
{
OutpostBattle.Add(obj.id, obj);
}
var archiveRecordManagerTableData = await LoadZip<ArchiveRecordManagerTable>("ArchiveRecordManagerTable.json", progress);
foreach (var obj in archiveRecordManagerTableData.records)
{
archiveRecordManagerTable.Add(obj.id, obj);
}
var gachaTypeTable = await LoadZip<GachaTypeTable>("GachaTypeTable.json", progress);
// Add the records to the dictionary
foreach (var obj in gachaTypeTable.records)
{
gachaTypes.Add(obj.id, obj); // Use obj.id as the key and obj (the GachaType) as the value
}
var eventManagerTable = await LoadZip<EventManagerTable>("EventManagerTable.json", progress);
// Add the records to the dictionary
foreach (var obj in eventManagerTable.records)
{
eventManagers.Add(obj.id, obj); // Use obj.id as the key and obj (the EventManager) as the value
}
var lwptable = await LoadZip<LiveWallpaperTable>("LiveWallpaperTable.json", progress);
// Add the records to the dictionary
foreach (var obj in lwptable.records)
{
lwptablemgrs.Add(obj.id, obj); // Use obj.id as the key and obj (the LiveWallpaperRecord) as the value
}
var userFrameData = await LoadZip<UserFrameTable>("UserFrameTable.json", progress);
foreach (var record in userFrameData.records)
{
userFrameTable[record.id] = record;
}
// Load and parse ArchiveEventDungeonStageTable.json
var archiveEventDungeonStageData = await LoadZip<ArchiveEventDungeonStageTable>("ArchiveEventDungeonStageTable.json", progress);
foreach (var obj in archiveEventDungeonStageData.records)
{
archiveEventDungeonStageRecords.Add(obj.id, obj);
}
var userTitleTable = await LoadZip<UserTitleTable>("UserTitleTable.json", progress);
foreach (var obj in userTitleTable.records)
{
userTitleRecords.Add(obj.id, obj);
}
// Load and parse ArchiveEventStoryTable.json
var archiveEventStoryTable = await LoadZip<ArchiveEventStoryTable>("ArchiveEventStoryTable.json", progress);
foreach (var obj in archiveEventStoryTable.records)
{
archiveEventStoryRecords.Add(obj.id, obj);
}
// Load and parse ArchiveEventQuestTable.json
var archiveEventQuestTable = await LoadZip<ArchiveEventQuestTable>("ArchiveEventQuestTable.json", progress);
foreach (var obj in archiveEventQuestTable.records)
{
archiveEventQuestRecords.Add(obj.id, obj);
}
// LOAD ARCHIVE MESSENGER CONDITION TABLE
var archiveMessengerConditionTable = await LoadZip<ArchiveMessengerConditionTable>("ArchiveMessengerConditionTable.json", progress);
foreach (var obj in archiveMessengerConditionTable.records)
{
archiveMessengerConditionRecords.Add(obj.id, obj);
}
var albumResourceTable = await LoadZip<AlbumResourceTable>("AlbumResourceTable.json", progress);
foreach (var obj in albumResourceTable.records)
{
albumResourceRecords.Add(obj.id, obj); // Now refers to the class-level field
}
var jukeboxListData = await LoadZip<JukeboxListTable>("JukeboxListTable.json", progress);
foreach (var obj in jukeboxListData.records)
{
jukeboxListDataRecords.Add(obj.id, obj); // Now refers to the class-level field
}
var jukeboxThemeData = await LoadZip<JukeboxThemeTable>("JukeboxThemeTable.json", progress);
foreach (var obj in jukeboxThemeData.records)
{
jukeboxThemeDataRecords.Add(obj.id, obj); // Now refers to the class-level field
}
var characterStatTable = await LoadZip<CharacterStatTable>("CharacterStatTable.json", progress);
foreach (var obj in characterStatTable.records)
{
this.characterStatTable.Add(obj.id, obj);
}
var skillinfoTable = await LoadZip<SkillInfoTable>("SkillInfoTable.json", progress);
foreach (var obj in skillinfoTable.records)
{
this.skillInfoTable.Add(obj.id, obj);
}
var costTable = await LoadZip<CostTable>("CostTable.json", progress);
foreach (var obj in costTable.records)
{
this.costTable.Add(obj.id, obj);
}
var mediasProductTable = await LoadZip<MidasProductTable>("MidasProductTable.json", progress);
foreach (var obj in mediasProductTable.records)
{
this.mediasProductTable.Add(obj.midas_product_id_proximabeta, obj);
}
var towerTable = await LoadZip<TowerTable>("TowerTable.json", progress);
foreach (var obj in towerTable.records)
{
this.towerTable.Add(obj.id, obj);
}
var triggerTable = await LoadZip<TriggerTable>("TriggerTable.json", progress);
foreach (var obj in triggerTable.records)
{
this.TriggerTable.Add(obj.id, obj);
}
var infracoreTable = await LoadZip<InfracoreTable>("InfraCoreGradeTable.json", progress);
foreach (var obj in infracoreTable.records)
{
this.InfracoreTable.Add(obj.id, obj);
}
var attrData = await LoadZip<AttractiveCounselCharacterTable>("AttractiveCounselCharacterTable.json", progress);
foreach (var obj in attrData.records)
{
this.AttractiveCounselCharacterTable.Add(obj.name_code, obj);
}
var attrLData = await LoadZip<AttractiveLevelRewardTable>("AttractiveLevelRewardTable.json", progress);
foreach (var obj in attrLData.records)
{
this.AttractiveLevelReward.Add(obj.id, obj);
}
var subquest = await LoadZip<SubquestTable>("SubQuestTable.json", progress);
foreach (var obj in subquest.records)
{
this.Subquests.Add(obj.id, obj);
}
var msgs = await LoadZip<MessengerDialogTable>("MessengerDialogTable.json", progress);
foreach (var obj in msgs.records)
{
this.Messages.Add(obj.id, obj);
}
var msgc = await LoadZip<MessengerMsgConditionTable>("MessengerConditionTriggerTable.json", progress);
foreach (var obj in msgc.records)
{
this.MessageConditions.Add(obj.id, obj);
}
var scr = await LoadZip<ScenarioRewardTable>("ScenarioRewardsTable.json", progress);
foreach (var obj in scr.records)
{
this.ScenarioRewards.Add(obj.condition_id, obj);
}
}
public MainQuestCompletionRecord? GetMainQuestForStageClearCondition(int stage)
{
foreach (var item in questDataRecords)
foreach (var item in QuestDataRecords)
{
if (item.Value.condition_id == stage)
{
@@ -571,15 +397,15 @@ namespace EpinelPS.Data
}
public MainQuestCompletionRecord? GetMainQuestByTableId(int tid)
{
return questDataRecords[tid];
return QuestDataRecords[tid];
}
public CampaignStageRecord? GetStageData(int stage)
{
return stageDataRecords[stage];
return StageDataRecords[stage];
}
public RewardTableRecord? GetRewardTableEntry(int rewardId)
{
return rewardDataRecords[rewardId];
return RewardDataRecords[rewardId];
}
/// <summary>
/// Returns the level and its minimum value for XP value
@@ -591,9 +417,9 @@ namespace EpinelPS.Data
{
int prevLevel = 0;
int prevValue = 0;
for (int i = 1; i < userExpDataRecords.Count + 1; i++)
for (int i = 1; i < UserExpDataRecords.Count + 1; i++)
{
var item = userExpDataRecords[i];
var item = UserExpDataRecords[i];
if (prevValue < targetExp)
{
@@ -609,9 +435,9 @@ namespace EpinelPS.Data
}
public int GetUserMinXpForLevel(int targetLevel)
{
for (int i = 1; i < userExpDataRecords.Count + 1; i++)
for (int i = 1; i < UserExpDataRecords.Count + 1; i++)
{
var item = userExpDataRecords[i];
var item = UserExpDataRecords[i];
if (targetLevel == item.level)
{
@@ -628,7 +454,7 @@ namespace EpinelPS.Data
{
string difficulty = keys[1];
foreach (var item in chapterCampaignData)
foreach (var item in ChapterCampaignData)
{
if (difficulty == "Normal" && item.Value.chapter == chapterNum)
{
@@ -649,7 +475,7 @@ namespace EpinelPS.Data
}
public int GetNormalChapterNumberFromFieldName(string field)
{
foreach (var item in chapterCampaignData)
foreach (var item in ChapterCampaignData)
{
if (item.Value.field_id == field)
{
@@ -659,14 +485,9 @@ namespace EpinelPS.Data
return -1;
}
public IEnumerable<int> GetAllCharacterTids()
{
return characterTable.Keys;
}
public IEnumerable<int> GetAllCostumes()
{
foreach (var item in characterCostumeTable)
foreach (var item in CharacterCostumeTable)
{
yield return item.Value.id;
}
@@ -674,18 +495,18 @@ namespace EpinelPS.Data
internal ClearedTutorialData GetTutorialDataById(int TableId)
{
return tutorialTable[TableId];
return TutorialTable[TableId];
}
public string? GetItemSubType(int itemType)
{
return itemEquipTable[itemType].item_sub_type;
return ItemEquipTable[itemType].item_sub_type;
}
internal IEnumerable<int> GetStageIdsForChapter(int chapterNumber, bool normal)
{
string mod = normal ? "Normal" : "Hard";
foreach (var item in stageDataRecords)
foreach (var item in StageDataRecords)
{
var data = item.Value;

View File

@@ -0,0 +1,11 @@
namespace EpinelPS.Data
{
[System.AttributeUsage(System.AttributeTargets.Field)]
public class LoadRecordAttribute(string file, string primaryKey, Type tableType) : Attribute
{
public string File { get; set; } = file;
public string PrimaryKey { get; set; } = primaryKey;
public Type TableType { get; set; } = tableType;
}
}

View File

@@ -436,11 +436,11 @@ namespace EpinelPS.Database
public bool HasCharacter(int c)
{
// Step 1: Get the 'name_code' of the input character with Tid 'c'
if (GameData.Instance.characterTable.TryGetValue(c, out var inputCharacterRecord))
if (GameData.Instance.CharacterTable.TryGetValue(c, out var inputCharacterRecord))
{
int targetNameCode = inputCharacterRecord.name_code;
// Step 2: Find all character IDs in 'characterTable' that have the same 'name_code'
var matchingCharacterIds = GameData.Instance.characterTable.Where(kvp => kvp.Value.name_code == targetNameCode).Select(kvp => kvp.Key).ToHashSet();
var matchingCharacterIds = GameData.Instance.CharacterTable.Where(kvp => kvp.Value.name_code == targetNameCode).Select(kvp => kvp.Key).ToHashSet();
// Step 3: Check if any of your owned characters have a 'Tid' in the set of matching IDs
return Characters.Any(ownedCharacter => matchingCharacterIds.Contains(ownedCharacter.Tid));
@@ -455,11 +455,11 @@ namespace EpinelPS.Database
public Character? GetCharacter(int c)
{
// Step 1: Get the 'name_code' of the input character with Tid 'c'
if (GameData.Instance.characterTable.TryGetValue(c, out var inputCharacterRecord))
if (GameData.Instance.CharacterTable.TryGetValue(c, out var inputCharacterRecord))
{
int targetNameCode = inputCharacterRecord.name_code;
// Step 2: Find all character IDs in 'characterTable' that have the same 'name_code'
var matchingCharacterIds = GameData.Instance.characterTable.Where(kvp => kvp.Value.name_code == targetNameCode).Select(kvp => kvp.Key).ToHashSet();
var matchingCharacterIds = GameData.Instance.CharacterTable.Where(kvp => kvp.Value.name_code == targetNameCode).Select(kvp => kvp.Key).ToHashSet();
// Step 3: Check if any of your owned characters have a 'Tid' in the set of matching IDs
return Characters.Where(ownedCharacter => matchingCharacterIds.Contains(ownedCharacter.Tid)).First();

View File

@@ -12,20 +12,22 @@
<NoWarn>$(NoWarn);SYSLIB0057</NoWarn>
<Version>0.1.4.2</Version>
<CETCompat>false</CETCompat>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ASodium" Version="0.6.1" />
<PackageReference Include="DnsClient" Version="1.8.0" />
<PackageReference Include="Google.Api.CommonProtos" Version="2.16.0" />
<PackageReference Include="Google.Protobuf.Tools" Version="3.29.1" />
<PackageReference Include="Grpc.AspNetCore" Version="2.67.0" />
<PackageReference Include="Google.Protobuf.Tools" Version="3.30.2" />
<PackageReference Include="Grpc.AspNetCore" Version="2.71.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Paseto.Core" Version="1.3.0" />
<PackageReference Include="Paseto.Core" Version="1.4.1" />
<PackageReference Include="PeterO.Cbor" Version="4.5.5" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="Sodium.Core" Version="1.3.5" />
<PackageReference Include="Sodium.Core" Version="1.4.0" />
</ItemGroup>
<ItemGroup>
@@ -61,4 +63,8 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EpinelPS.Analyzers\EpinelPS.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>
</Project>

View File

@@ -15,7 +15,7 @@ namespace EpinelPS.LobbyServer.Character
var user = GetUser();
// Get all character data from the game's character table
var fullchardata = GameData.Instance.characterTable.Values.ToList();
var fullchardata = GameData.Instance.CharacterTable.Values.ToList();
var targetCharacter = user.GetCharacterBySerialNumber(req.Csn) ?? throw new NullReferenceException();

View File

@@ -15,7 +15,7 @@ namespace EpinelPS.LobbyServer.Character
var character = user.Characters.FirstOrDefault(c => c.Csn == req.Csn) ?? throw new Exception("cannot find character");
var charRecord = GameData.Instance.characterTable.Values.FirstOrDefault(c => c.id == character.Tid) ?? throw new Exception("cannot find character record");
var charRecord = GameData.Instance.CharacterTable.Values.FirstOrDefault(c => c.id == character.Tid) ?? throw new Exception("cannot find character record");
var skillIdMap = new Dictionary<int, int>
{
{ 1, charRecord.ulti_skill_id },

View File

@@ -16,7 +16,7 @@ namespace EpinelPS.LobbyServer.Character
var user = GetUser();
// Get all character data from the game's character table
var fullchardata = GameData.Instance.characterTable.Values.ToList();
var fullchardata = GameData.Instance.CharacterTable.Values.ToList();
var targetCharacter = user.GetCharacterBySerialNumber(req.Csn) ?? throw new NullReferenceException();

View File

@@ -30,7 +30,7 @@ namespace EpinelPS.LobbyServer.Gacha
var user = GetUser();
var response = new ResExecuteGacha() { Reward = new NetRewardData() { PassPoint = new() } };
var entireallCharacterData = GameData.Instance.characterTable.Values.ToList();
var entireallCharacterData = GameData.Instance.CharacterTable.Values.ToList();
// Remove the .Values part since it's already a list.
// Group by name_code to treat same name_code as one character
// Always add characters with grade_core_id == 1 and 101

View File

@@ -26,7 +26,7 @@ namespace EpinelPS.LobbyServer.Gacha
var user = GetUser();
var response = new ResExecuteDailyFreeGacha();
var entireallCharacterData = GameData.Instance.characterTable.Values.ToList();
var entireallCharacterData = GameData.Instance.CharacterTable.Values.ToList();
// Remove the .Values part since it's already a list.
// Group by name_code to treat same name_code as one character
// Always add characters with grade_core_id == 1 and 101

View File

@@ -68,8 +68,8 @@ namespace EpinelPS.LobbyServer.Inventory
(int exp, int modules) AddExp(NetItemData srcItem, ItemData destItem)
{
int[] maxLevel = [0, 0, 3, 3, 4, 4, 5, 5, 5, 5];
var srcEquipRecord = GameData.Instance.itemEquipTable.Values.FirstOrDefault(x => x.id == srcItem.Tid);
var destEquipRecord = GameData.Instance.itemEquipTable.Values.FirstOrDefault(x => x.id == destItem.ItemType) ?? throw new NullReferenceException();;
var srcEquipRecord = GameData.Instance.ItemEquipTable.Values.FirstOrDefault(x => x.id == srcItem.Tid);
var destEquipRecord = GameData.Instance.ItemEquipTable.Values.FirstOrDefault(x => x.id == destItem.ItemType) ?? throw new NullReferenceException();;
int[] expNextTable = [.. GameData.Instance.itemEquipExpTable.Values
.Where(x => x.item_rare == destEquipRecord.item_rare)
.Select(x => x.exp)
@@ -120,7 +120,7 @@ namespace EpinelPS.LobbyServer.Inventory
private int GetSourceExp(NetItemData srcItem)
{
var item = GetUser().Items.FirstOrDefault(x => x.Isn == srcItem.Isn) ?? throw new NullReferenceException();
var equipRecord = GameData.Instance.itemEquipTable.Values.FirstOrDefault(x => x.id == item.ItemType) ?? throw new NullReferenceException();
var equipRecord = GameData.Instance.ItemEquipTable.Values.FirstOrDefault(x => x.id == item.ItemType) ?? throw new NullReferenceException();
var levelRecord = GameData.Instance.ItemEquipGradeExpTable.Values.FirstOrDefault(x => x.grade_core_id == equipRecord.grade_core_id);
int[] expNextTable = GameData.Instance.itemEquipExpTable.Values
.Where(x => x.item_rare == equipRecord.item_rare)
@@ -140,7 +140,7 @@ namespace EpinelPS.LobbyServer.Inventory
private static int CalcTotalExp(ItemData destItem)
{
int exp = 0;
var equipRecord = GameData.Instance.itemEquipTable.Values.FirstOrDefault(x => x.id == destItem.ItemType) ?? throw new NullReferenceException();
var equipRecord = GameData.Instance.ItemEquipTable.Values.FirstOrDefault(x => x.id == destItem.ItemType) ?? throw new NullReferenceException();
var levelRecord = GameData.Instance.ItemEquipGradeExpTable.Values.FirstOrDefault(x => x.grade_core_id == equipRecord.grade_core_id);
int[] expNextTable = GameData.Instance.itemEquipExpTable.Values
.Where(x => x.item_rare == equipRecord.item_rare)

View File

@@ -282,7 +282,7 @@ namespace EpinelPS
else
{
// Group characters by name_code and always add those with grade_core_id == 11, 103, and include grade_core_id == 201
var allCharacters = GameData.Instance.characterTable.Values
var allCharacters = GameData.Instance.CharacterTable.Values
.GroupBy(c => c.name_code) // Group by name_code to treat same name_code as one character 3999 = marian
.SelectMany(g => g.Where(c => c.grade_core_id == 1 || c.grade_core_id == 101 || c.grade_core_id == 201 || c.name_code == 3999)) // Always add characters with grade_core_id == 11 and 103
.ToList();
@@ -381,7 +381,7 @@ namespace EpinelPS
}
else
{
foreach (var tutorial in GameData.Instance.tutorialTable.Values)
foreach (var tutorial in GameData.Instance.TutorialTable.Values)
{
if (!user.ClearedTutorialData.ContainsKey(tutorial.id))
{
@@ -417,7 +417,7 @@ namespace EpinelPS
int tid = character.Tid;
// Get the character data from the character table
if (!GameData.Instance.characterTable.TryGetValue(tid, out var charData))
if (!GameData.Instance.CharacterTable.TryGetValue(tid, out var charData))
{
Console.WriteLine($"Character data not found for Tid {tid}");
continue;
@@ -445,7 +445,7 @@ namespace EpinelPS
int newGradeCoreId = Math.Min(inputGrade + 1, maxGradeCoreId); // +1 because inputGrade starts from 0 for SSRs
// Find the character with the same name_code and new grade_core_id
var newCharData = GameData.Instance.characterTable.Values.FirstOrDefault(c =>
var newCharData = GameData.Instance.CharacterTable.Values.FirstOrDefault(c =>
c.name_code == nameCode && c.grade_core_id == newGradeCoreId);
if (newCharData != null)
@@ -466,7 +466,7 @@ namespace EpinelPS
int newGradeCoreId = Math.Min(101 + inputGrade, maxGradeCoreId); // Starts at 101
// Find the character with the same name_code and new grade_core_id
var newCharData = GameData.Instance.characterTable.Values.FirstOrDefault(c =>
var newCharData = GameData.Instance.CharacterTable.Values.FirstOrDefault(c =>
c.name_code == nameCode && c.grade_core_id == newGradeCoreId);
if (newCharData != null)

View File

@@ -9,7 +9,7 @@ namespace EpinelPS.Utils
var character = user.Characters.FirstOrDefault(c => c.Csn == csn);
if (character == null) return 0;
var charRecord = GameData.Instance.characterTable.Values.FirstOrDefault(c => c.id == character.Tid);
var charRecord = GameData.Instance.CharacterTable.Values.FirstOrDefault(c => c.id == character.Tid);
if (charRecord == null) return 0;
var statRecord = GameData.Instance.characterStatTable.Values.FirstOrDefault(s => charRecord.stat_enhance_id == s.group + (character.Level - 1));