Skip to Content
Resourcesqb-vineyard Reference

qb-vineyard

qb-vineyard is a three-stage production chain for the vineyard job: pick grapes, press them into grape juice, ferment juice into wine, then sell either product to an NPC at a fixed price.

Of the job resources audited on this site, it has the tightest server-side validation. It is a useful reference implementation when you write your own.

The manifest at the commit linked above declares version 1.5.0.

Dependencies and start order

The README names qb-core and PolyZone. The manifest loads @qb-core/shared/locale.lua, locales, and config.lua as shared scripts, then PolyZone client.lua and BoxZone.lua plus client.lua, and server.lua on the server.

The implementation also uses qb-inventory for AddItem, RemoveItem, and the item box.

Start order: qb-core, qb-inventory, PolyZone, then qb-vineyard.

Required shared items: grape, grapejuice, wine.

Configuration

KeyDefaultMeaning
DebugfalseDraws zone outlines
PickAmount{ min = 8, max = 12 }Client-side pick animation count
GrapeAmount{ min = 8, max = 12 }Grapes granted per pick
GrapeJuiceAmount{ min = 6, max = 10 }Juice granted per press
WineAmount{ min = 6, max = 10 }Wine granted per fermentation
wineTimer180Fermentation duration
Sell.enabledtrueEnable the NPC buyer
Sell.prices{ wine = 15, grapejuice = 10 }Cash paid per unit
Sell.coords, Sell.zones, Sell.minZ, Sell.maxZSell zone geometry
Vineyard.start, Vineyard.wine, Vineyard.grapejuiceStage zone geometry

The crafting input quantities are not in the config. They are hardcoded in server.lua: 16 grape produce one batch of grape juice, and 23 grapejuice produce one batch of wine.

The 20 grape pickup points are also hardcoded in server.lua as a local grapeLocations table, not in config.lua. Moving the vineyard means editing both files.

Server surface

HandlerKindArguments
qb-vineyard:server:getGrapesnet eventnone
qb-vineyard:server:loadIngredientscallbacknone, returns a boolean
qb-vineyard:server:grapeJuicecallbacknone, returns a boolean
qb-vineyard:server:receiveWinenet eventnone
qb-vineyard:server:receiveGrapeJuicenet eventnone
qb-vineyard:server:sellItemsnet eventnone

Why this one is worth copying

Every reward path takes zero arguments. There is nothing for a client to forge. The server decides everything:

  1. Job check. playerHasVineyardJob requires PlayerData.job.name == 'vineyard'.
  2. Server-read position. playerIsNearGrapeLocation reads the ped with GetPlayerPed(src) and compares against the server’s own coordinate list, within 5.0 units.
  3. Rate limit. Grape pickups are throttled to one per 3000 ms per source using GetGameTimer.
  4. One-shot reward tokens. The crafting callbacks consume the input items first, then set wineRewardState[src] or grapeJuiceRewardState[src]. The matching reward event refuses unless that token is set, and clears it immediately.
  5. Cleanup. A playerDropped handler clears all three per-source tables.

The token pattern in step 4 is the piece most often missing elsewhere. It makes the reward event safe to expose, because firing it without having paid the input does nothing.

-- the shape, condensed from server.lua RegisterNetEvent('qb-vineyard:server:receiveWine', function() local src = source local Player = exports['qb-core']:GetPlayer(src) if not playerHasVineyardJob(Player) then return end if not wineRewardState[src] then return end wineRewardState[src] = nil -- grant the reward end)

Selling

sellItems iterates Config.Sell.prices, removes every unit of each matching item the player holds, sums amount * price, and adds the total to cash. The price comes from the config, never from the client.

It does not check the player’s distance to the sell zone, and it does not check the job. The client zone is the only gate, so a client that fires the event from anywhere sells its whole stock. If that matters, add a distance check against Config.Sell.coords, mirroring playerIsNearGrapeLocation.

Troubleshooting

SymptomLikely cause
Nothing happens when pickingThe job is not exactly vineyard, the player is more than 5 units from a hardcoded grape point, or the 3 second cooldown is active
Crafting consumes items but grants nothingThe reward event never fired. The token is set and cleared only by the matching pair
Crafting refuses with enough itemsThe thresholds are 16 grape and 23 grapejuice, hardcoded in server.lua
Moving the vineyard breaks pickinggrapeLocations lives in server.lua, not in config.lua
Selling pays nothingThe player holds none of the items listed in Config.Sell.prices

Proposed manual smoke test

Not yet executed. On staging:

  1. Confirm grape, grapejuice, and wine exist in your shared items.
  2. Pick grapes twice within three seconds and confirm the second is ignored.
  3. Run the full chain and confirm the hardcoded input thresholds.
  4. Fire a reward event without crafting first and confirm nothing is granted.
  5. Sell both products and confirm cash matches Config.Sell.prices.

Sources