The scenario
In our QBCore RP server, police officers would arrest suspects and collect fines. But splitting that money fairly among everyone who responded to the call was always a manual headache — someone had to calculate the split and do individual transfers.
I wanted a system where an officer could walk up to a specific NPC, enter the total amount, select which colleagues participated, and have the money split and distributed automatically.
The result: lokat_sharing_money — a PED-based fund distribution script with job authorization, grade checks, on-duty filtering, and dual banking support.
How it works
Step 1 — Interact with the PED. A security guard NPC is placed at the police station. Officers approach and interact via qb-target. The canInteract check runs client-side to hide the option from unauthorized players.
canInteract = function()
local PD = QBCore.Functions.GetPlayerData()
local job = PD.job.name
local grade = PD.job.grade.level
local okJob = false
for _, j in ipairs(Config.AuthorizedJobs) do
if j == job then okJob = true; break end
end
if not okJob then return false end
local need = Config.MinGradeToStart[job]
if need and grade < need then return false end
if Config.RequireOnDutyToStart and not PD.job.onduty then
return false
end
return true
end
Step 2 — Enter the amount. A qb-input dialog asks for the total amount to distribute.
Step 3 — Select recipients. The server returns a list of online players with the same job who are currently on-duty. The officer selects who participated using a checkbox-style qb-menu.
-- Server: filter same job, on-duty players
for _, pid in ipairs(players) do
local P = QBCore.Functions.GetPlayer(pid)
if P then
local dutyOK = (not Config.OnDutyOnly) or P.PlayerData.job.onduty
if dutyOK and P.PlayerData.job.name == myJob then
table.insert(list, { id = pid, name = fn .. " " .. ln })
end
end
end
Step 4 — Confirm and distribute. A summary screen shows the total and per-person amount. On confirm, the server deducts from the initiator's bank and distributes equally to each selected officer.
local perAmount = math.floor(total / #selectedPlayers)
for _, player in ipairs(selectedPlayers) do
local receiver = QBCore.Functions.GetPlayer(tonumber(player.id))
if receiver then
receiver.Functions.AddMoney("bank", perAmount, "job-distribution-received")
end
end
Key design decisions
Job is inferred server-side. The client never sends which job to filter by — the server reads the initiator's job directly. This prevents players from spoofing a different job to access the distribution pool.
On-duty check at distribution time. Even if someone was selected as a recipient, the server re-checks their on-duty status at payment time. If they clocked out between selection and confirmation, they get skipped.
Dual banking support. Works with both qb-banking and okokBanking. When okokBanking is selected, a transaction log entry is also added for each recipient.
Config.BankingSystem = "okokBanking" -- or "qb-banking"
Multi-language support. All UI strings are defined in a locale table. Switch between Japanese and English with a single config value.
Config.Locale = "en" -- or "ja"
Config overview
Config.AuthorizedJobs = { 'police', 'ambulance', 'mechanic' }
Config.MinGradeToStart = {
police = 1, -- sergeant and above
ambulance = 1,
mechanic = 0, -- any grade
}
Config.OnDutyOnly = true
Config.RequireOnDutyToStart = true
Config.Peds = {
{ model = "s_m_m_security_01",
coords = vector4(438.82, -991.98, 28.69, 215.72),
distance = 2.5 }
}
Built with AI, designed by a non-coder
Like everything in this series, I designed the UX flow and logic, and implemented it with Claude Opus as my coding partner. The tricky parts — server-side job inference, double on-duty checks, banking abstraction — all came from thinking through edge cases before writing a single line.
Next up: Vol.3 — Building an In-Server Arcade with Tetris, Breakout, and Space Invaders in FiveM.
Questions or want the full source? Drop a comment below.
Top comments (0)