Inventory and items

The server holds each player's inventory, through kcds.players.Inventory.

Naming items

Use an item's class GUID or its name in the game's item table, such as apple, bread or arrow_crude. Inventory.classes() lists them.

Items.model(item) gives the mesh a class has on the ground, or '' if the game's tables give none. Most of these also work as props, which shows a sword on its own, held still:

const { Items } = kcds.players;

kcds.world.Prop.spawn(Items.model('longSwordDuel'), position, undefined, undefined, 'none');

Changing an inventory

Each change is all or nothing, and answers { ok, code, revision, items }. A refused change has ok: false and a code, such as invalidItem, invalidAmount, insufficientItems, inventoryCapacity, unknownItem or staleRevision.

const answer = kcds.players.Inventory.add(player, { item: 'apple', amount: 3 });
if (!answer.ok) {
  console.log(`refused: ${answer.code}`);
}

// Shorter forms:
player.giveItem('bread', 2);
player.takeItem('apple', 1);
CallEffect
Inventory.add(player, { item, amount, metadata? })adds units to the row with the same properties, or a new row
Inventory.remove(player, { units: [{ id, amount }] })takes units out of rows
Inventory.editRow(player, { id, metadata })replaces a row's quality, condition and custom fields
Inventory.editCustom(player, { id, name?, description?, data? })sets a row's own name, description or data; null resets one
Inventory.set(player, { items, quickslots? })replaces everything and dresses the body; see Equipment
Inventory.transfer(from, to, { units })moves exact units between two players

metadata accepts quality, health or condition, poison, poisonCharges, onEquipBuffs, transient, and custom: { name, description, data }, where data is a plain object, not text.

notify: false in a request leaves out the "You received" notice.

Reading

Inventory.get(player) returns { revision, items, quickslots, ready }. Each row is { id, item, name, amount, quality, health, condition, equipped, metadata }. If you pass the revision you read with a change, the change is refused if the inventory has changed in the meantime.

Items that vanish on leaving

A row with transient: true leaves the inventory when its player disconnects, before playerDisconnect fires. So a script saving the inventory at that point never saves it. It always gets its own row, and reads back with metadata.transient. If you save at another time, you can leave such rows out yourself.

const { Inventory } = kcds.players;

// A sack over the prisoner's head, gone if they leave the server.
Inventory.add(prisoner, {
  item: 'BagOverHead_m01',
  amount: 1,
  metadata: { transient: true },
});

kcds.core.Events.on('playerDisconnect', (player) => {
  // The sack is not in what gets saved here.
  console.info(JSON.stringify(Inventory.get(player)?.items));
});

Events

  • playerInventoryReady: their game now shows the server's stock
  • playerInventoryChanged: with the reason for the change
  • playerItemUsed: food, potions, or ammunition spent

Your own item types

Items.register creates a type that borrows the look of a miscellaneous item, crafting material or document. It works with all inventory, stash and ground item calls.

const { Items } = kcds.players;

const sealedLetter = Items.register('myres:sealed_letter', {
  name: 'Sealed letter',
  description: 'A letter with the seal of the burgrave.',
  visual: 'crime_letterOfRecognition',
  weight: 0.1,
  usable: true,
  useLabel: 'Break the seal',
});
player.giveItem(sealedLetter);

kcds.core.Events.on('playerCustomItemUse', (player, row, revision) => {
  kcds.core.Chat.sendToPlayer(player, 'The seal breaks.');
});

Changing the game's items

Items.override(item, { weight, price, max, ...stats }) changes an item for all players, where max is the most units a player can hold. Items.clearOverride(item) restores the game's values. To apply the same when the server starts, use resources/item-config.json:

{
  "arrow_crude": { "max": 40, "price": 20 },
  "bread": { "weight": 0.2 }
}

Inventory.overrideStats(item, stats) rewrites a class's combat numbers for everyone. A new set replaces the previous one entirely. Inventory.itemStats(item) reads them, or returns null for a class that is unchanged.

const { Inventory } = kcds.players;

Inventory.overrideStats('longSwordDuel', { attack: 210, strReq: 12 });
Inventory.overrideStats('SkullCap02_m02_C2', { defenseStab: 150, noise: 0.1 });
Inventory.overrideStats('longSwordDuel', null); // back to the game's numbers

Other calls: Inventory.setWearRates({ default, armor, ... }) makes items wear faster or slower, Inventory.renameItem(item, name) renames an item for everyone, and Inventory.itemPrice prices one unit as the game does.

Items on the ground

kcds.world.GroundItem is a stack in the world that all players see and can pick up. Items players drop and arrows that land become ground items too.

const { GroundItem } = kcds.world;

GroundItem.spawn('apple', player.position, {}, 5);
GroundItem.spawn('bread', player.position, {}, 1, undefined, { name: 'harvest_table' });

const breadStack = GroundItem.all().find((stack) => stack.name === 'harvest_table');

kcds.core.Events.on('groundItemPickup', (stack, player) => {
  console.info(`${player.nickname} took it`);
});

Landing

A stack starts where it was spawned or dropped, and the game's physics lets it fall and roll. When the first player's game with a view of it sees it stop, it stays at that spot for everyone: resting turns true, position is where it lies, and groundItemResting fires. To wait for landing, listen for that event rather than polling the position:

const { GroundItem } = kcds.world;

const droppedApple = GroundItem.spawn('apple', { x: 1650, y: 2151, z: 40 });

kcds.core.Events.on('groundItemResting', (landed) => {
  if (landed === droppedApple) {
    console.info(`the apple landed at ${landed.position.z.toFixed(2)}`);
  }
});

A landed arrow or bolt is at rest from the start. If it sticks in a target, wall or tree, it stays there for all players, where the shooter's game saw it, and any player can pull it out once.

For one player only

A stack can be shown to a single player, whose game alone lays it and who alone can take it, such as an item a shop previews for a customer. showOnlyTo(null) shows it to everyone again. If that player leaves, nobody sees the stack until it is shown to someone else.

const preview = kcds.world.GroundItem.spawn('longSwordDuel', counter, {}, 1, undefined, {
  visibleTo: player,
});

To refuse a pick-up of the level's own items, use worldItemPickup; see Time, weather and the world.

In the player's game

On the client, kcds.players.Inventory.whenUsed(item, handler) takes over an item's actions on the inventory screen. The handler receives the use, and the server decides what it does.

Reference: Inventory, Items, GroundItem.