Skip to content

Lumberjack

hobbs_lumberjack, current version 1.0.2

Overview

A full lumberjack job for VORP. Your players take an axe into the woods, fell trees, chop the trunk into logs and carry the logs to a sawing station. There they saw boards, plane finished planks or split kindling at a chopping block, each round with its own little skill game. They can also plant seedlings, water and fertilize them, and come back later to fell the tree they grew.

Trees live on the server, so everyone sees the same forest. Felled trees in the fixed areas grow back after a cooldown, planted trees are saved in the database and survive restarts. Nothing in the script pays money. The wood lands in the inventory and you decide where it is sold or used.

How it plays

  1. A player with the lumberjack job uses the axe item. The axe appears in the hand and the lumberjack prompts show up.
  2. Near a tree in a logging area a marker lights up at the trunk. Hold E to fell it. The player is snapped to the trunk, swings for a while, then the tree creaks, leans and comes down with a sound everyone nearby hears.
  3. Hold E again at the fallen trunk to chop it. When the trunk fades out, one to four logs lie along where it was, depending on the tree model.
  4. Hold E at a log to pick it up. Each log is one woodlog in the inventory. Logs nobody wants disappear after an hour.
  5. At a sawing station, press E to open the recipe menu. Pick a recipe, say how many rounds, and start. The controls lock, the player saws or planes, and a few seconds into every round the skill game opens. Win it and the round ends at half time, lose it and the full time runs. Material is never lost in the game, only time.
  6. A chopping block works the same way with the axe: large pieces become small pieces, a log becomes kindling.
  7. Planting: use a seedling item (a hoe has to be in the bags), walk around with the translucent preview, press E. The player rakes, a needle check decides if the seedling takes, then it is in the ground. It only grows while watered, needs water again at half time, and grows faster with fertilizer given in the first ten minutes. Once grown it is a normal tree and can be felled.

Requirements

Resource Note
vorp_core Characters, jobs and notifications
vorp_inventory Items, usable items and the durability metadata of axe, saw, plane, hoe and watering can
vorp_progressbar The progress bar while felling, chopping and working at a station
nss_libs Prompts
oxmysql The table of planted trees

All five are hard dependencies, the manifest refuses to start without them. The job name lumberjack is used everywhere by default. Whatever gives your characters their job has to hand out that exact name, or you change the names in the config.

Installation

  1. Unpack the resource into resources and keep the folder name hobbs_lumberjack.
  2. Run sql/install.sql on your database. It inserts the 22 items below into items (existing items are skipped) and creates the table hobbs_lumberjack_seeds for planted trees. Restart vorp_inventory after the import so it picks up the new items.
  3. Add an image for every item to your inventory's item image folder, named like the item.
  4. Make sure your job system can give characters the job lumberjack, or edit Config.JobsAllowedToCut, Config.JobsAllowedToPlant and the jobLock of the stations.
  5. Add it to server.cfg after its dependencies:

    ensure oxmysql
    ensure vorp_core
    ensure vorp_inventory
    ensure vorp_progressbar
    ensure nss_libs
    ensure hobbs_lumberjack
    
  6. Start the server. The console prints [hobbs_lumberjack] started. and 0 saved seedlings loaded. If you see oxmysql not available, the database link is missing and seedlings will not be saved.

  7. Walk to the Owanjila area or to Monto's Rest with the axe. If the trees of a logging area do not show, check the console for WARN lines about zones or models.

Note

The script switches off two world IPLs (Config.DeactivatedIPL) because the original map trees stood in the way of a logging area. If you move the areas, clear that list or those map trees stay gone for nothing.

Configuration

config.lua only creates the table. The real settings are split by topic into config/, texts live in locales.lua. The whole config/ folder, locales.lua and the SQL file are open, everything else is encrypted.

Language, jobs and keys (config/general.lua)

Config.Locale = 'en'              -- "en" or "de"
Config.DebugMode = false          -- console output and the test commands
Config.ResourceTickRate = 999     -- ms, client loop for markers and proximity

Config.JobsAllowedToCut   = { "lumberjack" }
Config.JobsAllowedToPlant = { "lumberjack" }

