diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 4404954..733c026 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -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(); 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, }); diff --git a/src/AcDream.Core.Net/Messages/CreateObject.cs b/src/AcDream.Core.Net/Messages/CreateObject.cs index edafedd..da6cae2 100644 --- a/src/AcDream.Core.Net/Messages/CreateObject.cs +++ b/src/AcDream.Core.Net/Messages/CreateObject.cs @@ -89,6 +89,7 @@ public static class CreateObject IReadOnlyList TextureChanges, IReadOnlyList SubPalettes, uint? BasePaletteId, + float? ObjScale, string? Name); /// @@ -134,6 +135,13 @@ public static class CreateObject /// public static Parsed? TryParse(ReadOnlySpan 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 { diff --git a/src/AcDream.Core.Net/WorldSession.cs b/src/AcDream.Core.Net/WorldSession.cs index c85de0e..7de33b1 100644 --- a/src/AcDream.Core.Net/WorldSession.cs +++ b/src/AcDream.Core.Net/WorldSession.cs @@ -50,6 +50,7 @@ public sealed class WorldSession : IDisposable IReadOnlyList TextureChanges, IReadOnlyList SubPalettes, uint? BasePaletteId, + float? ObjScale, string? Name); /// Fires when the session finishes parsing a CreateObject. @@ -234,6 +235,7 @@ public sealed class WorldSession : IDisposable parsed.Value.TextureChanges, parsed.Value.SubPalettes, parsed.Value.BasePaletteId, + parsed.Value.ObjScale, parsed.Value.Name)); } }