From 0b936fa6e5567563cf29997c92549f2d95eff845 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 3 May 2025 11:52:09 -0400 Subject: [PATCH] refactoring part 2, use source generators for GameData --- EpinelPS.Analyzers/EpinelPS.Analyzers.csproj | 20 + .../LoadRecordInitializerGenerator.cs | 104 ++++ EpinelPS.sln | 26 + EpinelPS/Data/GameData.cs | 491 ++++++------------ EpinelPS/Data/LoadRecordAttribute.cs | 11 + EpinelPS/Database/JsonDb.cs | 8 +- EpinelPS/EpinelPS.csproj | 14 +- .../LobbyServer/Character/DoLimitBreak.cs | 2 +- .../LobbyServer/Character/SkillLevelUp.cs | 2 +- EpinelPS/LobbyServer/Character/UpgradeCore.cs | 2 +- EpinelPS/LobbyServer/Gacha/ExecGacha.cs | 2 +- .../LobbyServer/Gacha/ExecuteEventGacha.cs | 2 +- .../Inventory/IncreaseEquipmentExp.cs | 8 +- EpinelPS/Program.cs | 10 +- EpinelPS/Utils/FormulaUtils.cs | 2 +- 15 files changed, 346 insertions(+), 358 deletions(-) create mode 100644 EpinelPS.Analyzers/EpinelPS.Analyzers.csproj create mode 100644 EpinelPS.Analyzers/LoadRecordInitializerGenerator.cs create mode 100644 EpinelPS/Data/LoadRecordAttribute.cs diff --git a/EpinelPS.Analyzers/EpinelPS.Analyzers.csproj b/EpinelPS.Analyzers/EpinelPS.Analyzers.csproj new file mode 100644 index 0000000..d12afce --- /dev/null +++ b/EpinelPS.Analyzers/EpinelPS.Analyzers.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + true + enable + preview + true + false + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + diff --git a/EpinelPS.Analyzers/LoadRecordInitializerGenerator.cs b/EpinelPS.Analyzers/LoadRecordInitializerGenerator.cs new file mode 100644 index 0000000..ff96d37 --- /dev/null +++ b/EpinelPS.Analyzers/LoadRecordInitializerGenerator.cs @@ -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 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 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 = ""; + } +} \ No newline at end of file diff --git a/EpinelPS.sln b/EpinelPS.sln index 876297b..b0c1d05 100644 --- a/EpinelPS.sln +++ b/EpinelPS.sln @@ -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 diff --git a/EpinelPS/Data/GameData.cs b/EpinelPS/Data/GameData.cs index 23e3593..3dad3b4 100644 --- a/EpinelPS/Data/GameData.cs +++ b/EpinelPS/Data/GameData.cs @@ -23,60 +23,147 @@ namespace EpinelPS.Data } } - private ZipFile MainZip; - private MemoryStream ZipStream; - - - private readonly Dictionary questDataRecords = []; - private readonly Dictionary stageDataRecords = []; - private readonly Dictionary rewardDataRecords = []; - private readonly Dictionary userExpDataRecords = []; - private readonly Dictionary chapterCampaignData = []; - private readonly Dictionary characterCostumeTable = []; - public readonly Dictionary characterTable = []; - public readonly Dictionary tutorialTable = []; - public readonly Dictionary itemEquipTable = []; - public readonly Dictionary itemMaterialTable = []; - public readonly Dictionary itemEquipExpTable = []; - public readonly Dictionary ItemEquipGradeExpTable = []; - private readonly Dictionary FieldMapData = []; - private readonly Dictionary LevelData = []; - private readonly Dictionary TacticAcademyLessons = []; - public readonly Dictionary SidestoryRewardTable = []; - public readonly Dictionary PositionReward = []; - public readonly Dictionary FieldItems = []; - public readonly Dictionary OutpostBattle = []; - public readonly Dictionary jukeboxListDataRecords = []; - private readonly Dictionary jukeboxThemeDataRecords = []; - public readonly Dictionary gachaTypes = []; - public readonly Dictionary eventManagers = []; - public readonly Dictionary lwptablemgrs = []; - public readonly Dictionary albumResourceRecords = []; - public readonly Dictionary userFrameTable = []; - public readonly Dictionary archiveRecordManagerTable = []; - public readonly Dictionary archiveEventStoryRecords = []; - public readonly Dictionary archiveEventQuestRecords = []; - public readonly Dictionary archiveEventDungeonStageRecords = []; - public readonly Dictionary userTitleRecords = []; - public readonly Dictionary archiveMessengerConditionRecords = []; - public readonly Dictionary characterStatTable = []; - public readonly Dictionary skillInfoTable = []; - public readonly Dictionary costTable = []; - public readonly Dictionary mediasProductTable = []; - public readonly Dictionary towerTable = []; - public readonly Dictionary TriggerTable = []; - public readonly Dictionary InfracoreTable = []; - public readonly Dictionary AttractiveCounselCharacterTable = []; - public readonly Dictionary AttractiveLevelReward = []; - public readonly Dictionary Subquests = []; - public readonly Dictionary Messages = []; - public readonly Dictionary MessageConditions = []; - public readonly Dictionary 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 QuestDataRecords = []; + + [LoadRecord("CampaignStageTable.json", "id", typeof(CampaignStageTable))] + public readonly Dictionary StageDataRecords = []; + + [LoadRecord("RewardTable.json", "id", typeof(RewardTable))] + public readonly Dictionary RewardDataRecords = []; + + [LoadRecord("UserExpTable.json", "level", typeof(UserExpTable))] + public readonly Dictionary UserExpDataRecords = []; + + [LoadRecord("CampaignChapterTable.json", "chapter", typeof(CampaignChapterTable))] + public readonly Dictionary ChapterCampaignData = []; + + [LoadRecord("CharacterCostumeTable.json", "id", typeof(CharacterCostumeTable))] + public readonly Dictionary CharacterCostumeTable = []; + + [LoadRecord("CharacterTable.json", "id", typeof(CharacterTable))] + public readonly Dictionary CharacterTable = []; + + [LoadRecord("ContentsTutorialTable.json", "id", typeof(TutorialTable))] + public readonly Dictionary TutorialTable = []; + [LoadRecord("ItemEquipTable.json", "id", typeof(ItemEquipTable))] + + public readonly Dictionary ItemEquipTable = []; + + [LoadRecord("ItemMaterialTable.json", "id", typeof(ItemMaterialTable))] + public readonly Dictionary itemMaterialTable = []; + + [LoadRecord("ItemEquipExpTable.json", "id", typeof(ItemEquipExpTable))] + public readonly Dictionary itemEquipExpTable = []; + + [LoadRecord("ItemEquipGradeExpTable.json", "id", typeof(ItemEquipGradeExpTable))] + public readonly Dictionary ItemEquipGradeExpTable = []; + + [LoadRecord("CharacterLevelTable.json", "level", typeof(CharacterLevelTable))] + public readonly Dictionary LevelData = []; + + [LoadRecord("TacticAcademyFunctionTable.json", "id", typeof(TacticAcademyLessonTable))] + public readonly Dictionary TacticAcademyLessons = []; + + [LoadRecord("SideStoryStageTable.json", "id", typeof(SideStoryStageTable))] + public readonly Dictionary SidestoryRewardTable = []; + public readonly Dictionary PositionReward = []; + + [LoadRecord("FieldItemTable.json", "id", typeof(FieldItemTable))] + public readonly Dictionary FieldItems = []; + + [LoadRecord("OutpostBattleTable.json", "id", typeof(OutpostBattleTable))] + public readonly Dictionary OutpostBattle = []; + + [LoadRecord("JukeboxListTable.json", "id", typeof(JukeboxListTable))] + public readonly Dictionary jukeboxListDataRecords = []; + + [LoadRecord("JukeboxThemeTable.json", "id", typeof(JukeboxThemeTable))] + public readonly Dictionary jukeboxThemeDataRecords = []; + + [LoadRecord("GachaTypeTable.json", "id", typeof(GachaTypeTable))] + public readonly Dictionary gachaTypes = []; + + [LoadRecord("EventManagerTable.json", "id", typeof(EventManagerTable))] + public readonly Dictionary eventManagers = []; + + [LoadRecord("LiveWallpaperTable.json", "id", typeof(LiveWallpaperTable))] + public readonly Dictionary lwptablemgrs = []; + + [LoadRecord("AlbumResourceTable.json", "id", typeof(AlbumResourceTable))] + public readonly Dictionary albumResourceRecords = []; + + [LoadRecord("UserFrameTable.json", "id", typeof(UserFrameTable))] + public readonly Dictionary userFrameTable = []; + + [LoadRecord("ArchiveRecordManagerTable.json", "id", typeof(ArchiveRecordManagerTable))] + public readonly Dictionary archiveRecordManagerTable = []; + + [LoadRecord("ArchiveEventStoryTable.json", "id", typeof(ArchiveEventStoryTable))] + public readonly Dictionary archiveEventStoryRecords = []; + + [LoadRecord("ArchiveEventQuestTable.json", "id", typeof(ArchiveEventQuestTable))] + public readonly Dictionary archiveEventQuestRecords = []; + + [LoadRecord("ArchiveEventDungeonStageTable.json", "id", typeof(ArchiveEventDungeonStageTable))] + public readonly Dictionary archiveEventDungeonStageRecords = []; + + [LoadRecord("UserTitleTable.json", "id", typeof(UserTitleTable))] + public readonly Dictionary userTitleRecords = []; + + [LoadRecord("ArchiveMessengerConditionTable.json", "id", typeof(ArchiveMessengerConditionTable))] + public readonly Dictionary archiveMessengerConditionRecords = []; + + [LoadRecord("CharacterStatTable.json", "id", typeof(CharacterStatTable))] + public readonly Dictionary characterStatTable = []; + + [LoadRecord("SkillInfoTable.json", "id", typeof(SkillInfoTable))] + public readonly Dictionary skillInfoTable = []; + + [LoadRecord("CostTable.json", "id", typeof(CostTable))] + public readonly Dictionary costTable = []; + + [LoadRecord("MidasProductTable.json", "midas_product_id_proximabeta", typeof(MidasProductTable))] + public readonly Dictionary mediasProductTable = []; + + [LoadRecord("TowerTable.json", "id", typeof(TowerTable))] + public readonly Dictionary towerTable = []; + + [LoadRecord("TriggerTable.json", "id", typeof(TriggerTable))] + public readonly Dictionary TriggerTable = []; + + [LoadRecord("InfraCoreGradeTable.json", "id", typeof(InfracoreTable))] + public readonly Dictionary InfracoreTable = []; + + [LoadRecord("AttractiveCounselCharacterTable.json", "name_code", typeof(AttractiveCounselCharacterTable))] + public readonly Dictionary AttractiveCounselCharacterTable = []; + + [LoadRecord("AttractiveLevelRewardTable.json", "id", typeof(AttractiveLevelRewardTable))] + public readonly Dictionary AttractiveLevelReward = []; + + [LoadRecord("SubQuestTable.json", "id", typeof(SubquestTable))] + public readonly Dictionary Subquests = []; + + [LoadRecord("MessengerDialogTable.json", "id", typeof(MessengerDialogTable))] + public readonly Dictionary Messages = []; + + [LoadRecord("MessengerConditionTriggerTable.json", "id", typeof(MessengerMsgConditionTable))] + public readonly Dictionary MessageConditions = []; + + [LoadRecord("ScenarioRewardsTable.json", "condition_id", typeof(ScenarioRewardTable))] + public readonly Dictionary ScenarioRewards = []; + + static async Task BuildAsync() { await Load(); @@ -232,7 +319,7 @@ namespace EpinelPS.Data } #endregion - private async Task LoadZip(string entry, ProgressBar bar) + public async Task LoadZip(string entry, IProgress 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("MainQuestTable.json", progress); - foreach (var obj in questDataRecords.records) - { - this.questDataRecords.Add(obj.id, obj); - } + totalFiles = GameDataInitializer.TotalFiles; - var stageDataRecords = await LoadZip("CampaignStageTable.json", progress); - foreach (var obj in stageDataRecords.records) - { - this.stageDataRecords.Add(obj.id, obj); - } - - var rewardDataRecords = await LoadZip("RewardTable.json", progress); - foreach (var obj in rewardDataRecords.records) - { - this.rewardDataRecords.Add(obj.id, obj); - } - - var chapterCampaignData = await LoadZip("CampaignChapterTable.json", progress); - foreach (var obj in chapterCampaignData.records) - { - this.chapterCampaignData.Add(obj.chapter, obj); - } - - var userExpDataRecords = await LoadZip("UserExpTable.json", progress); - foreach (var obj in userExpDataRecords.records) - { - this.userExpDataRecords.Add(obj.level, obj); - } - - var characterCostumeTable = await LoadZip("CharacterCostumeTable.json", progress); - foreach (var obj in characterCostumeTable.records) - { - this.characterCostumeTable.Add(obj.id, obj); - } - - var characterTable = await LoadZip("CharacterTable.json", progress); - foreach (var obj in characterTable.records) - { - this.characterTable.Add(obj.id, obj); - } - - var tutorialTable = await LoadZip("ContentsTutorialTable.json", progress); - foreach (var obj in tutorialTable.records) - { - this.tutorialTable.Add(obj.id, obj); - } - - var itemEquipTable = await LoadZip("ItemEquipTable.json", progress); - foreach (var obj in itemEquipTable.records) - { - this.itemEquipTable.Add(obj.id, obj); - } - - var itemMaterialTable = await LoadZip("ItemMaterialTable.json", progress); - foreach (var obj in itemMaterialTable.records) - { - this.itemMaterialTable.Add(obj.id, obj); - } - - var itemEquipExpTable = await LoadZip("ItemEquipExpTable.json", progress); - foreach (var obj in itemEquipExpTable.records) - { - this.itemEquipExpTable.Add(obj.id, obj); - } - - var ItemEquipGradeExpTable = await LoadZip("ItemEquipGradeExpTable.json", progress); - foreach (var obj in ItemEquipGradeExpTable.records) - { - this.ItemEquipGradeExpTable.Add(obj.id, obj); - } - - var characterLevelTable = await LoadZip("CharacterLevelTable.json", progress); - foreach (var obj in characterLevelTable.records) - { - LevelData.Add(obj.level, obj); - } - - var tacticLessonTable = await LoadZip("TacticAcademyFunctionTable.json", progress); - foreach (var obj in tacticLessonTable.records) - { - TacticAcademyLessons.Add(obj.id, obj); - } - - var sidestoryTable = await LoadZip("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.json", progress); - foreach (var obj in fieldItems.records) - { - FieldItems.Add(obj.id, obj); - } - var battleOutpostTable = await LoadZip("OutpostBattleTable.json", progress); - foreach (var obj in battleOutpostTable.records) - { - OutpostBattle.Add(obj.id, obj); - } - - var archiveRecordManagerTableData = await LoadZip("ArchiveRecordManagerTable.json", progress); - foreach (var obj in archiveRecordManagerTableData.records) - { - archiveRecordManagerTable.Add(obj.id, obj); - } - - var gachaTypeTable = await LoadZip("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.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.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.json", progress); - foreach (var record in userFrameData.records) - { - userFrameTable[record.id] = record; - } - // Load and parse ArchiveEventDungeonStageTable.json - var archiveEventDungeonStageData = await LoadZip("ArchiveEventDungeonStageTable.json", progress); - foreach (var obj in archiveEventDungeonStageData.records) - { - archiveEventDungeonStageRecords.Add(obj.id, obj); - } - - var userTitleTable = await LoadZip("UserTitleTable.json", progress); - foreach (var obj in userTitleTable.records) - { - userTitleRecords.Add(obj.id, obj); - } - - // Load and parse ArchiveEventStoryTable.json - var archiveEventStoryTable = await LoadZip("ArchiveEventStoryTable.json", progress); - foreach (var obj in archiveEventStoryTable.records) - { - archiveEventStoryRecords.Add(obj.id, obj); - } - - // Load and parse ArchiveEventQuestTable.json - var archiveEventQuestTable = await LoadZip("ArchiveEventQuestTable.json", progress); - foreach (var obj in archiveEventQuestTable.records) - { - archiveEventQuestRecords.Add(obj.id, obj); - } - // LOAD ARCHIVE MESSENGER CONDITION TABLE - var archiveMessengerConditionTable = await LoadZip("ArchiveMessengerConditionTable.json", progress); - foreach (var obj in archiveMessengerConditionTable.records) - { - archiveMessengerConditionRecords.Add(obj.id, obj); - } - var albumResourceTable = await LoadZip("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.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.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.json", progress); - foreach (var obj in characterStatTable.records) - { - this.characterStatTable.Add(obj.id, obj); - } - - var skillinfoTable = await LoadZip("SkillInfoTable.json", progress); - foreach (var obj in skillinfoTable.records) - { - this.skillInfoTable.Add(obj.id, obj); - } - - var costTable = await LoadZip("CostTable.json", progress); - foreach (var obj in costTable.records) - { - this.costTable.Add(obj.id, obj); - } - - var mediasProductTable = await LoadZip("MidasProductTable.json", progress); - foreach (var obj in mediasProductTable.records) - { - this.mediasProductTable.Add(obj.midas_product_id_proximabeta, obj); - } - - var towerTable = await LoadZip("TowerTable.json", progress); - foreach (var obj in towerTable.records) - { - this.towerTable.Add(obj.id, obj); - } - - var triggerTable = await LoadZip("TriggerTable.json", progress); - foreach (var obj in triggerTable.records) - { - this.TriggerTable.Add(obj.id, obj); - } - - var infracoreTable = await LoadZip("InfraCoreGradeTable.json", progress); - foreach (var obj in infracoreTable.records) - { - this.InfracoreTable.Add(obj.id, obj); - } - - var attrData = await LoadZip("AttractiveCounselCharacterTable.json", progress); - foreach (var obj in attrData.records) - { - this.AttractiveCounselCharacterTable.Add(obj.name_code, obj); - } - - var attrLData = await LoadZip("AttractiveLevelRewardTable.json", progress); - foreach (var obj in attrLData.records) - { - this.AttractiveLevelReward.Add(obj.id, obj); - } - - var subquest = await LoadZip("SubQuestTable.json", progress); - foreach (var obj in subquest.records) - { - this.Subquests.Add(obj.id, obj); - } - - var msgs = await LoadZip("MessengerDialogTable.json", progress); - foreach (var obj in msgs.records) - { - this.Messages.Add(obj.id, obj); - } - - var msgc = await LoadZip("MessengerConditionTriggerTable.json", progress); - foreach (var obj in msgc.records) - { - this.MessageConditions.Add(obj.id, obj); - } - - var scr = await LoadZip("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]; } /// /// 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 GetAllCharacterTids() - { - return characterTable.Keys; - } public IEnumerable 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 GetStageIdsForChapter(int chapterNumber, bool normal) { string mod = normal ? "Normal" : "Hard"; - foreach (var item in stageDataRecords) + foreach (var item in StageDataRecords) { var data = item.Value; diff --git a/EpinelPS/Data/LoadRecordAttribute.cs b/EpinelPS/Data/LoadRecordAttribute.cs new file mode 100644 index 0000000..43e5aa2 --- /dev/null +++ b/EpinelPS/Data/LoadRecordAttribute.cs @@ -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; + } +} \ No newline at end of file diff --git a/EpinelPS/Database/JsonDb.cs b/EpinelPS/Database/JsonDb.cs index abef6f6..5052ae1 100644 --- a/EpinelPS/Database/JsonDb.cs +++ b/EpinelPS/Database/JsonDb.cs @@ -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(); diff --git a/EpinelPS/EpinelPS.csproj b/EpinelPS/EpinelPS.csproj index 91f6d36..2f549d1 100644 --- a/EpinelPS/EpinelPS.csproj +++ b/EpinelPS/EpinelPS.csproj @@ -12,20 +12,22 @@ $(NoWarn);SYSLIB0057 0.1.4.2 false + true + - - + + - + - + @@ -61,4 +63,8 @@ Always + + + + diff --git a/EpinelPS/LobbyServer/Character/DoLimitBreak.cs b/EpinelPS/LobbyServer/Character/DoLimitBreak.cs index 859897a..c46fc31 100644 --- a/EpinelPS/LobbyServer/Character/DoLimitBreak.cs +++ b/EpinelPS/LobbyServer/Character/DoLimitBreak.cs @@ -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(); diff --git a/EpinelPS/LobbyServer/Character/SkillLevelUp.cs b/EpinelPS/LobbyServer/Character/SkillLevelUp.cs index 6dff6dc..f38a10d 100644 --- a/EpinelPS/LobbyServer/Character/SkillLevelUp.cs +++ b/EpinelPS/LobbyServer/Character/SkillLevelUp.cs @@ -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 { { 1, charRecord.ulti_skill_id }, diff --git a/EpinelPS/LobbyServer/Character/UpgradeCore.cs b/EpinelPS/LobbyServer/Character/UpgradeCore.cs index 58be316..2e16496 100644 --- a/EpinelPS/LobbyServer/Character/UpgradeCore.cs +++ b/EpinelPS/LobbyServer/Character/UpgradeCore.cs @@ -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(); diff --git a/EpinelPS/LobbyServer/Gacha/ExecGacha.cs b/EpinelPS/LobbyServer/Gacha/ExecGacha.cs index 6fb7cfc..73a3451 100644 --- a/EpinelPS/LobbyServer/Gacha/ExecGacha.cs +++ b/EpinelPS/LobbyServer/Gacha/ExecGacha.cs @@ -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 diff --git a/EpinelPS/LobbyServer/Gacha/ExecuteEventGacha.cs b/EpinelPS/LobbyServer/Gacha/ExecuteEventGacha.cs index c46b977..3340253 100644 --- a/EpinelPS/LobbyServer/Gacha/ExecuteEventGacha.cs +++ b/EpinelPS/LobbyServer/Gacha/ExecuteEventGacha.cs @@ -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 diff --git a/EpinelPS/LobbyServer/Inventory/IncreaseEquipmentExp.cs b/EpinelPS/LobbyServer/Inventory/IncreaseEquipmentExp.cs index 562eaf6..cff5006 100644 --- a/EpinelPS/LobbyServer/Inventory/IncreaseEquipmentExp.cs +++ b/EpinelPS/LobbyServer/Inventory/IncreaseEquipmentExp.cs @@ -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) diff --git a/EpinelPS/Program.cs b/EpinelPS/Program.cs index 490e0ee..fef4a3c 100644 --- a/EpinelPS/Program.cs +++ b/EpinelPS/Program.cs @@ -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) diff --git a/EpinelPS/Utils/FormulaUtils.cs b/EpinelPS/Utils/FormulaUtils.cs index e864694..f08480c 100644 --- a/EpinelPS/Utils/FormulaUtils.cs +++ b/EpinelPS/Utils/FormulaUtils.cs @@ -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));