Spatial

Loot

Dropped-item entities: opaque payload, atomic first-claim, timed expiry. Server-only.

Server
services.AddCrossplayLoot();
Depends on
world
Shape
server-only · zero wire

Services you call 1

Crossplay implements these. Resolve them from DI and call them from your own systems.

ILootService

Dropped-item entities: Drop spawns a pickup at a position and holds its OPAQUE item payload; TryClaim atomically takes it (first claimer wins) and despawns the entity; unclaimed drops expire. Delivery is the GAME's call — its interaction policy claims and hands the payload to whatever container system it uses.

  • event Action<long, byte[], int> Claimed

    Raised after a successful AUTO-claim (AutoClaimRadius > 0): (claimerEntityId, item, count) — the drop is already despawned by the time this fires, and the claim went through the same atomic TryClaim path. Manual TryClaim calls do NOT raise it: the piece never learns who a manual claimer is — the caller does, and applies its own delivery. Composition bridges subscribe here to hand the payload to the auto-claimer.

  • long Drop(string zone, float x, float y, float z, byte[] appearance, byte[] item, int count, double despawnSeconds = 0)

    Spawns a loot entity at a position and remembers its opaque payload.

  • event Action<long> Expired

    Raised when a drop expires unclaimed: (entityId) — the entity is already despawned by the time this fires.

  • bool IsLoot(long entityId)

    True when the entity is an unclaimed drop.

  • bool TryClaim(long entityId, out byte[] item, out int count)

    Atomically claims a drop (first caller wins) and despawns the entity — call this from your game's pickup/interaction policy, then hand item to whatever container system you use. The atomic first-claim is the piece's guarantee; delivery is yours.

Configuration 1

Every tunable lives in an options object — there are no magic numbers to hunt for.

LootOptions

Loot tuning (defaults here, never inline).

  • float AutoClaimRadius { get; set; }

    When > 0, any PLAYER entity within this radius (world units) of an unclaimed drop claims it automatically on the piece's tick — the nearest player wins, through the same atomic TryClaim path (first-claimer-wins is preserved), and Claimed reports who got it. 0 (the default) = off: claiming stays entirely the game's call.

  • Dictionary<byte, List<DropTemplate>> DeathDropSets { get; set; }

    Multi-entry death drops: an entity kind → SEVERAL drops, each rolled by its own Chance (a boss leaves a coin AND a card; a regular enemy a coin plus a chance heal). When a kind has an entry here it is used INSTEAD of the single-entry DeathDrops table; a composition bridge rolls and drops each. Empty (the default) = only the single-entry table applies.

  • Dictionary<byte, DropTemplate> DeathDrops { get; set; }

    Pure DATA describing what an entity of a given kind leaves behind, keyed by the entity's appearance-kind byte. The piece itself never watches deaths — a composition bridge reads these templates and turns them into Drop calls when its game decides an entity of that kind is gone. Empty (the default) = inert.

  • double DefaultDespawnSeconds { get; set; }

    Despawn delay for unclaimed drops when a call leaves it at 0.