Skip to content

Wagoncraft

hobbs_wagoncraft, current version 1.0.1

Overview

A wagon building job. Players with the workshop job order a wagon at the workshop NPC, pay the materials and a fee, and the bare wagon goes up on sawhorses at the building site. From there it is finished task by task: sawing and planing planks at the workbench, nailing the side panels, fitting every wheel, the shaft and the driver's seat, then sanding both sides. Every task is a small minigame played in an animation with the right tool in hand, and the lumber stack next to the bench shrinks as the work goes on. When the last task is done, the wagon is written into your stable script and waits there for whoever ordered it.

Thirty-eight wagons are included, from a light farm cart to the Concord stagecoach, the hearse and the heavy freight wagons. Each has its own price, materials and measured positions for the sawhorses and work stands. The workshop also has a shared storage where materials can be kept, and whatever is in there counts towards a build.

How it plays

  1. You walk up to the workshop NPC. If you have the workshop job, a prompt offers to open the workshop. Without the job nothing shows.
  2. The menu opens with a live preview: the wagon you pick stands next to the NPC and a camera looks at it. You drag to turn it, scroll to zoom and use Page Up and Page Down to tilt. The right side shows materials, price, number of tasks and storage slots. You can search, sort, and mark favorites.
  3. In the Storage tab you put materials from your bag into the workshop or take them back out. Everyone with the job shares that storage.
  4. You give the wagon a name (4 to 20 characters) and hold the build button. The server takes the fee from your cash and the materials, first from the workshop storage, then from your bag. If a building site is free, the wagon appears there on sawhorses.
  5. At the site you see a ground marker at every spot that can be worked on right now. You walk to one, press E, and your character is placed at the stand and starts the animation. After five seconds the minigame opens: saw, plane, nail, fix the wheel or sand. Pass it, work five more seconds, and the task is done.
  6. Steps come in order. Within a step several people can work at the same time, one per spot: for example one on the left side panel and one on the right. When a step is complete you get a short note ("The wheels are on.").
  7. The last task hands the wagon over to the stable. The person who finished and the person who ordered both get the message that the wagon is waiting at the stable.
  8. Whoever ordered the build can give it up by holding Del at the site. Materials and fee are gone. A build nobody has touched for 14 days can be cleared by anyone with the job.

Requirements

Resource Note
vorp_core Characters, jobs, cash, notifications
vorp_inventory Materials in the bag, item labels, workshop storage moves
oxmysql Favorites, workshop storage, running builds, and the row in the stable table
nss_libs Prompts and the optional map blip
A stable script The finished wagon is inserted into its wagon table. Supported: VORP stables, sirevlc, syn and gum style tables (see Config.StableSystem)

The shop is tied to one or more VORP job names. Create the job before you set up the shop, the default is carpenter.

Installation

  1. Unpack the resource into resources and keep the folder name hobbs_wagoncraft.
  2. Make sure these items exist in your vorp_inventory items table, with images: carriage_parts, ironbar, copperbar, oilunrefined, nails, cotton, leather. The script reads their labels from the inventory database. A missing item is printed as a red line in the console on start, and the menu shows the raw item name instead of a label. You can rename the materials in Config.Wagons to items you already have.
  3. Create the job carpenter in VORP, or change job in Config.Shops to a job you already use.
  4. Open config.lua and set Config.StableSystem to the stable script you run. The default is "vorp".
  5. Add the resource to server.cfg after its dependencies and after your stable script:

    ensure oxmysql
    ensure vorp_core
    ensure vorp_inventory
    ensure nss_libs
    ensure vorp_stables   # or the stable script you set in Config.StableSystem
    ensure hobbs_wagoncraft
    
  6. Start the server. No SQL file is needed. On first start the script creates three tables:

    Table Holds
    hobbs_wagoncraft_builds One row per running build: site, owner, wagon, name and which tasks are done
    hobbs_workshop_storage Materials stored in each workshop
    hobbs_wagoncraft_favorites Favorite wagons per character
  7. Check the console for lines starting with [hobbs_wagoncraft]. "Unknown StableSystem" means step 4 is wrong, "Item 'x' not in the database" means step 2 is missing something. With Config.Debug = true you also see how many sites are in use and how many items loaded.

  8. If you want the Discord log, put your webhook URL in server_config.lua.