Config.Keys = {
    Chop        = 0xCEFD9220, -- E, fell a tree or chop a log
    Holster     = 0x4AF4D473, -- DEL, put the axe away
    Plant       = 0xCEFD9220, -- E, place the seedling
    PlantCancel = 0x4AF4D473, -- DEL, cancel
    Water       = 0x760A9C6F, -- G, water a growing tree
    Fertilize   = 0xB2F377E8, -- F, fertilize a growing tree
}

An empty job list means everyone may do it. If you want anyone to plant but only lumberjacks to fell, set JobsAllowedToPlant = {} and leave the cut list alone. The job check runs on the server for every action, not only when the axe is taken out.

Config.Notify sets the texture dictionary, duration and icons of the notifications, which go through vorp_core. Config.Marker is the red marker drawn at the targeted tree or log. Make it smaller or more transparent if it bothers you, it is pure optics.

Axe, logs, seeds and tools (config/items.lua)

Config.AxeItem = "lumberjack"
Config.AxeProp = "p_axe02x"

Config.AxeUseDurability      = true
Config.AxeMaxDurability      = 100
Config.AxeDurabilityPerCut   = 1     -- per felled tree
Config.AxeDurabilityPerLog   = 1     -- per chopped log
Config.AxeDurabilityPerSplit = 1     -- per round at the chopping block

Config.SawStationTools = {
    saw   = { useDurability = true, maxDurability = 100, wearPerCraft = 1 },
    plane = { useDurability = true, maxDurability = 100, wearPerCraft = 1 },
}

Config.LogItem = "woodlog"

Config.SeedItems = {
    { seedItem = "tree_seed_small", treeModel = "p_tree_pine_ponderosa_01", seedsNeeded = 1 },
    { seedItem = "tree_seed_big",   treeModel = "p_tree_pine_ponderosa_01", seedsNeeded = 1 },
}

Config.MaxPlantedSeeds = 50    -- planted trees on the whole server

Config.Tools = {
    Hoe         = { item = "hoe", wear = 1, maxDur = 100 },
    WaterCan    = { itemClean = "wateringcan", itemDirty = "wateringcan_dirtywater",
                    itemEmpty = "wateringcan_empty", wear = 1, maxDur = 10 },
    WaterBucket = { itemClean = "waterbucket", itemDirty = "waterbucket_dirtywater",
                    itemEmpty = "waterbucket_empty", useDurability = false },
    Fertilizer  = { item = "fertilizer" },
}

Durability is stored as item metadata. A fresh axe has no metadata yet, so the first swing writes the full value and the description "Durability = 100/100". At 0 the item is removed and the player gets a notification. Set AxeUseDurability = false if you sell axes that should last forever.

Only tools listed in SawStationTools wear out at the station. Any other tool named in a recipe's itemsNeeded is just checked for presence. If you add a recipe with a chisel and want it to wear, add chisel = { ... } here.

Both seedlings grow into the same pine by default even though the item texts call one a fir. If you want the small seedling to become a fir, set its treeModel to p_tree_douglasfir_02 or another model from config/trees.lua. A seed entry may also carry its own growthStages list, otherwise the default stages are used.

The watering can holds ten liters, one per watering, then it turns into the empty can. The bucket is used up in one go and the empty bucket comes back. Dirty water works but sets the tree back, see growth below.

Durations, distances and sound (config/timings.lua)

Config.ChopStandingDuration = 12      -- s, fell a standing tree
Config.ChopFallenDuration   = 12      -- s, chop a fallen log
Config.FallAnimDurationMs   = 5400    -- ms, creak, lean, fall
Config.TreeFadeOutMs        = 3000    -- ms, fade out before the tree is removed

Config.RespawnSecondsConfig = 3600    -- s, respawn of felled trees

Config.LogPickupProp        = "p_cs_cedarlog02x"
Config.LogPickupDistance    = 1.5    -- m, prompt range
Config.LogPickupLifetimeMin = 60     -- min, a log nobody picks up disappears (0 = never)

Config.TreeDetectionDistance = 2.0    -- m, standing trees
Config.LogDetectionDistance  = 1.0    -- m, fallen logs

