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