Upload files to "server"
This commit is contained in:
parent
be328c9be4
commit
ff2b8be533
823
server/main.lua
Normal file
823
server/main.lua
Normal file
|
|
@ -0,0 +1,823 @@
|
|||
-- server/main.lua
|
||||
-- Turfwar (single resource) - Turfs + Capture + Guard host + PlayerGang broadcast
|
||||
-- Works with the guards.lua you’re using (no GetPedCombatTarget).
|
||||
--
|
||||
-- REQUIRED Config tables in config.lua:
|
||||
-- Config.GANGS
|
||||
-- Config.JOIN_POINTS (ARRAY of { gangId=number, label=string, pos=vector3/4 })
|
||||
-- Config.TURFS (keyed by turfId)
|
||||
-- Optional:
|
||||
-- Config.SECONDS_TO_CAPTURE
|
||||
-- Config.INCOME = { intervalMinutes=60, ... }
|
||||
--
|
||||
-- Guard system:
|
||||
-- - One client is elected "GuardHost" (AI owner)
|
||||
-- - Server tells GuardHost to spawn/clear guards per turf
|
||||
-- - When all guards die, GuardHost triggers turfwar:guardsEmpty -> server frees capture again
|
||||
--
|
||||
-- Loadout system:
|
||||
-- - When player joins a gang -> give gang loadout
|
||||
-- - When player leaves gang (gangId 0) -> strip ALL weapons and ammo
|
||||
--
|
||||
-- Leaderboard:
|
||||
-- - "Most influence" = count of turfs currently owned per gang
|
||||
-- - Broadcast on capture + clientReady + reload + resource start
|
||||
|
||||
print("^2[turfwar]^7 server/main.lua LOADED")
|
||||
|
||||
local SecondsToCapture = (Config and tonumber(Config.SECONDS_TO_CAPTURE)) or 60
|
||||
|
||||
-- Police "Restore Peace" (how long police must hold to neutralize a turf)
|
||||
local PoliceRestoreToNeutral = (Config and Config.PlayerPolice and tonumber(Config.PlayerPolice.RESTORE_SECONDS)) or 30
|
||||
|
||||
|
||||
-- Loadouts is a GLOBAL provided by server/loadouts.lua (loaded before this file via fxmanifest)
|
||||
local Loadouts = Loadouts
|
||||
if not (Loadouts and Loadouts.ApplyForGang) then
|
||||
print("^1[turfwar]^7 WARNING: Loadouts not loaded! Check fxmanifest order (loadouts.lua before main.lua).")
|
||||
end
|
||||
|
||||
local function applyLoadoutSafe(src, gangId)
|
||||
if Loadouts and Loadouts.ApplyForGang then
|
||||
Loadouts.ApplyForGang(src, gangId)
|
||||
end
|
||||
end
|
||||
|
||||
AddEventHandler("playerJoining", function()
|
||||
local src = source
|
||||
|
||||
if not (TurfwarPersist and TurfwarPersist.Load) then
|
||||
PlayerGang[src] = PlayerGang[src] or 0
|
||||
return
|
||||
end
|
||||
|
||||
TurfwarPersist.Load(src, function(gangId, rank)
|
||||
PlayerGang[src] = tonumber(gangId) or 0
|
||||
|
||||
-- Push to the player + everyone so HUD/blips are correct right away
|
||||
TriggerClientEvent("turfwar:gangUpdate", src, PlayerGang[src])
|
||||
TriggerClientEvent("turfwar:setFaction", src, PlayerGang[src])
|
||||
TriggerClientEvent("turfwar:playerGang", -1, src, PlayerGang[src])
|
||||
|
||||
-- Apply loadout for their restored gang
|
||||
applyLoadoutSafe(src, PlayerGang[src])
|
||||
end)
|
||||
end)
|
||||
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Player gangs (server authoritative)
|
||||
-- ------------------------------------------------------------
|
||||
PlayerGang = PlayerGang or {} -- [src] = gangId
|
||||
|
||||
local function setPlayerGang(src, gangId)
|
||||
gangId = tonumber(gangId) or 0
|
||||
|
||||
local oldGang = tonumber(PlayerGang[src]) or 0
|
||||
PlayerGang[src] = gangId
|
||||
|
||||
-- Push gang bank balance (optional module)
|
||||
if TurfwarPayouts and TurfwarPayouts.GetGangBalance then
|
||||
local bal = (gangId ~= 0) and (TurfwarPayouts.GetGangBalance(gangId) or 0) or 0
|
||||
TriggerClientEvent("turfwar:gangbank:update", src, gangId, bal)
|
||||
end
|
||||
|
||||
-- Vehicle spawner hook (optional module)
|
||||
if VehicleSpawner and VehicleSpawner.OnGangChanged then
|
||||
VehicleSpawner.OnGangChanged(src, oldGang, gangId)
|
||||
end
|
||||
|
||||
-- Apply loadout rules whenever gang changes
|
||||
applyLoadoutSafe(src, gangId)
|
||||
|
||||
-- Send updates to the player + broadcast to all
|
||||
TriggerClientEvent("turfwar:gangUpdate", src, gangId)
|
||||
TriggerClientEvent("turfwar:setFaction", src, gangId) -- compatibility
|
||||
TriggerClientEvent("turfwar:playerGang", -1, src, gangId) -- broadcast to all clients
|
||||
|
||||
-- Join announcement in chat
|
||||
if gangId ~= 0 and gangId ~= oldGang then
|
||||
local g = (Config and Config.GANGS and Config.GANGS[gangId]) or nil
|
||||
local color = (g and g.chatColor) or "~s~"
|
||||
local gangName = (g and g.name) or ("Gang " .. tostring(gangId))
|
||||
local playerName = GetPlayerName(src) or ("ID " .. tostring(src))
|
||||
|
||||
TriggerClientEvent('chat:addMessage', -1, {
|
||||
args = {
|
||||
"[Turfwar]",
|
||||
("%s has joined the %s%s~s~!"):format(playerName, color, gangName)
|
||||
}
|
||||
})
|
||||
end
|
||||
-- Persist gang for next login
|
||||
if TurfwarPersist and TurfwarPersist.SaveGang then
|
||||
TurfwarPersist.SaveGang(src, gangId)
|
||||
AddEventHandler("playerJoining", function()
|
||||
local src = source
|
||||
if TurfwarPersist and TurfwarPersist.Load then
|
||||
local gangId, rank = TurfwarPersist.Load(src)
|
||||
PlayerGang[src] = tonumber(gangId) or 0
|
||||
|
||||
TriggerClientEvent("turfwar:gangUpdate", src, PlayerGang[src])
|
||||
TriggerClientEvent("turfwar:setFaction", src, PlayerGang[src])
|
||||
TriggerClientEvent("turfwar:playerGang", -1, src, PlayerGang[src])
|
||||
|
||||
applyLoadoutSafe(src, PlayerGang[src])
|
||||
else
|
||||
PlayerGang[src] = PlayerGang[src] or 0
|
||||
end
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
RegisterNetEvent("turfwar:requestFaction", function()
|
||||
local src = source
|
||||
TriggerClientEvent("turfwar:setFaction", src, tonumber(PlayerGang[src]) or 0)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("turfwar:setGang", function(gangId)
|
||||
local src = source
|
||||
setPlayerGang(src, gangId)
|
||||
|
||||
if TurfwarPayouts and TurfwarPayouts.PushGangBankToPlayer then
|
||||
TurfwarPayouts.PushGangBankToPlayer(src, tonumber(gangId) or 0)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("turfwar:requestAllPlayerGangs", function()
|
||||
local src = source
|
||||
for id, gang in pairs(PlayerGang) do
|
||||
TriggerClientEvent("turfwar:playerGang", src, id, tonumber(gang) or 0)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterCommand("joingang", function(src, args)
|
||||
local gangId = tonumber(args[1]) or 0
|
||||
setPlayerGang(src, gangId)
|
||||
end, false)
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- HQ spawn lookup (used by client to teleport on respawn)
|
||||
-- Config.JOIN_POINTS is an ARRAY (you use ipairs on client)
|
||||
-- ------------------------------------------------------------
|
||||
RegisterNetEvent('turfwar:getMyHQSpawn', function()
|
||||
local src = source
|
||||
local gangId = tonumber(PlayerGang[src]) or 0
|
||||
|
||||
local function findJoinPoint(forGang)
|
||||
for i, jp in ipairs(Config.JOIN_POINTS or {}) do
|
||||
if tonumber(jp.gangId) == tonumber(forGang) then
|
||||
return jp, i
|
||||
end
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local jp, idx = findJoinPoint(gangId)
|
||||
if not jp then
|
||||
print(("[turfwar] getMyHQSpawn src=%d gangId=%s -> NOT FOUND, fallback to 0"):format(src, tostring(gangId)))
|
||||
gangId = 0
|
||||
jp, idx = findJoinPoint(0)
|
||||
end
|
||||
|
||||
if not jp or not jp.pos then
|
||||
print(("[turfwar] getMyHQSpawn src=%d gangId=%s -> NO JOIN POINT / NO POS"):format(src, tostring(gangId)))
|
||||
TriggerClientEvent('turfwar:myHQSpawn', src, nil)
|
||||
return
|
||||
end
|
||||
|
||||
local v = jp.pos
|
||||
print(("[turfwar] getMyHQSpawn src=%d gangId=%d jpIndex=%s -> %.2f %.2f %.2f h=%.2f")
|
||||
:format(src, gangId, tostring(idx), v.x, v.y, v.z, v.w or 0.0))
|
||||
|
||||
TriggerClientEvent('turfwar:myHQSpawn', src, {
|
||||
x = v.x, y = v.y, z = v.z,
|
||||
h = v.w or 0.0,
|
||||
gangId = gangId
|
||||
})
|
||||
end)
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Turfs runtime state (GLOBAL for other server files if needed)
|
||||
-- ------------------------------------------------------------
|
||||
Turfs = Turfs or {}
|
||||
|
||||
local function vecToTable(v)
|
||||
return { x = v.x, y = v.y, z = v.z }
|
||||
end
|
||||
|
||||
local function initTurfsFromConfig()
|
||||
Turfs = {}
|
||||
|
||||
if not Config or not Config.TURFS then
|
||||
print("^1[turfwar]^7 Config.TURFS missing/empty")
|
||||
return
|
||||
end
|
||||
|
||||
for turfId, t in pairs(Config.TURFS) do
|
||||
Turfs[turfId] = {
|
||||
name = t.name or turfId,
|
||||
center = vecToTable(t.center),
|
||||
radius = tonumber(t.radius) or 80.0,
|
||||
owner = tonumber(t.owner) or 0,
|
||||
progress = 0,
|
||||
contestingGang = 0,
|
||||
guardsAlive = false,
|
||||
paused = false,
|
||||
lastActivity = 0,
|
||||
}
|
||||
end
|
||||
|
||||
-- Load persisted owners into runtime if payout module provides it
|
||||
if TurfwarPayouts and TurfwarPayouts.LoadTurfOwnersIntoRuntime then
|
||||
TurfwarPayouts.LoadTurfOwnersIntoRuntime(Turfs)
|
||||
end
|
||||
|
||||
local count = 0
|
||||
for _ in pairs(Turfs) do count = count + 1 end
|
||||
print(("^2[turfwar]^7 Turfs loaded: %d"):format(count))
|
||||
end
|
||||
|
||||
initTurfsFromConfig()
|
||||
|
||||
local function snapshotForClient()
|
||||
local payload = {}
|
||||
for turfId, t in pairs(Turfs) do
|
||||
payload[turfId] = {
|
||||
name = t.name,
|
||||
center = t.center,
|
||||
radius = t.radius,
|
||||
owner = t.owner,
|
||||
progress = t.progress,
|
||||
contestingGang = t.contestingGang,
|
||||
}
|
||||
end
|
||||
return payload
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Turf broadcast helpers (were missing before)
|
||||
-- ------------------------------------------------------------
|
||||
local function broadcastTurfUpdate(turfId)
|
||||
local t = Turfs[turfId]
|
||||
if not t then return end
|
||||
TriggerClientEvent("turfwar:turfUpdate", -1, turfId, t.owner, t.progress, t.contestingGang)
|
||||
end
|
||||
|
||||
local function broadcastTurfCaptured(turfId)
|
||||
local t = Turfs[turfId]
|
||||
if not t then return end
|
||||
TriggerClientEvent("turfwar:turfCaptured", -1, turfId, t.owner)
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Leaderboard (Most influence = most owned turfs)
|
||||
-- ------------------------------------------------------------
|
||||
local function buildLeaderboard()
|
||||
local counts = {} -- [gangId] = owned turf count
|
||||
|
||||
for _, turf in pairs(Turfs) do
|
||||
local owner = tonumber(turf.owner) or 0
|
||||
if owner ~= 0 then
|
||||
counts[owner] = (counts[owner] or 0) + 1
|
||||
end
|
||||
end
|
||||
|
||||
local rows = {}
|
||||
for gangId, g in pairs(Config.GANGS or {}) do
|
||||
gangId = tonumber(gangId) or 0
|
||||
if gangId ~= 0 then
|
||||
rows[#rows + 1] = {
|
||||
gangId = gangId,
|
||||
name = g.name or ("Gang " .. tostring(gangId)),
|
||||
value = counts[gangId] or 0,
|
||||
rgb = g.rgb or {255,255,255}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(rows, function(a, b)
|
||||
if a.value == b.value then return a.name < b.name end
|
||||
return a.value > b.value
|
||||
end)
|
||||
|
||||
return { title = "Most influence", rows = rows }
|
||||
end
|
||||
|
||||
local function broadcastLeaderboard(target)
|
||||
TriggerClientEvent("turfwar:leaderboard:update", target or -1, buildLeaderboard())
|
||||
end
|
||||
|
||||
RegisterNetEvent("turfwar:leaderboard:request", function()
|
||||
local src = source
|
||||
broadcastLeaderboard(src)
|
||||
end)
|
||||
|
||||
CreateThread(function()
|
||||
local announced = false
|
||||
while true do
|
||||
local interval = tonumber(Config?.INCOME?.intervalMinutes) or 60
|
||||
if interval < 0.1 then interval = 0.1 end
|
||||
|
||||
if not announced then
|
||||
print(("^3[turfwar]^7 Payout scheduler running (%.2f min interval)"):format(interval))
|
||||
announced = true
|
||||
end
|
||||
|
||||
Wait(math.floor(interval * 60 * 1000))
|
||||
|
||||
if TurfwarPayouts and TurfwarPayouts.DoPayoutTick then
|
||||
TurfwarPayouts.DoPayoutTick(Turfs)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Guard host election + guard spawn control
|
||||
-- ------------------------------------------------------------
|
||||
local GuardHost = nil
|
||||
|
||||
local function isValidSource(src)
|
||||
return src and tonumber(src) and GetPlayerName(src) ~= nil
|
||||
end
|
||||
|
||||
local function ensureGuardHost()
|
||||
if isValidSource(GuardHost) then return GuardHost end
|
||||
GuardHost = nil
|
||||
|
||||
for _, src in ipairs(GetPlayers()) do
|
||||
local n = tonumber(src)
|
||||
if isValidSource(n) then
|
||||
GuardHost = n
|
||||
print(("^2[turfwar]^7 GuardHost elected: %s"):format(GuardHost))
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return GuardHost
|
||||
end
|
||||
|
||||
RegisterNetEvent("turfwar:guardsClientReady", function()
|
||||
local src = source
|
||||
if not isValidSource(GuardHost) then
|
||||
GuardHost = src
|
||||
print(("^2[turfwar]^7 GuardHost assigned from guardsClientReady: %s"):format(GuardHost))
|
||||
|
||||
for turfId, t in pairs(Turfs) do
|
||||
if t.owner ~= 0 then
|
||||
local cfg = Config.TURFS and Config.TURFS[turfId]
|
||||
if cfg and (tonumber(cfg.guardCount) or 0) > 0 then
|
||||
t.guardsAlive = true
|
||||
TriggerClientEvent("turfwar:spawnGuards", GuardHost, {
|
||||
turfId = turfId,
|
||||
ownerFaction = t.owner,
|
||||
count = tonumber(cfg.guardCount) or 0,
|
||||
spawns = cfg.guardSpawns or {},
|
||||
model = cfg.guardModel or "g_m_y_lost_01",
|
||||
weapon = cfg.guardWeapon or "WEAPON_PISTOL",
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local function clearGuardsForTurf(turfId)
|
||||
local host = ensureGuardHost()
|
||||
if host then
|
||||
TriggerClientEvent("turfwar:clearGuards", host, turfId)
|
||||
end
|
||||
end
|
||||
|
||||
local function spawnGuardsForTurf(turfId)
|
||||
local host = ensureGuardHost()
|
||||
if not host then return end
|
||||
|
||||
local t = Turfs[turfId]
|
||||
local cfg = Config.TURFS and Config.TURFS[turfId]
|
||||
if not t or not cfg then return end
|
||||
|
||||
local count = tonumber(cfg.guardCount) or 0
|
||||
if t.owner == 0 or count <= 0 then
|
||||
t.guardsAlive = false
|
||||
clearGuardsForTurf(turfId)
|
||||
return
|
||||
end
|
||||
|
||||
t.guardsAlive = true
|
||||
TriggerClientEvent("turfwar:spawnGuards", host, {
|
||||
turfId = turfId,
|
||||
ownerFaction = t.owner,
|
||||
count = count,
|
||||
spawns = cfg.guardSpawns or {},
|
||||
model = cfg.guardModel or "g_m_y_lost_01",
|
||||
weapon = cfg.guardWeapon or "WEAPON_PISTOL",
|
||||
})
|
||||
end
|
||||
|
||||
RegisterNetEvent("turfwar:guardsEmpty", function(turfId)
|
||||
turfId = tostring(turfId)
|
||||
if Turfs[turfId] then
|
||||
Turfs[turfId].guardsAlive = false
|
||||
broadcastTurfUpdate(turfId)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterCommand("tw_respawn_guards", function(src, args)
|
||||
local turfId = tostring(args[1] or "")
|
||||
if turfId == "" or not Turfs[turfId] then return end
|
||||
spawnGuardsForTurf(turfId)
|
||||
end, true)
|
||||
|
||||
RegisterCommand("tw_clear_guards", function(src, args)
|
||||
local turfId = tostring(args[1] or "")
|
||||
if turfId == "" or not Turfs[turfId] then return end
|
||||
Turfs[turfId].guardsAlive = false
|
||||
clearGuardsForTurf(turfId)
|
||||
end, true)
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Client ready + snapshot
|
||||
-- ------------------------------------------------------------
|
||||
RegisterNetEvent("turfwar:clientReady", function()
|
||||
local src = source
|
||||
|
||||
TriggerClientEvent("turfwar:snapshot", src, snapshotForClient(), SecondsToCapture)
|
||||
|
||||
for id, gang in pairs(PlayerGang) do
|
||||
TriggerClientEvent("turfwar:playerGang", src, id, tonumber(gang) or 0)
|
||||
end
|
||||
|
||||
TriggerClientEvent("turfwar:playerGang", -1, src, tonumber(PlayerGang[src]) or 0)
|
||||
|
||||
applyLoadoutSafe(src, tonumber(PlayerGang[src]) or 0)
|
||||
|
||||
-- send leaderboard to joining client
|
||||
broadcastLeaderboard(src)
|
||||
end)
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Environment cash drop relay
|
||||
-- ------------------------------------------------------------
|
||||
RegisterNetEvent("environment:pedCashDrop", function(amount, coords)
|
||||
if type(coords) == "table" and coords.x and coords.y and coords.z then
|
||||
TriggerClientEvent("environment:spawnCashPickup", -1, amount, coords)
|
||||
else
|
||||
print("^3[turfwar]^7 environment:pedCashDrop received invalid coords payload")
|
||||
end
|
||||
end)
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Capture logic (bulletproof)
|
||||
-- ------------------------------------------------------------
|
||||
local LastPing = {}
|
||||
|
||||
-- Client-reported movement state (cached server-side)
|
||||
PlayerOnFoot = PlayerOnFoot or {} -- [src] = true/false
|
||||
RegisterNetEvent("turfwar:setOnFoot", function(onFoot)
|
||||
local src = source
|
||||
PlayerOnFoot[src] = (onFoot == true)
|
||||
end)
|
||||
|
||||
-- Presence tracking (pause rules)
|
||||
local Presence = {} -- Presence[turfId][src] = expiresMs
|
||||
local PRESENCE_TTL_MS = 3500
|
||||
local CONTEST_IDLE_RESET_MS = 6000 -- if no capture pings for this long, reset contest
|
||||
|
||||
local function notePresence(turfId, src, gangId)
|
||||
if tonumber(gangId) == 0 then return end -- neutral does NOT pause
|
||||
Presence[turfId] = Presence[turfId] or {}
|
||||
Presence[turfId][src] = GetGameTimer() + PRESENCE_TTL_MS
|
||||
end
|
||||
|
||||
local function cleanupPresence(turfId)
|
||||
local now = GetGameTimer()
|
||||
local bucket = Presence[turfId]
|
||||
if not bucket then return end
|
||||
for src, exp in pairs(bucket) do
|
||||
if (not exp) or exp < now or (not GetPlayerName(src)) then
|
||||
bucket[src] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- =========================================================
|
||||
-- Wanted stars on capture (anti-snipe)
|
||||
-- =========================================================
|
||||
local CAPTURE_WANTED_STARS = 2
|
||||
|
||||
local function awardWantedToPresence(turfId, stars)
|
||||
stars = tonumber(stars) or CAPTURE_WANTED_STARS
|
||||
cleanupPresence(turfId)
|
||||
|
||||
local bucket = Presence[turfId]
|
||||
if not bucket then return end
|
||||
|
||||
local policeGang = (Config.PlayerPolice and tonumber(Config.PlayerPolice.POLICE_GANG_ID)) or 3
|
||||
|
||||
for src, exp in pairs(bucket) do
|
||||
if exp and exp >= GetGameTimer() and GetPlayerName(src) then
|
||||
local g = tonumber(PlayerGang[src]) or 0
|
||||
if g ~= 0 and g ~= policeGang then
|
||||
TriggerClientEvent("turfwar:wanted:setMin", src, stars)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Clients ping this while standing inside a turf zone so defenders also count as "present"
|
||||
RegisterNetEvent("turfwar:notePresence", function(turfId)
|
||||
local src = source
|
||||
turfId = tostring(turfId or "")
|
||||
if turfId == "" or not Turfs[turfId] then return end
|
||||
|
||||
local gangId = tonumber(PlayerGang[src]) or 0
|
||||
notePresence(turfId, src, gangId)
|
||||
end)
|
||||
|
||||
local function isCapturePausedByOthers(turfId, capturingGang)
|
||||
cleanupPresence(turfId)
|
||||
local bucket = Presence[turfId]
|
||||
if not bucket then return false end
|
||||
|
||||
local cg = tonumber(capturingGang) or 0
|
||||
for src, _ in pairs(bucket) do
|
||||
local g = tonumber(PlayerGang[src]) or 0
|
||||
if g ~= 0 and g ~= cg then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- Hint throttle
|
||||
local HintCooldown = {}
|
||||
local function sendCaptureHint(src, msg)
|
||||
local now = GetGameTimer()
|
||||
local nextOk = HintCooldown[src] or 0
|
||||
if now < nextOk then return end
|
||||
HintCooldown[src] = now + 1500
|
||||
TriggerClientEvent("turfwar:captureHint", src, msg)
|
||||
end
|
||||
|
||||
local function canCapture(turfId, gangId)
|
||||
local t = Turfs[turfId]
|
||||
if not t then return false, "bad_turf" end
|
||||
if gangId == 0 then return false, "neutral_player" end
|
||||
|
||||
local policeGang = (Config.PlayerPolice and tonumber(Config.PlayerPolice.POLICE_GANG_ID)) or 3
|
||||
if gangId == policeGang then
|
||||
return false, "police_cannot_capture"
|
||||
end
|
||||
|
||||
if t.owner == gangId then return false, "already_owner" end
|
||||
if t.owner ~= 0 and t.guardsAlive then return false, "guards_alive" end
|
||||
return true, "ok"
|
||||
end
|
||||
|
||||
local function setPausedState(turfId, paused)
|
||||
local t = Turfs[turfId]
|
||||
if not t then return end
|
||||
paused = paused and true or false
|
||||
if t.paused == paused then return end -- only broadcast on change
|
||||
t.paused = paused
|
||||
TriggerClientEvent("turfwar:capturePaused", -1, turfId, paused)
|
||||
end
|
||||
|
||||
local function resetContest(turfId)
|
||||
local t = Turfs[turfId]
|
||||
if not t then return end
|
||||
if (t.contestingGang or 0) == 0 and (t.progress or 0) == 0 then return end
|
||||
t.contestingGang = 0
|
||||
t.progress = 0
|
||||
t.paused = false
|
||||
t.lastActivity = 0
|
||||
broadcastTurfUpdate(turfId)
|
||||
TriggerClientEvent("turfwar:capturePaused", -1, turfId, false)
|
||||
end
|
||||
|
||||
-- cleanup tick so contests can't get stuck forever
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(1500)
|
||||
local now = GetGameTimer()
|
||||
for turfId, t in pairs(Turfs) do
|
||||
if t.contestingGang and t.contestingGang ~= 0 then
|
||||
local last = tonumber(t.lastActivity) or 0
|
||||
if last > 0 and (now - last) > CONTEST_IDLE_RESET_MS then
|
||||
resetContest(turfId)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("turfwar:attemptCapture", function(turfId)
|
||||
local src = source
|
||||
turfId = tostring(turfId)
|
||||
|
||||
local gangId = tonumber(PlayerGang[src]) or 0
|
||||
local t = Turfs[turfId]
|
||||
if not t then return end
|
||||
|
||||
-- Presence: any non-neutral ping counts for pause rules (including police)
|
||||
notePresence(turfId, src, gangId)
|
||||
|
||||
-- Throttle per-player per-turf
|
||||
LastPing[src] = LastPing[src] or {}
|
||||
local now = GetGameTimer()
|
||||
local nextOk = LastPing[src][turfId] or 0
|
||||
if now < nextOk then return end
|
||||
LastPing[src][turfId] = now + 900
|
||||
|
||||
if gangId == 0 then return end
|
||||
|
||||
local policeGang = (Config.PlayerPolice and tonumber(Config.PlayerPolice.POLICE_GANG_ID)) or 3
|
||||
if gangId == policeGang then
|
||||
-- =========================
|
||||
-- POLICE: Restore the peace
|
||||
-- =========================
|
||||
|
||||
-- Nothing to do if already neutral
|
||||
if (tonumber(t.owner) or 0) == 0 then
|
||||
sendCaptureHint(src, "This area is already neutral.")
|
||||
return
|
||||
end
|
||||
|
||||
-- Same guard rule as normal capture (prevents bypassing defenses)
|
||||
if t.owner ~= 0 and t.guardsAlive then
|
||||
sendCaptureHint(src, "Clear the guards before restoring peace.")
|
||||
return
|
||||
end
|
||||
|
||||
-- On-foot enforcement
|
||||
if PlayerOnFoot[src] == false then
|
||||
sendCaptureHint(src, "Exit your vehicle to restore peace.")
|
||||
return
|
||||
end
|
||||
|
||||
-- Mark activity so it doesn't idle-reset
|
||||
t.lastActivity = now
|
||||
|
||||
-- If police take over the contest, reset contest state cleanly
|
||||
if (tonumber(t.contestingGang) or 0) ~= policeGang then
|
||||
t.contestingGang = policeGang
|
||||
t.progress = math.max(1, tonumber(t.progress) or 0)
|
||||
t.paused = false
|
||||
broadcastTurfUpdate(turfId)
|
||||
end
|
||||
|
||||
-- If other gangs are present, pause (same rule)
|
||||
if isCapturePausedByOthers(turfId, t.contestingGang) then
|
||||
if (tonumber(t.progress) or 0) < 1 then t.progress = 1 end
|
||||
setPausedState(turfId, true)
|
||||
broadcastTurfUpdate(turfId)
|
||||
return
|
||||
else
|
||||
setPausedState(turfId, false)
|
||||
end
|
||||
|
||||
-- Progress restore-peace
|
||||
t.progress = (tonumber(t.progress) or 0) + 1
|
||||
|
||||
if t.progress >= PoliceRestoreToNeutral then
|
||||
-- Neutralize turf
|
||||
t.owner = 0
|
||||
t.progress = 0
|
||||
t.contestingGang = 0
|
||||
t.paused = false
|
||||
t.lastActivity = 0
|
||||
|
||||
-- Persist neutral owner
|
||||
if TurfwarPayouts and TurfwarPayouts.SaveTurfOwner then
|
||||
TurfwarPayouts.SaveTurfOwner(turfId, 0)
|
||||
end
|
||||
|
||||
-- Clear guards (neutral turf should not have guards)
|
||||
t.guardsAlive = false
|
||||
clearGuardsForTurf(turfId)
|
||||
|
||||
-- Broadcast changes
|
||||
broadcastTurfCaptured(turfId) -- clients will see owner=0
|
||||
broadcastLeaderboard(-1)
|
||||
setPausedState(turfId, false)
|
||||
else
|
||||
broadcastTurfUpdate(turfId)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
-- On-foot enforcement: only block if we KNOW they're in a vehicle
|
||||
if PlayerOnFoot[src] == false then
|
||||
sendCaptureHint(src, "Exit your vehicle to capture.")
|
||||
return
|
||||
end
|
||||
|
||||
local ok, reason = canCapture(turfId, gangId)
|
||||
if not ok then
|
||||
if reason == "guards_alive" then
|
||||
sendCaptureHint(src, "Clear the guards before capturing.")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Mark activity so contest doesn't stick forever
|
||||
t.lastActivity = now
|
||||
|
||||
-- Start contest if none, or if different gang takes over
|
||||
if (tonumber(t.contestingGang) or 0) ~= gangId then
|
||||
t.contestingGang = gangId
|
||||
t.progress = math.max(1, tonumber(t.progress) or 0) -- ensure >0 for HUD
|
||||
t.paused = false
|
||||
broadcastTurfUpdate(turfId) -- immediate HUD pop
|
||||
end
|
||||
|
||||
-- Pause if any other non-neutral gang present
|
||||
if isCapturePausedByOthers(turfId, t.contestingGang) then
|
||||
if (tonumber(t.progress) or 0) < 1 then t.progress = 1 end
|
||||
setPausedState(turfId, true)
|
||||
broadcastTurfUpdate(turfId)
|
||||
return
|
||||
else
|
||||
setPausedState(turfId, false)
|
||||
end
|
||||
|
||||
-- Progress
|
||||
t.progress = (tonumber(t.progress) or 0) + 1
|
||||
|
||||
if t.progress >= SecondsToCapture then
|
||||
-- ✅ Award wanted stars to everyone currently in the zone (based on presence pings)
|
||||
awardWantedToPresence(turfId, CAPTURE_WANTED_STARS)
|
||||
|
||||
t.owner = gangId
|
||||
t.progress = 0
|
||||
t.contestingGang = 0
|
||||
t.paused = false
|
||||
t.lastActivity = 0
|
||||
|
||||
if TurfwarPayouts and TurfwarPayouts.SaveTurfOwner then
|
||||
TurfwarPayouts.SaveTurfOwner(turfId, gangId)
|
||||
end
|
||||
|
||||
broadcastTurfCaptured(turfId)
|
||||
|
||||
-- leaderboard refresh on capture
|
||||
broadcastLeaderboard(-1)
|
||||
|
||||
spawnGuardsForTurf(turfId)
|
||||
setPausedState(turfId, false)
|
||||
else
|
||||
broadcastTurfUpdate(turfId)
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Resource start / reload support
|
||||
-- ------------------------------------------------------------
|
||||
AddEventHandler("onResourceStart", function(res)
|
||||
if res ~= GetCurrentResourceName() then return end
|
||||
SecondsToCapture = (Config and tonumber(Config.SECONDS_TO_CAPTURE)) or 60
|
||||
initTurfsFromConfig()
|
||||
|
||||
-- leaderboard refresh on resource start
|
||||
broadcastLeaderboard(-1)
|
||||
end)
|
||||
|
||||
RegisterCommand("tw_reload_turfs", function(src)
|
||||
initTurfsFromConfig()
|
||||
TriggerClientEvent("turfwar:snapshot", -1, snapshotForClient(), SecondsToCapture)
|
||||
|
||||
-- leaderboard refresh on reload
|
||||
broadcastLeaderboard(-1)
|
||||
end, true)
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Player dropped cleanup
|
||||
-- ------------------------------------------------------------
|
||||
AddEventHandler("playerDropped", function()
|
||||
local src = source
|
||||
|
||||
PlayerGang[src] = nil
|
||||
TriggerClientEvent("turfwar:playerGang", -1, src, -1)
|
||||
|
||||
if PlayerOnFoot then PlayerOnFoot[src] = nil end
|
||||
|
||||
if GuardHost == src then
|
||||
print("^3[turfwar]^7 GuardHost dropped; will re-elect on next guardsClientReady")
|
||||
GuardHost = nil
|
||||
end
|
||||
|
||||
for turfId, bucket in pairs(Presence) do
|
||||
if bucket then bucket[src] = nil end
|
||||
end
|
||||
|
||||
LastPing[src] = nil
|
||||
HintCooldown[src] = nil
|
||||
end)
|
||||
200
server/payouts.lua
Normal file
200
server/payouts.lua
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
-- server/payouts.lua
|
||||
print("^2[turfwar]^7 server/payouts.lua LOADED (turf owners + gang bank + payouts)")
|
||||
|
||||
TurfwarPayouts = TurfwarPayouts or {}
|
||||
|
||||
-- ======================================================
|
||||
-- CONFIG (matches your existing DB)
|
||||
-- ======================================================
|
||||
local GANGBANK_TABLE = "turfwar_gang_accounts" -- (gang_id, balance, updated_at)
|
||||
local TURFOWNERS_TABLE = "turfwar_turf_owners" -- change if your table name differs
|
||||
|
||||
-- Ensure globals exist
|
||||
PlayerGang = PlayerGang or {}
|
||||
|
||||
local function i(v) return math.floor(tonumber(v) or 0) end
|
||||
|
||||
local function dbReady()
|
||||
return MySQL and MySQL.query and MySQL.update
|
||||
end
|
||||
|
||||
-- ======================================================
|
||||
-- Turf owner persistence (used by server/main.lua)
|
||||
-- ======================================================
|
||||
function TurfwarPayouts.SaveTurfOwner(turfId, gangId)
|
||||
turfId = tostring(turfId or "")
|
||||
gangId = i(gangId)
|
||||
if turfId == "" then return end
|
||||
if not dbReady() then
|
||||
print("^1[turfwar]^7 SaveTurfOwner: MySQL not ready")
|
||||
return
|
||||
end
|
||||
|
||||
MySQL.update.await(
|
||||
("INSERT INTO %s (turf_id, owner_gang) VALUES (?, ?) " ..
|
||||
"ON DUPLICATE KEY UPDATE owner_gang = VALUES(owner_gang)"):format(TURFOWNERS_TABLE),
|
||||
{ turfId, gangId }
|
||||
)
|
||||
end
|
||||
|
||||
function TurfwarPayouts.LoadTurfOwnersIntoRuntime(Turfs)
|
||||
if not dbReady() then
|
||||
print("^1[turfwar]^7 LoadTurfOwnersIntoRuntime: MySQL not ready")
|
||||
return
|
||||
end
|
||||
|
||||
local rows = MySQL.query.await(
|
||||
("SELECT turf_id, owner_gang FROM %s"):format(TURFOWNERS_TABLE),
|
||||
{}
|
||||
) or {}
|
||||
|
||||
local applied = 0
|
||||
for _, r in ipairs(rows) do
|
||||
local id = tostring(r.turf_id)
|
||||
local owner = i(r.owner_gang)
|
||||
if Turfs and Turfs[id] then
|
||||
Turfs[id].owner = owner
|
||||
Turfs[id].progress = 0
|
||||
Turfs[id].contestingGang = 0
|
||||
applied = applied + 1
|
||||
end
|
||||
end
|
||||
|
||||
print(("^2[turfwar]^7 Turf owners loaded from DB: %d applied"):format(applied))
|
||||
end
|
||||
|
||||
-- ======================================================
|
||||
-- Gang bank helpers
|
||||
-- ======================================================
|
||||
local function ensureGangRow(gangId)
|
||||
gangId = i(gangId)
|
||||
if gangId <= 0 then return end
|
||||
if not dbReady() then return end
|
||||
|
||||
-- requires gang_id to be UNIQUE/PK
|
||||
MySQL.update.await(
|
||||
("INSERT INTO %s (gang_id, balance) VALUES (?, 0) " ..
|
||||
"ON DUPLICATE KEY UPDATE gang_id = gang_id"):format(GANGBANK_TABLE),
|
||||
{ gangId }
|
||||
)
|
||||
end
|
||||
|
||||
local function getGangBalance(gangId)
|
||||
gangId = i(gangId)
|
||||
if gangId <= 0 then return 0 end
|
||||
if not dbReady() then return 0 end
|
||||
|
||||
local rows = MySQL.query.await(
|
||||
("SELECT balance FROM %s WHERE gang_id=? LIMIT 1"):format(GANGBANK_TABLE),
|
||||
{ gangId }
|
||||
) or {}
|
||||
|
||||
return tonumber(rows[1] and rows[1].balance) or 0
|
||||
end
|
||||
|
||||
local function addGangMoney(gangId, amount)
|
||||
gangId = i(gangId)
|
||||
amount = i(amount)
|
||||
if gangId <= 0 or amount == 0 then return getGangBalance(gangId) end
|
||||
if not dbReady() then return 0 end
|
||||
|
||||
ensureGangRow(gangId)
|
||||
|
||||
-- Atomic increment
|
||||
MySQL.update.await(
|
||||
("UPDATE %s SET balance = balance + ? WHERE gang_id = ?"):format(GANGBANK_TABLE),
|
||||
{ amount, gangId }
|
||||
)
|
||||
|
||||
return getGangBalance(gangId)
|
||||
end
|
||||
|
||||
-- Expose for other scripts if you want them
|
||||
function TurfwarPayouts.GetGangBalance(gangId)
|
||||
return getGangBalance(gangId)
|
||||
end
|
||||
|
||||
function TurfwarPayouts.AddGangMoney(gangId, amount)
|
||||
return addGangMoney(gangId, amount)
|
||||
end
|
||||
|
||||
-- ======================================================
|
||||
-- Push to HUD (server -> client)
|
||||
-- ======================================================
|
||||
function TurfwarPayouts.PushGangBankToPlayer(src, gangId)
|
||||
gangId = i(gangId)
|
||||
local bal = 0
|
||||
if gangId > 0 then
|
||||
bal = getGangBalance(gangId)
|
||||
end
|
||||
TriggerClientEvent("turfwar:gangbank:update", src, gangId, bal)
|
||||
end
|
||||
|
||||
-- Client requests "my gang bank"
|
||||
RegisterNetEvent("turfwar:gangbank:request", function()
|
||||
local src = source
|
||||
local gid = i(PlayerGang[src] or 0)
|
||||
TurfwarPayouts.PushGangBankToPlayer(src, gid)
|
||||
end)
|
||||
|
||||
-- ======================================================
|
||||
-- Payout tick (called from server/main.lua)
|
||||
-- ======================================================
|
||||
function TurfwarPayouts.DoPayoutTick(Turfs)
|
||||
if not dbReady() then
|
||||
print("^1[turfwar]^7 DoPayoutTick: MySQL not ready")
|
||||
return
|
||||
end
|
||||
|
||||
local inc = (Config and Config.INCOME) or {}
|
||||
local excluded = (inc and inc.excludedGangs) or {}
|
||||
local defaultPayout = i(inc.defaultTurfPayout or 10)
|
||||
|
||||
local totals = {} -- [gangId] = payout this tick
|
||||
|
||||
for turfId, t in pairs(Turfs or {}) do
|
||||
local owner = i(t.owner)
|
||||
if owner ~= 0 and not excluded[owner] then
|
||||
local cfg = (Config and Config.TURFS and Config.TURFS[turfId]) or nil
|
||||
local payout = defaultPayout
|
||||
if cfg and cfg.payout ~= nil then payout = i(cfg.payout) end
|
||||
|
||||
if payout > 0 then
|
||||
totals[owner] = (totals[owner] or 0) + payout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for gangId, amount in pairs(totals) do
|
||||
local newBal = addGangMoney(gangId, amount)
|
||||
print(("^2[turfwar]^7 Payout: gang %d +$%d => $%d"):format(gangId, amount, newBal))
|
||||
|
||||
-- Push updated balance to online players in that gang
|
||||
for _, pid in ipairs(GetPlayers()) do
|
||||
local src = tonumber(pid)
|
||||
if src and i(PlayerGang[src] or 0) == gangId then
|
||||
TriggerClientEvent("turfwar:gangbank:update", src, gangId, newBal)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ======================================================
|
||||
-- Debug
|
||||
-- ======================================================
|
||||
RegisterCommand("tw_gangbank_dump", function()
|
||||
if not dbReady() then
|
||||
print("^1[turfwar]^7 tw_gangbank_dump: MySQL not ready")
|
||||
return
|
||||
end
|
||||
|
||||
local rows = MySQL.query.await(
|
||||
("SELECT gang_id, balance, updated_at FROM %s ORDER BY gang_id"):format(GANGBANK_TABLE),
|
||||
{}
|
||||
) or {}
|
||||
|
||||
print(("^2[turfwar]^7 Gang accounts (%d rows):"):format(#rows))
|
||||
for _, r in ipairs(rows) do
|
||||
print((" gang=%s balance=%s updated=%s"):format(r.gang_id, r.balance, r.updated_at))
|
||||
end
|
||||
end, true)
|
||||
92
server/persistence.lua
Normal file
92
server/persistence.lua
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
-- server/persistence.lua
|
||||
print("^2[turfwar]^7 persistence.lua loaded (MySQL await version)")
|
||||
|
||||
TurfwarPersist = TurfwarPersist or {}
|
||||
|
||||
local function getIdentifier(src)
|
||||
local id = GetPlayerIdentifierByType(src, "license2")
|
||||
if id and id ~= "" then return id end
|
||||
|
||||
id = GetPlayerIdentifierByType(src, "license")
|
||||
if id and id ~= "" then return id end
|
||||
|
||||
local ids = GetPlayerIdentifiers(src)
|
||||
return ids and ids[1] or nil
|
||||
end
|
||||
|
||||
local function dbReady()
|
||||
return MySQL ~= nil
|
||||
end
|
||||
|
||||
-- Call once on resource start to confirm DB + table exists
|
||||
CreateThread(function()
|
||||
Wait(500)
|
||||
if not dbReady() then
|
||||
print("^1[turfwar]^7 ERROR: MySQL is nil. Did you add '@oxmysql/lib/MySQL.lua' BEFORE this file?")
|
||||
return
|
||||
end
|
||||
|
||||
-- Ensure table exists (auto-create)
|
||||
local ok, err = pcall(function()
|
||||
MySQL.query.await([[
|
||||
CREATE TABLE IF NOT EXISTS turfwar_players (
|
||||
identifier VARCHAR(64) NOT NULL PRIMARY KEY,
|
||||
gang_id INT NOT NULL DEFAULT 0,
|
||||
rank INT NOT NULL DEFAULT 0,
|
||||
last_seen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
ON UPDATE CURRENT_TIMESTAMP
|
||||
)
|
||||
]])
|
||||
end)
|
||||
|
||||
if ok then
|
||||
print("^2[turfwar]^7 DB ready: turfwar_players table ensured.")
|
||||
else
|
||||
print("^1[turfwar]^7 DB ERROR creating/ensuring table: " .. tostring(err))
|
||||
end
|
||||
end)
|
||||
|
||||
function TurfwarPersist.Load(src)
|
||||
if not dbReady() then return 0, 0 end
|
||||
|
||||
local ident = getIdentifier(src)
|
||||
if not ident then
|
||||
print(("^3[turfwar]^7 Persist.Load: no identifier for src=%s"):format(src))
|
||||
return 0, 0
|
||||
end
|
||||
|
||||
local row = MySQL.single.await("SELECT gang_id, rank FROM turfwar_players WHERE identifier = ?", { ident })
|
||||
|
||||
if row then
|
||||
return tonumber(row.gang_id) or 0, tonumber(row.rank) or 0
|
||||
end
|
||||
|
||||
-- first time: insert
|
||||
MySQL.insert.await("INSERT INTO turfwar_players (identifier, gang_id, rank) VALUES (?, 0, 0)", { ident })
|
||||
return 0, 0
|
||||
end
|
||||
|
||||
function TurfwarPersist.SaveGang(src, gangId)
|
||||
if not dbReady() then
|
||||
print("^1[turfwar]^7 Persist.SaveGang: MySQL not ready (MySQL=nil)")
|
||||
return
|
||||
end
|
||||
|
||||
local ident = getIdentifier(src)
|
||||
if not ident then
|
||||
print(("^1[turfwar]^7 Persist.SaveGang: no identifier for src=%s"):format(src))
|
||||
return
|
||||
end
|
||||
|
||||
gangId = tonumber(gangId) or 0
|
||||
|
||||
print(("^3[turfwar]^7 Persist.SaveGang: ident=%s gangId=%d"):format(ident, gangId))
|
||||
|
||||
local affected = MySQL.update.await([[
|
||||
INSERT INTO turfwar_players (identifier, gang_id, rank)
|
||||
VALUES (?, ?, 0)
|
||||
ON DUPLICATE KEY UPDATE gang_id = VALUES(gang_id)
|
||||
]], { ident, gangId })
|
||||
|
||||
print(("^2[turfwar]^7 Persist.SaveGang: DB affected=%s"):format(tostring(affected)))
|
||||
end
|
||||
74
server/player_police.lua
Normal file
74
server/player_police.lua
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
-- server/player_police.lua
|
||||
print("^2[turfwar]^7 server/player_police.lua loaded (police tracking)")
|
||||
|
||||
local PP = Config.PlayerPolice or {}
|
||||
local POLICE = tonumber(PP.POLICE_GANG_ID) or 3
|
||||
local PREFIX = PP.CHAT_PREFIX or "^4[POLICE]^7 "
|
||||
local ONLY_UP = (PP.ONLY_ANNOUNCE_ON_STAR_INCREASE ~= false)
|
||||
|
||||
local lastStars = {} -- [src] = stars
|
||||
|
||||
local function isPolice(src)
|
||||
local g = (PlayerGang and PlayerGang[src]) or 0
|
||||
return tonumber(g) == POLICE
|
||||
end
|
||||
|
||||
local function sendToPolice(eventName, ...)
|
||||
for _, sid in ipairs(GetPlayers()) do
|
||||
local p = tonumber(sid)
|
||||
if p and isPolice(p) then
|
||||
TriggerClientEvent(eventName, p, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function chatToPolice(msg)
|
||||
sendToPolice('turfwar:pp:chat', msg)
|
||||
end
|
||||
|
||||
RegisterNetEvent('turfwar:pp:starsChanged', function(stars, interior)
|
||||
local src = source
|
||||
stars = tonumber(stars) or 0
|
||||
interior = (interior == true)
|
||||
|
||||
local prev = lastStars[src] or 0
|
||||
lastStars[src] = stars
|
||||
|
||||
if stars > 0 then
|
||||
local announce = true
|
||||
if ONLY_UP then announce = (stars > prev) end
|
||||
if announce then
|
||||
chatToPolice(("%sA %d star crime has been detected"):format(PREFIX, stars))
|
||||
end
|
||||
end
|
||||
|
||||
-- tell police the mode changed
|
||||
sendToPolice('turfwar:pp:update', src, {
|
||||
type = "mode",
|
||||
stars = stars,
|
||||
interior = interior
|
||||
})
|
||||
end)
|
||||
|
||||
RegisterNetEvent('turfwar:pp:posUpdate', function(stars, interior, x, y, z, heading)
|
||||
local src = source
|
||||
stars = tonumber(stars) or 0
|
||||
interior = (interior == true)
|
||||
x = tonumber(x); y = tonumber(y); z = tonumber(z)
|
||||
heading = tonumber(heading) or 0.0
|
||||
if not x or not y or not z then return end
|
||||
|
||||
sendToPolice('turfwar:pp:update', src, {
|
||||
type = "pos",
|
||||
stars = stars,
|
||||
interior = interior,
|
||||
x = x, y = y, z = z,
|
||||
heading = heading
|
||||
})
|
||||
end)
|
||||
|
||||
AddEventHandler('playerDropped', function()
|
||||
local src = source
|
||||
lastStars[src] = nil
|
||||
sendToPolice('turfwar:pp:clear', src)
|
||||
end)
|
||||
96
server/police_wanted_escalate.lua
Normal file
96
server/police_wanted_escalate.lua
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
print("^2[turfwar]^7 police_wanted_escalate.lua loaded (server)")
|
||||
|
||||
local HEAT_WINDOW_SEC = 600
|
||||
local POLICE_GANG_ID = 3
|
||||
local MIN_STARS_FOR_COP_DAMAGE = 1
|
||||
|
||||
local function StarsForCount(n)
|
||||
if n <= 0 then return 0 end
|
||||
if n == 1 then return 2 end
|
||||
if n == 2 then return 3 end
|
||||
if n == 3 then return 4 end
|
||||
return 5
|
||||
end
|
||||
|
||||
-- heat[killerSrc] = { timestamps = { t1, t2, ... } }
|
||||
local heat = {}
|
||||
|
||||
local function nowSec() return os.time() end
|
||||
|
||||
local function pruneOld(killerSrc)
|
||||
local h = heat[killerSrc]
|
||||
if not h then return 0 end
|
||||
local cutoff = nowSec() - HEAT_WINDOW_SEC
|
||||
local keep = {}
|
||||
for _, t in ipairs(h.timestamps) do
|
||||
if t >= cutoff then keep[#keep+1] = t end
|
||||
end
|
||||
h.timestamps = keep
|
||||
return #keep
|
||||
end
|
||||
|
||||
local function addCopKill(killerSrc)
|
||||
heat[killerSrc] = heat[killerSrc] or { timestamps = {} }
|
||||
heat[killerSrc].timestamps[#heat[killerSrc].timestamps + 1] = nowSec()
|
||||
return pruneOld(killerSrc)
|
||||
end
|
||||
|
||||
-- IMPORTANT: This is the #1 failure point.
|
||||
-- Your PlayerGang table might not be global in THIS file.
|
||||
local function IsPolicePlayer(src)
|
||||
-- If PlayerGang is global, great:
|
||||
if PlayerGang ~= nil then
|
||||
return PlayerGang[src] == POLICE_GANG_ID
|
||||
end
|
||||
|
||||
-- Fallback option: ACE permission check (enable if you use it)
|
||||
-- return IsPlayerAceAllowed(src, "police")
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Debug helper
|
||||
local function dbg(msg)
|
||||
print(("^3[turfwar wanted]^7 %s"):format(msg))
|
||||
end
|
||||
|
||||
-- --------------- TEST PIPELINE (temporary) ---------------
|
||||
-- Set to true to bypass police checks while testing wiring
|
||||
local BYPASS_POLICE_CHECK = true
|
||||
-- --------------- /TEST PIPELINE --------------------------
|
||||
|
||||
RegisterNetEvent('turfwar:wanted:policeDamagedBy', function(killerSrc)
|
||||
local victimSrc = source
|
||||
killerSrc = tonumber(killerSrc) or 0
|
||||
dbg(("policeDamagedBy victim=%d killer=%d"):format(victimSrc, killerSrc))
|
||||
|
||||
if killerSrc <= 0 or killerSrc == victimSrc then return end
|
||||
|
||||
if not BYPASS_POLICE_CHECK then
|
||||
local isPolice = IsPolicePlayer(victimSrc)
|
||||
dbg((" IsPolicePlayer(%d)=%s"):format(victimSrc, tostring(isPolice)))
|
||||
if not isPolice then return end
|
||||
end
|
||||
|
||||
TriggerClientEvent('turfwar:wanted:setMinimum', killerSrc, MIN_STARS_FOR_COP_DAMAGE)
|
||||
dbg((" -> setMinimum %d stars to %d"):format(MIN_STARS_FOR_COP_DAMAGE, killerSrc))
|
||||
end)
|
||||
|
||||
RegisterNetEvent('turfwar:wanted:policeKilledBy', function(killerSrc)
|
||||
local victimSrc = source
|
||||
killerSrc = tonumber(killerSrc) or 0
|
||||
dbg(("policeKilledBy victim=%d killer=%d"):format(victimSrc, killerSrc))
|
||||
|
||||
if killerSrc <= 0 or killerSrc == victimSrc then return end
|
||||
|
||||
if not BYPASS_POLICE_CHECK then
|
||||
local isPolice = IsPolicePlayer(victimSrc)
|
||||
dbg((" IsPolicePlayer(%d)=%s"):format(victimSrc, tostring(isPolice)))
|
||||
if not isPolice then return end
|
||||
end
|
||||
|
||||
local count = addCopKill(killerSrc)
|
||||
local stars = StarsForCount(count)
|
||||
TriggerClientEvent('turfwar:wanted:setEscalated', killerSrc, stars, count, HEAT_WINDOW_SEC)
|
||||
dbg((" -> setEscalated killer=%d heat=%d stars=%d"):format(killerSrc, count, stars))
|
||||
end)
|
||||
Loading…
Reference in New Issue
Block a user