Skip to content

Crafting

hobbs_crafting, current version 1.0.1

Overview

One crafting system for the whole server. You define stations, categories and recipes in config.lua, the script does the rest: a workbench menu with search and favourites, a queue that is saved per station, crafts that keep running on the server after the menu is closed, XP and levels per category, recipes that are locked to a job or a rank, hidden recipes that players work out at an experiment bench, and a fish processing recipe that turns a whole catch into fillets in one go.

A station can sit at fixed coordinates, on every prop of a given model in the world (every campfire, for example), or inside an item the player uses. Finished goods wait at the station until the player picks them up, and they are picked up by themselves while the player stands there.

How it plays

You walk up to a station, say the anvil in Valentine, and press E. The menu opens and your character scribbles in a notebook while it is open. The Recipes tab lists everything this station makes, favourites first, then what you can craft right now, then the rest. A locked recipe tells you why: the level you still need, "No access" when it belongs to another job, or "Unknown recipe" when you have not found it yet.

You open a recipe, set the amount, and either craft it right away or put it into the queue. Ingredients leave your inventory the moment a craft starts. The Workbench tab shows what is running, what is queued and what is ready. You can close the menu and ride off, the craft keeps running on the server. Back at the station, finished goods move into your satchel on their own, and the XP for them is credited at that moment. If your satchel is full, the rest stays on the bench until you make room.

Stations with secret recipes get an Experiment tab. You lay out the ingredients you think belong together and test the formula. One of each ingredient is used up whether it works or not. Hit the right set and the recipe is yours for good. Get it partly right and the bench tells you which ingredients fit and which do not.

Requirements

Resource Note
vorp_core Characters, jobs, job ranks and the admin group
vorp_inventory Takes ingredients, gives results, weapons and returned items, registers the usable items that open item stations
oxmysql XP, discovered recipes, running crafts and queues
nss_libs The prompt at coordinate and prop stations

Notifications use the VORP tip at the bottom of the screen, so nothing else is needed for those. Jobs named in jobs fields are VORP job names, create them before you lock a station or recipe to them.

Installation

  1. Unpack the resource into resources and keep the folder name hobbs_crafting.
  2. Run sql/install.sql. It creates three tables, hobbs_crafting_xp (XP per character and category), hobbs_crafting_discoveries (recipes a character has found) and hobbs_crafting_pending (running, finished and queued crafts per character and station), and inserts the one item the shipped config needs, fish_fillet. The tables are also created on first start if you skip this step, the item is not.
  3. Add every item your recipes use to the items table of vorp_inventory, ingredients, results and returned items alike. The shipped recipes are examples that use items like meat, salt, iron_ore or beer, replace them with your own.
  4. Put the item images into vorp_inventory/html/img/items. The menu reads them from there, see Config.InventoryImgPath if your inventory keeps them somewhere else.
  5. Create the VORP jobs you want to use. The shipped config expects a job called blacksmith for the anvil.
  6. Add it to server.cfg after its dependencies:

    ensure oxmysql
    ensure vorp_core
    ensure vorp_inventory
    ensure nss_libs
    ensure hobbs_crafting
    
  7. Start the server and open a station. If nothing happens at the prompt, set Config.devmode = true for a moment, the console then tells you why access was denied (distance, item or job).

Configuration

All settings live in config.lua. Translations live in locales.lua. There is no server_config.lua and no webhook.

General

Config.Locale = 'en'       -- "en" or "de"
Config.devmode = false     -- console output for debugging
Config.InventoryImgPath = "vorp_inventory/html/img/items"
Config.OpenKey = 0xCEFD9220 -- E

Config.Locale picks the block in locales.lua. Every text the player sees is in there, including the guide that opens from the button next to the X, so you can reword all of it. Add a language by copying a block and setting Config.Locale to its key, a key that is missing in your block falls back to English.

Config.OpenKey is the key of the prompt at coordinate and prop stations. The value is a RedM control hash.

Config.UI = {
    AccentColor    = "#c41e1e",
    BackgroundMode = "dark",      -- "dark" or "light"
}

The accent colour is used for highlights and the XP bar. Switch to "light" if your other menus are light and the dark panel stands out too much.

Background crafting

Config.BackgroundCrafting = {
    Enabled      = true,
    AllowUIClose = true,
    Persistence  = true,
    TickSeconds  = 5,
}

Every craft gets a finish time on the server when it starts, so it keeps running while the menu is closed and still finishes after a restart. AllowUIClose lets the player close the menu while something is running. The running crafts are also shown as timers on the left edge of the screen while the menu is closed.

Picking up at the station

Config.AutoClaimAtStation = {
    Enabled     = true,
    TickSeconds = 5,
    MaxPerTick  = 3,     -- crafts per player and tick
}

