diff --git a/client/shop_points.lua b/client/shop_points.lua new file mode 100644 index 0000000..062bbe2 --- /dev/null +++ b/client/shop_points.lua @@ -0,0 +1,244 @@ +-- client/gang_shop_points.lua (DEBUG) +print("^2[turfwar]^7 gang_shop_points.lua loaded (DEBUG markers + blips)") + +Config = Config or {} +Config.ShopPoints = Config.ShopPoints or {} +Config.Shops = Config.Shops or {} + +local currentGang = 0 +local showAll = false +local debug = false + +-- Menu state +local menuOpen = false +local menuType = nil +local menuItems = {} + +local NumKey = { [1]=157,[2]=158,[3]=160,[4]=164,[5]=165,[6]=159,[7]=161,[8]=162,[9]=163 } + +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(gangId) + return gangId ~= 0 and gangId ~= 3 +end + +-- ====== DEBUG COMMANDS ====== +RegisterCommand("tw_shop_debug", function() + debug = not debug + Notify(("Shop debug: %s"):format(debug and "~g~ON~s~" or "~r~OFF~s~")) +end) + +RegisterCommand("tw_shop_showall", function() + showAll = not showAll + Notify(("Show ALL shop points: %s"):format(showAll and "~g~ON~s~" or "~r~OFF~s~")) +end) + +RegisterCommand("tw_shop_where", function() + local p = GetEntityCoords(PlayerPedId()) + local msg = ("You are at: vector3(%.3f, %.3f, %.3f) gang=%d"):format(p.x, p.y, p.z, currentGang) + print("^3[turfwar]^7 " .. msg) + Notify(msg) +end) + +-- ====== Gang update hook ====== +RegisterNetEvent("turfwar:myGang", function(gangId) + currentGang = tonumber(gangId) or 0 + if debug then + print(("^3[turfwar]^7 myGang received -> %s"):format(currentGang)) + end +end) + +-- ====== Blip so you can FIND it ====== +local shopBlip = 0 + +local function refreshBlip() + if DoesBlipExist(shopBlip) then + RemoveBlip(shopBlip) + shopBlip = 0 + end + + local p = Config.ShopPoints.ByGang and Config.ShopPoints.ByGang[currentGang] + if not p then + if debug then + print("^1[turfwar]^7 No shop point found for gangId=" .. tostring(currentGang)) + end + return + end + + shopBlip = AddBlipForCoord(p.x, p.y, p.z) + SetBlipSprite(shopBlip, 52) -- safe generic blip + SetBlipScale(shopBlip, 0.8) + SetBlipColour(shopBlip, 2) + SetBlipAsShortRange(shopBlip, false) + BeginTextCommandSetBlipName("STRING") + AddTextComponentString("Gang Shop Point") + EndTextCommandSetBlipName(shopBlip) + + if debug then + print(("^2[turfwar]^7 Shop blip set for gang %d at %.3f %.3f %.3f"):format(currentGang, p.x, p.y, p.z)) + end +end + +RegisterCommand("tw_shop_blip", function() + refreshBlip() + Notify("Shop blip refreshed.") +end) + +-- Auto refresh blip occasionally (in case gang changes) +CreateThread(function() + Wait(2000) + refreshBlip() + while true do + Wait(5000) + if shopBlip == 0 and currentGang ~= 0 then + refreshBlip() + end + end +end) + +-- ====== Menu (unchanged) ====== +local function printMenuToConsole() + print(("^3[turfwar]^7 ====== %s SHOP (1-9 buy, BACKSPACE close) ======"):format(menuType:upper())) + for i, it in ipairs(menuItems) do + if i > 9 then break end + print(("^3[turfwar]^7 [%d] %s ^2$%d^7 (id: %s)"):format(i, it.label or it.id, tonumber(it.price) or 0, it.id)) + end +end + +local function openMenu(kind) + if menuOpen then return end + menuOpen = true + menuType = kind + if kind == "weapons" then + menuItems = Config.Shops.WeaponList or {} + Notify("~y~Weapon Shop~s~ opened. Check F8. 1-9 buy. BACKSPACE closes.") + else + menuItems = Config.Shops.VehicleList or {} + Notify("~y~Vehicle Shop~s~ opened. Check F8. 1-9 buy. BACKSPACE closes.") + end + printMenuToConsole() +end + +local function closeMenu() + if not menuOpen then return end + menuOpen = false + menuType = nil + menuItems = {} + Notify("~r~Shop closed.~s~") +end + +local function buyIndex(idx) + if not menuOpen then return end + local it = menuItems[idx] + if not it then return end + + if menuType == "weapons" then + TriggerServerEvent("turfwar:shop:buyWeapon", it.id) + else + TriggerServerEvent("turfwar:shop:buyVehicle", it.id) + end +end + +CreateThread(function() + while true do + Wait(0) + if not menuOpen then goto continue end + if IsControlJustPressed(0, 177) then closeMenu() end -- backspace + for i = 1, 9 do + if IsControlJustPressed(0, NumKey[i]) then buyIndex(i) end + end + ::continue:: + end +end) + +local function RequestMyGang() + TriggerServerEvent("turfwar:gang:requestMyGang") +end + +CreateThread(function() + Wait(1500) + RequestMyGang() +end) + +AddEventHandler("playerSpawned", function() + -- give other systems time to set gang on server if needed + Wait(1500) + RequestMyGang() +end) + + +-- ====== Marker draw ====== +CreateThread(function() + local useKey = 38 -- E + local vehKey = 47 -- G + local useDist = 3.0 -- increased for easier testing + local markerDist = 150.0 -- increased so you can confirm it's working from farther away + + while true do + Wait(0) + + local ped = PlayerPedId() + local p = GetEntityCoords(ped) + + if not Config.ShopPoints.ByGang then + if debug then + Draw3DText(p.x, p.y, p.z + 1.0, "~r~Config.ShopPoints.ByGang is NIL~s~ (config not loaded)") + end + goto continue + end + + local function drawAt(pos, label) + local d = #(p - pos) + if d < markerDist then + DrawMarker(1, pos.x, pos.y, pos.z - 1.0, 0,0,0, 0,0,0, 1.2,1.2,0.8, 255,255,255, 140, false,true,2, false,nil,nil,false) + if d < useDist then + Draw3DText(pos.x, pos.y, pos.z + 0.25, ("~w~%s~n~~y~[E]~w~ Weapons ~y~[G]~w~ Vehicles"):format(label)) + if IsControlJustPressed(0, useKey) then openMenu("weapons") end + if IsControlJustPressed(0, vehKey) then openMenu("vehicles") end + end + end + end + + if showAll then + for gangId, pos in pairs(Config.ShopPoints.ByGang) do + drawAt(pos, ("Shop GANG[%s]"):format(gangId)) + end + else + if not allowedGang(currentGang) then + if debug then + Draw3DText(p.x, p.y, p.z + 1.0, ("~y~No marker: gang=%d (needs not 0/3)~s~"):format(currentGang)) + end + goto continue + end + + local pos = Config.ShopPoints.ByGang[currentGang] + if not pos then + if debug then + Draw3DText(p.x, p.y, p.z + 1.0, ("~r~No point for gang=%d~s~"):format(currentGang)) + end + goto continue + end + + local label = (Config.ShopPoints.LabelByGang and Config.ShopPoints.LabelByGang[currentGang]) or ("Gang Shop %d"):format(currentGang) + drawAt(pos, label) + end + + ::continue:: + end +end) diff --git a/client/tw_shop_cmd.lua b/client/tw_shop_cmd.lua new file mode 100644 index 0000000..4538169 --- /dev/null +++ b/client/tw_shop_cmd.lua @@ -0,0 +1,7 @@ +-- client/tw_shop_cmd.lua +print("^2[turfwar]^7 tw_shop_cmd.lua loaded") + +RegisterCommand("tw_shop", function() + print("^3[turfwar]^7 /tw_shop fired -> TriggerEvent turfwar:shop:debugOpen") + TriggerEvent("turfwar:shop:debugOpen", "vehicles") +end, false) diff --git a/client/vehicles.lua b/client/vehicles.lua new file mode 100644 index 0000000..68357a5 --- /dev/null +++ b/client/vehicles.lua @@ -0,0 +1,262 @@ +-- client/vehicles.lua +print("^2[turfwar]^7 client/vehicles.lua LOADED (HQ marker + spawn + doSpawn mutex)") + +Config = Config or {} +Config.Vehicles = Config.Vehicles or {} +Config.GANGS = Config.GANGS or {} +Config.JOIN_POINTS = Config.JOIN_POINTS or {} + +local currentGang = 0 +local myVehNetId = 0 +local myVehBlip = 0 +local nextSpawnAt = 0 +local myVehGangId = 0 + +-- GLOBAL mutex shared across ALL client scripts +_G.__turfwar_doSpawnBusy = _G.__turfwar_doSpawnBusy or false + +-- ========================= +-- 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 getHQPosForGang(gangId) + for _, jp in ipairs(Config.JOIN_POINTS or {}) do + if tonumber(jp.gangId) == tonumber(gangId) then + return jp.pos + end + end + return nil +end + +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() + 7000 + while not HasModelLoaded(hash) do + Wait(10) + if GetGameTimer() > timeout then + return nil + end + end + return hash +end + +local function getGangBlipColor(gangId) + gangId = tonumber(gangId) or 0 + local g = Config.GANGS and Config.GANGS[gangId] + local c = g and g.blipColor + return tonumber(c) or 2 +end + +local function setMyBlipForVehicle(veh) + if DoesBlipExist(myVehBlip) then + RemoveBlip(myVehBlip) + myVehBlip = 0 + end + if not veh or veh == 0 then return end + + myVehBlip = AddBlipForEntity(veh) + SetBlipSprite(myVehBlip, 225) + SetBlipScale(myVehBlip, 0.75) + SetBlipColour(myVehBlip, getGangBlipColor(myVehGangId)) -- <- gang color + SetBlipAsShortRange(myVehBlip, false) + + BeginTextCommandSetBlipName("STRING") + AddTextComponentString("Gang Vehicle") + EndTextCommandSetBlipName(myVehBlip) +end + + +-- ========================= +-- Gang updates (MATCH YOUR SERVER) +-- ========================= +local function setGang(gangId) + currentGang = tonumber(gangId) or 0 +end + +-- sent directly to the player on change +RegisterNetEvent("turfwar:gangUpdate", function(gangId) + setGang(gangId) +end) + +-- compatibility event your server also triggers +RegisterNetEvent("turfwar:setFaction", function(gangId) + setGang(gangId) +end) + +-- broadcast to all clients: (src, gangId) +RegisterNetEvent("turfwar:playerGang", function(src, gangId) + if src == GetPlayerServerId(PlayerId()) then + setGang(gangId) + end +end) + +RegisterCommand("tw_gang", function() + print("^3[turfwar]^7 currentGang = " .. tostring(currentGang)) + Notify("currentGang = " .. tostring(currentGang)) +end) + +-- On resource start, ask server for our current gang via existing requestFaction +CreateThread(function() + Wait(1000) + TriggerServerEvent("turfwar:requestFaction") +end) + +-- ========================= +-- Server -> Client spawn +-- ========================= +RegisterNetEvent("turfwar:veh:doSpawn", function(data) + if _G.__turfwar_doSpawnBusy then + print("^1[turfwar]^7 Ignoring duplicate doSpawn (global mutex busy)") + return + end + _G.__turfwar_doSpawnBusy = true + + local function done() + _G.__turfwar_doSpawnBusy = false + end + + local ok, err = pcall(function() + if type(data) ~= "table" or not data.model then + print("^1[turfwar]^7 doSpawn: bad data") + done() + return + end + + local hash = loadModel(data.model) + if not hash then + Notify("~r~Vehicle model failed to load.~s~") + done() + return + end + + local x = tonumber(data.x) or 0.0 + local y = tonumber(data.y) or 0.0 + local z = tonumber(data.z) or 0.0 + local h = tonumber(data.heading) or 0.0 + local plate = tostring(data.plate or "GANG") + + local veh = CreateVehicle(hash, x, y, z, h, true, false) + SetModelAsNoLongerNeeded(hash) + + if not veh or veh == 0 then + Notify("~r~Vehicle spawn failed.~s~") + done() + return + end + + SetVehicleOnGroundProperly(veh) + SetVehicleEngineOn(veh, true, true, false) + SetVehicleDirtLevel(veh, 0.0) + SetVehicleNumberPlateText(veh, plate) + + -- Apply RGB if provided + if data.rgb and type(data.rgb) == "table" then + local r = tonumber(data.rgb[1]) or 255 + local g = tonumber(data.rgb[2]) or 255 + local b = tonumber(data.rgb[3]) or 255 + SetVehicleCustomPrimaryColour(veh, r, g, b) + SetVehicleCustomSecondaryColour(veh, r, g, b) + end + + local netId = NetworkGetNetworkIdFromEntity(veh) + SetNetworkIdCanMigrate(netId, true) + + TriggerServerEvent("turfwar:veh:spawned", netId) + + myVehNetId = netId + myVehGangId = tonumber(data.gangId) or currentGang or 0 + setMyBlipForVehicle(veh) + + + done() + end) + + if not ok then + print("^1[turfwar]^7 doSpawn crashed: " .. tostring(err)) + _G.__turfwar_doSpawnBusy = false + end +end) + +RegisterNetEvent("turfwar:veh:notify", function(msg) + Notify(msg) +end) + +RegisterNetEvent("turfwar:veh:setLocal", function(netId, gangId) + myVehNetId = tonumber(netId) or 0 + myVehGangId = tonumber(gangId) or 0 +end) + + +RegisterNetEvent("turfwar:veh:clearLocal", function() + myVehNetId = 0 + myVehGangId = 0 + if DoesBlipExist(myVehBlip) then + RemoveBlip(myVehBlip) + myVehBlip = 0 + end +end) + +-- ========================= +-- HQ marker + E to request spawn +-- (matches server validation against HQ join point) +-- ========================= +CreateThread(function() + local useKey = (Config.Vehicles and Config.Vehicles.interactKey) or 38 -- E + local useDist = (Config.Vehicles and Config.Vehicles.interactDist) or 2.5 + local markerDist = (Config.Vehicles and Config.Vehicles.markerDist) or 20.0 + + while true do + Wait(0) + + if not currentGang or currentGang == 0 then + goto continue + end + + local hq = getHQPosForGang(currentGang) + if not hq then + goto continue + end + + local ped = PlayerPedId() + local p = GetEntityCoords(ped) + local d = #(p - hq) + + if d < markerDist then + DrawMarker(1, hq.x, hq.y, hq.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 < useDist then + Draw3DText(hq.x, hq.y, hq.z + 0.25, "~w~Gang Vehicle~n~~y~[E]~w~ Spawn / Replace") + + if IsControlJustPressed(0, useKey) then + local now = GetGameTimer() + if now < nextSpawnAt then goto continue end + nextSpawnAt = now + 1200 + + local pcoords = { x = p.x, y = p.y, z = p.z } + TriggerServerEvent("turfwar:veh:requestSpawn", pcoords) + end + end + end + + ::continue:: + end +end)