namespace AcDream.Core.Physics;
///
/// The retail-faithful exemption gate at the top of
/// CPhysicsObj::FindObjCollisions. Decides — based on the moving
/// object's bits and the target's raw
/// PhysicsState + decoded —
/// whether collision against the target should be skipped entirely
/// (return OK_TS) or proceed to broad-phase / shape dispatch.
///
///
/// Ported from the named retail decompilation:
///
///
/// - acclient_2013_pseudo_c.txt:276782 — target
/// ETHEREAL_PS=0x4 & IGNORE_COLLISIONS_PS=0x10: walk through.
/// - acclient_2013_pseudo_c.txt:276787 — viewer mover vs
/// creature target: walk through (camera ray ignores creatures).
/// - acclient_2013_pseudo_c.txt:276971 — mover with
/// IGNORE_CREATURES (state & 0x400) vs creature target:
/// walk through.
/// - acclient_2013_pseudo_c.txt:276807-276839 — PvP rule:
///
/// If both are players: skip unless target is Impenetrable,
/// or both are PK, or both are PKLite. Mismatched PK status (PK vs
/// non-PK, PK vs PKLite) is exempted — players in different pools
/// pass through each other, matching retail muscle memory.
///
///
///
///
///
/// Cross-checked against ACE
/// references/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:381-405
/// (line-for-line C# port of the same logic). #299: retail's pseudo-C
/// (acclient_2013_pseudo_c.txt:276824-276827) checks the MOVER's own
/// state & IS_IMPENETRABLE (0x80) — the sign-bit test on the
/// truncated 16-bit ebx->object_info.state the decompiler renders as
/// if (state_1 < 0), sitting immediately before the target's
/// IsImpenetrable() check at the same nesting level — in addition to
/// the target's own IsImpenetrable(), either alone disqualifying the
/// tentative PvP exemption. ACE's port has both; acdream previously had only
/// the target half. The bit position is confirmed against
/// OBJECTINFO::init@0x0050cf30 (this->state |= 0x80 when
/// weenie_obj->IsImpenetrable()) and ACE's own
/// ObjectInfoState.IsImpenetrable = 0x80
/// (ACE.Server/Physics/ObjectInfo.cs:17) — the earlier comment here
/// blaming ACE for an addition was itself the divergence; ACE was
/// retail-faithful and acdream was missing the mover half.
///
///
public static class CollisionExemption
{
private const uint ETHEREAL_PS = 0x4u; // acclient.h:2819
private const uint IGNORE_COLLISIONS_PS = 0x10u; // acclient.h:2821
///
/// Should the moving object skip collision testing against this
/// target entirely? Returns true if exempt (no further
/// shape dispatch).
///
/// Raw retail PhysicsState bits
/// captured at CreateObject time (ETHEREAL/IGNORE/etc.).
/// Decoded
/// from the target's PWD bitfield
/// plus its ItemType-derived IsCreature bit.
/// The moving object's
/// — typically the local player's
/// IsPlayer + (PK/PKLite/Impenetrable bits if known) flags.
public static bool ShouldSkip(uint targetState, EntityCollisionFlags targetFlags,
ObjectInfoState moverState)
{
// 1. Target ETHEREAL + IGNORE_COLLISIONS → instant-skip.
// Retail (acclient_2013_pseudo_c.txt:276782):
// `if ((state & 4) AND (state & 0x10)) return 1`
// BOTH bits are required. ETHEREAL-alone takes the retail
// `obstruction_ethereal` path instead (pc:276806): the flag is
// set to 1 on the SpherePath and the shape test still runs,
// but BSP Path 1 (sphere_intersects_solid) weakens solid-
// containment so the player passes through the open door.
// ACE's Door.Open() broadcasts ETHEREAL only (0x0001000C) —
// this faithful port makes open doors passable via the BSP
// sphere_intersects_solid path (no solid leaf at the opening),
// which subsumes the former AD-7 shim. Divergence register
// row AD-7 retired in the same commit as this change.
if ((targetState & ETHEREAL_PS) != 0 && (targetState & IGNORE_COLLISIONS_PS) != 0)
return true;
// 2. Viewer mover + creature target → walk through.
// acclient_2013_pseudo_c.txt:276787-276790.
bool moverIsViewer = (moverState & ObjectInfoState.IsViewer) != 0;
bool targetIsCreature = (targetFlags & EntityCollisionFlags.IsCreature) != 0;
if (moverIsViewer && targetIsCreature)
return true;
// 3. IGNORE_CREATURES mover + creature target → walk through.
// acclient_2013_pseudo_c.txt:276971.
bool moverIgnoresCreatures = (moverState & ObjectInfoState.IgnoreCreatures) != 0;
if (moverIgnoresCreatures && targetIsCreature)
return true;
// 4. PvP exemption block.
// acclient_2013_pseudo_c.txt:276807-276839.
bool moverIsPlayer = (moverState & ObjectInfoState.IsPlayer) != 0;
bool targetIsPlayer = (targetFlags & EntityCollisionFlags.IsPlayer) != 0;
if (moverIsPlayer && targetIsPlayer)
{
// Tentatively exempt (retail `ebp_1 = 1`). Then disqualify
// if any of the COLLIDE conditions hold.
bool collide = false;
// 4a. Impenetrable target OR impenetrable mover → collide.
// acclient_2013_pseudo_c.txt:276824-276827 — retail checks
// BOTH: the mover's own state&0x80 (IS_IMPENETRABLE) short-
// circuits first, then the target's IsImpenetrable(). #299:
// acdream previously ported only the target half.
if ((moverState & ObjectInfoState.IsImpenetrable) != 0)
collide = true;
if (!collide && (targetFlags & EntityCollisionFlags.IsImpenetrable) != 0)
collide = true;
// 4b. Both PK → collide.
// acclient_2013_pseudo_c.txt:276832-276836.
if (!collide
&& (moverState & ObjectInfoState.IsPK) != 0
&& (targetFlags & EntityCollisionFlags.IsPK) != 0)
{
collide = true;
}
// 4c. Both PKLite → collide.
// acclient_2013_pseudo_c.txt:276837.
if (!collide
&& (moverState & ObjectInfoState.IsPKLite) != 0
&& (targetFlags & EntityCollisionFlags.IsPKLite) != 0)
{
collide = true;
}
if (!collide)
return true; // exempt — non-PK pair walks through
}
return false; // proceed to broad-phase + shape dispatch
}
}