From 1dca65f91adfa7fd4285813d03628ba7eeb3f4ca Mon Sep 17 00:00:00 2001 From: Kei-Luna Date: Sat, 23 May 2026 19:03:52 +0900 Subject: [PATCH] ClimbTowerLogic_CheckCycleLevel TowerLevel_EnterLevel --- Common/Data/Excel/ClimbTowerTimeExcel.cs | 18 +++ Common/Data/Excel/TowerLevelExcel.cs | 20 +++ Common/Data/GameData.cs | 2 + .../Tower/ClimbTowerLogic_CheckCycleLevel.cs | 118 ++++++++++++++++++ .../Handlers/Tower/TowerLevel_EnterLevel.cs | 39 ++++++ 5 files changed, 197 insertions(+) create mode 100644 Common/Data/Excel/ClimbTowerTimeExcel.cs create mode 100644 Common/Data/Excel/TowerLevelExcel.cs create mode 100644 GameServer/Server/CallGS/Handlers/Tower/ClimbTowerLogic_CheckCycleLevel.cs create mode 100644 GameServer/Server/CallGS/Handlers/Tower/TowerLevel_EnterLevel.cs diff --git a/Common/Data/Excel/ClimbTowerTimeExcel.cs b/Common/Data/Excel/ClimbTowerTimeExcel.cs new file mode 100644 index 0000000..32a9d17 --- /dev/null +++ b/Common/Data/Excel/ClimbTowerTimeExcel.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace MikuSB.Data.Excel; + +[ResourceEntity("challenge/climbtower/climb_tower_time.json")] +public class ClimbTowerTimeExcel : ExcelResource +{ + [JsonProperty("ID")] public uint ID { get; set; } + [JsonProperty("StartTime")] public string StartTime { get; set; } = ""; + [JsonProperty("EndTime")] public string EndTime { get; set; } = ""; + + public override uint GetId() => ID; + + public override void Loaded() + { + GameData.ClimbTowerTimeData[ID] = this; + } +} diff --git a/Common/Data/Excel/TowerLevelExcel.cs b/Common/Data/Excel/TowerLevelExcel.cs new file mode 100644 index 0000000..0163f8f --- /dev/null +++ b/Common/Data/Excel/TowerLevelExcel.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace MikuSB.Data.Excel; + +[ResourceEntity("challenge/climbtower/level.json")] +public class TowerLevelExcel : ExcelResource +{ + [JsonProperty("ID")] public uint ID { get; set; } + [JsonProperty("MapID")] public uint MapID { get; set; } + [JsonProperty("FightID")] public uint FightID { get; set; } + [JsonProperty("TaskPath")] public string TaskPath { get; set; } = ""; + [JsonProperty("ConsumeVigor")] public List ConsumeVigor { get; set; } = []; + + public override uint GetId() => ID; + + public override void Loaded() + { + GameData.TowerLevelData[ID] = this; + } +} diff --git a/Common/Data/GameData.cs b/Common/Data/GameData.cs index c6e7433..81aa083 100644 --- a/Common/Data/GameData.cs +++ b/Common/Data/GameData.cs @@ -33,6 +33,8 @@ public static class GameData public static Dictionary BossPvpBossChallengeData { get; private set; } = []; public static Dictionary BossPvpBossData { get; private set; } = []; public static Dictionary BossPvpNumData { get; private set; } = []; + public static Dictionary ClimbTowerTimeData { get; private set; } = []; + public static Dictionary TowerLevelData { get; private set; } = []; public static Dictionary ProfileData { get; private set; } = []; public static Dictionary CardSkinPartsData { get; private set; } = []; public static Dictionary CallItemData { get; private set; } = []; diff --git a/GameServer/Server/CallGS/Handlers/Tower/ClimbTowerLogic_CheckCycleLevel.cs b/GameServer/Server/CallGS/Handlers/Tower/ClimbTowerLogic_CheckCycleLevel.cs new file mode 100644 index 0000000..a7e2e9c --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Tower/ClimbTowerLogic_CheckCycleLevel.cs @@ -0,0 +1,118 @@ +using MikuSB.Data; +using MikuSB.Data.Excel; +using MikuSB.Database; +using MikuSB.Database.Player; +using MikuSB.GameServer.Game.Player; +using MikuSB.Proto; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Tower; + +[CallGSApi("ClimbTowerLogic_CheckCycleLevel")] +public class ClimbTowerLogic_CheckCycleLevel : ICallGSHandler +{ + private const uint TowerGroupId = 3; + private const uint TimeSubId = 1; + + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var player = connection.Player!; + var current = ResolveCurrentCycle(GameData.ClimbTowerTimeData.Values, DateTime.Now); + if (current == null) + { + await CallGSRouter.SendScript(connection, "ClimbTowerLogic_CheckCycleLevel", "{}"); + return; + } + + var currentTimeId = GetAttr(player.Data, TowerGroupId, TimeSubId); + var sync = new NtfSyncPlayer(); + if (currentTimeId != current.ID) + { + ResetTowerAttrs(player, sync); + SetAttr(player.Data, TowerGroupId, TimeSubId, current.ID, sync, player); + DatabaseHelper.SaveDatabaseType(player.Data); + } + + await CallGSRouter.SendScript(connection, "ClimbTowerLogic_CheckCycleLevel", $$"""{"timeID":{{current.ID}}}""", sync); + } + + private static ClimbTowerTimeExcel? ResolveCurrentCycle(IEnumerable configs, DateTime now) + { + var parsed = configs + .Select(x => new + { + Config = x, + Start = ParseConfigTime(x.StartTime), + End = ParseConfigTime(x.EndTime) + }) + .Where(x => x.Start.HasValue && x.End.HasValue) + .OrderBy(x => x.Start) + .ToList(); + + var current = parsed.FirstOrDefault(x => x.Start <= now && now < x.End); + if (current != null) + return current.Config; + + var latestStarted = parsed.LastOrDefault(x => x.Start <= now); + if (latestStarted != null) + return latestStarted.Config; + + return parsed.FirstOrDefault()?.Config; + } + + private static DateTime? ParseConfigTime(string? raw) + { + if (string.IsNullOrWhiteSpace(raw)) + return null; + + var normalized = raw.Trim().Trim('[', ']'); + if (normalized.Length != 12) + return null; + + return DateTime.TryParseExact( + normalized, + "yyyyMMddHHmm", + System.Globalization.CultureInfo.InvariantCulture, + System.Globalization.DateTimeStyles.None, + out var value) + ? value + : null; + } + + private static uint GetAttr(PlayerGameData data, uint gid, uint sid) + { + return data.Attrs.FirstOrDefault(x => x.Gid == gid && x.Sid == sid)?.Val ?? 0; + } + + private static void ResetTowerAttrs(PlayerInstance player, NtfSyncPlayer sync) + { + var towerAttrs = player.Data.Attrs + .Where(x => x.Gid == TowerGroupId) + .ToList(); + + foreach (var attr in towerAttrs) + { + sync.Custom[player.ToPackedAttrKey(attr.Gid, attr.Sid)] = 0; + sync.Custom[player.ToShiftedAttrKey(attr.Gid, attr.Sid)] = 0; + } + + player.Data.Attrs.RemoveAll(x => x.Gid == TowerGroupId); + } + + private static void SetAttr(PlayerGameData data, uint gid, uint sid, uint value, NtfSyncPlayer sync, PlayerInstance player) + { + var attr = data.Attrs.FirstOrDefault(x => x.Gid == gid && x.Sid == sid); + if (attr == null) + { + attr = new PlayerAttr + { + Gid = gid, + Sid = sid + }; + data.Attrs.Add(attr); + } + + attr.Val = value; + sync.Custom[player.ToPackedAttrKey(gid, sid)] = value; + sync.Custom[player.ToShiftedAttrKey(gid, sid)] = value; + } +} diff --git a/GameServer/Server/CallGS/Handlers/Tower/TowerLevel_EnterLevel.cs b/GameServer/Server/CallGS/Handlers/Tower/TowerLevel_EnterLevel.cs new file mode 100644 index 0000000..4df6a7b --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Tower/TowerLevel_EnterLevel.cs @@ -0,0 +1,39 @@ +using MikuSB.Data; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Tower; + +[CallGSApi("TowerLevel_EnterLevel")] +public class TowerLevel_EnterLevel : ICallGSHandler +{ + private static readonly Random Random = new(); + + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var req = JsonSerializer.Deserialize(param); + if (req == null || req.LevelId == 0 || req.TeamId <= 0) + { + await CallGSRouter.SendScript(connection, "TowerLevel_EnterLevel", "{\"sErr\":\"error.BadParam\"}"); + return; + } + + if (!GameData.TowerLevelData.ContainsKey((uint)req.LevelId)) + { + await CallGSRouter.SendScript(connection, "TowerLevel_EnterLevel", "{\"sErr\":\"error.BadParam\"}"); + return; + } + + var rsp = $"{{\"nSeed\":{Random.Next(1, 1_000_000_000)}}}"; + await CallGSRouter.SendScript(connection, "TowerLevel_EnterLevel", rsp); + } +} + +internal sealed class TowerLevelEnterLevelParam +{ + [JsonPropertyName("nID")] + public int LevelId { get; set; } + + [JsonPropertyName("nTeamID")] + public int TeamId { get; set; } +}