Every TickSeconds the server looks at every player who has a station open or opened one recently and is still standing there, and moves up to MaxPerTick finished crafts into their inventory, one notification per craft. Set Enabled = false and players pick everything up by hand with "Pick up" or "Claim all". TickSeconds never goes below 2.

I keep this on because nobody likes clicking "Pick up" fifty times after a long batch. If you run many players and want to spread the inventory work, raise TickSeconds instead of turning it off.

XP and levels

Config.EnableXP         = true
Config.XPLevelThreshold = 100

XP is counted per category, not per station. Level is xp / XPLevelThreshold + 1, so with 100 XP per level a player with 250 XP in food is level 3 in food. Two stations that share a category share its XP. The XP of a craft is xpGain times the amount times the recipe's result amount, credited when the goods are picked up, and only for the part that fit into the inventory.

With EnableXP = false everyone is level 1 everywhere, the level bar is hidden and requiredLevel above 1 locks a recipe for good, so set those to 1 as well.

Fish processing

Config.FishProcessing = {
    filletItem  = "fish_fillet",
    filletLabel = "Fish fillet",
    saltItem    = "salt",
    saltPerFish = 1,
    fish = {
        "a_c_fishbluegil_01_sm", "a_c_fishperch_01_sm", "a_c_fishrockbass_01_sm",
        -- ...
        "a_c_fishchannelcatfish_01_xl",
    },
}

A recipe with fishProcessor = true does not use its ingredient list as a normal recipe would. It walks through the fish list, takes every fish the player carries and one saltItem per fish, and gives filletItem back, one per fish and two for a fish whose item name ends in _lg or _xl. Salt limits the batch: with 3 salt and 10 fish, 3 fish are processed. The craft takes one second per fish, at least 2 and at most 60. The ingredient list of the recipe is only shown in the menu, so list the salt there so players know what they need.

The fish names are the item names of your fishing script. Replace them with yours.

Stations

Config.CraftingStations = {
    {
        id = "campfire",
        name = "Campfire",
        triggerType = "prop",
        propModel = "p_campfirecombined01x",
        propDistance = 2.0,
        categories = { "food", "drinks", "materials", "fish" },
        jobs = {},
        showXP = true,
        illegal = false,
        blocked = false,
    },
    {
        id = "blacksmith",
        name = "Blacksmith",
        prompt = "Use the anvil",
        triggerType = "coords",
        coordDistance = 1.5,
        coords = {
            vector3(-285.36, 788.80, 118.40),   -- Valentine
            vector3(1225.70, -1296.55, 76.90),  -- Rhodes
        },
        blip = { enabled = true, sprite = 'blip_shop_blacksmith', scale = 0.2 },
        categories = { "materials", "tools" },
        jobs = { "blacksmith" },
        showXP = true,
        illegal = false,
        blocked = false,
    },
    {
        id = "dark_book",
        name = "Dark book",
        triggerType = "item",
        itemName = "dark_book",
        categories = { "drugs" },
        jobs = {},
        showXP = false,
        illegal = true,
        blocked = false,
    },
}
Key Meaning
id Unique name of the station. Recipes refer to it in stations, the queue and finished crafts are stored under it, so do not rename a station once players use it
name Title of the prompt group and of the menu
prompt Text on the prompt. Without it the open_prompt text from locales.lua is used ("Open")
triggerType "coords", "prop" or "item", see below
coords Only for "coords". One vector3 or a list, every entry is a place where the station opens
coordDistance Only for "coords". How close the player has to stand to see the prompt
blip Only for "coords". enabled, the sprite name and the scale of a map blip at every entry in coords
propModel Only for "prop". A prop model name, or a list of names, the station opens at every such prop in the world
propDistance Only for "prop". How close the player has to stand to the prop
itemName Only for "item". The usable item that opens the station
categories Category ids from Config.Categories. They become the tabs of the menu and decide which recipes a prop or item station shows
jobs {} lets everyone in, { "blacksmith" } only players with one of these VORP jobs. Others see "You don't have the required job!"
illegal true shows only recipes with CategoryIllegal = true, false shows only the others
showXP Shows the level bar in the menu header
blocked true switches the station off, the prompt disappears

The three kinds of station:

  • coords: the station sits at the listed places. A coordinate station only shows recipes that name it in their stations list, the categories alone are not enough here. That is on purpose, the anvil in Valentine should make exactly what you put on it.
  • prop: the station opens at every prop of that model in the world. The campfire example means every campfire, including the ones players place. A prop station shows every recipe of its categories plus every recipe that names it in stations.
  • item: the station opens when the player uses the item from the inventory. The item has to exist in the items table and be usable. The menu only works while the item is in the inventory, which makes it easy to hand out a "book of recipes" and take it away again. Item stations work like prop stations when it comes to which recipes they show.

