-- 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 ") 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 ") return end Cash.Set(src, tonumber(args[1]) or 0) end, false)