Files
2026-03-29 21:41:17 +03:00

163 lines
6.8 KiB
Lua

-- Weapon constant names (as shown on FiveM docs)
WEAPON_HASHES = {
[`WEAPON_SNOWLAUNCHER`] = "WEAPON_SNOWLAUNCHER",
[`WEAPON_COMPACTLAUNCHER`] = "WEAPON_COMPACTLAUNCHER",
[`WEAPON_MINIGUN`] = "WEAPON_MINIGUN",
[`WEAPON_GRENADELAUNCHER_SMOKE`] = "WEAPON_GRENADELAUNCHER_SMOKE",
[`WEAPON_HOMINGLAUNCHER`] = "WEAPON_HOMINGLAUNCHER",
[`WEAPON_RAILGUN`] = "WEAPON_RAILGUN",
[`WEAPON_FIREWORK`] = "WEAPON_FIREWORK",
[`WEAPON_GRENADELAUNCHER`] = "WEAPON_GRENADELAUNCHER",
[`WEAPON_RPG`] = "WEAPON_RPG",
[`WEAPON_RAYMINIGUN`] = "WEAPON_RAYMINIGUN",
[`WEAPON_EMPLAUNCHER`] = "WEAPON_EMPLAUNCHER",
[`WEAPON_RAILGUNXM3`] = "WEAPON_RAILGUNXM3",
-- explosives / throwable
[`WEAPON_GRENADE`] = "WEAPON_GRENADE",
[`WEAPON_BZGAS`] = "WEAPON_BZGAS",
[`WEAPON_PROXMINE`] = "WEAPON_PROXMINE",
[`WEAPON_PIPEBOMB`] = "WEAPON_PIPEBOMB",
[`WEAPON_ACIDPACKAGE`] = "WEAPON_ACIDPACKAGE",
[`WEAPON_SMOKEGRENADE`] = "WEAPON_SMOKEGRENADE",
-- pistols (examples from page)
[`WEAPON_VINTAGEPISTOL`] = "WEAPON_VINTAGEPISTOL",
[`WEAPON_PISTOL`] = "WEAPON_PISTOL",
[`WEAPON_PISTOLXM3`] = "WEAPON_PISTOLXM3",
-- shotguns (examples)
[`WEAPON_COMBATSHOTGUN`] = "WEAPON_COMBATSHOTGUN",
[`WEAPON_AUTOSHOTGUN`] = "WEAPON_AUTOSHOTGUN",
-- melee / blunt
[`WEAPON_STONE_HATCHET`] = "WEAPON_STONE_HATCHET",
[`WEAPON_GOLFCLUB`] = "WEAPON_GOLFCLUB",
[`WEAPON_HAMMER`] = "WEAPON_HAMMER",
[`WEAPON_CANDYCANE`] = "WEAPON_CANDYCANE",
[`WEAPON_NIGHTSTICK`] = "WEAPON_NIGHTSTICK",
[`WEAPON_CROWBAR`] = "WEAPON_CROWBAR",
[`WEAPON_FLASHLIGHT`] = "WEAPON_FLASHLIGHT",
[`WEAPON_DAGGER`] = "WEAPON_DAGGER",
[`WEAPON_POOLCUE`] = "WEAPON_POOLCUE",
[`WEAPON_BAT`] = "WEAPON_BAT",
[`WEAPON_BATTLEAXE`] = "WEAPON_BATTLEAXE",
[`WEAPON_STUNROD`] = "WEAPON_STUNROD",
[`WEAPON_MACHETE`] = "WEAPON_MACHETE",
[`WEAPON_SWITCHBLADE`] = "WEAPON_SWITCHBLADE",
[`WEAPON_HATCHET`] = "WEAPON_HATCHET",
[`WEAPON_BOTTLE`] = "WEAPON_BOTTLE",
-- other devices
[`WEAPON_HACKINGDEVICE`] = "WEAPON_HACKINGDEVICE",
[`WEAPON_STUNGUN`] = "WEAPON_STUNGUN",
[`WEAPON_STUNGUN_MP`] = "WEAPON_STUNGUN_MP",
}
--- Add missing weapon hashes from ox_inventory
if GetResourceState('ox_inventory') == 'started' then
local list = require "@ox_inventory.data.weapons"
for k in pairs(list.Weapons) do
WEAPON_HASHES[joaat(k)] = k
end
end
-- headshot ratio
local totalKills = 0
local totalKillsByHeadshot = 0
--- Batch the logs to send them in a single request and prevent spam
---@class BatchLogs
---@field deaths string[]
---@field actions string[]
local batchLogs = {
deaths = {},
actions = {},
}
---TODO: Check other causes of death, like vehicle collisions, falls, etc.
AddEventHandler('gameEventTriggered', function(event, args)
if event == "CEventNetworkEntityDamage" then
local playerPed = PlayerPedId()
local attacker = args[2]
-- Check if the attacker is the client
local isAttacker = attacker ~= playerPed
local victim = args[1]
local wasFatal = args[6] == 1
local isMelee = args[12] == 1
local _found, bone = GetPedLastDamageBone(victim)
local hasWeapon, clientWeaponHash = GetCurrentPedWeapon(playerPed, true)
local clientWeaponModel = hasWeapon and (WEAPON_HASHES[clientWeaponHash] or clientWeaponHash) or "unarmed"
if isAttacker and wasFatal and not isMelee then
totalKills = totalKills + 1
if bone == 31086 then
totalKillsByHeadshot = totalKillsByHeadshot + 1
end
Luxu.triggerServerEvent("hs:update", totalKillsByHeadshot / totalKills)
end
--- If the victim is the client, log the death
if victim == playerPed then
local isAttackerAPlayer = NetworkGetPlayerIndexFromPed(attacker)
local selfHarm = attacker == -1 -- Fell or Drowning
--- Check if the player died from falling or drowning
if selfHarm and wasFatal then
local playerName = GetPlayerName(Luxu.cache.playerId)
local logMessage = ("%s died from falling or drowning"):format(playerName)
batchLogs.deaths[#batchLogs.deaths + 1] = logMessage
return
end
--- Killed by another player
if wasFatal and isAttackerAPlayer ~= -1 then
local attackerServerId = GetPlayerServerId(isAttackerAPlayer)
local attackerName = GetPlayerName(isAttackerAPlayer)
local attackerHasWeapon, attackerWeaponHash = GetCurrentPedWeapon(attacker, true)
local attackerWeaponModel = attackerHasWeapon and GetWeapontypeModel(attackerWeaponHash) or "unarmed"
local victimName = GetPlayerName(Luxu.cache.playerId)
local logMessage = ("%s was killed by [%d]%s | Weapon %s:%s"):format(
victimName,
attackerServerId,
attackerName,
attackerWeaponModel,
attackerWeaponHash)
batchLogs.deaths[#batchLogs.deaths + 1] = logMessage
end
end
--- Check if any action was done by this client as the attacker
if attacker ~= PlayerPedId() then return end
local victimEntityType = IsEntityAPed(victim) and "ped" or IsEntityAVehicle(victim) and "vehicle" or
IsEntityAnObject(victim) and "object" or "unknown"
--- Log vehicle destruction
if victimEntityType == "vehicle" and wasFatal then
local playerName = GetPlayerName(Luxu.cache.playerId)
local plate = GetVehicleNumberPlateText(victim)
local logMessage = ("%s destroyed a vehicle with plate: [%s] using %s:%s"):format(playerName, plate,
clientWeaponModel, clientWeaponHash)
batchLogs.actions[#batchLogs.actions + 1] = logMessage
return
end
end
end)
-- Periodically send batched logs to the server using a latent trigger
Citizen.CreateThread(function()
while true do
if #batchLogs.deaths > 0 then
Luxu.triggerServerEventLatent("log:playerDeathBatch", 10000, batchLogs.deaths)
batchLogs.deaths = {} -- Clear the batch after sending
end
Citizen.Wait(10e3) -- Wait for 10 seconds before checking again
end
end)