structura foldere

mutat kq- folders in un singur folder [kq]
This commit is contained in:
2026-03-30 01:55:03 +03:00
parent af1286d583
commit c291b81f26
2319 changed files with 0 additions and 14 deletions
Binary file not shown.
@@ -0,0 +1,27 @@
# KQ_DYNO INSTALLATION GUIDE
This guide will provide step-by-step instructions on how to install and set up the KQ_DYNO script for FiveM.
## Step 1:
After downloading the script, unzip the folder and place it in the `resources` directory on your FiveM server.
## Step 2:
Open the `config.lua` file in the script folder and make sure that the correct framework is enabled.
## Step 3:
After the config file has been set up, add the script to your `server.cfg` file. Make sure that it's added **after** your framework of choice, so that it loads and starts properly.
## Step 4:
Configure your dyno locations in the `config.lua` file
## Done
Enjoy the script
## Extra info
To restart the script. Use our custom `/kq_dyno_restart` command. Simply restarting or ensuring it will cause you to crash
due to the custom props being unloaded from the memory.
https://kuzquality.com/
https://discord.gg/fZsyam7Rvz
@@ -0,0 +1,9 @@
-- Triggered when player starts a dyno run
function OnDynoStart(dynoKey)
end
-- Triggered when the dyno run is fully finished
function OnDynoFinish(dynoKey)
end
@@ -0,0 +1,448 @@
function GetVehicleStats(veh)
if not Config.dynoFormula then
return GetVehicleStatsVanilla(veh)
end
if Config.dynoFormula == 'highperformance1' then
return GetVehicleStatsAlternative(veh)
end
if Config.dynoFormula == 'highperformance2' then
return GetVehicleStatsAlternative2(veh)
end
if Config.dynoFormula == 'highperformance3' then
return GetVehicleStatsAlternative3(veh)
end
return GetVehicleStatsVanilla(veh)
end
function GetVehicleStatsVanilla(veh)
return UseCache('vehStats' .. veh, function()
local fInitialDriveForce = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fInitialDriveForce')
local fDriveInertia = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fDriveInertia')
local fInitialDriveMaxFlatVel = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fInitialDriveMaxFlatVel')
local fMass = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fMass')
local nInitialDriveGears = GetVehicleHandlingFloat(veh, 'CHandlingData', 'nInitialDriveGears')
local fDriveBiasFront = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fDriveBiasFront')
local drivetrainLoss = 0.1 -- awd
if fDriveBiasFront >= 0.85 then
drivetrainLoss = 0.13 -- fwd
end
if fDriveBiasFront <= 0.15 then
drivetrainLoss = 0.2 -- rwd
end
local wheelPower = GetWheelPower(veh)
if IsVehicleFwd(veh) then
wheelPower = wheelPower * fDriveBiasFront
else
wheelPower = wheelPower * (1 - fDriveBiasFront)
end
local rpm = GetVehicleCurrentRpm(veh)
local maxRpm = ((math.min(161.0, fInitialDriveMaxFlatVel) / nInitialDriveGears / 30) * 8500) * 0.95
local realRpm = math.floor(rpm * maxRpm)
local torque = math.floor((fInitialDriveForce * fMass) * (realRpm / maxRpm)
+ (wheelPower * 40)
* (math.min(4.0, math.max(0.7, (fMass / 1700) ^ 3)))
)
local hpMultiplier = 0.95
if fInitialDriveMaxFlatVel >= 150 then
hpMultiplier = 1.0
end
if fInitialDriveMaxFlatVel >= 159 then
hpMultiplier = 1.1
torque = math.floor(torque * 1.05)
end
if fInitialDriveMaxFlatVel >= 159.5 then
hpMultiplier = 1.15
torque = math.floor(torque * 1.15)
end
if fInitialDriveMaxFlatVel >= 164.0 then
hpMultiplier = 1.05
torque = math.floor(torque * 1.1)
end
local hp = math.abs(math.floor(DiminishingReturns(
((torque / 1.356) * (rpm * maxRpm) / 5252)
* fDriveInertia
* (math.min(160.0, fInitialDriveMaxFlatVel) / 150)
* wheelPower
* (1 - drivetrainLoss)
* hpMultiplier
* (math.min(2.0, math.max(0.95, fMass / 1400)))
)))
local torqueInUnits = torque
if UsingFtLbs() then
torqueInUnits = torque / 1.356
end
return hp, torqueInUnits, realRpm, rpm
end, 50)
end
function GetVehicleStatsAlternative(veh)
return UseCache('vehStats' .. veh, function()
local fInitialDriveForce = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fInitialDriveForce')
local fDriveInertia = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fDriveInertia')
local fInitialDriveMaxFlatVel = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fInitialDriveMaxFlatVel')
local fMass = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fMass')
local nInitialDriveGears = GetVehicleHandlingFloat(veh, 'CHandlingData', 'nInitialDriveGears')
local fDriveBiasFront = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fDriveBiasFront')
local drivetrainLoss = 0.1 -- awd
if fDriveBiasFront >= 0.85 then
drivetrainLoss = 0.13 -- fwd
end
if fDriveBiasFront <= 0.15 then
drivetrainLoss = 0.2 -- rwd
end
local wheelPower = GetWheelPower(veh)
if IsVehicleFwd(veh) then
wheelPower = wheelPower * fDriveBiasFront
else
wheelPower = wheelPower * (1 - fDriveBiasFront)
end
local rpm = GetVehicleCurrentRpm(veh)
local maxRpm = ((math.min(161.0, fInitialDriveMaxFlatVel) / nInitialDriveGears / 30) * 8500) * 0.95
local realRpm = math.floor(rpm * maxRpm)
local torque = math.floor((fInitialDriveForce * fMass) * (realRpm / maxRpm)
+ (wheelPower * 40)
* (math.min(4.0, math.max(0.7, (fMass / 1700) ^ 3)))
)
local hpMultiplier = 0.95
if fInitialDriveMaxFlatVel >= 150 then
hpMultiplier = 1.0
end
if fInitialDriveMaxFlatVel >= 159 then
hpMultiplier = 1.1
torque = math.floor(torque * 1.05)
end
if fInitialDriveMaxFlatVel >= 159.5 then
hpMultiplier = 1.15
torque = math.floor(torque * 1.15)
end
if fInitialDriveMaxFlatVel >= 164.0 then
hpMultiplier = 1.05
torque = math.floor(torque * 1.1)
end
local hp = math.abs(math.floor(DiminishingReturns(
((torque / 1.356) * (rpm * maxRpm) / 5252)
* fDriveInertia
* math.min(1.1, wheelPower)
* (1 - drivetrainLoss)
* hpMultiplier
* (math.min(2.0, math.max(0.95, fMass / 1400)))
)))
local torqueInUnits = torque
if UsingFtLbs() then
torqueInUnits = torque / 1.356
end
return hp, torqueInUnits, realRpm, rpm
end, 50)
end
function GetVehicleStatsAlternative2(veh)
return UseCache('vehStats' .. veh, function()
local fInitialDriveForce = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fInitialDriveForce')
local fDriveInertia = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fDriveInertia')
local fInitialDriveMaxFlatVel = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fInitialDriveMaxFlatVel')
local fMass = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fMass')
local nInitialDriveGears = GetVehicleHandlingFloat(veh, 'CHandlingData', 'nInitialDriveGears')
local fDriveBiasFront = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fDriveBiasFront')
local drivetrainLoss = 0.1 -- awd
if fDriveBiasFront >= 0.85 then
drivetrainLoss = 0.13 -- fwd
end
if fDriveBiasFront <= 0.15 then
drivetrainLoss = 0.2 -- rwd
end
local wheelPower = GetWheelPower(veh)
if IsVehicleFwd(veh) then
wheelPower = wheelPower * fDriveBiasFront
else
wheelPower = wheelPower * (1 - fDriveBiasFront)
end
local rpm = GetVehicleCurrentRpm(veh)
local maxRpm = ((math.min(161.0, fInitialDriveMaxFlatVel) / nInitialDriveGears / 30) * 8500) * 0.95
local realRpm = math.floor(rpm * maxRpm)
local torque = math.floor(((fInitialDriveForce * 0.5) * fMass) * (realRpm / maxRpm)
+ (math.min(3.0, wheelPower))
* (math.min(4.0, math.max(0.7, (fMass / 1700) ^ 3)))
)
local hpMultiplier = 0.95
if fInitialDriveMaxFlatVel >= 150 then
hpMultiplier = 1.0
end
if fInitialDriveMaxFlatVel >= 159 then
hpMultiplier = 1.1
torque = math.floor(torque * 1.05)
end
if fInitialDriveMaxFlatVel >= 159.5 then
hpMultiplier = 1.15
torque = math.floor(torque * 1.15)
end
if fInitialDriveMaxFlatVel >= 164.0 then
hpMultiplier = 1.05
torque = math.floor(torque * 1.1)
end
local hp = math.abs(math.floor(DiminishingReturns(
((torque / 1.356) * (rpm * maxRpm) / 5252)
* fDriveInertia
* (math.min(160.0, fInitialDriveMaxFlatVel) / 250)
* math.min(2.0, wheelPower)
* (1 - drivetrainLoss)
* hpMultiplier
* (math.min(2.0, math.max(0.95, fMass / 1400)))
)))
local torqueInUnits = torque
if UsingFtLbs() then
torqueInUnits = torque / 1.356
end
return hp, torqueInUnits, realRpm, rpm
end, 50)
end
function GetVehicleStatsAlternative3(veh)
return UseCache('vehStats' .. veh, function()
local fInitialDriveForce = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fInitialDriveForce')
local fDriveInertia = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fDriveInertia')
local fInitialDriveMaxFlatVel = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fInitialDriveMaxFlatVel')
local fMass = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fMass')
local nInitialDriveGears = GetVehicleHandlingFloat(veh, 'CHandlingData', 'nInitialDriveGears')
local fDriveBiasFront = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fDriveBiasFront')
local drivetrainLoss = 0.1 -- awd
if fDriveBiasFront >= 0.85 then
drivetrainLoss = 0.13 -- fwd
end
if fDriveBiasFront <= 0.15 then
drivetrainLoss = 0.2 -- rwd
end
local wheelPower = GetWheelPower(veh)
if IsVehicleFwd(veh) then
wheelPower = wheelPower * fDriveBiasFront
else
wheelPower = wheelPower * (1 - fDriveBiasFront)
end
local rpm = GetVehicleCurrentRpm(veh)
local maxRpm = ((math.min(161.0, fInitialDriveMaxFlatVel) / nInitialDriveGears / 35) * 8500) * 0.95
local realRpm = math.floor(rpm * maxRpm)
local torque = math.floor(((fInitialDriveForce * 0.4) * fMass) * (realRpm / maxRpm)
+ (math.min(2.0, wheelPower / 2))
* (math.min(4.0, math.max(0.7, (fMass / 1700) ^ 3)))
)
local hpMultiplier = 0.95
if fInitialDriveMaxFlatVel >= 150 then
hpMultiplier = 1.0
end
if fInitialDriveMaxFlatVel >= 159 then
hpMultiplier = 1.1
torque = math.floor(torque * 1.05)
end
if fInitialDriveMaxFlatVel >= 159.5 then
hpMultiplier = 1.15
torque = math.floor(torque * 1.15)
end
if fInitialDriveMaxFlatVel >= 164.0 then
hpMultiplier = 1.05
torque = math.floor(torque * 1.1)
end
local hp = math.abs(math.floor(DiminishingReturns(
((torque / 1.356) * (rpm * maxRpm) / 5252)
* fDriveInertia
* math.min(2.0, wheelPower / 2)
* (1 - drivetrainLoss)
* hpMultiplier
* (math.min(2.0, math.max(0.95, fMass / 1400)))
)))
local torqueInUnits = torque
if UsingFtLbs() then
torqueInUnits = torque / 1.356
end
return hp, torqueInUnits, realRpm, rpm
end, 50)
end
function GetWheelPower(veh)
local power = 0
for i = 0 , GetVehicleNumberOfWheels(veh) - 1 do
power = power + GetVehicleWheelPower(veh, i) * (GetVehicleWheelTireColliderSize(veh, i) * 2 + 0.15)
end
return power
end
function DiminishingReturns(x)
return x * (1 - (math.min(0.3, (x + 100) / 2400) - 0.05))
end
function UsingFtLbs()
return Config.torqueUnits == 'lb-ft' or Config.torqueUnits == 'lbs' or Config.torqueUnits == 'ft-lb'
end
function GetTorqueUnit()
if UsingFtLbs() then
return L('lb-ft')
end
return L('nm')
end
function IsPlayerUnreachable()
local playerPed = PlayerPedId()
return IsPedRagdoll(playerPed) or IsEntityDead(playerPed)
end
function KeybindTip(message)
SetTextComponentFormat("STRING")
AddTextComponentString(message)
EndTextCommandDisplayHelp(0, 0, 0, 200)
end
-- This function is responsible for all the tooltips displayed on top right of the screen, you could
-- replace it with a custom notification etc.
function Notify(message)
SetTextComponentFormat("STRING")
AddTextComponentString(message)
EndTextCommandDisplayHelp(0, 0, 0, -1)
end
RegisterNetEvent('kq_dyno:client:notify')
AddEventHandler('kq_dyno:client:notify', function(message)
Notify(message)
end)
function PlayAnim(dict, anim, flag, duration)
Citizen.CreateThread(function()
RequestAnimDict(dict)
local timeout = 0
while not HasAnimDictLoaded(dict) do
Citizen.Wait(50)
timeout = timeout + 1
if timeout > 100 then
return
end
end
TaskPlayAnim(PlayerPedId(), dict, anim, 1.5, 1.0, duration or -1, flag or 1, 0, false, false, false)
RemoveAnimDict(dict)
end)
end
function DrawDynoMarker(dyno)
DrawMarker(43, dyno.coords + vector3(0.0, 0.0, -1.0), 0.0, 0.0, 0.0, 0.0, 0.0, dyno.heading, 2.5, 1.0, 0.5, 40, 110, 250, 30, 0, 0, 0, 0)
end
function CanPerformDynoTests(dynoKey)
return UseCache('CanPerformDynoTests' .. dynoKey, function()
local dyno = Config.dynos[dynoKey]
return (not Config.jobWhitelist.enabled or (not PLAYER_JOB or not dyno.jobs or Contains(dyno.jobs, PLAYER_JOB)))
end, 5000)
end
-- Keybinds display
buttons = nil
keybinds = {}
function AddKeybindDisplay(key, label)
buttons = nil
table.insert(keybinds, {
key = '~' .. key .. '~',
label = label,
})
buttons = RequestScaleformMovie("INSTRUCTIONAL_BUTTONS")
while not HasScaleformMovieLoaded(buttons) do
Wait(0)
end
BeginScaleformMovieMethod(buttons, "CLEAR_ALL")
EndScaleformMovieMethod()
for k, keybind in pairs(keybinds) do
BeginScaleformMovieMethod(buttons, "SET_DATA_SLOT")
ScaleformMovieMethodAddParamInt(k - 1)
ScaleformMovieMethodAddParamPlayerNameString(keybind.key)
PushScaleformMovieMethodParameterString(keybind.label)
EndScaleformMovieMethod()
end
BeginScaleformMovieMethod(buttons, "DRAW_INSTRUCTIONAL_BUTTONS")
EndScaleformMovieMethod()
end
function ClearKeybinds()
buttons = nil
keybinds = {}
end
Citizen.CreateThread(function()
while true do
local sleep = 500
if buttons ~= nil then
sleep = 1
DrawScaleformMovieFullscreen(buttons, 255, 255, 255, 255, 0)
end
Citizen.Wait(sleep)
end
end)
@@ -0,0 +1,31 @@
if Config.esxSettings.enabled then
ESX = nil
if Config.esxSettings.useNewESXExport then
ESX = exports['es_extended']:getSharedObject()
else
Citizen.CreateThread(function()
while ESX == nil do
TriggerEvent('esx:getSharedObject', function(obj)
ESX = obj
end)
Citizen.Wait(0)
end
end)
end
Citizen.CreateThread(function()
while ESX == nil or ESX.GetPlayerData().job == nil do
Citizen.Wait(10)
end
ESX.PlayerData = ESX.GetPlayerData()
PLAYER_JOB = ESX.PlayerData.job.name
end)
RegisterNetEvent('esx:setJob')
AddEventHandler('esx:setJob', function(job)
ESX.PlayerData.job = job
PLAYER_JOB = job.name
end)
end
@@ -0,0 +1,18 @@
if Config.qbSettings.enabled then
QBCore = exports['qb-core']:GetCoreObject()
if QBCore.Functions.GetPlayerData() and QBCore.Functions.GetPlayerData().job then
PLAYER_JOB = QBCore.Functions.GetPlayerData().job.name
end
RegisterNetEvent('QBCore:Client:OnPlayerLoaded')
AddEventHandler('QBCore:Client:OnPlayerLoaded', function()
PLAYER_JOB = QBCore.Functions.GetPlayerData().job.name
end)
RegisterNetEvent('QBCore:Client:OnJobUpdate')
AddEventHandler('QBCore:Client:OnJobUpdate', function(JobInfo)
PLAYER_JOB = JobInfo.name
end)
end
@@ -0,0 +1,315 @@
Config = {}
Config.debug = false
--- If you're testing the script and editing the values DO NOT simply restart the script. As this script is using custom models (dynos)
--- it will crash if you just restart it. Instead use the `/kq_dyno_restart` command. It will safely restart the script without causing you to crash
--- SETTINGS FOR ESX
Config.esxSettings = {
enabled = false,
-- Whether or not to use the new ESX export method
useNewESXExport = true,
}
--- SETTINGS FOR QBCORE
Config.qbSettings = {
enabled = true,
}
--- BASIC
-- Torque units | 'nm' or 'lb-ft'
Config.torqueUnits = 'nm'
--- Horsepower and torque calculation formula
-- If you're not using vanilla or vanilla-like handling:
-- Try out different formulas and see what works best for your server.
-- 'vanilla' = Perfect setup for vanilla handling as well as handling files obeying the principles of vanilla GTA
-- 'highperformance1' = Good for servers using handling files which result in faster vehicles
-- 'highperformance2' = Good for servers using handling files which result in faster vehicles (extra)
-- 'highperformance3' = Good for servers using handling files which result in faster vehicles (extra)
---------------------------------------------
Config.dynoFormula = 'vanilla'
--- FRAMEWORK OPTIONS (MAKE SURE TO ENABLE YOUR FRAMEWORK IF USING ONE) <!>
Config.jobWhitelist = {
enabled = true,
-- To configure the jobs, set them on each dyno individually
}
-- Time it takes for the screens to turn off after a dyno run (in seconds)
Config.screenTimeout = 30
-- Whether to display the dyno sheet on the screen as UI
Config.displaySheetOnScreen = true
-- Determines the location of the dyno sheet
Config.screenSheetOffset = {
x = 0.84,
y = 0.833,
}
-- Dynos setup
-- coords = vector3 of the dyno location
-- heading = heading of the dyno
-- model = model defined in Config.dynoModels (By leaving this out, you will create a dyno without a model. Useful for MLOs with built-in dynos)
-- displays = table of displays
-- displayCoords = vector3 of the display location
-- displayTilt = angle of the display tilt,
-- displayHeading = heading of the display
-- displayType = display defined in Config.displayTypes
-- jobs = Table of jobs which are allowed to use the dyno (false or nil to allow everyone to use it)
Config.dynos = {
['bennys'] = {
coords = vector3(-214.28, -1318.14, 30.9),
heading = 180.0,
model = 'default_purple',
displays = {
{
displayCoords = vector3(-217.4, -1318.92, 32.55),
displayHeading = 90.0,
displayTilt = 3.0,
displayType = 'wall_tv_2',
},
{
displayCoords = vector3(-211.5, -1320.6, 30.89),
displayHeading = 250.0,
displayType = 'stand',
}
},
jobs = { 'mechanic' },
},
['lsc_harmony'] = {
coords = vector3(1182.66, 2636.5, 37.78),
heading = 0.0,
model = 'default_blue',
displays = {
{
displayCoords = vector3(1182.66, 2634.6, 39.3),
displayHeading = 180.0,
displayType = 'wall_tv',
},
},
jobs = { 'mechanic' },
},
['lsc_airport'] = {
coords = vector3(-1164.45, -2018.8, 13.18),
heading = 315.0,
model = 'default_red',
displays = {
{
displayCoords = vector3(-1164.3, -2014.53, 14.13),
displayHeading = 45.0,
displayType = 'wall_tv',
},
},
jobs = { 'mechanic' },
},
['import_export_garage'] = {
coords = vector3(980.2, -3002.11, -39.65),
heading = 90.0,
model = 'default_blue',
displays = {
{
displayCoords = vector3(978.5, -2999.35, -39.62),
displayHeading = 0.0,
displayType = 'stand',
},
},
jobs = { 'mechanic' },
},
--['no_model_liberty_walk_mlo'] = {
-- coords = vector3(1148.40, -792.69, 57.5),
-- heading = 90.0,
--
-- displays = {
-- {
-- displayCoords = vector3(1148.29, -795.0, 58.35),
-- displayHeading = 190.0,
-- displayType = 'monitor',
-- },
-- },
--
-- jobs = nil,
--},
}
-- This is just used to fill the default dynos with their rollers
Config.baseRollers = {
{
prop = 'kq_dyno_roller',
rotation = vector3(0.0, 90.0, 0.0),
offset = vector3(0.18, 0.6, -0.08),
direction = -1,
side = 1,
},
{
prop = 'kq_dyno_roller',
rotation = vector3(0.0, 90.0, 0.0),
offset = vector3(-0.18, 0.6, -0.08),
direction = -1,
side = 1,
},
{
prop = 'kq_dyno_roller',
rotation = vector3(0.0, 90.0, 0.0),
offset = vector3(0.18, -1.18, -0.08),
direction = -1,
side = 2,
},
{
prop = 'kq_dyno_roller',
rotation = vector3(0.0, 90.0, 0.0),
offset = vector3(-0.18, -1.18, -0.08),
direction = -1,
side = 2,
},
}
-- Dyno models
Config.dynoModels = {
['default_yellow'] = {
base = 'kq_dyno2_yellow',
textureVariation = 0,
heading = -90.0,
offset = vector3(0.0, 0.0, -0.04),
rollers = Config.baseRollers,
},
['default_red'] = {
base = 'kq_dyno2_red',
textureVariation = 0,
heading = -90.0,
offset = vector3(0.0, 0.0, -0.04),
rollers = Config.baseRollers,
},
['default_purple'] = {
base = 'kq_dyno2_purple',
textureVariation = 0,
heading = -90.0,
offset = vector3(0.0, 0.0, -0.04),
rollers = Config.baseRollers,
},
['default_green'] = {
base = 'kq_dyno2_green',
textureVariation = 0,
heading = -90.0,
offset = vector3(0.0, 0.0, -0.04),
rollers = Config.baseRollers,
},
['default_gray'] = {
base = 'kq_dyno2_gray',
textureVariation = 0,
heading = -90.0,
offset = vector3(0.0, 0.0, -0.04),
rollers = Config.baseRollers,
},
['default_blue'] = {
base = 'kq_dyno2_blue',
textureVariation = 0,
heading = -90.0,
offset = vector3(0.0, 0.0, -0.04),
rollers = Config.baseRollers,
},
['basic'] = {
base = 'kq_dyno',
textureVariation = 0,
heading = -90.0,
offset = vector3(0.0, 0.0, 0.0),
rollers = {
{
prop = 'kq_dyno_roller',
rotation = vector3(0.0, 90.0, 0.0),
offset = vector3(0.18, 0.9, -0.08),
direction = -1,
side = 1,
},
{
prop = 'kq_dyno_roller',
rotation = vector3(0.0, 90.0, 0.0),
offset = vector3(-0.18, 0.9, -0.08),
direction = -1,
side = 1,
},
{
prop = 'kq_dyno_roller',
rotation = vector3(0.0, 90.0, 0.0),
offset = vector3(0.18, -0.9, -0.08),
direction = -1,
side = 2,
},
{
prop = 'kq_dyno_roller',
rotation = vector3(0.0, 90.0, 0.0),
offset = vector3(-0.18, -0.9, -0.08),
direction = -1,
side = 2,
},
}
},
}
-- Display types
-- prop = prop of the display
-- offset = offset of the display (texture, not the prop)
-- heading = heading of the display (texture, not the prop)
-- size = size of the display
Config.displayTypes = {
['stand'] = {
prop = 'prop_cs_tv_stand',
offset = vector3(0.529, -0.08, 1.01),
heading = 180.0,
size = vector2(1.098, 0.54),
},
['monitor'] = {
prop = 'prop_tv_flat_03',
offset = vector3(0.35, -0.01, 0.025),
heading = 180.0,
size = vector2(0.7, 0.4),
},
['wall_tv'] = {
prop = 'prop_tv_flat_01',
offset = vector3(1.07, -0.06, -0.12),
heading = 180.0,
size = vector2(2.14, 1.2),
},
['wall_tv_2'] = {
prop = 'xm_prop_x17_tv_flat_01',
offset = vector3(0.798, -0.046, 0.152),
heading = 180.0,
size = vector2(1.5, 0.832),
},
}
-- https://docs.fivem.net/docs/game-references/controls/
-- Use the input index for the "input" value
Config.keybinds = {
start = {
label = 'E',
name = 'INPUT_PICKUP',
input = 38,
},
}
@@ -0,0 +1,22 @@
-- Discord webhook options
Config.webhook = {
enabled = false, -- Whether to send the dyno sheets to the discord webhook
-- To get the Discord webhook link, right click on a channel > Edit channel > Integrations > Webhooks > View webhooks > New webhook
url = 'YOUR_WEBHOOK_URL_HERE',
-- Here you can add webhooks for specific dynos. Based on the dyno key/index name (same as in config.lua)
dynoSpecific = {
['bennys'] = 'DYNO_SPECIFIC_WEBHOOK_URL_HERE', -- remove this line if you don't want to use a dyno specific webhooks
},
-- Replace this with the name of your server or a title you want on your dyno sheets
title = 'Red Valley - DynoTech',
-- Whether to include certain parts of the users info in the webhook messages
includeUserName = true,
includeSteamId = true,
color = 16723456,
}
@@ -0,0 +1,59 @@
fx_version 'cerulean'
games { 'gta5' }
lua54 'yes'
author 'KuzQuality | Kuzkay'
description 'Car dyno by KuzQuality'
version '1.4.0'
ui_page 'html/blank.html'
data_file 'DLC_ITYP_REQUEST' 'stream/kq_dyno_props.ytyp'
files {
'html/js/jquery.js',
'html/js/chart.js',
'html/js/chartjs-annotation.js',
'html/js/html2canvas.js',
'html/blank.html',
'html/index.html',
}
--
-- Server
--
server_scripts {
'config.lua',
'config_server.lua',
'locale.lua',
'server/server.lua',
'server/editable/editable.lua',
}
--
-- Client
--
client_scripts {
'config.lua',
'locale.lua',
'client/editable/api.lua',
'client/functions.lua',
'client/cache.lua',
'client/client.lua',
'client/spawning.lua',
'client/display.lua',
'client/editable/client.lua',
'client/editable/esx.lua',
'client/editable/qb.lua',
}
escrow_ignore {
'config.lua',
'config_server.lua',
'locale.lua',
'client/editable/*.lua',
'server/editable/*.lua',
}
dependency '/assetpacks'
@@ -0,0 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<body style="background-color: transparent;">
</body>
</html>
@@ -0,0 +1,228 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="js/jquery.js"></script>
<script src="js/chart.js"></script>
<script src="js/chartjs-annotation.js"></script>
<script src="js/html2canvas.js"></script>
</head>
<body>
<div class="chart">
<canvas id="chart"></canvas>
<span class="watermark">Dyno Technology by KuzQuality.com</span>
<span class="title" id="title"></span>
</div>
</body>
</html>
<script>
$(document).ready(() => {
let times = [];
let localeSet = false;
let title = '';
let peakHp = 'Peak HP';
let peakTorque = 'Peak Torque';
let torqueUnit = 'nm';
window.addEventListener('message', ({data}) => {
if (data.event === 'update') {
if (times.length > 600) {
times.shift();
}
times.push({
rpm: event.data.rpm,
hp: event.data.hp,
torque: event.data.torque,
});
setChart();
if (!localeSet) {
setLocale(data);
}
}
if (data.event === 'capture-image') {
captureImage(data.dynoKey);
}
});
function setLocale(data) {
localeSet = true;
title = data.title;
peakHp = data.peakHp;
peakTorque = data.peakTorque;
torqueUnit = data.torqueUnit;
$('#title').html(title);
}
function captureImage(dynoKey) {
$('.chart').addClass('screenshot');
$('.watermark').addClass('screenshot');
$('.title').addClass('screenshot');
html2canvas(document.querySelector(".chart")).then(canvas => {
const base64 = canvas.toDataURL();
fetch(`https://kq_dyno/SaveDynoImage`, {
method: 'POST',
body: JSON.stringify({img: base64, dynoKey}),
});
$('.chart').removeClass('screenshot');
$('.watermark').removeClass('screenshot');
$('.title').removeClass('screenshot');
});
}
function setChart() {
const chart = Chart.getChart("chart");
// If the chart exists, destroy it
if (chart) {
chart.destroy();
}
const xValues = times.map(({ rpm, hp, torque }) => rpm);
const hpValues = times.map(({ rpm, hp, torque }) => parseInt(hp));
const torqueValues = times.map(({ rpm, hp, torque }) => parseInt(torque));
const ctx = document.getElementById('chart').getContext('2d');
Chart.defaults.font.size = 33;
new Chart(ctx, {
type: 'line',
data: {
labels: xValues,
datasets: [
{
data: hpValues,
label: 'Horsepower',
borderColor: 'red',
fill: false
},
{
data: torqueValues,
label: 'Torque',
borderColor: 'blue',
fill: false
},
],
},
options: {
animation: false,
elements: {
point: {
radius: 1
},
},
scales: {
y: {
display: true,
min: 0,
max: Math.ceil(Math.max(Math.max(...torqueValues), Math.max(...hpValues)) * 1.1),
gridLines: {
color: 'rgba(255,255,255,0.3)'
},
},
x: {
type: 'category',
title: 'RPM',
display: true,
clip: true,
ticks: {
stepSize: 500, // Set the step size to 500
}
}
},
plugins: {
legend: {
display: false,
},
annotation: {
annotations: [
{
type: 'line',
mode: 'horizontal',
scaleID: 'y',
value: Math.max(...hpValues), // Set the value to where you want the line
borderColor: 'red', // Color of the line
borderWidth: 2, // Width of the line
borderDash: [20],
label: {
display: true,
content: peakHp + ' ' + Math.max(...hpValues), // Set the label content
enabled: true, // Show the label
position: '45%', // Position of the label ('start', 'center', or 'end')
}
},
{
type: 'line',
mode: 'horizontal',
scaleID: 'y',
value: Math.max(...torqueValues), // Set the value to where you want the line
borderColor: 'blue', // Color of the line
borderWidth: 2, // Width of the line
borderDash: [20],
label: {
display: true,
content: peakTorque + ' ' + Math.max(...torqueValues) + ' ' + torqueUnit, // Set the label content
enabled: true, // Show the label
position: '15%', // Position of the label ('start', 'center', or 'end')
}
}
]
}
}
},
});
}
});
</script>
<style>
html {
background-color: rgb(240, 240, 240);
}
.chart {
padding: 12px;
}
.chart.screenshot {
padding-top: 600px;
height: 1280px;
width: 1280px;
}
.watermark.screenshot {
bottom: -550px !important;
}
.watermark {
color: rgba(0, 0, 0, 0.2);
font-size: 22px;
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
font-family: Tahoma, sans-serif;
margin: 0;
}
.title {
color: rgba(148, 19, 19, 1);
font-size: 30px;
position: absolute;
bottom: 45px;
left: 50%;
transform: translateX(-50%);
font-family: Tahoma, sans-serif;
margin: 0;
}
.title.screenshot {
bottom: 130px !important;
font-size: 64px !important;
}
</style>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,22 @@
Locale = {
['lb-ft'] = 'lb-ft',
['nm'] = 'nm',
['Press ~{INPUT}~ to start the dyno test'] = 'Apasa ~{INPUT}~ pentru a incepe testul dyno',
['~r~This vehicle is FWD biased. Turn it around'] = '~r~Acest vehicul este cu tractiune fata. Intoarce-l',
['~r~This vehicle is RWD biased. Turn it around'] = '~r~Acest vehicul este cu tractiune spate. Intoarce-l',
['~r~The vehicle isn\'t aligned correctly'] = '~r~Vehiculul nu este aliniat corect',
['~r~You are not allowed to perform dyno tests'] = '~r~Nu ai permisiunea sa efectuezi teste dyno',
['Peak HP'] = 'HP Maxim',
['Peak torque'] = 'Cuplu Maxim',
-- Webhooks
['Vehicle'] = 'Vehicul',
['License plate'] = 'Numar de inmatriculare',
['Date'] = 'Data',
['User'] = 'Utilizator',
['Dyno performance report'] = 'Raport de performanta dyno',
-- Display
['KuzQuality - DynoTech'] = 'Red Valley - DynoTech',
}
@@ -0,0 +1,31 @@
function GetDiscordDescription(model, license, player)
local description = '- **' .. L('Vehicle') .. '**: ' .. model ..
'\n- **' .. L('License plate') .. '**: ' .. license ..
'\n- **' .. L('Date') .. '**: '.. os.date("%A, %m %B %Y - %H:%M")
if Config.webhook.includeUserName then
description = description .. '\n\n'.. L('User') .. ': ' .. GetPlayerName(player)
end
if Config.webhook.includeSteamId then
if not Config.webhook.includeUserName then
description = description .. '\n'
end
description = description .. '\n*' .. GetIdentifier(player) .. '*'
end
return description
end
function GetIdentifier(player)
for k, v in ipairs(GetPlayerIdentifiers(player)) do
if string.match(v, 'license:') then
return v:gsub('license:', '')
end
end
return ''
end
RegisterCommand('kq_dyno_restart', function(source)
TriggerClientEvent('kq_dyno:client:prepareRestart', -1, source)
end, true)