forked from Simnation/Main
237 lines
11 KiB
Lua
237 lines
11 KiB
Lua
local TIRE_LABLES <const> = {
|
|
[0] = "front left",
|
|
[1] = "front right",
|
|
[2] = "left middle",
|
|
[3] = "right middle",
|
|
[4] = "left rear",
|
|
[5] = "right rear",
|
|
[45] = "left middle #2",
|
|
[46] = "left middle #3",
|
|
[47] = "right middle #2",
|
|
[48] = "right middle #3",
|
|
}
|
|
|
|
---Displays a notification to the player
|
|
---@param message string
|
|
function DisplayNotification(serverId, message)
|
|
TriggerClientEvent('slashtires:displayNotification', serverId, message)
|
|
end
|
|
|
|
---Gets the label string for the spesifed tire index
|
|
---@param tireIndex integer
|
|
---@return string
|
|
local function getTireLabel(tireIndex)
|
|
return TIRE_LABLES[tireIndex] or string.format("unknown (%s)", tireIndex)
|
|
end
|
|
|
|
---Returns if the current player weapon can slash a tire
|
|
---@param playerPed integer
|
|
---@return boolean canSlash
|
|
---@return integer weaponHash
|
|
local function canCurrentWeaponSlashTires(playerPed)
|
|
local weaponHash = GetSelectedPedWeapon(playerPed)
|
|
return Config.AllowedWeapons[weaponHash] ~= nil, weaponHash
|
|
end
|
|
|
|
---Returns if the vehicle's model is blacklisted from getting slashed
|
|
---@param vehicleModel integer
|
|
---@return boolean isBlacklisted
|
|
---@return string blacklistReason
|
|
local function isVehicleModelBlacklisted(vehicleModel)
|
|
local blacklistReason = Config.VehicleBlacklist[vehicleModel]
|
|
-- NOTE: It is not possible to get the vehicle class server side, so emergency vehicles are sadly not blocked by the server :/ (but the amount of cheaters trying abusing this event is probably so low that i doesn't matter)
|
|
return blacklistReason ~= nil, VEHICLE_BLACKLIST_REASONS[blacklistReason] or 'vehicle_is_blacklisted'
|
|
end
|
|
|
|
---Checks if the ped should be given the flee task from the spesifed coords
|
|
---@param ped integer
|
|
---@param coords vector3
|
|
---@return boolean canFleePed
|
|
local function canGivePedFleeTask(ped, coords)
|
|
local dist = #(coords - GetEntityCoords(ped))
|
|
if dist > Config.AIReactionDistance then
|
|
return false
|
|
end
|
|
|
|
if IsPedAPlayer(ped) then
|
|
return false
|
|
end
|
|
|
|
-- Frozen peds can't flee anyway, and they are most likley a script handled ped (for stores and robberies for example)
|
|
if IsEntityPositionFrozen(ped) then
|
|
return false
|
|
end
|
|
|
|
-- Ignore dead peds
|
|
if GetEntityHealth(ped) == 0 then
|
|
return false
|
|
end
|
|
|
|
if not IsEntityVisible(ped) then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
---Makes the nearby peds flee from the ped
|
|
---@param coords vector3
|
|
---@param fleePed integer The ped to flee from
|
|
local function reactAndFleeNearbyPeds(peds, coords, fleePed)
|
|
for index = 1, #peds do
|
|
local ped = NetworkGetEntityFromNetworkId(peds[index])
|
|
|
|
if canGivePedFleeTask(ped, coords) then
|
|
TaskReactAndFleePed(ped, fleePed)
|
|
end
|
|
end
|
|
end
|
|
|
|
---Checks if the weapon should break
|
|
---@param weaponHash integer
|
|
---@return boolean shouldBreak
|
|
local function shouldWeaponBreak(weaponHash)
|
|
if not Config.CanWeaponsBreak then
|
|
return false
|
|
end
|
|
|
|
local chanceToBreak = Config.AllowedWeapons[weaponHash].breakChance
|
|
if not chanceToBreak then
|
|
warn(string.format("Weapon with hash %s does not have a specified chance at breaking. Please add it in under Config.AllowedWeapons or set Config.CanWeaponsBreak to false", weaponHash))
|
|
return false
|
|
end
|
|
|
|
if chanceToBreak == 0 then
|
|
return false
|
|
end
|
|
|
|
local chance = math.random(1, 1000) / 10
|
|
if chance > chanceToBreak then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
---Checks and validations function for when a player wants to slash a tire
|
|
---@param netId integer netId of the vehicle
|
|
---@param tireIndex integer The index of the tire that was slashed
|
|
---@param reactPeds table<integer, integer> The peds that the client belives should react to tire slashing
|
|
---@param hasBurstedTire boolean If the client already has bursted the tire on the client side
|
|
local function slashTire(netId, tireIndex, reactPeds, hasBurstedTire)
|
|
local src = source
|
|
local vehicle = NetworkGetEntityFromNetworkId(netId)
|
|
|
|
if vehicle == 0 then
|
|
Log(src, 'slashtires:cheaterOrError', string.format("attempted to slash a tire of a vehicle with an invalid netId: %s", netId), { source = src, netId = netId, tireIndex = tireIndex, hasBurstedTire = hasBurstedTire })
|
|
DisplayNotification(src, GetLocalization('no_vehicle_nearby'))
|
|
return
|
|
end
|
|
|
|
local entityType = GetEntityType(vehicle)
|
|
if entityType ~= 2 then
|
|
Log(src, 'slashtires:cheaterOrError', string.format("attempted to slash a tire of a non-vehicle entity with netId: %s", netId), { source = src, netId = netId, tireIndex = tireIndex, hasBurstedTire = hasBurstedTire, entityType = entityType })
|
|
DisplayNotification(src, GetLocalization('no_vehicle_nearby'))
|
|
return
|
|
end
|
|
|
|
local tireIndexType = type(tireIndex)
|
|
if tireIndexType ~= 'number' or not TIRE_LABLES[tireIndex] then
|
|
Log(src, 'slashtires:cheaterOrError', string.format("attempted to slash an unknown tire with index: %s (lua type: %s)", tireIndex, tireIndexType), { source = src, netId = netId, tireIndex = tireIndex, hasBurstedTire = hasBurstedTire, tireIndexType = tireIndexType })
|
|
DisplayNotification(src, GetLocalization('no_tire_nearby'))
|
|
return
|
|
end
|
|
|
|
local playerPed = GetPlayerPed(src)
|
|
local playerCoords = GetEntityCoords(playerPed)
|
|
local vehicleCoords = GetEntityCoords(vehicle)
|
|
local distance = #(vehicleCoords - playerCoords)
|
|
if distance > 15.0 then
|
|
Log(src, 'slashtires:cheater', string.format("attempted to slash the %s tire of a vehicle that was %.2f meters away", getTireLabel(tireIndex), distance), { source = src, netId = netId, tireIndex = tireIndex, hasBurstedTire = hasBurstedTire, vehicleCoords = vehicleCoords, playerCoords = playerCoords, distance = distance })
|
|
DisplayNotification(src, GetLocalization('too_far_away'))
|
|
return
|
|
end
|
|
|
|
local canPlayerSlash, reason = CanPlayerSlashTires(src, playerPed)
|
|
if not canPlayerSlash and reason then
|
|
Log(src, 'slashtires:cheaterOrError', string.format("attempted to slash the %s tire of a vehicle but was was blocked by a server bridge script (blocked reason: %s)", reason), { source = src, netId = netId, tireIndex = tireIndex, hasBurstedTire = hasBurstedTire, vehicleCoords = vehicleCoords, playerCoords = playerCoords, distance = distance, blockedReason = reason })
|
|
DisplayNotification(src, GetLocalization(reason))
|
|
return
|
|
end
|
|
|
|
if IsPedRagdoll(playerPed) then
|
|
Log(src, 'slashtires:cheaterOrError', "attempted to slash the %s tire of a vehicle but was blocked due to being in ragdoll", { source = src, netId = netId, tireIndex = tireIndex, hasBurstedTire = hasBurstedTire, vehicleCoords = vehicleCoords, playerCoords = playerCoords, distance = distance })
|
|
DisplayNotification(src, GetLocalization('in_ragdoll'))
|
|
return
|
|
end
|
|
|
|
local canWeaponSlash, weaponHash = canCurrentWeaponSlashTires(playerPed)
|
|
if not canWeaponSlash then
|
|
Log(src, 'slashtires:cheater', string.format("attempted to slash the %s tire of a vehicle but did not have a allowed weapon equipped (current player weapon hash: %s)", getTireLabel(tireIndex), weaponHash), { source = src, netId = netId, tireIndex = tireIndex, hasBurstedTire = hasBurstedTire, vehicleCoords = vehicleCoords, playerCoords = playerCoords, distance = distance, weaponHash = weaponHash })
|
|
DisplayNotification(src, GetLocalization('invalid_weapon'))
|
|
return
|
|
end
|
|
|
|
local hasItem = HasWeapon(src, weaponHash)
|
|
local weaponName = Config.AllowedWeapons[weaponHash]?.name or string.format("unknown (hash: %s)", weaponHash)
|
|
if not hasItem then
|
|
Log(src, 'slashtires:cheater', string.format("attempted to slash the %s tire of a vehicle but did not have the weapon item required (item needed: %s)", getTireLabel(tireIndex), weaponName), { source = src, netId = netId, tireIndex = tireIndex, hasBurstedTire = hasBurstedTire, vehicleCoords = vehicleCoords, playerCoords = playerCoords, distance = distance, weaponHash = weaponHash, weaponName = weaponName })
|
|
DisplayNotification(src, GetLocalization('invalid_weapon'))
|
|
return
|
|
end
|
|
|
|
local vehicleModel = GetEntityModel(vehicle)
|
|
local isBlacklisted, blacklistReason = isVehicleModelBlacklisted(vehicleModel)
|
|
if isBlacklisted then
|
|
Log(src, 'slashtires:cheater', string.format("attempted to slash the %s tire of a vehicle that was blacklisted (model: %s)", getTireLabel(tireIndex), vehicleModel), { source = src, netId = netId, tireIndex = tireIndex, hasBurstedTire = hasBurstedTire, vehicleCoords = vehicleCoords, playerCoords = playerCoords, distance = distance, weaponHash = weaponHash, weaponName = weaponName, vehicleModel = vehicleModel })
|
|
DisplayNotification(src, GetLocalization(blacklistReason))
|
|
return
|
|
end
|
|
|
|
local sendtAlert = false
|
|
if Config.Dispatch and #reactPeds > 0 then
|
|
local chance = math.random(0, 1000) / 10
|
|
if chance < Config.DispatchAlertChance then
|
|
sendtAlert = true
|
|
SendDispatchAlert(source, playerCoords, vehicle)
|
|
end
|
|
end
|
|
|
|
if Config.AIReactAndFlee then
|
|
reactAndFleeNearbyPeds(reactPeds, playerCoords, playerPed)
|
|
end
|
|
|
|
local wasWeaponRemoved = false
|
|
if shouldWeaponBreak(weaponHash) then
|
|
local success = RemoveWeapon(src, weaponHash, playerPed)
|
|
if success then
|
|
wasWeaponRemoved = true
|
|
end
|
|
elseif Config.ReducedItemDurability and Config.AllowedWeapons[weaponHash]?.durability then
|
|
local weaponBroke = ReduceDurabilityForWeapon(src, weaponHash, playerPed)
|
|
if weaponBroke then
|
|
wasWeaponRemoved = true
|
|
end
|
|
end
|
|
|
|
if wasWeaponRemoved then
|
|
DisplayNotification(source, GetLocalization('weapon_broke'))
|
|
end
|
|
|
|
local numberPlate = GetVehicleNumberPlateText(vehicle)
|
|
local data = { source = src, netId = netId, tireIndex = tireIndex, hasBurstedTire = hasBurstedTire, vehicleCoords = vehicleCoords, playerCoords = playerCoords, distance = distance, weaponHash = weaponHash, weaponName = weaponName, sendtAlert = sendtAlert, weaponRemoved = wasWeaponRemoved, vehicleModel = vehicleModel, numberPlate = numberPlate }
|
|
Log(src, 'slashtires:slashedTire', string.format("slashed the %s tire of a vehicle with plate \"%s\"", getTireLabel(tireIndex), numberPlate), data)
|
|
|
|
-- Event for other scripts to listen to
|
|
TriggerEvent('slashtires:slashedTire', data)
|
|
|
|
-- If the tire has already been bursted by the client then there isn't any need to send any events.
|
|
if hasBurstedTire then
|
|
return
|
|
end
|
|
|
|
local netOwner = NetworkGetEntityOwner(vehicle)
|
|
TriggerClientEvent('slashtires:burstTire', netOwner, netId, tireIndex)
|
|
end
|
|
|
|
RegisterServerEvent('slashtires:slashTire', slashTire)
|