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:
parent
564d39dfea
commit
ab6d96d113
18 changed files with 1152 additions and 17 deletions
|
|
@ -188,6 +188,29 @@ public sealed class CreateObjectTests
|
|||
Assert.Equal((byte)2, parsed.Value.CombatUse);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_ParentedChild_CapturesPlacementParentAndPositionSequence()
|
||||
{
|
||||
byte[] body = BuildMinimalCreateObjectWithWeenieHeader(
|
||||
guid: 0x60000002u,
|
||||
name: "Bow",
|
||||
itemType: (uint)ItemType.MissileWeapon,
|
||||
placementId: (uint)DatReaderWriter.Enums.Placement.LeftHand,
|
||||
parentGuid: 0x50000001u,
|
||||
parentLocation: (uint)DatReaderWriter.Enums.ParentLocation.LeftHand,
|
||||
positionSeq: 0x3456,
|
||||
instanceSeq: 0x789A);
|
||||
|
||||
var parsed = CreateObject.TryParse(body);
|
||||
|
||||
Assert.NotNull(parsed);
|
||||
Assert.Equal((uint)DatReaderWriter.Enums.Placement.LeftHand, parsed.Value.PlacementId);
|
||||
Assert.Equal(0x50000001u, parsed.Value.ParentGuid);
|
||||
Assert.Equal((uint)DatReaderWriter.Enums.ParentLocation.LeftHand, parsed.Value.ParentLocation);
|
||||
Assert.Equal((ushort)0x3456, parsed.Value.PositionSequence);
|
||||
Assert.Equal((ushort)0x789A, parsed.Value.InstanceSequence);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Retail radar: PublicWeenieDesc carries two independently gated bytes.
|
||||
// Absence is distinct from an explicitly transmitted zero because zero is
|
||||
|
|
@ -625,6 +648,11 @@ public sealed class CreateObjectTests
|
|||
byte? radarBehavior = null,
|
||||
byte? combatUse = null,
|
||||
ushort movementSeq = 0,
|
||||
uint? placementId = null,
|
||||
uint? parentGuid = null,
|
||||
uint? parentLocation = null,
|
||||
ushort positionSeq = 0,
|
||||
ushort instanceSeq = 0,
|
||||
uint materialType = 0,
|
||||
uint cooldownId = 0,
|
||||
double cooldownDuration = 0,
|
||||
|
|
@ -640,12 +668,27 @@ public sealed class CreateObjectTests
|
|||
bytes.Add(0);
|
||||
bytes.Add(0);
|
||||
|
||||
// PhysicsData: physics flags = 0, then PhysicsState u32, then 9 seq stamps
|
||||
// PhysicsData: optional placement + parent bootstrap, then 9 seq stamps
|
||||
// (PhysicsTimeStamp enum order; index 1 = ObjectMovement).
|
||||
WriteU32(bytes, 0);
|
||||
uint physicsFlags = 0;
|
||||
if (placementId.HasValue) physicsFlags |= (uint)CreateObject.PhysicsDescriptionFlag.AnimationFrame;
|
||||
if (parentGuid.HasValue) physicsFlags |= (uint)CreateObject.PhysicsDescriptionFlag.Parent;
|
||||
WriteU32(bytes, physicsFlags);
|
||||
WriteU32(bytes, physicsState);
|
||||
if (placementId.HasValue) WriteU32(bytes, placementId.Value);
|
||||
if (parentGuid.HasValue)
|
||||
{
|
||||
WriteU32(bytes, parentGuid.Value);
|
||||
WriteU32(bytes, parentLocation ?? 0u);
|
||||
}
|
||||
for (int i = 0; i < 9; i++)
|
||||
WriteU16(bytes, i == 1 ? movementSeq : (ushort)0);
|
||||
WriteU16(bytes, i switch
|
||||
{
|
||||
0 => positionSeq,
|
||||
1 => movementSeq,
|
||||
8 => instanceSeq,
|
||||
_ => 0,
|
||||
});
|
||||
Align4(bytes);
|
||||
|
||||
// Fixed WeenieHeader prefix per ACE SerializeCreateObject.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue