diff --git a/SSV2/includes/backend.lua b/SSV2/includes/backend.lua index a5bbc18..bf16ce3 100644 --- a/SSV2/includes/backend.lua +++ b/SSV2/includes/backend.lua @@ -500,7 +500,11 @@ function Backend:RegisterHandlers() Translator:OnTick() yield() - end) + end, { + exception_handler = function() + Backend:Cleanup() + end + }) ThreadManager:RegisterLooped("SS_POOLMGR", function() self:PoolMgr() diff --git a/SSV2/includes/classes/Set.lua b/SSV2/includes/classes/Set.lua index 6344361..47af147 100644 --- a/SSV2/includes/classes/Set.lua +++ b/SSV2/includes/classes/Set.lua @@ -10,14 +10,14 @@ -------------------------------------- -- Class: Set -------------------------------------- ----@generic T ---@class Set : { [T]: true } ----@field protected m_data table +---@field protected m_data table ---@field protected m_data_type string ----@overload fun(...): Set<...> -Set = {} +---@field new fun(...: T): Set +---@overload fun(...: T): Set +Set = { __type = "Set" } Set.__index = Set -Set.__type = "Set" + ---@diagnostic disable-next-line: param-type-mismatch setmetatable(Set, { __call = function(_, ...) @@ -25,13 +25,10 @@ setmetatable(Set, { end }) ----@generic T ----@param ... T ----@return Set function Set.new(...) ---@diagnostic disable-next-line: param-type-mismatch local instance = setmetatable({ m_data = {} }, Set) - local args = { ... } + local args = { ... } if (#args > 0) then instance.m_data_type = type(args[1]) @@ -43,7 +40,7 @@ function Set.new(...) return instance end ----@param element anyval +---@param element T function Set:Push(element) if (element == nil) then return @@ -67,12 +64,8 @@ function Set:Push(element) self.m_data[element] = true end ----@param element anyval +---@param element T function Set:Pop(element) - if (type(element) ~= self.m_data_type) then - return - end - self.m_data[element] = nil end @@ -80,7 +73,7 @@ function Set:Clear() self.m_data = {} end ----@param element anyval +---@param element T ---@return boolean function Set:Contains(element) return self.m_data[element] == true @@ -96,10 +89,14 @@ function Set:Size() return table.getlen(self.m_data) end +---@return fun(t: table, index?: T): T, true +---@return table function Set:Iter() return pairs(self.m_data) end +---@return fun(t: table, index?: T): T, true +---@return table function Set:__pairs() return pairs(self.m_data) end diff --git a/SSV2/includes/classes/gta/CEntity.lua b/SSV2/includes/classes/gta/CEntity.lua index 6a665db..862fa39 100644 --- a/SSV2/includes/classes/gta/CEntity.lua +++ b/SSV2/includes/classes/gta/CEntity.lua @@ -40,7 +40,7 @@ local CEntity = Class("CEntity", { symbolic_size = 0x028C }) ---@param entity handle ---@return CEntity function CEntity:init(entity) - if (not Game.IsScriptHandle(entity)) then + if (not ENTITY.DOES_ENTITY_EXIST(entity)) then error("Invalid entity!") end diff --git a/SSV2/includes/classes/gta/CPed.lua b/SSV2/includes/classes/gta/CPed.lua index 451e4bb..0ac1614 100644 --- a/SSV2/includes/classes/gta/CPed.lua +++ b/SSV2/includes/classes/gta/CPed.lua @@ -41,7 +41,7 @@ local CPed = Class("CPed", { parent = CEntity, symbolic_size = 0x161C }) ---@param ped handle ---@return CPed function CPed:init(ped) - if (not Game.IsScriptHandle(ped) or not ENTITY.IS_ENTITY_A_PED(ped)) then + if (not ENTITY.IS_ENTITY_A_PED(ped)) then error("Invalid entity!") end diff --git a/SSV2/includes/classes/gta/CVehicle.lua b/SSV2/includes/classes/gta/CVehicle.lua index 468ed9c..a052f4b 100644 --- a/SSV2/includes/classes/gta/CVehicle.lua +++ b/SSV2/includes/classes/gta/CVehicle.lua @@ -30,7 +30,7 @@ local CCarHandlingData = require("includes.classes.gta.CCarHandlingData") ---@ignore ---@class CVehicle : CEntity ---@field protected m_ptr pointer ----@field public m_physics_fragments phFragInst //0x30 `struct rage::phFragInst` +---@field public m_physics_fragments phFragInst //0x0030 `struct rage::phFragInst` ---@field public m_draw_data CVehicleDrawData ---@field public m_handling_data CHandlingData ---@field public m_model_info CVehicleModelInfo @@ -39,16 +39,21 @@ local CCarHandlingData = require("includes.classes.gta.CCarHandlingData") ---@field public m_velocity pointer ---@field public m_deform_god pointer ---@field public m_water_damage pointer ----@field public m_next_gear pointer ----@field public m_current_gear pointer ----@field public m_top_gear pointer +---@field public m_next_gear pointer +---@field public m_current_gear pointer +---@field public m_top_gear pointer +---@field public m_rpm pointer +---@field public m_rpm_2 pointer +---@field public m_clutch pointer // 0x08D4 +---@field public m_throttle pointer -- these two might be flipped +---@field public m_throttle_input pointer // ---@field public m_engine_health pointer ----@field public m_steering_input pointer // 0xD4 name might not correctly reflect what this actually is but this seems to store controller input (value is between 0.99 (left) .. -0.99 (right)) ----@field public m_current_steering pointer 0xDC // actual wheel steer. Wr'll use it to rewrite last known wheel steer after exiting a vehicle in IV-Style Exit so we'll no longer need to teleport outside or patch CTaskVehicleExit +---@field public m_steering_input pointer // 0x00D4 name might not correctly reflect what this actually is but this seems to store controller input (value is between 0.99 (left) .. -0.99 (right)) +---@field public m_current_steering pointer 0x00DC // actual wheel steer. Wr'll use it to rewrite last known wheel steer after exiting a vehicle in IV-Style Exit so we'll no longer need to teleport outside or patch CTaskVehicleExit ---@field public m_is_targetable pointer `bool` ---@field public m_door_lock_status pointer ----@field public m_wheels atArray -- 0xC30 ----@field public m_num_wheels number -- 0xC38 +---@field public m_wheels atArray -- 0x0C30 +---@field public m_num_wheels integer -- 0x0C38 ---@field public m_ride_height pointer ---@field private DumpFlags fun(self: CVehicle, enum_flags: Enum, get_func: fun(self: CVehicle, flag: integer): boolean): nil ---@overload fun(vehicle: integer): CVehicle|nil @@ -57,7 +62,7 @@ local CVehicle = Class("CVehicle", { parent = CEntity, symbolic_size = 0xC40 }) ---@param vehicle handle ---@return CVehicle function CVehicle:init(vehicle) - if (not Game.IsScriptHandle(vehicle) or not ENTITY.IS_ENTITY_A_VEHICLE(vehicle)) then + if (not ENTITY.IS_ENTITY_A_VEHICLE(vehicle)) then error("Invalid entity!") end @@ -79,6 +84,11 @@ function CVehicle:init(vehicle) instance.m_next_gear = ptr:add(0x0880) instance.m_current_gear = ptr:add(0x0882) instance.m_top_gear = ptr:add(0x0886) + instance.m_rpm = ptr:add(0x08C8) + instance.m_rpm_2 = ptr:add(0x08CC) + instance.m_clutch = ptr:add(0x08D4) + instance.m_throttle = ptr:add(0x08D8) + instance.m_throttle_input = ptr:add(0x08E0) instance.m_engine_health = ptr:add(0x0910) instance.m_handling_data = CHandlingData(ptr:add(0x0960):deref(), instance.m_model_info:GetVehicleType()) instance.m_deform_god = ptr:add(0x096C) diff --git a/SSV2/includes/classes/gta/CVehicleModelInfo.lua b/SSV2/includes/classes/gta/CVehicleModelInfo.lua index 668fa56..dda1d40 100644 --- a/SSV2/includes/classes/gta/CVehicleModelInfo.lua +++ b/SSV2/includes/classes/gta/CVehicleModelInfo.lua @@ -32,13 +32,12 @@ local CVehicleModelInfo = CStructView("CVehicleModelInfo", 0x08DC) ---@return CVehicleModelInfo function CVehicleModelInfo.new(ptr) return setmetatable({ - m_ptr = ptr, - m_vehicle_layout = ptr:add(0x00B0), - m_vehicle_type = ptr:add(0x0340), - m_wheel_scale = ptr:add(0x048C), - m_wheel_scale_rear = ptr:add(0x0490), - m_model_info_flags = ptr:add(0x057C), - m_throttle_position = ptr:add(0x08D8), + m_ptr = ptr, + m_vehicle_layout = ptr:add(0x00B0), + m_vehicle_type = ptr:add(0x0340), + m_wheel_scale = ptr:add(0x048C), + m_wheel_scale_rear = ptr:add(0x0490), + m_model_info_flags = ptr:add(0x057C), ---@diagnostic disable-next-line: param-type-mismatch }, CVehicleModelInfo) end diff --git a/SSV2/includes/data/config.lua b/SSV2/includes/data/config.lua index 8bc53cf..102febc 100644 --- a/SSV2/includes/data/config.lua +++ b/SSV2/includes/data/config.lua @@ -42,44 +42,48 @@ local Config = { auto_close = false, }, keyboard_keybinds = { - gui_toggle = "F5", + gui_toggle = "F5", kill_all_enemies = "F7", - enemies_flee = "F8", - -- missile_defence = "F9", - cobra_maneuver = "X", - flatbed = "X", - laser_sights = "L", - nos = "MOUSE5", - panik = "F12", - nos_purge = "X", - rod = "X", - drift_mode = "SHIFT", - -- trigger_bot = "SHIFT", - veh_mine = "NUMPAD0", - stop_anim = "G" + enemies_flee = "F8", + -- missile_defence = "F9", + cobra_maneuver = "X", + flatbed = "X", + laser_sights = "L", + nos = "MOUSE5", + rolling_launch = "N", + panik = "F12", + nos_purge = "X", + rod = "X", + drift_mode = "SHIFT", + -- trigger_bot = "SHIFT", + veh_mine = "NUMPAD0", + stop_anim = "G", + shift_up = "NUMPAD9", + shift_down = "NUMPAD3", + clutch = "NUMPAD5", }, gamepad_keybinds = { - flatbed = { + flatbed = { code = 288, name = "A" }, - laser_sights = { + laser_sights = { code = 303, name = "DPAD UP" }, - nos = { + nos = { code = 289, name = "X" }, - nos_purge = { + nos_purge = { code = 288, name = "A" }, - rod = { + rod = { code = 288, name = "A" }, - drift_mode = { + drift_mode = { code = 288, name = "A" }, @@ -87,14 +91,30 @@ local Config = { -- code = 0, -- name = "Unbound" -- }, - veh_mine = { + veh_mine = { code = 0, name = "Unbound" }, - stop_anim = { + stop_anim = { code = 0, name = "Unbound" - } + }, + rolling_launch = { + code = 0, + name = "Unbound" + }, + shift_up = { + code = 0, + name = "Unbound" + }, + shift_down = { + code = 0, + name = "Unbound" + }, + clutch = { + code = 0, + name = "Unbound" + }, }, features = { self = { @@ -140,6 +160,15 @@ local Config = { } }, }, + default_station = { + enabled = false, + station_name = "OFF", + display_name = "Off" + }, + manual_gearbox = { + enabled = false, + mode = 0, + }, bangs_rpm_max = 9000.0, bangs_rpm_min = 4000.0, performance_only = false, @@ -163,6 +192,7 @@ local Config = { mines = { enabled = false, selected_type_hash = -647126932, -- spike mines default + name = nil ---@type string? }, missile_defence = false, strong_crash = false, diff --git a/SSV2/includes/data/enums/vehicle_advanced_flags.lua b/SSV2/includes/data/enums/vehicle_advanced_flags.lua index c4d361b..480ecd3 100644 --- a/SSV2/includes/data/enums/vehicle_advanced_flags.lua +++ b/SSV2/includes/data/enums/vehicle_advanced_flags.lua @@ -22,8 +22,8 @@ local eVehicleAdvancedFlags = { GEARBOX_MANUAL = 10, GEARBOX_DIRECT_SHIFT = 11, GEARBOX_ELECTRIC = 12, - ASSIST_TRACTION_CONTROL = 13, - ASSIST_STABILITY_CONTROL = 14, + DISABLE_TRACTION_CONTROL = 13, + DISABLE_STABILITY_CONTROL = 14, ALLOW_REDUCED_SUSPENSION_FORCE = 15, HARD_REV_LIMIT = 16, HOLD_GEAR_WITH_WHEELSPIN = 17, diff --git a/SSV2/includes/data/globals_locals.lua b/SSV2/includes/data/globals_locals.lua index 31e1df8..ff66184 100644 --- a/SSV2/includes/data/globals_locals.lua +++ b/SSV2/includes/data/globals_locals.lua @@ -50,32 +50,6 @@ return { } } }, - freemode_boss_stuff = { - description = "freemode global", - file = "freemode.c", - LEGACY = { - value = 2733002, - pattern = [[if\s+\(!func_\d+\(4\) && !.*?\(Global_(\d{7})\.f_(\d{3}), 4\)\)]], - capture_group = 1, - offsets = { - { - value = 925, - capture_group = 2, - }, - } - }, - ENHANCED = { - value = 2733138, - pattern = [[if\s+\(!func_\d+\(4\) && !.*?\(Global_(\d{7})\.f_(\d{3}), 4\)\)]], - capture_group = 1, - offsets = { - { - value = 926, - capture_group = 2, - }, - } - } - }, freemode_business_global = { description = "Freemode Business Global", file = "freemode.c", @@ -104,6 +78,32 @@ return { capture_group = 1 } }, + freemode_boss_uid_str = { + description = "boss user id string", + file = "freemode.c", + LEGACY = { + value = 2686090, + pattern = [[Global_26(\d{5})\.f_(3\d{3})\.f_2\s+=\s+\{.*?\};]], + capture_group = 1, + offsets = { + { + value = 3083, + capture_group = 2, + }, + } + }, + ENHANCED = { + value = 2686095, + pattern = [[Global_26(\d{5})\.f_(3\d{3})\.f_2\s+=\s+\{.*?\};]], + capture_group = 1, + offsets = { + { + value = 3083, + capture_group = 2, + }, + } + } + }, arcade_bhub_global_1 = { description = "Arcade Business Hub Global 1", file = "apparcadebusinesshub.c", @@ -1073,6 +1073,34 @@ return { capture_group = 1 } }, + freemode_boss_offset_1 = { + description = "an offset at request_services_global used when registering as a boss", + file = "freemode.c", + LEGACY = { + value = 925, + pattern = [[if\s+\(!func_\d+\(4\) && !.*?\(Global_\d{7}\.f_(\d{3}), 4\)\)]], + capture_group = 1, + }, + ENHANCED = { + value = 926, + pattern = [[if\s+\(!func_\d+\(4\) && !.*?\(Global_\d{7}\.f_(\d{3}), 4\)\)]], + capture_group = 1, + } + }, + freemode_boss_offset_2 = { + description = "an offset at request_services_global used when registering as a boss", + file = "freemode.c", + LEGACY = { + value = 3988, + pattern = [[if\s+?\(\w+\(Global_27\d{5}\.f_(\d{4})\.f_263, true, true\)\)]], + capture_group = 1, + }, + ENHANCED = { + value = 3989, + pattern = [[if\s+?\(\w+\(Global_27\d{5}\.f_(\d{4})\.f_263, true, true\)\)]], + capture_group = 1, + } + }, service_vehicles_global = { description = [[Stores status of Service Vehicles. To find bitsets: diff --git a/SSV2/includes/data/handling_preset_callbacks.lua b/SSV2/includes/data/handling_preset_callbacks.lua index 78ced19..b8cb334 100644 --- a/SSV2/includes/data/handling_preset_callbacks.lua +++ b/SSV2/includes/data/handling_preset_callbacks.lua @@ -18,7 +18,7 @@ -- NOTE: This file is only for default callbacks. Please do not edit unless you're contributing a new default handling preset. -- For user-generated callbacks, you can define a new file with the same structure of this one. ----@alias HandlingPresetCallback fun(self: HandlingPreset, editor: HandlingEditor): boolean +---@alias HandlingPresetCallback fun(self: HandlingPreset, editor: VehicleFlagController): boolean ---@alias HandlingPresetCallbackData { onEnable: HandlingPresetCallback, onDisable: HandlingPresetCallback } ---@type dict @@ -65,10 +65,11 @@ return { onEnable = function(_) local PV = LocalPlayer:GetVehicle() if (not PV:IsValid()) then return true end - local stancer = PV.m_stancer - local deltas = stancer.m_deltas - local front = deltas[Enums.eWheelAxle.FRONT] - local rear = deltas[Enums.eWheelAxle.REAR] + local stancer = PV.m_stancer + local deltas = stancer.m_deltas + local front = deltas[Enums.eWheelAxle.FRONT] + local rear = deltas[Enums.eWheelAxle.REAR] + stancer:Lock(_T("VEH_OFFROAD_ABILITIES")) front.m_susp_comp = 0.1207 front.m_track_width = -0.047 rear.m_susp_comp = 0.1201 @@ -79,7 +80,9 @@ return { onDisable = function(_) local PV = LocalPlayer:GetVehicle() if (not PV:IsValid()) then return true end - PV.m_stancer:ResetDeltas(true) + local stancer = PV.m_stancer + stancer:ResetDeltas(true) + stancer:Unlock() PV:ActivatePhysics() return true end diff --git a/SSV2/includes/data/handling_presets.lua b/SSV2/includes/data/handling_presets.lua index b0d24d9..ec9a996 100644 --- a/SSV2/includes/data/handling_presets.lua +++ b/SSV2/includes/data/handling_presets.lua @@ -68,6 +68,7 @@ return { auto_apply = false, deltas = { [Enums.eHandlingEditorTypes.TYPE_HF] = { + ["SMOOTHED_COMPRESSION"] = true, ["OFFROAD_ABILITIES"] = true, ["OFFROAD_ABILITIES_X2"] = true, ["HAS_RALLY_TYRES"] = true, @@ -75,12 +76,12 @@ return { ["LESS_SNOW_SINK"] = true, }, [Enums.eHandlingEditorTypes.TYPE_AF] = { - ["DIFF_LOCKING_FRONT"] = true, - ["DIFF_LOCKING_REAR"] = true, - ["FORCE_SMOOTH_RPM"] = true, - ["HOLD_GEAR_WITH_WHEELSPIN"] = false, - ["ASSIST_TRACTION_CONTROL"] = false, - ["ASSIST_STABILITY_CONTROL"] = false, + ["DIFF_LOCKING_FRONT"] = true, + ["DIFF_LOCKING_REAR"] = true, + ["FORCE_SMOOTH_RPM"] = true, + ["HOLD_GEAR_WITH_WHEELSPIN"] = false, + ["DISABLE_TRACTION_CONTROL"] = false, + ["DISABLE_STABILITY_CONTROL"] = false, }, [Enums.eHandlingEditorTypes.TYPE_MIF] = { ["INCREASE_LOW_SPEED_TORQUE"] = true, }, }, @@ -94,11 +95,11 @@ return { deltas = { [Enums.eHandlingEditorTypes.TYPE_HF] = { ["FORCE_NO_TC_OR_SC"] = true }, [Enums.eHandlingEditorTypes.TYPE_AF] = { - ["HOLD_GEAR_WITH_WHEELSPIN"] = true, - ["HARD_REV_LIMIT"] = true, - ["FORCE_SMOOTH_RPM"] = false, - ["ASSIST_TRACTION_CONTROL"] = false, - ["ASSIST_STABILITY_CONTROL"] = false, + ["HOLD_GEAR_WITH_WHEELSPIN"] = true, + ["HARD_REV_LIMIT"] = true, + ["FORCE_SMOOTH_RPM"] = false, + ["DISABLE_TRACTION_CONTROL"] = true, + ["DISABLE_STABILITY_CONTROL"] = true, }, [Enums.eHandlingEditorTypes.TYPE_MIF] = { ["INCREASE_LOW_SPEED_TORQUE"] = true, diff --git a/SSV2/includes/data/script_globals.lua b/SSV2/includes/data/script_globals.lua index 4ee4f39..366bbbd 100644 --- a/SSV2/includes/data/script_globals.lua +++ b/SSV2/includes/data/script_globals.lua @@ -16,7 +16,7 @@ local SGSL = require("includes.services.SGSL") ---@class GGlobals ---@field GPBD_FM_3 ScriptGlobal ---@field MP_BUSINESS_STUFF ScriptGlobal ----@field FREEMODE_GLOBAL ScriptGlobal +---@field FM_SERVICES ScriptGlobal local GGlobals = {} GGlobals.__index = GGlobals @@ -30,7 +30,7 @@ local GlobalsRegistry = { GGlobals.MP_BUSINESS_STUFF = SGSL:Get(SGSL.data.mp_business_stuff):AsGlobal() end, function() - GGlobals.FREEMODE_GLOBAL = SGSL:Get(SGSL.data.freemode_boss_stuff):AsGlobal() + GGlobals.FM_SERVICES = SGSL:Get(SGSL.data.request_services_global):AsGlobal() end, } diff --git a/SSV2/includes/data/vehicle_hashmap.lua b/SSV2/includes/data/vehicle_hashmap.lua index db6631f..98764cc 100644 --- a/SSV2/includes/data/vehicle_hashmap.lua +++ b/SSV2/includes/data/vehicle_hashmap.lua @@ -8,7 +8,7 @@ --- Auto-generated on <08-02-2026> +-- Auto-generated on <20-05-2026> ---@diagnostic disable diff --git a/SSV2/includes/data/vehicles.lua b/SSV2/includes/data/vehicles.lua index 50f3d70..daa6763 100644 --- a/SSV2/includes/data/vehicles.lua +++ b/SSV2/includes/data/vehicles.lua @@ -8,7 +8,7 @@ --- Auto-generated on <08-02-2026> +-- Auto-generated on <20-05-2026> ---@diagnostic disable @@ -20,6448 +20,7368 @@ local VehicleDictionary = { display_name = "Adder", manufacturer = "Truffade", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, airbus = { model_hash = 1283517198, display_name = "Airport Bus", manufacturer = nil, class_id = 17, - class_name = "SERVICE" + class_name = "SERVICE", + enhanced_only = false }, airtug = { model_hash = 1560980623, display_name = "Airtug", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, akula = { model_hash = 1181327175, display_name = "Akula", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, akuma = { model_hash = 1672195559, display_name = "Akuma", manufacturer = "Dinka", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, aleutian = { model_hash = 4256087847, display_name = "Aleutian", manufacturer = "Vapid", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, alkonost = { model_hash = 3929093893, display_name = "RO-86 Alkonost", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, alpha = { model_hash = 767087018, display_name = "Alpha", manufacturer = "Albany", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, alphaz1 = { model_hash = 2771347558, display_name = "Alpha-Z1", manufacturer = "Buckingham", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, ambulance = { model_hash = 1171614426, display_name = "Ambulance", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, annihilator = { model_hash = 837858166, display_name = "Annihilator", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, annihilator2 = { model_hash = 295054921, display_name = "Annihilator Stealth", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, apc = { model_hash = 562680400, display_name = "APC", manufacturer = "HVY", class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, arbitergt = { model_hash = 1549009676, display_name = "Arbiter GT", manufacturer = "Imponte", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = true }, ardent = { model_hash = 159274291, display_name = "Ardent", manufacturer = "Ocelot", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, armytanker = { model_hash = 3087536137, display_name = "Army Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, armytrailer = { model_hash = 2818520053, display_name = "Army Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, armytrailer2 = { model_hash = 2657817814, display_name = "Army Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, asbo = { model_hash = 1118611807, display_name = "Asbo", manufacturer = "Maxwell", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, asea = { model_hash = 2485144969, display_name = "Asea", manufacturer = "Declasse", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, asea2 = { model_hash = 2487343317, display_name = "Asea", manufacturer = "Declasse", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, asterope = { model_hash = 2391954683, display_name = "Asterope", manufacturer = "Karin", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, asterope2 = { model_hash = 3553846961, display_name = "Asterope GZ", manufacturer = "Karin", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, astrale = { model_hash = 1596736441, display_name = "Astrale", manufacturer = "Pfister", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, astron = { model_hash = 629969764, display_name = "Astron", manufacturer = "Pfister", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, astron2 = { model_hash = 2803699023, display_name = "Astron Custom", manufacturer = "Pfister", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = true }, autarch = { model_hash = 3981782132, display_name = "Autarch", manufacturer = "Overflod", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, avarus = { model_hash = 2179174271, display_name = "Avarus", manufacturer = "LCC", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, avenger = { model_hash = 2176659152, display_name = "Avenger", manufacturer = "Mammoth", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, avenger2 = { model_hash = 408970549, display_name = "Avenger", manufacturer = "Mammoth", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, avenger3 = { model_hash = 3868033424, display_name = "Avenger", manufacturer = "Mammoth", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, avenger4 = { model_hash = 4225674290, display_name = "Avenger", manufacturer = "Mammoth", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, avisa = { model_hash = 2588363614, display_name = "Avisa", manufacturer = "Kraken", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, bagger = { model_hash = 2154536131, display_name = "Bagger", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, baletrailer = { model_hash = 3895125590, display_name = "Baletrailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, baller = { model_hash = 3486135912, display_name = "Baller", manufacturer = "Gallivanter", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, baller2 = { model_hash = 142944341, display_name = "Baller", manufacturer = "Gallivanter", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, baller3 = { model_hash = 1878062887, display_name = "Baller LE", manufacturer = "Gallivanter", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, baller4 = { model_hash = 634118882, display_name = "Baller LE LWB", manufacturer = "Gallivanter", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, baller5 = { model_hash = 470404958, display_name = "Baller LE (Armored)", manufacturer = "Gallivanter", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, baller6 = { model_hash = 666166960, display_name = "Baller LE LWB (Armored)", manufacturer = "Gallivanter", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, baller7 = { model_hash = 359875117, display_name = "Baller ST", manufacturer = "Gallivanter", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, baller8 = { model_hash = 3431608412, display_name = "Baller ST-D", manufacturer = "Gallivanter", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, banshee = { model_hash = 3253274834, display_name = "Banshee", manufacturer = "Bravado", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, banshee2 = { model_hash = 633712403, display_name = "Banshee 900R", manufacturer = "Bravado", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, banshee3 = { model_hash = 3634959571, display_name = "Banshee GTS", manufacturer = "Bravado", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, barracks = { model_hash = 3471458123, display_name = "Barracks", manufacturer = nil, class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, barracks2 = { model_hash = 1074326203, display_name = "Barracks Semi", manufacturer = "HVY", class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, barracks3 = { model_hash = 630371791, display_name = "Barracks", manufacturer = nil, class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, barrage = { model_hash = 4081974053, display_name = "Barrage", manufacturer = nil, class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, bati = { model_hash = 4180675781, display_name = "Bati 801", manufacturer = "Pegassi", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, bati2 = { model_hash = 3403504941, display_name = "Bati 801RR", manufacturer = "Pegassi", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, benson = { model_hash = 2053223216, display_name = "Benson", manufacturer = "Vapid", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, benson2 = { model_hash = 728350375, display_name = "Benson (Cluckin' Bell)", manufacturer = "Vapid", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, besra = { model_hash = 1824333165, display_name = "Besra", manufacturer = "Western", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, bestiagts = { model_hash = 1274868363, display_name = "Bestia GTS", manufacturer = "Grotti", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, bf400 = { model_hash = 86520421, display_name = "BF400", manufacturer = "Nagasaki", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, bfinjection = { model_hash = 1126868326, display_name = "Injection", manufacturer = "BF", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, biff = { model_hash = 850991848, display_name = "Biff", manufacturer = "HVY", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, bifta = { model_hash = 3945366167, display_name = "Bifta", manufacturer = "BF", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, bison = { model_hash = 4278019151, display_name = "Bison", manufacturer = "Bravado", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, bison2 = { model_hash = 2072156101, display_name = "Bison", manufacturer = "Bravado", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, bison3 = { model_hash = 1739845664, display_name = "Bison", manufacturer = "Bravado", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, bjxl = { model_hash = 850565707, display_name = "BeeJay XL", manufacturer = "Karin", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, blade = { model_hash = 3089165662, display_name = "Blade", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, blazer = { model_hash = 2166734073, display_name = "Blazer", manufacturer = "Nagasaki", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, blazer2 = { model_hash = 4246935337, display_name = "Blazer Lifeguard", manufacturer = "Nagasaki", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, blazer3 = { model_hash = 3025077634, display_name = "Hot Rod Blazer", manufacturer = "Nagasaki", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, blazer4 = { model_hash = 3854198872, display_name = "Street Blazer", manufacturer = "Nagasaki", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, blazer5 = { model_hash = 2704629607, display_name = "Blazer Aqua", manufacturer = "Nagasaki", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, blimp = { model_hash = 4143991942, display_name = "Atomic Blimp", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, blimp2 = { model_hash = 3681241380, display_name = "Xero Blimp", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, blimp3 = { model_hash = 3987008919, display_name = "Blimp", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, blista = { model_hash = 3950024287, display_name = "Blista", manufacturer = "Dinka", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, blista2 = { model_hash = 1039032026, display_name = "Blista Compact", manufacturer = "Dinka", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, blista3 = { model_hash = 3703315515, display_name = "Go Go Monkey Blista", manufacturer = "Dinka", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, bmx = { model_hash = 1131912276, display_name = "BMX", manufacturer = nil, class_id = 13, - class_name = "CYCLE" + class_name = "CYCLE", + enhanced_only = false }, boattrailer = { model_hash = 524108981, display_name = "Boat Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, boattrailer2 = { model_hash = 1835260592, display_name = "Boat Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, boattrailer3 = { model_hash = 1539159908, display_name = "Boat Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, bobcatxl = { model_hash = 1069929536, display_name = "Bobcat XL", manufacturer = "Vapid", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, bodhi2 = { model_hash = 2859047862, display_name = "Bodhi", manufacturer = "Canis", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, bombushka = { model_hash = 4262088844, display_name = "RM-10 Bombushka", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, boor = { model_hash = 996383885, display_name = "Boor", manufacturer = "Karin", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, boxville = { model_hash = 2307837162, display_name = "Boxville", manufacturer = "Brute", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, boxville2 = { model_hash = 4061868990, display_name = "Boxville", manufacturer = nil, class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, boxville3 = { model_hash = 121658888, display_name = "Boxville", manufacturer = "Brute", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, boxville4 = { model_hash = 444171386, display_name = "Boxville", manufacturer = "Brute", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, boxville5 = { model_hash = 682434785, display_name = "Armored Boxville", manufacturer = nil, class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, boxville6 = { model_hash = 3452201761, display_name = "Boxville (LSDS)", manufacturer = "Brute", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, brawler = { model_hash = 2815302597, display_name = "Brawler", manufacturer = "Coil", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, brickade = { model_hash = 3989239879, display_name = "Brickade", manufacturer = "MTL", class_id = 17, - class_name = "SERVICE" + class_name = "SERVICE", + enhanced_only = false }, brickade2 = { model_hash = 2718380883, display_name = "Brickade 6x6", manufacturer = "MTL", class_id = 17, - class_name = "SERVICE" + class_name = "SERVICE", + enhanced_only = false }, brigham = { model_hash = 3640468689, display_name = "Brigham", manufacturer = "Albany", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, brioso = { model_hash = 1549126457, display_name = "Brioso R/A", manufacturer = "Grotti", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, brioso2 = { model_hash = 1429622905, display_name = "Brioso 300", manufacturer = "Grotti", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, brioso3 = { model_hash = 15214558, display_name = "Brioso 300 Widebody", manufacturer = "Grotti", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, broadway = { model_hash = 2361724968, display_name = "Broadway", manufacturer = "Classique", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, bruiser = { model_hash = 668439077, display_name = "Apocalypse Bruiser", manufacturer = "Benefactor", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, bruiser2 = { model_hash = 2600885406, display_name = "Future Shock Bruiser", manufacturer = "Benefactor", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, bruiser3 = { model_hash = 2252616474, display_name = "Nightmare Bruiser", manufacturer = "Benefactor", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, brutus = { model_hash = 2139203625, display_name = "Apocalypse Brutus", manufacturer = "Declasse", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, brutus2 = { model_hash = 2403970600, display_name = "Future Shock Brutus", manufacturer = "Declasse", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, brutus3 = { model_hash = 2038858402, display_name = "Nightmare Brutus", manufacturer = "Declasse", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, btype = { model_hash = 117401876, display_name = "Roosevelt", manufacturer = "Albany", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, btype2 = { model_hash = 3463132580, display_name = "Fränken Stange", manufacturer = "Albany", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, btype3 = { model_hash = 3692679425, display_name = "Roosevelt Valor", manufacturer = "Albany", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, buccaneer = { model_hash = 3612755468, display_name = "Buccaneer", manufacturer = "Albany", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, buccaneer2 = { model_hash = 3281516360, display_name = "Buccaneer Custom", manufacturer = "Albany", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, buffalo = { model_hash = 3990165190, display_name = "Buffalo", manufacturer = "Bravado", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, buffalo2 = { model_hash = 736902334, display_name = "Buffalo S", manufacturer = "Bravado", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, buffalo3 = { model_hash = 237764926, display_name = "Sprunk Buffalo", manufacturer = "Bravado", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, buffalo4 = { model_hash = 3675036420, display_name = "Buffalo STX", manufacturer = "Bravado", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, buffalo5 = { model_hash = 165968051, display_name = "Buffalo EVX", manufacturer = "Bravado", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, bulldozer = { model_hash = 1886712733, display_name = "Dozer", manufacturer = "HVY", class_id = 10, - class_name = "INDUSTRIAL" + class_name = "INDUSTRIAL", + enhanced_only = false }, bullet = { model_hash = 2598821281, display_name = "Bullet", manufacturer = "Vapid", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, burrito = { model_hash = 2948279460, display_name = "Burrito", manufacturer = "Declasse", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, burrito2 = { model_hash = 3387490166, display_name = "Bugstars Burrito", manufacturer = "Declasse", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, burrito3 = { model_hash = 2551651283, display_name = "Burrito", manufacturer = "Declasse", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, burrito4 = { model_hash = 893081117, display_name = "Burrito", manufacturer = "Declasse", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, burrito5 = { model_hash = 1132262048, display_name = "Burrito", manufacturer = "Declasse", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, bus = { model_hash = 3581397346, display_name = "Bus", manufacturer = nil, class_id = 17, - class_name = "SERVICE" + class_name = "SERVICE", + enhanced_only = false }, buzzard = { model_hash = 788747387, display_name = "Buzzard Attack Chopper", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, buzzard2 = { model_hash = 745926877, display_name = "Buzzard", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, cablecar = { model_hash = 3334677549, display_name = "Cable Car", manufacturer = nil, class_id = 21, - class_name = "RAIL" + class_name = "RAIL", + enhanced_only = false }, caddy = { model_hash = 1147287684, display_name = "Caddy", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, caddy2 = { model_hash = 3757070668, display_name = "Caddy", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, caddy3 = { model_hash = 3525819835, display_name = "Caddy", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, calico = { model_hash = 3101054893, display_name = "Calico GTF", manufacturer = "Karin", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, camper = { model_hash = 1876516712, display_name = "Camper", manufacturer = "Brute", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, caracara = { model_hash = 1254014755, display_name = "Caracara", manufacturer = "Vapid", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, caracara2 = { model_hash = 2945871676, display_name = "Caracara 4x4", manufacturer = "Vapid", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, carbonizzare = { model_hash = 2072687711, display_name = "Carbonizzare", manufacturer = "Grotti", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, carbonrs = { model_hash = 11251904, display_name = "Carbon RS", manufacturer = "Nagasaki", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, cargobob = { model_hash = 4244420235, display_name = "Cargobob", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, cargobob2 = { model_hash = 1621617168, display_name = "Cargobob", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, cargobob3 = { model_hash = 1394036463, display_name = "Cargobob", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, cargobob4 = { model_hash = 2025593404, display_name = "Cargobob", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, cargobob5 = { model_hash = 3942284983, display_name = "DH-7 Iron Mule", manufacturer = "Buckingham", class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, cargoplane = { model_hash = 368211810, display_name = "Cargo Plane", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, cargoplane2 = { model_hash = 2336777441, display_name = "Cargo Plane", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, casco = { model_hash = 941800958, display_name = "Casco", manufacturer = "Lampadati", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, castigator = { model_hash = 1307736079, display_name = "Castigator", manufacturer = "Canis", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, cavalcade = { model_hash = 2006918058, display_name = "Cavalcade", manufacturer = "Albany", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, cavalcade2 = { model_hash = 3505073125, display_name = "Cavalcade", manufacturer = "Albany", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, cavalcade3 = { model_hash = 3265236814, display_name = "Cavalcade XL", manufacturer = "Albany", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, cerberus = { model_hash = 3493417227, display_name = "Apocalypse Cerberus", manufacturer = "MTL", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, cerberus2 = { model_hash = 679453769, display_name = "Future Shock Cerberus", manufacturer = "MTL", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, cerberus3 = { model_hash = 1909700336, display_name = "Nightmare Cerberus", manufacturer = "MTL", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, champion = { model_hash = 3379732821, display_name = "Champion", manufacturer = "Dewbauchee", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, chavosv6 = { model_hash = 1992041063, display_name = "Chavos V6", manufacturer = "Dinka", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, cheburek = { model_hash = 3306466016, display_name = "Cheburek", manufacturer = "RUNE", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, cheetah = { model_hash = 2983812512, display_name = "Cheetah", manufacturer = "Grotti", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, cheetah2 = { model_hash = 223240013, display_name = "Cheetah Classic", manufacturer = "Grotti", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, cheetah3 = { model_hash = 471399650, display_name = "LSCM Cheetah Classic", manufacturer = "Grotti", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, chernobog = { model_hash = 3602674979, display_name = "Chernobog", manufacturer = nil, class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, chimera = { model_hash = 6774487, display_name = "Chimera", manufacturer = "Nagasaki", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, chino = { model_hash = 349605904, display_name = "Chino", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, chino2 = { model_hash = 2933279331, display_name = "Chino Custom", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, cinquemila = { model_hash = 2767531027, display_name = "Cinquemila", manufacturer = "Lampadati", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, cliffhanger = { model_hash = 390201602, display_name = "Cliffhanger", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, clique = { model_hash = 2728360112, display_name = "Clique", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, clique2 = { model_hash = 3315674721, display_name = "Clique Wagon", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, club = { model_hash = 2196012677, display_name = "Club", manufacturer = "BF", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, coach = { model_hash = 2222034228, display_name = "Dashound", manufacturer = nil, class_id = 17, - class_name = "SERVICE" + class_name = "SERVICE", + enhanced_only = false }, cog55 = { model_hash = 906642318, display_name = "Cognoscenti 55", manufacturer = "Enus", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, cog552 = { model_hash = 704435172, display_name = "Cognoscenti 55 (Armored)", manufacturer = "Enus", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, cogcabrio = { model_hash = 330661258, display_name = "Cognoscenti Cabrio", manufacturer = "Enus", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, cognoscenti = { model_hash = 2264796000, display_name = "Cognoscenti", manufacturer = "Enus", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, cognoscenti2 = { model_hash = 3690124666, display_name = "Cognoscenti (Armored)", manufacturer = "Enus", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, comet2 = { model_hash = 3249425686, display_name = "Comet", manufacturer = "Pfister", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, comet3 = { model_hash = 2272483501, display_name = "Comet Retro Custom", manufacturer = "Pfister", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, comet4 = { model_hash = 1561920505, display_name = "Comet Safari", manufacturer = "Pfister", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, comet5 = { model_hash = 661493923, display_name = "Comet SR", manufacturer = "Pfister", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, comet6 = { model_hash = 2568944644, display_name = "Comet S2", manufacturer = "Pfister", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, comet7 = { model_hash = 1141395928, display_name = "Comet S2 Cabrio", manufacturer = "Pfister", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, conada = { model_hash = 3817135397, display_name = "Conada", manufacturer = "Buckingham", class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, conada2 = { model_hash = 2635962482, display_name = "Weaponized Conada", manufacturer = "Buckingham", class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, contender = { model_hash = 683047626, display_name = "Contender", manufacturer = "Vapid", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, coquette = { model_hash = 108773431, display_name = "Coquette", manufacturer = "Invetero", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, coquette2 = { model_hash = 1011753235, display_name = "Coquette Classic", manufacturer = "Invetero", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, coquette3 = { model_hash = 784565758, display_name = "Coquette BlackFin", manufacturer = "Invetero", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, coquette4 = { model_hash = 2566281822, display_name = "Coquette D10", manufacturer = "Invetero", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, coquette5 = { model_hash = 2336538363, display_name = "Coquette D1", manufacturer = "Invetero", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, coquette6 = { model_hash = 127317925, display_name = "Coquette D5", manufacturer = "Invetero", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, corsita = { model_hash = 3540279623, display_name = "Corsita", manufacturer = "Lampadati", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, coureur = { model_hash = 610429990, display_name = "La Coureuse", manufacturer = "Penaud", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, cruiser = { model_hash = 448402357, display_name = "Cruiser", manufacturer = nil, class_id = 13, - class_name = "CYCLE" + class_name = "CYCLE", + enhanced_only = false }, crusader = { model_hash = 321739290, display_name = "Crusader", manufacturer = "Canis", class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, cuban800 = { model_hash = 3650256867, display_name = "Cuban 800", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, cutter = { model_hash = 3288047904, display_name = "Cutter", manufacturer = "HVY", class_id = 10, - class_name = "INDUSTRIAL" + class_name = "INDUSTRIAL", + enhanced_only = false }, cyclone = { model_hash = 1392481335, display_name = "Cyclone", manufacturer = "Coil", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, cyclone2 = { model_hash = 386089410, display_name = "Cyclone II", manufacturer = "Coil", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = true }, cypher = { model_hash = 1755697647, display_name = "Cypher", manufacturer = "Ubermacht", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, daemon = { model_hash = 2006142190, display_name = "Daemon", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, daemon2 = { model_hash = 2890830793, display_name = "Daemon Custom", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, deathbike = { model_hash = 4267640610, display_name = "Apocalypse Deathbike", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, deathbike2 = { model_hash = 2482017624, display_name = "Future Shock Deathbike", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, deathbike3 = { model_hash = 2920466844, display_name = "Nightmare Deathbike", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, defiler = { model_hash = 822018448, display_name = "Defiler", manufacturer = "Shitzu", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, deity = { model_hash = 1532171089, display_name = "Deity", manufacturer = "Enus", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, deluxo = { model_hash = 1483171323, display_name = "Deluxo", manufacturer = "Imponte", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, deveste = { model_hash = 1591739866, display_name = "Deveste Eight", manufacturer = "Principe", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, deviant = { model_hash = 1279262537, display_name = "Deviant", manufacturer = "Schyster", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, diablous = { model_hash = 4055125828, display_name = "Diabolus", manufacturer = "Principe", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, diablous2 = { model_hash = 1790834270, display_name = "Diabolus Custom", manufacturer = "Principe", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, dilettante = { model_hash = 3164157193, display_name = "Dilettante", manufacturer = "Karin", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, dilettante2 = { model_hash = 1682114128, display_name = "Dilettante", manufacturer = "Karin", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, dinghy = { model_hash = 1033245328, display_name = "Dinghy", manufacturer = "Nagasaki", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, dinghy2 = { model_hash = 276773164, display_name = "Dinghy", manufacturer = "Nagasaki", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, dinghy3 = { model_hash = 509498602, display_name = "Dinghy", manufacturer = "Nagasaki", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, dinghy4 = { model_hash = 867467158, display_name = "Dinghy", manufacturer = "Nagasaki", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, dinghy5 = { model_hash = 3314393930, display_name = "Weaponized Dinghy", manufacturer = "Nagasaki", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, dloader = { model_hash = 1770332643, display_name = "Duneloader", manufacturer = "Bravado", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, docktrailer = { model_hash = 2154757102, display_name = nil, manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, docktug = { model_hash = 3410276810, display_name = "Docktug", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, dodo = { model_hash = 3393804037, display_name = "Dodo", manufacturer = "Mammoth", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, dominator = { model_hash = 80636076, display_name = "Dominator", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, dominator10 = { model_hash = 1579902654, display_name = "Dominator FX", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, dominator2 = { model_hash = 3379262425, display_name = "Pisswasser Dominator", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, dominator3 = { model_hash = 3308022675, display_name = "Dominator GTX", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, dominator4 = { model_hash = 3606777648, display_name = "Apocalypse Dominator", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, dominator5 = { model_hash = 2919906639, display_name = "Future Shock Dominator", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, dominator6 = { model_hash = 3001042683, display_name = "Nightmare Dominator", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, dominator7 = { model_hash = 426742808, display_name = "Dominator ASP", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, dominator8 = { model_hash = 736672010, display_name = "Dominator GTT", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, dominator9 = { model_hash = 3853757601, display_name = "Dominator GT", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, dorado = { model_hash = 3526923154, display_name = "Dorado", manufacturer = "Bravado", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, double = { model_hash = 2623969160, display_name = "Double-T", manufacturer = "Dinka", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, drafter = { model_hash = 686471183, display_name = "8F Drafter", manufacturer = "Obey", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, draugur = { model_hash = 3526730918, display_name = "Draugur", manufacturer = "Declasse", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, driftchavosv6 = { model_hash = 457814204, display_name = "Chavos V6", manufacturer = "Dinka", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, driftcheburek = { model_hash = 2828274931, display_name = "Cheburek", manufacturer = "RUNE", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, driftcypher = { model_hash = 258105345, display_name = "Cypher", manufacturer = "Ubermacht", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, driftdominator10 = { model_hash = 3355365473, display_name = "Dominator FX", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, driftdominator9 = { model_hash = 3439117103, display_name = "Dominator GT", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, drifteuros = { model_hash = 821121576, display_name = "Euros", manufacturer = "Annis", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, driftfr36 = { model_hash = 2815031719, display_name = "FR36", manufacturer = "Fathom", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, driftfuto = { model_hash = 4113404654, display_name = "Futo GTX", manufacturer = "Karin", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, driftfuto2 = { model_hash = 3005741670, display_name = "Futo", manufacturer = "Karin", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, driftgauntlet4 = { model_hash = 3933619103, display_name = "Gauntlet Hellfire", manufacturer = "Bravado", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, drifthardy = { model_hash = 3893408850, display_name = "Hardy", manufacturer = "Annis", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, driftjester = { model_hash = 2531693357, display_name = "Jester RR", manufacturer = "Dinka", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, driftjester3 = { model_hash = 3932276298, display_name = "Jester Classic", manufacturer = "Dinka", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, driftkeitora = { model_hash = 2739875593, display_name = "Keitora", manufacturer = "Shitzu", class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, driftl352 = { model_hash = 2312533665, display_name = "Drift Walton L35", manufacturer = "Declasse", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, driftnebula = { model_hash = 1690421418, display_name = "Nebula Turbo", manufacturer = "Vulcar", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, driftremus = { model_hash = 2670883828, display_name = "Remus", manufacturer = "Annis", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, driftrt3000 = { model_hash = 1730644782, display_name = "RT3000", manufacturer = "Dinka", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, driftsentinel = { model_hash = 3308310822, display_name = "Sentinel Classic Widebody", manufacturer = "Ubermacht", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, driftsentinel2 = { model_hash = 3288768346, display_name = "Sentinel XS", manufacturer = "Ubermacht", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, drifttampa = { model_hash = 2598648200, display_name = "Drift Tampa", manufacturer = "Declasse", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, driftvorschlag = { model_hash = 4151380270, display_name = "Vorschlaghammer", manufacturer = "Benefactor", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, driftyosemite = { model_hash = 2613313775, display_name = "Drift Yosemite", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, driftzr350 = { model_hash = 1923534526, display_name = "ZR350", manufacturer = "Annis", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, dubsta = { model_hash = 1177543287, display_name = "Dubsta", manufacturer = "Benefactor", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, dubsta2 = { model_hash = 3900892662, display_name = "Dubsta", manufacturer = "Benefactor", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, dubsta3 = { model_hash = 3057713523, display_name = "Dubsta 6x6", manufacturer = "Benefactor", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, dukes = { model_hash = 723973206, display_name = "Dukes", manufacturer = "Imponte", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, dukes2 = { model_hash = 3968823444, display_name = "Duke O'Death", manufacturer = "Imponte", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, dukes3 = { model_hash = 2134119907, display_name = "Beater Dukes", manufacturer = "Imponte", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, dump = { model_hash = 2164484578, display_name = "Dump", manufacturer = "HVY", class_id = 10, - class_name = "INDUSTRIAL" + class_name = "INDUSTRIAL", + enhanced_only = false }, dune = { model_hash = 2633113103, display_name = "Dune Buggy", manufacturer = "BF", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, dune2 = { model_hash = 534258863, display_name = "Space Docker", manufacturer = nil, class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, dune3 = { model_hash = 1897744184, display_name = "Dune FAV", manufacturer = "BF", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, dune4 = { model_hash = 3467805257, display_name = "Ramp Buggy", manufacturer = nil, class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, dune5 = { model_hash = 3982671785, display_name = "Ramp Buggy", manufacturer = nil, class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, duster = { model_hash = 970356638, display_name = "Duster", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, duster2 = { model_hash = 84351789, display_name = "Duster 300-H", manufacturer = "Western", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, dynasty = { model_hash = 310284501, display_name = "Dynasty", manufacturer = "Weeny", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, elegy = { model_hash = 196747873, display_name = "Elegy Retro Custom", manufacturer = "Annis", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, elegy2 = { model_hash = 3728579874, display_name = "Elegy RH8", manufacturer = "Annis", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, ellie = { model_hash = 3027423925, display_name = "Ellie", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, emerus = { model_hash = 1323778901, display_name = "Emerus", manufacturer = "Progen", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, emperor = { model_hash = 3609690755, display_name = "Emperor", manufacturer = "Albany", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, emperor2 = { model_hash = 2411965148, display_name = "Emperor", manufacturer = "Albany", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, emperor3 = { model_hash = 3053254478, display_name = "Emperor", manufacturer = "Albany", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, enduro = { model_hash = 1753414259, display_name = "Enduro", manufacturer = "Dinka", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, entity2 = { model_hash = 2174267100, display_name = "Entity XXR", manufacturer = "Overflod", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, entity3 = { model_hash = 1748565021, display_name = "Entity MT", manufacturer = "Overflod", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, entityxf = { model_hash = 3003014393, display_name = "Entity XF", manufacturer = "Overflod", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, envisage = { model_hash = 1121330119, display_name = "Envisage", manufacturer = "Bollokan", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, esskey = { model_hash = 2035069708, display_name = "Esskey", manufacturer = "Pegassi", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, eudora = { model_hash = 3045179290, display_name = "Eudora", manufacturer = "Willard", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, euros = { model_hash = 2038480341, display_name = "Euros", manufacturer = "Annis", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, eurosx32 = { model_hash = 3295372994, display_name = "Euros X32", manufacturer = "Annis", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, everon = { model_hash = 2538945576, display_name = "Everon", manufacturer = "Karin", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, everon2 = { model_hash = 4163619118, display_name = "Hotring Everon", manufacturer = "Karin", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, everon3 = { model_hash = 554408685, display_name = "Everon RS", manufacturer = "Karin", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, exemplar = { model_hash = 4289813342, display_name = "Exemplar", manufacturer = "Dewbauchee", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, f620 = { model_hash = 3703357000, display_name = "F620", manufacturer = "Ocelot", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, faction = { model_hash = 2175389151, display_name = "Faction", manufacturer = "Willard", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, faction2 = { model_hash = 2504420315, display_name = "Faction Custom 1", manufacturer = "Willard", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, faction3 = { model_hash = 2255212070, display_name = "Faction Custom Donk", manufacturer = "Willard", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, fagaloa = { model_hash = 1617472902, display_name = "Fagaloa", manufacturer = "Vulcar", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, faggio = { model_hash = 2452219115, display_name = "Faggio Sport", manufacturer = "Pegassi", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, faggio2 = { model_hash = 55628203, display_name = "Faggio", manufacturer = "Pegassi", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, faggio3 = { model_hash = 3005788552, display_name = "Faggio Mod", manufacturer = "Pegassi", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, fbi = { model_hash = 1127131465, display_name = "FIB", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, fbi2 = { model_hash = 2647026068, display_name = "FIB", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, fcr = { model_hash = 627535535, display_name = "FCR 1000", manufacturer = "Pegassi", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, fcr2 = { model_hash = 3537231886, display_name = "FCR 1000 Custom", manufacturer = "Pegassi", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, felon = { model_hash = 3903372712, display_name = "Felon", manufacturer = "Lampadati", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, felon2 = { model_hash = 4205676014, display_name = "Felon GT", manufacturer = "Lampadati", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, feltzer2 = { model_hash = 2299640309, display_name = "Feltzer", manufacturer = "Benefactor", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, feltzer3 = { model_hash = 2728226064, display_name = "Feltzer Classic", manufacturer = "Benefactor", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, firebolt = { model_hash = 3321950518, display_name = "Firebolt ASP", manufacturer = "Vapid", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, firetruk = { model_hash = 1938952078, display_name = "Fire Truck", manufacturer = "MTL", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, fixter = { model_hash = 3458454463, display_name = "Fixter", manufacturer = nil, class_id = 13, - class_name = "CYCLE" + class_name = "CYCLE", + enhanced_only = false }, flashgt = { model_hash = 3035832600, display_name = "Flash GT", manufacturer = "Vapid", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, flatbed = { model_hash = 1353720154, display_name = "Flatbed", manufacturer = "MTL", class_id = 10, - class_name = "INDUSTRIAL" + class_name = "INDUSTRIAL", + enhanced_only = false }, flatbed2 = { model_hash = 2412056353, display_name = "Custom Flatbed", manufacturer = "MTL", class_id = 10, - class_name = "INDUSTRIAL" + class_name = "INDUSTRIAL", + enhanced_only = false }, fmj = { model_hash = 1426219628, display_name = "FMJ", manufacturer = "Vapid", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, fmj2 = { model_hash = 3287642921, display_name = "FMJ MK V", manufacturer = "Vapid", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, forklift = { model_hash = 1491375716, display_name = "Forklift", manufacturer = "HVY", class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, formula = { model_hash = 340154634, display_name = "PR4", manufacturer = "Progen", class_id = 22, - class_name = "OPEN_WHEEL" + class_name = "OPEN_WHEEL", + enhanced_only = false }, formula2 = { model_hash = 2334210311, display_name = "R88", manufacturer = "Ocelot", class_id = 22, - class_name = "OPEN_WHEEL" + class_name = "OPEN_WHEEL", + enhanced_only = false }, fq2 = { model_hash = 3157435195, display_name = "FQ 2", manufacturer = "Fathom", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, fr36 = { model_hash = 3829141989, display_name = "FR36", manufacturer = "Fathom", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, freecrawler = { model_hash = 4240635011, display_name = "Freecrawler", manufacturer = "Canis", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, freight = { model_hash = 1030400667, display_name = "Freight Train", manufacturer = nil, class_id = 21, - class_name = "RAIL" + class_name = "RAIL", + enhanced_only = false }, freight2 = { model_hash = 3852738056, display_name = "Freight Train", manufacturer = nil, class_id = 21, - class_name = "RAIL" + class_name = "RAIL", + enhanced_only = false }, freightcar = { model_hash = 184361638, display_name = "Freight Train", manufacturer = nil, class_id = 21, - class_name = "RAIL" + class_name = "RAIL", + enhanced_only = false }, freightcar2 = { model_hash = 3186376089, display_name = "Freight Train", manufacturer = nil, class_id = 21, - class_name = "RAIL" + class_name = "RAIL", + enhanced_only = false }, freightcar3 = { model_hash = 2420957787, display_name = "Freight Train", manufacturer = nil, class_id = 21, - class_name = "RAIL" + class_name = "RAIL", + enhanced_only = false }, freightcont1 = { model_hash = 920453016, display_name = "Freight Train", manufacturer = nil, class_id = 21, - class_name = "RAIL" + class_name = "RAIL", + enhanced_only = false }, freightcont2 = { model_hash = 240201337, display_name = "Freight Train", manufacturer = nil, class_id = 21, - class_name = "RAIL" + class_name = "RAIL", + enhanced_only = false }, freightgrain = { model_hash = 642617954, display_name = "Freight Train", manufacturer = nil, class_id = 21, - class_name = "RAIL" + class_name = "RAIL", + enhanced_only = false }, freighttrailer = { model_hash = 3517691494, display_name = nil, manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, frogger = { model_hash = 744705981, display_name = "Frogger", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, frogger2 = { model_hash = 1949211328, display_name = "Frogger", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, fugitive = { model_hash = 1909141499, display_name = "Fugitive", manufacturer = "Cheval", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, furia = { model_hash = 960812448, display_name = "Furia", manufacturer = "Grotti", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, furoregt = { model_hash = 3205927392, display_name = "Furore GT", manufacturer = "Lampadati", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, fusilade = { model_hash = 499169875, display_name = "Fusilade", manufacturer = "Schyster", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, futo = { model_hash = 2016857647, display_name = "Futo", manufacturer = "Karin", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, futo2 = { model_hash = 2787736776, display_name = "Futo GTX", manufacturer = "Karin", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, gargoyle = { model_hash = 741090084, display_name = "Gargoyle", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, gauntlet = { model_hash = 2494797253, display_name = "Gauntlet", manufacturer = "Bravado", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, gauntlet2 = { model_hash = 349315417, display_name = "Redwood Gauntlet", manufacturer = "Bravado", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, gauntlet3 = { model_hash = 722226637, display_name = "Gauntlet Classic", manufacturer = "Bravado", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, gauntlet4 = { model_hash = 1934384720, display_name = "Gauntlet Hellfire", manufacturer = "Bravado", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, gauntlet5 = { model_hash = 2172320429, display_name = "Gauntlet Classic Custom", manufacturer = "Bravado", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, gauntlet6 = { model_hash = 1336514315, display_name = "Hotring Hellfire", manufacturer = "Bravado", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, gb200 = { model_hash = 1909189272, display_name = "GB200", manufacturer = "Vapid", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, gburrito = { model_hash = 2549763894, display_name = "Gang Burrito", manufacturer = "Declasse", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, gburrito2 = { model_hash = 296357396, display_name = "Gang Burrito", manufacturer = "Declasse", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, glendale = { model_hash = 75131841, display_name = "Glendale", manufacturer = "Benefactor", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, glendale2 = { model_hash = 3381377750, display_name = "Glendale Custom", manufacturer = "Benefactor", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, gp1 = { model_hash = 1234311532, display_name = "GP1", manufacturer = "Progen", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, graintrailer = { model_hash = 1019737494, display_name = "Graintrailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, granger = { model_hash = 2519238556, display_name = "Granger", manufacturer = "Declasse", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, granger2 = { model_hash = 4033620423, display_name = "Granger 3600LX", manufacturer = "Declasse", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, greenwood = { model_hash = 40817712, display_name = "Greenwood", manufacturer = "Bravado", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, gresley = { model_hash = 2751205197, display_name = "Gresley", manufacturer = "Bravado", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, growler = { model_hash = 1304459735, display_name = "Growler", manufacturer = "Pfister", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, gt500 = { model_hash = 2215179066, display_name = "GT500", manufacturer = "Grotti", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, gt750 = { model_hash = 1380582820, display_name = "GT750", manufacturer = "Grotti", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, guardian = { model_hash = 2186977100, display_name = "Guardian", manufacturer = "Vapid", class_id = 10, - class_name = "INDUSTRIAL" + class_name = "INDUSTRIAL", + enhanced_only = false }, habanero = { model_hash = 884422927, display_name = "Habanero", manufacturer = "Emperor", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, hakuchou = { model_hash = 1265391242, display_name = "Hakuchou", manufacturer = "Shitzu", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, hakuchou2 = { model_hash = 4039289119, display_name = "Hakuchou Drag", manufacturer = "Shitzu", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, halftrack = { model_hash = 4262731174, display_name = "Half-track", manufacturer = "Bravado", class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, handler = { model_hash = 444583674, display_name = "Dock Handler", manufacturer = nil, class_id = 10, - class_name = "INDUSTRIAL" + class_name = "INDUSTRIAL", + enhanced_only = false }, hardy = { model_hash = 1580292663, display_name = "Hardy", manufacturer = "Annis", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, hauler = { model_hash = 1518533038, display_name = "Hauler", manufacturer = "JoBuilt", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, hauler2 = { model_hash = 387748548, display_name = "Hauler Custom", manufacturer = "JoBuilt", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, havok = { model_hash = 2310691317, display_name = "Havok", manufacturer = "Nagasaki", class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, hellion = { model_hash = 3932816511, display_name = "Hellion", manufacturer = "Annis", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, hermes = { model_hash = 15219735, display_name = "Hermes", manufacturer = "Albany", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, hexer = { model_hash = 301427732, display_name = "Hexer", manufacturer = "LCC", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, hotknife = { model_hash = 37348240, display_name = "Hotknife", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, hotring = { model_hash = 1115909093, display_name = "Hotring Sabre", manufacturer = "Declasse", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, howard = { model_hash = 3287439187, display_name = "Howard NX-25", manufacturer = "Buckingham", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, hunter = { model_hash = 4252008158, display_name = "FH-1 Hunter", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, huntley = { model_hash = 486987393, display_name = "Huntley S", manufacturer = "Enus", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, hustler = { model_hash = 600450546, display_name = "Hustler", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, hydra = { model_hash = 970385471, display_name = "Hydra", manufacturer = "Mammoth", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, ignus = { model_hash = 2850852987, display_name = "Ignus", manufacturer = "Pegassi", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, ignus2 = { model_hash = 956849991, display_name = "Weaponized Ignus", manufacturer = "Pegassi", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = true }, imorgon = { model_hash = 3162245632, display_name = "Imorgon", manufacturer = "Overflod", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, impaler = { model_hash = 2198276962, display_name = "Impaler", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, impaler2 = { model_hash = 1009171724, display_name = "Apocalypse Impaler", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, impaler3 = { model_hash = 2370166601, display_name = "Future Shock Impaler", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, impaler4 = { model_hash = 2550461639, display_name = "Nightmare Impaler", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, impaler5 = { model_hash = 3816328113, display_name = "Impaler SZ", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, impaler6 = { model_hash = 4116524922, display_name = "Impaler LX", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, imperator = { model_hash = 444994115, display_name = "Apocalypse Imperator", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, imperator2 = { model_hash = 1637620610, display_name = "Future Shock Imperator", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, imperator3 = { model_hash = 3539435063, display_name = "Nightmare Imperator", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, inductor = { model_hash = 3397143273, display_name = "Inductor", manufacturer = nil, class_id = 13, - class_name = "CYCLE" + class_name = "CYCLE", + enhanced_only = false }, inductor2 = { model_hash = 2311345272, display_name = "Junk Energy Inductor", manufacturer = nil, class_id = 13, - class_name = "CYCLE" + class_name = "CYCLE", + enhanced_only = false }, infernus = { model_hash = 418536135, display_name = "Infernus", manufacturer = "Pegassi", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, infernus2 = { model_hash = 2889029532, display_name = "Infernus Classic", manufacturer = "Pegassi", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, ingot = { model_hash = 3005245074, display_name = "Ingot", manufacturer = "Vulcar", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, innovation = { model_hash = 4135840458, display_name = "Innovation", manufacturer = "LCC", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, insurgent = { model_hash = 2434067162, display_name = "Insurgent Pick-Up", manufacturer = "HVY", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, insurgent2 = { model_hash = 2071877360, display_name = "Insurgent", manufacturer = "HVY", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, insurgent3 = { model_hash = 2370534026, display_name = "Insurgent Pick-Up Custom", manufacturer = "HVY", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, intruder = { model_hash = 886934177, display_name = "Intruder", manufacturer = "Karin", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, issi2 = { model_hash = 3117103977, display_name = "Issi", manufacturer = "Weeny", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, issi3 = { model_hash = 931280609, display_name = "Issi Classic", manufacturer = "Weeny", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, issi4 = { model_hash = 628003514, display_name = "Apocalypse Issi", manufacturer = "Weeny", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, issi5 = { model_hash = 1537277726, display_name = "Future Shock Issi", manufacturer = "Weeny", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, issi6 = { model_hash = 1239571361, display_name = "Nightmare Issi", manufacturer = "Weeny", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, issi7 = { model_hash = 1854776567, display_name = "Issi Sport", manufacturer = "Weeny", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, issi8 = { model_hash = 1550581940, display_name = "Issi Rally", manufacturer = "Weeny", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, itali2 = { model_hash = 893780296, display_name = "Itali Classic", manufacturer = "Grotti", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, italigtb = { model_hash = 2246633323, display_name = "Itali GTB", manufacturer = "Progen", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, italigtb2 = { model_hash = 3812247419, display_name = "Itali GTB Custom", manufacturer = "Progen", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, italigto = { model_hash = 3963499524, display_name = "Itali GTO", manufacturer = "Grotti", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, italirsx = { model_hash = 3145241962, display_name = "Itali RSX", manufacturer = "Grotti", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, iwagen = { model_hash = 662793086, display_name = "I-Wagen", manufacturer = "Obey", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, jackal = { model_hash = 3670438162, display_name = "Jackal", manufacturer = "Ocelot", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, jb700 = { model_hash = 1051415893, display_name = "JB 700", manufacturer = "Dewbauchee", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, jb7002 = { model_hash = 394110044, display_name = "JB 700W", manufacturer = "Dewbauchee", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, jester = { model_hash = 2997294755, display_name = "Jester", manufacturer = "Dinka", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, jester2 = { model_hash = 3188613414, display_name = "Jester (Racecar)", manufacturer = "Dinka", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, jester3 = { model_hash = 4080061290, display_name = "Jester Classic", manufacturer = "Dinka", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, jester4 = { model_hash = 2712905841, display_name = "Jester RR", manufacturer = "Dinka", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, jester5 = { model_hash = 1484920335, display_name = "Jester RR Widebody", manufacturer = "Dinka", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, jet = { model_hash = 1058115860, display_name = "Jet", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, jetmax = { model_hash = 861409633, display_name = "Jetmax", manufacturer = "Shitzu", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, journey = { model_hash = 4174679674, display_name = "Journey", manufacturer = "Zirconium", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, journey2 = { model_hash = 2667889793, display_name = "Journey II", manufacturer = "Zirconium", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, jubilee = { model_hash = 461465043, display_name = "Jubilee", manufacturer = "Enus", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, jugular = { model_hash = 4086055493, display_name = "Jugular", manufacturer = "Ocelot", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, kalahari = { model_hash = 92612664, display_name = "Kalahari", manufacturer = "Canis", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, kamacho = { model_hash = 4173521127, display_name = "Kamacho", manufacturer = "Canis", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, kanjo = { model_hash = 409049982, display_name = "Blista Kanjo", manufacturer = "Dinka", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, kanjosj = { model_hash = 4230891418, display_name = "Kanjo SJ", manufacturer = "Dinka", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, keitora = { model_hash = 3892551591, display_name = "Keitora", manufacturer = "Shitzu", class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, khamelion = { model_hash = 544021352, display_name = "Khamelion", manufacturer = "Hijak", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, khanjali = { model_hash = 2859440138, display_name = "TM-02 Khanjali", manufacturer = nil, class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, komoda = { model_hash = 3460613305, display_name = "Komoda", manufacturer = "Lampadati", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, kosatka = { model_hash = 1336872304, display_name = "Kosatka", manufacturer = "RUNE", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, krieger = { model_hash = 3630826055, display_name = "Krieger", manufacturer = "Benefactor", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, kuruma = { model_hash = 2922118804, display_name = "Kuruma", manufacturer = "Karin", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, kuruma2 = { model_hash = 410882957, display_name = "Kuruma (Armored)", manufacturer = "Karin", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, l35 = { model_hash = 2531292011, display_name = "Walton L35", manufacturer = "Declasse", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, l352 = { model_hash = 691148275, display_name = "Walton L35 Stock", manufacturer = "Declasse", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, landstalker = { model_hash = 1269098716, display_name = "Landstalker", manufacturer = "Dundreary", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, landstalker2 = { model_hash = 3456868130, display_name = "Landstalker XL", manufacturer = "Dundreary", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, lazer = { model_hash = 3013282534, display_name = "P-996 LAZER", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, le7b = { model_hash = 3062131285, display_name = "RE-7B", manufacturer = "Annis", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, lectro = { model_hash = 640818791, display_name = "Lectro", manufacturer = "Principe", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, lguard = { model_hash = 469291905, display_name = "Lifeguard", manufacturer = "Declasse", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, limo2 = { model_hash = 4180339789, display_name = "Turreted Limo", manufacturer = "Benefactor", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, lm87 = { model_hash = 4284049613, display_name = "LM87", manufacturer = "Benefactor", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, locust = { model_hash = 3353694737, display_name = "Locust", manufacturer = "Ocelot", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, longfin = { model_hash = 1861786828, display_name = "Longfin", manufacturer = "Shitzu", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, luiva = { model_hash = 3356898886, display_name = "Luiva", manufacturer = "Progen", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, lurcher = { model_hash = 2068293287, display_name = "Lurcher", manufacturer = "Albany", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, luxor = { model_hash = 621481054, display_name = "Luxor", manufacturer = "Buckingham", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, luxor2 = { model_hash = 3080673438, display_name = "Luxor Deluxe", manufacturer = "Buckingham", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, lynx = { model_hash = 482197771, display_name = "Lynx", manufacturer = "Ocelot", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, mamba = { model_hash = 2634021974, display_name = "Mamba", manufacturer = "Declasse", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, mammatus = { model_hash = 2548391185, display_name = "Mammatus", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, manana = { model_hash = 2170765704, display_name = "Manana", manufacturer = "Albany", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, manana2 = { model_hash = 1717532765, display_name = "Manana Custom", manufacturer = "Albany", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, manchez = { model_hash = 2771538552, display_name = "Manchez", manufacturer = "Maibatsu", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, manchez2 = { model_hash = 1086534307, display_name = "Manchez Scout", manufacturer = "Maibatsu", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, manchez3 = { model_hash = 1384502824, display_name = "Manchez Scout C", manufacturer = "Maibatsu", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, marquis = { model_hash = 3251507587, display_name = "Marquis", manufacturer = "Dinka", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, marshall = { model_hash = 1233534620, display_name = "Marshall", manufacturer = "Cheval", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, massacro = { model_hash = 4152024626, display_name = "Massacro", manufacturer = "Dewbauchee", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, massacro2 = { model_hash = 3663206819, display_name = "Massacro (Racecar)", manufacturer = "Dewbauchee", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, maverick = { model_hash = 2634305738, display_name = "Maverick", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, maverick2 = { model_hash = 347619240, display_name = "Higgins Helitours Maverick", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, menacer = { model_hash = 2044532910, display_name = "Menacer", manufacturer = "HVY", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, mesa = { model_hash = 914654722, display_name = "Mesa", manufacturer = "Canis", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, mesa2 = { model_hash = 3546958660, display_name = "Mesa", manufacturer = "Canis", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, mesa3 = { model_hash = 2230595153, display_name = "Mesa", manufacturer = "Canis", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, metrotrain = { model_hash = 868868440, display_name = "Freight Train", manufacturer = nil, class_id = 21, - class_name = "RAIL" + class_name = "RAIL", + enhanced_only = false }, michelli = { model_hash = 1046206681, display_name = "Michelli GT", manufacturer = "Lampadati", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, microlight = { model_hash = 2531412055, display_name = "Ultralight", manufacturer = "Nagasaki", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, miljet = { model_hash = 165154707, display_name = "Miljet", manufacturer = "Buckingham", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, minimus = { model_hash = 3193860278, display_name = "Minimus", manufacturer = "Annis", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, minitank = { model_hash = 3040635986, display_name = "Invade and Persuade Tank", manufacturer = nil, class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, minivan = { model_hash = 3984502180, display_name = "Minivan", manufacturer = "Vapid", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, minivan2 = { model_hash = 3168702960, display_name = "Minivan Custom", manufacturer = "Vapid", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, mixer = { model_hash = 3510150843, display_name = "Mixer", manufacturer = "HVY", class_id = 10, - class_name = "INDUSTRIAL" + class_name = "INDUSTRIAL", + enhanced_only = false }, mixer2 = { model_hash = 475220373, display_name = "Mixer", manufacturer = "HVY", class_id = 10, - class_name = "INDUSTRIAL" + class_name = "INDUSTRIAL", + enhanced_only = false }, mogul = { model_hash = 3545667823, display_name = "Mogul", manufacturer = "Mammoth", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, molotok = { model_hash = 1565978651, display_name = "V-65 Molotok", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, monroe = { model_hash = 3861591579, display_name = "Monroe", manufacturer = "Pegassi", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, monster = { model_hash = 3449006043, display_name = "Monster", manufacturer = "Vapid", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, monster3 = { model_hash = 1721676810, display_name = "Apocalypse Sasquatch", manufacturer = "Bravado", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, monster4 = { model_hash = 840387324, display_name = "Future Shock Sasquatch", manufacturer = "Bravado", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, monster5 = { model_hash = 3579220348, display_name = "Nightmare Sasquatch", manufacturer = "Bravado", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, monstrociti = { model_hash = 802856453, display_name = "MonstroCiti", manufacturer = "Maibatsu", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, moonbeam = { model_hash = 525509695, display_name = "Moonbeam", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, moonbeam2 = { model_hash = 1896491931, display_name = "Moonbeam Custom", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, mower = { model_hash = 1783355638, display_name = "Lawn Mower", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, mule = { model_hash = 904750859, display_name = "Mule", manufacturer = "Maibatsu", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, mule2 = { model_hash = 3244501995, display_name = "Mule", manufacturer = "Maibatsu", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, mule3 = { model_hash = 2242229361, display_name = "Mule", manufacturer = "Maibatsu", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, mule4 = { model_hash = 1945374990, display_name = "Mule Custom", manufacturer = "Maibatsu", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, mule5 = { model_hash = 1343932732, display_name = "Mule", manufacturer = "Maibatsu", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, nebula = { model_hash = 3412338231, display_name = "Nebula Turbo", manufacturer = "Vulcar", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, nemesis = { model_hash = 3660088182, display_name = "Nemesis", manufacturer = "Principe", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, neo = { model_hash = 2674840994, display_name = "Neo", manufacturer = "Vysser", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, neon = { model_hash = 2445973230, display_name = "Neon", manufacturer = "Pfister", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, nero = { model_hash = 1034187331, display_name = "Nero", manufacturer = "Truffade", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, nero2 = { model_hash = 1093792632, display_name = "Nero Custom", manufacturer = "Truffade", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, nightblade = { model_hash = 2688780135, display_name = "Nightblade", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, nightshade = { model_hash = 2351681756, display_name = "Nightshade", manufacturer = "Imponte", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, nightshark = { model_hash = 433954513, display_name = "Nightshark", manufacturer = "HVY", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, nimbus = { model_hash = 2999939664, display_name = "Nimbus", manufacturer = "Buckingham", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, ninef = { model_hash = 1032823388, display_name = "9F", manufacturer = "Obey", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, ninef2 = { model_hash = 2833484545, display_name = "9F Cabrio", manufacturer = "Obey", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, niobe = { model_hash = 1881415402, display_name = "Niobe", manufacturer = "Ubermacht", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, nokota = { model_hash = 1036591958, display_name = "P-45 Nokota", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, novak = { model_hash = 2465530446, display_name = "Novak", manufacturer = "Lampadati", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, omnis = { model_hash = 3517794615, display_name = "Omnis", manufacturer = "Obey", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, omnisegt = { model_hash = 3789743831, display_name = "Omnis e-GT", manufacturer = "Obey", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, openwheel1 = { model_hash = 1492612435, display_name = "BR8", manufacturer = "Benefactor", class_id = 22, - class_name = "OPEN_WHEEL" + class_name = "OPEN_WHEEL", + enhanced_only = false }, openwheel2 = { model_hash = 1181339704, display_name = "DR1", manufacturer = "Declasse", class_id = 22, - class_name = "OPEN_WHEEL" + class_name = "OPEN_WHEEL", + enhanced_only = false }, oppressor = { model_hash = 884483972, display_name = "Oppressor", manufacturer = "Pegassi", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, oppressor2 = { model_hash = 2069146067, display_name = "Oppressor Mk II", manufacturer = "Pegassi", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, oracle = { model_hash = 1348744438, display_name = "Oracle XS", manufacturer = "Ubermacht", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, oracle2 = { model_hash = 3783366066, display_name = "Oracle", manufacturer = "Ubermacht", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, osiris = { model_hash = 1987142870, display_name = "Osiris", manufacturer = "Pegassi", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, outlaw = { model_hash = 408825843, display_name = "Outlaw", manufacturer = "Nagasaki", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, packer = { model_hash = 569305213, display_name = "Packer", manufacturer = "MTL", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, panthere = { model_hash = 2100457220, display_name = "Panthere", manufacturer = "Toundra", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, panto = { model_hash = 3863274624, display_name = "Panto", manufacturer = "Benefactor", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, paradise = { model_hash = 1488164764, display_name = "Paradise", manufacturer = "Bravado", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, paragon = { model_hash = 3847255899, display_name = "Paragon R", manufacturer = "Enus", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, paragon2 = { model_hash = 1416466158, display_name = "Paragon R (Armored)", manufacturer = "Enus", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, paragon3 = { model_hash = 3348919626, display_name = "Paragon S", manufacturer = "Enus", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, pariah = { model_hash = 867799010, display_name = "Pariah", manufacturer = "Ocelot", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, patriot = { model_hash = 3486509883, display_name = "Patriot", manufacturer = "Mammoth", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, patriot2 = { model_hash = 3874056184, display_name = "Patriot Stretch", manufacturer = "Mammoth", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, patriot3 = { model_hash = 3624880708, display_name = "Patriot Mil-Spec", manufacturer = "Mammoth", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, patrolboat = { model_hash = 4018222598, display_name = "Kurtz 31 Patrol Boat", manufacturer = nil, class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, pbus = { model_hash = 2287941233, display_name = "Police Prison Bus", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, pbus2 = { model_hash = 345756458, display_name = "Festival Bus", manufacturer = nil, class_id = 17, - class_name = "SERVICE" + class_name = "SERVICE", + enhanced_only = false }, pcj = { model_hash = 3385765638, display_name = "PCJ 600", manufacturer = "Shitzu", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, penetrator = { model_hash = 2536829930, display_name = "Penetrator", manufacturer = "Ocelot", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, penumbra = { model_hash = 3917501776, display_name = "Penumbra", manufacturer = "Maibatsu", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, penumbra2 = { model_hash = 3663644634, display_name = "Penumbra FF", manufacturer = "Maibatsu", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, peyote = { model_hash = 1830407356, display_name = "Peyote", manufacturer = "Vapid", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, peyote2 = { model_hash = 2490551588, display_name = "Peyote Gasser", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, peyote3 = { model_hash = 1107404867, display_name = "Peyote Custom", manufacturer = "Vapid", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, pfister811 = { model_hash = 2465164804, display_name = "811", manufacturer = "Pfister", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, phantom = { model_hash = 2157618379, display_name = "Phantom", manufacturer = "JoBuilt", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, phantom2 = { model_hash = 2645431192, display_name = "Phantom Wedge", manufacturer = "JoBuilt", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, phantom3 = { model_hash = 177270108, display_name = "Phantom Custom", manufacturer = "JoBuilt", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, phantom4 = { model_hash = 4165683409, display_name = "Phantom", manufacturer = "JoBuilt", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, phoenix = { model_hash = 2199527893, display_name = "Phoenix", manufacturer = "Imponte", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, picador = { model_hash = 1507916787, display_name = "Picador", manufacturer = "Cheval", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, pigalle = { model_hash = 1078682497, display_name = "Pigalle", manufacturer = "Lampadati", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, pipistrello = { model_hash = 4071505793, display_name = "Pipistrello", manufacturer = "Overflod", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, pizzaboy = { model_hash = 1968807591, display_name = "Pizza Boy", manufacturer = "Pegassi", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, polbuffalo = { model_hash = 287906327, display_name = "Buffalo Cruiser", manufacturer = "Bravado", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, polbuffalo6 = { model_hash = 617517171, display_name = "Buffalo STX Pursuit", manufacturer = "Bravado", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, polcaracara = { model_hash = 2346018232, display_name = "Caracara Pursuit", manufacturer = "Vapid", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, polcoquette4 = { model_hash = 2042703219, display_name = "Coquette D10 Pursuit", manufacturer = "Invetero", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, poldominator10 = { model_hash = 3521165271, display_name = "Dominator FX Interceptor", manufacturer = "Vapid", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, poldorado = { model_hash = 2666966727, display_name = "Dorado Cruiser", manufacturer = "Bravado", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, polfaction2 = { model_hash = 1891140410, display_name = "Outreach Faction", manufacturer = "Willard", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, polgauntlet = { model_hash = 3061199846, display_name = "Gauntlet Interceptor", manufacturer = "Bravado", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, polgreenwood = { model_hash = 1737348074, display_name = "Greenwood Cruiser", manufacturer = "Bravado", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, police = { model_hash = 2046537925, display_name = "Police Cruiser", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, police2 = { model_hash = 2667966721, display_name = "Police Cruiser", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, police3 = { model_hash = 1912215274, display_name = "Police Cruiser", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, police4 = { model_hash = 2321795001, display_name = "Unmarked Cruiser", manufacturer = "Vapid", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, police5 = { model_hash = 2620582743, display_name = "Stanier LE Cruiser", manufacturer = "Vapid", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, policeb = { model_hash = 4260343491, display_name = "Police Bike", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, policeb2 = { model_hash = 2373455159, display_name = "Police Bike", manufacturer = "Western", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, policeold1 = { model_hash = 2758042359, display_name = "Police Rancher", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, policeold2 = { model_hash = 2515846680, display_name = "Police Roadcruiser", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, policet = { model_hash = 456714581, display_name = "Police Transporter", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, policet3 = { model_hash = 2850111293, display_name = "Burrito (Bail Enforcement)", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, polimpaler5 = { model_hash = 1249425552, display_name = "Impaler SZ Cruiser", manufacturer = "Declasse", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, polimpaler6 = { model_hash = 1452003510, display_name = "Impaler LX Cruiser", manufacturer = "Declasse", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, polmav = { model_hash = 353883353, display_name = "Police Maverick", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, polterminus = { model_hash = 2973836112, display_name = "Terminus Patrol", manufacturer = "Canis", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, pony = { model_hash = 4175309224, display_name = "Pony", manufacturer = "Brute", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, pony2 = { model_hash = 943752001, display_name = "Pony", manufacturer = "Brute", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, postlude = { model_hash = 4000288633, display_name = "Postlude", manufacturer = "Dinka", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, pounder = { model_hash = 2112052861, display_name = "Pounder", manufacturer = "MTL", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, pounder2 = { model_hash = 1653666139, display_name = "Pounder Custom", manufacturer = "MTL", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, powersurge = { model_hash = 2908631255, display_name = "Powersurge", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, prairie = { model_hash = 2844316578, display_name = "Prairie", manufacturer = "Bollokan", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, pranger = { model_hash = 741586030, display_name = "Park Ranger", manufacturer = "Declasse", class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, predator = { model_hash = 3806844075, display_name = "Police Predator", manufacturer = nil, class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, premier = { model_hash = 2411098011, display_name = "Premier", manufacturer = "Declasse", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, previon = { model_hash = 1416471345, display_name = "Previon", manufacturer = "Karin", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, primo = { model_hash = 3144368207, display_name = "Primo", manufacturer = "Albany", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, primo2 = { model_hash = 2254540506, display_name = "Primo Custom", manufacturer = "Albany", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, proptrailer = { model_hash = 356391690, display_name = nil, manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, prototipo = { model_hash = 2123327359, display_name = "X80 Proto", manufacturer = "Grotti", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, pyro = { model_hash = 2908775872, display_name = "Pyro", manufacturer = "Buckingham", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, r300 = { model_hash = 1076201208, display_name = "300R", manufacturer = "Annis", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, radi = { model_hash = 2643899483, display_name = "Radius", manufacturer = "Vapid", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, raiden = { model_hash = 2765724541, display_name = "Raiden", manufacturer = "Coil", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, raiju = { model_hash = 239897677, display_name = "F-160 Raiju", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, raketrailer = { model_hash = 390902130, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, rallytruck = { model_hash = 2191146052, display_name = "Dune", manufacturer = "MTL", class_id = 17, - class_name = "SERVICE" + class_name = "SERVICE", + enhanced_only = false }, rancherxl = { model_hash = 1645267888, display_name = "Rancher XL", manufacturer = "Declasse", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, rancherxl2 = { model_hash = 1933662059, display_name = "Rancher XL", manufacturer = "Declasse", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, rapidgt = { model_hash = 2360515092, display_name = "Rapid GT", manufacturer = "Dewbauchee", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, rapidgt2 = { model_hash = 1737773231, display_name = "Rapid GT", manufacturer = "Dewbauchee", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, rapidgt3 = { model_hash = 2049897956, display_name = "Rapid GT Classic", manufacturer = "Dewbauchee", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, rapidgt4 = { model_hash = 1761301369, display_name = "Rapid GT X", manufacturer = "Dewbauchee", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, raptor = { model_hash = 3620039993, display_name = "Raptor", manufacturer = "BF", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, ratbike = { model_hash = 1873600305, display_name = "Rat Bike", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, ratel = { model_hash = 3758861739, display_name = "Ratel", manufacturer = "Vapid", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, ratloader = { model_hash = 3627815886, display_name = "Rat-Loader", manufacturer = nil, class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, ratloader2 = { model_hash = 3705788919, display_name = "Rat-Truck", manufacturer = "Bravado", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, rcbandito = { model_hash = 4008920556, display_name = "RC Bandito", manufacturer = nil, class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, reaper = { model_hash = 234062309, display_name = "Reaper", manufacturer = "Pegassi", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, rebel = { model_hash = 3087195462, display_name = "Rusty Rebel", manufacturer = "Karin", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, rebel2 = { model_hash = 2249373259, display_name = "Rebel", manufacturer = "Karin", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, rebla = { model_hash = 83136452, display_name = "Rebla GTS", manufacturer = "Ubermacht", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, reever = { model_hash = 1993851908, display_name = "Reever", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, regina = { model_hash = 4280472072, display_name = "Regina", manufacturer = "Dundreary", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, remus = { model_hash = 1377217886, display_name = "Remus", manufacturer = "Annis", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, rentalbus = { model_hash = 3196165219, display_name = "Rental Shuttle Bus", manufacturer = nil, class_id = 17, - class_name = "SERVICE" + class_name = "SERVICE", + enhanced_only = false }, retinue = { model_hash = 1841130506, display_name = "Retinue", manufacturer = "Vapid", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, retinue2 = { model_hash = 2031587082, display_name = "Retinue Mk II", manufacturer = "Vapid", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, revolter = { model_hash = 3884762073, display_name = "Revolter", manufacturer = "Ubermacht", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, rhapsody = { model_hash = 841808271, display_name = "Rhapsody", manufacturer = "Declasse", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, rhinehart = { model_hash = 2439462158, display_name = "Rhinehart", manufacturer = "Ubermacht", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, rhino = { model_hash = 782665360, display_name = "Rhino Tank", manufacturer = nil, class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, riata = { model_hash = 2762269779, display_name = "Riata", manufacturer = "Vapid", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, riot = { model_hash = 3089277354, display_name = "Police Riot", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, riot2 = { model_hash = 2601952180, display_name = "RCV", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, ripley = { model_hash = 3448987385, display_name = "Ripley", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, rocoto = { model_hash = 2136773105, display_name = "Rocoto", manufacturer = "Obey", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, rogue = { model_hash = 3319621991, display_name = "Rogue", manufacturer = "Western", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, romero = { model_hash = 627094268, display_name = "Romero Hearse", manufacturer = "Chariot", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, rrocket = { model_hash = 916547552, display_name = "Rampant Rocket", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, rt3000 = { model_hash = 3842363289, display_name = "RT3000", manufacturer = "Dinka", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, rubble = { model_hash = 2589662668, display_name = "Rubble", manufacturer = "JoBuilt", class_id = 10, - class_name = "INDUSTRIAL" + class_name = "INDUSTRIAL", + enhanced_only = false }, ruffian = { model_hash = 3401388520, display_name = "Ruffian", manufacturer = "Pegassi", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, ruiner = { model_hash = 4067225593, display_name = "Ruiner", manufacturer = "Imponte", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, ruiner2 = { model_hash = 941494461, display_name = "Ruiner 2000", manufacturer = "Imponte", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, ruiner3 = { model_hash = 777714999, display_name = "Ruiner", manufacturer = "Imponte", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, ruiner4 = { model_hash = 1706945532, display_name = "Ruiner ZZ-8", manufacturer = "Imponte", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, rumpo = { model_hash = 1162065741, display_name = "Rumpo", manufacturer = "Bravado", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, rumpo2 = { model_hash = 2518351607, display_name = "Rumpo", manufacturer = "Bravado", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, rumpo3 = { model_hash = 1475773103, display_name = "Rumpo Custom", manufacturer = "Bravado", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, ruston = { model_hash = 719660200, display_name = "Ruston", manufacturer = "Hijak", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, s80 = { model_hash = 3970348707, display_name = "S80RR", manufacturer = "Annis", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, s95 = { model_hash = 1133471123, display_name = "S95", manufacturer = "Karin", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = true }, sabregt = { model_hash = 2609945748, display_name = "Sabre Turbo", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, sabregt2 = { model_hash = 223258115, display_name = "Sabre Turbo Custom", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, sadler = { model_hash = 3695398481, display_name = "Sadler", manufacturer = "Vapid", class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, sadler2 = { model_hash = 734217681, display_name = "Sadler", manufacturer = "Vapid", class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, sanchez = { model_hash = 788045382, display_name = "Sanchez (livery)", manufacturer = "Maibatsu", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, sanchez2 = { model_hash = 2841686334, display_name = "Sanchez", manufacturer = "Maibatsu", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, sanctus = { model_hash = 1491277511, display_name = "Sanctus", manufacturer = "LCC", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, sandking = { model_hash = 3105951696, display_name = "Sandking XL", manufacturer = "Vapid", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, sandking2 = { model_hash = 989381445, display_name = "Sandking SWB", manufacturer = "Vapid", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, savage = { model_hash = 4212341271, display_name = "Savage", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, savestra = { model_hash = 903794909, display_name = "Savestra", manufacturer = "Annis", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, sc1 = { model_hash = 1352136073, display_name = "SC1", manufacturer = "Ubermacht", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, scarab = { model_hash = 3147997943, display_name = "Apocalypse Scarab", manufacturer = "HVY", class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, scarab2 = { model_hash = 1542143200, display_name = "Future Shock Scarab", manufacturer = "HVY", class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, scarab3 = { model_hash = 3715219435, display_name = "Nightmare Scarab", manufacturer = "HVY", class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, schafter2 = { model_hash = 3039514899, display_name = "Schafter", manufacturer = "Benefactor", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, schafter3 = { model_hash = 2809443750, display_name = "Schafter V12", manufacturer = "Benefactor", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, schafter4 = { model_hash = 1489967196, display_name = "Schafter LWB", manufacturer = "Benefactor", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, schafter5 = { model_hash = 3406724313, display_name = "Schafter V12 (Armored)", manufacturer = "Benefactor", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, schafter6 = { model_hash = 1922255844, display_name = "Schafter LWB (Armored)", manufacturer = "Benefactor", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, schlagen = { model_hash = 3787471536, display_name = "Schlagen GT", manufacturer = "Benefactor", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, schwarzer = { model_hash = 3548084598, display_name = "Schwartzer", manufacturer = "Benefactor", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, scorcher = { model_hash = 4108429845, display_name = "Scorcher", manufacturer = nil, class_id = 13, - class_name = "CYCLE" + class_name = "CYCLE", + enhanced_only = false }, scramjet = { model_hash = 3656405053, display_name = "Scramjet", manufacturer = "Declasse", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, scrap = { model_hash = 2594165727, display_name = "Scrap Truck", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, seabreeze = { model_hash = 3902291871, display_name = "Seabreeze", manufacturer = "Western", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, seashark = { model_hash = 3264692260, display_name = "Seashark", manufacturer = "Speedophile", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, seashark2 = { model_hash = 3678636260, display_name = "Seashark", manufacturer = "Speedophile", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, seashark3 = { model_hash = 3983945033, display_name = "Seashark", manufacturer = "Speedophile", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, seasparrow = { model_hash = 3568198617, display_name = "Sea Sparrow", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, seasparrow2 = { model_hash = 1229411063, display_name = "Sparrow", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, seasparrow3 = { model_hash = 1593933419, display_name = "Sparrow", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, seminole = { model_hash = 1221512915, display_name = "Seminole", manufacturer = "Canis", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, seminole2 = { model_hash = 2484160806, display_name = "Seminole Frontier", manufacturer = "Canis", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, sentinel = { model_hash = 1349725314, display_name = "Sentinel XS", manufacturer = "Ubermacht", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, sentinel2 = { model_hash = 873639469, display_name = "Sentinel", manufacturer = "Ubermacht", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, sentinel3 = { model_hash = 1104234922, display_name = "Sentinel", manufacturer = "Ubermacht", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, sentinel4 = { model_hash = 2938086457, display_name = "Sentinel Classic Widebody", manufacturer = "Ubermacht", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, sentinel5 = { model_hash = 2709293299, display_name = "Sentinel GTS", manufacturer = "Ubermacht", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, sentinel6 = { model_hash = 3936328504, display_name = "Sentinel XS4", manufacturer = "Ubermacht", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, serrano = { model_hash = 1337041428, display_name = "Serrano", manufacturer = "Benefactor", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, seven70 = { model_hash = 2537130571, display_name = "Seven-70", manufacturer = "Dewbauchee", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, shamal = { model_hash = 3080461301, display_name = "Shamal", manufacturer = "Buckingham", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, sheava = { model_hash = 819197656, display_name = "ETR1", manufacturer = "Emperor", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, sheriff = { model_hash = 2611638396, display_name = "Sheriff Cruiser", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, sheriff2 = { model_hash = 1922257928, display_name = "Sheriff SUV", manufacturer = nil, class_id = 18, - class_name = "EMERGENCY" + class_name = "EMERGENCY", + enhanced_only = false }, shinobi = { model_hash = 1353120668, display_name = "Shinobi", manufacturer = "Nagasaki", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, shotaro = { model_hash = 3889340782, display_name = "Shotaro", manufacturer = "Nagasaki", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, skylift = { model_hash = 1044954915, display_name = "Skylift", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, slamtruck = { model_hash = 3249056020, display_name = "Slamtruck", manufacturer = "Vapid", class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, slamvan = { model_hash = 729783779, display_name = "Slamvan", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, slamvan2 = { model_hash = 833469436, display_name = "Lost Slamvan", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, slamvan3 = { model_hash = 1119641113, display_name = "Slamvan Custom", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, slamvan4 = { model_hash = 2233918197, display_name = "Apocalypse Slamvan", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, slamvan5 = { model_hash = 373261600, display_name = "Future Shock Slamvan", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, slamvan6 = { model_hash = 1742022738, display_name = "Nightmare Slamvan", manufacturer = "Vapid", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, sm722 = { model_hash = 775514032, display_name = "SM722", manufacturer = "Benefactor", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, sovereign = { model_hash = 743478836, display_name = "Sovereign", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, specter = { model_hash = 1886268224, display_name = "Specter", manufacturer = "Dewbauchee", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, specter2 = { model_hash = 1074745671, display_name = "Specter Custom", manufacturer = "Dewbauchee", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, speeder = { model_hash = 231083307, display_name = "Speeder", manufacturer = "Pegassi", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, speeder2 = { model_hash = 437538602, display_name = "Speeder", manufacturer = "Pegassi", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, speedo = { model_hash = 3484649228, display_name = "Speedo", manufacturer = "Vapid", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, speedo2 = { model_hash = 728614474, display_name = "Clown Van", manufacturer = "Vapid", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, speedo4 = { model_hash = 219613597, display_name = "Speedo Custom", manufacturer = "Vapid", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, speedo5 = { model_hash = 4250167832, display_name = "Speedo Custom", manufacturer = "Vapid", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, squaddie = { model_hash = 4192631813, display_name = "Squaddie", manufacturer = "Mammoth", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, squalo = { model_hash = 400514754, display_name = "Squalo", manufacturer = "Shitzu", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, stafford = { model_hash = 321186144, display_name = "Stafford", manufacturer = "Enus", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, stalion = { model_hash = 1923400478, display_name = "Stallion", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, stalion2 = { model_hash = 3893323758, display_name = "Burger Shot Stallion", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, stanier = { model_hash = 2817386317, display_name = "Stanier", manufacturer = "Vapid", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, starling = { model_hash = 2594093022, display_name = "LF-22 Starling", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, stinger = { model_hash = 1545842587, display_name = "Stinger", manufacturer = "Grotti", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, stingergt = { model_hash = 2196019706, display_name = "Stinger GT", manufacturer = "Grotti", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, stingertt = { model_hash = 1447690049, display_name = "Itali GTO Stinger TT", manufacturer = "Grotti", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, stockade = { model_hash = 1747439474, display_name = "Stockade", manufacturer = "Brute", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, stockade3 = { model_hash = 4080511798, display_name = "Stockade", manufacturer = "Brute", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, stockade4 = { model_hash = 1089816240, display_name = "Bobcat Security Stockade", manufacturer = "Brute", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, stratum = { model_hash = 1723137093, display_name = "Stratum", manufacturer = "Zirconium", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, streamer216 = { model_hash = 191916658, display_name = "Streamer216", manufacturer = "Mammoth", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, streiter = { model_hash = 1741861769, display_name = "Streiter", manufacturer = "Benefactor", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, stretch = { model_hash = 2333339779, display_name = "Stretch", manufacturer = "Dundreary", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, strikeforce = { model_hash = 1692272545, display_name = "B-11 Strikeforce", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, stromberg = { model_hash = 886810209, display_name = "Stromberg", manufacturer = "Ocelot", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, stryder = { model_hash = 301304410, display_name = "Stryder", manufacturer = "Nagasaki", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, stunt = { model_hash = 2172210288, display_name = "Mallard", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, submersible = { model_hash = 771711535, display_name = "Submersible", manufacturer = nil, class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, submersible2 = { model_hash = 3228633070, display_name = "Kraken", manufacturer = nil, class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, sugoi = { model_hash = 987469656, display_name = "Sugoi", manufacturer = "Dinka", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, sultan = { model_hash = 970598228, display_name = "Sultan", manufacturer = "Karin", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, sultan2 = { model_hash = 872704284, display_name = "Sultan Classic", manufacturer = "Karin", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, sultan3 = { model_hash = 4003946083, display_name = "Sultan RS Classic", manufacturer = "Karin", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, sultanrs = { model_hash = 3999278268, display_name = "Sultan RS", manufacturer = "Karin", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, suntrap = { model_hash = 4012021193, display_name = "Suntrap", manufacturer = "Shitzu", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, superd = { model_hash = 1123216662, display_name = "Super Diamond", manufacturer = "Enus", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, supervolito = { model_hash = 710198397, display_name = "SuperVolito", manufacturer = "Buckingham", class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, supervolito2 = { model_hash = 2623428164, display_name = "SuperVolito Carbon", manufacturer = "Buckingham", class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, surano = { model_hash = 384071873, display_name = "Surano", manufacturer = "Benefactor", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, surfer = { model_hash = 699456151, display_name = "Surfer", manufacturer = "BF", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, surfer2 = { model_hash = 2983726598, display_name = "Surfer", manufacturer = "BF", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, surfer3 = { model_hash = 3259477733, display_name = "Surfer Custom", manufacturer = "BF", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, surge = { model_hash = 2400073108, display_name = "Surge", manufacturer = "Cheval", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, suzume = { model_hash = 687627128, display_name = "Suzume", manufacturer = "Overflod", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, swift = { model_hash = 3955379698, display_name = "Swift", manufacturer = "Buckingham", class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, swift2 = { model_hash = 1075432268, display_name = "Swift Deluxe", manufacturer = "Buckingham", class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, swinger = { model_hash = 500482303, display_name = "Swinger", manufacturer = "Ocelot", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, t20 = { model_hash = 1663218586, display_name = "T20", manufacturer = "Progen", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, taco = { model_hash = 1951180813, display_name = "Taco Van", manufacturer = "Brute", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, tahoma = { model_hash = 3833117047, display_name = "Tahoma Coupe", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, tailgater = { model_hash = 3286105550, display_name = "Tailgater", manufacturer = "Obey", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, tailgater2 = { model_hash = 3050505892, display_name = "Tailgater S", manufacturer = "Obey", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, taipan = { model_hash = 3160260734, display_name = "Taipan", manufacturer = "Cheval", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, tampa = { model_hash = 972671128, display_name = "Tampa", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, tampa2 = { model_hash = 3223586949, display_name = "Drift Tampa", manufacturer = "Declasse", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, tampa3 = { model_hash = 3084515313, display_name = "Weaponized Tampa", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, tampa4 = { model_hash = 2786546796, display_name = "Tampa GT", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, tanker = { model_hash = 3564062519, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, tanker2 = { model_hash = 1956216962, display_name = nil, manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, tankercar = { model_hash = 586013744, display_name = "Freight Train", manufacturer = nil, class_id = 21, - class_name = "RAIL" + class_name = "RAIL", + enhanced_only = false }, taxi = { model_hash = 3338918751, display_name = "Taxi", manufacturer = nil, class_id = 17, - class_name = "SERVICE" + class_name = "SERVICE", + enhanced_only = false }, technical = { model_hash = 2198148358, display_name = "Technical", manufacturer = "Karin", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, technical2 = { model_hash = 1180875963, display_name = "Technical Aqua", manufacturer = "Karin", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, technical3 = { model_hash = 1356124575, display_name = "Technical Custom", manufacturer = "Karin", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, tempesta = { model_hash = 272929391, display_name = "Tempesta", manufacturer = "Pegassi", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, tenf = { model_hash = 3400983137, display_name = "10F", manufacturer = "Obey", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, tenf2 = { model_hash = 274946574, display_name = "10F Widebody", manufacturer = "Obey", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, terbyte = { model_hash = 2306538597, display_name = "Terrorbyte", manufacturer = "Benefactor", class_id = 20, - class_name = "COMMERCIAL" + class_name = "COMMERCIAL", + enhanced_only = false }, terminus = { model_hash = 167522317, display_name = "Terminus", manufacturer = "Canis", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, tezeract = { model_hash = 1031562256, display_name = "Tezeract", manufacturer = "Pegassi", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, thrax = { model_hash = 1044193113, display_name = "Thrax", manufacturer = "Truffade", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, thrust = { model_hash = 1836027715, display_name = "Thrust", manufacturer = "Dinka", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, thruster = { model_hash = 1489874736, display_name = "Thruster", manufacturer = "Mammoth", class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, tigon = { model_hash = 2936769864, display_name = "Tigon", manufacturer = "Lampadati", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, tiptruck = { model_hash = 48339065, display_name = "Tipper", manufacturer = "Brute", class_id = 10, - class_name = "INDUSTRIAL" + class_name = "INDUSTRIAL", + enhanced_only = false }, tiptruck2 = { model_hash = 3347205726, display_name = "Tipper", manufacturer = nil, class_id = 10, - class_name = "INDUSTRIAL" + class_name = "INDUSTRIAL", + enhanced_only = false }, titan = { model_hash = 1981688531, display_name = "Titan", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, titan2 = { model_hash = 858355070, display_name = "Titan 250 D", manufacturer = "Eberhard", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, toreador = { model_hash = 1455990255, display_name = "Toreador", manufacturer = "Pegassi", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, torero = { model_hash = 1504306544, display_name = "Torero", manufacturer = "Pegassi", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, torero2 = { model_hash = 4129572538, display_name = "Torero XO", manufacturer = "Pegassi", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, tornado = { model_hash = 464687292, display_name = "Tornado", manufacturer = "Declasse", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, tornado2 = { model_hash = 1531094468, display_name = "Tornado", manufacturer = "Declasse", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, tornado3 = { model_hash = 1762279763, display_name = "Tornado", manufacturer = "Declasse", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, tornado4 = { model_hash = 2261744861, display_name = "Tornado", manufacturer = "Declasse", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, tornado5 = { model_hash = 2497353967, display_name = "Tornado Custom", manufacturer = "Declasse", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, tornado6 = { model_hash = 2736567667, display_name = "Tornado Rat Rod", manufacturer = "Declasse", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, toro = { model_hash = 1070967343, display_name = "Toro", manufacturer = "Lampadati", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, toro2 = { model_hash = 908897389, display_name = "Toro", manufacturer = "Lampadati", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, toros = { model_hash = 3126015148, display_name = "Toros", manufacturer = "Pegassi", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, tourbus = { model_hash = 1941029835, display_name = "Tour Bus", manufacturer = nil, class_id = 17, - class_name = "SERVICE" + class_name = "SERVICE", + enhanced_only = false }, towtruck = { model_hash = 2971866336, display_name = "Tow Truck", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, towtruck2 = { model_hash = 3852654278, display_name = "Tow Truck", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, towtruck3 = { model_hash = 3623402354, display_name = "Tow Truck", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, towtruck4 = { model_hash = 3392937977, display_name = "Tow Truck", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, tr2 = { model_hash = 2078290630, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, tr3 = { model_hash = 1784254509, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, tr4 = { model_hash = 2091594960, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, tractor = { model_hash = 1641462412, display_name = "Tractor", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, tractor2 = { model_hash = 2218488798, display_name = "Fieldmaster", manufacturer = "Stanley", class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, tractor3 = { model_hash = 1445631933, display_name = "Fieldmaster", manufacturer = "Stanley", class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, trailerlarge = { model_hash = 1502869817, display_name = "Mobile Operations Center", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, trailerlogs = { model_hash = 2016027501, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, trailers = { model_hash = 3417488910, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, trailers2 = { model_hash = 2715434129, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, trailers3 = { model_hash = 2236089197, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, trailers4 = { model_hash = 3194418602, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, trailers5 = { model_hash = 2960513480, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, trailersmall = { model_hash = 712162987, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, trailersmall2 = { model_hash = 2413121211, display_name = "Anti-Aircraft Trailer", manufacturer = "Vom Feuer", class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, trash = { model_hash = 1917016601, display_name = "Trashmaster", manufacturer = nil, class_id = 17, - class_name = "SERVICE" + class_name = "SERVICE", + enhanced_only = false }, trash2 = { model_hash = 3039269212, display_name = "Trashmaster", manufacturer = nil, class_id = 17, - class_name = "SERVICE" + class_name = "SERVICE", + enhanced_only = false }, trflat = { model_hash = 2942498482, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, tribike = { model_hash = 1127861609, display_name = "Whippet Race Bike", manufacturer = nil, class_id = 13, - class_name = "CYCLE" + class_name = "CYCLE", + enhanced_only = false }, tribike2 = { model_hash = 3061159916, display_name = "Endurex Race Bike", manufacturer = nil, class_id = 13, - class_name = "CYCLE" + class_name = "CYCLE", + enhanced_only = false }, tribike3 = { model_hash = 3894672200, display_name = "Tri-Cycles Race Bike", manufacturer = nil, class_id = 13, - class_name = "CYCLE" + class_name = "CYCLE", + enhanced_only = false }, trophytruck = { model_hash = 101905590, display_name = "Trophy Truck", manufacturer = "Vapid", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, trophytruck2 = { model_hash = 3631668194, display_name = "Desert Raid", manufacturer = "Vapid", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, tropic = { model_hash = 290013743, display_name = "Tropic", manufacturer = "Shitzu", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, tropic2 = { model_hash = 1448677353, display_name = "Tropic", manufacturer = "Shitzu", class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, tropos = { model_hash = 1887331236, display_name = "Tropos Rallye", manufacturer = "Lampadati", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, tug = { model_hash = 2194326579, display_name = "Tug", manufacturer = nil, class_id = 14, - class_name = "BOAT" + class_name = "BOAT", + enhanced_only = false }, tula = { model_hash = 1043222410, display_name = "Tula", manufacturer = "Mammoth", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, tulip = { model_hash = 1456744817, display_name = "Tulip", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, tulip2 = { model_hash = 268758436, display_name = "Tulip M-100", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, turismo2 = { model_hash = 3312836369, display_name = "Turismo Classic", manufacturer = "Grotti", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, turismo3 = { model_hash = 4171974011, display_name = "Turismo Omaggio", manufacturer = "Grotti", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, turismor = { model_hash = 408192225, display_name = "Turismo R", manufacturer = "Grotti", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, tvtrailer = { model_hash = 2524324030, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, tvtrailer2 = { model_hash = 471034616, display_name = "Trailer", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, tyrant = { model_hash = 3918533058, display_name = "Tyrant", manufacturer = "Overflod", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, tyrus = { model_hash = 2067820283, display_name = "Tyrus", manufacturer = "Progen", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, uranus = { model_hash = 1534326199, display_name = "Uranus LozSpeed", manufacturer = "Vapid", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, utillitruck = { model_hash = 516990260, display_name = "Utility Truck", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, utillitruck2 = { model_hash = 887537515, display_name = "Utility Truck", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, utillitruck3 = { model_hash = 2132890591, display_name = "Utility Truck", manufacturer = nil, class_id = 11, - class_name = "UTILITY" + class_name = "UTILITY", + enhanced_only = false }, vacca = { model_hash = 338562499, display_name = "Vacca", manufacturer = "Pegassi", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, vader = { model_hash = 4154065143, display_name = "Vader", manufacturer = "Shitzu", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, vagner = { model_hash = 1939284556, display_name = "Vagner", manufacturer = "Dewbauchee", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, vagrant = { model_hash = 740289177, display_name = "Vagrant", manufacturer = "Maxwell", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, valkyrie = { model_hash = 2694714877, display_name = "Valkyrie", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, valkyrie2 = { model_hash = 1543134283, display_name = "Valkyrie MOD.0", manufacturer = nil, class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, vamos = { model_hash = 4245851645, display_name = "Vamos", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, vectre = { model_hash = 2754593701, display_name = "Vectre", manufacturer = "Emperor", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, velum = { model_hash = 2621610858, display_name = "Velum", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, velum2 = { model_hash = 1077420264, display_name = "Velum 5-Seater", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, verlierer2 = { model_hash = 1102544804, display_name = "Verlierer", manufacturer = "Bravado", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, verus = { model_hash = 298565713, display_name = "Verus", manufacturer = "Dinka", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, vestra = { model_hash = 1341619767, display_name = "Vestra", manufacturer = "Buckingham", class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, vetir = { model_hash = 2014313426, display_name = "Vetir", manufacturer = nil, class_id = 19, - class_name = "MILITARY" + class_name = "MILITARY", + enhanced_only = false }, veto = { model_hash = 3437611258, display_name = "Veto Classic", manufacturer = "Dinka", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, veto2 = { model_hash = 2802050217, display_name = "Veto Modern", manufacturer = "Dinka", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, vigero = { model_hash = 3469130167, display_name = "Vigero", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, vigero2 = { model_hash = 2536587772, display_name = "Vigero ZX", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, vigero3 = { model_hash = 372621319, display_name = "Vigero ZX Convertible", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, vigilante = { model_hash = 3052358707, display_name = "Vigilante", manufacturer = nil, class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, vindicator = { model_hash = 2941886209, display_name = "Vindicator", manufacturer = "Dinka", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, virgo = { model_hash = 3796912450, display_name = "Virgo", manufacturer = "Albany", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, virgo2 = { model_hash = 3395457658, display_name = "Virgo Classic Custom", manufacturer = "Dundreary", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, virgo3 = { model_hash = 16646064, display_name = "Virgo Classic", manufacturer = "Dundreary", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, virtue = { model_hash = 669204833, display_name = "Virtue", manufacturer = "Ocelot", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, viseris = { model_hash = 3903371924, display_name = "Viseris", manufacturer = "Lampadati", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, visione = { model_hash = 3296789504, display_name = "Visione", manufacturer = "Grotti", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, vivanite = { model_hash = 2922168362, display_name = "Vivanite", manufacturer = "Karin", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, vivanite2 = { model_hash = 507621923, display_name = "Vivanite", manufacturer = "Karin", class_id = 17, - class_name = "SERVICE" + class_name = "SERVICE", + enhanced_only = false }, volatol = { model_hash = 447548909, display_name = "Volatol", manufacturer = nil, class_id = 16, - class_name = "PLANE" + class_name = "PLANE", + enhanced_only = false }, volatus = { model_hash = 2449479409, display_name = "Volatus", manufacturer = "Buckingham", class_id = 15, - class_name = "HELICOPTER" + class_name = "HELICOPTER", + enhanced_only = false }, voltic = { model_hash = 2672523198, display_name = "Voltic", manufacturer = "Coil", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, voltic2 = { model_hash = 989294410, display_name = "Rocket Voltic", manufacturer = "Coil", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, voodoo = { model_hash = 2006667053, display_name = "Voodoo Custom", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, voodoo2 = { model_hash = 523724515, display_name = "Voodoo", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, vorschlaghammer = { model_hash = 3054795149, display_name = "Vorschlaghammer", manufacturer = "Benefactor", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, vortex = { model_hash = 3685342204, display_name = "Vortex", manufacturer = "Pegassi", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, vstr = { model_hash = 1456336509, display_name = "V-STR", manufacturer = "Albany", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, warrener = { model_hash = 1373123368, display_name = "Warrener", manufacturer = "Vulcar", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, warrener2 = { model_hash = 579912970, display_name = "Warrener HKR", manufacturer = "Vulcar", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, washington = { model_hash = 1777363799, display_name = "Washington", manufacturer = "Albany", class_id = 1, - class_name = "SEDAN" + class_name = "SEDAN", + enhanced_only = false }, wastelander = { model_hash = 2382949506, display_name = "Wastelander", manufacturer = "MTL", class_id = 17, - class_name = "SERVICE" + class_name = "SERVICE", + enhanced_only = false }, weevil = { model_hash = 1644055914, display_name = "Weevil", manufacturer = "BF", class_id = 0, - class_name = "COMPACT" + class_name = "COMPACT", + enhanced_only = false }, weevil2 = { model_hash = 3300595976, display_name = "Weevil Custom", manufacturer = "BF", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, windsor = { model_hash = 1581459400, display_name = "Windsor", manufacturer = "Enus", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, windsor2 = { model_hash = 2364918497, display_name = "Windsor Drop", manufacturer = "Enus", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, winky = { model_hash = 4084658662, display_name = "Winky", manufacturer = "Vapid", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, wolfsbane = { model_hash = 3676349299, display_name = "Wolfsbane", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, woodlander = { model_hash = 1966698497, display_name = "Woodlander", manufacturer = "Karin", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, xa21 = { model_hash = 917809321, display_name = "XA-21", manufacturer = "Ocelot", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, xls = { model_hash = 1203490606, display_name = "XLS", manufacturer = "Benefactor", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, xls2 = { model_hash = 3862958888, display_name = "XLS (Armored)", manufacturer = "Benefactor", class_id = 2, - class_name = "SUV" + class_name = "SUV", + enhanced_only = false }, xtreme = { model_hash = 2515968713, display_name = "X-treme", manufacturer = "Pfister", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, yosemite = { model_hash = 1871995513, display_name = "Yosemite", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, yosemite1500 = { model_hash = 2398479240, display_name = "Yosemite 1500", manufacturer = "Declasse", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, yosemite2 = { model_hash = 1693751655, display_name = "Drift Yosemite", manufacturer = "Declasse", class_id = 4, - class_name = "MUSCLE" + class_name = "MUSCLE", + enhanced_only = false }, yosemite3 = { model_hash = 67753863, display_name = "Yosemite Rancher", manufacturer = "Declasse", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, youga = { model_hash = 65402552, display_name = "Youga", manufacturer = "Bravado", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, youga2 = { model_hash = 1026149675, display_name = "Youga Classic", manufacturer = "Bravado", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, youga3 = { model_hash = 1802742206, display_name = "Youga Classic 4x4", manufacturer = "Bravado", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, youga4 = { model_hash = 1486521356, display_name = "Youga Custom", manufacturer = "Vapid", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, youga5 = { model_hash = 2266063097, display_name = "Youga Custom", manufacturer = "Vapid", class_id = 12, - class_name = "VAN" + class_name = "VAN", + enhanced_only = false }, z190 = { model_hash = 838982985, display_name = "190z", manufacturer = "Karin", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, zeno = { model_hash = 655665811, display_name = "Zeno", manufacturer = "Overflod", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, zentorno = { model_hash = 2891838741, display_name = "Zentorno", manufacturer = "Pegassi", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, zhaba = { model_hash = 1284356689, display_name = "Zhaba", manufacturer = "RUNE", class_id = 9, - class_name = "OFF_ROAD" + class_name = "OFF_ROAD", + enhanced_only = false }, zion = { model_hash = 3172678083, display_name = "Zion", manufacturer = "Ubermacht", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, zion2 = { model_hash = 3101863448, display_name = "Zion Cabrio", manufacturer = "Ubermacht", class_id = 3, - class_name = "COUPE" + class_name = "COUPE", + enhanced_only = false }, zion3 = { model_hash = 1862507111, display_name = "Zion Classic", manufacturer = "Ubermacht", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false }, zombiea = { model_hash = 3285698347, display_name = "Zombie Bobber", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, zombieb = { model_hash = 3724934023, display_name = "Zombie Chopper", manufacturer = "Western", class_id = 8, - class_name = "MOTORCYCLE" + class_name = "MOTORCYCLE", + enhanced_only = false }, zorrusso = { model_hash = 3612858749, display_name = "Zorrusso", manufacturer = "Pegassi", class_id = 7, - class_name = "SUPER" + class_name = "SUPER", + enhanced_only = false }, zr350 = { model_hash = 2436313176, display_name = "ZR350", manufacturer = "Annis", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, zr380 = { model_hash = 540101442, display_name = "Apocalypse ZR380", manufacturer = "Annis", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, zr3802 = { model_hash = 3188846534, display_name = "Future Shock ZR380", manufacturer = "Annis", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, zr3803 = { model_hash = 2816263004, display_name = "Nightmare ZR380", manufacturer = "Annis", class_id = 6, - class_name = "SPORT" + class_name = "SPORT", + enhanced_only = false }, ztype = { model_hash = 758895617, display_name = "Z-Type", manufacturer = "Truffade", class_id = 5, - class_name = "SPORT_CLASSIC" + class_name = "SPORT_CLASSIC", + enhanced_only = false } } - return VehicleDictionary diff --git a/SSV2/includes/features/EnemiesFlee.lua b/SSV2/includes/features/EnemiesFlee.lua index 042c7fa..862c8a9 100644 --- a/SSV2/includes/features/EnemiesFlee.lua +++ b/SSV2/includes/features/EnemiesFlee.lua @@ -8,13 +8,13 @@ -- Scares nearby enemies and forces them to flee ----@class Terrifier +---@class EnemiesFlee ---@field private m_active boolean ---@field private m_last_triggered milliseconds -local Terrifier = { m_active = false, m_last_triggered = 0 } -Terrifier.__index = Terrifier +local EnemiesFlee = { m_active = false, m_last_triggered = 0 } +EnemiesFlee.__index = EnemiesFlee -function Terrifier:OnClick() +function EnemiesFlee:OnClick() if (self.m_active or Time.Millis() - self.m_last_triggered < 500) then return end @@ -83,7 +83,7 @@ function Terrifier:OnClick() end KeyManager:RegisterKeybind(GVars.keyboard_keybinds.enemies_flee, function() - Terrifier:OnClick() + EnemiesFlee:OnClick() end) -return Terrifier +return EnemiesFlee diff --git a/SSV2/includes/features/Speedometer.lua b/SSV2/includes/features/Speedometer.lua index 152a89d..15f9cb7 100644 --- a/SSV2/includes/features/Speedometer.lua +++ b/SSV2/includes/features/Speedometer.lua @@ -7,35 +7,61 @@ -- * Provide a copy of or a link to the original license (GPL-3.0 or later); see LICENSE.md or . -local UnitCases = { +local gui_registered = false +local COL_INDICATOR_RED = Color(1.0, 0.215, 0.215, 1.0):AsU32() +local COL_INDICATOR_GREEN = Color(0.1, 0.91, 0.0, 1.0):AsU32() +local COL_INDICATOR_BLUE = Color(0.215, 0.315, 1.0, 1.0):AsU32() +local COL_INDICATOR_AMBER = Color(1.0, 0.8, 0.0, 1.0):AsU32() +local COL_INDICATOR_OFF = Color(0.1, 0.1, 0.1, 0.5):AsU32() +local UNIT_MULTIPLIERS = { [0] = 1, [1] = 3.6, default = 2.236936 } +---@param veh PlayerVehicle +---@return boolean +local function get_is_tc_esc_dsabled(veh) + return veh:GetAdvancedFlag(Enums.eVehicleAdvancedFlags.DISABLE_TRACTION_CONTROL) + or veh:GetAdvancedFlag(Enums.eVehicleAdvancedFlags.DISABLE_STABILITY_CONTROL) +end + ---@class Speedometer +---@field private m_cached_ticks { x1: float, y1: float, x2: float, y2: float, angle: float, value: integer } +---@field private m_cached_unit_sizes dict +---@field private m_last_max_fractions integer +---@field private m_last_max_value integer +---@field private m_tick_marks integer +---@field private m_line_thickness float +---@field private m_font_scale float local Speedometer = { - cached_ticks = nil, - cached_unit_sizes = {}, - last_max_fractions = nil, - last_max_value = nil, - tick_marks = 10, - line_thickness = 3.0, - font_scale = 1.0, - _state = { + m_cached_ticks = {}, + m_cached_unit_sizes = {}, + m_last_max_fractions = 0, + m_last_max_value = 0, + m_tick_marks = 10, + m_line_thickness = 3.0, + m_font_scale = 1.0, + + ---@public + _state = { should_draw = false, speed_modifier = 1, - PV = nil, + PV = nil, ---@type PlayerVehicle IsEngineOn = false, HasABS = false, IsABSEngaged = false, IsESCEngaged = false, + IsESCDisabled = false, IsNOSActive = false, IsAircraft = false, IsSports = false, IsShootingFlares = false, + IsReversing = false, + IsStopped = false, Manufacturer = "", + GearName = "", NOSDangerRatio = 0.0, CurrentSpeed = 0, CurrentAltitude = 0, @@ -46,48 +72,70 @@ local Speedometer = { EngineHealth = 0, LandingGearState = Enums.eLandingGearState.UNK } -} -Speedometer.__index = Speedometer +}; Speedometer.__index = Speedometer +---@public function Speedometer:UpdateState() - self._state.PV = LocalPlayer:GetVehicle() - if (not self._state.PV) then - self._state.should_draw = false + local state = self._state + local PV = LocalPlayer:GetVehicle() + if not (PV and PV:IsValid()) then + state.should_draw = false return end - self._state.speed_modifier = Match(GVars.features.speedometer.speed_unit, UnitCases) - self._state.CurrentSpeed = self._state.PV:GetSpeed() * self._state.speed_modifier - self._state.MaxSpeed = math.floor( - (self._state.PV:GetDefaultMaxSpeed() - * (GVars.features.vehicle.fast_vehicles and 1.8 or 1)) - * self._state.speed_modifier - ) - self._state.CurrentAltitude = self._state.PV:GetHeightAboveGround() - self._state.Manufacturer = self._state.PV:GetManufacturerName() - self._state.IsEngineOn = self._state.PV:IsEngineOn() - self._state.HasABS = self._state.PV:HasABS() - self._state.IsABSEngaged = self._state.PV:IsABSEngaged() - self._state.IsESCEngaged = self._state.PV:IsESCEngaged() - self._state.IsNOSActive = self._state.PV:IsNOSActive() - self._state.IsAircraft = self._state.PV:IsPlane() or self._state.PV:IsHeli() - self._state.IsSports = self._state.PV:IsSportsOrSuper() - self._state.IsShootingFlares = self._state.PV.m_is_shooting_flares - self._state.Throttle = math.max(0.1, self._state.PV:GetThrottle()) - self._state.RPM = self._state.PV:GetRPM() - self._state.Gear = self._state.PV:GetCurrentGear() - self._state.EngineHealth = self._state.PV:GetEngineHealth() - self._state.NOSDangerRatio = self._state.PV:GetNOSDangerRatio() - self._state.LandingGearState = self._state.PV:GetLandingGearState() - self._state.should_draw = (self._state.PV and self._state.PV:IsValid() - and self._state.IsEngineOn + local speed_modifier = Match(GVars.features.speedometer.speed_unit, UNIT_MULTIPLIERS) + local fast_vehicles = GVars.features.vehicle.fast_vehicles + local IsEngineOn = PV:IsEngineOn() + state.PV = PV + state.speed_modifier = speed_modifier + state.CurrentSpeed = PV:GetSpeed() * speed_modifier + state.MaxSpeed = math.floor((PV:GetDefaultMaxSpeed() * (fast_vehicles and 1.8 or 1)) * speed_modifier) + state.CurrentAltitude = PV:GetHeightAboveGround() + state.Manufacturer = PV:GetManufacturerName() + state.IsEngineOn = IsEngineOn + state.HasABS = PV:HasABS() + state.IsABSEngaged = PV:IsABSEngaged() + state.IsESCEngaged = PV:IsESCEngaged() + state.IsESCDisabled = get_is_tc_esc_dsabled(PV) + state.IsNOSActive = PV:IsNOSActive() + state.IsAircraft = PV:IsPlane() or PV:IsHeli() + state.IsSports = PV:IsSportsOrSuper() + state.IsShootingFlares = PV.m_is_shooting_flares + state.Throttle = math.max(0.1, PV:GetThrottle()) + state.RPM = PV:GetRPM() + state.Gear = PV:GetCurrentGear() + state.GearName = PV:GetCurrentGearName() + state.EngineHealth = PV:GetEngineHealth() + state.NOSDangerRatio = PV:GetNOSDangerRatio() + state.LandingGearState = PV:GetLandingGearState() + state.IsReversing = PV:IsReversing() + state.IsStopped = PV:IsStopped() + state.should_draw = (PV and PV:IsValid() and IsEngineOn) and LocalPlayer:IsDriving() and not LocalPlayer:IsUsingPhone() and not LocalPlayer:IsBrowsingApps() and not HUD.IS_PAUSE_MENU_ACTIVE() and not CAM.IS_SCREEN_FADING_OUT() and not CAM.IS_SCREEN_FADED_OUT() - ) +end + +---@private +---@param scale float +function Speedometer:SetFontScale(scale) + if (self.m_font_scale ~= scale) then + self.m_font_scale = scale + ImGui.SetWindowFontScale(scale) + end +end + +---@private +function Speedometer:ResetFontScale() + if (self.m_font_scale == 1.0) then + return + end + + self.m_font_scale = 1.0 + ImGui.SetWindowFontScale(1.0) end ---@private @@ -97,27 +145,25 @@ end ---@param max_value number ---@param IsAircraft boolean function Speedometer:GenerateTicks(radius, start_angle, end_angle, max_value, IsAircraft) - local tick_step = not IsAircraft and 20 or 500 + local tick_step = not IsAircraft and 20 or 500 local max_fractions = math.ceil(max_value / tick_step) * tick_step - - if (self.last_max_fractions == max_fractions) and (self.last_max_value == max_value) then + if (self.m_last_max_fractions == max_fractions and self.m_last_max_value == max_value) then return end - self.last_max_fractions = max_fractions - self.last_max_value = max_value - self.cached_ticks = {} + self.m_last_max_fractions = max_fractions + self.m_last_max_value = max_value + self.m_cached_ticks = {} for i = 0, max_fractions, tick_step do - local fraction = i / max_fractions - local angle = start_angle + (end_angle - start_angle) * fraction + local fraction = i / max_fractions + local angle = start_angle + (end_angle - start_angle) * fraction local sin_a, cos_a = math.sin(angle), math.cos(angle) - - table.insert(self.cached_ticks, { - x1 = cos_a * (radius - 10), - y1 = sin_a * (radius - 10), - x2 = cos_a * radius, - y2 = sin_a * radius, + table.insert(self.m_cached_ticks, { + x1 = cos_a * (radius - 10), + y1 = sin_a * (radius - 10), + x2 = cos_a * radius, + y2 = sin_a * radius, angle = angle, value = i, }) @@ -128,40 +174,43 @@ end ---@param ImDrawlist userdata ---@param center vec2 function Speedometer:DrawABSNOSESCIndicators(ImDrawlist, center) - if (not self._state.PV) then - return - end - - if (self._state.HasABS) then - local abs_color = self._state.IsABSEngaged and Color(1.0, 0.8, 0.0, 1.0):AsU32() or - Color(0.1, 0.1, 0.1, 0.5):AsU32() + local state = self._state + if (not state.PV) then return end + local pos_y = center.y + 100 + if (state.HasABS) then + local abs_color = state.IsABSEngaged and COL_INDICATOR_AMBER or COL_INDICATOR_OFF ImGui.ImDrawListAddText( ImDrawlist, - center.x - 50, - center.y + 100, + center.x - 60, + pos_y, abs_color, "ABS" ) - local esc_color = self._state.IsESCEngaged and Color(1.0, 0.215, 0.215, 1.0):AsU32() or - Color(0.1, 0.1, 0.1, 0.5):AsU32() + + local esc_color = state.IsESCEngaged and COL_INDICATOR_RED or COL_INDICATOR_OFF + local esc_txt = "TC ESC" + if (state.IsESCDisabled) then + esc_color = COL_INDICATOR_RED + esc_txt = "TC/ESC OFF" + end ImGui.ImDrawListAddText( ImDrawlist, - center.x + 25, - center.y + 100, + center.x + 19, + pos_y, esc_color, - "ESC" + esc_txt ) end - if (self._state.IsNOSActive) then + if (state.IsNOSActive) then ImGui.ImDrawListAddText( ImDrawlist, - center.x - 12.5, - center.y + 100, - Color(0.215, 0.315, 1.0, 1.0):AsU32(), + center.x - 20, + pos_y, + COL_INDICATOR_BLUE, "NOS" ) end @@ -171,25 +220,23 @@ end ---@param ImDrawlist userdata ---@param center vec2 function Speedometer:DrawAirplaneIndicators(ImDrawlist, center) - local flare_color = self._state.IsShootingFlares and Color(0.1, 0.91, 0.0, 1.0) or Color(0.1, 0.1, 0.1, 0.5) - + local state = self._state + local flare_color = state.IsShootingFlares and COL_INDICATOR_GREEN or COL_INDICATOR_OFF ImGui.ImDrawListAddText( ImDrawlist, center.x - 50, center.y + 100, - flare_color:AsU32(), + flare_color, "FLRS" ) - local gearState = self._state.LandingGearState - local gear_color = (gearState > -1 and gearState < 4) - and Color(0.91, 0.1, 0.0, 1.0) or Color(0.1, 0.1, 0.1, 0.5) - + local gearState = state.LandingGearState + local gear_color = (gearState > -1 and gearState < 4) and COL_INDICATOR_RED or COL_INDICATOR_OFF ImGui.ImDrawListAddText( ImDrawlist, center.x + 30, center.y + 100, - gear_color:AsU32(), + gear_color, "GEAR" ) end @@ -234,10 +281,7 @@ end ---@param ImDrawlist userdata ---@param center vec2 function Speedometer:DrawLowerIndicators(ImDrawlist, center) - if self.font_scale ~= 0.77 then - ImGui.SetWindowFontScale(0.77) - self.font_scale = 0.77 - end + self:SetFontScale(0.77) if (self._state.IsAircraft) then self:DrawAirplaneIndicators(ImDrawlist, center) @@ -251,13 +295,14 @@ end ---@param center vec2 ---@param radius number function Speedometer:DrawEngineWarning(ImDrawList, center, radius) - if (self._state.EngineHealth < 800) then - local pulse = 0.5 + 0.5 * math.sin(Time.Now() * 5) - local color = Color(1, self._state.EngineHealth < 400 and pulse or 0.9, 0, 1):AsU32() + local state = self._state + if (state.EngineHealth < 800) then + local pulse = 0.5 + 0.5 * math.sin(Time.Now() * 5) + local color = Color(1, state.EngineHealth < 400 and pulse or 0.9, 0, 1):AsU32() local offset = vec2:new(-radius + 72, 15) - local p1 = center + offset + vec2:new(0, -7) - local p2 = center + offset + vec2:new(-12, 12) - local p3 = center + offset + vec2:new(12, 12) + local p1 = center + offset + vec2:new(0, -7) + local p2 = center + offset + vec2:new(-12, 12) + local p3 = center + offset + vec2:new(12, 12) ImGui.ImDrawListAddTriangle(ImDrawList, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, color) ImGui.ImDrawListAddText(ImDrawList, p1.x - 1, p1.y + 2.8, color, "!") ImGui.ImDrawListAddText(ImDrawList, 11.2, p1.x - 30, p1.y + 25, color, "CHECK ENGINE") @@ -273,11 +318,12 @@ end ---@param current_altitude number ---@param max_altitude number function Speedometer:DrawImpl(ImDrawList, center, radius, current_speed, max_speed, current_altitude, max_altitude) - local value = self._state.IsAircraft and current_altitude or current_speed - local max_value = self._state.IsAircraft and max_altitude or max_speed + local state = self._state + local value = state.IsAircraft and current_altitude or current_speed + local max_value = state.IsAircraft and max_altitude or max_speed local start_angle, end_angle = -math.pi * 1.25, math.pi * 0.25 - self:GenerateTicks(radius, start_angle, end_angle, max_value, self._state.IsAircraft) + self:GenerateTicks(radius, start_angle, end_angle, max_value, state.IsAircraft) local ratio = math.min(value / max_value, 1.0) local angle = start_angle + (end_angle - start_angle) * ratio @@ -301,7 +347,7 @@ function Speedometer:DrawImpl(ImDrawList, center, radius, current_speed, max_spe radius + 10, GVars.features.speedometer.colors.circle, 100, - self.line_thickness + self.m_line_thickness ) -- if IsNOSActive() then -- TODO: Replace with crash detection + red-ish color @@ -310,23 +356,24 @@ function Speedometer:DrawImpl(ImDrawList, center, radius, current_speed, max_spe -- end -- check engine - if (not self._state.IsAircraft) then - if (type(self._state.Manufacturer) == "string") then - local textWidth = ImGui.CalcTextSize(self._state.Manufacturer) + if (not state.IsAircraft) then + local mfr = state.Manufacturer + if (string.isvalid(mfr)) then + local textWidth = ImGui.CalcTextSize(mfr) ImGui.ImDrawListAddText( ImDrawList, 19.5, center.x - 5 - (textWidth / 2), center.y - 15 - (radius / 2), GVars.features.speedometer.colors.text - (math.pi * 10), - self._state.Manufacturer + mfr ) end self:DrawEngineWarning(ImDrawList, center, radius) end -- fractions - for _, tick in ipairs(self.cached_ticks or {}) do + for _, tick in ipairs(self.m_cached_ticks) do local p1 = center + vec2:new(tick.x1, tick.y1) local p2 = center + vec2:new(tick.x2, tick.y2) @@ -363,7 +410,7 @@ function Speedometer:DrawImpl(ImDrawList, center, radius, current_speed, max_spe -- analog needle for t = 0.0, 1.0, 0.25 do local segment = center + (needle_end - center) * t - local thickness = self.line_thickness * (1.0 - t * 0.5) + local thickness = self.m_line_thickness * (1.0 - t * 0.5) ImGui.ImDrawListAddLine( ImDrawList, @@ -381,28 +428,23 @@ function Speedometer:DrawImpl(ImDrawList, center, radius, current_speed, max_spe ImDrawList, center.x, center.y, - self.line_thickness * 17.0, + self.m_line_thickness * 17.0, GVars.features.speedometer.colors.needle_base ) - local unit_buff = (GVars.features.speedometer.speed_unit == 0 and "M/s") or - (GVars.features.speedometer.speed_unit == 1 and "Km/h") or "Mi/h" + local unit = GVars.features.speedometer.speed_unit + local unit_buff = (unit == 0 and "M/s") or (unit == 1 and "Km/h") or "Mi/h" local display_value = math.floor(current_speed) - local value_str = tostring(display_value) - local unit_size = self.cached_unit_sizes[unit_buff] + local value_str = tostring(display_value) + local unit_size = self.m_cached_unit_sizes[unit_buff] - if not unit_size then + if (not unit_size) then unit_size = vec2:new(ImGui.CalcTextSize(unit_buff)) - self.cached_unit_sizes[unit_buff] = unit_size - end - - if self.font_scale ~= 1.4 then - ImGui.SetWindowFontScale(1.4) - self.font_scale = 1.4 + self.m_cached_unit_sizes[unit_buff] = unit_size end + self:SetFontScale(1.4) local value_text_size = vec2:new(ImGui.CalcTextSize(value_str)) - ImGui.ImDrawListAddText( ImDrawList, center.x - (value_text_size.x * 0.5), @@ -411,11 +453,7 @@ function Speedometer:DrawImpl(ImDrawList, center, radius, current_speed, max_spe value_str ) - if self.font_scale ~= 1.0 then - ImGui.SetWindowFontScale(1.0) - self.font_scale = 1.0 - end - + self:ResetFontScale() ImGui.ImDrawListAddText( ImDrawList, center.x - unit_size.x * 0.5, @@ -424,21 +462,22 @@ function Speedometer:DrawImpl(ImDrawList, center, radius, current_speed, max_spe unit_buff ) - if (self._state.IsEngineOn) then + local IsAircraft = state.IsAircraft + if (state.IsEngineOn) then -- rpm/jet throttle - local rpm_ratio = self._state.IsAircraft and self._state.Throttle or self._state.RPM - local max_segments = 12 + local rpm_ratio = IsAircraft and state.Throttle or state.RPM + local max_segments = 12 local active_segments = math.floor(max_segments * rpm_ratio) - local normalized_rpm = math.min(math.max(rpm_ratio, 0.0), 1.0) - local r = normalized_rpm ^ 1.2 - local g = 1.0 - normalized_rpm - local b = 0.0 - local a = 1.0 - local rpm_color = Color(r, g, b, a):AsU32() + local normalized_rpm = math.min(math.max(rpm_ratio, 0.0), 1.0) + local r = normalized_rpm ^ 1.2 + local g = 1.0 - normalized_rpm + local b = 0.0 + local a = 1.0 + local rpm_color = Color(r, g, b, a):AsU32() for _ = 0, active_segments - 1 do local rpm_angle_range = start_angle - end_angle - local rpm_angle_end = start_angle + rpm_angle_range * rpm_ratio - 0.07 + local rpm_angle_end = start_angle + rpm_angle_range * rpm_ratio - 0.07 self:DrawSmoothArc( ImDrawList, @@ -453,31 +492,33 @@ function Speedometer:DrawImpl(ImDrawList, center, radius, current_speed, max_spe end end - if (self._state.IsAircraft) then - if self.font_scale ~= 0.77 then - ImGui.SetWindowFontScale(0.77) - self.font_scale = 0.77 - end - + local gear = state.Gear + local text_col = GVars.features.speedometer.colors.text + if (IsAircraft) then + self:SetFontScale(0.77) ImGui.ImDrawListAddText( ImDrawList, center.x - 43, center.y - 80, - GVars.features.speedometer.colors.text, + text_col, _F("Altitude [%.0fm]", current_altitude) ) - elseif (self._state.Gear) then - if (self.font_scale ~= 1.5) then - ImGui.SetWindowFontScale(1.5) - self.font_scale = 1.5 + elseif (gear) then + self:SetFontScale(1.5) + local gear_name = state.GearName + local gear_str = gear_name + local IsSports = state.IsSports + if (gear > 0 and gear < 255) then + local gbox_cfg = GVars.features.vehicle.manual_gearbox + if (gbox_cfg.enabled) then + gear_str = gbox_cfg.mode == 0 and gear_name or _F("S%s", gear_name) + else + gear_str = _F("D%s", gear_name) + end end - local gear_str = (self._state.Gear == 0) and ((current_speed > 1 and "R") or "N") or - _F("%s%d", self._state.IsSports and "S" or "D", self._state.Gear) - local gear_color = self._state.IsSports and Color(1.0, 0.3, 0.3, 1.0):AsU32() or - GVars.features.speedometer.colors.text - local gear_text_width, _ = ImGui.CalcTextSize(gear_str) - + local gear_color = IsSports and COL_INDICATOR_RED or text_col + local gear_text_width = ImGui.CalcTextSize(gear_str) ImGui.ImDrawListAddText( ImDrawList, center.x - (gear_text_width * 0.5), @@ -490,12 +531,14 @@ function Speedometer:DrawImpl(ImDrawList, center, radius, current_speed, max_spe self:DrawLowerIndicators(ImDrawList, center) end +---@public ---@param offset? float function Speedometer:Draw(offset) if (not LocalPlayer:IsDriving() or not GVars.features.speedometer.enabled) then return end + local state = self._state local pos = GVars.features.speedometer.pos local radius = 150 local resolution = Game.GetScreenResolution() @@ -512,17 +555,17 @@ function Speedometer:Draw(offset) | ImGuiWindowFlags.NoMove -- RIP Paul Walker - if (self._state.NOSDangerRatio >= 0.8) then - local window_width = radius * 1.3 + if (state.NOSDangerRatio >= 0.8) then + local width = radius * 1.3 local pulse = 0.5 + 0.5 * math.sin(Time.Now() * 12) - ImGui.SetNextWindowSize(window_width, 100, ImGuiCond.Always) + ImGui.SetNextWindowSize(width, 100, ImGuiCond.Always) ImGui.SetNextWindowPos(pos.x + radius * 0.7, pos.y - 120, ImGuiCond.Always) ImGui.PushStyleColor(ImGuiCol.WindowBg, 1, pulse, 0.02, 0.9) ImGui.PushStyleColor(ImGuiCol.Text, 0.1, 0.1, 0.1, 1.0) if (ImGui.Begin("##dangertomanifold", windowFlags)) then ImGui.SetWindowFontScale(1.4) local wrn_width = ImGui.CalcTextSize("Warning!!!") - ImGui.SetCursorPosX((window_width - wrn_width) / 2) + ImGui.SetCursorPosX((width - wrn_width) / 2) ImGui.Text("Warning!!!") ImGui.SetWindowFontScale(1.2) ImGui.Text("Danger to Manifold") @@ -535,8 +578,8 @@ function Speedometer:Draw(offset) ImGui.SetNextWindowBgAlpha(0.0) ImGui.SetNextWindowSize(radius * 2.5, radius * 2) ImGui.SetNextWindowPos(pos.x, pos.y, ImGuiCond.Always) - if ImGui.Begin("##SpeedometerWindow", windowFlags | ImGuiWindowFlags.NoBackground) then - if (not self._state.should_draw) then + if (ImGui.Begin("##SpeedometerWindow", windowFlags | ImGuiWindowFlags.NoBackground)) then + if (not state.should_draw) then ImGui.End() return end @@ -553,17 +596,18 @@ function Speedometer:Draw(offset) ImDrawList, center, radius, - self._state.CurrentSpeed, - self._state.MaxSpeed, - self._state.CurrentAltitude, + state.CurrentSpeed, + state.MaxSpeed, + state.CurrentAltitude, max_altitude ) ImGui.End() end end -GUI:RegisterIndependentGUI(function() - Speedometer:Draw() -end) +if (not gui_registered) then + GUI:RegisterIndependentGUI(function() Speedometer:Draw() end) + gui_registered = true +end return Speedometer diff --git a/SSV2/includes/features/extra/yim_actions/YimActionsV3.lua b/SSV2/includes/features/extra/yim_actions/YimActionsV3.lua index ef51549..c67ebd8 100644 --- a/SSV2/includes/features/extra/yim_actions/YimActionsV3.lua +++ b/SSV2/includes/features/extra/yim_actions/YimActionsV3.lua @@ -75,11 +75,15 @@ function YimActions:init() } instance:ReadSavedFavorites() - ThreadManager:RegisterLooped("SS_YIMACTIONS", function() instance:OnTick() end) Backend:RegisterEventCallbackAll(function() instance:ForceCleanup() end) Backend:RegisterFeatureEntityHandler("YimActions", function(handle) instance.CompanionManager:RemoveCompanionByHandle(handle) end) + ThreadManager:RegisterLooped("SS_YIMACTIONS", function() instance:OnTick() end, { + exception_handler = function() + instance:Cleanup() + end + }) instance.m_initialized = true return instance diff --git a/SSV2/includes/features/online/MPStatController.lua b/SSV2/includes/features/online/MPStatController.lua index d4e5301..3794a8c 100644 --- a/SSV2/includes/features/online/MPStatController.lua +++ b/SSV2/includes/features/online/MPStatController.lua @@ -160,7 +160,7 @@ end ---@param data { name: string, type: string, autolock?: boolean, lock_val?: anyval } function MPStatController:AddStat(data) local statName = data.name - local list = self.m_stats + local list = self.m_stats if (list[statName]) then return end local instance = MPStat.new(statName, data) @@ -172,7 +172,9 @@ function MPStatController:AddStat(data) end function MPStatController:OnTick() - if (not Game.IsOnline() or not self.m_last_tick:HasElapsed(5000)) then + if (not Game.IsOnline()) then return end + + if (not self.m_last_tick:HasElapsed(5000)) then return end diff --git a/SSV2/includes/features/online/yim_resupplier/YimResupplierV3.lua b/SSV2/includes/features/online/yim_resupplier/YimResupplierV3.lua index d872edd..830e420 100644 --- a/SSV2/includes/features/online/yim_resupplier/YimResupplierV3.lua +++ b/SSV2/includes/features/online/yim_resupplier/YimResupplierV3.lua @@ -86,7 +86,7 @@ function YRV3:init() self.m_thread = ThreadManager:RegisterLooped("SS_YRV3", function() self:OnTick() - end) + end, { exception_handler = self:Reset(true) }) local function __reset__() if (self.m_data_initialized) then self:Reset() end @@ -99,7 +99,9 @@ function YRV3:init() return self end -function YRV3:Reset() +---@param disable? boolean +---@param reason? string +function YRV3:Reset(disable, reason) self.m_total_sum = 0 self.m_total_sum_fmt = "$0" self.m_last_autosell_check_time = 0 @@ -111,7 +113,11 @@ function YRV3:Reset() self.m_sell_script_running = false self.m_initial_data_done = false self.m_data_initialized = false - self.m_state = Enums.eYRState.IDLE + self.m_state = disable and Enums.eYRState.ERROR or Enums.eYRState.IDLE + + if (disable) then + self:SetLastError(reason or "Unhandled Exception.") + end end function YRV3:Reload() diff --git a/SSV2/includes/features/self/miscellaneous.lua b/SSV2/includes/features/self/miscellaneous.lua index 3cc272c..55c9713 100644 --- a/SSV2/includes/features/self/miscellaneous.lua +++ b/SSV2/includes/features/self/miscellaneous.lua @@ -159,6 +159,7 @@ function SelfMisc:UpdateHandsUpAnim() end if (self.m_is_playing_hands_up_anim and not (LocalPlayer:IsOnFoot() and LocalPlayer:IsAlive())) then + LocalPlayer:ClearTasks() self.m_is_playing_hands_up_anim = false end end diff --git a/SSV2/includes/modules/HandlingEditor.lua b/SSV2/includes/features/vehicle/flag_controller.lua similarity index 84% rename from SSV2/includes/modules/HandlingEditor.lua rename to SSV2/includes/features/vehicle/flag_controller.lua index 4fef212..01565dc 100644 --- a/SSV2/includes/modules/HandlingEditor.lua +++ b/SSV2/includes/features/vehicle/flag_controller.lua @@ -7,11 +7,11 @@ -- * Provide a copy of or a link to the original license (GPL-3.0 or later); see LICENSE.md or . -local HandlingPreset = require("includes.structs.HandlingPreset") -local DefaultPresets = require("includes.data.handling_presets") +local HandlingPreset = require("includes.structs.HandlingPreset") +local DefaultPresets = require("includes.data.handling_presets") ---@type table, set: fun(veh: PlayerVehicle, flag: integer, toggle: boolean), get: fun(veh: PlayerVehicle, flag: integer): boolean }> -local FlagData = { +local FlagData = { [Enums.eHandlingEditorTypes.TYPE_AF] = { enum = Enums.eVehicleAdvancedFlags, get = function(veh, flag) return veh:GetAdvancedFlag(flag) end, @@ -34,7 +34,7 @@ local FlagData = { }, } ----@class HandlingEditor +---@class VehicleFlagController ---@field private m_vehicle PlayerVehicle ---@field private m_deltas table> ---@field private m_stale_deltas table>> -- save by model in case a vehicle despawns before cleanup @@ -45,7 +45,7 @@ local FlagData = { ---@field private m_file_name "handling_editor.json" ---@field private m_initialized boolean ---@field private m_presets_ready boolean -local HandlingEditor = { +local VehicleFlagController = { m_initialized = false, m_presets_ready = false, m_file_name = "handling_editor.json", @@ -54,10 +54,10 @@ local HandlingEditor = { m_active_presets = {}, m_owned_flags = {}, } -HandlingEditor.__index = HandlingEditor +VehicleFlagController.__index = VehicleFlagController ---@param veh PlayerVehicle -function HandlingEditor:init(veh) +function VehicleFlagController:init(veh) if (self.m_initialized) then log.warning("[HandlingEditor]: Attempt to re-initialize. Only one instance is allowed.") return self @@ -81,7 +81,7 @@ function HandlingEditor:init(veh) return self end -function HandlingEditor:FetchPresets() +function VehicleFlagController:FetchPresets() ThreadManager:Run(function() ---@type table local default_presets = {} @@ -117,18 +117,18 @@ end ---@nodiscard ---@return boolean -function HandlingEditor:IsInitialized() +function VehicleFlagController:IsInitialized() return self.m_initialized end ---@nodiscard ---@return boolean -function HandlingEditor:ArePresetsReady() +function VehicleFlagController:ArePresetsReady() return self.m_presets_ready end ---@return boolean -function HandlingEditor:HasEdits() +function VehicleFlagController:HasEdits() for _, data in pairs(self.m_deltas) do if (next(data) ~= nil) then return true @@ -145,49 +145,49 @@ end -- local is_owned = HandlingEditor:IsFlagOwned(_F("%d:%s", Enums.eHandlingEditorTypes.TYPE_HF, "FREEWHEEL_NO_GAS")) --``` ---@param flagName string -function HandlingEditor:IsFlagOwned(flagName) +function VehicleFlagController:IsFlagOwned(flagName) local refCount = self.m_owned_flags[flagName] return refCount and refCount > 0 or false end ---@return boolean -function HandlingEditor:IsAnyPresetEnabled() +function VehicleFlagController:IsAnyPresetEnabled() return next(self.m_active_presets) ~= nil end ----@param preset HandlingPreset +---@param presetName string ---@return boolean -function HandlingEditor:IsPresetEnabled(preset) - return self.m_active_presets[preset:GetName()] ~= nil +function VehicleFlagController:IsPresetEnabled(presetName) + return self.m_active_presets[presetName] ~= nil end ---@param name string ---@return boolean -function HandlingEditor:IsNameReserved(name) +function VehicleFlagController:IsNameReserved(name) return HandlingPreset.IsNameReserved(name) end ---@param preset HandlingPreset ---@return boolean -function HandlingEditor:DoesPresetExist(preset) +function VehicleFlagController:DoesPresetExist(preset) return self.m_preset_lookup[preset:GetName()] ~= nil end ---@param name string ---@return boolean -function HandlingEditor:DoesPresetExistByName(name) +function VehicleFlagController:DoesPresetExistByName(name) return self.m_preset_lookup[name] ~= nil end ---@param allowed_types integer -function HandlingEditor:AssertVehicleType(allowed_types) +function VehicleFlagController:AssertVehicleType(allowed_types) local vehType = self.m_vehicle:GetType() return Bit.IsBitSet(allowed_types, vehType) end ---@param presets? array ---@return array -- serialized presets -function HandlingEditor:SerializePresets(presets) +function VehicleFlagController:SerializePresets(presets) presets = presets or self.m_presets local buff = {} for _, preset in ipairs(presets) do @@ -196,11 +196,11 @@ function HandlingEditor:SerializePresets(presets) return buff end -function HandlingEditor:SavePresets() +function VehicleFlagController:SavePresets() Serializer:WriteToFile(self.m_file_name, self:SerializePresets()) end -function HandlingEditor:GetPresets() +function VehicleFlagController:GetPresets() return self.m_presets end @@ -210,7 +210,7 @@ end ---@param autoEnable? boolean ---@param cbFileName? string ---@return HandlingPreset -function HandlingEditor:GeneratePresetFromCurrentDeltas(name, vehicleTypes, description, autoEnable, cbFileName) +function VehicleFlagController:GeneratePresetFromCurrentDeltas(name, vehicleTypes, description, autoEnable, cbFileName) autoEnable = autoEnable or false local deltas = {} for hType, data in pairs(self.m_deltas) do @@ -235,7 +235,7 @@ end ---@nodiscard ---@param preset HandlingPreset ---@return boolean -function HandlingEditor:AddNewPreset(preset) +function VehicleFlagController:AddNewPreset(preset) self.m_presets_ready = false local name = preset:GetName() if (self.m_preset_lookup[name]) then @@ -253,7 +253,7 @@ function HandlingEditor:AddNewPreset(preset) end ---@param preset HandlingPreset -function HandlingEditor:RemovePreset(preset) +function VehicleFlagController:RemovePreset(preset) self.m_presets_ready = false if (preset:IsDefault()) then Notifier:ShowError("HandlingEditor", _T("VEH_FLAGS_PRESET_NO_DELETE")) @@ -274,7 +274,7 @@ function HandlingEditor:RemovePreset(preset) end ---@param data HandlingPresetData -function HandlingEditor:ImportPreset(data) +function VehicleFlagController:ImportPreset(data) if (not HandlingPreset.AssertArgs(data)) then return false end @@ -290,7 +290,7 @@ function HandlingEditor:ImportPreset(data) end ---@private -function HandlingEditor:SaveStaleState() +function VehicleFlagController:SaveStaleState() if (not self:HasEdits()) then return end local lastModel = self.m_vehicle:GetPreviousModelHash() @@ -300,7 +300,7 @@ function HandlingEditor:SaveStaleState() end ---@private -function HandlingEditor:ResetStaleState() +function VehicleFlagController:ResetStaleState() local model = self.m_vehicle:GetModelHash() local stales = self.m_stale_deltas if (stales[model]) then @@ -311,17 +311,17 @@ end ---@param preset HandlingPreset ---@param toggle boolean -function HandlingEditor:TogglePreset(preset, toggle) +function VehicleFlagController:TogglePreset(preset, toggle) if (not self.m_vehicle:IsValid()) then if (not toggle) then self:SaveStaleState() end return end - if (toggle == self:IsPresetEnabled(preset)) then + local name = preset:GetName() + if (toggle == self:IsPresetEnabled(name)) then return end - local name = preset:GetName() local vehBS = preset.m_vehicle_bitset if (not self:AssertVehicleType(vehBS)) then self.m_active_presets[name] = nil @@ -356,9 +356,15 @@ function HandlingEditor:TogglePreset(preset, toggle) ownedFlags[ownedFlagName] = (ownedFlags[ownedFlagName] or 0) + 1 end else + local default_data = self.m_deltas[editorType] + if (not default_data) then goto skip end + + local default_state = default_data[flagName] + if (default_state == nil) then goto skip end + ownedFlags[ownedFlagName] = (ownedFlags[ownedFlagName] or 1) - 1 if (ownedFlags[ownedFlagName] == 0) then - self:Push(editorType, flag, not state, vehBS, flagName) + self:Push(editorType, flag, default_state, vehBS, flagName) end end ::skip:: @@ -375,7 +381,7 @@ function HandlingEditor:TogglePreset(preset, toggle) end end -function HandlingEditor:ApplyPresets() +function VehicleFlagController:ApplyPresets() for _, preset in ipairs(self.m_presets) do if (preset.auto_apply) then self:TogglePreset(preset, true) @@ -389,7 +395,7 @@ end ---@param allowed_types integer ---@param flagName? string -- Mostly used internally to skip converting the int flag back to a string name. ---@return boolean -function HandlingEditor:Push(flagType, flag, state, allowed_types, flagName) +function VehicleFlagController:Push(flagType, flag, state, allowed_types, flagName) local vehicle = self.m_vehicle if (not vehicle:IsValid()) then return false end @@ -430,7 +436,7 @@ function HandlingEditor:Push(flagType, flag, state, allowed_types, flagName) end ---@param manual_trigger? boolean -function HandlingEditor:Reset(manual_trigger) +function VehicleFlagController:Reset(manual_trigger) local vehicle = self.m_vehicle if (not vehicle:IsValid()) then self:SaveStaleState() @@ -465,4 +471,4 @@ function HandlingEditor:Reset(manual_trigger) end end -return HandlingEditor +return VehicleFlagController diff --git a/SSV2/includes/features/vehicle/flares.lua b/SSV2/includes/features/vehicle/flares.lua index 11e82f7..ef72e3f 100644 --- a/SSV2/includes/features/vehicle/flares.lua +++ b/SSV2/includes/features/vehicle/flares.lua @@ -33,7 +33,7 @@ function Flares:Init() self.m_next_shot_time = 0 self.m_thread = ThreadManager:RegisterLooped("SS_FLARES", function() self:OnTick() - end, not GVars.features.vehicle.flares) + end, { suspended = not GVars.features.vehicle.flares }) end function Flares:ShouldRun() diff --git a/SSV2/includes/features/vehicle/launch_control.lua b/SSV2/includes/features/vehicle/launch_control.lua index 811037e..63d6dbe 100644 --- a/SSV2/includes/features/vehicle/launch_control.lua +++ b/SSV2/includes/features/vehicle/launch_control.lua @@ -9,11 +9,16 @@ local FeatureBase = require("includes.modules.FeatureBase") + +local COL_FG = Color.WHITE +local COL_BG = Color(0, 0, 0, 158) +local COL_GREEN = Color(111, 194, 118, 255) local eLaunchMode = { REALISTIC = 0, RIDICULOUS = 1 } + ---@enum eLaunchControlState local eLaunchControlState = { NONE = 1, @@ -23,7 +28,12 @@ local eLaunchControlState = { CANCELED = 5 } ----@class LaunchControlMgr +---@return boolean +local function is_control_released_2(action) + return (PAD.IS_CONTROL_RELEASED(0, action) or PAD.IS_DISABLED_CONTROL_RELEASED(0, action)) +end + +---@class LaunchControl ---@field private m_entity PlayerVehicle ---@field private m_is_active boolean ---@field private m_thread? Thread @@ -32,7 +42,9 @@ local eLaunchControlState = { ---@field private m_last_pop_time milliseconds ---@field private m_default_pops_off boolean ---@field private m_shocking_event_handle? handle ----@overload fun(pv: PlayerVehicle): LaunchControlMgr +---@field private m_rolling_speed_lock integer? +---@field private m_from_roll boolean +---@overload fun(pv: PlayerVehicle): LaunchControl ---@diagnostic disable-next-line local LaunchControl = setmetatable({}, FeatureBase) LaunchControl.__index = LaunchControl @@ -57,7 +69,7 @@ end function LaunchControl:ShouldRun() return self.m_entity and self.m_entity:IsValid() - and self.m_entity:IsCar() + and self.m_entity:IsLandVehicle() and LocalPlayer:IsAlive() and LocalPlayer:IsDriving() and (not GVars.features.vehicle.performance_only or self.m_entity:IsPerformanceCar()) @@ -78,11 +90,9 @@ end function LaunchControl:Update() local PV = self.m_entity - if (PV:IsElectric()) then - return - end + if (PV:IsElectric()) then return end - local handle = PV:GetHandle() + local handle = PV:GetHandle() local rpmThreshold = { min = GVars.features.vehicle.bangs_rpm_min / 1e4, max = GVars.features.vehicle.bangs_rpm_max / 1e4 @@ -115,20 +125,15 @@ function LaunchControl:Update() self.m_default_pops_off = true end - local gear = PV:GetCurrentGear() - if (gear == 0) then - return - end - if (not PV:IsMoving()) then rpmThreshold.min = rpmThreshold.min - 0.15 elseif (PV:GetHandlingFlag(Enums.eVehicleHandlingFlags.FREEWHEEL_NO_GAS)) then rpmThreshold.min = rpmThreshold.min + 0.2 end - local rpm = PV:GetRPM() - - if (PAD.IS_CONTROL_RELEASED(0, 71) and (rpm < 1.0) and math.is_inrange(rpm, rpmThreshold.min, rpmThreshold.max) and (gear ~= 0)) then + local rpm = PV:GetRPM() + local released = is_control_released_2(71) + if (released and (rpm < 1.0) and math.is_inrange(rpm, rpmThreshold.min, rpmThreshold.max) and not PV:IsReversing()) then if (Time.Millis() < self.m_last_pop_time) then return end @@ -171,20 +176,91 @@ function LaunchControl:RestoreExhaustPops() AUDIO.ENABLE_VEHICLE_EXHAUST_POPS(PV:GetHandle(), true) end +---@param veh PlayerVehicle +---@param rolling boolean +function LaunchControl:Charge(veh, rolling) + local main_color = self.m_timer:IsDone() and COL_GREEN or COL_FG + local isMoving = veh:IsMoving() + local cond2 = rolling and KeyManager:IsKeybindPressed("rolling_launch") or (isMoving == false and PAD.IS_CONTROL_PRESSED(0, 72)) + if (PAD.IS_CONTROL_PRESSED(0, 71) and cond2 and not veh:IsDriftButtonPressed()) then + if (veh:GetEngineHealth() <= 400) then + Notifier:ShowWarning("Samurai's Scripts", _T("VEH_LAUNCH_CTRL_ERR"), false, 5) + sleep(5000) + return + end + + self.m_timer:Resume() + self.m_state = eLaunchControlState.LOADING + if (not rolling) then + veh:Freeze() + else + if (not self.m_rolling_speed_lock) then + self.m_rolling_speed_lock = veh:GetSpeed() + else + local cvehicle = veh:Resolve() + VEHICLE.SET_VEHICLE_MAX_SPEED(veh:GetHandle(), self.m_rolling_speed_lock) + cvehicle.m_throttle:set_float(0.45) + cvehicle.m_throttle_input:set_float(0.45) + cvehicle.m_rpm:set_float(0.45) + cvehicle.m_rpm_2:set_float(0.45) + end + end + + local text = rolling and "Anti Lag" or "Launch Control" + Game.DrawText( + vec2:new(0.5, 0.9105), + text, + main_color, + vec2:new(0, 0.35), + 2, + true + ) + + Game.DrawProgressBar( + vec2:new(0.5, 0.9501), + 0.1, + 0.01, + main_color, + COL_BG, + math.min(1, math.max(0, self.m_timer:Elapsed() / 2000)) + ) + + if (self.m_timer:IsDone() and self.m_state == eLaunchControlState.LOADING) then + self.m_state = eLaunchControlState.READY + self.m_from_roll = self.m_from_roll or rolling + self.m_timer:Pause() + end + elseif (self.m_state ~= eLaunchControlState.NONE and self.m_state ~= eLaunchControlState.READY) then + if (PAD.IS_CONTROL_RELEASED(0, 71) or not cond2 or not self:ShouldRun()) then + main_color = COL_FG + if (not rolling) then + veh:Unfreeze() + else + self.m_rolling_speed_lock = nil + VEHICLE.SET_VEHICLE_MAX_SPEED(veh:GetHandle(), -1) + end + self.m_timer:Reset() + self.m_timer:Pause() + self.m_from_roll = false + self.m_state = eLaunchControlState.CANCELED + end + end +end + function LaunchControl:OnTick() local PV = self.m_entity if (not PV or not PV:IsValid() or not GVars.features.vehicle.launch_control) then - sleep(250) + yield() return end if (GVars.features.vehicle.performance_only and not self.m_entity:IsPerformanceCar()) then - sleep(250) + yield() return end if (not LocalPlayer:IsDriving()) then - sleep(250) + yield() return end @@ -198,63 +274,18 @@ function LaunchControl:OnTick() end local handle = PV:GetHandle() - local r, g, b, a = 255, 255, 255, 255 - if (not self.m_timer) then - self.m_timer = Timer.new(2000) - self.m_timer:Pause() + self.m_timer = Timer.new(2000, true) end - if (not PV:IsMoving() and PV:IsEngineOn()) then - if (PAD.IS_CONTROL_PRESSED(0, 71) and PAD.IS_CONTROL_PRESSED(0, 72) and not PV:IsDriftButtonPressed()) then - if (PV:GetEngineHealth() <= 400) then - Notifier:ShowWarning("Samurai's Scripts", _T("VEH_LAUNCH_CTRL_ERR"), false, 5) - sleep(5000) - return - end - - self.m_timer:Resume() - self.m_state = eLaunchControlState.LOADING - PV:Freeze() - - if (self.m_timer:IsDone()) then - r, g, b, a = 111, 194, 118, 255 - end - - Game.DrawText( - vec2:new(0.42, 0.936), - _T("VEH_LAUNCH_CTRL"), - Color(r, g, b, a), - vec2:new(0, 0.35), - 2 - ) - - Game.DrawProgressBar( - vec2:new(0.53, 0.95), - 0.1, - 0.01, - Color(r, g, b, a), - Color(0, 0, 0, 150), - math.min(1, math.max(0, self.m_timer:Elapsed() / 2000)) - ) - - if (self.m_timer:IsDone() and self.m_state == eLaunchControlState.LOADING) then - self.m_state = eLaunchControlState.READY - self.m_timer:Pause() - end - elseif (self.m_state ~= eLaunchControlState.NONE and self.m_state ~= eLaunchControlState.READY) then - if (PAD.IS_CONTROL_RELEASED(0, 71) or PAD.IS_CONTROL_RELEASED(0, 72) or not self:ShouldRun()) then - r, g, b, a = 255, 255, 255, 255 - PV:Unfreeze() - self.m_timer:Reset() - self.m_timer:Pause() - self.m_state = eLaunchControlState.CANCELED - end - end + local fwd_speed = PV:GetSpeedVector().y + local rolling = PV:IsCar() and math.is_inrange(fwd_speed, 8, PV:GetMaxSpeed()) + if (PV:IsEngineOn()) then + self:Charge(PV, rolling) end if (self.m_state == eLaunchControlState.READY) then - if (PAD.IS_CONTROL_PRESSED(0, 71) and PAD.IS_CONTROL_RELEASED(0, 72)) then + if (PAD.IS_CONTROL_PRESSED(0, 71) and PAD.IS_CONTROL_RELEASED(0, 72) and not KeyManager:IsKeyPressed(eVirtualKeyCodes.N)) then local realistic = GVars.features.vehicle.launch_control_mode == eLaunchMode.REALISTIC local max_speed = realistic and PV:GetDefaultMaxSpeed() - 1 or PV:GetMaxSpeed() - 1 local max_force = realistic and 2000 or 5000 @@ -262,33 +293,44 @@ function LaunchControl:OnTick() local start_time = Game.GetGameTimer() local end_time = start_time + 1200 + if (self.m_from_roll) then + max_push = math.min(PV:GetMaxSpeed(), PV:GetSpeed() + 15) + end + PHYSICS.SET_IN_ARENA_MODE(true) VEHICLE.SET_VEHICLE_MAX_LAUNCH_ENGINE_REVS_(handle, 0) PV:Unfreeze() + self.m_rolling_speed_lock = nil + VEHICLE.SET_VEHICLE_MAX_SPEED(handle, -1) self.m_state = eLaunchControlState.RUNNING while (PAD.IS_CONTROL_PRESSED(0, 71) and PV:GetSpeed() < max_push) do - local now = Game.GetGameTimer() - local t = math.min(1.0, (now - start_time) / (end_time - start_time)) - local power = math.lerp(0.0, max_force, t) - ENTITY.APPLY_FORCE_TO_ENTITY_CENTER_OF_MASS( - handle, - 1, - 0.0, - power, - 0.0, - false, - true, - false, - false - ) + if (not self.m_from_roll) then + local now = Game.GetGameTimer() + local t = math.min(1.0, (now - start_time) / (end_time - start_time)) + local power = math.lerp(0.0, max_force, t) + ENTITY.APPLY_FORCE_TO_ENTITY_CENTER_OF_MASS( + handle, + 1, + 0.0, + power, + 0.0, + false, + true, + false, + false + ) + else + VEHICLE.SET_VEHICLE_CHEAT_POWER_INCREASE(handle, 10) + end yield() end VEHICLE.SET_VEHICLE_MAX_LAUNCH_ENGINE_REVS_(handle, 1.0) PHYSICS.SET_IN_ARENA_MODE(false) - self.m_state = eLaunchControlState.NONE + self.m_state = eLaunchControlState.NONE + self.m_from_roll = false self.m_timer:Reset() self.m_timer:Pause() end diff --git a/SSV2/includes/features/vehicle/manual_gearbox.lua b/SSV2/includes/features/vehicle/manual_gearbox.lua new file mode 100644 index 0000000..ed990c8 --- /dev/null +++ b/SSV2/includes/features/vehicle/manual_gearbox.lua @@ -0,0 +1,290 @@ +-- Copyright (C) 2026 SAMURAI (xesdoog) & Contributors. +-- This file is part of Samurai's Scripts. +-- +-- Permission is hereby granted to copy, modify, and redistribute +-- this code as long as you respect these conditions: +-- * Credit the owner and contributors. +-- * Provide a copy of or a link to the original license (GPL-3.0 or later); see LICENSE.md or . + + +local FeatureBase = require("includes.modules.FeatureBase") +local HandlingPreset = require("includes.structs.HandlingPreset") + + +local eGearboxType = { + H_PATTERN = 0, + SEQUENTIAL = 1 +} + +---@return boolean +local function is_control_pressed_2(action) + return (PAD.IS_CONTROL_PRESSED(0, action) or PAD.IS_DISABLED_CONTROL_PRESSED(0, action)) +end + +---@class GearState +---@field m_current uint8_t +---@field m_next uint8_t +---@field m_selected uint8_t +---@field m_mutated boolean +---@field m_is_clutch_pressed boolean +---@field m_is_in_reverse boolean + +---@class ManualGearBox : FeatureBase +---@field private m_entity PlayerVehicle +---@field private m_cvehicle CVehicle +---@field private m_state GearState +---@field private m_flag_preset HandlingPreset +---@field private m_clutch_kick { enabled: boolean, rpm: float, timer: Timer } +---@field private m_clutch_kick_duration integer +local ManualGearBox = setmetatable({}, FeatureBase) +ManualGearBox.__index = ManualGearBox + +---@param pv PlayerVehicle +---@return ManualGearBox +function ManualGearBox.new(pv) + local base = FeatureBase.new(pv) + ---@diagnostic disable-next-line + return setmetatable(base, ManualGearBox) +end + +---@return boolean +function ManualGearBox:ShouldRun() + local veh = self.m_entity + return GVars.features.vehicle.manual_gearbox.enabled + and LocalPlayer:IsDriving() + and veh:IsValid() + and veh:IsLandVehicle() + and not veh:IsElectric() +end + +function ManualGearBox:Init() + local clutch_kick_duration = 400 + self.m_clutch_kick_duration = clutch_kick_duration + self.m_clutch_kick = { + enabled = false, + rpm = 0.0, + timer = Timer.new(clutch_kick_duration, true), + } + self.m_state = { + m_current = 0, + m_next = 0, + m_selected = 0, + m_mutated = false, + m_is_clutch_pressed = false, + m_is_in_reverse = false + } + + self.m_flag_preset = HandlingPreset.new({ + name = "Manual Gearbox", + deltas = { + [Enums.eHandlingEditorTypes.TYPE_AF] = { + ["GEARBOX_MANUAL"] = true, + ["GEARBOX_DIRECT_SHIFT"] = false, + ["GEARBOX_FULL_AUTO"] = false, + ["GEARBOX_ELECTRIC"] = false, + }, + }, + vehicle_bitset = 1 << Enums.eVehicleType.VEHICLE_TYPE_CAR | 1 << Enums.eVehicleType.VEHICLE_TYPE_BIKE + }) +end + +function ManualGearBox:Reset() + self.m_state = { + m_current = 0, + m_next = 0, + m_selected = 0, + m_mutated = false, + m_is_clutch_pressed = false, + m_is_in_reverse = false, + } + + self.m_entity.m_flag_controller:TogglePreset(self.m_flag_preset, false) + local cvehicle = self.m_cvehicle + if (not cvehicle) then return end + + local pCurrentGear = cvehicle.m_current_gear + if (pCurrentGear:is_valid() and pCurrentGear:get_byte() > 10) then + cvehicle.m_current_gear:set_byte(0) + cvehicle.m_next_gear:set_byte(0) + end +end + +function ManualGearBox:OnNewVehicle() + if (not self.m_entity:IsValid()) then + return + end + + local cvehicle = self.m_entity:Resolve() + self.m_cvehicle = cvehicle + local current_gear = cvehicle.m_current_gear:get_byte() + self.m_state.m_current = current_gear + self.m_state.m_selected = current_gear + self.m_state.m_next = cvehicle.m_next_gear:get_byte() + self.m_entity.m_flag_controller:TogglePreset(self.m_flag_preset, true) +end + +---@return boolean +function ManualGearBox:IsInReverse() + return self.m_state.m_is_in_reverse +end + +---@return uint8_t +function ManualGearBox:GetCurrentGear() + return self.m_state.m_selected +end + +---@param state GearState +---@param cvehicle CVehicle +function ManualGearBox:ShiftGears(state, cvehicle) + local gear = state.m_selected + cvehicle.m_current_gear:set_byte(gear) + cvehicle.m_next_gear:set_byte(gear) +end + +---@param state GearState +---@param cvehicle CVehicle +function ManualGearBox:OnClutchPress(state, cvehicle) + if (not state.m_mutated) then + local current_gear = cvehicle.m_current_gear:get_byte() + state.m_current = current_gear + state.m_next = cvehicle.m_next_gear:get_byte() + state.m_mutated = true + state.m_is_clutch_pressed = true + end +end + +---@param state GearState +---@param cvehicle CVehicle +function ManualGearBox:OnClutchRelease(state, cvehicle) + local changed_gears = state.m_mutated + if (state.m_mutated) then + state.m_mutated = false + state.m_is_clutch_pressed = false + end + + self:ShiftGears(state, cvehicle) + + if (PAD.IS_CONTROL_PRESSED(0, 71) or PAD.IS_CONTROL_PRESSED(0, 72)) then + if (self.m_clutch_kick.enabled) then return end + + self.m_clutch_kick.enabled = true + self.m_clutch_kick.rpm = cvehicle.m_rpm:get_float() + self.m_clutch_kick.timer:Reset() + end +end + +function ManualGearBox:HandleClutchKick() + local _t = self.m_clutch_kick + if (not _t.enabled) then return end + + local timer = _t.timer + if (timer:IsDone()) then + _t.enabled = false + _t.rpm = 0.0 + return + end + + local duration = self.m_clutch_kick_duration + local elapsed = timer:Elapsed() + local rpm = _t.rpm + local delta = math.ratio(elapsed, 0, duration) + local power = math.lerp(10.0, 1.0, delta ^ 2) * rpm + VEHICLE.SET_VEHICLE_CHEAT_POWER_INCREASE(self.m_entity:GetHandle(), power) +end + +---@param state GearState +---@param cvehicle CVehicle +function ManualGearBox:WhileClutchEngaged(state, cvehicle) + cvehicle.m_current_gear:set_byte(-1) -- underflow to uint8_max, otherwise the vehicle will still move slowly if these are set to 0 (also it will just drive normally in reverse) + cvehicle.m_next_gear:set_byte(-1) -- // + cvehicle.m_clutch:set_float(0.0) -- 0.f: engaged | 1.f: disengaged + + if (PAD.IS_CONTROL_PRESSED(0, 71) or PAD.IS_CONTROL_PRESSED(0, 72)) then + local rpm = math.lerp(cvehicle.m_rpm:get_float(), 1.0, 0.12) + cvehicle.m_rpm:set_float(rpm) + cvehicle.m_throttle:set_float(rpm) + cvehicle.m_throttle_input:set_float(rpm) + end +end + +---@param state GearState +---@param cvehicle CVehicle +function ManualGearBox:HandleGears(state, cvehicle) + local gearbox_type = GVars.features.vehicle.manual_gearbox.mode + local is_sequential = gearbox_type == eGearboxType.SEQUENTIAL + local allow_gear_select = is_sequential and true or state.m_is_clutch_pressed + if (not allow_gear_select) then return end + + if (KeyManager:IsKeybindJustPressed("shift_up")) then + local next_gear + if (self:IsInReverse()) then + next_gear = -1 + elseif (state.m_selected == -1) then + next_gear = 1 + else + local max_gears = cvehicle.m_top_gear:get_byte() + next_gear = math.min(max_gears, state.m_selected + 1) + end + + state.m_selected = next_gear + state.m_is_in_reverse = false + + if (is_sequential) then + self:ShiftGears(state, cvehicle) + end + end + + if (KeyManager:IsKeybindJustPressed("shift_down")) then + state.m_selected = math.max(-1, state.m_selected - 1) + + if (state.m_selected == -1 and self.m_entity:IsStopped()) then + state.m_is_in_reverse = true + state.m_selected = 0 + end + + if (is_sequential) then + self:ShiftGears(state, cvehicle) + end + end +end + +function ManualGearBox:HandleFwdReverse() + local veh = self.m_entity + local ctrl = self.m_state.m_is_in_reverse and 71 or 72 + local handle = veh:GetHandle() + + if (veh:GetSpeed() < 1) then + PAD.DISABLE_CONTROL_ACTION(0, ctrl, true) + VEHICLE.SET_VEHICLE_BURNOUT(handle, (is_control_pressed_2(71) and is_control_pressed_2(72))) + end + + if (PAD.IS_DISABLED_CONTROL_PRESSED(0, ctrl)) then + VEHICLE.SET_VEHICLE_BRAKE(handle, true) + end +end + +---@return boolean +function ManualGearBox:WantsNeutral() + return not self.m_entity:IsReversing() and self.m_state.m_selected <= 0 or false +end + +function ManualGearBox:Update() + if (not self.m_entity:IsValid()) then return end + + local state, cvehicle = self.m_state, self.m_cvehicle + if (KeyManager:IsKeybindPressed("clutch")) then + self:OnClutchPress(state, cvehicle) + elseif (KeyManager:IsKeybindJustReleased("clutch")) then + self:OnClutchRelease(state, cvehicle) + end + + if (state.m_is_clutch_pressed or self:WantsNeutral()) then + self:WhileClutchEngaged(state, cvehicle) + end + + self:HandleGears(state, cvehicle) + self:HandleFwdReverse() + self:HandleClutchKick() +end + +return ManualGearBox diff --git a/SSV2/includes/features/vehicle/stancer.lua b/SSV2/includes/features/vehicle/stancer.lua index 690a72e..a34e530 100644 --- a/SSV2/includes/features/vehicle/stancer.lua +++ b/SSV2/includes/features/vehicle/stancer.lua @@ -46,6 +46,7 @@ end ---@field private m_last_wheel_mod_check_time milliseconds ---@field private m_filename "stancer.json" ---@field private m_saved_models table> +---@field private m_lock { toggled: boolean, owner: string? } ---@field public m_base_values table ---@field public m_deltas table ---@field public m_wheels table> @@ -78,6 +79,7 @@ function Stancer:Init() self.m_last_tick = 0 self.m_last_wheel_mod_check_time = 0 self.m_reloading = false + self.m_lock = { toggled = false, owner = nil } self.m_bounce_mode = { enabled = false, margin = 0.09, @@ -117,6 +119,23 @@ function Stancer:ShouldRun() return self.m_entity and self.m_entity:IsValid() end +-- Marks Stancer as locked and owned by a feature +---@param ownerName string +function Stancer:Lock(ownerName) + self.m_lock.toggled = true + self.m_lock.owner = ownerName +end + +function Stancer:Unlock() + self.m_lock.toggled = false + self.m_lock.owner = nil +end + +---@return boolean locked, string? featureName +function Stancer:IsLocked() + return self.m_lock.toggled, self.m_lock.owner +end + function Stancer:InitSavedModels() if (not io.exists(self.m_filename)) then Serializer:WriteToFile(self.m_filename, {}) diff --git a/SSV2/includes/frontend/helpers/TableRenderer.lua b/SSV2/includes/frontend/helpers/TableRenderer.lua index 0a3679f..235a806 100644 --- a/SSV2/includes/frontend/helpers/TableRenderer.lua +++ b/SSV2/includes/frontend/helpers/TableRenderer.lua @@ -51,7 +51,7 @@ TableRenderer.__index = TableRenderer ---@return TableRenderer function TableRenderer.new() REF_COUNT = REF_COUNT + 1 - local uid = joaat("TABLE_RENDERER_" .. REF_COUNT) + local uid = _J("TABLE_RENDERER_" .. REF_COUNT) return setmetatable({ m_uid = uid }, TableRenderer) end @@ -59,18 +59,18 @@ end ---@param value anyval ---@param isKey boolean function TableRenderer:DrawObject(value, isKey) - local token = TableRendererToken.new(value) - local txtCol = token.m_color - local v = type(value) == "string" and _F('"%s"', value) or tostring(value) + local token = TableRendererToken.new(value) + local color = token.m_color + local v = type(value) == "string" and _F('"%s"', value) or tostring(value) if (not isKey) then - ImGui.TextColored(txtCol.r, txtCol.g, txtCol.b, txtCol.a, v) + ImGui.TextColored(color.r, color.g, color.b, color.a, v) return end ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, 0, 0) ImGui.TextColored(PURPLE.r, PURPLE.g, PURPLE.b, PURPLE.a, "[") ImGui.SameLine() - ImGui.TextColored(txtCol.r, txtCol.g, txtCol.b, txtCol.a, v) + ImGui.TextColored(color.r, color.g, color.b, color.a, v) ImGui.SameLine() ImGui.TextColored(PURPLE.r, PURPLE.g, PURPLE.b, PURPLE.a, "]") ImGui.PopStyleVar() diff --git a/SSV2/includes/frontend/stat_controller_ui.lua b/SSV2/includes/frontend/stat_controller_ui.lua index 1635bce..9f46310 100644 --- a/SSV2/includes/frontend/stat_controller_ui.lua +++ b/SSV2/includes/frontend/stat_controller_ui.lua @@ -14,7 +14,7 @@ local statChildSize = vec2:new(0, 170) local statSearchBuff = "" local newStatBuff = { name = "", type = "", lock_val = nil, autolock = false } local statDateEditBuff = nil -local currentYear = os.date("*t").year +local currentYear = os.date("*t").year ---@cast currentYear integer local currentBitView = 0 -- 0: hex | 1: binary local bitEditorIntChanged = false local statTypes = { "int", "float", "money", "bool", "string", "posix", "time", "date", "bitset" } @@ -160,7 +160,7 @@ local function drawBitsetEditor(mpStat, currentVal) ImGui.TextDisabled(bitViewStr) end ----@param buff osdate +---@param buff osdatefixed local function updateMaxDays(buff) local day = dateTimeDefault_t["day"] day.max = getMaxDaysForMonth(buff.year, buff.month) @@ -327,6 +327,11 @@ local function drawStatCards() end GUI:RegisterNewTab(Enums.eTabID.TAB_ONLINE, "SUBTAB_MPSTAT_CONTROLLER", function() + if (not Game.IsOnline()) then + ImGui.Text(_T("GENERIC_UNAVAILABLE_SP")) + return + end + ImGui.SetWindowFontScale(1.3) ImGui.Text(_T("SUBTAB_MPSTAT_CONTROLLER")) diff --git a/SSV2/includes/frontend/vehicle/handling_editor_ui.lua b/SSV2/includes/frontend/vehicle/handling_editor_ui.lua index a17e55b..6085b0a 100644 --- a/SSV2/includes/frontend/vehicle/handling_editor_ui.lua +++ b/SSV2/includes/frontend/vehicle/handling_editor_ui.lua @@ -7,11 +7,11 @@ -- * Provide a copy of or a link to the original license (GPL-3.0 or later); see LICENSE.md or . -local HandlingEditor = require("includes.modules.HandlingEditor") local Mutex = require("includes.classes.Mutex") local TableRenderer = require("includes.frontend.helpers.TableRenderer").new() local measureTextWidth = require("includes.frontend.helpers.measure_text_width") local PV = LocalPlayer:GetVehicle() +local FlagController = PV.m_flag_controller local CARS_BIT = Enums.eVehicleType.VEHICLE_TYPE_CAR local BIKES_BIT = Enums.eVehicleType.VEHICLE_TYPE_BIKE local BIT_TEST = Bit.IsBitSet @@ -161,10 +161,10 @@ local function drawVehicleFlags(data) -- also keeping them as is may help users who want to do research since they can use the exact flag name as a reference local flagName = pair.first local flagType = data.type - local isOwned = HandlingEditor:IsFlagOwned(_F("%d:%s", flagType, flagName)) + local isOwned = FlagController:IsFlagOwned(_F("%d:%s", flagType, flagName)) ImGui.BeginDisabled(isOwned) if (select(2, GUI:CustomToggle(flagName, isEnabled))) then - HandlingEditor:Push(flagType, flag, not isEnabled, data.allowed_types) + FlagController:Push(flagType, flag, not isEnabled, data.allowed_types) end ImGui.EndDisabled() if (isOwned) then @@ -179,7 +179,9 @@ local function drawVehicleFlags(data) end ImGui.EndChild() ImGui.Separator() - ImGui.Text(formatFlags(data.get_all(PV))) + ImGui.SetWindowFontScale(0.8) + ImGui.TextWrapped(formatFlags(data.get_all(PV))) + ImGui.SetWindowFontScale(1.0) end ImGui.EndDisabled() end @@ -223,7 +225,7 @@ local function drawImportWindow() TableRenderer:Draw(importedPreset.decoded, vec2:new(610, 620)) ImGui.Spacing() if (GUI:Button(_T("GENERIC_CONFIRM"), { size = btnSize })) then - if (HandlingEditor:ImportPreset(importedPreset.decoded)) then + if (FlagController:ImportPreset(importedPreset.decoded)) then Notifier:ShowSuccess("HandlingEditor", _T("VEH_FLAGS_PRESET_IMPORT_SUCCESS")) else Notifier:ShowError("HandlingEditor", _T("VEH_FLAGS_PRESET_IMPORT_FAIL")) @@ -241,7 +243,7 @@ local function drawImportWindow() end local function drawPresets() - if (not HandlingEditor:ArePresetsReady()) then + if (not FlagController:ArePresetsReady()) then ImGui.Text(ImGui.TextSpinner(_T("GENERIC_WAIT_LABEL"))) ImGui.Spacing() return @@ -263,7 +265,7 @@ local function drawPresets() ImGui.SetNextWindowBgAlpha(0.0) ImGui.BeginChild("##presetsScrollRegion", 0, GVars.ui.window_size.y * 0.675) - local presets = HandlingEditor:GetPresets() + local presets = FlagController:GetPresets() local count = #presets for i, preset in ipairs(presets) do ImGui.PushID(i) @@ -290,10 +292,10 @@ local function drawPresets() ImGui.EndChild() ImGui.Separator() - local unsupportedVeh = not HandlingEditor:AssertVehicleType(preset.m_vehicle_bitset) + local unsupportedVeh = not FlagController:AssertVehicleType(preset.m_vehicle_bitset) ImGui.BeginDisabled(unsupportedVeh) - GUI:CustomToggle(_T("GENERIC_ENABLE"), HandlingEditor:IsPresetEnabled(preset), { - onClick = function(v) HandlingEditor:TogglePreset(preset, v) end + GUI:CustomToggle(_T("GENERIC_ENABLE"), FlagController:IsPresetEnabled(preset:GetName()), { + onClick = function(v) FlagController:TogglePreset(preset, v) end }) ImGui.EndDisabled() if (unsupportedVeh) then @@ -303,8 +305,8 @@ local function drawPresets() GUI:CustomToggle(_T("GENERIC_AUTO_ENABLE"), preset.auto_apply, { onClick = function(v) preset.auto_apply = v - HandlingEditor:SavePresets() - if (v) then HandlingEditor:TogglePreset(preset, v) end + FlagController:SavePresets() + if (v) then FlagController:TogglePreset(preset, v) end end, tooltip = _T("VEH_FLAGS_AUTOENABLE_TT") }) @@ -366,7 +368,7 @@ local function drawPresets() end if (ImGui.DialogBox(_T("GENERIC_WARN_LABEL"), _T("GENERIC_CONFIRM_WARN"), ImGuiDialogBoxStyle.WARN)) then - HandlingEditor:RemovePreset(preset) + FlagController:RemovePreset(preset) end ImGui.SetNextWindowSizeConstraints(400, 160, 600, 600) @@ -462,7 +464,7 @@ local function drawNewPresetWindow() return end - if (HandlingEditor:DoesPresetExistByName(data.nameBuffer)) then + if (FlagController:DoesPresetExistByName(data.nameBuffer)) then GUI:Text(_T("GENERIC_NAME_ERR"), { color = Color.RED }) return end @@ -470,7 +472,7 @@ local function drawNewPresetWindow() ImGui.Spacing() ImGui.Separator() if (GUI:Button(_T("GENERIC_CONFIRM"), { size = btnSize })) then - if (HandlingEditor:AddNewPreset(HandlingEditor:GeneratePresetFromCurrentDeltas( + if (FlagController:AddNewPreset(FlagController:GeneratePresetFromCurrentDeltas( data.nameBuffer, data.vehTypesBs, data.descBuffer, @@ -493,7 +495,7 @@ return function() return end - if (not HandlingEditor:IsInitialized()) then + if (not FlagController:IsInitialized()) then ImGui.Text(_T("GENERIC_UNAVAILABLE")) return end @@ -527,11 +529,11 @@ return function() mainButtonLabelWidths[landIdx] = maxButtonWidth end - local hasPresets = HandlingEditor:IsAnyPresetEnabled() - local hasEdits = HandlingEditor:HasEdits() + local hasPresets = FlagController:IsAnyPresetEnabled() + local hasEdits = FlagController:HasEdits() ImGui.BeginDisabled(hasPresets or not hasEdits) if (GUI:Button(_T("GENERIC_RESET_DEFAULT"), { size = vec2:new(maxButtonWidth, 37.5) })) then - HandlingEditor:Reset(true) + FlagController:Reset(true) end ImGui.EndDisabled() if (hasPresets) then @@ -552,7 +554,7 @@ return function() if (Backend.debug_mode) then ImGui.SameLine() if (ImGui.Button("Parse Object", maxButtonWidth, 37.5) and hasEdits) then - print(table.serialize(HandlingEditor:GeneratePresetFromCurrentDeltas("test", 1 << CARS_BIT))) + print(table.serialize(FlagController:GeneratePresetFromCurrentDeltas("test", 1 << CARS_BIT))) end end diff --git a/SSV2/includes/frontend/vehicle/stancer_ui.lua b/SSV2/includes/frontend/vehicle/stancer_ui.lua index 5cb09f5..c60cec6 100644 --- a/SSV2/includes/frontend/vehicle/stancer_ui.lua +++ b/SSV2/includes/frontend/vehicle/stancer_ui.lua @@ -7,6 +7,7 @@ -- * Provide a copy of or a link to the original license (GPL-3.0 or later); see LICENSE.md or . +local WARN_YELLOW = Color("safety_yellow") local PV = LocalPlayer:GetVehicle() local Stancer = PV.m_stancer local frontStanceDeltas = Stancer.m_deltas[Enums.eWheelAxle.FRONT] @@ -156,6 +157,90 @@ local function DrawSlider(key, deltaTable, axle, needsPhysicsUpdate) end end +local function DrawSavedVehicles() + local saved_models = Stancer:GetSavedModels() + if (next(saved_models) ~= nil) then + if (GUI:Button(_T("VEH_STANCE_VIEW_SAVED"))) then + savedVehsWindow.should_draw = true + end + + ImGui.SameLine() + GVars.features.vehicle.stancer.auto_apply_saved = GUI:CustomToggle( + _T("VEH_STANCE_AUTOAPPLY"), + GVars.features.vehicle.stancer.auto_apply_saved, + { + tooltip = _T("VEH_STANCE_AUTOAPPLY_TT"), + onClick = function(v) + if (not v) then return end + ThreadManager:Run(function() Stancer:LoadSavedDeltas() end) + end + } + ) + end + + local is_saved = Stancer:IsVehicleModelSaved() + local save_label = _T(is_saved and "VEH_STANCE_UPDATE_MODEL" or "VEH_STANCE_SAVE_MODEL") + if (GUI:Button(save_label)) then + if (is_saved) then + ImGui.OpenPopup(save_label) + else + Stancer:SaveCurrentVehicle() + end + end + + if (is_saved and ImGui.DialogBox(save_label, _T("VEH_STANCE_UPDATE_WARN", Stancer:GetCurrentModelName()), ImGuiDialogBoxStyle.WARN)) then + Stancer:SaveCurrentVehicle() + end + + if (not savedVehsWindow.should_draw) then + return + end + + ImGui.Begin("##viewSavedVehicles", + ImGuiWindowFlags.NoTitleBar | + ImGuiWindowFlags.NoMove | + ImGuiWindowFlags.NoResize + ) + GUI:QuickConfigWindow(_T("VEH_STANCE_VIEW_SAVED"), function() + if (ImGui.BeginListBox("##savedVehList", -1, 360)) then + for modelName in pairs(saved_models) do + local name = Game.GetVehicleDisplayName(modelName) + if (ImGui.Selectable(name, (selectedSavedModel == modelName))) then + selectedSavedModel = modelName + end + end + ImGui.EndListBox() + end + + ImGui.Separator() + + ImGui.BeginDisabled(#selectedSavedModel == 0) + if (GUI:Button(_T("GENERIC_APPLY"))) then + Stancer:LoadSavedDeltas(selectedSavedModel) + end + + ImGui.SameLine() + if (GUI:Button(_T("GENERIC_REMOVE"))) then + Stancer:RemoveSavedVehicle(selectedSavedModel) + end + ImGui.EndDisabled() + + ImGui.SameLine() + if (GUI:Button(_T("GENERIC_REMOVE_ALL"))) then + ImGui.OpenPopup(_T("GENERIC_REMOVE_ALL")) + end + + if (ImGui.DialogBox(_T("GENERIC_REMOVE_ALL"))) then + Stancer:RemoveAllSavedVehicles() + savedVehsWindow.should_draw = false + end + end, function() + savedVehsWindow.should_draw = false + end, true) + + ImGui.End() +end + return function() if (PV:GetHandle() == 0) then ImGui.Text(_T("GENERIC_NOT_IN_VEH")) @@ -167,6 +252,13 @@ return function() return end + local isLocked, ownerName = Stancer:IsLocked() + if (isLocked and ownerName) then + GUI:Text(_T("VEH_STANCE_LOCKED_FMT", ownerName), { color = WARN_YELLOW }) + ImGui.Spacing() + end + + ImGui.BeginDisabled(isLocked) if (GUI:Button(_T("GENERIC_RESET_ALL"))) then ThreadManager:Run(function() Stancer:Reset() @@ -257,7 +349,7 @@ return function() ImGui.EndDisabled() ImGui.SameLine() - Stancer.m_bounce_mode.enabled, _ = GUI:CustomToggle(_T("VEH_STANCE_BOUNCE_MODE"), + Stancer.m_bounce_mode.enabled = GUI:CustomToggle(_T("VEH_STANCE_BOUNCE_MODE"), Stancer.m_bounce_mode.enabled, { tooltip = _T("VEH_STANCE_BOUNCE_MODE_TT"), @@ -268,86 +360,7 @@ return function() ) ImGui.Separator() - - local saved_models = Stancer:GetSavedModels() - if (next(saved_models) ~= nil) then - if (GUI:Button(_T("VEH_STANCE_VIEW_SAVED"))) then - savedVehsWindow.should_draw = true - end - - ImGui.SameLine() - GVars.features.vehicle.stancer.auto_apply_saved = GUI:CustomToggle( - _T("VEH_STANCE_AUTOAPPLY"), - GVars.features.vehicle.stancer.auto_apply_saved, - { - tooltip = _T("VEH_STANCE_AUTOAPPLY_TT"), - onClick = function(v) - if (not v) then return end - ThreadManager:Run(function() Stancer:LoadSavedDeltas() end) - end - } - ) - end - - local is_saved = Stancer:IsVehicleModelSaved() - local save_label = _T(is_saved and "VEH_STANCE_UPDATE_MODEL" or "VEH_STANCE_SAVE_MODEL") - if (GUI:Button(save_label)) then - if (is_saved) then - ImGui.OpenPopup(save_label) - else - Stancer:SaveCurrentVehicle() - end - end - - if (is_saved and ImGui.DialogBox(save_label, _T("VEH_STANCE_UPDATE_WARN", Stancer:GetCurrentModelName()), ImGuiDialogBoxStyle.WARN)) then - Stancer:SaveCurrentVehicle() - end - - if (savedVehsWindow.should_draw) then - ImGui.Begin("##viewSavedVehicles", - ImGuiWindowFlags.NoTitleBar | - ImGuiWindowFlags.NoMove | - ImGuiWindowFlags.NoResize - ) - GUI:QuickConfigWindow(_T("VEH_STANCE_VIEW_SAVED"), function() - if (ImGui.BeginListBox("##savedVehList", -1, 360)) then - for modelName in pairs(saved_models) do - local name = Game.GetVehicleDisplayName(modelName) - if (ImGui.Selectable(name, (selectedSavedModel == modelName))) then - selectedSavedModel = modelName - end - end - ImGui.EndListBox() - end - - ImGui.Separator() - - ImGui.BeginDisabled(#selectedSavedModel == 0) - if (GUI:Button(_T("GENERIC_APPLY"))) then - Stancer:LoadSavedDeltas(selectedSavedModel) - end - - ImGui.SameLine() - if (GUI:Button(_T("GENERIC_REMOVE"))) then - Stancer:RemoveSavedVehicle(selectedSavedModel) - end - ImGui.EndDisabled() - - ImGui.SameLine() - if (GUI:Button(_T("GENERIC_REMOVE_ALL"))) then - ImGui.OpenPopup(_T("GENERIC_REMOVE_ALL")) - end - - if (ImGui.DialogBox(_T("GENERIC_REMOVE_ALL"))) then - Stancer:RemoveAllSavedVehicles() - savedVehsWindow.should_draw = false - end - end, function() - savedVehsWindow.should_draw = false - end, true) - - ImGui.End() - end - + DrawSavedVehicles() + ImGui.EndDisabled() ImGui.Spacing() end diff --git a/SSV2/includes/frontend/vehicle/vehicle_ui.lua b/SSV2/includes/frontend/vehicle/vehicle_ui.lua index f1b4b93..e368331 100644 --- a/SSV2/includes/frontend/vehicle/vehicle_ui.lua +++ b/SSV2/includes/frontend/vehicle/vehicle_ui.lua @@ -7,102 +7,78 @@ -- * Provide a copy of or a link to the original license (GPL-3.0 or later); see LICENSE.md or . -local selected_mine_name -local Refs = require("includes.data.refs") -local default_cfg = require("includes.data.config") -local driftMG = require("includes.features.vehicle.drift_minigame") -local customPaintsUI = require("includes.frontend.vehicle.custom_paints_ui") -local engine_swap_index = 1 -local vehicleTab = GUI:RegisterNewTab(Enums.eTabID.TAB_VEHICLE, "SUBTAB_CARS", nil, nil, true) -local optionPopup = { +local Refs = require("includes.data.refs") +local default_cfg = require("includes.data.config") +local driftMG = require("includes.features.vehicle.drift_minigame") +local customPaintsUI = require("includes.frontend.vehicle.custom_paints_ui") +local engine_swap_index = 1 +local vehicleTab = GUI:RegisterNewTab(Enums.eTabID.TAB_VEHICLE, "SUBTAB_CARS", nil, nil, true) +local optionPopup = { flags = ImGuiWindowFlags.NoResize | ImGuiWindowFlags.AlwaysAutoResize, label = "##optionsPopup", should_draw = false, ---@type function? callback = nil } -DriftMinigame = LocalPlayer:GetVehicle():AddFeature(driftMG) +DriftMinigame = LocalPlayer:GetVehicle():AddFeature(driftMG) + +local vehicleRadioStations = { { station = "OFF", name = "Off" } } +for _, v in ipairs(Audio.RadioStations) do + local station = v.station + if (station:startswith("HIDDEN") or station == "RADIO_30_DLC_HEI4_MIX1_REVERB") then + goto continue + end + + table.insert(vehicleRadioStations, v) + ::continue:: +end local function speedoOptions() local resolution = Game.GetScreenResolution() + local cfg = GVars.features.speedometer ImGui.Text(_T("VEH_SPEED_UNIT")) ImGui.Separator() - GVars.features.speedometer.speed_unit, _ = ImGui.RadioButton("M/s", GVars.features.speedometer.speed_unit, 0) + + cfg.speed_unit = ImGui.RadioButton("M/s", cfg.speed_unit, 0) ImGui.SameLine() - GVars.features.speedometer.speed_unit, _ = ImGui.RadioButton("Km/h", GVars.features.speedometer.speed_unit, 1) + cfg.speed_unit = ImGui.RadioButton("Km/h", cfg.speed_unit, 1) ImGui.SameLine() - GVars.features.speedometer.speed_unit, _ = ImGui.RadioButton("Mi/h", GVars.features.speedometer.speed_unit, 2) + cfg.speed_unit = ImGui.RadioButton("Mi/h", cfg.speed_unit, 2) ImGui.Spacing() ImGui.Text(_T("GENERIC_POSITION_LABEL")) ImGui.Separator() - GVars.features.speedometer.pos.x, _ = ImGui.SliderFloat( - _T("GENERIC_LEFT_RIGHT_LABEL"), - GVars.features.speedometer.pos.x, - 0.0, - resolution.x - (GVars.features.speedometer.radius * 2.2) - ) - GVars.features.speedometer.pos.y, _ = ImGui.SliderFloat( - _T("GENERIC_UP_DOWN_LABEL"), - GVars.features.speedometer.pos.y, 0.0, - resolution.y - (GVars.features.speedometer.radius * 2) - ) + + cfg.pos.x = ImGui.SliderFloat(_T("GENERIC_LEFT_RIGHT_LABEL"), cfg.pos.x, 0.0, resolution.x - (cfg.radius * 2.2)) + cfg.pos.y = ImGui.SliderFloat(_T("GENERIC_UP_DOWN_LABEL"), cfg.pos.y, 0.0, resolution.y - (cfg.radius * 2)) ImGui.Spacing() ImGui.Text(_T("GENERIC_COLORS_LABEL")) ImGui.Separator() - GVars.features.speedometer.colors.circle, _ = ImGui.ColorEditU32(_T("VEH_SPEED_CIRCLE"), - GVars.features.speedometer.colors.circle - ) - - GVars.features.speedometer.colors.circle_bg, _ = ImGui.ColorEditU32(_T("VEH_SPEED_BG"), - GVars.features.speedometer.colors.circle_bg - ) - - GVars.features.speedometer.colors.text, _ = ImGui.ColorEditU32(_T("VEH_SPEED_TEXT"), - GVars.features.speedometer.colors.text - ) - - GVars.features.speedometer.colors.markings, _ = ImGui.ColorEditU32(_T("VEH_SPEED_MARK"), - GVars.features.speedometer.colors.markings - ) - - GVars.features.speedometer.colors.needle, _ = ImGui.ColorEditU32(_T("VEH_SPEED_NEEDLE"), - GVars.features.speedometer.colors.needle - ) - GVars.features.speedometer.colors.needle_base, _ = ImGui.ColorEditU32(_T("VEH_SPEED_NEEDLE_BASE"), - GVars.features.speedometer.colors.needle_base - ) + cfg.colors.circle = ImGui.ColorEditU32(_T("VEH_SPEED_CIRCLE"), cfg.colors.circle) + cfg.colors.circle_bg = ImGui.ColorEditU32(_T("VEH_SPEED_BG"), cfg.colors.circle_bg) + cfg.colors.text = ImGui.ColorEditU32(_T("VEH_SPEED_TEXT"), cfg.colors.text) + cfg.colors.markings = ImGui.ColorEditU32(_T("VEH_SPEED_MARK"), cfg.colors.markings) + cfg.colors.needle = ImGui.ColorEditU32(_T("VEH_SPEED_NEEDLE"), cfg.colors.needle) + cfg.colors.needle_base = ImGui.ColorEditU32(_T("VEH_SPEED_NEEDLE_BASE"), cfg.colors.needle_base) - if GUI:Button(_T("GENERIC_RESET")) then - GVars.features.speedometer.colors = default_cfg.features.speedometer.colors + if (GUI:Button(_T("GENERIC_RESET"))) then + cfg.colors = table.copy(default_cfg.features.speedometer.colors) end end local function driftOptions() - GVars.features.vehicle.drift.mode, _ = ImGui.Combo(_T("VEH_DRIFT_MODE"), - GVars.features.vehicle.drift.mode, - _F("%s\0%s\0%s\0", _T("VEH_DRIFT_MODE_STRONG"), _T("VEH_DRIFT_MODE_SLIPPERY"), _T("VEH_DRIFT_MODE_MIXED")) - ) + local cfg = GVars.features.vehicle.drift + cfg.mode = ImGui.Combo(_T("VEH_DRIFT_MODE"), cfg.mode, { _T("VEH_DRIFT_MODE_STRONG"), _T("VEH_DRIFT_MODE_SLIPPERY"), _T("VEH_DRIFT_MODE_MIXED") }, 3) + cfg.power = ImGui.SliderInt(_T("VEH_POWER_GAIN"), cfg.power, 10, 100) - GVars.features.vehicle.drift.power, _ = ImGui.SliderInt(_T("VEH_POWER_GAIN"), - GVars.features.vehicle.drift.power, - 10, - 100 - ) - - ImGui.BeginDisabled(GVars.features.vehicle.drift.mode == 1) - GVars.features.vehicle.drift.intensity, _ = ImGui.SliderInt(_T("VEH_DRIFT_MODE_INTENSITY"), - GVars.features.vehicle.drift.intensity, - 0, - 3 - ) + ImGui.BeginDisabled(cfg.mode == 1) + cfg.intensity = ImGui.SliderInt(_T("VEH_DRIFT_MODE_INTENSITY"), cfg.intensity, 0, 3) ImGui.EndDisabled() GUI:HelpMarker(_T("VEH_DRIFT_MODE_INTENSITY_TT")) - GVars.features.vehicle.drift.smoke_fx.enabled = GUI:CustomToggle(_T("VEH_DRIFT_SMOKE"), - GVars.features.vehicle.drift.smoke_fx.enabled, + cfg.smoke_fx.enabled = GUI:CustomToggle(_T("VEH_DRIFT_SMOKE"), cfg.smoke_fx.enabled, { tooltip = _T("VEH_DRIFT_SMOKE_TT"), onClick = function() @@ -115,47 +91,37 @@ local function driftOptions() } ) - if (GVars.features.vehicle.drift.smoke_fx.enabled) then - ImGui.ColorEditVec3(_T("VEH_DRIFT_SMOKE_COL"), GVars.features.vehicle.drift.smoke_fx.color) + if (cfg.smoke_fx.enabled) then + ImGui.ColorEditVec3(_T("VEH_DRIFT_SMOKE_COL"), cfg.smoke_fx.color) end end local function driftMinigameOptions() - GVars.features.vehicle.drift_minigame.score_sound, _ = GUI:CustomToggle(_T("VEH_DRIFT_MINIGAME_SOUND_OPT"), - GVars.features.vehicle.drift_minigame.score_sound, + local cfg = GVars.features.vehicle.drift_minigame + cfg.score_sound = GUI:CustomToggle(_T("VEH_DRIFT_MINIGAME_SOUND_OPT"), cfg.score_sound, { tooltip = _T("VEH_DRIFT_MINIGAME_SOUND_OPT_TT") } ) ImGui.Spacing() - ImGui.BulletText(_T("VEH_DRIFT_MINIGAME_PB_LABEL", - string.formatint(GVars.features.vehicle.drift_minigame.player_best)) - ) + ImGui.BulletText(_T("VEH_DRIFT_MINIGAME_PB_LABEL", string.formatint(cfg.player_best))) end local function nosOptions() - GVars.features.vehicle.nos.power, _ = ImGui.SliderInt(_T("VEH_POWER_GAIN"), GVars.features.vehicle.nos.power, 10, 100) - - GVars.features.vehicle.nos.screen_effect, _ = GUI:CustomToggle(_T("VEH_NOS_SCREEN_FX"), - GVars.features.vehicle.nos.screen_effect - ) - - GVars.features.vehicle.nos.sound_effect, _ = GUI:CustomToggle(_T("VEH_NOS_SOUND_FX"), - GVars.features.vehicle.nos.sound_effect - ) - - GVars.features.vehicle.nos.can_damage_engine, _ = GUI:CustomToggle(_T("VEH_NOS_DAMAGE_CB"), - GVars.features.vehicle.nos.can_damage_engine, - { tooltip = _T("VEH_NOS_DAMAGE_TT") } - ) + local cfg = GVars.features.vehicle.nos + cfg.power = ImGui.SliderInt(_T("VEH_POWER_GAIN"), cfg.power, 10, 100) + cfg.screen_effect = GUI:CustomToggle(_T("VEH_NOS_SCREEN_FX"), cfg.screen_effect) + cfg.sound_effect = GUI:CustomToggle(_T("VEH_NOS_SOUND_FX"), cfg.sound_effect) + cfg.can_damage_engine = GUI:CustomToggle(_T("VEH_NOS_DAMAGE_CB"), cfg.can_damage_engine, { tooltip = _T("VEH_NOS_DAMAGE_TT") }) end local function minesOptions() - if (ImGui.BeginCombo("Vehicle Mine Type", selected_mine_name or "Unselected")) then + local cfg = GVars.features.vehicle.mines + if (ImGui.BeginCombo(_T("GENERIC_TYPE"), cfg.name or _T("GENERIC_NONE"))) then for _, pair in pairs(LocalPlayer:GetVehicle().mines) do - local selected = GVars.features.vehicle.mines.selected_type_hash == pair.second + local selected = cfg.selected_type_hash == pair.second if (ImGui.Selectable(pair.first, selected)) then - GVars.features.vehicle.mines.selected_type_hash = pair.second - selected_mine_name = pair.first + cfg.selected_type_hash = pair.second + cfg.name = pair.first end end @@ -163,19 +129,48 @@ local function minesOptions() end end +local function defaultStationOptions() + ImGui.Spacing() + local cfg = GVars.features.vehicle.default_station + if (ImGui.BeginCombo("##defaultRadio", cfg.display_name)) then + for _, v in ipairs(vehicleRadioStations) do + local station = v.station + local name = v.name + local selected = cfg.station_name == station + if (ImGui.Selectable(name, selected)) then + cfg.station_name = station + cfg.display_name = name + ThreadManager:Run(function() + if (not LocalPlayer:IsDriving()) then return end + LocalPlayer:GetVehicle():SetRadioStation(station) + end) + end + end + + ImGui.EndCombo() + end +end + +local function gearboxOptions() + ImGui.Spacing() + local cfg = GVars.features.vehicle.manual_gearbox + cfg.mode = ImGui.Combo("##gboxManual", cfg.mode, { _T("VEH_GEARBOX_MAN_WCLUTCH"), _T("VEH_GEARBOX_SEQUENTIAL") }, 2) +end + local function popsOptions() - GVars.features.vehicle.bangs_rpm_min = ImGui.SliderFloat("Pops & Bangs RPM Min", - GVars.features.vehicle.bangs_rpm_min, + local cfg = GVars.features.vehicle + cfg.bangs_rpm_min = ImGui.SliderFloat("Pops & Bangs RPM Min", + cfg.bangs_rpm_min, 2000.0, - GVars.features.vehicle.bangs_rpm_max - 1000.0, - "%.0f RPM", GVars.features.vehicle.bangs_rpm_min + cfg.bangs_rpm_max - 1000.0, + "%.0f RPM", cfg.bangs_rpm_min ) - GVars.features.vehicle.bangs_rpm_max = ImGui.SliderFloat("Pops & Bangs RPM Max", - GVars.features.vehicle.bangs_rpm_max, - GVars.features.vehicle.bangs_rpm_min + 1000.0, + cfg.bangs_rpm_max = ImGui.SliderFloat("Pops & Bangs RPM Max", + cfg.bangs_rpm_max, + cfg.bangs_rpm_min + 1000.0, 9000.0, - "%.0f RPM", GVars.features.vehicle.bangs_rpm_max + "%.0f RPM", cfg.bangs_rpm_max ) end @@ -411,10 +406,10 @@ vehicleTab:AddBoolCommand("VEH_LAUNCH_CTRL", return GVars.features.vehicle.launch_control end, callback = function() - optionPopup.label = _T("VEH_LAUNCH_CTRL") + optionPopup.label = _T("VEH_LAUNCH_CTRL_MODE") optionPopup.should_draw = true optionPopup.callback = function() - GVars.features.vehicle.launch_control_mode, _ = ImGui.Combo(_T("VEH_LAUNCH_CTRL_MODE"), + GVars.features.vehicle.launch_control_mode = ImGui.Combo("##launchCtrlMode", GVars.features.vehicle.launch_control_mode, _F("%s\0%s\0", _T("VEH_LAUNCH_CTRL_REALISTIC"), _T("VEH_LAUNCH_CTRL_RIDICULOUS")) ) @@ -459,6 +454,50 @@ vehicleTab:AddBoolCommand("VEH_MINES", } } ) +vehicleTab:AddBoolCommand("VEH_DEFAULT_RADIO", + { + gvar_key = "features.vehicle.default_station.enabled", + meta = { description = "VEH_DEFAULT_RADIO_TT" }, + translate_label = true, + options_data = { + condition = function() + return GVars.features.vehicle.default_station.enabled + end, + callback = function() + optionPopup.callback = defaultStationOptions + optionPopup.label = _T("VEH_DEFAULT_RADIO") + optionPopup.should_draw = true + end + } + } +) +vehicleTab:AddBoolCommand("VEH_MANUAL_GEARBOX", + { + gvar_key = "features.vehicle.manual_gearbox.enabled", + meta = { description = "VEH_MANUAL_GEARBOX_TT" }, + translate_label = true, + on_enable = function() + local PV = LocalPlayer:GetVehicle() + if (not PV:IsValid()) then return end + PV.m_manual_gearbox:OnNewVehicle() + end, + on_disable = function() + local PV = LocalPlayer:GetVehicle() + if (not PV:IsValid()) then return end + PV.m_manual_gearbox:Reset() + end, + options_data = { + condition = function() + return GVars.features.vehicle.manual_gearbox.enabled + end, + callback = function() + optionPopup.callback = gearboxOptions + optionPopup.label = _T("VEH_MANUAL_GEARBOX_TYPE") + optionPopup.should_draw = true + end + } + } +) vehicleTab:RegisterGUI(function() vehicleTab:GetGridRenderer():Draw() diff --git a/SSV2/includes/init.lua b/SSV2/includes/init.lua index be492dd..b5cc6b8 100644 --- a/SSV2/includes/init.lua +++ b/SSV2/includes/init.lua @@ -98,7 +98,7 @@ require("includes.modules.Color") GPointers = require("includes.data.pointers") GGlobals = require("includes.data.script_globals") Memory = require("includes.modules.Memory") -KeyManager = require("includes.services.KeyManager"):init() +KeyManager = require("includes.services.KeyManager") GUI = require("includes.services.GUI") Notifier = require("includes.services.ToastNotifier").new() CommandExecutor = require("includes.services.CommandExecutor"):init() diff --git a/SSV2/includes/lib/extensions/std_math.lua b/SSV2/includes/lib/extensions/std_math.lua index 658bb05..c98607e 100644 --- a/SSV2/includes/lib/extensions/std_math.lua +++ b/SSV2/includes/lib/extensions/std_math.lua @@ -7,8 +7,16 @@ -- * Provide a copy of or a link to the original license (GPL-3.0 or later); see LICENSE.md or . -local Cast = require("includes.modules.Cast") -local fmt = string.format +local Cast = require("includes.modules.Cast") +local fmt = string.format +local std_abs = math.abs +local std_floor = math.floor +local std_ceil = math.ceil +local std_cos = math.cos +local std_max = math.max +local std_min = math.min +local std_sin = math.sin +local std_sqrt = math.sqrt ---@param n float ---@param x integer number of decimal points @@ -23,9 +31,7 @@ function math.sum(...) local result = 0 local args = type(...) == "table" and ... or { ... } local __len = #args - if (__len == 0) then - return 0 - end + if (__len == 0) then return 0 end for i = 1, __len do if (type(args[i]) == "number") then @@ -61,7 +67,7 @@ end ---@return boolean function math.is_equal(a, b, e) e = e or 1e-6 - return a == b or math.abs(a - b) < e + return a == b or std_abs(a - b) < e end local INT_SIZES = { @@ -119,21 +125,29 @@ end ---@param max number ---@return number function math.clamp(v, min, max) - return math.max(min, math.min(max, v)) + return std_max(min, std_min(max, v)) end +-- Returns a value between 0 and 1 representing the min/max normalization of `v` +---@param v number +---@param min number minimum value +---@param max number maximum value +function math.ratio(v, min, max) + return (v - min) / (max - min) +end; math.normalize = math.ratio + ---@param a number ---@param b number ----@param t number delta +---@param t float delta a float between 0 and 1 function math.lerp(a, b, t) return a + (b - a) * math.clamp(t, 0, 1) end --- Generates a triangular wave oscillating between 1 and -1 +-- Generates a triangular wave oscillating between -1 and 1 ---@param t number ---@return number function math.tent(t) - return 2 * math.abs(2 * (t - math.floor(t + 0.5))) - 1 + return 2 * std_abs(2 * (t - std_floor(t + 0.5))) - 1 end -- 3x² - 2x² diff --git a/SSV2/includes/lib/meta.lua b/SSV2/includes/lib/meta.lua index f0f765f..dc3da4e 100644 --- a/SSV2/includes/lib/meta.lua +++ b/SSV2/includes/lib/meta.lua @@ -8,6 +8,7 @@ ---@meta +---@diagnostic disable: duplicate-doc-field --#region Generic Containers @@ -80,6 +81,15 @@ GenericClass = setmetatable({}, { ---@alias anyval table|metatable|userdata|lightuserdata|function|string|number|boolean Any Lua value except nil. ---@alias optional T? +-- LuaLS has these fields defined as integer|string which is annoying as fuck +---@class osdatefixed : osdate +---@field year integer +---@field month integer +---@field day integer +---@field hour integer +---@field min integer +---@field sec integer + --#endregion --#region Functional Types diff --git a/SSV2/includes/lib/translations/__hashmap.json b/SSV2/includes/lib/translations/__hashmap.json index 4fb079b..cad7fe8 100644 --- a/SSV2/includes/lib/translations/__hashmap.json +++ b/SSV2/includes/lib/translations/__hashmap.json @@ -262,7 +262,7 @@ "VEH_AUTO_LOCK": 2929456827, "VEH_AUTO_LOCK_TT": 3593309825, "VEH_LAUNCH_CTRL": 1671031000, - "VEH_LAUNCH_CTRL_TT": 2400379767, + "VEH_LAUNCH_CTRL_TT": 2763265565, "VEH_IV_EXIT": 2851644593, "VEH_IV_EXIT_TT": 1049465994, "VEH_KEEP_WHEELS_TURNED": 602098388, @@ -965,5 +965,13 @@ "YRV3_DASHBOARD_BOSS_REGISTER": 248929079, "MPSTAT_CONTROLLDER_DESC": 1596639631, "MPSTAT_LOCK_VAL_TT": 48368387, - "YRV3_DASHBOARD_MANAGE_FUNDS": 926196671 + "YRV3_DASHBOARD_MANAGE_FUNDS": 926196671, + "VEH_STANCE_LOCKED_FMT": 4040292814, + "VEH_DEFAULT_RADIO": 3883542029, + "VEH_DEFAULT_RADIO_TT": 1680347732, + "VEH_MANUAL_GEARBOX": 1395185966, + "VEH_MANUAL_GEARBOX_TT": 3142855508, + "VEH_MANUAL_GEARBOX_TYPE": 4266478295, + "VEH_GEARBOX_MAN_WCLUTCH": 1596093975, + "VEH_GEARBOX_SEQUENTIAL": 3372169230 } \ No newline at end of file diff --git a/SSV2/includes/lib/translations/de-DE.lua b/SSV2/includes/lib/translations/de-DE.lua index ffbd9c1..26b713d 100644 --- a/SSV2/includes/lib/translations/de-DE.lua +++ b/SSV2/includes/lib/translations/de-DE.lua @@ -311,7 +311,7 @@ return { ["VEH_HIGH_BEAMS"] = "Fernlicht auf Hupe", ["VEH_IV_EXIT"] = "Ausgang im IV-Stil", ["VEH_KEEP_WHEELS_TURNED"] = "Halten Sie die Räder in Bewegung", - ["VEH_LAUNCH_CTRL_TT"] = "Simuliert die Startsteuerung. Nur für Performance-Fahrzeuge verfügbar.", + ["VEH_LAUNCH_CTRL_TT"] = "Simuliert die Startsteuerung in zwei Modi:\n – Stationär: Halten Sie [ACCELETRATE] + [BRAKE] 3 Sekunden lang gedrückt und lassen Sie dann die Bremse los.\n – Rollen: Halten Sie bei einer Geschwindigkeit unter der Höchstgeschwindigkeit [ACCELERATE] + die zugewiesene Tastenkombination (Standard [N]) 3 Sekunden lang gedrückt und lassen Sie dann die Tastenkombination los.\n\nHINWEIS: Der stationäre Modus funktioniert nicht mit „Manuellem Getriebe“.", ["VEH_AUTO_LOCK_TT"] = "Verriegelt Ihr Fahrzeug automatisch, wenn Sie sich von ihm entfernen, und entriegelt es wieder, wenn Sie versuchen, wieder einzusteigen oder zu einem anderen Fahrzeug zu wechseln.", ["VEH_IV_EXIT_TT"] = "Imitiert den Fahrzeugausstiegsstil von GTA IV: Halten Sie [F] eine Sekunde lang gedrückt, um den Motor auszuschalten, oder drücken Sie normal, um ihn laufen zu lassen.", ["VEH_KEEP_WHEELS_TURNED_TT"] = "Hält die Räder Ihres Fahrzeugs in jedem Winkel, in dem Sie sie vor dem Verlassen des Fahrzeugs gelassen haben.", @@ -965,5 +965,13 @@ return { ["YRV3_DASHBOARD_BOSS_CEO_FMT"] = "CEO von %s", ["MPSTAT_CONTROLLDER_DESC"] = "Verwalten, bearbeiten und sperren Sie Multiplayer-Statistiken dauerhaft.", ["MPSTAT_LOCK_VAL_TT"] = "Sperrt die Statistik auf den aktuellen Wert.\n\nHinweis: Sie müssen Ihren gewünschten Wert festlegen, bevor Sie diese Option aktivieren, da durch die Aktivierung des Schalters der aktuelle Statistikwert erfasst und gespeichert wird, damit er im Hintergrund geschrieben wird.", - ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "Fonds verwalten" + ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "Fonds verwalten", + ["VEH_STANCE_LOCKED_FMT"] = "„Stancer“ gehört derzeit „%s“. Deaktivieren Sie die Besitzerfunktion, um die Benutzeroberfläche zu entsperren.", + ["VEH_GEARBOX_SEQUENTIAL"] = "Sequentiell", + ["VEH_MANUAL_GEARBOX_TYPE"] = "Getriebetyp", + ["VEH_DEFAULT_RADIO"] = "Standardradio", + ["VEH_DEFAULT_RADIO_TT"] = "Zwingt das Radio Ihres Fahrzeugs jedes Mal zum ausgewählten Sender, wenn Sie das Fahrzeug wechseln.\n\nSie können einen Sender auswählen, indem Sie die Taste [ drücken. . .] Optionsschaltfläche nach dem Umschalten der Funktion.", + ["VEH_MANUAL_GEARBOX"] = "Manuelles Getriebe", + ["VEH_GEARBOX_MAN_WCLUTCH"] = "Manuell mit Kupplung", + ["VEH_MANUAL_GEARBOX_TT"] = "Ermöglicht das manuelle Schalten der Gänge in Autos und Motorrädern.\n\nHinweis: Wenn diese Option aktiviert ist, funktioniert „Launch Control“ im Stillstand nicht; Nur der „Rolling“-Modus funktioniert." } diff --git a/SSV2/includes/lib/translations/en-US.lua b/SSV2/includes/lib/translations/en-US.lua index 44373c8..2599e37 100644 --- a/SSV2/includes/lib/translations/en-US.lua +++ b/SSV2/includes/lib/translations/en-US.lua @@ -554,7 +554,7 @@ return { ["VEH_AUTO_LOCK"] = "Auto Lock", ["VEH_AUTO_LOCK_TT"] = "Automatically locks your vehicle when you move away from it and unlocks it again when you try to re-enter or switch to a different vehicle.", ["VEH_LAUNCH_CTRL"] = "Launch Control", - ["VEH_LAUNCH_CTRL_TT"] = "Simulates launch control. Only available for performance cars.", + ["VEH_LAUNCH_CTRL_TT"] = "Simulates launch control in two modes:\n - Stationary: Hold [ACCELETRATE] + [BRAKE] for 3 seconds then release the brake.\n - Rolling: While moving at a speed less than max, hold [ACCELERATE] + the assigned keybind (default [N]) for 3 seconds then release the keybind.\n\nNOTE: Stationary mode does not work with 'Manual Gearbox'.", ["VEH_LAUNCH_CTRL_MODE"] = "Launch Control Mode", ["VEH_LAUNCH_CTRL_REALISTIC"] = "Realistic", ["VEH_LAUNCH_CTRL_RIDICULOUS"] = "Ridiculous", @@ -701,6 +701,7 @@ return { ["VEH_STANCE_WHEEL_SIZE"] = "Wheel Size", ["VEH_STANCE_NON_STOCK"] = "This option requires non-stock wheels.", ["VEH_STANCE_INCOMPATIBLE"] = "This option is incompatible with current vehicle.", + ["VEH_STANCE_LOCKED_FMT"] = "'Stancer' is currently owned by '%s'. Disable the owner feature to unlock the user interface.", ["VEH_STANCE_FRONT_AXLE"] = "Front Axle", ["VEH_STANCE_REAR_AXLE"] = "Rear Axle", ["VEH_STANCE_COPY_FB"] = "Copy Front To Back", @@ -720,6 +721,13 @@ return { ["VEH_STANCE_VIEW_SAVED"] = "View Saved Vehicles", ["VEH_LOCKED"] = "Vehicle locked.", ["VEH_UNLOCKED"] = "Vehicle unlocked.", + ["VEH_DEFAULT_RADIO"] = "Default Radio", + ["VEH_DEFAULT_RADIO_TT"] = "Forces your vehicle's radio to the selected station each time you switch vehicles.\n\nYou can select a station by pressing the [. . .] options button after toggling the feature.", + ["VEH_MANUAL_GEARBOX"] = "Manual Gearbox", + ["VEH_MANUAL_GEARBOX_TT"] = "Allows you to manually shift gears in cars and motorcycles.\n\nNote: When this is enabled, 'Launch Control' will not work while stationary; only 'Rolling' mode will work.", + ["VEH_MANUAL_GEARBOX_TYPE"] = "Gearbox Type", + ["VEH_GEARBOX_MAN_WCLUTCH"] = "Manual With Clutch", + ["VEH_GEARBOX_SEQUENTIAL"] = "Sequential", --#endregion --#region World diff --git a/SSV2/includes/lib/translations/es-ES.lua b/SSV2/includes/lib/translations/es-ES.lua index d98d87a..d9758ca 100644 --- a/SSV2/includes/lib/translations/es-ES.lua +++ b/SSV2/includes/lib/translations/es-ES.lua @@ -311,7 +311,7 @@ return { ["VEH_AUTO_LOCK_TT"] = "Bloquea automáticamente su vehículo cuando se aleja de él y lo desbloquea nuevamente cuando intenta volver a ingresar o cambiar a un vehículo diferente.", ["VEH_LAUNCH_CTRL"] = "Control de lanzamiento", ["VEH_RGB_LIGHTS"] = "Faros RGB", - ["VEH_LAUNCH_CTRL_TT"] = "Simula el control de lanzamiento. Sólo disponible para autos de alto rendimiento.", + ["VEH_LAUNCH_CTRL_TT"] = "Simula el control de lanzamiento en dos modos:\n - Estacionario: mantenga presionado [ACELERAR] + [FRENO] durante 3 segundos y luego suelte el freno.\n - Rodante: mientras se mueve a una velocidad inferior a la máxima, mantenga presionado [ACELERAR] + la combinación de teclas asignada (predeterminada [N]) durante 3 segundos y luego suelte la combinación de teclas.\n\nNOTA: El modo estacionario no funciona con la 'Caja de cambios manual'.", ["VEH_IV_EXIT_TT"] = "Imita el estilo de salida del vehículo de GTA IV: mantén presionado [F] durante un segundo para apagar el motor o presiona normalmente para dejarlo funcionando.", ["VEH_MINES"] = "Minas de vehículos", ["VEH_KEEP_WHEELS_TURNED"] = "Mantenga las ruedas en marcha", @@ -965,5 +965,13 @@ return { ["YRV3_DASHBOARD_BOSS_RETIRE"] = "Retirarse", ["YRV3_DASHBOARD_BOSS_REGISTER"] = "Regístrate como jefe", ["MPSTAT_LOCK_VAL_TT"] = "Bloquea la estadística al valor actual.\n\nNota: Debe establecer el valor deseado antes de activar esta opción, ya que al activar la opción se captura y guarda el valor de la estadística actual para escribirlo en segundo plano.", - ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "Administrar fondos" + ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "Administrar fondos", + ["VEH_STANCE_LOCKED_FMT"] = "'Stancer' actualmente es propiedad de '%s'. Deshabilite la función de propietario para desbloquear la interfaz de usuario.", + ["VEH_DEFAULT_RADIO"] = "Radio predeterminada", + ["VEH_GEARBOX_SEQUENTIAL"] = "Secuencial", + ["VEH_MANUAL_GEARBOX"] = "Caja de cambios manual", + ["VEH_GEARBOX_MAN_WCLUTCH"] = "Manual con embrague", + ["VEH_MANUAL_GEARBOX_TYPE"] = "Tipo de caja de cambios", + ["VEH_MANUAL_GEARBOX_TT"] = "Le permite cambiar de marcha manualmente en automóviles y motocicletas.\n\nNota: cuando esto está habilitado, el 'Control de inicio' no funcionará mientras esté parado; Sólo funcionará el modo 'Rolling'.", + ["VEH_DEFAULT_RADIO_TT"] = "Fuerza la radio de tu vehículo a la estación seleccionada cada vez que cambias de vehículo.\n\nPuedes seleccionar una estación presionando el botón [. . .] botón de opciones después de alternar la función." } diff --git a/SSV2/includes/lib/translations/fr-FR.lua b/SSV2/includes/lib/translations/fr-FR.lua index aa66904..bd05e37 100644 --- a/SSV2/includes/lib/translations/fr-FR.lua +++ b/SSV2/includes/lib/translations/fr-FR.lua @@ -315,7 +315,7 @@ return { ["VEH_SPEED_BG"] = "Arrière-plan", ["VEH_SPEED_CIRCLE"] = "Cercle", ["VEH_MINES"] = "Mines de véhicules", - ["VEH_LAUNCH_CTRL_TT"] = "Simule le contrôle de lancement. Uniquement disponible pour les voitures de performance.", + ["VEH_LAUNCH_CTRL_TT"] = "Simule le contrôle de lancement dans deux modes :\n - Stationnaire : maintenez [ACCÉLÉRER] + [BRAKE] pendant 3 secondes, puis relâchez le frein.\n - Roulant : tout en vous déplaçant à une vitesse inférieure à la vitesse maximale, maintenez [ACCÉLÉRER] + la combinaison de touches attribuée (par défaut [N]) pendant 3 secondes, puis relâchez la combinaison de touches.\n\nREMARQUE : le mode stationnaire ne fonctionne pas avec la « boîte de vitesses manuelle ».", ["VEH_FLAPPY_DOORS"] = "Portes battantes", ["VEH_IV_EXIT_TT"] = "Imite le style de sortie du véhicule de GTA IV : maintenez [F] pendant une seconde pour éteindre le moteur ou appuyez normalement pour le laisser tourner.", ["VEH_MINES_TT"] = "Vous permet de larguer une mine depuis n'importe quel véhicule terrestre.", @@ -965,5 +965,13 @@ return { ["MPSTAT_CONTROLLDER_DESC"] = "Gérez, modifiez et verrouillez les statistiques multijoueurs avec persistance.", ["YRV3_DASHBOARD_BOSS_PRES_FMT"] = "Président de %s", ["MPSTAT_LOCK_VAL_TT"] = "Verrouille la statistique sur la valeur actuelle.\n\nRemarque : Vous devez définir la valeur souhaitée avant de l'activer, car l'activation de la bascule capture et enregistre la valeur de la statistique actuelle pour l'écrire en arrière-plan.", - ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "Gérer les fonds" + ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "Gérer les fonds", + ["VEH_STANCE_LOCKED_FMT"] = "'Stancer' appartient actuellement à '%s'. Désactivez la fonction propriétaire pour déverrouiller l’interface utilisateur.", + ["VEH_MANUAL_GEARBOX"] = "Boîte de vitesses manuelle", + ["VEH_MANUAL_GEARBOX_TT"] = "Vous permet de changer manuellement de vitesse dans les voitures et les motos.\n\nRemarque : lorsque cette option est activée, « Contrôle de lancement » ne fonctionnera pas à l'arrêt ; seul le mode « Rolling » fonctionnera.", + ["VEH_GEARBOX_SEQUENTIAL"] = "Séquentiel", + ["VEH_DEFAULT_RADIO"] = "Radio par défaut", + ["VEH_MANUAL_GEARBOX_TYPE"] = "Type de boîte de vitesses", + ["VEH_GEARBOX_MAN_WCLUTCH"] = "Manuel avec embrayage", + ["VEH_DEFAULT_RADIO_TT"] = "Force la radio de votre véhicule à la station sélectionnée à chaque fois que vous changez de véhicule.\n\nVous pouvez sélectionner une station en appuyant sur la touche [. . .] bouton d’options après avoir activé la fonctionnalité." } diff --git a/SSV2/includes/lib/translations/it-IT.lua b/SSV2/includes/lib/translations/it-IT.lua index 1a716c8..76079a4 100644 --- a/SSV2/includes/lib/translations/it-IT.lua +++ b/SSV2/includes/lib/translations/it-IT.lua @@ -311,7 +311,7 @@ return { ["VEH_KEEP_WHEELS_TURNED"] = "Mantieni le ruote girate", ["VEH_IV_EXIT"] = "Uscita IV-Stile", ["VEH_FLAPPY_DOORS"] = "Porte a soffietto", - ["VEH_LAUNCH_CTRL_TT"] = "Simula il controllo del lancio. Disponibile solo per auto ad alte prestazioni.", + ["VEH_LAUNCH_CTRL_TT"] = "Simula il controllo del lancio in due modalità:\n - Stazionario: tieni premuto [ACCELETRATE] + [BRAKE] per 3 secondi, quindi rilascia il freno.\n - Rolling: mentre ti muovi a una velocità inferiore alla massima, tieni premuto [ACCELETRATE] + la combinazione di tasti assegnata (predefinita [N]) per 3 secondi, quindi rilascia la combinazione di tasti.\n\nNOTA: la modalità stazionaria non funziona con il \"cambio manuale\".", ["VEH_SPEED_CIRCLE"] = "Cerchio", ["VEH_KEEP_WHEELS_TURNED_TT"] = "Mantiene le ruote del tuo veicolo nell'angolazione che hai lasciato prima di scendere dal veicolo.", ["VEH_IV_EXIT_TT"] = "Imita lo stile di uscita del veicolo di GTA IV: tieni premuto [F] per un secondo per spegnere il motore o premi normalmente per lasciarlo acceso.", @@ -965,5 +965,13 @@ return { ["YRV3_DASHBOARD_BOSS_PRES_FMT"] = "Presidente di %s", ["YRV3_DASHBOARD_BOSS_TYPE"] = "Tipo di capo", ["MPSTAT_LOCK_VAL_TT"] = "Blocca la statistica sul valore corrente.\n\nNota: è necessario impostare il valore desiderato prima di attivare questa opzione, poiché l'attivazione dell'interruttore acquisisce e salva il valore della statistica corrente da scrivere in background.", - ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "Gestisci i fondi" + ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "Gestisci i fondi", + ["VEH_STANCE_LOCKED_FMT"] = "'Stancer' è attualmente di proprietà di '%s'. Disabilitare la funzione proprietario per sbloccare l'interfaccia utente.", + ["VEH_MANUAL_GEARBOX"] = "Cambio manuale", + ["VEH_DEFAULT_RADIO"] = "Radio predefinita", + ["VEH_MANUAL_GEARBOX_TYPE"] = "Tipo di cambio", + ["VEH_GEARBOX_SEQUENTIAL"] = "Sequenziale", + ["VEH_MANUAL_GEARBOX_TT"] = "Ti consente di cambiare marcia manualmente in auto e moto.\n\nNota: quando è abilitato, \"Launch Control\" non funzionerà da fermo; funzionerà solo la modalità 'Rolling'.", + ["VEH_DEFAULT_RADIO_TT"] = "Forza la radio del tuo veicolo alla stazione selezionata ogni volta che cambi veicolo.\n\nPuoi selezionare una stazione premendo il pulsante [. . .] pulsante delle opzioni dopo aver attivato la funzione.", + ["VEH_GEARBOX_MAN_WCLUTCH"] = "Manuale Con Frizione" } diff --git a/SSV2/includes/lib/translations/ja-JP.lua b/SSV2/includes/lib/translations/ja-JP.lua index fbe3096..f4542f3 100644 --- a/SSV2/includes/lib/translations/ja-JP.lua +++ b/SSV2/includes/lib/translations/ja-JP.lua @@ -311,7 +311,7 @@ return { ["VEH_AUTO_LOCK_TT"] = "車両から離れると自動的にロックされ、再乗車するか別の車両に乗り換えようとすると再びロックが解除されます。", ["VEH_SPEED_CIRCLE"] = "丸", ["VEH_SPEED_BG"] = "背景", - ["VEH_LAUNCH_CTRL_TT"] = "ローンチコントロールをシミュレートします。パフォーマンスカーにのみご利用いただけます。", + ["VEH_LAUNCH_CTRL_TT"] = "2 つのモードでローンチ コントロールをシミュレートします:\n - 静止: [ACCELETRATE] + [BRAKE] を 3 秒間押し続けてからブレーキを放します。\n - ローリング: 最大速度未満で移動中に、[ACCELERATE] + 割り当てられたキーバインド (デフォルトは [N]) を 3 秒間押し続けてからキーバインドを放します。\n\n注: 静止モードは「手動ギアボックス」では機能しません。", ["VEH_LAUNCH_CTRL"] = "ローンチコントロール", ["VEH_FLAPPY_DOORS_TT"] = "なぜこれを作ったのか分かりません。", ["VEH_RGB_LIGHTS_TT"] = "車のヘッドライトで RGB ループを開始します。このウィンドウの下部で速度を調整できます。", @@ -965,5 +965,13 @@ return { ["YRV3_DASHBOARD_BOSS_RETIRE"] = "引退", ["YRV3_DASHBOARD_BOSS_REGISTER"] = "ボスとして登録する", ["MPSTAT_LOCK_VAL_TT"] = "統計を現在の値にロックします。\n\n注: トグルをアクティブにすると、バックグラウンドで書き込まれる現在の統計値がキャプチャおよび保存されるため、これをオンにする前に希望の値を設定する必要があります。", - ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "資金を管理する" + ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "資金を管理する", + ["VEH_STANCE_LOCKED_FMT"] = "「Stancer」は現在「%s」によって所有されています。ユーザー インターフェイスのロックを解除するには、所有者機能を無効にします。", + ["VEH_MANUAL_GEARBOX"] = "マニュアルギアボックス", + ["VEH_GEARBOX_SEQUENTIAL"] = "一連", + ["VEH_MANUAL_GEARBOX_TT"] = "車やオートバイのギアを手動でシフトできるようにします。\n\n注: これを有効にすると、停止中は「ローンチ コントロール」が機能しなくなります。 「ローリング」モードのみが機能します。", + ["VEH_DEFAULT_RADIO_TT"] = "車両を切り替えるたびに、車両のラジオを選択した放送局に強制的に送信します。\n\n[. 。機能を切り替えた後、.] オプション ボタンを押します。", + ["VEH_MANUAL_GEARBOX_TYPE"] = "ギアボックスの種類", + ["VEH_GEARBOX_MAN_WCLUTCH"] = "マニュアルクラッチ付", + ["VEH_DEFAULT_RADIO"] = "デフォルトの無線" } diff --git a/SSV2/includes/lib/translations/ko-KR.lua b/SSV2/includes/lib/translations/ko-KR.lua index 32a5718..30e042c 100644 --- a/SSV2/includes/lib/translations/ko-KR.lua +++ b/SSV2/includes/lib/translations/ko-KR.lua @@ -308,7 +308,7 @@ return { ["VEH_AUTO_LOCK"] = "자동 잠금", ["VEH_FLAPPY_DOORS_TT"] = "내가 왜 이것을 만들었는지 전혀 모르겠습니다.", ["VEH_STRONG_CRASH_TT"] = "캠 흔들림, 화면 효과, 더 많은 변형 및 손상으로 충돌을 더욱 무섭게 만듭니다. 고속 충돌은 치명적일 수 있습니다.", - ["VEH_LAUNCH_CTRL_TT"] = "발사 제어를 시뮬레이션합니다. 고성능 차량에만 사용 가능합니다.", + ["VEH_LAUNCH_CTRL_TT"] = "두 가지 모드로 발사 제어를 시뮬레이션합니다.\n - 고정: [ACCELETRATE] + [BRAKE]를 3초 동안 누른 다음 브레이크를 놓습니다.\n - 롤링: 최대보다 낮은 속도로 이동하는 동안 [ACCELERATE] + 할당된 키 바인딩(기본값 [N])을 3초 동안 누른 다음 키 바인딩을 놓습니다.\n\n참고: 고정 모드는 '수동 기어박스'에서는 작동하지 않습니다.", ["VEH_FLAPPY_DOORS"] = "플래피 도어", ["VEH_LAUNCH_CTRL"] = "발사 제어", ["VEH_IV_EXIT_TT"] = "GTA IV의 차량 하차 스타일을 모방합니다. [F]를 1초 동안 누르면 엔진이 꺼지고, 보통을 누르면 계속 작동됩니다.", @@ -965,5 +965,13 @@ return { ["YRV3_DASHBOARD_BOSS_REGISTER"] = "상사로 등록", ["MPSTAT_CONTROLLDER_DESC"] = "지속적으로 멀티플레이어 통계를 관리, 편집 및 잠급니다.", ["MPSTAT_LOCK_VAL_TT"] = "통계를 현재 값으로 잠급니다.\n\n참고: 토글을 활성화하면 백그라운드에 쓸 현재 통계 값을 캡처하고 저장하기 때문에 이 기능을 켜기 전에 원하는 값을 설정해야 합니다.", - ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "자금 관리" + ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "자금 관리", + ["VEH_STANCE_LOCKED_FMT"] = "'Stancer'는 현재 '%s'이(가) 소유하고 있습니다. 사용자 인터페이스를 잠금 해제하려면 소유자 기능을 비활성화하십시오.", + ["VEH_GEARBOX_SEQUENTIAL"] = "잇달아 일어나는", + ["VEH_MANUAL_GEARBOX_TT"] = "자동차와 오토바이의 기어를 수동으로 변속할 수 있습니다.\n\n참고: 이 기능이 활성화되면 정지 상태에서는 '런치 컨트롤'이 작동하지 않습니다. '롤링' 모드만 작동합니다.", + ["VEH_DEFAULT_RADIO_TT"] = "차량을 전환할 때마다 차량의 라디오가 선택된 방송국으로 강제 전환됩니다.\n\n[를 눌러 방송국을 선택할 수 있습니다. . .] 기능을 전환한 후 옵션 버튼을 누릅니다.", + ["VEH_MANUAL_GEARBOX_TYPE"] = "기어박스 유형", + ["VEH_DEFAULT_RADIO"] = "기본 라디오", + ["VEH_MANUAL_GEARBOX"] = "수동변속기", + ["VEH_GEARBOX_MAN_WCLUTCH"] = "클러치가 있는 수동" } diff --git a/SSV2/includes/lib/translations/pl-PL.lua b/SSV2/includes/lib/translations/pl-PL.lua index c39d9fc..9422f54 100644 --- a/SSV2/includes/lib/translations/pl-PL.lua +++ b/SSV2/includes/lib/translations/pl-PL.lua @@ -308,7 +308,7 @@ return { ["VEH_RGB_LIGHTS_TT"] = "Uruchamia pętlę RGB w reflektorach pojazdu. Możesz dostosować prędkość w dolnej części tego okna.", ["VEH_KEEP_WHEELS_TURNED"] = "Trzymaj koła skręcone", ["VEH_STRONG_CRASH"] = "Silniejsze awarie", - ["VEH_LAUNCH_CTRL_TT"] = "Symuluje kontrolę startu. Dostępne tylko dla samochodów wyczynowych.", + ["VEH_LAUNCH_CTRL_TT"] = "Symuluje kontrolę startu w dwóch trybach:\n - Na postoju: Przytrzymaj [ACCELETRATE] + [HAMULEC] przez 3 sekundy, a następnie zwolnij hamulec.\n - Toczenie: Podczas jazdy z prędkością mniejszą niż maksymalna, przytrzymaj [ACCELERATE] + przypisany klawisz (domyślnie [N]) przez 3 sekundy, a następnie zwolnij klawisz.\n\nUWAGA: Tryb stacjonarny nie działa z ręczną skrzynią biegów.", ["VEH_LAUNCH_CTRL"] = "Uruchom kontrolę", ["VEH_IV_EXIT_TT"] = "Naśladuje styl opuszczania pojazdu z GTA IV: Przytrzymaj [F] przez jedną sekundę, aby wyłączyć silnik, lub naciśnij normalnie, aby pozostawić włączony.", ["VEH_MINES"] = "Kopalnie pojazdów", @@ -965,5 +965,13 @@ return { ["YRV3_DASHBOARD_BOSS_REGISTER"] = "Zarejestruj się jako szef", ["YRV3_DASHBOARD_BOSS_PRES_FMT"] = "Prezydent %s", ["MPSTAT_LOCK_VAL_TT"] = "Blokuje statystykę do bieżącej wartości.\n\nUwaga: Przed włączeniem tej opcji musisz ustawić żądaną wartość, ponieważ aktywacja przełącznika przechwytuje i zapisuje bieżącą wartość statystyki do zapisania w tle.", - ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "Zarządzaj funduszami" + ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "Zarządzaj funduszami", + ["VEH_STANCE_LOCKED_FMT"] = "„Stancer” jest obecnie własnością „%s”. Wyłącz funkcję właściciela, aby odblokować interfejs użytkownika.", + ["VEH_MANUAL_GEARBOX_TYPE"] = "Typ skrzyni biegów", + ["VEH_DEFAULT_RADIO"] = "Domyślne radio", + ["VEH_GEARBOX_SEQUENTIAL"] = "Ciągły", + ["VEH_DEFAULT_RADIO_TT"] = "Wymusza przełączenie radia pojazdu na wybraną stację przy każdej zmianie pojazdu.\n\nMożesz wybrać stację, naciskając przycisk [. . .] przycisk opcji po przełączeniu tej funkcji.", + ["VEH_MANUAL_GEARBOX_TT"] = "Umożliwia ręczną zmianę biegów w samochodach i motocyklach.\n\nUwaga: gdy ta opcja jest włączona, funkcja „Launch Control” nie będzie działać podczas postoju; będzie działać tylko tryb „Rolling”.", + ["VEH_MANUAL_GEARBOX"] = "Ręczna skrzynia biegów", + ["VEH_GEARBOX_MAN_WCLUTCH"] = "Ręczny ze sprzęgłem" } diff --git a/SSV2/includes/lib/translations/pt-BR.lua b/SSV2/includes/lib/translations/pt-BR.lua index 504855d..9833fc8 100644 --- a/SSV2/includes/lib/translations/pt-BR.lua +++ b/SSV2/includes/lib/translations/pt-BR.lua @@ -309,7 +309,7 @@ return { ["VEH_FLAPPY_DOORS_TT"] = "Não tenho ideia de por que fiz isso.", ["VEH_IV_EXIT"] = "Saída estilo IV", ["VEH_AUTO_LOCK_TT"] = "Tranca automaticamente o seu veículo quando você se afasta dele e destranca-o novamente quando você tenta entrar novamente ou mudar para um veículo diferente.", - ["VEH_LAUNCH_CTRL_TT"] = "Simula o controle de lançamento. Disponível apenas para carros de alto desempenho.", + ["VEH_LAUNCH_CTRL_TT"] = "Simula o controle de lançamento em dois modos:\n - Estacionário: segure [ACCELETRATE] + [BRAKE] por 3 segundos e depois solte o freio.\n - Rolando: enquanto se move a uma velocidade menor que a máxima, segure [ACCELERATE] + a tecla atribuída (padrão [N]) por 3 segundos e depois solte a tecla.\n\nNOTA: O modo estacionário não funciona com 'Caixa de câmbio manual'.", ["VEH_KEEP_WHEELS_TURNED"] = "Mantenha as rodas giradas", ["VEH_AUTO_LOCK"] = "Bloqueio automático", ["VEH_LAUNCH_CTRL"] = "Controle de lançamento", @@ -965,5 +965,13 @@ return { ["YRV3_DASHBOARD_BOSS_REGISTER"] = "Registre-se como chefe", ["MPSTAT_CONTROLLDER_DESC"] = "Gerencie, edite e bloqueie estatísticas multijogador com persistência.", ["MPSTAT_LOCK_VAL_TT"] = "Bloqueia a estatística para o valor atual.\n\nNota: Você deve definir o valor desejado antes de ativar esta opção, pois a ativação da alternância captura e salva o valor da estatística atual para ser gravado em segundo plano.", - ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "Gerenciar fundos" + ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "Gerenciar fundos", + ["VEH_STANCE_LOCKED_FMT"] = "'Sancer' é atualmente propriedade de '%s'. Desative o recurso de proprietário para desbloquear a interface do usuário.", + ["VEH_DEFAULT_RADIO"] = "Rádio padrão", + ["VEH_DEFAULT_RADIO_TT"] = "Força o rádio do seu veículo para a estação selecionada sempre que você troca de veículo.\n\nVocê pode selecionar uma estação pressionando o botão [. . .] botão de opções após alternar o recurso.", + ["VEH_GEARBOX_SEQUENTIAL"] = "Sequencial", + ["VEH_MANUAL_GEARBOX"] = "Caixa de velocidades manual", + ["VEH_MANUAL_GEARBOX_TYPE"] = "Tipo de caixa de velocidades", + ["VEH_GEARBOX_MAN_WCLUTCH"] = "Manual com embreagem", + ["VEH_MANUAL_GEARBOX_TT"] = "Permite que você mude manualmente de marcha em carros e motocicletas.\n\nObservação: quando ativado, o 'Controle de Lançamento' não funcionará enquanto estiver parado; apenas o modo 'Rolling' funcionará." } diff --git a/SSV2/includes/lib/translations/ru-RU.lua b/SSV2/includes/lib/translations/ru-RU.lua index 8b5c329..0828add 100644 --- a/SSV2/includes/lib/translations/ru-RU.lua +++ b/SSV2/includes/lib/translations/ru-RU.lua @@ -311,7 +311,7 @@ return { ["VEH_FLAPPY_DOORS_TT"] = "Я понятия не имею, почему я это сделал.", ["VEH_IV_EXIT"] = "Выход в стиле IV", ["VEH_LAUNCH_CTRL"] = "Контроль запуска", - ["VEH_LAUNCH_CTRL_TT"] = "Имитирует управление запуском. Доступно только для высокопроизводительных автомобилей.", + ["VEH_LAUNCH_CTRL_TT"] = "Имитирует управление запуском в двух режимах:\n - Неподвижный: удерживайте [ACCELERATE] + [BRAKE] в течение 3 секунд, затем отпустите тормоз.\n - Вращение: при движении со скоростью ниже максимальной удерживайте [ACCELERATE] + назначенную комбинацию клавиш (по умолчанию [N]) в течение 3 секунд, затем отпустите комбинацию клавиш.\n\nПРИМЕЧАНИЕ: Стационарный режим не работает с «Ручной коробкой передач».", ["VEH_IV_EXIT_TT"] = "Имитирует стиль выхода из автомобиля GTA IV: удерживайте [F] в течение одной секунды, чтобы выключить двигатель, или нажмите обычное нажатие, чтобы оставить его работающим.", ["VEH_SPEED_CIRCLE"] = "Круг", ["VEH_SPEED_BG"] = "Фон", @@ -965,5 +965,13 @@ return { ["YRV3_DASHBOARD_BOSS_REGISTER"] = "Зарегистрироваться как босс", ["YRV3_DASHBOARD_BOSS_TYPE"] = "Тип босса", ["MPSTAT_LOCK_VAL_TT"] = "Привязывает статистику к текущему значению.\n\nПримечание: перед включением этого параметра необходимо установить желаемое значение, поскольку активация переключателя фиксирует и сохраняет текущее значение статистики для записи в фоновом режиме.", - ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "Управление средствами" + ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "Управление средствами", + ["VEH_STANCE_LOCKED_FMT"] = "«Stancer» в настоящее время принадлежит «%s». Отключите функцию владельца, чтобы разблокировать пользовательский интерфейс.", + ["VEH_MANUAL_GEARBOX_TYPE"] = "Тип коробки передач", + ["VEH_DEFAULT_RADIO"] = "Радио по умолчанию", + ["VEH_MANUAL_GEARBOX"] = "Механическая коробка передач", + ["VEH_GEARBOX_SEQUENTIAL"] = "Последовательный", + ["VEH_DEFAULT_RADIO_TT"] = "Принудительно переключает радиоприемник вашего автомобиля на выбранную станцию ​​каждый раз, когда вы меняете транспортное средство.\n\nВы можете выбрать станцию, нажав кнопку [. . .] кнопка опций после переключения функции.", + ["VEH_GEARBOX_MAN_WCLUTCH"] = "Руководство со сцеплением", + ["VEH_MANUAL_GEARBOX_TT"] = "Позволяет вручную переключать передачи в автомобилях и мотоциклах.\n\nПримечание: если этот параметр включен, «Управление запуском» не будет работать во время стоянки; будет работать только режим «Rolling»." } diff --git a/SSV2/includes/lib/translations/zh-CN.lua b/SSV2/includes/lib/translations/zh-CN.lua index b208913..cdf84b5 100644 --- a/SSV2/includes/lib/translations/zh-CN.lua +++ b/SSV2/includes/lib/translations/zh-CN.lua @@ -307,7 +307,7 @@ return { ["VEH_RGB_LIGHTS"] = "RGB 头灯", ["VEH_FLAPPY_DOORS_TT"] = "我不知道我为什么要做这个。", ["VEH_IV_EXIT"] = "IV 式退出", - ["VEH_LAUNCH_CTRL_TT"] = "模拟发射控制。仅适用于高性能汽车。", + ["VEH_LAUNCH_CTRL_TT"] = "在两种模式下模拟启动控制:\n - 静止:按住 [ACCELETRATE] + [BRAKE] 3 秒钟,然后松开刹车。\n - 滚动:以低于最大速度移动时,按住 [ACCELERATE] + 指定的按键绑定(默认 [N])3 秒钟,然后松开按键绑定。\n\n注意:静止模式不适用于“手动变速箱”。", ["VEH_AUTO_LOCK_TT"] = "当您离开车辆时自动锁定您的车辆,并在您尝试重新进入或切换到其他车辆时再次解锁。", ["VEH_MINES"] = "车辆地雷", ["VEH_LAUNCH_CTRL"] = "发射控制", @@ -965,5 +965,13 @@ return { ["YRV3_DASHBOARD_BOSS_RETIRED"] = "退休", ["MPSTAT_CONTROLLDER_DESC"] = "持久管理、编辑和锁定多人游戏统计数据。", ["MPSTAT_LOCK_VAL_TT"] = "将统计数据锁定为当前值。\n\n注意:您必须在打开此功能之前设置所需的值,因为激活切换会捕获并保存要在后台写入的当前统计数据值。", - ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "管理资金" + ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "管理资金", + ["VEH_STANCE_LOCKED_FMT"] = "“Stancer”当前由“%s”拥有。禁用所有者功能以解锁用户界面。", + ["VEH_DEFAULT_RADIO"] = "默认收音机", + ["VEH_DEFAULT_RADIO_TT"] = "每次切换车辆时,强制将车辆的无线电广播到选定的电台。\n\n您可以通过按 [ 选择电台。 。 .] 切换功能后的选项按钮。", + ["VEH_MANUAL_GEARBOX_TYPE"] = "变速箱类型", + ["VEH_GEARBOX_MAN_WCLUTCH"] = "手动带离合器", + ["VEH_MANUAL_GEARBOX"] = "手动变速箱", + ["VEH_GEARBOX_SEQUENTIAL"] = "顺序", + ["VEH_MANUAL_GEARBOX_TT"] = "允许您在汽车和摩托车中手动换档。\n\n注意:启用此功能后,“启动控制”在静止时将不起作用;只有“滚动”模式才有效。" } diff --git a/SSV2/includes/lib/translations/zh-TW.lua b/SSV2/includes/lib/translations/zh-TW.lua index 27ac337..4edae8a 100644 --- a/SSV2/includes/lib/translations/zh-TW.lua +++ b/SSV2/includes/lib/translations/zh-TW.lua @@ -318,7 +318,7 @@ return { ["VEH_MINES"] = "車輛地雷", ["VEH_MINES_TT"] = "允許你從任何陸地車輛上投下地雷。", ["VEH_SPEED_MARK"] = "標記", - ["VEH_LAUNCH_CTRL_TT"] = "模擬發射控制。僅適用於高性能汽車。", + ["VEH_LAUNCH_CTRL_TT"] = "在兩種模式下模擬啟動控制:\n - 靜止:按住 [ACCELETRATE] + [BRAKE] 3 秒鐘,然後放開煞車。 \n - 捲動:以低於最大速度移動時,按住 [ACCELERATE] + 指定的按鍵綁定(預設 [N])3 秒鐘,然後放開按鍵綁定。 \n\n注意:靜止模式不適用於「手排變速箱」。", ["VEH_LAUNCH_CTRL"] = "發射控制", ["VEH_KEEP_WHEELS_TURNED_TT"] = "在離開車輛之前,使車輪保持在您離開的任何角度。", ["VEH_KEEP_WHEELS_TURNED"] = "保持車輪轉動", @@ -965,5 +965,13 @@ return { ["YRV3_DASHBOARD_BOSS_REGISTER"] = "註冊成為老闆", ["MPSTAT_CONTROLLDER_DESC"] = "持久管理、編輯和鎖定多人遊戲統計數據。", ["MPSTAT_LOCK_VAL_TT"] = "將統計資料鎖定為目前值。 \n\n注意:您必須在開啟此功能之前設定所需的值,因為啟動切換會擷取並儲存要在背景寫入的目前統計資料值。", - ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "管理資金" + ["YRV3_DASHBOARD_MANAGE_FUNDS"] = "管理資金", + ["VEH_STANCE_LOCKED_FMT"] = "「Stancer」目前由「%s」擁有。停用所有者功能以解鎖使用者介面。", + ["VEH_DEFAULT_RADIO"] = "預設收音機", + ["VEH_GEARBOX_SEQUENTIAL"] = "順序", + ["VEH_MANUAL_GEARBOX_TYPE"] = "變速箱類型", + ["VEH_MANUAL_GEARBOX_TT"] = "允許您在汽車和摩托車中手動換檔。 \n\n注意:啟用此功能後,「啟動控制」在靜止時將無法運作;只有「滾動」模式才有效。", + ["VEH_DEFAULT_RADIO_TT"] = "每次切換車輛時,強制將車輛的無線電廣播到選定的電台。 \n\n您可以按 [ 選擇電台。 。 .] 切換功能後的選項按鈕。", + ["VEH_GEARBOX_MAN_WCLUTCH"] = "手動帶離合器", + ["VEH_MANUAL_GEARBOX"] = "手排變速箱" } diff --git a/SSV2/includes/modules/Accessor.lua b/SSV2/includes/modules/Accessor.lua index ecb5db2..272058d 100644 --- a/SSV2/includes/modules/Accessor.lua +++ b/SSV2/includes/modules/Accessor.lua @@ -263,6 +263,37 @@ function Accessor:ClearBits(bits) self:WriteInt(v) end +---@param offset integer +---@param index integer +---@return self, integer +function Accessor:AtPackedBit(offset, index) + local bucket = index // 32 + local bitPos = index % 32 + return self:At(offset, bucket), bitPos +end + +---@param offset integer +---@param bit integer +---@return integer +function Accessor:GetPackedBit(offset, bit) + local instance, bitPos = self:AtPackedBit(offset, bit) + return instance:GetBit(bitPos) +end + +---@param offset integer +---@param bit integer +function Accessor:SetPackedBit(offset, bit) + local instance, bitPos = self:AtPackedBit(offset, bit) + instance:SetBit(bitPos) +end + +---@param offset integer +---@param bit integer +function Accessor:ClearPackedBit(offset, bit) + local instance, bitPos = self:AtPackedBit(offset, bit) + instance:ClearBit(bitPos) +end + -------------------------------------------------------- -------------------------------------------------------- -------------------------------------------------------- diff --git a/SSV2/includes/modules/Audio.lua b/SSV2/includes/modules/Audio.lua index b2b5796..103db71 100644 --- a/SSV2/includes/modules/Audio.lua +++ b/SSV2/includes/modules/Audio.lua @@ -209,3 +209,16 @@ Audio.RadioStations = { { station = "RADIO_05_TALK_01", name = "West Coast Talk Radio" }, { station = "RADIO_13_JAZZ", name = "Worldwide FM" }, } + +ThreadManager:Run(function(s) + for _, v in ipairs(Audio.RadioStations) do + local gxt = Game.GetGXTLabel(v.station) + if (gxt ~= "NULL") then + v.name = gxt + end + end + + table.sort(Audio.RadioStations, function(a, b) + return a.name < b.name + end) +end) diff --git a/SSV2/includes/modules/Chrono.lua b/SSV2/includes/modules/Chrono.lua index 9f7ef50..724d5e9 100644 --- a/SSV2/includes/modules/Chrono.lua +++ b/SSV2/includes/modules/Chrono.lua @@ -356,7 +356,7 @@ end -------------------------------------- ---@class DateTime ---@field private m_epoch seconds ----@field private m_dt osdate +---@field private m_dt osdatefixed ---@field private m_fmt_warn boolean ---@overload fun(p1: (seconds|osdateparam)?): DateTime local DateTime = { __type = "DateTime" } @@ -368,7 +368,7 @@ setmetatable(DateTime, { end }) ----@param p1 (seconds|osdate)? +---@param p1 (seconds|osdate|osdatefixed)? ---@return DateTime function DateTime.new(p1) local epoch @@ -436,7 +436,7 @@ function DateTime:Epoch() return self.m_epoch end ----@return osdate +---@return osdatefixed function DateTime:AsTable() return self.m_dt end diff --git a/SSV2/includes/modules/Game.lua b/SSV2/includes/modules/Game.lua index 41f778c..c233796 100644 --- a/SSV2/includes/modules/Game.lua +++ b/SSV2/includes/modules/Game.lua @@ -381,6 +381,13 @@ function Game.ShowButtonPrompt(text) end end +---@param text string +---@return float +function Game.GetTextScreenWidth(text) + HUD.BEGIN_TEXT_COMMAND_GET_SCREEN_WIDTH_OF_DISPLAY_TEXT(text) + return HUD.END_TEXT_COMMAND_GET_SCREEN_WIDTH_OF_DISPLAY_TEXT(true) +end + ---@param position vec2 ---@param width float ---@param height float diff --git a/SSV2/includes/modules/LocalPlayer.lua b/SSV2/includes/modules/LocalPlayer.lua index 32b7c0b..73505bf 100644 --- a/SSV2/includes/modules/LocalPlayer.lua +++ b/SSV2/includes/modules/LocalPlayer.lua @@ -32,27 +32,21 @@ local SGSL = require("includes.services.SGSL") ---@field private m_last_vehicle? Vehicle ---@field private m_feat_mgr FeatureManager ---@field private m_money_controller PlayerMoneyController ----@field public CurrentMovementClipset? string ----@field public CurrentStrafeClipset? string ----@field public CurrentWeaponMovementClipset? string +---@field private m_clipsets { movement?: string, strafe?: string, weapon?: string } ---@field protected m_internal CPed ---@field SetAsNoLongerNeeded nil LocalPlayer = Class("LocalPlayer", { parent = Player }) ----@override LocalPlayer.new = nil ----@override LocalPlayer.Create = nil ----@override LocalPlayer.Delete = nil ----@override LocalPlayer.SetAsNoLongerNeeded = nil - -LocalPlayer.m_vehicle = require("includes.modules.PlayerVehicle") -LocalPlayer.m_money_controller = require("includes.services.PlayerMoneyController").new() -LocalPlayer.m_feat_mgr = require("includes.services.FeatureManager").new(LocalPlayer) +LocalPlayer.m_clipsets = {} +LocalPlayer.m_vehicle = require("includes.modules.PlayerVehicle") +LocalPlayer.m_money_controller = require("includes.services.PlayerMoneyController").new() +LocalPlayer.m_feat_mgr = require("includes.services.FeatureManager").new(LocalPlayer) ---@diagnostic disable LocalPlayer.m_feat_mgr:Add(Autoheal.new(LocalPlayer)) @@ -191,7 +185,10 @@ function LocalPlayer:OnVehicleSwitch() veh:Reset() sleep(500) - veh:Set(self:GetVehicleNative()) + local nativeVeh = self:GetVehicleNative() + if (ENTITY.DOES_ENTITY_EXIST(nativeVeh)) then + veh:Set(nativeVeh) + end end function LocalPlayer:OnVehicleExit() @@ -489,6 +486,7 @@ function LocalPlayer:SetMovementClipset(data, isJson) self:ResetMovementClipsets() s:sleep(100) + local clipsets = self.m_clipsets local handle = self:GetHandle() local clipsetName = isJson and data.Name or data.mvmt if (clipsetName) then @@ -497,20 +495,20 @@ function LocalPlayer:SetMovementClipset(data, isJson) PED.SET_PED_MOVEMENT_CLIPSET(handle, clipsetName, 1.0) PED.SET_PED_ALTERNATE_MOVEMENT_ANIM(handle, 0, "move_clown@generic", "idle", 1090519040, true) TASK.SET_PED_CAN_PLAY_AMBIENT_IDLES(handle, true, true) - self.CurrentMovementClipset = clipsetName + clipsets.movement = clipsetName end end if (data.wmvmt) then PED.SET_PED_WEAPON_MOVEMENT_CLIPSET(handle, data.wmvmt) - self.CurrentWeaponMovementClipset = data.wmvmt + clipsets.weapon = data.wmvmt end if (data.strf) then local loaded = pcall(TaskWait, Game.RequestClipSet, data.strf) if (loaded) then PED.SET_PED_STRAFE_CLIPSET(handle, data.strf) - self.CurrentStrafeClipset = data.strf + clipsets.strafe = data.strf end end @@ -522,16 +520,12 @@ end function LocalPlayer:ResetMovementClipsets() local handle = self:GetHandle() - PED.RESET_PED_MOVEMENT_CLIPSET(handle, 0.3) PED.RESET_PED_STRAFE_CLIPSET(handle) PED.RESET_PED_WEAPON_MOVEMENT_CLIPSET(handle) PED.CLEAR_PED_ALTERNATE_MOVEMENT_ANIM(handle, 0, -8.0) WEAPON.SET_WEAPON_ANIMATION_OVERRIDE(handle, 3839837909) -- default - - self.CurrentMovementClipset = nil - self.CurrentStrafeClipset = nil - self.CurrentWeaponMovementClipset = nil + self.m_clipsets = {} end function LocalPlayer:Cleanup() @@ -553,36 +547,113 @@ end ---@param bossType int8_t -- -1 = retire | 0 = CEO | 1 = MC function LocalPlayer:RegisterAsBoss(bossType) + if (not Game.IsOnline()) then return end + if (not math.is_inrange(bossType, -1, 1)) then return end + if (bossType == -1) then + self:Retire() + return + end + + ThreadManager:Run(function() + local pid = self:GetID() + local GPBD_FM_3 = GGlobals.GPBD_FM_3:At(pid, 615):At(10) + local FM_SERVICES = GGlobals.FM_SERVICES + local boss_offset_1 = SGSL:Get(SGSL.data.freemode_boss_offset_1):GetValue() + local boss_offset_2 = SGSL:Get(SGSL.data.freemode_boss_offset_2):GetValue() + local is_mc = bossType == 1 + + GPBD_FM_3:WriteInt(pid) + GPBD_FM_3:At(433):WriteInt(bossType) + GPBD_FM_3:At(470):WriteInt(bossType) + + local pUserID = malloc(0x4) + local pInt0 = malloc(0x4) + local pInt1 = malloc(0x4) + STATS.GET_BOSS_GOON_UUID(stats.get_character_index(), pInt0:get_address(), pInt1:get_address()) + local strUserID, userID = NETWORK.NETWORK_PLAYER_GET_USERID(pid, pUserID:get_address()) + local int0, int1 = pInt0:get_int(), pInt1:get_int() + free(pUserID) + free(pInt0) + free(pInt1) + GPBD_FM_3:At(9, 0):WriteInt(int0) + GPBD_FM_3:At(9, 1):WriteInt(int1) + + local sgsl_obj = SGSL:Get(SGSL.data.freemode_boss_uid_str) + local UID_GLOBAL = sgsl_obj:AsGlobal() + local uid_str_offset = sgsl_obj:GetOffset(1) + UID_GLOBAL:At(uid_str_offset):At(2):WriteString(strUserID, 64) + + local cloudTime = stats.get_int("MPX_BOSS_END_TIME") + if (cloudTime <= 0) then + cloudTime = NETWORK.GET_CLOUD_TIME_AS_INT() + else + cloudTime = cloudTime - 43200 + end + stats.set_int("MPX_BOSS_END_TIME", cloudTime) + GPBD_FM_3:At(1):WriteInt(cloudTime) + + if (DECORATOR.DECOR_IS_REGISTERED_AS_TYPE("Player_Boss", 3)) then + DECORATOR.DECOR_SET_INT(self:GetHandle(), "Player_Boss", pid) + end + + GPBD_FM_3:At(25):WriteInt(-1) + GPBD_FM_3:At(26):WriteInt(-1) + FM_SERVICES:At(boss_offset_1):ClearBit(15) + FM_SERVICES:At(boss_offset_2):At(227):WriteInt(-1) + FM_SERVICES:At(boss_offset_2):At(263):WriteInt(-1) + + if (FM_SERVICES:At(boss_offset_2):GetPackedBit(7, 15)) then + FM_SERVICES:At(boss_offset_2):ClearPackedBit(7, 15) + end + + GPBD_FM_3:At(4):ClearBit(30) + GPBD_FM_3:At(4):ClearBit(28) + + if (FM_SERVICES:At(boss_offset_2):At(342):ReadInt() == 0) then + FM_SERVICES:At(boss_offset_2):At(342):WriteInt(1) + end + + local textType = -1408096250 + if (is_mc) then + STATS.PLAYSTATS_CHANGE_MC_ROLE(int0, int1, -1, -1, GPBD_FM_3:At(434):ReadInt(), 4, GPBD_FM_3:At(472):ReadInt()) -- p2 and p3 are supposed to be Global_1947782.f_2 and Global_1947782.f_3 respectively but I can't be bothered atm + textType = -1629413369 + end + + local business = is_mc and YRV3:GetClubhouse() or YRV3:GetOffice() + if (business) then + local businessName = business:GetCustomName() + GPBD_FM_3:At(106):WriteString(businessName, 64) + GPBD_FM_3:At(122):WriteInt(LOCALIZATION.LOCALIZATION_GET_SYSTEM_LANGUAGE()) + -- STATS.PLAYSTATS_NAMED_USER_CONTENT_(true, bossType, int0, int1, textType, businessName, -81044133) -- this native is missing + end + end) +end + +function LocalPlayer:Retire() + if not (Game.IsOnline() and self:IsBoss()) then return end + ThreadManager:Run(function() local pid = self:GetID() local handle = self:GetHandle() - local freemode_offset = SGSL:Get(SGSL.data.freemode_boss_stuff):GetOffset(1) - local GPBD_FM_3 = GGlobals.GPBD_FM_3:At(pid, 615) - local isRetiring = bossType == -1 - - if (isRetiring) then - GGlobals.FREEMODE_GLOBAL:At(freemode_offset):ClearBit(17) - GPBD_FM_3:At(10):At(4):ClearBit(30) - if (DECORATOR.DECOR_IS_REGISTERED_AS_TYPE("Player_Goon", 3) and DECORATOR.DECOR_EXIST_ON(handle, "Player_Goon")) then - DECORATOR.DECOR_REMOVE(handle, "Player_Goon") - end - if (DECORATOR.DECOR_IS_REGISTERED_AS_TYPE("Player_Boss", 3) and DECORATOR.DECOR_EXIST_ON(handle, "Player_Boss")) then - DECORATOR.DECOR_REMOVE(handle, "Player_Boss") - end - else - GGlobals.FREEMODE_GLOBAL:At(freemode_offset):SetBit(17) - if (DECORATOR.DECOR_IS_REGISTERED_AS_TYPE("Player_Boss", 3)) then - DECORATOR.DECOR_SET_INT(self:GetHandle(), "Player_Boss", pid) - end + local freemode_offset = SGSL:Get(SGSL.data.freemode_boss_offset_1):GetValue() + local GPBD_FM_3 = GGlobals.GPBD_FM_3:At(pid, 615):At(10) + + GGlobals.FM_SERVICES:At(freemode_offset):ClearBit(17) + GPBD_FM_3:At(4):ClearBit(30) + if (DECORATOR.DECOR_IS_REGISTERED_AS_TYPE("Player_Goon", 3) and DECORATOR.DECOR_EXIST_ON(handle, "Player_Goon")) then + DECORATOR.DECOR_REMOVE(handle, "Player_Goon") + end + if (DECORATOR.DECOR_IS_REGISTERED_AS_TYPE("Player_Boss", 3) and DECORATOR.DECOR_EXIST_ON(handle, "Player_Boss")) then + DECORATOR.DECOR_REMOVE(handle, "Player_Boss") end - GPBD_FM_3:At(10):At(433):WriteInt(bossType) - GPBD_FM_3:At(10):At(470):WriteInt(bossType) - GPBD_FM_3:At(10):WriteInt(isRetiring and -1 or pid) + GPBD_FM_3:At(433):WriteInt(-1) + GPBD_FM_3:At(470):WriteInt(-1) + GPBD_FM_3:WriteInt(-1) end) end @@ -590,29 +661,60 @@ Backend:RegisterEventCallbackAll(function() LocalPlayer:Reset() end) -ThreadManager:RegisterLooped("SS_PV_HANDLER", function() +local function self_thread() + LocalPlayer.m_feat_mgr:Update() + LocalPlayer.m_money_controller:Update() +end + +local function player_vehicle_thread() yield() + if (LocalPlayer:GetRoomHash() ~= 0) then + return + end + local PV = LocalPlayer.m_vehicle local handle = PV:GetHandle() local nativeVeh = LocalPlayer:GetVehicleNative() + if (not ENTITY.DOES_ENTITY_EXIST(nativeVeh)) then + return + end + + if (VEHICLE.IS_VEHICLE_BEING_BROUGHT_TO_HALT(nativeVeh)) then + return + end + + if (CAM.IS_SCREEN_FADING_OUT() or CAM.IS_SCREEN_FADED_OUT()) then + return + end if (PV:IsValid()) then if (LocalPlayer:IsOnFoot()) then LocalPlayer:OnVehicleExit() elseif (LocalPlayer:IsDriving() and handle ~= nativeVeh) then LocalPlayer:OnVehicleSwitch() + return + end + else + if (handle ~= 0) then + PV:Cleanup() + return + end + + if (LocalPlayer:IsDriving() and ENTITY.IS_ENTITY_A_VEHICLE(nativeVeh)) then + PV:Set(nativeVeh) end - elseif (LocalPlayer:IsDriving()) then - PV:Set(nativeVeh) end +end - if (handle ~= 0 and not PV:IsValid()) then - PV:Cleanup() +ThreadManager:RegisterLooped("SS_SELF", self_thread, { + exception_handler = function() + LocalPlayer:Reset() end -end) +}) -ThreadManager:RegisterLooped("SS_SELF", function() - LocalPlayer.m_feat_mgr:Update() - LocalPlayer.m_money_controller:Update() -end) +ThreadManager:RegisterLooped("SS_VEHICLE_CONTROLLER", player_vehicle_thread, { + exception_handler = function() + LocalPlayer:GetVehicle():Cleanup() + end +}) diff --git a/SSV2/includes/modules/PlayerVehicle.lua b/SSV2/includes/modules/PlayerVehicle.lua index ce5e4e7..2923da8 100644 --- a/SSV2/includes/modules/PlayerVehicle.lua +++ b/SSV2/includes/modules/PlayerVehicle.lua @@ -8,7 +8,6 @@ local StateMachine = require("includes.structs.StateMachine") -local HandlingEditor = require("includes.modules.HandlingEditor") local Speedometer = require("includes.features.Speedometer") local FeatureMgr = require("includes.services.FeatureManager") local NosMgr = require("includes.features.vehicle.nos") @@ -23,7 +22,9 @@ local CarCrash = require("includes.features.vehicle.car_crashes") local VehMines = require("includes.features.vehicle.mines") local MiscVehicle = require("includes.features.vehicle.misc_vehicle") local CobraManeuver = require("includes.features.vehicle.cobra_maneuver") +local FlagController = require("includes.features.vehicle.flag_controller") local Stancer = require("includes.features.vehicle.stancer") +local ManualGearBox = require("includes.features.vehicle.manual_gearbox") ---@class GenericToggleable ---@field is_toggled boolean @@ -42,19 +43,20 @@ local Stancer = require("includes.features.vehicle.stancer") ---@field private m_feat_mgr FeatureManager ---@field private m_nos_mgr NosMgr ---@field private m_abs_mgr BFD ----@field private m_lctrl_mgr LaunchControlMgr +---@field private m_lctrl_mgr LaunchControl ---@field private m_threads array ---@field private m_default_max_speed float ---@field private m_has_loud_radio boolean ---@field private m_generic_toggleables table ----@field private m_handling_editor HandlingEditor ---@field public m_default_xenon_lights { enabled: boolean, index: integer } ---@field public m_default_tire_smoke { enabled: boolean, color: vec3 } ---@field public m_autopilot { eligible: boolean, state: eAutoPilotState, initial_nozzle_pos: integer, last_interrupted?: seconds } ---@field public m_engine_swap_compatible boolean ---@field public m_is_shooting_flares boolean ---@field public m_is_flatbed boolean cache it so we don't have to call natives in UI threads +---@field public m_flag_controller VehicleFlagController ---@field public m_stancer Stancer +---@field public m_manual_gearbox ManualGearBox ---@overload fun(handle: handle, opts?: { noassert: boolean }): PlayerVehicle local PlayerVehicle = Class("PlayerVehicle", { parent = Vehicle }) @@ -98,12 +100,13 @@ function PlayerVehicle:AddFeature(feat) end function PlayerVehicle:InitFeatures() - self.m_feat_mgr = FeatureMgr.new(self) + self.m_feat_mgr = FeatureMgr.new(self) ---@diagnostic disable-next-line - self.m_lctrl_mgr = self.m_feat_mgr:Add(LaunchControlMgr.new(self)) - self.m_nos_mgr = self.m_feat_mgr:Add(NosMgr.new(self)) - self.m_abs_mgr = self.m_feat_mgr:Add(BFD.new(self)) - self.m_stancer = self.m_feat_mgr:Add(Stancer.new(self)) + self.m_lctrl_mgr = self.m_feat_mgr:Add(LaunchControlMgr.new(self)) + self.m_nos_mgr = self.m_feat_mgr:Add(NosMgr.new(self)) + self.m_abs_mgr = self.m_feat_mgr:Add(BFD.new(self)) + self.m_stancer = self.m_feat_mgr:Add(Stancer.new(self)) + self.m_manual_gearbox = self.m_feat_mgr:Add(ManualGearBox.new(self)) self.m_feat_mgr:Add(FlappyDoors.new(self)) self.m_feat_mgr:Add(DriftMode.new(self)) @@ -117,7 +120,7 @@ function PlayerVehicle:InitFeatures() end function PlayerVehicle:InitHandlingEditor() - self.m_handling_editor = HandlingEditor:init(self) + self.m_flag_controller = FlagController:init(self) end ---@return PlayerVehicle @@ -157,16 +160,23 @@ function PlayerVehicle.new(handle) instance.m_feat_mgr:Cleanup() end) - table.insert(instance.m_threads, ThreadManager:RegisterLooped("SS_VEHICLE", function() + local thread = ThreadManager:RegisterLooped("SS_VEHICLE", function() instance:Main() - end)) + end, { + exception_handler = function() + instance:Cleanup() + end + }) + table.insert(instance.m_threads, thread) return instance end ---@param handle handle function PlayerVehicle:Set(handle) - if (handle == self.m_handle) then return end + if (handle == self.m_handle or not ENTITY.IS_ENTITY_A_VEHICLE(handle)) then + return + end self.m_handle = handle local new_model = ENTITY.GET_ENTITY_MODEL(handle) @@ -181,12 +191,22 @@ function PlayerVehicle:Set(handle) self.m_default_tire_smoke.enabled = VEHICLE.IS_TOGGLE_MOD_ON(handle, 20) self.m_autopilot.eligible = self:IsAircraft() - if (GVars.features.vehicle.no_turbulence and VEHICLE.IS_THIS_MODEL_A_PLANE(new_model)) then + + local feats_cfg = GVars.features.vehicle + if (feats_cfg.no_turbulence and VEHICLE.IS_THIS_MODEL_A_PLANE(new_model)) then VEHICLE.SET_PLANE_TURBULENCE_MULTIPLIER(handle, 0.0) end - self.m_handling_editor:ApplyPresets() + if (feats_cfg.default_station.enabled) then + self:SetRadioStation(feats_cfg.default_station.station_name) + end + + self.m_flag_controller:ApplyPresets() self.m_stancer:OnNewVehicle() + + if (feats_cfg.manual_gearbox.enabled) then + self.m_manual_gearbox:OnNewVehicle() + end -- self:ResumeThreads() -- self.m_feat_mgr:OnEnable() end @@ -204,7 +224,8 @@ function PlayerVehicle:Reset() self:RestoreExhaustPops() self:RestoreAllPatches() self:ResetAllGenericToggleables() - self.m_handling_editor:Reset() + self.m_flag_controller:Reset() + self.m_manual_gearbox:Reset() --[[ We're resetting default stance when the player switches vehicles because we currently don't have @@ -400,9 +421,30 @@ end ---@return number function PlayerVehicle:GetCurrentGear() + if (GVars.features.vehicle.manual_gearbox.enabled and self.m_manual_gearbox) then + return self.m_manual_gearbox:GetCurrentGear() + end return VEHICLE.GET_VEHICLE_CURRENT_DRIVE_GEAR_(self:GetHandle()) end +---@return string +function PlayerVehicle:GetCurrentGearName() + local current = self:GetCurrentGear() + local current_str = tostring(current) + if (current <= 0 or current > 100) then + return self:IsReversing() and "R" or "N" + end + return current_str +end + +---@return boolean +function PlayerVehicle:IsReversing() + if (GVars.features.vehicle.manual_gearbox.enabled and self.m_manual_gearbox) then + return self.m_manual_gearbox:IsInReverse() + end + return self:GetSpeedVector().y < 0 +end + ---@param toggle boolean function PlayerVehicle:ToggleSubwoofer(toggle) AUDIO.SET_VEHICLE_RADIO_LOUD(self:GetHandle(), toggle) diff --git a/SSV2/includes/modules/Tab.lua b/SSV2/includes/modules/Tab.lua index 0df59b8..22d0066 100644 --- a/SSV2/includes/modules/Tab.lua +++ b/SSV2/includes/modules/Tab.lua @@ -236,7 +236,7 @@ function Tab:AddLoopedCommand(label, opts) local command_name = label:lower():gsub("%s+", ""):trim() local config_value = table.get_nested_key(g_table, gvar_key) local suspended_thread = not config_value - local thread = ThreadManager:RegisterLooped(_F("SS_%s", command_name:upper()), opts.callback, suspended_thread) + local thread = ThreadManager:RegisterLooped(_F("SS_%s", command_name:upper()), opts.callback, { suspended = suspended_thread }) local function toggle() local v = table.get_nested_key(g_table, gvar_key) diff --git a/SSV2/includes/modules/Vehicle.lua b/SSV2/includes/modules/Vehicle.lua index ff8f2de..8abd5ba 100644 --- a/SSV2/includes/modules/Vehicle.lua +++ b/SSV2/includes/modules/Vehicle.lua @@ -173,6 +173,15 @@ function Vehicle:GetRadioStationName() return Game.GetGXTLabel(AUDIO.GET_PLAYER_RADIO_STATION_NAME()) end +---@param station_name string +function Vehicle:SetRadioStation(station_name) + if (self:GetRadioStationName() == station_name) then + return + end + + AUDIO.SET_VEH_RADIO_STATION(self:GetHandle(), station_name) +end + ---@return boolean function Vehicle:IsRadioOn() return AUDIO.IS_VEHICLE_RADIO_ON(self:GetHandle()) @@ -361,6 +370,16 @@ function Vehicle:IsMoving() return not self:IsStopped() end +---@return boolean +function Vehicle:IsMovingForward() + return self:GetSpeedVector().y > 0 +end + +---@return boolean +function Vehicle:IsReversing() + return self:GetSpeedVector().y < 0 +end + ---@param ped handle ---@return boolean function Vehicle:IsPedInVehicle(ped) diff --git a/SSV2/includes/modules/World.lua b/SSV2/includes/modules/World.lua index d31c93d..5b90452 100644 --- a/SSV2/includes/modules/World.lua +++ b/SSV2/includes/modules/World.lua @@ -130,6 +130,10 @@ end) ThreadManager:RegisterLooped("SS_WORLD", function() World.m_feat_mgr:Update() -end) +end, { + exception_handler = function() + World:Cleanup() + end +}) return World diff --git a/SSV2/includes/services/CommandExecutor.lua b/SSV2/includes/services/CommandExecutor.lua index 08102e7..851578b 100644 --- a/SSV2/includes/services/CommandExecutor.lua +++ b/SSV2/includes/services/CommandExecutor.lua @@ -643,12 +643,8 @@ function CommandExecutor:GetDefaultCommands() end local newkey = KeyManager:GetKey(args[1]) - if (not newkey or not IsInstance(newkey, Key)) then - Notifier:ShowError( - "CommandExecutor", - "Unknown parameter.\nUsage example: !setkey F8", - true - ) + if (not newkey) then + Notifier:ShowError("CommandExecutor", "Unknown parameter.\nUsage example: !setkey F8", true) return end diff --git a/SSV2/includes/services/GroupManager.lua b/SSV2/includes/services/GroupManager.lua index df79b79..5e353a0 100644 --- a/SSV2/includes/services/GroupManager.lua +++ b/SSV2/includes/services/GroupManager.lua @@ -25,7 +25,11 @@ function GroupManager:init(bsv2) ThreadManager:RegisterLooped("SS_GROUPMGR", function(s) self:OnTick(s) yield() - end) + end, { + exception_handler = function() + self:Cleanup() + end + }) end return self diff --git a/SSV2/includes/services/KeyManager.lua b/SSV2/includes/services/KeyManager.lua index ce35eed..f83d088 100644 --- a/SSV2/includes/services/KeyManager.lua +++ b/SSV2/includes/services/KeyManager.lua @@ -9,17 +9,16 @@ ---@diagnostic disable: lowercase-global ---#region consts - +--#region defs ---@enum eControlType -eControlType = { +eControlType = { KEYBOARD = 0x0, CONTROLLER = 0x1 } ---@enum eVirtualKeyCodes -eVirtualKeyCodes = { -- https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes +eVirtualKeyCodes = { -- https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes DIGIT_0 = 0x30, DIGIT_1 = 0x31, DIGIT_2 = 0x32, @@ -198,19 +197,37 @@ eVirtualKeyCodes = { -- https://learn.microsoft.com/en-us/windows/wi Z = 0x5A, } -local WM_KEYDOWN = 0x0100 -local WM_KEYUP = 0x0101 -local WM_LBUTTONDOWN = 0x0201 -local WM_LBUTTONUP = 0x0202 -local WM_MBUTTONDOWN = 0x0207 -local WM_MBUTTONUP = 0x0208 -local WM_MOUSEWHEEL = 0x020A -local WM_RBUTTONDOWN = 0x0204 -local WM_RBUTTONUP = 0x0205 -local WM_SYSKEYDOWN = 0x0104 -local WM_SYSKEYUP = 0x0105 -local WM_XBUTTONDOWN = 0x020B -local WM_XBUTTONUP = 0x020C +local WM_KEYDOWN = 0x0100 +local WM_KEYUP = 0x0101 +local WM_LBUTTONDOWN = 0x0201 +local WM_LBUTTONUP = 0x0202 +local WM_MBUTTONDOWN = 0x0207 +local WM_MBUTTONUP = 0x0208 +local WM_MOUSEWHEEL = 0x020A -- TODO +local WM_RBUTTONDOWN = 0x0204 +local WM_RBUTTONUP = 0x0205 +local WM_SYSKEYDOWN = 0x0104 +local WM_SYSKEYUP = 0x0105 +local WM_XBUTTONDOWN = 0x020B +local WM_XBUTTONUP = 0x020C + +local KeyDownMessageSet = Set( + WM_KEYDOWN, + WM_LBUTTONDOWN, + WM_MBUTTONDOWN, + WM_RBUTTONDOWN, + WM_SYSKEYDOWN, + WM_XBUTTONDOWN +) + +local KeyUpMessageSet = Set( + WM_KEYUP, + WM_LBUTTONUP, + WM_MBUTTONUP, + WM_RBUTTONUP, + WM_SYSKEYUP, + WM_XBUTTONUP +) --#endregion @@ -230,8 +247,8 @@ local WM_XBUTTONUP = 0x020C ---@field m_just_pressed boolean ---@field m_just_released boolean ---@field protected m_prev_pressed boolean -Key = {} -Key.__index = Key +local Key = {} +Key.__index = Key ---@param code integer ---@param name string @@ -277,48 +294,106 @@ function Key:EndFrame() end -- redundant ---@field private m_keymap_by_code table ---@field private m_keymap_by_name table ---@field private m_registered_keybinds table ----@field private BeginFrame function ----@field private EndFrame function ----@field private HandleCallbacks function -KeyManager = Class("KeyManager") -KeyManager.m_keys = {} -KeyManager.m_registered_keybinds = {} -KeyManager.m_keymap_by_code = {} -KeyManager.m_keymap_by_name = {} +---@field private m_initialized boolean +---@overload fun(): KeyManager +local KeyManager = Class("KeyManager") function KeyManager:init() - ---@class KeyManager - local instance = setmetatable({}, KeyManager) + if (self.m_initialized) then return self end + if (_G.KeyManager) then return _G.KeyManager end + + self.m_keys = {} + self.m_registered_keybinds = {} + self.m_keymap_by_code = {} + self.m_keymap_by_name = {} for name, code in pairs(eVirtualKeyCodes) do local key = Key.new(code, name) - table.insert(instance.m_keys, key) - instance.m_keymap_by_code[code] = key - instance.m_keymap_by_name[name] = key + table.insert(self.m_keys, key) + self.m_keymap_by_code[code] = key + self.m_keymap_by_name[name] = key end event.register_handler(menu_event.Wndproc, function(_, msg, wParam, _) - instance:EventHandler(_, msg, wParam, _) + self:EventHandler(_, msg, wParam, _) end) ThreadManager:RegisterLooped("SS_KEYMGR", function() - instance:BeginFrame() - instance:HandleCallbacks() - instance:EndFrame() + self:BeginFrame() + self:HandleCallbacks() + self:EndFrame() end) - return instance + self.m_initialized = true + return self end -- still not perfectly in sync but much better than the duct taped approach of manually clearing just_pressed in a separate fiber 🤦‍♂️ +---@private function KeyManager:BeginFrame() for _, key in pairs(self.m_keys) do key:BeginFrame() end end +---@private function KeyManager:EndFrame() end -- redundant +---@private +---@param msg integer +---@param wParam integer +function KeyManager:OnEvent(msg, wParam) + local key = self:GetKeyByCode(wParam) + if (not key) then return end + + if (KeyDownMessageSet:Contains(msg)) then + key:UpdateState(true) + elseif (KeyUpMessageSet:Contains(msg)) then + key:UpdateState(false) + end +end + +---@private +function KeyManager:HandleCallbacks() + for _, key in pairs(self.m_registered_keybinds) do + if (not key.callback) then + goto continue + end + + if (not key.m_repeat_on_hold) then + if (key.m_just_pressed) then + key.callback() + end + else + if (key.m_pressed) then + key.callback() + end + end + + ::continue:: + end +end + +---@private +function KeyManager:EventHandler(_, msg, wParam, _) + if (msg == WM_XBUTTONDOWN or msg == WM_XBUTTONUP) then + -- the value for secondary mouse buttons is different between keydown and keyup events + local xButton = (wParam >> 16) + if (xButton == 1) then + wParam = 0x10020 + elseif (xButton == 2) then + wParam = 0x20040 + end + end + + if (msg == WM_LBUTTONUP) then + wParam = 0x1 + elseif (msg == WM_RBUTTONUP) then + wParam = 0x2 + end + self:OnEvent(msg, wParam) +end + ---@param code eVirtualKeyCodes ---@return Key|nil function KeyManager:GetKeyByCode(code) @@ -378,6 +453,19 @@ function KeyManager:IsAnyKeyPressed() return false, nil, nil end +---@param key eVirtualKeyCodes|string +---@return boolean +function KeyManager:IsKeyReleased(key) + return not self:IsKeyPressed(key) +end + +---@param key eVirtualKeyCodes|string +---@return boolean +function KeyManager:IsKeyJustReleased(key) + local _key = self:GetKey(key) + return _key and _key.m_just_released or false +end + ---@param keybindName string ---@return eControlType, (integer|string)? function KeyManager:GetKeybind(keybindName) @@ -437,29 +525,27 @@ function KeyManager:IsKeybindJustPressed(keybindName) return false end ----@param msg integer ----@param wParam integer -function KeyManager:OnEvent(msg, wParam) - local key = self:GetKeyByCode(wParam) - if (not key) then - return +---@param keybindName string +---@return boolean +function KeyManager:IsKeybindJustReleased(keybindName) + local controlType, key = self:GetKeybind(keybindName) + if (not key) then return false end + + if (controlType == eControlType.KEYBOARD) then + return self:IsKeyJustReleased(key) end - if (msg == WM_KEYDOWN or - msg == WM_SYSKEYDOWN or - msg == WM_XBUTTONDOWN or - msg == WM_LBUTTONDOWN or - msg == WM_RBUTTONDOWN - ) then - key:UpdateState(true) - elseif (msg == WM_KEYUP or - msg == WM_SYSKEYUP or - msg == WM_XBUTTONUP or - msg == WM_LBUTTONUP or - msg == WM_RBUTTONUP - ) then - key:UpdateState(false) + if (controlType == eControlType.CONTROLLER and type(key) == "number") then + return key ~= 0 and PAD.IS_CONTROL_JUST_RELEASED(0, key) end + + return false +end + +---@param keybindName string +---@return boolean +function KeyManager:IsKeybindReleased(keybindName) + return not self:IsKeybindPressed(keybindName) end ---@param key eVirtualKeyCodes | string @@ -525,45 +611,8 @@ function KeyManager:RemoveKeybind(key) self.m_registered_keybinds[k.m_code] = nil end -function KeyManager:HandleCallbacks() - for _, key in pairs(self.m_registered_keybinds) do - if (not key.callback) then - goto continue - end - - if (not key.m_repeat_on_hold) then - if (key.m_just_pressed) then - key.callback() - end - else - if (key.m_pressed) then - key.callback() - end - end - - ::continue:: - end -end - -function KeyManager:EventHandler(_, msg, wParam, _) - if (msg == WM_XBUTTONDOWN or msg == WM_XBUTTONUP) then - -- the value for secondary mouse buttons is different between keydown and keyup events - local xButton = (wParam >> 16) - if (xButton == 1) then - wParam = 0x10020 - elseif (xButton == 2) then - wParam = 0x20040 - end - end - - if (msg == WM_LBUTTONUP) then - wParam = 0x1 - elseif (msg == WM_RBUTTONUP) then - wParam = 0x2 - end - self:OnEvent(msg, wParam) -end - --#endregion -return KeyManager +local singleInstance = KeyManager() +_G.KeyManager = singleInstance +return singleInstance diff --git a/SSV2/includes/services/PatternScanner.lua b/SSV2/includes/services/PatternScanner.lua index 4665f38..611fc3b 100644 --- a/SSV2/includes/services/PatternScanner.lua +++ b/SSV2/includes/services/PatternScanner.lua @@ -178,8 +178,8 @@ function PatternScanner:RetryScan() return end - local sizeof_failed = #self.m_failed_patterns - if (sizeof_failed == 0) then + local count = #self.m_failed_patterns + if (count == 0) then log.debug("[PatternScanner] No failed pointers to rescan.") return end @@ -188,7 +188,7 @@ function PatternScanner:RetryScan() local success = 0 self.m_state = eScannerState.BUSY - for i = sizeof_failed, 1, -1 do + for i = count, 1, -1 do local pattern = self.m_failed_patterns[i] if (pattern:Scan()) then table.remove(self.m_failed_patterns, i) @@ -198,7 +198,7 @@ function PatternScanner:RetryScan() yield() end - log.fdebug("[PatternScanner] Recovered %d/%d failed pattern(s)", success, sizeof_failed) + log.fdebug("[PatternScanner] Recovered %d/%d failed pattern(s)", success, count) self.m_state = eScannerState.DONE end) end diff --git a/SSV2/includes/services/PlayerMoneyController.lua b/SSV2/includes/services/PlayerMoneyController.lua index 64c6da2..894f4b6 100644 --- a/SSV2/includes/services/PlayerMoneyController.lua +++ b/SSV2/includes/services/PlayerMoneyController.lua @@ -7,6 +7,17 @@ -- * Provide a copy of or a link to the original license (GPL-3.0 or later); see LICENSE.md or . +local TransactionSystemFuncs = { + [0] = { + transfer = NETSHOPPING.NET_GAMESERVER_TRANSFER_BANK_TO_WALLET, + get_status = NETSHOPPING.NET_GAMESERVER_TRANSFER_BANK_TO_WALLET_GET_STATUS + }, + [1] = { + transfer = NETSHOPPING.NET_GAMESERVER_TRANSFER_WALLET_TO_BANK, + get_status = NETSHOPPING.NET_GAMESERVER_TRANSFER_WALLET_TO_BANK_GET_STATUS + } +} + ---@class PlayerMoneyController ---@field private m_bank_balance integer ---@field private m_wallet_balance integer @@ -85,23 +96,22 @@ function PlayerMoneyController:UseTransactionSystem(amount, operation) end local success, p0, p1 = false, 0, false - success, p0, p1 = NETSHOPPING.NET_GAMESERVER_GET_SESSION_STATE_AND_STATUS(p0, p1) + success, p0, p1 = NETSHOPPING.NET_GAMESERVER_GET_SESSION_STATE_AND_STATUS(p0, p1) if (not success or p0 ~= 8) then log.warning("Transaction failed!") return false end - local tansferFunc = (operation == 0) and NETSHOPPING.NET_GAMESERVER_TRANSFER_BANK_TO_WALLET or NETSHOPPING.NET_GAMESERVER_TRANSFER_WALLET_TO_BANK - local statusFunc = (operation == 0) and NETSHOPPING.NET_GAMESERVER_TRANSFER_BANK_TO_WALLET_GET_STATUS or NETSHOPPING.NET_GAMESERVER_TRANSFER_WALLET_TO_BANK_GET_STATUS - - if (not tansferFunc(stats.get_character_index(), amount)) then + local funcs_t = TransactionSystemFuncs[operation] + if (not funcs_t.transfer(stats.get_character_index(), amount)) then log.warning("Transaction failed!") return false end - local status = statusFunc() + local get_status = funcs_t.get_status + local status = get_status() while (status == 1) do - status = statusFunc() + status = get_status() yield() end @@ -144,6 +154,8 @@ function PlayerMoneyController:Deposit(amount) end function PlayerMoneyController:Update() + if (not Game.IsOnline()) then return end + if (not self.m_last_tick:HasElapsed(1000)) then return end diff --git a/SSV2/includes/services/RawDataService.lua b/SSV2/includes/services/RawDataService.lua index a61ad43..8d1ef32 100644 --- a/SSV2/includes/services/RawDataService.lua +++ b/SSV2/includes/services/RawDataService.lua @@ -19,6 +19,7 @@ ---@field manufacturer string ---@field class_id eVehicleClass ---@field class_name string +---@field enhanced_only boolean ---@class RawWeaponData ---@field model_name string diff --git a/SSV2/includes/services/ThreadManager.lua b/SSV2/includes/services/ThreadManager.lua index 2427235..d92e31a 100644 --- a/SSV2/includes/services/ThreadManager.lua +++ b/SSV2/includes/services/ThreadManager.lua @@ -58,36 +58,36 @@ eThreadStage = { ---@field private m_last_yield_at seconds ---@field private m_avg_work_ms milliseconds ---@field private m_avg_cycle_ms milliseconds ----@overload fun(name: string, callback: function): Thread +---@field private m_exc_handler function? +---@overload fun(name: string, callback: function, exceptionHandler?: function): Thread local Thread = Class("Thread") ---@param name string ---@param callback function -function Thread.new(name, callback) +---@param exceptionHandler? function +function Thread.new(name, callback, exceptionHandler) if (not string.isvalid(name)) then name = string.random(5, true):upper() end - return setmetatable( - { - m_name = name, - m_callback = callback, - m_can_run = false, - m_should_pause = false, - m_wants_exit = false, - m_state = eThreadState.UNK, - m_stage = eThreadStage.IDLE, - m_time_created = TimePoint.new(), - m_time_started = 0, - m_last_entry_at = 0, - m_last_exit_at = 0, - m_last_yield_at = 0, - m_avg_work_ms = 0, - m_avg_cycle_ms = 0, - }, + return setmetatable({ + m_name = name, + m_callback = callback, + m_exc_handler = exceptionHandler, + m_can_run = false, + m_should_pause = false, + m_wants_exit = false, + m_state = eThreadState.UNK, + m_stage = eThreadStage.IDLE, + m_time_created = TimePoint.new(), + m_time_started = 0, + m_last_entry_at = 0, + m_last_exit_at = 0, + m_last_yield_at = 0, + m_avg_work_ms = 0, + m_avg_cycle_ms = 0, ---@diagnostic disable-next-line: param-type-mismatch - Thread - ) + }, Thread) end ---@return string @@ -111,6 +111,11 @@ function Thread:GetCallback() return self.m_callback end +---@return function? +function Thread:GetExceptionHandler() + return self.m_exc_handler +end + ---@return milliseconds function Thread:GetTimeCreated() return self.m_time_created:Value() @@ -146,11 +151,22 @@ function Thread:IsSuspended() return self.m_state == eThreadState.SUSPENDED end +---@param func function +function Thread:RegisterExceptionHandler(func) + self.m_exc_handler = func +end + +function Thread:OnError() + local handler = self.m_exc_handler + if (not handler) then return end + pcall(handler) +end + ---@param s? script_util function Thread:OnTick(s) self.m_can_run = (type(self.m_callback) == "function") if (not self.m_can_run) then - log.fwarning("Thread %s was terminated because it has no callback", self.m_name) + log.fwarning("Thread %s was terminated. Nothing to execute!", self.m_name) self:Stop() return end @@ -192,7 +208,8 @@ function Thread:OnTick(s) if (self.m_should_terminate or not ok) then if (not ok and err) then - log.fwarning("Thread %s was terminated due to an unhandled exception: %s", self.m_name, err) + log.fwarning("Thread %s was terminated due to an exception: %s", self.m_name, err) + self:OnError() end self:Stop() @@ -442,13 +459,16 @@ end -- Creates a thread that runs in a loop. ---@param name string ----@param func fun(s?: script_util) ----@param suspended? boolean ----@param is_debug_thread? boolean -function ThreadManager:RegisterLooped(name, func, suspended, is_debug_thread) - local isMock = (API_VER == Enums.eGameBranch.MOCK) - if (isMock and not is_debug_thread) then return end - if (is_debug_thread and not isMock) then return end +---@param func fun(s: script_util?) +---@param kwargs? { suspended: boolean?, is_debug_thread: boolean?, exception_handler: function? } +---@return Thread? +function ThreadManager:RegisterLooped(name, func, kwargs) + kwargs = kwargs or {} + local is_mock_env = (API_VER == Enums.eGameBranch.MOCK) + local is_debug = kwargs.is_debug_thread or false + + if (is_mock_env and not is_debug) then return end + if (is_debug and not is_mock_env) then return end if (not string.isvalid(name)) then name = string.random(5, true):upper() @@ -459,16 +479,11 @@ function ThreadManager:RegisterLooped(name, func, suspended, is_debug_thread) return end - local thread = Thread(name, func) - if (suspended) then - thread:Suspend() - end + local thread = Thread(name, func, kwargs.exception_handler) + if (kwargs.suspended) then thread:Suspend() end self.m_threads[name] = thread - self:Run(function(s) - thread:OnTick(s) - end) - + self:Run(function(s) thread:OnTick(s) end) return thread end diff --git a/SSV2/includes/services/asset_browsers/VehicleBrowser.lua b/SSV2/includes/services/asset_browsers/VehicleBrowser.lua index c8590d5..6fb009a 100644 --- a/SSV2/includes/services/asset_browsers/VehicleBrowser.lua +++ b/SSV2/includes/services/asset_browsers/VehicleBrowser.lua @@ -10,6 +10,7 @@ local AssetBrowserBase = require("includes.services.asset_browsers.AssetBrowserBase") local RawDataService = require("includes.services.RawDataService") local Manufacturers = require("includes.data.refs").t_VehicleManufacturers +local IS_ENHANCED = Game.IsEnhanced() ---@type array @@ -152,8 +153,13 @@ end ---@override ---@param v Pair +---@return boolean function VehicleBrowser:TryFilters(_, v) local data = v.second + if (data.enhanced_only and not IS_ENHANCED) then + return false + end + return self:FilterByManufacturer(data.manufacturer) and self:FilterByClass(data.class_id) end diff --git a/SSV2/includes/structs/HandlingPreset.lua b/SSV2/includes/structs/HandlingPreset.lua index 52ec0af..26fbda0 100644 --- a/SSV2/includes/structs/HandlingPreset.lua +++ b/SSV2/includes/structs/HandlingPreset.lua @@ -89,8 +89,8 @@ end ---@field private m_cached_flags array> ---@field private m_is_default_preset boolean ---@field private m_is_user_generated boolean ----@field private m_on_enable_callback? fun(self: HandlingPreset, editor: HandlingEditor): any ----@field private m_on_disable_callback? fun(self: HandlingPreset, editor: HandlingEditor): any +---@field private m_on_enable_callback? fun(self: HandlingPreset, editor: VehicleFlagController): any +---@field private m_on_disable_callback? fun(self: HandlingPreset, editor: VehicleFlagController): any ---@field public m_callback_defs_filename string ---@field public m_deltas table> ---@field public m_vehicle_bitset integer -- bitset of eVehicleType (cars and bikes only) @@ -183,8 +183,8 @@ function HandlingPreset:Serialize() end ---@private ----@param func fun(self: HandlingPreset, editor: HandlingEditor) ----@param editorInst HandlingEditor +---@param func fun(self: HandlingPreset, editor: VehicleFlagController) +---@param editorInst VehicleFlagController ---@param funcIndex integer -- 1 OnEnable | 2 OnDisable function HandlingPreset:__run(func, editorInst, funcIndex) if (type(func) ~= "function") then return end @@ -222,12 +222,12 @@ function HandlingPreset:__run(func, editorInst, funcIndex) end) end ----@param editorInst HandlingEditor +---@param editorInst VehicleFlagController function HandlingPreset:OnEnable(editorInst) self:__run(self.m_on_enable_callback, editorInst, 1) end ----@param editorInst HandlingEditor +---@param editorInst VehicleFlagController function HandlingPreset:OnDisable(editorInst) self:__run(self.m_on_disable_callback, editorInst, 2) end diff --git a/SSV2/includes/structs/MemoryPatch.lua b/SSV2/includes/structs/MemoryPatch.lua index a42e399..7686968 100644 --- a/SSV2/includes/structs/MemoryPatch.lua +++ b/SSV2/includes/structs/MemoryPatch.lua @@ -17,9 +17,8 @@ ---@field private OnEnable fun(patch: MemoryPatch): any ---@field private OnDisable fun(patch: MemoryPatch): any ---@field public m_state any Capture a default state to reset later if needed. *(see `PlayerVehicle:AddMemoryPatch` for default state example)* -local MemoryPatch = {} -MemoryPatch.__index = MemoryPatch -MemoryPatch.__type = "MemoryPatch" +local MemoryPatch = { __type = "MemoryPatch" } +MemoryPatch.__index = MemoryPatch ---@param name string ---@param onEnable function @@ -27,9 +26,9 @@ MemoryPatch.__type = "MemoryPatch" ---@return MemoryPatch function MemoryPatch.new(name, onEnable, onDisable) return setmetatable({ - m_name = name, + m_name = name, m_enabled = false, - OnEnable = onEnable, + OnEnable = onEnable, OnDisable = onDisable }, MemoryPatch) end @@ -58,9 +57,7 @@ function MemoryPatch:Apply() return nil end) - if (not ok) then - return nil - end + if (not ok) then return nil end self.m_enabled = true return res @@ -80,9 +77,7 @@ function MemoryPatch:Restore() return nil end) - if (not ok) then - return nil - end + if (not ok) then return nil end self.m_enabled = false return res diff --git a/dev/scripts/data_generators/game_data_gen.py b/dev/scripts/data_generators/game_data_gen.py index c214b24..1671a03 100644 --- a/dev/scripts/data_generators/game_data_gen.py +++ b/dev/scripts/data_generators/game_data_gen.py @@ -155,6 +155,7 @@ def gen_vehicles(class_name: str = None): for veh in jsondata: str_name = str(veh["Name"]).lower() joaat_hash = veh["Hash"] + enhanced_only = joaat_hash in (0x438F6593, 0x170341C2, 0xA71D0D4F, 0x5C54030C, 0x39085F47) out[str_name] = { "model_hash": joaat_hash, @@ -162,6 +163,7 @@ def gen_vehicles(class_name: str = None): "manufacturer": veh["ManufacturerDisplayName"]["English"], "class_id": veh["ClassId"], "class_name": veh["Class"], + "enhanced_only": enhanced_only } hash_map[joaat_hash] = str_name @@ -170,7 +172,6 @@ def gen_vehicles(class_name: str = None): write_lua_table(LUA_DATA_PATH / "vehicles.lua", out, class_name) write_lua_table(LUA_DATA_PATH / "vehicle_hashmap.lua", hash_map) - def gen_peds(class_name: str = None): jsondata = read_raw_file("peds.json") out = {} diff --git a/docs/Features.md b/docs/Features.md index 87344fc..0a27d4a 100644 --- a/docs/Features.md +++ b/docs/Features.md @@ -195,7 +195,7 @@ _Vehicle-centric features extending the capabilities of vanilla vehicles._ - **Launch Control** - Simulates launch control by making your vehicle accelerate faster from a standstill and decreasing wheel spin. + Simulates launch control by making your vehicle accelerate faster from a standstill and decreasing wheel spin. Also provides a "Rolling" mode where you can hold a button while moving forward to build boost. - **IV-Style Exit** @@ -217,6 +217,19 @@ _Vehicle-centric features extending the capabilities of vanilla vehicles._ Score points by drifting around Los Santos. You can bank your points for cash in Single Player. +- **Manual Gearbox** + + Allows you to manually switch gears when driving cars and bikes. Has two modes: + + - **Manual With Clutch**: Requires pressing the clutch to change gears _(simplified, no stall)_. + - **Sequential**: Directly shift up/down without a clutch. You can still use the clutch to rev and/or clutch-kick. + + **NOTE**: While not in reverse gear, **[S]** always works as a brake; the car will not automatically go in reverse when you hold the brake button so you must select reverse gear then use **[S]** to drive backwards and vice versa. + +- **Default Radio** + + Select a default radio station to be automatically set each time you switch vehicles. + - **Ram _(AKA Shunt)_** Gives you the ability to ram other vehicles, peds, or objects in 3 directions.