Doors, gates, graves and keys

Doors

Doors belong to the level, so the server does not create them. It learns about a door when the first player's game reports having built it, and from then on the server decides whether it is open or locked. Doors are kept per dimension by the game entity's GUID, and their state is sent only when it changes.

const { Door } = kcds.world;

kcds.core.Events.on('doorInteract', (player, door, action, keySide) => {
  if (door.guid === '00000000000000ab' && action === 'lockpick') {
    return false;
  }
});

const vaultDoor = Door.find('ab');
if (vaultDoor) {
  vaultDoor.locked = true;
  vaultDoor.setOpen(false);
}

Finding doors

  • Door.all() and Door.getById(id) return the doors the server knows, or null for an unknown id.
  • Door.find(guid, dimension?) takes up to sixteen hex digits, with or without 0x. It returns the door, or null if the server does not know it yet.
  • Door.closestTo(player) asks the player's game which door they are at. It returns a promise of { found, reason, door }, with guid and name when found.

Properties

Read only: id, guid (sixteen lower-case hex digits), name, dimension, lockKey, canBePicked, builtLocked, position, open, side, lockedByScript.

locked can also be written. Players cannot open a lock a script set.

lockLevel is the lock's difficulty, from 0 (easy) to 1 (hard). A number overrides the level's value for all players in that dimension, and null restores it.

setOpen(open, side?) opens or closes it with nobody pushing, and opening also unlocks. It returns false if it was already in that state.

The doorInteract event

doorInteract(player, door, action, keySide) fires when a player uses a door, with the action 'open', 'close', 'unlock' or 'lockpick'. The server first refuses what the game's rules forbid. Returning false refuses the rest, and all players' copies of the door return to the server's state.

Removing a level door

Door.remove(guid, dimension?) takes a door and its collision out of a dimension for all players there. With no dimension, it uses the global one, which means all dimensions. door.destroy() does the same in the door's own dimension.

A removed door is not listed anywhere and cannot be used there. Door.restore(guid, dimension?) puts it back, and Door.removed(dimension?) lists the removed ones.

const { Door } = kcds.world;

const doorway = Door.find('41977678219215de');
Door.remove(doorway.guid, 7); // an open doorway in dimension 7

// Later:
Door.restore(doorway.guid, 7);

A removal is a level edit (Level editing). It lasts while the server runs and the resource that made it is started. Remember that the server only knows a door once a player's game has shown it.

Adding a new door

Door.spawn(definition, dimension?) hangs a new door, in the global dimension by default. Players there can open, close, unlock and pick it, under the same checks as level doors. New doors start closed, and later joiners get them too.

const guildDoor = kcds.world.Door.spawn({
  model: 'lvl1_door_a_left', // from Door.models(), by path or file name
  position: [1650.5, 2160, 35.4], // the hinge pivot
  rotation: [0, 0, 0.7071, 0.7071], // optional: x, y, z, w
  name: 'guild hall',
  locked: true,
  lock: 'boltAndKey', // 'none', 'bolt', 'boltAndKey' (default) or 'key'
  canBePicked: true,
  lockLevel: 0.8, // 0 to 1; leave out for the game's value
});
guildDoor.setOpen(true);
  • Door.models() lists each leaf used in the shipped levels, as { path, name, hinge, placements }. The hinge side is part of the mesh.
  • door.spawned is true for a new door, and door.model gives its leaf. A level door has model too.
  • door.toJSON() gives the spawn definition with the current lock state, or null for a level door. The server keeps nothing after a restart, so save this and hang the door again at startup.
  • door.destroy() takes it down for good. The resource that hung it owns it, so it also goes when that resource stops.
  • Its GUID is the server's own, 6b636473 plus a number, and stays the same on all machines until the server stops. Use Door.find(door.guid, dimension) to find it again.
  • NPCs do not walk through a new door, because the level's navigation has no link for it.

Reference: Door.

Gates

Level gates, such as the drawbridge, portcullises and palisade gates of Trosky and Kuttenberg, are the same for all players. The server owns each gate's state. Players' games move the gate there smoothly, and latecomers see it where it is.

const { Gate } = kcds.world;

for (const gate of Gate.all('trosecko')) {
  console.info(gate.key, gate.states);
}

const [firstGate] = Gate.all('trosecko');
firstGate.open(); // a barred gate has its bar removed first
firstGate.close({ bar: false }); // closes without replacing the bar
  • Finding: Gate.all(level?) and Gate.find(key). A gate's key stays the same across server starts.
  • State: state, states, isOpen, moving, secondsTo(state), setState(state), open(), close().
  • Bars: hasBar, barred, removeBar(), and placeBar(), which leaves the gate shut.
  • Events: gateChanged, gateBarChanged.

Reference: Gate.

Graves

The level's graves, where a player digs with a shovel, are also the same for all players. Once dug with the game's digging, a grave is open for everyone, including those joining later. Scripts can dig or refill one.

Players' games tell the server where graves are, so Grave.all() lists the ones in the server's level once a player has been near them. Nothing is saved, and all graves are full again after a server start.

const { Grave } = kcds.world;

kcds.core.Events.on('graveDug', (player, grave) => {
  kcds.core.Chat.sendToAll(`${player.nickname} dug out a grave.`);
  setTimeout(() => grave.reset(), 10 * 60 * 1000); // refills after ten minutes
});

const closest = Grave.nearest({ x: 3187.1, y: 495.9, z: 40.5 }, 50);
if (closest && !closest.dug) {
  closest.dig();
}
  • Static: Grave.all(near?), nearest first when near is given, Grave.get(id), Grave.nearest(position, radius?) and Grave.resetAll().
  • On a grave: id (its location in whole centimetres, "x,y,z", stable across starts), position, dug, distanceTo(position), dig(), reset().
  • Event: graveDug.

Reference: Grave.

Keys for a house

area.lockKey makes one key work on all the doors and chests in an area. Whoever carries it gets the game's Unlock prompt, and a Lock hint on the U key at an unlocked door or chest. The server checks the key on each use.

const house = kcds.world.Area.getById('006588875df00335');
if (house) {
  house.lockKey = 'key_home';
  house.setLocked(true);
}

A door or chest can have its own key that takes priority, such as a supervisor's key for one room. Use door.setKey(item) or stash.keyClass, and null or '' returns to the area's key.

player.keyBypass = true lets one player, such as an admin, open everything the server holds without a key. Their game shows those locks as unlocked, and nobody else's game changes.