Config.RenderRangeMeters         = 200.0
Config.BroadcastRangeMeters      = 220.0
Config.SubscriptionTickMs        = 2569

Config.FallSoundFile        = "treefallen.mp3"
Config.FallSoundMaxVolume   = 0.6
Config.FallSoundMaxDistance = 60.0    -- m

The two chop durations are base values. Every tree model multiplies them with its timeScale, so a big ponderosa at 2.3 takes about 28 seconds to fell and another 28 to chop. If your players find that slow, lower the base and the whole forest follows.

RespawnSecondsConfig is the cooldown for trees in the fixed areas and zones. Planted trees never respawn, once chopped they are gone. BroadcastRangeMeters has to stay larger than RenderRangeMeters, the server sends a player the trees inside the broadcast range and the client spawns the ones inside the render range.

The fall sound and the chop sound are mp3 files in html/sounds and get quieter with distance. Other players within FallSoundMaxDistance hear a tree come down.

Animations (config/animations.lua)

Everything the player does has a dictionary, clip and prop here: the swing while felling, the log chop, the log pickup, walking with the axe, watering with can or bucket, fertilizing and the rake while planting. Config.ChopSound sets the chop rhythm (cycleMs) and when inside that cycle the hit lands (impactOffsetMs), so the sound matches the animation. You will rarely touch this file. If you swap the axe prop, adjust Config.AxeAttachWalk so it sits on the back while walking.

Growth of planted trees (config/growth.lua)

Config.GrowthMinutesNoFert   = 120   -- minutes without fertilizer
Config.GrowthMinutesWithFert = 80    -- minutes with fertilizer
Config.FertilizeWindowMinutes = 10   -- fertilizer only works this long after planting

Config.GrowthTickSec    = 60
Config.GrowthDbFlushSec = 300

Config.MatureTreeCleanupRestarts = 4
Config.DirtyWaterPenaltySec = 10 * 60

Config.DefaultGrowthStages = {
    { model = "p_tree_pine_ponderosa_07" },
    { model = "p_tree_pine_ponderosa_06" },
    { model = "p_tree_pine_ponderosa_02" },
}

Config.GrowingTreePromptDistance = 1.5   -- m, water and fertilizer prompt
Config.PlantGhostDistance        = 1.2   -- m, preview in front of the player
Config.SeedMinSpacing            = 5.0   -- m, distance to other trees when planting

Config.PlantMinigame = {
    enabled = true,
    rounds  = 1,
    ragdoll = true,
    levels  = {
        { duration = 2100, zone = 7 },
        { duration = 1700, zone = 8.4 },
        { duration = 1300, zone = 9.8 },
        { duration = 1200, zone = 10.5 },
        { duration = 1000, zone = 14 },
    },
}

Growth time only runs while the tree is watered. At half time the water runs out once and the tree waits until someone waters it again. Fertilizer shortens the total to GrowthMinutesWithFert, but only when given within the first ten minutes, so players have to bring it along. Dirty water counts as watering but takes ten minutes of progress away.

The three stage models show in order as the tree passes each third of its time, then the grown model from the seed item appears and the tree is "standing" like any other. The server ticks every GrowthTickSec seconds and writes plain progress to the database every GrowthDbFlushSec seconds, stage changes and watering right away, and everything on resource stop.

MatureTreeCleanupRestarts keeps the table from filling with forgotten trees: every resource start counts one restart for each planted tree, and on the fourth start the tree is removed. With MaxPlantedSeeds = 50 nobody can plant more than fifty on the whole server, the seedling item says so when the limit is reached.

The planting check picks one of the levels at random: duration is how long the needle takes for one pass, zone the width of the light field in percent. A miss snaps the seedling, the item is taken and the player stumbles if ragdoll is on. Set enabled = false and planting always succeeds.

Tree models (config/trees.lua)

Config.DefaultTreeInfo = { logs = 2, timeScale = 1.0, zOffset = -0.20, trunkRadius = 0.50 }

