diff --git a/src/game/backend/PersonalVehicles.cpp b/src/game/backend/PersonalVehicles.cpp new file mode 100644 index 0000000..657f6ec --- /dev/null +++ b/src/game/backend/PersonalVehicles.cpp @@ -0,0 +1,517 @@ +#include "PersonalVehicles.hpp" +#include "core/backend/FiberPool.hpp" +#include "core/backend/ScriptMgr.hpp" +#include "game/backend/Self.hpp" +#include "game/gta/data/VehicleValues.hpp" +#include "game/gta/Natives.hpp" +#include "game/gta/ScriptFunction.hpp" +#include "game/gta/ScriptLocal.hpp" +#include "game/gta/ScriptGlobal.hpp" +#include "game/gta/Stats.hpp" +#include "types/script/globals/GPBD_FM.hpp" +#include "types/script/globals/MPSV.hpp" +#include "types/script/globals/FreemodeGeneral.hpp" +#include "types/script/globals/g_SavedMPGlobals.hpp" + +#define MAX_GARAGE_NUM 33 + +namespace YimMenu +{ + // TO-DO: Use script functions for these instead? + + static int GetPropertyGarageOffset(int property) + { + switch (property) + { + case 0: return 13 * 0; + case 1: return 13 * 1; + case 2: return 13 * 2; + case 3: return 13 * 3; + case 4: return 13 * 4; + //Property 5 is not used. + case 6: return 65; + case 7: return 75; + case 8: return 88; + case 9: return 108; + case 10: return 128; + case 11: return 148; + case 12: return 159; + case 13: return 179; + case 14: return 191; + case 15: return 192; + case 16: return 202; + case 17: return 212; + case 18: return 227; + case 19: return 237; + case 20: return 247; + case 21: return 258; + case 22: return 268; + case 23: return 281; + case 24: return 294; + case 25: return 307; + case 26: return 317; + case 27: return 337; + case 28: return 350; + case 29: return 363; + case 30: return 415; // The Vinewood Club Garage + case 31: return 515; + case 32: return 537; + case MAX_GARAGE_NUM + 0: return 156; // Mobile Operations Center + case MAX_GARAGE_NUM + 1: return 224; // Nightclub B1 + case MAX_GARAGE_NUM + 2: return 223; // Terrorbyte + case MAX_GARAGE_NUM + 3: return 278; // Kosatka + } + + return -1; + } + + static int GetPropertyGarageSize(int property) + { + switch (property) + { + case MAX_GARAGE_NUM + 0: // Mobile Operations Center + case MAX_GARAGE_NUM + 2: // Terrorbyte + case MAX_GARAGE_NUM + 3: // Kosatka + case 14: return 1; + case 31: return 2; + case MAX_GARAGE_NUM + 1: return 3; // Nightclub B1 + case 11: return 8; + case 6: + case 15: + case 16: + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 32: + case 25: return 10; + case 13: return 11; + case 0: + case 1: + case 2: + case 3: + case 4: + case 7: + case 23: + case 24: + case 27: + case 28: return 13; + case 8: + case 9: + case 10: + case 12: + case 26: return 20; + case 29: return 50; + case 30: return 100; // The Vinewood Club Garage + } + + return -1; + } + + static int GetPropertyStatState(int property) + { + std::string stat = ""; + switch (property) + { + case 0: stat = "MPX_PROPERTY_HOUSE"; break; + case 1: stat = "MPX_MULTI_PROPERTY_1"; break; + case 2: stat = "MPX_MULTI_PROPERTY_2"; break; + case 3: stat = "MPX_MULTI_PROPERTY_3"; break; + case 4: stat = "MPX_MULTI_PROPERTY_4"; break; + case 5: stat = "MPX_PROP_OFFICE"; break; + case 6: stat = "MPX_PROP_CLUBHOUSE"; break; + case 7: stat = "MPX_MULTI_PROPERTY_5"; break; + case 8: stat = "MPX_PROP_OFFICE_GAR1"; break; + case 9: stat = "MPX_PROP_OFFICE_GAR2"; break; + case 10: stat = "MPX_PROP_OFFICE_GAR3"; break; + case 11: stat = "MPX_PROP_IE_WAREHOUSE"; break; + case 12: stat = "MPX_PROP_HANGAR"; break; + case 13: stat = "MPX_PROP_DEFUNCBASE"; break; + case 14: stat = "MPX_PROP_NIGHTCLUB"; break; + case 15: stat = "MPX_PROP_MEGAWARE_GAR1"; break; + case 16: stat = "MPX_PROP_MEGAWARE_GAR2"; break; + case 17: stat = "MPX_PROP_MEGAWARE_GAR3"; break; + case 18: stat = "MPX_PROP_ARENAWARS_GAR1"; break; + case 19: stat = "MPX_PROP_ARENAWARS_GAR2"; break; + case 20: stat = "MPX_PROP_ARENAWARS_GAR3"; break; + case 21: stat = "MPX_PROP_CASINO_GAR1"; break; + case 22: stat = "MPX_PROP_ARCADE_GAR1"; break; + case 23: stat = "MPX_MULTI_PROPERTY_6"; break; + case 24: stat = "MPX_MULTI_PROPERTY_7"; break; + case 25: stat = "MPX_PROP_AUTO_SHOP"; break; + case 26: stat = "MPX_PROP_SECURITY_OFFICE_GAR"; break; + case 27: stat = "MPX_MULTI_PROPERTY_8"; break; + case 28: stat = "MPX_MULTI_PROPERTY_9"; break; + case 29: stat = "MPX_MULTSTOREY_GAR_OWNED"; break; + case 31: stat = "MPX_PROP_BAIL_OFFICE"; break; + case 32: stat = "MPX_PROP_HACKER_DEN"; break; + case 30: + case MAX_GARAGE_NUM + 0: + case MAX_GARAGE_NUM + 1: + case MAX_GARAGE_NUM + 2: + case MAX_GARAGE_NUM + 3: return 1; + default: return -1; + } + + return Stats::GetInt(stat); + } + + static std::string GetStaticPropertyName(int property, int garageSlotIterator) + { + switch (property) + { + case 12: // Hangar + { + auto id = GPBD_FM::Get()->Entries[Self::GetPlayer().GetId()].PropertyData.HangarData.Index; + switch (id) + { + case 1: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_HANGAR_1"); // LSIA Hangar 1 + case 2: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_HANGAR_2"); // LSIA Hangar A17 + case 3: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_HANGAR_3"); // Fort Zancudo Hangar A2 + case 4: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_HANGAR_4"); // Fort Zancudo Hangar 3497 + case 5: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_HANGAR_5"); // Fort Zancudo Hangar 3499 + } + break; + } + case 13: // Facility + { + auto id = GPBD_FM::Get()->Entries[Self::GetPlayer().GetId()].PropertyData.DefunctBaseData.Index; + switch (id) + { + case 1: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_DBASE_1"); // Grand Senora Desert Facility + case 2: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_DBASE_2"); // Route 68 Facility + case 3: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_DBASE_3"); // Sandy Shores Facility + case 4: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_DBASE_4"); // Mount Gordo Facility + case 5: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_DBASE_6"); // Paleto Bay Facility + case 6: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_DBASE_7"); // Lago Zancudo Facility + case 7: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_DBASE_8"); // Zancudo River Facility + case 8: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_DBASE_9"); // Ron Alternates Wind Farm Facility + case 9: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_DBASE_10"); // Land Act Reservoir Facility + } + break; + } + case 14: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_BHUB_CLUBG"); // Nightclub Service Entrance + case 15: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_BHUB_GAR1"); // Nightclub B2 + case 16: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_BHUB_GAR2"); // Nightclub B3 + case 17: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_BHUB_GAR3"); // Nightclub B4 + case 18: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("ARENA_GAR_F0"); // Arena Workshop + case 19: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("ARENA_GAR_F1"); // Arena Workshop B1 + case 20: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("ARENA_GAR_F2"); // Arena Workshop B2 + case 21: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("CASINO_GARNAME"); // Casino Penthouse + case 22: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("ARCADE_GARNAME"); // Arcade + case 25: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("AUT_SHP_GAR"); // Auto Shop + case 26: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("FIXER_GARNAME"); // Agency + case 29: // Eclipse Blvd Garage + { + int level = (garageSlotIterator - 1) / 10; + switch (level) + { + case 0: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MSG_B1"); // Eclipse Blvd Garage B1 + case 1: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MSG_B2"); // Eclipse Blvd Garage B2 + case 2: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MSG_B3"); // Eclipse Blvd Garage B3 + case 3: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MSG_B4"); // Eclipse Blvd Garage B4 + case 4: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MSG_B5"); // Eclipse Blvd Garage B5 + } + } + case 30: // The Vinewood Club Garage + { + int level = (garageSlotIterator - 1) / 10; + switch (level) + { + case 0: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("VPG_B1"); // Basement Level 1 + case 1: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("VPG_B2"); // Basement Level 2 + case 2: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("VPG_B3"); // Basement Level 3 + case 3: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("VPG_B4"); // Basement Level 4 + case 4: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("VPG_B5"); // Basement Level 5 + } + } + case 31: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("BO_GARNAME"); // Bail Office + case 32: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("HD_GARNAME"); // Garment Factory + case MAX_GARAGE_NUM + 0: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("GRTRUCK"); // Mobile Operations Center + case MAX_GARAGE_NUM + 1: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_BHUB_GAR0"); // Nightclub B1 + case MAX_GARAGE_NUM + 2: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_BHUB_CLUBT"); // Terrorbyte + case MAX_GARAGE_NUM + 3: return HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION("MP_BHUB_SUB"); // Kosatka + } + + return ""; + } + + PersonalVehicles::PersonalVehicle::PersonalVehicle(int id, MPSV_Entry* data) : + m_Id(id), + m_Data(data) + { + m_Model = m_Data->VehicleModel; + m_Plate = m_Data->NumberPlateText; + m_Name = std::format("{} ({})", HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION(VEHICLE::GET_DISPLAY_NAME_FROM_VEHICLE_MODEL(m_Model)), m_Plate); + + SetGarage(); + } + + int PersonalVehicles::PersonalVehicle::GetId() + { + return m_Id; + } + + MPSV_Entry* PersonalVehicles::PersonalVehicle::GetData() + { + return m_Data; + } + + joaat_t PersonalVehicles::PersonalVehicle::GetModel() + { + return m_Model; + } + + std::string PersonalVehicles::PersonalVehicle::GetPlate() + { + return m_Plate; + } + + std::string PersonalVehicles::PersonalVehicle::GetName() + { + return m_Name; + } + + std::string PersonalVehicles::PersonalVehicle::GetGarage() + { + return m_Garage; + } + + void PersonalVehicles::PersonalVehicle::SetGarage() + { + for (int propertyIterator = 0; propertyIterator < MAX_GARAGE_NUM + 4; propertyIterator++) + { + auto propertyStatState = GetPropertyStatState(propertyIterator); + if (propertyStatState > 0) + { + auto garageSize = GetPropertyGarageSize(propertyIterator); + auto garageOffset = GetPropertyGarageOffset(propertyIterator); + for (int garageSlotIterator = 1; garageSlotIterator <= garageSize; garageSlotIterator++) + { + auto itemInSlot = *ScriptGlobal(1940530).At(garageOffset).At(garageSlotIterator).As() - 1; + if (itemInSlot == m_Id) + { + auto staticPropertyString = GetStaticPropertyName(propertyIterator, garageSlotIterator); + if (staticPropertyString.empty()) + { + m_Garage = HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION(ScriptGlobal(1312335).At(propertyStatState, 1951).At(16).As()); + } + else + { + m_Garage = staticPropertyString; + } + return; + } + } + } + } + } + + bool PersonalVehicles::PersonalVehicle::Despawn() + { + if (auto veh = GetCurrentHandle(); veh.IsValid()) + { + veh.BringToHalt(); + + m_Data->PersonalVehicleFlags.Clear(ePersonalVehicleFlags::TRIGGER_SPAWN_TOGGLE); + for (int i = 0; veh.IsValid(); i++) + { + ScriptMgr::Yield(100ms); + if (i > 30) + { + LOG(WARNING) << "Despawn() Timed out despawning Personal Vehicle."; + return false; + } + } + } + + return true; + } + + bool PersonalVehicles::PersonalVehicle::Repair() + { + if (m_Data->PersonalVehicleFlags.IsSet(ePersonalVehicleFlags::DESTROYED) && m_Data->PersonalVehicleFlags.IsSet(ePersonalVehicleFlags::HAS_INSURANCE)) + { + m_Data->PersonalVehicleFlags.Clear(ePersonalVehicleFlags::DESTROYED); + m_Data->PersonalVehicleFlags.Clear(ePersonalVehicleFlags::IMPOUNDED); + m_Data->PersonalVehicleFlags.Clear(ePersonalVehicleFlags::UNK2); + return true; + } + + return false; + } + + bool PersonalVehicles::PersonalVehicle::Request(bool bring) + { + if (auto freemodeGeneral = FreemodeGeneral::Get()) + { + if (freemodeGeneral->RequestedPersonalVehicleId != -1) + return false; + + if (!GetCurrent()->Despawn()) + return false; + + Repair(); + + ScriptMgr::Yield(100ms); + + if (bring) + freemodeGeneral->NodeDistanceCheck = 0; + freemodeGeneral->PersonalVehicleRequested = TRUE; // am_pi_menu also sets the field 956 but I guess we don't need that + freemodeGeneral->Exec1Impound = FALSE; // not really sure what this does + freemodeGeneral->RequestedPersonalVehicleId = m_Id; + + ScriptMgr::Yield(100ms); + + *ScriptLocal("freemode"_J, 19447).At(176).As() = 0; + + if (bring) + { + for (int i = 0; !GetCurrentHandle().IsValid(); i++) + { + ScriptMgr::Yield(100ms); + if (i > 30) + break; + } + + if (auto veh = GetCurrentHandle(); veh.IsValid()) + { + auto coords = Self::GetPed().GetPosition(); + auto heading = Self::GetPed().GetHeading(); + veh.SetPosition(coords); + veh.SetHeading(heading); + veh.SetOnGroundProperly(); + Self::GetPed().SetInVehicle(veh); + } + } + + return true; + } + + return false; + } + + Vehicle PersonalVehicles::PersonalVehicle::Clone(rage::fvector3 coords, float heading) + { + if (auto veh = Vehicle::Create(m_Model, coords, heading)) + { + auto oldVal = m_Data->IsPersonalVehicle; + + m_Data->IsPersonalVehicle = 0; + + // This sometimes fails to apply the crew logo since ADD_VEHICLE_EMBLEM needs to be called in a loop + static ScriptFunction applyMPSVData("freemode"_J, ScriptPointer("ApplyMPSVData", "5D ? ? ? 38 2A 71").Add(1).Rip()); + applyMPSVData.Call(veh.GetHandle(), m_Data, true, true, false); + + m_Data->IsPersonalVehicle = oldVal; + return veh; + } + + return nullptr; + } + + std::unique_ptr PersonalVehicles::GetCurrentImpl() + { + auto savedMPGlobals = g_SavedMPGlobals::Get(); + auto MPSV = MPSV::Get(); + if (savedMPGlobals && MPSV) + { + auto id = savedMPGlobals->Entries[0].GeneralSaved.LastSavedCar; + auto data = &MPSV->Entries[id]; + return std::make_unique(id, data); + } + + return nullptr; + } + + Vehicle PersonalVehicles::GetCurrentHandleImpl() + { + if (auto freemodeGeneral = FreemodeGeneral::Get()) + { + if (auto veh = freemodeGeneral->PersonalVehicleIndex; veh != -1) + return Vehicle(veh); + } + + return nullptr; + } + + void PersonalVehicles::UpdateImpl() + { + const auto now = std::chrono::high_resolution_clock::now(); + if (std::chrono::duration_cast(now - m_LastUpdate) < 10s) + return; + + m_LastUpdate = std::chrono::high_resolution_clock::now(); + + FiberPool::Push([] { + RegisterVehicles(); + RegisterGarages(); + }); + } + + void PersonalVehicles::RegisterVehiclesImpl() + { + if (auto mpsv = MPSV::Get()) + { + const auto arraySize = *(int*)mpsv; + + for (int i = 0; i < arraySize; i++) + { + if (i % 100 == 0) + ScriptMgr::Yield(); + + auto data = &MPSV::Get()->Entries[i]; + + const auto model = data->VehicleModel; + const auto& it = m_PVLookup.find(i); + const auto exists = it != m_PVLookup.end(); + + if (STREAMING::IS_MODEL_A_VEHICLE(model)) + { + auto veh = std::make_unique(i, data); + + if (exists) + { + if (veh->GetName() != it->second) + { + m_PersonalVehicles.erase(it->second); + + it->second = veh->GetName(); + m_PersonalVehicles.emplace(veh->GetName(), std::move(veh)); + } + else + { + m_PersonalVehicles[veh->GetName()]->SetGarage(); + } + + continue; + } + + m_PVLookup.emplace(i, veh->GetName()); + m_PersonalVehicles.emplace(veh->GetName(), std::move(veh)); + continue; + } + + if (exists) + { + m_PersonalVehicles.erase(it->second); + m_PVLookup.erase(i); + } + } + } + } + + void PersonalVehicles::RegisterGaragesImpl() + { + m_Garages.clear(); + for (const auto& [name, ptr] : GetPersonalVehicles()) + { + if (auto garageName = ptr->GetGarage(); !garageName.empty()) + { + m_Garages.emplace(garageName); + } + } + } +} \ No newline at end of file diff --git a/src/game/backend/PersonalVehicles.hpp b/src/game/backend/PersonalVehicles.hpp new file mode 100644 index 0000000..8207f51 --- /dev/null +++ b/src/game/backend/PersonalVehicles.hpp @@ -0,0 +1,93 @@ +#pragma once +#include "core/util/Joaat.hpp" +#include "game/gta/Vehicle.hpp" + +#include + +struct MPSV_Entry; + +namespace YimMenu +{ + class PersonalVehicles + { + public: + class PersonalVehicle + { + int m_Id; + MPSV_Entry* m_Data; + joaat_t m_Model; + std::string m_Plate; + std::string m_Name; + std::string m_Garage; + + public: + PersonalVehicle(int id, MPSV_Entry* data); + + int GetId(); + MPSV_Entry* GetData(); + joaat_t GetModel(); + std::string GetPlate(); + std::string GetName(); + std::string GetGarage(); + void SetGarage(); + + bool Despawn(); + bool Repair(); + bool Request(bool bring = false); + Vehicle Clone(rage::fvector3 coords, float heading); + }; + + static std::unique_ptr GetCurrent() + { + return GetInstance().GetCurrentImpl(); + } + + static Vehicle GetCurrentHandle() + { + return GetInstance().GetCurrentHandleImpl(); + } + + static std::map>& GetPersonalVehicles() + { + return GetInstance().m_PersonalVehicles; + } + + static std::set& GetGarages() + { + return GetInstance().m_Garages; + } + + static void Update() + { + GetInstance().UpdateImpl(); + } + + static void RegisterGarages() + { + GetInstance().RegisterGaragesImpl(); + } + + static void RegisterVehicles() + { + GetInstance().RegisterVehiclesImpl(); + } + + private: + static PersonalVehicles& GetInstance() + { + static PersonalVehicles instance; + return instance; + } + + std::unique_ptr GetCurrentImpl(); + Vehicle GetCurrentHandleImpl(); + void UpdateImpl(); + void RegisterVehiclesImpl(); + void RegisterGaragesImpl(); + + std::map> m_PersonalVehicles; + std::map m_PVLookup; + std::set m_Garages; + std::chrono::time_point m_LastUpdate; + }; +} \ No newline at end of file diff --git a/src/game/features/vehicle/DespawnPersonalVehicle.cpp b/src/game/features/vehicle/DespawnPersonalVehicle.cpp new file mode 100644 index 0000000..b7909a5 --- /dev/null +++ b/src/game/features/vehicle/DespawnPersonalVehicle.cpp @@ -0,0 +1,21 @@ +#include "core/commands/Command.hpp" +#include "game/backend/PersonalVehicles.hpp" +#include "game/pointers/Pointers.hpp" + +namespace YimMenu +{ + class DespawnPersonalVehicle : public Command + { + using Command::Command; + + virtual void OnCall() override + { + if (!*Pointers.IsSessionStarted) + return; + + PersonalVehicles::GetCurrent()->Despawn(); + } + }; + + static DespawnPersonalVehicle _DespawnPersonalVehicle{"despawnpv", "Despawn Personal Vehicle", "Returns your current Personal Vehicle to storage."}; +} \ No newline at end of file diff --git a/src/game/features/vehicle/FixAllVehicles.cpp b/src/game/features/vehicle/FixAllVehicles.cpp index 5f4ac47..e9b7fb1 100644 --- a/src/game/features/vehicle/FixAllVehicles.cpp +++ b/src/game/features/vehicle/FixAllVehicles.cpp @@ -1,6 +1,7 @@ #include "core/commands/Command.hpp" #include "core/frontend/Notifications.hpp" -#include "types/script/globals/MPSV.hpp" +#include "game/backend/PersonalVehicles.hpp" +#include "game/pointers/Pointers.hpp" namespace YimMenu::Features { @@ -10,27 +11,21 @@ namespace YimMenu::Features virtual void OnCall() override { - if (auto mpsv = MPSV::Get()) - { - int count = 0; - for (int i = 0; i < *(int*)mpsv; i++) - { - if (mpsv->Entries[i].PersonalVehicleFlags.IsSet(1) && mpsv->Entries[i].PersonalVehicleFlags.IsSet(2)) - { - mpsv->Entries[i].PersonalVehicleFlags.Clear(1); - mpsv->Entries[i].PersonalVehicleFlags.Clear(6); - mpsv->Entries[i].PersonalVehicleFlags.Clear(16); - mpsv->Entries[i].PersonalVehicleFlags.Set(0); - mpsv->Entries[i].PersonalVehicleFlags.Set(11); - count++; - } - } + if (!*Pointers.IsSessionStarted) + return; - if (count > 0) - Notifications::Show("Fix All Vehicles", std::format("{} vehicles fixed.", count), NotificationType::Success); - else - Notifications::Show("Fix All Vehicles", "No vehicles to fix."); + int count = 0; + for (const auto& it : PersonalVehicles::GetPersonalVehicles()) + { + const auto& personalVeh = it.second; + if (personalVeh->Repair()) + count++; } + + if (count > 0) + Notifications::Show("Fix All Vehicles", std::format("{} vehicles fixed.", count), NotificationType::Success); + else + Notifications::Show("Fix All Vehicles", "No vehicles to fix."); } }; diff --git a/src/game/features/vehicle/RequestPersonalVehicle.cpp b/src/game/features/vehicle/RequestPersonalVehicle.cpp new file mode 100644 index 0000000..fee70f4 --- /dev/null +++ b/src/game/features/vehicle/RequestPersonalVehicle.cpp @@ -0,0 +1,21 @@ +#include "core/commands/Command.hpp" +#include "game/backend/PersonalVehicles.hpp" +#include "game/pointers/Pointers.hpp" + +namespace YimMenu +{ + class RequestPersonalVehicle : public Command + { + using Command::Command; + + virtual void OnCall() override + { + if (!*Pointers.IsSessionStarted) + return; + + PersonalVehicles::GetCurrent()->Request(); + } + }; + + static RequestPersonalVehicle _RequestPersonalVehicle{"requestpv", "Request Personal Vehicle", "Requests your current Personal Vehicle."}; +} \ No newline at end of file diff --git a/src/game/frontend/submenus/Vehicle.cpp b/src/game/frontend/submenus/Vehicle.cpp index 89e7068..63fda32 100644 --- a/src/game/frontend/submenus/Vehicle.cpp +++ b/src/game/frontend/submenus/Vehicle.cpp @@ -13,7 +13,7 @@ namespace YimMenu::Submenus auto main = std::make_shared("Main"); auto globals = std::make_shared("Globals"); - auto tools = std::make_shared("Tools", 1); + auto tools = std::make_shared("Tools", 2); auto misc = std::make_shared("Misc"); globals->AddItem(std::make_shared("vehiclegodmode"_J, "Godmode")); @@ -26,6 +26,8 @@ namespace YimMenu::Submenus tools->AddItem(std::make_shared("repairvehicle"_J)); tools->AddItem(std::make_shared("fixallvehicles"_J)); tools->AddItem(std::make_shared("callmechanic"_J)); + tools->AddItem(std::make_shared("requestpv"_J)); + tools->AddItem(std::make_shared("despawnpv"_J)); tools->AddItem(std::make_shared("savepersonalvehicle"_J)); misc->AddItem(std::make_shared("speedometer"_J)); diff --git a/src/game/frontend/submenus/Vehicle/SpawnVehicle.cpp b/src/game/frontend/submenus/Vehicle/SpawnVehicle.cpp index 86a1653..7737b63 100644 --- a/src/game/frontend/submenus/Vehicle/SpawnVehicle.cpp +++ b/src/game/frontend/submenus/Vehicle/SpawnVehicle.cpp @@ -2,15 +2,18 @@ #include "core/commands/BoolCommand.hpp" #include "core/backend/ScriptMgr.hpp" #include "core/backend/FiberPool.hpp" +#include "core/frontend/Notifications.hpp" #include "game/backend/Self.hpp" +#include "game/backend/PersonalVehicles.hpp" #include "game/gta/data/Vehicles.hpp" -#include "game/gta/Vehicle.hpp" #include "game/gta/Natives.hpp" namespace YimMenu::Submenus { static BoolCommand spawnInsideVehicle{"spawninsideveh", "Spawn Inside", "Spawn inside the vehicle."}; static BoolCommand spawnVehicleMaxed{"spawnvehmaxed", "Spawn Maxed", "Spawn the vehicle maxed."}; + static BoolCommand spawnInsidePersonalVehicle{"spawninsidepv", "Spawn Inside", "Spawn inside the personal vehicle."}; + static BoolCommand spawnClonePersonalVehicle{"spawnclonepv", "Spawn Clone", "Spawn a clone of the persone vehicle."}; static Vector3 GetVehicleSpawnLoc(joaat_t hash, bool spawnInside) { @@ -31,15 +34,18 @@ namespace YimMenu::Submenus return ENTITY::GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(Self::GetPed().GetHandle(), 0.f, y_offset, 0.f); } - std::shared_ptr BuildSpawnVehicleMenu() + std::shared_ptr RenderSpawnNewVehicle() { - auto menu = std::make_shared("Spawn"); + auto tab = std::make_shared("New Vehicle"); + + auto spawn = std::make_shared("Spawn"); + auto settings = std::make_shared("Settings"); static std::vector vehicleNames{}; static std::vector vehicleClasses{}; static int selectedClass{-1}; - menu->AddItem(std::make_unique([] { + spawn->AddItem(std::make_unique([] { static bool init = [] { FiberPool::Push([] { std::unordered_map nameCount; @@ -96,40 +102,44 @@ namespace YimMenu::Submenus ImGui::EndCombo(); } - const int visible = std::min(10, static_cast(vehicleNames.size())); + const int visible = std::min(20, static_cast(vehicleNames.size())); const float height = visible * ImGui::GetTextLineHeightWithSpacing(); if (ImGui::BeginListBox("##vehicles", {300.f, height})) { - std::string lower = search; - std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); - for (int veh = 0; veh < vehicleNames.size(); veh++) + if (vehicleNames.empty()) { - if (vehicleNames[veh].empty()) - continue; - - auto hash = g_VehicleHashes[veh]; - auto name = vehicleNames[veh]; - auto lowerName = name; - std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); - - bool matchesSearch = lowerName.find(lower) != std::string::npos; - bool matchesClass = selectedClass == -1 || vehicleClasses[veh] == selectedClass; - if (matchesSearch && matchesClass) + ImGui::Text("Natives not cached yet."); + } + else + { + std::string lower = search; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + for (int veh = 0; veh < vehicleNames.size(); veh++) { - ImGui::PushID(hash); - if (ImGui::Selectable(name.c_str())) + auto hash = g_VehicleHashes[veh]; + auto name = vehicleNames[veh]; + auto lowerName = name; + std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); + + bool matchesSearch = lowerName.find(lower) != std::string::npos; + bool matchesClass = selectedClass == -1 || vehicleClasses[veh] == selectedClass; + if (matchesSearch && matchesClass) { - FiberPool::Push([hash] { - auto handle = Vehicle::Create(hash, GetVehicleSpawnLoc(hash, spawnInsideVehicle.GetState()), Self::GetPed().GetHeading()); + ImGui::PushID(hash); + if (ImGui::Selectable(name.c_str())) + { + FiberPool::Push([hash] { + auto handle = Vehicle::Create(hash, GetVehicleSpawnLoc(hash, spawnInsideVehicle.GetState()), Self::GetPed().GetHeading()); - if (spawnInsideVehicle.GetState()) - Self::GetPed().SetInVehicle(handle); + if (spawnInsideVehicle.GetState()) + Self::GetPed().SetInVehicle(handle); - if (spawnVehicleMaxed.GetState()) - handle.Upgrade(); - }); + if (spawnVehicleMaxed.GetState()) + handle.Upgrade(); + }); + } + ImGui::PopID(); } - ImGui::PopID(); } } @@ -137,8 +147,126 @@ namespace YimMenu::Submenus } })); - menu->AddItem(std::make_shared("spawninsideveh"_J)); - menu->AddItem(std::make_shared("spawnvehmaxed"_J)); + settings->AddItem(std::make_shared("spawninsideveh"_J)); + settings->AddItem(std::make_shared("spawnvehmaxed"_J)); + + tab->AddItem(spawn); + tab->AddItem(settings); + return tab; + } + + std::shared_ptr RenderSpawnPersonalVehicle() + { + auto tab = std::make_shared("Personal Vehicle"); + + auto spawn = std::make_shared("Spawn"); + auto settings = std::make_shared("Settings"); + + static std::string selectedGarageStr{""}; + + spawn->AddItem(std::make_unique([] { + if (!*Pointers.IsSessionStarted) + return ImGui::TextDisabled("Join GTA Online."); + + PersonalVehicles::Update(); + + static char search[64]; + ImGui::SetNextItemWidth(300.f); + ImGui::InputTextWithHint("Name", "Search", search, sizeof(search)); + + ImGui::SetNextItemWidth(300.f); + if (ImGui::BeginCombo("Garage", selectedGarageStr.empty() ? "All" : selectedGarageStr.c_str())) + { + if (ImGui::Selectable("All", selectedGarageStr.empty())) + { + selectedGarageStr.clear(); + } + for (auto garage : PersonalVehicles::GetGarages()) + { + if (ImGui::Selectable(garage.c_str(), garage == selectedGarageStr)) + { + selectedGarageStr = garage; + } + } + + ImGui::EndCombo(); + } + + const int visible = std::min(20, static_cast(PersonalVehicles::GetPersonalVehicles().size())); + const float height = visible * ImGui::GetTextLineHeightWithSpacing(); + if (ImGui::BeginListBox("##personalvehicles", {300.f, height})) + { + if (PersonalVehicles::GetPersonalVehicles().empty()) + { + ImGui::Text("Stats not loaded yet."); + } + else + { + std::string lowerSearch = search; + std::transform(lowerSearch.begin(), lowerSearch.end(), lowerSearch.begin(), tolower); + for (const auto& it : PersonalVehicles::GetPersonalVehicles()) + { + const auto& label = it.first; + const auto& personalVeh = it.second; + + auto lowerName = label; + std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); + + bool matchesSearch = lowerName.find(lowerSearch) != std::string::npos; + bool matchesGarage = selectedGarageStr.empty() || personalVeh->GetGarage() == selectedGarageStr; + if (matchesSearch && matchesGarage) + { + ImGui::PushID(personalVeh->GetId()); + if (ImGui::Selectable(label.c_str())) + { + FiberPool::Push([&personalVeh] { + if (spawnClonePersonalVehicle.GetState()) + { + auto coords = GetVehicleSpawnLoc(personalVeh->GetModel(), spawnInsidePersonalVehicle.GetState()); + auto heading = Self::GetPed().GetHeading(); + auto handle = personalVeh->Clone(coords, heading); + + if (spawnInsidePersonalVehicle.GetState()) + Self::GetPed().SetInVehicle(handle); + } + else + { + if (!personalVeh->Request(spawnInsidePersonalVehicle.GetState())) + Notifications::Show("Spawn Personal Vehicle", "Failed to spawn Personal Vehicle.", NotificationType::Error); + } + }); + } + ImGui::PopID(); + } + } + } + + ImGui::EndListBox(); + } + })); + + settings->AddItem(std::make_shared("spawninsidepv"_J)); + settings->AddItem(std::make_shared("spawnclonepv"_J)); + + + tab->AddItem(spawn); + tab->AddItem(settings); + return tab; + } + + std::shared_ptr BuildSpawnVehicleMenu() + { + auto menu = std::make_shared("Spawn"); + + menu->AddItem(std::make_shared([] { + ImGui::BeginTabBar("Spawn"); + })); + menu->AddItem(RenderSpawnNewVehicle()); + menu->AddItem(RenderSpawnPersonalVehicle()); + menu->AddItem(std::make_shared([] { + ImGui::EndTabBar(); + })); + return menu; } } \ No newline at end of file diff --git a/src/game/gta/Vehicle.cpp b/src/game/gta/Vehicle.cpp index b02358c..3413b22 100644 --- a/src/game/gta/Vehicle.cpp +++ b/src/game/gta/Vehicle.cpp @@ -174,6 +174,20 @@ namespace YimMenu VEHICLE::SET_REDUCED_SUSPENSION_FORCE(GetHandle(), lower); } + void Vehicle::BringToHalt(float distance, int duration) + { + ENTITY_ASSERT_VALID(); + + VEHICLE::BRING_VEHICLE_TO_HALT(GetHandle(), distance, duration, FALSE); + } + + bool Vehicle::SetOnGroundProperly() + { + ENTITY_ASSERT_VALID(); + + return VEHICLE::SET_VEHICLE_ON_GROUND_PROPERLY(GetHandle(), 5.f); + } + std::string Vehicle::GetFullName() { auto model = ENTITY::GET_ENTITY_MODEL(GetHandle()); diff --git a/src/game/gta/Vehicle.hpp b/src/game/gta/Vehicle.hpp index 382ece8..a8e17de 100644 --- a/src/game/gta/Vehicle.hpp +++ b/src/game/gta/Vehicle.hpp @@ -36,6 +36,9 @@ namespace YimMenu // stance void LowerStance(bool lower); + void BringToHalt(float distance = 1.0f, int duration = 1); + bool SetOnGroundProperly(); + std::string GetFullName(); }; } \ No newline at end of file diff --git a/src/game/scripting/libraries/Vehicle.cpp b/src/game/scripting/libraries/Vehicle.cpp index 098ee37..4465053 100644 --- a/src/game/scripting/libraries/Vehicle.cpp +++ b/src/game/scripting/libraries/Vehicle.cpp @@ -100,6 +100,18 @@ namespace YimMenu::Lua return 0; } + static int BringToHalt(lua_State* state) + { + GetObject(state, 1).BringToHalt(luaL_checknumber(state, 2), luaL_checkinteger(state, 3)); + return 0; + } + + static int SetOnGroundProperly(lua_State* state) + { + lua_pushboolean(state, GetObject(state, 1).SetOnGroundProperly()); + return 1; + } + static int GetFullName(lua_State* state) { auto name = GetObject(state, 1).GetFullName(); @@ -126,6 +138,8 @@ namespace YimMenu::Lua SetFunction(state, IsBoostActive, "is_boost_active"); SetFunction(state, SetBoostCharge, "set_boost_charge"); SetFunction(state, LowerStance, "lower_stance"); + SetFunction(state, BringToHalt, "bring_to_halt"); + SetFunction(state, SetOnGroundProperly, "set_on_ground_properly"); SetFunction(state, GetFullName, "get_full_name"); } lua_setfield(state, -2, "__index"); // prototype diff --git a/src/types/script/globals/FreemodeGeneral.hpp b/src/types/script/globals/FreemodeGeneral.hpp index cbbc8c1..6692d5b 100644 --- a/src/types/script/globals/FreemodeGeneral.hpp +++ b/src/types/script/globals/FreemodeGeneral.hpp @@ -58,7 +58,15 @@ struct FreemodeGeneral { uint64_t PAD_0000[301]; VEHICLE_INDEX PersonalVehicleIndex; // 301 - uint64_t PAD_0302[6553]; + uint64_t PAD_0302[653]; + SCR_BOOL PersonalVehicleRequested; // 955 + uint64_t PAD_0956[13]; + SCR_BOOL NodeDistanceCheck; // 969 + uint64_t PAD_0970[48]; + SCR_INT RequestedPersonalVehicleId; // 1018 + uint64_t PAD_1019[2]; + SCR_BOOL Exec1Impound; // 1021 + uint64_t PAD_1022[5833]; STREET_DEALERS StreetDealers; // 6855 uint64_t PAD_6879[71]; DAILY_RESET_DATA DailyReset; // 6950 diff --git a/src/types/script/globals/MPSV.hpp b/src/types/script/globals/MPSV.hpp index 9230724..9a1cc1c 100644 --- a/src/types/script/globals/MPSV.hpp +++ b/src/types/script/globals/MPSV.hpp @@ -1,6 +1,18 @@ #pragma once #include "types/script/types.hpp" +enum class ePersonalVehicleFlags +{ + TRIGGER_SPAWN_TOGGLE = 0, + DESTROYED = 1, + HAS_INSURANCE = 2, + UNK0 = 3, + IMPOUNDED = 6, + UNK1 = 10, + SPAWN_AT_MORS_MUTUAL = 11, + UNK2 = 16 +}; + struct MPSV_Entry { SCR_INT NumberPlateTextIndex; // 0 (SET/GET_VEHICLE_NUMBER_PLATE_TEXT) @@ -41,7 +53,7 @@ struct MPSV_Entry SCR_INT ImportExportVehicle; // 101 SCR_INT TyreType; // 102 SCR_BITSET Flags3; // 103 (flag 0 is set if the MLJ decor exists) - SCR_BITSET PersonalVehicleFlags; // 104 + SCR_BITSET PersonalVehicleFlags; // 104 SCR_INT ImpoundTime; // 105 (MP_STAT_MPSV_IMPOUND_TIME) SCR_INT PremiumPaid; // 106 (MP_STAT_MPSV_PREMIUM_PAID) TEXT_LABEL_63 DestroyerName; // 107