diff --git a/GameServer/Server/CallGS/Handlers/House/House_Func/HouseArcade.cs b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseArcade.cs new file mode 100644 index 0000000..0415e1a --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseArcade.cs @@ -0,0 +1,97 @@ +using MikuSB.Proto; +using System.Text.Json.Nodes; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.House; + +// ArcadeGameEnterMainUI +// Returns all girl IDs (1-25) as unlocked, and syncs TeachMode + EndlessMode attrs. +[HouseFunc("ArcadeGameEnterMainUI")] +public class ArcadeGameEnterMainUI : IHouseFuncHandler +{ + private const uint ArcadeGid = 101; + private const uint EndlessModeStateSid = 18000 + 5; + private const uint TeachModeConditionSid = 18000 + 36 + 8; + + public async Task Handle(Connection connection, string param) + { + var girlList = new JsonArray(); + for (int i = 1; i <= 25; i++) girlList.Add(i); + + var rsp = new JsonObject + { + ["FuncName"] = "ArcadeGameEnterMainUI", + ["tbUnlockGirlList"] = girlList + }; + + var player = connection.Player!; + var sync = new NtfSyncPlayer(); + sync.Custom[player.ToPackedAttrKey(ArcadeGid, TeachModeConditionSid)] = 1; + sync.Custom[player.ToShiftedAttrKey(ArcadeGid, TeachModeConditionSid)] = 1; + const uint endlessAllUnlocked = 0x3FFFFFE; + sync.Custom[player.ToPackedAttrKey(ArcadeGid, EndlessModeStateSid)] = endlessAllUnlocked; + sync.Custom[player.ToShiftedAttrKey(ArcadeGid, EndlessModeStateSid)] = endlessAllUnlocked; + + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString(), sync); + } +} + +// ArcadeGameEnter — returns a random seed for level generation. +[HouseFunc("ArcadeGameEnter")] +public class ArcadeGameEnter : IHouseFuncHandler +{ + private static readonly Random Random = new(); + + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject + { + ["FuncName"] = "ArcadeGameEnter", + ["nSeed"] = Random.Next(1, 1_000_000_000) + }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +// ArcadeGameSettlement — acknowledges round end; nAddExp=0 on private server. +[HouseFunc("ArcadeGameSettlement")] +public class ArcadeGameSettlement : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject { ["FuncName"] = "ArcadeGameSettlement", ["nAddExp"] = 0 }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +// ArcadeGameLogSettlement — acknowledges log upload (no client data required). +[HouseFunc("ArcadeGameLogSettlement")] +public class ArcadeGameLogSettlement : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject { ["FuncName"] = "ArcadeGameLogSettlement" }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +// ArcadeGameGetLevelReward — UI refresh only on client side. +[HouseFunc("ArcadeGameGetLevelReward")] +public class ArcadeGameGetLevelReward : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject { ["FuncName"] = "ArcadeGameGetLevelReward" }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +// ArcadeGameGetAchReward — UI refresh only on client side. +[HouseFunc("ArcadeGameGetAchReward")] +public class ArcadeGameGetAchReward : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject { ["FuncName"] = "ArcadeGameGetAchReward" }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} diff --git a/GameServer/Server/CallGS/Handlers/House/House_Func/HouseNpc.cs b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseNpc.cs new file mode 100644 index 0000000..9ea1217 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseNpc.cs @@ -0,0 +1,59 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.House; + +[HouseFunc("ChangeNpcSuit")] +public class ChangeNpcSuit : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var req = JsonSerializer.Deserialize(param); + var rsp = new JsonObject + { + ["FuncName"] = "ChangeNpcSuitSuccess", + ["NpcId"] = req?.NpcId ?? 0, + ["SuitId"] = req?.SuitId ?? 0 + }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +[HouseFunc("ChangeNpcSuitByAreaId")] +public class ChangeNpcSuitByAreaId : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var req = JsonSerializer.Deserialize(param); + var rsp = new JsonObject + { + ["FuncName"] = "ChangeNpcSuitByAreaIdRsp", + ["NpcId"] = req?.NpcId ?? 0, + ["SuitId"] = req?.SuitId ?? 0 + }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +[HouseFunc("ChangeGirlBeachSuitId")] +public class ChangeGirlBeachSuitId : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var req = JsonSerializer.Deserialize(param); + var rsp = new JsonObject + { + ["FuncName"] = "ChangeGirlBeachSuitIdSuccess", + ["NpcId"] = req?.NpcId ?? 0, + ["SuitId"] = req?.SuitId ?? 0 + }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +internal sealed class NpcSuitParam +{ + [JsonPropertyName("NpcId")] public int NpcId { get; set; } + [JsonPropertyName("SuitId")] public int SuitId { get; set; } +} diff --git a/GameServer/Server/CallGS/Handlers/House/House_Func/HousePub.cs b/GameServer/Server/CallGS/Handlers/House/House_Func/HousePub.cs new file mode 100644 index 0000000..a4d08f2 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/House/House_Func/HousePub.cs @@ -0,0 +1,74 @@ +using System.Text.Json.Nodes; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.House; + +// PubGameEnter — returns nSeed for client-side game initialization. +[HouseFunc("PubGameEnter")] +public class PubGameEnter : IHouseFuncHandler +{ + private static readonly Random Random = new(); + + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject + { + ["FuncName"] = "PubGameEnter", + ["nSeed"] = Random.Next(1, 1_000_000_000), + ["nModeType"] = 1, + ["bIsGuide"] = false, + ["bHasTry"] = false + }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +[HouseFunc("PubGameMulExit")] +public class PubGameMulExit : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject { ["FuncName"] = "PubGameMulExit" }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +// PubGameSettlement — nAddExp=0 on private server. +[HouseFunc("PubGameSettlement")] +public class PubGameSettlement : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject { ["FuncName"] = "PubGameSettlement", ["nAddExp"] = 0 }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +[HouseFunc("PubGameGetReward")] +public class PubGameGetReward : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject { ["FuncName"] = "PubGameGetReward" }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +[HouseFunc("PubGameGetAchReward")] +public class PubGameGetAchReward : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject { ["FuncName"] = "PubGameGetAchReward" }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +[HouseFunc("PubGameAchievementFinish")] +public class PubGameAchievementFinish : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject { ["FuncName"] = "PubGameAchievementFinish" }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} diff --git a/GameServer/Server/CallGS/Handlers/House/House_Func/HouseThrow.cs b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseThrow.cs new file mode 100644 index 0000000..72cb95b --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseThrow.cs @@ -0,0 +1,76 @@ +using System.Text.Json.Nodes; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.House; + +// GameEnterMainUI (Throw) — tblockGirlList empty = no blocked girls. +[HouseFunc("GameEnterMainUI")] +public class ThrowGameEnterMainUI : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject + { + ["FuncName"] = "GameEnterMainUI", + ["tblockGirlList"] = new JsonArray() + }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +[HouseFunc("ThrowGameTutorialFinish")] +public class ThrowGameTutorialFinish : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject { ["FuncName"] = "ThrowGameTutorialFinish" }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +// ThrowGameEnter — returns nSeed for level generation. +[HouseFunc("ThrowGameEnter")] +public class ThrowGameEnter : IHouseFuncHandler +{ + private static readonly Random Random = new(); + + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject + { + ["FuncName"] = "ThrowGameEnter", + ["nSeed"] = Random.Next(1, 1_000_000_000) + }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +// ThrowGameSettlement — nAddExp=0 on private server. +[HouseFunc("ThrowGameSettlement")] +public class ThrowGameSettlement : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject { ["FuncName"] = "ThrowGameSettlement", ["nAddExp"] = 0 }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +[HouseFunc("ThrowGameGetLevelReward")] +public class ThrowGameGetLevelReward : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject { ["FuncName"] = "ThrowGameGetLevelReward" }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} + +[HouseFunc("ThrowGameGetAchReward")] +public class ThrowGameGetAchReward : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var rsp = new JsonObject { ["FuncName"] = "ThrowGameGetAchReward" }; + await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + } +} diff --git a/GameServer/Server/CallGS/Handlers/House/House_Func/IHouseFuncHandler.cs b/GameServer/Server/CallGS/Handlers/House/House_Func/IHouseFuncHandler.cs new file mode 100644 index 0000000..b275fec --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/House/House_Func/IHouseFuncHandler.cs @@ -0,0 +1,12 @@ +namespace MikuSB.GameServer.Server.CallGS.Handlers.House; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public class HouseFuncAttribute(string funcName) : Attribute +{ + public string FuncName { get; } = funcName; +} + +public interface IHouseFuncHandler +{ + Task Handle(Connection connection, string param); +} diff --git a/GameServer/Server/CallGS/Handlers/House/House_Request.cs b/GameServer/Server/CallGS/Handlers/House/House_Request.cs index d3971be..1e54582 100644 --- a/GameServer/Server/CallGS/Handlers/House/House_Request.cs +++ b/GameServer/Server/CallGS/Handlers/House/House_Request.cs @@ -1,4 +1,4 @@ -using MikuSB.Proto; +using System.Reflection; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -8,93 +8,30 @@ namespace MikuSB.GameServer.Server.CallGS.Handlers.House; [CallGSApi("House_Request")] public class House_Request : ICallGSHandler { - private static readonly Random Random = new(); + private static readonly Dictionary Handlers = []; - // GID for all house/arcade attributes - private const uint ArcadeGid = 101; - // HouseStorage.HouseArcadeStart = 18000 - // Attr_GirlEndlessModeState = 5 → SID 18005 - // ConditionType.TeachMode = 8, Attr_ConditionValStart = 36 → SID 18000+36+8 = 18044 - private const uint EndlessModeStateSid = 18000 + 5; - private const uint TeachModeConditionSid = 18000 + 36 + 8; // = 18044 + static House_Request() + { + foreach (var type in Assembly.GetExecutingAssembly().GetTypes()) + { + foreach (var attr in type.GetCustomAttributes()) + Handlers[attr.FuncName] = (IHouseFuncHandler)Activator.CreateInstance(type)!; + } + } public async Task Handle(Connection connection, string param, ushort seqNo) { var req = JsonSerializer.Deserialize(param); if (req?.FuncName == null) return; - switch (req.FuncName) + if (Handlers.TryGetValue(req.FuncName, out var handler)) { - case "ArcadeGameEnterMainUI": - await HandleArcadeGameEnterMainUI(connection); - break; - case "ArcadeGameEnter": - await HandleArcadeGameEnter(connection); - break; - case "ArcadeGameSettlement": - await HandleArcadeGameSettlement(connection); - break; - case "ArcadeGameLogSettlement": - await HandleArcadeGameLogSettlement(connection); - break; - default: - var err = new JsonObject { ["FuncName"] = req.FuncName, ["sErr"] = "error.NotImplemented" }; - await CallGSRouter.SendScript(connection, "House_Request", err.ToJsonString()); - break; + await handler.Handle(connection, param); + return; } - } - private static async Task HandleArcadeGameEnterMainUI(Connection connection) - { - var girlList = new JsonArray(); - for (int i = 1; i <= 25; i++) - girlList.Add(i); - - var rsp = new JsonObject - { - ["FuncName"] = "ArcadeGameEnterMainUI", - ["tbUnlockGirlList"] = girlList - }; - - var player = connection.Player!; - var sync = new NtfSyncPlayer(); - - sync.Custom[player.ToPackedAttrKey(ArcadeGid, TeachModeConditionSid)] = 1; - sync.Custom[player.ToShiftedAttrKey(ArcadeGid, TeachModeConditionSid)] = 1; - - // Bits 1-25 set → all girls have endless mode unlocked - const uint endlessAllUnlocked = 0x3FFFFFE; // bits 1-25 - sync.Custom[player.ToPackedAttrKey(ArcadeGid, EndlessModeStateSid)] = endlessAllUnlocked; - sync.Custom[player.ToShiftedAttrKey(ArcadeGid, EndlessModeStateSid)] = endlessAllUnlocked; - - await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString(), sync); - } - - private static async Task HandleArcadeGameEnter(Connection connection) - { - var seed = Random.Next(1, 1_000_000_000); - var rsp = new JsonObject - { - ["FuncName"] = "ArcadeGameEnter", - ["nSeed"] = seed - }; - await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); - } - - private static async Task HandleArcadeGameSettlement(Connection connection) - { - var rsp = new JsonObject - { - ["FuncName"] = "ArcadeGameSettlement", - ["nAddExp"] = 0 - }; - await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); - } - - private static async Task HandleArcadeGameLogSettlement(Connection connection) - { - var rsp = new JsonObject { ["FuncName"] = "ArcadeGameLogSettlement" }; - await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + var err = new JsonObject { ["FuncName"] = req.FuncName, ["sErr"] = "error.NotImplemented" }; + await CallGSRouter.SendScript(connection, "House_Request", err.ToJsonString()); } }