Turfwar/client/shop.lua

639 lines
19 KiB
Lua

-- 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)