Developers
The lation weed server and client exports, the events it fires and its server and client hooks, with signatures, return shapes and Lua examples.
Exports, events and hooks for connecting lation weed to other resources. Every export is called on exports.lation_weed. A player is their server id (source), and a lab is its id: the number purchaseLab returns, which is also the routing bucket of the lab and the bucketId of its plants.
Server exports
| Export | Returns |
|---|---|
GetRuntimeConfig(section?) | The live configuration |
purchaseLab(source, labKey, options?) | { ok, labId?, error? } |
getLabBalance(labId) | The lab wallet's balance |
getPlayerData(source, field?) | A player's level, XP and stats |
addXP(source, amount) | { ok, xp?, level?, error? } |
removeXP(source, amount) | { ok, xp?, level?, error? } |
setLevel(source, level) | { ok, xp?, level?, error? } |
addPlayerData(source, dataType, amount) | true, or nil when refused |
getLevelData() | The XP for each level |
getPlayerPlantCount(source) | How many plants a player owns |
getPlantData(plantId, property?) | A plant, or one field of it |
getAllPlants() | Every plant |
getPlantsByOwner(owner) | A player's plants |
getPlantsByLab(labId) | The plants in a lab |
setPlantData(plantId, values) | { ok, plant?, error?, field? } |
The exports that change something answer { ok = true, ... } when it worked, and { ok = false, error = '<code>' } when nothing changed and the code says why. The plants and player records the exports hand out are copies: changing one changes nothing in the game.
GetRuntimeConfig
Server export
exports.lation_weed:GetRuntimeConfig(section)Parameters
sectionstringoptionaldefaultnilsetup,growth,planting,dealer,strains,fertilizers,soils,logs,labs,farms,experience,tables,shoporjoints; every section when left out
Returns
table | nil- That section, keyed as on the configuration page, or a table of every section keyed by name.
nilfor an unknown section.
The table is the live configuration, not a copy: read it, and change settings in /weedadmin. It is the only export in PascalCase. The logs section holds your Discord webhook, so don't pass it to clients.
local planting = exports.lation_weed:GetRuntimeConfig('planting')
print(('players can own %d plants'):format(planting.max))purchaseLab
Server export
local result = exports.lation_weed:purchaseLab(source, labKey, options)Parameters
sourcenumber- The server id of an online player
labKeystring- The lab's key, such as
mission_row_1 options.skipPaymentbooleanoptionaldefaultfalsetruegrants the lab without charging the player
Returns
table{ ok = true, labId = number }, or{ ok = false, error = string }
Buys or grants a lab, for selling labs through your own script. It ignores the lab's Purchase interaction and Marketplace listing switches, but still checks the player's level, the labs-per-player cap and whether someone already owns the lab. Unless skipPayment is set, it charges the lab's price from the Labs > Accounts > Purchase account. The new owner starts with an empty stash.
The error is one of no-identifier, invalid-key, lab-not-found, level-too-low, max-per-player, already-owned, insufficient-funds or insert-failed.
A lab granted with skipPayment still records its price as the price paid, so selling it later refunds the Sell refund share of that price.
local result = exports.lation_weed:purchaseLab(source, 'mission_row_1', { skipPayment = true })
if not result.ok then
print(('lab purchase failed: %s'):format(result.error))
return
end
print(('granted lab %d'):format(result.labId))getLabBalance
Server export
exports.lation_weed:getLabBalance(labId)Parameters
labIdnumber- The lab's id, from
purchaseLabor a plant'sbucketId
Returns
number- The lab wallet's balance, or
0when no lab has that id
There is no export that sells or takes back a lab. However a lab ends (sold, removed by an admin, or reclaimed for inactivity), its wallet goes back to its owner and its stash is emptied; see selling a lab.
local plant = exports.lation_weed:getPlantData(plantId)
if plant and plant.bucketId > 0 then
print(('that lab holds $%d'):format(exports.lation_weed:getLabBalance(plant.bucketId)))
endgetPlayerData
Server export
exports.lation_weed:getPlayerData(source, field)Parameters
sourcenumber- The server id of an online player
fieldstringoptionaldefaultnil- One field to return; the whole record when left out
Returns
table | any | nil- The record (below), or the value of
field.nilwhen the player has no identifier.
A player with no record gets one at level 1 on the first call. The record is the cached one, so treat it as read-only and change XP with addXP, removeXP and setLevel. The level is always the one the player's XP reaches on the live level ladder.
{
identifier = 'ABC12345',
level = 2,
exp = 3150,
plants_searched = 12, -- farm searches that found seeds
plants_grown = 9, -- harvests
bud_produced = 84,
joints_rolled = 30,
weed_bagged = 15,
}local level = exports.lation_weed:getPlayerData(source, 'level')
if level and level >= 3 then
-- let them in
endaddXP
Server export
local result = exports.lation_weed:addXP(source, amount)Parameters
sourcenumber- The server id of an online player
amountnumber- XP to give, above 0. It is rounded to a whole number.
Returns
table{ ok = true, xp = number, level = number }with the new total and level, or{ ok = false, error = string }
Gives XP the way a harvest does: the player moves up the level ladder and gets the level up notification through lation_weed:levelUp. The XP is taken as given: no strain's XP multiplier applies. Like gameplay XP, it is saved when the player leaves and every 3 minutes.
local result = exports.lation_weed:addXP(source, 250)
if result.ok then
print(('now level %d with %d XP'):format(result.level, result.xp))
endremoveXP
Server export
local result = exports.lation_weed:removeXP(source, amount)Parameters
sourcenumber- The server id of an online player
amountnumber- XP to take, above 0. It is rounded to a whole number.
Returns
table- Like
addXP's
Takes XP away, stopping at 0. A player whose XP falls below their level's threshold drops to the level the ladder puts them at and is told through lation_weed:levelDown.
exports.lation_weed:removeXP(source, 1000)setLevel
Server export
local result = exports.lation_weed:setLevel(source, level)Parameters
sourcenumber- The server id of an online player
levelnumber- A whole level on the ladder, from 1 to the number of levels
Returns
table- Like
addXP's
Puts a player at a level. XP already inside that level stays; otherwise it moves to the level's threshold. The player is told when the level goes up or down.
exports.lation_weed:setLevel(source, 3)The error of all three is one of:
error | Why |
|---|---|
invalid-amount | addXP or removeXP: not a number above 0 |
invalid-level | setLevel: not a whole level on the ladder |
player-not-found | No such player online, or their record couldn't be loaded |
addPlayerData
Server export
exports.lation_weed:addPlayerData(source, dataType, amount)Parameters
sourcenumber- The server id of an online player
dataTypestringexp, or one of the counters:plants_searched,plants_grown,bud_produced,joints_rolled,weed_baggedamountnumber- How much to add. Anything 0 or below does nothing.
Returns
boolean | niltrue, ornilwhen the arguments don't fit or the player has no record
The lower-level way in, for the lifetime counters. Adding exp levels up and notifies like addXP, which is the one to use for XP.
exports.lation_weed:addPlayerData(source, 'joints_rolled', 1)getLevelData
Server export
exports.lation_weed:getLevelData()Returns
table- The total XP for each level, in order, starting with level 1 at
0
local levels = exports.lation_weed:getLevelData()
-- { 0, 2500, 10000, 20000, 50000, 100000 }getPlayerPlantCount
Server export
exports.lation_weed:getPlayerPlantCount(source)Parameters
sourcenumber- The server id of an online player
Returns
number- How many plants the player owns, outdoors and in labs. Plants put down by lab workers belong to the lab and aren't counted.
if exports.lation_weed:getPlayerPlantCount(source) >= 10 then
-- a big grower
endgetPlantData
Server export
exports.lation_weed:getPlantData(plantId, property)Parameters
plantIdnumber- The plant's id
propertystringoptionaldefaultnil- One field to return; the whole plant when left out
Returns
table | any | nil- A copy of the plant (below), the value of
property, ornilwhen there is no such plant
The plant:
{
id = 42,
owner = 'ABC12345', -- the identifier of who put it down; 'lab_<labId>' for a lab worker's plant
strainId = 'og_kush', -- nil until a seed is planted
model = 'shoe_shuffler_prop_weed_med_green_01a',
x = 0.0, y = 0.0, z = 0.0, w = 0.0,
stage = 5, -- 1 empty pot, 2 pot with soil, 3 to 8 growing
growth = 40, -- 0 to 100
health = 100, -- 0 to 100
water = 55, -- 0 to 100
fertilizer = 30, -- 0 to 100
soil = 'ls_basic_soil', -- nil until soil is added
fertilizerType = 'ls_basic_fertilizer',
isCovered = false, -- under cover, so no rain
deathTimer = nil, -- seconds left while the plant is dying
bucketId = 0, -- 0 outdoors; the lab's id inside a lab
}local health = exports.lation_weed:getPlantData(plantId, 'health')getAllPlants
Server export
exports.lation_weed:getAllPlants()Returns
table- Every plant on the server, keyed by plant id, each a copy shaped like
getPlantData's
for plantId, plant in pairs(exports.lation_weed:getAllPlants()) do
if plant.growth >= 100 then
print(('plant %d is ready'):format(plantId))
end
endgetPlantsByOwner
Server export
exports.lation_weed:getPlantsByOwner(owner)Parameters
ownernumber | string- The player's server id while they are online, or their identifier as text: the
citizenidon Qbox and QBCore, the identifier on ESX,tostring(charId)on ox_core
Returns
table- The plants the player owns, as a list in id order, each a copy shaped like
getPlantData's. Empty when they own none.
local mine = exports.lation_weed:getPlantsByOwner(source)
print(('%d plants'):format(#mine))getPlantsByLab
Server export
exports.lation_weed:getPlantsByLab(labId)Parameters
labIdnumber- The lab's id, the same one
getLabBalancetakes
Returns
table- The plants inside the lab, as a list in id order, each a copy shaped like
getPlantData's, the lab workers' plants included
for _, plant in ipairs(exports.lation_weed:getPlantsByLab(labId)) do
print(plant.id, plant.strainId, plant.growth)
endsetPlantData
Server export
local result = exports.lation_weed:setPlantData(plantId, values)
local result = exports.lation_weed:setPlantData(plantId, field, value)Parameters
plantIdnumber- The plant's id
valuestable- The fields to set (below), or the name of one field followed by its value
Returns
table{ ok = true, plant = table }with a copy of the plant as it is now, or{ ok = false, error = string, field = string }
Sets one or more fields of a plant, checked the way the admin panel's plant editor checks them, then saved, put back in the growth loop where it belongs and shown to the players who can see the plant, like a change the game makes itself.
| Field | Accepts |
|---|---|
water, fertilizer, health, growth | A number, kept within 0 to 100 and rounded |
stage | 1 to 8. Stage 3 and above needs a strain. |
strainId | A strain's key; '' or false empties it |
soil | A soil's key; '' or false empties it |
fertilizerType | A fertilizer's key; '' or false empties it |
isCovered | true or false |
What the game keeps in step for you:
- Setting
growthon a growing plant without astagemoves the stage, and the model, to the one that growth shows, as the growth loop does. At 100 the plant is ready to harvest. - Giving health back to a dying plant stops its death timer.
- A stage or strain change picks the model that stage shows.
Anything else about a plant (its owner, where it stands, its model, its death timer) can't be set. One refused field refuses the whole call, and nothing changes.
error | Why |
|---|---|
plant-not-found | No plant with that id |
invalid-values | No fields given |
unknown-field | A field that can't be set; field names it |
invalid-value | A value that doesn't fit: not a number, a stage outside 1 to 8, a key the config doesn't have; field names it |
strain-required | Stage 3 or above on a plant with no strain |
database | The plant couldn't be saved |
After every successful call, lation_weed:server:plantChanged fires with the plant.
-- a sprinkler: top up every plant in a lab
for _, plant in ipairs(exports.lation_weed:getPlantsByLab(labId)) do
exports.lation_weed:setPlantData(plant.id, { water = 100 })
end
-- one field
local result = exports.lation_weed:setPlantData(42, 'health', 100)
if not result.ok then
print(('refused: %s'):format(result.error))
end
-- plant a strain in a pot that has soil
exports.lation_weed:setPlantData(42, { strainId = 'og_kush', stage = 3 })Client exports
openLabMarketplace
Client export
exports.lation_weed:openLabMarketplace()Returns
booleantruewhen the marketplace opened,falsewhen it couldn't load
Opens the lab marketplace for the local player, from your own phone app or NPC for example. Players buy through it with the usual rules, so labs with their Marketplace listing off aren't shown.
if not exports.lation_weed:openLabMarketplace() then
print('marketplace unavailable')
endEvents
Server
Local server events: listen with AddEventHandler in a server script.
lation_weed:configUpdated
Server event
AddEventHandler('lation_weed:configUpdated', function(snapshot) end)Parameters
snapshottable- Every section, keyed by name, in the shape an export file has. The
logssection is included.
Fires when the configuration loads on start, and after every save, reset or import in the admin panel.
AddEventHandler('lation_weed:configUpdated', function(snapshot)
print(('players can now own %d plants'):format(snapshot.planting.max))
end)lation_weed:server:plantChanged
Server event
AddEventHandler('lation_weed:server:plantChanged', function(plantId, plant) end)Parameters
plantIdnumber- The plant's id
planttable | nil- The new plant, shaped like
getPlantData's;nilwhen it was removed
Fires when a plant is created, when one is removed by harvest, destroy, death, cleanup, an admin, a sale or a lab being closed, and after every successful setPlantData. Other changes to a living plant (growth, care, the admin panel's editor) don't fire it. The plant passed is the server's own record, so read it without changing it.
AddEventHandler('lation_weed:server:plantChanged', function(plantId, plant)
if not plant then
print(('plant %d is gone'):format(plantId))
end
end)lation_weed:server:plantNeedsAttention
Server event
AddEventHandler('lation_weed:server:plantNeedsAttention', function(plantId, reason) end)Parameters
plantIdnumber- The plant's id
reasonstringharvest: the plant reached 100% growth
AddEventHandler('lation_weed:server:plantNeedsAttention', function(plantId, reason)
if reason ~= 'harvest' then return end
local owner = exports.lation_weed:getPlantData(plantId, 'owner')
print(('plant %d owned by %s is ready'):format(plantId, owner))
end)Client
lation_weed:runtimeReady
Client event
AddEventHandler('lation_weed:runtimeReady', function() end)Fires when the configuration reaches this client: after joining, after a restart, and after every save in the admin panel. Listen with AddEventHandler.
lation_weed:levelUp
Client event
RegisterNetEvent('lation_weed:levelUp', function(newLevel) end)Parameters
newLevelnumber- The level the player reached
Sent by the server when the player levels up, from gameplay, addXP, setLevel or a level ladder edited in the admin panel; lation weed shows its own notification as well. Listen with RegisterNetEvent.
RegisterNetEvent('lation_weed:levelUp', function(newLevel)
print(('reached weed level %d'):format(newLevel))
end)lation_weed:levelDown
Client event
RegisterNetEvent('lation_weed:levelDown', function(newLevel) end)Parameters
newLevelnumber- The level the player dropped to
Sent by the server when removeXP or setLevel moves the player down a level; lation weed shows its own notification as well. A level ladder edited in the admin panel moves players down without it.
lation_weed:notify
Client event
TriggerClientEvent('lation_weed:notify', source, message, type)Parameters
messagestring- The text
typestringoptionaldefaultnilsuccess,error,infoand the like, as your notification system reads them
Send it from the server to show a notification through the system set under Setup > Integrations > Notifications.
TriggerClientEvent('lation_weed:notify', source, 'Your harvest is ready', 'success')Hooks
Hooks are functions in files that ship open: server/utils/hooks.lua and client/utils/hooks.lua. Edit them in place.
onLabRaidAttempt
Server hook
function Hooks.onLabRaidAttempt(data) endParameters
data.raiderSourcenumber- The raider's server id
data.raiderIdentifierstring | nil- The raider's identifier
data.raiderNamestring- The raider's character name
data.isPoliceboolean- The raider holds a police job
data.ownerIdentifierstring- The owner's identifier
data.ownerSourcenumber | nil- The owner's server id, while they are online
data.labIdnumber- The lab's id
data.labKeystring- The lab's key
data.labLabelstring- The lab's name
data.coordsvector3 | nil- The lab's entrance
Runs when a player confirms a break-in and the server accepts it, before the skill check. Empty by default; use it for dispatch alerts.
function Hooks.onLabRaidAttempt(data)
if data.isPolice then return end
exports['ps-dispatch']:CustomAlert({
message = 'Possible break in detected',
coords = data.coords,
sprite = 499,
color = 1,
scale = 1.0,
length = 3,
jobs = { 'police' },
})
endonLabRaidBreached
Server hook
function Hooks.onLabRaidBreached(data) endParameters
data.messagestring- The alert text, already translated
data.notifyOwnerboolean- Labs > Raids > Notify the owner is on
data.ownerIdentifierstring- The owner's identifier
data.ownerSourcenumber | nil- The owner's server id, while they are online
data.labIdnumber- The lab's id
data.labKeystring- The lab's key
data.labLabelstring- The lab's name
data.raiderSourcenumber- The raider's server id
data.raiderIdentifierstring | nil- The raider's identifier
data.raiderNamestring- The raider's character name
data.isPoliceboolean- The raider holds a police job
Runs after every successful break-in, whatever Labs > Raids > Notify the owner is set to and whether or not the owner is online, so a dispatch, phone or mail script keeps working with the in-game notice off. By default it sends the owner a notification when notifyOwner is on and they are online.
function Hooks.onLabRaidBreached(data)
if data.notifyOwner and data.ownerSource then
TriggerClientEvent('lation_weed:notify', data.ownerSource, data.message, 'error')
return
end
-- the owner is offline, or the in-game notice is off: send your own here
-- exports['my-phone']:SendMail(data.ownerIdentifier, data.labLabel, data.message)
endIf you edited this hook before 4.7.0, check data.notifyOwner before sending your own in-game notice.
onJointSmoked (server)
Server hook
function Hooks.onJointSmoked(data) endParameters
data.sourcenumber- The player's server id
data.identifierstring | nil- The player's identifier
data.strainIdstring- The strain's key
data.strainLabelstring | nil- The strain's name
data.jointItemstring- The joint item
data.stressReliefnumber- The joint's stress relief,
0when it is off data.durationnumber- How long the smoke lasts, in milliseconds
Runs on the server when a player finishes smoking and the joint is taken from their inventory. Empty by default.
function Hooks.onJointSmoked(data)
print(('%s smoked a %s joint'):format(data.identifier, data.strainLabel or data.strainId))
endonJointSmoked (client)
Client hook
function Hooks.onJointSmoked(data) endParameters
data.strainIdstring- The strain's key
data.strainLabelstring | nil- The strain's name
data.jointItemstring | nil- The joint item
data.stressReliefnumber- The joint's stress relief,
0when it is off data.durationnumber | nil- How long the smoke lasted, in milliseconds
Runs on the player's client once they finish smoking and the effects apply. By default it relieves stress with the hud:server:RelieveStress event that QBCore and Qbox HUDs listen for:
function Hooks.onJointSmoked(data)
if not data then return end
local amount = tonumber(data.stressRelief) or 0
if amount <= 0 then return end
TriggerServerEvent('hud:server:RelieveStress', amount)
endIf your HUD handles stress another way, change this function.
Open files
These files ship open, so you can fit the script to your server:
| File | What it holds |
|---|---|
bridge/server.lua, bridge/client.lua | Framework and inventory support: players, jobs, money, items and usable items, each with a slot for a custom one |
server/utils/perms.lua | IsAdmin, the check for /weedadmin |
server/utils/stash.lua, client/utils/stash.lua | The lab stash on each inventory, and ClearStash, which empties it when a lab is sold or closed. A custom inventory needs both. |
server/utils/weather.lua | Rain detection |
server/utils/logging.lua | fivemanage and Discord logging |
server/utils/hooks.lua, client/utils/hooks.lua | The hooks above |
client/utils/ui.lua | Notifications, menus, dialogs, progress bars, text UI and skill checks, with the custom slots |
client/utils/interact.lua | The interaction systems with a custom slot, and how close players must be to use a prompt (InteractDistance, 2 m) |
shared/defaults.lua | The shipped defaults (server only); see configuration |
server/settings.lua | What the admin panel can edit: every option's type and limits, and the four marked restart |
config/icons.lua, config/animations.lua | Icons and animations |
locales/*.json | Every string |
Still stuck? Ask in our Discord: the people who write the scripts answer.
Last updated