Upload files to "client"
This commit is contained in:
parent
72bf3dbec1
commit
4cfaeceae8
202
client/environment.lua
Normal file
202
client/environment.lua
Normal 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
132
client/finance.lua
Normal 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 it’s 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
73
client/gang_ff.lua
Normal 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 player’s 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,
|
||||||
|
-- you’d need per-victim explosion damage (not provided directly here).
|
||||||
|
-- So we leave explosives allowed by default.
|
||||||
|
end)
|
||||||
253
client/gang_shops.lua
Normal file
253
client/gang_shops.lua
Normal 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
196
client/gangbank.lua
Normal 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)
|
||||||
Loading…
Reference in New Issue
Block a user