226 lines
11 KiB
C#
226 lines
11 KiB
C#
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<u32, complex(1133)> }</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 < 100000 ⇒ 房内不撞;
|
||
/// houseId ≥ 11200003 ⇒ 跨房不撞;最大值 ≈ 1.12e12 < 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 步长。必须 > 模板节点号上限(实测 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;
|
||
}
|