Felling trees

Players with an axe can chop down the level's trees, and this works without any script. After enough blows, a tree falls away from the chopper in the same way on all screens and lies as a trunk. Further blows cut the trunk into items, and later the tree grows back. kcds.world.Trees lets scripts change or refuse each step.

A tree is any mesh under objects/natural/vegetation/trees/. Fallen branches and stumps do not count.

From first blow to regrowth

A swing counts as a blow when all of these are true:

  • the blade meets the tree, according to the game's weapon trace (playerWeaponHit)
  • the player holds an axe, meaning a melee weapon named like one, or one of your axes
  • they stand within reach metres, 3.5 by default
  • they have not landed a blow in the last swingSeconds, 0.4 by default

The tree falls after hits blows, 8 by default. The standing tree and its collision are hidden in that dimension, and its mesh falls as a prop, pivoting about its foot: slowly at first, quickly at the end, with a small bounce. The server moves it, so everyone sees the same pose, and latecomers see it lying down. On a slope the trunk rests along the ground.

The trunk is cut after trunkHits axe blows, 6 by default. The trunk is solid until then. It disappears and gives drops to the chopper's inventory, or to the ground with dropTo: 'ground'.

The tree grows back after regrowMinutes, 30 by default, once no player is within regrowClearance metres, 10 by default, so nobody sees it appear in front of them.

Settings

const { Trees } = kcds.world;

Trees.configure({
  hits: 5,
  trunkHits: 4,
  regrowMinutes: 60,
  drops: [{ item: 'bsmt_woodExotic', amount: 3 }],
});

// For one species, by the start of its mesh path:
Trees.setSpecies('objects/natural/vegetation/trees/normal_trees/quercus', {
  hits: 14,
  height: 22,
});
SettingDefaultMeaning
enabledtruewhether trees can be chopped
hits8blows to fell a tree
trunkHits6blows to cut a trunk
regrowMinutes30real minutes until regrowth
regrowClearance10metres that must be free of players
reach3.5metres from the chopper's feet to the blow
swingSeconds0.4shortest time between one player's blows
height15trunk length in metres at scale 1
trunkRadius0.35trunk radius in metres, for blows on it
drops[{ item: 'bsmt_woodExotic', amount: 2 }]items from a trunk
dropTo'inventory''inventory' or 'ground'
stumpnulla prop model left where the tree stood
axesnullitem classes that chop; null means all axes

The game has no log or firewood item. To use one, register it with kcds.players.Items.register and put it in drops.

Events

Return false from a refusable server event to stop that step.

const { Events, Chat } = kcds.core;
const { Vector3 } = kcds.math;

const sacredGrove = new Vector3(500, 500, 30);

Events.on('playerTreeChop', (player, tree, blow) => {
  if (tree.position.distance(sacredGrove) < 30) {
    return false;
  }
  Chat.sendToPlayer(player, `${blow.number} / ${blow.of}`);
});

Events.on('playerTreeFell', (player, tree, fall) => {
  fall.direction = new Vector3(0, 1, 0); // always falls north
});

Events.on('playerTrunkCut', (player, tree, items) => {
  items.push({ item: 'apple', amount: 1 });
});

// Trees return only by day.
Events.on('treeRegrow', () => kcds.world.World.hour >= 6);
Server eventArgumentsRefusable
playerTreeChopplayer, tree, blowyes
playerTreeFellplayer, tree, fallyes; fall.direction can change
treeFelledtreeno
playerTrunkChopplayer, tree, blowyes
playerTrunkCutplayer, tree, itemsyes; items can change
treeRegrowtreeyes; delays it by a minute
treeRegrowntreeno

Client scripts of players in the tree's dimension hear treeChop(tree, blow), treeFalling(tree, fall), treeFelled(tree), trunkCut(tree, items) and treeRegrown(tree), useful for sounds, dust or a notice:

// client
kcds.core.Events.on('treeFalling', (tree, fall) => {
  kcds.ui.Hud.showNotification(`Timber! (${fall.seconds.toFixed(1)} s)`);
});

Working with a tree

A tree someone has chopped becomes a Tree, with these properties:

PropertyMeaning
id<mesh>@<x>,<y>,<z>
speciesits mesh
position, dimensionwhere it is
hits, healthits blows
state'standing', 'falling', 'felled' or 'cut'
felledAt, regrowAtits times

Methods: tree.fell(direction?) fells it at once, tree.regrow() brings it back and tree.setHits(n) sets its blows.

const { Trees } = kcds.world;

// A storm fells the tree a client's ray found.
kcds.core.Events.fromClient('storm', (player, object) => {
  Trees.of(object)?.fell(new kcds.math.Vector3(1, 0, 0));
});

const fallenCount = Trees.felled().length;
Trees.regrowAll(); // restores the whole forest

Keeping trees across restarts

Nothing is saved automatically, so a restart brings back all trees. Trees.state() returns plain JSON you can store, and Trees.restore() applies it. Felled trees lie where they lay, cut ones stay hidden, and regrowth times continue.

const fs = require('node:fs');
const path = require('node:path');

const { Trees } = kcds.world;
const { Events, Runtime } = kcds.core;
const forestFile = path.join(Runtime.serverDir, 'data', 'forest.json');

Events.on('resourceStop', () => {
  fs.mkdirSync(path.dirname(forestFile), { recursive: true });
  fs.writeFileSync(forestFile, JSON.stringify(Trees.state()));
});

Events.on('resourceStart', () => {
  if (fs.existsSync(forestFile)) {
    Trees.restore(JSON.parse(fs.readFileSync(forestFile, 'utf8')));
  }
});

Reference: Trees, Events.