using Ananta.SDK.Network;
using Ananta.SDK.Rpc;
using Ananta.Server.Configuration;
using Ananta.Server.Protocol.Client4229938;
using GameMethods = Ananta.Server.RpcTypes.Client4229938.Methods.Game;
namespace Ananta.Server.Handlers.Game;
///
/// 掉落物拾取的两个回执 —— 2026-09-25 接入。
///
/// 起因:用户回报「路人 NPC 手里的手机能打落拾取,但他们的 BBChat 点不开」。
/// 掉手机这件事客户端本地就能做(GM 命令 DebugEnableDropPhoneWhenDeath,
/// 补丁和工具/ClientGmCommands.txt:320),掉出来的手机是客户端自己生成的 SceneItem;
/// 但拾取时它发了两个 INVOKE 给服务端:
///
/// AskPickUpSceneItemTransferToItem (67297154) u64 sceneitemuid
/// AskEnemyItemPickUp (67453017) u64 enemyinstanceid + i32 binditemsindex
///
/// 这两个 MethodId 在本服务端零 handler、也不在白名单(全项目 grep 只命中 MethodId.cs),
/// ⇒ 走 DefaultUnknownInvoke 兜底或直接 await 挂起(硬规则 94)。
///
/// 本文件只回空 OK,不做内容。理由(见 2026-09-25 记忆 §路人手机调研):
/// NPC 手机里的聊天一个字节都不走服务端 ——
/// GUI/Chat/RandomChatManager.lua:13 GetChatId(npcId, itemId) 用
/// AgentConfig.GetConfig(npcId) 的 AppearanceTag / BodyTag / SexType / JobTag 去匹配
/// 35 行 NPCChatAtmosphereNewConfig(84028000-84028034),按 60/Weight 加权
/// 随机挑 1 条 NPCChatConfig 聊天链,然后打开面板。
/// ⇒ 想让"每个人手机不一样",要动的是 npcId(AgentConfig id),不是往服务端塞文本。
///
/// ⛑ 注册的 kind 用 Both:按 AskControlAgent 那次的教训,同族 RPC 会有 NOTIFY 变体,
/// 只注册 Invoke 就是静默丢包;ReturnEmptyOkAsync 对 NOTIFY 是 no-op。
///
internal sealed partial class GameRouter
{
/// per-session:本局拾取过的掉落物 uid(去重后只用于日志计数)。
private const string SceneItemPickupStateKey = "client4229938-sceneitem-pickup";
///
/// AskPickUpSceneItemTransferToItem(67297154) —— 把场景里的掉落物收进背包。
///
/// 客户端本地掉落、本地回收,服务端没有 SceneItem 账本(我们从未推过
/// SyncEnemyBeginSceneItemDrop 68357238 / SyncDropBelonging 68316751),
/// 所以这里能做的只有放行:不回执,拾取动作 await 到超时。
///
[Handler(MethodId.AskPickUpSceneItemTransferToItem, HandlerPacketKind.Both)]
private async Task OnAskPickUpSceneItemTransferToItem(Connection conn, UxRpcMessage msg)
{
ulong uid = 0;
try
{
var args = msg.GetArgs();
if (args is not null)
uid = args.sceneitemuid;
}
catch (Exception ex)
{
// ⛑ 不 throw:这是 INVOKE,抛异常等于不回执 ⇒ 客户端卡死。解析不出 uid 也要放行。
conn.Log.Warn($"[PICKUP] AskPickUpSceneItemTransferToItem 解析失败(按 uid=0 处理): {ex.Message}");
}
await conn.ReturnEmptyOkAsync(msg);
if (!PrivateServerConfigStore.Current.Gameplay.SceneItemPickupEnabled)
return;
var items = msg.Context.Session.Items;
if (items.TryGetValue(SceneItemPickupStateKey, out var raw) is false || raw is not HashSet set)
{
set = [];
items[SceneItemPickupStateKey] = set;
}
bool fresh;
lock (set)
fresh = set.Add(uid);
conn.Log.Info($"[PICKUP] sceneitem uid={uid} kind={(msg.IsInvoke ? "invoke" : "notify")} " +
$"{(fresh ? "new" : "repeat")} — 本局已拾取 {set.Count} 件(服务端无 SceneItem 账本,只回执)");
}
///
/// AskEnemyItemPickUp(67453017) —— 从敌人/NPC 身上拿走第 N 件携带物(belonging)。
///
/// 官方完整链是 SyncAgentBelongings → SyncDropBelonging(68316751) →
/// SyncEnemyBeginSceneItemDrop(68357238) → 本包。我们只发过第一步
/// (GameRouter.HackNpc.cs 给路人递手机),后两步是 T3-9 的整块待办。
/// 客户端仍会自发这个包 ⇒ 同样只放行。
///
[Handler(MethodId.AskEnemyItemPickUp, HandlerPacketKind.Both)]
private async Task OnAskEnemyItemPickUp(Connection conn, UxRpcMessage msg)
{
ulong enemy = 0;
int index = -1;
try
{
var args = msg.GetArgs();
if (args is not null)
{
enemy = args.enemyinstanceid;
index = args.binditemsindex;
}
}
catch (Exception ex)
{
conn.Log.Warn($"[PICKUP] AskEnemyItemPickUp 解析失败(按 0/-1 处理): {ex.Message}");
}
await conn.ReturnEmptyOkAsync(msg);
if (!PrivateServerConfigStore.Current.Gameplay.SceneItemPickupEnabled)
return;
conn.Log.Info($"[PICKUP] belonging from enemy={enemy} index={index} " +
$"kind={(msg.IsInvoke ? "invoke" : "notify")}(belongings 是 HackNpc 递的手机,服务端无掉落账本)");
}
}