Upload files to "client"

This commit is contained in:
tanthius 2026-02-12 04:15:14 +00:00
parent 72bf3dbec1
commit 4cfaeceae8
5 changed files with 856 additions and 0 deletions

202
client/environment.lua Normal file
View File

@ -0,0 +1,202 @@
print("^2[environment]^7 Cash drop system loaded")
-- =====================
-- CONFIG
-- =====================
local MAX_CASH = 200 -- Maximum possible drop
local WEIGHT_POWER = 2.2 -- Higher = lower values more common
local CHECK_INTERVAL = 1000 -- How often we scan peds (ms)
local PICKUP_RADIUS = 1.25
local PICKUP_CHECK_MS = 200
local PICKUP_COOLDOWN = 750 -- ms between pickup attempts per drop
-- Prevent duplicate drops per ped handle
local processedPeds = {}
-- dropId -> { obj=entity, coords=vector3, amount=number }
local spawnedDrops = {}
local pickupCooldown = {} -- dropId -> lastAttempt GetGameTimer()
-- =====================
-- WEIGHTED RANDOM CASH
-- =====================
local function getWeightedCash()
local roll = math.random()
local weighted = roll ^ WEIGHT_POWER
return math.floor(weighted * MAX_CASH)
end
-- =====================
-- PED DEATH MONITOR
-- =====================
CreateThread(function()
math.randomseed(GetGameTimer())
while true do
Wait(CHECK_INTERVAL)
local peds = GetGamePool("CPed")
for _, ped in ipairs(peds) do
if DoesEntityExist(ped)
and not IsPedAPlayer(ped)
and IsEntityDead(ped)
and not processedPeds[ped] then
processedPeds[ped] = true
local cash = getWeightedCash()
if cash > 0 then
local c = GetEntityCoords(ped)
TriggerServerEvent("environment:pedCashDrop", cash, { x = c.x, y = c.y, z = c.z })
end
end
end
end
end)
-- =====================
-- ARG PARSER (server compatibility)
-- =====================
local function parseSpawnArgs(p1, p2, p3, p4, p5)
-- Preferred (your server currently uses):
-- (dropId, amount, coordsTable)
-- Also supports:
-- (dropId, amount, x, y, z)
-- (amount, coordsTable) [fallback id]
-- (amount, x, y, z) [fallback id]
local dropId, amount, coords
-- (dropId, amount, coordsTable) OR (dropId, amount, x, y, z)
if type(p1) == "number" and (type(p2) == "number" or type(p2) == "string") then
local maybeDropId = tonumber(p1)
local maybeAmount = tonumber(p2) or 0
if type(p3) == "table" and p3.x and p3.y and p3.z then
dropId = maybeDropId
amount = maybeAmount
coords = vector3(p3.x, p3.y, p3.z)
return dropId, amount, coords
elseif type(p3) == "number" and type(p4) == "number" and type(p5) == "number" then
dropId = maybeDropId
amount = maybeAmount
coords = vector3(p3, p4, p5)
return dropId, amount, coords
end
end
-- (amount, coordsTable) OR (amount, x, y, z) -> local fallback id
local maybeAmount = tonumber(p1) or 0
if type(p2) == "table" and p2.x and p2.y and p2.z then
dropId = GetGameTimer() + math.random(1, 999999)
amount = maybeAmount
coords = vector3(p2.x, p2.y, p2.z)
return dropId, amount, coords
elseif type(p2) == "number" and type(p3) == "number" and type(p4) == "number" then
dropId = GetGameTimer() + math.random(1, 999999)
amount = maybeAmount
coords = vector3(p2, p3, p4)
return dropId, amount, coords
end
return nil, nil, nil
end
-- =====================
-- CASH PICKUP SPAWN
-- =====================
RegisterNetEvent("environment:spawnCashPickup", function(p1, p2, p3, p4, p5)
local dropId, amount, coords = parseSpawnArgs(p1, p2, p3, p4, p5)
if not dropId or not coords then
print(("^1[environment]^7 spawnCashPickup: bad args types: %s %s %s %s %s")
:format(type(p1), type(p2), type(p3), type(p4), type(p5)))
return
end
amount = tonumber(amount) or 0
if amount <= 0 then return end
-- If already exists, delete old one first
local existing = spawnedDrops[dropId]
if existing and existing.obj and DoesEntityExist(existing.obj) then
DeleteEntity(existing.obj)
end
local model = GetHashKey("prop_cash_pile_01")
RequestModel(model)
while not HasModelLoaded(model) do Wait(0) end
local obj = CreateObject(model, coords.x, coords.y, coords.z - 0.9, false, false, false)
PlaceObjectOnGroundProperly(obj)
FreezeEntityPosition(obj, true)
SetEntityAsMissionEntity(obj, true, true)
spawnedDrops[dropId] = { obj = obj, coords = coords, amount = amount }
-- print(("[environment] Cash drop #%s: $%s at %.2f %.2f %.2f"):format(dropId, amount, coords.x, coords.y, coords.z))
end)
-- =====================
-- WALK-OVER COLLECT LOOP
-- =====================
CreateThread(function()
while true do
Wait(PICKUP_CHECK_MS)
local ped = PlayerPedId()
if not ped or ped == 0 then goto continue end
if IsEntityDead(ped) then goto continue end
local pcoords = GetEntityCoords(ped)
local now = GetGameTimer()
for dropId, data in pairs(spawnedDrops) do
if data and data.coords and data.obj and DoesEntityExist(data.obj) then
local dist = #(pcoords - data.coords)
if dist <= PICKUP_RADIUS then
local last = pickupCooldown[dropId] or 0
if (now - last) > PICKUP_COOLDOWN then
pickupCooldown[dropId] = now
TriggerServerEvent("environment:pickupCash", dropId)
end
end
end
end
::continue::
end
end)
-- =====================
-- FEEDBACK
-- =====================
RegisterNetEvent("environment:pickupFeedback", function(amount)
amount = tonumber(amount) or 0
if amount <= 0 then return end
PlaySoundFrontend(-1, "PICK_UP", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
BeginTextCommandThefeedPost("STRING")
AddTextComponentSubstringPlayerName(("+~g~$%d~s~"):format(amount))
EndTextCommandThefeedPostTicker(false, true)
end)
-- =====================
-- REMOVE / DESPAWN
-- =====================
RegisterNetEvent("environment:removeCashPickup", function(dropId)
dropId = tonumber(dropId)
if not dropId then return end
local data = spawnedDrops[dropId]
if data and data.obj and DoesEntityExist(data.obj) then
-- Helpful in some cases where entity control is finicky
NetworkRequestControlOfEntity(data.obj)
SetEntityAsMissionEntity(data.obj, true, true)
DeleteEntity(data.obj)
end
spawnedDrops[dropId] = nil
pickupCooldown[dropId] = nil
end)

132
client/finance.lua Normal file
View File

@ -0,0 +1,132 @@
-- client/finance.lua
print("^2[turfwar]^7 finance client loaded")
if IsDuplicityVersion() then return end
Config = Config or {}
Config.Finance = Config.Finance or {}
local uiOpen = false
local atmToken = nil
local lastUse = 0
local USE_COOLDOWN = 800
local function CanUse()
local now = GetGameTimer()
if (now - lastUse) < USE_COOLDOWN then return false end
lastUse = now
return true
end
local function Notify(msg)
BeginTextCommandThefeedPost("STRING")
AddTextComponentSubstringPlayerName(msg)
EndTextCommandThefeedPostTicker(false, false)
end
local function Draw3DText(coords, text)
local onScreen, x, y = World3dToScreen2d(coords.x, coords.y, coords.z)
if not onScreen then return end
SetTextScale(0.35, 0.35)
SetTextFont(4)
SetTextCentre(true)
SetTextEntry("STRING")
AddTextComponentSubstringPlayerName(text)
DrawText(x, y)
end
local function ForceCloseATMUI()
uiOpen = false
atmToken = nil
SetNuiFocus(false, false)
SendNUIMessage({ type = "atm:close" })
end
AddEventHandler("onClientResourceStart", function(res)
if res ~= GetCurrentResourceName() then return end
Wait(800)
ForceCloseATMUI()
end)
AddEventHandler("playerSpawned", function()
Wait(800)
ForceCloseATMUI()
end)
-- Server says OK -> open UI
RegisterNetEvent("turfwar:atm:openGranted", function(token)
atmToken = token
uiOpen = true
SetNuiFocus(true, true)
SendNUIMessage({ type = "atm:open" })
TriggerServerEvent("turfwar:atm:balance", atmToken)
end)
RegisterNetEvent("turfwar:atm:openDenied", function(reason)
print(("^1[turfwar]^7 ATM denied: %s"):format(tostring(reason)))
Notify(("~r~ATM denied~s~ (%s)"):format(tostring(reason)))
end)
-- NUI callbacks
RegisterNUICallback("atm:close", function(_, cb)
ForceCloseATMUI()
cb(true)
end)
RegisterNUICallback("atm:deposit", function(data, cb)
if not uiOpen or not atmToken then cb(false) return end
TriggerServerEvent("turfwar:bank:deposit", atmToken, data.amount)
cb(true)
end)
RegisterNUICallback("atm:withdraw", function(data, cb)
if not uiOpen or not atmToken then cb(false) return end
TriggerServerEvent("turfwar:bank:withdraw", atmToken, data.amount)
cb(true)
end)
-- Only update UI when its open
RegisterNetEvent("turfwar:money:update", function(cash, bank)
if not uiOpen then return end
SendNUIMessage({ type = "atm:balances", cash = cash, bank = bank })
end)
-- ATM Detection (CLIENT!)
CreateThread(function()
while true do
Wait(0)
local ped = PlayerPedId()
if ped == 0 then Wait(500) goto continue end
local pcoords = GetEntityCoords(ped)
local dist = Config.Finance.INTERACT_DIST or 1.5
local key = Config.Finance.INTERACT_KEY or 38
local found = false
for _, model in ipairs(Config.Finance.ATM_MODELS or {}) do
-- ✅ correct signature
local atm = GetClosestObjectOfType(pcoords.x, pcoords.y, pcoords.z, 1.6, model, false, false, false)
if atm and atm ~= 0 then
found = true
local acoords = GetEntityCoords(atm)
Draw3DText(vector3(acoords.x, acoords.y, acoords.z + 0.95), "~g~E~w~ Use ATM")
if #(pcoords - acoords) <= dist then
if IsControlJustPressed(0, key) and CanUse() then
-- ✅ send payload server expects
TriggerServerEvent("turfwar:atm:open", {
x = acoords.x, y = acoords.y, z = acoords.z,
model = model
})
end
end
end
end
if not found then Wait(150) end
::continue::
end
end)

73
client/gang_ff.lua Normal file
View File

@ -0,0 +1,73 @@
-- server/gang_ff.lua
print("^2[turfwar]^7 server/gang_ff.lua loaded (server-side same-gang FF block)")
-- Requires PlayerGang to be GLOBAL (see patch in server/main.lua)
PlayerGang = PlayerGang or {}
local function gangOf(src)
return tonumber(PlayerGang[src]) or 0
end
local function sameGangNonNeutral(a, b)
local ga = gangOf(a)
local gb = gangOf(b)
return ga ~= 0 and ga == gb
end
-- Helper: resolve victim player from weaponDamageEvent payload
local function resolveVictimPlayerFromWeaponData(data)
if not data then return nil end
-- Different builds/resources may use different keys; try common ones
local netId =
data.hitGlobalId or
data.hitEntity or
data.hitNetId or
data.entity or
data.victimNetId
if type(netId) ~= "number" then return nil end
local ent = NetworkGetEntityFromNetworkId(netId)
if not ent or ent == 0 or not DoesEntityExist(ent) then return nil end
if not IsEntityAPed(ent) then return nil end
if not IsPedAPlayer(ent) then return nil end
-- For player peds, owner should be that players source
local victimSrc = NetworkGetEntityOwner(ent)
if not victimSrc or victimSrc == 0 then return nil end
return victimSrc
end
-- Blocks bullets/melee/etc
AddEventHandler("weaponDamageEvent", function(sender, data)
-- sender is attacker (source)
if not sender or sender == 0 then return end
local victimSrc = resolveVictimPlayerFromWeaponData(data)
if not victimSrc then return end
if sameGangNonNeutral(sender, victimSrc) then
CancelEvent()
-- Uncomment for debug:
-- print(("[turfwar_ff] BLOCK weaponDamageEvent: %s -> %s gang=%s"):format(sender, victimSrc, gangOf(sender)))
end
end)
-- Blocks explosions (grenades, rockets, etc)
AddEventHandler("explosionEvent", function(sender, ev)
if not sender or sender == 0 then return end
if not ev then return end
-- explosionEvent has a 'pos' and can have 'ownerNetId' etc; there isn't always a single victim.
-- But it can still stop friendly grenade spam within gang by blocking the explosion entirely.
-- If you don't use explosives, you can remove this handler.
local attackerGang = gangOf(sender)
if attackerGang == 0 then return end
-- If you want to allow explosives vs other gangs but still protect same-gang,
-- youd need per-victim explosion damage (not provided directly here).
-- So we leave explosives allowed by default.
end)

253
client/gang_shops.lua Normal file
View File

@ -0,0 +1,253 @@
-- client/gang_shops.lua
print("^2[turfwar]^7 gang_shops.lua loaded (client)")
Config = Config or {}
Config.Shops = Config.Shops or {}
Config.GANGS = Config.GANGS or {}
local currentGang = 0
local lastBal = 0
-- =========================
-- Helpers
-- =========================
local function Notify(msg)
BeginTextCommandThefeedPost("STRING")
AddTextComponentSubstringPlayerName(msg)
EndTextCommandThefeedPostTicker(false, false)
end
local function Draw3DText(x, y, z, text)
local onScreen, _x, _y = World3dToScreen2d(x, y, z)
if not onScreen then return end
SetTextScale(0.35, 0.35)
SetTextFont(4)
SetTextProportional(1)
SetTextEntry("STRING")
SetTextCentre(1)
AddTextComponentString(text)
DrawText(_x, _y)
end
local function allowedGang()
if Config.Shops.AllowedGang then return Config.Shops.AllowedGang(currentGang) end
return currentGang ~= 0 and currentGang ~= 3
end
local function gangRGB(gangId)
local g = Config.GANGS and Config.GANGS[gangId]
if g and g.rgb and #g.rgb == 3 then
return g.rgb[1], g.rgb[2], g.rgb[3]
end
return 255, 255, 255
end
-- =========================
-- Receive gang updates
-- Hook into your existing system.
-- =========================
RegisterNetEvent("turfwar:myGang", function(gangId)
currentGang = tonumber(gangId) or 0
TriggerServerEvent("turfwar:myGang:set", currentGang) -- keep server cache aligned
end)
-- Fallback if your server broadcasts playerGang (you can remove if not needed)
RegisterNetEvent("turfwar:playerGang", function(src, gangId)
if src == GetPlayerServerId(PlayerId()) then
currentGang = tonumber(gangId) or 0
TriggerServerEvent("turfwar:myGang:set", currentGang)
end
end)
RegisterNetEvent("turfwar:shop:notify", function(msg)
Notify(msg)
end)
RegisterNetEvent("turfwar:shop:balance", function(bal)
lastBal = tonumber(bal) or 0
end)
RegisterNetEvent("turfwar:gangbank:requestRefresh", function()
-- If you already have a gangbank refresh event, call it here.
-- Otherwise harmless.
TriggerServerEvent("turfwar:gangbank:request")
end)
-- =========================
-- Grant weapon/ammo (after server approves purchase)
-- =========================
RegisterNetEvent("turfwar:shop:grantWeapon", function(item, gangId)
if not item then return end
local ped = PlayerPedId()
if item.armor then
local amt = tonumber(item.armorAmount) or 50
local cur = GetPedArmour(ped)
SetPedArmour(ped, math.min(100, cur + amt))
return
end
local wep = item.weapon
if not wep then return end
local hash = GetHashKey(wep)
if not HasPedGotWeapon(ped, hash, false) then
GiveWeaponToPed(ped, hash, 0, false, true)
end
local ammo = tonumber(item.ammo) or 0
if ammo > 0 then
AddAmmoToPed(ped, hash, ammo)
end
end)
RegisterNetEvent("turfwar:shop:grantAmmo", function(item)
if not item or not item.weapon then return end
local ped = PlayerPedId()
local hash = GetHashKey(item.weapon)
local ammo = tonumber(item.ammo) or 0
if ammo > 0 then
AddAmmoToPed(ped, hash, ammo)
end
end)
-- =========================
-- Spawn purchased vehicle (after server approves)
-- =========================
local function loadModel(model)
local hash = type(model) == "number" and model or GetHashKey(model)
if not IsModelInCdimage(hash) then return nil end
RequestModel(hash)
local timeout = GetGameTimer() + 5000
while not HasModelLoaded(hash) do
Wait(10)
if GetGameTimer() > timeout then return nil end
end
return hash
end
RegisterNetEvent("turfwar:shop:spawnPurchasedVehicle", function(item, gangId)
if not item or not item.model then return end
local ped = PlayerPedId()
local p = GetEntityCoords(ped)
local h = GetEntityHeading(ped)
local hash = loadModel(item.model)
if not hash then
Notify("~r~Vehicle model failed to load.")
return
end
-- Spawn in front of player
local forward = (Config.Shops.VehicleSpawn and Config.Shops.VehicleSpawn.forward) or 6.0
local up = (Config.Shops.VehicleSpawn and Config.Shops.VehicleSpawn.up) or 0.2
local fw = GetEntityForwardVector(ped)
local spawn = vector3(p.x + fw.x * forward, p.y + fw.y * forward, p.z + up)
local veh = CreateVehicle(hash, spawn.x, spawn.y, spawn.z, h, true, false)
SetModelAsNoLongerNeeded(hash)
if veh and veh ~= 0 then
SetVehicleOnGroundProperly(veh)
-- Gang color
local r,g,b = gangRGB(gangId)
SetVehicleCustomPrimaryColour(veh, r, g, b)
SetVehicleCustomSecondaryColour(veh, r, g, b)
-- Nice defaults
SetVehicleDirtLevel(veh, 0.0)
SetVehicleEngineOn(veh, true, true, false)
-- Warp player in
TaskWarpPedIntoVehicle(ped, veh, -1)
end
end)
-- =========================
-- “UI”: simple chat/menu commands (starter)
-- Later we can move to NUI
-- =========================
local function printShopList(title, list)
TriggerServerEvent("turfwar:shop:getBalance")
Wait(150)
print(("^3[turfwar]^7 === %s === (Gang Balance: %s)"):format(title, lastBal))
for _, it in ipairs(list) do
print(("^3[turfwar]^7 - %s ^2$%d^7 (id: %s)"):format(it.label or it.id, it.price or 0, it.id))
end
end
RegisterCommand("gshop_weapons", function()
if not allowedGang() then Notify("~r~Gang shop not available for your faction.") return end
printShopList("WEAPON SHOP", Config.Shops.WeaponList or {})
Notify("Open console (F8) to see weapon list. Buy: /gshop_buyweapon <id>")
end)
RegisterCommand("gshop_ammo", function()
if not allowedGang() then Notify("~r~Gang shop not available for your faction.") return end
printShopList("AMMO SHOP", Config.Shops.AmmoList or {})
Notify("Open console (F8) to see ammo list. Buy: /gshop_buyammo <id>")
end)
RegisterCommand("gshop_vehicles", function()
if not allowedGang() then Notify("~r~Gang shop not available for your faction.") return end
printShopList("VEHICLE SHOP", Config.Shops.VehicleList or {})
Notify("Open console (F8) to see vehicle list. Buy: /gshop_buyveh <id>")
end)
RegisterCommand("gshop_buyweapon", function(_, args)
if not allowedGang() then return end
local id = args[1]
if not id then Notify("Usage: /gshop_buyweapon <id>") return end
TriggerServerEvent("turfwar:shop:buyWeapon", id)
end)
RegisterCommand("gshop_buyammo", function(_, args)
if not allowedGang() then return end
local id = args[1]
if not id then Notify("Usage: /gshop_buyammo <id>") return end
TriggerServerEvent("turfwar:shop:buyAmmo", id)
end)
RegisterCommand("gshop_buyveh", function(_, args)
if not allowedGang() then return end
local id = args[1]
if not id then Notify("Usage: /gshop_buyveh <id>") return end
TriggerServerEvent("turfwar:shop:buyVehicle", id)
end)
-- =========================
-- World markers (optional)
-- Press E near configured coords to open lists
-- =========================
CreateThread(function()
while true do
Wait(0)
if not allowedGang() then goto continue end
local ped = PlayerPedId()
local p = GetEntityCoords(ped)
local function handleLocations(locs, hint, cmd)
for _, s in ipairs(locs or {}) do
local d = #(p - s.coords)
if d < (Config.Shops.MarkerDist or 25.0) then
DrawMarker(1, s.coords.x, s.coords.y, s.coords.z - 1.0, 0,0,0, 0,0,0, 1.2,1.2,0.8, 255,255,255, 120, false,true,2, false,nil,nil,false)
if d < (Config.Shops.UseDist or 2.0) then
Draw3DText(s.coords.x, s.coords.y, s.coords.z + 0.3, ("~w~%s~n~~y~[E]~w~ %s"):format(s.label or "Shop", hint))
if IsControlJustPressed(0, Config.Shops.InteractKey or 38) then
ExecuteCommand(cmd)
end
end
end
end
end
handleLocations(Config.Shops.WeaponShopLocations, "Open Weapon Shop", "gshop_weapons")
handleLocations(Config.Shops.VehicleShopLocations, "Open Vehicle Shop", "gshop_vehicles")
::continue::
end
end)

196
client/gangbank.lua Normal file
View File

@ -0,0 +1,196 @@
-- client/gangbank.lua
print("^2[turfwar]^7 gangbank.lua loaded (client, robust + rgb)")
local currentGang = 0
local lastBalance = 0
local lastRequestAt = 0
local REQUEST_COOLDOWN_MS = 800
local function isHiddenGang(gangId)
gangId = tonumber(gangId) or 0
return gangId == 0 or gangId == 3
end
-- ✅ Get gang RGB from Config.GANGS (supports numeric or string keys)
local function getGangRGB(gangId)
gangId = tonumber(gangId) or 0
local fallback = { 255, 255, 255 }
if not (Config and Config.GANGS) then
return fallback
end
local g = Config.GANGS[gangId] or Config.GANGS[tostring(gangId)]
if g and type(g.rgb) == "table" then
local r = tonumber(g.rgb[1]) or tonumber(g.rgb.r)
local gg = tonumber(g.rgb[2]) or tonumber(g.rgb.g)
local b = tonumber(g.rgb[3]) or tonumber(g.rgb.b)
if r and gg and b then
return { r, gg, b }
end
end
return fallback
end
local function setLabelForGang(gangId)
local label = "Gang Bank"
gangId = tonumber(gangId) or 0
if Config and Config.GANGS then
local g = Config.GANGS[gangId] or Config.GANGS[tostring(gangId)]
if g and g.name then label = g.name end
end
local rgb = getGangRGB(gangId)
-- ✅ Send label + rgb so NUI can color it
SendNUIMessage({
type = "gangbank:label",
label = label,
gangId = gangId,
rgb = rgb
})
end
local function show()
SendNUIMessage({ type = "gangbank:show" })
end
local function hide()
SendNUIMessage({ type = "gangbank:hide" })
end
local function setBalance(balance)
local b = tonumber(balance) or 0
lastBalance = b
-- ✅ debug print shows the correct updated balance
print(("[GangBank] sending NUI gang=%d balance=%d"):format(currentGang, lastBalance))
local rgb = getGangRGB(currentGang)
-- ✅ Send rgb here too (in case update arrives without a label)
SendNUIMessage({
type = "gangbank:update",
gangId = currentGang,
balance = lastBalance,
rgb = rgb
})
end
local function applyVisibility()
if isHiddenGang(currentGang) then
hide()
return
end
setLabelForGang(currentGang)
show()
end
local function requestMyGangBank(force)
local now = GetGameTimer()
if not force and (now - lastRequestAt) < REQUEST_COOLDOWN_MS then return end
lastRequestAt = now
TriggerServerEvent("turfwar:gangbank:request")
end
-- Retry helper (handles "request too early" / race conditions)
local function requestWithRetries(maxTries, delayMs)
CreateThread(function()
maxTries = tonumber(maxTries) or 6
delayMs = tonumber(delayMs) or 800
for i = 1, maxTries do
requestMyGangBank(true)
Wait(delayMs)
if currentGang ~= 0 then
-- If gangs can legitimately be $0, you can remove this check.
if lastBalance > 0 then
return
end
end
end
end)
end
-- Ask shortly after load (covers resource restart / joining late)
CreateThread(function()
Wait(900)
requestWithRetries(8, 900)
end)
-- ------------------------------------------------------------
-- Gang changes (server sends BOTH of these in your main.lua)
-- ------------------------------------------------------------
-- Broadcast to all clients: (src, gangId)
RegisterNetEvent("turfwar:playerGang", function(src, gangId)
if tonumber(src) ~= tonumber(GetPlayerServerId(PlayerId())) then return end
currentGang = tonumber(gangId) or 0
print(("^5[GangBank]^7 turfwar:playerGang -> gang=%d"):format(currentGang))
applyVisibility()
requestWithRetries(6, 700)
end)
-- Sent directly to the player: (gangId)
RegisterNetEvent("turfwar:gangUpdate", function(gangId)
currentGang = tonumber(gangId) or 0
print(("^5[GangBank]^7 turfwar:gangUpdate -> gang=%d"):format(currentGang))
applyVisibility()
requestWithRetries(6, 700)
end)
-- Compatibility: some parts of your system also send this
RegisterNetEvent("turfwar:setFaction", function(gangId)
currentGang = tonumber(gangId) or 0
print(("^5[GangBank]^7 turfwar:setFaction -> gang=%d"):format(currentGang))
applyVisibility()
requestWithRetries(6, 700)
end)
-- ------------------------------------------------------------
-- Balance update from server
-- ------------------------------------------------------------
RegisterNetEvent("turfwar:gangbank:update", function(gangId, balance)
gangId = tonumber(gangId) or 0
balance = tonumber(balance) or 0
-- ⚠️ IMPORTANT RACE FIX:
-- If we ALREADY know we're in a real gang, ignore "gangId=0" updates
if currentGang ~= 0 and gangId == 0 then
print(("^3[GangBank]^7 Ignored update gangId=0 (known gang=%d) balance=%d"):format(currentGang, balance))
return
end
currentGang = gangId
print(("^2[GangBank]^7 UPDATE gang=%d balance=%d"):format(currentGang, balance))
if isHiddenGang(currentGang) then
hide()
return
end
setLabelForGang(currentGang)
show()
setBalance(balance)
end)
-- ------------------------------------------------------------
-- Resource restart
-- ------------------------------------------------------------
AddEventHandler("onClientResourceStart", function(res)
if res ~= GetCurrentResourceName() then return end
currentGang = 0
lastBalance = 0
hide()
CreateThread(function()
Wait(900)
requestWithRetries(8, 900)
end)
end)