Aller au contenu principal
Services autour du jeu

Cette page n’est pas encore traduite : la version anglaise est affichée.

Inventory

Un seul ensemble de lignes possédées, qu’utilisent les tirs, le butin, les capacités et le déplacement. Les piles évoluent par incrément, et le plafond par propriétaire se comporte à la limite comme vous l’avez choisi.

Quand l’utiliser

  • Les joueurs possèdent des choses, et chaque possession est une ligne avec un propriétaire : on la lit par propriétaire, on la plafonne par propriétaire, et le comportement en cas de dépassement est déclaré au lieu d’être implicite.
  • Une quantité s’accumule : une pile évolue par incrément avec une clé d’idempotence, si bien qu’un débit retenté ne débite pas deux fois.
  • D’autres modules puisent dans le même ensemble : les tirs débitent les munitions, le butin est attribué, et les achats apparaissent sous forme de lignes rattachées à leur droit d’usage (entitlement).
  • Inutile si le nombre ne se possède pas. Les PV, l’XP et les temps de recharge (cooldown) relèvent des statistiques.

Qui fait quoi

ActeurCe qu’il fait sur cette page
playerlit ses propres possessions et les dépense
backend-serviceattribue, incrémente et révoque pour le compte d’un joueur, en nommant le joueur au nom duquel il agit

En bref

Depuis l’autorité fn : attribuer des munitions, déplacer un objet vers l’emplacement d’équipement primary, vérifier qu’on peut payer avant de dépenser
// fn authority — a cloud function, or a dedicated server holding a host key
var bag = await player.Inventory.Container("bag");
var equipment = await player.Inventory.Container("equipment");

await player.Inventory.Grant("ammo.shell", count: 20);
await bag.Move(itemId, to: equipment, slot: "primary");

if (await player.Inventory.CanAfford("ammo.shell", 1))
    await player.Inventory.Consume("ammo.shell", 1);
// fn authority — a cloud function, or a dedicated server holding a host key
const bag = await player.inventory.container('bag');
const equipment = await player.inventory.container('equipment');

await player.inventory.grant('ammo.shell', { count: 20 });
await bag.move(itemId, { to: equipment, slot: 'primary' });

if (await player.inventory.canAfford('ammo.shell', 1))
  await player.inventory.consume('ammo.shell', 1);
# fn authority — a cloud function, or a dedicated server holding a host key
bag = await player.inventory.container("bag")
equipment = await player.inventory.container("equipment")

await player.inventory.grant("ammo.shell", count=20)
await bag.move(item_id, to=equipment, slot="primary")

if await player.inventory.can_afford("ammo.shell", 1):
    await player.inventory.consume("ammo.shell", 1)
Bientôt — Go

La déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.

// fn authority — a cloud function, or a dedicated server holding a host key
Client->Commerce->Entitlements->Grant(FPSIdempotencyKey(GrantId), PlayerId, PSKeys::Item::AmmoShell);

// spending is an instance act on the entitlement you hold
Entitlement->Spend(FPSIdempotencyKey(SpendId), /*Amount*/ 1,
    TPSOnResult<void>::CreateLambda([](const TPSResult<void>& Result)
    {
        // short on the item is a declared refusal, not a silent no-op
        if (Result.IsRefused()) { DeclineReload(Result.Refusal()); }
    }));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.
// fn authority — a cloud function, or a dedicated server holding a host key
var bag = await player.Inventory.Container("bag");
var equipment = await player.Inventory.Container("equipment");

await player.Inventory.Grant("ammo.shell", count: 20);
await bag.Move(itemId, to: equipment, slot: "primary");

if (await player.Inventory.CanAfford("ammo.shell", 1))
    await player.Inventory.Consume("ammo.shell", 1);

Une session joueur effectue les lectures, les déplacements et la vérification de solvabilité avec les mêmes appels. Attribuer, consommer et détruire ne lui reviennent pas : la plateforme refuse ces appels comme forbidden et nomme le droit qui manque à l’appelant, quel que soit le binding qui a passé l’appel.