Note

The included shop, its NPC, the preview spot and the building site are examples. Move them to where your workshop is. The debug command /wagoncraft_site prints the line for a new site, see Configuration below.

Configuration

Settings are split into three files. config.lua is the shop, the wagons and the prices, and every client downloads it. config_build.lua is the building process. server_config.lua never leaves the server and holds the webhook. Texts live in locales.lua.

Language and debug

Config.Locale = "en"   -- "en" or "de"
Config.Debug  = false

Config.Debug turns on console output and the four setup commands. Keep it off on a live server, the commands are not protected by anything else.

Notifications

Config.Notify = {
    dict     = "generic_textures",
    icon     = "tick",
    warnIcon = "cross",     -- used for failures and aborts
    duration = 5000,
}

All messages go through the VORP left notification. The title is notifyTitle in locales.lua.

Keys

Config.Keys = {
    open = 0xCEFD9220,      -- E, open the shop menu
}

The keys at the building site are in config_build.lua (see below).

Preview camera

Config.Camera = {
    view  = { distance = 8.5, height = 3.4, angle = 30, aim = 1.8, fov = 45 },
    zoom  = { min = 0.6, max = 1.8, step = 0.1 },
    tilt  = { min = -12, max = 88, step = 1.5 },
    light = { enabled = true, color = { 255, 236, 205 }, range = 10.0, intensity = 20.0, toward = 0.4, height = 2.0 },
}

The camera stands next to the preview wagon on the NPC's side, turned angle degrees towards the front, and looks at a point aim meters above the ground. When a wagon is picked for the first time in a session the script probes a set of angles and distances around the wagon and keeps the one with the clearest view, so a wall behind the spawn point does not end up in the picture. The light is a warm lamp between camera and wagon so the preview reads well at night. Set light.enabled = false if you find it too bright in daylight.

Distances and names

Config.UI = {
    promptDistance = 2.0,   -- m, distance to the NPC for the prompt
    shopReach     = 10.0,   -- m, max distance to the NPC for orders and storage (checked by the server)
    nameMinLen    = 4,      -- wagon name length in characters
    nameMaxLen    = 20,
}

shopReach is checked on the server for every order and every storage move, so a client cannot do those from across the map. The name may not contain < or >.

Stable system

Config.StableSystem = "vorp"   -- "vorp" | "sirevlc" | "syn" | "gum"

This decides which table the finished wagon is inserted into:

Value Table What is written
vorp stables type = 'wagon', status = 1, isDefault = 0, empty gear and inventory
sirevlc sirevlc_wagons model hash, price = resalePrice, stashlimit and stashslots from the wagon entry, default equipment
syn wagons selected = 0, empty items
gum wagons like syn plus an empty weapons column

Owner is the character who ordered the build. If the value is wrong the console says "Unknown StableSystem" on start and every order is refused with a note to tell the stable master.

Workshop storage

Config.Workshop = {
    enabled = true,
}

With storage on, the menu has a Storage tab. Materials in there count towards a build and are used before the bag. Storage is per shop and shared by everyone with the job. Turn it off and materials come from the bag only.

Shops

Config.Shops = {
    [1] = {
        name = "Wagon Workshop",
        job  = { "carpenter" },    -- jobs that may order and build here, "all" = everyone
        npc = {
            enabled = true,
            model   = "mp_u_m_m_fos_musician_01",
            pos     = {x = -371.22, y = 800.22, z = 115.13, heading = 313.87},
            scenario = "WORLD_HUMAN_CLIPBOARD",   -- "" = just stands there
        },
        wagonSpawn = {x = -395.18, y = 803.38, z = 115.95, heading = 269.83},
        sites = {
            { x = -365.22, y = 805.17, z = 115.08, h = 269.16 },
        },
        blip = { enabled = false, sprite = -1989306548 },
    },
}
Key Meaning
name Shown in the menu header and on the blip
job VORP job names allowed to order, build and use the storage. "all" opens it to everyone
npc The clerk. pos is also where the prompt and the server distance check are
wagonSpawn Where the preview wagon stands while the menu is open. Leave room around it, the camera needs it
sites Building sites, one wagon per site. h points forward along the shaft. No free site means no new orders
blip Map blip at the NPC, off by default

