diff --git a/EpinelPS/Data/GameData.cs b/EpinelPS/Data/GameData.cs index bf93ae4..72399de 100644 --- a/EpinelPS/Data/GameData.cs +++ b/EpinelPS/Data/GameData.cs @@ -291,6 +291,24 @@ namespace EpinelPS.Data [LoadRecord("DailyEventTable.json", "Id")] public readonly Dictionary DailyEventTable = []; + // SimulationRoom Data Tables + [LoadRecord("SimulationRoomChapterTable.json", "Id")] + public readonly Dictionary SimulationRoomChapterTable = []; + [LoadRecord("SimulationRoomStageLocationTable.json", "Id")] + public readonly Dictionary SimulationRoomStageLocationTable = []; + [LoadRecord("SimulationRoomSelectionEventTable.json", "Id")] + public readonly Dictionary SimulationRoomSelectionEventTable = []; + [LoadRecord("SimulationRoomSelectionGroupTable.json", "Id")] + public readonly Dictionary SimulationRoomSelectionGroupTable = []; + [LoadRecord("SimulationRoomBattleEventTable.json", "Id")] + public readonly Dictionary SimulationRoomBattleEventTable = []; + [LoadRecord("SimulationRoomLevelScalingTable.json", "Id")] + public readonly Dictionary SimulationRoomLevelScalingTable = []; + [LoadRecord("SimulationRoomBuffPreviewTable.json", "Id")] + public readonly Dictionary SimulationRoomBuffPreviewTable = []; + [LoadRecord("SimulationRoomBuffTable.json", "Id")] + public readonly Dictionary SimulationRoomBuffTable = []; + static async Task BuildAsync() { await Load(); diff --git a/EpinelPS/Data/JsonStaticDataReplenish.cs b/EpinelPS/Data/JsonStaticDataReplenish.cs new file mode 100644 index 0000000..f6caf78 --- /dev/null +++ b/EpinelPS/Data/JsonStaticDataReplenish.cs @@ -0,0 +1,155 @@ +using MemoryPack; + +namespace EpinelPS.Data; + +[MemoryPackable] +public partial class SimulationRoomStageLocationRecord +{ + public int Id; + public int ScheduleGroupId; + public int ChapterId; + public int StageGroupId; + public int StageEssentialValue; + public int EventSelectionValue; + public SimulationRoomLocation Location; + public int Weight; +} + +[MemoryPackable] +public partial class SimulationRoomBattleEventRecord +{ + public int Id { get; set; } + public SimulationRoomEvent EventType { get; set; } + public SimulationRoomScalingType ScalingType { get; set; } + public SimulationRoomConditionType DifficultyConditionType { get; set; } + public int DifficultyConditionValue { get; set; } + public SimulationRoomConditionType ChapterConditionType { get; set; } + public int ChapterConditionValue { get; set; } + public int Weight { get; set; } + public bool SpotAutocontrol { get; set; } + public int MonsterStageLv { get; set; } + public int DynamicObjectStageLv { get; set; } + public int StandardBattlePower { get; set; } + public int StageStatIncreaseGroupId { get; set; } + public bool IsUseQuickBattle { get; set; } + public int SpotId { get; set; } + public bool UseOcMode { get; set; } + public int UseSeasonId { get; set; } + public int BattleEventGroup { get; set; } +} + +[MemoryPackable] +public partial class SimulationRoomBuffPreviewRecord +{ + public int Id { get; set; } + public SimulationRoomEvent EventType { get; set; } + public PreviewType PreviewType { get; set; } + public string? PreviewTarget { get; set; } + public int Weight { get; set; } + public string? DescriptionLocalkey { get; set; } +} + +[MemoryPackable] +public partial class SimulationRoomBuffRecord +{ + public int Id { get; set; } + public int GroupId { get; set; } + public SimulationRoomBuffMainTarget MainTarget { get; set; } + public List? SubTarget { get; set; } + public SimulationRoomBuffGrade Grade { get; set; } + public int Weight { get; set; } + public SimulationRoomBubbleType BubbleType { get; set; } + public string? NameLocalkey { get; set; } + public string? DescriptionLocalkey { get; set; } + public List? ParameterLocalkey { get; set; } + public string? ResourceId { get; set; } + public SimulationRoomBuffFunctionType FunctionType { get; set; } + public List? BuffValue { get; set; } +} + +[MemoryPackable] +public partial class SimulationRoomSelectionEventRecord +{ + public int Id { get; set; } + public SimulationRoomEvent EventType { get; set; } + public SimulationRoomConditionType ChapterConditionType { get; set; } + public int ChapterConditionValue { get; set; } + public int SelectionGroupId { get; set; } + public int SelectionValue { get; set; } + public string? NameLocalkey { get; set; } + public string? DescriptionLocalkey { get; set; } + public int Weight { get; set; } +} + +[MemoryPackable] +public partial class SimulationRoomBuffValueData +{ + public int FunctionValueLevel { get; set; } + public int BattlePowerLevel { get; set; } +} + +public enum SimulationRoomScalingType +{ + DataRef = 0, + None = 1, +} + +public enum SimulationRoomConditionType +{ + Range = 0, // 0x0 + Select = 1, // 0x0 +} + +public enum PreviewType +{ + MainTarget = 0, // 0x0 + Bubble = 1, // 0x0 + Grade = 2 +} + +public enum SimulationRoomBuffMainTarget +{ + Shoot = 0, // 0x0 + Attack = 1, // 0x0 + Survive = 2 +} + +public enum SimulationRoomBuffSubTarget +{ + AR = 0, + RL = 1, + SR = 2, + MG = 3, + SG = 4, + SMG = 5, + ELYSION = 6, + MISSILIS = 7, + TETRA = 8, + PILGRIM = 9, + Attacker = 10, + Defender = 11, + Supporter = 12, + ALL = 13 +} + +public enum SimulationRoomBuffGrade +{ + R = 0, + SR = 1, + SSR = 2, + EPIC = 3 +} + +public enum SimulationRoomBubbleType +{ + TypeA = 0, + TypeB = 1, + TypeC = 2, + TypeD = 3 +} + +public enum SimulationRoomBuffFunctionType +{ + Function = 0, + HealAfterBattle = 1 +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Simroom/ClearBattle.cs b/EpinelPS/LobbyServer/Simroom/ClearBattle.cs new file mode 100644 index 0000000..63c35f9 --- /dev/null +++ b/EpinelPS/LobbyServer/Simroom/ClearBattle.cs @@ -0,0 +1,61 @@ +using EpinelPS.Database; +using EpinelPS.Utils; +using log4net; + +namespace EpinelPS.LobbyServer.Simroom +{ + [PacketPath("/simroom/clearbattle")] + public class ClearBattle : LobbyMsgHandler + { + private static readonly ILog log = LogManager.GetLogger(typeof(ClearBattle)); + + protected override async Task HandleAsync() + { + // {"location":{"chapter":3,"stage":3,"order":2},"event":111011143,"teamNumber":1,"antiCheatAdditionalInfo":{"clientLocalTime":"638993283799771900"}} + ReqClearSimRoomBattle req = await ReadData(); + User user = GetUser(); + + ResClearSimRoomBattle response = new() + { + Result = SimRoomResult.Success + }; + + // OverclockOptionChangedHps + + // Teams + try + { + var team = SimRoomHelper.GetTeamData(user, req.TeamNumber, [.. req.RemainingHps]); + if (team is not null) response.Teams.Add(team); + } + catch (Exception e) + { + log.Error($"ClearBattle Response Team Exception :{e.Message}"); + } + + SimRoomHelper.UpdateUserRemainingHps(user, [.. req.RemainingHps], req.TeamNumber); + + if (req.BattleResult == 1) + { + // BuffOptions + try + { + var buffOptions = SimRoomHelper.GetBuffOptions(user, req.Location); + if (buffOptions is not null && buffOptions.Count > 0) + { + response.BuffOptions.AddRange(buffOptions); + } + } + catch (Exception e) + { + log.Error($"ClearBattle Response BuffOptions Exception :{e.Message}"); + } + } + + JsonDb.Save(); + await WriteDataAsync(response); + } + + + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Simroom/ClearBattleFast.cs b/EpinelPS/LobbyServer/Simroom/ClearBattleFast.cs new file mode 100644 index 0000000..2070e5b --- /dev/null +++ b/EpinelPS/LobbyServer/Simroom/ClearBattleFast.cs @@ -0,0 +1,56 @@ +using EpinelPS.Database; +using EpinelPS.Utils; +using log4net; + +namespace EpinelPS.LobbyServer.Simroom +{ + [PacketPath("/simroom/fastclearbattle")] + public class ClearBattleFast : LobbyMsgHandler + { + private static readonly ILog log = LogManager.GetLogger(typeof(ClearBattle)); + + protected override async Task HandleAsync() + { + // {"location":{"chapter":3,"stage":3,"order":2},"event":111011143,"teamNumber":1,"antiCheatAdditionalInfo":{"clientLocalTime":"638993283799771900"}} + ReqFastClearSimRoomBattle req = await ReadData(); + User user = GetUser(); + + ResFastClearSimRoomBattle response = new() + { + Result = SimRoomResult.Success + }; + + // OverclockOptionChangedHps + + // Teams + try + { + var team = SimRoomHelper.GetTeamData(user, req.TeamNumber, null); + if (team is not null) response.Teams.Add(team); + } + catch (Exception e) + { + log.Error($"ClearBattleFast Response Team Exception :{e.Message}"); + } + + SimRoomHelper.UpdateUserRemainingHps(user, teamNumber: req.TeamNumber); + + // BuffOptions + try + { + var buffOptions = SimRoomHelper.GetBuffOptions(user, req.Location); + if (buffOptions is not null && buffOptions.Count > 0) + { + response.BuffOptions.AddRange(buffOptions); + } + } + catch (Exception e) + { + log.Error($"ClearBattleFast Response BuffOptions Exception: {e.Message}"); + } + + JsonDb.Save(); + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Simroom/EnterBattle.cs b/EpinelPS/LobbyServer/Simroom/EnterBattle.cs new file mode 100644 index 0000000..3b5a368 --- /dev/null +++ b/EpinelPS/LobbyServer/Simroom/EnterBattle.cs @@ -0,0 +1,20 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Simroom +{ + [PacketPath("/simroom/enterbattle")] + public class EnterBattle : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + await ReadData(); + + ResEnterSimRoomBattle response = new() + { + Result = SimRoomResult.Success + }; + + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Simroom/GetSimRoomData.cs b/EpinelPS/LobbyServer/Simroom/GetSimRoomData.cs index ee78c60..f8d0163 100644 --- a/EpinelPS/LobbyServer/Simroom/GetSimRoomData.cs +++ b/EpinelPS/LobbyServer/Simroom/GetSimRoomData.cs @@ -1,5 +1,6 @@ using Google.Protobuf.WellKnownTypes; using EpinelPS.Utils; +using EpinelPS.Data; namespace EpinelPS.LobbyServer.Simroom { @@ -8,63 +9,192 @@ namespace EpinelPS.LobbyServer.Simroom { protected override async Task HandleAsync() { - ReqGetSimRoom req = await ReadData(); + await ReadData(); User user = GetUser(); + // ResGetSimRoom Fields + // SimRoomStatus Status + // int CurrentDifficulty + // long NextRenewAt + // RepeatedField ClearInfos + // RepeatedField Events + // RepeatedField RemainingHps + // RepeatedField Buffs + // RepeatedField LegacyBuffs + // RepeatedField OverclockOptionList + // NetSimRoomOverclockData OverclockData + // Timestamp NextLegacyBuffResetDate + // NetSimRoomSimpleModeBuffSelectionInfo NextSimpleModeBuffSelectionInfo + // NetSimRoomChapterInfo LastPlayedChapter + // bool IsSimpleModeSkipEnabled + + + var CurrentDifficulty = user.ResetableData.SimRoomData.CurrentDifficulty; + var currentChapter = user.ResetableData.SimRoomData.CurrentChapter; + ResGetSimRoom response = new() { - OverclockData = new NetSimRoomOverclockData - { - CurrentSeasonData = new NetSimRoomOverclockSeasonData - { - SeasonStartDate = Timestamp.FromDateTimeOffset(DateTime.UtcNow), - SeasonEndDate = Timestamp.FromDateTimeOffset(DateTime.UtcNow.AddDays(7)), - IsSeasonOpen = true, - Season = 1, - SubSeason = 1, - SeasonWeekCount = 1 - }, - - CurrentSeasonHighScore = new NetSimRoomOverclockHighScoreData - { - CreatedAt = Timestamp.FromDateTimeOffset(DateTime.UtcNow), - OptionLevel = 1, - Season = 1, - SubSeason = 1, - OptionList = {} - }, - - CurrentSubSeasonHighScore = new NetSimRoomOverclockHighScoreData - { - CreatedAt = Timestamp.FromDateTimeOffset(DateTime.UtcNow), - OptionLevel = 1, - Season = 1, - SubSeason = 1, - OptionList = {} - }, - - LatestOption = new NetSimRoomOverclockOptionSettingData - { - Season = 1, - OptionList = {} - } - }, - Status = SimRoomStatus.Ready, - CurrentDifficulty = 1, - NextRenewAt = DateTime.UtcNow.AddDays(7).Ticks, - NextLegacyBuffResetDate = Timestamp.FromDateTimeOffset(DateTime.UtcNow.AddDays(7)) + CurrentDifficulty = CurrentDifficulty, + // NextRenewAt: Resets at 2 AM daily + NextRenewAt = DateTimeHelper.GetNextDayAtTime("China Standard Time", 2).Ticks, + // NextLegacyBuffResetDate: Resets at 2 AM every Tuesday + NextLegacyBuffResetDate = DateTimeHelper.GetNextWeekdayAtTime("China Standard Time", DayOfWeek.Tuesday, 2).ToTimestamp(), + IsSimpleModeSkipEnabled = user.ResetableData.SimRoomData.IsSimpleModeSkipEnabled, + }; + // LegacyBuffs + response.LegacyBuffs.AddRange(user.ResetableData.SimRoomData.LegacyBuffs); + + // OverclockData + response.OverclockData = GetOverclockData(user: user); + + // ClearInfos + response.ClearInfos.AddRange(GetClearInfos(user)); + + // OverclockOptionList + // response.OverclockOptionList.Add([]); + + // check if user is in sim room if (user.ResetableData.SimRoomData.Entered) { response.Status = SimRoomStatus.Progress; - - response.CurrentDifficulty = user.ResetableData.SimRoomData.CurrentDifficulty; + response.Events.AddRange(SimRoomHelper.GetSimRoomEvents(user)); + + // TODO: Get RemainingHps + response.RemainingHps.AddRange(GetCharacterHp(user)); + + response.LastPlayedChapter = new NetSimRoomChapterInfo() + { + Chapter = currentChapter, + Difficulty = CurrentDifficulty, + }; + + // Buffs = Buffs + LegacyBuffs + response.Buffs.AddRange(user.ResetableData.SimRoomData.Buffs); + response.Buffs.AddRange(user.ResetableData.SimRoomData.LegacyBuffs); + + // response.NextSimpleModeBuffSelectionInfo = new() + // { + // RemainingBuffSelectCount = 8 - response.Buffs.Count, + // BuffOptions = { user.ResetableData.SimRoomData.Buffs } + // }; + } await WriteDataAsync(response); } + + /// + /// Get clear infos + /// + /// + /// List of cleared chapters + private static List GetClearInfos(User user) + { + List clearInfos = []; + try + { + var receivedRewards = user.ResetableData.SimRoomData.ReceivedRewardChapters; + if (receivedRewards.Count > 0) + { + // Get the last received reward chapter + var lastReceivedReward = receivedRewards.OrderBy(x => x.Difficulty).ThenBy(x => x.Chapter).LastOrDefault(); + if (lastReceivedReward is not null) + { + var CurrentDifficulty = lastReceivedReward.Difficulty; + var CurrentChapter = lastReceivedReward.Chapter; + + // Get all chapters where difficulty is less than or equal to current difficulty + var ChapterRecords = GameData.Instance.SimulationRoomChapterTable.Values.Where(x => x.DifficultyId <= CurrentDifficulty).ToList(); + + foreach (var chapterRecord in ChapterRecords) + { + // check if chapter is less than or equal to current chapter + if (chapterRecord.DifficultyId == CurrentDifficulty && chapterRecord.Chapter <= CurrentChapter) + { + clearInfos.Add(new NetSimRoomChapterInfo() + { + Chapter = chapterRecord.Chapter, + Difficulty = chapterRecord.DifficultyId, + }); + } + // check if difficulty is less than current difficulty + else if (chapterRecord.DifficultyId < CurrentDifficulty) + { + clearInfos.Add(new NetSimRoomChapterInfo() + { + Chapter = chapterRecord.Chapter, + Difficulty = chapterRecord.DifficultyId, + }); + } + } + } + } + } + catch (Exception e) + { + Logging.WriteLine($"Get ClearInfos Exception: {e.Message}", LogType.Error); + } + return clearInfos; + } + + public static List GetCharacterHp(User user) + { + List hps = []; + if (user.UserTeams.TryGetValue((int)TeamType.SimulationRoom, out var userTeamData)) + { + if (userTeamData.Teams.Count > 0 && userTeamData.Teams[0].Slots.Count > 0) + { + foreach (var slot in userTeamData.Teams[0].Slots) + { + hps.Add(new() { Csn = slot.Value, Hp = 100000 }); + } + } + } + return hps; + } + + private static NetSimRoomOverclockData GetOverclockData(User user) + { + return new NetSimRoomOverclockData + { + CurrentSeasonData = new NetSimRoomOverclockSeasonData + { + SeasonStartDate = Timestamp.FromDateTimeOffset(DateTime.UtcNow.Date.AddDays(-1)), + SeasonEndDate = Timestamp.FromDateTimeOffset(DateTime.UtcNow.Date.AddDays(7)), + IsSeasonOpen = true, + Season = 1, + SubSeason = 1, + SeasonWeekCount = 1 + }, + + CurrentSeasonHighScore = new NetSimRoomOverclockHighScoreData + { + CreatedAt = Timestamp.FromDateTimeOffset(DateTime.UtcNow.Date.AddDays(-1)), + OptionLevel = 1, + Season = 1, + SubSeason = 1, + OptionList = { 1 } + }, + + CurrentSubSeasonHighScore = new NetSimRoomOverclockHighScoreData + { + CreatedAt = Timestamp.FromDateTimeOffset(DateTime.UtcNow.Date.AddDays(-1)), + OptionLevel = 1, + Season = 1, + SubSeason = 1, + OptionList = { 1 } + }, + + LatestOption = new NetSimRoomOverclockOptionSettingData + { + Season = 1, + OptionList = { 1 } + } + }; + } } } diff --git a/EpinelPS/LobbyServer/Simroom/ProceedBuffFunction.cs b/EpinelPS/LobbyServer/Simroom/ProceedBuffFunction.cs new file mode 100644 index 0000000..c70c5a0 --- /dev/null +++ b/EpinelPS/LobbyServer/Simroom/ProceedBuffFunction.cs @@ -0,0 +1,42 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Simroom +{ + [PacketPath("/simroom/proceedbufffunction")] + public class ProceedBuffFunction : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // { "location": { "chapter": 3, "stage": 6, "order": 1 }, "event": 22116, "selectionNumber": 2, "selectionGroupElementId": 221162, "buffToDelete": 2030608 } + ReqProceedSimRoomBuffFunction req = await ReadData(); + // ReqProceedSimRoomBuffFunction Field NetSimRoomEventLocationInfo location, int event, int selectionNumber, int selectionGroupElementId, int buffToDelete + User user = GetUser(); + + // ReqProceedSimRoomBuffFunction Field SimRoomResult Result, RepeatedField AcquiredBuff, RepeatedField DeletedBuff + ResProceedSimRoomBuffFunction response = new() + { + Result = SimRoomResult.Success + }; + if (req.BuffToDelete > 0) + { + response.DeletedBuff.Add(req.BuffToDelete); + } + + // Update + var location = req.Location; + // Check + var events = user.ResetableData.SimRoomData.Events; + var simRoomEventIndex = events.FindIndex(x => x.Location.Chapter == location.Chapter && x.Location.Stage == location.Stage && x.Location.Order == location.Order); + if (simRoomEventIndex < 0) + { + Logging.Warn("Not Fond UserSimRoomEvent"); + await WriteDataAsync(response); + } + SimRoomHelper.UpdateUserSimRoomEvent(user, index: simRoomEventIndex, events: events, selectionNumber: req.SelectionNumber, isDone: true); + + JsonDb.Save(); + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Simroom/ProceedNikkeFunction.cs b/EpinelPS/LobbyServer/Simroom/ProceedNikkeFunction.cs new file mode 100644 index 0000000..a4ed13a --- /dev/null +++ b/EpinelPS/LobbyServer/Simroom/ProceedNikkeFunction.cs @@ -0,0 +1,113 @@ +using EpinelPS.Data; +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Simroom +{ + [PacketPath("/simroom/proceednikkefunction")] + public class ProceedNikkeFunction : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // { "location": { "chapter": 3, "stage": 6, "order": 1 }, "event": 10040, "selectionNumber": 1, "selectionGroupElementId": 100401 } + ReqProceedSimRoomNikkeFunction req = await ReadData(); + User user = GetUser(); + + ResProceedSimRoomNikkeFunction response = new() + { + Result = SimRoomResult.Success + }; + + var location = req.Location; + // Check + var events = user.ResetableData.SimRoomData.Events; + var simRoomEventIndex = events.FindIndex(x => x.Location.Chapter == location.Chapter && x.Location.Stage == location.Stage && x.Location.Order == location.Order); + if (simRoomEventIndex < 0) + { + Logging.Warn("Not Fond UserSimRoomEvent"); + await WriteDataAsync(response); + } + + // changedHps + try + { + if (GameData.Instance.SimulationRoomSelectionGroupTable.TryGetValue(req.SelectionGroupElementId, out var selectionGroup)) + { + if (selectionGroup.EventFunctionType == SimulationRoomEventFunctionType.Heal) + { + if (selectionGroup.EventFunctionTargetType == SimulationRoomEventfunctionTargetType.All) + { + var changedRemainingHps = UpdateUserRemainingHps(user, selectionGroup.EventFunctionValue, type: SimulationRoomEventFunctionType.Heal); + response.ChangedHps.AddRange(changedRemainingHps.Select(x => new NetSimRoomCharacterHp { Csn = x.Csn, Hp = x.Hp })); + + SimRoomHelper.UpdateUserSimRoomEvent(user, index: simRoomEventIndex, events, selectionNumber: req.SelectionNumber, isDone: true); + } + else + { + Logging.Warn($"Not implement EventFunctionTargetType: {selectionGroup.EventFunctionTargetType}"); + } + } + else if (selectionGroup.EventFunctionType == SimulationRoomEventFunctionType.Resurrection) + { + if (selectionGroup.EventFunctionTargetType == SimulationRoomEventfunctionTargetType.All) + { + var changedRemainingHps = UpdateUserRemainingHps(user, selectionGroup.EventFunctionValue, type: SimulationRoomEventFunctionType.Resurrection); + response.ChangedHps.AddRange(changedRemainingHps.Select(x => new NetSimRoomCharacterHp { Csn = x.Csn, Hp = x.Hp })); + + SimRoomHelper.UpdateUserSimRoomEvent(user, index: simRoomEventIndex, events, selectionNumber: req.SelectionNumber, isDone: true); + } + else + { + Logging.Warn($"Not implement EventFunctionTargetType: {selectionGroup.EventFunctionTargetType}"); + } + } + else + { + Logging.Warn($"Not implement EventFunctionType: {selectionGroup.EventFunctionType}"); + } + + } + else + { + Logging.Warn("Not Fond SimulationRoomSelectionGroup"); + } + } + catch (Exception e) + { + Logging.Warn($"ProceedNikkeFunction ChangedHps Exception {e.Message}"); + } + + // Teams + var team = SimRoomHelper.GetTeamData(user, 1, null); + if (team is not null) response.Teams.Add(team); + + JsonDb.Save(); + await WriteDataAsync(response); + } + + public static List UpdateUserRemainingHps(User user, int HpValue, SimulationRoomEventFunctionType type = SimulationRoomEventFunctionType.Heal) + { + var remainingHps = user.ResetableData.SimRoomData.RemainingHps; + if (remainingHps is not null && remainingHps.Count > 0) + { + for (int i = 0; i < remainingHps.Count; i++) + { + if (type is SimulationRoomEventFunctionType.Heal && remainingHps[i].Hp >= 0) + { + // Heal + remainingHps[i] = new SimRoomCharacterHp { Csn = remainingHps[i].Csn, Hp = HpValue }; + } + else if (type is SimulationRoomEventFunctionType.Resurrection && remainingHps[i].Hp < 0) + { + // Resurrection + remainingHps[i] = new SimRoomCharacterHp { Csn = remainingHps[i].Csn, Hp = HpValue }; + } + } + user.ResetableData.SimRoomData.RemainingHps = remainingHps; + return remainingHps; + } + return remainingHps; + } + + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Simroom/Quit.cs b/EpinelPS/LobbyServer/Simroom/Quit.cs index 1a96fb3..a1403ad 100644 --- a/EpinelPS/LobbyServer/Simroom/Quit.cs +++ b/EpinelPS/LobbyServer/Simroom/Quit.cs @@ -16,7 +16,35 @@ namespace EpinelPS.LobbyServer.Simroom Result = SimRoomResult.Success, }; + try + { + foreach (var item in req.BuffsToAdd) + { + if (!user.ResetableData.SimRoomData.LegacyBuffs.Contains(item)) + user.ResetableData.SimRoomData.LegacyBuffs.Add(item); + } + } + catch (Exception e) + { + Logging.Warn($"QuitSimRoom BuffsToAdd Exception {e.Message}"); + } + + try + { + foreach (var item in req.BuffsToDelete) + { + user.ResetableData.SimRoomData.LegacyBuffs.Remove(item); + } + } + catch (Exception e) + { + Logging.Warn($"QuitSimRoom BuffsToDelete Exception {e.Message}"); + } + user.ResetableData.SimRoomData.Entered = false; + user.ResetableData.SimRoomData.Events = []; + user.ResetableData.SimRoomData.RemainingHps = []; + user.ResetableData.SimRoomData.Buffs = []; JsonDb.Save(); diff --git a/EpinelPS/LobbyServer/Simroom/SelectBuff.cs b/EpinelPS/LobbyServer/Simroom/SelectBuff.cs new file mode 100644 index 0000000..6e2684b --- /dev/null +++ b/EpinelPS/LobbyServer/Simroom/SelectBuff.cs @@ -0,0 +1,76 @@ +using EpinelPS.Data; +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Simroom +{ + [PacketPath("/simroom/selectbuff")] + public class SelectBuff : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // {"location":{"chapter":3,"stage":4,"order":3},"event":111011152,"buffToAdd":1030504} + ReqSelectSimRoomBuff req = await ReadData(); + User user = GetUser(); + + ResSelectSimRoomBuff response = new() + { + Result = SimRoomResult.Success + }; + + // Update User SimRoomData Buffs + try + { + var buffs = user.ResetableData.SimRoomData.Buffs; + if (req.BuffToDelete > 0) + { + if (buffs.Contains(req.BuffToDelete)) buffs.Remove(req.BuffToDelete); + } + + if (req.BuffToAdd > 0) + { + if (!buffs.Contains(req.BuffToDelete)) buffs.Add(req.BuffToAdd); + } + user.ResetableData.SimRoomData.Buffs = buffs; + } + catch (Exception e) + { + Logging.Warn($"Update User SimRoomData Buffs Exception: {e.Message}"); + } + + // NetRewardData Reward + // NetRewardData RewardByRewardUpEvent + // NetRewardData RewardByOverclock + + var location = req.Location; + if (location is not null) + { + var events = user.ResetableData.SimRoomData.Events; + var simRoomEventIndex = events.FindIndex(x => x.Location.Chapter == location.Chapter && x.Location.Stage == location.Stage && x.Location.Order == location.Order); + if (simRoomEventIndex > -1) + { + // Update User SimRoomData Events + SimRoomHelper.UpdateUserSimRoomEvent(user, simRoomEventIndex, events, battleProgress: (int)SimRoomBattleEventProgress.RewardReceived); + + // Reward + var sorted = events.OrderBy(x => x.Location.Stage).ThenBy(x => x.Location.Order).ToList(); // Sort by Stage, Order + var last = sorted[^1]; // Get last event + if (last.Location.Chapter == location.Chapter && last.Location.Stage == location.Stage && last.Location.Order == location.Order) + { + var difficulty = user.ResetableData.SimRoomData.CurrentDifficulty; + var reward = SimRoomHelper.SimRoomReceivedReward(user, difficulty, location.Chapter); + if (reward is not null) response.Reward = reward; + } + } + + JsonDb.Save(); + } + + await WriteDataAsync(response); + } + + + + + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Simroom/SelectDifficulty.cs b/EpinelPS/LobbyServer/Simroom/SelectDifficulty.cs index cd00340..b8d1494 100644 --- a/EpinelPS/LobbyServer/Simroom/SelectDifficulty.cs +++ b/EpinelPS/LobbyServer/Simroom/SelectDifficulty.cs @@ -16,14 +16,18 @@ namespace EpinelPS.LobbyServer.Simroom Result = SimRoomResult.Success, }; + user.ResetableData.SimRoomData.Entered = true; user.ResetableData.SimRoomData.CurrentDifficulty = req.Difficulty; user.ResetableData.SimRoomData.CurrentChapter = req.StartingChapter; - - // TODO: generate buffs - JsonDb.Save(); + List events = SimRoomHelper.GetSimRoomEvents(user); + user.ResetableData.SimRoomData.Events = [.. events.Select(SimRoomHelper.NetToM)]; + JsonDb.Save(); + + response.Events.AddRange(events); + await WriteDataAsync(response); } } diff --git a/EpinelPS/LobbyServer/Simroom/SelectEvent.cs b/EpinelPS/LobbyServer/Simroom/SelectEvent.cs new file mode 100644 index 0000000..f49a505 --- /dev/null +++ b/EpinelPS/LobbyServer/Simroom/SelectEvent.cs @@ -0,0 +1,23 @@ +using EpinelPS.Utils; +using EpinelPS.Database; + +namespace EpinelPS.LobbyServer.Simroom +{ + [PacketPath("/simroom/selectevent")] + public class SelectEvent : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // { "location": { "chapter": 3, "stage": 1, "order": 3 }, "event": 111021115 } + ReqSelectSimRoomEvent req = await ReadData(); + // User user = GetUser(); + + ResSelectSimRoomEvent response = new() + { + Result = SimRoomResult.Success, + }; + + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Simroom/SelectSelectionEvent.cs b/EpinelPS/LobbyServer/Simroom/SelectSelectionEvent.cs new file mode 100644 index 0000000..84af7a4 --- /dev/null +++ b/EpinelPS/LobbyServer/Simroom/SelectSelectionEvent.cs @@ -0,0 +1,34 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Simroom +{ + [PacketPath("/simroom/selectselectionevent")] + public class SelectSelectionEvent : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + ReqSelectSimRoomSelectionEvent req = await ReadData(); + User user = GetUser(); + + ResSelectSimRoomSelectionEvent response = new() + { + Result = SimRoomResult.Success + }; + + var location = req.Location; + // Check + var events = user.ResetableData.SimRoomData.Events; + var simRoomEventIndex = events.FindIndex(x => x.Location.Chapter == location.Chapter && x.Location.Stage == location.Stage && x.Location.Order == location.Order); + if (simRoomEventIndex < 0) + { + Logging.Warn("Not Fond UserSimRoomEvent"); + await WriteDataAsync(response); + } + SimRoomHelper.UpdateUserSimRoomEvent(user, index: simRoomEventIndex, events: events, selectionNumber: req.SelectionNumber); + + JsonDb.Save(); + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Simroom/SimRoomHelper.cs b/EpinelPS/LobbyServer/Simroom/SimRoomHelper.cs new file mode 100644 index 0000000..6424af0 --- /dev/null +++ b/EpinelPS/LobbyServer/Simroom/SimRoomHelper.cs @@ -0,0 +1,633 @@ +using EpinelPS.Data; +using EpinelPS.Database; +using EpinelPS.Utils; +using log4net; +using Newtonsoft.Json; + +namespace EpinelPS.LobbyServer.Simroom +{ + public static class SimRoomHelper + { + private static readonly ILog log = LogManager.GetLogger(typeof(SimRoomHelper)); + private static readonly Random _rand = new(); + + /// + /// Get SimRoomEvent By User or DifficultyId and ChapterId + /// + /// User + /// DifficultyId + /// ChapterId + /// The list of NetSimRoomEvent + public static List GetSimRoomEvents(User user, int difficultyId = 0, int chapterId = 0) + { + List netSimRooms = []; + int currentDifficulty = user.ResetableData.SimRoomData.CurrentDifficulty; + int currentChapter = user.ResetableData.SimRoomData.CurrentChapter; + + if (difficultyId > 0) currentDifficulty = difficultyId; + if (chapterId > 0) currentChapter = chapterId; + + var events = user.ResetableData.SimRoomData.Events; + if (events.Count > 1) + { + return [.. events.Select(MToNet)]; + } + + var simRoomChapter = GameData.Instance.SimulationRoomChapterTable.Values.FirstOrDefault(x => x.Chapter == currentChapter && x.DifficultyId == currentDifficulty); + log.Debug($"Fond SimulationRoomChapter Chapter: {currentChapter}, DifficultyId: {currentDifficulty} Data: {JsonConvert.SerializeObject(simRoomChapter)}"); + + var buffPreviewRecords = GameData.Instance.SimulationRoomBuffPreviewTable.Values.ToList(); + var simRoomBattleEventRecords = GameData.Instance.SimulationRoomBattleEventTable.Values.ToList(); + var simRoomSelectionEventRecords = GameData.Instance.SimulationRoomSelectionEventTable.Values.ToList(); + var simRoomSelectionEventGroupRecords = GameData.Instance.SimulationRoomSelectionGroupTable.Values.ToList(); + + if (simRoomChapter is null) return netSimRooms; + + for (int i = 1; i <= simRoomChapter.StageValue; i++) + { + if (i == simRoomChapter.StageValue) // BossBattle + { + var battleBuffPreviews = GameData.Instance.SimulationRoomBuffPreviewTable.Values.Where(x => x.EventType is SimulationRoomEvent.BossBattle).ToList(); + var randomBuffPreview = GetRandomItems(battleBuffPreviews, 1); + var simRoomBattleEvent = CreateSimRoomBattleEvent(chapter: simRoomChapter, stage: i, order: 1, simRoomBattleEventRecords, randomBuffPreview[0]); + events.Add(NetToM(simRoomBattleEvent)); + netSimRooms.Add(simRoomBattleEvent); + } + else if (i == simRoomChapter.StageValue - 1) // Selection + { + // Maintenance Selection + var simRoomSelectionEventRecord = simRoomSelectionEventRecords.OrderBy(x => x.Id).ToList() + .FindLast(x => x.EventType is SimulationRoomEvent.Maintenance); + + var simRoomEvent = new NetSimRoomEvent() + { + Location = new NetSimRoomEventLocationInfo { Chapter = simRoomChapter.Chapter, Stage = i, Order = 1 }, + }; + + if (simRoomSelectionEventRecord is not null) + { + simRoomEvent.Selection = new NetSimRoomSelectionEvent + { + Id = simRoomSelectionEventRecord.Id, + SelectedNumber = 1 + }; + var groupRecordsBySelectionGroupId = simRoomSelectionEventGroupRecords.FindAll(x => x.SelectionGroupId == simRoomSelectionEventRecord.SelectionGroupId); + foreach (var groupRecord in groupRecordsBySelectionGroupId) + { + simRoomEvent.Selection.Group.Add(new NetSimRoomSelectionGroupElement { SelectionNumber = groupRecord.SelectionNumber, Id = groupRecord.Id }); + } + } + + events.Add(NetToM(simRoomEvent)); + netSimRooms.Add(simRoomEvent); + log.Debug($"stage: {i}, NetSimRoomEvent: {JsonConvert.SerializeObject(simRoomEvent)}"); + + // Random + var RandomSimRoomSelectionEventRecords = simRoomSelectionEventRecords.FindAll(x + => x.EventType is SimulationRoomEvent.RandomSelection or SimulationRoomEvent.EnhanceBuff); + + var RandomSimRoomSelectionEventRecord = GetRandomItems(RandomSimRoomSelectionEventRecords, 1); + + var RandomSimRoomEvent = new NetSimRoomEvent + { + Location = new NetSimRoomEventLocationInfo { Chapter = simRoomChapter.Chapter, Stage = i, Order = 2 }, + }; + if (RandomSimRoomSelectionEventRecord != null && RandomSimRoomSelectionEventRecord.Count >= 1) + { + RandomSimRoomEvent.Selection = new NetSimRoomSelectionEvent + { + Id = RandomSimRoomSelectionEventRecord[0].Id, + SelectedNumber = 2 + }; + + var groupRecordsBySelectionGroupId = simRoomSelectionEventGroupRecords.FindAll(x + => x.SelectionGroupId == RandomSimRoomSelectionEventRecord[0].SelectionGroupId); + + foreach (var groupRecord in groupRecordsBySelectionGroupId) + { + RandomSimRoomEvent.Selection.Group.Add(new NetSimRoomSelectionGroupElement { SelectionNumber = groupRecord.SelectionNumber, Id = groupRecord.Id }); + } + } + + events.Add(NetToM(RandomSimRoomEvent)); + netSimRooms.Add(RandomSimRoomEvent); + log.Debug($"stage: {i}, NetSimRoomEvent: {JsonConvert.SerializeObject(RandomSimRoomEvent)}"); + } + else + { + var battleBuffPreviews = buffPreviewRecords.FindAll(x + => x.EventType is SimulationRoomEvent.NormalBattle or SimulationRoomEvent.EliteBattle); + var randomBuffPreview = GetRandomItems(battleBuffPreviews, 3); + int order = 0; + foreach (var simRoomBuffPreview in randomBuffPreview) + { + order += 1; + var simRoomBattleEvent = CreateSimRoomBattleEvent(chapter: simRoomChapter, stage: i, order: order, simRoomBattleEventRecords, simRoomBuffPreview); + events.Add(NetToM(simRoomBattleEvent)); + netSimRooms.Add(simRoomBattleEvent); + } + } + } + // user.AddTrigger() + user.ResetableData.SimRoomData.Events = events; + JsonDb.Save(); + return netSimRooms; + } + + /// + /// Get BuffOptions By User And SimRoomEventLocationInfo + /// + /// User + /// NetSimRoomEventLocationInfo + /// List + public static List GetBuffOptions(User user, NetSimRoomEventLocationInfo location) + { + var events = user.ResetableData.SimRoomData.Events; + var simRoomEventIndex = events.FindIndex(x => x.Location.Chapter == location.Chapter && x.Location.Stage == location.Stage && x.Location.Order == location.Order); + if (simRoomEventIndex > -1) + { + var simRoomEvent = events[simRoomEventIndex]; + if (GameData.Instance.SimulationRoomBuffPreviewTable.TryGetValue(simRoomEvent.Battle.BuffPreviewId, out var buffPreview)) + { + log.Debug($"SimRoomBuffPreview: {JsonConvert.SerializeObject(buffPreview)}"); + List buffRecords = []; + if (buffPreview.PreviewType is PreviewType.Bubble) + { + var bubbleType = GetBubbleType(buffPreview.PreviewTarget); + buffRecords = [.. GameData.Instance.SimulationRoomBuffTable.Values + .Where(x => x.BubbleType == bubbleType + && x.Grade is SimulationRoomBuffGrade.SR or SimulationRoomBuffGrade.SSR )]; + } + else if (buffPreview.PreviewType is PreviewType.MainTarget) + { + var mainTarget = GetMainTarget(buffPreview.PreviewTarget); + buffRecords = [.. GameData.Instance.SimulationRoomBuffTable.Values + .Where(x => x.MainTarget == mainTarget + && x.Grade is SimulationRoomBuffGrade.SR or SimulationRoomBuffGrade.SSR)]; + } + else if (buffPreview.PreviewType is PreviewType.Grade) + { + buffRecords = [.. GameData.Instance.SimulationRoomBuffTable.Values + .Where(x => x.Grade == SimulationRoomBuffGrade.EPIC)]; + } + else + { + log.Warn($"Not Implement SimulationRoomBuffPreview.PreviewType: {buffPreview.PreviewType}"); + } + if (buffRecords.Count > 0) + { + var selectedBuffs = buffRecords.GetRandomItems(3); + log.Debug($"Selected Buffs: {JsonConvert.SerializeObject(selectedBuffs)}"); + + // user SimRoomEvent update + UpdateUserSimRoomEvent(user, simRoomEventIndex, events, battleProgress: (int)SimRoomBattleEventProgress.RewardWaiting, BuffOptions: [.. selectedBuffs.Select(x => x.Id)]); + return [.. selectedBuffs.Select(x => x.Id)]; + } + else + { + log.Warn($"Not Font SimulationRoomBuff"); + } + } + } + else + { + log.Warn($"Not Font User.ResetableData.SimRoomData.Events"); + } + + return []; + } + + /// + /// Get TeamData By User Or SimRoomCharacterHp + /// + /// User + /// Int + /// List + /// NetTeamData + public static NetTeamData GetTeamData(User user, int teamNumber, List? remainingHps) + { + try + { + if (remainingHps is not null && remainingHps.Count > 0) + { + var team = new NetTeamData() + { + TeamNumber = teamNumber + }; + int slot = 1; + foreach (var item in remainingHps) + { + team.Slots.Add(new NetTeamSlot() { Slot = slot, Value = item.Csn }); + slot += 1; + } + return team; + } + else + { + if (user.UserTeams.TryGetValue((int)TeamType.SimulationRoom, out var teamData)) + { + var team = teamData.Teams.FirstOrDefault(t => t.TeamNumber == teamNumber); + if (team is not null) return team; + } + } + } + catch (Exception e) + { + log.Error($"Get User Teams Exception: {e.Message}"); + } + + // default value is null + return null; + } + + /// + /// Randomly select a specified number of elements from the list. + /// + public static List GetRandomItems(this List? list, int count) + { + if (list is null || list.Count == 0) + return []; + + // If the number of requests exceeds the list length, retrieve all. + count = Math.Min(count, list.Count); + + return [.. list.OrderBy(x => _rand.Next()).Take(count)]; + } + + /// + /// Create SimRoomBattleEvent + /// + /// SimulationRoomChapterRecord + /// + /// + /// + /// + /// NetSimRoomEvent + private static NetSimRoomEvent CreateSimRoomBattleEvent(SimulationRoomChapterRecord chapter, int stage, int order, + List simRoomBattleEventRecords, SimulationRoomBuffPreviewRecord randomBuffPreview) + { + var simRoomEvent = new NetSimRoomEvent(); + var location = new NetSimRoomEventLocationInfo(); + var battle = new NetSimRoomBattleEvent(); + + location.Chapter = chapter.Chapter; + location.Stage = stage; + location.Order = order; + var simRoomBuffPreviewBattleEvents = simRoomBattleEventRecords.FindAll(x + => x.EventType == randomBuffPreview.EventType && x.DifficultyConditionValue <= chapter.DifficultyId); + log.Debug($"EventType: {randomBuffPreview.EventType}, SimRoomBattleEventRecord Count: {simRoomBuffPreviewBattleEvents.Count}"); + var randomBattleEvents = GetRandomItems(simRoomBuffPreviewBattleEvents, 1); + foreach (var battleEvent in randomBattleEvents) + { + battle.Id = battleEvent.Id; + battle.RemainingTargetHealth = 10000; + battle.BuffPreviewId = randomBuffPreview.Id; + } + + simRoomEvent.Location = location; + simRoomEvent.Battle = battle; + log.Debug($"stage: {stage}, NetSimRoomEvent: {JsonConvert.SerializeObject(simRoomEvent)}"); + return simRoomEvent; + } + + public static NetSimRoomEvent MToNet(SimRoomEvent simRoomEvent) + { + var netSimRoomEvent = new NetSimRoomEvent + { + Selected = simRoomEvent.Selected, + }; + if (simRoomEvent.Location is not null && simRoomEvent.Location.Chapter > 0) + { + netSimRoomEvent.Location = new NetSimRoomEventLocationInfo + { + Chapter = simRoomEvent.Location.Chapter, + Stage = simRoomEvent.Location.Stage, + Order = simRoomEvent.Location.Order + }; + } + + if (simRoomEvent.Battle is not null && simRoomEvent.Battle.Id > 0) + { + netSimRoomEvent.Battle = new NetSimRoomBattleEvent + { + Id = simRoomEvent.Battle.Id, + BuffOptions = { simRoomEvent.Battle.BuffOptions }, + Progress = (SimRoomBattleEventProgress)simRoomEvent.Battle.Progress, + RemainingTargetHealth = simRoomEvent.Battle.RemainingTargetHealth, + BuffPreviewId = simRoomEvent.Battle.BuffPreviewId, + }; + } + + if (simRoomEvent.Selection is not null && simRoomEvent.Selection.Id > 0) + { + netSimRoomEvent.Selection = new NetSimRoomSelectionEvent + { + Id = simRoomEvent.Selection.Id, + SelectedNumber = simRoomEvent.Selection.SelectedNumber, + }; + simRoomEvent.Selection.Group.ForEach(g => + { + netSimRoomEvent.Selection.Group.Add(new NetSimRoomSelectionGroupElement + { + SelectionNumber = g.SelectionNumber, + Id = g.Id, + IsDone = g.IsDone, + RandomBuff = g.RandomBuff, + }); + }); + } + return netSimRoomEvent; + } + + public static SimRoomEvent NetToM(NetSimRoomEvent simRoomEvent) + { + var netSimRoomEvent = new SimRoomEvent + { + Selected = simRoomEvent.Selected, + EventCase = (int)simRoomEvent.EventCase, + }; + if (simRoomEvent.Location is not null && simRoomEvent.Location.Chapter > 0) + { + netSimRoomEvent.Location = new SimRoomEventLocationInfo + { + Chapter = simRoomEvent.Location.Chapter, + Stage = simRoomEvent.Location.Stage, + Order = simRoomEvent.Location.Order + }; + } + + if (simRoomEvent.Battle is not null && simRoomEvent.Battle.Id > 0) + { + netSimRoomEvent.Battle = new SimRoomBattleEvent + { + Id = simRoomEvent.Battle.Id, + Progress = (int)simRoomEvent.Battle.Progress, + RemainingTargetHealth = simRoomEvent.Battle.RemainingTargetHealth, + BuffPreviewId = simRoomEvent.Battle.BuffPreviewId, + }; + foreach (var item in simRoomEvent.Battle.BuffOptions) + { + netSimRoomEvent.Battle.BuffOptions.Add(item); + } + } + + if (simRoomEvent.Selection is not null && simRoomEvent.Selection.Id > 0) + { + netSimRoomEvent.Selection = new SimRoomSelectionEvent + { + Id = simRoomEvent.Selection.Id, + SelectedNumber = simRoomEvent.Selection.SelectedNumber, + }; + foreach (var g in simRoomEvent.Selection.Group) + { + netSimRoomEvent.Selection.Group.Add(new SimRoomSelectionGroupElement + { + SelectionNumber = g.SelectionNumber, + Id = g.Id, + IsDone = g.IsDone, + RandomBuff = g.RandomBuff, + }); + } + } + return netSimRoomEvent; + } + + public static SimulationRoomBubbleType GetBubbleType(string previewTarget) + { + // Type_C + switch (previewTarget) + { + case "Type_A": + return SimulationRoomBubbleType.TypeA; + case "Type_B": + return SimulationRoomBubbleType.TypeB; + case "Type_C": + return SimulationRoomBubbleType.TypeC; + case "Type_D": + return SimulationRoomBubbleType.TypeD; + default: + log.Warn("Unknown preview target: " + previewTarget); + return SimulationRoomBubbleType.TypeA; + } + } + + public static SimulationRoomBuffMainTarget GetMainTarget(string previewTarget) + { + // Shoot = 0, Attack = 1, Survive = 2 + switch (previewTarget) + { + case "Shoot": + return SimulationRoomBuffMainTarget.Shoot; + case "Attack": + return SimulationRoomBuffMainTarget.Attack; + case "Survive": + return SimulationRoomBuffMainTarget.Survive; + default: + log.Warn("Unknown preview target: " + previewTarget); + return SimulationRoomBuffMainTarget.Attack; + } + } + + /// + /// Update User SimRoomEvent Events + /// + public static void UpdateUserSimRoomEvent(User user, int index, List events, int selectionNumber = 0, + bool isDone = false, int battleProgress = 0, List? BuffOptions = null) + { + try + { + var simRoomEvent = events[index]; + // user SimRoomEvent update + simRoomEvent.Selected = true; + + // Update Selection Group + var groupIndex = simRoomEvent.Selection.Group.FindIndex(x => x.SelectionNumber == selectionNumber); + if (groupIndex > -1 && isDone) + { + var group = simRoomEvent.Selection.Group[groupIndex]; + group.IsDone = isDone; + simRoomEvent.Selection.Group[groupIndex] = group; + } + else + { + log.Warn("Not Fond SimRoomSelectionEvent.Group"); + } + + // Udapte Battle Progress + if (battleProgress > 0) simRoomEvent.Battle.Progress = battleProgress; + + // Update BuffOptions + if (BuffOptions is not null && BuffOptions.Count > 0) + { + simRoomEvent.Battle.BuffOptions = BuffOptions; + } + + events[index] = simRoomEvent; + user.ResetableData.SimRoomData.Events = events; + log.Debug($"UpdateUserSimRoomEvent After UserSimRoomEventData: {JsonConvert.SerializeObject(user.ResetableData.SimRoomData.Events)}"); + } + catch (Exception e) + { + log.Error($"Update UserSimRoomEvent Events Exception: {e.Message}"); + } + } + + public static List UpdateUserRemainingHps(User user, List? remainingHps = null, int teamNumber = 1, int HpValue = 10000) + { + var userRemainingHps = user.ResetableData.SimRoomData.RemainingHps; + try + { + // req remainingHps + if (remainingHps is not null && remainingHps.Count > 0) + { + foreach (var characterHp in remainingHps) + { + var userCharacterHpIndex = userRemainingHps.FindIndex(x => x.Csn == characterHp.Csn); + if (userCharacterHpIndex > -1) + { + userRemainingHps[userCharacterHpIndex] = new SimRoomCharacterHp { Csn = characterHp.Csn, Hp = characterHp.Hp }; + } + else + { + userRemainingHps.Add(new SimRoomCharacterHp { Csn = characterHp.Csn, Hp = characterHp.Hp }); + } + } + } + + // get user team + if (user.UserTeams.TryGetValue((int)TeamType.SimulationRoom, out var userTeam)) + { + var team = userTeam.Teams.FirstOrDefault(x => x.TeamNumber == teamNumber); + if (team is not null) + { + foreach (var slot in team.Slots) + { + if (userRemainingHps.FindIndex(x => x.Csn == slot.Value) < 0) userRemainingHps.Add(new SimRoomCharacterHp { Csn = slot.Value, Hp = HpValue }); + } + } + } + // update user + user.ResetableData.SimRoomData.RemainingHps = userRemainingHps; + return userRemainingHps; + } + catch (Exception e) + { + log.Error($"Update UserRemainingHps Exception: {e.Message}"); + } + return userRemainingHps; + } + + + public static NetRewardData? SimRoomReceivedReward(User user, int difficultyId, int chapterId) + { + // check if reward is received + NetRewardData? reward = null; + if (!IsRewardReceived(user, difficultyId, chapterId)) + { + + var allChapterRecords = GameData.Instance.SimulationRoomChapterTable.Values; + var chapter = allChapterRecords + .FirstOrDefault(x => x.DifficultyId == difficultyId && x.Chapter == chapterId); + + if (chapter is not null && chapter.RewardId > 0) + { + var receivedRewardChapters = user.ResetableData.SimRoomData.ReceivedRewardChapters; + var receivedRewardChapter = receivedRewardChapters + .OrderBy(x => x.Difficulty) + .ThenBy(x => x.Chapter) + .LastOrDefault(); + SimulationRoomChapterRecord? receivedRewardChapterRecord = null; + if (receivedRewardChapter is not null) + { + receivedRewardChapterRecord = allChapterRecords + .FirstOrDefault(x => x.DifficultyId == receivedRewardChapter.Difficulty + && x.Chapter == receivedRewardChapter.Chapter); + } + + reward = new NetRewardData(); + if (receivedRewardChapterRecord is null) + { + // Claiming the reward for the first time + reward = RewardUtils.RegisterRewardsForUser(user, rewardId: chapter.RewardId); + AddRewardChapter(user, difficultyId, chapterId); + } + // Check if the received reward chapter is lower than the current chapter + else if (receivedRewardChapterRecord.DifficultyId == difficultyId && receivedRewardChapterRecord.Chapter < chapter.Chapter) + { + // Claiming the reward for a higher chapter + reward = CalculateIncrementalReward(user, chapter, receivedRewardChapterRecord); + AddRewardChapter(user, difficultyId, chapterId); + } + // Check if the received reward chapter is lower than the current difficulty + else if (receivedRewardChapterRecord.DifficultyId < difficultyId) + { + // Claiming the reward for a higher difficulty + reward = RewardUtils.RegisterRewardsForUser(user, rewardId: chapter.RewardId); + AddRewardChapter(user, difficultyId, chapterId); + } + } + } + return reward; + } + + /// + /// Check if reward is received + /// + /// True if reward is received, otherwise false + private static bool IsRewardReceived(User user, int difficultyId, int chapterId) + { + var isReceived = user.ResetableData.SimRoomData.ReceivedRewardChapters.Any(x => x.Chapter == chapterId && x.Difficulty == difficultyId); + log.Debug($"IsRewardReceived (diff: {difficultyId}, chapter: {chapterId}): {isReceived}"); + return isReceived; + } + /// + /// Add reward chapter + /// + private static void AddRewardChapter(User user, int difficulty, int chapter) + { + user.ResetableData.SimRoomData.ReceivedRewardChapters.Add(new SimRoomChapterInfo() + { + Difficulty = difficulty, + Chapter = chapter + }); + } + + /// + /// Calculate incremental reward + /// + /// Incremental reward data + private static NetRewardData CalculateIncrementalReward(User user, + SimulationRoomChapterRecord chapter, SimulationRoomChapterRecord receivedChapterRecord) + { + var reward = new NetRewardData(); + var rewardRecord = GameData.Instance.GetRewardTableEntry(chapter.RewardId); + var receivedRewardRecord = GameData.Instance.GetRewardTableEntry(receivedChapterRecord.RewardId); + + if (rewardRecord?.Rewards == null || receivedRewardRecord?.Rewards == null) + { + // If rewardRecord or receivedRewardRecord is empty, return the complete reward record + reward = RewardUtils.RegisterRewardsForUser(user, rewardId: chapter.RewardId); + } + else + { + var receivedRewardIds = receivedRewardRecord.Rewards + .Where(x => x != null) + .ToDictionary(x => x.RewardId, x => x.RewardValue); + + foreach (var rewardItem in rewardRecord.Rewards.Where(x => x != null)) + { + int receivedAmount = receivedRewardIds.GetValueOrDefault(rewardItem.RewardId, 0); + int remainingAmount = Math.Max(0, rewardItem.RewardValue - receivedAmount); + + if (remainingAmount > 0) + { + RewardUtils.AddSingleObject(user, ref reward, rewardItem.RewardId, + rewardType: rewardItem.RewardType, remainingAmount); + } + } + } + + return reward; + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Simroom/SimpleModeSelectBuff.cs b/EpinelPS/LobbyServer/Simroom/SimpleModeSelectBuff.cs new file mode 100644 index 0000000..0b19a3d --- /dev/null +++ b/EpinelPS/LobbyServer/Simroom/SimpleModeSelectBuff.cs @@ -0,0 +1,47 @@ +using EpinelPS.Utils; +using EpinelPS.Database; + +namespace EpinelPS.LobbyServer.Simroom +{ + [PacketPath("/simroom/simplemode/selectbuff")] + public class SimpleModeSelectBuff : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // { "buffToAdd": 1010106, "buffToDelete": 1010105 } + ReqSelectSimRoomSimpleModeBuff req = await ReadData(); + User user = GetUser(); + + + List buffs = user.ResetableData.SimRoomData.Buffs; + List legacyBuffs = user.ResetableData.SimRoomData.LegacyBuffs; + if (req.BuffToDelete > 0) + { + if (buffs.Contains(req.BuffToDelete)) buffs.Remove(req.BuffToDelete); + if (legacyBuffs.Contains(req.BuffToDelete)) legacyBuffs.Remove(req.BuffToDelete); + } + + if (req.BuffToAdd > 0) + { + if (!buffs.Contains(req.BuffToDelete)) buffs.Add(req.BuffToDelete); + if (!legacyBuffs.Contains(req.BuffToDelete)) legacyBuffs.Add(req.BuffToDelete); + } + + ResSelectSimRoomSimpleModeBuff response = new() + { + Result = SimRoomResult.Reset, + NextSimpleModeBuffSelectionInfo = new() + { + RemainingBuffSelectCount = 8 - buffs.Count, + BuffOptions = { buffs }, + }, + }; + + user.ResetableData.SimRoomData.Entered = false; + + JsonDb.Save(); + + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Simroom/SimpleModeSetSkipOption.cs b/EpinelPS/LobbyServer/Simroom/SimpleModeSetSkipOption.cs new file mode 100644 index 0000000..4d666a2 --- /dev/null +++ b/EpinelPS/LobbyServer/Simroom/SimpleModeSetSkipOption.cs @@ -0,0 +1,24 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Simroom +{ + [PacketPath("/simroom/simplemode/setskipoption")] + public class SimpleModeSetSkipOption : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + var req = await ReadData(); + // ReqSetSimRoomSimpleModeSkipOption Fields + // bool Enabled + User user = GetUser(); + + ResSetSimRoomSimpleModeSkipOption response = new(); + + user.ResetableData.SimRoomData.IsSimpleModeSkipEnabled = req.Enabled; + + JsonDb.Save(); + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Simroom/SimpleModeSkipAll.cs b/EpinelPS/LobbyServer/Simroom/SimpleModeSkipAll.cs new file mode 100644 index 0000000..d26ffd1 --- /dev/null +++ b/EpinelPS/LobbyServer/Simroom/SimpleModeSkipAll.cs @@ -0,0 +1,41 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Simroom +{ + [PacketPath("/simroom/simplemode/skipall")] + public class SimpleModeSkipAll : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + await ReadData(); + var user = GetUser(); + + // ResSkipAllSimRoomSimpleMode Fields + // SimRoomResult Result + // NetRewardData Reward + // NetRewardData RewardByRewardUpEvent + + ResSkipAllSimRoomSimpleMode response = new() + { + Result = SimRoomResult.Success + }; + + // Reward + try + { + user.ResetableData.SimRoomData.CurrentDifficulty = 5; + user.ResetableData.SimRoomData.CurrentChapter = 3; + var reward = SimRoomHelper.SimRoomReceivedReward(user, 5, 3); // 5 = difficulty, 3 = stage + if (reward is not null) response.Reward = reward; + } + catch (Exception ex) + { + Logging.WriteLine($"SkipAllSimpleMode Reward Exception: {ex.Message}", LogType.Error); + } + + JsonDb.Save(); + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Simroom/SimpleModeSkipBuffSelection.cs b/EpinelPS/LobbyServer/Simroom/SimpleModeSkipBuffSelection.cs new file mode 100644 index 0000000..d30c088 --- /dev/null +++ b/EpinelPS/LobbyServer/Simroom/SimpleModeSkipBuffSelection.cs @@ -0,0 +1,25 @@ +using EpinelPS.Utils; +using EpinelPS.Database; + +namespace EpinelPS.LobbyServer.Simroom +{ + [PacketPath("/simroom/simplemode/skipbuffselection")] + public class SimpleModeSkipBuffSelection : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + await ReadData(); + User user = GetUser(); + ResSkipSimRoomSimpleModeBuffSelection response = new() + { + Result = SimRoomResult.Reset, + }; + + user.ResetableData.SimRoomData.Entered = false; + + JsonDb.Save(); + + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Simroom/SimpleModeStart.cs b/EpinelPS/LobbyServer/Simroom/SimpleModeStart.cs new file mode 100644 index 0000000..db63f9d --- /dev/null +++ b/EpinelPS/LobbyServer/Simroom/SimpleModeStart.cs @@ -0,0 +1,65 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Simroom +{ + [PacketPath("/simroom/simplemode/start")] + public class SimpleModeStart : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + await ReadData(); + User user = GetUser(); + + // ResStartSimRoomSimpleMode Fields + // SimRoomResult Result + // RepeatedField Events + // NextSimpleModeBuffSelectionInfo + + ResStartSimRoomSimpleMode response = new() + { + Result = SimRoomResult.Success, + // NextSimpleModeBuffSelectionInfo = new() + // { + // BuffOptions = { user.ResetableData.SimRoomData.LegacyBuffs } + // } + }; + + + // Events + try + { + user.ResetableData.SimRoomData.Entered = true; + var simRoomEvents = SimRoomHelper.GetSimRoomEvents(user, 5, 3); // 5 = difficulty, 3 = stage + if (simRoomEvents is not null) + { + foreach (var simRoomEvent in simRoomEvents) + { + // Check if event is battle and is first order + if (simRoomEvent.EventCase == NetSimRoomEvent.EventOneofCase.Battle && simRoomEvent.Location.Order == 1) + { + var location = simRoomEvent.Location; + if (location is null) continue; + SimRoomHelper.GetBuffOptions(user, location); + } + } + JsonDb.Save(); + + var userSimRoomEvents = user.ResetableData.SimRoomData.Events; + if (userSimRoomEvents is not null) + { + simRoomEvents = [.. userSimRoomEvents.Select(SimRoomHelper.MToNet)]; + } + // Add events to response + response.Events.AddRange(simRoomEvents); + } + } + catch (Exception ex) + { + Logging.WriteLine($"SimpleModeStart Events Exception: {ex.Message}", LogType.Error); + } + + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/Models/DbModels.cs b/EpinelPS/Models/DbModels.cs index f30402d..3b8a1af 100644 --- a/EpinelPS/Models/DbModels.cs +++ b/EpinelPS/Models/DbModels.cs @@ -65,17 +65,17 @@ namespace EpinelPS.Models // For harmony cubes that can be equipped to multiple characters public List CsnList = []; } - + public class EquipmentAwakeningData { public long Isn; public NetEquipmentAwakeningOption Option; - public bool IsNewData; - + public bool IsNewData; + public EquipmentAwakeningData() { Option = new NetEquipmentAwakeningOption(); - IsNewData = false; + IsNewData = false; } } public class EventData @@ -115,12 +115,66 @@ namespace EpinelPS.Models public int Defense; public int Hp; } - public class SimroomData + + // Simroom Data + public class SimRoomData { public int CurrentDifficulty; public int CurrentChapter; + public List Buffs = []; + public List LegacyBuffs = []; + public List Events = []; + public List RemainingHps = []; + public List ReceivedRewardChapters = []; + public bool IsSimpleModeSkipEnabled = false; public bool Entered = false; } + public class SimRoomEvent + { + public SimRoomEventLocationInfo Location = new(); + public bool Selected; + public SimRoomBattleEvent Battle = new(); + public SimRoomSelectionEvent Selection = new(); + public int EventCase; + } + public class SimRoomEventLocationInfo + { + public int Chapter; + public int Stage; + public int Order; + } + public class SimRoomChapterInfo + { + public int Difficulty; + public int Chapter; + } + public class SimRoomBattleEvent + { + public int Id; + public List BuffOptions = []; + public int Progress; + public int RemainingTargetHealth; + public int BuffPreviewId; + } + public class SimRoomSelectionEvent + { + public int Id; + public int SelectedNumber; + public List Group = []; + } + public class SimRoomSelectionGroupElement + { + public int SelectionNumber; + public int Id; + public bool IsDone; + public int RandomBuff; + } + public class SimRoomCharacterHp + { + public long Csn; + public int Hp; + } + public class ResetableData { public int WipeoutCount = 0; @@ -128,10 +182,10 @@ namespace EpinelPS.Models public int InterceptionTickets = 3; public List CompletedDailyMissions = []; public int DailyMissionPoints; - public SimroomData SimRoomData = new(); + public SimRoomData SimRoomData = new(); public Dictionary DailyCounselCount = []; - + } public class WeeklyResetableData { @@ -267,7 +321,7 @@ namespace EpinelPS.Models public bool RecievedFinalReward { get; set; } public bool CompletedPerfectly { get; set; } } - + public class PassRankData { public int PassRank { get; set; } = 0; diff --git a/EpinelPS/Models/UserModel.cs b/EpinelPS/Models/UserModel.cs index e144578..b2284e8 100644 --- a/EpinelPS/Models/UserModel.cs +++ b/EpinelPS/Models/UserModel.cs @@ -3,6 +3,7 @@ using EpinelPS.Database; using EpinelPS.Utils; namespace EpinelPS.Models; + public class ClearedTutorialData { public int Id; @@ -34,6 +35,7 @@ public class User public DateTime BanEnd; public int BanId = 0; public DateTime LastReset = DateTime.MinValue; + public DateTime LastWeeklyReset = DateTime.MinValue; // Game data public List CompletedScenarios = []; @@ -86,7 +88,7 @@ public class User public List Memorial = []; public List JukeboxBgm = []; public List FavoriteItems = []; - + public List FavoriteItemQuests = []; public Dictionary TowerProgress = []; @@ -403,7 +405,36 @@ public class User return LastReset < todayResetTime; } - public void ResetDataIfNeeded() + private bool ShouldResetWeekly() + { + var nowLocal = DateTime.UtcNow; + + // Calculate the most recent Tuesday reset time + DayOfWeek currentDay = nowLocal.DayOfWeek; + int daysSinceTuesday = ((int)currentDay - (int)DayOfWeek.Tuesday + 7) % 7; + + // Get the date of the most recent Tuesday + DateTime thisTuesday = nowLocal.Date.AddDays(-daysSinceTuesday); + + // Compute the weekly reset time + DateTime weeklyResetTime = new( + thisTuesday.Year, + thisTuesday.Month, + thisTuesday.Day, + JsonDb.Instance.ResetHourUtcTime, 0, 0 + ); + + // If nowLocal is before the weekly reset time, subtract a week + if (nowLocal < weeklyResetTime) + { + weeklyResetTime = weeklyResetTime.AddDays(-7); + } + + // If user's last reset was before the last scheduled 2 PM, they need reset + return LastWeeklyReset < weeklyResetTime; + } + + /*public void ResetDataIfNeeded() { if (!ShouldResetUser()) return; @@ -412,5 +443,41 @@ public class User LastReset = DateTime.UtcNow; ResetableData = new(); JsonDb.Save(); + }*/ + + public void ResetDataIfNeeded() + { + bool needsSave = false; + + // Check daily reset + if (ShouldResetUser()) + { + Logging.WriteLine("Resetting daily user data...", LogType.Warning); + + LastReset = DateTime.UtcNow; + ResetableData = new() + { + SimRoomData = new() + { + LegacyBuffs = ResetableData.SimRoomData.LegacyBuffs // Retain old LegacyBuffs data + } + }; + needsSave = true; + } + + // Check weekly reset + if (ShouldResetWeekly()) + { + Logging.WriteLine("Resetting weekly user data...", LogType.Warning); + + LastWeeklyReset = DateTime.UtcNow; + ResetableData = new(); + needsSave = true; + } + + if (needsSave) + { + JsonDb.Save(); + } } } \ No newline at end of file diff --git a/EpinelPS/Utils/DateTimeHelper.cs b/EpinelPS/Utils/DateTimeHelper.cs new file mode 100644 index 0000000..c791e0f --- /dev/null +++ b/EpinelPS/Utils/DateTimeHelper.cs @@ -0,0 +1,90 @@ + +namespace EpinelPS.Utils +{ + public static class DateTimeHelper + { + /// + /// Get the specified time of the next day in the specified time zone + /// + /// Time zone ID, such as "China Standard Time", "Eastern Standard Time", "UTC" + /// Hours (0-23) + /// Minutes (0-59) + /// The specified time of the next day + public static DateTime GetNextDayAtTime(string timeZoneId = "", int hour = 0, int minute = 0) + { + // Get the current time of the target time zone and the current time zone + (DateTime currentTime, TimeZoneInfo tz) = GetCurrentTimeWithZone(timeZoneId); + + DateTime nextDay = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, hour, minute, 0).AddDays(1); + + return TimeZoneInfo.ConvertTimeToUtc(nextDay, tz); + } + + /// + /// Get the next specified day of the week + specified time in the specified time zone + /// + /// Time zone ID, such as "China Standard Time"、"Eastern Standard Time"、"UTC" + /// Target day of the week, for example DayOfWeek.Monday + /// Hours (0-23) + /// Minutes (0-59) + /// Next specified day of the week Specified time + public static DateTime GetNextWeekdayAtTime(string timeZoneId, DayOfWeek targetDay, int hour, int minute = 0) + { + // Get the current time of the target time zone and the current time zone + (DateTime currentTime, TimeZoneInfo tz) = GetCurrentTimeWithZone(timeZoneId); + + // Calculate the number of days until the target weekday + int daysUntilTarget = ((int)targetDay - (int)currentTime.DayOfWeek + 7) % 7; + if (daysUntilTarget == 0) daysUntilTarget = 7; // If today is the target day of the week, take next week + + DateTime nextWeekday = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, hour, minute, 0).AddDays(daysUntilTarget); + + return TimeZoneInfo.ConvertTimeToUtc(nextWeekday, tz); + } + + /// + /// Get a specific day of next month in the specified time zone at a specified time + /// + /// Time zone ID, such as "China Standard Time"、"Eastern Standard Time"、"UTC" + /// Target date (1-31, ensure that this date exists in the month) + /// Hours (0-23) + /// Minutes (0-59) + /// Specified date and time next month + public static DateTime GetNextMonthDayAtTime(string timeZoneId, int day, int hour, int minute = 0) + { + // Get the current time of the target time zone and the current time zone + (DateTime currentTime, TimeZoneInfo tz) = GetCurrentTimeWithZone(timeZoneId); + + // Calculate the year and month of next month + int year = currentTime.Month == 12 ? currentTime.Year + 1 : currentTime.Year; + int month = currentTime.Month == 12 ? 1 : currentTime.Month + 1; + + // Ensure the date is valid (avoid situations like 30th February) + int daysInMonth = DateTime.DaysInMonth(year, month); + int targetDay = Math.Min(day, daysInMonth); + + // Construct target time + DateTime target = new(year, month, targetDay, hour, minute, 0); + + return TimeZoneInfo.ConvertTimeToUtc(target, tz); + } + + /// + /// Get the current time and timezone object for a specified timezone + /// + /// Time zone ID, such as "China Standard Time"、"Eastern Standard Time"、"UTC" + /// (currentTime, tz) tuple + public static (DateTime currentTime, TimeZoneInfo tz) GetCurrentTimeWithZone(string timeZoneId) + { + + // Get the target time zone + TimeZoneInfo tz = TimeZoneInfo.Local; + if (timeZoneId != null && timeZoneId != "") + tz = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId); + DateTime currentTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz); + return (currentTime, tz); + } + + } +} + diff --git a/EpinelPS/gameconfig.json b/EpinelPS/gameconfig.json index db87b88..c7984ae 100644 --- a/EpinelPS/gameconfig.json +++ b/EpinelPS/gameconfig.json @@ -1,11 +1,11 @@ { "StaticDataMpk": { - "Url": "https://cloud.nikke-kr.com/prdenv/139-cf218bd245/staticdata/data/qa-251030-10b/473550/mpk/StaticData.pack", - "Version": "data/qa-251030-10b/473550", - "Salt1": "UfBN5TYtYG7pAY6lxoZXyA7tBOf1rdoPKfxbdB/6n0M=", - "Salt2": "zgmjiq+i9OdM6TjDHKav1JepFUaWLXRtismYpUk7lt4=" + "Url": "https://cloud.nikke-kr.com/prdenv/139-c596af726e/staticdata/data/qa-251120-10b-p1/477874/mpk/StaticData.pack", + "Version": "data/qa-251120-10b-p1/477874", + "Salt1": "t8iCAEGhWUzYFk6umjwwxY6Y4IrqGQcil/rAG6M/ofw=", + "Salt2": "eBWJaf2wR/WdkfhYEc9x7gHmZX8inyrDf6sA5l7N4zk=" }, - "ResourceBaseURL": "https://cloud.nikke-kr.com/prdenv/139-bfaa7caf86/{Platform}", + "ResourceBaseURL": "https://cloud.nikke-kr.com/prdenv/139-b131234cad/{Platform}", "GameMinVer": "100.0.1", "GameMaxVer": "150.0.2", "TargetVersion": "139.8.13"