diff --git a/Common/Data/Excel/TowerEventLevelExcel.cs b/Common/Data/Excel/TowerEventLevelExcel.cs new file mode 100644 index 0000000..4e9218f --- /dev/null +++ b/Common/Data/Excel/TowerEventLevelExcel.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace MikuSB.Data.Excel; + +[ResourceEntity("challenge/tower_event/level.json")] +public class TowerEventLevelExcel : 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.TowerEventLevelData[ID] = this; + } +} diff --git a/Common/Data/GameData.cs b/Common/Data/GameData.cs index a1f04af..c8e7b1e 100644 --- a/Common/Data/GameData.cs +++ b/Common/Data/GameData.cs @@ -38,6 +38,7 @@ public static class GameData public static Dictionary> ClimbTowerAwardData { get; private set; } = []; public static Dictionary ClimbTowerLevelOrderData { get; private set; } = []; public static Dictionary TowerLevelData { get; private set; } = []; + public static Dictionary TowerEventLevelData { get; private set; } = []; public static Dictionary OtherItemData { get; private set; } = []; public static Dictionary ProfileData { get; private set; } = []; public static Dictionary CardSkinPartsData { get; private set; } = []; diff --git a/GameServer/Server/CallGS/Handlers/Chapter/Chapter_DealLevelSettlement.cs b/GameServer/Server/CallGS/Handlers/Chapter/Chapter_DealLevelSettlement.cs index ab53260..50be564 100644 --- a/GameServer/Server/CallGS/Handlers/Chapter/Chapter_DealLevelSettlement.cs +++ b/GameServer/Server/CallGS/Handlers/Chapter/Chapter_DealLevelSettlement.cs @@ -65,6 +65,13 @@ public class Chapter_DealLevelSettlement : ICallGSHandler return response; } + if (string.Equals(sCmd, "TowerEventChapter_LevelSettlement", StringComparison.Ordinal)) + { + var (response, sync) = TowerEventChapter_LevelSettlement.HandleSettlement(connection.Player!, tbParam); + extraSync = sync; + return response; + } + return tbParam?.DeepClone() ?? new JsonObject(); } diff --git a/GameServer/Server/CallGS/Handlers/Tower/TowerEventChapter_EnterLevel.cs b/GameServer/Server/CallGS/Handlers/Tower/TowerEventChapter_EnterLevel.cs new file mode 100644 index 0000000..1909c4d --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Tower/TowerEventChapter_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("TowerEventChapter_EnterLevel")] +public class TowerEventChapter_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, "TowerEventChapter_EnterLevel", "{\"sErr\":\"error.BadParam\"}"); + return; + } + + if (!GameData.TowerEventLevelData.ContainsKey((uint)req.LevelId)) + { + await CallGSRouter.SendScript(connection, "TowerEventChapter_EnterLevel", "{\"sErr\":\"error.BadParam\"}"); + return; + } + + var rsp = $"{{\"nSeed\":{Random.Next(1, 1_000_000_000)}}}"; + await CallGSRouter.SendScript(connection, "TowerEventChapter_EnterLevel", rsp); + } +} + +internal sealed class TowerEventEnterLevelParam +{ + [JsonPropertyName("nID")] + public int LevelId { get; set; } + + [JsonPropertyName("nTeamID")] + public int TeamId { get; set; } +} diff --git a/GameServer/Server/CallGS/Handlers/Tower/TowerEventChapter_LevelSettlement.cs b/GameServer/Server/CallGS/Handlers/Tower/TowerEventChapter_LevelSettlement.cs new file mode 100644 index 0000000..bb0de54 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Tower/TowerEventChapter_LevelSettlement.cs @@ -0,0 +1,82 @@ +using MikuSB.Database; +using MikuSB.Database.Player; +using MikuSB.GameServer.Game.Player; +using MikuSB.Proto; +using MikuSB.Util; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Tower; + +[CallGSApi("TowerEventChapter_LevelSettlement")] +public class TowerEventChapter_LevelSettlement : ICallGSHandler +{ + private const uint LevelStateGroupId = 21; + private const uint LaunchPassGroupId = 22; + private const uint PassedFlagMask = (1u << 8) | 0b111u; + private static readonly Logger Logger = new("TowerEvent"); + + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var (response, sync) = HandleSettlement(connection.Player!, JsonNode.Parse(param)); + await CallGSRouter.SendScript(connection, "TowerEventChapter_LevelSettlement", response.ToJsonString(), sync); + } + + public static (JsonNode Response, NtfSyncPlayer Sync) HandleSettlement(PlayerInstance player, JsonNode? tbParam) + { + var req = tbParam?.Deserialize(); + if (req == null || req.LevelId == 0 || req.ChapterId == 0) + { + Logger.Error($"Invalid tower event settlement payload: {tbParam?.ToJsonString() ?? "null"}"); + return (new JsonObject { ["sErr"] = "error.BadParam" }, new NtfSyncPlayer()); + } + + var sync = new NtfSyncPlayer(); + + var levelStateAttr = GetOrCreateAttr(player.Data, LevelStateGroupId, (uint)req.LevelId); + levelStateAttr.Val |= PassedFlagMask; + SyncAttr(sync, player, levelStateAttr); + + var passAttr = GetOrCreateAttr(player.Data, LaunchPassGroupId, (uint)req.LevelId); + passAttr.Val = Math.Max(1u, passAttr.Val + 1); + SyncAttr(sync, player, passAttr); + + Logger.Info( + $"TowerEvent settlement saved. uid={player.Uid} chapterId={req.ChapterId} levelId={req.LevelId} " + + $"levelStateVal={levelStateAttr.Val} passVal={passAttr.Val}"); + + DatabaseHelper.SaveDatabaseType(player.Data); + return (new JsonObject(), sync); + } + + private static PlayerAttr GetOrCreateAttr(PlayerGameData data, uint gid, uint sid) + { + var attr = data.Attrs.FirstOrDefault(x => x.Gid == gid && x.Sid == sid); + if (attr != null) + return attr; + + attr = new PlayerAttr + { + Gid = gid, + Sid = sid + }; + data.Attrs.Add(attr); + return attr; + } + + private static void SyncAttr(NtfSyncPlayer sync, PlayerInstance player, PlayerAttr attr) + { + sync.Custom[player.ToPackedAttrKey(attr.Gid, attr.Sid)] = attr.Val; + sync.Custom[player.ToShiftedAttrKey(attr.Gid, attr.Sid)] = attr.Val; + } +} + +internal sealed class TowerEventSettlementParam +{ + [JsonPropertyName("nID")] + public int LevelId { get; set; } + + [JsonPropertyName("nChapterID")] + public int ChapterId { get; set; } +}