329 lines
11 KiB
Lua
329 lines
11 KiB
Lua
|
|
local config = require 'config.client'
|
||
|
|
local currentStatusList = {}
|
||
|
|
local casings = {}
|
||
|
|
local currentCasing = nil
|
||
|
|
local bloodDrops = {}
|
||
|
|
local currentBloodDrop = nil
|
||
|
|
local fingerprints = {}
|
||
|
|
local currentFingerprint = 0
|
||
|
|
local shotAmount = 0
|
||
|
|
|
||
|
|
local statusList = {
|
||
|
|
fight = locale('evidence.red_hands'),
|
||
|
|
widepupils = locale('evidence.wide_pupils'),
|
||
|
|
redeyes = locale('evidence.red_eyes'),
|
||
|
|
weedsmell = locale('evidence.weed_smell'),
|
||
|
|
gunpowder = locale('evidence.gunpowder'),
|
||
|
|
chemicals = locale('evidence.chemicals'),
|
||
|
|
heavybreath = locale('evidence.heavy_breathing'),
|
||
|
|
sweat = locale('evidence.sweat'),
|
||
|
|
handbleed = locale('evidence.handbleed'),
|
||
|
|
confused = locale('evidence.confused'),
|
||
|
|
alcohol = locale('evidence.alcohol'),
|
||
|
|
heavyalcohol = locale('evidence.heavy_alcohol'),
|
||
|
|
agitated = locale('evidence.agitated'),
|
||
|
|
}
|
||
|
|
|
||
|
|
local ignoredWeapons = {
|
||
|
|
[`weapon_unarmed`] = true,
|
||
|
|
[`weapon_snowball`] = true,
|
||
|
|
[`weapon_stungun`] = true,
|
||
|
|
[`weapon_petrolcan`] = true,
|
||
|
|
[`weapon_hazardcan`] = true,
|
||
|
|
[`weapon_fireextinguisher`] = true,
|
||
|
|
}
|
||
|
|
|
||
|
|
local function dropBulletCasing(weapon, ped)
|
||
|
|
local randX = math.random() + math.random(-1, 1)
|
||
|
|
local randY = math.random() + math.random(-1, 1)
|
||
|
|
local coords = GetOffsetFromEntityInWorldCoords(ped, randX, randY, 0)
|
||
|
|
local serial = exports.ox_inventory:getCurrentWeapon().metadata.serial
|
||
|
|
TriggerServerEvent('evidence:server:CreateCasing', weapon, serial, coords)
|
||
|
|
Wait(300)
|
||
|
|
end
|
||
|
|
|
||
|
|
local function dnaHash(s)
|
||
|
|
local h = string.gsub(s, '.', function(c)
|
||
|
|
return string.format('%02x', string.byte(c))
|
||
|
|
end)
|
||
|
|
return h
|
||
|
|
end
|
||
|
|
|
||
|
|
RegisterNetEvent('evidence:client:SetStatus', function(statusId, time)
|
||
|
|
if time > 0 and statusList[statusId] then
|
||
|
|
if not currentStatusList?[statusId] or currentStatusList[statusId].time < 20 then
|
||
|
|
currentStatusList[statusId] = {
|
||
|
|
text = statusList[statusId],
|
||
|
|
time = time
|
||
|
|
}
|
||
|
|
exports.qbx_core:Notify(currentStatusList[statusId].text, 'error')
|
||
|
|
end
|
||
|
|
elseif statusList[statusId] then
|
||
|
|
currentStatusList[statusId] = nil
|
||
|
|
end
|
||
|
|
TriggerServerEvent('evidence:server:UpdateStatus', currentStatusList)
|
||
|
|
end)
|
||
|
|
|
||
|
|
RegisterNetEvent('evidence:client:AddBlooddrop', function(bloodId, citizenid, bloodtype, coords)
|
||
|
|
bloodDrops[bloodId] = {
|
||
|
|
citizenid = citizenid,
|
||
|
|
bloodtype = bloodtype,
|
||
|
|
coords = vec3(coords.x, coords.y, coords.z - 0.9)
|
||
|
|
}
|
||
|
|
end)
|
||
|
|
|
||
|
|
RegisterNetEvent('evidence:client:RemoveBlooddrop', function(bloodId)
|
||
|
|
bloodDrops[bloodId] = nil
|
||
|
|
currentBloodDrop = 0
|
||
|
|
end)
|
||
|
|
|
||
|
|
RegisterNetEvent('evidence:client:AddFingerPrint', function(fingerId, fingerprint, coords)
|
||
|
|
fingerprints[fingerId] = {
|
||
|
|
fingerprint = fingerprint,
|
||
|
|
coords = vec3(coords.x, coords.y, coords.z - 0.9)
|
||
|
|
}
|
||
|
|
end)
|
||
|
|
|
||
|
|
RegisterNetEvent('evidence:client:RemoveFingerprint', function(fingerId)
|
||
|
|
fingerprints[fingerId] = nil
|
||
|
|
currentFingerprint = 0
|
||
|
|
end)
|
||
|
|
|
||
|
|
RegisterNetEvent('evidence:client:ClearBlooddropsInArea', function()
|
||
|
|
local pos = GetEntityCoords(cache.ped)
|
||
|
|
local bloodDropList = {}
|
||
|
|
if lib.progressCircle({
|
||
|
|
duration = 5000,
|
||
|
|
position = 'bottom',
|
||
|
|
label = locale('progressbar.blood_clear'),
|
||
|
|
useWhileDead = false,
|
||
|
|
canCancel = true,
|
||
|
|
disable = {
|
||
|
|
move = false,
|
||
|
|
car = false,
|
||
|
|
combat = true,
|
||
|
|
mouse = false
|
||
|
|
}
|
||
|
|
})
|
||
|
|
then
|
||
|
|
if bloodDrops and next(bloodDrops) then
|
||
|
|
for bloodId in pairs(bloodDrops) do
|
||
|
|
if #(pos - bloodDrops[bloodId].coords) < 10.0 then
|
||
|
|
bloodDropList[#bloodDropList + 1] = bloodId
|
||
|
|
end
|
||
|
|
end
|
||
|
|
TriggerServerEvent('evidence:server:ClearBlooddrops', bloodDropList)
|
||
|
|
exports.qbx_core:Notify(locale('success.blood_clear'), 'success')
|
||
|
|
end
|
||
|
|
else
|
||
|
|
exports.qbx_core:Notify(locale('error.blood_not_cleared'), 'error')
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
|
||
|
|
RegisterNetEvent('evidence:client:AddCasing', function(casingId, weapon, coords, serie)
|
||
|
|
casings[casingId] = {
|
||
|
|
type = weapon,
|
||
|
|
serie = serie and serie or locale('evidence.serial_not_visible'),
|
||
|
|
coords = vec3(coords.x, coords.y, coords.z - 0.9)
|
||
|
|
}
|
||
|
|
end)
|
||
|
|
|
||
|
|
RegisterNetEvent('evidence:client:RemoveCasing', function(casingId)
|
||
|
|
casings[casingId] = nil
|
||
|
|
currentCasing = 0
|
||
|
|
end)
|
||
|
|
|
||
|
|
RegisterNetEvent('evidence:client:ClearCasingsInArea', function()
|
||
|
|
local pos = GetEntityCoords(cache.ped)
|
||
|
|
local casingList = {}
|
||
|
|
|
||
|
|
if lib.progressCircle({
|
||
|
|
duration = 5000,
|
||
|
|
position = 'bottom',
|
||
|
|
label = locale('progressbar.bullet_casing'),
|
||
|
|
useWhileDead = false,
|
||
|
|
canCancel = true,
|
||
|
|
disable = {
|
||
|
|
move = false,
|
||
|
|
car = false,
|
||
|
|
combat = true,
|
||
|
|
mouse = false,
|
||
|
|
}
|
||
|
|
})
|
||
|
|
then
|
||
|
|
if casings and next(casings) then
|
||
|
|
for casingId in pairs(casings) do
|
||
|
|
if #(pos - casings[casingId].coords) < 10.0 then
|
||
|
|
casingList[#casingList + 1] = casingId
|
||
|
|
end
|
||
|
|
end
|
||
|
|
TriggerServerEvent('evidence:server:ClearCasings', casingList)
|
||
|
|
exports.qbx_core:Notify(locale('success.bullet_casing_removed'), 'success')
|
||
|
|
end
|
||
|
|
else
|
||
|
|
exports.qbx_core:Notify(locale('error.bullet_casing_not_removed'), 'error')
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
|
||
|
|
local function updateStatus()
|
||
|
|
if not LocalPlayer.state.isLoggedIn then return end
|
||
|
|
if currentStatusList and next(currentStatusList) then
|
||
|
|
for k in pairs(currentStatusList) do
|
||
|
|
if currentStatusList[k].time > 0 then
|
||
|
|
currentStatusList[k].time -= 10
|
||
|
|
else
|
||
|
|
currentStatusList[k].time = 0
|
||
|
|
end
|
||
|
|
end
|
||
|
|
TriggerServerEvent('evidence:server:UpdateStatus', currentStatusList)
|
||
|
|
end
|
||
|
|
if shotAmount > 0 then
|
||
|
|
shotAmount = 0
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
CreateThread(function()
|
||
|
|
while true do
|
||
|
|
Wait(10000)
|
||
|
|
updateStatus()
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
|
||
|
|
local function onPlayerShooting()
|
||
|
|
shotAmount += 1
|
||
|
|
if shotAmount > 5 and not currentStatusList?.gunpowder then
|
||
|
|
if math.random(1, 10) <= 7 then
|
||
|
|
TriggerEvent('evidence:client:SetStatus', 'gunpowder', 200)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
dropBulletCasing(cache.weapon, cache.ped)
|
||
|
|
end
|
||
|
|
|
||
|
|
CreateThread(function() -- Gunpowder Status when shooting
|
||
|
|
while true do
|
||
|
|
Wait(0)
|
||
|
|
if IsPedShooting(cache.ped) and not ignoredWeapons[cache.weapon] then
|
||
|
|
onPlayerShooting()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
|
||
|
|
---@param coords vector3
|
||
|
|
---@return string
|
||
|
|
local function getStreetLabel(coords)
|
||
|
|
local s1, s2 = GetStreetNameAtCoord(coords.x, coords.y, coords.z)
|
||
|
|
local street1 = GetStreetNameFromHashKey(s1)
|
||
|
|
local street2 = GetStreetNameFromHashKey(s2)
|
||
|
|
local streetLabel = street1
|
||
|
|
if street2 then
|
||
|
|
streetLabel = streetLabel .. ' | ' .. street2
|
||
|
|
end
|
||
|
|
local sanitized = streetLabel:gsub("%'", "")
|
||
|
|
return sanitized
|
||
|
|
end
|
||
|
|
|
||
|
|
local function getPlayerDistanceFromCoords(coords)
|
||
|
|
local pos = GetEntityCoords(cache.ped)
|
||
|
|
return #(pos - coords)
|
||
|
|
end
|
||
|
|
|
||
|
|
---@class DrawEvidenceIfInRangeArgs
|
||
|
|
---@field evidenceId integer
|
||
|
|
---@field coords vector3
|
||
|
|
---@field text string
|
||
|
|
---@field metadata table
|
||
|
|
---@field serverEventOnPickup string
|
||
|
|
|
||
|
|
---@param args DrawEvidenceIfInRangeArgs
|
||
|
|
local function drawEvidenceIfInRange(args)
|
||
|
|
if getPlayerDistanceFromCoords(args.coords) >= 1.5 then return end
|
||
|
|
qbx.drawText3d({text = args.text, coords = args.coords})
|
||
|
|
if IsControlJustReleased(0, 47) then
|
||
|
|
TriggerServerEvent(args.serverEventOnPickup, args.evidenceId, args.metadata)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
--- draw 3D text on the ground to show evidence, if they press pickup button, set metadata and add it to their inventory.
|
||
|
|
CreateThread(function()
|
||
|
|
while true do
|
||
|
|
Wait(0)
|
||
|
|
if currentCasing and currentCasing ~= 0 then
|
||
|
|
drawEvidenceIfInRange({
|
||
|
|
evidenceId = currentCasing,
|
||
|
|
coords = casings[currentCasing].coords,
|
||
|
|
text = locale('info.bullet_casing', casings[currentCasing].type),
|
||
|
|
metadata = {
|
||
|
|
type = locale('info.casing'),
|
||
|
|
street = getStreetLabel(casings[currentCasing].coords),
|
||
|
|
ammolabel = config.ammoLabels[exports.qbx_core:GetWeapons()[casings[currentCasing].type].ammotype],
|
||
|
|
ammotype = casings[currentCasing].type,
|
||
|
|
serie = casings[currentCasing].serie
|
||
|
|
},
|
||
|
|
serverEventOnPickup = 'evidence:server:AddCasingToInventory'
|
||
|
|
})
|
||
|
|
end
|
||
|
|
|
||
|
|
if currentBloodDrop and currentBloodDrop ~= 0 then
|
||
|
|
drawEvidenceIfInRange({
|
||
|
|
evidenceId = currentBloodDrop,
|
||
|
|
coords = bloodDrops[currentBloodDrop].coords,
|
||
|
|
text = locale('info.blood_text', dnaHash(bloodDrops[currentBloodDrop].citizenid)),
|
||
|
|
metadata = {
|
||
|
|
type = locale('info.blood'),
|
||
|
|
street = getStreetLabel(bloodDrops[currentBloodDrop].coords),
|
||
|
|
dnalabel = dnaHash(bloodDrops[currentBloodDrop].citizenid),
|
||
|
|
bloodtype = bloodDrops[currentBloodDrop].bloodtype
|
||
|
|
},
|
||
|
|
serverEventOnPickup = 'evidence:server:AddBlooddropToInventory'
|
||
|
|
})
|
||
|
|
end
|
||
|
|
|
||
|
|
if currentFingerprint and currentFingerprint ~= 0 then
|
||
|
|
drawEvidenceIfInRange({
|
||
|
|
evidenceId = currentFingerprint,
|
||
|
|
coords = fingerprints[currentFingerprint].coords,
|
||
|
|
text = locale('info.fingerprint_text'),
|
||
|
|
metadata = {
|
||
|
|
type = locale('info.fingerprint'),
|
||
|
|
street = getStreetLabel(fingerprints[currentFingerprint].coords),
|
||
|
|
fingerprint = fingerprints[currentFingerprint].fingerprint
|
||
|
|
},
|
||
|
|
serverEventOnPickup = 'evidence:server:AddFingerprintToInventory'
|
||
|
|
})
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
|
||
|
|
local function canDiscoverEvidence()
|
||
|
|
return LocalPlayer.state.isLoggedIn
|
||
|
|
and QBX.PlayerData.job.type == 'leo'
|
||
|
|
and QBX.PlayerData.job.onduty
|
||
|
|
and IsPlayerFreeAiming(cache.playerId)
|
||
|
|
and cache.weapon == `WEAPON_FLASHLIGHT`
|
||
|
|
end
|
||
|
|
|
||
|
|
---@param evidence table<number, {coords: vector3}>
|
||
|
|
---@return number? evidenceId
|
||
|
|
local function getCloseEvidence(evidence)
|
||
|
|
local pos = GetEntityCoords(cache.ped, true)
|
||
|
|
for evidenceId, v in pairs(evidence) do
|
||
|
|
local dist = #(pos - v.coords)
|
||
|
|
if dist < 1.5 then
|
||
|
|
return evidenceId
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
CreateThread(function()
|
||
|
|
while true do
|
||
|
|
local closeEvidenceSleep = 1000
|
||
|
|
if canDiscoverEvidence() then
|
||
|
|
closeEvidenceSleep = 10
|
||
|
|
currentCasing = getCloseEvidence(casings) or currentCasing
|
||
|
|
currentBloodDrop = getCloseEvidence(bloodDrops) or currentBloodDrop
|
||
|
|
currentFingerprint = getCloseEvidence(fingerprints) or currentFingerprint
|
||
|
|
end
|
||
|
|
Wait(closeEvidenceSleep)
|
||
|
|
end
|
||
|
|
end)
|