Config.TreeModels = {
    ["p_tree_pine_ponderosa_01"] = { pool = "pine", logs = 4, timeScale = 2.3, zOffset = -0.20, trunkRadius = 0.50 },
    -- ...
    ["p_tree_pine_ponderosa_07"] = { pool = "pine", logs = 1, timeScale = 1.3, zOffset = -0.20, trunkRadius = 0.40 },
    ["p_tree_douglasfir_01"]     = { pool = "fir",  logs = 4, timeScale = 2.3, zOffset = -0.50, trunkRadius = 0.40 },
    -- ...
}

Config.ZoneTreeMinSpacing = 6.0
Key Meaning
pool "pine" or "fir", the zones pick their models from a pool
logs Logs the trunk gives after chopping
timeScale Multiplier on felling and chopping time
zOffset Height correction so the roots sit in the ground
trunkRadius How the fallen trunk rests on the ground

Twelve models ship, seven ponderosa pines and five douglas firs, from a one-log sapling to a four-log giant. If you add a model, give it a pool or the zones will not use it. Missing values fall back to DefaultTreeInfo.

Logging areas, zones and towns (config/world.lua)

Config.LumberAreaBlip = { sprite = 1904459580, scale = 0.2, enabled = true }

Config.LumberLocations = {
    {
        label       = "Owanjila",
        coords      = { x = 711.33, y = 1883.11, z = 240.44 },
        blipEnabled = true,
        jobs        = {},
        trees = {
            { model = "p_tree_pine_ponderosa_03", x = 700.43, y = 1847.24, z = 238.0, h = 0.0 },
            -- ...
        },
    },
}

Config.LumberZones = {
    {
        label       = "Bluewater Marsh",
        coords      = { x = 2711.42, y = -125.39, z = 42.88 },
        blipEnabled = true,
        jobs        = { "lumberjack" },
        polygon = {
            { x = 2739.98, y = -172.84 },
            { x = 2773.92, y = -66.37 },
            -- ...
        },
        treeCount = 50,
        modelPool = "fir",
    },
}

Config.TownRestrictions = {
    { town_name = "Valentine",  chop_allowed = false },
    -- ...
}

There are two ways to put trees in the world. A location has a hand-placed list of trees with model and position, good for a small clearing where every tree should sit exactly right. A zone is a polygon, and on every start the server scatters treeCount trees from the modelPool at random inside it, at least ZoneTreeMinSpacing apart (a zone can set its own minSpacing). The z of a zone tree comes from the zone's coords, so keep the polygon on fairly level ground. If the polygon is too small for the count, the console says how many fit.

jobs on an area or zone is a second lock on top of JobsAllowedToCut. Owanjila is open to every job that may cut at all, the two zones are for lumberjacks only. The shipped areas are examples, replace them with your own.

TownRestrictions lists the game's town zones where felling and planting are forbidden. The check uses the town the player is actually standing in, so a tree you place inside Valentine cannot be felled until you set chop_allowed = true or remove the line.

Sawing stations and recipes (config/sawstation.lua)

Config.SawStation = {
    Kinds = {
        saw   = { model = "p_group_vallogs01" },
        split = { model = "p_stumpwoodsplit01x" },
    },
    PromptKey    = 0xCEFD9220,   -- E
    PromptRadius = 1.2,          -- m
    StopKey         = 0x4AF4D473,         -- DEL, stop after the current round
    AllowedControls = { 0x9720FCEE },     -- T, chat

    Minigame = {
        enabled    = true,
        delay      = 2,         -- s of work before the game opens
        share      = 0.5,       -- a won round ends after this share of the duration
        minSeconds = 5,         -- no game when less time than this would be left for it
        games = {
            saw   = { name = "Saegen",  settings = { saegSwing = 1.5, saegKnots = 2, saegLives = 1 } },
            plane = { name = "Hobeln",  settings = { hobelBumps = 5, hobelTolerance = 0.07, hobelLives = 1, hobelSpeed = 360 } },
            axe   = { name = "Spalten", settings = { spaltSwing = 3, spaltWindow = 18, spaltKnots = 1, spaltLives = 1 } },
        },
    },
}

