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