This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

226 lines
11 KiB
C#
Raw Permalink Blame History

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