This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

198 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package routers
import (
"context"
"sync"
)
var _ = sync.Mutex{}
var _ = context.Background
//
// ★★ 生活日程的正式通道 —— SyncFavorNpcSpoonAgentId(64003213)。
// 背景(09-25 实机):里希按日程站在跑步机旁边 / 上面,但只会站着玩手机。
// 在出场包 AgentSyncClientInfo.AgentDataSetsActivityCfgId 里填活动 id 不够
// —— 那个字段只是「这个 NPC 归哪条活动管」,客户端还需要知道
// 「他此刻占着几号 spoon 位、站哪个点、朝哪边」才会去播那条活动的表演。
// 取证来源(补丁和工具\DUMPS\readable_world_dump_4229938_final.zip):
// <list type="bullet">
// <item>06_RUNTIME_CODE/.../Auto.Client/UX/Game/IGameToClient.cs:
// void SyncFavorNpcSpoonAgentId(uint agentTag, int spoonAgentId, UXVector3 Position,
// FavorNpcBusyInfo busyInfo = null);</item>
// <item>.../LX6/Units/Module/LifeScheduleNpc/LifeScheduleNpcModule.cs:
// AttachNet(net, activityId, storySpoonAgentId, hasAmbientLoopPose, ambientLoopPos,
// ambientLoopFacing) + SetAmbientLoopPoseContext /
// TryGetStoryLoopPose / BeginAmbientEnterAnchoredPlay
// ⇒ 表演的「入场动画」是从 loop pose 反推锚点后播的。</item>
// <item>.../L50/StoryClient/Story/World/S009/SLifeScheduleNpcNet.cs(Spoon 的
// 「日程表NpcNet(Client S树)」):复制的字段就是
// NpcId / NpcName / ActivityId / AgentTag / SpoonAgentId / HasAmbientLoopPose /
// AmbientLoopPos / AmbientLoopFacing ⇒ 逐项对上了上面那条 RPC。</item>
// <item>.../Auto.Client/UX/Game/NpcTimeTableInfo.cs:
// CurrentSpoonAgentId + SpoonPosition + 五段
// NpcScheduleInfo{ActivityId, StartDaySecond, Position, SpoonAgentId, EndDaySecond,
// RaidId} ⇒ 登录帧里那三处我们一直填 0(见 RuntimePayloadFactory)。</item>
// </list>
// ⛑⛑ spoonAgentId 到底该填什么,官方没有配置表(它住在客户端 Spoon 资产里,
// 扫遍 01_CONFIGS / 02_INTERIORS / 03_ZONEGRAPHS / 04_WORLD_JSON 都是零命中)。
// 目前最自圆其说的取法是「填这个角色自己的 AgentId」—— 因为出场包
// AgentSyncClientInfo.SpoonAgentId(GameRouter.DonorNpc.cs:234)官方/donor
// 填的就是 NpcFormworkId,两边同一个值客户端才可能对上号。所以默认值
// agentId,同时留 fixed 手动试号。
// 线格式(RPCDeserializeAuto.lua:1601 的 reader):
// <code>
// u32 agentTag | i32 spoonAgentId | f32 x, f32 y, f32 z | [0x00 = busyInfo 为 null]
// </code>
// ⛑ 基本类型不带存在标记(Plate 那条已实机证过:裸写就能解出来)。
//
//
// 推一条 SyncFavorNpcSpoonAgentId(64003213)。
//
// 好感 NPC 的 AgentTag(里希 = 12)。
// spoon 位号。
func GameRouter_NpcLife_PushNpcSpoonAgentAsync(ctx context.Context, session *GameSession, agentTag uint32, spoonAgentId int32, x float32, y float32, z float32, cfg any) int32 {
// Router implementation for GameRouter_NpcLife_PushNpcSpoonAgentAsync
return 0
}
//
// 常驻 NPC 落地后的挂钩:给它补一条「你现在占着几号 spoon 位」。
// ⛑ 只在它真的有活动(有坐标、有活动 id)且拿到了 AgentTag 时才发,
// 否则等于告诉客户端「去一个不存在的位子」。
//
func GameRouter_NpcLife_EnsureNpcSpoonAsync(ctx context.Context, session *GameSession, spiritId uint32, agentId uint32, activityId uint32, x float32, y float32, z float32) any {
// Router implementation for GameRouter_NpcLife_EnsureNpcSpoonAsync
var zero any; return zero
}
//
// 落地后的 POI 兜底:gameplay.npcLife.poiActionId 非 0 且开关打开时,
// 给刚放出来的单位补一帧 SyncAgentPlayPOIAction。
//
func GameRouter_NpcLife_EnsureNpcPoiActionAsync(ctx context.Context, session *GameSession, unitId uint64) any {
// Router implementation for GameRouter_NpcLife_EnsureNpcPoiActionAsync
var zero any; return zero
}
//
// ★★ 「这个角色此刻该演什么」的唯一裁决点(09-25 23:0x 加,把里希那一套推广到
// 所有有日程的角色)。常驻摆放(GameRouter.RosterNpc)与切人留场
// (GameRouter.Switching)都走这里 —— 两处各写一份必然漂移。
// 优先级:活动级 rosterNpc.activityPoiOverrides (按
// AgentDataSetsActivityConfig.Id) &gt; 角色级 rosterNpc.poiOverrides
// (按 spiritId) &gt; 调用方给的默认值。
// ⛑⛑ 为什么「配了 POI 就不带活动 id」:生活日程的 ambient 会盖掉 POI
// —— 里希最早就是「一边打电话一边跑步」(规则 163)。
// 没配映射的活动照旧带活动 id,走生活日程(那是官方那条路,只是没表演)。
// ⛑ 朝向返回 float?:调用方要能区分「没配」(沿用各自原来的朝向)
// 和「配了 0」。常驻原来恒 0,切人留场原来用玩家切走时的朝向。
//
func GameRouter_NpcLife_ResolveActivityPresentation(ctx context.Context, spiritId uint32, activityId uint32, defaultPoiActionId uint32) any {
// Router implementation for GameRouter_NpcLife_ResolveActivityPresentation
var zero any; return zero
}
//
// ★★ 落地坐标的**活动级微调**(gameplay.rosterNpc.activityPoiOverrides[].offsetX/Y/Z)。
// 官方 MapPosition 是策划打的点,跟场景家具常常差几十厘米;这种偏差没有表可查,
// 只能实机挪。字段语义(世界轴 / 朝向 0 时右 = +X)见
// ActivityPoiOverride.OffsetX。
// ⛑ 三个调用点必须**都**走这里(常驻摆放 / 切人留场 / 主动切到日程点),
// 否则同一个角色会因为出场方式不同而站在两个不同的地方。
// ⛑ 没配 / 查不到 ⇒ 原样返回,零开销、零行为变化。
//
func GameRouter_NpcLife_ApplyActivityOffset(ctx context.Context, activityId uint32, x float32, y float32, z float32) any {
// Router implementation for GameRouter_NpcLife_ApplyActivityOffset
var zero any; return zero
}
// 按 NpcLifeSettings.SpoonAgentIdMode 决定 spoon 位号。
func GameRouter_NpcLife_ResolveSpoonAgentId(ctx context.Context, cfg any, agentId uint32) int32 {
// Router implementation for GameRouter_NpcLife_ResolveSpoonAgentId
return 0
}
// =================================================================================
// ① 时刻表 SyncFavorNpcTimeTableInfos(64219939) / SyncAgentTagTimeTableInfo(64400736)
// =================================================================================
//
// ★★ spoon 帧的硬前提:客户端 NpcDaliyManager.lua:143
// OnSyncFavorNpcSpoonAgentId 一进门就判
// table.isNilOrEmpty(self.NpcTimeTableInfos[agentTag]),空则 return
// ⇒ 没有时刻表时 64003213 整帧被静默丢弃(不报错、不打日志)。
// 09-25 19:36 第一次实机推 spoon「零反应」就是这个原因。
// 线格式(RPCDeserializeAuto.lua:2327 / :3003):
// <code>
// 64219939: FF + Int7(count) + 每项 [u32 agentTag] + [Complex NpcTimeTableInfo]
// 64400736: u32 agentTag + [Complex NpcTimeTableInfo]
// </code>
// ⛑ NpcTimeTableInfo 本体交给 UxSerializer —— 它和登录帧
// FavorNpcDailyScheduleInfos 用的是同一套(Complex,TypeMark 0xFF,
// Position/SpoonPosition 是 Struct 不带标记,TempSchedule 可空),
// 别手搓字节。只有最外层字典要自己写:UxSerializer.Serialize(Dictionary)
// 顶层没有 field ⇒ count 会按 Int32 写,而客户端这里读的是 ReadDict7Bit。
//
func GameRouter_NpcLife_PushNpcTimeTableAsync(ctx context.Context, session *GameSession, focusAgentTag uint32, spoonAgentId int32, cfg any) int32 {
// Router implementation for GameRouter_NpcLife_PushNpcTimeTableAsync
return 0
}
//
// 造一份 Dictionary&lt;uint, NpcTimeTableInfo&gt;。与登录帧同源(NpcDailySchedule.json
// 的五段 × NpcActivityCatalog.json 的 MapPosition),区别是
// 那一条会把 CurrentSpoonAgentId /
// SpoonPosition(以及当前时段的 SpoonAgentId)填上真值 —— 登录帧那三处恒为 0。
//
func GameRouter_NpcLife_BuildTimeTables(ctx context.Context, focusAgentTag uint32, spoonAgentId int32, cfg any) map[string]any {
// Router implementation for GameRouter_NpcLife_BuildTimeTables
return nil
}
// 当前时段落在五段里的第几段(找不到就 -1)。与 NpcActivityCatalog.CurrentFor 同判据。
func GameRouter_NpcLife_CurrentSliceIndex(ctx context.Context, agentTag uint32, daySecond int32) int32 {
// Router implementation for GameRouter_NpcLife_CurrentSliceIndex
return 0
}
// spoon 坐标 = 当前时段那条活动的 MapPosition(查不到就是零向量)。
func GameRouter_NpcLife_SpoonPositionFor(ctx context.Context, agentTag uint32) any {
// Router implementation for GameRouter_NpcLife_SpoonPositionFor
var zero any; return zero
}
// =================================================================================
// ① 兜底 POI 动作 SyncAgentPlayPOIAction(68459398)
// =================================================================================
//
// 让某个单位直接播一个 POI 动作(UrbanDiversityPOIActionConfig)。
// 通道是已验证过的原生帧(GameRouter.Ambient.cs:892 同款):
// 跑步机 = 198「站姿_运动_跑步机跑步」(MainActionGroup 400 / MainActions [86190])。
// ⛑ 开了生活日程(AgentDataSetsActivityCfgId 非 0)时客户端会盖掉它
// ⇒ 要验这条就把 gameplay.rosterNpc.dailyLife 关掉。
//
func GameRouter_NpcLife_PushNpcPoiActionAsync(ctx context.Context, session *GameSession, unitId uint64, poiActionId uint32) int32 {
// Router implementation for GameRouter_NpcLife_PushNpcPoiActionAsync
return 0
}
// =================================================================================
// ② C2S 查询:客户端主动问「好感 NPC 都在哪儿」
// =================================================================================
//
// C2S AskQueryAllFavorNpcAgentPos(63357395) / AskQueryFavorNpcAgentPos(63216726)。
// ★★ 为什么这条比上面那条还重要:**客户端自己在问**,而 `unknown-methods.log` 里
// 63357395 已经出现过 54 次 —— 我们每次都回「空字典」(`DefaultReturnCatalog4229938`
// 的 `dict7-empty` = `FF 01`)⇒ 客户端眼里「城里一个好感 NPC 都不在场」,
// 那 `LifeScheduleNpcModule` 自然不会给任何人排表演。
// 返回类型(客户端 C# ClientToGameDelegate):
// UXRPCTask&lt;Dictionary&lt;uint, FavorNpcPosInfo&gt;&gt;,
// FavorNpcPosInfo { uint RaidId; UXVector3 Position; }(key = AgentTag)。
// 线格式:`FF` + Int7(count+1) + 每项 [u32 agentTag] + [FF + u32 RaidId + f32×3]。
// ⛑ count 用 UxWriter.Int7(内部已 +1),别自己再加。
//
func GameRouter_NpcLife_OnAskQueryFavorNpcAgentPos(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_NpcLife_OnAskQueryFavorNpcAgentPos
var zero any; return zero
}