using Ananta.SDK.Rpc; using Ananta.Server.ClientData.Client4229938; using Ananta.Server.Protocol.Client4229938; using GameMethods = Ananta.Server.RpcTypes.Client4229938.Methods.Game; namespace Ananta.Server.Handlers.Game; /// /// Mall shelf purchases for build 4229938 (MallManager.AskBuyCommodity -> AskMallBuyCommodity). /// /// The client sends (commodityId, buyCount, isAutoExchange) and its reply reader consumes nothing, so /// the answer is a bare OK and every observable effect travels as a push: the wallet totals, the /// backpack delta, and - for the wallet currencies - the money-gain popup. Failure is reported with a /// non-zero err, which is exactly what the client turns into "purchase failed", so a successful /// purchase must always answer OK. /// /// Prices are charged in the commodity's ConsumeItemId (a wallet currency or a backpack item). This /// private server never blocks a purchase for lack of funds: a shortfall is logged and waived. /// internal sealed partial class GameRouter { [Handler(MethodId.AskMallBuyCommodity, HandlerPacketKind.Invoke)] private async Task AskMallBuyCommodity(Connection conn, UxRpcMessage msg) { var state = GetWorldState(msg.Context); var request = ParseEconomyRequest4229938(msg.Body); var count = Math.Clamp(request.Count == 0 ? 1u : request.Count, 1u, 999u); if (!EconomyConfigRepository.TryMallCommodity(request.Id, out var commodity)) { conn.Log.Warn($"[MALL] unknown commodity {request.Id} (body={Convert.ToHexString(msg.Body)})"); await conn.ReturnEmptyOkAsync(msg); return; } var changes = new EconomyChanges4229938(); ChargeCurrency4229938( state, changes, commodity.ConsumeItemId, commodity.UnitPrice * count, conn.Log, $"mall {commodity.Id}"); var granted = 0u; if (commodity.DropId != 0 && EconomyConfigRepository.TryDrop(commodity.DropId, out var drop) && !drop.IsEmpty) { GrantDrop4229938(changes, drop, count, $"mall {commodity.Id}", conn.Log); granted = count; } else { // Recharge vouchers and similar rows ship an empty drop: the row's BindId (or its first // bound commodity id) is the thing the player actually receives. var bindId = commodity.BindId != 0 ? commodity.BindId : (commodity.CommodityBindIds.Count > 0 ? commodity.CommodityBindIds[0] : 0u); if (bindId != 0) { AddGrant4229938(changes, bindId, count, isBind: true); granted = count; conn.Log.Info($"[MALL] commodity {commodity.Id} has empty drop {commodity.DropId}, granted BindId {bindId} x{count}"); } else { conn.Log.Warn($"[MALL] commodity {commodity.Id} grants nothing (drop={commodity.DropId} bindId=0)"); } } var summary = await ApplyEconomy4229938Async(conn, state, changes, $"mall {commodity.Id}"); conn.Log.Info( $"[MALL] buy commodity={commodity.Id} mall={commodity.MallId} count={count} granted={granted} " + $"price={commodity.UnitPrice} currency={commodity.ConsumeItemId} " + $"autoExchange={request.Flag} drop={commodity.DropId} -> {summary}"); await conn.ReturnEmptyOkAsync(msg); } }