diff --git a/GameServer/Server/CallGS/Handlers/House/House_Func/HouseArcade.cs b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseArcade.cs index 0415e1a..5d0f824 100644 --- a/GameServer/Server/CallGS/Handlers/House/House_Func/HouseArcade.cs +++ b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseArcade.cs @@ -3,39 +3,26 @@ 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; + private static readonly int[] ArcadeUnlockedGirlList = [1, 5, 13, 23, 10]; public async Task Handle(Connection connection, string param) { - var girlList = new JsonArray(); - for (int i = 1; i <= 25; i++) girlList.Add(i); + var unlockGirls = new JsonArray(); + foreach (var girlId in ArcadeUnlockedGirlList) + unlockGirls.Add(girlId); var rsp = new JsonObject { - ["FuncName"] = "ArcadeGameEnterMainUI", - ["tbUnlockGirlList"] = girlList + ["tbUnlockGirlList"] = unlockGirls, + ["FuncName"] = "ArcadeGameEnterMainUI" }; - - 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); + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Success(rsp)); } } -// ArcadeGameEnter — returns a random seed for level generation. [HouseFunc("ArcadeGameEnter")] public class ArcadeGameEnter : IHouseFuncHandler { @@ -48,50 +35,203 @@ public class ArcadeGameEnter : IHouseFuncHandler ["FuncName"] = "ArcadeGameEnter", ["nSeed"] = Random.Next(1, 1_000_000_000) }; - await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Success(rsp)); } } -// ArcadeGameSettlement — acknowledges round end; nAddExp=0 on private server. [HouseFunc("ArcadeGameSettlement")] public class ArcadeGameSettlement : IHouseFuncHandler { + private const uint HouseArcadeInfoStart = 18000; + private const uint ArcadeAttrExpOffset = 2; + private const uint ArcadeAttrEndlessScoreOffset = 4; + private const uint ArcadeAttrGirlEndlessModeStateOffset = 5; + private const uint ArcadeAttrGirlNormalModeStateOffset = 10; + private const uint ArcadeAttrGirlNormalModeStateEndOffset = 35; + private const uint ArcadeAttrConditionValStartOffset = 36; + private const uint ArcadeAttrConditionValEndOffset = 55; + private const uint ArcadeAttrPropUseStartOffset = 56; + private const uint ArcadeAttrPropUseEndOffset = 250; + private const int ArcadeGameModeNormal = 1; + private const int ArcadeGameModeEndless = 2; + public async Task Handle(Connection connection, string param) { - var rsp = new JsonObject { ["FuncName"] = "ArcadeGameSettlement", ["nAddExp"] = 0 }; - await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var player = connection.Player!; + var sync = new NtfSyncPlayer(); + var modeType = HouseJson.NumField(root, "nModeType"); + var finishRound = Math.Max(0, HouseJson.NumField(root, "nFinishRound")); + var maxScore = Math.Max(0, HouseJson.NumField(root, "nMaxScore")); + + if (root["tbConditionVal"] is JsonArray conditions) + { + foreach (var conditionNode in conditions) + { + if (conditionNode is not JsonArray condition || condition.Count < 2) continue; + var id = HouseJson.ToInt(condition[0]); + var value = Math.Max(0, HouseJson.ToInt(condition[1])); + if (id < 0) continue; + var sid = HouseArcadeInfoStart + ArcadeAttrConditionValStartOffset + (uint)id; + if (sid > HouseArcadeInfoStart + ArcadeAttrConditionValEndOffset) continue; + var prev = HouseAttr.Read(player, sid); + if ((uint)value > prev) + await HouseAttr.SetAsync(connection, sid, (uint)value, sync); + } + } + + if (root["tbPropUse"] is JsonArray propUses) + { + var slotStart = HouseArcadeInfoStart + ArcadeAttrPropUseStartOffset; + var slotEnd = HouseArcadeInfoStart + ArcadeAttrPropUseEndOffset; + var slotState = new Dictionary(); + var emptySlots = new Queue(); + for (uint sid = slotStart; sid <= slotEnd; sid++) + { + var value = HouseAttr.Read(player, sid); + if (value == 0) + { + emptySlots.Enqueue(sid); + continue; + } + + slotState[sid] = new ArcadePropUseSlot + { + Type = (byte)(value & 0xff), + Id = (byte)((value >> 8) & 0xff), + Count = (ushort)((value >> 16) & 0xffff) + }; + } + + foreach (var propUseNode in propUses) + { + if (propUseNode is not JsonArray propUse || propUse.Count < 3) continue; + var type = HouseJson.ToInt(propUse[0]) & 0xff; + var id = HouseJson.ToInt(propUse[1]) & 0xff; + var count = HouseJson.ToInt(propUse[2]); + if (count <= 0) continue; + + uint? foundSid = null; + foreach (var (sid, slot) in slotState) + { + if (slot.Type == type && slot.Id == id) + { + foundSid = sid; + break; + } + } + + if (foundSid != null) + { + var slot = slotState[foundSid.Value]; + slot.Count = (ushort)Math.Min(0xffff, slot.Count + count); + slotState[foundSid.Value] = slot; + await HouseAttr.SetAsync(connection, foundSid.Value, HouseAttr.PackArcadePropUse(slot.Type, slot.Id, slot.Count), sync); + } + else if (emptySlots.Count > 0) + { + var sid = emptySlots.Dequeue(); + var packedCount = (ushort)Math.Min(0xffff, count); + slotState[sid] = new ArcadePropUseSlot { Type = type, Id = id, Count = packedCount }; + await HouseAttr.SetAsync(connection, sid, HouseAttr.PackArcadePropUse(type, id, packedCount), sync); + } + } + } + + var girlList = root["tbGirlDataList"] as JsonArray; + var nAddExp = 0; + if (modeType == ArcadeGameModeNormal) + { + if (girlList != null) + { + foreach (var girlNode in girlList) + { + var girlId = HouseJson.ToInt(girlNode); + if (girlId <= 0) continue; + var sid = HouseArcadeInfoStart + ArcadeAttrGirlNormalModeStateOffset + (uint)girlId; + if (sid > HouseArcadeInfoStart + ArcadeAttrGirlNormalModeStateEndOffset) continue; + var prev = HouseAttr.Read(player, sid); + await HouseAttr.SetAsync(connection, sid, prev + (uint)finishRound, sync); + } + } + nAddExp = finishRound * 10; + } + else if (modeType == ArcadeGameModeEndless) + { + var scoreSid = HouseArcadeInfoStart + ArcadeAttrEndlessScoreOffset; + var prevScore = HouseAttr.Read(player, scoreSid); + if ((uint)maxScore > prevScore) + await HouseAttr.SetAsync(connection, scoreSid, (uint)maxScore, sync); + + var bitsSid = HouseArcadeInfoStart + ArcadeAttrGirlEndlessModeStateOffset; + var bits = HouseAttr.Read(player, bitsSid); + var originalBits = bits; + if (girlList != null) + { + foreach (var girlNode in girlList) + { + var girlId = HouseJson.ToInt(girlNode); + if (girlId is > 0 and < 31) + bits |= 1u << girlId; + } + } + if (bits != originalBits) + await HouseAttr.SetAsync(connection, bitsSid, bits, sync); + + nAddExp = maxScore / 10; + } + + if (nAddExp > 0) + { + var expSid = HouseArcadeInfoStart + ArcadeAttrExpOffset; + var prevExp = HouseAttr.Read(player, expSid); + await HouseAttr.SetAsync(connection, expSid, prevExp + (uint)nAddExp, sync); + } + + var rsp = new JsonObject + { + ["nAddExp"] = nAddExp, + ["FuncName"] = "ArcadeGameSettlement" + }; + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Success(rsp), sync); + } + + private struct ArcadePropUseSlot + { + public int Type { get; set; } + public int Id { get; set; } + public ushort Count { get; set; } } } -// 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()); + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Success(rsp)); } } -// 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()); + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Success(rsp)); } } -// 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()); + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Success(rsp)); } } diff --git a/GameServer/Server/CallGS/Handlers/House/House_Func/HouseBedroom.cs b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseBedroom.cs new file mode 100644 index 0000000..6a2c12c --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseBedroom.cs @@ -0,0 +1,113 @@ +using MikuSB.Proto; +using System.Text.Json.Nodes; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.House; + +[HouseFunc("GirlRegister")] +public class GirlRegister : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var girlId = HouseJson.NumField(root, "GirlId"); + var sync = new NtfSyncPlayer(); + if (girlId > 0) + await HouseAttr.SetAsync(connection, HouseAttr.GirlRoomNumSid(girlId), HouseAttr.BedroomRegisteredNoRoom, sync); + + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Synthesize(root), sync); + } +} + +[HouseFunc("SetBedroomGirlId")] +public class SetBedroomGirlId : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var bedroomId = HouseJson.NumField(root, "BedroomId"); + var girlId = HouseJson.NumField(root, "GirlId"); + var sync = new NtfSyncPlayer(); + if (bedroomId > 0 && girlId > 0) + await HouseAttr.MoveGirlIntoRoomAsync(connection, girlId, bedroomId, sync); + + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Synthesize(root), sync); + } +} + +[HouseFunc("GirlRoomChange")] +public class GirlRoomChange : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var girlId = HouseJson.NumField(root, "GirlId"); + var roomId = HouseJson.NumField(root, "RoomId"); + var oldRoomId = girlId > 0 ? (int)HouseAttr.Read(connection.Player!, HouseAttr.GirlRoomNumSid(girlId)) : 0; + var sync = new NtfSyncPlayer(); + if (girlId > 0 && roomId > 0) + await HouseAttr.MoveGirlIntoRoomAsync(connection, girlId, roomId, sync); + + var rsp = new JsonObject + { + ["FuncName"] = "GirlRoomChangeSuccess", + ["GirlId"] = girlId, + ["OldRoomId"] = oldRoomId, + ["NewRoomId"] = roomId + }; + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Success(rsp), sync); + } +} + +[HouseFunc("GirlLeaveRoom")] +public class GirlLeaveRoom : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var bedroomId = HouseJson.NumField(root, "BedroomId"); + var girlId = HouseJson.NumField(root, "GirlId"); + var sync = new NtfSyncPlayer(); + if (bedroomId > 0 && girlId > 0) + { + await HouseAttr.SetAsync(connection, HouseAttr.BedroomSlotSid(bedroomId), 0, sync); + await HouseAttr.SetAsync(connection, HouseAttr.GirlRoomNumSid(girlId), HouseAttr.BedroomRegisteredNoRoom, sync); + } + + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Synthesize(root), sync); + } +} + +[HouseFunc("ExchangeRoomGirl")] +public class ExchangeRoomGirl : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var roomId1 = HouseJson.NumField(root, "RoomId1"); + var roomId2 = HouseJson.NumField(root, "RoomId2"); + var sync = new NtfSyncPlayer(); + if (roomId1 > 0 && roomId2 > 0 && roomId1 != roomId2) + { + var slot1 = HouseAttr.BedroomSlotSid(roomId1); + var slot2 = HouseAttr.BedroomSlotSid(roomId2); + var girl1 = HouseAttr.Read(connection.Player!, slot1); + var girl2 = HouseAttr.Read(connection.Player!, slot2); + await HouseAttr.SetAsync(connection, slot1, girl2, sync); + await HouseAttr.SetAsync(connection, slot2, girl1, sync); + if (girl1 > 0) await HouseAttr.SetAsync(connection, HouseAttr.GirlRoomNumSid((int)girl1), (uint)roomId2, sync); + if (girl2 > 0) await HouseAttr.SetAsync(connection, HouseAttr.GirlRoomNumSid((int)girl2), (uint)roomId1, sync); + } + + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Synthesize(root), sync); + } +} diff --git a/GameServer/Server/CallGS/Handlers/House/House_Func/HouseFurniture.cs b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseFurniture.cs new file mode 100644 index 0000000..fefd500 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseFurniture.cs @@ -0,0 +1,29 @@ +using MikuSB.Proto; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.House; + +[HouseFunc("SetGroupFurIndex")] +public class SetGroupFurIndex : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var areaId = HouseJson.NumField(root, "AreaId"); + var groupId = HouseJson.NumField(root, "GroupId"); + var index = HouseJson.NumField(root, "Index"); + var sync = new NtfSyncPlayer(); + if (areaId > 0 && groupId is >= 1 and <= 10) + { + var sid = (uint)(areaId * 50 + 20); + var prev = HouseAttr.Read(connection.Player!, sid); + var shift = (groupId - 1) * 3; + var mask = ~(0b111u << shift); + var next = (prev & mask) | (((uint)index & 0b111u) << shift); + await HouseAttr.SetAsync(connection, sid, next, sync, sendImmediate: true); + } + + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Synthesize(root), sync); + } +} diff --git a/GameServer/Server/CallGS/Handlers/House/House_Func/HouseGirlLove.cs b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseGirlLove.cs new file mode 100644 index 0000000..4815f1d --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseGirlLove.cs @@ -0,0 +1,60 @@ +using MikuSB.Proto; +using System.Text.Json.Nodes; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.House; + +[HouseFunc("SetPlayerRingInfo")] +public class SetPlayerRingInfo : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var ringId = HouseJson.NumField(root, "RingId"); + var ringPos = HouseJson.NumField(root, "RingPos"); + var ringOffset = HouseJson.NumField(root, "RingOffset"); + var sync = new NtfSyncPlayer(); + if (ringPos is >= 1 and <= 10 && ringOffset is >= 0 and <= 2) + { + var sid = HouseAttr.PlayerRingInfoSidBase + (uint)ringPos; + var prev = HouseAttr.Read(connection.Player!, sid); + var shift = ringOffset * 10; + var mask = ~(0x3ffu << shift); + var next = (prev & mask) | (((uint)ringId & 0x3ffu) << shift); + await HouseAttr.SetAsync(connection, sid, next, sync, deleteIfZero: true, sendImmediate: true); + } + + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Synthesize(root), sync); + } +} + +[HouseFunc("ReadGirlLoveStory")] +public class ReadGirlLoveStory : IHouseFuncHandler +{ + public async Task Handle(Connection connection, string param) + { + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var girlId = HouseJson.NumField(root, "GirlId"); + var index = HouseJson.NumField(root, "Index"); + var sync = new NtfSyncPlayer(); + if (girlId > 0 && index is >= 0 and <= 30) + { + var sid = (uint)(girlId * 50 + 2); + var prev = HouseAttr.Read(connection.Player!, sid); + var next = prev | (1u << index); + await HouseAttr.SetAsync(connection, sid, next, sync, sendImmediate: true); + } + + var rsp = new JsonObject + { + ["GirlId"] = girlId, + ["Index"] = index, + ["tbReward"] = new JsonArray(), + ["FuncName"] = "ReadGirlLoveStorySuccess" + }; + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Success(rsp), sync); + } +} diff --git a/GameServer/Server/CallGS/Handlers/House/House_Func/HouseHotSpring.cs b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseHotSpring.cs new file mode 100644 index 0000000..a5612e0 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseHotSpring.cs @@ -0,0 +1,90 @@ +using MikuSB.Proto; +using System.Text.Json.Nodes; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.House; + +[HouseFunc("UnLockWaterGunGameItem")] +public class UnLockWaterGunGameItem : IHouseFuncHandler +{ + private const uint HouseHotSpringInfoStart = 15000; + private const uint WaterGunItemCollectBegin = 17; + + public async Task Handle(Connection connection, string param) + { + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var sync = new NtfSyncPlayer(); + if (root["tbItem"] is JsonArray items) + { + var touched = new Dictionary(); + foreach (var node in items) + { + var itemId = HouseJson.ToInt(node); + if (itemId is < 0 or > 60) continue; + var offset = itemId / 30; + var bit = itemId % 30; + var sid = HouseHotSpringInfoStart + WaterGunItemCollectBegin + (uint)offset; + var cur = touched.TryGetValue(sid, out var existing) + ? existing + : HouseAttr.Read(connection.Player!, sid); + touched[sid] = cur | (1u << bit); + } + + foreach (var (sid, value) in touched) + await HouseAttr.SetAsync(connection, sid, value, sync); + } + + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Synthesize(root), sync); + } +} + +[HouseFunc("RouletteEnd")] +public class RouletteEnd : IHouseFuncHandler +{ + private const uint HouseHotSpringInfoStart = 15000; + private const uint MaxRoundsInWaterGun = 16; + private const uint HasCompleteGameGuide = 19; + private const uint HotSpringGirlInfoStart = 15100; + private const uint HotSpringGirlAttrCount = 10; + private const uint HotSpringGirlHasCompleteStory = 1; + private const int RoulettePlayModePlot = 1; + private const int RoulettePlayModeEndless = 2; + private const int RouletteEndReasonSuccess = 0; + private const int RouletteTutorialGirlId = 5; + + public async Task Handle(Connection connection, string param) + { + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var modeType = HouseJson.NumField(root, "ModeType"); + var girlId = HouseJson.NumField(root, "GirlId"); + var isSuccess = HouseJson.NumField(root, "IsSuccess"); + var rounds = HouseJson.NumField(root, "Rounds"); + var sync = new NtfSyncPlayer(); + + if (isSuccess == RouletteEndReasonSuccess) + { + if (modeType == RoulettePlayModePlot && girlId > 0) + { + await HouseAttr.SetAsync( + connection, + HotSpringGirlInfoStart + (uint)(girlId * (int)HotSpringGirlAttrCount) + HotSpringGirlHasCompleteStory, + 1, + sync); + if (girlId == RouletteTutorialGirlId) + await HouseAttr.SetAsync(connection, HouseHotSpringInfoStart + HasCompleteGameGuide, 1, sync); + } + else if (modeType == RoulettePlayModeEndless && rounds > 0) + { + var sid = HouseHotSpringInfoStart + MaxRoundsInWaterGun; + var prev = HouseAttr.Read(connection.Player!, sid); + if ((uint)rounds > prev) + await HouseAttr.SetAsync(connection, sid, (uint)rounds, sync); + } + } + + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Synthesize(root), sync); + } +} diff --git a/GameServer/Server/CallGS/Handlers/House/House_Func/HouseNpc.cs b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseNpc.cs index 9ea1217..5f63347 100644 --- a/GameServer/Server/CallGS/Handlers/House/House_Func/HouseNpc.cs +++ b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseNpc.cs @@ -1,6 +1,5 @@ -using System.Text.Json; +using MikuSB.Proto; using System.Text.Json.Nodes; -using System.Text.Json.Serialization; namespace MikuSB.GameServer.Server.CallGS.Handlers.House; @@ -9,14 +8,26 @@ public class ChangeNpcSuit : IHouseFuncHandler { public async Task Handle(Connection connection, string param) { - var req = JsonSerializer.Deserialize(param); + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var npcId = HouseJson.NumField(root, "NpcId"); + var suitId = HouseJson.NumField(root, "SuitId"); + var sync = new NtfSyncPlayer(); + await HouseAttr.SetAsync(connection, (uint)(npcId * 50 + 7), (uint)Math.Max(0, suitId), sync, sendImmediate: true); + var rsp = new JsonObject { - ["FuncName"] = "ChangeNpcSuitSuccess", - ["NpcId"] = req?.NpcId ?? 0, - ["SuitId"] = req?.SuitId ?? 0 + ["NpcId"] = npcId, + ["SuitId"] = suitId, + ["npcSuit"] = new JsonObject + { + ["NpcId"] = npcId, + ["SuitId"] = suitId + }, + ["FuncName"] = "ChangeNpcSuitSuccess" }; - await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Success(rsp), sync); } } @@ -25,14 +36,45 @@ public class ChangeNpcSuitByAreaId : IHouseFuncHandler { public async Task Handle(Connection connection, string param) { - var req = JsonSerializer.Deserialize(param); - var rsp = new JsonObject + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var npcId = HouseJson.NumField(root, "NpcId"); + var areaId = HouseJson.NumField(root, "AreaId"); + var suitId = HouseJson.NumField(root, "SuitId"); + var sync = new NtfSyncPlayer(); + + if (npcId > 0 && areaId > 0) { - ["FuncName"] = "ChangeNpcSuitByAreaIdRsp", - ["NpcId"] = req?.NpcId ?? 0, - ["SuitId"] = req?.SuitId ?? 0 - }; - await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + uint[] slotSids = Enumerable.Range(24, 6).Select(i => (uint)(npcId * 50 + i)).ToArray(); + uint? chosenSid = null; + foreach (var sid in slotSids) + { + if ((HouseAttr.Read(connection.Player!, sid) & 0xffffu) == (uint)areaId) + { + chosenSid = sid; + break; + } + } + + if (chosenSid == null) + { + foreach (var sid in slotSids) + { + if (HouseAttr.Read(connection.Player!, sid) == 0) + { + chosenSid = sid; + break; + } + } + } + + chosenSid ??= slotSids[0]; + var packed = (((uint)suitId & 0xffffu) << 16) | ((uint)areaId & 0xffffu); + await HouseAttr.SetAsync(connection, chosenSid.Value, packed, sync, sendImmediate: true); + } + + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Synthesize(root), sync); } } @@ -41,19 +83,15 @@ 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()); + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var npcId = HouseJson.NumField(root, "NpcId"); + var suitId = HouseJson.NumField(root, "SuitId"); + var sync = new NtfSyncPlayer(); + if (npcId > 0) + await HouseAttr.SetAsync(connection, (uint)(npcId * 50 + 8), (uint)Math.Max(0, suitId), sync, sendImmediate: true); + + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Synthesize(root), sync); } } - -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/HouseShared.cs b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseShared.cs new file mode 100644 index 0000000..2f3195f --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseShared.cs @@ -0,0 +1,180 @@ +using MikuSB.Database.Player; +using MikuSB.GameServer.Game.Player; +using MikuSB.Proto; +using System.Text.Json.Nodes; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.House; + +internal static class HouseRequestScript +{ + internal static readonly Dictionary FuncNameMap = new() + { + ["ChangeGirlBeachSuitId"] = "ChangeGirlBeachSuitIdSuccess", + ["ChangeNpcSuitByAreaId"] = "ChangeNpcSuitByAreaIdRsp", + ["SetGroupFurIndex"] = "SetGroupFurSuccess", + ["RouletteEnd"] = "RouletteEndRsp", + ["UnLockWaterGunGameItem"] = "UnLockWaterGunGameItemRsp", + ["ChangeNpcSuit"] = "ChangeNpcSuitSuccess", + ["GiveGiftToArea"] = "GiveGiftToAreaSuccess", + ["BuyRefreshMapTimes"] = "BuyRefreshMapTimesSuccess", + ["CompleteHousePuzzleTask"] = "CompleteHousePuzzleTaskSuccess", + ["CompleteStarWishTask"] = "CompleteStarWishTaskSuccess", + ["SetPlayerRingInfo"] = "SetPlayerRingInfoSuccess", + ["ReadGirlLoveStory"] = "ReadGirlLoveStorySuccess", + ["GirlRoomChange"] = "GirlRoomChangeSuccess", + ["GirlRegister"] = "GirlRegisterSuccess" + }; + + internal static string Synthesize(JsonObject request) + { + var funcName = request["FuncName"]?.GetValue(); + var response = CreateSuccessObject(); + foreach (var (key, value) in request) + { + if (key is "FuncName" or "tblog") + continue; + + response[key] = value?.DeepClone(); + } + + if (!string.IsNullOrEmpty(funcName)) + response["FuncName"] = FuncNameMap.TryGetValue(funcName, out var renamed) ? renamed : funcName; + + return response.ToJsonString(); + } + + internal static string Success(JsonObject? extra = null) => CreateSuccessObject(extra).ToJsonString(); + + internal static JsonObject CreateSuccessObject(JsonObject? extra = null) + { + var response = new JsonObject + { + ["bSuccess"] = true, + ["nResult"] = 0 + }; + + if (extra != null) + { + foreach (var (key, value) in extra) + response[key] = value?.DeepClone(); + } + + return response; + } +} + +internal static class HouseJson +{ + internal static JsonObject? ParseObject(string json) + { + try + { + return JsonNode.Parse(json) as JsonObject; + } + catch + { + return null; + } + } + + internal static int NumField(JsonObject? obj, string name) => ToInt(obj?[name]); + + internal static int ToInt(JsonNode? node) + { + if (node == null) return 0; + try + { + if (node is JsonValue value) + { + if (value.TryGetValue(out var i)) return i; + if (value.TryGetValue(out var u)) return unchecked((int)u); + if (value.TryGetValue(out var l)) return unchecked((int)l); + if (value.TryGetValue(out var s) && int.TryParse(s, out var parsed)) return parsed; + } + } + catch + { + } + return 0; + } +} + +internal static class HouseAttr +{ + internal const uint Gid = 101; + internal const uint BedroomStartSid = 2550; + internal const uint BedroomRegisteredNoRoom = 100; + internal const uint PlayerRingInfoSidBase = 3174; + + internal static uint Read(PlayerInstance player, uint sid) + { + var attr = player.Data.Attrs.FirstOrDefault(x => x.Gid == Gid && x.Sid == sid); + return attr?.Val ?? 0; + } + + internal static async Task SetAsync( + Connection connection, + uint sid, + uint value, + NtfSyncPlayer sync, + bool deleteIfZero = false, + bool sendImmediate = false) + { + var player = connection.Player!; + var attr = player.Data.Attrs.FirstOrDefault(x => x.Gid == Gid && x.Sid == sid); + if (value == 0 && deleteIfZero) + { + if (attr != null) + player.Data.Attrs.Remove(attr); + } + else + { + if (attr == null) + { + attr = new PlayerAttr { Gid = Gid, Sid = sid }; + player.Data.Attrs.Add(attr); + } + attr.Val = value; + } + + sync.Custom[player.ToPackedAttrKey(Gid, sid)] = value; + sync.Custom[player.ToShiftedAttrKey(Gid, sid)] = value; + if (sendImmediate) + await player.SendPacket(CmdIds.NtfSetAttr, new NtfSetAttr { Gid = Gid, Sid = sid, Val = value }); + } + + internal static uint BedroomSlotSid(int roomId) => BedroomStartSid + (uint)roomId; + + internal static uint GirlRoomNumSid(int girlId) => (uint)(girlId * 50 + 1); + + internal static async Task MoveGirlIntoRoomAsync(Connection connection, int girlId, int roomId, NtfSyncPlayer sync) + { + var player = connection.Player!; + var oldRoom = (int)Read(player, GirlRoomNumSid(girlId)); + var targetSlotSid = BedroomSlotSid(roomId); + var oldSlotSid = oldRoom is >= 1 and < 100 ? BedroomSlotSid(oldRoom) : 0; + var occupant = Read(player, targetSlotSid); + + await SetAsync(connection, targetSlotSid, (uint)girlId, sync); + await SetAsync(connection, GirlRoomNumSid(girlId), (uint)roomId, sync); + + if (oldRoom is >= 1 and < 100 && oldRoom != roomId) + await SetAsync(connection, oldSlotSid, 0, sync); + + if (occupant > 0 && occupant != (uint)girlId) + { + if (oldRoom is >= 1 and < 100 && oldRoom != roomId) + { + await SetAsync(connection, oldSlotSid, occupant, sync); + await SetAsync(connection, GirlRoomNumSid((int)occupant), (uint)oldRoom, sync); + } + else + { + await SetAsync(connection, GirlRoomNumSid((int)occupant), BedroomRegisteredNoRoom, sync); + } + } + } + + internal static uint PackArcadePropUse(int type, int id, ushort count) => + (((uint)count & 0xffffu) << 16) | (((uint)id & 0xffu) << 8) | ((uint)type & 0xffu); +} diff --git a/GameServer/Server/CallGS/Handlers/House/House_Func/HouseThrow.cs b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseThrow.cs index 72cb95b..c3b0ce8 100644 --- a/GameServer/Server/CallGS/Handlers/House/House_Func/HouseThrow.cs +++ b/GameServer/Server/CallGS/Handlers/House/House_Func/HouseThrow.cs @@ -1,8 +1,8 @@ +using MikuSB.Proto; 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 { @@ -13,21 +13,28 @@ public class ThrowGameEnterMainUI : IHouseFuncHandler ["FuncName"] = "GameEnterMainUI", ["tblockGirlList"] = new JsonArray() }; - await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Success(rsp)); } } [HouseFunc("ThrowGameTutorialFinish")] public class ThrowGameTutorialFinish : IHouseFuncHandler { + private const uint HouseBeachInfoStart = 17000; + private const uint ThrowGameTutorialOffset = 10; + public async Task Handle(Connection connection, string param) { - var rsp = new JsonObject { ["FuncName"] = "ThrowGameTutorialFinish" }; - await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var index = Math.Max(0, HouseJson.NumField(root, "Index")); + var sync = new NtfSyncPlayer(); + await HouseAttr.SetAsync(connection, HouseBeachInfoStart + ThrowGameTutorialOffset + (uint)index, 1, sync); + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Synthesize(root), sync); } } -// ThrowGameEnter — returns nSeed for level generation. [HouseFunc("ThrowGameEnter")] public class ThrowGameEnter : IHouseFuncHandler { @@ -40,18 +47,52 @@ public class ThrowGameEnter : IHouseFuncHandler ["FuncName"] = "ThrowGameEnter", ["nSeed"] = Random.Next(1, 1_000_000_000) }; - await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Success(rsp)); } } -// ThrowGameSettlement — nAddExp=0 on private server. [HouseFunc("ThrowGameSettlement")] public class ThrowGameSettlement : IHouseFuncHandler { + private const uint HouseBeachInfoStart = 17000; + private const uint ThrowGameChallengePointsOffset = 2; + private const uint ThrowGameUnlockDrinkStartOffset = 50; + private const int ThrowGameModeChallenge = 2; + public async Task Handle(Connection connection, string param) { - var rsp = new JsonObject { ["FuncName"] = "ThrowGameSettlement", ["nAddExp"] = 0 }; - await CallGSRouter.SendScript(connection, "House_Request", rsp.ToJsonString()); + var root = HouseJson.ParseObject(param); + if (root == null) return; + + var modeType = HouseJson.NumField(root, "nModeType"); + var score = Math.Max(0, HouseJson.NumField(root, "nScore")); + var sync = new NtfSyncPlayer(); + if (root["tbUnlockDrink"] is JsonObject unlockDrink && unlockDrink["nDrinkIDs"] is JsonArray drinks) + { + foreach (var drinkNode in drinks) + { + var drinkId = HouseJson.ToInt(drinkNode); + if (drinkId <= 0) continue; + var sid = HouseBeachInfoStart + ThrowGameUnlockDrinkStartOffset + (uint)drinkId; + var prev = HouseAttr.Read(connection.Player!, sid); + await HouseAttr.SetAsync(connection, sid, prev + 1, sync); + } + } + + if (modeType == ThrowGameModeChallenge && score > 0) + { + var sid = HouseBeachInfoStart + ThrowGameChallengePointsOffset; + var prev = HouseAttr.Read(connection.Player!, sid); + if ((uint)score > prev) + await HouseAttr.SetAsync(connection, sid, (uint)score, sync); + } + + var rsp = new JsonObject + { + ["nAddExp"] = score, + ["FuncName"] = "ThrowGameSettlement" + }; + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Success(rsp), sync); } } @@ -61,7 +102,7 @@ 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()); + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Success(rsp)); } } @@ -71,6 +112,6 @@ 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()); + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Success(rsp)); } } diff --git a/GameServer/Server/CallGS/Handlers/House/House_Request.cs b/GameServer/Server/CallGS/Handlers/House/House_Request.cs index 1e54582..a76e343 100644 --- a/GameServer/Server/CallGS/Handlers/House/House_Request.cs +++ b/GameServer/Server/CallGS/Handlers/House/House_Request.cs @@ -1,6 +1,5 @@ using System.Reflection; using System.Text.Json; -using System.Text.Json.Nodes; using System.Text.Json.Serialization; namespace MikuSB.GameServer.Server.CallGS.Handlers.House; @@ -30,8 +29,9 @@ public class House_Request : ICallGSHandler return; } - var err = new JsonObject { ["FuncName"] = req.FuncName, ["sErr"] = "error.NotImplemented" }; - await CallGSRouter.SendScript(connection, "House_Request", err.ToJsonString()); + var root = HouseJson.ParseObject(param); + if (root == null) return; + await CallGSRouter.SendScript(connection, "House_Request", HouseRequestScript.Synthesize(root)); } }