Developer
Overview
Three integration layers: server exports for progression, blueprints and bench registration; a named hook system any resource can attach to; and editable handler stubs in the open/ folder.
Server Exports
Progression
getPlayerLevel(src): Player's crafting level (from ml_skills when external skills are enabled)getPlayerCategoryLevel(src, categoryId): Level for one recipe categoryaddXp(src, amount): Award crafting XP
Blueprints
getPlayerBlueprints(src): Map of blueprint id to{ type = 'permanent' | 'item' }givePlayerBlueprint(src, blueprintId): Permanently unlock a blueprint (alias:unlockPermanentBlueprint)removePlayerBlueprint(src, blueprintId): Remove a permanent unlock
Benches
setEntityAsBench(entity, benchTypeId): Register an existing entity as a usable bench. Automatically cleared when the registering resource stopsremoveEntityAsBench(entity): Unregister itsetCoordAsBench(coord, benchTypeId, routingBucket?): Create a bench at coordinates (coord.w= heading). Returns{ success, id, remove }whereremove()deletes it again
Data & Queue
getCategories(): All recipe categoriesgetRecipe(recipeId): One recipe with ingredients, results and blueprintscreateRecipe(data): Create a recipe programmaticallygetPlayerQueue(src): The player's queued craftsgetQueuedCrafts(benchLocationId): Queue of one bench
Hook System
Hooks let another resource validate, modify or observe crafting without touching ml_crafting. Register from any server script:
1local hookId = exports.ml_crafting:registerHook('crafting:preCraft', {
2 priority = 10,
3 fn = function(ctx)
4 -- return false to block
5 -- return { success = false, message = 'msg' } to block with a reason
6 -- return { quantity = 1 } to modify the context
7 return true
8 end
9})
10
11exports.ml_crafting:unregisterHook('crafting:preCraft', hookId)Hooks run in ascending priority. A table return without success is merged into the context. Hooks registered by a resource are removed automatically when that resource stops.
Hook points
crafting:canInteract: Player opens a bench. ctx:src, benchType, benchLocationcrafting:enter: After access is granted, before the menu data is built. Same ctxcrafting:getCategories: Filter or extend the category list. ctx addscategoriescrafting:getRecipes: Filter or extend a category's recipe list. ctx addscategoryId, recipescrafting:getRecipe: Modify a single recipe before display. ctx addsrecipecrafting:preCraft: Before ingredients are taken. ctx:src, recipe, quantity, benchType, benchLocation. Can changequantityor block the craftcrafting:postCraft: After results are delivered. ctx addsresults. Observe-onlycrafting:exit: Player leaves the bench. ctx:src, benchType, benchLocation. Observe-only
Open Handlers
The open/ folder is never escrowed. The shipped stubs are pre-registered into the hook system at priority 50.
Server
1Handlers.CanPlayerUseBench = function(src, benchType, benchLocation)
2 return true -- return false (optionally with a message) to block
3end
4
5Handlers.OnBeforeCraft = function(src, recipe, quantity)
6 return true -- return false to block the craft
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 -- return a modified results table to replace the output
20endOnLevelUpfires only with the internal progression (Config.ExternalSkills.Enabled = false)ModifyCraftResultsruns right before items are handed out, last chance to add, remove or retag results
Client
1Handlers.CanInteractWithBench = function(benchType, entity)
2 return true -- return false to hide the interaction
3end
4
5Handlers.OnCraftStarted = function(recipe)
6end
7
8Handlers.GetCustomAnimation = function(recipe)
9 return nil -- or { dict = '...', clip = '...', flag = 49 }
10end
11
12Handlers.OnBenchEnter = function(benchType, entity)
13end
14
15Handlers.OnBenchExit = function(benchType, entity)
16end
17
18Handlers.OnCraftCompleted = function(recipe, results)
19endSkill Bonuses
Recipes can carry skill bonuses, set in the recipe editor. When the player has the bound ml_skills skill unlocked, the bonus applies automatically. Four bonus types are supported; values are percentages, and multiple bonuses of the same type stack additively:
time_reduction: Shorter craft time, capped at 90%extra_xp: More XP from the craftextra_quantity: Chance-scaled extra output quantityingredient_reduction: Chance to consume fewer ingredients
Examples
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)
8 -- later: bench.remove()
9end1Handlers.CanPlayerUseBench = function(src, benchType, benchLocation)
2 if benchType.id == 'medic_bench' and not Bridge.HasJob(src, 'ambulance') then
3 return false, 'error_not_authorized'
4 end
5 return true
6end1exports.ml_crafting:registerHook('crafting:postCraft', {
2 priority = 10,
3 fn = function(ctx)
4 -- ctx.src, ctx.recipe, ctx.quantity, ctx.results
5 TriggerEvent('myeconomy:craftTax', ctx.src, ctx.recipe.id, ctx.quantity)
6 end
7})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
7
8Handlers.OnCraftCompleted = function(recipe, results)
9end1Handlers.OnBenchEnter = function(benchType, entity)
2 local coords = GetEntityCoords(cache.ped)
3 print(('Entered bench at %.1f %.1f %.1f'):format(coords.x, coords.y, coords.z))
4end
5
6Handlers.OnCraftCompleted = function(recipe, results)
7end