From 437b606f7118a08a1cd1ed51a466b3798393b8d6 Mon Sep 17 00:00:00 2001 From: QuantumMalice Date: Thu, 11 Jul 2024 08:37:55 -0600 Subject: [PATCH] reupload --- README.md | 75 ++++++++++++ client.lua | 169 +++++++++++++++++++++++++++ data/progress.lua | 67 +++++++++++ data/vehicle.lua | 96 +++++++++++++++ fxmanifest.lua | 25 ++++ modules/handler.lua | 278 ++++++++++++++++++++++++++++++++++++++++++++ server.lua | 94 +++++++++++++++ 7 files changed, 804 insertions(+) create mode 100644 README.md create mode 100644 client.lua create mode 100644 data/progress.lua create mode 100644 data/vehicle.lua create mode 100644 fxmanifest.lua create mode 100644 modules/handler.lua create mode 100644 server.lua diff --git a/README.md b/README.md new file mode 100644 index 0000000..26f0a12 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +![Main Banner](https://cdn.discordapp.com/attachments/688864735646580762/1178975479861104710/QM-main_2.png?ex=6690e8fa&is=668f977a&hm=fb8c29b6587b5f2571120960f5f85564912b1284f0f0341ca5aef2df3f6c72f2&) + +## __Features:__ +➢ Tire loss on impact
+➢ Reduces torque based on current health
+➢ Prevents crazy handling from low fuel
+➢ Disables vehicle after heavy collisions
+➢ Disables controls while airborne/flipped
+➢ Repair/Wash item integration (clean, tire, engine)
+ +*Idle -* `0.0ms`
+*Driving -* `0.0ms ~ 0.02ms`
+ +## __Dependencies:__ +* [ox_lib](https://github.com/overextended/ox_lib) +* [ox_inventory](https://github.com/overextended/ox_inventory) *(Optional)* + +## ***ox_inventory***: +```lua + ["cleaningkit"] = { + label = "Cleaning Kit", + weight = 250, + stack = true, + close = true, + description = "A microfiber cloth with some soap will let your car sparkle again!", + client = { + image = "cleaningkit.png", + }, + server = { + export = 'vehiclehandler.cleaningkit' + } + }, + + ["tirekit"] = { + label = "Tire Kit", + weight = 250, + stack = true, + close = true, + description = "A nice toolbox with stuff to repair your tire", + client = { + image = "tirekit.png", + }, + server = { + export = 'vehiclehandler.tirekit' + } + }, + + ["repairkit"] = { + label = "Repairkit", + weight = 2500, + stack = true, + close = true, + description = "A nice toolbox with stuff to repair your vehicle", + client = { + image = "repairkit.png", + }, + server = { + export = 'vehiclehandler.repairkit', + } + }, + + ["advancedrepairkit"] = { + label = "Advanced Repairkit", + weight = 5000, + stack = true, + close = true, + description = "A nice toolbox with stuff to repair your vehicle", + client = { + image = "advancedkit.png", + }, + server = { + export = 'vehiclehandler.advancedrepairkit', + } + }, +``` diff --git a/client.lua b/client.lua new file mode 100644 index 0000000..b3ae26b --- /dev/null +++ b/client.lua @@ -0,0 +1,169 @@ +local ox_lib, msg_lib = lib.checkDependency('ox_lib', '3.17.0') +if not ox_lib then print(msg_lib) return end + +if GetResourceState('ox_inventory') == 'started' then + local ox_inv, msg_inv = lib.checkDependency('ox_inventory', '2.39.1') + if not ox_inv then print(msg_inv) return end +end + +local Class = require 'modules.handler' +local Settings = lib.load('data.vehicle') +local Handler = nil + +local function startThreads(vehicle) + if not vehicle then return end + if not Handler or Handler:isActive() then return end + + Handler:setActive(true) + + local oxfuel = Handler:isFuelOx() + local units = Handler:getUnits() + local class = GetVehicleClass(vehicle) or false + local speedBuffer, healthBuffer, bodyBuffer, roll, airborne = {0.0,0.0}, {0.0,0.0}, {0.0,0.0}, 0.0, false + + CreateThread(function() + while (cache.vehicle == vehicle) and (cache.seat == -1) do + + -- Retrieve latest vehicle data + bodyBuffer[1] = GetVehicleBodyHealth(vehicle) + healthBuffer[1] = GetVehicleEngineHealth(vehicle) + speedBuffer[1] = GetEntitySpeed(vehicle) * units + + -- Driveability handler (health, fuel) + local fuelLevel = oxfuel and Entity(vehicle).state.fuel or GetVehicleFuelLevel(vehicle) + if healthBuffer[1] <= 0 or fuelLevel <= 6.4 then + if IsVehicleDriveable(vehicle, true) then + SetVehicleUndriveable(vehicle, true) + end + end + + -- Reduce torque after half-life + if not Handler:isLimited() then + if healthBuffer[1] < 500 then + Handler:setLimited(true) + + CreateThread(function() + while (cache.vehicle == vehicle) and (healthBuffer[1] < 500) do + local newtorque = (healthBuffer[1] + 500) / 1100 + SetVehicleCheatPowerIncrease(vehicle, newtorque) + Wait(1) + end + + Handler:setLimited(false) + end) + end + end + + -- Prevent rotation controls while flipped/airborne + if Settings.regulated[class] then + if speedBuffer[1] < 2.0 then + if airborne then airborne = false end + roll = GetEntityRoll(vehicle) + else + airborne = IsEntityInAir(vehicle) + end + + if (roll > 75.0 or roll < -75.0) or airborne then + SetVehicleOutOfControl(vehicle, false, false) + end + end + + -- Damage handler + local bodyDiff = bodyBuffer[2] - bodyBuffer[1] + if bodyDiff >= 1 then + + -- Calculate latest damage + local bodyDamage = bodyDiff * Settings.globalmultiplier * Settings.classmultiplier[class] + local vehicleHealth = healthBuffer[1] - bodyDamage + + -- Update engine health + if vehicleHealth ~= healthBuffer[1] and vehicleHealth > 0 then + SetVehicleEngineHealth(vehicle, vehicleHealth) + elseif vehicleHealth ~= 0 then + SetVehicleEngineHealth(vehicle, 0.0) -- prevent negative engine health + end + + -- Prevent negative body health + if bodyBuffer[1] < 0 then + SetVehicleBodyHealth(vehicle, 0.0) + end + + -- Prevent negative tank health (explosion) + if GetVehiclePetrolTankHealth(vehicle) < 0 then + SetVehiclePetrolTankHealth(vehicle, 0.0) + end + end + + -- Impact handler + local speedDiff = speedBuffer[2] - speedBuffer[1] + if speedDiff >= Settings.threshold.speed then + + -- Handle wheel loss + if Settings.breaktire then + if bodyDiff >= Settings.threshold.tire then + math.randomseed(GetGameTimer()) + Handler:breakTire(vehicle, math.random(0, 1)) + end + end + + -- Handle heavy impact + if speedDiff >= Settings.threshold.heavy then + SetVehicleUndriveable(vehicle, true) + SetVehicleEngineHealth(vehicle, 0.0) -- Disable vehicle completely + end + end + + -- Store data for next cycle + bodyBuffer[2] = bodyBuffer[1] + healthBuffer[2] = healthBuffer[1] + speedBuffer[2] = speedBuffer[1] + + Wait(100) + end + + Handler:setActive(false) + + -- Retrigger thread if admin spawns a new vehicle while in one + if cache.vehicle and cache.seat == -1 then + if Handler:isLimited() then Handler:setLimited(false) end + startThreads(cache.vehicle) + end + end) +end + +lib.onCache('seat', function(seat) + if seat == -1 then + startThreads(cache.vehicle) + end +end) + +lib.callback.register('vehiclehandler:adminfuel', function(newlevel) + if not Handler or not Handler:isActive() then return end + return Handler:adminfuel(newlevel) +end) + +lib.callback.register('vehiclehandler:adminwash', function() + if not Handler or not Handler:isActive() then return end + return Handler:adminwash() +end) + +lib.callback.register('vehiclehandler:adminfix', function() + if not Handler or not Handler:isActive() then return end + return Handler:adminfix() +end) + +lib.callback.register('vehiclehandler:basicwash', function() + if not Handler then return end + return Handler:basicwash() +end) + +lib.callback.register('vehiclehandler:basicfix', function(fixtype) + if not fixtype or type(fixtype) ~= 'string' then return end + if not Handler then return end + return Handler:basicfix(fixtype) +end) + +CreateThread(function() + Handler = Class:new() + startThreads(cache.vehicle) +end) \ No newline at end of file diff --git a/data/progress.lua b/data/progress.lua new file mode 100644 index 0000000..3a15c7f --- /dev/null +++ b/data/progress.lua @@ -0,0 +1,67 @@ +return { + ['cleankit'] = { + label = "Cleaning vehicle", + duration = math.random(10000, 20000), + position = 'bottom', + useWhileDead = false, + canCancel = true, + disable = { + car = true, + move = true, + combat = true, + mouse = false, + }, + }, + ['tirekit'] = { + label = "Repairing tires", + duration = math.random(10000, 20000), + position = 'bottom', + useWhileDead = false, + canCancel = true, + disable = { + car = true, + move = true, + combat = true, + mouse = false, + }, + anim = { + dict = "anim@amb@clubhouse@tutorial@bkr_tut_ig3@", + clip = "machinic_loop_mechandplayer", + flag = 10 + }, + }, + ['smallkit'] = { + label = "Repairing vehicle", + duration = math.random(15000, 20000), + position = 'bottom', + useWhileDead = false, + canCancel = true, + disable = { + car = true, + move = true, + combat = true, + mouse = false, + }, + anim = { + dict = "mini@repair", + clip = "fixing_a_player" + }, + }, + ['bigkit'] = { + label = "Repairing vehicle", + duration = math.random(25000, 30000), + position = 'bottom', + useWhileDead = false, + canCancel = true, + disable = { + car = true, + move = true, + combat = true, + mouse = false, + }, + anim = { + dict = "mini@repair", + clip = "fixing_a_player" + }, + } +} \ No newline at end of file diff --git a/data/vehicle.lua b/data/vehicle.lua new file mode 100644 index 0000000..fbe9c20 --- /dev/null +++ b/data/vehicle.lua @@ -0,0 +1,96 @@ +return { + units = 'mph' , -- (mph, kmh) + breaktire = true, -- Enable/Disable breaking off vehicle wheel on impact + threshold = { + tire = 50.0, -- Health difference needed to break off wheel (LastHealth - CurrentHealth) + speed = 50.0, -- Speed difference needed to trigger collision events (LastSpeed - CurrentSpeed) + heavy = 90.0, -- Speed difference needed for a heavy collision event (LastSpeed - CurrentSpeed) + }, + globalmultiplier = 20.0, + classmultiplier = { + [0] = 1.0, -- 0: Compacts + 1.0, -- 1: Sedans + 1.0, -- 2: SUVs + 0.95, -- 3: Coupes + 1.0, -- 4: Muscle + 0.95, -- 5: Sports Classics + 0.95, -- 6: Sports + 0.95, -- 7: Super + 0.47, -- 8: Motorcycles + 0.7, -- 9: Off-road + 0.25, -- 10: Industrial + 0.35, -- 11: Utility + 0.85, -- 12: Vans + 1.0, -- 13: Cycles + 0.4, -- 14: Boats + 0.7, -- 15: Helicopters + 0.7, -- 16: Planes + 0.75, -- 17: Service + 0.35, -- 18: Emergency + 0.27, -- 19: Military + 0.43, -- 20: Commercial + 0.1 -- 21: Trains + }, + regulated = { + [0] = true, -- compacts + true, -- sedans + true, -- SUV's + true, -- coupes + true, -- muscle + true, -- sport classic + true, -- sport + true, -- super + false, -- motorcycle + true, -- offroad + true, -- industrial + true, -- utility + true, -- vans + false, -- bicycles + false, -- boats + false, -- helicopter + false, -- plane + true, -- service + true, -- emergency + false, -- military + true, -- commercial + false, -- trains + true, -- open wheel + }, + backengine = { + [`ninef`] = true, + [`adder`] = true, + [`vagner`] = true, + [`t20`] = true, + [`infernus`] = true, + [`zentorno`] = true, + [`reaper`] = true, + [`comet2`] = true, + [`jester`] = true, + [`jester2`] = true, + [`cheetah`] = true, + [`cheetah2`] = true, + [`prototipo`] = true, + [`turismor`] = true, + [`pfister811`] = true, + [`ardent`] = true, + [`nero`] = true, + [`nero2`] = true, + [`tempesta`] = true, + [`vacca`] = true, + [`bullet`] = true, + [`osiris`] = true, + [`entityxf`] = true, + [`turismo2`] = true, + [`fmj`] = true, + [`re7b`] = true, + [`tyrus`] = true, + [`italigtb`] = true, + [`penetrator`] = true, + [`monroe`] = true, + [`ninef2`] = true, + [`stingergt`] = true, + [`surfer`] = true, + [`surfer2`] = true, + [`comet3`] = true, + } +} \ No newline at end of file diff --git a/fxmanifest.lua b/fxmanifest.lua new file mode 100644 index 0000000..0c0cccb --- /dev/null +++ b/fxmanifest.lua @@ -0,0 +1,25 @@ +fx_version 'cerulean' +game 'gta5' +lua54 'yes' + +name 'vehiclehandler' +description 'Collision/damage handling for FiveM.' +author 'QuantumMalice' +version '1.1.2' + +files { + 'data/*.lua', + 'modules/handler.lua', +} + +shared_scripts { + '@ox_lib/init.lua', +} + +client_scripts { + 'client.lua', +} + +server_scripts { + 'server.lua', +} \ No newline at end of file diff --git a/modules/handler.lua b/modules/handler.lua new file mode 100644 index 0000000..f0c36d9 --- /dev/null +++ b/modules/handler.lua @@ -0,0 +1,278 @@ +local Progress = lib.load('data.progress') +local Settings = lib.load('data.vehicle') + +local BONES = { + [0] = 'wheel_lf', + 'wheel_rf', + 'wheel_lm', + 'wheel_rm', + 'wheel_lr', + 'wheel_rr' +} + +---@class Handler : OxClass +---@field private private { active: boolean, limited: boolean, oxfuel: boolean, units: number } +local Handler = lib.class('vehiclehandler') + +function Handler:constructor() + self:setActive(false) + self:setLimited(false) + self.private.oxfuel = GetResourceState('ox_fuel') == 'started' and true or false + self.private.units = Settings.units == 'mph' and 2.23694 or 3.6 +end + +function Handler:isActive() return self.private.active end + +function Handler:isLimited() return self.private.limited end + +function Handler:isFuelOx() return self.private.oxfuel end + +function Handler:getUnits() return self.private.units end + +function Handler:isValid() + if not cache.ped then return false end + if cache.vehicle or IsPedInAnyPlane(cache.ped) then return true end + + return false +end + +function Handler:isWheelBroken(vehicle, coords) + if not vehicle or not coords then return false end + + for k,v in pairs(BONES) do + local bone = GetEntityBoneIndexByName(vehicle, v) + + if bone ~= -1 then + if IsVehicleTyreBurst(vehicle, k, true) then + local pos = GetWorldPositionOfEntityBone(vehicle, bone) + + if #(coords - pos) < 2.5 then + return true + end + end + end + end + + return false +end + +function Handler:getEngineData(vehicle) + if not vehicle or vehicle == 0 then return end + + local backengine = Settings.backengine[GetEntityModel(vehicle)] + local distance = backengine and -2.5 or 2.5 + local offset = GetOffsetFromEntityInWorldCoords(vehicle, 0, distance, 0) + local index = backengine and 5 or 4 + local health = GetVehicleEngineHealth(vehicle) + + return backengine, offset, index, health +end + +function Handler:setActive(state) + if state ~= nil and type(state) == 'boolean' then + self.private.active = state + end +end + +function Handler:setLimited(state) + if state ~= nil and type(state) == 'boolean' then + self.private.limited = state + end +end + +function Handler:breakTire(vehicle, index) + if vehicle == nil or type(vehicle) ~= 'number' then return end + if index == nil or type(index) ~= 'number' then return end + + local bone = GetEntityBoneIndexByName(vehicle, BONES[index]) + + if bone ~= -1 then + if not IsVehicleTyreBurst(vehicle, index, true) then + + lib.callback('vehiclehandler:sync', false, function() + SetVehicleTyreBurst(vehicle, index, true, 1000.0) + BreakOffVehicleWheel(vehicle, index, true, true, true, false) + end) + end + end +end + +function Handler:adminfuel(newlevel) + if not self:isValid() then return false end + if not newlevel then return false end + + newlevel = lib.math.clamp(newlevel, 0.0, 100.0) + + lib.callback('vehiclehandler:sync', false, function() + if self:isFuelOx() then + Entity(cache.vehicle).state.fuel = newlevel + end + + SetVehicleFuelLevel(cache.vehicle, newlevel) + DecorSetFloat(cache.vehicle, '_FUEL_LEVEL', GetVehicleFuelLevel(cache.vehicle)) + + SetVehicleUndriveable(cache.vehicle, false) + SetVehicleEngineOn(cache.vehicle, true, true, true) + end) + + return true +end + +function Handler:adminwash() + if not self:isValid() then return false end + + lib.callback('vehiclehandler:sync', false, function() + SetVehicleDirtLevel(cache.vehicle, 0.0) + WashDecalsFromVehicle(cache.vehicle, 1.0) + end) + + return true +end + +function Handler:adminfix() + if not self:isValid() then return false end + + lib.callback('vehiclehandler:sync', false, function() + SetVehicleFixed(cache.vehicle) + ResetVehicleWheels(cache.vehicle, true) + + if self:isFuelOx() then + Entity(cache.vehicle).state.fuel = 100.0 + end + + SetVehicleFuelLevel(cache.vehicle, 100.0) + DecorSetFloat(cache.vehicle, '_FUEL_LEVEL', GetVehicleFuelLevel(cache.vehicle)) + + SetVehicleUndriveable(cache.vehicle, false) + SetVehicleEngineOn(cache.vehicle, true, true, true) + end) + + return true +end + +function Handler:basicwash() + if not cache.ped then return false end + + local pos = GetEntityCoords(cache.ped) + local vehicle,_ = lib.getClosestVehicle(pos, 3.0, false) + if vehicle == nil or vehicle == 0 then return false end + + local vehpos = GetEntityCoords(vehicle) + if #(pos - vehpos) > 3.0 or cache.vehicle then return false end + + local success = false + LocalPlayer.state:set("inv_busy", true, true) + TaskStartScenarioInPlace(cache.ped, "WORLD_HUMAN_MAID_CLEAN", 0, true) + + if lib.progressCircle(Progress['cleankit']) then + success = true + + lib.callback('vehiclehandler:sync', false, function() + SetVehicleDirtLevel(vehicle, 0.0) + WashDecalsFromVehicle(vehicle, 1.0) + end) + end + + ClearAllPedProps(cache.ped) + ClearPedTasks(cache.ped) + + LocalPlayer.state:set("inv_busy", false, true) + + return success +end + +function Handler:basicfix(fixtype) + if not cache.ped then return false end + if not fixtype or type(fixtype) ~= 'string' then return false end + + local coords = GetEntityCoords(cache.ped) + local vehicle,_ = lib.getClosestVehicle(coords, 3.0, false) + if vehicle == nil or vehicle == 0 then return false end + + if fixtype == 'tirekit' then + local found = self:isWheelBroken(vehicle, coords) + + if found then + local lastengine = GetVehicleEngineHealth(vehicle) + local lastbody = GetVehicleBodyHealth(vehicle) + local lasttank = GetVehiclePetrolTankHealth(vehicle) + local lastdirt = GetVehicleDirtLevel(vehicle) + local success = false + + LocalPlayer.state:set("inv_busy", true, true) + + if lib.progressCircle(Progress[fixtype]) then + success = true + + lib.callback('vehiclehandler:sync', false, function() + SetVehicleFixed(vehicle) + SetVehicleEngineHealth(vehicle, lastengine) + SetVehicleBodyHealth(vehicle, lastbody) + SetVehiclePetrolTankHealth(vehicle, lasttank) + SetVehicleDirtLevel(vehicle, lastdirt) + end) + end + + LocalPlayer.state:set("inv_busy", false, true) + + return success + end + elseif fixtype == 'smallkit' or fixtype == 'bigkit' then + local backengine, offset, hoodindex, health = self:getEngineData(vehicle) + + if fixtype == 'smallkit' and health < 500 or fixtype == 'bigkit' and health < 1000 then + if #(coords - offset) < 2.0 then + local success = false + + LocalPlayer.state:set("inv_busy", true, true) + + if hoodindex then + SetVehicleDoorOpen(vehicle, hoodindex, false, false) + end + + if lib.progressCircle(Progress[fixtype]) then + success = true + + lib.callback('vehiclehandler:sync', false, function() + if fixtype == 'smallkit' then + SetVehicleEngineHealth(vehicle, 500.0) + end + + SetVehicleUndriveable(vehicle, false) + end) + end + + if hoodindex then + SetVehicleDoorShut(vehicle, hoodindex, false) + end + + LocalPlayer.state:set("inv_busy", false, true) + + if success and fixtype == 'bigkit' then + CreateThread(function() + Wait(1000) + SetVehicleFixed(vehicle) + end) + end + + return success + else + if backengine then + lib.notify({ + title = 'Engine bay is in back', + type = 'error' + }) + end + end + else + lib.notify({ + title = 'Cannot repair vehicle any further', + type = 'error' + }) + end + end + + return false +end + +return Handler \ No newline at end of file diff --git a/server.lua b/server.lua new file mode 100644 index 0000000..5f91f92 --- /dev/null +++ b/server.lua @@ -0,0 +1,94 @@ +lib.versionCheck("QuantumMalice/vehiclehandler") + +local ox_lib, msg_lib = lib.checkDependency('ox_lib', '3.17.0') +if not ox_lib then print(msg_lib) return end + +if GetResourceState('ox_inventory') == 'started' then + local ox_inv, msg_inv = lib.checkDependency('ox_inventory', '2.39.1') + if not ox_inv then print(msg_inv) return end + + exports('cleaningkit', function(event, item, inventory, slot, data) + if event == 'usingItem' then + local src = inventory.id + if not src then return false end + + local success = lib.callback.await('vehiclehandler:basicwash', src) + if success then return end + + return false + end + end) + + exports('tirekit', function(event, item, inventory, slot, data) + if event == 'usingItem' then + local src = inventory.id + if not src then return false end + + local success = lib.callback.await('vehiclehandler:basicfix', src, 'tirekit') + if success then return end + + return false + end + end) + + exports('repairkit', function(event, item, inventory, slot, data) + if event == 'usingItem' then + local src = inventory.id + if not src then return false end + + local success = lib.callback.await('vehiclehandler:basicfix', src, 'smallkit') + if success then return end + + return false + end + end) + + exports('advancedrepairkit', function(event, item, inventory, slot, data) + if event == 'usingItem' then + local src = inventory.id + if not src then return false end + + local success = lib.callback.await('vehiclehandler:basicfix', src, 'bigkit') + if success then return end + + return false + end + end) +end + +lib.callback.register('vehiclehandler:sync', function() + return true +end) + +lib.addCommand('fix', { + help = 'Repair current vehicle', + restricted = 'group.admin' +}, function(source, args, raw) + lib.callback('vehiclehandler:adminfix', source, function() end) +end) + +lib.addCommand('wash', { + help = 'Clean current vehicle', + restricted = 'group.admin' +}, function(source, args, raw) + lib.callback('vehiclehandler:adminwash', source, function() end) +end) + +lib.addCommand('setfuel', { + help = 'Set vehicle fuel level', + params = { + { + name = 'level', + type = 'number', + help = 'Amount of fuel to set', + }, + }, + restricted = 'group.admin' +}, function(source, args, raw) + local level = args.level + + if level then + lib.callback('vehiclehandler:adminfuel', source, function() + end, level) + end +end) \ No newline at end of file