namespace AcDream.Core.Physics; /// /// Result of retail CPhysicsObj::set_state (0x00514DD0) and /// the nested set_hidden call (0x00514C60). /// 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, } /// /// 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. /// public static class RetailPhysicsStateTransitions { /// /// Retail CPhysicsObj constructor state at 0x00512508. /// This deliberately does not contain Hidden: a normal visible /// CreateObject must not manufacture an UnHide script. /// 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); } }