feat(vfx): port retail hidden and teleport presentation

Preserve canonical live-object ownership across Hidden transitions and remote teleport placement so effects, collision, streaming, and targeting remain synchronized.
This commit is contained in:
Erik 2026-07-14 14:59:48 +02:00
parent a51ebc66e9
commit 1e98d81448
46 changed files with 4883 additions and 127 deletions

View file

@ -45,6 +45,70 @@ public static class PhysicsObjUpdate
body.calc_acceleration();
}
/// <summary>
/// Commits the contact/walkable/collision-response tail of retail
/// <c>CPhysicsObj::SetPositionInternal</c> (<c>0x00515330</c>) in its
/// original order. The pre-transition flags are explicit because a
/// deferred placement may temporarily park the body without contact while
/// still needing the source edge when its destination cell becomes ready.
/// </summary>
/// <remarks>
/// Retail writes Contact and recalculates acceleration, calls
/// <c>set_on_walkable</c> (which invokes HitGround/LeaveGround), then calls
/// <c>handle_all_collisions</c> at <c>0x005154FE</c>. Collision response
/// must therefore observe any velocity change made by the movement
/// callback; moving that callback after reflection changes the result.
/// </remarks>
public static void CommitSetPositionTransition(
PhysicsBody body,
bool inContact,
bool onWalkable,
bool collisionNormalValid,
Vector3 collisionNormal,
bool previousContact,
bool previousOnWalkable,
Action? hitGround = null,
Action? leaveGround = null)
{
ArgumentNullException.ThrowIfNull(body);
// SetPositionInternal replaces Contact first but retains the source
// OnWalkable bit through its first calc_acceleration call. A deferred
// teleport may have parked the live body with both bits cleared, so
// restore the captured source bit explicitly before reproducing that
// ordering.
if (previousOnWalkable)
body.TransientState |= TransientStateFlags.OnWalkable;
else
body.TransientState &= ~TransientStateFlags.OnWalkable;
if (inContact)
body.TransientState |= TransientStateFlags.Contact;
else
body.TransientState &= ~TransientStateFlags.Contact;
body.calc_acceleration();
bool finalOnWalkable = inContact && onWalkable;
if (finalOnWalkable)
body.TransientState |= TransientStateFlags.OnWalkable;
else
body.TransientState &= ~TransientStateFlags.OnWalkable;
if (!previousOnWalkable && finalOnWalkable)
hitGround?.Invoke();
else if (previousOnWalkable && !finalOnWalkable)
leaveGround?.Invoke();
body.calc_acceleration();
HandleAllCollisions(
body,
collisionNormalValid,
collisionNormal,
previousContact,
previousOnWalkable,
finalOnWalkable);
}
/// <summary>
/// retail <c>handle_all_collisions</c> (0x00514780). Reflects or zeros the body's
/// <see cref="PhysicsBody.Velocity"/> (retail m_velocityVector) based on