Equipment

The server holds what a player wears, as part of their inventory. Each row (InventoryRow) has equipped, the number of units the body wears or holds. To dress or undress a player, change equipped through kcds.players.Inventory.set. Their game then dresses the body with its own equipment code and reports what is really worn.

Slots

You do not name a slot. The game chooses from the class's armour type, which Inventory.classes() gives as slot. It is a name from the table below, or null for things the body does not wear, such as weapons, food, glasses, sword sheaths and horse gear.

const slotOf = new Map(kcds.players.Inventory.classes().map((entry) => [entry.name, entry.slot]));

slotOf.get('HoseJoined01_m03_C'); // "leg_trousers"
slotOf.get('TunicShort05_m09_D'); // "body_cloth"
slotOf.get('longSwordDuel'); // null

Most class names start with their armour type: HoseJoined01_m03_C is HoseJoined, and BascinetVisor04_m02_A5 is BascinetVisor. Belts, rings and necklaces have lower-case names, such as belt_4slot, ring_golden_cross and necklace_silver_cross_insignia.

Body slots

Slots sharing a place, such as Cloth 1 and Cloth 2, take one piece at a time.

SlotPlaceArmour typesNeeds
head_coifCoif 1CoifCap
head_coif_paddedCoif 2CoifSmall, CoifLarge, CoifMail
head_capCap 1Cap, LeatherCap, F_Hood, F_Bonnet, F_CapAndWimple, F_Hat, F_HoodOpen, F_Veil, F_VeilAndWimplehead_coif_padded empty
head_helmetCap 2KettleHat, SkullCap, BascinetOpen, BascinetVisorhead_coif_padded worn
head_hoodHoodHood
collarCollarCollarPadded, CollarMail
body_clothCloth 1TunicShort, TunicLong, F_SimpleDress, F_Smock, LeatherApron
body_cloth_paddedCloth 2GambesonShort, GambesonLong, Caftan, Pourpoint
body_chainmailChainmailMailShort, MailLongbody_cloth_padded worn
body_platePlateBrigandine, Cuirassbody_cloth_padded worn
body_coatCoatCoat, Waffenrock, Habit
sleevesSleevesArmBrigandine, ArmPlatebody_cloth_padded worn
glovesGlovesGloves, Gauntlets
leg_trousersTrousers 1HoseJoined, HoseLoose, HoseSeparate
leg_trousers_paddedTrousers 2LegsPadded, LegsChain
leg_armorLegArmorLegsBrigandine, LegsPlateleg_trousers_padded worn
bootBootShoes, BootsAnkle, BootsKnee, F_Shoes
spurSpurSpursboot worn
beltBeltBelt
pouchPouchPouch
ringRingRing
necklaceNecklaceNecklace

The body wears one belt and one pouch. A belt with quick slots (belt_4slot) uses the belt slot, and a pouch (Pouch_test_4slot) uses pouch. The apprentice and master badges, Cap01_mApprentice and Cap01_mBlacksmith, also use belt.

Horses have their own slots: horse_head, horse_saddle, horse_torso and horse_shoe. See Horses and dogs.

A complete harness

The game can dress all of this in one Inventory.set: LegsPadded01_m07_C3, GambesonLong01_m09_C3, CollarPadded01_m08_C1, CoifSmall02_m07_E1, MailLong02_m01_C2, Cuirass07_m03_A4, ArmPlate03_m02_D1, LegsPlate02_m01_B4, Gauntlets05_m01_A5, BascinetVisor04_m02_A5, Waffenrock08_mPisek_B, Spurs01_m01_C, belt_4slot, ring_golden_cross and necklace_silver_cross_insignia.

Pieces that clash

The game would keep only some of a set of clashing pieces. So the server refuses such an Inventory.set with invalidEquipment before anything changes. The answer does not say which rows clash.

Only rows with equipped > 0 count, and each unit is a piece, so equipped: 2 is two pieces. Weapons, ammunition and classes with slot null are not counted.

