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;
}