Esta página aún no está traducida: se muestra la versión en inglés.
Inventory
Un solo conjunto de filas con dueño que usan los disparos, el botín, las habilidades y el desplazamiento. Las pilas cambian por incremento, y el tope por dueño se comporta en el límite como tú elijas.
Cuándo usarlo
- Los jugadores tienen cosas, y cada posesión es una fila con dueño: se lee por dueño, tiene un tope por dueño y lo que pasa al desbordarse se declara, no viene por defecto.
- Una cantidad se acumula: una pila (stack) cambia mediante un incremento con clave de idempotencia, así que un descuento que se reintenta no descuenta dos veces.
- Los demás módulos gastan de un mismo conjunto: los disparos descuentan munición, el botín otorga objetos y las compras aparecen como filas vinculadas a su derecho de uso (entitlement).
- No lo necesitas si nadie puede poseer ese número. Los HP, la XP y los tiempos de recarga (cooldown) van en las estadísticas.
Quién hace qué
| Actor | Qué hace en esta página |
|---|---|
player | lee lo que posee y gasta de ahí |
backend-service | otorga, incrementa y revoca en nombre de un jugador, e indica en nombre de quién actúa |
En resumen
fn: otorga munición, mueve un artículo al espacio de equipo primary y comprueba que alcance antes de gastar// 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)Todavía no está decidido cómo declarar con atributos en Go (O-1). Los ejemplos llegarán cuando se elija la forma de escribirlo.
// 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);Una sesión de jugador hace las lecturas, los movimientos y la comprobación de saldo con las mismas llamadas. Otorgar, consumir y destruir no le corresponde: la plataforma lo rechaza como forbidden e indica qué permiso le falta a quien llama, sin importar desde qué binding se hizo la llamada.
El modelo
Un inventario no introduce conceptos propios. Es un preset: una forma armada con lo que la entidad ya ofrece, así que todo lo que sigue es una declaración de entidad y no un mecanismo propio de esta página.
| Declara | Qué es |
|---|---|
| un tipo con dueño | la posesión pertenece a un dueño, y la selección por dueño es una operación propia de la entidad |
un ref a un artículo del catálogo | la referencia guarda el id del artículo y nunca su key, así que renombrar una clave es seguro. La definición en sí vive en Catalog & Commerce |
| un aspecto de pila con incremento | una pila cambia por una delta, no sobrescribiendo lo que se leyó (ver abajo) |
| un tope por dueño con su comportamiento en el límite | uno de tres, y no hay valor por defecto: refuse · redirect hacia un depósito de dueño declarado · discard with event |
Pasa una clave de idempotencia con cada incremento
Un incremento no es idempotente: dos incrementos son dos incrementos. Debe llevar una clave de idempotencia; si no, un descuento reintentado descuenta dos veces. Para conocer el resultado, haz una lectura dirigida.
Qué se cumple para toda posesión.
| Siempre | Qué es |
|---|---|
el tope no tiene valor por defecto | las tres respuestas ante una bolsa llena son tres juegos distintos: un rechazo hace que el jugador pierda el botín frente a sus ojos, una redirección es un correo o un almacén que se desborda, y un descarte es una pérdida silenciosa, legítima solo porque se declaró y se puede observar. Ningún valor por defecto sirve para los tres, así que decide la declaración |
el dueño es inmutable | nada cambia de manos editando un campo: una posesión se mueve como una revocación más un nuevo otorgamiento con origen declarado, y ambos hechos quedan registrados |
una transferencia entre dos jugadores | es otra promesa: necesita custodia (escrow) y antifraude, y queda fuera de esta versión |
una fila | muestra un derecho de uso, no es una segunda fuente de él: lo comprado vive en Catalog & Commerce, y la fila de aquí lo representa |
Errores
- La instancia no existe o un predicado la oculta: en ambos casos la respuesta es not found, así que un rechazo nunca revela que algo existe pero no es tuyo.
- Un campo que no está declarado en el aspecto (incluidos los anidados) y un campo obligatorio sin valor son rechazos de validación que nombran el campo.
- La versión no coincidió: es una falla de precondición, y vale la pena repetir después de volver a leer.
- Una escritura en nombre de un jugador que no lo nombra es un rechazo de validación, no una escritura silenciosa como si fuera otra persona.
- Sin permiso para leer o escribir, la respuesta es forbidden, y distingue la lectura de la escritura.
Límites
Las cifras llegarán con el capítulo sobre los límites de la plataforma; lo que pasa al alcanzar cada tope ya está definido.
- Instancias por dueño: según la regla declarada arriba, y no hay valor por defecto.
- Tamaño de la instancia guardada: la escritura se rechaza como conflicto, y el rechazo nombra el campo culpable y el tamaño medido. Al tope se llega por acumulación, así que puedes ver que se acerca antes de la escritura que falla.
- Frecuencia de cambios en una instancia: un rechazo por límite de frecuencia que indica cuándo volver a intentar.
- Tamaño de página de una selección: la página se recorta al tope y la marca de “hay más” sigue en verdadero; devolver menos filas sin esa marca está prohibido.