Upload files to "client"
This commit is contained in:
parent
4cfaeceae8
commit
4910c39102
558
client/guards.lua
Normal file
558
client/guards.lua
Normal file
|
|
@ -0,0 +1,558 @@
|
||||||
|
-- client/guards.lua
|
||||||
|
print("^2[turfwar]^7 CLIENT guards.lua LOADED (invincible guards + manual damage both ways)")
|
||||||
|
|
||||||
|
local DEBUG = false
|
||||||
|
local function dbg(msg) if DEBUG then print(("^3[turfwar]^7 [guards] %s"):format(msg)) end end
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Handshake + request gang snapshot
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
CreateThread(function()
|
||||||
|
while not NetworkIsSessionStarted() do Wait(250) end
|
||||||
|
TriggerServerEvent("turfwar:guardsClientReady")
|
||||||
|
Wait(1500)
|
||||||
|
TriggerServerEvent("turfwar:requestAllPlayerGangs")
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Local gang id (for colours only)
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
local LocalGangId = 0
|
||||||
|
RegisterNetEvent("turfwar:gangUpdate", function(g) LocalGangId = tonumber(g) or 0 end)
|
||||||
|
RegisterNetEvent("turfwar:setFaction", function(g) LocalGangId = tonumber(g) or LocalGangId or 0 end)
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- All players' gang cache (server must broadcast turfwar:playerGang)
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
local PlayerGangByServerId = {} -- [serverId] = gangId
|
||||||
|
RegisterNetEvent("turfwar:playerGang", function(serverId, gangId)
|
||||||
|
serverId = tonumber(serverId)
|
||||||
|
gangId = tonumber(gangId)
|
||||||
|
if not serverId then return end
|
||||||
|
if not gangId or gangId < 0 then
|
||||||
|
PlayerGangByServerId[serverId] = nil
|
||||||
|
else
|
||||||
|
PlayerGangByServerId[serverId] = gangId
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterCommand("tw_gangcache", function()
|
||||||
|
print("^6[turfwar]^7 PlayerGangByServerId cache:")
|
||||||
|
for k,v in pairs(PlayerGangByServerId) do
|
||||||
|
print((" %s => %s"):format(k, v))
|
||||||
|
end
|
||||||
|
end, false)
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Relationship group (behaviour only; damage is manual)
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
local GUARD_REL = nil
|
||||||
|
local REL_INIT = false
|
||||||
|
|
||||||
|
local function ensureRel()
|
||||||
|
if REL_INIT then return end
|
||||||
|
REL_INIT = true
|
||||||
|
|
||||||
|
GUARD_REL = AddRelationshipGroup("TW_GUARDS")
|
||||||
|
SetRelationshipBetweenGroups(0, GUARD_REL, GUARD_REL)
|
||||||
|
|
||||||
|
-- neutral to players by default; we script combat
|
||||||
|
SetRelationshipBetweenGroups(3, GUARD_REL, `PLAYER`)
|
||||||
|
SetRelationshipBetweenGroups(3, `PLAYER`, GUARD_REL)
|
||||||
|
|
||||||
|
local neutralGroups = {
|
||||||
|
`AMBIENT_GANG_LOST`,
|
||||||
|
`AMBIENT_GANG_BALLAS`,
|
||||||
|
`AMBIENT_GANG_FAMILY`,
|
||||||
|
`AMBIENT_GANG_MEXICAN`,
|
||||||
|
`AMBIENT_GANG_SALVA`,
|
||||||
|
`AMBIENT_GANG_WEICHENG`,
|
||||||
|
`COP`,
|
||||||
|
`SECURITY_GUARD`,
|
||||||
|
}
|
||||||
|
for _, grp in ipairs(neutralGroups) do
|
||||||
|
SetRelationshipBetweenGroups(3, GUARD_REL, grp)
|
||||||
|
SetRelationshipBetweenGroups(3, grp, GUARD_REL)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- UI config
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
local MARKER_MAX_DIST = 70.0
|
||||||
|
local BLIP_SPRITE = 270
|
||||||
|
local BLIP_SCALE = 0.65
|
||||||
|
local ENEMY_BLIP_COLOUR = 1
|
||||||
|
local FRIENDLY_ALPHA = 160
|
||||||
|
|
||||||
|
local FACTION_BLIP_COLOUR = { [1]=5,[2]=2,[3]=3,[4]=40,[5]=7 }
|
||||||
|
local FACTION_RGB = {
|
||||||
|
[1]={r=255,g=220,b=0},
|
||||||
|
[2]={r=0,g=200,b=0},
|
||||||
|
[3]={r=0,g=130,b=255},
|
||||||
|
[4]={r=60,g=60,b=60},
|
||||||
|
[5]={r=160,g=80,b=255}
|
||||||
|
}
|
||||||
|
local ENEMY_RGB = { r=255, g=0, b=0 }
|
||||||
|
|
||||||
|
local function isFriendly(ownerFaction)
|
||||||
|
return (LocalGangId ~= 0 and ownerFaction ~= 0 and LocalGangId == ownerFaction)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getColours(ownerFaction)
|
||||||
|
if isFriendly(ownerFaction) then
|
||||||
|
return (FACTION_RGB[ownerFaction] or {r=255,g=255,b=255}), (FACTION_BLIP_COLOUR[ownerFaction] or 0)
|
||||||
|
end
|
||||||
|
return ENEMY_RGB, ENEMY_BLIP_COLOUR
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- State
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
local GuardState = {} -- [turfId] = { ownerFaction, peds, blips, spawnPoints }
|
||||||
|
local GuardMeta = {} -- [ped] = { turfId, ownerFaction, spawn=vector3(...) }
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Helpers
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
local function loadModel(model)
|
||||||
|
local h = GetHashKey(model)
|
||||||
|
if not IsModelInCdimage(h) then return nil end
|
||||||
|
RequestModel(h)
|
||||||
|
local timeout = GetGameTimer() + 5000
|
||||||
|
while not HasModelLoaded(h) do
|
||||||
|
Wait(10)
|
||||||
|
if GetGameTimer() > timeout then return nil end
|
||||||
|
end
|
||||||
|
return h
|
||||||
|
end
|
||||||
|
|
||||||
|
local function delPed(p)
|
||||||
|
if p and DoesEntityExist(p) then
|
||||||
|
GuardMeta[p] = nil
|
||||||
|
SetEntityAsMissionEntity(p, true, true)
|
||||||
|
DeleteEntity(p)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function delBlip(b)
|
||||||
|
if b and DoesBlipExist(b) then RemoveBlip(b) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function clearTurf(turfId)
|
||||||
|
local st = GuardState[turfId]
|
||||||
|
if not st then return end
|
||||||
|
for _, p in pairs(st.peds or {}) do delPed(p) end
|
||||||
|
for _, b in pairs(st.blips or {}) do delBlip(b) end
|
||||||
|
GuardState[turfId] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function makeBlip(ped, ownerFaction)
|
||||||
|
local blip = AddBlipForEntity(ped)
|
||||||
|
SetBlipSprite(blip, BLIP_SPRITE)
|
||||||
|
SetBlipScale(blip, BLIP_SCALE)
|
||||||
|
SetBlipAsShortRange(blip, true)
|
||||||
|
local _, col = getColours(ownerFaction)
|
||||||
|
SetBlipColour(blip, col)
|
||||||
|
BeginTextCommandSetBlipName("STRING")
|
||||||
|
AddTextComponentString("Guard")
|
||||||
|
EndTextCommandSetBlipName(blip)
|
||||||
|
return blip
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isGuardPed(ped)
|
||||||
|
return ped and ped ~= 0 and GuardMeta[ped] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function standAt(ped, pos)
|
||||||
|
ClearPedTasks(ped)
|
||||||
|
TaskStandGuard(ped, pos.x, pos.y, pos.z, 0.0, "WORLD_HUMAN_GUARD_STAND", 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function configureGuard(ped)
|
||||||
|
ensureRel()
|
||||||
|
|
||||||
|
SetEntityAsMissionEntity(ped, true, true)
|
||||||
|
SetPedRelationshipGroupHash(ped, GUARD_REL)
|
||||||
|
|
||||||
|
SetEntityMaxHealth(ped, 220)
|
||||||
|
SetEntityHealth(ped, 220)
|
||||||
|
SetPedArmour(ped, 75)
|
||||||
|
SetPedAccuracy(ped, 55)
|
||||||
|
|
||||||
|
SetPedAlertness(ped, 1)
|
||||||
|
SetPedSeeingRange(ped, 120.0)
|
||||||
|
SetPedHearingRange(ped, 120.0)
|
||||||
|
|
||||||
|
SetPedCombatAbility(ped, 2)
|
||||||
|
SetPedCombatMovement(ped, 2)
|
||||||
|
SetPedCombatRange(ped, 2)
|
||||||
|
SetPedCombatAttributes(ped, 46, true)
|
||||||
|
SetPedDropsWeaponsWhenDead(ped, false)
|
||||||
|
|
||||||
|
SetCanAttackFriendly(ped, false, false)
|
||||||
|
SetBlockingOfNonTemporaryEvents(ped, true)
|
||||||
|
SetPedKeepTask(ped, true)
|
||||||
|
SetPedFleeAttributes(ped, 0, false)
|
||||||
|
SetPedSuffersCriticalHits(ped, false)
|
||||||
|
SetPedShootRate(ped, 450)
|
||||||
|
|
||||||
|
-- KEY: they never take native damage (prevents guard→guard deaths / weird ownership)
|
||||||
|
SetEntityInvincible(ped, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function applyNoCollisionAmongGuards(st)
|
||||||
|
local peds = st.peds or {}
|
||||||
|
for i = 1, #peds do
|
||||||
|
local a = peds[i]
|
||||||
|
if a and DoesEntityExist(a) then
|
||||||
|
for j = i + 1, #peds do
|
||||||
|
local b = peds[j]
|
||||||
|
if b and DoesEntityExist(b) then
|
||||||
|
SetEntityNoCollisionEntity(a, b, true)
|
||||||
|
SetEntityNoCollisionEntity(b, a, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Nearest enemy player
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
local AGGRO_RANGE = 90.0
|
||||||
|
local function findNearestEnemyPlayerPed(ownerFaction, fromPos)
|
||||||
|
local bestPed, bestDist = nil, AGGRO_RANGE + 0.01
|
||||||
|
|
||||||
|
for _, pid in ipairs(GetActivePlayers()) do
|
||||||
|
local spid = GetPlayerServerId(pid)
|
||||||
|
local gang = PlayerGangByServerId[spid]
|
||||||
|
|
||||||
|
if gang and gang ~= 0 and gang ~= ownerFaction then
|
||||||
|
local ped = GetPlayerPed(pid)
|
||||||
|
if ped and ped ~= 0 and DoesEntityExist(ped) and not IsEntityDead(ped) then
|
||||||
|
local d = #(GetEntityCoords(ped) - fromPos)
|
||||||
|
if d < bestDist then
|
||||||
|
bestDist = d
|
||||||
|
bestPed = ped
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return bestPed, bestDist
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Spawn/clear events (host only)
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
RegisterNetEvent("turfwar:spawnGuards", function(payload)
|
||||||
|
if type(payload) ~= "table" then return end
|
||||||
|
|
||||||
|
local turfId = tostring(payload.turfId)
|
||||||
|
local ownerFaction = tonumber(payload.ownerFaction) or 0
|
||||||
|
local count = tonumber(payload.count) or 0
|
||||||
|
local spawns = payload.spawns or {}
|
||||||
|
|
||||||
|
if ownerFaction == 0 or count <= 0 then
|
||||||
|
clearTurf(turfId)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
clearTurf(turfId)
|
||||||
|
|
||||||
|
GuardState[turfId] = {
|
||||||
|
ownerFaction = ownerFaction,
|
||||||
|
peds = {},
|
||||||
|
blips = {},
|
||||||
|
spawnPoints = spawns
|
||||||
|
}
|
||||||
|
|
||||||
|
local model = loadModel(payload.model or "g_m_y_lost_01")
|
||||||
|
if not model then
|
||||||
|
print("^1[turfwar]^7 Guard model failed to load")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, count do
|
||||||
|
local pos = spawns[i]
|
||||||
|
if not pos then break end
|
||||||
|
|
||||||
|
local ped = CreatePed(4, model, pos.x, pos.y, pos.z, 0.0, true, true)
|
||||||
|
if ped and DoesEntityExist(ped) then
|
||||||
|
configureGuard(ped)
|
||||||
|
|
||||||
|
local weapon = payload.weapon or "WEAPON_PISTOL"
|
||||||
|
GiveWeaponToPed(ped, GetHashKey(weapon), 250, false, true)
|
||||||
|
SetCurrentPedWeapon(ped, GetHashKey(weapon), true)
|
||||||
|
|
||||||
|
standAt(ped, pos)
|
||||||
|
|
||||||
|
GuardState[turfId].peds[i] = ped
|
||||||
|
GuardState[turfId].blips[i] = makeBlip(ped, ownerFaction)
|
||||||
|
|
||||||
|
GuardMeta[ped] = {
|
||||||
|
turfId = turfId,
|
||||||
|
ownerFaction = ownerFaction,
|
||||||
|
spawn = vector3(pos.x, pos.y, pos.z)
|
||||||
|
}
|
||||||
|
|
||||||
|
if not NetworkHasControlOfEntity(ped) then
|
||||||
|
NetworkRequestControlOfEntity(ped)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
SetModelAsNoLongerNeeded(model)
|
||||||
|
applyNoCollisionAmongGuards(GuardState[turfId])
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent("turfwar:clearGuards", function(turfId)
|
||||||
|
clearTurf(tostring(turfId))
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Manual damage BOTH ways
|
||||||
|
-- - Enemy player -> guard
|
||||||
|
-- - Guard -> enemy player
|
||||||
|
-- Notes:
|
||||||
|
-- - We DO NOT trust weapon hash indexes across builds; use fixed damage values.
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
local GUARD_HIT_DMG_TO_PLAYER = 18 -- per hit (tune)
|
||||||
|
local PLAYER_HIT_DMG_TO_GUARD = 25 -- per hit (tune)
|
||||||
|
|
||||||
|
-- per-attacker throttle (prevents one bullet generating multiple events)
|
||||||
|
local lastHitTick = {} -- [attackerNetId .. ":" .. victimNetId] = gameTimer
|
||||||
|
|
||||||
|
local function shouldThrottle(attacker, victim)
|
||||||
|
local a = tostring(attacker or 0)
|
||||||
|
local v = tostring(victim or 0)
|
||||||
|
local key = a .. ":" .. v
|
||||||
|
local now = GetGameTimer()
|
||||||
|
local last = lastHitTick[key] or 0
|
||||||
|
if now - last < 90 then return true end -- ~11 hits/sec max
|
||||||
|
lastHitTick[key] = now
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function gangFromPlayerPed(attackerPed)
|
||||||
|
if not attackerPed or attackerPed == 0 or not IsPedAPlayer(attackerPed) then return nil end
|
||||||
|
local playerIndex = NetworkGetPlayerIndexFromPed(attackerPed)
|
||||||
|
if not playerIndex or playerIndex == -1 then return nil end
|
||||||
|
local serverId = GetPlayerServerId(playerIndex)
|
||||||
|
if not serverId then return nil end
|
||||||
|
return PlayerGangByServerId[serverId]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function applyDamageToPed(victimPed, amount)
|
||||||
|
if not victimPed or victimPed == 0 or not DoesEntityExist(victimPed) then return end
|
||||||
|
if amount <= 0 then return end
|
||||||
|
|
||||||
|
local hp = GetEntityHealth(victimPed)
|
||||||
|
local newHp = hp - amount
|
||||||
|
|
||||||
|
if newHp <= 0 then
|
||||||
|
-- kill
|
||||||
|
SetEntityHealth(victimPed, 0)
|
||||||
|
else
|
||||||
|
SetEntityHealth(victimPed, newHp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
AddEventHandler("gameEventTriggered", function(name, args)
|
||||||
|
if name ~= "CEventNetworkEntityDamage" then return end
|
||||||
|
|
||||||
|
local victim = args[1]
|
||||||
|
local attacker = args[2]
|
||||||
|
|
||||||
|
if not victim or victim == 0 or not DoesEntityExist(victim) then return end
|
||||||
|
if not attacker or attacker == 0 or not DoesEntityExist(attacker) then return end
|
||||||
|
if shouldThrottle(attacker, victim) then return end
|
||||||
|
|
||||||
|
local victimIsGuard = isGuardPed(victim)
|
||||||
|
local attackerIsGuard = isGuardPed(attacker)
|
||||||
|
local attackerIsPlayer = IsPedAPlayer(attacker)
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Guard vs Guard: ignore + calm
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
if victimIsGuard and attackerIsGuard then
|
||||||
|
ClearEntityLastDamageEntity(victim)
|
||||||
|
local meta = GuardMeta[victim]
|
||||||
|
if meta and meta.spawn then
|
||||||
|
ClearPedTasksImmediately(victim)
|
||||||
|
standAt(victim, meta.spawn)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Player -> Guard (manual damage if ENEMY)
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
if victimIsGuard and attackerIsPlayer then
|
||||||
|
ClearEntityLastDamageEntity(victim)
|
||||||
|
|
||||||
|
local meta = GuardMeta[victim]
|
||||||
|
if not meta then return end
|
||||||
|
|
||||||
|
local attackerGang = gangFromPlayerPed(attacker)
|
||||||
|
if not attackerGang or attackerGang == 0 then
|
||||||
|
-- if your gang cache isn’t populated, guards will appear immune.
|
||||||
|
-- run /tw_gangcache on host to confirm.
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if attackerGang == meta.ownerFaction then
|
||||||
|
-- friendly player: no damage
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- manual damage: temporarily allow kill by turning off invincibility only at death
|
||||||
|
local hp = GetEntityHealth(victim)
|
||||||
|
local newHp = hp - PLAYER_HIT_DMG_TO_GUARD
|
||||||
|
if newHp <= 0 then
|
||||||
|
SetEntityInvincible(victim, false)
|
||||||
|
SetEntityHealth(victim, 0)
|
||||||
|
else
|
||||||
|
SetEntityHealth(victim, newHp)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Guard -> Player (manual damage if ENEMY)
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
if attackerIsGuard and IsPedAPlayer(victim) then
|
||||||
|
-- Determine which turf/owner this guard belongs to
|
||||||
|
local meta = GuardMeta[attacker]
|
||||||
|
if not meta then return end
|
||||||
|
|
||||||
|
local victimGang = gangFromPlayerPed(victim)
|
||||||
|
if not victimGang or victimGang == 0 then
|
||||||
|
-- neutral players don't get shot by guards (tune if you want)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if victimGang == meta.ownerFaction then
|
||||||
|
-- friendly player
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
applyDamageToPed(victim, GUARD_HIT_DMG_TO_PLAYER)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Combat gate:
|
||||||
|
-- If enemy exists nearby -> fight that enemy
|
||||||
|
-- Else -> stand down (stops spraying)
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
Wait(300)
|
||||||
|
|
||||||
|
for _, st in pairs(GuardState) do
|
||||||
|
local owner = tonumber(st.ownerFaction) or 0
|
||||||
|
local spawns = st.spawnPoints or {}
|
||||||
|
|
||||||
|
for idx, g in pairs(st.peds or {}) do
|
||||||
|
if g and DoesEntityExist(g) and not IsEntityDead(g) and NetworkHasControlOfEntity(g) then
|
||||||
|
local gPos = GetEntityCoords(g)
|
||||||
|
local enemyPed = nil
|
||||||
|
|
||||||
|
if owner ~= 0 then
|
||||||
|
enemyPed = select(1, findNearestEnemyPlayerPed(owner, gPos))
|
||||||
|
end
|
||||||
|
|
||||||
|
if enemyPed then
|
||||||
|
TaskCombatPed(g, enemyPed, 0, 16)
|
||||||
|
else
|
||||||
|
if IsPedInCombat(g, 0) or IsPedShooting(g) then
|
||||||
|
local pos = spawns[idx] and vector3(spawns[idx].x, spawns[idx].y, spawns[idx].z) or gPos
|
||||||
|
ClearPedTasksImmediately(g)
|
||||||
|
standAt(g, pos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Marker + blip colour update
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
Wait(0)
|
||||||
|
|
||||||
|
local me = PlayerPedId()
|
||||||
|
if not me or me == 0 then goto cont end
|
||||||
|
local myC = GetEntityCoords(me)
|
||||||
|
|
||||||
|
for _, st in pairs(GuardState) do
|
||||||
|
local rgb, col = getColours(st.ownerFaction)
|
||||||
|
for idx, ped in pairs(st.peds or {}) do
|
||||||
|
if ped and DoesEntityExist(ped) and not IsEntityDead(ped) then
|
||||||
|
local p = GetEntityCoords(ped)
|
||||||
|
local d = #(myC - p)
|
||||||
|
|
||||||
|
if d <= MARKER_MAX_DIST then
|
||||||
|
DrawMarker(
|
||||||
|
2, p.x, p.y, p.z + 1.1,
|
||||||
|
0.0,0.0,0.0,
|
||||||
|
0.0,0.0,0.0,
|
||||||
|
0.25,0.25,0.25,
|
||||||
|
rgb.r,rgb.g,rgb.b, FRIENDLY_ALPHA,
|
||||||
|
false,true,2,false,nil,nil,false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local b = st.blips and st.blips[idx]
|
||||||
|
if b and DoesBlipExist(b) then
|
||||||
|
SetBlipColour(b, col)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
::cont::
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Cleanup: delete dead guards, notify server when turf empty
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
Wait(800)
|
||||||
|
|
||||||
|
for turfId, st in pairs(GuardState) do
|
||||||
|
local alive = 0
|
||||||
|
|
||||||
|
for idx = #st.peds, 1, -1 do
|
||||||
|
local ped = st.peds[idx]
|
||||||
|
local blip = st.blips and st.blips[idx]
|
||||||
|
|
||||||
|
local deadOrGone = (not ped) or (not DoesEntityExist(ped)) or IsEntityDead(ped)
|
||||||
|
|
||||||
|
if deadOrGone then
|
||||||
|
if blip then delBlip(blip) end
|
||||||
|
if ped and DoesEntityExist(ped) then delPed(ped) end
|
||||||
|
table.remove(st.peds, idx)
|
||||||
|
if st.blips then table.remove(st.blips, idx) end
|
||||||
|
else
|
||||||
|
alive = alive + 1
|
||||||
|
-- keep invincible true (prevents guard deaths from guard shots)
|
||||||
|
SetEntityInvincible(ped, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if alive == 0 then
|
||||||
|
TriggerServerEvent("turfwar:guardsEmpty", turfId)
|
||||||
|
GuardState[turfId] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
160
client/killfeed_chat.lua
Normal file
160
client/killfeed_chat.lua
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
print("^2[turfwar]^7 killfeed_chat.lua loaded (client)")
|
||||||
|
|
||||||
|
local PlayerGang = {} -- [serverId] = gangId
|
||||||
|
|
||||||
|
RegisterNetEvent('turfwar:playerGang', function(serverId, gangId)
|
||||||
|
PlayerGang[tonumber(serverId)] = tonumber(gangId) or 0
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Optional snapshot
|
||||||
|
CreateThread(function()
|
||||||
|
Wait(1500)
|
||||||
|
TriggerServerEvent('turfwar:requestAllPlayerGangs')
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent('turfwar:allPlayerGangs', function(map)
|
||||||
|
if type(map) ~= "table" then return end
|
||||||
|
for sid, gid in pairs(map) do
|
||||||
|
PlayerGang[tonumber(sid)] = tonumber(gid) or 0
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function weaponLabelFromHash(hash)
|
||||||
|
local t = {
|
||||||
|
[`WEAPON_UNARMED`] = "Fists",
|
||||||
|
[`WEAPON_KNIFE`] = "Knife",
|
||||||
|
[`WEAPON_PISTOL`] = "Pistol",
|
||||||
|
[`WEAPON_COMBATPISTOL`] = "Combat Pistol",
|
||||||
|
[`WEAPON_APPISTOL`] = "AP Pistol",
|
||||||
|
[`WEAPON_SMG`] = "SMG",
|
||||||
|
[`WEAPON_ASSAULTRIFLE`] = "Assault Rifle",
|
||||||
|
[`WEAPON_CARBINERIFLE`] = "Carbine Rifle",
|
||||||
|
[`WEAPON_PUMPSHOTGUN`] = "Pump Shotgun",
|
||||||
|
[`WEAPON_SNIPERRIFLE`] = "Sniper Rifle",
|
||||||
|
[`WEAPON_HEAVYSNIPER`] = "Heavy Sniper",
|
||||||
|
[`WEAPON_GRENADE`] = "Grenade",
|
||||||
|
[`WEAPON_EXPLOSION`] = "Explosion",
|
||||||
|
[`WEAPON_RUN_OVER_BY_CAR`] = "Vehicle",
|
||||||
|
}
|
||||||
|
if t[hash] then return t[hash] end
|
||||||
|
|
||||||
|
local disp = GetWeaponDisplayNameFromHash(hash)
|
||||||
|
if disp and disp ~= "" then
|
||||||
|
local label = GetLabelText(disp)
|
||||||
|
if label and label ~= "NULL" then return label end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tostring(hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function gangRgbTable(gangId)
|
||||||
|
gangId = tonumber(gangId) or 0
|
||||||
|
local g = (Config and Config.GANGS and Config.GANGS[gangId]) or nil
|
||||||
|
if g and type(g.rgb) == "table" then
|
||||||
|
local r = tonumber(g.rgb[1]) or 255
|
||||||
|
local gg = tonumber(g.rgb[2]) or 255
|
||||||
|
local b = tonumber(g.rgb[3]) or 255
|
||||||
|
return { r, gg, b }
|
||||||
|
end
|
||||||
|
return {255,255,255}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- =========================================================
|
||||||
|
-- Victim reports once (PvP + non-player deaths)
|
||||||
|
-- =========================================================
|
||||||
|
local lastSentAt = 0
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
Wait(0)
|
||||||
|
|
||||||
|
local ped = PlayerPedId()
|
||||||
|
if ped ~= 0 and IsEntityDead(ped) then
|
||||||
|
local now = GetGameTimer()
|
||||||
|
if now - lastSentAt > 2000 then
|
||||||
|
lastSentAt = now
|
||||||
|
|
||||||
|
local killerPed = GetPedSourceOfDeath(ped)
|
||||||
|
local weaponHash = GetPedCauseOfDeath(ped)
|
||||||
|
local victimServerId = GetPlayerServerId(PlayerId())
|
||||||
|
|
||||||
|
local reported = false
|
||||||
|
|
||||||
|
-- PvP killer?
|
||||||
|
if killerPed and killerPed ~= 0 and IsEntityAPed(killerPed) and IsPedAPlayer(killerPed) then
|
||||||
|
local killerPlayer = NetworkGetPlayerIndexFromPed(killerPed)
|
||||||
|
local killerServerId = killerPlayer and GetPlayerServerId(killerPlayer) or 0
|
||||||
|
|
||||||
|
if killerServerId and killerServerId > 0 then
|
||||||
|
local myCoords = GetEntityCoords(ped)
|
||||||
|
local kCoords = GetEntityCoords(killerPed)
|
||||||
|
local dist = #(myCoords - kCoords)
|
||||||
|
|
||||||
|
local ok, bone = GetPedLastDamageBone(ped)
|
||||||
|
local headshot = (ok and bone == 31086)
|
||||||
|
|
||||||
|
TriggerServerEvent('turfwar:killfeed:report', {
|
||||||
|
killer = killerServerId,
|
||||||
|
victim = victimServerId,
|
||||||
|
weapon = weaponHash,
|
||||||
|
headshot = headshot,
|
||||||
|
distance = dist,
|
||||||
|
killerGang = PlayerGang[killerServerId] or 0,
|
||||||
|
victimGang = PlayerGang[victimServerId] or 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
reported = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Non-player death fallback
|
||||||
|
if not reported then
|
||||||
|
TriggerServerEvent('turfwar:killfeed:report', {
|
||||||
|
killer = 0,
|
||||||
|
victim = victimServerId,
|
||||||
|
weapon = weaponHash or 0,
|
||||||
|
headshot = false,
|
||||||
|
distance = 0,
|
||||||
|
killerGang = 0,
|
||||||
|
victimGang = PlayerGang[victimServerId] or 0,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
while IsEntityDead(PlayerPedId()) do Wait(200) end
|
||||||
|
Wait(500)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- =========================================================
|
||||||
|
-- Receive broadcast -> send to NUI
|
||||||
|
-- =========================================================
|
||||||
|
RegisterNetEvent('turfwar:killfeed:chat', function(data)
|
||||||
|
if type(data) ~= "table" then return end
|
||||||
|
|
||||||
|
local victimRgb = gangRgbTable(data.victimGang)
|
||||||
|
local killerRgb = gangRgbTable(data.killerGang)
|
||||||
|
|
||||||
|
local deathOnly = (data.isDeathOnly == true) or (tonumber(data.killer or 0) <= 0)
|
||||||
|
|
||||||
|
if deathOnly then
|
||||||
|
SendNUIMessage({
|
||||||
|
type = "killfeed:add",
|
||||||
|
isDeathOnly = true,
|
||||||
|
victimName = data.victimName or "Unknown",
|
||||||
|
victimRgb = victimRgb,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
SendNUIMessage({
|
||||||
|
type = "killfeed:add",
|
||||||
|
killerName = data.killerName or "Unknown",
|
||||||
|
victimName = data.victimName or "Unknown",
|
||||||
|
weapon = weaponLabelFromHash(tonumber(data.weapon or 0) or 0),
|
||||||
|
headshot = (data.headshot == true),
|
||||||
|
distance = tonumber(data.distance or 0) or 0,
|
||||||
|
killerRgb = killerRgb,
|
||||||
|
victimRgb = victimRgb,
|
||||||
|
})
|
||||||
|
end)
|
||||||
108
client/leaderboard.lua
Normal file
108
client/leaderboard.lua
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
print("^2[turfwar]^7 leaderboard client loaded")
|
||||||
|
|
||||||
|
local leaderboard = {}
|
||||||
|
|
||||||
|
-- Map FiveM blipColor IDs -> RGB (approx).
|
||||||
|
-- We only need the ones you use: 0,2,3,5,7,40.
|
||||||
|
local BLIP_RGB = {
|
||||||
|
[0] = {255, 255, 255}, -- White
|
||||||
|
[2] = { 60, 200, 60}, -- Green
|
||||||
|
[3] = { 70, 120, 255}, -- Blue
|
||||||
|
[5] = {255, 220, 60}, -- Yellow
|
||||||
|
[7] = {190, 90, 255}, -- Purple
|
||||||
|
[40] = { 40, 40, 40}, -- Dark grey / black
|
||||||
|
}
|
||||||
|
|
||||||
|
local function getGangName(gangId)
|
||||||
|
if Config and Config.GANGS and Config.GANGS[gangId] and Config.GANGS[gangId].name then
|
||||||
|
return Config.GANGS[gangId].name
|
||||||
|
end
|
||||||
|
return ("Gang %s"):format(gangId)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getGangRGB(gangId)
|
||||||
|
local blip = 0
|
||||||
|
if Config and Config.GANGS and Config.GANGS[gangId] and Config.GANGS[gangId].blipColor then
|
||||||
|
blip = tonumber(Config.GANGS[gangId].blipColor) or 0
|
||||||
|
end
|
||||||
|
return BLIP_RGB[blip] or {255, 255, 255}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drawText(x, y, text, r, g, b, a, scale)
|
||||||
|
SetTextFont(4)
|
||||||
|
SetTextScale(scale, scale)
|
||||||
|
SetTextColour(r, g, b, a)
|
||||||
|
SetTextOutline()
|
||||||
|
SetTextWrap(0.0, 1.0)
|
||||||
|
BeginTextCommandDisplayText("STRING")
|
||||||
|
AddTextComponentSubstringPlayerName(text)
|
||||||
|
EndTextCommandDisplayText(x, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ask server for current leaderboard on join/resource start
|
||||||
|
CreateThread(function()
|
||||||
|
Wait(1500)
|
||||||
|
TriggerServerEvent("turfwar:requestGangLeaderboard")
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent("turfwar:gangLeaderboard", function(rankedGangIds)
|
||||||
|
leaderboard = rankedGangIds or {}
|
||||||
|
-- debug:
|
||||||
|
-- print("^2[turfwar]^7 leaderboard: " .. json.encode(leaderboard))
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Compact HUD render (smaller box, slightly bigger text)
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
Wait(0)
|
||||||
|
|
||||||
|
if not leaderboard or #leaderboard == 0 then
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local maxRows = math.min(#leaderboard, 8) -- fewer rows keeps it compact
|
||||||
|
|
||||||
|
-- Layout (top-left)
|
||||||
|
local x = 0.018
|
||||||
|
local y = 0.185
|
||||||
|
|
||||||
|
-- Slightly bigger than before (but still realistic)
|
||||||
|
local titleScale = 0.32
|
||||||
|
local rowScale = 0.28
|
||||||
|
|
||||||
|
-- Smaller/tighter panel
|
||||||
|
local rowH = 0.018
|
||||||
|
local padX = 0.008
|
||||||
|
local padY = 0.008
|
||||||
|
|
||||||
|
local panelW = 0.14 -- ~⅓ smaller than before (was 0.22)
|
||||||
|
local panelH = padY + 0.020 + (maxRows * rowH) + padY
|
||||||
|
|
||||||
|
-- Background panel (more compact)
|
||||||
|
DrawRect(x + panelW/2, y + panelH/2, panelW, panelH, 0, 0, 0, 105)
|
||||||
|
|
||||||
|
-- Title
|
||||||
|
drawText(x + padX, y + padY, "Top Ranked Gang", 255, 255, 255, 235, titleScale)
|
||||||
|
|
||||||
|
-- Rows
|
||||||
|
local startY = y + padY + 0.020
|
||||||
|
for i = 1, maxRows do
|
||||||
|
local gangId = leaderboard[i]
|
||||||
|
local name = getGangName(gangId)
|
||||||
|
local r,g,b = table.unpack(getGangRGB(gangId))
|
||||||
|
|
||||||
|
local rowY = startY + ((i-1) * rowH)
|
||||||
|
|
||||||
|
-- very faint row highlight
|
||||||
|
DrawRect(x + panelW/2, rowY + 0.009, panelW, rowH, 255, 255, 255, 8)
|
||||||
|
|
||||||
|
-- rank number (lighter grey / near-white, with outline)
|
||||||
|
drawText(x + padX, rowY, ("%d."):format(i), 235, 235, 235, 235, rowScale)
|
||||||
|
|
||||||
|
-- gang name (colored)
|
||||||
|
drawText(x + padX + 0.020, rowY, name, r, g, b, 245, rowScale)
|
||||||
|
end
|
||||||
|
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
end)
|
||||||
28
client/loadouts.lua
Normal file
28
client/loadouts.lua
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
-- client/loadouts.lua (STRIP-ONLY for neutrals)
|
||||||
|
print("^2[turfwar]^7 client/loadouts.lua LOADED (STRIP-ONLY - shop handles weapons)")
|
||||||
|
|
||||||
|
local currentGang = 0
|
||||||
|
|
||||||
|
RegisterNetEvent("turfwar:setFaction", function(gangId)
|
||||||
|
currentGang = tonumber(gangId) or 0
|
||||||
|
end)
|
||||||
|
RegisterNetEvent("turfwar:gangUpdate", function(gangId)
|
||||||
|
currentGang = tonumber(gangId) or 0
|
||||||
|
end)
|
||||||
|
RegisterNetEvent("turfwar:setMyGang", function(gangId)
|
||||||
|
currentGang = tonumber(gangId) or 0
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent("turfwar:stripWeapons", function()
|
||||||
|
if currentGang ~= 0 then
|
||||||
|
-- only strip neutral
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local ped = PlayerPedId()
|
||||||
|
if not ped or ped == 0 then return end
|
||||||
|
RemoveAllPedWeapons(ped, true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent("turfwar:applyLoadout", function(loadout)
|
||||||
|
-- disabled: shop owns weapons
|
||||||
|
end)
|
||||||
1088
client/main.lua
Normal file
1088
client/main.lua
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user