A loadout is a mapping from slot ids to item ids. Nothing else — no scenes, no
meshes, no weapon resources. A server receives one from an untrusted client,
checks it against a DotLoadoutSchema and a DotLoadoutEntitlements, and hands
it to the game, on a machine that may have none of the content installed.
Requires dot-core.
var res := await loadouts.active_for(user_key)
for entry in loadouts.resolve(res.value):
arsenal.give(weapon_for(entry["item"]), entry["arsenal_slot"])
var published := await loadouts.publish(user_key, submitted)
if not published.ok:
DotLog.warn("game", "loadout refused: %s" % published.error)
The same trade dot-user-avatar makes,
for the same reason.
Two failure modes it is built around
Everyone owns everything. DotLoadoutEntitlements starts empty and
entitlement_source starts unset, so an unwired server permits only items
marked free. That is loud and gets fixed in a minute. The other default —
granting everything — is silent, and is a game where every unlock is free.
A schema change locks players out. Retiring an item or adding a required
slot makes every saved loadout invalid. conform repairs on the way out of
a store, so a player who has not logged in for a month gets a slightly different
gun rather than an error. validate refuses on the way in from a client,
because a client that can make the server repair its way to a legal loadout can
put anything anywhere.
Where a game plugs in
- DotItem
One thing a player can take: kind, slots, tags, cost, weight, entitlement.
- DotItemCatalogue + resolverCallable
Every item by id, plus the optional, client-side resolver that turns one into an asset. A server never calls it.
- DotLoadoutSlot
A position and what fits in it: kinds, required tags, forbidden tags.
- DotLoadoutSchema
The slots, the catalogue, and the point and weight budgets — a game mode’s equipment rules in one resource.
- DotLoadoutEntitlements + DotLoadoutManager.entitlement_source
What one player may take, and where that comes from.
- DotLoadoutStore subclass
_fetch,_store,_remove,_open,_close,_writable,_store_name. Memory and local-JSON ship.- DotLoadoutValidator
validatefor the trust boundary,conformfor the way out of a store.- DotPickup / DotPickupField
Things on the floor.
wants_fnandtags_fndecide who may take what.- Signals
loadouts_changed,loadout_refused,loadout_conformed,taken,collected,respawned.
Pickups are not Area3Ds
Respawn timers count ticks, not seconds, and reach is a distance test rather than a physics overlap. Both because a client predicts picking something up and the server re-runs it: an area callback fires on whichever frame the physics server got to it, which is not the same frame on two machines.
A publish appends unless it replaces
A player with nothing saved is handed a synthesised default. If publishing adds a second loadout rather than replacing that one, then on a schema with a cap of one, the only publish they would ever make is refused.
