-- client/main.lua print("^2[turfwar]^7 CLIENT main.lua LOADED (uniforms + join points + HQ blips + turfs + capture ping + leaderboard + HQ respawn + capture HUD colors)") local currentGang = 0 local isPolice = false local POLICE_GANG_ID = 3 local hqBlips = {} -- Turfs client cache local Turfs = {} -- keyed by turfId local TurfBlips = {} -- { [turfId] = { radiusBlip=..., centerBlip=... } } local SecondsToCapture = 60 -- Capture ping throttling (prevents spamming server every frame) local nextCapturePing = {} -- [turfId] = gameTimeMs -- Leaderboard cache (rank only) local GangLeaderboard = {} --======================================================== -- 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 EnsurePlayerModel(modelName) if not modelName or modelName == "" then return false end local model = joaat(modelName) if not IsModelInCdimage(model) or not IsModelValid(model) then print(("^1[turfwar]^7 INVALID model: %s"):format(modelName)) return false end RequestModel(model) local timeout = GetGameTimer() + 8000 while not HasModelLoaded(model) do Wait(0) if GetGameTimer() > timeout then print(("^1[turfwar]^7 TIMEOUT loading model: %s"):format(modelName)) return false end end SetPlayerModel(PlayerId(), model) SetModelAsNoLongerNeeded(model) Wait(200) return true end local function ClearAllProps(ped) for i = 0, 7 do ClearPedProp(ped, i) end end RegisterNetEvent("turfwar:leaderboard:update", function(payload) SendNUIMessage({ type = "turfwar:leaderboard:update", payload = payload }) end) CreateThread(function() Wait(1500) TriggerServerEvent("turfwar:leaderboard:request") end) --======================================================== -- Freemode helpers (hair color init WITHOUT wiping outfit) --======================================================== local function IsFreemodePed(ped) local m = GetEntityModel(ped) return (m == joaat("mp_m_freemode_01") or m == joaat("mp_f_freemode_01")) end -- One-time init per model swap. This fixes the "-1 hair color" issue. local freemodeInited = false local function InitFreemodePaletteOnce(ped) if freemodeInited then return end if not IsFreemodePed(ped) then return end -- If already initialized, don't touch anything. local c1 = GetPedHairColor(ped) if c1 ~= nil and c1 ~= -1 then freemodeInited = true return end -- IMPORTANT: This can change appearance, so we do it ONCE right after model set, -- and then we will apply your uniform components immediately after. SetPedDefaultComponentVariation(ped) SetPedHeadBlendData( ped, 0, 0, 0, -- shapeFirst, shapeSecond, shapeThird 0, 0, 0, -- skinFirst, skinSecond, skinThird 0.5, 0.5, 0.0, false ) for i = 0, 19 do SetPedFaceFeature(ped, i, 0.0) end freemodeInited = true print("^2[turfwar]^7 Freemode palette init completed (hair colors should work now).") end local function ApplyHairColorIfAny(ped, uniform) if not uniform or not uniform.hairColor then return end if not IsFreemodePed(ped) then -- Hair color is only meaningful on freemode peds (most other peds ignore it) return end local primary = tonumber(uniform.hairColor.primary) local highlight = tonumber(uniform.hairColor.highlight) if primary == nil then return end if highlight == nil then highlight = primary end -- Hats can visually hide hair; don't force, just clear if they want hair visible. if uniform.forceHairVisible then ClearPedProp(ped, 0) -- hat SetPedComponentVariation(ped, 1, 0, 0, 0) -- mask end local beforeP = GetPedHairColor(ped) local beforeH = GetPedHairHighlightColor(ped) SetPedHairColor(ped, primary, highlight) local afterP = GetPedHairColor(ped) local afterH = GetPedHairHighlightColor(ped) print(("[turfwar] hairColor %s/%s -> %s/%s (requested %d/%d)") :format(tostring(beforeP), tostring(beforeH), tostring(afterP), tostring(afterH), primary, highlight)) end --======================================================== -- Model Correction --======================================================== RegisterCommand("tw_modelname", function() local ped = PlayerPedId() local m = GetEntityModel(ped) local known = { "mp_m_freemode_01", "mp_f_freemode_01", "player_zero", "player_one", "player_two", "s_m_y_cop_01", "s_m_y_sheriff_01", "s_m_y_hwaycop_01", "s_m_y_ranger_01", "s_m_m_security_01", } print(("[turfwar] model hash=%d"):format(m)) for _, name in ipairs(known) do if m == GetHashKey(name) then print(("[turfwar] model name=%s"):format(name)) return end end print("[turfwar] model name=UNKNOWN (not in quick list)") end, false) --======================================================== -- Capture HUD state + gang color styling --======================================================== local captureHud = { active = false, turfId = nil } local function rgbToCss(rgb, fallback) if type(rgb) ~= "table" then return fallback end local r = tonumber(rgb[1]) or tonumber(rgb.r) local g = tonumber(rgb[2]) or tonumber(rgb.g) local b = tonumber(rgb[3]) or tonumber(rgb.b) if not r or not g or not b then return fallback end return ("rgb(%d,%d,%d)"):format(r, g, b) end local function gangColorCss(gangId, fallback) local g = (Config and Config.GANGS and Config.GANGS[tonumber(gangId) or 0]) or nil return rgbToCss(g and g.rgb, fallback) end local function CaptureHUD_Style(contestingGang, ownerGang) SendNUIMessage({ type = "capture:style", fill = gangColorCss(contestingGang, "rgba(255,255,255,0.85)"), bg = gangColorCss(ownerGang, "rgba(255,255,255,0.14)") }) end local function CaptureHUD_Start(turfId) local t = Turfs[turfId] if not t then return end captureHud.active = true captureHud.turfId = turfId local owner = tonumber(t.owner) or 0 SendNUIMessage({ type = "capture:start", turfName = t.name or turfId, fill = gangColorCss(currentGang, "rgba(255,255,255,0.85)"), bg = gangColorCss(owner, "rgba(255,255,255,0.14)") }) end local function CaptureHUD_Stop() if not captureHud.active then return end captureHud.active = false captureHud.turfId = nil SendNUIMessage({ type = "capture:stop" }) end local function CaptureHUD_Set(progress) if not captureHud.active then return end local p = tonumber(progress) or 0 if p < 0 then p = 0 end local t = (SecondsToCapture > 0) and (p / SecondsToCapture) or 0 if t < 0 then t = 0 end if t > 1 then t = 1 end SendNUIMessage({ type = "capture:set", t = t }) end RegisterNetEvent("turfwar:captureHint", function(msg) BeginTextCommandThefeedPost("STRING") AddTextComponentSubstringPlayerName(msg) EndTextCommandThefeedPostTicker(false, false) SendNUIMessage({ type="capture:hint", text=msg }) end) RegisterNetEvent("turfwar:capturePaused", function(turfId, paused) SendNUIMessage({ type="capture:paused", paused = paused and true or false }) end) --======================================================== -- Bulletproof capture helpers --======================================================== local function InTurfZone(turfId) local t = Turfs[turfId] if not t or not t.center then return false end local ped = PlayerPedId() if not ped or ped == 0 then return false end local cx = tonumber(t.center.x) local cy = tonumber(t.center.y) local cz = tonumber(t.center.z) local radius = tonumber(t.radius) or 0.0 if not cx or not cy or not cz or radius <= 0.0 then return false end local p = GetEntityCoords(ped) local d = #(p - vector3(cx, cy, cz)) return d <= radius end local function EligibleForCapture(turfId) local t = Turfs[turfId] if not t then return false end local owner = tonumber(t.owner) or 0 return (currentGang ~= 0) and (owner ~= currentGang) end --======================================================== -- On-foot state reporting (server uses this to block vehicle capturing) --======================================================== CreateThread(function() while true do Wait(350) local ped = PlayerPedId() if ped and ped ~= 0 then local onFoot = not IsPedInAnyVehicle(ped, false) TriggerServerEvent("turfwar:setOnFoot", onFoot) end end end) --======================================================== -- Police: no wanted stars (Gang 3) --======================================================== local function UpdatePoliceWantedState() isPolice = (currentGang == POLICE_GANG_ID) if isPolice then SetMaxWantedLevel(0) ClearPlayerWantedLevel(PlayerId()) else SetMaxWantedLevel(5) end end CreateThread(function() while true do Wait(500) if isPolice then local pid = PlayerId() if GetPlayerWantedLevel(pid) ~= 0 then ClearPlayerWantedLevel(pid) end end end end) --======================================================== -- Player arrow color (your minimap arrow) --======================================================== local function UpdatePlayerBlipColor(gangId) local blip = GetMainPlayerBlipId() if not blip or blip == 0 then return end local gang = Config.GANGS and Config.GANGS[gangId] local color = (gang and gang.blipColor) or 0 SetBlipSprite(blip, 6) ShowHeadingIndicatorOnBlip(blip, true) SetBlipColour(blip, color) SetBlipScale(blip, 1.0) SetBlipAsShortRange(blip, false) end --======================================================== -- Uniform application (Male/Female variants + safe setters) -- Drop-in section for client/main.lua --======================================================== local ApplyGangUniform -- forward declaration local SafeSetComponent -- forward declaration local SafeSetProp -- forward declaration local ReapplyUniformForSeconds -- forward declaration -- --------------------------------------------------------- -- Safe setters (validate drawable/texture limits) -- --------------------------------------------------------- SafeSetComponent = function(ped, compId, drawable, texture) compId = tonumber(compId) or 0 drawable = tonumber(drawable) or 0 texture = tonumber(texture) or 0 local maxDraw = GetNumberOfPedDrawableVariations(ped, compId) if maxDraw <= 0 then print(("[turfwar] comp %d has no drawables (model mismatch?)"):format(compId)) return false end if drawable < 0 or drawable >= maxDraw then print(("[turfwar] INVALID comp %d drawable %d (max %d) - SKIP"):format(compId, drawable, maxDraw)) return false end local maxTex = GetNumberOfPedTextureVariations(ped, compId, drawable) if maxTex <= 0 then maxTex = 1 end if texture < 0 or texture >= maxTex then print(("[turfwar] INVALID comp %d tex %d (max %d) -> clamp"):format(compId, texture, maxTex)) texture = math.max(0, math.min(texture, maxTex - 1)) end SetPedComponentVariation(ped, compId, drawable, texture, 0) return true end SafeSetProp = function(ped, propId, drawable, texture) propId = tonumber(propId) or 0 drawable = tonumber(drawable) or -1 texture = tonumber(texture) or 0 -- drawable < 0 means "remove this prop" if drawable < 0 then ClearPedProp(ped, propId) return true end local maxDraw = GetNumberOfPedPropDrawableVariations(ped, propId) if maxDraw <= 0 then print(("[turfwar] prop %d has no drawables"):format(propId)) return false end if drawable >= maxDraw then print(("[turfwar] INVALID prop %d drawable %d (max %d) - SKIP"):format(propId, drawable, maxDraw)) return false end local maxTex = GetNumberOfPedPropTextureVariations(ped, propId, drawable) if maxTex <= 0 then maxTex = 1 end if texture < 0 or texture >= maxTex then print(("[turfwar] INVALID prop %d tex %d (max %d) -> clamp"):format(propId, texture, maxTex)) texture = math.max(0, math.min(texture, maxTex - 1)) end ClearPedProp(ped, propId) SetPedPropIndex(ped, propId, drawable, texture, true) return true end -- --------------------------------------------------------- -- ApplyGangUniform -- Supports: -- uniform.components / uniform.props (legacy) -- uniform.components_m / uniform.props_m -- uniform.components_f / uniform.props_f -- -- IMPORTANT: -- - Hair is now player-controlled: DO NOT apply component 2 or hairColor here. -- - Do NOT force Config.FREEMODE_MODEL here (appearance system sets model). -- - Only swap model if uniform.model is explicitly set. -- --------------------------------------------------------- ApplyGangUniform = function(gangId) local uniform = Config.UNIFORMS and Config.UNIFORMS[gangId] if not uniform then return end local ped = PlayerPedId() if not ped or ped == 0 then return end -- Optional: uniform-specific model override ONLY local usedModel = nil if uniform.model and uniform.model ~= "" then usedModel = uniform.model end local beforeModel = GetEntityModel(ped) if usedModel then local changed = EnsurePlayerModel(usedModel) ped = PlayerPedId() local afterModel = GetEntityModel(ped) print(("[turfwar] model swap %s -> %s (requested %s) changed=%s") :format(tostring(beforeModel), tostring(afterModel), tostring(usedModel), tostring(changed))) if changed then freemodeInited = false end end -- Refresh ped after any model swap ped = PlayerPedId() if not ped or ped == 0 then return end -- Determine gender by current model local model = GetEntityModel(ped) local isFemale = (model == joaat("mp_f_freemode_01")) -- Pick the correct component/prop lists -- Backward compatible with legacy `components/props` local comps = uniform.components local props = uniform.props if isFemale then comps = uniform.components_f or comps props = uniform.props_f or props else comps = uniform.components_m or comps props = uniform.props_m or props end -- Ensure freemode palette once (doesn't wipe outfit every time) InitFreemodePaletteOnce(ped) -- Clear props first (if configured) if uniform.clearProps then ClearAllProps(ped) end -- Apply components (safe) if comps then for _, comp in ipairs(comps) do -- Hair is player-controlled now; ignore comp 2 even if present in data if tonumber(comp.id) ~= 2 then SafeSetComponent(ped, comp.id, comp.drawable, comp.texture or 0) end end end -- Apply props (safe) if props then for _, prop in ipairs(props) do SafeSetProp(ped, prop.id, prop.drawable, prop.texture or 0) end end -- Hair color is player-controlled now; do not apply from uniform -- ApplyHairColorIfAny(ped, uniform) print(("^2[turfwar]^7 Applied uniform: %s (gangId=%s, gender=%s)"):format( uniform.label or "Uniform", tostring(gangId), isFemale and "female" or "male" )) end -- --------------------------------------------------------- -- Reapply helper (prevents other scripts briefly overriding) -- --------------------------------------------------------- ReapplyUniformForSeconds = function(gangId, seconds) local endTime = GetGameTimer() + ((tonumber(seconds) or 0) * 1000) CreateThread(function() while GetGameTimer() < endTime do ApplyGangUniform(gangId) Wait(500) end end) end -- Your existing ApplyUniformSoon can stay as-is, but included here for completeness local function ApplyUniformSoon(gangId) CreateThread(function() Wait(Config.UNIFORM_APPLY_DELAY or 700) ApplyGangUniform(gangId) -- Helps when other resources overwrite clothing right after spawn ReapplyUniformForSeconds(gangId, 4) end) end --======================================================== -- HQ Respawn teleport --======================================================== local function TeleportToHQ(spawn) if not spawn then return end local ped = PlayerPedId() if not ped or ped == 0 then return end local x, y, z = spawn.x, spawn.y, spawn.z local h = spawn.h or 0.0 DoScreenFadeOut(250) while not IsScreenFadedOut() do Wait(0) end RequestCollisionAtCoord(x, y, z) SetEntityCoordsNoOffset(ped, x, y, z, false, false, false) SetEntityHeading(ped, h) local t = GetGameTimer() + 2500 while not HasCollisionLoadedAroundEntity(ped) and GetGameTimer() < t do Wait(0) end DoScreenFadeIn(250) end --======================================================== -- HQ Blips (always visible) --======================================================== local function CreateHQBlips() for _, b in ipairs(hqBlips) do if DoesBlipExist(b) then RemoveBlip(b) end end hqBlips = {} for _, jp in ipairs(Config.JOIN_POINTS or {}) do local gang = (Config.GANGS or {})[jp.gangId] local blip = AddBlipForCoord(jp.pos.x, jp.pos.y, jp.pos.z) SetBlipSprite(blip, 58) SetBlipScale(blip, 0.9) SetBlipColour(blip, gang and gang.blipColor or 0) SetBlipDisplay(blip, 4) SetBlipAsShortRange(blip, false) BeginTextCommandSetBlipName("STRING") AddTextComponentString(jp.label or ("Gang " .. tostring(jp.gangId))) EndTextCommandSetBlipName(blip) table.insert(hqBlips, blip) end print(("^2[turfwar]^7 HQ blips created: %d"):format(#hqBlips)) end --======================================================== -- Turf blips --======================================================== local function ClearTurfBlips() for _, b in pairs(TurfBlips) do if b.radiusBlip and DoesBlipExist(b.radiusBlip) then RemoveBlip(b.radiusBlip) end if b.centerBlip and DoesBlipExist(b.centerBlip) then RemoveBlip(b.centerBlip) end end TurfBlips = {} end local function EnsureTurfBlip(turfId, turf) if TurfBlips[turfId] then return end if not turf or not turf.center then return end local radius = tonumber(turf.radius) or 0.0 if radius <= 0.0 then return end local cx = tonumber(turf.center.x) or 0.0 local cy = tonumber(turf.center.y) or 0.0 local cz = tonumber(turf.center.z) or 0.0 local center = vector3(cx, cy, cz) local r = AddBlipForRadius(center.x, center.y, center.z, radius) SetBlipAlpha(r, 80) SetBlipDisplay(r, 4) SetBlipAsShortRange(r, true) local c = AddBlipForCoord(center.x, center.y, center.z) SetBlipSprite(c, 84) SetBlipScale(c, 0.75) SetBlipDisplay(c, 4) SetBlipAsShortRange(c, true) BeginTextCommandSetBlipName("STRING") AddTextComponentString(turf.name or turfId) EndTextCommandSetBlipName(c) TurfBlips[turfId] = { radiusBlip = r, centerBlip = c } local owner = tonumber(turf.owner) or 0 local col = ((Config.GANGS or {})[owner] and (Config.GANGS or {})[owner].blipColor) or 0 SetBlipColour(r, col) SetBlipColour(c, col) end local function UpdateTurfBlipColor(turfId) local b = TurfBlips[turfId] local t = Turfs[turfId] if not b or not t then return end local owner = tonumber(t.owner) or 0 local col = ((Config.GANGS or {})[owner] and (Config.GANGS or {})[owner].blipColor) or 0 if b.radiusBlip and DoesBlipExist(b.radiusBlip) then SetBlipColour(b.radiusBlip, col) end if b.centerBlip and DoesBlipExist(b.centerBlip) then SetBlipColour(b.centerBlip, col) end end --======================================================== -- Leaderboard HUD (compact) --======================================================== local BLIP_RGB = { [0] = {255, 255, 255}, [2] = { 60, 200, 60}, [3] = { 70, 120, 255}, [5] = {255, 220, 60}, [7] = {190, 90, 255}, [40] = { 40, 40, 40}, } local function getGangName(gangId) local g = Config and Config.GANGS and Config.GANGS[gangId] return (g and g.name) or ("Gang " .. tostring(gangId)) end local function getGangRGB(gangId) local g = Config and Config.GANGS and Config.GANGS[gangId] local blip = (g and tonumber(g.blipColor)) or 0 return BLIP_RGB[blip] or {255, 255, 255} end local function drawText(x, y, text, r, g, b, a, scale) SetTextFont(4) SetTextScale(scale, scale) SetTextColour(r, g, b, a) SetTextOutline() BeginTextCommandDisplayText("STRING") AddTextComponentSubstringPlayerName(text) EndTextCommandDisplayText(x, y) end CreateThread(function() while true do Wait(0) if not GangLeaderboard or #GangLeaderboard == 0 then goto continue end local maxRows = math.min(#GangLeaderboard, 8) local x = 0.018 local y = 0.185 local titleScale = 0.32 local rowScale = 0.28 local rowH = 0.018 local padX = 0.008 local padY = 0.008 local panelW = 0.14 local panelH = padY + 0.020 + (maxRows * rowH) + padY DrawRect(x + panelW/2, y + panelH/2, panelW, panelH, 0, 0, 0, 105) drawText(x + padX, y + padY, "Gang Wealth (Rank)", 255, 255, 255, 235, titleScale) local startY = y + padY + 0.020 for i = 1, maxRows do local gangId = GangLeaderboard[i] local name = getGangName(gangId) local r, g, b = table.unpack(getGangRGB(gangId)) local rowY = startY + ((i - 1) * rowH) DrawRect(x + panelW/2, rowY + 0.009, panelW, rowH, 255, 255, 255, 8) drawText(x + padX, rowY, ("%d."):format(i), 235, 235, 235, 235, rowScale) drawText(x + padX + 0.020, rowY, name, r, g, b, 245, rowScale) end ::continue:: end end) --======================================================== -- Net Events --======================================================== RegisterNetEvent("turfwar:gangUpdate", function(gangId) currentGang = tonumber(gangId) or 0 UpdatePoliceWantedState() CaptureHUD_Stop() Notify(("Gang set to: %s"):format((Config.GANGS[currentGang] and Config.GANGS[currentGang].name) or "Unknown")) ApplyUniformSoon(currentGang) UpdatePlayerBlipColor(currentGang) end) RegisterNetEvent("turfwar:setFaction", function(gangId) currentGang = tonumber(gangId) or 0 UpdatePoliceWantedState() CaptureHUD_Stop() ApplyUniformSoon(currentGang) UpdatePlayerBlipColor(currentGang) end) RegisterNetEvent("turfwar:snapshot", function(payload, secondsToCapture) SecondsToCapture = tonumber(secondsToCapture) or SecondsToCapture Turfs = payload or {} nextCapturePing = {} if captureHud.active and captureHud.turfId and not Turfs[captureHud.turfId] then CaptureHUD_Stop() end ClearTurfBlips() for turfId, turf in pairs(Turfs) do EnsureTurfBlip(turfId, turf) UpdateTurfBlipColor(turfId) end end) RegisterNetEvent("turfwar:turfUpdate", function(turfId, owner, progress, contestingGang) if not Turfs[turfId] then return end owner = tonumber(owner) or 0 local cont = tonumber(contestingGang) or 0 local prog = math.max(0, tonumber(progress) or 0) Turfs[turfId].owner = owner Turfs[turfId].progress = prog Turfs[turfId].contestingGang = cont EnsureTurfBlip(turfId, Turfs[turfId]) UpdateTurfBlipColor(turfId) local inZone = InTurfZone(turfId) local eligible = EligibleForCapture(turfId) if currentGang == 0 or not inZone or not eligible then if captureHud.active and captureHud.turfId == turfId then CaptureHUD_Stop() end return end if cont == currentGang and cont ~= 0 then if (not captureHud.active) or (captureHud.turfId ~= turfId) then CaptureHUD_Start(turfId) end CaptureHUD_Style(cont, owner) CaptureHUD_Set(prog) else if captureHud.active and captureHud.turfId == turfId then CaptureHUD_Stop() end end end) RegisterNetEvent("turfwar:turfCaptured", function(turfId, newOwner) if not Turfs[turfId] then return end Turfs[turfId].owner = newOwner Turfs[turfId].progress = 0 Turfs[turfId].contestingGang = 0 EnsureTurfBlip(turfId, Turfs[turfId]) UpdateTurfBlipColor(turfId) if captureHud.active and captureHud.turfId == turfId then CaptureHUD_Stop() end local name = Turfs[turfId].name or turfId local gangName = (Config.GANGS[newOwner] and Config.GANGS[newOwner].name) or ("Gang " .. tostring(newOwner)) Notify(("Turf captured: %s -> %s"):format(name, gangName)) end) RegisterNetEvent("turfwar:gangLeaderboard", function(rankedGangIds) GangLeaderboard = rankedGangIds or {} end) -- HQ spawn response local awaitingHQ = false RegisterNetEvent('turfwar:myHQSpawn', function(spawn) awaitingHQ = false TeleportToHQ(spawn) end) -- Server tells us to ensure at least N wanted stars (anti-snipe capture penalty) RegisterNetEvent("turfwar:wanted:setMin", function(minStars) minStars = tonumber(minStars) or 0 if minStars <= 0 then return end -- Police are forced to 0 wanted in your script; ignore if isPolice then return end local pid = PlayerId() local cur = GetPlayerWantedLevel(pid) or 0 if cur < minStars then SetMaxWantedLevel(5) SetPlayerWantedLevel(pid, minStars, false) SetPlayerWantedLevelNow(pid, false) end end) RegisterNetEvent("turfwar:shop:syncGangRequest", function(nonce) local g = 0 if exports and exports.turfwar and exports.turfwar.GetCurrentGang then g = exports.turfwar:GetCurrentGang() end TriggerServerEvent("turfwar:shop:syncGangResponse", nonce, g) end) --======================================================== -- Spawn / resource start --======================================================== AddEventHandler("playerSpawned", function() ApplyUniformSoon(currentGang) UpdatePoliceWantedState() CaptureHUD_Stop() CreateThread(function() Wait(1000) UpdatePlayerBlipColor(currentGang) end) CreateThread(function() Wait(1500) TriggerServerEvent('turfwar:getMyHQSpawn') Wait(2000) TriggerServerEvent('turfwar:getMyHQSpawn') end) end) AddEventHandler("onClientResourceStart", function(res) if res ~= GetCurrentResourceName() then return end CreateThread(function() Wait(500) CreateHQBlips() TriggerServerEvent("turfwar:clientReady") TriggerServerEvent("turfwar:requestFaction") Wait(1500) TriggerServerEvent("turfwar:requestGangLeaderboard") ApplyUniformSoon(currentGang) UpdatePoliceWantedState() Wait(1000) UpdatePlayerBlipColor(currentGang) Wait(1200) TriggerServerEvent('turfwar:getMyHQSpawn') end) end) --======================================================== -- Join points (press E) --======================================================== CreateThread(function() while true do local ped = PlayerPedId() local pcoords = GetEntityCoords(ped) local sleep = 500 for _, jp in ipairs(Config.JOIN_POINTS or {}) do local dist = #(pcoords - jp.pos) if dist < 20.0 then sleep = 0 DrawMarker(1, jp.pos.x, jp.pos.y, jp.pos.z - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.2, 1.2, 0.5, 255, 255, 255, 120, false, true, 2, false, nil, nil, false) if dist < (Config.JOIN_RADIUS or 2.0) then Draw3DText(jp.pos.x, jp.pos.y, jp.pos.z + 0.5, ("~w~Press ~g~E~w~ to join ~b~%s~w~"):format(jp.label)) if IsControlJustPressed(0, 38) then TriggerServerEvent("turfwar:setGang", jp.gangId) end end end end Wait(sleep) end end) --======================================================== -- Turf visuals + capture ping (HUD is driven by turfUpdate) --======================================================== CreateThread(function() while true do Wait(0) if not Turfs or next(Turfs) == nil then Wait(500) goto continue end local ped = PlayerPedId() local p = GetEntityCoords(ped) for turfId, t in pairs(Turfs) do if not t or not t.center then goto continue_turf end local cx = tonumber(t.center.x) local cy = tonumber(t.center.y) local cz = tonumber(t.center.z) local radius = tonumber(t.radius) or 0.0 if not cx or not cy or not cz or radius <= 0.0 then goto continue_turf end local center = vector3(cx, cy, cz) local owner = tonumber(t.owner) or 0 local d = #(p - center) local inZone = (d <= radius) local inZoneAndEligible = (currentGang ~= 0) and inZone and (owner ~= currentGang) -- ✅ Presence ping: counts defenders + attackers as "in zone" if currentGang ~= 0 and inZone then local now = GetGameTimer() nextCapturePing["_presence_" .. turfId] = nextCapturePing["_presence_" .. turfId] or 0 if now >= nextCapturePing["_presence_" .. turfId] then nextCapturePing["_presence_" .. turfId] = now + 1000 TriggerServerEvent("turfwar:notePresence", turfId) end end -- Capture ping (only when eligible) if inZoneAndEligible then local now = GetGameTimer() if not nextCapturePing[turfId] or now >= nextCapturePing[turfId] then nextCapturePing[turfId] = now + 1000 TriggerServerEvent("turfwar:attemptCapture", turfId) end end if captureHud.active and captureHud.turfId == turfId and not inZone then CaptureHUD_Stop() end if d < (radius + 40.0) then DrawMarker(1, center.x, center.y, center.z - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 2.0, 0.6, 255, 255, 255, 80, false, true, 2, false, nil, nil, false) if d < 25.0 then local prog = tonumber(t.progress) or 0 local cont = tonumber(t.contestingGang) or 0 local ownerName = (Config.GANGS[owner] and Config.GANGS[owner].name) or "Neutral" local text = ("%s\nOwner: %s"):format(t.name or turfId, ownerName) if cont ~= 0 then local contName = (Config.GANGS[cont] and Config.GANGS[cont].name) or ("Gang " .. cont) text = text .. ("\nContesting: %s (%d/%d)"):format(contName, prog, SecondsToCapture) end Draw3DText(center.x, center.y, center.z + 1.2, text) end end ::continue_turf:: end ::continue:: end end) --======================================================== -- UI Cash update --======================================================== local function SetPauseMenuMoney(cash, bank) cash = math.floor(tonumber(cash) or 0) bank = math.floor(tonumber(bank) or 0) StatSetInt(`MP0_WALLET_BALANCE`, cash, true) StatSetInt(`MP1_WALLET_BALANCE`, cash, true) StatSetInt(`MP0_BANK_BALANCE`, bank, true) StatSetInt(`MP1_BANK_BALANCE`, bank, true) StatSetInt(`BANK_BALANCE`, bank, true) end RegisterNetEvent("turfwar:money:update", function(cash, bank) SetPauseMenuMoney(cash, bank) end) CreateThread(function() Wait(3000) TriggerServerEvent("turfwar:money:request") end) --======================================================== -- Environment cash pickup spawn (client) --======================================================== RegisterNetEvent("environment:spawnCashPickup", function(amount, coords) local model = GetHashKey("prop_cash_pile_01") RequestModel(model) while not HasModelLoaded(model) do Wait(0) end CreateObject(model, coords.x, coords.y, coords.z - 0.9, true, true, false) print(("[Cash Drop] $%s at %.2f %.2f %.2f"):format(amount, coords.x, coords.y, coords.z)) end) --======================================================== -- Death watcher (requests HQ after respawn) --======================================================== CreateThread(function() local wasDead = false while true do Wait(200) local ped = PlayerPedId() if not DoesEntityExist(ped) then goto continue end local dead = IsEntityDead(ped) if dead and not wasDead then CaptureHUD_Stop() CreateThread(function() if awaitingHQ then return end awaitingHQ = true while IsEntityDead(PlayerPedId()) do Wait(250) end Wait(250) TriggerServerEvent('turfwar:getMyHQSpawn') end) end wasDead = dead ::continue:: end end)