Game services
The things a game keeps for its players: realms, inventories, currencies, items, cards and decks, XP, skill trees, unlocks and stats. You define them in the console; your game server grants them; your players' clients read them, buy with currency, build decks and unlock skill nodes. Five services cover them, each switched on for the project on its own:
| Service | What it covers |
|---|---|
| Realms | Seasons, or any span of time that starts every player with a fresh inventory |
| Inventory | Inventories, currencies and their ledger, items, and buying items with currency |
| Cards | Card types and templates, the cards a player owns, decks, buying cards |
| Progression | XP, skill trees and unlocks |
| Stats | Counters per player and per environment |
Like everything else, all of it is per environment: your test environment has its own definitions and its own players' things. Definitions are config, so you can promote them from test to production.
Who does what
Two rules keep a game fair when players can edit their own clients:
- Only your server grants. Crediting and debiting currency, granting and taking away items and cards, adding XP, recording unlocks and moving stats need your server key. A player's token gets
forbiddenfrom every one of those routes, whatever it sends. - Prices are always the definitions'. A client names what it buys, never what it costs. The price is read from the definition when the purchase is made.
With a player's token, :playerId in a path is me, and reaches only that player's things. With your server key it is the player's id, and a player of another environment is not-found.
| A player's client, with its token | Your server, with its server key |
|---|---|
| Reads realms, inventories, balances and the ledger | Everything a client does, for any of its players |
| Reads items, cards, decks, XP, trees and unlocks | Credits, debits, consumes and refunds currency |
| Reads its stats and the environment's | Grants and takes away items and cards |
| Buys items and cards with currency | Adds XP, records and removes unlocks |
| Uses and opens its items | Moves player and environment stats |
| Builds and edits its own decks | Reads the definitions: currencies, items, cards, trees |
| Unlocks skill nodes it can pay for | Unlocks nodes priced in a consumed currency (see below) |
Realms
A realm is a span of time whose things are its own: a new realm starts every player with an empty inventory, and what they held in the last one stays there. Every environment has realm 0, permanent, so a game without seasons never thinks about realms: everything happens in realm 0.
On the Game data pages, Realms lists them. Each has a number, a key, a name, a start and optionally an end, and two switches: switched on (playable) and listed (shown). The current realm is the one that is switched on, listed, started and not ended, with the highest number: that is where anything held per realm goes unless a request names another. GET /realms/current answers it (not-found while no realm is open), and GET /realms every open one, newest first.
Inventories
A player has one account inventory for what outlives realms, and one inventory per realm they have played. Inventories are made the first time anything is put in them. A path names one as account, current (the current realm's) or its id, for example GET /players/me/inventories/account/balances. A request that puts something somewhere takes inventoryId, or realmId (null for the account), and otherwise uses the place the definition says: the account for what is held on the account, the current realm for what is held per realm.
Currencies
A currency is held on the account or per realm, and is one of two kinds:
- Balance: earned and spent, never below zero, with an optional most a balance may hold.
- Consumed: only what was spent is stored. What the player is allowed in all is your game's rule (their level, say), which your server sends as
allowancewhen it spends. A spend succeeds only if what was spent plus this spend is within the allowance. Giving points back (a skill tree reset) is a refund.
Every movement is written with a line in the player's ledger, in the same transaction: the change, the balance after it, a reason (your machine key, such as match.settlement) and a reference (a match, an order). A wrong movement is put right by another, never by editing the ledger. GET /players/:playerId/ledger pages it newest first: pass the answer's next as cursor for the page after, as with every list.
Your server moves currency with POST /players/:playerId/currencies/:currencyKey/credit (and debit, consume, refund). Each needs an Idempotency-Key header naming that one movement, the same on every retry: a retry answers the first movement's receipt with replayed: true and moves nothing, so a server that timed out can always ask again. Derive the key from what the movement is for (match-8812:reward), never from a clock. The same key for a different movement is refused with idempotency-conflict.
Items
An item type is owned counted (a player holds a number of them) or one by one (each item its own, with its own data), held on the account or per realm, and may have a most one inventory holds. A type can be switched off, need an unlock before it can be used (requiresUnlock), be usable, and be for sale at a price list.
Your server grants with POST /players/:playerId/items/grant, and takes away an item with POST /players/:playerId/items/:itemId/consume, or some of a counted type with POST /players/:playerId/item-stacks/consume. Each needs an Idempotency-Key header, as currency does, naming that one grant or take-back: a retry answers the first answer and gives or takes nothing more. The same holds for counted cards (/card-stacks/grant, /card-stacks/consume) and XP (POST /players/:playerId/xp). A grantKey also makes a grant of one item happen once, whatever the header: granting again under the same key answers the first item with alreadyGranted: true. Derive either from what the grant is for (starter:sword, crate-8812:slot-2), never from a clock or a random number.
A player uses an item with POST /players/me/items/:itemId/use, or one of a counted type with POST /players/me/item-stacks/use, which consumes it. An item whose category is container is opened with POST /players/me/items/:itemId/open, once: a second open is refused with already-consumed. What a container holds is your game's, so opening only consumes it; your server then grants its contents under keys derived from the item's id (crate-8812:slot-2), so a retry never gives twice.
Prices and buying
An item type, a card type or a skill node carries a price list: an amount in each of one or more currencies, and a rule saying whether every price is paid (all) or any one of them (any; name it with payWith, or the first the player can pay is paid). A purchase charges the prices and grants what was bought in one transaction, so a player never pays without receiving.
POST /players/me/purchases/items and POST /players/me/purchases/cards take an optional Idempotency-Key: a retried purchase answers the first one's receipts with replayed: true and grants nothing again. A price in a consumed currency needs the allowance, which only your server sends, so a client cannot buy with one.
Cards and decks
Cards are their own model beside items. A card type may be for sale, be a starter (one your game gives every new player), and limit how many copies one deck holds. A player owns cards one by one (each with its own template and data) or by count (a number of a type), or both. A card template is a frame or style; owning one is the unlock card-template:<key>, which your server records.
A card has no place of its own, so granting, buying or building a deck names the inventory, the current realm's when nothing is named. A deck belongs to one inventory, and each line is an owned card or a card type with a count. A player's client makes and edits its own decks (POST /players/me/decks, and PUT /players/me/decks/:deckId/cards/:cardId or /types/:cardTypeKey); up to 50 decks a player and 200 lines a deck. GET /players/me/decks/:deckId/validation checks the general rules (the cards are the player's, each type within its limit, and a size cap if you pass maxSize) and lists every problem; what else makes a deck playable is your game's to check on your server.
Progression
- XP is a number per player per track (
accountunless you name one). Your server adds it; what it means (a level, a rank) is your game's. - Skill trees are nodes, each priced, each unlocked once every node it depends on is.
POST /players/me/skill-nodes/:nodeKey/unlockpays the price and records the unlocknode:<key>together; unlocking a node already unlocked charges nothing. A node priced in a consumed currency is unlocked by your server, which sends the allowance.GET /players/me/skill-treesanswers each tree with each node's price and whether it is unlocked or unlockable; a hidden tree is left out for a player's token. - Unlocks are keys a player holds: skill nodes, card templates, or your own (
feature:crafting). A feature gated by an unlock asks one question:GET /players/me/unlocks?prefix=feature:.
Stats
64-bit counters for a player (/players/:playerId/stats) or the whole environment (/stats), in one realm or, without a realmId, across every realm. Your server moves one with POST .../stats/:key/increment and { "by": 1 }, which adds atomically, so increments made at the same moment all count. Values are answered as strings, since a 64-bit number does not fit a JSON number.
Your own data
A studio can keep its own fields on item types, card types and card templates (up to 4 KB each) and on an item or a card a player owns (up to 1 KB), as a JSON object called data. The platform keeps it and never reads it. Sizes are measured written compactly; a request over the limit is refused with invalid-request, naming body.data.
Deleting a player
Deleting a player (DELETE /players/:playerId) deletes their game state with them: inventories, balances and their ledger, items, cards, decks, XP, unlocks and stats. The environment's own stats stay.
Selling currency
With the Inventory service on, a Commerce product whose grant is a currency this environment defines credits the player's balance directly when it is bought, and a refund takes back what the player still holds of it. You do not need to collect those grants.
In the console
- Game data, in each environment's sidebar: Realms, Currencies, Item types, Card types, Card templates and Skill trees, each added, edited and deleted in place. A key cannot be changed once made: what players hold names its definition by key. Changing definitions needs the Config permission.
- Players: a player's page shows their inventories with balances, items and cards, their ledger, decks, XP, unlocks and stats, for anybody who may view players. Somebody with Manage players can adjust a balance: the change is a ledger line with the reason
staff.adjustmentand the note they give, which is required. Nothing is ever overwritten.
Refusals
A refusal is the platform's, as everywhere: { "error", "message", "details"? }, where details carries the figures behind it (what is held, what was asked, the limit). Besides the codes every route can answer (see the API reference):
error |
Status | When |
|---|---|---|
not-found |
404 | No such definition, item, card, deck or inventory, or not this player's |
invalid-request |
400 | A request that cannot be meant, such as a consumed currency spent without allowance; or data too big |
insufficient-balance |
409 | Not enough of a currency, or of a counted item or card |
allowance-exceeded |
409 | A consumed currency's spend would pass the allowance |
balance-cap |
409 | A credit would pass the most the currency allows |
idempotency-conflict |
409 | The Idempotency-Key already names a different movement, grant or purchase |
disabled |
409 | The definition is switched off |
not-purchasable |
409 | Not for sale |
owned-limit |
409 | The inventory holds as many as allowed, or the player as many decks |
locked |
409 | It needs an unlock, or skill nodes, the player does not have |
not-usable |
409 | It cannot be used, or does not open |
already-consumed |
409 | Already used, opened or taken away |
no-current-realm |
409 | Something held per realm, and no realm is open |
Every route, with its fields, is in the API reference, under Game services.