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)
 | 
