143 lines
7.9 KiB
Go
143 lines
7.9 KiB
Go
package routers
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
)
|
||
|
||
var _ = sync.Mutex{}
|
||
var _ = context.Background
|
||
|
||
//
|
||
// 房产(House)所有权 —— 2026-09-30。用户报的症状:
|
||
// 「手机 app 里买房产会播动画 + 显示已购买,但实际走到那栋房子显示未购买」。
|
||
// <para>根因(一句话):客户端判断「这套房是不是我的」只有一条路径 ——
|
||
// gBuyHouseUtils.CheckHasBuyTheHouse(houseId) →
|
||
// gPlayerManager.infoMinor.bindData.housesInfo.HouseInfoList →
|
||
// PlayerInfoMinorData.lua:54 的 t.housesInfo = info.InfoMinor.housesInfo。
|
||
// 也就是说所有权完全由登录包决定,而服务端从来没往里写过。
|
||
// 手机里那套「已购买」是 BuyHouseUtils.BuyTheHouse 拿到 error=0 之后的
|
||
// 本地乐观行为(自己 SendMessage(ON_BUY_HOUSE_SUCCESS) + 开
|
||
// HOUSE_REWARD_PANEL 播 HouseConfig.GameVideo),
|
||
// 服务端当时既不记账也不推包 —— 所以面板一关 / 一重登 / 一开大地图,客户端重算一次就翻回「未购买」。</para>
|
||
// <para>本文件负责三件事:</para>
|
||
// <list type="number">
|
||
// <item>记账:GrantHouseOwnershipAsync —— 写
|
||
// WorldEntryState.Progress.OwnedHouseIds(PlayerProgressStore 落盘),
|
||
// 并推一次 SyncAddHouse(64459483) 让本次会话立刻生效
|
||
// (登录包里没有它,客户端得靠这条增量补上)。</item>
|
||
// <item>推送:PushSyncAddHouseAsync —— 线格式见
|
||
// HouseOwnershipWire4229938。</item>
|
||
// <item>读房:AskLoadHouseData(63109556) —— 客户端 C# 侧
|
||
// AskLoadHouseData(uint houseId) : UXRPCTask<HouseBuildingClientData>
|
||
// 唯一的用途是拿「这栋房子的装修内容」。2026-09-30 起这里还负责下发
|
||
// 「房子自带的家具」(gameplay.houseFurniture,见
|
||
// HouseFurnitureTemplateCatalog4229938)—— 在此之前一律回 57 字节空房,
|
||
// 那就是玩家看到的毛坯房。同时保证 OwnerPid 是自己而不是 0
|
||
// (兜底表给的是 0)。</item>
|
||
// </list>
|
||
// <para>本轮明确不做(用户拍板「只做所有权落地」+「自带家具」):
|
||
// 车库 / 停车(SyncHouseParking)、家具编辑 / 购买 / 摆放
|
||
// (AskAddBuildHouseIndoor / AskSaveHouseConfiguration / AskModifyHouse /
|
||
// 墙体编辑 / livehouse)、鱼缸与收藏品的运行时数据(FurnitureGadgetDataDict)、
|
||
// 访客与点赞、以及真实扣款(保持免费)。</para>
|
||
// <para>配置依赖:Configs/HouseConfig.json(5 条,见
|
||
// HouseCatalog4229938)。商店侧 NpcShopCatalog.json 早已含 94100080
|
||
// 「新启房产」,5 件商品齐全,所以 AskNpcShopCommodityInfo 不用改。</para>
|
||
//
|
||
//
|
||
// 购买落地:记所有权 + 落盘 + 推 SyncAddHouse。
|
||
// <para>⛔ 只在「第一次拿到」时推。BuyHouseUtils.SyncAddHouse 是
|
||
// table.insert(不是 set),同一套房推两次客户端列表里就有两条重复项。
|
||
// 已拥有时静默返回 —— 登录包已经把这条给过客户端了。</para>
|
||
// 写进日志的结果串(granted / already-owned / no-world)。
|
||
//
|
||
func GameRouter_House_GrantHouseOwnershipAsync(ctx context.Context, session *GameSession, houseId uint32, log any) string {
|
||
// Router implementation for GameRouter_House_GrantHouseOwnershipAsync
|
||
return ""
|
||
}
|
||
|
||
//
|
||
// 推 SyncAddHouse(64459483):payload 是**单个** HouseBaseInfo 的 complex。
|
||
// <para>⛔ 直接把 HouseActiveInfo 交给 NotifyAsync<T> ——
|
||
// UxSerializer 对非 Inline 的 [UxContract] 类型会自己写那一个 0xFF 包装,
|
||
// 正好对上客户端的 Base.ReadComplex(reader, Auto.Reader[368])。
|
||
// **不要**再包一层 Inline 壳(多一个 0xFF ⇒ 整包错位)。</para>
|
||
//
|
||
func GameRouter_House_PushSyncAddHouseAsync(ctx context.Context, session *GameSession, houseId uint32, log any) any {
|
||
// Router implementation for GameRouter_House_PushSyncAddHouseAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// 63109556 — 客户端 C# 侧唯一调用点:
|
||
// UXRPCTask<HouseBuildingClientData> AskLoadHouseData(uint houseId)。
|
||
// 入参就是一个裸 u32(ClientToGameDelegate.lua 的
|
||
// AskLoadHouseData_Serializer 只写一个 primitive),返回一个 complex(590)。
|
||
// <para>2026-09-30 起这条路要下发「房子自带的家具」。在此之前我们一律回
|
||
// 权威兜底表那份 57 字节全空 HouseBuildingClientData
|
||
// (DefaultReturnCatalog4229938 的 case 63109556u,逐字节由
|
||
// Auto.Reader[590] 推出)—— 只把 OwnerPid 换成自己的 pid,
|
||
// 因为兜底给的是 0(「无主」),跟所有权落地直接冲突。
|
||
// 空 FloorBuildInfoDict 就是毛坯房的直接原因。</para>
|
||
// <para>现在:houseId → HouseConfig.BuildId → HouseBuildConfig.IndoorId /
|
||
// Floors / FurnitureTemplateIds → HouseFurnitureTemplateConfig,
|
||
// 把模板树翻成 PlacedFurnitureInfo 树塞进 FloorBuildInfoDict。
|
||
// 详见 HouseFurnitureTree4229938 与
|
||
// HouseBuildingClientData4229938(线格式逐字对照在那儿)。</para>
|
||
// <para>兜底仍然保留:关掉开关 / houseId 查不到 / 房子没有可装修体
|
||
// (岸北悠居公寓 BuildId = 0)/ 模板树为空 —— 任一条命中就回 57 字节空房,
|
||
// 和改动前逐字节一致。</para>
|
||
//
|
||
func GameRouter_House_AskLoadHouseData(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_House_AskLoadHouseData
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// 「自带家具」下发分支。true = 已经回过包,调用方直接 return。
|
||
// <para>任何一步查不到都返回 false,让调用方走 57 字节兜底 —— 这样出问题时的表现
|
||
// 和改动前一模一样(毛坯),不会退化成半截包 / 空包。</para>
|
||
//
|
||
func GameRouter_House_TryReturnHouseFurnitureAsync(ctx context.Context, conn any, msg any, houseId uint32, owned bool) bool {
|
||
// Router implementation for GameRouter_House_TryReturnHouseFurnitureAsync
|
||
return true
|
||
}
|
||
|
||
//
|
||
// FloorBuildInfoDict 的键。
|
||
// <para>结论(2026-09-30 取证):键 = 楼层号,<u>1 基</u>,地面层 = 1,取值 1..Floors。
|
||
// 所以默认 floor 就是正解。</para>
|
||
// <para>证据链(全部来自客户端自己的文件,不是推测):</para>
|
||
// <list type="number">
|
||
// <item>HouseBuildConfig.FloorsName = [{floor:1,__RAW__name:"1F"}];
|
||
// 素光小筑 [{1,"1F"},{2,"2F"}],且长度恰好等于 Floors。</item>
|
||
// <item>HouseBuildConfig.BuildBound / CameraBound / CameraTopViewPoint
|
||
// 一律用 floor: 1,2(同时带 IndoorBoundId: 1,2,数值与 floor 相同)。</item>
|
||
// <item>HomeBuildPanelStore_Floor.lua:SelectOption(initFloor - 1)、
|
||
// houseCamera.floor = index + 1 ⇒ 游戏侧 floor 是 1 基、只有 UI 选项是 0 基;
|
||
// GetFloorByPlayerPosition 两处兜底都 return 1。</item>
|
||
// <item>LuaGmUtils.lua:166 的
|
||
// for floorKey, indoorBuildInfo in pairs(buildData.FloorBuildInfoDict) —— 变量名就叫
|
||
// floorKey;而且它对每个键都加载一遍家具(去重表 loadedFurnitureIds 建在
|
||
// 键循环体内部)⇒ 多写键 = 家具被创建多遍。所以默认绝不能「把所有候选键都写上碰运气」。</item>
|
||
// <item>HouseUtils.lua 三个函数(Add/Remove/ModifyOneFurnitureInfo)里的
|
||
// floor = floor or 0 只是「参数缺失」的兜底;0 本身是非法值(会走
|
||
// "无法找到楼层[0]的家具信息" 的 warn 分支)。</item>
|
||
// </list>
|
||
// <para>indoor / all 保留为诊断开关:GmSyncHouseIndoorBuildInfo(houseId,
|
||
// indoorId, …) 那条 GM 链路确实按 indoorId 组织,万一登录链路将来被证伪可立刻切过去验证。
|
||
// all 会同时写 1..Floors 与 IndoorId,一定产生重复家具,只用于确认
|
||
// 「客户端到底取哪个键」。</para>
|
||
//
|
||
func GameRouter_House_ResolveFurnitureFloorKeys(ctx context.Context, mode any, build any) []any {
|
||
// Router implementation for GameRouter_House_ResolveFurnitureFloorKeys
|
||
return nil
|
||
}
|
||
|
||
func GameRouter_House_LogFurnitureTree(ctx context.Context, log any, houseId uint32, nodes []any, depth int32) any {
|
||
// Router implementation for GameRouter_House_LogFurnitureTree
|
||
var zero any; return zero
|
||
}
|
||
|