Creating A Simple Job
This walks through a complete, minimal job resource — an NPC prompt, a start event, server-side validation, a reward, a notification, and cleanup — using nothing but wc_libs. By the end you'll have a working wc_stagecoach-style job you can copy and adapt.
What you'll need
- ✓ A resource folder, e.g.
wc_stagecoach, with afxmanifest.lua,client/main.lua, andserver/main.lua. - ✓
dependency 'wc_libs'andshared_scripts { '@wc_libs/init.lua' }declared.
-
Declare the dependency
Every wc_libs resource needs this in its manifest so the
wcproxy is available and the resource fails loudly if wc_libs isn't running.wc_stagecoach/fxmanifest.luafx_version 'adamant' game 'rdr3' name 'wc_stagecoach' dependency 'wc_libs' shared_scripts { '@wc_libs/init.lua' } client_scripts { 'client/main.lua' } server_scripts { 'server/main.lua' } -
Spawn the NPC and create a prompt
Spawn the clerk once on resource start, and use
WatchPromptto show a "[G] Buy Ticket" prompt only while the player is nearby — it handles the range-check loop and prompt visibility for you.wc_stagecoach/client/main.lualocal clerkPed CreateThread(function() local hash = wc:LoadModel("a_m_m_hillfolk_01") clerkPed = wc:SpawnPed(hash, -290.5, 800.2, 118.9, 160.0, true, true) wc:CreateBlip(-290.5, 800.2, 118.9, "Stagecoach Station") local prompt = wc:CreatePrompt("Buy Ticket [G]") wc:WatchPrompt(prompt, clerkPed, 2.5, function() TriggerServerEvent('wc_stagecoach:start') end) end) -
Fire a start event — client asks, server decides
The client only asks to start the job. All the real decisions (is a run already active, can the player afford it, are they in the right job) happen server-side.
wc_stagecoach/server/main.lualocal ActiveRuns = {} RegisterNetEvent('wc_stagecoach:start', function() local src = source -- server validation: no double-starts if ActiveRuns[src] then wc:Notify(src, { variant = 'fail', title = "You already have a run active." }) return end ActiveRuns[src] = { reward = 50, startedAt = os.time() } TriggerClientEvent('wc_stagecoach:begin', src, ActiveRuns[src]) end)Security. Never trust a client-sent reward amount. The server decidesreward = 50here and remembers it inActiveRuns— the client never gets a chance to influence the payout. -
Send the player on their way
On the client, react to the server's
beginevent by dropping a destination blip and GPS route withCreateMissionMarker.wc_stagecoach/client/main.lualocal bag = wc:CreateCleanupBag() RegisterNetEvent('wc_stagecoach:begin', function(run) local dest = vector3(273.5, -1258.0, 67.0) -- Valentine bag:AddBlip(wc:CreateMissionMarker(dest.x, dest.y, dest.z, "Delivery Point", { route = true })) bag:AddGPS() wc:TopNotify('Stagecoach Run', 'Deliver the mail sack to Valentine') wc:WatchPlayerNear(dest, 5.0, function() bag:Clean() TriggerServerEvent('wc_stagecoach:complete') end, { timeoutMs = 600000 }) end) -
Validate completion and pay the reward
The server re-checks
ActiveRuns[src]exists before paying anyone — a player who never actually started a run (or is replaying an old client event) gets nothing.wc_stagecoach/server/main.luaRegisterNetEvent('wc_stagecoach:complete', function() local src = source local run = ActiveRuns[src] if not run then return end -- no active run — ignore ActiveRuns[src] = nil wc:AddMoney(src, run.reward, 0) wc:Notify(src, { title = ("Stagecoach Complete: +$%d"):format(run.reward), variant = 'avanced' }) end) -
Clean up on disconnect
If the player disconnects mid-run, remove their entry so it doesn't leak memory or block a future run. See the Safe Resource Cleanup guide for the full pattern.
wc_stagecoach/server/main.luaAddEventHandler('playerDropped', function() ActiveRuns[source] = nil end)
Common mistakes
- Paying the reward from the client's
begin/completeevent directly instead of round-tripping through the server — that lets a modified client just fire the event and get paid without ever visiting Valentine. - Not tracking
ActiveRunsserver-side at all and trusting "the client says I'm done" — always keep authoritative state on the server. - Forgetting
dependency 'wc_libs'— the resource will error on start with nowcproxy available.