Camera, aiming, pictures and local figures

These client features work on the player's own machine.

Free camera

kcds.ui.NoClip flies the camera through walls. In 'body' mode the player's body goes too, so others see it fly, and it lands where the flight ends. In 'camera' mode only the view moves.

// client
const { NoClip, Key } = kcds.ui;

Key.bind('f3', () => {
  if (NoClip.isActive()) {
    NoClip.disable();
  } else {
    NoClip.enable({ mode: 'body', speed: 20 });
  }
});

Keys: W A S D move, Space goes up, C goes down, Shift is fast and Alt is slow.

For cutscenes and spectators, use input: false and steer it yourself with NoClip.move(...), setPose, lookAt and focus(target, distance). NoClip.toScreen(point) and screenLine(x, y) help pick things on screen. noclipChanged(active, reason) tells you when it ends.

Who may fly

Everyone may fly by default, and the server decides otherwise. Setting player.noclipAllowed = false ends that player's flight with noclipChanged(false, 'notAllowed'). After that, NoClip.enable() returns 'notAllowed' and does nothing. The refusal comes from their game, so a client script cannot get around it.

To keep flying for staff, set it when a player joins:

// server
kcds.core.Events.on('playerConnect', (player) => {
  player.noclipAllowed = staff.has(player.steamId);
});

Client scripts can check NoClip.isAllowed() and the noclipAllowedChanged(allowed) event, or test the result:

// client
const { NoClip, Hud, Key } = kcds.ui;

Key.bind('f3', () => {
  if (NoClip.isActive()) {
    NoClip.disable();
  } else if (NoClip.enable() === 'notAllowed') {
    Hud.showNotification('Flying is not allowed here.');
  }
});

For a shot the server controls, see Camera.startShot in Server rules.

Finding what the player looks at

Client scripts can ask their own game about the world and get the answer within a frame or two, with no trip to the server. This suits aiming, fishing, building, or pressing a key while looking at something.

// client
const { Key, Camera } = kcds.ui;
const { World } = kcds.world;
const { Events } = kcds.core;

Key.bind('e', () => {
  const ray = Camera.rayFromScreen({ range: 4 });
  const hit = ray && World.raycast(ray.origin, ray.target, { mode: 'anything' });
  if (!hit) {
    return;
  }
  if (hit.entityClass === 'AnimDoor') {
    Events.toServer('useDoor', hit.entityGuid);
  } else if (hit.surface === 'mat_water') {
    Events.toServer('fish', hit.position);
  } else if (hit.category === 'tree') {
    Events.toServer('chop', { model: hit.model, position: hit.position });
  } else if (hit.player) {
    Events.toServer('greet', hit.player.id);
  }
});
CallResult
Camera.getPose(){ position, forward, right, up, fov, aspectRatio }, or null in a menu or while loading
Camera.rayFromScreen({ x, y, range }){ origin, direction, target }; x and y run from -1 to 1, default centre
World.raycast(from, to, { mode, ignoreSelf })the first thing hit, or null
World.traceAll(from, to, { mode, maxHits })everything along the line, nearest first, up to 8
World.groundHeight(position, { up, down })the ground's height under a point; a bridge or floor counts
World.snapToGround(...)the same, as a hit
World.entitiesAround(centre, radius, { class, max, physicalOnly })entities in a sphere up to 256 m, nearest first, with class, name and level GUID

mode is 'cover' (the world, props and doors), 'anything' (bodies too) or 'ground'.

What a hit contains

FieldMeaning
kind'terrain', 'entity', 'vegetation', 'brush' or 'static'
surfacesuch as mat_wood or mat_water
model, categorya tree, bush or placed mesh; category is 'tree', 'bush', 'plant' or 'rock'
entityClass, entityNamefor an entity
entityGuidthe level's sixteen hex digits, the same on all machines
playerwhose body it was

Do not treat a client's answer as proof. A server that must rely on it should check for itself. On the server, World.raycast asks a player's game and returns a promise.

Pictures from any camera

kcds.world.Render.view takes a picture from a camera you choose, without the player seeing that view. The promise gives a PNG data URL to show in a web view or send to your server.

// client
const { Vector3 } = kcds.math;

const shot = await kcds.world.Render.view({
  position: new Vector3(1652.5, 2151.5, 37.5),
  lookAt: new Vector3(1647.5, 2155.5, 36),
  fov: 60, // degrees; leave out or use 0 for the game's value
  width: 960, // PNG width; leave out for the window's size
});
// shot is { png: 'data:image/png;base64,iVBORw0...', width: 960, height: 530 }

Pose: give it as for Camera.startShot: position, then either rotation (a quaternion) or lookAt with an optional roll. A pose startShot refuses throws the same error here.

Contents: the picture is the frame as the game renders it, with the HUD as the player has it. Hide elements with Hud.setHidden. KCDS's chat and nameplates are not included.

While it is taken: this takes about a tenth of a second, during which the player keeps seeing the previous frame, so the game seems to pause briefly. The game's eye adaptation follows the other view, so a much brighter or darker view can briefly change the player's brightness.

Timing and failure: one picture is taken at a time, and others wait. The promise rejects with Render.view failed: <reason> when the world changed, the view could not be made, or no frame arrived in 15 seconds. A minimised window draws no frames.

A picture of a character alone on a transparent background is not available yet.

Character creator

kcds.ui.CharacterCreator opens the game's character stage, with a panel of choices next to the figure.

// client
kcds.ui.CharacterCreator.open({ title: 'Who are you?', genders: 'both' });

kcds.core.Events.on('characterCreatorClosed', (appearance, reason) => {
  if (reason === 'accepted') {
    kcds.core.Events.toServer('saveLook', appearance);
  }
});

It changes nothing itself. Send the accepted look to the server and apply it with player.setAppearance. The freeroam resource does this with /creator.

Local NPCs

kcds.characters.LocalNpc is a body shown only on this machine, such as a menu figure, a guide or a preview. A machine can have up to 64, and they disappear when your resource stops.

// client
const guide = kcds.characters.LocalNpc.create({
  soul: '210170d8-afb2-4cbd-9df5-da2a8e722af4',
  name: 'Guide',
  position: new kcds.math.Vector3(100, 200, 10),
});
guide.say('Follow the road to the mill.');

The local player

kcds.players.LocalPlayer is the Player at this machine, or null while loading. Compare ids with it when the server sends an event meant for one player.

Reference: NoClip, Camera, Render, World, CharacterCreator, LocalNpc, LocalPlayer.