175 lines
9.4 KiB
Go
175 lines
9.4 KiB
Go
package routers
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
)
|
||
|
||
var _ = sync.Mutex{}
|
||
var _ = context.Background
|
||
|
||
//
|
||
// 厕所 QTE / 马桶鸵鸟骑乘(2026-09-30 新增)。
|
||
// <para>★ 玩法本体:ToiletOstrichGamePanelStore.lua —— 骑上马桶鸵鸟后弹一个音游式三键 QTE
|
||
// (QTE_DURATION=2 / PERFECT_WINDOW=0.1 / GREAT_WINDOW=0.2,满分 100),
|
||
// 失败时鸵鸟逃跑。面板与判定全在客户端,服务端要做的是「让骑乘坐实」+「接住逃跑上报」。</para>
|
||
// <para>★ 触发链(三条证据实锤,见 GameMethods.AskRideAgent4229938 的契约注释)</para>
|
||
// <list type="number">
|
||
// <item>面板由 C#→lua 回调打开:CsToLuaHandler.lua 的
|
||
// module.OnStartRideQTE(riderPid, beRidenPid) ⇒
|
||
// gPanelManager:CheckShow(gPanelId.TOILET_OSTRICH_GAME_PANEL, { npcPid = beRidenPid })。
|
||
// 也就是说 C# 认定「骑乘成立」时才 fire,lua 只是被动开面板。</item>
|
||
// <item>面板里 SceneDataMgr.GetUnit(self.npcPid) ⇒ 被骑的那只必须是世界里真实存在的 unit。</item>
|
||
// <item>失败分支会主动上报 AskToiletOstrichFlee;成功分支只发本地信号 5753。</item>
|
||
// </list>
|
||
// <para>★ 为什么不需要新的生成通道</para>
|
||
// 马桶鸵鸟(40122028 / 40652007 / 40922011 / 42046401 / 45000446,规范 id
|
||
// AgentConfig.ToiletOstrich = 42046401)在 NpcCatalog.json 里是标准 Agent
|
||
// (GeneralModelId=86963029、EnemyClassType=1、faction 18000300「通用怪物」),
|
||
// 而且 AgentSummonCatalog.json 里本来就有它 ⇒ 直接用既有的服务端直生成通道
|
||
// (GameAdminBridge.SpawnAgentAsync)就能出,客户端 ai 状态树
|
||
// (MassAI.Npc.MassAetherAI.Tasks.TryOnRideAgent / TryDownRideAgent,走
|
||
// BehaviorActionConfigId 40214321 + AISetting 40213146)自带骑乘能力,服务端不用给标记。
|
||
// 面板入口见 GameAdminBridge.AgentSummonCatalog(它被置顶到「生成敌人」下拉框第一位)。
|
||
// <para>★ 一次只动一个(实测顺序,按这个走)</para>
|
||
// <list type="number">
|
||
// <item>生成 42046401 → 走到旁边按骑乘。</item>
|
||
// <item>面板出来了 ⇒ 本文件的默认配置已经够用,接下来看
|
||
// [TOILET] 日志里 flee 出现时鸵鸟是否正确消失。</item>
|
||
// <item>面板不出来 ⇒ 把 gameplay.toiletQte.pushRideState 打开(推
|
||
// SyncRideAgent(rider, ridee, true)),这与自动驾驶那条「服务端推状态」是同一个模式;
|
||
// ⛑ 注意 GameRouter.VehicleNav.cs §43.1 的包序教训:状态包要排在动作包之前,
|
||
// 所以这里也是先推状态、再回 ack。</item>
|
||
// </list>
|
||
// <para>⛑ 回滚:把本文件涉及的三个 mid 从 GameRouter.cs 的
|
||
// Enabled4229938MethodIds 里删掉即可(handler 不再注册 = 回到
|
||
// ReturnEmptyOkAsync / DefaultUnknownNotify 兜底,不在场时行为与今天之前一致);
|
||
// 或者一个开关 gameplay.toiletQte.enabled=false 只关「回头推 + 回收」,不动注册。</para>
|
||
//
|
||
// 模板是不是马桶鸵鸟。GameAdminBridge.SpawnAgentAsync 用它决定「生成成坐骑还是生成成敌人」。
|
||
func GameRouter_ToiletQte_IsToiletOstrichAgent(ctx context.Context, agentId uint32) bool {
|
||
// Router implementation for GameRouter_ToiletQte_IsToiletOstrichAgent
|
||
return true
|
||
}
|
||
|
||
// 当前正骑着哪只(session 级)。下马/逃跑时要靠它知道该撤哪个 uid 的状态。
|
||
func GameRouter_ToiletQte_GetRidingMount(ctx context.Context, session *GameSession) uint64 {
|
||
// Router implementation for GameRouter_ToiletQte_GetRidingMount
|
||
return 0
|
||
}
|
||
|
||
func GameRouter_ToiletQte_SetRidingMount(ctx context.Context, session *GameSession, uid uint64) any {
|
||
// Router implementation for GameRouter_ToiletQte_SetRidingMount
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// 建立/解除完整骑乘状态(② + ③④)。顺序照抄捐赠端:SyncRideAgent 先,345/346 后。
|
||
// ⛑⛑ 必须在 ReturnEmptyOkAsync **之前**发完 —— §43.1 的包序教训。
|
||
//
|
||
func GameRouter_ToiletQte_PublishToiletRideAsync(ctx context.Context, conn any, rider uint64, mount uint64, enter bool) any {
|
||
// Router implementation for GameRouter_ToiletQte_PublishToiletRideAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// uid → 模板 id,**只为马桶鸵鸟**记一条。
|
||
// <para>⛑ 为什么不直接查敌人账本:坐骑模式是 engage=false(不下令开战、不登记进
|
||
// LocalEnemyRuntimeState.Enemies),账本里根本没有它;而 AskRideAgent /
|
||
// AskToiletOstrichFlee 收到的载荷只有 unit uid,没有模板 id ⇒ 日志里
|
||
// isOstrich=? 就再也判不出来了。</para>
|
||
// 会话销毁即失效(存在 session.Items 里),本来也是一次性的东西。
|
||
//
|
||
func GameRouter_ToiletQte_ToiletMounts(ctx context.Context, session *GameSession) map[string]any {
|
||
// Router implementation for GameRouter_ToiletQte_ToiletMounts
|
||
return nil
|
||
}
|
||
|
||
// 面板生成马桶鸵鸟时登记一条(由 GameAdminBridge.SpawnAgentAsync 调用)。
|
||
func GameRouter_ToiletQte_NoteToiletMountSpawned(ctx context.Context, session *GameSession, uid uint64, agentId uint32) any {
|
||
// Router implementation for GameRouter_ToiletQte_NoteToiletMountSpawned
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// uid 是不是本服自己生成的(GameAdminBridge.ServerSpawnUidBase 起的池子)。
|
||
// <para>⛑⛑ 2026-09-30 实测事故:AskToiletOstrichFlee 送来过 uid=19000,
|
||
// 而 19000 是场景自带的真实敌人([LENEMY] ... uid=19000 serverSpawned=False hp=1500,
|
||
// 在 -4739,169,-2828),当时代码无条件回收 ⇒ 把一只无辜的场景敌人删了。
|
||
// 根因是 AskRideAgent 我们一律回 0 字节成功,客户端于是对任何可按 F 的单位
|
||
// 都 fire OnStartRideQTE,失败后又对那个单位发 flee。</para>
|
||
// <para>⇒ 回收的硬门槛改成「只回收本服生成的单位」:客户端这条 Notify 只在马桶 QTE
|
||
// 失败时发,而我们的马桶一定是我们自己 spawn 的,两条判据交集足够;场景单位落在池子外,
|
||
// 天然被排除。ServerSpawnUidBase = 0x200000000000(= 35184372088832)远高于任何
|
||
// 场景单位 uid。</para>
|
||
//
|
||
func GameRouter_ToiletQte_IsServerSpawnedUid(ctx context.Context, uid uint64) bool {
|
||
// Router implementation for GameRouter_ToiletQte_IsServerSpawnedUid
|
||
return true
|
||
}
|
||
|
||
// ========================================================================================
|
||
// AskRideAgent(67575069) —— C2S Invoke,载荷 ulong agentId,**回包必须是 0 字节**
|
||
// ========================================================================================
|
||
func GameRouter_ToiletQte_AskRideAgent4229938Handler(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_ToiletQte_AskRideAgent4229938Handler
|
||
var zero any; return zero
|
||
}
|
||
|
||
// ========================================================================================
|
||
// AskStopRideAgent(67449345) —— C2S Invoke,**无参**,回包也是 0 字节
|
||
// ========================================================================================
|
||
func GameRouter_ToiletQte_AskStopRideAgent4229938Handler(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_ToiletQte_AskStopRideAgent4229938Handler
|
||
var zero any; return zero
|
||
}
|
||
|
||
// ========================================================================================
|
||
// AskToiletOstrichFlee(67154315) —— C2S **Notify**,载荷 ulong agentid(QTE 失败时客户端上报)
|
||
// ========================================================================================
|
||
func GameRouter_ToiletQte_AskToiletOstrichFlee4229938Handler(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_ToiletQte_AskToiletOstrichFlee4229938Handler
|
||
var zero any; return zero
|
||
}
|
||
|
||
// ------------------------------------------------------------------ helpers
|
||
//
|
||
// 当前「骑的人」是谁 —— 也就是 SyncRideAgent 的 rider。
|
||
// 出场单位优先(WorldEntryControlUnit),没有就退回当前操控体
|
||
// (ActiveSpiritUnitId,切人后它会跟着变)。拿不到返回 0(日志里就是 0,此时不推状态包)。
|
||
//
|
||
func GameRouter_ToiletQte_CurrentRiderUnit(ctx context.Context, session *GameSession) uint64 {
|
||
// Router implementation for GameRouter_ToiletQte_CurrentRiderUnit
|
||
return 0
|
||
}
|
||
|
||
//
|
||
// 读出 AskRideAgent 里的单位 id。
|
||
// ⛑ AskStopRideAgent 没有载荷,别拿它来调这个方法。
|
||
// 解析失败不能抛:这是客户端**上报**类请求,服务端炸了会把整帧链路带断(规则 168 的同族问题)。
|
||
//
|
||
func GameRouter_ToiletQte_TryReadRideTarget(ctx context.Context, msg any) uint64 {
|
||
// Router implementation for GameRouter_ToiletQte_TryReadRideTarget
|
||
return 0
|
||
}
|
||
|
||
//
|
||
// 读出 AskToiletOstrichFlee 里的单位 id。
|
||
// ⛑ 两条载荷在线上字节相同(都是单个 u64),但**各用各的契约** —— 互相借会在将来某一方
|
||
// 加字段时静默解析错位,而这里正好是「客户端上报」路径,错了只会静默丢事件。
|
||
//
|
||
func GameRouter_ToiletQte_TryReadFleeTarget(ctx context.Context, msg any) uint64 {
|
||
// Router implementation for GameRouter_ToiletQte_TryReadFleeTarget
|
||
return 0
|
||
}
|
||
|
||
//
|
||
// uid → 模板 id。先查马桶鸵鸟登记表(坐骑模式不 engage ⇒ 不进敌人账本),再查敌人账本,
|
||
// 都没有返回 0(日志里打成 ?,那是正常的,不代表解析失败)。
|
||
//
|
||
func GameRouter_ToiletQte_LookupEnemyTemplate(ctx context.Context, session *GameSession, uid uint64) uint32 {
|
||
// Router implementation for GameRouter_ToiletQte_LookupEnemyTemplate
|
||
return 0
|
||
}
|
||
|