local TIRE_LABLES = { [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 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)