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