Upload files to "client"
This commit is contained in:
parent
4910c39102
commit
293cf701c4
37
client/money_hud.lua
Normal file
37
client/money_hud.lua
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
print("^2[turfwar]^7 money_hud.lua loaded")
|
||||
|
||||
CreateThread(function()
|
||||
Wait(1000)
|
||||
SendNUIMessage({ type = "money:show" })
|
||||
end)
|
||||
|
||||
RegisterNetEvent('turfwar:money:update', function(cash, bank)
|
||||
SendNUIMessage({
|
||||
type = "money:update",
|
||||
cash = cash or 0,
|
||||
bank = bank or 0
|
||||
})
|
||||
end)
|
||||
|
||||
-- Optional: hide HUD when paused
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(250)
|
||||
if IsPauseMenuActive() then
|
||||
SendNUIMessage({ type = "money:hide" })
|
||||
else
|
||||
SendNUIMessage({ type = "money:show" })
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler('onClientResourceStart', function(resName)
|
||||
if resName ~= GetCurrentResourceName() then return end
|
||||
Wait(500)
|
||||
TriggerServerEvent('turfwar:money:request')
|
||||
end)
|
||||
|
||||
AddEventHandler('playerSpawned', function()
|
||||
Wait(500)
|
||||
TriggerServerEvent('turfwar:money:request')
|
||||
end)
|
||||
201
client/player_police.lua
Normal file
201
client/player_police.lua
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
-- client/player_police.lua
|
||||
print("^2[turfwar]^7 client/player_police.lua loaded (police tracking)")
|
||||
|
||||
local PP = Config.PlayerPolice or {}
|
||||
local POLICE_GANG_ID = tonumber(PP.POLICE_GANG_ID) or 3
|
||||
|
||||
local isPolice = false
|
||||
local myStars = 0
|
||||
local myInterior = false
|
||||
|
||||
-- targets[serverId] = {stars, interior, lastPos, circleBlip, realtimeBlip, lastPingAt}
|
||||
local targets = {}
|
||||
|
||||
local function vec3(x,y,z) return vector3(x+0.0, y+0.0, z+0.0) end
|
||||
local function InInterior(ped) return (GetInteriorFromEntity(ped) ~= 0) end
|
||||
|
||||
local function ClearBlipSafe(blip)
|
||||
if blip and DoesBlipExist(blip) then RemoveBlip(blip) end
|
||||
end
|
||||
|
||||
local function EnsureTarget(src)
|
||||
if not targets[src] then
|
||||
targets[src] = {
|
||||
stars = 0,
|
||||
interior = false,
|
||||
lastPos = nil,
|
||||
circleBlip = nil,
|
||||
realtimeBlip = nil,
|
||||
lastPingAt = 0
|
||||
}
|
||||
end
|
||||
return targets[src]
|
||||
end
|
||||
|
||||
local function ClearTarget(src)
|
||||
local t = targets[src]
|
||||
if not t then return end
|
||||
ClearBlipSafe(t.circleBlip)
|
||||
ClearBlipSafe(t.realtimeBlip)
|
||||
targets[src] = nil
|
||||
end
|
||||
|
||||
local function UpdateVisual(src)
|
||||
if not isPolice then return end
|
||||
local t = targets[src]
|
||||
if not t then return end
|
||||
|
||||
local stars = tonumber(t.stars) or 0
|
||||
local interior = (t.interior == true)
|
||||
|
||||
-- 4-5 stars realtime, but interior forces "2-star mode"
|
||||
local effective = stars
|
||||
if stars >= 4 and interior then
|
||||
effective = 2
|
||||
end
|
||||
|
||||
if stars <= 0 then
|
||||
ClearBlipSafe(t.circleBlip); t.circleBlip = nil
|
||||
ClearBlipSafe(t.realtimeBlip); t.realtimeBlip = nil
|
||||
return
|
||||
end
|
||||
|
||||
if not t.lastPos then return end
|
||||
|
||||
-- clear visuals that don't match mode
|
||||
if effective <= 3 then
|
||||
ClearBlipSafe(t.realtimeBlip); t.realtimeBlip = nil
|
||||
end
|
||||
if effective >= 4 then
|
||||
ClearBlipSafe(t.circleBlip); t.circleBlip = nil
|
||||
end
|
||||
|
||||
if effective == 1 or effective == 2 then
|
||||
local radius = (effective == 1) and (PP.CIRCLE_RADIUS_1STAR or 220.0) or (PP.CIRCLE_RADIUS_2STAR or 160.0)
|
||||
|
||||
ClearBlipSafe(t.circleBlip)
|
||||
t.circleBlip = AddBlipForRadius(t.lastPos.x, t.lastPos.y, t.lastPos.z, radius)
|
||||
SetBlipAlpha(t.circleBlip, 90)
|
||||
|
||||
elseif effective == 3 then
|
||||
-- ping exact position every update (client sends every 2s)
|
||||
local now = GetGameTimer()
|
||||
if (now - (t.lastPingAt or 0)) > 250 then
|
||||
t.lastPingAt = now
|
||||
|
||||
local blip = AddBlipForCoord(t.lastPos.x, t.lastPos.y, t.lastPos.z)
|
||||
SetBlipSprite(blip, PP.PING_BLIP_SPRITE or 161)
|
||||
SetBlipScale(blip, PP.PING_BLIP_SCALE or 1.0)
|
||||
BeginTextCommandSetBlipName("STRING")
|
||||
AddTextComponentString("Wanted Suspect (Ping)")
|
||||
EndTextCommandSetBlipName(blip)
|
||||
|
||||
CreateThread(function()
|
||||
Wait(PP.PING_BLIP_LIFETIME_MS or 1500)
|
||||
ClearBlipSafe(blip)
|
||||
end)
|
||||
end
|
||||
|
||||
elseif effective >= 4 then
|
||||
-- realtime blip
|
||||
if not t.realtimeBlip or not DoesBlipExist(t.realtimeBlip) then
|
||||
t.realtimeBlip = AddBlipForCoord(t.lastPos.x, t.lastPos.y, t.lastPos.z)
|
||||
SetBlipSprite(t.realtimeBlip, PP.REALTIME_BLIP_SPRITE or 1)
|
||||
SetBlipScale(t.realtimeBlip, PP.REALTIME_BLIP_SCALE or 0.9)
|
||||
BeginTextCommandSetBlipName("STRING")
|
||||
AddTextComponentString("Wanted Suspect")
|
||||
EndTextCommandSetBlipName(t.realtimeBlip)
|
||||
else
|
||||
SetBlipCoords(t.realtimeBlip, t.lastPos.x, t.lastPos.y, t.lastPos.z)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Police-only chat from server
|
||||
RegisterNetEvent('turfwar:pp:chat', function(msg)
|
||||
if not isPolice then return end
|
||||
TriggerEvent('chat:addMessage', { args = { msg } })
|
||||
end)
|
||||
|
||||
-- Police-only tracking updates
|
||||
RegisterNetEvent('turfwar:pp:update', function(offenderSrc, data)
|
||||
if not isPolice then return end
|
||||
if type(data) ~= "table" then return end
|
||||
|
||||
local t = EnsureTarget(offenderSrc)
|
||||
|
||||
if data.type == "mode" then
|
||||
t.stars = tonumber(data.stars) or 0
|
||||
t.interior = (data.interior == true)
|
||||
UpdateVisual(offenderSrc)
|
||||
return
|
||||
end
|
||||
|
||||
if data.type == "pos" then
|
||||
t.stars = tonumber(data.stars) or 0
|
||||
t.interior = (data.interior == true)
|
||||
t.lastPos = vec3(data.x, data.y, data.z)
|
||||
UpdateVisual(offenderSrc)
|
||||
return
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('turfwar:pp:clear', function(offenderSrc)
|
||||
ClearTarget(offenderSrc)
|
||||
end)
|
||||
|
||||
-- Detect if THIS player is police (from your existing broadcast)
|
||||
RegisterNetEvent('turfwar:playerGang', function(src, gangId)
|
||||
if src ~= GetPlayerServerId(PlayerId()) then return end
|
||||
isPolice = (tonumber(gangId) or 0) == POLICE_GANG_ID
|
||||
|
||||
if not isPolice then
|
||||
for id, t in pairs(targets) do
|
||||
ClearBlipSafe(t.circleBlip)
|
||||
ClearBlipSafe(t.realtimeBlip)
|
||||
end
|
||||
targets = {}
|
||||
end
|
||||
end)
|
||||
|
||||
-- How often offenders send updates (based on their own stars)
|
||||
local function CurrentInterval(stars)
|
||||
if stars <= 0 then return 1000 end
|
||||
if stars == 1 then return PP.UPDATE_MS_1STAR or 5000 end
|
||||
if stars == 2 then return PP.UPDATE_MS_2STAR or 2000 end
|
||||
if stars == 3 then return PP.UPDATE_MS_3STAR or 2000 end
|
||||
return PP.UPDATE_MS_45STAR_REALTIME or 500
|
||||
end
|
||||
|
||||
-- Watch stars/interior changes (all players)
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(500)
|
||||
local ped = PlayerPedId()
|
||||
local stars = GetPlayerWantedLevel(PlayerId()) or 0
|
||||
local interior = InInterior(ped)
|
||||
|
||||
if stars ~= myStars or interior ~= myInterior then
|
||||
myStars = stars
|
||||
myInterior = interior
|
||||
TriggerServerEvent('turfwar:pp:starsChanged', myStars, myInterior)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Stream offender position updates (all players with stars)
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(CurrentInterval(myStars))
|
||||
|
||||
if myStars > 0 then
|
||||
local ped = PlayerPedId()
|
||||
local c = GetEntityCoords(ped)
|
||||
local h = GetEntityHeading(ped)
|
||||
local interior = InInterior(ped)
|
||||
myInterior = interior
|
||||
|
||||
TriggerServerEvent('turfwar:pp:posUpdate', myStars, interior, c.x, c.y, c.z, h)
|
||||
end
|
||||
end
|
||||
end)
|
||||
136
client/police_wanted_escalate.lua
Normal file
136
client/police_wanted_escalate.lua
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
print("^2[turfwar]^7 police_wanted_escalate.lua loaded (client)")
|
||||
|
||||
-- -------------------------
|
||||
-- Tunables
|
||||
-- -------------------------
|
||||
local DMG_POLL_MS = 200
|
||||
local DMG_COOLDOWN_MS = 2500 -- per killer, per victim
|
||||
|
||||
-- -------------------------
|
||||
-- State
|
||||
-- -------------------------
|
||||
local sentThisDeath = false
|
||||
local lastDeathAt = 0
|
||||
local dmgCooldown = {} -- [killerSid] = lastSentAt (GetGameTimer ms)
|
||||
|
||||
-- -------------------------
|
||||
-- Helpers
|
||||
-- -------------------------
|
||||
local function killerServerIdFromEntity(ent)
|
||||
if not ent or ent == 0 or not DoesEntityExist(ent) then return 0 end
|
||||
|
||||
local killerPed = ent
|
||||
if IsEntityAVehicle(ent) then
|
||||
killerPed = GetPedInVehicleSeat(ent, -1)
|
||||
end
|
||||
|
||||
if not killerPed or killerPed == 0 or not DoesEntityExist(killerPed) then return 0 end
|
||||
if not IsEntityAPed(killerPed) then return 0 end
|
||||
if not IsPedAPlayer(killerPed) then return 0 end
|
||||
|
||||
local idx = NetworkGetPlayerIndexFromPed(killerPed)
|
||||
if idx == -1 then return 0 end
|
||||
|
||||
return tonumber(GetPlayerServerId(idx)) or 0
|
||||
end
|
||||
|
||||
-- Reset death flag when alive again
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(250)
|
||||
if sentThisDeath and not IsEntityDead(PlayerPedId()) then
|
||||
sentThisDeath = false
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- -------------------------
|
||||
-- KILL detection (keep what works)
|
||||
-- -------------------------
|
||||
AddEventHandler('gameEventTriggered', function(name, args)
|
||||
if name ~= 'CEventNetworkEntityDamage' then return end
|
||||
|
||||
local victimEntity = args[1]
|
||||
local attackerEntity = args[2]
|
||||
local victimDied = args[6]
|
||||
|
||||
if victimEntity ~= PlayerPedId() then return end
|
||||
if not victimDied then return end
|
||||
|
||||
if sentThisDeath then return end
|
||||
if not IsEntityDead(PlayerPedId()) then return end
|
||||
|
||||
local now = GetGameTimer()
|
||||
if now - lastDeathAt < 1500 then return end
|
||||
lastDeathAt = now
|
||||
sentThisDeath = true
|
||||
|
||||
local killerSid = killerServerIdFromEntity(attackerEntity)
|
||||
if killerSid <= 0 then
|
||||
-- fallback
|
||||
killerSid = killerServerIdFromEntity(GetEntityLastDamageEntity(PlayerPedId()))
|
||||
end
|
||||
if killerSid > 0 then
|
||||
TriggerServerEvent('turfwar:wanted:policeKilledBy', killerSid)
|
||||
end
|
||||
end)
|
||||
|
||||
-- -------------------------
|
||||
-- DAMAGE detection (reliable polling)
|
||||
-- -------------------------
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(DMG_POLL_MS)
|
||||
|
||||
local ped = PlayerPedId()
|
||||
if ped == 0 or not DoesEntityExist(ped) then goto continue end
|
||||
if IsEntityDead(ped) then goto continue end
|
||||
|
||||
-- Was I damaged by any ped since last clear?
|
||||
if HasEntityBeenDamagedByAnyPed(ped) then
|
||||
local lastEnt = GetEntityLastDamageEntity(ped)
|
||||
local killerSid = killerServerIdFromEntity(lastEnt)
|
||||
|
||||
if killerSid > 0 then
|
||||
local now = GetGameTimer()
|
||||
local last = dmgCooldown[killerSid] or 0
|
||||
|
||||
if (now - last) >= DMG_COOLDOWN_MS then
|
||||
dmgCooldown[killerSid] = now
|
||||
TriggerServerEvent('turfwar:wanted:policeDamagedBy', killerSid)
|
||||
end
|
||||
end
|
||||
|
||||
-- Clear flags so we don't spam forever
|
||||
ClearEntityLastDamageEntity(ped)
|
||||
ClearPedLastWeaponDamage(ped)
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
end)
|
||||
|
||||
-- -------------------------
|
||||
-- Wanted application (killer client)
|
||||
-- -------------------------
|
||||
RegisterNetEvent('turfwar:wanted:setMinimum', function(minStars)
|
||||
local pid = PlayerId()
|
||||
local current = GetPlayerWantedLevel(pid)
|
||||
local newStars = math.max(current, tonumber(minStars) or 0)
|
||||
|
||||
if newStars ~= current then
|
||||
ClearPlayerWantedLevel(pid)
|
||||
SetPlayerWantedLevel(pid, newStars, false)
|
||||
SetPlayerWantedLevelNow(pid, true)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('turfwar:wanted:setEscalated', function(targetStars, count, windowSec)
|
||||
local pid = PlayerId()
|
||||
local current = GetPlayerWantedLevel(pid)
|
||||
local newStars = math.max(current, tonumber(targetStars) or 0)
|
||||
|
||||
ClearPlayerWantedLevel(pid)
|
||||
SetPlayerWantedLevel(pid, newStars, false)
|
||||
SetPlayerWantedLevelNow(pid, true)
|
||||
end)
|
||||
11
client/pvp.lua
Normal file
11
client/pvp.lua
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
-- client/pvp.lua
|
||||
-- Ensures PvP stays enabled (some resources toggle it off)
|
||||
print("^2[turfwar]^7 client/pvp.lua loaded")
|
||||
|
||||
CreateThread(function()
|
||||
while true do
|
||||
NetworkSetFriendlyFireOption(true)
|
||||
SetCanAttackFriendly(PlayerPedId(), true, true)
|
||||
Wait(1000)
|
||||
end
|
||||
end)
|
||||
638
client/shop.lua
Normal file
638
client/shop.lua
Normal file
|
|
@ -0,0 +1,638 @@
|
|||
-- client/shop.lua
|
||||
print("^2[turfwar]^7 client/shop.lua loaded (gang+police shop NUI + markers + blips + shop vehicle ownership + debug)")
|
||||
|
||||
local currentGang = 0
|
||||
local shopOpen = false
|
||||
local lastOpenAttempt = 0
|
||||
|
||||
-- Debug toggles
|
||||
local DEBUG_MARKERS = false -- /tw_shopdebug
|
||||
|
||||
-- Shop blips (map icons)
|
||||
local ShopBlips = {}
|
||||
|
||||
-- Shop vehicle (1 per player) + blip
|
||||
local myShopVeh = 0
|
||||
local myShopVehBlip = 0
|
||||
|
||||
-- Server sync throttle
|
||||
local lastSyncedGang = -1
|
||||
local lastSyncAt = 0
|
||||
|
||||
-- =========================
|
||||
-- Helpers
|
||||
-- =========================
|
||||
local function Notify(msg)
|
||||
BeginTextCommandThefeedPost("STRING")
|
||||
AddTextComponentSubstringPlayerName(msg)
|
||||
EndTextCommandThefeedPostTicker(false, false)
|
||||
end
|
||||
|
||||
local function v3(pos)
|
||||
if not pos then return vector3(0.0, 0.0, 0.0) end
|
||||
return vector3(pos.x + 0.0, pos.y + 0.0, pos.z + 0.0)
|
||||
end
|
||||
|
||||
local function dist(a, b)
|
||||
local dx = (a.x + 0.0) - (b.x + 0.0)
|
||||
local dy = (a.y + 0.0) - (b.y + 0.0)
|
||||
local dz = (a.z + 0.0) - (b.z + 0.0)
|
||||
return math.sqrt(dx * dx + dy * dy + dz * dz)
|
||||
end
|
||||
|
||||
local function iterList(t)
|
||||
if type(t) ~= "table" then
|
||||
return function() return nil end
|
||||
end
|
||||
|
||||
local n = #t
|
||||
if n > 0 then
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
if i <= n then return i, t[i] end
|
||||
end
|
||||
end
|
||||
|
||||
return pairs(t)
|
||||
end
|
||||
|
||||
local function getGangRGB(gangId)
|
||||
local fallback = { r = 10, g = 10, b = 10 }
|
||||
|
||||
local gangs = (Config and Config.GANGS) or nil
|
||||
if type(gangs) ~= "table" then return fallback end
|
||||
|
||||
local g = gangs[tonumber(gangId) or 0]
|
||||
if type(g) ~= "table" then return fallback end
|
||||
|
||||
local rgb = g.rgb
|
||||
if type(rgb) ~= "table" then return fallback end
|
||||
|
||||
local r = tonumber(rgb.r or rgb[1])
|
||||
local gg = tonumber(rgb.g or rgb[2])
|
||||
local b = tonumber(rgb.b or rgb[3])
|
||||
|
||||
if not r or not gg or not b then return fallback end
|
||||
return { r = r, g = gg, b = b }
|
||||
end
|
||||
|
||||
local function drawMarkerAt(posIn)
|
||||
local shops = Config.Shops or {}
|
||||
local m = shops.Marker or {}
|
||||
|
||||
local pos = v3(posIn)
|
||||
local t = tonumber(m.type) or 1
|
||||
|
||||
local sc = m.scale
|
||||
if sc == nil then
|
||||
sc = vector3(1.2, 1.2, 0.9)
|
||||
elseif type(sc) == "table" and sc.x == nil then
|
||||
sc = vector3(
|
||||
tonumber(sc[1]) or 1.2,
|
||||
tonumber(sc[2]) or 1.2,
|
||||
tonumber(sc[3]) or 0.9
|
||||
)
|
||||
end
|
||||
|
||||
local rgba = m.rgba or { 80, 160, 255, 140 }
|
||||
|
||||
local z = pos.z
|
||||
local found, gz
|
||||
for _, off in ipairs({ 300.0, 100.0, 30.0, 10.0 }) do
|
||||
found, gz = GetGroundZFor_3dCoord(pos.x, pos.y, pos.z + off, true)
|
||||
if found then
|
||||
z = gz + 0.05
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
DrawMarker(
|
||||
t,
|
||||
pos.x, pos.y, z,
|
||||
0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0,
|
||||
sc.x, sc.y, sc.z,
|
||||
rgba[1] or 80,
|
||||
rgba[2] or 160,
|
||||
rgba[3] or 255,
|
||||
rgba[4] or 140,
|
||||
false, true, 2, false, nil, nil, false
|
||||
)
|
||||
end
|
||||
|
||||
-- =========================================================
|
||||
-- NEW: Server->Client gang sync responder
|
||||
-- =========================================================
|
||||
RegisterNetEvent("turfwar:shop:syncGangRequest", function(nonce)
|
||||
print(("[turfwar:shop] syncGangRequest nonce=%s -> reply gang=%d"):format(tostring(nonce), tonumber(currentGang) or 0))
|
||||
TriggerServerEvent("turfwar:shop:syncGangResponse", nonce, currentGang or 0)
|
||||
end)
|
||||
|
||||
-- =========================
|
||||
-- Vehicle ownership (1 per player) + blip + gang colour
|
||||
-- =========================
|
||||
local function clearMyShopVehicle()
|
||||
if myShopVehBlip ~= 0 and DoesBlipExist(myShopVehBlip) then
|
||||
RemoveBlip(myShopVehBlip)
|
||||
end
|
||||
myShopVehBlip = 0
|
||||
|
||||
if myShopVeh ~= 0 and DoesEntityExist(myShopVeh) then
|
||||
SetEntityAsMissionEntity(myShopVeh, true, true)
|
||||
DeleteEntity(myShopVeh)
|
||||
end
|
||||
myShopVeh = 0
|
||||
end
|
||||
|
||||
local function createMyShopVehBlip(veh)
|
||||
if myShopVehBlip ~= 0 and DoesBlipExist(myShopVehBlip) then
|
||||
RemoveBlip(myShopVehBlip)
|
||||
end
|
||||
|
||||
local blip = AddBlipForEntity(veh)
|
||||
SetBlipSprite(blip, 225)
|
||||
SetBlipScale(blip, 0.85)
|
||||
SetBlipAsShortRange(blip, false)
|
||||
|
||||
local g = Config and Config.GANGS and Config.GANGS[currentGang]
|
||||
if g and g.blipColor then
|
||||
SetBlipColour(blip, g.blipColor)
|
||||
end
|
||||
|
||||
BeginTextCommandSetBlipName("STRING")
|
||||
AddTextComponentString(currentGang == 3 and "Police Vehicle" or "My Vehicle")
|
||||
EndTextCommandSetBlipName(blip)
|
||||
|
||||
myShopVehBlip = blip
|
||||
end
|
||||
|
||||
local function applyGangColorsToVehicle(veh, gangId)
|
||||
local rgb = getGangRGB(gangId)
|
||||
SetVehicleModKit(veh, 0)
|
||||
SetVehicleCustomPrimaryColour(veh, rgb.r, rgb.g, rgb.b)
|
||||
SetVehicleCustomSecondaryColour(veh, rgb.r, rgb.g, rgb.b)
|
||||
SetVehicleDirtLevel(veh, 0.0)
|
||||
end
|
||||
|
||||
local function resolveVehicleModel(modelIn)
|
||||
local model = tostring(modelIn or "")
|
||||
if model ~= "" then return model end
|
||||
|
||||
if Config and Config.Shops and Config.Shops.GangVehicleModels and Config.Shops.GangVehicleModels[currentGang] then
|
||||
return tostring(Config.Shops.GangVehicleModels[currentGang])
|
||||
end
|
||||
|
||||
if Config and Config.Vehicles and Config.Vehicles.gangModels and Config.Vehicles.gangModels[currentGang] then
|
||||
return tostring(Config.Vehicles.gangModels[currentGang])
|
||||
end
|
||||
|
||||
return "sultan" -- fallback only
|
||||
end
|
||||
|
||||
local function findBestVehicleSpawn()
|
||||
local byGang = Config and Config.Shops and Config.Shops.LocationsByGang
|
||||
local g = byGang and byGang[currentGang]
|
||||
local pads = g and g.vehicles
|
||||
|
||||
local ped = PlayerPedId()
|
||||
local p = GetEntityCoords(ped)
|
||||
|
||||
if type(pads) == "table" then
|
||||
local best, bestD = nil, 999999.0
|
||||
for _, v in pairs(pads) do
|
||||
local pos = v3(v)
|
||||
local d = dist(pos, p)
|
||||
if d < bestD then
|
||||
bestD = d
|
||||
best = v
|
||||
end
|
||||
end
|
||||
|
||||
if best then
|
||||
local pos = v3(best)
|
||||
local heading = tonumber(best.w) or tonumber(best.h) or 0.0
|
||||
return pos, heading
|
||||
end
|
||||
end
|
||||
|
||||
local fwd = GetEntityForwardVector(ped)
|
||||
local pos = vector3(p.x + fwd.x * 6.0, p.y + fwd.y * 6.0, p.z)
|
||||
return pos, GetEntityHeading(ped)
|
||||
end
|
||||
|
||||
local function spawnVehicleClient(modelName)
|
||||
clearMyShopVehicle()
|
||||
|
||||
local model = resolveVehicleModel(modelName)
|
||||
local hash = GetHashKey(model)
|
||||
|
||||
if not IsModelInCdimage(hash) or not IsModelAVehicle(hash) then
|
||||
Notify(("Invalid vehicle model: %s"):format(model))
|
||||
return
|
||||
end
|
||||
|
||||
RequestModel(hash)
|
||||
local deadline = GetGameTimer() + 7000
|
||||
while not HasModelLoaded(hash) do
|
||||
Wait(0)
|
||||
if GetGameTimer() > deadline then
|
||||
Notify(("Failed to load model: %s"):format(model))
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local pos, heading = findBestVehicleSpawn()
|
||||
|
||||
RequestCollisionAtCoord(pos.x, pos.y, pos.z)
|
||||
for _ = 1, 20 do
|
||||
local found, gz = GetGroundZFor_3dCoord(pos.x, pos.y, pos.z + 50.0, true)
|
||||
if found then
|
||||
pos = vector3(pos.x, pos.y, gz + 0.5)
|
||||
break
|
||||
end
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
local veh = CreateVehicle(hash, pos.x, pos.y, pos.z, heading, true, false)
|
||||
if veh == 0 then
|
||||
Notify("Vehicle spawn failed.")
|
||||
SetModelAsNoLongerNeeded(hash)
|
||||
return
|
||||
end
|
||||
|
||||
SetEntityAsMissionEntity(veh, true, true)
|
||||
SetVehicleOnGroundProperly(veh)
|
||||
SetVehicleEngineOn(veh, true, true, false)
|
||||
SetVehRadioStation(veh, "OFF")
|
||||
|
||||
if tonumber(currentGang) == 3 then
|
||||
local a = (Config and Config.Shops and Config.Shops.PoliceVehicleAppearance) or {}
|
||||
if a.primary ~= nil and a.secondary ~= nil then
|
||||
SetVehicleColours(veh, tonumber(a.primary) or 0, tonumber(a.secondary) or 0)
|
||||
end
|
||||
if a.pearlescent ~= nil or a.wheel ~= nil then
|
||||
SetVehicleExtraColours(veh, tonumber(a.pearlescent) or 0, tonumber(a.wheel) or 0)
|
||||
end
|
||||
if a.livery ~= nil then
|
||||
local liv = tonumber(a.livery)
|
||||
if liv and liv >= 0 then SetVehicleLivery(veh, liv) end
|
||||
end
|
||||
if a.clean then
|
||||
SetVehicleDirtLevel(veh, 0.0)
|
||||
WashDecalsFromVehicle(veh, 1.0)
|
||||
end
|
||||
else
|
||||
applyGangColorsToVehicle(veh, currentGang)
|
||||
end
|
||||
|
||||
myShopVeh = veh
|
||||
createMyShopVehBlip(veh)
|
||||
TaskWarpPedIntoVehicle(PlayerPedId(), veh, -1)
|
||||
|
||||
SetModelAsNoLongerNeeded(hash)
|
||||
Notify(("Spawned: %s"):format(model))
|
||||
end
|
||||
|
||||
RegisterNetEvent("turfwar:shop:grantVehicle", function(model)
|
||||
print(("[turfwar:shop] grantVehicle RECEIVED model=%s gang=%d"):format(tostring(model), tonumber(currentGang) or -1))
|
||||
spawnVehicleClient(model)
|
||||
end)
|
||||
|
||||
AddEventHandler("onResourceStop", function(res)
|
||||
if res ~= GetCurrentResourceName() then return end
|
||||
clearMyShopVehicle()
|
||||
end)
|
||||
|
||||
-- =========================
|
||||
-- Payload selector (Police vs Gang)
|
||||
-- =========================
|
||||
local function buildShopPayload()
|
||||
local s = Config.Shops or {}
|
||||
if currentGang == 3 then
|
||||
return { weapons = s.PoliceWeapons or {}, ammo = s.PoliceAmmo or {}, vehicles = s.PoliceVehicles or {}, gangs = Config.GANGS or {} }
|
||||
end
|
||||
return { weapons = s.Weapons or {}, ammo = s.Ammo or {}, vehicles = s.Vehicles or {}, gangs = Config.GANGS or {} }
|
||||
end
|
||||
|
||||
local function getGangShopLocations(gangId)
|
||||
local s = Config.Shops or {}
|
||||
local byGang = s.LocationsByGang or {}
|
||||
local g = byGang[tonumber(gangId) or 0]
|
||||
if type(g) ~= "table" then return nil end
|
||||
return { shop = g.shop or {}, vehicles = g.vehicles or {} }
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- NUI Open/Close
|
||||
-- =========================
|
||||
local function openShop(tab, allowedTabs)
|
||||
if shopOpen then return end
|
||||
shopOpen = true
|
||||
|
||||
SetNuiFocus(true, true)
|
||||
SetNuiFocusKeepInput(false)
|
||||
|
||||
SendNUIMessage({
|
||||
type = "shop:open",
|
||||
tab = tab or "weapons",
|
||||
gangId = currentGang,
|
||||
gangRGB = getGangRGB(currentGang),
|
||||
payload = buildShopPayload(),
|
||||
allowedTabs = allowedTabs or { "weapons", "ammo", "vehicles" },
|
||||
})
|
||||
|
||||
TriggerServerEvent("turfwar:shop:requestBalance")
|
||||
end
|
||||
|
||||
local function closeShop()
|
||||
if not shopOpen then return end
|
||||
shopOpen = false
|
||||
SetNuiFocus(false, false)
|
||||
SendNUIMessage({ type = "shop:close" })
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Shop Blips
|
||||
-- =========================
|
||||
local function clearShopBlips()
|
||||
for _, b in ipairs(ShopBlips) do
|
||||
if DoesBlipExist(b) then RemoveBlip(b) end
|
||||
end
|
||||
ShopBlips = {}
|
||||
end
|
||||
|
||||
local function addShopBlip(posIn, label)
|
||||
local pos = v3(posIn)
|
||||
local blip = AddBlipForCoord(pos.x, pos.y, pos.z)
|
||||
|
||||
SetBlipSprite(blip, 374)
|
||||
SetBlipScale(blip, 0.75)
|
||||
SetBlipAsShortRange(blip, true)
|
||||
|
||||
local g = Config.GANGS and Config.GANGS[currentGang]
|
||||
SetBlipColour(blip, (g and g.blipColor) or 0)
|
||||
|
||||
BeginTextCommandSetBlipName("STRING")
|
||||
AddTextComponentString(label)
|
||||
EndTextCommandSetBlipName(blip)
|
||||
|
||||
ShopBlips[#ShopBlips + 1] = blip
|
||||
end
|
||||
|
||||
local function buildShopBlips()
|
||||
clearShopBlips()
|
||||
|
||||
if currentGang == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local gLocs = getGangShopLocations(currentGang)
|
||||
if not gLocs then
|
||||
print(("^1[turfwar]^7 No shop locations for gangId=%d. Check Config.Shops.LocationsByGang[%d] in shops.lua"):format(currentGang, currentGang))
|
||||
return
|
||||
end
|
||||
|
||||
local isPolice = (currentGang == 3)
|
||||
|
||||
for _, pos in iterList(gLocs.shop) do
|
||||
addShopBlip(pos, isPolice and "Police Armory" or "Gang Shop")
|
||||
end
|
||||
|
||||
for _, pos in iterList(gLocs.vehicles) do
|
||||
addShopBlip(pos, isPolice and "Police Vehicles" or "Gang Vehicles")
|
||||
end
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Commands
|
||||
-- =========================
|
||||
RegisterCommand("tw_mygang", function()
|
||||
Notify(("currentGang=%d"):format(currentGang or -1))
|
||||
print(("^3[turfwar]^7 tw_mygang -> currentGang=%d"):format(currentGang or -1))
|
||||
end, false)
|
||||
|
||||
RegisterCommand("tw_shopdebug", function()
|
||||
DEBUG_MARKERS = not DEBUG_MARKERS
|
||||
Notify(("Shop marker debug: %s"):format(DEBUG_MARKERS and "~g~ON~s~" or "~r~OFF~s~"))
|
||||
print(("^3[turfwar]^7 DEBUG_MARKERS=%s"):format(tostring(DEBUG_MARKERS)))
|
||||
end, false)
|
||||
|
||||
-- =========================
|
||||
-- Server sync + gang setter
|
||||
-- =========================
|
||||
local function SyncGangToServer()
|
||||
local now = GetGameTimer()
|
||||
if currentGang == lastSyncedGang and (now - lastSyncAt) < 1000 then return end
|
||||
lastSyncedGang = currentGang
|
||||
lastSyncAt = now
|
||||
TriggerServerEvent("turfwar:setFaction", currentGang)
|
||||
end
|
||||
|
||||
local function setGang(newGang)
|
||||
newGang = tonumber(newGang) or 0
|
||||
if newGang == currentGang then
|
||||
SyncGangToServer()
|
||||
return
|
||||
end
|
||||
|
||||
clearMyShopVehicle()
|
||||
|
||||
currentGang = newGang
|
||||
SyncGangToServer()
|
||||
|
||||
if shopOpen then
|
||||
SendNUIMessage({ type = "shop:gang", gangId = currentGang, gangRGB = getGangRGB(currentGang) })
|
||||
TriggerServerEvent("turfwar:shop:requestBalance")
|
||||
end
|
||||
|
||||
buildShopBlips()
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Events from your gang system
|
||||
-- =========================
|
||||
RegisterNetEvent("turfwar:setMyGang", function(gangId)
|
||||
setGang(gangId)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("turfwar:setFaction", function(gangId)
|
||||
print(("^3[turfwar]^7 shop.lua got turfwar:setFaction -> %d"):format(tonumber(gangId) or 0))
|
||||
setGang(gangId)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("turfwar:gangUpdate", function(gangId)
|
||||
print(("^3[turfwar]^7 shop.lua got turfwar:gangUpdate -> %d"):format(tonumber(gangId) or 0))
|
||||
setGang(gangId)
|
||||
end)
|
||||
|
||||
-- =========================
|
||||
-- Shop UI info events
|
||||
-- =========================
|
||||
RegisterNetEvent("turfwar:shop:balance", function(gangId, balance)
|
||||
SendNUIMessage({
|
||||
type = "shop:balance",
|
||||
gangId = tonumber(gangId) or 0,
|
||||
balance = tonumber(balance) or 0
|
||||
})
|
||||
end)
|
||||
|
||||
RegisterNetEvent("turfwar:shop:result", function(ok, message, newBalance)
|
||||
if message and message ~= "" then Notify(message) end
|
||||
if newBalance ~= nil then
|
||||
SendNUIMessage({
|
||||
type = "shop:balance",
|
||||
gangId = currentGang,
|
||||
balance = tonumber(newBalance) or 0
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("turfwar:shop:grantWeapon", function(weaponName, ammo)
|
||||
print(("[turfwar:shop] grantWeapon RECEIVED weapon=%s ammo=%s gang=%d"):format(
|
||||
tostring(weaponName), tostring(ammo), tonumber(currentGang) or -1
|
||||
))
|
||||
|
||||
local ped = PlayerPedId()
|
||||
local wHash = GetHashKey(weaponName)
|
||||
GiveWeaponToPed(ped, wHash, tonumber(ammo) or 0, false, true)
|
||||
|
||||
-- verify after a short delay (detects scripts stripping weapons)
|
||||
CreateThread(function()
|
||||
Wait(250)
|
||||
local hasIt = HasPedGotWeapon(ped, wHash, false)
|
||||
print(("[turfwar:shop] grantWeapon CHECK weapon=%s has=%s"):format(tostring(weaponName), tostring(hasIt)))
|
||||
end)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("turfwar:shop:grantAmmo", function(weaponName, amount)
|
||||
print(("[turfwar:shop] grantAmmo RECEIVED for=%s amount=%s gang=%d"):format(
|
||||
tostring(weaponName), tostring(amount), tonumber(currentGang) or -1
|
||||
))
|
||||
|
||||
local ped = PlayerPedId()
|
||||
local wHash = GetHashKey(weaponName)
|
||||
AddAmmoToPed(ped, wHash, tonumber(amount) or 0)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("turfwar:shop:debugOpen", function(tab)
|
||||
openShop(tab or "weapons", { "weapons", "ammo", "vehicles" })
|
||||
end)
|
||||
|
||||
-- =========================
|
||||
-- NUI callbacks
|
||||
-- =========================
|
||||
RegisterNUICallback("shop:close", function(_, cb)
|
||||
closeShop()
|
||||
cb({ ok = true })
|
||||
end)
|
||||
|
||||
RegisterNUICallback("shop:buyWeapon", function(data, cb)
|
||||
TriggerServerEvent("turfwar:shop:buyWeapon", tostring(data.itemId or ""))
|
||||
cb({ ok = true })
|
||||
end)
|
||||
|
||||
RegisterNUICallback("shop:buyAmmo", function(data, cb)
|
||||
TriggerServerEvent("turfwar:shop:buyAmmo", tostring(data.itemId or ""))
|
||||
cb({ ok = true })
|
||||
end)
|
||||
|
||||
RegisterNUICallback("shop:buyVehicle", function(data, cb)
|
||||
TriggerServerEvent("turfwar:shop:buyVehicle", tostring(data.itemId or ""))
|
||||
cb({ ok = true })
|
||||
end)
|
||||
|
||||
-- =========================
|
||||
-- Faction request (on join/spawn)
|
||||
-- =========================
|
||||
local function RequestFaction()
|
||||
TriggerServerEvent("turfwar:requestFaction")
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
Wait(1500)
|
||||
RequestFaction()
|
||||
end)
|
||||
|
||||
AddEventHandler("playerSpawned", function()
|
||||
Wait(1500)
|
||||
RequestFaction()
|
||||
end)
|
||||
|
||||
-- =========================
|
||||
-- Marker + Interact loop
|
||||
-- =========================
|
||||
CreateThread(function()
|
||||
Wait(1000)
|
||||
print("^3[turfwar]^7 shop.lua marker loop running")
|
||||
|
||||
buildShopBlips()
|
||||
|
||||
while true do
|
||||
Wait(0)
|
||||
|
||||
local ped = PlayerPedId()
|
||||
local p = GetEntityCoords(ped)
|
||||
|
||||
local shops = Config.Shops or {}
|
||||
local md = tonumber(shops.MarkerDist) or 25.0
|
||||
local id = tonumber(shops.InteractDist) or 2.0
|
||||
local key = tonumber(shops.InteractKey) or 38
|
||||
|
||||
if DEBUG_MARKERS then
|
||||
DrawMarker(1, p.x, p.y, p.z - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.5, 0.5, 0.25, 255, 255, 255, 180, false, true, 2, false, nil, nil, false)
|
||||
end
|
||||
|
||||
if currentGang ~= 0 then
|
||||
local gLocs = getGangShopLocations(currentGang)
|
||||
if gLocs then
|
||||
local isPolice = (currentGang == 3)
|
||||
|
||||
local lists = {
|
||||
{ tab = "weapons", locs = gLocs.shop, allowed = { "weapons", "ammo" } },
|
||||
{ tab = "vehicles", locs = gLocs.vehicles, allowed = { "vehicles" } },
|
||||
}
|
||||
|
||||
local canOpen = false
|
||||
local openTab = nil
|
||||
local openAllowed = nil
|
||||
|
||||
for _, entry in ipairs(lists) do
|
||||
for _, posAny in iterList(entry.locs) do
|
||||
local d = dist(v3(posAny), p)
|
||||
if d <= md then
|
||||
drawMarkerAt(posAny)
|
||||
if d <= id then
|
||||
canOpen = true
|
||||
openTab = entry.tab
|
||||
openAllowed = entry.allowed
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if canOpen and not shopOpen then
|
||||
BeginTextCommandDisplayHelp("STRING")
|
||||
AddTextComponentSubstringPlayerName(
|
||||
isPolice and "Press ~INPUT_CONTEXT~ to open Police Armory" or
|
||||
"Press ~INPUT_CONTEXT~ to open Gang Shop"
|
||||
)
|
||||
EndTextCommandDisplayHelp(0, false, true, 0)
|
||||
|
||||
if IsControlJustPressed(0, key) then
|
||||
local now = GetGameTimer()
|
||||
if now - lastOpenAttempt > 400 then
|
||||
lastOpenAttempt = now
|
||||
openShop(openTab or "weapons", openAllowed)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if shopOpen and IsControlJustPressed(0, 322) then
|
||||
closeShop()
|
||||
end
|
||||
end
|
||||
end)
|
||||
Loading…
Reference in New Issue
Block a user