434 lines
		
	
	
		
			No EOL
		
	
	
		
			15 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			434 lines
		
	
	
		
			No EOL
		
	
	
		
			15 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
--[[
 | 
						|
---------------------------------------------------
 | 
						|
LUXART VEHICLE CONTROL V3 (FOR FIVEM)
 | 
						|
---------------------------------------------------
 | 
						|
Coded by Lt.Caine
 | 
						|
ELS Clicks by Faction
 | 
						|
Additional Modification by TrevorBarns
 | 
						|
---------------------------------------------------
 | 
						|
FILE: cl_utils.lua
 | 
						|
PURPOSE: Utilities for siren assignments and tables
 | 
						|
		 and other common functions.
 | 
						|
---------------------------------------------------
 | 
						|
This program is free software: you can redistribute it and/or modify
 | 
						|
it under the terms of the GNU General Public License as published by
 | 
						|
the Free Software Foundation, either version 3 of the License, or
 | 
						|
(at your option) any later version.
 | 
						|
 | 
						|
This program is distributed in the hope that it will be useful,
 | 
						|
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
GNU General Public License for more details.
 | 
						|
 | 
						|
You should have received a copy of the GNU General Public License
 | 
						|
along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
						|
---------------------------------------------------
 | 
						|
]]
 | 
						|
UTIL = { }
 | 
						|
 | 
						|
local approved_tones = nil
 | 
						|
local tone_options = { }
 | 
						|
local tone_table_names_ids = { }
 | 
						|
local profile = nil
 | 
						|
local tone_main_mem_id = nil
 | 
						|
local tone_PMANU_id = nil
 | 
						|
local tone_SMANU_id = nil
 | 
						|
local tone_AUX_id = nil
 | 
						|
local tone_ARHRN_id = nil
 | 
						|
 | 
						|
---------------------------------------------------------------------
 | 
						|
--[[Return sub-table for sirens or plugin settings tables, given veh, and name of whatever setting.]]
 | 
						|
function UTIL:GetProfileFromTable(print_name, tbl, veh, ignore_missing_default)
 | 
						|
	local ignore_missing_default = ignore_missing_default or false
 | 
						|
	local veh_name = GetDisplayNameFromVehicleModel(GetEntityModel(veh))
 | 
						|
	local lead_and_trail_wildcard = veh_name:gsub('%d+', '#')
 | 
						|
	local lead = veh_name:match('%d*%a+')
 | 
						|
	local trail = veh_name:gsub(lead, ''):gsub('%d+', '#')
 | 
						|
	local trail_only_wildcard = string.format('%s%s', lead, trail)
 | 
						|
 | 
						|
	local profile_table, profile
 | 
						|
	if tbl ~= nil then
 | 
						|
		if tbl[veh_name] ~= nil then							--Does profile exist as outlined in vehicle.meta
 | 
						|
			profile_table = tbl[veh_name]
 | 
						|
			profile = veh_name
 | 
						|
			UTIL:Print(Lang:t('info.profile_found', {ver = STORAGE:GetCurrentVersion(), tbl = print_name, profile = profile, model = veh_name}))
 | 
						|
		elseif tbl[trail_only_wildcard] ~= nil then				--Does profile exist using # as wildcard for any trailing digits.
 | 
						|
			profile_table = tbl[trail_only_wildcard]
 | 
						|
			profile = trail_only_wildcard
 | 
						|
			UTIL:Print(Lang:t('info.profile_found', {ver = STORAGE:GetCurrentVersion(), tbl = print_name, profile = profile, model = veh_name}))
 | 
						|
		elseif tbl[lead_and_trail_wildcard] ~= nil then			--Does profile exist using # as wildcard for any digits.
 | 
						|
			profile_table = tbl[lead_and_trail_wildcard]
 | 
						|
			profile = lead_and_trail_wildcard
 | 
						|
			UTIL:Print(Lang:t('info.profile_found', {ver = STORAGE:GetCurrentVersion(), tbl = print_name, profile = profile, model = veh_name}))
 | 
						|
		else
 | 
						|
			if tbl['DEFAULT'] ~= nil then
 | 
						|
				profile_table = tbl['DEFAULT']
 | 
						|
				profile = 'DEFAULT'
 | 
						|
				UTIL:Print(Lang:t('info.profile_default_console', {ver = STORAGE:GetCurrentVersion(), tbl = print_name, model = veh_name}))
 | 
						|
				if print_name == 'SIRENS' then
 | 
						|
					HUD:ShowNotification(Lang:t('info.profile_default_frontend', {model = veh_name}))
 | 
						|
				end
 | 
						|
			else
 | 
						|
				profile_table = { }
 | 
						|
				profile = false
 | 
						|
				if not ignore_missing_default then
 | 
						|
					UTIL:Print(Lang:t('warning.profile_missing', {ver = STORAGE:GetCurrentVersion(), tbl = print_name, model = veh_name}), true)
 | 
						|
				end
 | 
						|
			end
 | 
						|
		end
 | 
						|
	else
 | 
						|
		profile_table = { }
 | 
						|
		profile = false
 | 
						|
		HUD:ShowNotification(Lang:t('error.profile_nil_table', {tbl = print_name}), true)
 | 
						|
		UTIL:Print(Lang:t('error.profile_nil_table_console', {ver = STORAGE:GetCurrentVersion(), tbl = print_name}), true)
 | 
						|
	end
 | 
						|
	
 | 
						|
	return profile_table, profile
 | 
						|