Add more shops as [2], [3] and so on. A shop can have several sites, each one is a separate build. The build table stores the site as shopId:siteIndex, so do not reorder shops or sites while a build is running.

Fee

Config.FeeShare = 0.45   -- share of the resale price, rounded down

The fee is paid in cash on top of the materials when ordering. With the default 45 percent a cart with resalePrice = 120 costs 54 dollars plus materials. Set it to 0 if you want materials only.

Categories and wagons

Config.Categories = { "carts", "coaches", "travel", "work", "special" }

Config.Wagons = {
    { category = "carts", hash = "cart01", resalePrice = 120, stashLimit = 90, stashSlots = 90,
      materials = { carriage_parts = 2, ironbar = 2, oilunrefined = 1, nails = 1 } },
    { category = "coaches", hash = "stagecoach001x", resalePrice = 450, stashLimit = 200, stashSlots = 200,
      materials = { carriage_parts = 18, ironbar = 3, copperbar = 2, cotton = 2, leather = 2, oilunrefined = 2, nails = 2 } },
    -- ...
}
Key Meaning
category One of Config.Categories, menu group. Name is category_<id> in locales.lua
hash Wagon model name. Display name is wagon_<hash> in locales.lua
resalePrice Base for the fee, and the price written for sirevlc stables
stashLimit, stashSlots Written to the sirevlc_wagons row, shown in the menu as storage slots
materials Item name and amount, from your inventory

The included list:

Group Hash Name Resale price
Carts cart01 Light Farm Cart 120
Carts cart02 Farm Cart with Side Boards 120
Carts cart03 Small Market Cart 250
Carts cart04 Small Yard Cart 200
Carts cart05 Water Barrel Cart 125
Carts cart07 Farmer's Cart 200
Carts cart08 Country Cart 200
Carts cart06 Large Freight Cart 300
Passenger Coaches buggy02 Open Buggy 250
Passenger Coaches buggy01 Buggy with Leather Top 250
Passenger Coaches buggy03 Family Buggy 250
Passenger Coaches coach5 Victoria 300
Passenger Coaches coach4 Landau 350
Passenger Coaches coach2 Closed Town Coach 700
Passenger Coaches coach6 Wagonette 300
Passenger Coaches stagecoach003x Town Stagecoach 400
Passenger Coaches stagecoach002x Light Country Stagecoach 400
Passenger Coaches stagecoach001x Concord Stagecoach 450
Passenger Coaches stagecoach005x Overland Stagecoach 400
Passenger Coaches stagecoach006x Horse Omnibus 500
Covered Wagons wagon02x Camp Covered Wagon 600
Covered Wagons wagon03x Reinforced Camp Covered Wagon 500
Covered Wagons wagon06x Supply Covered Wagon 300
Work Wagons utilliwag Flatbed Wagon 300
Work Wagons wagon04x Light Farm Wagon 700
Work Wagons wagon05x Open Freight Wagon 900
Work Wagons chuckwagon000x Chuck Wagon 700
Work Wagons chuckwagon002x Equipment Wagon 600
Work Wagons supplywagon Large Supply Wagon 500
Work Wagons gatchuck Heavy Freight Wagon 800
Work Wagons logwagon Log Wagon 700
Work Wagons coal_wagon Coal Wagon 750
Work Wagons oilWagon01x Small Oil Wagon 300
Work Wagons oilWagon02x Large Oil Wagon 400
Trade Wagons wagondairy01x Dairy Wagon 500
Trade Wagons wagonwork01x Delivery Wagon 500
Trade Wagons wagontraveller01x Merchant Wagon 750
Trade Wagons coach3 Hearse 400

Remove entries you do not want. To add a wagon, add its entry here, a wagon_<hash> name in every language block of locales.lua, and ideally a measured entry in Config.Build.models (see below). Without a measured entry the wagon still works, it then gets one sawhorse per axle and calculated work stands.

Building site (config_build.lua)

