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

@ -0,0 +1,89 @@
namespace AcDream.Core.Physics;
/// <summary>
/// Result of retail <c>CPhysicsObj::set_state</c> (<c>0x00514DD0</c>) and
/// the nested <c>set_hidden</c> call (<c>0x00514C60</c>).
/// </summary>
public readonly record struct RetailPhysicsStateTransition(
PhysicsStateFlags PreviousState,
PhysicsStateFlags RequestedState,
PhysicsStateFlags FinalState,
bool LightingChanged,
bool NoDrawChanged,
RetailHiddenTransition HiddenTransition)
{
public bool HasChanges => PreviousState != FinalState;
}
public enum RetailHiddenTransition
{
None,
BecameHidden,
BecameVisible,
}
/// <summary>
/// Pure port of retail's PhysicsState side-effect ordering. The server's raw
/// state is installed first; Lighting and NoDraw are processed next; Hidden is
/// processed last and may rewrite ReportCollisions/IgnoreCollisions.
/// </summary>
public static class RetailPhysicsStateTransitions
{
/// <summary>
/// Retail <c>CPhysicsObj</c> constructor state at <c>0x00512508</c>.
/// This deliberately does not contain Hidden: a normal visible
/// CreateObject must not manufacture an UnHide script.
/// </summary>
public const PhysicsStateFlags ConstructorState =
PhysicsStateFlags.EdgeSlide
| PhysicsStateFlags.Lighting
| PhysicsStateFlags.Gravity
| PhysicsStateFlags.ReportCollisions;
public static RetailPhysicsStateTransition Apply(
PhysicsStateFlags previousState,
PhysicsStateFlags requestedState)
{
// CPhysicsObj::set_state compares only the low 16 bits. Every state
// with a set_state side effect (Lighting, NoDraw, Hidden) lives there.
uint changedLow = ((uint)previousState ^ (uint)requestedState) & 0xFFFFu;
bool lightingChanged =
(changedLow & (uint)PhysicsStateFlags.Lighting) != 0;
bool noDrawChanged =
(changedLow & (uint)PhysicsStateFlags.NoDraw) != 0;
bool hiddenChanged =
(changedLow & (uint)PhysicsStateFlags.Hidden) != 0;
PhysicsStateFlags finalState = requestedState;
RetailHiddenTransition hiddenTransition = RetailHiddenTransition.None;
if (hiddenChanged)
{
if ((requestedState & PhysicsStateFlags.Hidden) != 0)
{
// set_hidden(true): report_collision_end, clear reporting,
// then enable collision-ignore before hiding from the cell.
finalState &= ~PhysicsStateFlags.ReportCollisions;
finalState |= PhysicsStateFlags.Hidden
| PhysicsStateFlags.IgnoreCollisions;
hiddenTransition = RetailHiddenTransition.BecameHidden;
}
else
{
// set_hidden(false): clear ignore and restore collision
// reporting even when the incoming raw state omitted it.
finalState &= ~(PhysicsStateFlags.Hidden
| PhysicsStateFlags.IgnoreCollisions);
finalState |= PhysicsStateFlags.ReportCollisions;
hiddenTransition = RetailHiddenTransition.BecameVisible;
}
}
return new RetailPhysicsStateTransition(
previousState,
requestedState,
finalState,
lightingChanged,
noDrawChanged,
hiddenTransition);
}
}