├── README.md ├── metadata.json └── random_events.lua /README.md: -------------------------------------------------------------------------------- 1 | # RandomEvents 2 | A Lua script for YimMenu about Random Events. 3 | 4 | ![RandomEvents](https://i.imgur.com/QKU0stG.png) 5 | ![ESP](https://i.imgur.com/UAZMGov.png) -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "creator": "ShinyWasabi", 3 | "name": "RandomEvents", 4 | "description": "A Lua script for YimMenu about Random Events." 5 | } -------------------------------------------------------------------------------- /random_events.lua: -------------------------------------------------------------------------------- 1 | local re_tab = gui.get_tab("GUI_TAB_NETWORK"):add_tab("Random Events") 2 | 3 | local RE = { 4 | CORE = { 5 | REQUEST_RE_HASH = -126218586, 6 | GSBD_RE = 1882379, 7 | GPBD_FM_2 = 1882780, 8 | FMRE_DATA = 15827 9 | }, 10 | IDS = { 11 | DRUG_VEHICLE = 0, 12 | MOVIE_PROPS = 1, 13 | GOLDEN_GUN = 2, 14 | VEHICLE_LIST = 3, 15 | SLASHER = 4, 16 | PHANTOM_CAR = 5, 17 | SIGHTSEEING = 6, 18 | SMUGGLER_TRAIL = 7, 19 | CERBERUS = 8, 20 | SMUGGLER_PLANE = 9, 21 | CRIME_SCENE = 10, 22 | METAL_DETECTOR = 11, 23 | CONVOY = 12, 24 | ROBBERY = 13, 25 | XMAS_MUGGER = 14, 26 | BANK_SHOOTOUT = 15, 27 | ARMOURED_TRUCK = 16, 28 | POSSESSED_ANIMALS = 17, 29 | GHOSTHUNT = 18, 30 | XMAS_TRUCK = 19, 31 | COMMUNITY_OUTREACH = 20 32 | }, 33 | STATES = { 34 | INACTIVE = 0, 35 | AVAILABLE = 1, 36 | ACTIVE = 2, 37 | CLEANUP = 3 38 | }, 39 | LOCALS = { 40 | END_REASONS = { 41 | [0] = { 1820, 115 }, 42 | [1] = { 1947, 137 }, 43 | [2] = { 1833, 93 }, 44 | [3] = { 1621, 83 }, 45 | [4] = { 1650, 83 }, 46 | [5] = { 1630, 83 }, 47 | [6] = { 1875, 84 }, 48 | [7] = { 2122, 130 }, 49 | [8] = { 1642, 91 }, 50 | [9] = { 1894, 178 }, 51 | [10] = { 2023, 151 }, 52 | [11] = { 1881, 93 }, 53 | [12] = { 2812, 437 }, 54 | [13] = { 1809, 87 }, 55 | [14] = { 1673, 83 }, 56 | [15] = { 2284, 221 }, 57 | [16] = { 1975, 113 }, 58 | [17] = { 1646, 83 }, 59 | [18] = { 1606, 88 }, 60 | [19] = { 1520, 91 }, 61 | [20] = { 2066, 191 } 62 | }, 63 | COORD_COOLDOWNS = { 64 | [0] = { 1820, 146 }, 65 | [1] = { 1947, 168 }, 66 | [2] = { 1833, 128 }, 67 | [3] = { 1621, 114 }, 68 | [4] = { 1650, 114 }, 69 | [5] = { 1630, 114 }, 70 | [6] = { 1875, 115 }, 71 | [7] = { 2122, 166 }, 72 | [8] = { 1642, 122 }, 73 | [9] = { 1894, 209 }, 74 | [10] = { 2023, 186 }, 75 | [11] = { 1881, 129 }, 76 | [12] = { 2812, 470 }, 77 | [13] = { 1809, 124 }, 78 | [14] = { 1673, 114 }, 79 | [15] = { 2284, 256 }, 80 | [16] = { 1975, 148 }, 81 | [17] = { 1646, 114 }, 82 | [18] = { 1606, 119 }, 83 | [19] = { 1520, 122 }, 84 | [20] = { 2066, 229 } 85 | } 86 | }, 87 | FUNC_POINTERS = { 88 | FM_RETURN_TRUE = 0x76BC, 89 | FM_RETURN_FALSE = 0x7E4A0, 90 | SHOULD_TRIGGER = { 91 | -- *_SHOULD_TRIGGER functions determine if the event can progress to the Active state from the Available state. 92 | -- There are also the *_SHOULD_BE_AVAILABLE functions, which determine if the event can progress to the Available state from the Inactive state. The events that have this function don't have any special conditions to be triggered in their SHOULD_TRIGGER functions, they just return true. 93 | [0] = 0x2C9317, 94 | [1] = 0x2C922B, 95 | [2] = 0x2C91FE, 96 | [3] = 0x2C91C0, 97 | [4] = 0x2C85A7, 98 | [5] = 0x2C8247, -- PHANTOM_CAR_SHOULD_BE_AVAILABLE 99 | [6] = 0x2C8165, 100 | [7] = 0x2C8122, 101 | [8] = 0x2C7E69, 102 | [9] = 0x2C7E11, 103 | [10] = 0x2C7DC6, 104 | [11] = 0x2C7D7B, 105 | [12] = 0x2C7D2D, 106 | [13] = 0x2C7CDF, 107 | [14] = 0x2C798E, -- XMAS_MUGGER_SHOULD_BE_AVAILABLE 108 | [15] = 0x2C790E, 109 | [16] = 0x2C78B4, 110 | [17] = 0x2C7732, 111 | [18] = 0x2C73C6, 112 | [19] = 0x2C7390, -- XMAS_TRUCK_SHOULD_BE_AVAILABLE 113 | [20] = 0x2C72E8 114 | }, 115 | REAL_COORDS = { 116 | [0] = 0x22CB46, -- The Slashers 117 | [1] = 0x22D489, -- Phantom Car 118 | [2] = 0x23150D, -- Cerberus Surprise 119 | [3] = 0x22D93C, -- The Gooch 120 | [4] = 0x22DA1D, -- Possessed Animals 121 | [5] = 0xCE8B -- Ghosts Exposed 122 | } 123 | }, 124 | SCRIPTS = { 125 | [0] = "fm_content_drug_vehicle", 126 | [1] = "fm_content_movie_props", 127 | [2] = "fm_content_golden_gun", 128 | [3] = "fm_content_vehicle_list", 129 | [4] = "fm_content_slasher", 130 | [5] = "fm_content_phantom_car", 131 | [6] = "fm_content_sightseeing", 132 | [7] = "fm_content_smuggler_trail", 133 | [8] = "fm_content_cerberus", 134 | [9] = "fm_content_smuggler_plane", 135 | [10] = "fm_content_crime_scene", 136 | [11] = "fm_content_metal_detector", 137 | [12] = "fm_content_convoy", 138 | [13] = "fm_content_robbery", 139 | [14] = "fm_content_xmas_mugger", 140 | [15] = "fm_content_bank_shootout", 141 | [16] = "fm_content_armoured_truck", 142 | [17] = "fm_content_possessed_animals", 143 | [18] = "fm_content_ghosthunt", 144 | [19] = "fm_content_xmas_truck", 145 | [20] = "fm_content_community_outreach" 146 | }, 147 | COOLDOWNS = { 148 | [0] = "SUM22_RE_DRUG_VEHICLE_INACTIVE_TIME", 149 | [1] = "SUM22_RE_MOVIE_PROPS_INACTIVE_TIME", 150 | [2] = "SUM22_RE_GOLDEN_GUN_INACTIVE_TIME", 151 | [3] = "SUM22_RE_VEHICLE_LIST_INACTIVE_TIME", 152 | [4] = "STANDARDCONTROLLERVOLUME_COOLDOWN", 153 | [5] = "STANDARDTARGETTINGTIME_COOLDOWN", 154 | [6] = "SSP2_COOLDOWN", 155 | [7] = "SUM22_RE_SMUGGLER_TRAIL_INACTIVE_TIME", 156 | [8] = "NC_SOURCE_TRUCK_COOLDOWN", 157 | [9] = "SUM22_RE_SMUGGLER_PLANE_INACTIVE_TIME", 158 | [10] = "SUM22_RE_CRIME_SCENE_INACTIVE_TIME", 159 | [11] = "SUM22_RE_METAL_DETECTOR_INACTIVE_TIME", 160 | [12] = "XM22_RE_GANG_CONVOY_INACTIVE_TIME", 161 | [13] = "XM22_RE_ROBBERY_INACTIVE_TIME", 162 | [14] = "STANDARD_KEYBIND_COOLDOWN", 163 | [15] = "XM22_RE_BANK_SHOOTOUT_INACTIVE_TIME", 164 | [16] = 295362, -- Global_262145.f_33217 - Armored Truck (doesn't have a tunable) 165 | [17] = "STANDARDCONTROLLERVOLUME_COOLDOWN", 166 | [18] = "SUM23_RE_GHOSTHUNT_INACTIVE_TIME", 167 | [19] = "XMAS_TRUCK_INACTIVE_TIME", 168 | [20] = "RE_COMMUNITY_OUTREACH_INACTIVE_TIME" 169 | }, 170 | AVAILABILITIES = { 171 | [0] = "SUM22_RE_DRUG_VEHICLE_AVAILABLE_TIME", 172 | [1] = "SUM22_RE_MOVIE_PROPS_AVAILABLE_TIME", 173 | [2] = "SUM22_RE_GOLDEN_GUN_AVAILABLE_TIME", 174 | [3] = "SUM22_RE_VEHICLE_LIST_AVAILABLE_TIME", 175 | [4] = "STANDARDCONTROLLERVOLUME_AVAILABILITY", 176 | [5] = "STANDARDTARGETTINGTIME_AVAILABILITY", 177 | [6] = "SSP2_AVAILABILITY", 178 | [7] = "SUM22_RE_SMUGGLER_TRAIL_AVAILABLE_TIME", 179 | [8] = "NC_SOURCE_TRUCK_AVAILABILITY", 180 | [9] = "SUM22_RE_SMUGGLER_PLANE_AVAILABLE_TIME", 181 | [10] = "SUM22_RE_CRIME_SCENE_AVAILABLE_TIME", 182 | [11] = "SUM22_RE_METAL_DETECTOR_AVAILABLE_TIME", 183 | [12] = "XM22_RE_GANG_CONVOY_AVAILABLE_TIME", 184 | [13] = "XM22_RE_ROBBERY_AVAILABLE_TIME", 185 | [14] = "STANDARD_KEYBIND_AVAILABILITY", 186 | [15] = "XM22_RE_BANK_SHOOTOUT_AVAILABLE_TIME", 187 | [16] = 295363, -- Global_262145.f_33218 - Armored Truck (doesn't have a tunable) 188 | [17] = "STANDARDCONTROLLERVOLUME_AVAILABILITY", 189 | [18] = "SUM23_RE_GHOSTHUNT_AVAILABLE_TIME", 190 | [19] = "XMAS_TRUCK_AVAILABLE_TIME", 191 | [20] = "RE_COMMUNITY_OUTREACH_AVAILABLE_TIME" 192 | }, 193 | NAMES = { 194 | [0] = "Drug Vehicle", 195 | [1] = "Movie Props", 196 | [2] = "Sleeping Guard", 197 | [3] = "Exotic Exports", 198 | [4] = "The Slashers", 199 | [5] = "Phantom Car", 200 | [6] = "Sightseeing", 201 | [7] = "Smuggler Trail", 202 | [8] = "Cerberus Surprise", 203 | [9] = "Smuggler Plane", 204 | [10] = "Crime Scene", 205 | [11] = "Metal Detector", 206 | [12] = "Finders Keepers", 207 | [13] = "Shop Robbery", 208 | [14] = "The Gooch", 209 | [15] = "Weazel Plaza Shootout", 210 | [16] = "Armored Truck", 211 | [17] = "Possessed Animals", 212 | [18] = "Ghosts Exposed", 213 | [19] = "Happy Holidays Hauler", 214 | [20] = "Community Outreach" 215 | } 216 | } 217 | 218 | local selected_event = 0 219 | local selected_target = 0 220 | local selected_loc = 0 221 | local set_cooldown = 1800000 222 | local set_availability = 900000 223 | local enable_esp = true 224 | local enable_line = true 225 | local enable_sphere = true 226 | local enable_notifications = true 227 | local force_freemode_host = true 228 | local apply_in_minutes = false 229 | local set_target_player = false 230 | local enable_tunables = false 231 | local bypass_requirements = false 232 | local disable_all_events = false 233 | local remove_activated_events_limit = false 234 | local logging = false 235 | local notified_available = {} 236 | local notified_active = {} 237 | local is_tunable_active = {} 238 | 239 | local event_coords = vec3:new(0, 0, 0) 240 | local event_state = RE.STATES.INACTIVE 241 | local event_trigger_range = 0.0 242 | local event_timer = 0 243 | local event_variation = 0 244 | local event_cooldown = 0 245 | local event_availability = 0 246 | local max_num_re = 0 247 | local max_activated_events = 0 248 | local num_active_events = 0 249 | local event_host_id = 0 250 | local target_player_id = 0 251 | local re_initialized = false 252 | local variations_registered = false 253 | local cooldown_time_left = "00:00:00" 254 | local availability_time_left = "00:00:00" 255 | local event_host_name = "**Invalid**" 256 | local max_variations = {} 257 | local target_players = {} 258 | 259 | local function CLAMP(value, min, max) 260 | return math.min(math.max(value, min), max) 261 | end 262 | 263 | local function COMBO_CLEANUP() 264 | selected_loc = 0 265 | if selected_event == RE.IDS.ARMOURED_TRUCK then 266 | set_cooldown = globals.get_int(RE.COOLDOWNS[selected_event]) 267 | set_availability = globals.get_int(RE.AVAILABILITIES[selected_event]) 268 | else 269 | set_cooldown = tunables.get_int(RE.COOLDOWNS[selected_event]) 270 | set_availability = tunables.get_int(RE.AVAILABILITIES[selected_event]) 271 | end 272 | end 273 | 274 | local function HELP_MARKER(text) 275 | ImGui.SameLine() 276 | ImGui.TextDisabled("(?)") 277 | if ImGui.IsItemHovered() then 278 | ImGui.BeginTooltip() 279 | ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35) 280 | ImGui.TextUnformatted(text) 281 | ImGui.PopTextWrapPos() 282 | ImGui.EndTooltip() 283 | end 284 | end 285 | 286 | local function SHOULD_DISABLE_ESP() 287 | return script.is_active("appinternet") or 288 | script.is_active("appcamera") or 289 | HUD.IS_PAUSE_MENU_ACTIVE() or 290 | NETWORK.NETWORK_IS_IN_MP_CUTSCENE() or 291 | not CAM.IS_GAMEPLAY_CAM_RENDERING() or 292 | HUD.IS_HUD_COMPONENT_ACTIVE(16) or -- HUD_RADIO_STATIONS 293 | HUD.IS_HUD_COMPONENT_ACTIVE(19) or -- HUD_WEAPON_WHEEL 294 | globals.get_int(23831 + 4) == 1 -- Is selector UI rendering 295 | end 296 | 297 | local function GET_FMMC_TYPE_OF_EVENT(event) 298 | return locals.get_int("freemode", RE.CORE.FMRE_DATA + 253 + 1 + (event + 1)) 299 | end 300 | 301 | local function REGISTER_MAX_VARIATIONS() 302 | if variations_registered then 303 | return 304 | end 305 | 306 | if script.is_active("freemode") and re_initialized then 307 | for event = 0, max_num_re do 308 | local fmmc_type = GET_FMMC_TYPE_OF_EVENT(event) 309 | local max_variation = scr_function.call_script_function("freemode", "GMFV", "2D 02 04 00 00 38 00 55", "int", { 310 | { "int", fmmc_type }, 311 | { "int", 0 } 312 | }) 313 | 314 | max_variations[event] = max_variation - 1 315 | end 316 | 317 | variations_registered = true 318 | end 319 | end 320 | 321 | -- This script event enables the first bit of FMRE Client & Server Bitset, which makes the script ignore all the SHOULD_TRIGGER/SHOULD_BE_AVAILABLE conditions. 322 | local function REQUEST_RANDOM_EVENT(event, variation) 323 | local fmmc_type = GET_FMMC_TYPE_OF_EVENT(event) 324 | local args = { RE.CORE.REQUEST_RE_HASH, 0, -1, fmmc_type, 0, variation, 1 } -- The last argument makes the script set the client requested bit of all the other players in the session. 325 | 326 | network.trigger_script_event(-1, args) 327 | end 328 | 329 | local function HOOK_SHOULD_TRIGGER_FUNCTIONS(value) 330 | for event = 0, max_num_re do 331 | if event == RE.IDS.PHANTOM_CAR or event == RE.IDS.XMAS_MUGGER or event == RE.IDS.XMAS_TRUCK then 332 | locals.set_int("freemode", RE.CORE.FMRE_DATA + (1 + (event * 12)) + 5, value) 333 | else 334 | locals.set_int("freemode", RE.CORE.FMRE_DATA + (1 + (event * 12)) + 1 + 1, value) 335 | end 336 | end 337 | end 338 | 339 | local function RESTORE_SHOULD_TRIGGER_FUNCTIONS() 340 | for event = 0, max_num_re do 341 | if event == RE.IDS.PHANTOM_CAR or event == RE.IDS.XMAS_MUGGER or event == RE.IDS.XMAS_TRUCK then 342 | locals.set_int("freemode", RE.CORE.FMRE_DATA + (1 + (event * 12)) + 5, RE.FUNC_POINTERS.SHOULD_TRIGGER[event]) 343 | else 344 | locals.set_int("freemode", RE.CORE.FMRE_DATA + (1 + (event * 12)) + 1 + 1, RE.FUNC_POINTERS.SHOULD_TRIGGER[event]) 345 | end 346 | end 347 | end 348 | 349 | -- The original functions return a null vector, preventing the script event from updating the initial trigger point's value. To resolve this, I replace the local pointers with the addresses of functions that actually return the event's coordinates. 350 | local function PATCH_EVENT_COORDS() 351 | -- The game crashes on session change if event set the values in a loop when script is not active for some reason. 352 | if script.is_active("fm_content_slasher") then 353 | locals.set_int("fm_content_slasher", 426 + 108, RE.FUNC_POINTERS.REAL_COORDS[0]) 354 | end 355 | if script.is_active("fm_content_phantom_car") then 356 | locals.set_int("fm_content_phantom_car", 409 + 108, RE.FUNC_POINTERS.REAL_COORDS[1]) 357 | end 358 | if script.is_active("fm_content_cerberus") then 359 | locals.set_int("fm_content_cerberus", 424 + 108, RE.FUNC_POINTERS.REAL_COORDS[2]) 360 | end 361 | if script.is_active("fm_content_xmas_mugger") then 362 | locals.set_int("fm_content_xmas_mugger", 438 + 108, RE.FUNC_POINTERS.REAL_COORDS[3]) 363 | end 364 | if script.is_active("fm_content_possessed_animals") then 365 | locals.set_int("fm_content_possessed_animals", 422 + 108, RE.FUNC_POINTERS.REAL_COORDS[4]) 366 | end 367 | if script.is_active("fm_content_ghosthunt") then 368 | locals.set_int("fm_content_ghosthunt", 442 + 108, RE.FUNC_POINTERS.REAL_COORDS[5]) 369 | end 370 | end 371 | 372 | local function RESET_UPDATE_EVENT_COORDS_COOLDOWN() 373 | for event = 0, max_num_re do 374 | if script.is_active(RE.SCRIPTS[event]) then 375 | local base_address = RE.LOCALS.COORD_COOLDOWNS[event][1] 376 | local offset = RE.LOCALS.COORD_COOLDOWNS[event][2] 377 | 378 | locals.set_int(RE.SCRIPTS[event], base_address + offset + 1, 1) -- There will be a race condition here since the script will set the value back to 0 just before sending the script event, but this shouldn't be a big problem. 379 | end 380 | end 381 | end 382 | 383 | local function SET_MAX_NUMBER_OF_ACTIVATED_EVENTS_COUNT(count) 384 | tunables.set_int("FMREMAXACTIVATEDEVENTS", count) 385 | end 386 | 387 | local function SET_EVENT_STATE(event, state) 388 | globals.set_int(RE.CORE.GSBD_RE + 1 + (1 + (event * 15)), state) 389 | end 390 | 391 | local function SET_EVENT_COOLDOWN(event, value) 392 | locals.set_int("freemode", RE.CORE.FMRE_DATA + (1 + (event * 12)) + 6, value) 393 | end 394 | 395 | local function SET_EVENT_AVAILABILITY(event, value) 396 | locals.set_int("freemode", RE.CORE.FMRE_DATA + (1 + (event * 12)) + 7, value) 397 | end 398 | 399 | local function SET_EVENT_TARGET(target_id) 400 | globals.set_int(RE.CORE.GSBD_RE + 319, target_id) -- Phantom Car Target 401 | globals.set_int(RE.CORE.GSBD_RE + 319 + 1, target_id) -- The Gooch Target 402 | end 403 | 404 | local function SET_EVENT_END_REASON(event, reason) 405 | local base_address = RE.LOCALS.END_REASONS[event][1] 406 | local offset = RE.LOCALS.END_REASONS[event][2] 407 | 408 | locals.set_int(RE.SCRIPTS[event], base_address + offset, reason) 409 | end 410 | 411 | local function SET_SPECIAL_EVENT_TUNABLES(toggle) 412 | tunables.set_int("STANDARDCONTROLLERVOLUME", toggle and 1 or -1) 413 | tunables.set_int("STANDARDTARGETTINGTIME", toggle and 1 or -1) 414 | tunables.set_int("SSP2POSIX", toggle and NETWORK.GET_CLOUD_TIME_AS_INT() or -1) -- Make the posix match with the seed (it will get stuck on day 1, though). 415 | tunables.set_int("NC_SOURCE_TRUCK_HEAD_COUNT", toggle and 3 or 1) 416 | tunables.set_int("STANDARD_KEYBIND_SELECTION", toggle and 1 or -1) 417 | tunables.set_bool("ENABLE_MAZEBANKSHOOTOUT_DLC22022", toggle) 418 | tunables.set_bool("ENABLE_HALLOWEEN_POSSESSED_ANIMAL", toggle) 419 | tunables.set_bool("ENABLE_HALLOWEEN_GHOSTHUNT", toggle) 420 | tunables.set_bool(-818123201, toggle) -- This will invalidate ENABLE_HALLOWEEN_GHOSTHUNT. 421 | tunables.set_bool(2093114948, toggle) 422 | end 423 | 424 | local function CHECK_EVENT_TUNABLES() 425 | is_tunable_active[RE.IDS.DRUG_VEHICLE] = tunables.get_bool("SUM_RANDOM_EVENT_DRUG_VEHICLE_ENABLE") 426 | is_tunable_active[RE.IDS.MOVIE_PROPS] = tunables.get_bool("COLLECTABLES_MOVIE_PROPS") 427 | is_tunable_active[RE.IDS.GOLDEN_GUN] = true -- Sleeping Guard (no tunable) 428 | is_tunable_active[RE.IDS.VEHICLE_LIST] = true -- Exotic Exports (no tunable) 429 | is_tunable_active[RE.IDS.SLASHER] = tunables.get_int("STANDARDCONTROLLERVOLUME") ~= -1 430 | is_tunable_active[RE.IDS.PHANTOM_CAR] = tunables.get_int("STANDARDTARGETTINGTIME") ~= -1 431 | is_tunable_active[RE.IDS.SIGHTSEEING] = tunables.get_int("SSP2POSIX") > 0 432 | is_tunable_active[RE.IDS.SMUGGLER_TRAIL] = tunables.get_bool("ENABLE_SU22_SMUGGLER_TRAIL") 433 | is_tunable_active[RE.IDS.CERBERUS] = tunables.get_int("NC_SOURCE_TRUCK_HEAD_COUNT") == 3 434 | is_tunable_active[RE.IDS.SMUGGLER_PLANE] = tunables.get_bool("ENABLE_SU22_SMUGGLER_PLANE") 435 | is_tunable_active[RE.IDS.CRIME_SCENE] = tunables.get_bool("ENABLE_SUM22_CRIME_SCENES") 436 | is_tunable_active[RE.IDS.METAL_DETECTOR] = tunables.get_bool("COLLECTABLES_BURIED_STASH") 437 | is_tunable_active[RE.IDS.CONVOY] = tunables.get_bool("ENABLE_GANGCONVOY_DLC22022") 438 | is_tunable_active[RE.IDS.ROBBERY] = tunables.get_bool("XM22_ROBBERY_ENABLE") 439 | is_tunable_active[RE.IDS.XMAS_MUGGER] = tunables.get_int("STANDARD_KEYBIND_SELECTION") ~= -1 440 | is_tunable_active[RE.IDS.BANK_SHOOTOUT] = tunables.get_bool("ENABLE_MAZEBANKSHOOTOUT_DLC22022") 441 | is_tunable_active[RE.IDS.ARMOURED_TRUCK] = tunables.get_bool("ENABLE_RANDOM_EVENT_ARMORED_TRUCK") 442 | is_tunable_active[RE.IDS.POSSESSED_ANIMALS] = tunables.get_bool("ENABLE_HALLOWEEN_POSSESSED_ANIMAL") 443 | is_tunable_active[RE.IDS.GHOSTHUNT] = tunables.get_bool("ENABLE_HALLOWEEN_GHOSTHUNT") or tunables.get_bool(-818123201) 444 | is_tunable_active[RE.IDS.XMAS_TRUCK] = tunables.get_bool(2093114948) 445 | is_tunable_active[RE.IDS.COMMUNITY_OUTREACH] = tunables.get_bool("ENABLE_RE_COMMUNITY_OUTREACH") 446 | end 447 | 448 | local function ARE_EVENTS_INITIALIZED() 449 | return globals.get_int(RE.CORE.GPBD_FM_2 + (1 + (self.get_id() * 149)) + 82) == 1 450 | end 451 | 452 | local function GET_MAX_NUMBER_OF_EVENTS() 453 | return locals.get_int("freemode", RE.CORE.FMRE_DATA + 253) - 1 454 | end 455 | 456 | local function GET_MAX_NUMBER_OF_ACTIVATED_EVENTS_COUNT() 457 | return tunables.get_int("FMREMAXACTIVATEDEVENTS") 458 | end 459 | 460 | local function GET_PLAYER_STATE(event, player_id) 461 | return globals.get_int(RE.CORE.GPBD_FM_2 + (1 + (player_id * 149)) + 82 + 1 + (1 + (event * 3))) 462 | end 463 | 464 | local function GET_EVENT_STATE(event) 465 | return globals.get_int(RE.CORE.GSBD_RE + 1 + (1 + (event * 15))) 466 | end 467 | 468 | local function GET_EVENT_VARIATION(event) 469 | return globals.get_int(RE.CORE.GSBD_RE + 1 + (1 + (event * 15)) + 6) 470 | end 471 | 472 | local function GET_EVENT_COORDS(event) 473 | return globals.get_vec3(RE.CORE.GSBD_RE + 1 + (1 + (event * 15)) + 10) 474 | end 475 | 476 | local function GET_EVENT_TRIGGER_RANGE(event) 477 | return globals.get_float(RE.CORE.GSBD_RE + 1 + (1 + (event * 15)) + 13) 478 | end 479 | 480 | local function GET_EVENT_TIMER(event) 481 | return globals.get_int(RE.CORE.GSBD_RE + 1 + (1 + (event * 15)) + 1) 482 | end 483 | 484 | local function GET_EVENT_COOLDOWN(event) 485 | return locals.get_int("freemode", RE.CORE.FMRE_DATA + (1 + (event * 12)) + 6) 486 | end 487 | 488 | local function GET_EVENT_AVAILABILITY(event) 489 | return locals.get_int("freemode", RE.CORE.FMRE_DATA + (1 + (event * 12)) + 7) 490 | end 491 | 492 | local function GET_NUM_LOCALLY_ACTIVE_EVENTS() 493 | local num_events = 0 494 | 495 | for event = 0, max_num_re do 496 | local local_state = GET_PLAYER_STATE(event, self.get_id()) 497 | 498 | if local_state ~= RE.STATES.INACTIVE then 499 | num_events = num_events + 1 500 | end 501 | end 502 | 503 | return num_events 504 | end 505 | 506 | local function GET_TRIGGERER_INDEX(event) 507 | for player = 0, 31 do 508 | local player_state = GET_PLAYER_STATE(event, player) 509 | 510 | if player_state == RE.STATES.ACTIVE then 511 | return player 512 | end 513 | end 514 | 515 | return -1 516 | end 517 | 518 | local function GET_TARGET_PLAYERS() 519 | local player_table = {} 520 | 521 | for player = 0, 31 do 522 | local player_name = PLAYER.GET_PLAYER_NAME(player) 523 | 524 | if player_name ~= "**Invalid**" then 525 | table.insert(player_table, {id = player, name = player_name}) 526 | end 527 | end 528 | 529 | return player_table 530 | end 531 | 532 | local function GET_EVENT_TIME_LEFT(event_time, timer) 533 | local time_passed = MISC.ABSI(NETWORK.GET_TIME_DIFFERENCE(NETWORK.GET_NETWORK_TIME(), timer)) 534 | local diff = NETWORK.GET_TIME_DIFFERENCE(event_time, time_passed) 535 | local total_seconds = math.floor(diff / 1000) 536 | local hours = math.floor(total_seconds / 3600) 537 | local minutes = math.floor((total_seconds % 3600) / 60) 538 | local seconds = total_seconds % 60 539 | local formatted = "" 540 | 541 | if hours < 1 then 542 | formatted = string.format("%02d:%02d", minutes, seconds) 543 | else 544 | formatted = string.format("%02d:%02d:%02d", hours, minutes, seconds) 545 | end 546 | 547 | return formatted 548 | end 549 | 550 | local function LOOPED_UPDATE_RE_DATA() 551 | re_initialized = ARE_EVENTS_INITIALIZED() 552 | max_num_re = GET_MAX_NUMBER_OF_EVENTS() 553 | max_activated_events = GET_MAX_NUMBER_OF_ACTIVATED_EVENTS_COUNT() 554 | num_active_events = GET_NUM_LOCALLY_ACTIVE_EVENTS() 555 | target_players = GET_TARGET_PLAYERS() 556 | event_state = GET_EVENT_STATE(selected_event) 557 | event_coords = GET_EVENT_COORDS(selected_event) 558 | event_variation = GET_EVENT_VARIATION(selected_event) 559 | event_trigger_range = GET_EVENT_TRIGGER_RANGE(selected_event) 560 | event_timer = GET_EVENT_TIMER(selected_event) 561 | event_cooldown = GET_EVENT_COOLDOWN(selected_event) 562 | event_availability = GET_EVENT_AVAILABILITY(selected_event) 563 | cooldown_time_left = GET_EVENT_TIME_LEFT(event_cooldown, event_timer) 564 | availability_time_left = GET_EVENT_TIME_LEFT(event_availability, event_timer) 565 | event_host_id = NETWORK.NETWORK_GET_HOST_OF_SCRIPT(RE.SCRIPTS[selected_event], selected_event, 0) 566 | event_host_name = PLAYER.GET_PLAYER_NAME(event_host_id) 567 | end 568 | 569 | local function LOOPED_RENDER_ESP() 570 | if SHOULD_DISABLE_ESP() then 571 | return 572 | end 573 | 574 | for event = 0, max_num_re do 575 | local state = GET_EVENT_STATE(event) 576 | local coords = GET_EVENT_COORDS(event) 577 | local timer = GET_EVENT_TIMER(event) 578 | local availability = GET_EVENT_AVAILABILITY(event) 579 | local time_left = GET_EVENT_TIME_LEFT(availability, timer) 580 | local trigger_range = GET_EVENT_TRIGGER_RANGE(event) 581 | local colors = state == RE.STATES.ACTIVE and { 0, 153, 51 } or { 93, 182, 229 } 582 | 583 | if state ~= RE.STATES.INACTIVE and coords ~= vec3:new(0, 0, 0) then 584 | local distance = MISC.GET_DISTANCE_BETWEEN_COORDS(self.get_pos().x, self.get_pos().y, self.get_pos().z, coords.x, coords.y, coords.z, false) 585 | local km_or_m = (distance < 1000) and "m" or "km" 586 | local formatted_distance = (distance < 1000) and distance or (distance / 1000.0) 587 | local text = string.format("%s (%.2f%s) %s", (state == RE.STATES.ACTIVE and "~HUD_COLOUR_GREEN~" or "") .. RE.NAMES[event] .. "~s~", formatted_distance, km_or_m, (state == RE.STATES.AVAILABLE and "~n~" .. time_left or "")) 588 | local screen_x = 0.0 589 | local screen_y = 0.0 590 | 591 | _, screen_x, screen_y = GRAPHICS.GET_SCREEN_COORD_FROM_WORLD_COORD(coords.x, coords.y, coords.z, screen_x, screen_y) 592 | 593 | if enable_line then 594 | GRAPHICS.DRAW_LINE(self.get_pos().x, self.get_pos().y, self.get_pos().z, coords.x, coords.y, coords.z, colors[1], colors[2], colors[3], 255) 595 | end 596 | 597 | HUD.BEGIN_TEXT_COMMAND_DISPLAY_TEXT("STRING") 598 | HUD.ADD_TEXT_COMPONENT_SUBSTRING_PLAYER_NAME(text) 599 | HUD.SET_TEXT_RENDER_ID(1) 600 | HUD.SET_TEXT_OUTLINE() 601 | HUD.SET_TEXT_CENTRE(true) 602 | HUD.SET_TEXT_DROP_SHADOW() 603 | HUD.SET_TEXT_SCALE(0, 0.3) 604 | HUD.SET_TEXT_FONT(4) 605 | HUD.SET_TEXT_COLOUR(255, 255, 255, 240) 606 | HUD.END_TEXT_COMMAND_DISPLAY_TEXT(screen_x, screen_y - 0.03, 0) 607 | 608 | if enable_sphere and state ~= RE.STATES.ACTIVE then 609 | GRAPHICS.DRAW_MARKER(28, coords.x, coords.y, coords.z, 0, 0, 0, 0, 180, 0, trigger_range, trigger_range, trigger_range, colors[1], colors[2], colors[3], 40, true, true, 2, false, nil, nil, false) 610 | end 611 | end 612 | end 613 | end 614 | 615 | local function LOOPED_NOTIFY_PLAYER() 616 | for event = 0, max_num_re do 617 | local state = GET_EVENT_STATE(event) 618 | 619 | if state == RE.STATES.ACTIVE then 620 | if not notified_active[event] then 621 | local triggerer_index = GET_TRIGGERER_INDEX(event) 622 | 623 | if triggerer_index ~= -1 then 624 | local triggerer_name = PLAYER.GET_PLAYER_NAME(triggerer_index) 625 | 626 | gui.show_message("Random Events", "" .. RE.NAMES[event] .. " is triggered by " .. triggerer_name .. ".") 627 | if logging then log.info("" .. RE.NAMES[event] .. " is triggered by " .. triggerer_name .. ".") end 628 | notified_active[event] = true 629 | end 630 | end 631 | end 632 | 633 | if state == RE.STATES.AVAILABLE then 634 | if not notified_available[event] then 635 | if is_tunable_active[event] then 636 | gui.show_message("Random Events", "" .. RE.NAMES[event] .. " is available.") 637 | if logging then log.info("" .. RE.NAMES[event] .. " is available.") end 638 | else 639 | gui.show_warning("Random Events", "" .. RE.NAMES[event] .. " is available.\nWarning: Tunable is not active.") 640 | if logging then log.warning("" .. RE.NAMES[event] .. " is available (tunable is not active).") end 641 | end 642 | notified_available[event] = true 643 | end 644 | end 645 | 646 | if state == RE.STATES.INACTIVE then 647 | notified_available[event] = false 648 | notified_active[event] = false 649 | end 650 | end 651 | end 652 | 653 | event.register_handler(menu_event.PlayerMgrInit, function() 654 | selected_target = 0 655 | target_player_id = 0 656 | end) 657 | 658 | event.register_handler(menu_event.ScriptsReloaded, function() 659 | RESTORE_SHOULD_TRIGGER_FUNCTIONS() 660 | end) 661 | 662 | event.register_handler(menu_event.MenuUnloaded, function() 663 | RESTORE_SHOULD_TRIGGER_FUNCTIONS() 664 | end) 665 | 666 | script.register_looped("Random Events", function() 667 | LOOPED_UPDATE_RE_DATA() 668 | REGISTER_MAX_VARIATIONS() 669 | 670 | if re_initialized then 671 | if enable_esp then 672 | LOOPED_RENDER_ESP() 673 | end 674 | 675 | if enable_tunables then 676 | SET_SPECIAL_EVENT_TUNABLES(true) 677 | end 678 | 679 | if bypass_requirements then 680 | HOOK_SHOULD_TRIGGER_FUNCTIONS(RE.FUNC_POINTERS.FM_RETURN_TRUE) 681 | end 682 | 683 | if disable_all_events then 684 | HOOK_SHOULD_TRIGGER_FUNCTIONS(RE.FUNC_POINTERS.FM_RETURN_FALSE) 685 | end 686 | 687 | if remove_activated_events_limit then 688 | SET_MAX_NUMBER_OF_ACTIVATED_EVENTS_COUNT(max_num_re + 1) 689 | end 690 | 691 | if set_target_player then 692 | SET_EVENT_TARGET(target_player_id) 693 | end 694 | 695 | if force_freemode_host and NETWORK.NETWORK_GET_HOST_OF_SCRIPT("freemode", -1, 0) ~= self.get_id() then 696 | network.force_script_host("freemode") 697 | end 698 | 699 | if enable_notifications then 700 | CHECK_EVENT_TUNABLES() 701 | LOOPED_NOTIFY_PLAYER() 702 | end 703 | 704 | PATCH_EVENT_COORDS() 705 | RESET_UPDATE_EVENT_COORDS_COOLDOWN() 706 | end 707 | end) 708 | 709 | re_tab:add_imgui(function() 710 | if not re_initialized then 711 | ImGui.Text("Random Events are not initialized.") 712 | return 713 | end 714 | 715 | if ImGui.BeginCombo("Select Event", RE.NAMES[selected_event]) then 716 | for event = 0, #RE.NAMES do 717 | local is_selected = (event == selected_event) 718 | local state = GET_EVENT_STATE(event) 719 | 720 | if state == RE.STATES.INACTIVE then 721 | ImGui.PushStyleColor(ImGuiCol.Text, 1, 0, 0, 1) 722 | elseif state == RE.STATES.AVAILABLE then 723 | ImGui.PushStyleColor(ImGuiCol.Text, 1, 1, 1, 1) 724 | elseif state == RE.STATES.ACTIVE then 725 | ImGui.PushStyleColor(ImGuiCol.Text, 0, 1, 0, 1) 726 | end 727 | 728 | if ImGui.Selectable(RE.NAMES[event], is_selected) then 729 | selected_event = event 730 | COMBO_CLEANUP() 731 | end 732 | 733 | if is_selected then 734 | ImGui.SetItemDefaultFocus() 735 | end 736 | 737 | ImGui.PopStyleColor() 738 | end 739 | ImGui.EndCombo() 740 | end 741 | 742 | selected_loc, on_modified = ImGui.InputInt("Select Location (0-" .. max_variations[selected_event] .. ")", selected_loc) 743 | 744 | if on_modified then 745 | selected_loc = CLAMP(selected_loc, 0, max_variations[selected_event]) 746 | end 747 | 748 | if num_active_events >= max_activated_events then 749 | ImGui.TextColored(1, 0, 0, 1, "Active Events: " .. num_active_events .. "/" .. max_activated_events) 750 | else 751 | ImGui.Text("Active Events: " .. num_active_events .. "/" .. max_activated_events) 752 | end 753 | HELP_MARKER("Shows the current number of active events (locally) out of the maximum allowed.") 754 | 755 | if ImGui.Button("Launch Event") then 756 | script.run_in_fiber(function(script) 757 | if event_state ~= RE.STATES.ACTIVE then 758 | REQUEST_RANDOM_EVENT(selected_event, selected_loc) 759 | script:sleep(500) 760 | if event_state == RE.STATES.INACTIVE and event_variation ~= selected_loc then 761 | gui.show_error("Random Events", "Failed to launch event. Are you freemode host?") 762 | end 763 | else 764 | gui.show_error("Random Events", "Event is already active.") 765 | end 766 | end) 767 | end 768 | 769 | ImGui.SameLine() 770 | 771 | if ImGui.Button("Kill Event") then 772 | if event_state == RE.STATES.AVAILABLE then 773 | SET_EVENT_STATE(selected_event, RE.STATES.CLEANUP) 774 | elseif event_state == RE.STATES.ACTIVE then 775 | if event_host_id == self.get_id() then 776 | SET_EVENT_END_REASON(selected_event, 3) -- 3 means the fm_content_* script mission is completed and can be gracefully terminated. 777 | else 778 | gui.show_error("Random Events", "Failed to kill event. You must be event host.") 779 | end 780 | else 781 | gui.show_error("Random Events", "Event is not active.") 782 | end 783 | end 784 | 785 | ImGui.SameLine() 786 | 787 | if ImGui.Button("Teleport to Event") then 788 | script.run_in_fiber(function() 789 | if event_state >= RE.STATES.AVAILABLE then 790 | if event_coords ~= vec3:new(0, 0, 0) then 791 | PED.SET_PED_COORDS_KEEP_VEHICLE(self.get_ped(), event_coords.x, event_coords.y, event_coords.z) 792 | else 793 | gui.show_error("Random Events", "Failed to teleport to event. Wait for coordinates to be updated.") 794 | end 795 | else 796 | gui.show_error("Random Events", "Event is not active.") 797 | end 798 | end) 799 | end 800 | 801 | ImGui.Separator() 802 | 803 | ImGui.Text("State: " .. 804 | (event_state == RE.STATES.INACTIVE and "Inactive - launching in " .. cooldown_time_left or 805 | event_state == RE.STATES.AVAILABLE and "Available - deactivating in " .. availability_time_left or 806 | event_state == RE.STATES.ACTIVE and "Active" or 807 | event_state == RE.STATES.CLEANUP and "Cleanup" or 808 | "N/A")) 809 | HELP_MARKER("Shows the current state of the event.\n- Inactive\n- Available\n- Active\n- Cleanup") 810 | 811 | if event_state == RE.STATES.ACTIVE then 812 | ImGui.Text("Host: " .. event_host_name) 813 | 814 | ImGui.SameLine() 815 | 816 | if event_host_id ~= self.get_id() then 817 | if ImGui.SmallButton("Take Control") then 818 | script.run_in_fiber(function() 819 | if script.is_active(RE.SCRIPTS[selected_event]) then 820 | network.force_script_host(RE.SCRIPTS[selected_event]) 821 | else 822 | gui.show_error("Random Events", "Event script is not active. Are you a participant?") 823 | end 824 | end) 825 | end 826 | else 827 | ImGui.BeginDisabled() 828 | ImGui.SmallButton("Take Control") 829 | ImGui.EndDisabled() 830 | end 831 | end 832 | 833 | ImGui.Text("Location: " .. (event_variation ~= -1 and event_variation or "N/A")) 834 | HELP_MARKER("Shows the current location of the event.") 835 | 836 | ImGui.Text("Trigger Range: " .. (event_state >= RE.STATES.AVAILABLE and math.floor(event_trigger_range) .. " meters" or "N/A")) 837 | HELP_MARKER("Shows the distance required to trigger the event when available.") 838 | 839 | ImGui.Text("Cooldown: " .. math.floor(event_cooldown / 60000) .. " minutes") 840 | HELP_MARKER("Shows the duration that the event will be in the inactive state.") 841 | 842 | ImGui.Text("Availability: " .. math.floor(event_availability / 60000) .. " minutes") 843 | HELP_MARKER("Shows the duration that the event will be in the available state.") 844 | 845 | ImGui.Separator() 846 | 847 | if ImGui.CollapsingHeader("Cooldown & Availability Editor") then 848 | set_cooldown = ImGui.InputInt("Cooldown##cooldown", set_cooldown) 849 | 850 | if ImGui.Button("Apply##apply_cooldown") then 851 | script.run_in_fiber(function(script) 852 | local value = apply_in_minutes and (set_cooldown * 60000) or set_cooldown 853 | SET_EVENT_COOLDOWN(selected_event, value) 854 | script:sleep(500) 855 | if event_cooldown ~= value then 856 | gui.show_error("Random Events", "Failed to set event cooldown. Are you freemode host?") 857 | end 858 | end) 859 | end 860 | 861 | set_availability = ImGui.InputInt("Availability##availability", set_availability) 862 | 863 | if ImGui.Button("Apply##apply_availability") then 864 | script.run_in_fiber(function(script) 865 | local value = apply_in_minutes and (set_availability * 60000) or set_availability 866 | SET_EVENT_AVAILABILITY(selected_event, value) 867 | script:sleep(500) 868 | if event_availability ~= value then 869 | gui.show_error("Random Events", "Failed to set event availability. Are you freemode host?") 870 | end 871 | end) 872 | end 873 | 874 | apply_in_minutes = ImGui.Checkbox("Apply in Minutes", apply_in_minutes) 875 | end 876 | 877 | if ImGui.CollapsingHeader("Settings") then 878 | enable_esp = ImGui.Checkbox("ESP", enable_esp) 879 | if enable_esp then 880 | ImGui.SameLine() 881 | enable_sphere = ImGui.Checkbox("Sphere", enable_sphere) 882 | ImGui.SameLine() 883 | enable_line = ImGui.Checkbox("Line", enable_line) 884 | end 885 | 886 | ImGui.Separator() 887 | 888 | set_target_player, on_tick = ImGui.Checkbox("Set Target Player", set_target_player) 889 | HELP_MARKER("Allows you to set the target of Phantom Car and Gooch.") 890 | 891 | if on_tick then 892 | if not set_target_player then 893 | SET_EVENT_TARGET(-1) 894 | end 895 | end 896 | 897 | if set_target_player then 898 | selected_target = CLAMP(selected_target, 0, #target_players - 1) 899 | 900 | if ImGui.BeginCombo("Select Target", target_players[selected_target + 1].name) then 901 | for event, player in ipairs(target_players) do 902 | local is_selected = (event - 1 == selected_target) 903 | 904 | if ImGui.Selectable(player.name .. " (ID: " .. player.id ..")", is_selected) then 905 | selected_target = event - 1 906 | target_player_id = player.id 907 | end 908 | 909 | if is_selected then 910 | ImGui.SetItemDefaultFocus() 911 | end 912 | end 913 | ImGui.EndCombo() 914 | end 915 | 916 | ImGui.Separator() 917 | end 918 | 919 | enable_tunables, on_tick = ImGui.Checkbox("Enable Tunables", enable_tunables) 920 | HELP_MARKER("Enables the tunables of special events such as Cerberus, Sightseeing, etc.") 921 | 922 | if on_tick then 923 | if not enable_tunables then 924 | SET_SPECIAL_EVENT_TUNABLES(false) 925 | end 926 | end 927 | 928 | if not disable_all_events then 929 | bypass_requirements, on_tick = ImGui.Checkbox("Bypass Requirements", bypass_requirements) 930 | 931 | if on_tick then 932 | if not bypass_requirements then 933 | RESTORE_SHOULD_TRIGGER_FUNCTIONS() 934 | end 935 | end 936 | else 937 | bypass_requirements = false 938 | ImGui.BeginDisabled() 939 | ImGui.Checkbox("Bypass Requirements", bypass_requirements) 940 | ImGui.EndDisabled() 941 | end 942 | HELP_MARKER("Bypasses all the requirements to trigger an event such as is tunable enabled, number of players, time of day, etc. Use with caution.") 943 | 944 | disable_all_events, on_tick = ImGui.Checkbox("Disable All Events", disable_all_events) 945 | HELP_MARKER("Prevents all the events from being triggered.") 946 | 947 | if on_tick then 948 | if not disable_all_events then 949 | RESTORE_SHOULD_TRIGGER_FUNCTIONS() 950 | end 951 | end 952 | 953 | remove_activated_events_limit, on_tick = ImGui.Checkbox("Remove Max Activated Events Limit", remove_activated_events_limit) 954 | HELP_MARKER("Removes the limit on the maximum number of activated events you can have. Use with caution.") 955 | 956 | if on_tick then 957 | if not remove_activated_events_limit then 958 | SET_MAX_NUMBER_OF_ACTIVATED_EVENTS_COUNT(3) 959 | end 960 | end 961 | 962 | enable_notifications = ImGui.Checkbox("Notifications", enable_notifications) 963 | HELP_MARKER("Notifies you whenever an event is available or triggered.") 964 | if enable_notifications then 965 | ImGui.SameLine() 966 | logging = ImGui.Checkbox("Also Log", logging) 967 | else 968 | logging = false 969 | end 970 | 971 | force_freemode_host = ImGui.Checkbox("Force Freemode Host", force_freemode_host) 972 | HELP_MARKER("Forces you to always become freemode host. It is required for script to work correctly.") 973 | end 974 | end) --------------------------------------------------------------------------------