using Ananta.SDK.Serialization; using Auto = Ananta.Server.RpcTypes.Client4229938.Auto; namespace Ananta.Server.Protocol.Client4229938; /// /// AskLoadHouseData(63109556) 的返回体 —— 2026-09-30「自带家具」。 /// /// ⛔ 先纠正一个编号坑:客户端有两套互不相通的 Auto.Reader 编号, /// 千万别混用: /// /// Lua 侧(S2C 推送):LuaGen/AutoGen/RPCDeserializeAuto.lua,定义 /// Auto.Reader[N] + midToReader[mid] + midToName[mid](833 条)。 /// AskLoadHouseData 不在这张表里(它是 C# 侧请求/应答链路)⇒ /// 拿 Lua 的编号去指这条返回体是错的。 /// C# 侧(请求/应答):在 Assembly-CSharp 里,本仓库读不到。 /// /// /// 本 DTO 的字段顺序/编码不是靠编号推的,是逐字节验出来的(一次性探针 /// %TEMP%\houseprobe,反射调 UxSerializer.Serialize 再和权威兜底表比): /// /// 空 payload(OwnerPid = 0)与 DefaultReturnCatalog4229938 里 /// case 63109556u 的 57 字节模板 MATCH ⇒ 字段顺序 + 三个计数编码全对。 /// 四套房的非空 payload 被按 Lua Auto.Reader[321]/[1010]/[390] 手写的解析器 /// 完整吃掉(remain = 0),家具件数与模板一致。 /// /// /// 下面这套 Lua 编号是亲自读过、可信的(用于对照字段顺序): /// /// Auto.Reader[321] = HouseInfo { HouseId, ParkingSpaceVehicleIdDict, FloorBuildInfoDict, /// CurPlacedFurnitureInstanceId, WallInfo, Configuration, /// FashionShowcaseDict, PlacementStatistics } /// Auto.Reader[1010] = IndoorBuildInfo { Root = ReadComplex(390) } /// Auto.Reader[390] = PlacedFurnitureInfo /// /// /// 本返回体(HouseBuildingClientData,dump TypeDefIndex 33347,7 个 FieldIndex)逐字: /// /// OwnerPid u64 /// FloorBuildInfoDict ReadDict7Bit(u32 → complex) ← ★ 家具树宿主;键 = 楼层号(1 基,见下) /// WallInfo complex HouseWallInfo /// FashionShowcaseDict ReadDict7Bit(u64 → complex) /// Configuration complex PlayerSingleHouseConfiguration /// FurnitureGadgetDataDict ReadDict7Bit(u64 → complex) /// DisabledFurniturePlacedIds ReadList7Bit(u64) (带 UXRPCParameterLengthLimit(1024)) /// /// /// ⚠️ FloorBuildInfoDict 的键是 楼层号,1 基,地面层 = 1,取值 1..Floors /// —— 不是 indoorId。取证链见 PrivateServerConfig.HouseFurnitureSettings.KeyMode /// 与 GameRouter.House.cs 的 ResolveFurnitureFloorKeys。 /// /// ⛔⛔ 三个 ReadDict7Bit 不能用 Auto.Reader[321](HouseInfo)那套 Int32 计数。 /// Base.ReadDict7Bit(LX6/Service/RPCDeserializeBase.lua)是 /// 「0xFF 标记 + 偏置 varint(线上写 count+1)」,而 Base.ReadDict 是 /// 「0xFF + Int32 计数」。UxSerializer.WriteDictionary 的默认计数编码是 /// Int32 ⇒ 这里每个 7bit 字段都必须显式写 /// [UxCollection(Count = UxCountEncoding.Int7)],否则空表会写成 /// FF 00 00 00 00(客户端读到 count=0 但多吞 3 个字节 ⇒ 整包错位)。 /// 判据:权威兜底表 DefaultReturnCatalog4229938 里这条 57 字节的 /// FloorBuildInfoDict 段就是 FF 01,不是 FF 00 00 00 00。 /// /// ⛔ Root.ChildrenDict 反过来是 Base.ReadDict /// (Auto.Reader[390] 里 ChildrenDict = Base.ReadDict(r:ReadUInt64, ReadComplex(390))) /// ⇒ 那里必须用 Int32 计数(空表 FF 00 00 00 00)。两个字段编码不一样, /// 别顺手统一。 /// [UxContract] internal sealed class HouseBuildingClientData4229938 { public ulong OwnerPid; [UxCollection(Count = UxCountEncoding.Int7, ValueObjectEncoding = UxObjectEncoding.Complex)] public Dictionary FloorBuildInfoDict = new(); public Auto.HouseWallInfo WallInfo = new(); [UxCollection(Count = UxCountEncoding.Int7, ValueObjectEncoding = UxObjectEncoding.Complex)] public Dictionary FashionShowcaseDict = new(); public Auto.PlayerSingleHouseConfiguration Configuration = new(); [UxCollection(Count = UxCountEncoding.Int7, ValueObjectEncoding = UxObjectEncoding.Complex)] public Dictionary FurnitureGadgetDataDict = new(); [UxCollection(Count = UxCountEncoding.Int7)] public List DisabledFurniturePlacedIds = new(); } /// /// HouseBuildingClientData.FurnitureGadgetDataDict 的 value 类型(FurnitureGadgetData)。 /// /// ⛔ 下面这一族 Auto.Reader[N] 编号(1088 / 1073 / 932 / 1133 / 289 / 1089) /// 属于 C# 侧编号空间,本仓库读不到那张表 ⇒ 只当形状备忘,别拿它去 Lua 的 /// RPCDeserializeAuto.lua 里查(那边的编号完全不同)。已亲自验证过的 Lua 编号只有 /// [321] HouseInfo / [1010] IndoorBuildInfo / [390] PlacedFurnitureInfo。 /// /// 形状逐字抄自客户端 reader,但本轮永远不下发(FurnitureGadgetDataDict 恒为空字典)—— /// 鱼缸 / 收藏品是「家具运行时数据」,不在「房子自带家具」范围内。 /// 保留完整字段只是为了让这个类型将来真被填上时字节是对的,而不是个只会写 0xFF 的空壳。 /// [UxContract] internal sealed class HouseFurnitureGadgetData4229938 { public HouseFishTankGadgetData4229938? FishTank; public HouseCollectibleGadgetData4229938? Collectible; } /// Auto.Reader[1073]:{ SlotFish: Dict7Bit<u32, complex(1133)> }。 [UxContract] internal sealed class HouseFishTankGadgetData4229938 { [UxCollection(Count = UxCountEncoding.Int7, ValueObjectEncoding = UxObjectEncoding.Complex)] public Dictionary SlotFish = new(); } /// Auto.Reader[932]。 [UxContract] internal sealed class HouseCollectibleGadgetData4229938 { public uint LootConfigId; public ulong ItemUniqueId; } /// Auto.Reader[1133](HouseFishInfo)。 [UxContract] internal sealed class HouseFishInfo4229938 { public uint UniqueId; public uint TemplateId; public float Weight; public float Length; public HouseFishPlacedInfo4229938? PlacedInfo; } /// Auto.Reader[289]:{ Furniture: complex(1089) }。 [UxContract] internal sealed class HouseFishPlacedInfo4229938 { public HouseFishPlacedFurnitureInfo4229938? Furniture; } /// Auto.Reader[1089]。 [UxContract] internal sealed class HouseFishPlacedFurnitureInfo4229938 { public uint HouseId; public ulong FurniturePlacedId; public uint FurnitureSlotId; } /// /// 把 模板树 /// 翻译成客户端认识的 PlacedFurnitureInfo 树。 /// /// 关键点: /// /// Position/Rotation 直接搬模板里的**绝对世界坐标**,不做任何变换 /// —— 模板叶子本来就落在房子所在的真实坐标上(绿屿公馆 14001 → 约 (2254..2294, 160.72, 1394..1404))。 /// PlacedInstanceId 必须**全局唯一且非 0**:客户端的 /// gFurnitureUIDManager.uid2FurnitureGoDict 是**跨房子**的全局表, /// 四套房共用一套小 id 会让「先去 A 再去 B」互相踩。这里用 /// houseId * 100000 + 模板节点号(节点号 ≤ 14150 < 100000 ⇒ 房内不撞; /// houseId ≥ 11200003 ⇒ 跨房不撞;最大值 ≈ 1.12e12 < 2^53 ⇒ Lua number 也能精确表示)。 /// GadgetInstanceId 恒 0 —— 家具本体是客户端按 FurnitureId 自己实例化的, /// 机关(鱼缸/电视…)才需要 gadget,那是另一条链路。 /// 父子关系照抄模板树(绿屿公馆的模板有 9 个「桌上的小物件」是挂在桌子下面的), /// 客户端用 ChildrenDict 表达父子,没有 ParentPlacedInstanceId 字段。 /// /// internal static class HouseFurnitureTree4229938 { /// 每套房子的 placedId 步长。必须 > 模板节点号上限(实测 14150)。 private const ulong PlacedIdStride = 100000UL; /// /// 生成 IndoorBuildInfo.Root:FurnitureId = 0 的纯容器, /// 顶层家具挂进 ChildrenDict。 /// internal static Auto.PlacedFurnitureInfo BuildRoot( uint houseId, IReadOnlyList topLevel, out int furnitureCount) { var root = new Auto.PlacedFurnitureInfo { FurnitureId = 0u, Position = new Auto.UXVector3(), Rotation = new Auto.UXVector3(), GadgetInstanceId = 0UL, PlacedInstanceId = 0UL, }; furnitureCount = 0; foreach (var node in topLevel) root.ChildrenDict[PlacedId(houseId, node.NodeId)] = Convert(houseId, node, ref furnitureCount); return root; } private static Auto.PlacedFurnitureInfo Convert( uint houseId, ClientData.Client4229938.HouseFurnitureTemplateNode4229938 node, ref int furnitureCount) { if (node.FurnitureId != 0) furnitureCount++; var placed = new Auto.PlacedFurnitureInfo { FurnitureId = node.FurnitureId, Position = new Auto.UXVector3 { X = node.X, Y = node.Y, Z = node.Z }, Rotation = new Auto.UXVector3 { X = node.RotationX, Y = node.RotationY, Z = node.RotationZ }, GadgetInstanceId = 0UL, PlacedInstanceId = PlacedId(houseId, node.NodeId), }; foreach (var child in node.Children) placed.ChildrenDict[PlacedId(houseId, child.NodeId)] = Convert(houseId, child, ref furnitureCount); return placed; } private static ulong PlacedId(uint houseId, uint nodeId) => (ulong)houseId * PlacedIdStride + nodeId; }