Config.SawingStations = {
    { kind = "saw",   position = { x = -1432.2801, y = -208.2755, z = 101.4215, h = 85.7130 },  jobLock = { "lumberjack" }, recipes = "saw" },
    { kind = "split", position = { x = -1421.4729, y = -205.41226, z = 101.25,  h = 176.2944 }, jobLock = { "lumberjack" }, recipes = "split" },
    -- ...
}

The script spawns the station props itself, so you only need coordinates and a heading. Four stations ship at Monto's Rest, two saw benches and two chopping blocks, as examples for your own spots. jobLock is per station, an empty list opens it to everyone.

The minigame opens delay seconds into every round. Its time limit is share of the round minus the delay. Won, the round ends after share of the duration, so a 25 second recipe takes 12 seconds with a clean game. Lost or cancelled, the full 25 run. If a recipe is so short that fewer than minSeconds would be left for the game, the round is just the progress bar. enabled = false turns the games off everywhere. The settings are the fixed values of each game (knots, bumps, swing speed), the stroke count and cut depth are fitted to the time limit automatically. The German names are the internal keys of the games, leave them.

Recipes are grouped by station kind:

Config.SawRecipes = {
    saw = {
        { label = "Large piece of wood",
          itemsNeeded = { "saw" },
          items = {
              itemsLoosing = { { itemName = "woodlog", amount = 1 } },
              gainedItems  = { { itemName = "hwood",   amount = 5 } },
          },
          duration = 25,
        },
        { label = "Small planks",
          itemsNeeded = { "saw" },
          items = {
              itemsLoosing = { { itemName = "hplanks", amount = 5 } },
              gainedItems  = { { itemName = "planks",  amount = 10 } },
          },
          duration = 50,
          alternatives = {
              { itemsLoosing = { { itemName = "woodlog", amount = 1 } }, duration = 50 },
          },
        },
        { label = "Finished plank",
          itemsNeeded = { "plane" },
          items = {
              itemsLoosing = { { itemName = "planks", amount = 5 }, { itemName = "beewax", amount = 1 } },
              gainedItems  = { { itemName = "wood_plank", amount = 5 } },
          },
          duration  = 35,
          animation = Plane.animation, prop = Plane.prop, stand = Plane.stand,
        },
    },
    split = {
        { label = "Kindling",
          itemsNeeded = { Config.AxeItem },
          items = {
              itemsLoosing = { { itemName = "woodlog",      amount = 1 } },
              gainedItems  = { { itemName = "kindlingwood", amount = 20 } },
          },
          duration  = 40,
          animation = Split.animation, prop = Split.prop, stand = Split.stand,
        },
    },
}
Key Meaning
label Shown in the menu
itemsNeeded Tools that have to be in the bags, not used up. The first one decides which game is played
items.itemsLoosing Materials taken per round
items.gainedItems Results per round
duration Seconds per round
animation, prop, stand Optional, override the default saw animation and tool. stand is where the player stands relative to the station
game Optional key of Minigame.games, false for no game
alternatives Other inputs for the same result, each with its own itemsLoosing and optional duration

The shipped saw list: large piece of wood, small piece of wood, large planks, small planks (from large planks or straight from a log), squared timber and finished plank, which needs the plane and a beeswax. The chopping block splits large pieces into small ones and a log into kindling. To add a recipe, copy an entry and change the item names, the labels in the menu come from your items table.

Plane and Split hold the animation, tool prop and stand offset of planing and splitting. If the player stands wrong at your own station, switch on Config.DebugMode, stand right, and read the values from /stationstand and /tooloffset.

Texts (locales.lua)

English and German ship. Every prompt, notification, menu text and minigame hint has a key. Copy a block, translate it, and set Config.Locale to its key. title_<topic> are the notification titles, mg_* the minigame texts, ui_* the station menu.

Items

