initial memorypack format support

This commit is contained in:
Mikhail Tyukin
2025-07-17 13:51:49 -04:00
parent 2c5d8acc93
commit 04a9fe29c4
10 changed files with 414 additions and 428 deletions

4
.editorconfig Normal file
View File

@@ -0,0 +1,4 @@
[*.cs]
# CA5397: Do not use deprecated SslProtocols values
dotnet_diagnostic.CA5397.severity = none

View File

@@ -42,12 +42,15 @@ public class LoadRecordInitializerGenerator : IIncrementalGenerator
if (context.SemanticModel.GetDeclaredSymbol(variable) is not IFieldSymbol symbol)
return null;
if (symbol.Type is not INamedTypeSymbol namedSymbol)
return null;
foreach (var attr in symbol.GetAttributes())
{
if (attr.ConstructorArguments.Length == 3)
if (attr.ConstructorArguments.Length == 2)
{
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)
if (attr.ConstructorArguments[0].Value is not string fileName || attr.ConstructorArguments[1].Value is not string key)
return null;
return new LoadFieldInfo
@@ -56,7 +59,7 @@ public class LoadRecordInitializerGenerator : IIncrementalGenerator
FieldName = symbol.Name,
FileName = fileName,
Key = key,
RecordTypeName = recordType.ToDisplayString()
RecordTypeName = namedSymbol.TypeArguments[1].Name
};
}
}
@@ -71,23 +74,24 @@ public class LoadRecordInitializerGenerator : IIncrementalGenerator
sb.AppendLine("using System.Threading.Tasks;");
sb.AppendLine();
sb.AppendLine("namespace EpinelPS.Data;");
sb.AppendLine();
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(" {");
sb.AppendLine($"\tpublic static int TotalFiles = {fieldInfos.Length};");
sb.AppendLine("\tpublic static async Task InitializeGameData(IProgress<double> progress = null)");
sb.AppendLine("\t{");
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($"\t\tvar {tempVar} = await {info.ContainingClass}.Instance.LoadZip<{info.RecordTypeName}>(\"{info.FileName}\", progress);");
sb.AppendLine($"\t\tforeach (var obj in {tempVar})");
sb.AppendLine("\t\t{");
sb.AppendLine($"\t\t\t{info.ContainingClass}.Instance.{info.FieldName}.Add(obj.{info.Key}, obj);");
sb.AppendLine("\t\t}");
}
sb.AppendLine(" }");
sb.AppendLine("\t}");
sb.AppendLine("}");
return sb.ToString();

View File

