From 5fd4da9d698b5d54961232952ecb57d07728a007 Mon Sep 17 00:00:00 2001 From: Mikhail Tyukin Date: Sat, 19 Jul 2025 16:08:50 -0400 Subject: [PATCH] fix character counsel, server reset --- EpinelPS/Data/GameData.cs | 8 ++- EpinelPS/Data/JsonStaticData.cs | 20 ++++---- EpinelPS/Database/JsonDb.cs | 35 ++++++++++++- EpinelPS/EpinelPS.csproj | 2 +- EpinelPS/LobbyServer/Auth/DoEnterServer.cs | 18 ++++--- EpinelPS/LobbyServer/Character/Counsel.cs | 21 -------- .../LobbyServer/Character/Counsel/Check.cs | 18 +++++++ .../Character/Counsel/DoCounsel.cs | 49 +++++++++++++++++++ EpinelPS/LobbyServer/Event/EnterEventField.cs | 2 + .../LobbyServer/Event/Shop/ListProductList.cs | 24 +++++++++ EpinelPS/LobbyServer/Mission/ObtainDaily.cs | 4 +- .../Mission/Rewards/GetDailyRewards.cs | 2 + .../Mission/Rewards/GetWeeklyRewards.cs | 2 + .../LobbyServer/Outpost/GetOutpostData.cs | 1 + EpinelPS/LobbyServer/Stage/ClearStage.cs | 11 +++-- EpinelPS/LobbyServer/Stage/EnterStage.cs | 4 +- 16 files changed, 171 insertions(+), 50 deletions(-) delete mode 100644 EpinelPS/LobbyServer/Character/Counsel.cs create mode 100644 EpinelPS/LobbyServer/Character/Counsel/Check.cs create mode 100644 EpinelPS/LobbyServer/Character/Counsel/DoCounsel.cs create mode 100644 EpinelPS/LobbyServer/Event/Shop/ListProductList.cs diff --git a/EpinelPS/Data/GameData.cs b/EpinelPS/Data/GameData.cs index 682431e..c7fdfa7 100644 --- a/EpinelPS/Data/GameData.cs +++ b/EpinelPS/Data/GameData.cs @@ -450,10 +450,16 @@ namespace EpinelPS.Data } } + + + + // sanity checks + if (QuestDataRecords.Count == 0) throw new Exception("QuestDataRecords should not be empty"); } public MainQuestCompletionRecord? GetMainQuestForStageClearCondition(int stage) { + if (QuestDataRecords.Count == 0) throw new Exception("QuestDataRecords should not be empty"); foreach (var item in QuestDataRecords) { if (item.Value.condition_id == stage) @@ -581,7 +587,7 @@ namespace EpinelPS.Data int chVal = data.chapter_id - 1; - if (chapterNumber == chVal && data.mod == mod && data.stage_type == StageType.Main) + if (chapterNumber == chVal && data.chapter_mod == mod && data.stage_type == StageType.Main) { yield return data.id; } diff --git a/EpinelPS/Data/JsonStaticData.cs b/EpinelPS/Data/JsonStaticData.cs index 6a4c5b5..4202eac 100644 --- a/EpinelPS/Data/JsonStaticData.cs +++ b/EpinelPS/Data/JsonStaticData.cs @@ -107,22 +107,22 @@ namespace EpinelPS.Data { public int id; public int chapter_id; - public ChapterMod mod; + public ChapterMod chapter_mod; public int parent_id; public int group_id; - public string name = ""; + public string name_localkey = ""; public StageCategory stage_category; public StageType stage_type; - public bool allow_autobattle; + public bool spot_autocontrol; public int enter_condition; - public int monster_stage_level; - public int dynobj_stage_level; + public int monster_stage_lv; + public int dynamic_object_stage_lv; 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; + public int stage_stat_increase_group_id; + public bool is_use_quick_battle; + public int field_monster_id; + public int spot_id; + public int reward_id; public ScenarioType enter_scenario_type; public string enter_scenario = ""; public ScenarioType exit_scenario_type; diff --git a/EpinelPS/Database/JsonDb.cs b/EpinelPS/Database/JsonDb.cs index 7961feb..8611e01 100644 --- a/EpinelPS/Database/JsonDb.cs +++ b/EpinelPS/Database/JsonDb.cs @@ -261,6 +261,7 @@ namespace EpinelPS.Database public DateTime BanStart; public DateTime BanEnd; public int BanId = 0; + public DateTime LastReset = DateTime.MinValue; // Game data public List CompletedScenarios = []; @@ -411,7 +412,6 @@ namespace EpinelPS.Database foreach (var item in FieldInfoNew) { if (item.Key.Contains("hard") && isNorm) continue; - if (item.Key.Contains("normal") && !isNorm) continue; if (item.Value.CompletedStages.Contains(id)) { return true; @@ -601,6 +601,38 @@ namespace EpinelPS.Database MessengerData.Add(msg); return msg; } + + private bool ShouldResetUser() + { + var nowLocal = DateTime.Now; + + // Compute the last reset threshold (most recent 2 PM before or at nowLocal) + DateTime todayResetTime = new( + nowLocal.Year, + nowLocal.Month, + nowLocal.Day, + JsonDb.Instance.ResetHourLocalTime, 0, 0 + ); + + if (nowLocal < todayResetTime) + { + todayResetTime = todayResetTime.AddDays(-1); + } + + // If user's last reset was before the last scheduled 2 PM, they need reset + return LastReset < todayResetTime; + } + + public void ResetDataIfNeeded() + { + if (!ShouldResetUser()) return; + + Logging.WriteLine("Resetting user...", LogType.Warning); + + LastReset = DateTime.Now; + ResetableData = new(); + JsonDb.Save(); + } } public class CoreInfo { @@ -615,6 +647,7 @@ namespace EpinelPS.Database public LogType LogLevel = LogType.Debug; public int MaxInterceptionCount = 3; + public int ResetHourLocalTime = 14; } internal class JsonDb { diff --git a/EpinelPS/EpinelPS.csproj b/EpinelPS/EpinelPS.csproj index e9195cd..f877775 100644 --- a/EpinelPS/EpinelPS.csproj +++ b/EpinelPS/EpinelPS.csproj @@ -10,7 +10,7 @@ true True $(NoWarn);SYSLIB0057 - 0.134.4.3 + 0.135.4.3 false true diff --git a/EpinelPS/LobbyServer/Auth/DoEnterServer.cs b/EpinelPS/LobbyServer/Auth/DoEnterServer.cs index 97ad131..42ea207 100644 --- a/EpinelPS/LobbyServer/Auth/DoEnterServer.cs +++ b/EpinelPS/LobbyServer/Auth/DoEnterServer.cs @@ -45,14 +45,18 @@ namespace EpinelPS.LobbyServer.Auth .Encode(); - ResEnterServer response = new(); - - response.GameClientToken = token; - response.FeatureDataInfo = new NetFeatureDataInfo() { }; // TODO - response.Identifier = new NetLegacyUserIdentifier() { Server = 1000, Usn = (long)user.ID }; - response.ShouldRestartAfter = Duration.FromTimeSpan(TimeSpan.FromSeconds(86400)); + ResEnterServer response = new() + { + GameClientToken = token, + FeatureDataInfo = new NetFeatureDataInfo() { }, // TODO + Identifier = new NetLegacyUserIdentifier() { Server = 1000, Usn = (long)user.ID }, + ShouldRestartAfter = Duration.FromTimeSpan(TimeSpan.FromSeconds(86400)), + + EncryptionToken = ByteString.CopyFromUtf8(encryptionToken) + }; + + user.ResetDataIfNeeded(); - response.EncryptionToken = ByteString.CopyFromUtf8(encryptionToken); await WriteDataAsync(response); } } diff --git a/EpinelPS/LobbyServer/Character/Counsel.cs b/EpinelPS/LobbyServer/Character/Counsel.cs deleted file mode 100644 index 56bb92e..0000000 --- a/EpinelPS/LobbyServer/Character/Counsel.cs +++ /dev/null @@ -1,21 +0,0 @@ -using EpinelPS.Utils; - -namespace EpinelPS.LobbyServer.Character -{ - [PacketPath("/character/attractive/counsel")] - public class Counsel : LobbyMsgHandler - { - protected override async Task HandleAsync() - { - var req = await ReadData(); - var user = GetUser(); - - ResCharacterCounsel response = new(); - response.Attractive = new(); - response.Exp = new(); - - // TODO - await WriteDataAsync(response); - } - } -} diff --git a/EpinelPS/LobbyServer/Character/Counsel/Check.cs b/EpinelPS/LobbyServer/Character/Counsel/Check.cs new file mode 100644 index 0000000..3f1976e --- /dev/null +++ b/EpinelPS/LobbyServer/Character/Counsel/Check.cs @@ -0,0 +1,18 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Character.Counsel; + +[PacketPath("/character/attractive/check")] +public class CheckCharacterCounsel : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + var req = await ReadData(); + var user = GetUser(); + + ResCounseledBefore response = new(); + + // TODO: Validate response from real server and pull info from user info + await WriteDataAsync(response); + } +} diff --git a/EpinelPS/LobbyServer/Character/Counsel/DoCounsel.cs b/EpinelPS/LobbyServer/Character/Counsel/DoCounsel.cs new file mode 100644 index 0000000..502f367 --- /dev/null +++ b/EpinelPS/LobbyServer/Character/Counsel/DoCounsel.cs @@ -0,0 +1,49 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Character.Counsel; + +[PacketPath("/character/attractive/counsel")] +public class DoCounsel : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + var req = await ReadData(); + var user = GetUser(); + + ResCharacterCounsel response = new(); + + foreach (var currency in user.Currency) + { + response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value }); + } + + var currentBondInfo = user.BondInfo.Where(x => x.NameCode == req.NameCode); + + NetUserAttractiveData data; + + if (currentBondInfo.Any()) + { + data = currentBondInfo.First(); + + // TODO update + response.Attractive = data; + } + else + { + data = new() + { + NameCode = req.NameCode, + // TODO + }; + + response.Attractive = data; + user.BondInfo.Add(data); + } + + JsonDb.Save(); + + // TODO: Validate response from real server and pull info from user info + await WriteDataAsync(response); + } +} diff --git a/EpinelPS/LobbyServer/Event/EnterEventField.cs b/EpinelPS/LobbyServer/Event/EnterEventField.cs index 0f0f214..561d36e 100644 --- a/EpinelPS/LobbyServer/Event/EnterEventField.cs +++ b/EpinelPS/LobbyServer/Event/EnterEventField.cs @@ -17,6 +17,8 @@ namespace EpinelPS.LobbyServer.Event Json = "{}" }; + + // Retrieve collected objects if (!user.FieldInfoNew.TryGetValue(req.MapId, out FieldInfoNew? field)) diff --git a/EpinelPS/LobbyServer/Event/Shop/ListProductList.cs b/EpinelPS/LobbyServer/Event/Shop/ListProductList.cs new file mode 100644 index 0000000..f35db56 --- /dev/null +++ b/EpinelPS/LobbyServer/Event/Shop/ListProductList.cs @@ -0,0 +1,24 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Event.Shop +{ + [PacketPath("/event/shopproductlist")] + public class ListProductList : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + var req = await ReadData(); + var user = GetUser(); + + var response = new ResShopProductList(); + response.Shops.Add(new NetShopProductData() + { + + }); + + // TODO implement properly + + await WriteDataAsync(response); + } + } +} diff --git a/EpinelPS/LobbyServer/Mission/ObtainDaily.cs b/EpinelPS/LobbyServer/Mission/ObtainDaily.cs index 72e62a2..c1aae81 100644 --- a/EpinelPS/LobbyServer/Mission/ObtainDaily.cs +++ b/EpinelPS/LobbyServer/Mission/ObtainDaily.cs @@ -22,8 +22,8 @@ namespace EpinelPS.LobbyServer.Mission { if (user.ResetableData.CompletedDailyMissions.Contains(item)) { - Console.WriteLine("already completed daily mission"); - continue; + Logging.WriteLine("already completed daily mission", LogType.Warning); + continue; } if (!GameData.Instance.TriggerTable.TryGetValue(item, out TriggerRecord? key)) throw new Exception("unknown TID"); diff --git a/EpinelPS/LobbyServer/Mission/Rewards/GetDailyRewards.cs b/EpinelPS/LobbyServer/Mission/Rewards/GetDailyRewards.cs index 85904d2..7100c24 100644 --- a/EpinelPS/LobbyServer/Mission/Rewards/GetDailyRewards.cs +++ b/EpinelPS/LobbyServer/Mission/Rewards/GetDailyRewards.cs @@ -10,6 +10,8 @@ namespace EpinelPS.LobbyServer.Mission.Rewards await ReadData(); var user = GetUser(); + user.ResetDataIfNeeded(); + var response = new ResGetDailyRewardedData(); response.Ids.Add(user.ResetableData.CompletedDailyMissions); diff --git a/EpinelPS/LobbyServer/Mission/Rewards/GetWeeklyRewards.cs b/EpinelPS/LobbyServer/Mission/Rewards/GetWeeklyRewards.cs index 604f39d..834b9a2 100644 --- a/EpinelPS/LobbyServer/Mission/Rewards/GetWeeklyRewards.cs +++ b/EpinelPS/LobbyServer/Mission/Rewards/GetWeeklyRewards.cs @@ -10,6 +10,8 @@ namespace EpinelPS.LobbyServer.Mission.Rewards await ReadData(); var user = GetUser(); + user.ResetDataIfNeeded(); + var response = new ResGetWeeklyRewardedData(); response.Ids.Add(user.WeeklyResetableData.CompletedWeeklyMissions); diff --git a/EpinelPS/LobbyServer/Outpost/GetOutpostData.cs b/EpinelPS/LobbyServer/Outpost/GetOutpostData.cs index 0a2c647..3d16359 100644 --- a/EpinelPS/LobbyServer/Outpost/GetOutpostData.cs +++ b/EpinelPS/LobbyServer/Outpost/GetOutpostData.cs @@ -10,6 +10,7 @@ namespace EpinelPS.LobbyServer.Outpost { var req = await ReadData(); var user = GetUser(); + user.ResetDataIfNeeded(); var battleTime = DateTime.UtcNow - user.BattleTime; var battleTimeMs = (long)(battleTime.TotalNanoseconds / 100); diff --git a/EpinelPS/LobbyServer/Stage/ClearStage.cs b/EpinelPS/LobbyServer/Stage/ClearStage.cs index 256add8..7fb3836 100644 --- a/EpinelPS/LobbyServer/Stage/ClearStage.cs +++ b/EpinelPS/LobbyServer/Stage/ClearStage.cs @@ -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.mod); + var stageMapId = GameData.Instance.GetMapIdFromChapter(clearedStage.chapter_id, clearedStage.chapter_mod); if (user.FieldInfoNew.Count == 0) { @@ -91,7 +91,7 @@ namespace EpinelPS.LobbyServer.Stage if (clearedStage.stage_category == StageCategory.Normal || clearedStage.stage_category == StageCategory.Boss || clearedStage.stage_category == StageCategory.Hard) { - if (clearedStage.mod == ChapterMod.Hard) + if (clearedStage.chapter_mod == ChapterMod.Hard) { if (StageId > user.LastHardStageCleared) user.LastHardStageCleared = StageId; @@ -135,7 +135,7 @@ namespace EpinelPS.LobbyServer.Stage // Mark chapter as completed if boss stage was completed if (clearedStage.stage_category == StageCategory.Boss && clearedStage.stage_type == StageType.Main) { - if (clearedStage.mod == ChapterMod.Hard) + if (clearedStage.chapter_mod == ChapterMod.Hard) user.AddTrigger(TriggerType.HardChapterClear, 1, clearedStage.chapter_id); else user.AddTrigger(TriggerType.ChapterClear, 1, clearedStage.chapter_id); @@ -152,10 +152,11 @@ namespace EpinelPS.LobbyServer.Stage private static void DoQuestSpecificUserOperations(User user, int clearedStageId) { var quest = GameData.Instance.GetMainQuestForStageClearCondition(clearedStageId); + + user.AddTrigger(TriggerType.CampaignClear, 1, clearedStageId); if (quest != null) { user.SetQuest(quest.id, false); - user.AddTrigger(TriggerType.CampaignClear, 1, clearedStageId); user.AddTrigger(TriggerType.MainQuestClear, 1, quest.id); } @@ -181,7 +182,7 @@ namespace EpinelPS.LobbyServer.Stage user.BondInfo.Add(new() { NameCode = 3001, Lv = 1 }); user.BondInfo.Add(new() { NameCode = 3005, Lv = 1 }); - + user.AddTrigger(TriggerType.ObtainCharacter, 1, 3001); user.AddTrigger(TriggerType.ObtainCharacter, 1, 1018); user.AddTrigger(TriggerType.ObtainCharacter, 1, 1015); diff --git a/EpinelPS/LobbyServer/Stage/EnterStage.cs b/EpinelPS/LobbyServer/Stage/EnterStage.cs index f7ed2c6..edb37bb 100644 --- a/EpinelPS/LobbyServer/Stage/EnterStage.cs +++ b/EpinelPS/LobbyServer/Stage/EnterStage.cs @@ -15,7 +15,7 @@ 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.mod); + var map = GameData.Instance.GetMapIdFromChapter(clearedStage.chapter_id, clearedStage.chapter_mod); if (clearedStage.stage_category == StageCategory.Boss) { @@ -27,7 +27,7 @@ namespace EpinelPS.LobbyServer.Stage info.BossEntered = true; } - user.AddTrigger(TriggerType.CampaignStart, 1); + user.AddTrigger(TriggerType.CampaignStart, 1, req.StageId); JsonDb.Save();