package routers import ( "context" "sync" ) var _ = sync.Mutex{} var _ = context.Background // // 五子棋**单机**(人机 / 残局)—— EnterGomokuZoneDoubleAI(67614971) 与 // EnterGomokuZoneEndGame(67341006)。 // ★ 现象与根因链(四刀,每一刀都有本机取证) // // R153 入口未登记:这两条从未进白名单 ⇒ 走 DefaultReturnCatalog4229938 // 兜底(:1611 / :1710)回 8 个 0x00 = **裸 u64 = 0**。客户端 // [warning]StartGomoke AI: TryGetLogicAgent fail, uid=0 / // StartGomoke EndGame: TryGetLogicAgent fail, uid=0。 // ⇒ 必须登记并回一个**真实存在的**实体 uid。 // R155 只有 uid 装不下身份:SyncLogicAgentEnter 的 interop 签名只有一个 ulong, // 而 LogicAgent.AgentConfigId 是 uint ⇒ 只注册 uid 会让客户端报 // 创建AgentUnit失败!AgentConfigId为0!。 // 唯一能送 AgentConfig 的通道是 **Aether 静态 NPC**(NpcFormworkId 就是 AgentConfig id), // 而客户端在请求体第 2 个字段 agentid 里**自己把该填哪个告诉我们了**。 // R156 缺棋局状态推送(本轮真正卡住落子的那一刀): // 客户端点子链是 TryRayIntersectBoard → PlaceGomokuPiece → // AskPlaceGomokuPiece,而闸门是 GomokuManager.get_NowMyTurn(); // 唯一能置起"我的回合"的输入就是 SyncGameGroundZoneInfo(68004609)。 // 实测 AskPlaceGomokuPiece / PlaceGomokuPiece / SyncGameGroundZoneInfo // 在全部历史日志里 **0 命中** ⇒ 客户端从没收到过 ⇒ 闸门恒关 ⇒ **一条落子包都不发**。 // R185 参与方列表不能为空:客户端靠 ipairs(zoneInfo.ParticipantInfos) 拿到 // pendingOpponentAgentId。推空列表 ⇒ 它永远是 0 ⇒ 闸门还是不开。 // 且列表元素是**多态**的,每个元素自带 1B tag = GomokuWire.ParticipantTagGomoku(12); // 若用裸 [UxContract](默认 0xFF)会被 Dispatch 判成 undefined 分支全部丢弃。 // // ★ 三步法与主线自己的 GameRouter.ChineseChess.cs(SpawnChineseChessAiAsync) // **完全同形**(那里注释原话:"与五子棋 R153/R155/R157 三步完全同形"): // ① SyncLogicAgentEnter 注册 uid → ② SendCivilianAsync 送 AgentConfig + 站位 // → ③ 把同一个 uid 回给客户端 / 写进 ParticipantInfos[].AgentInstanceId。 // ⛑⛑ 不要加"等 NPC 落地"的延迟:象棋的实测证据是客户端 // TryGetOrConvertToUnit 查不到时**会等待**(10 秒后才打"等待服务器对手 NPC 单位超时"), // 不是立刻失败 ⇒ 时序不是问题,缺的是状态推送。曾按"时序"假设加过延迟,方向是错的。 // 契约 DTO 见 Drmk.RpcTypes/Client4229938/GomokuWireContracts.cs(文件头有逐字段的本机复核)。 // // ───────────────────────────────────────────────────────────────────── // 两条单机入口 // ───────────────────────────────────────────────────────────────────── // // EnterGomokuZoneDoubleAI(67614971) 人机对战。 // C2S = u64 gadgetuid + u32 agentid + bool useblackpiece + byte difficulty + bool canuseskill。 // RET = **裸 u64**(对手实体的真实 uid)。 // func GameRouter_GomokuSolo_EnterGomokuZoneDoubleAI4229938(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_GomokuSolo_EnterGomokuZoneDoubleAI4229938 var zero any; return zero } // // EnterGomokuZoneEndGame(67341006) 残局。 // C2S = u64 gadgetuid + u32 agentid + u32 endgameid。RET = **裸 u64**(同上)。 // ⛑ 与 DoubleAI 的差别只有:请求里**没有** useBlack 字段 ⇒ 按残局约定「玩家先手 = 黑」。 // func GameRouter_GomokuSolo_EnterGomokuZoneEndGame4229938(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_GomokuSolo_EnterGomokuZoneEndGame4229938 var zero any; return zero } // 把对手 uid 按「裸 u64」拼成 8 字节小端回给客户端。 func GameRouter_GomokuSolo_ReturnGomokuUidAsync(ctx context.Context, conn any, msg any, uid uint64) any { // Router implementation for GameRouter_GomokuSolo_ReturnGomokuUidAsync var zero any; return zero } // ───────────────────────────────────────────────────────────────────── // ★★★ R156 核心:把「棋局已开始、现在轮到谁、对手是谁」推给客户端 // ───────────────────────────────────────────────────────────────────── // // 推 SyncGameGroundZoneInfo(68004609) + GomokuZoneInfo(多态 tag 13)。 // 这是「点棋盘没反应」与「能落子」之间缺的那一刀 —— 客户端 get_NowMyTurn() // 的置位只有这一个输入来源。 // ★ 座位/先手(别与象棋的「颜色」混淆):五子棋 CurrentTurn 是 **SeatIndex 座位号**。 // GomokuWire.SeatIndexBlack = 0;客户端两个单机入口都 useBlack=true // ⇒ 玩家 mySeatIndex = 0、黑棋先手 ⇒ CurrentTurn 必须是 0。 // 玩家 SeatIndex=0 / AI SeatIndex=1 / CurrentTurn=玩家座位 —— **三者必须自洽**, // 写错任一个都是「点不动」。 // 回滚 = 删两个 handler 里对本方法的调用(两行)。 // func GameRouter_GomokuSolo_PushGomokuZoneInfoAsync(ctx context.Context, conn any, gadgetUid uint64, gameType byte, aiUid uint64, playerUsesBlack bool) any { // Router implementation for GameRouter_GomokuSolo_PushGomokuZoneInfoAsync var zero any; return zero } // ───────────────────────────────────────────────────────────────────── // 造对手 —— 三步法,与 GameRouter.ChineseChess.cs 的 SpawnChineseChessAiAsync 同形 // ───────────────────────────────────────────────────────────────────── // // 建一个「客户端查得到的 AI 对手」,返回它的**真实 uid**。三步: // ① SyncLogicAgentEnter 注册 uid(客户端 TryGetLogicAgent 查的就是这张表); // ② 用**客户端给的 agentId** 走 Aether 静态 NPC 通道(NpcFormworkId = AgentConfig id, // 不送的话客户端建 Unit 时报 创建AgentUnit失败!AgentConfigId为0!); // ③ 把同一个 uid 交回调用方(回包 + 写进 ParticipantInfos)。 // ★ R157 复用:**同一会话同一张桌只 spawn 一次**。理由(象棋同源注释原话): // 「旧的那个删不掉(本版客户端无可靠删除通道)⇒ 场上越来越多」。 // 实测本仓 MethodId 里没有 SyncAetherAIStaticNpcRemove*; // SyncRemoveNpc(68446556) 的 reader 是 Int32 ⇒ 装不下 830… 段的 u64。 // ⇒ 与其猜"怎么删",不如**根本不重复建**。 // 缓存键带 gadgetUid(庙会区有多张 WuZiQi 桌,要各有一个对手,不能互相顶掉)。 // 作用域用 TcpSession.Items —— 断线即弃,不会泄漏到别的会话。 // func GameRouter_GomokuSolo_SpawnGomokuOpponentAsync(ctx context.Context, conn any, gadgetUid uint64, agentId uint32, playerUsesBlack bool, tag string, endGameId uint32) any { // Router implementation for GameRouter_GomokuSolo_SpawnGomokuOpponentAsync var zero any; return zero } // // 挑对手模板:优先用客户端请求里带来的 agentId(实测 40131483「廖永强」), // 它不在 NpcCatalog 里时才退回通用市民(骰子 PickDiceOpponentItem 同款挑法)。 // func GameRouter_GomokuSolo_PickGomokuOpponentItem(ctx context.Context, agentId uint32) any { // Router implementation for GameRouter_GomokuSolo_PickGomokuOpponentItem var zero any; return zero } // 摊位表里离玩家最近的那个五子棋机关;表为空时返回 null。 func GameRouter_GomokuSolo_FindGomokuStall(ctx context.Context, px float32, pz float32) any { // Router implementation for GameRouter_GomokuSolo_FindGomokuStall var zero any; return zero } // // 造 NPC 失败时的兜底 uid(骰子 FallbackUid 同款)。刻意不是 0 —— // 0 会被客户端当成「进场失败」(套圈注释的判据),至少让它走到 // 「等待 Agent 实体同步」而不是当场判死,日志里也好区分。 // func GameRouter_GomokuSolo_GomokuFallbackUid(ctx context.Context, ) uint64 { // Router implementation for GameRouter_GomokuSolo_GomokuFallbackUid return 0 }