Le modèle

Un inventaire n’introduit aucune notion propre. C’est un preset : une forme assemblée à partir de ce que fournit déjà l’entité. Tout ce qui suit relève donc de la déclaration d’entité, pas d’un mécanisme propre à cette page.

DéclareCe que c’est
un type possédéla possession appartient à un propriétaire, et la sélection par propriétaire est une opération native de l’entité
une ref vers un objet du cataloguela référence stocke l'id de l’objet (item) et jamais sa key : renommer une clé est donc sans risque. La définition elle-même se trouve dans le commerce
un aspect de pile avec incrémentune pile évolue par delta au lieu d’écraser ce qui a été lu (voir plus bas)
un plafond par propriétaire avec son comportement à la limitel’un des trois, et il n’y a pas de valeur par défaut : refuse · redirect vers un réceptacle de propriétaire déclaré · discard with event

Passez une clé d’idempotence à chaque incrément

Un incrément n’est pas idempotent : deux incréments font deux incréments. Il doit porter une clé d’idempotence, sinon un débit retenté débite deux fois ; pour connaître le résultat, faites une lecture ciblée.

a grant arrives; the owner is at the capthe cap's declared behaviour (no default)the loot stays where it wasinto a declared owner bucketgone, with an event refuseredirectdiscard with event
Une attribution dans un sac plein : c’est le comportement déclaré du plafond qui tranche, et il n’y a pas de valeur par défaut

Ce qui vaut pour toute possession.

ToujoursCe que c’est
le plafond n'a pas de valeur par défautles trois réponses à un sac plein correspondent à trois jeux différents : un refus fait perdre le butin sous les yeux du joueur, une redirection, c’est du courrier ou un entrepôt qui déborde, et une suppression est une perte discrète, légitime uniquement parce qu’elle a été déclarée et qu’on peut l’observer. Aucune valeur par défaut ne convient aux trois : c’est la déclaration qui choisit
le propriétaire est immuablerien ne change de mains en modifiant un champ : une possession se déplace par une révocation suivie d’une nouvelle attribution avec une origine déclarée, et les deux faits restent dans l’historique
un transfert entre deux joueursest une autre promesse : il demande un séquestre et une protection antifraude, et il sort du périmètre de cette version
une ligneaffiche un droit d’usage au lieu d’en être une seconde source : ce qui a été acheté vit dans le commerce, et la ligne ici le représente

Erreurs

  • L’instance n’existe pas, ou un prédicat la masque : dans les deux cas, la réponse est not found. Un refus ne révèle donc jamais qu’une chose existe sans vous appartenir.
  • Un champ non déclaré dans l’aspect (champs imbriqués compris), ou un champ obligatoire sans valeur, donne un refus de validation qui nomme le champ.
  • La version ne correspond pas : c’est une precondition failure, à retenter après avoir relu.
  • Une écriture pour le compte d’un joueur qui ne nomme pas ce joueur est un refus de validation, pas une écriture silencieuse sous l’identité de quelqu’un d’autre.
  • L’absence de permission en lecture ou en écriture répond forbidden, en distinguant la lecture de l’écriture.

Limites

Les chiffres arriveront avec le chapitre sur les limites de la plateforme ; ce qui se passe à chaque plafond est déjà fixé.

  • Les instances par propriétaire : selon la règle déclarée ci-dessus, et il n’y a pas de valeur par défaut.
  • La taille de l’instance stockée : l’écriture est refusée comme conflit, et le refus nomme le champ fautif et la taille mesurée. Le plafond s’atteint par accumulation : on voit donc venir la limite avant l’écriture qui échoue.
  • La fréquence des modifications d’une même instance : un refus pour limite de débit, avec un délai.
  • La taille de page d’une sélection : la page est coupée au plafond et l’indicateur « il y en a d’autres » reste à vrai ; renvoyer moins de lignes sans l’indicateur est interdit.