Config.Build = {
    siteRange  = 80.0,   -- m, the site is set up on the client within this range
    siteReach  = 15.0,   -- m, max distance to the site for a task (checked by the server)
    spotReach  = 1.5,    -- m, the prompt shows up within this range of a work spot
    slack      = 20,     -- s, extra time a task may take (lag)
    around     = { before = 5, after = 5 },   -- s, work before and after a minigame
    staleDays  = 14,     -- days without progress, then anyone with the job may clear the build

    keys = {
        work    = 0xCEFD9220,   -- E, start and stop working
        abandon = 0x4AF4D473,   -- DEL (hold), give up the build
    },

    marker = { type = 0x94FDAE17, size = 0.6, color = { 200, 160, 90, 120 } },
    -- ...
}

siteRange is how close a player has to be before the wagon, sawhorses, workbench and lumber are spawned on their client. slack matters if your players have high ping: the server only accepts a task that took between the fastest possible run and the full minigame time plus slack. marker is the ground ring at every spot that is open right now, set it to false to turn it off.

Sawhorses, workbench and stock

    sawhorse = { model = 'p_sawhorse03x', turn = 180.0 },
    lift     = 0.30,
    standOff = { sides = 0.30, wheels = 0.45, front = 0.50, seat = 0.30 },

    bench = {
        model = 'p_group_vallogs01', x = -3.95, y = -1.70, z = 0.00, h = 90.0,
        stands = {
            saw   = { forward = -0.623, right = 0.048, headingDelta = -10.17 },
            plane = { forward = -0.623, right = 0.048, headingDelta = 79.83 },
        },
    },

    props = {
        { model = 'p_bat_lumber05ax_stack_01c', x = -3.70, y = 4.55, z = 0.00, h = 272.5, till = 'saw' },
        { model = 'p_bat_lumber05ax_stack_01b', x = -3.15, y = 4.55, z = 0.00, h = 275.0, from = 'saw', till = 'plane' },
        { model = 'p_bat_lumber05ax_stack_01a', x = -2.55, y = 4.60, z = 0.00, h = 277.5, from = 'plane', till = 'body' },
    },

All positions are relative to the site: x right, y forward, z up, h in degrees. The bench and the stock props are placed the same way at every site, so one measurement fits all your workshops as long as the ground around the site is flat enough. The three lumber stacks are the "shrinking" stock: a prop shows once the step in from is done and disappears once the step in till is done. standOff is how far in front of a part you stand for wagons without their own measured stands. Use /wagoncraft_setup to move all of this in game and print the result.

Steps and works

    steps = {
        { key = 'saw',    at = 'bench',  count = 2, work = 'saw' },
        { key = 'plane',  at = 'bench',  count = 1, work = 'plane' },
        { key = 'body',   at = 'sides',  work = 'nail' },
        { key = 'wheels', at = 'wheels', work = 'wheel' },
        { key = 'front',  at = 'front',  work = 'stake' },
        { key = 'seat',   at = 'seat',   work = 'seat' },
        { key = 'sand',   at = 'sides',  work = 'sand' },
    },
at Spots created
bench count passes at the workbench
sides One per side, left and right
wheels One per wheel of that model, from data/wagon_wheels.lua
front One at the shaft
seat One at the driver's seat

A two-wheel cart has 2 + 1 + 2 + 2 + 1 + 1 + 2 = 11 tasks, a four-wheel wagon 13. The menu shows that number. Prompt texts are step_<key> and the step-complete note is done_<key> in locales.lua. Remove a step to shorten the job, for example sand.

    works = {
        saw   = { anim = { dict = 'amb_work@world_human_saw_wood@male_a@base', clip = 'base', flag = 1 }, tool = 'saw',
                  game = { name = 'Saegen', min = 6, time = 60, timeKey = 'saegTime',
                           settings = { saegStrokes = 16, saegSwing = 1.5, saegKnots = 2, saegLives = 1 } } },
        nail  = { anim = { dict = 'amb_work@world_human_hammer@wall@male_a@trans', clip = 'base_trans_base', flag = 1 }, tool = 'hammer',
                  game = { name = 'Nageln', min = 6, time = 60, timeKey = 'nagelTime',
                           settings = { nagelScene = 'wand', nagelSpare = 1, nagelSwing = 2.0, nagelWindow = 3 } } },
        -- plane, wheel, stake, seat, sand ...
    },

