using Ananta.SDK.Network; using Ananta.SDK.Rpc; using Ananta.SDK.Serialization; using Ananta.Server.Protocol.Client4229938; using SceneMethods = Ananta.Server.RpcTypes.Client4229938.Methods.GameScene; namespace Ananta.Server.Handlers.Game; /// /// Spoon server-action output ports. The client's spoon task graph asks the SERVER which /// output port to take (AskGetSpoonServerOutputPortIndexByType, UXRPCTask<uint>) or to /// perform an action (AskDoSpoonServerActionByType). We answer port 0 — byte-identical to /// the typed-default fallback — and, for do-action, push the nearest repair shop's /// inventory so the mechanic's "assortment" dialog branch opens with content. /// Full param decoding (SpoonServerActionParam) is deferred: the raw body is logged. /// internal sealed partial class GameRouter { [Handler(MethodId.AskGetSpoonServerOutputPortIndexByType, HandlerPacketKind.Invoke)] private Task SpoonOutputPortProbe(Connection conn, UxRpcMessage msg) { conn.Log.Info($"[SPOON] output-port probe bytes={msg.Body.Length} -> port 0"); return conn.ReturnAsync(msg, 0u); } [Handler(MethodId.AskDoSpoonServerActionByType, HandlerPacketKind.Invoke)] private async Task SpoonServerAction(Connection conn, UxRpcMessage msg) { conn.Log.Info($"[SPOON] do-action bytes={msg.Body.Length} -> ok (port 0)"); await conn.ReturnEmptyOkAsync(msg); // Push the nearest repair shop's inventory when the player is close to one — // the mechanic's assortment dialog opens empty without this push. var state = GetStateIfExists(conn.Session); if (state is null || !state.HasLastReportedPlayerTransform) return; var px = state.LastReportedPlayerPosition.X; var pz = state.LastReportedPlayerPosition.Z; var west = ShopInteriors[0]; var seaside = ShopInteriors[1]; float D2(SceneMethods.UxVector3 e) => (px - e.X) * (px - e.X) + (pz - e.Z) * (pz - e.Z); var (shopId, dist2) = D2(west.Entrance) <= D2(seaside.Entrance) ? (94100069u, D2(west.Entrance)) : (94100070u, D2(seaside.Entrance)); if (dist2 > 60f * 60f) return; ShopCommodityCache.Value.TryGetValue(shopId, out var ids); var items = (ids ?? []).Select(id => new RpcTypes.Client4229938.Methods.Game.ShopCommodityItem { TemplateId = id, Count = 1, Status = 0, Discount = 100, MaxBuyCount = 0, }).ToList(); await conn.Session.NotifyAsync(MethodId.IGameToClient_SyncFullCommodityInfos, UxSerializer.Serialize( new RpcTypes.Client4229938.Methods.Game.SyncFullCommodityInfos { shopId = shopId, infos = items, }), CancellationToken.None); conn.Log.Info($"[SPOON] do-action -> pushed {items.Count} commodities shop={shopId} (dist={MathF.Sqrt(dist2):F0}m)"); } }