feat(D.2b): Slice 2 — DollEntityBuilder (player Setup+ObjDesc -> doll WorldEntity)
Mirrors the palette/part-override mapping at GameWindow.cs:3390-3431 in a testable static helper. Build() accepts plain (SubPaletteId, Offset, Length) and (PartIndex, GfxObjId) tuples, builds PaletteOverride only when subPalettes.Count > 0 (same gate as GameWindow), and poses the entity at origin facing the viewer (191.367905° / +Z). Reserved synthetic guid 0xDA11D011 keeps the doll distinct from the live player and satisfies EntitySpawnAdapter's ServerGuid != 0 guard. 7 new tests green; suite 594/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ebcdf44c0c
commit
362d41aacf
2 changed files with 202 additions and 0 deletions
118
src/AcDream.App/Rendering/DollEntityBuilder.cs
Normal file
118
src/AcDream.App/Rendering/DollEntityBuilder.cs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Builds the dedicated paperdoll WorldEntity — retail's <c>makeObject(player)</c>
|
||||
/// clone: the player's Setup id + current ObjDesc (base palette + subpalette overlays
|
||||
/// + part overrides), posed at the scene origin facing the viewer.
|
||||
///
|
||||
/// <para>
|
||||
/// The palette / part-override mapping mirrors the inline construction in
|
||||
/// <c>GameWindow.cs</c> around lines 3390–3431. Extracted here so it is
|
||||
/// unit-testable without dats and so the paperdoll renderer owns a clean
|
||||
/// seam: it calls <see cref="Build"/> with fresh player state each time the
|
||||
/// ObjDesc changes, and the renderer only has to swap the entity into its
|
||||
/// dedicated mini-scene.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// The <see cref="DollServerGuid"/> is a reserved synthetic guid that is:
|
||||
/// (a) non-zero (so <c>EntitySpawnAdapter.OnCreate</c>'s <c>ServerGuid != 0</c>
|
||||
/// guard accepts it), and (b) high enough that it never collides with a real
|
||||
/// server-assigned guid.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class DollEntityBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserved synthetic guid for the paperdoll clone. High, deliberately
|
||||
/// outside the server's assignable range, and non-zero.
|
||||
/// </summary>
|
||||
public const uint DollServerGuid = 0xDA11_D011u;
|
||||
|
||||
// retail set_heading: 191.367905° about +Z, expressed as a Quaternion
|
||||
// (axis-angle, axis = (0,0,1), θ = 191.367905° in radians).
|
||||
private static readonly float _headingRad = 191.367905f * (MathF.PI / 180f);
|
||||
private static readonly Quaternion _dollRotation =
|
||||
Quaternion.CreateFromAxisAngle(new Vector3(0f, 0f, 1f), _headingRad);
|
||||
|
||||
/// <summary>
|
||||
/// Builds a synthetic <see cref="WorldEntity"/> for the paperdoll mini-scene.
|
||||
/// </summary>
|
||||
/// <param name="setupId">The player's Setup dat id (0x02xxxxxx).</param>
|
||||
/// <param name="meshRefs">Pre-resolved mesh refs (may be empty; caller fills them in).</param>
|
||||
/// <param name="basePaletteId">
|
||||
/// ObjDesc base palette id (0x04xxxxxx). Passed only when subpalettes are
|
||||
/// also present — mirrors GameWindow which only builds a PaletteOverride
|
||||
/// when <c>SubPalettes.Count > 0</c>.
|
||||
/// </param>
|
||||
/// <param name="subPalettes">
|
||||
/// Subpalette overlays from the server ObjDesc. Each tuple carries the
|
||||
/// subpalette dat id, byte offset into the base palette, and byte length.
|
||||
/// Null or empty → <c>PaletteOverride</c> on the returned entity is null.
|
||||
/// </param>
|
||||
/// <param name="partOverrides">
|
||||
/// AnimPartChange swaps from the server ObjDesc. Each tuple is a
|
||||
/// (PartIndex, replacement GfxObj id) pair. Null or empty → empty array.
|
||||
/// </param>
|
||||
public static WorldEntity Build(
|
||||
uint setupId,
|
||||
IReadOnlyList<MeshRef> meshRefs,
|
||||
uint? basePaletteId = null,
|
||||
IReadOnlyList<(uint SubPaletteId, byte Offset, byte Length)>? subPalettes = null,
|
||||
IReadOnlyList<(byte PartIndex, uint GfxObjId)>? partOverrides = null)
|
||||
{
|
||||
// --- palette override (mirrors GameWindow:3395-3405) ---
|
||||
// Only build when there are sub-palette overlays — same gate as GameWindow.
|
||||
PaletteOverride? paletteOverride = null;
|
||||
if (subPalettes is { Count: > 0 } spList)
|
||||
{
|
||||
var ranges = new PaletteOverride.SubPaletteRange[spList.Count];
|
||||
for (int i = 0; i < spList.Count; i++)
|
||||
ranges[i] = new PaletteOverride.SubPaletteRange(
|
||||
spList[i].SubPaletteId,
|
||||
spList[i].Offset,
|
||||
spList[i].Length);
|
||||
paletteOverride = new PaletteOverride(
|
||||
BasePaletteId: basePaletteId ?? 0u,
|
||||
SubPalettes: ranges);
|
||||
}
|
||||
|
||||
// --- part overrides (mirrors GameWindow:3407-3418) ---
|
||||
PartOverride[] entityPartOverrides;
|
||||
if (partOverrides is null or { Count: 0 })
|
||||
{
|
||||
entityPartOverrides = Array.Empty<PartOverride>();
|
||||
}
|
||||
else
|
||||
{
|
||||
entityPartOverrides = new PartOverride[partOverrides.Count];
|
||||
for (int i = 0; i < partOverrides.Count; i++)
|
||||
entityPartOverrides[i] = new PartOverride(
|
||||
partOverrides[i].PartIndex,
|
||||
partOverrides[i].GfxObjId);
|
||||
}
|
||||
|
||||
// --- assemble entity (mirrors GameWindow:3420-3431) ---
|
||||
// Id=0: the paperdoll renderer assigns its own render-local id when it
|
||||
// registers this entity with the mini-scene GpuWorldState. We set zero
|
||||
// here so the builder stays pure (no static counter). The caller may
|
||||
// replace it before registration.
|
||||
return new WorldEntity
|
||||
{
|
||||
Id = 0u,
|
||||
ServerGuid = DollServerGuid,
|
||||
SourceGfxObjOrSetupId = setupId,
|
||||
Position = Vector3.Zero,
|
||||
Rotation = _dollRotation,
|
||||
MeshRefs = meshRefs,
|
||||
PaletteOverride = paletteOverride,
|
||||
PartOverrides = entityPartOverrides,
|
||||
ParentCellId = null, // paperdoll mini-scene has no parent cell
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue