From a9b57fc1b72ec4032f1ea22668d3a6cc8db4dce4 Mon Sep 17 00:00:00 2001 From: Kei-Luna Date: Wed, 27 May 2026 06:09:29 +0900 Subject: [PATCH] MoneySync --- Common/Data/Excel/OtherItemExcel.cs | 9 ++ GameServer/Game/Player/PlayerInstance.cs | 27 +++- .../CallGS/Handlers/Misc/Adjust_Record.cs | 59 ++++++++ .../CallGS/Handlers/Shop/IBLogic_BuyGoods.cs | 132 +++++++++++++++++- .../Packet/Send/Misc/PacketNtfCallScript.cs | 2 + 5 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 GameServer/Server/CallGS/Handlers/Misc/Adjust_Record.cs diff --git a/Common/Data/Excel/OtherItemExcel.cs b/Common/Data/Excel/OtherItemExcel.cs index 685c0a1..2db7076 100644 --- a/Common/Data/Excel/OtherItemExcel.cs +++ b/Common/Data/Excel/OtherItemExcel.cs @@ -10,8 +10,17 @@ public class OtherItemExcel : ExcelResource public uint Detail { get; set; } public uint Particular { get; set; } public uint Level { get; set; } + public string LuaType { get; set; } = ""; + [JsonProperty("UseMode")] public JToken? UseModeRaw { get; set; } + [JsonProperty("Param1")] public JToken? Param1Raw { get; set; } [JsonProperty("GMnum")] public JToken? GMnumRaw { get; set; } + [JsonIgnore] + public uint UseMode => ReadUInt(UseModeRaw); + + [JsonIgnore] + public uint Param1 => ReadUInt(Param1Raw); + [JsonIgnore] public uint GMnum => ReadUInt(GMnumRaw); diff --git a/GameServer/Game/Player/PlayerInstance.cs b/GameServer/Game/Player/PlayerInstance.cs index 0269f3f..811ad59 100644 --- a/GameServer/Game/Player/PlayerInstance.cs +++ b/GameServer/Game/Player/PlayerInstance.cs @@ -205,6 +205,8 @@ public class PlayerInstance(PlayerGameData data) Pid = (ulong)Data.Uid, Account = displayName, Provider = displayName, + Channel = "gm", + Subchannel = "gm", Name = displayName, Level = Data.Level, Sex = Data.Gender, @@ -242,6 +244,11 @@ public class PlayerInstance(PlayerGameData data) proto.StrAttrs[ToShiftedAttrKey(x.Gid, x.Sid)] = x.Val; } + foreach (var (key, value) in BuildMoneySync()) + { + proto.Money[key] = value; + } + proto.ShowItems.AddRange(Data.ShowItems); return proto; @@ -295,6 +302,24 @@ public class PlayerInstance(PlayerGameData data) return (gid << 16) | sid; } + public Dictionary BuildMoneySync() + { + var currentMoney = (int)Math.Min(int.MaxValue, GetAttrValue(1, 3)); + var sync = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["."] = currentMoney, + ["gm.gm"] = currentMoney, + ["jinshan.jinshan"] = currentMoney, + ["pc_jinshan.pc_jinshan"] = currentMoney + }; + return sync; + } + + private uint GetAttrValue(uint gid, uint sid) + { + return Data.Attrs.FirstOrDefault(x => x.Gid == gid && x.Sid == sid)?.Val ?? 0; + } + public void BuildPlayerAttr(bool additional = false) { var bootstrapAttrs = BuildLobbyBootstrapAttrs().ToList(); @@ -445,4 +470,4 @@ public class PlayerInstance(PlayerGameData data) yield return (132, 1, 0); } #endregion -} \ No newline at end of file +} diff --git a/GameServer/Server/CallGS/Handlers/Misc/Adjust_Record.cs b/GameServer/Server/CallGS/Handlers/Misc/Adjust_Record.cs new file mode 100644 index 0000000..e14d2ae --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Misc/Adjust_Record.cs @@ -0,0 +1,59 @@ +using MikuSB.Database; +using MikuSB.Database.Player; +using MikuSB.GameServer.Game.Player; +using MikuSB.Proto; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Misc; + +[CallGSApi("Adjust_Record")] +public class Adjust_Record : ICallGSHandler +{ + private const uint GroupId = 107; + + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var req = JsonSerializer.Deserialize(param); + if (req == null || req.Type == 0) + { + await CallGSRouter.SendScript(connection, "Adjust_Record", "null"); + return; + } + + var player = connection.Player!; + var sync = new NtfSyncPlayer(); + var attr = GetOrCreateAttr(player, req.Type); + + if (attr.Val == 0) + { + attr.Val = 1; + sync.Custom[player.ToPackedAttrKey(GroupId, req.Type)] = 1; + sync.Custom[player.ToShiftedAttrKey(GroupId, req.Type)] = 1; + DatabaseHelper.SaveDatabaseType(player.Data); + } + + await CallGSRouter.SendScript(connection, "Adjust_Record", "null", sync); + } + + private static PlayerAttr GetOrCreateAttr(PlayerInstance player, uint sid) + { + var attr = player.Data.Attrs.FirstOrDefault(x => x.Gid == GroupId && x.Sid == sid); + if (attr != null) + return attr; + + attr = new PlayerAttr + { + Gid = GroupId, + Sid = sid + }; + player.Data.Attrs.Add(attr); + return attr; + } +} + +internal sealed class AdjustRecordParam +{ + [JsonPropertyName("nType")] + public uint Type { get; set; } +} diff --git a/GameServer/Server/CallGS/Handlers/Shop/IBLogic_BuyGoods.cs b/GameServer/Server/CallGS/Handlers/Shop/IBLogic_BuyGoods.cs index d9c25d9..d3c091a 100644 --- a/GameServer/Server/CallGS/Handlers/Shop/IBLogic_BuyGoods.cs +++ b/GameServer/Server/CallGS/Handlers/Shop/IBLogic_BuyGoods.cs @@ -6,6 +6,7 @@ using MikuSB.Database.Player; using MikuSB.Enums.Item; using MikuSB.GameServer.Game.Player; using MikuSB.Proto; +using System.Globalization; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -17,11 +18,21 @@ public class IBLogic_BuyGoods : ICallGSHandler { private const uint BuyGroupId = 26; private const uint RedGroupId = 113; + private const uint CashGroupId = 1; + private const uint BattlePassGroupId = 25; + private const uint BattlePassCurIdSid = 1; + private const uint BattlePassStatusSid = 2; public async Task Handle(Connection connection, string param, ushort seqNo) { var req = JsonSerializer.Deserialize(param); var player = connection.Player!; + if (req?.Type == 3 && req.GoodsId > 0 && req.Count > 0) + { + await HandleBattlePassPurchase(connection, player, req); + return; + } + if (req == null || req.GoodsId == 0 || req.Count == 0 || @@ -93,6 +104,46 @@ public class IBLogic_BuyGoods : ICallGSHandler await CallGSRouter.SendScript(connection, "IBLogic_BuyGoods", rsp.ToJsonString(), sync); } + private static async Task HandleBattlePassPurchase(Connection connection, PlayerInstance player, IbBuyGoodsParam req) + { + var sync = new NtfSyncPlayer(); + var battlePassId = ResolveCurrentBattlePassId(); + if (battlePassId > 0) + { + var curIdAttr = GetOrCreateAttr(player, BattlePassGroupId, BattlePassCurIdSid); + curIdAttr.Val = battlePassId; + SyncAttr(player, sync, curIdAttr); + } + + var statusAttr = GetOrCreateAttr(player, BattlePassGroupId, BattlePassStatusSid); + if (statusAttr.Val < 2) + { + statusAttr.Val = 2; + SyncAttr(player, sync, statusAttr); + } + + var buyCountAttr = GetOrCreateAttr(player, BuyGroupId, req.GoodsId); + buyCountAttr.Val += req.Count; + SyncAttr(player, sync, buyCountAttr); + + var redAttr = GetOrCreateAttr(player, RedGroupId, req.GoodsId); + if (redAttr.Val == 0) + { + redAttr.Val = 1; + SyncAttr(player, sync, redAttr); + } + + DatabaseHelper.SaveDatabaseType(player.Data); + + var rsp = new JsonObject + { + ["nGoodsId"] = (int)req.GoodsId, + ["tbGoods"] = new JsonArray() + }; + + await CallGSRouter.SendScript(connection, "IBLogic_BuyGoods", rsp.ToJsonString(), sync); + } + private static List> BuildRewardItems(IbGoodsExcel goods, IbBuyGoodsParam req) { var rewards = new List>(); @@ -169,9 +220,12 @@ public class IBLogic_BuyGoods : ICallGSHandler } case ItemTypeEnum.TYPE_USEABLE: { - var item = AddOtherItem(player.InventoryManager.InventoryData, reward[0], detail, particular, level, count); - if (item != null) - sync.Items.Add(item.ToProto()); + if (!TryGrantCashBox(player, sync, detail, particular, level, count)) + { + var item = AddOtherItem(player.InventoryManager.InventoryData, reward[0], detail, particular, level, count); + if (item != null) + sync.Items.Add(item.ToProto()); + } break; } case ItemTypeEnum.TYPE_WEAPON_PART: @@ -281,6 +335,78 @@ public class IBLogic_BuyGoods : ICallGSHandler return item; } + private static bool TryGrantCashBox(PlayerInstance player, NtfSyncPlayer sync, uint detail, uint particular, uint level, uint count) + { + var templateId = (uint)GameResourceTemplateId.FromGdpl((uint)ItemTypeEnum.TYPE_USEABLE, detail, particular, level); + if (!GameData.OtherItemData.TryGetValue(templateId, out var otherItem)) + return false; + + uint moneyType = otherItem.LuaType switch + { + "money_box" => 1, + "gold_box" => 2, + "silver_box" => 3, + "vigor_box" => 4, + _ => 0 + }; + + if (moneyType == 0 || otherItem.Param1 == 0) + return false; + + var amount = checked(otherItem.Param1 * count); + var sid = moneyType * 2 + 1; + var attr = GetOrCreateAttr(player, CashGroupId, sid); + attr.Val += amount; + SyncAttr(player, sync, attr); + if (moneyType == 1) + { + foreach (var (key, value) in player.BuildMoneySync()) + sync.Money[key] = value; + } + return true; + } + + private static uint ResolveCurrentBattlePassId() + { + var now = DateTime.Now; + var parsed = GameData.BattlePassTimeData.Values + .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.Id; + + var latestStarted = parsed.LastOrDefault(x => x.Start <= now && x.End > x.Start); + return latestStarted?.Config.Id ?? 0; + } + + 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", + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var value) + ? value + : null; + } + private static PlayerAttr GetOrCreateAttr(PlayerInstance player, uint gid, uint sid) { var attr = player.Data.Attrs.FirstOrDefault(x => x.Gid == gid && x.Sid == sid); diff --git a/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs b/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs index 7e65044..8dd4e5e 100644 --- a/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs +++ b/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs @@ -126,6 +126,8 @@ public class PacketNtfCallScript : BasePacket sync.Custom[Player.ToPackedAttrKey(gid, sid)] = val; sync.Custom[Player.ToShiftedAttrKey(gid, sid)] = val; } + foreach (var (key, value) in Player.BuildMoneySync()) + sync.Money[key] = value; proto.ExtraSync = sync; SetData(proto);