Each work is an animation, a tool in hand and a minigame. time is the time limit of the game in seconds, min the fastest run the server accepts. The settings are the difficulty: number of strokes, spare nails, how fast the gauge swings and so on. There are five games:

name Used for How it plays
Saegen Sawing planks Hold to draw the saw back, release in the green band. Knots narrow the band
Hobeln Planing Hold to push the plane over the board, release in the green run-out
Nageln Side panels, shaft, seat Click when the swaying nail stands straight, then strike in rhythm. A bent nail is pulled with the claw
Radreparatur Wheels Wind up and release in the green band for each nail, then tighten the nut with the wrench
Schleifen Sanding Run the block over the wood paper by paper, pull splinters along the grain first

Failing or quitting a game ends the task, the spot stays open and can be tried again. If you want a work without a game, replace game with seconds = 10 and the task is just the animation for that long.

Tools

    tools = {
        hammer = { model = 'p_hammer01x',    bone = 'PH_R_Hand', pos = { 0.0, 0.0, 0.0 }, rot = { 0.0, 0.0, 0.0 } },
        saw    = { model = 'p_saw01',        bone = 'PH_R_Hand', pos = { 0.0, 0.4, 0.0 }, rot = { 90.0, 0.0, 0.0 } },
        plane  = { model = 'p_woodplane01x', bone = 'PH_R_Hand', pos = { 0.0, 0.0, 0.0 }, rot = { 0.0, 0.0, 0.0 } },
    },

The tool a worker holds is published through the player's state bag, so every player within 80 meters sees it in their hand. Sanding has no tool.

Measured models

    models = {
        ["cart01"] = { lift = 0.00, pitch = -3.0, roll = 0.0,
            horses = { { x = 0.00, y = -0.74, h = 180.0 }, { x = 0.00, y = 1.42, h = 180.0 } },
            stands = { left = { x = -1.32, y = 0.66, h = 270.0 }, right = { x = 1.24, y = 0.56, h = 90.0 },
                       wheel_lf = { x = -1.44, y = -0.00, h = 270.0 }, wheel_rf = { x = 1.39, y = 0.04, h = 90.1 },
                       front = { x = -0.01, y = 2.24, h = 165.0 }, seat = { x = 0.02, y = -1.24, h = 3.2 } } },
        -- ...
    },

One entry per wagon model, lower case. lift, pitch and roll put the wagon on the sawhorses so the wheels do not sink into the ground, horses are the sawhorses relative to the wagon, stands are the work positions relative to the wagon. All 38 included wagons are measured. A wagon without an entry gets one sawhorse per axle and stands calculated from the model's bones and size, which is fine for a first test. /wagoncraft_measure <model> and /wagoncraft_stands <model> print a finished line for this table.

Discord webhook (server_config.lua)

Config.DiscordWebhook = ""   -- empty = off

One embed per event: build started (player, wagon, site, cost), wagon built (owner, wagon, value, stable id), build abandoned, delivery to the stable failed, and every storage deposit and withdrawal. Titles and bodies are the webhook* keys in locales.lua.

Languages

locales.lua ships English and German. Copy a block, translate it and set Config.Locale to its key. The block also holds the wagon names, the category names, the minigame hints and the webhook texts, so a new language needs all of them.

Items

Item Used for
carriage_parts Every wagon, 2 to 35 pieces depending on size
ironbar Every wagon
nails Every wagon
oilunrefined Every wagon
copperbar The market cart, the large freight cart and many of the bigger wagons
cotton Coaches, the hearse and the reinforced covered wagon
leather Same wagons as cotton

The script does not create or hand out any item, it only takes materials. Names are whatever you write in Config.Wagons, so you can swap them for your own crafting items. Labels in the menu come from your inventory database.

Commands and keys

Player

