Rigs of Rods 2026.01
Soft-body Physics Simulation
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1/*
2 This source file is part of Rigs of Rods
3 Copyright 2005-2012 Pierre-Michel Ricordel
4 Copyright 2007-2012 Thomas Fischer
5 Copyright 2013-2020 Petr Ohlidal
6
7 For more information, see http://www.rigsofrods.org/
8
9 Rigs of Rods is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License version 3, as
11 published by the Free Software Foundation.
12
13 Rigs of Rods is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Rigs of Rods. If not, see <http://www.gnu.org/licenses/>.
20*/
21
22#include "Actor.h"
23#include "Application.h"
24#include "AppContext.h"
25#include "CacheSystem.h"
26#include "CameraManager.h"
27#include "ChatSystem.h"
28#include "Collisions.h"
29#include "Console.h"
30#include "ContentManager.h"
31#include "DashBoardManager.h"
32#include "DiscordRpc.h"
33#include "ErrorUtils.h"
34#include "GameContext.h"
35#include "GfxScene.h"
36#include "GUI_DirectionArrow.h"
38#include "GUI_GameControls.h"
39#include "GUI_LoadingWindow.h"
40#include "GUI_MainSelector.h"
41#include "GUI_MessageBox.h"
46#include "GUIManager.h"
47#include "GUIUtils.h"
48#include "InputEngine.h"
49#include "Language.h"
50#include "MumbleIntegration.h"
51#include "OutGauge.h"
52#include "OverlayWrapper.h"
53#include "PlatformUtils.h"
54#include "RoRVersion.h"
55#include "ScriptEngine.h"
56#include "ServerScriptEngine.h"
57#include "Skidmark.h"
58#include "SoundScriptManager.h"
59#include "Terrain.h"
60#include "Utils.h"
61#include <Overlay/OgreOverlaySystem.h>
62#include <ctime>
63#include <iomanip>
64#include <string>
65#include <fstream>
66
67#ifdef USE_CURL
68# include <curl/curl.h>
69#endif //USE_CURL
70
71#ifdef __cplusplus
72extern "C" {
73#endif
74
75int main(int argc, char *argv[])
76{
77 using namespace RoR;
78
79#ifdef USE_CURL
80 curl_global_init(CURL_GLOBAL_ALL); // MUST init before any threads are started
81#endif
82
83#ifndef _DEBUG
84 try
85 {
86#endif
87
88 // Create cvars, set default values
89 App::GetConsole()->cVarSetupBuiltins();
90
91 // Record main thread ID for checks
92 App::GetAppContext()->SetUpThreads();
93
94 // Update cvars 'sys_process_dir', 'sys_user_dir'
95 if (!App::GetAppContext()->SetUpProgramPaths())
96 {
97 return -1; // Error already displayed
98 }
99
100 // Create OGRE default logger early
101 App::GetAppContext()->SetUpLogging();
102
103 // User directories
104 App::sys_config_dir ->setStr(PathCombine(App::sys_user_dir->getStr(), "config"));
105 App::sys_cache_dir ->setStr(PathCombine(App::sys_user_dir->getStr(), "cache"));
106 App::sys_thumbnails_dir->setStr(PathCombine(App::sys_user_dir->getStr(), "thumbnails"));
107 App::sys_savegames_dir ->setStr(PathCombine(App::sys_user_dir->getStr(), "savegames"));
108 App::sys_screenshot_dir->setStr(PathCombine(App::sys_user_dir->getStr(), "screenshots"));
109 App::sys_scripts_dir ->setStr(PathCombine(App::sys_user_dir->getStr(), "scripts"));
110 App::sys_server_scripts_dir->setStr(PathCombine(App::sys_user_dir->getStr(), "server_scripts"));
111 App::sys_projects_dir ->setStr(PathCombine(App::sys_user_dir->getStr(), "projects"));
112 App::sys_repo_attachments_dir->setStr(PathCombine(App::sys_user_dir->getStr(), "repo_attachments"));
113
114 // Load RoR.cfg - updates cvars
115 App::GetConsole()->loadConfig();
116
117 // Process command line params - updates 'cli_*' cvars
118 App::GetConsole()->processCommandLine(argc, argv);
119
120 if (App::app_state->getEnum<AppState>() == AppState::PRINT_HELP_EXIT)
121 {
122 App::GetConsole()->showCommandLineUsage();
123 return 0;
124 }
125 if (App::app_state->getEnum<AppState>() == AppState::PRINT_VERSION_EXIT)
126 {
127 App::GetConsole()->showCommandLineVersion();
128 return 0;
129 }
130
131 // Find resources dir, update cvar 'sys_resources_dir'
132 if (!App::GetAppContext()->SetUpResourcesDir())
133 {
134 return -1; // Error already displayed
135 }
136
137 // Make sure config directory exists - to save 'ogre.cfg'
138 CreateFolder(App::sys_config_dir->getStr());
139
140 // Load and start OGRE renderer, uses config directory
141 if (!App::GetAppContext()->SetUpRendering())
142 {
143 return -1; // Error already displayed
144 }
145
146 Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);
147
148 // Deploy base config files from 'skeleton.zip'
149 if (!App::GetAppContext()->SetUpConfigSkeleton())
150 {
151 return -1; // Error already displayed
152 }
153
154 Ogre::OverlaySystem* overlay_system = new Ogre::OverlaySystem(); //Overlay init
155
156 Ogre::ConfigOptionMap ropts = App::GetAppContext()->GetOgreRoot()->getRenderSystem()->getConfigOptions();
157 int resolution = Ogre::StringConverter::parseInt(Ogre::StringUtil::split(ropts["Video Mode"].currentValue, " x ")[0], 1024);
158 int fsaa = 2 * (Ogre::StringConverter::parseInt(ropts["FSAA"].currentValue, 0) / 4);
159 int res = std::pow(2, std::floor(std::log2(resolution)));
160
161 Ogre::TextureManager::getSingleton().createManual ("EnvironmentTexture",
162 Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_CUBE_MAP, res / 4, res / 4, 0,
163 Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET, 0, false, fsaa);
164 Ogre::TextureManager::getSingleton ().createManual ("Refraction",
165 Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, res / 2, res / 2, 0,
166 Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET, 0, false, fsaa);
167 Ogre::TextureManager::getSingleton ().createManual ("Reflection",
168 Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, res / 2, res / 2, 0,
169 Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET, 0, false, fsaa);
170
171 if (!App::diag_warning_texture->getBool())
172 {
173 // We overwrite the default warning texture (yellow stripes) with something unobtrusive
174 Ogre::uchar data[3] = {0};
175 Ogre::PixelBox pixels(1, 1, 1, Ogre::PF_BYTE_RGB, &data);
176 Ogre::TextureManager::getSingleton()._getWarningTexture()->getBuffer()->blitFromMemory(pixels);
177 }
178
179 App::GetContentManager()->AddResourcePack(ContentManager::ResourcePack::FLAGS);
180 App::GetContentManager()->AddResourcePack(ContentManager::ResourcePack::FONTS);
181 App::GetContentManager()->AddResourcePack(ContentManager::ResourcePack::ICONS);
182 App::GetContentManager()->AddResourcePack(ContentManager::ResourcePack::OGRE_CORE);
183 App::GetContentManager()->AddResourcePack(ContentManager::ResourcePack::WALLPAPERS);
184 App::GetContentManager()->AddResourcePack(ContentManager::ResourcePack::SCRIPTS);
185
186#ifndef NOLANG
187 App::GetLanguageEngine()->setup();
188#endif // NOLANG
189 App::GetConsole()->regBuiltinCommands(); // Call after localization had been set up
190
191 App::GetContentManager()->InitContentManager();
192
193 // Set up rendering
194 App::CreateGfxScene(); // Creates OGRE SceneManager, needs content manager
195 App::GetGfxScene()->GetSceneManager()->addRenderQueueListener(overlay_system);
196 App::CreateCameraManager(); // Creates OGRE Camera
197 App::GetGfxScene()->GetEnvMap().SetupEnvMap(); // Needs camera
198
199 App::CreateGuiManager(); // Needs scene manager
200
201 App::GetDiscordRpc()->Init();
202
203 App::GetAppContext()->SetUpInput();
204
205#ifdef USE_ANGELSCRIPT
206 CreateFolder(App::sys_scripts_dir->getStr());
207 App::CreateScriptEngine();
208 CreateFolder(App::sys_server_scripts_dir->getStr());
209 App::CreateServerScript();
210 CreateFolder(App::sys_projects_dir->getStr());
211#endif
212
213 App::GetGuiManager()->SetUpMenuWallpaper();
214
215 // Add "this is obsolete" marker file to old config location
216 App::GetAppContext()->SetUpObsoleteConfMarker();
217
218 App::CreateThreadPool();
219
220 // Load inertia config file
221 App::GetGameContext()->GetActorManager()->GetInertiaConfig().LoadDefaultInertiaModels();
222
223 // Load mod cache
224 if (App::app_force_cache_purge->getBool())
225 {
226 App::GetGameContext()->PushMessage(Message(MSG_APP_MODCACHE_PURGE_REQUESTED));
227 }
228 else if (App::cli_force_cache_update->getBool() || App::app_force_cache_update->getBool())
229 {
230 App::GetGameContext()->PushMessage(Message(MSG_APP_MODCACHE_UPDATE_REQUESTED));
231 }
232 else
233 {
234 App::GetGameContext()->PushMessage(Message(MSG_APP_MODCACHE_LOAD_REQUESTED));
235 }
236
237 // Load startup scripts (console, then RoR.cfg)
238 if (App::cli_custom_scripts->getStr() != "")
239 {
240 Ogre::StringVector script_names = Ogre::StringUtil::split(App::cli_custom_scripts->getStr(), ",");
241 for (Ogre::String const& scriptname: script_names)
242 {
243 LOG(fmt::format("Loading startup script '{}' (from command line)", scriptname));
244 // We cannot call `loadScript()` directly because modcache isn't up yet - gadgets cannot be resolved
246 req->lsr_category = ScriptCategory::CUSTOM;
247 req->lsr_filename = scriptname;
248 App::GetGameContext()->PushMessage(Message(MSG_APP_LOAD_SCRIPT_REQUESTED, req));
249 // errors are logged by OGRE & AngelScript
250 }
251 }
252 if (App::app_custom_scripts->getStr() != "")
253 {
254 Ogre::StringVector script_names = Ogre::StringUtil::split(App::app_custom_scripts->getStr(), ",");
255 for (Ogre::String const& scriptname: script_names)
256 {
257 LOG(fmt::format("Loading startup script '{}' (from config file)", scriptname));
258 // We cannot call `loadScript()` directly because modcache isn't up yet - gadgets cannot be resolved
260 req->lsr_category = ScriptCategory::CUSTOM;
261 req->lsr_filename = scriptname;
262 App::GetGameContext()->PushMessage(Message(MSG_APP_LOAD_SCRIPT_REQUESTED, req));
263 // errors are logged by OGRE & AngelScript
264 }
265 }
266
267 // Handle game state presets
268 if (App::cli_server_host->getStr() != "" && App::cli_server_port->getInt() != 0) // Multiplayer, commandline
269 {
270 App::mp_server_host->setStr(App::cli_server_host->getStr());
271 App::mp_server_port->setVal(App::cli_server_port->getInt());
272 App::GetGameContext()->PushMessage(Message(MSG_NET_CONNECT_REQUESTED));
273 }
274 else if (App::mp_join_on_startup->getBool()) // Multiplayer, conf file
275 {
276 App::GetGameContext()->PushMessage(Message(MSG_NET_CONNECT_REQUESTED));
277 }
278 else // Single player
279 {
280 if (App::cli_preset_terrain->getStr() != "") // Terrain, commandline
281 {
282 App::GetGameContext()->PushMessage(Message(MSG_SIM_LOAD_TERRN_REQUESTED, App::cli_preset_terrain->getStr()));
283 }
284 else if (App::diag_preset_terrain->getStr() != "") // Terrain, conf file
285 {
286 App::GetGameContext()->PushMessage(Message(MSG_SIM_LOAD_TERRN_REQUESTED, App::diag_preset_terrain->getStr()));
287 }
288 else // Main menu
289 {
290 if (App::cli_resume_autosave->getBool())
291 {
292 if (FileExists(PathCombine(App::sys_savegames_dir->getStr(), "autosave.sav")))
293 {
294 App::GetGameContext()->PushMessage(RoR::Message(MSG_SIM_LOAD_SAVEGAME_REQUESTED, "autosave.sav"));
295 }
296 }
297 else if (App::app_skip_main_menu->getBool())
298 {
299 // MainMenu disabled (singleplayer mode) -> go directly to map selector (traditional behavior)
300 RoR::Message m(MSG_GUI_OPEN_SELECTOR_REQUESTED);
301 m.payload = reinterpret_cast<void*>(new LoaderType(LT_Terrain));
302 App::GetGameContext()->PushMessage(m);
303 }
304 else
305 {
306 App::GetGameContext()->PushMessage(Message(MSG_GUI_OPEN_MENU_REQUESTED));
307 }
308 }
309 }
310
311 App::app_state->setVal((int)AppState::MAIN_MENU);
312 App::GetGuiManager()->MenuWallpaper->show();
313
314#ifdef USE_OPENAL
315 if (App::audio_menu_music->getBool())
316 {
317 App::GetSoundScriptManager()->createInstance("tracks/main_menu_tune", -1);
318 SOUND_START(-1, SS_TRIG_MAIN_MENU);
319 }
320#endif // USE_OPENAL
321
322 // Hack to properly init DearIMGUI integration - force rendering image
323 // Will be properly fixed under OGRE 2x
324 App::GetGuiManager()->LoadingWindow.SetProgress(100, "Hack", /*renderFrame=*/true);
325 App::GetGuiManager()->LoadingWindow.SetVisible(false);
326
327 // --------------------------------------------------------------
328 // Main rendering and event handling loop
329 // --------------------------------------------------------------
330
331 auto start_time = std::chrono::high_resolution_clock::now();
332
333 while (App::app_state->getEnum<AppState>() != AppState::SHUTDOWN)
334 {
335 App::GetAppContext()->PrepareProfiler();
336 OgreBites::WindowEventUtilities::messagePump();
337
338 // Halt physics (wait for async tasks to finish)
339 if (App::app_state->getEnum<AppState>() == AppState::SIMULATION)
340 {
341 App::GetGameContext()->GetActorManager()->SyncWithSimThread();
342 }
343
344 // Game events
345 OgreProfileBegin("RoR message queue");
346 while (App::GetGameContext()->HasMessages())
347 {
348 Message m = App::GetGameContext()->PopMessage();
349 bool failed_m = false;
350 switch (m.type)
351 {
352
353 // -- Application events --
354
355 case MSG_APP_SHUTDOWN_REQUESTED:
356 {
357 try
358 {
359 if (App::app_state->getEnum<AppState>() == AppState::SIMULATION)
360 {
361 App::GetGameContext()->SaveScene("autosave.sav");
362 }
363 App::GetConsole()->saveConfig(); // RoR.cfg
364 App::GetDiscordRpc()->Shutdown();
365 #ifdef USE_SOCKETW
366 if (App::mp_state->getEnum<MpState>() == MpState::CONNECTED)
367 {
368 App::GetNetwork()->Disconnect();
369 }
370 #endif // USE_SOCKETW
371 App::app_state->setVal((int)AppState::SHUTDOWN);
372 App::GetScriptEngine()->setEventsEnabled(false); // Hack to enable fast shutdown without cleanup.
373 }
374 catch (...)
375 {
376 HandleMsgQueueException(m.type);
377 }
378 break;
379 }
380
381 case MSG_APP_SCREENSHOT_REQUESTED:
382 {
383 try
384 {
385 App::GetGuiManager()->SetMouseCursorVisibility(GUIManager::MouseCursorVisibility::HIDDEN);
386 App::GetAppContext()->CaptureScreenshot();
387 App::GetGuiManager()->SetMouseCursorVisibility(GUIManager::MouseCursorVisibility::VISIBLE);
388 }
389 catch (...)
390 {
391 HandleMsgQueueException(m.type);
392 }
393 break;
394 }
395
396 case MSG_APP_DISPLAY_FULLSCREEN_REQUESTED:
397 {
398 try
399 {
400 App::GetAppContext()->ActivateFullscreen(true);
401 App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_NOTICE,
402 _L("Display mode changed to fullscreen"));
403 }
404 catch (...)
405 {
406 HandleMsgQueueException(m.type);
407 }
408 break;
409 }
410
411 case MSG_APP_DISPLAY_WINDOWED_REQUESTED:
412 {
413 try
414 {
415 App::GetAppContext()->ActivateFullscreen(false);
416 App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_NOTICE,
417 _L("Display mode changed to windowed"));
418 }
419 catch (...)
420 {
421 HandleMsgQueueException(m.type);
422 }
423 break;
424 }
425
426 case MSG_APP_MODCACHE_LOAD_REQUESTED:
427 {
428 try
429 {
430 if (!App::GetCacheSystem()->IsModCacheLoaded()) // If not already loaded...
431 {
432 App::GetGuiManager()->SetMouseCursorVisibility(GUIManager::MouseCursorVisibility::HIDDEN);
433 App::GetContentManager()->InitModCache(CacheValidity::UNKNOWN);
434 }
435 }
436 catch (...)
437 {
438 HandleMsgQueueException(m.type);
439 }
440 break;
441 }
442
443 case MSG_APP_MODCACHE_UPDATE_REQUESTED:
444 {
445 try
446 {
447 if (App::app_state->getEnum<AppState>() == AppState::MAIN_MENU) // No actors must be spawned; they keep pointers to CacheEntries
448 {
449 RoR::Log("[RoR|ModCache] Cache update requested");
450 App::GetGuiManager()->SetMouseCursorVisibility(GUIManager::MouseCursorVisibility::HIDDEN);
451 App::GetContentManager()->InitModCache(CacheValidity::NEEDS_UPDATE);
452 }
453 }
454 catch (...)
455 {
456 HandleMsgQueueException(m.type);
457 }
458 break;
459 }
460
461 case MSG_APP_MODCACHE_PURGE_REQUESTED:
462 {
463 try
464 {
465 if (App::app_state->getEnum<AppState>() == AppState::MAIN_MENU) // No actors must be spawned; they keep pointers to CacheEntries
466 {
467 RoR::Log("[RoR|ModCache] Cache rebuild requested");
468 App::GetGuiManager()->SetMouseCursorVisibility(GUIManager::MouseCursorVisibility::HIDDEN);
469 App::GetContentManager()->InitModCache(CacheValidity::NEEDS_REBUILD);
470 }
471 }
472 catch (...)
473 {
474 HandleMsgQueueException(m.type);
475 }
476 break;
477 }
478
479 case MSG_APP_LOAD_SCRIPT_REQUESTED:
480 {
481 LoadScriptRequest* request = static_cast<LoadScriptRequest*>(m.payload);
482 try
483 {
484 ActorPtr actor = App::GetGameContext()->GetActorManager()->GetActorById(request->lsr_associated_actor);
485 int32_t netuid = -1;
486 if (request->lsr_category == ScriptCategory::AI_BOT)
487 {
488 // Network user must be registered before script's `main()` runs.
489 ROR_ASSERT(App::mp_state->getEnum<MpState>() == MpState::LOCAL_SCRIPT && "AI_BOT scripts can only be loaded under 'mp_state = LOCAL_SCRIPT (3)'.");
491 std::memset(&u, 0, sizeof(u));
492 strcpy(u.username, "Bot");
493 strcpy(u.language, "cz_CZ");
494 strcpy(u.sessiontype, "normal");
495 strcpy(u.clientname, "Script");
496 strcpy(u.clientversion, "Script");
498 // Register with server script - fills UID and color
499 App::GetServerScript()->createClient(u);
500 netuid = u.uniqueid;
501 // Register locally
502 App::GetNetwork()->AddBotUserInfo(u);
503 App::GetGuiManager()->MpClientList.UpdateClients(); // OK to invoke directly - processing MSG_APP_LOAD_SCRIPT_REQUESTED
504 }
505 // Notifications for script manipulations are sent by loadScript().
506 ScriptUnitID_t nid = App::GetScriptEngine()->loadScript(request->lsr_filename, request->lsr_category, actor, netuid, request->lsr_buffer);
507 if (nid == SCRIPTUNITID_INVALID && request->lsr_category == ScriptCategory::AI_BOT)
508 {
509 // AI_BOT failed, clean up network user
510 App::GetServerScript()->queueMessageUserLeave(netuid);
511 App::GetNetwork()->RemoveBotUserInfo(netuid);
512 App::GetGuiManager()->MpClientList.UpdateClients(); // OK to invoke directly - processing MSG_APP_LOAD_SCRIPT_REQUESTED
513 }
514 }
515 catch (...)
516 {
517 HandleMsgQueueException(m.type);
518 }
519 delete request;
520 break;
521 }
522
523 case MSG_APP_UNLOAD_SCRIPT_REQUESTED:
524 {
525 ScriptUnitID_t* id = static_cast<ScriptUnitID_t*>(m.payload);
526 try
527 {
528 ScriptCategory cat = App::GetScriptEngine()->getScriptUnit(*id).scriptCategory;
529 int32_t netuid = App::GetScriptEngine()->getScriptUnit(*id).associatedNetUid;
530 // Notifications for script manipulations are sent by unloadScript().
531 App::GetScriptEngine()->unloadScript(*id);
532 if (cat == ScriptCategory::AI_BOT)
533 {
534 // AI_BOT script unloaded, clean up local network user
535 App::GetNetwork()->RemoveBotUserInfo(netuid);
536 App::GetGuiManager()->MpClientList.UpdateClients(); // OK to invoke directly - processing MSG_APP_UNLOAD_SCRIPT_REQUESTED
537 // Also clean up any actors spawned by the bot
538 for (ActorPtr& actor : App::GetGameContext()->GetActorManager()->GetLocalActors())
539 {
540 if (actor->ar_driveable == AI && actor->ar_net_source_id == netuid)
541 {
542 App::GetGameContext()->PushMessage(Message(MSG_SIM_DELETE_ACTOR_REQUESTED, static_cast<void*>(new ActorPtr(actor))));
543 }
544 }
545 }
546 }
547 catch (...)
548 {
549 HandleMsgQueueException(m.type);
550 }
551 delete id;
552 break;
553 }
554
555 case MSG_APP_SCRIPT_THREAD_STATUS:
556 {
557 ScriptEventArgs* args = static_cast<ScriptEventArgs*>(m.payload);
558 try
559 {
560 App::GetScriptEngine()->triggerEvent(SE_ANGELSCRIPT_THREAD_STATUS,
561 args->arg1, args->arg2ex, args->arg3ex, args->arg4ex, args->arg5ex, args->arg6ex, args->arg7ex);
562 delete args;
563 }
564 catch (...)
565 {
566 HandleMsgQueueException(m.type);
567 }
568 break;
569 }
570
571 case MSG_APP_REINIT_INPUT_REQUESTED:
572 {
573 try
574 {
575 LOG(fmt::format("[RoR] !! Reinitializing input engine !!"));
576 App::DestroyInputEngine();
577 App::GetAppContext()->SetUpInput();
578 LOG(fmt::format("[RoR] DONE Reinitializing input engine."));
579 App::GetGuiManager()->LoadingWindow.SetVisible(false); // Shown by `GUI::GameSettings` when changing 'grab mode'
580 }
581 catch (...)
582 {
583 HandleMsgQueueException(m.type);
584 }
585 break;
586 }
587
588 case MSG_APP_REINIT_UI_REQUESTED:
589 {
590 try
591 {
592 float ui_scale_clamped = Ogre::Math::Clamp(App::ui_scale_factor->getFloat(), UI_SCALE_FACTOR_MIN, UI_SCALE_FACTOR_MAX);
593 if (App::ui_scale_factor->getFloat() != ui_scale_clamped)
594 {
595 LOG(fmt::format("[RoR] Invalid value of 'ui_scale_factor' ({}), clamping to bounds <{}, {}>",
596 App::ui_scale_factor->getFloat(), UI_SCALE_FACTOR_MIN, UI_SCALE_FACTOR_MAX));
597 App::ui_scale_factor->setVal(ui_scale_clamped);
598 }
599 LOG(fmt::format("[RoR] Reinitializing UI (scale factor: {})", App::ui_scale_factor->getFloat()));
600 App::GetGuiManager()->ReinitUI(); // OK to invoke directly - no ImGui frame is in progress while processing messages.
601 if (App::app_state->getEnum<AppState>() == AppState::SIMULATION)
602 {
603 for (ActorPtr& actor: App::GetGameContext()->GetActorManager()->GetActors())
604 {
605 actor->ar_dashboard->applyUiScale(); // MyGUI-based dashboards
606 }
607 }
608 }
609 catch (...)
610 {
611 HandleMsgQueueException(m.type);
612 }
613 break;
614 }
615
616 // -- Network events --
617
618 case MSG_NET_CONNECT_REQUESTED:
619 {
620#if USE_SOCKETW
621 try
622 {
623 if (App::GetServerScript()->IsRunning())
624 {
625 App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_NOTICE,
626 _L("Stopping local server script engine before connecting to remote server."));
627 App::GetServerScript()->Close();
628 App::mp_state->setVal((int)MpState::DISABLED);
629 }
630 App::GetNetwork()->StartConnecting();
631 }
632 catch (...)
633 {
634 HandleMsgQueueException(m.type);
635 }
636#endif
637 break;
638 }
639
640 case MSG_NET_DISCONNECT_REQUESTED:
641 {
642#if USE_SOCKETW
643 try
644 {
645 if (App::mp_state->getEnum<MpState>() == MpState::CONNECTED)
646 {
647 App::GetNetwork()->Disconnect();
648 if (App::app_state->getEnum<AppState>() == AppState::MAIN_MENU)
649 {
650 App::GetGuiManager()->MainSelector.Close(); // We may get disconnected while still in map selection
651 App::GetGameContext()->PushMessage(Message(MSG_GUI_OPEN_MENU_REQUESTED));
652 }
653 }
654 }
655 catch (...)
656 {
657 HandleMsgQueueException(m.type);
658 }
659#endif // USE_SOCKETW
660 break;
661 }
662
663 case MSG_NET_SERVER_KICK:
664 {
665 try
666 {
667 App::GetGameContext()->PushMessage(Message(MSG_NET_DISCONNECT_REQUESTED));
668 App::GetGameContext()->PushMessage(Message(MSG_SIM_UNLOAD_TERRN_REQUESTED));
669 App::GetGameContext()->PushMessage(Message(MSG_GUI_OPEN_MENU_REQUESTED));
670 App::GetGuiManager()->ShowMessageBox(_LC("Network", "Network disconnected"), m.description.c_str());
671 }
672 catch (...)
673 {
674 HandleMsgQueueException(m.type);
675 }
676 break;
677 }
678
679 case MSG_NET_RECV_ERROR:
680 {
681 try
682 {
683 App::GetGameContext()->PushMessage(Message(MSG_NET_DISCONNECT_REQUESTED));
684 App::GetGameContext()->PushMessage(Message(MSG_SIM_UNLOAD_TERRN_REQUESTED));
685 App::GetGameContext()->PushMessage(Message(MSG_GUI_OPEN_MENU_REQUESTED));
686 App::GetGuiManager()->ShowMessageBox(_L("Network fatal error: "), m.description.c_str());
687 }
688 catch (...)
689 {
690 HandleMsgQueueException(m.type);
691 }
692 break;
693 }
694
695 case MSG_NET_CONNECT_STARTED:
696 {
697 try
698 {
699 App::GetGuiManager()->LoadingWindow.SetProgressNetConnect(m.description);
700 App::GetGuiManager()->MultiplayerSelector.SetVisible(false);
701 App::GetGameContext()->PushMessage(Message(MSG_GUI_CLOSE_MENU_REQUESTED));
702 }
703 catch (...)
704 {
705 HandleMsgQueueException(m.type);
706 }
707 break;
708 }
709
710 case MSG_NET_CONNECT_PROGRESS:
711 {
712 try
713 {
714 App::GetGuiManager()->LoadingWindow.SetProgressNetConnect(m.description);
715 }
716 catch (...)
717 {
718 HandleMsgQueueException(m.type);
719 }
720 break;
721 }
722
723 case MSG_NET_CONNECT_SUCCESS:
724 {
725#if USE_SOCKETW
726 try
727 {
728 App::GetGuiManager()->LoadingWindow.SetVisible(false);
729 App::GetNetwork()->StopConnecting();
730 App::mp_state->setVal((int)RoR::MpState::CONNECTED);
732 if (!App::GetMumble())
733 {
734 App::CreateMumble();
735 }
736 if (App::GetNetwork()->GetTerrainName() != "any")
737 {
738 App::GetGameContext()->PushMessage(Message(MSG_SIM_LOAD_TERRN_REQUESTED, App::GetNetwork()->GetTerrainName()));
739 }
740 else
741 {
742 // Connected -> go directly to map selector
743 if (App::diag_preset_terrain->getStr().empty())
744 {
745 RoR::Message m(MSG_GUI_OPEN_SELECTOR_REQUESTED);
746 m.payload = reinterpret_cast<void*>(new LoaderType(LT_Terrain));
747 App::GetGameContext()->PushMessage(m);
748 }
749 else
750 {
751 App::GetGameContext()->PushMessage(Message(MSG_SIM_LOAD_TERRN_REQUESTED, App::diag_preset_terrain->getStr()));
752 }
753 }
754 }
755 catch (...)
756 {
757 HandleMsgQueueException(m.type);
758 }
759#endif // USE_SOCKETW
760 break;
761 }
762
763 case MSG_NET_CONNECT_FAILURE:
764 {
765#if USE_SOCKETW
766 try
767 {
768 App::GetGuiManager()->LoadingWindow.SetVisible(false);
769 App::GetNetwork()->StopConnecting();
770 App::GetGameContext()->PushMessage(Message(MSG_NET_DISCONNECT_REQUESTED));
771 App::GetGameContext()->PushMessage(Message(MSG_GUI_OPEN_MENU_REQUESTED));
772 App::GetGuiManager()->ShowMessageBox(
773 _LC("Network", "Multiplayer: connection failed"), m.description.c_str());
774 }
775 catch (...)
776 {
777 HandleMsgQueueException(m.type);
778 }
779#endif // USE_SOCKETW
780 break;
781 }
782
783 case MSG_NET_REFRESH_SERVERLIST_SUCCESS:
784 {
785 GUI::MpServerInfoVec* data = static_cast<GUI::MpServerInfoVec*>(m.payload);
786 try
787 {
788 App::GetGuiManager()->MultiplayerSelector.UpdateServerlist(data);
789 }
790 catch (...)
791 {
792 HandleMsgQueueException(m.type);
793 }
794 delete data;
795 break;
796 }
797
798 case MSG_NET_REFRESH_SERVERLIST_FAILURE:
799 {
800 CurlFailInfo* failinfo = static_cast<CurlFailInfo*>(m.payload);
801 try
802 {
803 App::GetGuiManager()->MultiplayerSelector.DisplayRefreshFailed(failinfo);
804 }
805 catch (...)
806 {
807 HandleMsgQueueException(m.type);
808 }
809 delete failinfo;
810 break;
811 }
812
813 case MSG_NET_REFRESH_REPOLIST_SUCCESS:
814 {
816 try
817 {
818 App::GetGuiManager()->RepositorySelector.UpdateResources(data);
819 }
820 catch (...)
821 {
822 HandleMsgQueueException(m.type);
823 }
824 delete data;
825 break;
826 }
827
828 case MSG_NET_OPEN_RESOURCE_SUCCESS:
829 {
831 try
832 {
833 App::GetGuiManager()->RepositorySelector.UpdateResourceFilesAndDescription(data);
834 }
835 catch (...)
836 {
837 HandleMsgQueueException(m.type);
838 }
839 delete data;
840 break;
841 }
842
843 case MSG_NET_REFRESH_REPOLIST_FAILURE:
844 {
845 CurlFailInfo* failinfo = static_cast<CurlFailInfo*>(m.payload);
846 try
847 {
848 App::GetGuiManager()->RepositorySelector.ShowError(failinfo);
849 }
850 catch (...)
851 {
852 HandleMsgQueueException(m.type);
853 }
854 delete failinfo;
855 break;
856 }
857
858 case MSG_NET_FETCH_AI_PRESETS_SUCCESS:
859 {
860 try
861 {
862 App::GetGuiManager()->TopMenubar.ai_presets_extern_fetching = false;
863 App::GetGuiManager()->TopMenubar.ai_presets_extern.Parse(m.description.c_str());
864 App::GetGuiManager()->TopMenubar.RefreshAiPresets();
865 }
866 catch (...)
867 {
868 HandleMsgQueueException(m.type);
869 }
870 break;
871 }
872
873 case MSG_NET_FETCH_AI_PRESETS_FAILURE:
874 {
875 try
876 {
877 App::GetGuiManager()->TopMenubar.ai_presets_extern_fetching = false;
878 App::GetGuiManager()->TopMenubar.ai_presets_extern_error = m.description;
879 App::GetGuiManager()->TopMenubar.RefreshAiPresets();
880 }
881 catch (...)
882 {
883 HandleMsgQueueException(m.type);
884 }
885 break;
886 }
887
888 case MSG_NET_ADD_PEEROPTIONS_REQUESTED:
889 {
890 PeerOptionsRequest* request = static_cast<PeerOptionsRequest*>(m.payload);
891 try
892 {
893 // Record the options for future incoming traffic.
894 App::GetNetwork()->AddPeerOptions(request);
895
896 // On MUTE_CHAT also purge old messages
898 {
899 App::GetConsole()->purgeNetChatMessagesByUser(request->por_uid);
900 }
901
902 // MUTE existing actors if needed
904 {
905 for (ActorPtr& actor : App::GetGameContext()->GetActorManager()->GetActors())
906 {
907 if (actor->ar_net_source_id == request->por_uid)
908 {
909 App::GetGameContext()->PushMessage(Message(MSG_SIM_MUTE_NET_ACTOR_REQUESTED, new ActorPtr(actor)));
910 }
911 }
912 }
913
914 // HIDE existing actors if needed
916 {
917 for (ActorPtr& actor : App::GetGameContext()->GetActorManager()->GetActors())
918 {
919 if (actor->ar_net_source_id == request->por_uid)
920 {
921 App::GetGameContext()->PushMessage(Message(MSG_SIM_HIDE_NET_ACTOR_REQUESTED, new ActorPtr(actor)));
922 }
923 }
924 }
925 }
926 catch (...)
927 {
928 HandleMsgQueueException(m.type);
929 }
930 delete request;
931 break;
932 }
933
934 case MSG_NET_REMOVE_PEEROPTIONS_REQUESTED:
935 {
936 PeerOptionsRequest* request = static_cast<PeerOptionsRequest*>(m.payload);
937 try
938 {
939 // Record the options for future incoming traffic.
940 App::GetNetwork()->RemovePeerOptions(request);
941
942 // un-MUTE existing actors if needed
944 {
945 for (ActorPtr& actor : App::GetGameContext()->GetActorManager()->GetActors())
946 {
947 if (actor->ar_net_source_id == request->por_uid)
948 {
949 App::GetGameContext()->PushMessage(Message(MSG_SIM_UNMUTE_NET_ACTOR_REQUESTED, new ActorPtr(actor)));
950 }
951 }
952 }
953
954 // un-HIDE existing actors if needed
956 {
957 for (ActorPtr& actor : App::GetGameContext()->GetActorManager()->GetActors())
958 {
959 if (actor->ar_net_source_id == request->por_uid)
960 {
961 App::GetGameContext()->PushMessage(Message(MSG_SIM_UNHIDE_NET_ACTOR_REQUESTED, new ActorPtr(actor)));
962 }
963 }
964 }
965 }
966 catch (...)
967 {
968 HandleMsgQueueException(m.type);
969 }
970 delete request;
971 break;
972 }
973
974 case MSG_NET_DOWNLOAD_REPOIMAGE_SUCCESS:
975 case MSG_NET_DOWNLOAD_REPOIMAGE_FAILURE: // If failed there is no file on disk so placeholder will be set instead.
976 {
978 try
979 {
980 App::GetGuiManager()->RepositorySelector.LoadDownloadedImage(rq);
981 }
982 catch (...)
983 {
984 HandleMsgQueueException(m.type);
985 }
986 delete rq;
987 break;
988 }
989
990 case MSG_NET_DOWNLOAD_REPOFILE_REQUESTED:
991 {
992 RepoFileInstallRequest* request = static_cast<RepoFileInstallRequest*>(m.payload);
993 try
994 {
995 App::GetGuiManager()->RepositorySelector.QueueInstallRepoFile(request);
996
997 App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_NOTICE,
998 fmt::format(_LC("RepositorySelector", "Repo file installation requested: {}"), request->rfir_filename));
999 }
1000 catch (...)
1001 {
1002 HandleMsgQueueException(m.type);
1003 }
1004 delete request;
1005 break;
1006 }
1007
1008 // -- Gameplay events --
1009
1010 case MSG_SIM_PAUSE_REQUESTED:
1011 {
1012 try
1013 {
1014 for (ActorPtr& actor: App::GetGameContext()->GetActorManager()->GetActors())
1015 {
1016 actor->muteAllSounds();
1017 }
1018 App::sim_state->setVal((int)SimState::PAUSED);
1019 }
1020 catch (...)
1021 {
1022 HandleMsgQueueException(m.type);
1023 }
1024 break;
1025 }
1026
1027 case MSG_SIM_UNPAUSE_REQUESTED:
1028 {
1029 try
1030 {
1031 for (ActorPtr& actor: App::GetGameContext()->GetActorManager()->GetActors())
1032 {
1033 if (!actor->ar_muted_by_peeropt)
1034 {
1035 actor->unmuteAllSounds();
1036 }
1037 }
1038 App::sim_state->setVal((int)SimState::RUNNING);
1039 }
1040 catch (...)
1041 {
1042 HandleMsgQueueException(m.type);
1043 }
1044 break;
1045 }
1046
1047 case MSG_SIM_LOAD_TERRN_REQUESTED:
1048 {
1049 try
1050 {
1051 App::GetGuiManager()->SetMouseCursorVisibility(GUIManager::MouseCursorVisibility::HIDDEN);
1052 App::GetGuiManager()->LoadingWindow.SetProgress(5, _L("Loading resources"));
1053 App::GetContentManager()->LoadGameplayResources();
1054
1055 if (App::GetGameContext()->LoadTerrain(m.description))
1056 {
1057 App::GetGameContext()->CreatePlayerCharacter();
1058 // Spawn preselected vehicle; commandline has precedence
1059 if (App::cli_preset_vehicle->getStr() != "")
1060 App::GetGameContext()->SpawnPreselectedActor(App::cli_preset_vehicle->getStr(), App::cli_preset_veh_config->getStr()); // Needs character for position
1061 else if (App::diag_preset_vehicle->getStr() != "")
1062 App::GetGameContext()->SpawnPreselectedActor(App::diag_preset_vehicle->getStr(), App::diag_preset_veh_config->getStr()); // Needs character for position
1063 App::GetGameContext()->GetSceneMouse().InitializeVisuals();
1064 App::CreateOverlayWrapper();
1065 App::GetGuiManager()->DirectionArrow.LoadOverlay();
1066 if (App::audio_menu_music->getBool())
1067 {
1068 SOUND_KILL(-1, SS_TRIG_MAIN_MENU);
1069 }
1070 if (App::gfx_sky_mode->getEnum<GfxSkyMode>() == GfxSkyMode::SANDSTORM)
1071 {
1072 App::GetGfxScene()->GetSceneManager()->setAmbientLight(Ogre::ColourValue(0.7f, 0.7f, 0.7f));
1073 }
1074 else
1075 {
1076 App::GetGfxScene()->GetSceneManager()->setAmbientLight(Ogre::ColourValue(0.3f, 0.3f, 0.3f));
1077 }
1078 App::GetDiscordRpc()->UpdatePresence();
1079 App::sim_state->setVal((int)SimState::RUNNING);
1080 App::app_state->setVal((int)AppState::SIMULATION);
1081 App::GetGuiManager()->GameMainMenu .SetVisible(false);
1082 App::GetGuiManager()->MenuWallpaper->hide();
1083 App::GetGuiManager()->LoadingWindow.SetVisible(false);
1084 App::GetGuiManager()->SetMouseCursorVisibility(GUIManager::MouseCursorVisibility::VISIBLE);
1085 App::gfx_fov_external->setVal(App::gfx_fov_external_default->getInt());
1086 App::gfx_fov_internal->setVal(App::gfx_fov_internal_default->getInt());
1087 #ifdef USE_SOCKETW
1088 if (App::mp_state->getEnum<MpState>() == MpState::CONNECTED)
1089 {
1090 App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_NOTICE,
1091 fmt::format(_LC("ChatBox", "Press {} to start chatting"),
1092 App::GetInputEngine()->getEventCommandTrimmed(EV_COMMON_ENTER_CHATMODE)), "lightbulb.png");
1093 }
1094 #endif // USE_SOCKETW
1095 if (App::io_outgauge_mode->getInt() > 0)
1096 {
1097 App::GetOutGauge()->Connect();
1098 }
1099 }
1100 else
1101 {
1102 if (App::mp_state->getEnum<MpState>() == MpState::CONNECTED)
1103 {
1104 App::GetGameContext()->PushMessage(Message(MSG_NET_DISCONNECT_REQUESTED));
1105 }
1106 else
1107 {
1108 App::GetGameContext()->PushMessage(Message(MSG_GUI_OPEN_MENU_REQUESTED));
1109 }
1110 App::GetGuiManager()->LoadingWindow.SetVisible(false);
1111 failed_m = true;
1112 }
1113 }
1114 catch (...)
1115 {
1116 HandleMsgQueueException(m.type);
1117 }
1118 break;
1119 }
1120
1121 case MSG_SIM_UNLOAD_TERRN_REQUESTED:
1122 {
1123 try
1124 {
1125 if (App::sim_state->getEnum<SimState>() == SimState::EDITOR_MODE)
1126 {
1127 App::GetGameContext()->GetTerrain()->GetTerrainEditor()->WriteSeparateOutputFile();
1128 }
1129 App::GetGameContext()->SaveScene("autosave.sav");
1130 App::GetGameContext()->ChangePlayerActor(nullptr);
1131 App::GetGameContext()->GetActorManager()->CleanUpSimulation();
1132 App::GetGameContext()->GetCharacterFactory()->DeleteAllCharacters();
1133 App::GetGameContext()->GetSceneMouse().DiscardVisuals();
1134 App::DestroyOverlayWrapper();
1135 App::GetCameraManager()->ResetAllBehaviors();
1136 App::GetGuiManager()->CollisionsDebug.CleanUp();
1137 App::GetGuiManager()->MainSelector.Close();
1138 App::GetGuiManager()->LoadingWindow.SetVisible(false);
1139 App::GetGuiManager()->MenuWallpaper->show();
1140 App::GetGuiManager()->TopMenubar.ai_waypoints.clear();
1141 App::sim_state->setVal((int)SimState::OFF);
1142 App::app_state->setVal((int)AppState::MAIN_MENU);
1143 App::GetGameContext()->UnloadTerrain();
1144 App::GetGfxScene()->ClearScene();
1145 App::sim_terrain_name->setStr("");
1146 App::sim_terrain_gui_name->setStr("");
1147 App::GetOutGauge()->Close();
1148 App::GetSoundScriptManager()->SetListener(/*position:*/Ogre::Vector3::ZERO, /*direction:*/Ogre::Vector3::ZERO, /*up:*/Ogre::Vector3::UNIT_Y, /*velocity:*/Ogre::Vector3::ZERO);
1149 App::GetSoundScriptManager()->getSoundManager()->CleanUp();
1150 App::GetGameContext()->GetRaceSystem().ResetRaceUI();
1151 }
1152 catch (...)
1153 {
1154 HandleMsgQueueException(m.type);
1155 }
1156 break;
1157 }
1158
1159 case MSG_SIM_LOAD_SAVEGAME_REQUESTED:
1160 {
1161 try
1162 {
1163 std::string terrn_filename = App::GetGameContext()->ExtractSceneTerrain(m.description);
1164 if (terrn_filename == "")
1165 {
1166 Str<400> msg; msg << _L("Could not read savegame file") << "'" << m.description << "'";
1167 App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_ERROR, msg.ToCStr());
1168 if (App::app_state->getEnum<AppState>() == AppState::MAIN_MENU)
1169 {
1170 App::GetGameContext()->PushMessage(Message(MSG_GUI_OPEN_MENU_REQUESTED));
1171 }
1172 }
1173 else if (terrn_filename == App::sim_terrain_name->getStr())
1174 {
1175 App::GetGameContext()->LoadScene(m.description);
1176 }
1177 else if (terrn_filename != App::sim_terrain_name->getStr() && App::mp_state->getEnum<MpState>() == MpState::CONNECTED)
1178 {
1179 Str<400> msg; msg << _L("Error while loading scene: Terrain mismatch");
1180 App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_ERROR, msg.ToCStr());
1181 }
1182 else
1183 {
1184 if (App::sim_terrain_name->getStr() != "")
1185 {
1186 App::GetGameContext()->PushMessage(Message(MSG_SIM_UNLOAD_TERRN_REQUESTED));
1187 }
1188
1189 RoR::LogFormat("[RoR|Savegame] Loading terrain '%s' ...", terrn_filename.c_str());
1190 App::GetGameContext()->PushMessage(Message(MSG_SIM_LOAD_TERRN_REQUESTED, terrn_filename));
1191 // Loading terrain may produce actor-spawn requests; the savegame-request must be posted after them.
1192 App::GetGameContext()->ChainMessage(Message(MSG_SIM_LOAD_SAVEGAME_REQUESTED, m.description));
1193 }
1194 }
1195 catch (...)
1196 {
1197 HandleMsgQueueException(m.type);
1198 }
1199 break;
1200 }
1201
1202 case MSG_SIM_SPAWN_ACTOR_REQUESTED:
1203 {
1204 ActorSpawnRequest* rq = static_cast<ActorSpawnRequest*>(m.payload);
1205 try
1206 {
1207 if (App::app_state->getEnum<AppState>() == AppState::SIMULATION)
1208 {
1209 App::GetGameContext()->SpawnActor(*rq);
1210 }
1211 }
1212 catch (...)
1213 {
1214 HandleMsgQueueException(m.type);
1215 }
1216 delete rq;
1217 break;
1218 }
1219
1220 case MSG_SIM_MODIFY_ACTOR_REQUESTED:
1221 {
1222 ActorModifyRequest* rq = static_cast<ActorModifyRequest*>(m.payload);
1223 try
1224 {
1225 if (App::app_state->getEnum<AppState>() == AppState::SIMULATION)
1226 {
1227 App::GetGameContext()->ModifyActor(*rq);
1228 }
1229 }
1230 catch (...)
1231 {
1232 HandleMsgQueueException(m.type);
1233 }
1234 delete rq;
1235 break;
1236 }
1237
1238 case MSG_SIM_DELETE_ACTOR_REQUESTED:
1239 {
1240 ActorPtr* actor_ptr = static_cast<ActorPtr*>(m.payload);
1241 try
1242 {
1243 ROR_ASSERT(actor_ptr);
1244 if (App::app_state->getEnum<AppState>() == AppState::SIMULATION)
1245 {
1246 App::GetGameContext()->DeleteActor(*actor_ptr);
1247 }
1248 }
1249 catch (...)
1250 {
1251 HandleMsgQueueException(m.type);
1252 }
1253 delete actor_ptr;
1254 break;
1255 }
1256
1257 case MSG_SIM_SEAT_PLAYER_REQUESTED:
1258 {
1259 ActorPtr* actor_ptr = static_cast<ActorPtr*>(m.payload);
1260 try
1261 {
1262 ROR_ASSERT(actor_ptr); // Even if leaving vehicle, the pointer must be valid.
1263 if (App::app_state->getEnum<AppState>() == AppState::SIMULATION)
1264 {
1265 App::GetGameContext()->ChangePlayerActor(*actor_ptr);
1266 }
1267 }
1268 catch (...)
1269 {
1270 HandleMsgQueueException(m.type);
1271 }
1272 delete actor_ptr;
1273 break;
1274 }
1275
1276 case MSG_SIM_TELEPORT_PLAYER_REQUESTED:
1277 {
1278 Ogre::Vector3* pos = static_cast<Ogre::Vector3*>(m.payload);
1279 try
1280 {
1281 if (App::app_state->getEnum<AppState>() == AppState::SIMULATION)
1282 {
1283 App::GetGameContext()->TeleportPlayer(pos->x, pos->z);
1284 }
1285 }
1286 catch (...)
1287 {
1288 HandleMsgQueueException(m.type);
1289 }
1290 delete pos;
1291 break;
1292 }
1293
1294 case MSG_SIM_HIDE_NET_ACTOR_REQUESTED:
1295 {
1296 ActorPtr* actor_ptr = static_cast<ActorPtr*>(m.payload);
1297 try
1298 {
1299 ROR_ASSERT(actor_ptr);
1300 if ((App::mp_state->getEnum<MpState>() == MpState::CONNECTED) &&
1301 ((*actor_ptr)->ar_state == ActorState::NETWORKED_OK))
1302 {
1303 ActorPtr actor = *actor_ptr;
1304 actor->ar_state = ActorState::NETWORKED_HIDDEN; // Stop net. updates
1305 App::GetGfxScene()->RemoveGfxActor(actor->GetGfxActor()); // Remove visuals (also stops updating SimBuffer)
1306 actor->GetGfxActor()->GetSimDataBuffer().simbuf_actor_state = ActorState::NETWORKED_HIDDEN; // Hack - manually propagate the new state to SimBuffer so Character can reflect it.
1307 actor->GetGfxActor()->SetAllMeshesVisible(false);
1308 actor->GetGfxActor()->SetCastShadows(false);
1309 actor->muteAllSounds(); // Stop sounds
1310 actor->forceAllFlaresOff();
1311 actor->setSmokeEnabled(false);
1312 }
1313 }
1314 catch (...)
1315 {
1316 HandleMsgQueueException(m.type);
1317 }
1318 delete actor_ptr;
1319 break;
1320 }
1321
1322 case MSG_SIM_UNHIDE_NET_ACTOR_REQUESTED:
1323 {
1324 ActorPtr* actor_ptr = static_cast<ActorPtr*>(m.payload);
1325 try
1326 {
1327 ROR_ASSERT(actor_ptr);
1328 if (App::mp_state->getEnum<MpState>() == MpState::CONNECTED &&
1329 ((*actor_ptr)->ar_state == ActorState::NETWORKED_HIDDEN))
1330 {
1331 ActorPtr actor = *actor_ptr;
1332 actor->ar_state = ActorState::NETWORKED_OK; // Resume net. updates
1333 App::GetGfxScene()->RegisterGfxActor(actor->GetGfxActor()); // Restore visuals (also resumes updating SimBuffer)
1334 actor->GetGfxActor()->SetAllMeshesVisible(true);
1335 actor->GetGfxActor()->SetCastShadows(true);
1336 actor->unmuteAllSounds(); // Unmute sounds
1337 actor->setSmokeEnabled(true);
1338 }
1339 }
1340 catch (...)
1341 {
1342 HandleMsgQueueException(m.type);
1343 }
1344 delete actor_ptr;
1345 break;
1346 }
1347
1348 case MSG_SIM_MUTE_NET_ACTOR_REQUESTED:
1349 {
1350 ActorPtr* actor_ptr = static_cast<ActorPtr*>(m.payload);
1351 try
1352 {
1353 ROR_ASSERT(actor_ptr);
1354 ActorPtr actor = *actor_ptr;
1355 const bool valid_remote_actor = (App::mp_state->getEnum<MpState>() == MpState::CONNECTED) && actor->ar_state == ActorState::NETWORKED_OK;
1356 const bool valid_ai_actor = (App::mp_state->getEnum<MpState>() == MpState::LOCAL_SCRIPT) && actor->ar_state == ActorState::LOCAL_SIMULATED && actor->ar_driveable == ActorType::AI;
1357 if (valid_remote_actor || valid_ai_actor)
1358 {
1359 actor->ar_muted_by_peeropt = true;
1360 actor->muteAllSounds();
1361 }
1362 }
1363 catch (...)
1364 {
1365 HandleMsgQueueException(m.type);
1366 }
1367 delete actor_ptr;
1368 break;
1369 }
1370
1371 case MSG_SIM_UNMUTE_NET_ACTOR_REQUESTED:
1372 {
1373 ActorPtr* actor_ptr = static_cast<ActorPtr*>(m.payload);
1374 try
1375 {
1376 ROR_ASSERT(actor_ptr);
1377 ActorPtr actor = *actor_ptr;
1378 const bool valid_remote_actor = (App::mp_state->getEnum<MpState>() == MpState::CONNECTED) && actor->ar_state == ActorState::NETWORKED_OK;
1379 const bool valid_ai_actor = (App::mp_state->getEnum<MpState>() == MpState::LOCAL_SCRIPT) && actor->ar_state == ActorState::LOCAL_SIMULATED && actor->ar_driveable == ActorType::AI;
1380 if (valid_remote_actor || valid_ai_actor)
1381 {
1382 actor->ar_muted_by_peeropt = false;
1383 actor->unmuteAllSounds();
1384 }
1385 }
1386 catch (...)
1387 {
1388 HandleMsgQueueException(m.type);
1389 }
1390 delete actor_ptr;
1391 break;
1392 }
1393
1394 case MSG_SIM_SCRIPT_EVENT_TRIGGERED:
1395 {
1396 ScriptEventArgs* args = static_cast<ScriptEventArgs*>(m.payload);
1397 try
1398 {
1399 if (args->type == SE_GENERIC_FREEFORCES_ACTIVITY && args->arg1 == freeForcesActivityType::FREEFORCESACTIVITY_BROKEN)
1400 {
1401 App::GetGfxScene()->OnFreeForceBroken(args->arg2ex);
1402 }
1403 else if (args->type == SE_GENERIC_MODCACHE_ACTIVITY && args->arg1 == modCacheActivityType::MODCACHEACTIVITY_ENTRY_ADDED && App::mp_state->getEnum<MpState>() == MpState::CONNECTED)
1404 {
1405 // Catch up to other players who may already be driving this mod
1406 App::GetGameContext()->GetActorManager()->RetryFailedStreamRegistrations(args);
1407 }
1408 App::GetScriptEngine()->triggerEvent(args->type, args->arg1, args->arg2ex, args->arg3ex, args->arg4ex, args->arg5ex, args->arg6ex, args->arg7ex, args->arg8ex);
1409
1410 }
1411 catch (...)
1412 {
1413 HandleMsgQueueException(m.type);
1414 }
1415 delete args;
1416 break;
1417 }
1418
1419 case MSG_SIM_SCRIPT_CALLBACK_QUEUED:
1420 {
1421 ScriptCallbackArgs* args = static_cast<ScriptCallbackArgs*>(m.payload);
1422 try
1423 {
1424 App::GetScriptEngine()->envokeCallback(args->eventsource->es_script_handler, args->eventsource, args->node);
1425 }
1426 catch (...)
1427 {
1428 HandleMsgQueueException(m.type);
1429 }
1430 delete args;
1431 break;
1432 }
1433
1434 case MSG_SIM_ACTOR_LINKING_REQUESTED:
1435 {
1436 // Estabilishing a physics linkage between 2 actors modifies a global linkage table
1437 // and triggers immediate update of every actor's linkage tables,
1438 // so it has to be done sequentially on main thread.
1439 // ---------------------------------------------------------------------------------
1440 ActorLinkingRequest* request = static_cast<ActorLinkingRequest*>(m.payload);
1441 try
1442 {
1443 ActorPtr actor = App::GetGameContext()->GetActorManager()->GetActorById(request->alr_actor_instance_id);
1444 if (actor)
1445 {
1446 switch (request->alr_type)
1447 {
1448 case ActorLinkingRequestType::HOOK_LOCK:
1449 case ActorLinkingRequestType::HOOK_UNLOCK:
1450 case ActorLinkingRequestType::HOOK_TOGGLE:
1451 actor->hookToggle(request->alr_hook_group, request->alr_type);
1452 break;
1453
1454 case ActorLinkingRequestType::HOOK_MOUSE_TOGGLE:
1455 actor->hookToggle(request->alr_hook_group, request->alr_type, request->alr_hook_mousenode);
1456 TRIGGER_EVENT_ASYNC(SE_TRUCK_MOUSE_GRAB, request->alr_actor_instance_id);
1457 break;
1458
1459 case ActorLinkingRequestType::TIE_TOGGLE:
1460 actor->tieToggle(request->alr_tie_group);
1461 break;
1462
1463 case ActorLinkingRequestType::ROPE_TOGGLE:
1464 actor->ropeToggle(request->alr_rope_group);
1465 break;
1466
1467 case ActorLinkingRequestType::SLIDENODE_TOGGLE:
1468 actor->toggleSlideNodeLock();
1469 break;
1470 }
1471 }
1472 }
1473 catch (...)
1474 {
1475 HandleMsgQueueException(m.type);
1476 }
1477 delete request;
1478 break;
1479 }
1480
1481 case MSG_SIM_ADD_FREEFORCE_REQUESTED:
1482 {
1483 FreeForceRequest* rq = static_cast<FreeForceRequest*>(m.payload);
1484 try
1485 {
1486 App::GetGameContext()->GetActorManager()->AddFreeForce(rq);
1487 }
1488 catch (...)
1489 {
1490 HandleMsgQueueException(m.type);
1491 }
1492 delete rq;
1493 break;
1494 }
1495
1496 case MSG_SIM_MODIFY_FREEFORCE_REQUESTED:
1497 {
1498 FreeForceRequest* rq = static_cast<FreeForceRequest*>(m.payload);
1499 try
1500 {
1501 App::GetGameContext()->GetActorManager()->ModifyFreeForce(rq);
1502 }
1503 catch (...)
1504 {
1505 HandleMsgQueueException(m.type);
1506 }
1507 delete rq;
1508 break;
1509 }
1510
1511 case MSG_SIM_REMOVE_FREEFORCE_REQUESTED:
1512 {
1513 FreeForceID_t* rq = static_cast<FreeForceID_t*>(m.payload);
1514 try
1515 {
1516 App::GetGameContext()->GetActorManager()->RemoveFreeForce(*rq);
1517 App::GetGfxScene()->OnFreeForceRemoved(*rq);
1518 }
1519 catch (...)
1520 {
1521 HandleMsgQueueException(m.type);
1522 }
1523 delete rq;
1524 break;
1525 }
1526
1527 // -- GUI events ---
1528
1529 case MSG_GUI_OPEN_MENU_REQUESTED:
1530 {
1531 try
1532 {
1533 App::GetGuiManager()->GameMainMenu.SetVisible(true);
1534 }
1535 catch (...)
1536 {
1537 HandleMsgQueueException(m.type);
1538 }
1539 break;
1540 }
1541
1542 case MSG_GUI_CLOSE_MENU_REQUESTED:
1543 {
1544 try
1545 {
1546 App::GetGuiManager()->GameMainMenu.SetVisible(false);
1547 }
1548 catch (...)
1549 {
1550 HandleMsgQueueException(m.type);
1551 }
1552 break;
1553 }
1554
1555 case MSG_GUI_OPEN_SELECTOR_REQUESTED:
1556 {
1557 LoaderType* type = static_cast<LoaderType*>(m.payload);
1558 try
1559 {
1560 App::GetGuiManager()->MainSelector.Show(*type, m.description);
1561 }
1562 catch (...)
1563 {
1564 HandleMsgQueueException(m.type);
1565 }
1566 delete type;
1567 break;
1568 }
1569
1570 case MSG_GUI_CLOSE_SELECTOR_REQUESTED:
1571 {
1572 try
1573 {
1574 App::GetGuiManager()->MainSelector.Close();
1575 }
1576 catch (...)
1577 {
1578 HandleMsgQueueException(m.type);
1579 }
1580 break;
1581 }
1582
1583 case MSG_GUI_MP_CLIENTS_REFRESH:
1584 {
1585 try
1586 {
1587 App::GetGuiManager()->MpClientList.UpdateClients();
1588 }
1589 catch (...)
1590 {
1591 HandleMsgQueueException(m.type);
1592 }
1593 break;
1594 }
1595
1596 case MSG_GUI_SHOW_MESSAGE_BOX_REQUESTED:
1597 {
1598 GUI::MessageBoxConfig* conf = static_cast<GUI::MessageBoxConfig*>(m.payload);
1599 try
1600 {
1601 App::GetGuiManager()->ShowMessageBox(*conf);
1602 }
1603 catch (...)
1604 {
1605 HandleMsgQueueException(m.type);
1606 }
1607 delete conf;
1608 break;
1609 }
1610
1611 case MSG_NET_DOWNLOAD_REPOFILE_PROGRESS:
1612 {
1613 int* percentage = static_cast<int*>(m.payload);
1614 try
1615 {
1616 if (percentage)
1617 {
1618 App::GetGuiManager()->LoadingWindow.SetProgress(*percentage, m.description, false);
1619 }
1620 }
1621 catch (...)
1622 {
1623 HandleMsgQueueException(m.type);
1624 }
1625 delete percentage;
1626 break;
1627 }
1628
1629 case MSG_NET_DOWNLOAD_REPOFILE_SUCCESS:
1630 case MSG_NET_DOWNLOAD_REPOFILE_FAILURE:
1631 {
1632 RepoFileInstallRequest* request = static_cast<RepoFileInstallRequest*>(m.payload);
1633 try
1634 {
1635 App::GetGuiManager()->LoadingWindow.SetVisible(false);
1636 App::GetGuiManager()->RepositorySelector.SetVisible(true);
1637 App::GetGuiManager()->RepositorySelector.InstallDownloadedRepoFile(m.type, request);
1638 }
1639 catch (...)
1640 {
1641 HandleMsgQueueException(m.type);
1642 }
1643 delete request;
1644 break;
1645 }
1646
1647 case MSG_GUI_REFRESH_TUNING_MENU_REQUESTED:
1648 {
1649 try
1650 {
1651 App::GetGuiManager()->TopMenubar.RefreshTuningMenu();
1652 }
1653 catch (...)
1654 {
1655 HandleMsgQueueException(m.type);
1656 }
1657 break;
1658 }
1659
1660 case MSG_GUI_SHOW_CHATBOX_REQUESTED:
1661 {
1662 try
1663 {
1664 App::GetGuiManager()->ChatBox.SetVisible(true);
1665 if (m.description != "")
1666 {
1667 App::GetGuiManager()->ChatBox.AssignBuffer(m.description);
1668 }
1669 }
1670 catch (...)
1671 {
1672 HandleMsgQueueException(m.type);
1673 }
1674 break;
1675 }
1676
1677 case MSG_GUI_OPEN_MP_SETTINGS_REQUESTED:
1678 {
1679 try
1680 {
1681 App::GetGuiManager()->GameMainMenu.SetVisible(false);
1682 App::GetGuiManager()->MultiplayerSelector.SetVisible(true);
1683 App::GetGuiManager()->MultiplayerSelector.SetSettingsTabSelected();
1684 }
1685 catch (...)
1686 {
1687 HandleMsgQueueException(m.type);
1688 }
1689 break;
1690 }
1691
1692 // -- Editing events --
1693
1694 case MSG_EDI_MODIFY_GROUNDMODEL_REQUESTED:
1695 {
1696 try
1697 {
1698 ground_model_t* modified_gm = static_cast<ground_model_t*>(m.payload);
1699 ground_model_t* live_gm = App::GetGameContext()->GetTerrain()->GetCollisions()->getGroundModelByString(modified_gm->name);
1700 *live_gm = *modified_gm; // Copy over
1701 //DO NOT `delete` the payload - it's a weak pointer, the data are owned by `RoR::Collisions`; See `enum MsgType` in file 'Application.h'.
1702 }
1703 catch (...)
1704 {
1705 HandleMsgQueueException(m.type);
1706 }
1707 break;
1708 }
1709
1710 case MSG_EDI_ENTER_TERRN_EDITOR_REQUESTED:
1711 {
1712 try
1713 {
1714 if (App::sim_state->getEnum<SimState>() != SimState::EDITOR_MODE)
1715 {
1716 App::sim_state->setVal((int)SimState::EDITOR_MODE);
1717 App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_NOTICE,
1718 _L("Entered terrain editing mode"));
1719
1720 App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_NOTICE,
1721 fmt::format(_L("Press {} or middle mouse click to select an object"),
1722 App::GetInputEngine()->getEventCommandTrimmed(EV_COMMON_ENTER_OR_EXIT_TRUCK)), "lightbulb.png");
1723
1724 }
1725 }
1726 catch (...)
1727 {
1728 HandleMsgQueueException(m.type);
1729 }
1730 break;
1731 }
1732
1733 case MSG_EDI_LEAVE_TERRN_EDITOR_REQUESTED:
1734 {
1735 try
1736 {
1737 if (App::sim_state->getEnum<SimState>() == SimState::EDITOR_MODE)
1738 {
1739 App::GetGameContext()->GetTerrain()->GetTerrainEditor()->WriteSeparateOutputFile(); // Always write 'editor_out.log'
1740 App::GetGameContext()->GetTerrain()->GetTerrainEditor()->ClearSelectedObject();
1741 App::sim_state->setVal((int)SimState::RUNNING);
1742 App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_NOTICE,
1743 _L("Left terrain editing mode"));
1744 }
1745 }
1746 catch (...)
1747 {
1748 HandleMsgQueueException(m.type);
1749 }
1750 break;
1751 }
1752
1753 case MSG_EDI_SAVE_TERRN_CHANGES_REQUESTED:
1754 {
1755 try
1756 {
1757 if (App::sim_state->getEnum<SimState>() == SimState::EDITOR_MODE
1758 && App::GetGameContext()->GetTerrain()->getCacheEntry()->resource_bundle_type == "FileSystem")
1759 {
1760 // This is a project (unzipped mod) - update TOBJ files in place
1761 App::GetGameContext()->GetTerrain()->GetTerrainEditor()->WriteEditsToTobjFiles();
1762 App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_NOTICE,
1763 _L("Terrain files have been updated"));
1764 }
1765 }
1766 catch (...)
1767 {
1768 HandleMsgQueueException(m.type);
1769 }
1770 break;
1771 }
1772
1773 case MSG_EDI_LOAD_BUNDLE_REQUESTED:
1774 {
1775 CacheEntryPtr* entry_ptr = static_cast<CacheEntryPtr*>(m.payload);
1776 try
1777 {
1778 App::GetCacheSystem()->LoadResource(*entry_ptr);
1779 TRIGGER_EVENT_ASYNC(SE_GENERIC_MODCACHE_ACTIVITY,
1780 /*ints*/ MODCACHEACTIVITY_BUNDLE_LOADED, (*entry_ptr)->number, 0, 0,
1781 /*strings*/ (*entry_ptr)->resource_group);
1782 }
1783 catch (...)
1784 {
1785 HandleMsgQueueException(m.type);
1786 }
1787 delete entry_ptr;
1788 break;
1789 }
1790
1791 case MSG_EDI_RELOAD_BUNDLE_REQUESTED:
1792 {
1793 // To reload the bundle, it's resource group must be destroyed and re-created. All actors using it must be deleted.
1794 CacheEntryPtr* entry_ptr = static_cast<CacheEntryPtr*>(m.payload);
1795 try
1796 {
1797 bool all_clear = true;
1798 for (ActorPtr& actor: App::GetGameContext()->GetActorManager()->GetActors())
1799 {
1800 if (actor->GetGfxActor()->GetResourceGroup() == (*entry_ptr)->resource_group)
1801 {
1802 App::GetGameContext()->PushMessage(Message(MSG_SIM_DELETE_ACTOR_REQUESTED, static_cast<void*>(new ActorPtr(actor))));
1803 all_clear = false;
1804 }
1805 }
1806
1807 if (all_clear)
1808 {
1809 // Nobody uses the RG anymore -> destroy and re-create it.
1810 App::GetCacheSystem()->ReLoadResource(*entry_ptr);
1811
1812 TRIGGER_EVENT_ASYNC(SE_GENERIC_MODCACHE_ACTIVITY,
1813 /*ints*/ MODCACHEACTIVITY_BUNDLE_RELOADED, (*entry_ptr)->number, 0, 0,
1814 /*strings*/ (*entry_ptr)->resource_group);
1815
1816 delete entry_ptr;
1817 }
1818 else
1819 {
1820 // Re-post the same message again so that it's message chain is executed later.
1821 App::GetGameContext()->PushMessage(m);
1822 failed_m = true;
1823 }
1824 }
1825 catch (...)
1826 {
1827 HandleMsgQueueException(m.type);
1828 }
1829 break;
1830 }
1831
1832 case MSG_EDI_UNLOAD_BUNDLE_REQUESTED:
1833 {
1834 // Unloading bundle means the resource group will be destroyed. All actors using it must be deleted.
1835 CacheEntryPtr* entry_ptr = static_cast<CacheEntryPtr*>(m.payload);
1836 try
1837 {
1838 bool all_clear = true;
1839 for (ActorPtr& actor: App::GetGameContext()->GetActorManager()->GetActors())
1840 {
1841 ROR_ASSERT(actor);
1842 ROR_ASSERT(actor->getUsedActorEntry());
1843 const bool uses_actor_rg = (actor->getUsedActorEntry()->resource_group == (*entry_ptr)->resource_group);
1844 // Skin entry is optional.
1845 const bool uses_skin_rg = (actor->getUsedSkinEntry() && actor->getUsedSkinEntry()->resource_group == (*entry_ptr)->resource_group);
1846 // Look for addonparts, too.
1847 const bool uses_addonpart_rg = std::find_if(
1848 actor->getUsedAddonpartEntries().begin(),
1849 actor->getUsedAddonpartEntries().end(),
1850 [entry_ptr](const CacheEntryPtr& ap_entry)
1851 {
1852 return ap_entry->resource_group == (*entry_ptr)->resource_group;
1853 }) != actor->getUsedAddonpartEntries().end();
1854 // Finally look for assetpacks
1855 const bool uses_assetpack_rg = std::find_if(
1856 actor->getUsedAssetpackEntries().begin(),
1857 actor->getUsedAssetpackEntries().end(),
1858 [entry_ptr](const CacheEntryPtr& ap_entry)
1859 {
1860 return ap_entry->resource_group == (*entry_ptr)->resource_group;
1861 }) != actor->getUsedAssetpackEntries().end();
1862 if (uses_actor_rg || uses_skin_rg || uses_addonpart_rg || uses_assetpack_rg)
1863 {
1864 App::GetGameContext()->PushMessage(Message(MSG_SIM_DELETE_ACTOR_REQUESTED, static_cast<void*>(new ActorPtr(actor))));
1865 all_clear = false;
1866 }
1867 }
1868 // Check terrain, too! Could have been uninstalled via RepoUI.
1869 if (App::GetGameContext()->GetTerrain()
1870 && App::GetGameContext()->GetTerrain()->getCacheEntry()->resource_group == (*entry_ptr)->resource_group)
1871 {
1872 if (App::mp_state->getEnum<MpState>() == MpState::CONNECTED)
1873 {
1874 App::GetGameContext()->PushMessage(Message(MSG_NET_DISCONNECT_REQUESTED));
1875 }
1876 else
1877 {
1878 App::GetGameContext()->PushMessage(Message(MSG_SIM_UNLOAD_TERRN_REQUESTED));
1879 }
1880 all_clear = false;
1881 }
1882
1883 if (all_clear)
1884 {
1885 // Nobody uses the RG anymore -> destroy it.
1886 App::GetCacheSystem()->UnLoadResource(*entry_ptr);
1887
1888 TRIGGER_EVENT_ASYNC(SE_GENERIC_MODCACHE_ACTIVITY,
1889 /*ints*/ MODCACHEACTIVITY_BUNDLE_UNLOADED, (*entry_ptr)->number, 0, 0);
1890
1891 delete entry_ptr;
1892 }
1893 else
1894 {
1895 // Re-post the same message again so that it's message chain is executed later.
1896 App::GetGameContext()->PushMessage(m);
1897 failed_m = true;
1898 }
1899 }
1900 catch (...)
1901 {
1902 HandleMsgQueueException(m.type);
1903 }
1904
1905 break;
1906 }
1907
1908 case MSG_EDI_DELETE_BUNDLE_REQUESTED:
1909 {
1910 try
1911 {
1912 std::string bundle_filepath;
1913 if (!App::GetCacheSystem()->IsRepoFileInstalled(m.description, /*[out]*/bundle_filepath))
1914 {
1915 break; // nothing to do
1916 }
1917
1918 // make sure the bundle is unloaded
1919 bool all_clear = true;
1920 for (const CacheEntryPtr& entry: App::GetCacheSystem()->GetEntries())
1921 {
1922 if (entry->resource_bundle_path == bundle_filepath && entry->resource_group != "")
1923 {
1924 App::GetGameContext()->PushMessage(Message(MSG_EDI_UNLOAD_BUNDLE_REQUESTED, static_cast<void*>(new CacheEntryPtr(entry))));
1925 all_clear = false;
1926 }
1927 }
1928
1929 if (all_clear)
1930 {
1931 std::string bundle_basename, bundle_dirpath;
1932 Ogre::StringUtil::splitFilename(bundle_filepath, bundle_basename, bundle_dirpath);
1933 App::GetCacheSystem()->DeleteResourceBundleByFilename(bundle_basename);
1934 App::GetGuiManager()->RepositorySelector.NotifyRepoFileUninstalled(bundle_basename);
1935 }
1936 else
1937 {
1938 // Re-post the same message again so that it's message chain is executed later.
1939 App::GetGameContext()->PushMessage(m);
1940 failed_m = true;
1941 }
1942 }
1943 catch (...)
1944 {
1945 HandleMsgQueueException(m.type);
1946 }
1947 break;
1948 }
1949
1950 case MSG_EDI_CREATE_PROJECT_REQUESTED:
1951 {
1952 CreateProjectRequest* request = static_cast<CreateProjectRequest*>(m.payload);
1953 try
1954 {
1955 if (!App::GetCacheSystem()->CreateProject(request))
1956 {
1957 failed_m = true;
1958 }
1959 }
1960 catch (...)
1961 {
1962 HandleMsgQueueException(m.type);
1963 }
1964 delete request;
1965 break;
1966 }
1967
1968 case MSG_EDI_MODIFY_PROJECT_REQUESTED:
1969 {
1970 ModifyProjectRequest* request = static_cast<ModifyProjectRequest*>(m.payload);
1971 try
1972 {
1973 if (App::mp_state->getEnum<MpState>() != MpState::CONNECTED) // Do not allow tuning in multiplayer
1974 {
1975 App::GetCacheSystem()->ModifyProject(request);
1976 }
1977 }
1978 catch (...)
1979 {
1980 HandleMsgQueueException(m.type);
1981 }
1982 delete request;
1983 break;
1984 }
1985
1986 case MSG_EDI_DELETE_PROJECT_REQUESTED:
1987 {
1988 CacheEntryPtr* entry_ptr = static_cast<CacheEntryPtr*>(m.payload);
1989 try
1990 {
1991 App::GetCacheSystem()->DeleteProject(*entry_ptr);
1992 }
1993 catch (...)
1994 {
1995 HandleMsgQueueException(m.type);
1996 }
1997 delete entry_ptr;
1998 break;
1999 }
2000
2001 case MSG_EDI_ADD_FREEBEAMGFX_REQUESTED:
2002 {
2003 FreeBeamGfxRequest* request = static_cast<FreeBeamGfxRequest*>(m.payload);
2004 try
2005 {
2006 App::GetGfxScene()->AddFreeBeamGfx(request);
2007 }
2008 catch (...)
2009 {
2010 HandleMsgQueueException(m.type);
2011 }
2012 delete request;
2013 break;
2014 }
2015
2016 case MSG_EDI_MODIFY_FREEBEAMGFX_REQUESTED:
2017 {
2018 FreeBeamGfxRequest* request = static_cast<FreeBeamGfxRequest*>(m.payload);
2019 try
2020 {
2021 App::GetGfxScene()->ModifyFreeBeamGfx(request);
2022 }
2023 catch (...)
2024 {
2025 HandleMsgQueueException(m.type);
2026 }
2027 delete request;
2028 break;
2029 }
2030
2031 case MSG_EDI_DELETE_FREEBEAMGFX_REQUESTED:
2032 {
2033 FreeBeamGfxID_t* request = static_cast<FreeBeamGfxID_t*>(m.payload);
2034 try
2035 {
2036 App::GetGfxScene()->RemoveFreeBeamGfx(*request);
2037 }
2038 catch (...)
2039 {
2040 HandleMsgQueueException(m.type);
2041 }
2042 delete request;
2043 break;
2044 }
2045
2046 default:;
2047 }
2048
2049 // Process chained messages
2050 if (!failed_m)
2051 {
2052 for (Message& chained_msg: m.chain)
2053 {
2054 App::GetGameContext()->PushMessage(chained_msg);
2055 }
2056 }
2057
2058 } // Game events block
2059 OgreProfileEnd("RoR message queue");
2060
2061 // Check FPS limit
2062 if (App::gfx_fps_limit->getInt() > 0)
2063 {
2064 OgreProfile("RoR FPS limiter");
2065 const float min_frame_time = 1.0f / Ogre::Math::Clamp(App::gfx_fps_limit->getInt(), 5, 240);
2066 float dt = std::chrono::duration<float>(std::chrono::high_resolution_clock::now() - start_time).count();
2067 while (dt < min_frame_time)
2068 {
2069 dt = std::chrono::duration<float>(std::chrono::high_resolution_clock::now() - start_time).count();
2070 }
2071 } // Check FPS limit block
2072
2073 OgreProfileBegin("RoR Main Loop");
2074 // Calculate delta time
2075 const auto now = std::chrono::high_resolution_clock::now();
2076 const float dt = std::chrono::duration<float>(now - start_time).count();
2077 start_time = now;
2078
2079#ifdef USE_SOCKETW
2080 // Process incoming network traffic
2081 if (App::mp_state->getEnum<MpState>() == MpState::CONNECTED)
2082 {
2083 std::vector<RoR::NetRecvPacket> packets = App::GetNetwork()->GetIncomingStreamData();
2084 if (!packets.empty())
2085 {
2087 if (App::app_state->getEnum<AppState>() == AppState::SIMULATION)
2088 {
2089 App::GetGameContext()->GetActorManager()->HandleActorStreamData(packets);
2090 App::GetGameContext()->GetCharacterFactory()->handleStreamData(packets); // Update characters last (or else beam coupling might fail)
2091 }
2092 }
2093 }
2094#endif // USE_SOCKETW
2095
2096 // Set arcade controls and hydro coupling settings for player actors
2097 // Default to false for other actors.
2098 if (App::app_state->getEnum<AppState>() == AppState::SIMULATION)
2099 {
2100 ActorPtr player_actor = App::GetGameContext()->GetPlayerActor();
2101 for (ActorPtr actor : App::GetGameContext()->GetActorManager()->GetActors())
2102 {
2103 bool is_player_actor = player_actor != nullptr && actor->getInstanceId() == player_actor->getInstanceId();
2104 actor->ar_arcade_controls = is_player_actor && App::io_arcade_controls->getBool();
2105 actor->ar_hydro_speed_coupling_enabled = is_player_actor && App::io_hydro_coupling->getBool();
2106 }
2107 }
2108
2109 // Process input events
2110 OgreProfileBegin("Input processing");
2111 if (dt != 0.f)
2112 {
2113 App::GetInputEngine()->Capture();
2114 App::GetInputEngine()->updateKeyBounces(dt);
2115
2116 if (!App::GetGuiManager()->GameControls.IsInteractiveKeyBindingActive())
2117 {
2118 if (!App::GetGuiManager()->MainSelector.IsVisible() && !App::GetGuiManager()->MultiplayerSelector.IsVisible() &&
2119 !App::GetGuiManager()->GameSettings.IsVisible() && !App::GetGuiManager()->GameControls.IsVisible() &&
2120 !App::GetGuiManager()->GameAbout.IsVisible() && !App::GetGuiManager()->RepositorySelector.IsVisible())
2121 {
2122 App::GetGameContext()->HandleSavegameHotkeys();
2123 }
2124 App::GetGameContext()->UpdateGlobalInputEvents();
2125 App::GetGuiManager()->UpdateInputEvents(dt);
2126
2127 if (App::app_state->getEnum<AppState>() == AppState::SIMULATION)
2128 {
2129 if (App::sim_state->getEnum<SimState>() == SimState::EDITOR_MODE)
2130 {
2131 App::GetGameContext()->UpdateSkyInputEvents(dt);
2132 App::GetGameContext()->GetTerrain()->GetTerrainEditor()->UpdateInputEvents(dt);
2133 }
2134 else
2135 {
2136 App::GetGameContext()->GetCharacterFactory()->Update(dt); // Character MUST be updated before CameraManager, otherwise camera position is always 1 frame behind the character position, causing stuttering.
2137 }
2138 App::GetCameraManager()->UpdateInputEvents(dt);
2139 App::GetOverlayWrapper()->update(dt);
2140 App::GetGameContext()->GetRepairMode().UpdateInputEvents(dt);
2141 App::GetGameContext()->GetActorManager()->UpdateInputEvents(dt);
2142 if (App::sim_state->getEnum<SimState>() == SimState::RUNNING)
2143 {
2144 if (App::GetCameraManager()->GetCurrentBehavior() != CameraManager::CAMERA_BEHAVIOR_FREE)
2145 {
2146 App::GetGameContext()->UpdateSimInputEvents(dt);
2147 }
2148
2149 App::GetGameContext()->UpdateSkyInputEvents(dt);
2150 for (ActorPtr actor : App::GetGameContext()->GetActorManager()->GetActors())
2151 {
2152 if (actor->ar_state != ActorState::NETWORKED_OK)
2153 {
2154 App::GetGameContext()->UpdateCommonInputEvents(dt, actor);
2155 if (actor->ar_state != ActorState::LOCAL_REPLAY)
2156 {
2157 if (actor->ar_driveable == TRUCK)
2158 {
2159 App::GetGameContext()->UpdateTruckInputEvents(dt, actor);
2160 }
2161 if (actor->ar_driveable == AIRPLANE)
2162 {
2163 App::GetGameContext()->UpdateAirplaneInputEvents(dt, actor);
2164 }
2165 if (actor->ar_driveable == BOAT)
2166 {
2167 App::GetGameContext()->UpdateBoatInputEvents(dt, actor);
2168 }
2169 }
2170
2171 actor->UpdatePropAnimInputEvents();
2172 for (ActorPtr linked_actor : actor->ar_linked_actors)
2173 {
2174 linked_actor->UpdatePropAnimInputEvents();
2175 }
2176 }
2177 }
2178 }
2179 } // app state SIMULATION
2180 } // interactive key binding mode
2181 } // dt != 0
2182 OgreProfileEnd("Input processing");
2183
2184 // Update OutGauge device
2185 if (App::io_outgauge_mode->getInt() > 0)
2186 {
2187 OgreProfile("OutGauge");
2188 App::GetOutGauge()->Update(dt, App::GetGameContext()->GetPlayerActor());
2189 }
2190
2191 // Early GUI updates which require halted physics
2192 App::GetGuiManager()->NewImGuiFrame(dt);
2193 if (App::app_state->getEnum<AppState>() == AppState::SIMULATION)
2194 {
2195 OgreProfile("Scene and GUI");
2196 App::GetGuiManager()->DrawSimulationGui(dt);
2197 for (ActorPtr& actor : App::GetGameContext()->GetActorManager()->GetActors())
2198 {
2199 actor->GetGfxActor()->UpdateDebugView();
2200 }
2201 if (App::GetGameContext()->GetPlayerActor())
2202 {
2203 App::GetGuiManager()->VehicleInfoTPanel.UpdateStats(dt, App::GetGameContext()->GetPlayerActor());
2204 if (App::GetGuiManager()->FrictionSettings.IsVisible())
2205 {
2206 App::GetGuiManager()->FrictionSettings.setActiveCol(App::GetGameContext()->GetPlayerActor()->ar_last_fuzzy_ground_model);
2207 }
2208 }
2209 }
2210
2211#ifdef USE_MUMBLE
2212 if (App::GetMumble())
2213 {
2214 OgreProfile("Mumble");
2215 App::GetMumble()->Update(); // 3d voice over network
2216 }
2217#endif // USE_MUMBLE
2218
2219#ifdef USE_OPENAL
2220 OgreProfileBegin("3D audio");
2221 App::GetSoundScriptManager()->update(dt); // update 3d audio listener position
2222 OgreProfileEnd("3D audio");
2223#endif // USE_OPENAL
2224
2225#ifdef USE_ANGELSCRIPT
2226 OgreProfileBegin("Scripting");
2227 App::GetScriptEngine()->framestep(dt);
2228 OgreProfileEnd("Scripting");
2229#endif // USE_ANGELSCRIPT
2230
2231 if (App::io_ffb_enabled->getBool() &&
2232 App::sim_state->getEnum<SimState>() == SimState::RUNNING)
2233 {
2234 OgreProfile("Force Feedback");
2235 App::GetAppContext()->GetForceFeedback().Update();
2236 }
2237
2238 OgreProfileBegin("Simulation");
2239 if (App::sim_state->getEnum<SimState>() == SimState::RUNNING)
2240 {
2241 App::GetGameContext()->GetSceneMouse().UpdateSimulation();
2242 }
2243
2244 // Create snapshot of simulation state for Gfx/GUI updates
2245 if (App::sim_state->getEnum<SimState>() == SimState::RUNNING || // Obviously
2246 App::sim_state->getEnum<SimState>() == SimState::PAUSED || // Avoid dangling (DISPOSED) pointers in simbuffer
2247 App::sim_state->getEnum<SimState>() == SimState::EDITOR_MODE) // Needed for character movement
2248 {
2249 App::GetGfxScene()->BufferSimulationData();
2250 }
2251
2252 // Calculate elapsed simulation time (taking simulation speed and pause into account)
2253 float dt_sim = 0.f;
2254 if (App::sim_state->getEnum<SimState>() == SimState::RUNNING && !App::GetGameContext()->GetActorManager()->IsSimulationPaused())
2255 {
2256 dt_sim = dt * App::GetGameContext()->GetActorManager()->GetSimulationSpeed();
2257 }
2258
2259 // Advance simulation
2260 if (App::sim_state->getEnum<SimState>() == SimState::RUNNING)
2261 {
2262 if (App::GetGameContext()->GetTerrain()->getWater())
2263 {
2264 App::GetGameContext()->GetTerrain()->getWater()->FrameStepWaveField(dt_sim);
2265 }
2266 App::GetGameContext()->UpdateActors(); // *** Start new physics tasks. No reading from Actor N/B beyond this point.
2267 }
2268 OgreProfileEnd("Simulation");
2269
2270 // Scene and GUI updates
2271 OgreProfileBegin("Scene and GUI"); // Adds up to existing profile
2272 if (App::app_state->getEnum<AppState>() == AppState::MAIN_MENU)
2273 {
2274 App::GetGuiManager()->DrawMainMenuGui();
2275 }
2276 else if (App::app_state->getEnum<AppState>() == AppState::SIMULATION)
2277 {
2278 App::GetGfxScene()->UpdateScene(dt_sim); // Draws GUI as well
2279 }
2280 OgreProfileEnd("Scene and GUI");
2281
2282 OgreProfileEnd("RoR Main Loop");
2283
2284 // Render!
2285 Ogre::RenderWindow* render_window = RoR::App::GetAppContext()->GetRenderWindow();
2286 if (render_window->isClosed())
2287 {
2288 App::GetGameContext()->PushMessage(Message(MSG_APP_SHUTDOWN_REQUESTED));
2289 }
2290 else
2291 {
2292 App::GetAppContext()->GetOgreRoot()->renderOneFrame();
2293 if (!render_window->isActive() && render_window->isVisible())
2294 {
2295 render_window->update(); // update even when in background !
2296 }
2297 } // Render block
2298
2299 App::GetGuiManager()->ApplyGuiCaptureKeyboard();
2300
2301 App::GetGuiManager()->UpdateMouseCursorVisibility();
2302
2303 } // End of main rendering/input loop
2304
2305#ifndef _DEBUG
2306 }
2307 catch (Ogre::Exception& e)
2308 {
2309 LOG(e.getFullDescription());
2310 ErrorUtils::ShowError(_L("An exception has occured!"), e.getFullDescription());
2311 }
2312 catch (std::runtime_error& e)
2313 {
2314 LOG(e.what());
2315 ErrorUtils::ShowError(_L("An exception (std::runtime_error) has occured!"), e.what());
2316 }
2317#endif
2318
2319 return 0;
2320}
2321
2322#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
2323INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
2324{
2325 return main(__argc, __argv);
2326}
2327#endif
2328
2329#ifdef __cplusplus
2330}
2331#endif
System integration layer; inspired by OgreBites::ApplicationContext.
Central state/object manager and communications hub.
#define ROR_ASSERT(_EXPR)
Definition Application.h:40
void LOG(const char *msg)
Legacy alias - formerly a macro.
#define BITMASK_IS_1(VAR, FLAGS)
Definition BitFlags.h:14
A database of user-installed content alias 'mods' (vehicles, terrains...)
#define _L
Race direction arrow and text info (using OGRE Overlay)
Game state manager and message-queue provider.
Handles controller inputs from player.
#define _LC(ctx, str)
Definition Language.h:38
Platform-specific utilities. We use narrow UTF-8 encoded strings as paths. Inspired by http://utf8eve...
#define SOUND_KILL(_ACTOR_, _TRIG_)
#define SOUND_START(_ACTOR_, _TRIG_)
Ogre::RenderWindow * GetRenderWindow()
Definition AppContext.h:70
Wrapper for classic c-string (local buffer) Refresher: strlen() excludes '\0' terminator; strncat() A...
Definition Str.h:36
const char * ToCStr() const
Definition Str.h:46
ScriptCategory
Note: Either of these can be loaded from script using game.pushMessage(MSG_APP_LOAD_SCRIPT_REQUESTED....
int main(int argc, char *argv[])
Definition main.cpp:75
INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
Definition main.cpp:2323
AppContext * GetAppContext()
void SendStreamSetup()
void HandleStreamData(std::vector< RoR::NetRecvPacket > packet_buffer)
std::vector< MpServerInfo > MpServerInfoVec
void Log(const char *msg)
The ultimate, application-wide logging function. Adds a line (any length) in 'RoR....
LoaderType
< Search mode for ModCache::Query() & Operation mode for GUI::MainSelector
int FreeForceID_t
Unique sequentially generated ID of FreeForce; use ActorManager::GetFreeForceNextId().
void LogFormat(const char *format,...)
Improved logging utility. Uses fixed 2Kb buffer.
int ScriptUnitID_t
Unique sequentially generated ID of a loaded and running scriptin session. Use ScriptEngine::getScrip...
int FreeBeamGfxID_t
Index into GfxScene::m_gfx_freebeams, use RoR::FREEBEAMGFXID_INVALID as empty value.
@ PEEROPT_HIDE_ACTORS
Spawn actors hidden and immediatelly hide existing actors.
Definition RoRnet.h:133
@ PEEROPT_MUTE_ACTORS
Spawn actors muted and immediatelly mute existing actors.
Definition RoRnet.h:132
@ PEEROPT_MUTE_CHAT
CHAT and PRIVCHAT messages will not be allowed through.
Definition RoRnet.h:131
@ AUTH_RANKED
ranked status
Definition RoRnet.h:79
static int ShowError(const std::string &title, const std::string &message)
shows a simple error message box
Estabilishing a physics linkage between 2 actors modifies a global linkage table and triggers immedia...
Definition SimData.h:909
ActorLinkingRequestType alr_type
Definition SimData.h:911
ActorInstanceID_t alr_actor_instance_id
Definition SimData.h:910
NodeNum_t alr_hook_mousenode
Definition SimData.h:914
Creates subdirectory in 'My Games\Rigs of Rods\projects', pre-populates it with files and adds modcac...
Used by MSG_EDI_[ADD/MODIFY]_FREEBEAMGFX_REQUESTED; tailored for use with AngelScript thru GameScript...
Definition GfxData.h:293
Common for ADD and MODIFY requests; tailored for use with AngelScript thru GameScript::pushMessage().
Definition SimData.h:784
std::string lsr_filename
Load from resource ('.as' file or '.gadget' file); If buffer is supplied, use this as display name on...
ScriptCategory lsr_category
std::string lsr_buffer
Load from memory buffer.
ActorInstanceID_t lsr_associated_actor
For ScriptCategory::ACTOR.
Unified game event system - all requests and state changes are reported using a message.
Definition GameContext.h:52
MsgType type
Definition GameContext.h:57
std::string description
Definition GameContext.h:58
void * payload
Definition GameContext.h:59
std::vector< Message > chain
Posted after the message is processed.
Definition GameContext.h:60
Payload of MSG_NET_{ADD/REMOVE}_PEEROPTIONS_REQUESTED.
Definition Network.h:105
int por_uid
RoRnet unique user ID.
Definition Network.h:106
BitMask_t por_peeropts
See RoRnet::PeerOptions.
Definition Network.h:107
Payload for MSG_NET_INSTALL_REPOFILE_REQUEST message - also used for update (overwrites existing)
eventsource_t * eventsource
Args for eventCallbackEx() queued via MSG_SIM_SCRIPT_EVENT_TRIGGERED See descriptions at enum RoR::sc...
int es_script_handler
AngelScript function ID.
Definition Collisions.h:45
Surface friction properties.
Definition SimData.h:704
int32_t authstatus
auth status set by server: AUTH_*
Definition RoRnet.h:192
char clientname[10]
the name and version of the client. For exmaple: "ror" or "gamebot"
Definition RoRnet.h:200
char language[10]
user's language. For example "de-DE" or "en-US"
Definition RoRnet.h:199
char username[RORNET_MAX_USERNAME_LEN]
the nickname of the user (UTF-8)
Definition RoRnet.h:196
char sessiontype[10]
the requested session type. For example "normal", "bot", "rcon"
Definition RoRnet.h:203
uint32_t uniqueid
user unique id
Definition RoRnet.h:191
char clientversion[25]
a version number of the client. For example 1 for RoR 0.35
Definition RoRnet.h:201