Main/resources/[tools]/vehicleDebug/cl_debugger.lua
2025-06-07 08:51:21 +02:00

263 lines
5.8 KiB
Lua

Debugger = {
speedBuffer = {},
speed = 0.0,
accel = 0.0,
decel = 0.0,
toggle = false
}
--[[ Functions ]]--
function TruncateNumber(value)
value = value * Config.Precision
return (value % 1.0 > 0.5 and math.ceil(value) or math.floor(value)) / Config.Precision
end
function Debugger:Set(vehicle)
self.vehicle = vehicle
self:ResetStats()
local handlingText = ""
-- Loop fields.
for key, field in pairs(Config.Fields) do
-- Get field type.
local fieldType = Config.Types[field.type]
if fieldType == nil then error("no field type") end
-- Get value.
local value = fieldType.getter(vehicle, "CHandlingData", field.name)
if type(value) == "vector3" then
value = ("%s,%s,%s"):format(value.x, value.y, value.z)
elseif field.type == "float" then
value = TruncateNumber(value)
end
-- Get input.
local input = ([[
<input
oninput='updateHandling(this.id, this.value)'
id='%s'
value=%s
>
</input>
]]):format(key, value)
-- Append text.
handlingText = handlingText..([[
<div class='tooltip'><span class='tooltip-text'>%s</span><span>%s</span>%s</div>
]]):format(field.description or "Unspecified.", field.name, input)
end
-- Update text.
self:Invoke("updateText", {
["handling-fields"] = handlingText,
})
end
function Debugger:UpdateVehicle()
local ped = PlayerPedId()
local isInVehicle = IsPedInAnyVehicle(ped, false)
local vehicle = isInVehicle and GetVehiclePedIsIn(ped, false)
if self.isInVehicle ~= isInVehicle or self.vehicle ~= vehicle then
self.vehicle = vehicle
self.isInVehicle = isInVehicle
if isInVehicle and DoesEntityExist(vehicle) then
self:Set(vehicle)
end
end
end
function Debugger:UpdateInput()
if self.hasFocus then
DisableControlAction(0, 1)
DisableControlAction(0, 2)
end
end
function Debugger:UpdateAverages()
if not DoesEntityExist(self.vehicle or 0) then return end
-- Get the speed.
local speed = GetEntitySpeed(self.vehicle)
-- Speed buffer.
table.insert(self.speedBuffer, speed)
if #self.speedBuffer > 100 then
table.remove(self.speedBuffer, 1)
end
-- Calculate averages.
local accel = 0.0
local decel = 0.0
local accelCount = 0
local decelCount = 0
for k, v in ipairs(self.speedBuffer) do
if k > 1 then
local change = (v - self.speedBuffer[k - 1])
if change > 0.0 then
accel = accel + change
accelCount = accelCount + 1
else
decel = accel + change
decelCount = decelCount + 1
end
end
end
accel = accel / accelCount
decel = decel / decelCount
-- Set tops.
self.speed = math.max(self.speed, speed)
self.accel = math.max(self.accel, accel)
self.decel = math.min(self.decel, decel)
-- Update text.
self:Invoke("updateText", {
["top-speed"] = self.speed * 2.236936,
["top-accel"] = self.accel * 60.0 * 2.236936,
["top-decel"] = math.abs(self.decel) * 60.0 * 2.236936,
})
end
function Debugger:ResetStats()
self.speed = 0.0
self.accel = 0.0
self.decel = 0.0
self.speedBuffer = {}
end
function Debugger:SetHandling(key, value)
if not DoesEntityExist(self.vehicle or 0) then return end
-- Get field.
local field = Config.Fields[key]
if field == nil then error("no field") end
-- Get field type.
local fieldType = Config.Types[field.type]
if fieldType == nil then error("no field type") end
-- Set field.
fieldType.setter(self.vehicle, "CHandlingData", field.name, value)
-- Needed for some values to work.
ModifyVehicleTopSpeed(self.vehicle, 1.0)
end
function Debugger:CopyHandling()
local text = ""
-- Line writer.
local function writeLine(append)
if text ~= "" then
text = text.."\n\t\t\t"
end
text = text..append
end
-- Get vehicle.
local vehicle = self.vehicle
if not DoesEntityExist(vehicle) then return end
-- Loop fields.
for key, field in pairs(Config.Fields) do
-- Get field type.
local fieldType = Config.Types[field.type]
if fieldType == nil then error("no field type") end
-- Get value.
local value = fieldType.getter(vehicle, "CHandlingData", field.name, true)
local nValue = tonumber(value)
-- Append text.
if nValue ~= nil then
writeLine(("<%s value=\"%s\" />"):format(field.name, field.type == "float" and TruncateNumber(nValue) or nValue))
elseif field.type == "vector" then
writeLine(("<%s x=\"%s\" y=\"%s\" z=\"%s\" />"):format(field.name, value.x, value.y, value.z))
end
end
-- Copy text.
self:Invoke("copyText", text)
end
function Debugger:Focus(toggle)
if toggle and not DoesEntityExist(self.vehicle or 0) then return end
SetNuiFocus(toggle, toggle)
SetNuiFocusKeepInput(toggle)
self.hasFocus = toggle
self:Invoke("setFocus", toggle)
end
function Debugger:ToggleOn(toggleData)
-- if toggle and not DoesEntityExist(self.vehicle or 0) then return end
self.toggle = toggleData
self:Invoke("toggle", toggleData)
end
function Debugger:Invoke(_type, data)
SendNUIMessage({
callback = {
type = _type,
data = data,
},
})
end
--[[ Threads ]]--
Citizen.CreateThread(function()
while true do
Citizen.Wait(1000)
Debugger:UpdateVehicle()
end
end)
Citizen.CreateThread(function()
while true do
if Debugger.isInVehicle then
Citizen.Wait(0)
Debugger:UpdateInput()
Debugger:UpdateAverages()
else
Citizen.Wait(500)
end
end
end)
--[[ NUI Events ]]--
RegisterNUICallback("updateHandling", function(data, cb)
cb(true)
Debugger:SetHandling(tonumber(data.key), data.value)
end)
RegisterNUICallback("copyHandling", function(data, cb)
cb(true)
Debugger:CopyHandling()
end)
RegisterNUICallback("resetStats", function(data, cb)
cb(true)
Debugger:ResetStats()
end)
--[[ Commands ]]--
RegisterCommand("+vehicleDebug", function()
if Debugger.toggleOn == false then return end
Debugger:Focus(not Debugger.hasFocus)
end, true)
RegisterKeyMapping("+vehicleDebug", "Vehicle Debugger", "keyboard", "lmenu")
RegisterCommand("vehdebug", function()
Debugger:ToggleOn(not Debugger.toggleOn)
Debugger.toggleOn = not Debugger.toggleOn
end, true)