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¶
- Unpack the resource into
resourcesand keep the folder namehobbs_crafting. - Run
sql/install.sql. It creates three tables,hobbs_crafting_xp(XP per character and category),hobbs_crafting_discoveries(recipes a character has found) andhobbs_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. - Add every item your recipes use to the
itemstable ofvorp_inventory, ingredients, results and returned items alike. The shipped recipes are examples that use items likemeat,salt,iron_oreorbeer, replace them with your own. - Put the item images into
vorp_inventory/html/img/items. The menu reads them from there, seeConfig.InventoryImgPathif your inventory keeps them somewhere else. - Create the VORP jobs you want to use. The shipped config expects a job called
blacksmithfor the anvil. -
Add it to
server.cfgafter its dependencies: -
Start the server and open a station. If nothing happens at the prompt, set
Config.devmode = truefor 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.
Menu colours¶
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¶
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
stationslist, 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
itemstable 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.
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.
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.
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:
A radial menu or a prop interaction can open a station from the client:
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.