305 lines
7.9 KiB
Lua
305 lines
7.9 KiB
Lua
-- server/cash.lua
|
|
print("^2[turfwar]^7 cash.lua loaded")
|
|
|
|
Cash = {}
|
|
|
|
local cache = {}
|
|
|
|
local function PushMoney(src)
|
|
TriggerClientEvent('turfwar:money:update', src, Cash.Get(src), Cash.GetBank(src))
|
|
end
|
|
|
|
-- Safe notify helper (won't crash if you don't have turfwar:notify hooked)
|
|
local function Notify(src, msg)
|
|
TriggerClientEvent('chat:addMessage', src, { args = { "ATM", tostring(msg) } })
|
|
end
|
|
|
|
local function getIdentifier(src)
|
|
local ids = GetPlayerIdentifiers(src)
|
|
if not ids or #ids == 0 then return nil end
|
|
|
|
-- Prefer license:
|
|
for _, id in ipairs(ids) do
|
|
if id:sub(1, 8) == "license:" then
|
|
return id
|
|
end
|
|
end
|
|
|
|
-- Fallback (steam/discord/etc.)
|
|
return ids[1]
|
|
end
|
|
|
|
local function ensureLoaded(src)
|
|
local identifier = getIdentifier(src)
|
|
if not identifier then return nil end
|
|
if not cache[identifier] then
|
|
Cash.Load(src)
|
|
end
|
|
return identifier
|
|
end
|
|
|
|
function Cash.Load(src)
|
|
local identifier = getIdentifier(src)
|
|
if not identifier then return end
|
|
|
|
local rows = MySQL.query.await(
|
|
'SELECT cash, bank FROM player_money WHERE identifier = ?',
|
|
{ identifier }
|
|
)
|
|
local row = (rows and rows[1]) or nil
|
|
|
|
if not row then
|
|
MySQL.insert.await(
|
|
'INSERT INTO player_money (identifier, cash, bank) VALUES (?, 0, 0)',
|
|
{ identifier }
|
|
)
|
|
row = { cash = 0, bank = 0 }
|
|
end
|
|
|
|
cache[identifier] = {
|
|
cash = tonumber(row.cash) or 0,
|
|
bank = tonumber(row.bank) or 0
|
|
}
|
|
|
|
PushMoney(src)
|
|
end
|
|
|
|
function Cash.Get(src)
|
|
local identifier = ensureLoaded(src)
|
|
if not identifier then return 0 end
|
|
return cache[identifier].cash or 0
|
|
end
|
|
|
|
function Cash.GetBank(src)
|
|
local identifier = ensureLoaded(src)
|
|
if not identifier then return 0 end
|
|
return cache[identifier].bank or 0
|
|
end
|
|
|
|
function Cash.Add(src, amount)
|
|
amount = tonumber(amount) or 0
|
|
if amount == 0 then return end
|
|
|
|
local identifier = ensureLoaded(src)
|
|
if not identifier then return end
|
|
|
|
local data = cache[identifier]
|
|
data.cash = math.max(0, (data.cash or 0) + amount)
|
|
|
|
MySQL.update(
|
|
'UPDATE player_money SET cash = ? WHERE identifier = ?',
|
|
{ data.cash, identifier }
|
|
)
|
|
|
|
PushMoney(src)
|
|
end
|
|
|
|
function Cash.Remove(src, amount)
|
|
Cash.Add(src, -(tonumber(amount) or 0))
|
|
end
|
|
|
|
function Cash.Set(src, amount)
|
|
amount = math.max(0, tonumber(amount) or 0)
|
|
|
|
local identifier = ensureLoaded(src)
|
|
if not identifier then return end
|
|
|
|
cache[identifier].cash = amount
|
|
|
|
MySQL.update(
|
|
'UPDATE player_money SET cash = ? WHERE identifier = ?',
|
|
{ amount, identifier }
|
|
)
|
|
|
|
PushMoney(src)
|
|
end
|
|
|
|
function Cash.SetBank(src, amount)
|
|
amount = math.max(0, tonumber(amount) or 0)
|
|
|
|
local identifier = ensureLoaded(src)
|
|
if not identifier then return end
|
|
|
|
cache[identifier].bank = amount
|
|
|
|
MySQL.update(
|
|
'UPDATE player_money SET bank = ? WHERE identifier = ?',
|
|
{ amount, identifier }
|
|
)
|
|
|
|
PushMoney(src)
|
|
end
|
|
|
|
AddEventHandler('playerJoining', function()
|
|
Cash.Load(source)
|
|
end)
|
|
|
|
AddEventHandler('playerDropped', function()
|
|
local src = source
|
|
local identifier = getIdentifier(src)
|
|
if identifier and cache[identifier] then
|
|
local data = cache[identifier]
|
|
MySQL.update(
|
|
'UPDATE player_money SET cash = ?, bank = ? WHERE identifier = ?',
|
|
{ data.cash or 0, data.bank or 0, identifier }
|
|
)
|
|
cache[identifier] = nil
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('turfwar:money:request', function()
|
|
local src = source
|
|
Cash.Load(src)
|
|
PushMoney(src)
|
|
end)
|
|
|
|
-- =========================================================
|
|
-- Ped cash drops -> claim once -> add to player's cash
|
|
-- =========================================================
|
|
local CashDrops = {}
|
|
local NextDropId = 1
|
|
|
|
RegisterNetEvent("environment:pedCashDrop", function(amount, coords)
|
|
amount = tonumber(amount) or 0
|
|
if amount < 1 or amount > 1000 then return end
|
|
if type(coords) ~= "table" or coords.x == nil or coords.y == nil or coords.z == nil then return end
|
|
|
|
local dropId = NextDropId
|
|
NextDropId = NextDropId + 1
|
|
|
|
CashDrops[dropId] = {
|
|
amount = amount,
|
|
taken = false,
|
|
createdAt = os.time()
|
|
}
|
|
|
|
TriggerClientEvent("environment:spawnCashPickup", -1, dropId, amount, coords)
|
|
|
|
SetTimeout(5 * 60 * 1000, function()
|
|
if CashDrops[dropId] and not CashDrops[dropId].taken then
|
|
CashDrops[dropId] = nil
|
|
TriggerClientEvent("environment:removeCashPickup", -1, dropId)
|
|
end
|
|
end)
|
|
end)
|
|
|
|
RegisterNetEvent("environment:pickupCash", function(dropId)
|
|
local src = source
|
|
dropId = tonumber(dropId)
|
|
if not dropId then return end
|
|
|
|
local drop = CashDrops and CashDrops[dropId]
|
|
if not drop or drop.taken then return end
|
|
|
|
drop.taken = true
|
|
local amount = tonumber(drop.amount) or 0
|
|
|
|
CashDrops[dropId] = nil
|
|
|
|
if amount > 0 then
|
|
Cash.Add(src, amount)
|
|
end
|
|
|
|
TriggerClientEvent("environment:removeCashPickup", -1, dropId)
|
|
TriggerClientEvent("environment:pickupFeedback", src, amount)
|
|
end)
|
|
|
|
-- =========================================================
|
|
-- ATM-Gated Bank Transactions (server authoritative)
|
|
-- Requires: server/atm_access.lua exporting RequireATM
|
|
-- =========================================================
|
|
|
|
local function RequireATM(src, token)
|
|
return exports[GetCurrentResourceName()]:RequireATM(src, token, true)
|
|
end
|
|
|
|
local function clampAmount(x)
|
|
x = tonumber(x)
|
|
if not x then return nil end
|
|
x = math.floor(x)
|
|
if x <= 0 then return nil end
|
|
return x
|
|
end
|
|
|
|
RegisterNetEvent("turfwar:bank:deposit", function(token, amount)
|
|
local src = source
|
|
|
|
local ok, err = RequireATM(src, token)
|
|
if not ok then
|
|
print(("^1[ATM BLOCK]^7 deposit src=%s reason=%s"):format(src, err))
|
|
return
|
|
end
|
|
|
|
amount = clampAmount(amount)
|
|
if not amount then return end
|
|
|
|
local cash = Cash.Get(src)
|
|
if cash < amount then
|
|
Notify(src, "Not enough cash.")
|
|
return
|
|
end
|
|
|
|
Cash.Set(src, cash - amount)
|
|
Cash.SetBank(src, Cash.GetBank(src) + amount)
|
|
end)
|
|
|
|
RegisterNetEvent("turfwar:bank:withdraw", function(token, amount)
|
|
local src = source
|
|
|
|
local ok, err = RequireATM(src, token)
|
|
if not ok then
|
|
print(("^1[ATM BLOCK]^7 withdraw src=%s reason=%s"):format(src, err))
|
|
return
|
|
end
|
|
|
|
amount = clampAmount(amount)
|
|
if not amount then return end
|
|
|
|
local bank = Cash.GetBank(src)
|
|
if bank < amount then
|
|
Notify(src, "Not enough money in bank.")
|
|
return
|
|
end
|
|
|
|
Cash.SetBank(src, bank - amount)
|
|
Cash.Set(src, Cash.Get(src) + amount)
|
|
end)
|
|
|
|
RegisterNetEvent("turfwar:atm:balance", function(token)
|
|
local src = source
|
|
local ok = RequireATM(src, token)
|
|
if not ok then return end
|
|
PushMoney(src)
|
|
end)
|
|
|
|
-- =========================================================
|
|
-- Admin
|
|
-- =========================================================
|
|
|
|
RegisterCommand("cash", function(src)
|
|
if src == 0 then
|
|
print("[turfwar] /cash can only be used in-game.")
|
|
return
|
|
end
|
|
|
|
TriggerClientEvent('chat:addMessage', src, {
|
|
args = { "Money", ("Cash: $%d | Bank: $%d"):format(Cash.Get(src), Cash.GetBank(src)) }
|
|
})
|
|
end, false)
|
|
|
|
RegisterCommand("addcash", function(src, args)
|
|
if src == 0 then
|
|
print("[turfwar] Use in-game: /addcash <amount>")
|
|
return
|
|
end
|
|
Cash.Add(src, tonumber(args[1]) or 0)
|
|
end, false)
|
|
|
|
RegisterCommand("setcash", function(src, args)
|
|
if src == 0 then
|
|
print("[turfwar] Use in-game: /setcash <amount>")
|
|
return
|
|
end
|
|
Cash.Set(src, tonumber(args[1]) or 0)
|
|
end, false)
|