using AcDream.Core.Items;
namespace AcDream.Core.Physics;
///
/// Per-entity flags driving the retail-faithful PvP / Player /
/// Impenetrable exemption logic in FindObjCollisions. Decoded
/// from PublicWeenieDesc._bitfield at CreateObject time.
///
///
/// Bit positions (verified against
/// docs/research/named-retail/acclient_2013_pseudo_c.txt:406898-406918
/// where ACCWeenieObject::IsPK/IsPKLite/IsImpenetrable read directly
/// from this->pwd._bitfield):
///
///
/// - BF_PLAYER = 0x8 (bit 3) →
/// - BF_PLAYER_KILLER = 0x20 (bit 5) →
/// - BF_FREE_PKSTATUS = 0x200000 (bit 21) →
/// - BF_PKLITE_PKSTATUS = 0x2000000 (bit 25) →
///
///
///
/// is NOT a PWD bit — retail derives it from
/// PublicWeenieDesc._type matching ITEM_TYPE_CREATURE
/// (acclient.h ITEM_TYPE enum). Set at registration time by callers that
/// already know the item type.
///
///
[Flags]
public enum EntityCollisionFlags : byte
{
None = 0x00,
/// Set when BF_PLAYER (0x8) is set in pwd._bitfield.
IsPlayer = 0x01,
/// Derived from ItemType.Creature on the spawn payload.
IsCreature = 0x02,
/// Set when BF_PLAYER_KILLER (0x20) is set.
IsPK = 0x04,
/// Set when BF_PKLITE_PKSTATUS (0x2000000) is set.
IsPKLite = 0x08,
/// Set when BF_FREE_PKSTATUS (0x200000) is set (a.k.a. "Free" PK status — cannot be PKed).
IsImpenetrable = 0x10,
///
/// Runtime collision metadata: the target has a retail
/// CWeenieObject. This is deliberately distinct from
/// because doors, portals, and items also have a
/// weenie. OBJECTINFO::missile_ignore at 0x0050CEB0
/// requires this distinction for ethereal targets.
///
HasWeenie = 0x20,
///
/// AP-71 (Campaign P Slice P4, 2026-07-30): set when BOTH
/// BF_ADMIN (0x100000) and BF_IMMUNE_CELL_RESTRICTIONS
/// (0x400000) are set in pwd._bitfield — the exact two-bit AND
/// retail's ACCWeenieObject::CanBypassMoveRestrictions
/// (0x0058c500) evaluates to let a mover through an access-restricted
/// (house-barrier) cell without an owner/guest-list check. Bit names
/// confirmed at acclient.h:6452-6454
/// (PublicWeenieDesc::BitfieldIndex); BF_ADMIN is
/// independently decoded the same way in
/// .
///
CanBypassMoveRestrictions = 0x40,
}
/// Helpers to convert raw retail bitfields into .
public static class EntityCollisionFlagsExt
{
///
/// #297 (target-side refresh): exactly the subset of
/// that
/// can produce — IsPlayer/IsPK/IsPKLite/IsImpenetrable/
/// CanBypassMoveRestrictions. Disjoint from
/// (derived from ItemType) and
/// (set once at registration), so a live PWD-bitfield refresh can replace
/// exactly this mask without disturbing either.
///
public const EntityCollisionFlags PwdBitfieldDerivedMask =
EntityCollisionFlags.IsPlayer
| EntityCollisionFlags.IsPK
| EntityCollisionFlags.IsPKLite
| EntityCollisionFlags.IsImpenetrable
| EntityCollisionFlags.CanBypassMoveRestrictions;
///
/// Decode the player/PK/PKLite/Impenetrable bits from a
/// PublicWeenieDesc._bitfield value (the WeenieHeader trailer
/// field acdream's parser surfaces as ObjectDescriptionFlags).
///
/// Bit positions per
/// docs/research/named-retail/acclient.h:6431-6463
/// (PublicWeenieDesc::BitfieldIndex) and
/// acclient_2013_pseudo_c.txt:441868-441890
/// (PublicWeenieDesc::SetPlayerKillerStatus).
///
public static EntityCollisionFlags FromPwdBitfield(uint bitfield)
{
var flags = EntityCollisionFlags.None;
if ((bitfield & 0x8u) != 0) flags |= EntityCollisionFlags.IsPlayer;
if ((bitfield & 0x20u) != 0) flags |= EntityCollisionFlags.IsPK;
if ((bitfield & 0x200000u) != 0) flags |= EntityCollisionFlags.IsImpenetrable;
if ((bitfield & 0x2000000u) != 0) flags |= EntityCollisionFlags.IsPKLite;
// AP-71: BF_ADMIN (0x100000) AND BF_IMMUNE_CELL_RESTRICTIONS (0x400000),
// matching CanBypassMoveRestrictions' own AND (not OR) of the two bits.
if ((bitfield & 0x100000u) != 0 && (bitfield & 0x400000u) != 0)
flags |= EntityCollisionFlags.CanBypassMoveRestrictions;
return flags;
}
///
/// TS-23 (Campaign P Slice P3, 2026-07-30): translate the decoded
/// per-entity (the PWD-bitfield
/// bit-space) into the bits
/// CollisionExemption.ShouldSkip and the moverFlags argument to
/// PhysicsEngine.ResolveWithTransition actually consume — a
/// DIFFERENT bit-space (retail OBJECTINFO::init 0x0050cf30
/// `state |= 0x80/0x800/0x1000`, acclient.h:6190-6194) that must not be
/// confused with the PWD wire numbering.
/// is deliberately NOT translated here — every existing mover-flags call
/// site already derives from its
/// own GUID-prefix heuristic (correct per #184 Slice 2b) and this helper
/// only fills the gap that heuristic cannot: PK/PKLite/Impenetrable.
///
public static ObjectInfoState ToMoverState(this EntityCollisionFlags flags)
{
var state = ObjectInfoState.None;
if ((flags & EntityCollisionFlags.IsPK) != 0) state |= ObjectInfoState.IsPK;
if ((flags & EntityCollisionFlags.IsPKLite) != 0) state |= ObjectInfoState.IsPKLite;
if ((flags & EntityCollisionFlags.IsImpenetrable) != 0) state |= ObjectInfoState.IsImpenetrable;
// AP-71 (Campaign P Slice P4): the mover's own house-restriction bypass,
// consumed by ObjectInfo.CheckEntryRestrictions.
if ((flags & EntityCollisionFlags.CanBypassMoveRestrictions) != 0)
state |= ObjectInfoState.CanBypassMoveRestrictions;
return state;
}
///
/// TS-23 (Campaign P Slice P3, 2026-07-30): the one shared
/// ClientObjectTable-backed mover-flags lookup every physics
/// call site (local player world-entry, remote DR sweep + teleport,
/// ordinary movers) uses — was inlined three times (App
/// GameWindow/LivePresentationComposition/
/// RemoteTeleportController) before being consolidated here.
/// An entity with no row, or a row with no wire bitfield yet, resolves
/// to — a no-op OR into moverFlags,
/// bit-identical to every pre-P3 caller.
///
public static ObjectInfoState ResolveMoverPvpState(this ClientObjectTable objects, uint serverGuid)
{
ArgumentNullException.ThrowIfNull(objects);
return objects.Get(serverGuid)?.PublicWeenieBitfield is { } bitfield
? FromPwdBitfield(bitfield).ToMoverState()
: ObjectInfoState.None;
}
}