Upload files to "server"
This commit is contained in:
parent
964a6fc764
commit
1ece4a3f2f
88
server/appearance.lua
Normal file
88
server/appearance.lua
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
-- server/appearance.lua
|
||||
print("^2[turfwar]^7 appearance.lua loaded (server)")
|
||||
|
||||
local function getLicense(src)
|
||||
for _, id in ipairs(GetPlayerIdentifiers(src)) do
|
||||
if id:sub(1, 8) == "license:" then
|
||||
return id
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function defaultRow(license)
|
||||
return {
|
||||
license = license,
|
||||
gender = "m",
|
||||
hair_drawable = 0,
|
||||
hair_texture = 0,
|
||||
hair_color = 0,
|
||||
hair_highlight = 0
|
||||
}
|
||||
end
|
||||
|
||||
RegisterNetEvent("turfwar:appearance:request", function()
|
||||
local src = source
|
||||
local license = getLicense(src)
|
||||
if not license then
|
||||
print(("^1[turfwar]^7 appearance: missing license for src %d"):format(src))
|
||||
return
|
||||
end
|
||||
|
||||
local row = exports.oxmysql:singleSync(
|
||||
"SELECT gender, hair_drawable, hair_texture, hair_color, hair_highlight FROM turfwar_appearance WHERE license = ?",
|
||||
{ license }
|
||||
)
|
||||
|
||||
if not row then
|
||||
local def = defaultRow(license)
|
||||
exports.oxmysql:insertSync(
|
||||
"INSERT INTO turfwar_appearance (license, gender, hair_drawable, hair_texture, hair_color, hair_highlight) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
{ def.license, def.gender, def.hair_drawable, def.hair_texture, def.hair_color, def.hair_highlight }
|
||||
)
|
||||
row = def
|
||||
end
|
||||
|
||||
TriggerClientEvent("turfwar:appearance:update", src, row)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("turfwar:appearance:setGender", function(gender)
|
||||
local src = source
|
||||
local license = getLicense(src)
|
||||
if not license then return end
|
||||
|
||||
gender = (gender == "f") and "f" or "m"
|
||||
|
||||
exports.oxmysql:updateSync(
|
||||
"UPDATE turfwar_appearance SET gender = ? WHERE license = ?",
|
||||
{ gender, license }
|
||||
)
|
||||
|
||||
local row = exports.oxmysql:singleSync(
|
||||
"SELECT gender, hair_drawable, hair_texture, hair_color, hair_highlight FROM turfwar_appearance WHERE license = ?",
|
||||
{ license }
|
||||
)
|
||||
TriggerClientEvent("turfwar:appearance:update", src, row)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("turfwar:appearance:setHair", function(drawable, texture, color, highlight)
|
||||
local src = source
|
||||
local license = getLicense(src)
|
||||
if not license then return end
|
||||
|
||||
drawable = tonumber(drawable) or 0
|
||||
texture = tonumber(texture) or 0
|
||||
color = tonumber(color) or 0
|
||||
highlight = tonumber(highlight) or color
|
||||
|
||||
exports.oxmysql:updateSync(
|
||||
"UPDATE turfwar_appearance SET hair_drawable=?, hair_texture=?, hair_color=?, hair_highlight=? WHERE license=?",
|
||||
{ drawable, texture, color, highlight, license }
|
||||
)
|
||||
|
||||
local row = exports.oxmysql:singleSync(
|
||||
"SELECT gender, hair_drawable, hair_texture, hair_color, hair_highlight FROM turfwar_appearance WHERE license = ?",
|
||||
{ license }
|
||||
)
|
||||
TriggerClientEvent("turfwar:appearance:update", src, row)
|
||||
end)
|
||||
104
server/atm_access.lua
Normal file
104
server/atm_access.lua
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
-- server/atm_access.lua
|
||||
print("^2[turfwar]^7 atm_access.lua loaded (server-side gatekeeper)")
|
||||
|
||||
Config = Config or {}
|
||||
Config.Finance = Config.Finance or {}
|
||||
|
||||
local USE_RADIUS = tonumber(Config.Finance.ATM_USE_RADIUS) or 1.8
|
||||
local TTL_MS = tonumber(Config.Finance.ATM_SESSION_TTL_MS) or 15000
|
||||
|
||||
local sessions = {}
|
||||
|
||||
local function dist3(a, b)
|
||||
local dx = (a.x - b.x)
|
||||
local dy = (a.y - b.y)
|
||||
local dz = (a.z - b.z)
|
||||
return math.sqrt(dx*dx + dy*dy + dz*dz)
|
||||
end
|
||||
|
||||
local function newToken()
|
||||
return ("%08x%08x"):format(math.random(0, 0xffffffff), math.random(0, 0xffffffff))
|
||||
end
|
||||
|
||||
local function getPlayerCoords(src)
|
||||
local ped = GetPlayerPed(src)
|
||||
if not ped or ped == 0 then return nil end
|
||||
local c = GetEntityCoords(ped)
|
||||
return { x = c.x, y = c.y, z = c.z }
|
||||
end
|
||||
|
||||
RegisterNetEvent("turfwar:atm:open", function(payload)
|
||||
local src = source
|
||||
if type(payload) ~= "table" then
|
||||
TriggerClientEvent("turfwar:atm:openDenied", src, "bad_payload")
|
||||
return
|
||||
end
|
||||
|
||||
local x, y, z = tonumber(payload.x), tonumber(payload.y), tonumber(payload.z)
|
||||
local model = payload.model
|
||||
|
||||
if not x or not y or not z then
|
||||
TriggerClientEvent("turfwar:atm:openDenied", src, "bad_coords")
|
||||
return
|
||||
end
|
||||
|
||||
local okModel = false
|
||||
for _, m in ipairs(Config.Finance.ATM_MODELS or {}) do
|
||||
if m == model then okModel = true break end
|
||||
end
|
||||
if not okModel then
|
||||
TriggerClientEvent("turfwar:atm:openDenied", src, "bad_model")
|
||||
return
|
||||
end
|
||||
|
||||
local pc = getPlayerCoords(src)
|
||||
if not pc then
|
||||
TriggerClientEvent("turfwar:atm:openDenied", src, "no_ped")
|
||||
return
|
||||
end
|
||||
|
||||
local atmC = { x = x, y = y, z = z }
|
||||
if dist3(pc, atmC) > (USE_RADIUS + 0.75) then
|
||||
TriggerClientEvent("turfwar:atm:openDenied", src, "too_far")
|
||||
return
|
||||
end
|
||||
|
||||
local token = newToken()
|
||||
sessions[src] = {
|
||||
token = token,
|
||||
expires = GetGameTimer() + TTL_MS,
|
||||
atm = { x = x, y = y, z = z, model = model }
|
||||
}
|
||||
|
||||
TriggerClientEvent("turfwar:atm:openGranted", src, token)
|
||||
end)
|
||||
|
||||
local function RequireATM(src, token, verbose)
|
||||
local s = sessions[src]
|
||||
if not s then return false, "no_session" end
|
||||
if token ~= s.token then return false, "bad_token" end
|
||||
if GetGameTimer() > (s.expires or 0) then
|
||||
sessions[src] = nil
|
||||
return false, "expired"
|
||||
end
|
||||
|
||||
local pc = getPlayerCoords(src)
|
||||
if not pc then return false, "no_ped" end
|
||||
|
||||
local atmC = { x = s.atm.x, y = s.atm.y, z = s.atm.z }
|
||||
if dist3(pc, atmC) > (USE_RADIUS + 0.75) then
|
||||
if verbose then
|
||||
print(("^1[ATM BLOCK]^7 src=%d moved away from ATM"):format(src))
|
||||
end
|
||||
sessions[src] = nil
|
||||
return false, "moved_away"
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
exports("RequireATM", RequireATM)
|
||||
|
||||
AddEventHandler("playerDropped", function()
|
||||
sessions[source] = nil
|
||||
end)
|
||||
304
server/cash.lua
Normal file
304
server/cash.lua
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
-- 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)
|
||||
160
server/finance.lua
Normal file
160
server/finance.lua
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
print("^2[turfwar]^7 finance server loaded")
|
||||
|
||||
-- Uses your existing Cash API:
|
||||
-- Cash.Get(src), Cash.Add(src, amt), Cash.Remove(src, amt)
|
||||
-- Cash.GetBank(src), Cash.SetBank(src, amt)
|
||||
|
||||
local function Notify(src, msg)
|
||||
-- This is your existing chat/notify path
|
||||
TriggerClientEvent('turfwar:finance:notify', src, msg)
|
||||
end
|
||||
|
||||
local function Feed(src, kind, amount)
|
||||
-- NEW: GTA feed popup (client will render it)
|
||||
-- kind: "deposit" | "withdraw" | "balance"
|
||||
TriggerClientEvent('turfwar:finance:feed', src, kind, amount)
|
||||
end
|
||||
|
||||
local function PushMoney(src)
|
||||
-- Keep using your existing HUD/stat updater (in main.lua)
|
||||
TriggerClientEvent('turfwar:money:update', src, Cash.Get(src), Cash.GetBank(src))
|
||||
end
|
||||
|
||||
local function parseAmount(arg)
|
||||
local n = tonumber(arg)
|
||||
if not n then return nil end
|
||||
n = math.floor(n)
|
||||
if n <= 0 then return nil end
|
||||
return n
|
||||
end
|
||||
|
||||
-- Simple anti-spam
|
||||
local lastCmd = {}
|
||||
local CMD_COOLDOWN_MS = 700
|
||||
|
||||
local function canRun(src)
|
||||
local now = GetGameTimer()
|
||||
local last = lastCmd[src] or 0
|
||||
if (now - last) < CMD_COOLDOWN_MS then
|
||||
return false
|
||||
end
|
||||
lastCmd[src] = now
|
||||
return true
|
||||
end
|
||||
|
||||
AddEventHandler('playerDropped', function()
|
||||
lastCmd[source] = nil
|
||||
end)
|
||||
|
||||
-- Make sure Cash is loaded before commands are used
|
||||
CreateThread(function()
|
||||
Wait(0)
|
||||
if type(Cash) ~= "table" then
|
||||
print("^1[turfwar]^7 finance.lua: Cash table missing. Ensure cash.lua loads before finance.lua in fxmanifest.")
|
||||
return
|
||||
end
|
||||
if type(Cash.Get) ~= "function" or type(Cash.Add) ~= "function" or type(Cash.Remove) ~= "function" then
|
||||
print("^1[turfwar]^7 finance.lua: Missing Cash.Get/Add/Remove. Check cash.lua.")
|
||||
end
|
||||
if type(Cash.GetBank) ~= "function" or type(Cash.SetBank) ~= "function" then
|
||||
print("^1[turfwar]^7 finance.lua: Missing Cash.GetBank/SetBank. Check cash.lua.")
|
||||
end
|
||||
end)
|
||||
|
||||
-- ------------------------------------------------
|
||||
-- /deposit <amount>
|
||||
-- ------------------------------------------------
|
||||
RegisterCommand("deposit", function(src, args)
|
||||
if not src or src <= 0 then return end
|
||||
if not canRun(src) then return end
|
||||
|
||||
local amount = parseAmount(args[1])
|
||||
if not amount then
|
||||
Notify(src, "~r~Usage: /deposit <amount>")
|
||||
return
|
||||
end
|
||||
|
||||
local cash = Cash.Get(src)
|
||||
if cash < amount then
|
||||
Notify(src, "~r~Not enough cash.")
|
||||
return
|
||||
end
|
||||
|
||||
-- Move cash -> bank
|
||||
Cash.Remove(src, amount)
|
||||
Cash.SetBank(src, Cash.GetBank(src) + amount)
|
||||
|
||||
-- Update your HUD (no duplication)
|
||||
PushMoney(src)
|
||||
|
||||
-- NEW: GTA feed popup (single line)
|
||||
Feed(src, "deposit", amount)
|
||||
|
||||
-- Keep your existing notify too (optional; remove if you only want GTA feed)
|
||||
Notify(src, ("~g~Deposited $%d"):format(amount))
|
||||
end, false)
|
||||
|
||||
-- ------------------------------------------------
|
||||
-- /withdraw <amount>
|
||||
-- ------------------------------------------------
|
||||
RegisterCommand("withdraw", function(src, args)
|
||||
if not src or src <= 0 then return end
|
||||
if not canRun(src) then return end
|
||||
|
||||
local amount = parseAmount(args[1])
|
||||
if not amount then
|
||||
Notify(src, "~r~Usage: /withdraw <amount>")
|
||||
return
|
||||
end
|
||||
|
||||
local bank = Cash.GetBank(src)
|
||||
if bank < amount then
|
||||
Notify(src, "~r~Not enough in bank.")
|
||||
return
|
||||
end
|
||||
|
||||
-- Move bank -> cash
|
||||
Cash.SetBank(src, bank - amount)
|
||||
Cash.Add(src, amount)
|
||||
|
||||
-- Update your HUD (no duplication)
|
||||
PushMoney(src)
|
||||
|
||||
-- NEW: GTA feed popup (single line)
|
||||
Feed(src, "withdraw", amount)
|
||||
|
||||
-- Keep your existing notify too (optional)
|
||||
Notify(src, ("~g~Withdrew $%d"):format(amount))
|
||||
end, false)
|
||||
|
||||
-- ------------------------------------------------
|
||||
-- /balance
|
||||
-- ------------------------------------------------
|
||||
RegisterCommand("balance", function(src)
|
||||
if not src or src <= 0 then return end
|
||||
|
||||
local cash = Cash.Get(src)
|
||||
local bank = Cash.GetBank(src)
|
||||
|
||||
PushMoney(src)
|
||||
|
||||
-- Optional: feed (shows bank balance)
|
||||
Feed(src, "balance", bank)
|
||||
|
||||
Notify(src, ("~g~Cash:~w~ $%d ~b~Bank:~w~ $%d"):format(cash, bank))
|
||||
end, false)
|
||||
|
||||
|
||||
local atmOpenUntil = 0
|
||||
|
||||
local function OpenATMInterface()
|
||||
atmOpenUntil = GetGameTimer() + 15000 -- 15s window
|
||||
PlaySoundFrontend(-1, "ATM_WINDOW", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
|
||||
|
||||
BeginTextCommandThefeedPost("STRING")
|
||||
AddTextComponentSubstringPlayerName("~g~ATM~s~: /deposit <amt> /withdraw <amt> /balance")
|
||||
EndTextCommandThefeedPostTicker(false, false)
|
||||
end
|
||||
|
||||
-- When player presses E at ATM, call this instead of the old help:
|
||||
-- OpenATMInterface()
|
||||
245
server/gang_shops.lua
Normal file
245
server/gang_shops.lua
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
-- server/gang_shops.lua
|
||||
print("^2[turfwar]^7 gang_shops.lua loaded (server)")
|
||||
|
||||
Config = Config or {}
|
||||
Config.Shops = Config.Shops or {}
|
||||
|
||||
-- IMPORTANT:
|
||||
-- This assumes you already have:
|
||||
-- - player gang stored server-side somewhere, OR a callback/event to fetch it.
|
||||
--
|
||||
-- Below uses a simple server cache:
|
||||
local playerGang = {} -- [src] = gangId
|
||||
|
||||
-- You likely already broadcast this elsewhere.
|
||||
-- Hook into your existing gang set event if you have one.
|
||||
RegisterNetEvent("turfwar:myGang:set", function(gangId)
|
||||
local src = source
|
||||
playerGang[src] = tonumber(gangId) or 0
|
||||
end)
|
||||
|
||||
AddEventHandler("playerDropped", function()
|
||||
local src = source
|
||||
playerGang[src] = nil
|
||||
end)
|
||||
|
||||
local function getGangId(src)
|
||||
return tonumber(playerGang[src]) or 0
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- DB helpers (gang bank)
|
||||
-- =========================
|
||||
local function dbFetchBalance(gangId, cb)
|
||||
exports.oxmysql:query("SELECT balance FROM turfwar_gang_accounts WHERE gang_id = ? LIMIT 1", { gangId }, function(rows)
|
||||
local bal = 0
|
||||
if rows and rows[1] and rows[1].balance then bal = tonumber(rows[1].balance) or 0 end
|
||||
cb(bal)
|
||||
end)
|
||||
end
|
||||
|
||||
local function dbEnsureRow(gangId, cb)
|
||||
exports.oxmysql:query("INSERT IGNORE INTO turfwar_gang_accounts (gang_id, balance) VALUES (?, 0)", { gangId }, function()
|
||||
cb()
|
||||
end)
|
||||
end
|
||||
|
||||
local function dbDeduct(gangId, amount, cb)
|
||||
amount = tonumber(amount) or 0
|
||||
if amount <= 0 then cb(false, "bad_amount") return end
|
||||
|
||||
dbEnsureRow(gangId, function()
|
||||
exports.oxmysql:query("UPDATE turfwar_gang_accounts SET balance = balance - ? WHERE gang_id = ? AND balance >= ?", {
|
||||
amount, gangId, amount
|
||||
}, function(result)
|
||||
-- oxmysql UPDATE result varies by version; handle common cases:
|
||||
local changed = 0
|
||||
if type(result) == "table" and result.affectedRows then changed = result.affectedRows end
|
||||
if type(result) == "number" then changed = result end
|
||||
|
||||
cb(changed and changed > 0, changed and changed > 0 and nil or "insufficient")
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Lookup helpers
|
||||
-- =========================
|
||||
local function findWeaponItem(itemId)
|
||||
if not Config.Shops or not Config.Shops.WeaponList then return nil end
|
||||
for _, it in ipairs(Config.Shops.WeaponList) do
|
||||
if it.id == itemId then return it end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function findAmmoItem(itemId)
|
||||
if not Config.Shops or not Config.Shops.AmmoList then return nil end
|
||||
for _, it in ipairs(Config.Shops.AmmoList) do
|
||||
if it.id == itemId then return it end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function findVehicleItem(itemId)
|
||||
if not Config.Shops or not Config.Shops.VehicleList then return nil end
|
||||
for _, it in ipairs(Config.Shops.VehicleList) do
|
||||
if it.id == itemId then return it end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Purchase: weapons
|
||||
-- =========================
|
||||
RegisterNetEvent("turfwar:shop:buyWeapon", function(itemId)
|
||||
local src = source
|
||||
local gangId = getGangId(src)
|
||||
|
||||
if not (Config.Shops.AllowedGang and Config.Shops.AllowedGang(gangId)) then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~You can't use the gang shop.")
|
||||
return
|
||||
end
|
||||
|
||||
local item = findWeaponItem(itemId)
|
||||
if not item then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~Invalid item.")
|
||||
return
|
||||
end
|
||||
|
||||
local price = tonumber(item.price) or 0
|
||||
if price < 0 then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~Bad price config.")
|
||||
return
|
||||
end
|
||||
|
||||
dbDeduct(gangId, price, function(ok, reason)
|
||||
if not ok then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~Not enough gang cash.")
|
||||
return
|
||||
end
|
||||
|
||||
-- Grant on client (server-authoritative purchase)
|
||||
TriggerClientEvent("turfwar:shop:grantWeapon", src, item, gangId)
|
||||
TriggerClientEvent("turfwar:shop:notify", src, ("~g~Purchased: ~s~%s"):format(item.label or item.id))
|
||||
TriggerClientEvent("turfwar:gangbank:requestRefresh", src) -- optional hook
|
||||
end)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("turfwar:shop:buyAmmo", function(itemId)
|
||||
local src = source
|
||||
local gangId = getGangId(src)
|
||||
|
||||
if not (Config.Shops.AllowedGang and Config.Shops.AllowedGang(gangId)) then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~You can't use the gang shop.")
|
||||
return
|
||||
end
|
||||
|
||||
local item = findAmmoItem(itemId)
|
||||
if not item then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~Invalid ammo item.")
|
||||
return
|
||||
end
|
||||
|
||||
local price = tonumber(item.price) or 0
|
||||
if price <= 0 then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~Bad price config.")
|
||||
return
|
||||
end
|
||||
|
||||
dbDeduct(gangId, price, function(ok)
|
||||
if not ok then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~Not enough gang cash.")
|
||||
return
|
||||
end
|
||||
|
||||
TriggerClientEvent("turfwar:shop:grantAmmo", src, item)
|
||||
TriggerClientEvent("turfwar:shop:notify", src, ("~g~Purchased: ~s~%s"):format(item.label or item.id))
|
||||
TriggerClientEvent("turfwar:gangbank:requestRefresh", src)
|
||||
end)
|
||||
end)
|
||||
|
||||
-- =========================
|
||||
-- Purchase: vehicles
|
||||
-- =========================
|
||||
RegisterNetEvent("turfwar:shop:buyVehicle", function(itemId)
|
||||
local src = source
|
||||
local gangId = getGangId(src)
|
||||
|
||||
if not (Config.Shops.AllowedGang and Config.Shops.AllowedGang(gangId)) then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~You can't use the gang shop.")
|
||||
return
|
||||
end
|
||||
|
||||
local item = findVehicleItem(itemId)
|
||||
if not item then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~Invalid vehicle.")
|
||||
return
|
||||
end
|
||||
|
||||
local price = tonumber(item.price)
|
||||
if price == nil then price = 0 end
|
||||
|
||||
-- Allow FREE vehicles (price = 0). Only reject negative.
|
||||
if price < 0 then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~Bad price config.")
|
||||
return
|
||||
end
|
||||
|
||||
local function afterPaid()
|
||||
-- Use your existing spawn-pad system
|
||||
if not VehicleSpawner or not VehicleSpawner.SpawnModelFor then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~VehicleSpawner not available.")
|
||||
return
|
||||
end
|
||||
|
||||
-- Support special model alias for "gang default"
|
||||
local model = item.model
|
||||
if model == "__GANG_DEFAULT__" then
|
||||
model = (Config.Vehicles and Config.Vehicles.gangModels and Config.Vehicles.gangModels[gangId])
|
||||
end
|
||||
|
||||
if type(model) ~= "string" or model == "" then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~No vehicle configured for your gang.")
|
||||
return
|
||||
end
|
||||
|
||||
local ok2, reason = VehicleSpawner.SpawnModelFor(src, model, { platePrefix = "G" })
|
||||
if not ok2 then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~Vehicle spawn failed.")
|
||||
return
|
||||
end
|
||||
|
||||
TriggerClientEvent("turfwar:shop:notify", src, ("~g~Purchased: ~s~%s"):format(item.label or item.id))
|
||||
TriggerClientEvent("turfwar:gangbank:requestRefresh", src)
|
||||
end
|
||||
|
||||
-- If free, don't touch the gang bank
|
||||
if price == 0 then
|
||||
afterPaid()
|
||||
return
|
||||
end
|
||||
|
||||
-- Deduct first (server authority)
|
||||
dbDeduct(gangId, price, function(ok)
|
||||
if not ok then
|
||||
TriggerClientEvent("turfwar:shop:notify", src, "~r~Not enough gang cash.")
|
||||
return
|
||||
end
|
||||
afterPaid()
|
||||
end)
|
||||
end)
|
||||
|
||||
|
||||
-- Optional: allow client to query gang balance (for UI)
|
||||
RegisterNetEvent("turfwar:shop:getBalance", function()
|
||||
local src = source
|
||||
local gangId = getGangId(src)
|
||||
if gangId <= 0 then
|
||||
TriggerClientEvent("turfwar:shop:balance", src, 0)
|
||||
return
|
||||
end
|
||||
dbFetchBalance(gangId, function(bal)
|
||||
TriggerClientEvent("turfwar:shop:balance", src, bal)
|
||||
end)
|
||||
end)
|
||||
Loading…
Reference in New Issue
Block a user