package routers import ( "context" "sync" ) var _ = sync.Mutex{} var _ = context.Background // Generic character switching. No protagonist-specific switch path lives here. // // 面板「切人演出」指定要播的那一条 SwitchSpiritConfig 行;0 = 照老规矩自己挑 // (距离 / DisperseAnchorsByCharacter / 权重)。 // // // true = 真的把切人演出(SyncPreSwitchSpirit / SyncSwitchSpiritConfigId)推出去了; // false = 什么演出都没播(世界没就绪 / 上一次还 pending / 角色不在可控花名册 / // 目标就是当前角色 / 演出开关关着 / 退化成静默换皮 / 锚点超限)。 // 面板「切人演出」卡靠这个返回值给玩家一句人话,而不是一律回「已播放」。 // func GameRouter_Switching_OnSwitchSpirit(ctx context.Context, rpcCtx *RpcContext, requestedSpiritId uint32, allowShow bool, forcedSwitchShowId uint32) bool { // Router implementation for GameRouter_Switching_OnSwitchSpirit return true } // // Phase 2:客户端播完 pre-switch Timeline。落点早已定好(见 OnSwitchSpirit), // 这里只把控制权交给已经站在落点上的那个角色,不再把它挪回玩家发起切人时的位置。 // func GameRouter_Switching_OnPreSwitchSpiritFinish(ctx context.Context, rpcCtx *RpcContext) any { // Router implementation for GameRouter_Switching_OnPreSwitchSpiritFinish var zero any; return zero } // // 撤掉这出演出 spawn 出来的演员(WorldEntryState.PendingSwitchShowActors)。 // ⛑ 移除通道与召唤物一致:SyncLogicAgentLeave(68281244) —— 这类单位是 // SpawnAgentAsync 出来的,用 SyncAetherAINpcRemove(ambient 行人通道)发了等于没发。 // 幂等:列表清空后重复调用无副作用。 // func GameRouter_Switching_DismissSwitchShowActorsAsync(ctx context.Context, rpcCtx *RpcContext, state any, why string) any { // Router implementation for GameRouter_Switching_DismissSwitchShowActorsAsync var zero any; return zero } // 演出没等到回执(丢包 / 客户端没播):按已定好的落点直接完成移交。 func GameRouter_Switching_CompleteSwitchWithoutTimelineAsync(ctx context.Context, rpcCtx *RpcContext, state any, reason string) any { // Router implementation for GameRouter_Switching_CompleteSwitchWithoutTimelineAsync var zero any; return zero } func GameRouter_Switching_ClearPendingSwitch(ctx context.Context, state any) any { // Router implementation for GameRouter_Switching_ClearPendingSwitch var zero any; return zero } // // Server-driven character switch, used by the admin panel instead of the in-game wheel. // The wheel (Z) is client-side only: it lists FightSpiritConfig rows whose CanJoin is true // (15 of 40 here) and switching from it never tells the server anything. Publishing a // SyncTaskRoleTeam roster cannot widen it either — a non-empty team greys out every entry the // team does not name (see TOPIC_切换角色.md §6), which is worse than leaving it alone. // So the panel drives OnSwitchSpirit directly: same handoff the client's own // AskSwitchSpirit takes, but without the client having to offer the character first. That // means every roster row is reachable, including the CanJoin=false ones (Bansy, Aileen, // Mimiko, Ringo, ...) and rows kept out of the roster by the placeholder-icon filter until // they are added through content.roster.additionalTemplateIds (15021042 Lemon Sour). // func GameRouter_Switching_SwitchSpiritAsync(ctx context.Context, session *GameSession, spiritId uint32) any { // Router implementation for GameRouter_Switching_SwitchSpiritAsync var zero any; return zero } // // 面板「切人演出」卡片专用:**指定**要播的那一条 SwitchSpiritConfig 行。 // ⛑ 与上面的 SwitchSpiritAsync 唯一的区别就是 allowShow:true + // forcedSwitchShowId —— 那条是「静默换皮」(面板挑人用),这条是「播演出」 // (会发 SyncPreSwitchSpirit / SyncSwitchSpiritConfigId,并把玩家落到该行的 Position 锚点)。 // ⛑ 201 行里 AgentId 非空的行服务端这条链路补不出随从(见 ClientConfigRepository 注释), // 面板默认不列这类行;真指定了也只是演出里少了那几个陪衬角色,不会崩。 // func GameRouter_Switching_SwitchSpiritWithShowAsync(ctx context.Context, session *GameSession, spiritId uint32, switchShowId uint32) any { // Router implementation for GameRouter_Switching_SwitchSpiritWithShowAsync var zero any; return zero } // // 客户端 GM 控制台的「就地切人」:GmSwitchSpiritHere(69758035), // 参数是 uint32 的 FightSpirit id(ClientToGameSceneGMDelegate.lua:2132)。 // 抓包样本(logs/unknown-methods.log):hex=D337E500 → 15020499、 // hex=2C7D6402、hex=00000000。共 3 次 INVOKE,此前一直是 // unimplemented —— 控制台按下去只回一个空 ack,什么都不会发生。 // ⛑ 这是客户端自己的原生入口(GM 面板 324 个方法之一),不是我们造的: // 它证明官方确实有一条「不播演出、就地换人」的切人路径。接上它等于拿到一条 // 完全原生的切人通道,可以拿来跟 AskSwitchSpirit 那条 14 秒演出路径做对照。 // "Here" = 就地:走与 AskSwitchSpirit 完全相同的身份移交,但 // allowShow:false ⇒ 不发 SyncPreSwitchSpirit / SyncSwitchSpiritConfigId, // 不播 LiftOff 演出、不传送到演出锚点(换皮 + 原地移交)。 // func GameRouter_Switching_GmSwitchSpiritHere(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_Switching_GmSwitchSpiritHere var zero any; return zero } // // Publish the character-wheel roster through SyncTaskRoleTeam (64534724). // Pressing Z sends nothing to the server — the rotation list is built entirely client-side, and // because retail never called this method the client has always used its own fallback: the // FightSpiritConfig rows with CanJoin=true (exactly the 15 characters the player sees, including // Yinglong but not Mimiko). This notify is the one server-side handle on that list // (SpiritManager.lua:233 <- GameToClientImpl.lua:2311 fills taskRoleTeam from enableRoleIds). // Opt-in only: an empty roster config means nothing is sent and the client keeps its default. // Non-empty takes the list over wholesale, so anything to keep has to be listed too. // Re-published on every scene load because lua does table.clear + refill — it is idempotent by // design, and re-sending is how the list survives a scene change. // func GameRouter_Switching_PublishSwitchRoleTeamAsync(ctx context.Context, rpcCtx *RpcContext) any { // Router implementation for GameRouter_Switching_PublishSwitchRoleTeamAsync var zero any; return zero } func GameRouter_Switching_SwitchSpiritDirectSameScene4229938(ctx context.Context, rpcCtx *RpcContext, state any, templateId uint32, unitId uint64, switchPosition Vec3, switchFacing float32, switchShowId uint32, targetInWorld bool) any { // Router implementation for GameRouter_Switching_SwitchSpiritDirectSameScene4229938 var zero any; return zero } // // ★ AskSwitchSpiritComplete(67272187) —— 客户端「整条切人演出播完了」的回执。 // ⛑⛑ 以前它是**空转**的:`GameRouter.UnhandledBatch.cs` 把它挂在 SkillNeutralAccepts // 那个「只打一行 [COMBAT] accept 就 ReturnEmptyOkAsync」的兜底处理器上 // ⇒ 演出到底播没播完、播了多久,服务端完全不知道(09-26 00:2x 评估 CBT3 资料时发现的盲区)。 // CBT3 那份 `切人传送时机_apex传送实现.md` 用它标定 apexDelay;我们现在没有 apex 传送, // 但这条时间戳本身就是「演出真实时长」的唯一实测来源 —— 先记下来,别再丢。 // ⛑ 客户端在切人流程里一共只发两个 C2S:这个 + ReportPreSwitchSpiritFinish(67654364)。 // ReportPreTeleportFinish(63032762) **不发**(那是传送流程的),别拿它当切人信号。 // func GameRouter_Switching_AskSwitchSpiritComplete(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_Switching_AskSwitchSpiritComplete var zero any; return zero }