Key Where Action
E Within 2 m of the workshop NPC, with the job Open the workshop
Mouse drag, mouse wheel, Page Up, Page Down In the menu Turn, zoom and tilt the preview camera
Hold the build button In the menu Order the selected wagon
E At an open work spot Start the task. Pressing E again during the task stops it
Hold Del Near the wagon, as the owner (or after 14 days with the job) Give up the build
Mouse button or Space In a minigame Play, as the hint at the top of each game says

While a task runs, movement and weapons are blocked. Only looking around, E and the chat stay open. Walking away more than a meter, dying, or the build disappearing aborts the task.

There are no chat commands for players.

Setup (only with Config.Debug = true)

Command What it does
/wagoncraft_site Prints your position as a sites line for config.lua, and relative to the nearest site if there is one within 30 m
/wagoncraft_setup [model] Shows a site with wagon, workbench, stock props, a worker and rings at every stand. Walk through bench, stands, props and the standOff distances, move them, print the lines for config_build.lua. Default model is cart01
/wagoncraft_measure <model> Puts the wagon on sawhorses at the nearest site, or 6 m in front of you. Adjust lift, pitch, roll and each sawhorse, print the models line
/wagoncraft_stands <model> Shows the worker at every stand of that model, lets you move each one and switch between the works done there, prints the stands line

Keys in the setup tools: Enter prints the current values to F8, Del ends the tool, F picks the next part, the arrow keys move the part (for the wagon itself they pitch and roll it), Q and E turn it, and R switches the work shown at a stand. The prompts on screen name every key. The tools cannot be started while you are working on a build, and a build at that site is hidden while a tool is open.

Exports and events

The script offers no exports. Other resources do not need to call anything, the wagon arrives in the stable table directly.

FAQ

The prompt at the NPC does not show. You need one of the jobs in job for that shop. The job is read when you pick your character and again whenever VORP changes it. The prompt also hides while the menu is open, on horseback or in a wagon.

"The building site is taken." Every site holds one build. Either add another site to sites or wait until the running build is finished or given up. A build nobody has worked on for staleDays can be cleared by anyone with the job, hold Del at the wagon.

Where does the money go? The fee is taken from the player's cash when the wagon is ordered and is not paid to anyone. Materials come out of the workshop storage first, then out of the bag. Nothing is refunded when a build is given up. If the order itself fails, for example because the database does not answer, fee and materials go back, and if the bag is full by then the materials land in the workshop storage.

The wagon is finished but not in the stable. The last task tried to insert the row and failed. The player gets "The wagon could not be taken to the stable", the last task is reopened, the console prints the reason in red and the webhook gets an error embed. Check Config.StableSystem and whether that table exists, then do the last task again.

"It doesn't go that fast, once more." The server timed the task. With the defaults a task must take at least 5 + 6 + 5 - 1 = 15 seconds and at most 5 + 60 + 5 + 20 = 90 seconds. A player who got that message was faster than the minigame allows, usually a desync. Raise slack if players with bad connections see "That took too long" instead.

Can several players build the same wagon? Yes. Anyone with the shop job can take any open spot. Within a step every spot can be worked by a different player at the same time, the server marks a spot as busy while someone is on it. The next step opens once every spot of the current one is done.

What happens on a server restart? Running builds are stored in hobbs_wagoncraft_builds and come back with the same progress. A task that was in progress during the restart is simply open again. If the database is down on start, the script retries every 30 seconds and refuses orders until it has loaded.

I removed a wagon from the config but a site is still blocked. A build row for a wagon that is no longer in Config.Wagons cannot be loaded. The console names it on start. Either put the wagon back or delete that row from hobbs_wagoncraft_builds.

The preview wagon clips into a wall or the camera is inside something. Move wagonSpawn to an open spot. The camera probes for a clear angle but it can only use what is free around the wagon.

The sawhorses or stands are off for a new wagon. Turn on Config.Debug, run /wagoncraft_measure <model> and /wagoncraft_stands <model>, press Enter in each tool and paste the printed lines into Config.Build.models.

How heavy is it? Nothing is spawned until a player is within siteRange (80 m) of a site with a build on it. Near a site the client checks every half second, otherwise every two seconds. Ground markers are only drawn while an open spot is near. The server validates every task but keeps no timers beyond the running task windows.

Changelog

1.0.1

First release.