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¶
- A player with the lumberjack job uses the axe item. The axe appears in the hand and the lumberjack prompts show up.
- 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.
- 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.
- Hold E at a log to pick it up. Each log is one
woodlogin the inventory. Logs nobody wants disappear after an hour. - 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.
- A chopping block works the same way with the axe: large pieces become small pieces, a log becomes kindling.
- 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¶
- Unpack the resource into
resourcesand keep the folder namehobbs_lumberjack. - Run
sql/install.sqlon your database. It inserts the 22 items below intoitems(existing items are skipped) and creates the tablehobbs_lumberjack_seedsfor planted trees. Restartvorp_inventoryafter the import so it picks up the new items. - Add an image for every item to your inventory's item image folder, named like the item.
- Make sure your job system can give characters the job
lumberjack, or editConfig.JobsAllowedToCut,Config.JobsAllowedToPlantand thejobLockof the stations. -
Add it to
server.cfgafter its dependencies: -
Start the server. The console prints
[hobbs_lumberjack] started.and0 saved seedlings loaded.If you seeoxmysql not available, the database link is missing and seedlings will not be saved. - 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
WARNlines 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.