diff --git a/src/game/frontend/submenus/network/RandomEvents.cpp b/src/game/frontend/submenus/network/RandomEvents.cpp index aafa7f1..f91d1d1 100644 --- a/src/game/frontend/submenus/network/RandomEvents.cpp +++ b/src/game/frontend/submenus/network/RandomEvents.cpp @@ -3,9 +3,11 @@ #include "core/backend/ScriptMgr.hpp" #include "core/frontend/Notifications.hpp" #include "game/backend/Self.hpp" +#include "game/backend/Tunables.hpp" #include "game/gta/Scripts.hpp" #include "game/gta/ScriptFunction.hpp" #include "game/pointers/Pointers.hpp" +#include "types/script/globals/GPBD_FM_2.hpp" #include "types/script/globals/GSBD_RandomEvents.hpp" #include "types/script/locals/FMRandomEvents.hpp" #include "types/script/ScriptEvent.hpp" @@ -66,7 +68,7 @@ namespace YimMenu::Submenus "Community Outreach" }; - static constexpr std::array RandomEventScripts = { + static constexpr std::array randomEventScripts = { "fm_content_drug_vehicle"_J, "fm_content_movie_props"_J, "fm_content_golden_gun"_J, @@ -90,14 +92,66 @@ namespace YimMenu::Submenus "fm_content_community_outreach"_J }; - static YimMenu::ScriptFunction getNumFMMCVariations("GMFV", "freemode"_J, "5D ? ? ? 01 72 02 39 04", 1, true); + static constexpr std::array randomEventCooldowns = { + "SUM22_RE_DRUG_VEHICLE_INACTIVE_TIME"_J, + "SUM22_RE_MOVIE_PROPS_INACTIVE_TIME"_J, + "SUM22_RE_GOLDEN_GUN_INACTIVE_TIME"_J, + "SUM22_RE_VEHICLE_LIST_INACTIVE_TIME"_J, + "STANDARDCONTROLLERVOLUME_COOLDOWN"_J, + "STANDARDTARGETTINGTIME_COOLDOWN"_J, + "SSP2_COOLDOWN"_J, + "SUM22_RE_SMUGGLER_TRAIL_INACTIVE_TIME"_J, + "NC_SOURCE_TRUCK_COOLDOWN"_J, + "SUM22_RE_SMUGGLER_PLANE_INACTIVE_TIME"_J, + "SUM22_RE_CRIME_SCENE_INACTIVE_TIME"_J, + "SUM22_RE_METAL_DETECTOR_INACTIVE_TIME"_J, + "XM22_RE_GANG_CONVOY_INACTIVE_TIME"_J, + "XM22_RE_ROBBERY_INACTIVE_TIME"_J, + "STANDARD_KEYBIND_COOLDOWN"_J, + "XM22_RE_BANK_SHOOTOUT_INACTIVE_TIME"_J, + (joaat_t)NULL, + "STANDARDCONTROLLERVOLUME_COOLDOWN"_J, + "SUM23_RE_GHOSTHUNT_INACTIVE_TIME"_J, + "XMAS_TRUCK_INACTIVE_TIME"_J, + "RE_COMMUNITY_OUTREACH_INACTIVE_TIME"_J + }; + + static constexpr std::array randomEventAvailabilities = { + "SUM22_RE_DRUG_VEHICLE_AVAILABLE_TIME"_J, + "SUM22_RE_MOVIE_PROPS_AVAILABLE_TIME"_J, + "SUM22_RE_GOLDEN_GUN_AVAILABLE_TIME"_J, + "SUM22_RE_VEHICLE_LIST_AVAILABLE_TIME"_J, + "STANDARDCONTROLLERVOLUME_AVAILABILITY"_J, + "STANDARDTARGETTINGTIME_AVAILABILITY"_J, + "SSP2_AVAILABILITY"_J, + "SUM22_RE_SMUGGLER_TRAIL_AVAILABLE_TIME"_J, + "NC_SOURCE_TRUCK_AVAILABILITY"_J, + "SUM22_RE_SMUGGLER_PLANE_AVAILABLE_TIME"_J, + "SUM22_RE_CRIME_SCENE_AVAILABLE_TIME"_J, + "SUM22_RE_METAL_DETECTOR_AVAILABLE_TIME"_J, + "XM22_RE_GANG_CONVOY_AVAILABLE_TIME"_J, + "XM22_RE_ROBBERY_AVAILABLE_TIME"_J, + "STANDARD_KEYBIND_AVAILABILITY"_J, + "XM22_RE_BANK_SHOOTOUT_AVAILABLE_TIME"_J, + (joaat_t)NULL, + "STANDARDCONTROLLERVOLUME_AVAILABILITY"_J, + "SUM23_RE_GHOSTHUNT_AVAILABLE_TIME"_J, + "XMAS_TRUCK_AVAILABLE_TIME"_J, + "RE_COMMUNITY_OUTREACH_AVAILABLE_TIME"_J + }; + + static ScriptFunction getNumFMMCVariations("GNFMMCV", "freemode"_J, "5D ? ? ? 01 72 02 39 04", 1, true); + static GPBD_FM_2* GPBDFM2 = nullptr; static GSBD_RandomEvents* GSBDRandomEvents = nullptr; static RANDOM_EVENTS_FREEMODE_DATA* FMRandomEvents = nullptr; static eRandomEvent selectedEvent = DRUG_VEHICLE; static int selectedSubvariation = 0; - static int numSubVariations = 30; + static int numSubvariations = 29; + static int setCooldown = 1800000; + static int setAvailability = 900000; + static bool applyInMinutes = false; - std::string GetEventTimeLeftStr(int eventTime, int timer) + static std::string GetEventTimeLeftStr(int eventTime, int timer) { int timePassed = (*Pointers.NetworkTime - timer) < 0 ? -(*Pointers.NetworkTime - timer) : (*Pointers.NetworkTime - timer); int diff = (eventTime - timePassed); @@ -129,20 +183,53 @@ namespace YimMenu::Submenus return "N/A"; } + static int GetNumLocallyActiveEvents() + { + int numEvents{}; + + for (int event = DRUG_VEHICLE; event < MAX_EVENTS; event++) + { + if (GPBDFM2->Entries[Self::GetPlayer().GetId()].RandomEventsClientData.EventData[event].State != eRandomEventClientState::INACTIVE) + numEvents++; + } + + return numEvents; + } + + static void ResetEventTunables(eRandomEvent event) + { + if (event == ARMOURED_TRUCK) // It doesn't have tunables + { + setCooldown = *ScriptGlobal(262145).At(33719).As(); + setAvailability = *ScriptGlobal(262145).At(33720).As(); + } + else + { + // Phantom Car's cooldown is actually 2147483647ms if STANDARDTARGETTINGTIME is not enabled + setCooldown = *Tunables::GetTunable(randomEventCooldowns[event]).As(); + setAvailability = *Tunables::GetTunable(randomEventAvailabilities[event]).As(); + } + } + + static void OnComboChange() + { + selectedSubvariation = 0; + numSubvariations = getNumFMMCVariations.Call(FMRandomEvents->MissionData.FMMCData[selectedEvent].FMMCType, 0) - 1; + ResetEventTunables(selectedEvent); + } + std::shared_ptr BuildRandomEventsMenu() { auto menu = std::make_shared("Random Events"); menu->AddItem(std::make_unique([] { - GSBDRandomEvents = nullptr; - FMRandomEvents = nullptr; - + GPBDFM2 = GPBD_FM_2::Get(); GSBDRandomEvents = GSBD_RandomEvents::Get(); - if (!GSBDRandomEvents) - return ImGui::Text("Freemode global block is not loaded yet."); + if (!GPBDFM2 || !GSBDRandomEvents) + return ImGui::Text("Freemode global block is not loaded."); - if (GSBDRandomEvents->InitState != eRandomEventInitState::INITIALIZED) - return ImGui::Text("Random Events are not initialized yet."); + if (GPBDFM2->Entries[Self::GetPlayer().GetId()].RandomEventsClientData.InitState != eRandomEventClientInitState::INITIALIZED) + return ImGui::Text("Random Events are not initialized."); if (auto freemode = Scripts::FindScriptThread("freemode"_J)) { @@ -176,8 +263,8 @@ namespace YimMenu::Submenus if (ImGui::Selectable(randomEventNames[event], event == selectedEvent)) { FiberPool::Push([event] { - selectedEvent = (eRandomEvent)event; - numSubVariations = getNumFMMCVariations.Call(FMRandomEvents->MissionData.FMMCData[selectedEvent].FMMCType, 0) - 1; + selectedEvent = (eRandomEvent)event; + OnComboChange(); }); } @@ -187,11 +274,18 @@ namespace YimMenu::Submenus ImGui::EndCombo(); } - if (ImGui::InputInt(std::format("Select Location (0-{})", numSubVariations).c_str(), &selectedSubvariation)) + if (ImGui::InputInt(std::format("Select Location (0-{})", numSubvariations).c_str(), &selectedSubvariation)) { - selectedSubvariation = std::clamp(selectedSubvariation, 0, numSubVariations); + selectedSubvariation = std::clamp(selectedSubvariation, 0, numSubvariations); } + int numActiveEvents = GetNumLocallyActiveEvents(); + int maxActiveEvents = *Tunables::GetTunable("FMREMAXACTIVATEDEVENTS"_J).As(); + if (numActiveEvents >= maxActiveEvents) + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Active Events: %d/%d", numActiveEvents, maxActiveEvents); + else + ImGui::Text("Active Events: %d/%d", numActiveEvents, maxActiveEvents); + if (ImGui::Button("Launch Event")) { FiberPool::Push([] { @@ -214,10 +308,6 @@ namespace YimMenu::Submenus } }); } - if (ImGui::IsItemHovered()) - { - ImGui::SetTooltip("Force launches the event."); - } ImGui::SameLine(); @@ -230,7 +320,7 @@ namespace YimMenu::Submenus } else if (GSBDRandomEvents->EventData[selectedEvent].State == eRandomEventState::ACTIVE) { - ScriptFunction terminateFMContentScript("TFMCS", RandomEventScripts[(int)selectedEvent], "2D 01 05 00 00 5D ? ? ?"); + ScriptFunction terminateFMContentScript("TFMCS", randomEventScripts[(int)selectedEvent], "2D 01 05 00 00 5D"); terminateFMContentScript.Call(false); // Is it ok to do this when we're not host? } else @@ -239,10 +329,6 @@ namespace YimMenu::Submenus } }); } - if (ImGui::IsItemHovered()) - { - ImGui::SetTooltip("Terminates the event."); - } ImGui::SameLine(); @@ -267,14 +353,10 @@ namespace YimMenu::Submenus } }); } - if (ImGui::IsItemHovered()) - { - ImGui::SetTooltip("Teleports to the event."); - } if (GSBDRandomEvents->EventData[selectedEvent].State == eRandomEventState::ACTIVE) { - if (auto eventThread = Scripts::FindScriptThread(RandomEventScripts[(int)selectedEvent])) + if (auto eventThread = Scripts::FindScriptThread(randomEventScripts[(int)selectedEvent])) { if (auto netComponent = reinterpret_cast(eventThread)->m_NetComponent) { @@ -296,8 +378,37 @@ namespace YimMenu::Submenus } ImGui::Text("State: %s", GetEventStateString().c_str()); - ImGui::Text("Location: %d", GSBDRandomEvents->EventData[selectedEvent].Subvariation); - ImGui::Text("Trigger Range: %.2f", GSBDRandomEvents->EventData[selectedEvent].TriggerRange); // Default value is 400, it will be updated once the event switches to the available state + if (GSBDRandomEvents->EventData[selectedEvent].State == eRandomEventState::INACTIVE) + { + ImGui::Text("Location: N/A"); + ImGui::Text("Trigger Range: N/A"); + } + else + { + ImGui::Text("Location: %d", GSBDRandomEvents->EventData[selectedEvent].Subvariation); + ImGui::Text("Trigger Range: %.2f", GSBDRandomEvents->EventData[selectedEvent].TriggerRange); // Default value is 400, it will be updated once the event switches to the available state + } + + // We should probably put this into a separate group, but I just don't want to do the same safety checks before rendering it + ImGui::SeparatorText("Cooldown & Availability"); + + ImGui::InputInt("##cooldown", &setCooldown); + ImGui::SameLine(); + if (ImGui::Button("Set Cooldown")) + { + int value = applyInMinutes ? (setCooldown * 60000) : setCooldown; + FMRandomEvents->EventData[selectedEvent].InactiveTime = value; + } + + ImGui::InputInt("##availability", &setAvailability); + ImGui::SameLine(); + if (ImGui::Button("Set Availability")) + { + int value = applyInMinutes ? (setAvailability * 60000) : setAvailability; + FMRandomEvents->EventData[selectedEvent].AvailableTime = value; + } + + ImGui::Checkbox("Apply in Minutes", &applyInMinutes); })); return menu; diff --git a/src/types/script/globals/GPBD_FM_2.hpp b/src/types/script/globals/GPBD_FM_2.hpp new file mode 100644 index 0000000..9aa0bcb --- /dev/null +++ b/src/types/script/globals/GPBD_FM_2.hpp @@ -0,0 +1,120 @@ +#pragma once +#include "../Timer.hpp" +#include "../types.hpp" + +enum class eRaceRoundType +{ + NOT_SELECTED = -1, + FINISH_FIRST, + MOST_POINTS, + FASTEST_TIME +}; + +enum class eRaceFinalRoundType +{ + NOT_SELECTED, + HEAD_TO_HEAD +}; + +enum class eRandomEventClientState +{ + INACTIVE, + TRIGGERING, // This is different than the server available state + ACTIVE, + CLEANUP +}; + +enum class eRandomEventClientFlags +{ + FORCE_LAUNCH +}; + +enum class eRandomEventClientInitState +{ + INITIALIZING, + INITIALIZED +}; + +struct GANGOPS_DATA +{ + SCR_INT UNK_0000; // 0 + SCR_ARRAY PlayerIndexes; // 1 + SCR_ARRAY PlayerRoles; // 6 + SCR_ARRAY ArePlayersReady; // 11 + SCR_ARRAY PlayerCuts; // 16 + SCR_INT HighlightedPlayer; // 22 + SCR_INT GangopsFlowMissionProgFlags; // 23 + SCR_INT GangopsFMMissionProgFlags; // 24 + SCR_INT GangopsFMMissionSkipFlags; // 25 + SCR_INT GangopsHeistStatusFlags; // 26 + SCR_INT Flags; // 27 + SCR_BOOL IsPlanningScreenReady; // 28 + SCR_BOOL QuittedPlanningScreen; // 29 + SCR_BOOL IsTutorialActive; // 30 + SCR_ARRAY IsTutorialActiveForPlayer; // 31 + TIMER FinaleForceLaunchTimer; // 36 (H2_FINALE_FORCE_LAUNCH_TIME) + SCR_BOOL IsFinaleForceLaunched; // 38 (if players don't click ready in 3 minutes) +}; +static_assert(sizeof(GANGOPS_DATA) == 39 * 8); + +struct RANDOM_EVENTS_CLIENT_EVENT_DATA +{ + alignas(8) eRandomEventClientState State; // 0 + SCR_ARRAY, 1> Flags; // 1 +}; +static_assert(sizeof(RANDOM_EVENTS_CLIENT_EVENT_DATA) == 3 * 8); + +struct RANDOM_EVENTS_CLIENT_DATA +{ + alignas(8) eRandomEventClientInitState InitState; // 0 + SCR_ARRAY EventData; // 1 + SCR_BITSET IsParticipantInEventFlags; // 65 + SCR_INT UNK_0066; // 66 (unused) +}; +static_assert(sizeof(RANDOM_EVENTS_CLIENT_DATA) == 67 * 8); + +struct GPBD_FM_2_Entry +{ + SCR_INT NumTeams; // 0 + SCR_INT NumRounds; // 1 + SCR_INT RoundsPlayed; // 2 + SCR_INT RoundScore; // 3 + SCR_INT CurrentTeam; // 4 + SCR_INT Flags; // 5 + SCR_INT TotalScore; // 6 + SCR_INT TotalKills; // 7 + SCR_INT TotalDeaths; // 8 + SCR_INT RPEarned; // 9 + SCR_INT CashEarned; // 10 + uint64_t PAD_0011[2]; // 11 + SCR_INT APEarned; // 13 + SCR_INT Duration; // 14 + alignas(8) eRaceRoundType RaceRoundType; // 15 + alignas(8) eRaceFinalRoundType RaceFinalRoundType; // 16 + SCR_INT Flags2; // 17 + SCR_INT Vote; // 18 + SCR_INT NumVotes; // 19 + SCR_INT MaxVotes; // 20 + SCR_INT PlayerTeam; // 21 + uint64_t PAD_0022[8]; // 22 (mission stuff) + SCR_INT TunableCloudCRC; // 30 + SCR_INT CatalogCloudCRC; // 31 + SCR_INT UNK_0032; // 32 + SCR_INT Flags3; // 33 + SCR_INT UNK_0034; // 34 + SCR_BOOL HasVoted; // 35 + TIMER UNK_0036; // 36 + TIMER UNK_0038; // 38 + SCR_VEC3 CoronaPosition; // 40 + GANGOPS_DATA GangopsData; // 43 + RANDOM_EVENTS_CLIENT_DATA RandomEventsClientData; // 82 +}; +static_assert(sizeof(GPBD_FM_2_Entry) == 149 * 8); + +struct GPBD_FM_2 +{ + SCR_ARRAY Entries; + + static GPBD_FM_2* Get(); +}; +static_assert(sizeof(GPBD_FM_2) == 4769 * 8); \ No newline at end of file diff --git a/src/types/script/globals/GSBD_RandomEvents.hpp b/src/types/script/globals/GSBD_RandomEvents.hpp index b9c595e..f10939b 100644 --- a/src/types/script/globals/GSBD_RandomEvents.hpp +++ b/src/types/script/globals/GSBD_RandomEvents.hpp @@ -16,7 +16,7 @@ enum class eRandomEventState CLEANUP }; -enum class eRandomEventBitset +enum class eRandomEventFlags { TRIGGERED_BY_CLIENT, FORCE_LAUNCH, @@ -36,7 +36,7 @@ struct RANDOM_EVENTS_DATA { alignas(8) eRandomEventState State; // 0 TIMER TimerState; // 1 - SCR_ARRAY, 1> Bitset; // 3 + SCR_ARRAY, 1> Flags; // 3 SCR_INT Variation; // 5 SCR_INT Subvariation; // 6 RANDOM_EVENTS_RESERVATION_DATA ReservationData; // 7 diff --git a/src/types/script/globals/Globals.cpp b/src/types/script/globals/Globals.cpp index f89d8fe..11e5dd2 100644 --- a/src/types/script/globals/Globals.cpp +++ b/src/types/script/globals/Globals.cpp @@ -1,5 +1,6 @@ #include "GlobalPlayerBD.hpp" #include "GPBD_FM.hpp" +#include "GPBD_FM_2.hpp" #include "GPBD_FM_3.hpp" #include "GPBD_Kicking.hpp" #include "GPBD_MissionName.hpp" @@ -23,6 +24,7 @@ DEFINE_GLOBAL_ACCESSOR(GlobalPlayerBD, 2657994); DEFINE_GLOBAL_ACCESSOR(GPBD_FM, 1845270); +DEFINE_GLOBAL_ACCESSOR(GPBD_FM_2, 1882925); DEFINE_GLOBAL_ACCESSOR(GPBD_FM_3, 1887694); DEFINE_GLOBAL_ACCESSOR(GPBD_Kicking, 1874328); DEFINE_GLOBAL_ACCESSOR(GPBD_MissionName, 1873815); diff --git a/src/types/script/locals/FMRandomEvents.hpp b/src/types/script/locals/FMRandomEvents.hpp index 1d4adfb..d5a0ae4 100644 --- a/src/types/script/locals/FMRandomEvents.hpp +++ b/src/types/script/locals/FMRandomEvents.hpp @@ -7,7 +7,7 @@ namespace rage class scrThread; } -enum class eRandomEventFlags +enum class eRandomEventFMFlags { RESTART = 2, DISABLE_DISTANCE_CHECK = 4 @@ -22,7 +22,7 @@ struct RANDOM_EVENTS_LOCAL_DATA SCR_INT AvailablePtr; // 5 SCR_INT InactiveTime; // 6 SCR_INT AvailableTime; // 7 - SCR_BITSET Flags; // 8 + SCR_BITSET Flags; // 8 TIMER LastTriggerAttempt; // 9 SCR_INT UNK_0011; // 11 (unused, ranges from 0 to 4) };