legacy-crafting
Integration
Exports and events for opening the crafting/admin UI and syncing benches, with working examples.
Integration
All server-side functionality is exposed as lib.callback-backed exports (call with exports['legacy-crafting']:name(...) from server scripts, or lib.callback.await('legacy-crafting:name', false, ...) from client scripts). Two net events drive client-side UI/state.
Opening the crafting UI for a player
Client-side, call the global function (in client/client.lua), which internally awaits the server export:
-- put this in any client script in this resource, or trigger it from your own client script
OpenCrafting(benchId)
-- internally does:
-- lib.callback.await('legacy-crafting:openBench', false, benchId)
Server export used under the hood:
-- server/server.lua:333
local result = exports['legacy-crafting']:openBench(source, benchId)
-- returns bench data + recipes + XP/tier + cooldowns + ingredient availability,
-- or { error = true, message = ... } if the player fails the required_jobs check
Opening the admin panel
Fire the net event at a player who should see the admin UI (put this in a server script, e.g. your own admin menu or command):
-- server-side
TriggerClientEvent('legacy-crafting:openAdmin', src)
Client-side (client/client.lua:253) this triggers OpenAdmin(), which awaits:
lib.callback.await('legacy-crafting:getInitData', false)
-- and only opens the UI if initData.isAdmin == true
isAdmin is computed server-side via the same admin check used everywhere else (framework admin OR ACE membership in Config.AdminGroups).
Crafting an item programmatically
-- server-side
local result = exports['legacy-crafting']:craft(source, { recipeId = 12, benchId = 3 })
-- result = { success = true, message = ..., newXP = ..., newTier = ..., tiers = ..., newCooldowns = ... }
-- or { success = false, message = '...' } on failure (level, job, cooldown, ingredients, inventory space)
Refreshing benches on all clients after admin edits
After any bench create/update/delete outside the normal admin flow, broadcast a resync (put this in a server script):
-- server-side, after inserting/updating/deleting legacy_crafting_benches rows directly
TriggerClientEvent('legacy-crafting:syncBenches', -1)
Client-side (client/benches.lua:193) this re-fetches legacy-crafting:getBenchList and respawns all bench objects/zones/blips via fetchAndSpawn()/spawnBenches().
Note:
fetchAndSpawn()also runs automatically 2000ms after resource start, with no retry/backoff. Ifoxmysqlisn't ready yet at that point, benches simply won't appear until the nextsyncBenchesevent.
Reading bench data / spawned entities client-side
-- client-side, from any script in this resource
local bench = GetBenchData(benchId) -- cached table from legacy-crafting:getBenchList
local entity = GetBenchObject(benchId) -- spawned prop entity handle, or nil if not spawned
Admin CRUD exports (server, admin-gated)
All of the following require the caller to pass IsAdmin() server-side (framework admin or Config.AdminGroups ACE membership); non-admins get an empty table or { success = false }:
exports['legacy-crafting']:getAllRecipes(source)
exports['legacy-crafting']:getBenchRecipes(source, benchId)
exports['legacy-crafting']:saveBenchRecipes(source, { benchId = 3, recipeIds = {1,2,3} })
exports['legacy-crafting']:getCategories(source)
exports['legacy-crafting']:saveCategory(source, { id = nil, name = 'Tools', ... })
exports['legacy-crafting']:deleteCategory(source, id)
exports['legacy-crafting']:getRecipes(source)
exports['legacy-crafting']:saveRecipe(source, { id = nil, output_item = 'lockpick', ingredients = { ... } })
exports['legacy-crafting']:deleteRecipe(source, id)
exports['legacy-crafting']:getBenches(source)
exports['legacy-crafting']:saveBench(source, { id = nil, prop_model = 'prop_tool_bench02', ... })
exports['legacy-crafting']:deleteBench(source, id)
exports['legacy-crafting']:getAllItems(source)
exports['legacy-crafting']:getTiers(source)
exports['legacy-crafting']:saveTier(source, { id = nil, label = 'Novice', xp = 0, color = '#6B7280' })
exports['legacy-crafting']:deleteTier(source, id)
exports['legacy-crafting']:canOpenStorage(source, benchId) -- returns boolean
exports['legacy-crafting']:getInitData(source)
exports['legacy-crafting']:getBenchList(source)
saveBench and deleteBench both automatically broadcast legacy-crafting:syncBenches to all clients — you don't need to fire it yourself when using these exports.
Admin bench placement flow (client)
From client/placement.lua, used by the admin UI to let staff position a bench in-world:
-- new prop placement
StartBenchPlacement('prop_tool_bench02', function(result)
if result then
-- result = { x,y,z,heading, playerX,playerY,playerZ,playerHeading,
-- cameraAngle, cameraHeight, cameraDistance,
-- propOffsetX, propOffsetY, propOffsetZ }
-- send this to legacy-crafting:saveBench
end
end)
-- selecting an existing world prop instead of spawning a new one
StartExistingPropSelection(function(result)
if result then
-- result additionally has: useExistingProp = true, propModelHash
end
end)
IsPlacementActive()inclient/placement.luaalways returnsfalse— it is not wired up, so don't rely on it to detect an in-progress placement.