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
Ananta-ps/Ananta.Server/Ananta.Core/Protocol/Client4229938/HouseBuildingWire4229938.cs
T

225 lines
11 KiB
C#
Raw Normal View History

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