Перейти к основному содержимому
Сервисы вокруг игры

Эта страница ещё не переведена — показана английская версия.

Inventory

Один набор строк с владельцем, которым пользуются выстрелы, выпадающие предметы, способности и перемещение. Стопки меняются инкрементом, а на пределе, заданном для владельца, происходит то, что вы выбрали сами.

Когда это нужно

  • У игроков есть вещи, и каждая такая вещь — строка инвентаря (inventory) с владельцем. Её читают по владельцу, число строк ограничено для каждого владельца, а что происходит при переполнении, объявляется явно, а не берётся по умолчанию.
  • Количество накапливается: стопка (stack) меняется инкрементом с ключом идемпотентности, поэтому повторное списание не спишет дважды.
  • Другие модули тратят из одного набора: выстрелы списывают патроны, выпавшие предметы добавляют добычу, а покупки появляются строками, привязанными к праву владения (entitlement).
  • Не нужен, если числом нельзя владеть. HP, XP и перезарядки относятся к характеристикам (stat).

Кто что делает

АкторЧто делает на этой странице
playerчитает то, что принадлежит ему, и тратит из этого
backend-serviceвыдаёт, увеличивает и отзывает от имени игрока и указывает, от чьего имени действует

Коротко

С полномочиями fn: выдать патроны, переложить предмет в слот экипировки primary, проверить, хватает ли, перед тем как потратить
// 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)
Скоро — Go

Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.

// 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);

Сессия игрока делает чтение, перемещение и проверку «хватает ли» теми же вызовами. Выдавать, расходовать и уничтожать ей не положено: платформа отклоняет их как forbidden и называет право, которого не хватает вызывающему, — из какого бы биндинга ни пришёл вызов.

Модель

Своих понятий у инвентаря нет. Это пресет — форма, собранная из того, что уже дают сущности. Поэтому всё, что описано ниже, — объявления сущности, а не механизмы этой страницы.

ОбъявлениеЧто это
тип с владельцемзапись о владении принадлежит владельцу, а выборка по владельцу — собственная операция сущности
ref на предмет каталогассылка хранит id предмета, а не его key, поэтому ключ можно безопасно переименовать. Само определение предмета описано в модуле Catalog & Commerce
аспект стопки с инкрементомстопка меняется на дельту, а не перезаписью прочитанного значения (см. ниже)
предел на владельца и поведение на границеодно из трёх, и значения по умолчанию нет: refuse · redirect в объявленную корзину владельца · discard with event

Передавайте ключ идемпотентности с каждым инкрементом

Инкремент не идемпотентен: два инкремента — это два инкремента. Без ключа идемпотентности повторное списание спишет дважды. Чтобы узнать результат, прочитайте конкретную запись по адресу.

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
Выдача в полную сумку: решает объявленное поведение предела, значения по умолчанию нет

Что верно для всего, чем владеют.

ВсегдаЧто это
у предела нет значения по умолчаниютри ответа на полную сумку — это три разные игры. Отказ оставляет добычу лежать перед игроком. Перенаправление — это почта или переполненный склад. Удаление — тихая потеря, допустимая только потому, что она объявлена и её видно. Ни один вариант не подходит всем трём играм, поэтому выбирает объявление
владелец неизмененправкой поля ничего из рук в руки не передаётся: вещь переходит через отзыв плюс новую выдачу с объявленным источником, и оба факта остаются в истории
передача между двумя игроками— это другое обещание: нужны эскроу и защита от мошенничества, и в эту версию оно не входит
строкаотображает право владения, а не служит его вторым источником: купленное хранится в модуле Catalog & Commerce, а строка здесь его представляет

Ошибки

  • Экземпляра нет или его скрывает предикат: в обоих случаях ответ not found, поэтому отказ никогда не выдаёт, что объект существует, но принадлежит не вам.
  • Поле, не объявленное в аспекте (включая вложенные), и обязательное поле без значения — отказы валидации с именем поля.
  • Версия не совпала — precondition failure. Повторить стоит, перечитав запись.
  • Запись от имени игрока, в которой игрок не указан, — отказ валидации, а не молчаливая запись от чужого имени.
  • Нет разрешения (permission) на чтение или запись — ответ forbidden, причём чтение и запись различаются.

Ограничения

Конкретные числа появятся вместе с главой об ограничениях платформы, а поведение на каждом пределе зафиксировано уже сейчас.

  • Экземпляры на владельца: по объявленному выше правилу, значения по умолчанию нет.
  • Размер хранимого экземпляра: запись отклоняется как конфликт, а в отказе названы поле-нарушитель и измеренный размер. Предел достигается постепенным накоплением, так что приближение к нему видно ещё до записи, которая не пройдёт.
  • Частота изменений одного экземпляра: отказ по ограничению частоты с указанием срока.
  • Размер страницы выборки: страница обрезается до предела, а флаг «есть ещё» остаётся выставленным. Вернуть меньше строк без этого флага запрещено.