package routers import ( "context" "sync" ) var _ = sync.Mutex{} var _ = context.Background // // 硬直 / 虚弱 / 死亡表现 的回执与出口,2026-09-23 补上。 // 为什么单独一个文件:在此之前服务端只做"发射"——推伤害、推架势条、推死亡包——却没有一条 // 客户端回执的路径,于是客户端的状态机有入口无出口。玩家看到的三条症状全部出自这里: // 1. 「击倒 / 打晕之后敌人卡住」:客户端用 ReportUnitStandUp(67329238) 和 // AskAgentExitStiff(67581079) 告诉我们"硬直结束了",而且 ReportUnitStandUp 是 // Invoke——客户端阻塞等回执。我们没有 handler,客户端的起身流程就一直挂着, // 单位停在硬直动作里,既不受击也不攻击。 // 2. 「虚弱窗口关不掉」:破防时我们只改自己的账本,从不推 SyncEnemyDisarmState(true), // 窗口到点也从不推 SyncEnemyDisarmState(false) + 架势条回满。客户端的虚弱状态是 // 靠这两个包进出场的(kimika 捐赠端 GameRouter.EnemyGroggy.cs 的完整闭环)。 // 3. 「死了之后敌人又会出现,变成打不死的状态」:SyncEnemyPrepareDie 只让客户端播死亡, // 尸体要 SyncClearSpoonEnemies(68237191) 才拆得掉。我们从来没发过它,所以单位带着一条 // 空血条继续站在场上;而它的账本在死亡序列末尾已经被删,之后的命中无人结算 // (serverSpawned 且未注册 ⇒ 直接 return),就成了"打不死的幽灵"。 // 来源:kimika 捐赠端(2026-09-22 用户口述可信方)GameRouter.EnemyGroggy.cs / GameRouter.Enemy.cs。 // 与之不同的只有一点:捐赠端把 stand-up 注册成 HandlerPacketKind.Invoke,这里不带 kind 注册, // 两种包形态都收、内部再判 IsInvoke 回执——只收 Invoke 会漏掉以 Notify 形态发来的那一次 // (本服务端自己的 AskEnemyStartFall 就是踩过这个才改成双形态的)。 // // ---------------------------------------------------------------- 硬直 (stiff) // AskAgentEnterStiff(67352229) — 客户端说这个单位进硬直了。Notify,不用回。 // 记下来是为了让攻击 AI 别在硬直里继续挥(GameRouter.EnemyAiActions)。 func GameRouter_EnemyStiff_LocalEnemyEnterStiff(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_EnemyStiff_LocalEnemyEnterStiff var zero any; return zero } // AskAgentExitStiff(67581079) — 客户端的硬直结束了。Notify,但同样两种形态都收。 // ⛑⛑ 2026-09-23 17:3x:这里不再关闭虚弱窗口。日志证据(console-20260923-172435.log): // uid=…836 在 17:30:04 破防,17:30:05 就被 client-exit-stiff 关掉 —— 窗口只活了 1 s, // 因为 67149 一次性动作播完客户端立刻上报退出硬直。而我们正是靠周期重推 67149 来"撑" // 这个窗口的,于是第一次破防之后每次都被自己提前结束 = "第一次正常,后面不正常"。 // 窗口的权威出口只剩两个:ReportUnitStandUp(真起身)和 groggy 超时兜底。 // (捐赠端 kimika 是拿它关窗口的,但它没有周期重推,所以不冲突;我们不能照搬。) func GameRouter_EnemyStiff_LocalEnemyExitStiff(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_EnemyStiff_LocalEnemyExitStiff var zero any; return zero } // ReportUnitStandUp(67329238) — 单位起身,Invoke,必须回执。 // 这是"虚弱结束"的权威信号:收到即关窗口,不等超时。 func GameRouter_EnemyStiff_LocalEnemyStandUp(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_EnemyStiff_LocalEnemyStandUp var zero any; return zero } func GameRouter_EnemyStiff_TryReadStiffAgentId(ctx context.Context, msg any) uint64 { // Router implementation for GameRouter_EnemyStiff_TryReadStiffAgentId return 0 } // ---------------------------------------------------------------- 虚弱 (groggy) 进出场 // 破防:推 SyncEnemyDisarmState(true) + 架势条 0。 // 客户端的虚弱 UI 在这个 build 里是半成品(捐赠端注释:SyncEnemyPoiseRate 的 C# 实现有反向 // guard、SyncEnemyDisarmState 的 C# 体是空的、CheckShowDisarmBar 还卡 maxDisarmValue<=0), // 真正被客户端"听进去"的是这两条:它自己上报的硬直,以及敌人在这段时间里不再挥击。 // 账本侧(Poise 归零、GroggyUntilTicks)由调用方在锁内写好,这里只负责出场包。 func GameRouter_EnemyStiff_EnterLocalEnemyGroggyAsync(ctx context.Context, session *GameSession, uid uint64, reason string) any { // Router implementation for GameRouter_EnemyStiff_EnterLocalEnemyGroggyAsync var zero any; return zero } // 虚弱结束:清窗口、架势回满、推 SyncEnemyDisarmState(false) + 架势条 1。 // 幂等:对不在虚弱中的单位是 no-op,所以 stand-up 报告和超时不会重复推。 func GameRouter_EnemyStiff_ExitLocalEnemyGroggyAsync(ctx context.Context, session *GameSession, uid uint64, reason string) any { // Router implementation for GameRouter_EnemyStiff_ExitLocalEnemyGroggyAsync var zero any; return zero } // 每个 AI tick 调一次:关掉到期的虚弱窗口。stand-up 报告没来时这就是兜底出口。 // 由 GameRouter.EnemyAiActions 的 worker 驱动;worker 退出后若敌人还活着会由命中路径补。 func GameRouter_EnemyStiff_MaintainLocalEnemyGroggyAsync(ctx context.Context, session *GameSession, now int64) any { // Router implementation for GameRouter_EnemyStiff_MaintainLocalEnemyGroggyAsync var zero any; return zero } // ---------------------------------------------------------------- 死亡表现 / 尸体清理 // 拆尸体:SyncClearSpoonEnemies(68237191)。幂等——第一个调用者赢,后来的都是 no-op。 // 这是"死了之后敌人还站在场上"的唯一解药;SyncEnemyPrepareDie 只负责播动画。 func GameRouter_EnemyStiff_CompleteLocalEnemyDeathClearAsync(ctx context.Context, session *GameSession, uid uint64, reason string) any { // Router implementation for GameRouter_EnemyStiff_CompleteLocalEnemyDeathClearAsync var zero any; return zero } // ReportEnemyPrepareFinish(67427299) — 客户端说死亡准备流程走完了,立刻拆尸体。 func GameRouter_EnemyStiff_LocalEnemyPrepareFinish(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_EnemyStiff_LocalEnemyPrepareFinish var zero any; return zero } // ReportAgentDiePerfEnd(67638502) — 客户端说死亡表现播完了(Invoke,必须回执, // 否则客户端的死亡流程会挂住)。同样立刻拆尸体。 func GameRouter_EnemyStiff_LocalEnemyDiePerfEnd(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_EnemyStiff_LocalEnemyDiePerfEnd var zero any; return zero } // 尸体清理的兜底扫描:客户端一个报告都没发来时,超时也要把尸体拆掉。 // 由攻击 AI worker 驱动。 // ⛑⛑ 2026-09-23 19:4x:这里曾经在扫描时顺手周期补推一次倒地动作,已撤销 —— 那是 // "远程杀死敌人放两次倒地动画"的一半原因。远程打完玩家就停手,hook 播的那次 88300 本来 // 完整播完了,周期补推又重启了一次。补推只能挂在"确实被顶掉了"的证据上,也就是客户端 // 的 AskAgentEnterStiff 上报(ReassertLocalEnemyDeathAsync,且带 900 ms 静默窗口)。 func GameRouter_EnemyStiff_SweepLocalEnemyDeathClearsAsync(ctx context.Context, session *GameSession, now int64) any { // Router implementation for GameRouter_EnemyStiff_SweepLocalEnemyDeathClearsAsync var zero any; return zero }