One piece per place:

  • two slots sharing a place: a shirt or a gambeson, hose or padded legs, a coif or a padded coif, a cap or a helmet
  • two pieces in one slot, such as two shirts or two rings

Other conflicts:

  • A cap is not worn over a padded coif.
  • Plate legs with iron shoes, such as LegsPlate02_m01_B4, cover the feet, so shoes cannot go with them, though spurs can.

Pieces needing another beneath:

PieceNeeds
a helmeta padded coif
mail, plate or sleevesa gambeson
plate legspadded legs
spursshoes, or plate legs with iron shoes

Put both in the same Inventory.set. The game dresses in two passes, so row order does not matter. When taking off a lower piece, take off what depends on it too, such as a helmet with its padded coif.

Swapping: to put a piece on an occupied place, change both in one Inventory.set, using equipped: 0 for the old and equipped: 1 for the new.

Weapons and quick slots

Weapons and ammunition use equipped too. quickslots in Inventory.set decides which weapon sits in which quick slot. A belt adds weapon slots and a pouch adds item slots.

Dressing or undressing one piece

// Puts one row on or takes it off, leaving the other rows as they are.
function setWorn(player, rowId, worn) {
  const { Inventory } = kcds.players;
  const current = Inventory.get(player);
  if (!current) {
    return false;
  }
  const items = current.items.map((row) => {
    let equipped = row.equipped;
    if (row.id === rowId) {
      equipped = worn ? 1 : 0;
    }
    return { id: row.id, item: row.item, amount: row.amount, metadata: row.metadata, equipped };
  });
  const answer = Inventory.set(player, {
    items,
    quickslots: current.quickslots,
    revision: current.revision,
  });
  return answer.ok;
}
  • The game moves only rows whose equipped changed, and leaves whatever the player changed in the meantime.
  • revision refuses the change with staleRevision if the inventory has changed, so read it again.
  • equipped is only for equipment and weapons, and can be at most the row's amount. Otherwise you get invalidEquipment, as with clashing pieces, so setWorn on an occupied place returns false.

ok: true does not yet mean the body wears it, only that the server accepted the state. When the game has done what it can, playerInventoryChanged fires with the reason equipment, and the rows show what is really worn:

kcds.core.Events.on('playerInventoryChanged', (player, change) => {
  if (change.reason !== 'equipment') {
    return;
  }
  const rows = kcds.players.Inventory.get(player)?.items ?? [];
  const worn = rows.filter((row) => row.equipped > 0);
  console.log(`${player.nickname} wears ${worn.map((row) => row.name).join(', ')}`);
});

Reading what someone wears

  • Inventory.get(player).items with equipped > 0 gives the rows worn or held, with quality and condition.
  • player.equipment gives the classes the body wears, as 32 hex digits.
  • player.rightHandItem and leftHandItem give the class in each hand, or ''.

Client scripts see the same for each player their game shows.

Restoring what a player wore

Save the inventory as Inventory.get gives it on disconnect, and return it with Inventory.set when they come back. Their game dresses them by equipped and restores the quick slots. A player's joining name comes with their client, so wait for playerReady.

const { Inventory } = kcds.players;
const { Events } = kcds.core;
const savedOutfits = new Map();

Events.on('playerDisconnect', (player) => {
  const current = Inventory.get(player);
  if (!current) {
    return;
  }
  const items = current.items.map((row) => ({
    id: row.id,
    item: row.item,
    amount: row.amount,
    metadata: row.metadata,
    equipped: row.equipped,
  }));
  savedOutfits.set(player.nickname, { items, quickslots: current.quickslots });
});

Events.on('playerReady', (player) => {
  const outfit = savedOutfits.get(player.nickname);
  if (outfit) {
    Inventory.set(player, outfit);
  }
});

This example only keeps it in memory. A real server should save it in a database.

Reference: Inventory.