263 lines
		
	
	
	
		
			5.8 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			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)
 | 