end
 | 
						|
 | 
						|
---------------------------------------------------------------------
 | 
						|
--[[Shorten oversized <gameName> strings in SIREN_ASSIGNMENTS (SIRENS.LUA).
 | 
						|
    GTA only allows 11 characters. So to reduce confusion we'll shorten it if the user does not.]]
 | 
						|
function UTIL:FixOversizeKeys(TABLE)
 | 
						|
	for i, tbl in pairs(TABLE) do
 | 
						|
		if string.len(i) > 11 then
 | 
						|
			local shortened_gameName = string.sub(i,1,11)
 | 
						|
			TABLE[shortened_gameName] = TABLE[i]
 | 
						|
			TABLE[i] = nil
 | 
						|
		end
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
---------------------------------------------------------------------
 | 
						|
--[[Sets profile name and approved_tones table a copy of SIREN_ASSIGNMENTS for this vehicle]]
 | 
						|
function UTIL:UpdateApprovedTones(veh)
 | 
						|
	approved_tones, profile = UTIL:GetProfileFromTable('SIRENS', SIREN_ASSIGNMENTS, veh)
 | 
						|
	
 | 
						|
	if profile == false then
 | 
						|
		UTIL:Print(Lang:t('error.profile_none_found_console', {game_name = GetDisplayNameFromVehicleModel(GetEntityModel(veh))}), true)
 | 
						|
		HUD:ShowNotification(Lang:t('error.profile_none_found_frontend'), true)
 | 
						|
	end
 | 
						|
 | 
						|
	if profile then
 | 
						|
		if not UTIL:IsApprovedTone('MAIN_MEM') then
 | 
						|
			UTIL:SetToneByPos('MAIN_MEM', 2)
 | 
						|
		end
 | 
						|
		if not UTIL:IsApprovedTone('PMANU') then
 | 
						|
			UTIL:SetToneByPos('PMANU', 2)
 | 
						|
		end
 | 
						|
		if not UTIL:IsApprovedTone('SMANU') then
 | 
						|
			UTIL:SetToneByPos('SMANU', 3)
 | 
						|
		end
 | 
						|
		if not UTIL:IsApprovedTone('AUX') then
 | 
						|
			UTIL:SetToneByPos('AUX', 2)
 | 
						|
		end
 | 
						|
		if not UTIL:IsApprovedTone('ARHRN') then
 | 
						|
			UTIL:SetToneByPos('ARHRN', 1)
 | 
						|
		end
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
--[[Getter for approved_tones table, used in RageUI]]
 | 
						|
function UTIL:GetApprovedTonesTable()
 | 
						|
	if approved_tones == nil then
 | 
						|
		if veh ~= nil then
 | 
						|
			UpdateApprovedTones(veh)
 | 
						|
		else
 | 
						|
			UpdateApprovedTones('DEFAULT')
 | 
						|
		end
 | 
						|
	end
 | 
						|
	return approved_tones
 | 
						|
end
 | 
						|
---------------------------------------------------------------------
 | 
						|
--[[Builds a table that we store tone_options in (disabled, button & cycle, cycle only, button only).
 | 
						|
    Users can set default option of siren by using optional index .Option in SIREN_ASSIGNMENTS table in SIRENS.LUA]]
 | 
						|
function UTIL:BuildToneOptions()
 | 
						|
	local temp_array = { }
 | 
						|
	local option
 | 
						|
	for i, id in pairs(approved_tones) do
 | 
						|
		if SIRENS[id] ~= nil then
 | 
						|
			option = SIRENS[id].Option or 1
 | 
						|
			temp_array[id] = option
 | 
						|
		end
 | 
						|
	end
 | 
						|
	tone_options = temp_array
 | 
						|
end
 | 
						|
 | 
						|
--Setter for single tone_option
 | 
						|
function UTIL:SetToneOption(tone_id, option)
 | 
						|
	tone_options[tone_id] = option
 | 
						|
end
 | 
						|
 | 
						|
--Getter for single tone_option
 | 
						|
function UTIL:GetToneOption(tone_id)
 | 
						|
	return tone_options[tone_id]
 | 
						|
end
 | 
						|
 | 
						|
--Getter for tone_options table (used for saving)
 | 
						|
function UTIL:GetToneOptionsTable()
 | 
						|
	return tone_options
 | 
						|
end
 | 
						|
---------------------------------------------------------------------
 | 
						|
--[[RageUI requires a specific table layout, this builds it according to SIREN_ASSIGNMENTS > approved_tones.]]
 | 
						|
function UTIL:GetApprovedTonesTableNameAndID()
 | 
						|
	local temp_array = { }
 | 
						|
	for i, tone_id in pairs(approved_tones) do
 | 
						|
		if i ~= 1 then
 | 
						|
			table.insert(temp_array, { Name = SIRENS[tone_id].Name, Value = tone_id } )
 | 
						|
		end
 | 
						|
	end
 | 
						|
	return temp_array
 | 
						|
end
 | 
						|
 | 
						|
---------------------------------------------------------------------
 | 
						|
--[[Getter for tone id by passing string abbreviation (MAIN_MEM, PMANU, etc.)]]
 | 
						|
function UTIL:GetToneID(tone_string)
 | 
						|
	if tone_string == 'MAIN_MEM' then
 | 
						|
		return tone_main_mem_id
 | 
						|
	elseif tone_string == 'PMANU' then
 | 
						|
		return tone_PMANU_id
 | 
						|
	elseif tone_string == 'SMANU' then
 | 
						|
		return tone_SMANU_id
 | 
						|
	elseif tone_string == 'AUX' then
 | 
						|
		return tone_AUX_id
 | 
						|
	elseif tone_string == 'ARHRN' then
 | 
						|
		return tone_ARHRN_id
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
--[[Setter for ToneID by passing string abbreviation of tone (MAIN_MEM, PMANU, etc.) and position of desired tone in approved_tones.]]
 | 
						|
function UTIL:SetToneByPos(tone_string, pos)
 | 
						|
	if profile then
 | 
						|
		if approved_tones[pos] ~= nil then
 | 
						|
			if tone_string == 'MAIN_MEM' then
 | 
						|
				tone_main_mem_id = approved_tones[pos]
 | 
						|
			elseif tone_string == 'PMANU' then
 | 
						|
				tone_PMANU_id = approved_tones[pos]
 | 
						|
			elseif tone_string == 'SMANU' then
 | 
						|
				tone_SMANU_id = approved_tones[pos]
 | 
						|
			elseif tone_string == 'AUX' then
 | 
						|
				tone_AUX_id = approved_tones[pos]
 | 
						|
			elseif tone_string == 'ARHRN' then
 | 
						|
				tone_ARHRN_id = approved_tones[pos]
 | 
						|
			end
 | 
						|
		else
 | 
						|
			HUD:ShowNotification(Lang:t('warning.too_few_tone_frontend', {code = 403}), false)
 | 
						|
			UTIL:Print(Lang:t('warning.too_few_tone_console', {ver = STORAGE:GetCurrentVersion(), code = 403, tone_string = tone_string, pos = pos}), true)
 | 
						|
		end
 | 
						|
	else
 | 
						|
		HUD:ShowNotification(Lang:t('warning.tone_position_nil_frontend', {code = 404}), false)
 | 
						|
		UTIL:Print(Lang:t('warning.tone_position_nil_console', {ver = STORAGE:GetCurrentVersion(), code = 404, tone_string = tone_string, pos = pos}), true)
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
--[[Getter for position of passed tone string. Used in RageUI for P/S MANU and AUX Siren.]]
 | 
						|
function UTIL:GetTonePos(tone_string)
 | 
						|
	local current_id = UTIL:GetToneID(tone_string)
 | 
						|
	for i, tone_id in pairs(approved_tones) do
 | 
						|
		if tone_id == current_id then
 | 
						|
			return i
 | 
						|
		end
 | 
						|
	end
 | 
						|
	return -1
 | 
						|
end
 | 
						|
 | 
						|
--[[Getter for Tone ID at index/pos in approved_tones]]
 | 
						|
function UTIL:GetToneAtPos(pos)
 | 
						|
	if approved_tones[pos] ~= nil then
 | 
						|
		return approved_tones[pos]
 | 
						|
	end
 | 
						|
	return nil
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
--[[Setter for ToneID by passing string abbreviation of tone (MAIN_MEM, PMANU, etc.) and specific ID.]]
 | 
						|
function UTIL:SetToneByID(tone_string, tone_id)
 | 
						|
	if UTIL:IsApprovedTone(tone_id) then
 | 
						|
		if tone_string == 'MAIN_MEM' then
 | 
						|
			tone_main_mem_id = tone_id
 | 
						|
		elseif tone_string == 'PMANU' then
 | 
						|
			tone_PMANU_id = tone_id
 | 
						|
		elseif tone_string == 'SMANU' then
 | 
						|
			tone_SMANU_id = tone_id
 | 
						|
		elseif tone_string == 'AUX' then
 | 
						|
			tone_AUX_id = tone_id
 | 
						|
		elseif tone_string == 'ARHRN' then
 | 
						|
			tone_ARHRN_id = tone_id
 | 
						|
		end
 | 
						|
	else
 | 
						|
		HUD:ShowNotification(Lang:t('warning.tone_id_nil_frontend', {ver = STORAGE:GetCurrentVersion()}), false)
 | 
						|
		UTIL:Print(Lang:t('warning.tone_id_nil_console', {ver = STORAGE:GetCurrentVersion(), tone_string = tone_string, tone_id = tone_id}), true)
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
---------------------------------------------------------------------
 | 
						|
--[[Gets next tone based off vehicle profile and current tone.]]
 | 
						|
function UTIL:GetNextSirenTone(current_tone, veh, main_tone, last_pos)
 | 
						|
	local main_tone = main_tone or false
 | 
						|
	local last_pos = last_pos or nil
 | 
						|
	local result
 | 
						|
 | 
						|
	if last_pos == nil then
 | 
						|
		for i, tone_id in pairs(approved_tones) do
 | 
						|
			if tone_id == current_tone then
 | 
						|
				temp_pos = i
 | 
						|
				break
 | 
						|
			end
 | 
						|
		end
 | 
						|
	else
 | 
						|
		temp_pos = last_pos
 | 
						|
	end
 | 
						|
 | 
						|
	if temp_pos < #approved_tones then
 | 
						|
		temp_pos = temp_pos+1
 | 
						|
		result = approved_tones[temp_pos]
 | 
						|
	else
 | 
						|
		temp_pos = 2
 | 
						|
		result = approved_tones[2]
 | 
						|
	end
 | 
						|
 | 
						|
	if main_tone then
 | 
						|
		--Check if the tone is set to 'disable' or 'button-only' if so, find next tone
 | 
						|
		if tone_options[result] > 2 then
 | 
						|
			result = UTIL:GetNextSirenTone(result, veh, main_tone, temp_pos)
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	return result
 | 
						|
end
 | 
						|
 | 
						|
---------------------------------------------------------------------
 | 
						|
--[[Get count of approved tones used when mapping RegisteredKeys]]
 | 
						|
function UTIL:GetToneCount()
 | 
						|
	return #approved_tones
 | 
						|
end
 | 
						|
 | 
						|
---------------------------------------------------------------------
 | 
						|
--[[Ensure not all sirens are disabled / button only]]
 | 
						|
function UTIL:IsOkayToDisable()
 | 
						|
	local count = 0
 | 
						|
	for i, option in pairs(tone_options) do
 | 
						|
		if i ~= 1 then
 | 
						|
			if option < 3 then
 | 
						|
				count = count + 1
 | 
						|
			end
 | 
						|
		end
 | 
						|
	end
 | 
						|
	if count > 1 then
 | 
						|
		return true
 | 
						|
	end
 | 
						|
	return false
 | 
						|
end
 | 
						|
 | 
						|
------------------------------------------------
 | 
						|
--[[Handle changing of tone_table custom names]]
 | 
						|
function UTIL:ChangeToneString(tone_id, new_name)
 | 
						|
	STORAGE:SetCustomToneStrings(true)
 | 
						|
	SIRENS[tone_id].Name = new_name
 | 
						|
end
 | 
						|
 | 
						|
------------------------------------------------
 | 
						|
--[[Used to verify tone is allowed before playing.]]
 | 
						|
function UTIL:IsApprovedTone(tone)
 | 
						|
	for i, approved_tone in ipairs(approved_tones) do
 | 
						|
		if approved_tone == tone then
 | 
						|
			return true
 | 
						|
		end
 | 
						|
	end
 | 
						|
	return false
 | 
						|
end
 | 
						|
 | 
						|
---------------------------------------------------------------------
 | 
						|
--[[Returns String <gameName> used for saving, loading, and debugging]]
 | 
						|
function UTIL:GetVehicleProfileName()
 | 
						|
	return profile
 | 
						|
end
 | 
						|
 | 
						|
---------------------------------------------------------------------
 | 
						|
--[[Prints to FiveM console, prints more when debug flag is enabled or overridden for important information]]
 | 
						|
function UTIL:Print(string, override)
 | 
						|
	override = override or false
 | 
						|
	if debug_mode or override then
 | 
						|
		print(string)
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
---------------------------------------------------------------------
 | 
						|
--[[Finds index of element in table given table and element.]]
 | 
						|
function UTIL:IndexOf(tbl, tgt)
 | 
						|
	for i, v in pairs(tbl) do
 | 
						|
		if v == tgt then
 | 
						|
			return i
 | 
						|
		end
 | 
						|
	end
 | 
						|
	return nil
 | 
						|
end
 | 
						|
 | 
						|
---------------------------------------------------------------------
 | 
						|
--[[This function looks like #!*& for user convenience (and my lack of skill or abundance of laziness),
 | 
						|
	it is called when needing to change an extra, it allows users to do things like ['<model>'] = { Brake = 1 } while
 | 
						|
	also allowing advanced users to write configs like this ['<model>'] = { Brake = { add = { 3, 4 }, remove = { 5, 6 }, repair = true } }
 | 
						|
	which can add and remove multiple different extras at once and adds flag to repair the vehicle
 | 
						|
	for extras that are too large and require the vehicle to be reloaded. Once it figures out the
 | 
						|
	users config layout it calls itself again (recursive) with the id we actually need toggled right now.]]
 | 
						|
function UTIL:TogVehicleExtras(veh, extra_id, state, repair)
 | 
						|
	local repair = repair or false
 | 
						|
	if type(extra_id) == 'table' then
 | 
						|
		-- Toggle Same Extras Mode
 | 
						|
		if extra_id.toggle ~= nil then
 | 
						|
			-- Toggle Multiple Extras
 | 
						|
			if type(extra_id.toggle) == 'table' then
 | 
						|
				for i, singe_extra_id in ipairs(extra_id.toggle) do
 | 
						|
					UTIL:TogVehicleExtras(veh, singe_extra_id, state, extra_id.repair)
 | 
						|
				end
 | 
						|
			-- Toggle a Single Extra (no table)
 | 
						|
			else
 | 
						|
				UTIL:TogVehicleExtras(veh, extra_id.toggle, state, extra_id.repair)
 | 
						|
			end
 | 
						|
		-- Toggle Different Extras Mode
 | 
						|
		elseif extra_id.add ~= nil and extra_id.remove ~= nil then
 | 
						|
			if type(extra_id.add) == 'table' then
 | 
						|
				for i, singe_extra_id in ipairs(extra_id.add) do
 | 
						|
					UTIL:TogVehicleExtras(veh, singe_extra_id, state, extra_id.repair)
 | 
						|
				end
 | 
						|
			else
 | 
						|
				UTIL:TogVehicleExtras(veh, extra_id.add, state, extra_id.repair)
 | 
						|
			end
 | 
						|
			if type(extra_id.remove) == 'table' then
 | 
						|
				for i, singe_extra_id in ipairs(extra_id.remove) do
 | 
						|
					UTIL:TogVehicleExtras(veh, singe_extra_id, not state, extra_id.repair)
 | 
						|
				end
 | 
						|
			else
 | 
						|
				UTIL:TogVehicleExtras(veh, extra_id.remove, not state, extra_id.repair)
 | 
						|
			end
 | 
						|
		end
 | 
						|
	else
 | 
						|
		if state then
 | 
						|
			if not IsVehicleExtraTurnedOn(veh, extra_id) then
 | 
						|
				local doors =  { }
 | 
						|
				if repair then
 | 
						|
					for i = 0,6 do
 | 
						|
						doors[i] = GetVehicleDoorAngleRatio(veh, i)
 | 
						|
					end
 | 
						|
				end
 | 
						|
				SetVehicleAutoRepairDisabled(veh, not repair)
 | 
						|
				SetVehicleExtra(veh, extra_id, false)
 | 
						|
				UTIL:Print(Lang:t('info.extra_on', {extra = extra_id}), false)
 | 
						|
				SetVehicleAutoRepairDisabled(veh, false)
 | 
						|
				if repair then
 | 
						|
					for i = 0,6 do
 | 
						|
						if doors[i] > 0.0 then
 | 
						|
							SetVehicleDoorOpen(veh, i, true, false)
 | 
						|
						end
 | 
						|
					end
 | 
						|
				end
 | 
						|
			end
 | 
						|
		else
 | 
						|
			if IsVehicleExtraTurnedOn(veh, extra_id) then
 | 
						|
				SetVehicleExtra(veh, extra_id, true)
 | 
						|
				UTIL:Print(Lang:t('info.extra_off', {extra = extra_id}), false)
 | 
						|
			end
 | 
						|
		end
 | 
						|
	end
 | 
						|
	SetVehicleAutoRepairDisabled(veh, false)
 | 
						|
end |