Turfwar/client/guards.lua

559 lines
19 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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