feat(combat): port retail held weapon parenting

Select the default combat mode from ordered equipped objects so bows request missile stance. Parse CreateObject parent metadata and ParentEvent, then render held objects as separate children composed from setup holding locations and placement frames each animation tick.
This commit is contained in:
Erik 2026-07-11 13:02:26 +02:00
parent 564d39dfea
commit ab6d96d113
18 changed files with 1152 additions and 17 deletions

View file

@ -118,6 +118,10 @@ public sealed class WorldSession : IDisposable
ushort InstanceSequence = 0,
ushort MovementSequence = 0,
ushort ServerControlSequence = 0,
ushort PositionSequence = 0,
uint? ParentGuid = null,
uint? ParentLocation = null,
uint? PlacementId = null,
// PublicWeenieDesc optional-tail bytes. null means the corresponding
// flag was absent; zero means the server explicitly sent the enum's
// undefined/default value.
@ -175,6 +179,10 @@ public sealed class WorldSession : IDisposable
InstanceSequence: parsed.InstanceSequence,
MovementSequence: parsed.MovementSequence,
ServerControlSequence: parsed.ServerControlSequence,
PositionSequence: parsed.PositionSequence,
ParentGuid: parsed.ParentGuid,
ParentLocation: parsed.ParentLocation,
PlacementId: parsed.PlacementId,
RadarBlipColor: parsed.RadarBlipColor,
RadarBehavior: parsed.RadarBehavior,
CombatUse: parsed.CombatUse,
@ -243,6 +251,12 @@ public sealed class WorldSession : IDisposable
/// </summary>
public event Action<VectorUpdate.Parsed>? VectorUpdated;
/// <summary>
/// Fires for retail <c>ParentEvent (0xF749)</c>, which attaches a separate
/// child object to a creature holding location (weapons, shields, ammo).
/// </summary>
public event Action<ParentEvent.Parsed>? ParentUpdated;
/// <summary>
/// Fires when the server broadcasts a <c>SetState (0xF74B)</c> game
/// message — a previously-spawned entity's <c>PhysicsState</c>
@ -886,6 +900,12 @@ public sealed class WorldSession : IDisposable
new DeleteObject.Parsed(
parsed.Value.Guid, parsed.Value.InstanceSequence, FromPickup: true));
}
else if (op == ParentEvent.Opcode)
{
var parsed = ParentEvent.TryParse(body);
if (parsed is not null)
ParentUpdated?.Invoke(parsed.Value);
}
else if (op == UpdateMotion.Opcode)
{
// Phase 6.6: the server sends UpdateMotion (0xF74C) whenever an