Once the menu is open, the player has to stay near the station. For a coordinate station that is coordDistance plus one metre around one of its coordinates, for a prop station propDistance plus two metres around the spot where the menu was opened. Walk further away and crafting, picking up and experiments are refused until the menu is opened again.

The coordinates in the shipped config are examples, replace them with your own.

Categories

Config.Categories = {
    { id = "food",      label = "Food",      blocked = false },
    { id = "drinks",    label = "Drinks",    blocked = false },
    { id = "materials", label = "Materials", blocked = false },
    { id = "tools",     label = "Tools",     blocked = false },
    { id = "fish",      label = "Fish",      blocked = false },
    { id = "drugs",     label = "Drugs",     blocked = false },
}
Key Meaning
id Used in categories of a station and category of a recipe. XP is stored under this id, so do not rename a category once players have XP in it
label Name of the tab in the menu
blocked true hides every recipe of the category at every station

Add as many as you like. A category only shows up in a menu when the station lists it.

Recipes

Every recipe has the same fields. The shipped list has one example for every option, here is the plain one and the special ones.

Config.Recipes = {
    {
        id = "steak",
        label = "Steak",
        category = "food",
        craftTime = 5,
        requiredLevel = 1,
        xpGain = 1,
        jobs = nil, grade = nil,
        blocked = false,
        hidden = false,
        CategoryIllegal = false,
        stations = nil,
        ingredients = {
            { item = "meat", label = "Meat", amount = 2 },
            { item = "salt", label = "Salt", amount = 1 },
        },
        result = { item = "steak", amount = 1, Itsweapon = false },
        giveBack = {},
    },
}
Key Meaning
id Unique name. Found recipes and the queue are stored under it, do not rename a hidden recipe once someone has found it
label Name in the menu
category A category id. Decides the tab, which stations show it and where the XP goes
craftTime Seconds per craft. Five crafts of a 5 second recipe take 25 seconds
requiredLevel Level in this category the player needs. Shown under the name as "From level 2" until reached
xpGain XP per produced piece, credited on pick up
jobs nil for everyone, { "blacksmith" } only for these VORP jobs
grade Minimum job rank, only checked when jobs is set. nil means any rank
lockHint Text shown under a job locked recipe instead of "No access", for example "Blacksmiths only"
hidden true lists the recipe as "???" with "Unknown recipe" until the player has found it
discoverMethod "craft" lets the player find a hidden recipe at the experiment bench
CategoryIllegal true shows the recipe only at stations with illegal = true
stations nil means every prop or item station that has the category. A list of station ids limits it to those, and a coordinate station needs to be named here
ingredients List of item, label and amount. All of them are taken when the craft starts
result item and amount per craft. Itsweapon = true gives a weapon with that name instead of an item
giveBack Items returned per craft, for example the empty bottle
fishProcessor true turns the recipe into the fish processing recipe, see above
blocked true switches the recipe off everywhere

A few of the shipped examples, with what they show:

Job and rank: only blacksmiths of rank 2 and up, with a hint for everyone else.

jobs = { "blacksmith" }, grade = 2,
lockHint = "Blacksmiths only",
stations = { "blacksmith" },

Returned items: two beers per craft, the bottle comes back.

ingredients = {
    { item = "water", label = "Water", amount = 2 },
    { item = "wheat", label = "Wheat", amount = 2 },
    { item = "bottle", label = "Empty bottle", amount = 1 },
},
result = { item = "beer", amount = 2, Itsweapon = false },
giveBack = {
    { item = "bottle", amount = 1 },
},

Weapon: the result is created as a weapon, so use the weapon name.

result = { item = "WEAPON_MELEE_KNIFE", amount = 1, Itsweapon = true },

Hidden: the omelette is not listed until the player lays out exactly egg, salt and onion at the experiment bench. Amounts do not matter for the discovery, only which items.

hidden = true, discoverMethod = "craft",
ingredients = {
    { item = "egg", label = "Egg", amount = 3 },
    { item = "salt", label = "Salt", amount = 1 },
    { item = "onion", label = "Onion", amount = 1 },
},

Illegal: moonshine only shows at the dark book, because that station has illegal = true.

CategoryIllegal = true,
stations = nil,

Note

The Experiment tab only appears at a station that has at least one hidden recipe with discoverMethod = "craft". A hidden recipe that is locked to another job stays invisible for everyone else, even in the experiment, laying out its ingredients then consumes them and reports "You don't have the required job!".

Limits built into the script