Item Used for
lumberjack The axe. Use it to take it out or put it away, needed to fell, chop and split. Wears out
woodlog A log picked up from a chopped trunk. Input of nearly every recipe
hwood Large piece of wood, 5 per log at the saw
wood Small piece of wood, 10 per log at the saw or per 5 large pieces at the block
hplanks Large planks, 5 per log
planks Small planks, 10 per 5 large planks or per log
squared_timber Squared timber, 4 per log
wood_plank Finished plank, 5 from 5 small planks and a beeswax with the plane
kindlingwood Kindling, 20 per log at the chopping block
beewax Beeswax, one per round of finished planks
tree_seed_small, tree_seed_big Seedlings. Use to plant
saw Tool for the saw recipes. Wears out
plane Tool for the finished plank. Wears out
hoe Has to be in the bags to plant. Wears out
fertilizer One per fertilized tree
wateringcan, wateringcan_dirtywater, wateringcan_empty Ten waterings, then the empty can
waterbucket, waterbucket_dirtywater, waterbucket_empty One watering, the empty bucket comes back

The script never fills a can or bucket and does not sell anything. Filling water containers and buying tools, seeds, fertilizer and beeswax is for your shops and other scripts.

Commands and keys

With the axe out:

Key Action
Hold E Fell the targeted tree, chop the fallen trunk, or pick up a log
DEL Put the axe away

Planting and tending, near a seedling:

Key Action
E Plant the seedling at the preview
DEL Cancel planting
Hold G Water (shown only with a can or bucket with water in the bags)
Hold F Fertilize (shown only in the first ten minutes with fertilizer in the bags)

At a station:

Key Action
E Open the recipe menu
Enter / Esc Select and back in the menu, as the hint bar at the bottom shows
DEL Stop after the current round
T Chat stays open while working, everything else is locked

In the minigames:

Key Action
Mouse button or Space Draw the saw, push the plane, swing the axe, stop the planting needle
Esc Cancel the game, the round then runs its full time

There are no chat commands for players. With Config.DebugMode = true these test commands exist for you: /treefalltest [model] drops a test tree in front of you, /treesoundtest plays the fall sound, /treecleanup removes test trees, /treestatus prints the trees around you, /stationstand and /tooloffset px py pz rx ry rz measure stand and tool positions at the nearest station.

FAQ

The axe does nothing when used. The character needs one of the jobs in Config.JobsAllowedToCut. Without it the item says "You do not have the right job to fell trees." Also, the axe cannot be taken out on a horse, in a wagon, while swimming or cuffed, and it is put away if any of that happens.

I see the tree but no prompt. The prompt only shows with the axe out, within TreeDetectionDistance (2 m) of a standing tree or 1 m of a fallen trunk. Logs on the ground need the axe out as well. Inside a town from TownRestrictions the prompt shows, but the action is refused.

How do I add a logging area? Either a location with a tree list or a zone with a polygon in config/world.lua. For a zone, walk the edge of your patch, note four or more x and y pairs, set a center for the blip and a treeCount. Restart the resource and read the console if it could not place all trees.

What happens on a restart? Fixed and zone trees all stand again, zone trees in new random spots. Logs lying on the ground are gone, they are not saved. Planted trees are loaded from the database with their progress and water state, a felled planted tree is loaded lying down. Every start counts one restart for the planted trees, and after four they are cleaned up.

Two players felled the same tree. They cannot. The server locks a tree for the first player and refuses the second with "This tree is no longer there." A lock is released when the player disconnects or after the felling time plus a minute, and a completion that arrives faster than the felling time is thrown away.

The log cannot be picked up. The inventory has no room for a woodlog, or the item is missing from the items table. In the second case the server console prints an error asking whether you imported the SQL file.

The station says I am missing a tool, but it is in my bags. A tool with durability 0 counts as missing. Saw and plane break at 0, the item disappears with a notification. Check the item description, it shows "Durability = n/100".

Material vanished at the station. It should not. If the results do not fit in the bags, the round refunds the material. Only if the refund fails too, the player gets "You lost: ..." and the server console logs the character and items so you can hand them back.

Does the script pay money? No. Nothing is bought or sold here. Give the wood a price in your shops or craft with it elsewhere.

Are there exports or events for other scripts? No. All events are internal between client and server.

Is it heavy on the server? Trees are plain data on the server. Every SubscriptionTickMs it sends each player only the trees and logs within BroadcastRangeMeters that the player does not have yet. The client spawns props only inside RenderRangeMeters and removes them again when the player walks off. Growth ticks once a minute and writes to the database in batches.

Changelog

1.0.2

First release.