166 lines
9.3 KiB
Go
166 lines
9.3 KiB
Go
package routers
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
)
|
||
|
||
var _ = sync.Mutex{}
|
||
var _ = context.Background
|
||
|
||
//
|
||
// 场景物品(路灯 / 街边道具 / 可破坏物 / 念动力目标)的服务端流式下发 —— AnantaWorldMod
|
||
// 的等价实现,不需要 frida 注入器。
|
||
// 原理与 GameRouter.GadgetGrid.cs 完全同构:客户端自己有烘焙好的 SceneItemBlob,服务端
|
||
// 只要告诉它「哪几个 40m 网格在视野内」,客户端就从 blob 里把物品铺出来。
|
||
// 注入器每 700ms 调 SceneItemMgr.SyncSceneItemGridAOIAddByIndex(cx, cz, exceptIds, sectorIdList,
|
||
// playerStandardIndex);服务端等价物 = 推 SyncDestructibleGridAOIDecrease(68969593),
|
||
// 接收端 GameSceneToClientImpl 三连展开正是 remove → increase → AddDecrease(→AddByIndex)。
|
||
// ⛑️⛑️ 必须用 68969593,不是 68934309:68934309 的 args 只装得下 aoiInfo 一个字段
|
||
// (dump.cs TypeDefIndex 35754),它的接收端是记账用的;网格下发只能走 Decrease 孪生
|
||
// (与 gadget 的 68956797 同律)。用错 mid 的表现是「推了、日志干净、一个都不出」。
|
||
// 网格→sector 表直接复用 GadgetCellsCatalog4229938(Configs/GadgetCells.json):
|
||
// 那份文件本身就是 AnantaWorldMod 运行时导出的 cell→sector(5116 格,meta 写明
|
||
// "runtime export from 4229938 client"),两个家族用的是同一张烘焙表。
|
||
//
|
||
func GameRouter_SceneItemGrid_SceneItemGridStateOf(ctx context.Context, session *GameSession) any {
|
||
// Router implementation for GameRouter_SceneItemGrid_SceneItemGridStateOf
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// ★★★ R93(自 donor v2.1「打气球」移植)—— 把「脚本自建的可破坏物」登记为受保护,
|
||
// 直到显式撤下。心跳 / 刷新重声明 AOI 窗口时,这些 id 会进 ExceptIds 并随
|
||
// addInfos 一起重发,从而不被客户端按烘焙 blob 覆盖掉。
|
||
//
|
||
func GameRouter_SceneItemGrid_ProtectDestructibles(ctx context.Context, session *GameSession, items []any) any {
|
||
// Router implementation for GameRouter_SceneItemGrid_ProtectDestructibles
|
||
var zero any; return zero
|
||
}
|
||
|
||
// ★ R93 — 撤下保护(离开气球区 / 小游戏结束时用)。
|
||
func GameRouter_SceneItemGrid_UnprotectDestructibles(ctx context.Context, session *GameSession, ids []any) any {
|
||
// Router implementation for GameRouter_SceneItemGrid_UnprotectDestructibles
|
||
var zero any; return zero
|
||
}
|
||
|
||
// 世界 ready 钩子:启动场景物品流。幂等;配置关闭时 no-op。
|
||
func GameRouter_SceneItemGrid_EnsureSceneItemGrid(ctx context.Context, session *GameSession) any {
|
||
// Router implementation for GameRouter_SceneItemGrid_EnsureSceneItemGrid
|
||
var zero any; return zero
|
||
}
|
||
|
||
// 断线 / 切场景:停循环。已经铺出来的物品留在原地。
|
||
func GameRouter_SceneItemGrid_StopSceneItemGrid(ctx context.Context, session *GameSession) any {
|
||
// Router implementation for GameRouter_SceneItemGrid_StopSceneItemGrid
|
||
var zero any; return zero
|
||
}
|
||
|
||
// 已下发网格数(面板 / 调试用)。
|
||
func GameRouter_SceneItemGrid_SceneItemGridSentCount(ctx context.Context, session *GameSession) int32 {
|
||
// Router implementation for GameRouter_SceneItemGrid_SceneItemGridSentCount
|
||
return 0
|
||
}
|
||
|
||
func GameRouter_SceneItemGrid_RunSceneItemGridLoopAsync(ctx context.Context, session *GameSession, owner any, token context.Context) any {
|
||
// Router implementation for GameRouter_SceneItemGrid_RunSceneItemGridLoopAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_SceneItemGrid_TickSceneItemGridAsync(ctx context.Context, session *GameSession, owner any) any {
|
||
// Router implementation for GameRouter_SceneItemGrid_TickSceneItemGridAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// 把 (cx,cz) 周围还没发过的网格按 sector 列表分组下发,一组一条消息。返回发出的格子数。
|
||
//
|
||
func GameRouter_SceneItemGrid_PushSceneItemGridAsync(ctx context.Context, session *GameSession, owner any, cx int32, cz int32, reason byte, fullResend bool, spawns []any) int32 {
|
||
// Router implementation for GameRouter_SceneItemGrid_PushSceneItemGridAsync
|
||
return 0
|
||
}
|
||
|
||
//
|
||
// ★ donor《content-streaming-implementation》§4 —— 显式的「请卸载这批格子」。
|
||
// 单独一条 68969593:addGridInfo 空表、removeGridInfo 装出界的格子。
|
||
// ⛑ donor §4 提醒 family 差异:68969593 没有 extraInfo / activeIds / inactiveIds
|
||
// 三个字段(那是 gadget 侧才有的),别照 GadgetGrid 那份填。
|
||
// ⛑ BuildingId 必须写 int32(值 0),漏写会让整条 payload 位移。
|
||
//
|
||
func GameRouter_SceneItemGrid_PushSceneItemGridRemovalAsync(ctx context.Context, session *GameSession, removed []any, cx int32, cz int32, reason byte) any {
|
||
// Router implementation for GameRouter_SceneItemGrid_PushSceneItemGridRemovalAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// 念动力探针(硬规则 28):DestructibleGridAOIIncrease.addInfos 可以凭空造一个可抓的
|
||
// SceneItem —— 给 CfgId(SceneitemConfig 里 CanQInteraction=true 的条目)+
|
||
// Position 就够,客户端会实例化成真物件并被 MindPowerMgr 认成 Q 目标。
|
||
// 这是 T1-22 里唯一能直接验证「服务端显式摆放」这条路的手段:箱子出现 ⇒ 路通,家具不出
|
||
// 只是缺坐标;箱子都不出 ⇒ 先修通道。
|
||
// ⛑️ 只在本会话第一次推送时摆:心跳 / 刷新 / 跨格重声明都会重发整窗,摆第二次会叠箱子。
|
||
// ⛑️ 序列化的坑:DestructibleInfo 的客户端 reader 是多态的
|
||
// (ReadStaticDestructibleInfo 跳转表:0=null / 1=BASE / 2=Dynamic / 3=Static / 4=Task)。
|
||
// 这里先走 UxSerializer 的常规写法;若客户端日志出现
|
||
// deserialize failed ... parameter=DestructibleInfo,就说明缺那个 1=BASE 的标签字节,
|
||
// 需要改成手写裸字节(见 SceneItemGridMethods4229938.cs 里 DestructibleInfo4229938 的注释)。
|
||
// 失败的包只会被客户端丢弃,不会污染场景,所以这个探针可以放心试。
|
||
//
|
||
//
|
||
// ★ R93 —— 把登记进 Protected 的可破坏物 DTO 归一到本管线使用的
|
||
// SceneMethods.DestructibleInfo4229938。
|
||
// 气球(GameRouter.Balloon.cs)持有的是 BowlingWire 那份 DestructibleInfo
|
||
// —— 它是**真机验证过能建出 hold** 的标注(Marker=1 / BASE),而网格通道这份是同构双胞胎
|
||
// (SceneMethods,字段一致但 UXVector3 用的是 Auto 版)。两份绝不能混用(会静默串位),
|
||
// 所以登记时存 object、心跳时在这里逐字段映射。认不出的类型返回 null(该件不摆)。
|
||
//
|
||
func GameRouter_SceneItemGrid_ToGridDestructibleInfo(ctx context.Context, info any) any {
|
||
// Router implementation for GameRouter_SceneItemGrid_ToGridDestructibleInfo
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_SceneItemGrid_BuildTestBoxSpawns(ctx context.Context, session *GameSession, owner any, px float32, py float32, pz float32) []any {
|
||
// Router implementation for GameRouter_SceneItemGrid_BuildTestBoxSpawns
|
||
return nil
|
||
}
|
||
|
||
type SceneItemGridSessionState struct {
|
||
SyncRoot any `json:"syncRoot"`
|
||
Cts *context.CancelFunc `json:"cts"`
|
||
Loop *any `json:"loop"`
|
||
// 已经下发过的网格;只在刷新 / 心跳 / 跨格重声明时重发。
|
||
Sent []any `json:"sent"`
|
||
LastCellX int32 `json:"lastCellX"`
|
||
LastCellZ int32 `json:"lastCellZ"`
|
||
HasCell bool `json:"hasCell"`
|
||
LoadedSent bool `json:"loadedSent"`
|
||
// 可破坏物总开关 SyncDestructibleSceneAOIActive(68418115) 本会话是否已推。
|
||
ActiveSwitchSent bool `json:"activeSwitchSent"`
|
||
// 念动力测试盒本会话是否已摆过(只摆一次,见 SpawnTestBox)。
|
||
TestBoxSpawned bool `json:"testBoxSpawned"`
|
||
//
|
||
// ★★★ R93(自 donor v2.1「打气球」移植)—— 本会话「脚本自建、但不在客户端烘焙
|
||
// SceneItemBlob 里」的可破坏物保护表(气球的 12/16 颗球就属于这一类)。
|
||
// 病因(donor 真机实测):心跳 / 刷新每 5 s 重发一次 SyncDestructibleGridAOIDecrease
|
||
// 声明 AOI 窗口;该报文 aoiInfo.addInfos 为空 + addGridInfo.ExceptIds 为空 ⇒
|
||
// 客户端语义是「这些格子改为从你自己的 SceneItemBlob 重新解析」⇒ 服务端合成的球
|
||
// (blob 里没有)每次心跳都被整片抹掉。
|
||
// 修法:把这类 id 记在这里,心跳时 ① 塞进 addGridInfo.ExceptIds(明确告诉客户端
|
||
// 「别删这些」),② 并把其 DestructibleInfo 重新塞回 aoiInfo.addInfos(保证还在)。
|
||
// ⛑ Info 是 object:登记方可能持有 BowlingWire 那份 DestructibleInfo(真机验证过
|
||
// 能建出 hold 的标注),由 ToGridDestructibleInfo 归一。
|
||
// 🆕 2026-09-30:本通道改**严格增量**后,这张表实际上**退化为空转** —— 因为「重推整窗
|
||
// ⇒ addInfos 空 ⇒ 客户端按 blob 重建 ⇒ 抹掉服务端合成物」这条链路的**前半段不再发生**
|
||
// (已推过的格子根本不会再发)。气球本体本来就是 GameRouter.Balloon.cs 用
|
||
// SyncDestructibleObjectAddList(68963429) + 自带的 68969593 报文建的,不依赖这里。
|
||
// 表**保留不删**:一旦把 heartbeatMs / refreshMs / resendWindowOnMove 回滚成整窗重推,
|
||
// 它立刻重新生效(否则气球会被抹掉,R93 的病会原样复发)。
|
||
//
|
||
Protected map[uint64]any `json:"protected"`
|
||
LastPushMs int64 `json:"lastPushMs"`
|
||
ReadyAtMs int64 `json:"readyAtMs"`
|
||
Pushes int32 `json:"pushes"`
|
||
CellsSent int32 `json:"cellsSent"`
|
||
}
|
||
|