A few numbers are fixed in the code and not in the config, so you know what players run into:

Limit Value
Amount per craft 50
Entries in the queue 20
Crafts started with "Start all" at once 20, together at most 200 pieces
Unclaimed crafts per player and station 200, then "Too many pending crafts. Please claim your items first."
Items on the experiment bench 16
Time between two craft starts, "Claim all" presses or experiments 1 second

Items

The script ships one item in sql/install.sql:

Item Use
fish_fillet Result of the fish processing example

Every other item name in config.lua belongs to the example recipes and must exist in your items table before the recipe works:

Item Where it appears
meat, salt, steak Steak
egg, onion, omelette Hidden omelette
water, wheat, bottle, beer Beer with returned bottle
iron_ore, coal, iron_ingot Iron ingot, blacksmith only
wood Hunting knife, together with iron_ingot
corn, sugar, moonshine Moonshine at the dark book
dark_book Usable item that opens the illegal station
a_c_fish* Fish items of the processing recipe

Weapons given by Itsweapon = true are not items, they go through the weapon system of vorp_inventory.

Commands and keys

Command Who What it does
/craftblock recipe <id> true Admin group or console Switches a recipe off, false switches it on again
/craftblock category <id> true Admin group or console Switches a category off everywhere
/craftblock station <id> true Admin group or console Switches a station off

Admin means the VORP character group admin. The change lasts until the next restart, edit blocked in config.lua for good. A station switched off this way keeps its prompt until the resource restarts, the menu then refuses to open with "This station is currently blocked."

Key Where Action
E At a station Open the menu (Config.OpenKey)
Backspace Menu open Close the menu

Everything else is done with the mouse in the menu. The pause menu is disabled while the menu is open.

Exports and events

Client:

Export What it does
OpenCraftStation(stationId) Opens a station as if the player pressed the prompt. The server still checks distance, item and job
CloseCraftMenu() Closes the menu
IsCraftUIOpen() true while the menu is open
IsCrafting() Same as IsCraftUIOpen()
GetCurrentStation() Id of the open station, or nil

Server:

Export What it does
OpenCraftStationServer(source, stationId) Asks the player's client to open the station
AddCraftXP(source, stationId, amount) Adds XP to the first category of that station
SetCraftXP(source, stationId, amount) Sets the XP of every category of that station to that value
GetPlayerCraftData(source, stationId, cb) Returns { xp, level, stationId }, xp is the highest category XP of the player. Also handed to cb if given

An example from another resource, a mission reward of 50 XP at the anvil:

exports.hobbs_crafting:AddCraftXP(source, "blacksmith", 50)

A radial menu or a prop interaction can open a station from the client:

exports.hobbs_crafting:OpenCraftStation("campfire")

FAQ

The prompt does not show at my coordinate station. The player has to be within coordDistance of one of the coords, and blocked has to be false. Also check that nss_libs starts before hobbs_crafting.

The menu opens but the list is empty. At a coordinate station every recipe needs the station id in its stations list. At a prop or item station the recipe's category has to be in the station's categories. An illegal station only shows CategoryIllegal recipes and the other way round, and a blocked category hides everything in it.

The prompt shows at the campfire but the menu says "You are not at the station". The server measures the distance to the prop itself with propDistance plus two metres. Raise propDistance a little if the prop is large.

How do I add a station? Copy one of the three examples, give it a new id, pick the trigger type, list its categories and jobs. For a coordinate station, also add its id to the stations list of every recipe it should make. That is all, no restart of other resources is needed.

A player changed job but the menu still shows the old recipes. A job or rank change closes the station session on the server. Anything the player tries from the old menu is refused, and the next time the menu is opened it shows the recipes for the new job. Job names are compared without regard to case.

What happens on a server restart? Running and finished crafts, the queue, XP and found recipes are in the database. A craft that was running finishes at its original time. The queue is checked against the inventory when the menu is opened again, jobs the player can no longer afford are dropped with a note.

Where do finished goods go when the satchel is full? They stay on the bench. The script gives what fits, keeps the rest as a smaller pending craft and credits the XP for the part that was handed out. Returned items like the bottle are split the same way.

Does the script cost or give money? No. Only items change hands, there is no price and no payout anywhere.

Why does the experiment use up my ingredients when it fails? That is the point of it. One of each laid out item is consumed on every attempt, success or not, so guessing has a price. A wrong attempt with some correct items shows which ones were right, so players get closer each time.

Is the resource heavy? The client only scans for props when the player has moved more than about half a metre or three seconds have passed, and sleeps otherwise. The server keeps player data in a cache and writes it to the database in intervals, on disconnect and on resource stop.

Changelog

1.0.1

First release.