Random Event Fixes & Improvements (#170)

This commit is contained in:
Arthur
2025-04-12 20:07:32 +03:00
committed by GitHub
parent 5791c1962e
commit cdf1cfef18
5 changed files with 268 additions and 35 deletions
@@ -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<int*>();
setAvailability = *ScriptGlobal(262145).At(33720).As<int*>();
}
else
{
// Phantom Car's cooldown is actually 2147483647ms if STANDARDTARGETTINGTIME is not enabled
setCooldown = *Tunables::GetTunable(randomEventCooldowns[event]).As<int*>();
setAvailability = *Tunables::GetTunable(randomEventAvailabilities[event]).As<int*>();
}
}
static void OnComboChange()
{
selectedSubvariation = 0;
numSubvariations = getNumFMMCVariations.Call<int>(FMRandomEvents->MissionData.FMMCData[selectedEvent].FMMCType, 0) - 1;
ResetEventTunables(selectedEvent);
}
std::shared_ptr<Category> BuildRandomEventsMenu()
{
auto menu = std::make_shared<Category>("Random Events");
menu->AddItem(std::make_unique<ImGuiItem>([] {
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<int>(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<int*>();
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<void>(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<GtaThread*>(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;
+120
View File
@@ -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<uint64_t, 4> PlayerIndexes; // 1
SCR_ARRAY<uint64_t, 4> PlayerRoles; // 6
SCR_ARRAY<uint64_t, 4> ArePlayersReady; // 11
SCR_ARRAY<uint64_t, 5> 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<uint64_t, 4> 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<SCR_BITSET<eRandomEventClientFlags>, 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<RANDOM_EVENTS_CLIENT_EVENT_DATA, 21> EventData; // 1
SCR_BITSET<uint64_t> 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<GPBD_FM_2_Entry, 32> Entries;
static GPBD_FM_2* Get();
};
static_assert(sizeof(GPBD_FM_2) == 4769 * 8);
@@ -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<SCR_BITSET<eRandomEventBitset>, 1> Bitset; // 3
SCR_ARRAY<SCR_BITSET<eRandomEventFlags>, 1> Flags; // 3
SCR_INT Variation; // 5
SCR_INT Subvariation; // 6
RANDOM_EVENTS_RESERVATION_DATA ReservationData; // 7
+2
View File
@@ -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);
+2 -2
View File
@@ -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<eRandomEventFlags> Flags; // 8
SCR_BITSET<eRandomEventFMFlags> Flags; // 8
TIMER LastTriggerAttempt; // 9
SCR_INT UNK_0011; // 11 (unused, ranges from 0 to 4)
};