feat(net): L.3b — capture per-object friction + elasticity from CreateObject
Companion to L.3a (a1c27b3) which ported the velocity-reflection bounce.
Previously the CreateObject parser did `pos += 4` for both Friction and
Elasticity floats — silently dropping the wire data so every entity got
the PhysicsBody constructor default (0.05 elasticity, 0.5 friction).
Server-set bouncier surfaces or stickier objects therefore felt
identical to inert walls on collision. Inelastic projectiles via
PhysicsState bit 0x20000 (already plumbed in Commit A) had no per-
object elasticity to override.
Now the parser captures the floats, surfaces them on Parsed +
EntitySpawn, leaving the values at default (null) when their
PhysicsDescriptionFlag bits aren't set. Subscribers (e.g., the
remote-entity dead-reckoning path, future spell-projectile rendering)
can apply them when they wire elasticity to PhysicsBody.Elasticity.
The local player's PhysicsBody is constructed at controller init,
not from a CreateObject — so this commit alone produces no
user-visible local-player change. Effect lands when remote/projectile
physics consume EntitySpawn.Elasticity.
Files:
- CreateObject.cs:284-294: declare friction + elasticity accumulators.
- CreateObject.cs:467-487: parse floats instead of skipping.
- CreateObject.cs:543-555: propagate to Parsed via both return paths.
- WorldSession.cs:67-71: extend EntitySpawn record.
- WorldSession.cs:665-668: pipe through to subscribers.
Tests: 1491 still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a1c27b3afb
commit
851e88364d
2 changed files with 49 additions and 7 deletions
|
|
@ -120,7 +120,13 @@ public static class CreateObject
|
|||
ushort ServerControlSequence = 0,
|
||||
ushort ForcePositionSequence = 0,
|
||||
uint? PhysicsState = null,
|
||||
uint? ObjectDescriptionFlags = null);
|
||||
uint? ObjectDescriptionFlags = null,
|
||||
// L.3b (2026-04-30): per-object friction + elasticity from the
|
||||
// wire. Default to null when their PhysicsDescriptionFlag bits
|
||||
// weren't set; subscribers fall back to PhysicsBody constructor
|
||||
// defaults (0.05f elasticity, 0.5f friction).
|
||||
float? Friction = null,
|
||||
float? Elasticity = null);
|
||||
|
||||
/// <summary>
|
||||
/// The relevant subset of the server-sent <c>MovementData</c> /
|
||||
|
|
@ -286,6 +292,13 @@ public static class CreateObject
|
|||
// "ObjectDescriptionFlags" at the WeenieHeader trailer.
|
||||
uint? physicsState = null;
|
||||
uint? objectDescriptionFlags = null;
|
||||
// L.3b (2026-04-30): per-object friction + elasticity. Wire-encoded
|
||||
// when their PhysicsDescriptionFlag bits are set. Default values
|
||||
// come from PhysicsBody constructors; these overrides drive the
|
||||
// velocity-reflection bounce magnitude per object (e.g., bouncier
|
||||
// platforms vs. inert walls).
|
||||
float? friction = null;
|
||||
float? elasticity = null;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -453,8 +466,25 @@ public static class CreateObject
|
|||
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.Friction) != 0)
|
||||
{
|
||||
if (body.Length - pos < 4) return PartialResult();
|
||||
friction = BinaryPrimitives.ReadSingleLittleEndian(body.Slice(pos));
|
||||
pos += 4;
|
||||
}
|
||||
if ((physicsFlags & PhysicsDescriptionFlag.Elasticity) != 0)
|
||||
{
|
||||
// L.3b (2026-04-30): capture instead of skipping. The wire
|
||||
// float is the per-object elasticity used by the velocity-
|
||||
// reflection bounce (CPhysicsObj::set_elasticity at
|
||||
// acclient_2013_pseudo_c.txt:277817, clamped to [0, 0.1]).
|
||||
// Was previously dropped — every object got the default
|
||||
// 0.05f, so server-set bouncier surfaces felt identical to
|
||||
// walls.
|
||||
if (body.Length - pos < 4) return PartialResult();
|
||||
elasticity = BinaryPrimitives.ReadSingleLittleEndian(body.Slice(pos));
|
||||
pos += 4;
|
||||
}
|
||||
if ((physicsFlags & PhysicsDescriptionFlag.Translucency) != 0) pos += 4;
|
||||
if ((physicsFlags & PhysicsDescriptionFlag.Velocity) != 0) pos += 12; // vec3
|
||||
if ((physicsFlags & PhysicsDescriptionFlag.Acceleration) != 0) pos += 12;
|
||||
|
|
@ -510,14 +540,18 @@ public static class CreateObject
|
|||
return new Parsed(guid, position, setupTableId, animParts,
|
||||
textureChanges, subPalettes, basePaletteId, objScale, name, itemType, motionState, motionTableId,
|
||||
instanceSeq, teleportSeq, serverControlSeq, forcePositionSeq,
|
||||
physicsState, objectDescriptionFlags);
|
||||
physicsState, objectDescriptionFlags,
|
||||
friction, elasticity);
|
||||
|
||||
// Local helper: if we ran out of fields past PhysicsData, still
|
||||
// return the useful prefix (guid/position/setup/animParts/textures/palettes/scale/motion).
|
||||
Parsed PartialResult() => new(
|
||||
guid, position, setupTableId, animParts,
|
||||
textureChanges, subPalettes, basePaletteId, objScale, null, null, motionState, motionTableId,
|
||||
PhysicsState: physicsState, ObjectDescriptionFlags: objectDescriptionFlags);
|
||||
PhysicsState: physicsState,
|
||||
ObjectDescriptionFlags: objectDescriptionFlags,
|
||||
Friction: friction,
|
||||
Elasticity: elasticity);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -63,7 +63,13 @@ public sealed class WorldSession : IDisposable
|
|||
// ObjectDescriptionFlags: retail PWD._bitfield (acclient.h:6431-6463)
|
||||
// — drives IsPlayer/IsPK/IsPKLite/IsImpenetrable for PvP gating.
|
||||
uint? PhysicsState = null,
|
||||
uint? ObjectDescriptionFlags = null);
|
||||
uint? ObjectDescriptionFlags = null,
|
||||
// L.3b (2026-04-30): per-object physics tuning from the wire.
|
||||
// Friction defaults to PhysicsBody constructor value (0.5f).
|
||||
// Elasticity defaults to 0.05f. When set, drives the velocity-
|
||||
// reflection bounce magnitude (clamped to [0, 0.1] retail-side).
|
||||
float? Friction = null,
|
||||
float? Elasticity = null);
|
||||
|
||||
/// <summary>Fires when the session finishes parsing a CreateObject.</summary>
|
||||
public event Action<EntitySpawn>? EntitySpawned;
|
||||
|
|
@ -657,7 +663,9 @@ public sealed class WorldSession : IDisposable
|
|||
parsed.Value.MotionState,
|
||||
parsed.Value.MotionTableId,
|
||||
parsed.Value.PhysicsState,
|
||||
parsed.Value.ObjectDescriptionFlags));
|
||||
parsed.Value.ObjectDescriptionFlags,
|
||||
parsed.Value.Friction,
|
||||
parsed.Value.Elasticity));
|
||||
}
|
||||
}
|
||||
else if (op == DeleteObject.Opcode)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue