feat(net+app): apply ObjScale from PhysicsData (Phase 5c)
The Nullified Statue of a Drudge renders correctly in color/shape
after Phase 5b's SubPalette fix, but the user reported it's rendering
at base drudge size when it should be larger. AC statues use the
PhysicsDescriptionFlag.ObjScale field to scale the base mesh; my
parser was consuming-and-skipping those 4 bytes.
Changes:
- CreateObject.TryParse: extract the u32 float from the ObjScale
field instead of advancing past it. Declaration moved to the top
of the method alongside other accumulators so the PartialResult
local function at the bottom can reference it for the truncation
fallback path. Same structural change for position and setupTableId
since PartialResult already needed them too.
- CreateObject.Parsed gains ObjScale (float?).
- WorldSession.EntitySpawn gains ObjScale; propagated through the
fire site in ProcessDatagram.
- GameWindow.OnLiveEntitySpawned bakes a scale matrix into every
MeshRef's PartTransform when ObjScale != 1.0, following the same
pattern the offline scenery hydration already uses. No change to
WorldEntity or StaticMeshRenderer — the scale is absorbed into the
per-part transform the renderer already multiplies through.
Tests: 77 core + 83 net = 160, all green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
733f8ff601
commit
f67f7851e6
3 changed files with 32 additions and 7 deletions
|
|
@ -684,6 +684,14 @@ public sealed class GameWindow : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
// Apply ObjScale by baking a scale matrix into each MeshRef's
|
||||
// PartTransform. Scenery hydration already does this pattern
|
||||
// (scaleMat baked into PartTransform at Setup flatten time).
|
||||
// Fallback to 1.0 if the server didn't send ObjScale (common for
|
||||
// creatures/characters whose size is intrinsic to the mesh).
|
||||
float scale = spawn.ObjScale ?? 1.0f;
|
||||
var scaleMat = System.Numerics.Matrix4x4.CreateScale(scale);
|
||||
|
||||
var meshRefs = new List<AcDream.Core.World.MeshRef>();
|
||||
for (int partIdx = 0; partIdx < parts.Count; partIdx++)
|
||||
{
|
||||
|
|
@ -697,7 +705,11 @@ public sealed class GameWindow : IDisposable
|
|||
if (resolvedOverridesByPart is not null && resolvedOverridesByPart.TryGetValue(partIdx, out var partOverrides))
|
||||
surfaceOverrides = partOverrides;
|
||||
|
||||
meshRefs.Add(new AcDream.Core.World.MeshRef(mr.GfxObjId, mr.PartTransform)
|
||||
// Pre-multiply scale so it happens FIRST (local mesh coords) before
|
||||
// the part-local transform and the entity-root transform.
|
||||
var transform = scale == 1.0f ? mr.PartTransform : scaleMat * mr.PartTransform;
|
||||
|
||||
meshRefs.Add(new AcDream.Core.World.MeshRef(mr.GfxObjId, transform)
|
||||
{
|
||||
SurfaceOverrides = surfaceOverrides,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ public static class CreateObject
|
|||
IReadOnlyList<TextureChange> TextureChanges,
|
||||
IReadOnlyList<SubPaletteSwap> SubPalettes,
|
||||
uint? BasePaletteId,
|
||||
float? ObjScale,
|
||||
string? Name);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -134,6 +135,13 @@ public static class CreateObject
|
|||
/// </summary>
|
||||
public static Parsed? TryParse(ReadOnlySpan<byte> body)
|
||||
{
|
||||
// Accumulators declared at the top so PartialResult (local function
|
||||
// at the bottom) can reference them before they're conditionally
|
||||
// populated — C# rejects forward references otherwise.
|
||||
ServerPosition? position = null;
|
||||
uint? setupTableId = null;
|
||||
float? objScale = null;
|
||||
|
||||
try
|
||||
{
|
||||
int pos = 0;
|
||||
|
|
@ -229,7 +237,6 @@ public static class CreateObject
|
|||
pos += 4;
|
||||
}
|
||||
|
||||
ServerPosition? position = null;
|
||||
if ((physicsFlags & PhysicsDescriptionFlag.Position) != 0)
|
||||
{
|
||||
if (body.Length - pos < 32) return null;
|
||||
|
|
@ -263,7 +270,6 @@ public static class CreateObject
|
|||
pos += 4;
|
||||
}
|
||||
|
||||
uint? setupTableId = null;
|
||||
if ((physicsFlags & PhysicsDescriptionFlag.CSetup) != 0)
|
||||
{
|
||||
if (body.Length - pos < 4) return null;
|
||||
|
|
@ -288,7 +294,12 @@ public static class CreateObject
|
|||
if (body.Length - pos < childCount * 8) return null;
|
||||
pos += childCount * 8; // each child = guid u32 + locationId u32
|
||||
}
|
||||
if ((physicsFlags & PhysicsDescriptionFlag.ObjScale) != 0) pos += 4;
|
||||
if ((physicsFlags & PhysicsDescriptionFlag.ObjScale) != 0)
|
||||
{
|
||||
if (body.Length - pos < 4) return PartialResult();
|
||||
objScale = BinaryPrimitives.ReadSingleLittleEndian(body.Slice(pos));
|
||||
pos += 4;
|
||||
}
|
||||
if ((physicsFlags & PhysicsDescriptionFlag.Friction) != 0) pos += 4;
|
||||
if ((physicsFlags & PhysicsDescriptionFlag.Elasticity) != 0) pos += 4;
|
||||
if ((physicsFlags & PhysicsDescriptionFlag.Translucency) != 0) pos += 4;
|
||||
|
|
@ -316,13 +327,13 @@ public static class CreateObject
|
|||
}
|
||||
|
||||
return new Parsed(guid, position, setupTableId, animParts,
|
||||
textureChanges, subPalettes, basePaletteId, name);
|
||||
textureChanges, subPalettes, basePaletteId, objScale, name);
|
||||
|
||||
// Local helper: if we ran out of fields past PhysicsData, still
|
||||
// return the useful prefix (guid/position/setup/animParts/textures/palettes).
|
||||
// return the useful prefix (guid/position/setup/animParts/textures/palettes/scale).
|
||||
Parsed PartialResult() => new(
|
||||
guid, position, setupTableId, animParts,
|
||||
textureChanges, subPalettes, basePaletteId, null);
|
||||
textureChanges, subPalettes, basePaletteId, objScale, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ public sealed class WorldSession : IDisposable
|
|||
IReadOnlyList<CreateObject.TextureChange> TextureChanges,
|
||||
IReadOnlyList<CreateObject.SubPaletteSwap> SubPalettes,
|
||||
uint? BasePaletteId,
|
||||
float? ObjScale,
|
||||
string? Name);
|
||||
|
||||
/// <summary>Fires when the session finishes parsing a CreateObject.</summary>
|
||||
|
|
@ -234,6 +235,7 @@ public sealed class WorldSession : IDisposable
|
|||
parsed.Value.TextureChanges,
|
||||
parsed.Value.SubPalettes,
|
||||
parsed.Value.BasePaletteId,
|
||||
parsed.Value.ObjScale,
|
||||
parsed.Value.Name));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue