Developer

6 min readUpdated 2 weeks ago

Overview

Other resources work with crafting through server exports, a hook system any resource can attach to, and handler files in open/, which stay unencrypted.

Server Exports

Progression

  • getPlayerLevel(src): crafting level of the player, from ml_skills when external skills are on
  • getPlayerCategoryLevel(src, categoryId): level in one recipe category with the internal progression. With ml_skills it returns the global level
  • addXp(src, amount): gives crafting XP. With ml_skills the XP goes to Config.ExternalSkills.Mapping.Category, so set it or the XP is skipped

Blueprints

  • getPlayerBlueprints(src): map of blueprint ID to { type = 'permanent' } or { type = 'item' }
  • givePlayerBlueprint(src, blueprintId): unlocks a blueprint for good. unlockPermanentBlueprint does the same
  • removePlayerBlueprint(src, blueprintId): removes a permanent unlock

Benches

  • setCoordAsBench(coord, benchTypeId, routingBucket?): creates a bench at a position, with coord.w as heading, saved in the database. Returns { success, id, remove }, and remove() deletes it

To turn every prop of a model into a bench, use Config.ModelBenches instead.

Data and Queue

  • getCategories(): all recipe categories
  • getRecipe(recipeId): one recipe with ingredients, results and blueprints
  • createRecipe(data): creates a recipe from code
  • getPlayerQueue(src): queued crafts of the player
  • getQueuedCrafts(benchLocationId): queue of one bench

Hooks

Hooks let another resource check, change or watch crafting from its own code:

your_script/server.lua
1local hookId = exports.ml_crafting:registerHook('crafting:preCraft', {
2    priority = 10,
3    fn = function(ctx)
4        if ctx.quantity > 5 then
5            return { success = false, message = 'Max 5 at a time' }
6        end
7        return true
8    end
9})
10
11exports.ml_crafting:unregisterHook('crafting:preCraft', hookId)

Hooks run from the lowest priority up. Return false, or { success = false, message = '...' }, to block. A table with no success field is merged into ctx. Hooks of a resource are removed when it stops. runHook, transformHook and emitHook run a hook point from your own code.

Hook Points

  • crafting:canInteract: a player opens a bench. ctx: src, benchType, benchLocation
  • crafting:enter: access was granted, before the menu data is built. Same ctx
  • crafting:getCategories: filter or add categories. ctx adds categories
  • crafting:getRecipes: filter or add recipes of a category. ctx adds categoryId and recipes
  • crafting:getRecipe: change one recipe before it shows. ctx adds recipe
  • crafting:preCraft: before the ingredients are taken. ctx: src, recipe, quantity, benchType, benchLocation. Can change quantity or block
  • crafting:postCraft: after the results are given. ctx adds results, queueId, ownerIdentifier and recipientIdentifier. Watch only
  • crafting:exit: the player leaves the bench. Watch only
  • crafting:projectContribution: a player donates to a community project. ctx: src, project, itemName, amount. Watch only
  • crafting:projectCompleted: a project reaches 100%. ctx: project, contributors. Watch only

Server Handlers

Located in open/server.lua. Each handler is registered as a hook at priority 50.

open/server.lua
1Handlers.CanPlayerUseBench = function(src, benchType, benchLocation)
2    return true
3end
4
5Handlers.OnBeforeCraft = function(src, recipe, quantity)
6    return true
7end
8
9Handlers.OnAfterCraft = function(src, recipe, quantity, results)
10end
11
12Handlers.OnBlueprintUnlocked = function(src, blueprintId)
13end
14
15Handlers.OnLevelUp = function(src, category, newLevel)
16end
17
18Handlers.ModifyCraftResults = function(src, recipe, results)
19    return nil
20end
21
22Handlers.VerifyWorldBench = function(src, model, coords, benchType)
23    return nil
24end
25
26Handlers.OnProjectContribution = function(src, project, itemName, amount)
27end
28
29Handlers.OnProjectCompleted = function(project, contributors)
30end
  • CanPlayerUseBench: return false, or false and a message, to refuse a bench
  • OnBeforeCraft: return false to block a craft
  • OnAfterCraft: runs after the results are given
  • OnBlueprintUnlocked: a player unlocks a blueprint
  • OnLevelUp: a global level up of the internal progression. category is 'global'
  • ModifyCraftResults: runs just before the items are given. Return a new results table to change them
  • VerifyWorldBench: return false to refuse a world prop as a model bench. With Config.ModelBenches.RequireVerification = true, return true to accept it
  • OnProjectContribution, OnProjectCompleted: community project events

The same file holds the base check used by portable stations when Outpost runs. Leave it as it is unless you change the base rules.

Client Handlers

Located in open/client.lua.

open/client.lua
1Handlers.CanInteractWithBench = function(benchType, entity)
2    return true
3end
4
5Handlers.OnCraftStarted = function(recipe)
6end
7
8Handlers.OnCraftCompleted = function(recipe, results)
9end
10
11Handlers.GetCustomAnimation = function(recipe)
12    return nil
13end
14
15Handlers.OnBenchEnter = function(benchType, entity)
16end
17
18Handlers.OnBenchExit = function(benchType, entity)
19end
  • CanInteractWithBench: return false to hide the bench option
  • OnCraftStarted, OnCraftCompleted: a craft starts or ends
  • GetCustomAnimation: return { dict = '...', clip = '...', flag = 49 } to replace the craft animation
  • OnBenchEnter, OnBenchExit: the player starts or stops using a bench

The shipped file hides the HUD while the player uses a bench. Set HideHudAtBench = false at the top to keep it, or add your HUD to the HudResources list. OnCraftCompleted also holds a sample stress call, safe to remove.

Community Project Event

A server event fires when a project reaches 100%, for rewards of your own such as opening a door.

server.lua
1AddEventHandler('ml_crafting:projectCompleted', function(data)
2    if data.id ~= 'the-project-id' then return end
3    for _, c in ipairs(data.contributors) do
4        print(c.name, c.score, c.share)
5    end
6end)
  • data.id, data.title: the project. The editor shows this snippet with the real ID filled in
  • data.contributors: list of { playerId, name, score, share }, largest first. share goes from 0 to 1, so 0.42 is 42% of the total

The event always carries the contributors, also for projects that hide them on the board.

Skill Bonuses

Set per recipe in the editor. The bonus applies while the player has the bound ml_skills skill. Values are percentages, and bonuses of the same type add up.

  • time_reduction: shorter craft time, up to 90%
  • extra_xp: more XP
  • extra_quantity: chance of extra output
  • extra_drop_chance: better odds on byproducts
  • ingredient_reduction: chance to use fewer ingredients

Examples

your_script/server.lua
1local bench = exports.ml_crafting:setCoordAsBench(
2    vector4(-261.95, -956.42, 31.22, 90.0),
3    'weapons_bench'
4)
5
6if bench.success then
7    print('Bench created with id ' .. bench.id)
8end
open/server.lua
1Handlers.CanPlayerUseBench = function(src, benchType, benchLocation)
2    local job = Bridge.GetJob(src)
3    if benchType.id == 'medic_bench' and (not job or job.name ~= 'ambulance') then
4        return false, 'error_not_authorized'
5    end
6    return true
7end
your_script/server.lua
1exports.ml_crafting:registerHook('crafting:postCraft', {
2    priority = 10,
3    fn = function(ctx)
4        TriggerEvent('myeconomy:craftTax', ctx.src, ctx.recipe.id, ctx.quantity)
5    end
6})
open/client.lua
1Handlers.GetCustomAnimation = function(recipe)
2    if recipe.category_id == 'cooking' then
3        return { dict = 'amb@prop_human_bbq@male@base', clip = 'base', flag = 49 }
4    end
5    return nil
6end