146 lines
8.8 KiB
Go
146 lines
8.8 KiB
Go
package routers
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
)
|
||
|
||
var _ = sync.Mutex{}
|
||
var _ = context.Background
|
||
|
||
//
|
||
// 房产自带家具的「家具机关(furniture gadget)」—— 2026-10-01。
|
||
// <para>用户报的症状:「沙发椅子已经生成了的没法交互」——
|
||
// 家具模型都在(AskLoadHouseData 的 FloorBuildInfoDict 已上线),
|
||
// 但椅子坐不了、衣柜开不了、电视点不亮。</para>
|
||
// <para>根因(一句话):客户端把家具建出来之后会再问一次服务端要机关实例 id ——
|
||
// HouseGadgetManager.lua:CreateGadgetsForFurnitures →
|
||
// AskCreateHouseFurnitureGadgets(63581446),读应答里的
|
||
// mapping.placedID2GadgetInstanceID,非 0 才写进 placedId2GadgetIdMap
|
||
// 并 gFurnitureUtils:TrySetGadgetInstanceId(furnitureGo, id)。
|
||
// 而这条 RPC 在服务端一直是 DefaultReturnCatalog4229938.cs:624 的
|
||
// body=[0xFF,0x01] shape="struct(631:dict7-empty)" —— 恒回空映射
|
||
// ⇒ placedId2GadgetIdMap 恒空 ⇒ 所有机关失效。日志铁证(console-latest.log:319-321):
|
||
// <code>
|
||
// -> rpc AskCreateHouseFurnitureGadgets [0x03CA2D06] 274b
|
||
// ! [RPC4229938] unimplemented AskCreateHouseFurnitureGadgets -> typed-default struct(631:dict7-empty) 2b
|
||
// <- ret ... e=0 2b
|
||
// </code></para>
|
||
// <para>★ 为什么说「家具树本身是对的,只缺这一步」:客户端请求的 id 个数
|
||
// 逐套精确等于该房模板树里「HouseFurnitureConfig.PathId != 0」的家具数 ——
|
||
// 绿屿 33/33、旧物仓 3/3、观澜 6/6、素光 1/1(unknown-methods.log 请求体逐字节解码后与模板树对齐)。
|
||
// 而且请求里的 id 正好是 PlacedInstanceId = houseId*100000 + nodeId 的 32 位截断形态
|
||
// (绿屿首件 0xC5388A93 - 0xC5381F20 = 14003 = 模板节点号)。</para>
|
||
// <para>★ 机关 prefab 从哪来:HouseFurnitureConfig.PathId
|
||
// 就是 GadgetCatalog.json 的 pathId(逐条核对过,见
|
||
// HouseFurnitureGadgetCatalog4229938 的注释表)。位置也不用反推 ——
|
||
// HouseFurnitureTemplateConfig 节点的 Position 就是世界绝对坐标
|
||
// (绿屿椅 (2290.629, 160.720, 1397.878)、素光衣柜 (1096.527, 13.040, 2375.721)),
|
||
// Rotation.Y 就是 yaw。</para>
|
||
// <para>本文件做的事:</para>
|
||
// <list type="bullet">
|
||
// <item>AskCreateHouseFurnitureGadgets:对请求里每个 placedId
|
||
// 反解 nodeId → FurnitureId → PathId,回 placedId → gadgetInstanceId 映射;
|
||
// spawnGadgetEntities=true(默认)时同时用 SyncGadgetAdd(68843189)
|
||
// 把机关实体摆到家具节点的世界坐标上(uniqueId = placedId,与返回值同源)。</item>
|
||
// <item>AskDestoryHouseFurnitureGadgets:摘掉记录 + 推
|
||
// SyncGadgetRemoveList(68199571),回空 body(客户端该 RPC 的应答 reader 是空函数)。</item>
|
||
// </list>
|
||
// <para>⛔ 两个开关都要留(gameplay.houseFurniture):
|
||
// gadgets 是总开关;spawnGadgetEntities 决定要不要真的 SyncGadgetAdd。
|
||
// 后者是实测分流用的 —— 有两种可能,客户端行为决定哪一种:
|
||
// <list type="number">
|
||
// <item>H1(默认,spawn=true):机关实体得由服务端 SyncGadgetAdd 造出来,
|
||
// 客户端 OnGadgetSlotCompLoaded 的 gGadgetManager:GetEntitySearchByInstanceId(gadgetId) 才找得到。</item>
|
||
// <item>H2(spawn=false):机关 GameObject 本来就在家具 prefab 里
|
||
// (RegisterFurnitureLuaSlotReplace 用的就是 houseFurniture.gadgetGameObject),
|
||
// 服务端只需发一个唯一的非 0 id,客户端自己 TrySetGadgetInstanceId 就能把它点亮。
|
||
// OnGadgetSlotCompLoaded 里那个 10 帧重试计时器更像 H2(id 要等 TrySetGadgetInstanceId 之后才可寻址)。</item>
|
||
// </list>
|
||
// 上机判据:H1 若把椅子/灯渲染成两遍就是猜错了,把 spawnGadgetEntities 改 false 即可(热重载,不用重编译)。</para>
|
||
// <para>回滚:gameplay.houseFurniture.gadgets=false(回到恒空映射兜底),
|
||
// 或把两条 mid 从 Enabled4229938MethodIds 删掉。两者都与改动前逐字节一致。</para>
|
||
//
|
||
//
|
||
// 63581446 — HouseGadgetManager.CreateGadgetsForFurnitures 的请求:
|
||
// 「这些 placedId 的家具,给我它们的机关实例 id」。
|
||
// <para>进房子时每层各发一次(素光小筑两层会发两次,同一节点出现两遍)。
|
||
// 客户端离开房子时 DestroyGadgetsForFurnitures 会发 AskDestoryHouseFurnitureGadgets,
|
||
// 但场景切换也会清空客户端的表(OnBeforeSwitchScene → ClearAll),
|
||
// 所以这里每次请求都重推 SyncGadgetAdd(同一个 instanceId,幂等刷新),
|
||
// 否则「出去再进来」家具就再也点不动了。</para>
|
||
//
|
||
func GameRouter_HouseFurnitureGadget_OnCreateHouseFurnitureGadgets(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_HouseFurnitureGadget_OnCreateHouseFurnitureGadgets
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// 63759452 — HouseGadgetManager.DestroyGadgetsForFurnitures(官方拼写就是 Destory)。
|
||
// 离开房子 / 切场景时客户端会发;应答是 void(客户端 reader 是空函数)。
|
||
//
|
||
func GameRouter_HouseFurnitureGadget_OnDestoryHouseFurnitureGadgets(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_HouseFurnitureGadget_OnDestoryHouseFurnitureGadgets
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// ★★ 2026-10-01:把家具机关实体在每次网格 AOI 推送之后补回来。
|
||
// <para>为什么必须补:SyncGadgetGridAOIDecrease 的 addGridInfo 在客户端是
|
||
// 「当前 AOI 快照」语义 —— 本仓 GameRouter.GadgetGrid.cs 的注释早就记过这一点
|
||
// (「an AOIMove that only carried the newly entered cells made every previously streamed prop
|
||
// disappear」)。世界摆件(摊位 / 派对家具 / 健身房器材)被撤掉后,客户端能从自己的
|
||
// blob里再铺出来,所以它们没事;而家具机关是纯服务端造的、blob 里没有
|
||
// ⇒ 一撤就永久消失。</para>
|
||
// <para>实测铁证(E:\anatacbt\log\Player.log,2026-10-01 10:23–10:24):
|
||
// <code>
|
||
// 10:23:26.554 S2C NOTIFY SyncGadgetAddList (3731b) ← 33 件建出来
|
||
// 10:23:34.103 [DRMK-HG] entityHits=33/33 ← 全在
|
||
// 10:23:37.496 S2C NOTIFY SyncGadgetGridAOIDecrease (1363b) ← 第一次网格快照
|
||
// 10:23:56.345 [DRMK-HG] entityHits=0/33 ← 全没了
|
||
// </code>
|
||
// 该窗口内唯一的机关类 S2C 就是 SyncGadgetGridAOIDecrease(每 5 秒一次,
|
||
// cells=81)。</para>
|
||
// <para>补发走同一条通道(SyncGadgetAddList)且 uniqueId = placedId 不变 ——
|
||
// 与 OnCreateHouseFurnitureGadgets 里那条「每次请求都重推」是同一个幂等刷新手法,
|
||
// 不会多出第二份家具。只补玩家附近(<300 m)那一套房的,跑到别的区就不做无用功。</para>
|
||
// <para>关掉:gameplay.houseFurniture.respawnAfterGridPush=false(热重载,不用重编译)。</para>
|
||
//
|
||
func GameRouter_HouseFurnitureGadget_TickHouseFurnitureGadgetsAsync(ctx context.Context, session *GameSession, state any, px float32, pz float32) any {
|
||
// Router implementation for GameRouter_HouseFurnitureGadget_TickHouseFurnitureGadgetsAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
// 只补玩家附近那一套房的机关;玩家跑到别的区就不做无用功。
|
||
func GameRouter_HouseFurnitureGadget_IsNearPlayer(ctx context.Context, x float32, z float32, px float32, pz float32) bool {
|
||
// Router implementation for GameRouter_HouseFurnitureGadget_IsNearPlayer
|
||
return true
|
||
}
|
||
|
||
//
|
||
// 把客户端发来的 placedId 反解成模板节点号。
|
||
// <para>⛔ 线上是 32 位截断形态:客户端发的是
|
||
// (houseId * 100000 + nodeId) & 0xFFFFFFFF,高 4 字节恒为 0
|
||
// (实测绿屿首件 93 8A 38 C5 00 00 00 00 = 0xC5388A93,
|
||
// 而 0xC5388A93 - (11200003*100000 mod 2^32) = 14003)。
|
||
// 因为 houseId ≥ 11200003 ⇒ houseId*100000 ≈ 1.12e12 > 2^32,截断必然发生。</para>
|
||
// <para>同时兼容「万一某条链路发的是完整 64 位值」:placedId > 0xFFFFFFFF
|
||
// 时直接减 houseId*100000。两条路都算一遍、只认落在
|
||
// 1..99999(模板节点号上限实测 14150)的那个。</para>
|
||
//
|
||
func GameRouter_HouseFurnitureGadget_ResolveNodeIdFromPlacedId(ctx context.Context, placedId uint64, houseId uint32, baseOffset uint64) uint32 {
|
||
// Router implementation for GameRouter_HouseFurnitureGadget_ResolveNodeIdFromPlacedId
|
||
return 0
|
||
}
|
||
|
||
//
|
||
// 家具节点的机关落点。模板 Position 本来就是世界绝对坐标,直接搬;
|
||
// 少数家具带 GadgetOffset(实测 6 个 float:pos xyz + rot xyz,全表只有 2 条有值,
|
||
// 且都不在四套房的模板树里),有就叠加上去,避免以后换模板踩坑。
|
||
//
|
||
func GameRouter_HouseFurnitureGadget_ApplyGadgetOffset(ctx context.Context, node any, furnitureId uint32) any {
|
||
// Router implementation for GameRouter_HouseFurnitureGadget_ApplyGadgetOffset
|
||
var zero any; return zero
|
||
}
|
||
|