@@ -13,6 +13,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{4BB2E7
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EpinelPS.Analyzers", "EpinelPS.Analyzers\EpinelPS.Analyzers.csproj", "{E3B18A3D-B20B-447D-BCBE-12931E18B41E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

View File

@@ -1,9 +1,11 @@
using EpinelPS.Utils;
using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Data;
using System.Diagnostics;
using System.Security.Cryptography;
using EpinelPS.Utils;
using ICSharpCode.SharpZipLib.Zip;
using MemoryPack;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace EpinelPS.Data
{
@@ -33,171 +35,175 @@ namespace EpinelPS.Data
private int totalFiles = 1;
private int currentFile = 0;
// TODO: all of the data types need to be changed to match the game
private bool UseMemoryPack = false;
public readonly Dictionary<string, MapInfo> MapData = [];
[LoadRecord("MainQuestTable.json", "id", typeof(MainQuestCompletionTable))]
[LoadRecord("MainQuestTable.json", "id")]
public readonly Dictionary<int, MainQuestCompletionRecord> QuestDataRecords = [];
[LoadRecord("CampaignStageTable.json", "id", typeof(CampaignStageTable))]
[LoadRecord("CampaignStageTable.json", "id")]
public readonly Dictionary<int, CampaignStageRecord> StageDataRecords = [];
[LoadRecord("RewardTable.json", "id", typeof(RewardTable))]
[LoadRecord("RewardTable.json", "id")]
public readonly Dictionary<int, RewardTableRecord> RewardDataRecords = [];
[LoadRecord("UserExpTable.json", "level", typeof(UserExpTable))]
[LoadRecord("UserExpTable.json", "level")]
public readonly Dictionary<int, UserExpRecord> UserExpDataRecords = [];
[LoadRecord("CampaignChapterTable.json", "chapter", typeof(CampaignChapterTable))]
[LoadRecord("CampaignChapterTable.json", "chapter")]
public readonly Dictionary<int, CampaignChapterRecord> ChapterCampaignData = [];
[LoadRecord("CharacterCostumeTable.json", "id", typeof(CharacterCostumeTable))]
[LoadRecord("CharacterCostumeTable.json", "id")]
public readonly Dictionary<int, CharacterCostumeRecord> CharacterCostumeTable = [];
[LoadRecord("CharacterTable.json", "id", typeof(CharacterTable))]
[LoadRecord("CharacterTable.json", "id")]
public readonly Dictionary<int, CharacterRecord> CharacterTable = [];
[LoadRecord("ContentsTutorialTable.json", "id", typeof(TutorialTable))]
[LoadRecord("ContentsTutorialTable.json", "id")]
public readonly Dictionary<int, ClearedTutorialData> TutorialTable = [];
[LoadRecord("ItemEquipTable.json", "id", typeof(ItemEquipTable))]
[LoadRecord("ItemEquipTable.json", "id")]
public readonly Dictionary<int, ItemEquipRecord> ItemEquipTable = [];
[LoadRecord("ItemMaterialTable.json", "id", typeof(ItemMaterialTable))]
[LoadRecord("ItemMaterialTable.json", "id")]
public readonly Dictionary<int, ItemMaterialRecord> itemMaterialTable = [];
[LoadRecord("ItemEquipExpTable.json", "id", typeof(ItemEquipExpTable))]
[LoadRecord("ItemEquipExpTable.json", "id")]
public readonly Dictionary<int, ItemEquipExpRecord> itemEquipExpTable = [];
[LoadRecord("ItemEquipGradeExpTable.json", "id", typeof(ItemEquipGradeExpTable))]
[LoadRecord("ItemEquipGradeExpTable.json", "id")]
public readonly Dictionary<int, ItemEquipGradeExpRecord> ItemEquipGradeExpTable = [];
[LoadRecord("CharacterLevelTable.json", "level", typeof(CharacterLevelTable))]
[LoadRecord("CharacterLevelTable.json", "level")]
public readonly Dictionary<int, CharacterLevelData> LevelData = [];
[LoadRecord("TacticAcademyFunctionTable.json", "id", typeof(TacticAcademyLessonTable))]
[LoadRecord("TacticAcademyFunctionTable.json", "id")]
public readonly Dictionary<int, TacticAcademyLessonRecord> TacticAcademyLessons = [];
[LoadRecord("SideStoryStageTable.json", "id", typeof(SideStoryStageTable))]
[LoadRecord("SideStoryStageTable.json", "id")]
public readonly Dictionary<int, SideStoryStageRecord> SidestoryRewardTable = [];
[LoadRecord("FieldItemTable.json", "id", typeof(FieldItemTable))]
[LoadRecord("FieldItemTable.json", "id")]
public readonly Dictionary<int, FieldItemRecord> FieldItems = [];
[LoadRecord("OutpostBattleTable.json", "id", typeof(OutpostBattleTable))]
[LoadRecord("OutpostBattleTable.json", "id")]
public readonly Dictionary<int, OutpostBattleTableRecord> OutpostBattle = [];
[LoadRecord("JukeboxListTable.json", "id", typeof(JukeboxListTable))]
[LoadRecord("JukeboxListTable.json", "id")]
public readonly Dictionary<int, JukeboxListRecord> jukeboxListDataRecords = [];
[LoadRecord("JukeboxThemeTable.json", "id", typeof(JukeboxThemeTable))]
[LoadRecord("JukeboxThemeTable.json", "id")]
public readonly Dictionary<int, JukeboxThemeRecord> jukeboxThemeDataRecords = [];
[LoadRecord("GachaTypeTable.json", "id", typeof(GachaTypeTable))]
[LoadRecord("GachaTypeTable.json", "id")]
public readonly Dictionary<int, GachaType> gachaTypes = [];
[LoadRecord("EventManagerTable.json", "id", typeof(EventManagerTable))]
[LoadRecord("EventManagerTable.json", "id")]
public readonly Dictionary<int, EventManager> eventManagers = [];
[LoadRecord("LiveWallpaperTable.json", "id", typeof(LiveWallpaperTable))]
[LoadRecord("LiveWallpaperTable.json", "id")]
public readonly Dictionary<int, LiveWallpaperRecord> lwptablemgrs = [];
[LoadRecord("AlbumResourceTable.json", "id", typeof(AlbumResourceTable))]
[LoadRecord("AlbumResourceTable.json", "id")]
public readonly Dictionary<int, AlbumResourceRecord> albumResourceRecords = [];
[LoadRecord("UserFrameTable.json", "id", typeof(UserFrameTable))]
[LoadRecord("UserFrameTable.json", "id")]
public readonly Dictionary<int, UserFrameTableRecord> userFrameTable = [];
[LoadRecord("ArchiveRecordManagerTable.json", "id", typeof(ArchiveRecordManagerTable))]
[LoadRecord("ArchiveRecordManagerTable.json", "id")]
public readonly Dictionary<int, ArchiveRecordManagerRecord> archiveRecordManagerTable = [];
[LoadRecord("ArchiveEventStoryTable.json", "id", typeof(ArchiveEventStoryTable))]
[LoadRecord("ArchiveEventStoryTable.json", "id")]
public readonly Dictionary<int, ArchiveEventStoryRecord> archiveEventStoryRecords = [];
[LoadRecord("ArchiveEventQuestTable.json", "id", typeof(ArchiveEventQuestTable))]
[LoadRecord("ArchiveEventQuestTable.json", "id")]
public readonly Dictionary<int, ArchiveEventQuestRecord> archiveEventQuestRecords = [];
[LoadRecord("ArchiveEventDungeonStageTable.json", "id", typeof(ArchiveEventDungeonStageTable))]
[LoadRecord("ArchiveEventDungeonStageTable.json", "id")]
public readonly Dictionary<int, ArchiveEventDungeonStageRecord> archiveEventDungeonStageRecords = [];
[LoadRecord("UserTitleTable.json", "id", typeof(UserTitleTable))]
[LoadRecord("UserTitleTable.json", "id")]
public readonly Dictionary<int, UserTitleRecord> userTitleRecords = [];
[LoadRecord("ArchiveMessengerConditionTable.json", "id", typeof(ArchiveMessengerConditionTable))]
[LoadRecord("ArchiveMessengerConditionTable.json", "id")]
public readonly Dictionary<int, ArchiveMessengerConditionRecord> archiveMessengerConditionRecords = [];
[LoadRecord("CharacterStatTable.json", "id", typeof(CharacterStatTable))]
[LoadRecord("CharacterStatTable.json", "id")]
public readonly Dictionary<int, CharacterStatRecord> characterStatTable = [];
[LoadRecord("SkillInfoTable.json", "id", typeof(SkillInfoTable))]
[LoadRecord("SkillInfoTable.json", "id")]
public readonly Dictionary<int, SkillInfoRecord> skillInfoTable = [];
[LoadRecord("CostTable.json", "id", typeof(CostTable))]
[LoadRecord("CostTable.json", "id")]
public readonly Dictionary<int, CostRecord> costTable = [];
[LoadRecord("MidasProductTable.json", "midas_product_id_proximabeta", typeof(MidasProductTable))]
[LoadRecord("MidasProductTable.json", "midas_product_id_proximabeta")]
public readonly Dictionary<string, MidasProductRecord> mediasProductTable = [];
[LoadRecord("TowerTable.json", "id", typeof(TowerTable))]
[LoadRecord("TowerTable.json", "id")]
public readonly Dictionary<int, TowerRecord> towerTable = [];
[LoadRecord("TriggerTable.json", "id", typeof(TriggerTable))]
[LoadRecord("TriggerTable.json", "id")]
public readonly Dictionary<int, TriggerRecord> TriggerTable = [];
[LoadRecord("InfraCoreGradeTable.json", "id", typeof(InfracoreTable))]
[LoadRecord("InfraCoreGradeTable.json", "id")]
public readonly Dictionary<int, InfracoreRecord> InfracoreTable = [];
[LoadRecord("AttractiveCounselCharacterTable.json", "name_code", typeof(AttractiveCounselCharacterTable))]
[LoadRecord("AttractiveCounselCharacterTable.json", "name_code")]
public readonly Dictionary<int, AttractiveCounselCharacterRecord> AttractiveCounselCharacterTable = [];
[LoadRecord("AttractiveLevelRewardTable.json", "id", typeof(AttractiveLevelRewardTable))]
[LoadRecord("AttractiveLevelRewardTable.json", "id")]
public readonly Dictionary<int, AttractiveLevelRewardRecord> AttractiveLevelReward = [];
[LoadRecord("SubQuestTable.json", "id", typeof(SubquestTable))]
[LoadRecord("SubQuestTable.json", "id")]
public readonly Dictionary<int, SubquestRecord> Subquests = [];
[LoadRecord("MessengerDialogTable.json", "id", typeof(MessengerDialogTable))]
[LoadRecord("MessengerDialogTable.json", "id")]
public readonly Dictionary<string, MessengerDialogRecord> Messages = [];
[LoadRecord("MessengerConditionTriggerTable.json", "id", typeof(MessengerMsgConditionTable))]
[LoadRecord("MessengerConditionTriggerTable.json", "id")]
public readonly Dictionary<int, MessengerMsgConditionRecord> MessageConditions = [];
[LoadRecord("ScenarioRewardsTable.json", "condition_id", typeof(ScenarioRewardTable))]
[LoadRecord("ScenarioRewardsTable.json", "condition_id")]
public readonly Dictionary<string, ScenarioRewardRecord> ScenarioRewards = [];
// Note: same data types are intentional
[LoadRecord("ProductOfferTable.json", "id", typeof(ProductOfferTable))]
[LoadRecord("ProductOfferTable.json", "id")]
public readonly Dictionary<int, ProductOfferRecord> ProductOffers = [];
[LoadRecord("PopupPackageListTable.json", "id", typeof(ProductOfferTable))]
[LoadRecord("PopupPackageListTable.json", "id")]
public readonly Dictionary<int, ProductOfferRecord> PopupPackages = [];
[LoadRecord("InterceptNormalTable.json", "id", typeof(InterceptionTable))]
[LoadRecord("InterceptNormalTable.json", "id")]
public readonly Dictionary<int, InterceptionRecord> InterceptNormal = [];
[LoadRecord("InterceptSpecialTable.json", "id", typeof(InterceptionTable))]
[LoadRecord("InterceptSpecialTable.json", "id")]
public readonly Dictionary<int, InterceptionRecord> InterceptSpecial = [];
[LoadRecord("ConditionRewardTable.json", "id", typeof(ConditionRewardTable))]
[LoadRecord("ConditionRewardTable.json", "id")]
public readonly Dictionary<int, ConditionRewardRecord> ConditionRewards = [];
[LoadRecord("ItemConsumeTable.json", "id", typeof(ItemConsumeTable))]
[LoadRecord("ItemConsumeTable.json", "id")]
public readonly Dictionary<int, ItemConsumeRecord> ConsumableItems = [];
[LoadRecord("ItemRandomTable.json", "id", typeof(RandomItemTable))]
[LoadRecord("ItemRandomTable.json", "id")]
public readonly Dictionary<int, RandomItemRecord> RandomItem = [];
[LoadRecord("LostSectorTable.json", "id", typeof(LostSectorTable))]
[LoadRecord("LostSectorTable.json", "id")]
public readonly Dictionary<int, LostSectorRecord> LostSector = [];
[LoadRecord("LostSectorStageTable.json", "id", typeof(LostSectorStageTable))]
[LoadRecord("LostSectorStageTable.json", "id")]
public readonly Dictionary<int, LostSectorStageRecord> LostSectorStages = [];
[LoadRecord("ItemPieceTable.json", "id", typeof(ItemPieceTable))]
[LoadRecord("ItemPieceTable.json", "id")]
public readonly Dictionary<int, ItemPieceRecord> PieceItems = [];
[LoadRecord("GachaGradeProbTable.json", "id", typeof(GachaGradeProbTable))]
[LoadRecord("GachaGradeProbTable.json", "id")]
public readonly Dictionary<int, GachaGradeProbRecord> GachaGradeProb = [];
[LoadRecord("GachaListProbTable.json", "id", typeof(GachaListProbTable))]
[LoadRecord("GachaListProbTable.json", "id")]
public readonly Dictionary<int, GachaListProbRecord> GachaListProb = [];
[LoadRecord("RecycleResearchStatTable.json", "id", typeof(RecycleResearchStatTable))]
[LoadRecord("RecycleResearchStatTable.json", "id")]
public readonly Dictionary<int, RecycleResearchStatRecord> RecycleResearchStats = [];
[LoadRecord("RecycleResearchLevelTable.json", "id", typeof(RecycleResearchLevelTable))]
[LoadRecord("RecycleResearchLevelTable.json", "id")]
public readonly Dictionary<int, RecycleResearchLevelRecord> RecycleResearchLevels = [];
static async Task<GameData> BuildAsync()
{
await Load();
@@ -232,7 +238,10 @@ namespace EpinelPS.Data
MpkSize = rawBytes2.Length;
}
LoadGameData(filePath, GameConfig.Root.StaticData);
if (UseMemoryPack)
LoadGameData(mpkFilePath, GameConfig.Root.StaticDataMpk);
else
LoadGameData(filePath, GameConfig.Root.StaticData);
if (MainZip == null) throw new Exception("failed to read zip file");
}
@@ -371,25 +380,49 @@ namespace EpinelPS.Data
}
#endregion
public async Task<T> LoadZip<T>(string entry, IProgress<double> bar) where T : new()
public async Task<X[]> LoadZip<X>(string entry, IProgress<double> bar) where X : new()
{
var fileEntry = MainZip.GetEntry(entry);
if (fileEntry == null)
try
{
Logging.WriteLine(entry + " does not exist in static data", LogType.Error);
return new T();
if (UseMemoryPack) entry = entry.Replace(".json", ".mpk");
var fileEntry = MainZip.GetEntry(entry);
if (fileEntry == null)
{
Logging.WriteLine(entry + " does not exist in static data", LogType.Error);
return [];
}
X[]? deserializedObject;
if (UseMemoryPack)
{
var stream = MainZip.GetInputStream(fileEntry);
deserializedObject = await MemoryPackSerializer.DeserializeAsync<X[]>(stream);
}
else
{
using StreamReader fileReader = new(MainZip.GetInputStream(fileEntry));
string fileString = await fileReader.ReadToEndAsync();
var obj = JsonConvert.DeserializeObject<DataTable<X>>(fileString);
if (obj == null) throw new Exception("deserializeobject failed");
deserializedObject = obj.records.ToArray();
}
if (deserializedObject == null) throw new Exception("failed to parse " + entry);
currentFile++;
bar.Report((double)currentFile / totalFiles);
return deserializedObject;
}
catch(Exception ex)
{
Logging.WriteLine($"Failed to parse {entry}:\n{ex.ToString()}\n", LogType.Error);
return [];
}
using StreamReader fileReader = new(MainZip.GetInputStream(fileEntry));
string fileString = await fileReader.ReadToEndAsync();
T? deserializedObject = JsonConvert.DeserializeObject<T>(fileString);
if (deserializedObject == null) throw new Exception("failed to parse " + entry);
currentFile++;
bar.Report((double)currentFile / totalFiles);
return deserializedObject;
}
public async Task Parse()
@@ -397,6 +430,7 @@ namespace EpinelPS.Data
using var progress = new ProgressBar();
totalFiles = GameDataInitializer.TotalFiles;
if (totalFiles == 0) throw new Exception("Source generator failed.");
await GameDataInitializer.InitializeGameData(progress);
@@ -407,9 +441,9 @@ namespace EpinelPS.Data
item.Name.StartsWith("LostSectorMap/")
)
{
var x = await LoadZip<MapInfoTable>(item.Name, progress);
var x = await LoadZip<MapInfo>(item.Name, progress);
foreach (var map in x.records)
foreach (var map in x)
{
MapData.Add(map.id, map);
}
@@ -540,14 +574,14 @@ namespace EpinelPS.Data
internal IEnumerable<int> GetStageIdsForChapter(int chapterNumber, bool normal)
{
string mod = normal ? "Normal" : "Hard";
ChapterMod mod = normal ? ChapterMod.Normal : ChapterMod.Hard;
foreach (var item in StageDataRecords)
{
var data = item.Value;
int chVal = data.chapter_id - 1;
if (chapterNumber == chVal && data.chapter_mod == mod && data.stage_type == "Main")
if (chapterNumber == chVal && data.mod == mod && data.stage_type == StageType.Main)
{
yield return data.id;
}
@@ -622,10 +656,10 @@ namespace EpinelPS.Data
return false;
}
internal string GetMapIdFromChapter(int chapter, int mod)
internal string GetMapIdFromChapter(int chapter, ChapterMod mod)
{
CampaignChapterRecord data = ChapterCampaignData[chapter - 1];
if (mod != 0)
if (mod == ChapterMod.Hard)
return data.hard_field_id;
else return data.field_id;
}

View File

@@ -1,50 +1,148 @@
namespace EpinelPS.Data
using System.Data;
using MemoryPack;
namespace EpinelPS.Data
{
public class MainQuestCompletionRecord
public class DataTable<T>
{
public string version { get; set; } = "";
public List<T> records { get; set; } = [];
}
public enum Category
{
Campaign_View,
Outpost_View,
Ark_View,
Lobby_Enter,
Campaign_Enter,
Outpost_Building_Enter,
Ark_Marker,
Campaign_Clear,
Outpost_Select,
Tribe_Tower_Clear,
Tribe_Tower_Enter,
Tribe_Tower_View,
Run_Gacha,
Npc_Talk,
FieldObject_Collection,
End,
LostSector_View,
LostSector_Clear,
SimulationRoom_View,
EventQuest_Stage_Enter,
EventQuest_Stage_Clear,
EventQuest_Stage_Group_Clear,
EventQuest_Popup_Enter,
Normal_Chapter_View,
Field_Interaction_Action_Trigger,
FavoriteItemQuest_Stage_Enter,
FavoriteItemQuest_Stage_Clear,
FavoriteItemQuest_Stage_Group_Clear,
SimulationRoom_Select
}
public enum ScenarioType
{
None,
FieldTalk,
Dialog,
Balloon
}
public enum ChapterMod
{
Normal,
Hard
}
public enum StageCategory
{
Normal,
Story,
Hard,
Extra,
Boss
}
public enum StageType
{
None,
Main,
Sub,
Emergency,
EventQuest,
FavoriteItemQuest
}
public enum EquipmentRarityType
{
None,
T1,
T2,
T3,
T4,
T5,
T6,
T7,
T8,
T9,
T10
}
[MemoryPackable]
public partial class MainQuestCompletionRecord
{
public int id;
public int group_id;
public string category = "";
public Category category;
public int condition_id;
public string condition_ui_localkey = "";
public string shortcut_type = "";
public int shortcut_value;
public string name_localkey = "";
public string description_localkey = "";
public int next_main_quest_id = 0;
public int reward_id = 0;
public ScenarioType scenario_type;
public string episode_id = "";
public int target_chapter_id;
public string header_bg_resource_id = "";
}
public class MainQuestCompletionTable
{
public List<MainQuestCompletionRecord> records = [];
}
public class CampaignStageRecord
[MemoryPackable]
public partial class CampaignStageRecord
{
public int id;
public int chapter_id;
public string stage_category = "";
public ChapterMod mod;
public int parent_id;
public int group_id;
public string name = "";
public StageCategory stage_category;
public StageType stage_type;
public bool allow_autobattle;
public int enter_condition;
public int monster_stage_level;
public int dynobj_stage_level;
public int standard_battle_power;
public int statinc_groupid;
public bool allow_quickbattle;
public int fieldmonster_id;
public int spotid;
public int reward_id = 0;
/// <summary>
/// Can be Normal or Hard
/// </summary>
public string chapter_mod = "";
public string stage_type = "";
public ScenarioType enter_scenario_type;
public string enter_scenario = "";
public ScenarioType exit_scenario_type;
public string exit_scenario = "";
public int current_outpost_battle_id;
public int cleared_battleid;
public int fixedCharacterid;
public int characterLevel;
}
public class CampaignStageTable
{
public List<CampaignStageRecord> records = [];
}
public class RewardTableRecord
[MemoryPackable]
public partial class RewardTableRecord
{
public int id;
public int user_exp;
public int character_exp;
public RewardEntry[]? rewards;
}
public class RewardTable
{
public List<RewardTableRecord> records = [];
}
public class RewardEntry
[MemoryPackable]
public partial class RewardEntry
{
/// <summary>
/// example: 1000000
@@ -55,8 +153,6 @@
public int reward_id;
public int reward_value;
}
public class ClearedTutorialData
{
public int id;
@@ -66,12 +162,8 @@
public int NextId;
public bool SaveTutorial;
}
public class TutorialTable
{
public List<ClearedTutorialData> records = [];
}
public class CharacterLevelData
[MemoryPackable]
public partial class CharacterLevelData
{
/// <summary>
/// level
@@ -94,17 +186,13 @@
/// </summary>
public int character_exp2 = 0;
}
public class CharacterLevelTable
{
public List<CharacterLevelData> records = [];
}
public class TacticAcademyLessonReward
{
public int lesson_reward_id;
public int lesson_reward_value;
}
public class TacticAcademyLessonRecord
[MemoryPackable]
public partial class TacticAcademyLessonRecord
{
public int currency_id;
public int currency_value;
@@ -114,24 +202,16 @@
public TacticAcademyLessonReward[]? lesson_reward;
}
public class TacticAcademyLessonTable
{
public List<TacticAcademyLessonRecord> records = [];
}
public class CampaignChapterRecord
[MemoryPackable]
public partial class CampaignChapterRecord
{
public int id;
public int chapter;
public string field_id = "";
public string hard_field_id = "";
}
public class CampaignChapterTable
{
public List<CampaignChapterRecord> records = [];
}
public class CharacterRecord
[MemoryPackable]
public partial class CharacterRecord
{
public int id;
public int piece_id;
@@ -169,12 +249,9 @@
public bool prism_is_active;
public bool is_detail_close;
}
public class CharacterTable
{
public List<CharacterRecord> records = [];
}
public class ItemEquipRecord
[MemoryPackable]
public partial class ItemEquipRecord
{
public int id;
public string name_localkey = "";
@@ -204,12 +281,9 @@
public int option_slot;
public int option_slot_success_ratio;
}
public class ItemEquipTable
{
public List<ItemEquipRecord> records = [];
}
public class FieldItemRecord
[MemoryPackable]
public partial class FieldItemRecord
{
public int id;
public string item_type = "";
@@ -217,11 +291,9 @@
public bool is_final_reward;
public string difficulty = "";
}
public class FieldItemTable
{
public List<FieldItemRecord> records = [];
}
public class JukeboxListRecord
[MemoryPackable]
public partial class JukeboxListRecord
{
public int id;
public int theme;
@@ -235,12 +307,8 @@
public string get_info_value = "";
}
public class JukeboxListTable
{
public List<JukeboxListRecord> records = [];
}
public class JukeboxThemeRecord
[MemoryPackable]
public partial class JukeboxThemeRecord
{
public int id;
public string name_localkey = "";
@@ -250,11 +318,8 @@
public string bg_color = "";
}
public class JukeboxThemeTable
{
public List<JukeboxThemeRecord> records = [];
}
public class OutpostBattleTableRecord
[MemoryPackable]
public partial class OutpostBattleTableRecord
{
public int id;
public int credit;
@@ -262,12 +327,9 @@
public int character_exp2;
public int user_exp;
}
public class OutpostBattleTable
{
public List<OutpostBattleTableRecord> records = [];
}
public class GachaPriceGroup
[MemoryPackable]
public partial class GachaPriceGroup
{
public int gacha_price_type;
public int gacha_price_value_count_1;
@@ -275,7 +337,7 @@
public int gacha_price_value_count_10;
}
public class GachaType
public partial class GachaType
{
public int id;
public string type = "";
@@ -298,12 +360,8 @@
public int previous_gacha_id;
}
public class GachaTypeTable
{
public List<GachaType> records = [];
}
public class GachaGradeProbRecord
[MemoryPackable]
public partial class GachaGradeProbRecord
{
public int id;
public int group_id;
@@ -311,23 +369,16 @@
public int prob;
public int gacha_list_id;
}
public class GachaGradeProbTable
{
public List<GachaGradeProbRecord> records = [];
}
public class GachaListProbRecord
[MemoryPackable]
public partial class GachaListProbRecord
{
public int id;
public int group_id;
public int gacha_id;
}
public class GachaListProbTable
{
public List<GachaListProbRecord> records = [];
}
public class EventManager
[MemoryPackable]
public partial class EventManager
{
public int id;
public string event_system_type = "";
@@ -348,23 +399,17 @@
public string active_type = "";
public string banner_print_type = "";
}
[MemoryPackable]
public class EventManagerTable
{
public List<EventManager> records = [];
}
public class LiveWallpaperRecord
public partial class LiveWallpaperRecord
{
public int id;
public string livewallpaper_type = "";
}
[MemoryPackable]
public class LiveWallpaperTable
{
public List<LiveWallpaperRecord> records = [];
}
public class AlbumResourceRecord
public partial class AlbumResourceRecord
{
public int id;
public int sub_category_id;
@@ -376,12 +421,9 @@
public string dialogtype = "";
}
public class AlbumResourceTable
{
public List<AlbumResourceRecord> records = [];
}
public class UserFrameTableRecord
[MemoryPackable]
public partial class UserFrameTableRecord
{
public int id;
public string resource_id = "";
@@ -394,12 +436,9 @@
public bool is_sub_resource_prism;
}
public class UserFrameTable
{
public List<UserFrameTableRecord> records = [];
}
public class ArchiveRecordManagerRecord
[MemoryPackable]
public partial class ArchiveRecordManagerRecord
{
public int id;
public string record_type = "";
@@ -416,12 +455,9 @@
public string record_unlock_bg_addressable = "";
}
public class ArchiveRecordManagerTable
{
public List<ArchiveRecordManagerRecord> records = [];
}
public class ArchiveEventStoryRecord
[MemoryPackable]
public partial class ArchiveEventStoryRecord
{
public int id;
public int event_id;
@@ -433,12 +469,9 @@
public int archive_currency_item_id;
}
public class ArchiveEventStoryTable
{
public List<ArchiveEventStoryRecord> records = [];
}
public class ArchiveEventQuestRecord
[MemoryPackable]
public partial class ArchiveEventQuestRecord
{
public int id;
public int event_quest_manager_id;
@@ -450,12 +483,9 @@
public string end_scenario_id = "";
}
public class ArchiveEventQuestTable
{
public List<ArchiveEventQuestRecord> records = [];
}
public class ArchiveEventDungeonStageRecord
[MemoryPackable]
public partial class ArchiveEventDungeonStageRecord
{
public int id;
public int group;
@@ -466,11 +496,8 @@
public bool is_repeat_clear;
}
public class ArchiveEventDungeonStageTable
{
public List<ArchiveEventDungeonStageRecord> records = [];
}
public class UserTitleRecord
[MemoryPackable]
public partial class UserTitleRecord
{
public int id;
public int order;
@@ -483,32 +510,23 @@
public bool not_acquired_is_visible;
}
public class UserTitleTable
{
public List<UserTitleRecord> records = [];
}
public class ArchiveMessengerConditionList
[MemoryPackable]
public partial class ArchiveMessengerConditionList
{
public string condition_type = "";
public int condition_id;
}
public class ArchiveMessengerConditionRecord
[MemoryPackable]
public partial class ArchiveMessengerConditionRecord
{
public int id;
public int archive_messenger_group_id;
public List<ArchiveMessengerConditionList> archive_messenger_condition_list = [];
public string tid = "";
}
public class ArchiveMessengerConditionTable
{
public string version = "";
public List<ArchiveMessengerConditionRecord> records = [];
}
public class CharacterStatRecord
[MemoryPackable]
public partial class CharacterStatRecord
{
public int id;
public int group;
@@ -520,12 +538,8 @@
public int level_bio_resist;
}
public class CharacterStatTable
{
public List<CharacterStatRecord> records = [];
}
public class ItemMaterialRecord
[MemoryPackable]
public partial class ItemMaterialRecord
{
public int id;
public string name_localkey = "";
@@ -540,12 +554,9 @@
public int stack_max;
}
public class ItemMaterialTable
{
public List<ItemMaterialRecord> records = [];
}
public class SkillInfoRecord
[MemoryPackable]
public partial class SkillInfoRecord
{
public int id;
public int group_id;
@@ -564,11 +575,6 @@
public string description_value = "";
}
public class SkillInfoTable
{
public List<SkillInfoRecord> records = [];
}
public class CostRecord
{
public int id;
@@ -582,11 +588,8 @@
public int item_value;
}
public class CostTable
{
public List<CostRecord> records = [];
}
public class MidasProductRecord
[MemoryPackable]
public partial class MidasProductRecord
{
public int id;
public string product_type = "";
@@ -597,10 +600,7 @@
public bool is_free;
public string cost = "";
}
public class MidasProductTable
{
public List<MidasProductRecord> records = [];
}
public enum ShopCategoryType
{
None = 0,
@@ -613,7 +613,8 @@
Mileage = 7,
Trade = 8
}
public class TowerRecord
[MemoryPackable]
public partial class TowerRecord
{
public int id;
public int floor;
@@ -621,12 +622,9 @@
public int standard_battle_power;
public int reward_id;
}
public class TowerTable
{
public List<TowerRecord> records = [];
}
public class ItemEquipExpRecord
[MemoryPackable]
public partial class ItemEquipExpRecord
{
public int id;
public int level;
@@ -635,56 +633,39 @@
public int grade_core_id;
}
public class ItemEquipExpTable
{
public List<ItemEquipExpRecord> records = [];
}
public class ItemEquipGradeExpRecord
[MemoryPackable]
public partial class ItemEquipGradeExpRecord
{
public int id;
public int exp;
public string item_rare = "";
public EquipmentRarityType item_rare;
public int grade_core_id;
public int exp;
}
public class ItemEquipGradeExpTable
{
public List<ItemEquipGradeExpRecord> records = [];
}
public class UserExpRecord
[MemoryPackable]
public partial class UserExpRecord
{
public int level;
public int exp;
public int reward_id;
}
public class UserExpTable
{
public List<UserExpRecord> records = [];
}
public class CharacterCostumeRecord
[MemoryPackable]
public partial class CharacterCostumeRecord
{
public int id;
}
public class CharacterCostumeTable
{
public List<CharacterCostumeRecord> records = [];
}
public class SideStoryStageRecord
[MemoryPackable]
public partial class SideStoryStageRecord
{
public int id;
public int first_clear_reward;
}
public class SideStoryStageTable
{
public List<SideStoryStageRecord> records = [];
}
public class TriggerRecord
[MemoryPackable]
public partial class TriggerRecord
{
public int id;
public int condition_id;
@@ -694,15 +675,13 @@
public bool print_value;
public int before_trigger_id;
}
public class TriggerTable
{
public List<TriggerRecord> records = [];
}
public class InfracoreFunction
{
public int function;
}
public class InfracoreRecord
[MemoryPackable]
public partial class InfracoreRecord
{
public int id;
public int grade;
@@ -710,22 +689,16 @@
public int infra_core_exp;
public List<InfracoreFunction> function_list = [];
}
public class InfracoreTable
{
public List<InfracoreRecord> records = [];
}
public class AttractiveCounselCharacterRecord
[MemoryPackable]
public partial class AttractiveCounselCharacterRecord
{
public int id;
public int name_code;
public int collect_reward_id;
}
public class AttractiveCounselCharacterTable
{
public List<AttractiveCounselCharacterRecord> records = [];
}
public class AttractiveLevelRewardRecord
[MemoryPackable]
public partial class AttractiveLevelRewardRecord
{
public int id;
public int name_code;
@@ -733,11 +706,8 @@
public int attractive_level;
public int costume;
}
public class AttractiveLevelRewardTable
{
public List<AttractiveLevelRewardRecord> records = [];
}
public class SubquestRecord
[MemoryPackable]
public partial class SubquestRecord
{
public int id;
public int group_id;
@@ -747,11 +717,9 @@
public string end_messenger_conversation_id = "";
public int before_sub_quest_id;
}
public class SubquestTable
{
public List<SubquestRecord> records = [];
}
public class MessengerDialogRecord
[MemoryPackable]
public partial class MessengerDialogRecord
{
public string id = "";
public string conversation_id = "";
@@ -759,41 +727,36 @@
public bool is_opener;
public int reward_id;
}
public class MessengerDialogTable
{
public List<MessengerDialogRecord> records = [];
}
public class MessengerMsgConditionRecord
[MemoryPackable]
public partial class MessengerMsgConditionRecord
{
public int id;
public string tid = "";
public int reward_id;
}
public class MessengerMsgConditionTable
public enum ScenarioRewardCondition
{
public List<MessengerMsgConditionRecord> records = [];
}
public class ScenarioRewardRecord
{
public int id;
public string condition_id = "";
public string condition_type = "";
public int reward_id;
}
public class ScenarioRewardTable
{
public List<ScenarioRewardRecord> records = [];
MainScenario,
AttractiveScenario
}
public class ProductOfferRecord
[MemoryPackable]
public partial class ScenarioRewardRecord
{
public int id;
public ScenarioRewardCondition condition_type;
public string condition_id = "";
public int reward_id;
}
[MemoryPackable]
public partial class ProductOfferRecord
{
public int id;
}
public class ProductOfferTable
{
public List<ProductOfferRecord> records = [];
}
public class InterceptionRecord
[MemoryPackable]
public partial class InterceptionRecord
{
public int id;
public int group;
@@ -801,11 +764,9 @@
public int percent_condition_reward_group;
public long fixed_damage;
}
public class InterceptionTable
{
public List<InterceptionRecord> records = [];
}
public class ConditionRewardRecord
[MemoryPackable]
public partial class ConditionRewardRecord
{
public int id;
public int group;
@@ -814,10 +775,7 @@
public long value_max;
public int reward_id;
}
public class ConditionRewardTable
{
public List<ConditionRewardRecord> records = [];
}
public enum ItemSubType
{
BundleBox,
@@ -829,7 +787,8 @@
ArcadeItem,
EquipCombination
}
public class ItemConsumeRecord
[MemoryPackable]
public partial class ItemConsumeRecord
{
public int id;
public string use_type = "";
@@ -838,22 +797,18 @@
public int use_id;
public int use_value;
}
public class ItemConsumeTable
{
public List<ItemConsumeRecord> records = [];
}
public class ItemPieceRecord
[MemoryPackable]
public partial class ItemPieceRecord
{
public int id;
public string use_type = "";
public int use_id;
public int use_value;
}
public class ItemPieceTable
{
public List<ItemPieceRecord> records = [];
}
public class RandomItemRecord
[MemoryPackable]
public partial class RandomItemRecord
{
public int id;
public int group_id;
@@ -863,11 +818,9 @@
public int reward_value_max;
public int ratio;
}
public class RandomItemTable
{
public List<RandomItemRecord> records = [];
}
public class RecycleResearchStatRecord
[MemoryPackable]
public partial class RecycleResearchStatRecord
{
public int id;
public string recycle_type = "";
@@ -877,11 +830,9 @@
public int defense;
public int hp;
}
public class RecycleResearchStatTable
{
public List<RecycleResearchStatRecord> records = [];
}
public class RecycleResearchLevelRecord
[MemoryPackable]
public partial class RecycleResearchLevelRecord
{
public int id;
public string recycle_type = "";
@@ -890,16 +841,15 @@
public int item_id;
public int item_value;
}
public class RecycleResearchLevelTable
{
public List<RecycleResearchLevelRecord> records = [];
}
public enum ContentOpenType
{
Stage,
NonUpdate
}
public class LostSectorRecord
[MemoryPackable]
public partial class LostSectorRecord
{
public int id;
public int sector;
@@ -910,20 +860,14 @@
public int open_condition_value;
}
public class LostSectorTable
{
public List<LostSectorRecord> records = [];
}
public class LostSectorStageRecord
[MemoryPackable]
public partial class LostSectorStageRecord
{
public int id;
public bool is_use_quick_battle;
public int sector;
}
public class LostSectorStageTable
{
public List<LostSectorStageRecord> records = [];
}
public class ItemSpawner
{
@@ -936,14 +880,13 @@
public string positionId = "";
public int stageId;
}
public class MapInfo
[MemoryPackable]
public partial class MapInfo
{
public string id { get; set; } = "";
public List<ItemSpawner> ItemSpawner { get; set; } = [];
public List<StageSpawner> StageSpawner { get; set; } = [];
}
public class MapInfoTable
{
public List<MapInfo> records = [];
}
}

View File

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

View File

@@ -27,6 +27,7 @@
<PackageReference Include="Google.Api.CommonProtos" Version="2.17.0" />
<PackageReference Include="Google.Protobuf.Tools" Version="3.31.1" />
<PackageReference Include="Grpc.AspNetCore" Version="2.71.0" />
<PackageReference Include="MemoryPack" Version="1.21.4" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Paseto.Core" Version="1.4.1" />

View File

@@ -31,7 +31,7 @@ namespace EpinelPS.LobbyServer.Stage
var response = new ResClearStage();
var clearedStage = GameData.Instance.GetStageData(StageId) ?? throw new Exception("cleared stage cannot be null");
var stageMapId = GameData.Instance.GetMapIdFromChapter(clearedStage.chapter_id, clearedStage.chapter_mod);
var stageMapId = GameData.Instance.GetMapIdFromChapter(clearedStage.chapter_id, clearedStage.mod);
if (user.FieldInfoNew.Count == 0)
{
@@ -89,33 +89,29 @@ namespace EpinelPS.LobbyServer.Stage
});
}
if (clearedStage.stage_category == "Normal" || clearedStage.stage_category == "Boss" || clearedStage.stage_category == "Hard")
if (clearedStage.stage_category == StageCategory.Normal || clearedStage.stage_category == StageCategory.Boss || clearedStage.stage_category == StageCategory.Hard)
{
if (clearedStage.chapter_mod == "Hard")
if (clearedStage.mod == ChapterMod.Hard)
{
if (StageId > user.LastHardStageCleared)
user.LastHardStageCleared = StageId;
}
else if (clearedStage.chapter_mod == "Normal")
else
{
if (StageId > user.LastNormalStageCleared)
user.LastNormalStageCleared = StageId;
}
else
{
Console.WriteLine("Unknown chapter mod " + clearedStage.chapter_mod);
}
}
else if (clearedStage.stage_category == "Extra")
else if (clearedStage.stage_category == StageCategory.Extra)
{
// TODO
}
else
{
Console.WriteLine("Unknown stage category " + clearedStage.stage_category);
}
if (clearedStage.stage_type != "Sub")
if (clearedStage.stage_type != StageType.Sub)
{
// add outpost reward level if unlocked
if (user.MainQuestData.TryGetValue(21, out bool _))
@@ -137,9 +133,9 @@ namespace EpinelPS.LobbyServer.Stage
// Mark chapter as completed if boss stage was completed
if (clearedStage.stage_category == "Boss" && clearedStage.stage_type == "Main")
if (clearedStage.stage_category == StageCategory.Boss && clearedStage.stage_type == StageType.Main)
{
if (clearedStage.chapter_mod == "Hard")
if (clearedStage.mod == ChapterMod.Hard)
user.AddTrigger(TriggerType.HardChapterClear, 1, clearedStage.chapter_id);
else
user.AddTrigger(TriggerType.ChapterClear, 1, clearedStage.chapter_id);

View File

@@ -15,9 +15,9 @@ namespace EpinelPS.LobbyServer.Stage
var response = new ResEnterStage();
var clearedStage = GameData.Instance.GetStageData(req.StageId) ?? throw new Exception("cleared stage cannot be null");
var map = GameData.Instance.GetMapIdFromChapter(clearedStage.chapter_id, clearedStage.chapter_id);
var map = GameData.Instance.GetMapIdFromChapter(clearedStage.chapter_id, clearedStage.mod);
if (clearedStage.stage_category == "Boss")
if (clearedStage.stage_category == StageCategory.Boss)
{
// When entering a boss stage, unlock boss information in campaign
if (!user.FieldInfoNew.ContainsKey(map))

View File

@@ -12,7 +12,7 @@ namespace EpinelPS.LobbyServer.Stage
ReqGetStageData req = await ReadData<ReqGetStageData>();
var user = GetUser();
var mapId = GameData.Instance.GetMapIdFromChapter(req.Chapter, req.Mod);
var mapId = GameData.Instance.GetMapIdFromChapter(req.Chapter, (ChapterMod)req.Mod);
ResGetStageData response = new()
{