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.
5 KiB
Retail default combat mode and held-item parenting
Sources
ClientCombatSystem::GetDefaultCombatMode @ 0x0056B310ACCWeenieObject::GetObjectAtLocation @ 0x0058CE00CM_Physics::DispatchSB_ParentEvent @ 0x006ACAF0SmartBox::HandleParentEvent @ 0x004535D0SmartBox::DoParentEvent @ 0x00452290CPhysicsObj::set_parent @ 0x00515A90CPhysicsObj::add_child @ 0x0050F870CPhysicsObj::UpdateChild @ 0x00512D50CPartArray::SetPlacementFrame @ 0x005193D0Frame::combine @ 0x005122E0
Cross-references:
- ACE
Creature_Equipment.GetPlacementLocationandTrySetChild - ACE
GameMessageParentEvent - ACE
WorldObject.SerializePhysicsData - DatReaderWriter
Setup.HoldingLocations,LocationType, andSetup.PlacementFrames
Default combat mode
GetDefaultCombatMode(showError):
player = get local player weenie
if player is missing:
return NonCombat
weaponId = player.GetObjectAtLocation(
MeleeWeapon | MissileWeapon | TwoHanded,
priority = 0)
if weaponId != 0:
weapon = get weenie weaponId
if weapon is missing:
return NonCombat
if weapon.PublicWeenieDesc.CombatUse == Missile:
return Missile
return Melee
heldId = player.GetObjectAtLocation(Held, priority = 0)
if heldId == 0:
return Melee
held = get weenie heldId
if held is missing:
return NonCombat
if held.InqType has the Caster bit (0x00008000):
return Magic
if showError == false:
print the retail cannot-use-held-item notice
return NonCombat
GetObjectAtLocation walks the player's ordered inventory-placement list and
returns the first entry whose location intersects the requested mask (and whose
priority intersects the requested priority when that priority is non-zero).
ParentEvent wire and freshness
DispatchParentEvent(blob):
require opcode 0xF749
parentGuid = read u32
childGuid = read u32
parentLocation = read u32
placementId = read u32
parentInstanceSequence = read u16
childPositionSequence = read u16
return HandleParentEvent(...)
HandleParentEvent(...):
parent = GetObjectA(parentGuid)
if parent is missing OR event parent-instance is newer than parent:
queue blob for parentGuid
return Queued
if event parent-instance is older than parent:
return Stale
child = GetObjectA(childGuid)
if child is missing:
queue blob for childGuid
return Queued
DoParentEvent(child, parent, parentLocation, placementId,
childPositionSequence)
Applying the attachment
DoParentEvent(child, parent, location, placement, positionSequence):
if positionSequence is not strictly newer than child.positionTimestamp:
return
child.positionTimestamp = positionSequence
parentWasMissing = child.parent is null
if parentWasMissing and parent is not the local player:
mark the parent's weenie as having parented state
child.set_parent(parent, location)
child.SetPlacementFrame(placement)
set_parent(child, parent, location):
holding = parent.setup.HoldingLocations[location]
if holding is missing:
fail
store child with holding.PartId and holding.Frame in parent's child list
detach child from its former parent and world cell
child.parent = parent
child.cell = parent.cell
UpdateChild(parent, child, holding.PartId, holding.Frame)
UpdateChild(parent, child, parentPartId, holdingFrame):
if parentPartId is a real part:
parentFrame = parent.parts[parentPartId].worldFrame
else:
parentFrame = parent.objectWorldFrame
child.objectWorldFrame = Combine(parentFrame, holdingFrame)
SetPlacementFrame(child, placement):
frame = child.setup.PlacementFrames[placement]
if frame is missing:
frame = child.setup.PlacementFrames[Default]
install frame (or null when Default is also absent)
update every child part from child.objectWorldFrame
With System.Numerics row-vector matrices, the render equivalent is:
childRootRelativeToParent = holdingFrame * parentPartRelativeToParent
childPartRelativeToParent = childPlacementPart * childRootRelativeToParent
childWorldRoot = parent's WorldEntity position + rotation
The child root and each child part must be recomputed after the parent's animation advances, because the referenced hand part moves every frame.
CreateObject bootstrap
An equipped child's CreateObject PhysicsData carries the same relationship
without requiring a later ParentEvent:
AnimationFrame flag -> child Placement id
Parent flag -> parent/wielder guid + ParentLocation
timestamp[0] -> child position sequence
timestamp[8] -> child instance sequence
ACE uses this form for already-equipped children sent while an observer begins tracking a creature and at login. ParentEvent remains necessary for live equip/re-parent changes and may arrive before the child's CreateObject, so the client must retain it until both objects exist.