Show system_info.txt, console.txt, json file, userreport_hw.txt paths in the terminal...
[0ad.git] / source / ps / GameSetup / GameSetup.cpp
blobeeafa0978c5c3f0e37a3f97b337fc6951ec8da59
1 /* Copyright (C) 2022 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "ps/GameSetup/GameSetup.h"
22 #include "graphics/GameView.h"
23 #include "graphics/MapReader.h"
24 #include "graphics/TerrainTextureManager.h"
25 #include "gui/CGUI.h"
26 #include "gui/GUIManager.h"
27 #include "i18n/L10n.h"
28 #include "lib/app_hooks.h"
29 #include "lib/config2.h"
30 #include "lib/external_libraries/libsdl.h"
31 #include "lib/file/common/file_stats.h"
32 #include "lib/input.h"
33 #include "lib/timer.h"
34 #include "lobby/IXmppClient.h"
35 #include "network/NetServer.h"
36 #include "network/NetClient.h"
37 #include "network/NetMessage.h"
38 #include "network/NetMessages.h"
39 #include "ps/CConsole.h"
40 #include "ps/CLogger.h"
41 #include "ps/ConfigDB.h"
42 #include "ps/Filesystem.h"
43 #include "ps/Game.h"
44 #include "ps/GameSetup/Atlas.h"
45 #include "ps/GameSetup/Paths.h"
46 #include "ps/GameSetup/Config.h"
47 #include "ps/GameSetup/CmdLineArgs.h"
48 #include "ps/GameSetup/HWDetect.h"
49 #include "ps/Globals.h"
50 #include "ps/GUID.h"
51 #include "ps/Hotkey.h"
52 #include "ps/Joystick.h"
53 #include "ps/Loader.h"
54 #include "ps/Mod.h"
55 #include "ps/ModIo.h"
56 #include "ps/Profile.h"
57 #include "ps/ProfileViewer.h"
58 #include "ps/Profiler2.h"
59 #include "ps/Pyrogenesis.h" // psSetLogDir
60 #include "ps/scripting/JSInterface_Console.h"
61 #include "ps/TouchInput.h"
62 #include "ps/UserReport.h"
63 #include "ps/Util.h"
64 #include "ps/VideoMode.h"
65 #include "ps/VisualReplay.h"
66 #include "ps/World.h"
67 #include "renderer/Renderer.h"
68 #include "renderer/SceneRenderer.h"
69 #include "renderer/VertexBufferManager.h"
70 #include "scriptinterface/FunctionWrapper.h"
71 #include "scriptinterface/JSON.h"
72 #include "scriptinterface/ScriptInterface.h"
73 #include "scriptinterface/ScriptStats.h"
74 #include "scriptinterface/ScriptContext.h"
75 #include "scriptinterface/ScriptConversions.h"
76 #include "simulation2/Simulation2.h"
77 #include "soundmanager/scripting/JSInterface_Sound.h"
78 #include "soundmanager/ISoundManager.h"
79 #include "tools/atlas/GameInterface/GameLoop.h"
81 #if !(OS_WIN || OS_MACOSX || OS_ANDROID) // assume all other platforms use X11 for wxWidgets
82 #define MUST_INIT_X11 1
83 #include <X11/Xlib.h>
84 #else
85 #define MUST_INIT_X11 0
86 #endif
88 extern void RestartEngine();
90 #include <fstream>
91 #include <iostream>
93 #include <boost/algorithm/string/classification.hpp>
94 #include <boost/algorithm/string/join.hpp>
95 #include <boost/algorithm/string/split.hpp>
97 ERROR_GROUP(System);
98 ERROR_TYPE(System, SDLInitFailed);
99 ERROR_TYPE(System, VmodeFailed);
100 ERROR_TYPE(System, RequiredExtensionsMissing);
102 thread_local std::shared_ptr<ScriptContext> g_ScriptContext;
104 bool g_InDevelopmentCopy;
105 bool g_CheckedIfInDevelopmentCopy = false;
107 ErrorReactionInternal psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED(flags))
109 // If we're fullscreen, then sometimes (at least on some particular drivers on Linux)
110 // displaying the error dialog hangs the desktop since the dialog box is behind the
111 // fullscreen window. So we just force the game to windowed mode before displaying the dialog.
112 // (But only if we're in the main thread, and not if we're being reentrant.)
113 if (Threading::IsMainThread())
115 static bool reentering = false;
116 if (!reentering)
118 reentering = true;
119 g_VideoMode.SetFullscreen(false);
120 reentering = false;
124 // We don't actually implement the error display here, so return appropriately
125 return ERI_NOT_IMPLEMENTED;
128 void MountMods(const Paths& paths, const std::vector<CStr>& mods)
130 OsPath modPath = paths.RData()/"mods";
131 OsPath modUserPath = paths.UserData()/"mods";
133 size_t userFlags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE;
134 size_t baseFlags = userFlags|VFS_MOUNT_MUST_EXIST;
135 size_t priority = 0;
136 for (size_t i = 0; i < mods.size(); ++i)
138 priority = i + 1; // Mods are higher priority than regular mountings, which default to priority 0
140 OsPath modName(mods[i]);
141 // Only mount mods from the user path if they don't exist in the 'rdata' path.
142 if (DirectoryExists(modPath / modName / ""))
143 g_VFS->Mount(L"", modPath / modName / "", baseFlags, priority);
144 else
145 g_VFS->Mount(L"", modUserPath / modName / "", userFlags, priority);
148 // Mount the user mod last. In dev copy, mount it with a low priority. Otherwise, make it writable.
149 g_VFS->Mount(L"", modUserPath / "user" / "", userFlags, InDevelopmentCopy() ? 0 : priority + 1);
152 static void InitVfs(const CmdLineArgs& args, int flags)
154 TIMER(L"InitVfs");
156 const bool setup_error = (flags & INIT_HAVE_DISPLAY_ERROR) == 0;
158 const Paths paths(args);
160 OsPath logs(paths.Logs());
161 CreateDirectories(logs, 0700);
163 psSetLogDir(logs);
164 // desired location for crashlog is now known. update AppHooks ASAP
165 // (particularly before the following error-prone operations):
166 AppHooks hooks = {0};
167 hooks.bundle_logs = psBundleLogs;
168 hooks.get_log_dir = psLogDir;
169 if (setup_error)
170 hooks.display_error = psDisplayError;
171 app_hooks_update(&hooks);
173 g_VFS = CreateVfs();
175 const OsPath readonlyConfig = paths.RData()/"config"/"";
177 // Mount these dirs with highest priority so that mods can't overwrite them.
178 g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE, VFS_MAX_PRIORITY); // (adding XMBs to archive speeds up subsequent reads)
179 if (readonlyConfig != paths.Config())
180 g_VFS->Mount(L"config/", readonlyConfig, 0, VFS_MAX_PRIORITY-1);
181 g_VFS->Mount(L"config/", paths.Config(), 0, VFS_MAX_PRIORITY);
182 g_VFS->Mount(L"screenshots/", paths.UserData()/"screenshots"/"", 0, VFS_MAX_PRIORITY);
183 g_VFS->Mount(L"saves/", paths.UserData()/"saves"/"", VFS_MOUNT_WATCH, VFS_MAX_PRIORITY);
185 // Engine localization files (regular priority, these can be overwritten).
186 g_VFS->Mount(L"l10n/", paths.RData()/"l10n"/"");
188 // Mods will be mounted later.
190 // note: don't bother with g_VFS->TextRepresentation - directories
191 // haven't yet been populated and are empty.
195 static void InitPs(bool setup_gui, const CStrW& gui_page, ScriptInterface* srcScriptInterface, JS::HandleValue initData)
198 // console
199 TIMER(L"ps_console");
201 g_Console->Init();
204 // hotkeys
206 TIMER(L"ps_lang_hotkeys");
207 LoadHotkeys(g_ConfigDB);
210 if (!setup_gui)
212 // We do actually need *some* kind of GUI loaded, so use the
213 // (currently empty) Atlas one
214 g_GUI->SwitchPage(L"page_atlas.xml", srcScriptInterface, initData);
215 return;
218 // GUI uses VFS, so this must come after VFS init.
219 g_GUI->SwitchPage(gui_page, srcScriptInterface, initData);
222 void InitPsAutostart(bool networked, JS::HandleValue attrs)
224 // The GUI has not been initialized yet, so use the simulation scriptinterface for this variable
225 ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
226 ScriptRequest rq(scriptInterface);
228 JS::RootedValue playerAssignments(rq.cx);
229 Script::CreateObject(rq, &playerAssignments);
231 if (!networked)
233 JS::RootedValue localPlayer(rq.cx);
234 Script::CreateObject(rq, &localPlayer, "player", g_Game->GetPlayerID());
235 Script::SetProperty(rq, playerAssignments, "local", localPlayer);
238 JS::RootedValue sessionInitData(rq.cx);
240 Script::CreateObject(
242 &sessionInitData,
243 "attribs", attrs,
244 "playerAssignments", playerAssignments);
246 InitPs(true, L"page_loading.xml", &scriptInterface, sessionInitData);
250 void InitInput()
252 g_Joystick.Initialise();
254 // register input handlers
255 // This stack is constructed so the first added, will be the last
256 // one called. This is important, because each of the handlers
257 // has the potential to block events to go further down
258 // in the chain. I.e. the last one in the list added, is the
259 // only handler that can block all messages before they are
260 // processed.
261 in_add_handler(game_view_handler);
263 in_add_handler(CProfileViewer::InputThunk);
265 in_add_handler(HotkeyInputActualHandler);
267 // gui_handler needs to be registered after (i.e. called before!) the
268 // hotkey handler so that input boxes can be typed in without
269 // setting off hotkeys.
270 in_add_handler(gui_handler);
271 // Likewise for the console.
272 in_add_handler(conInputHandler);
274 in_add_handler(touch_input_handler);
276 // Should be called after scancode map update (i.e. after the global input, but before UI).
277 // This never blocks the event, but it does some processing necessary for hotkeys,
278 // which are triggered later down the input chain.
279 // (by calling this before the UI, we can use 'EventWouldTriggerHotkey' in the UI).
280 in_add_handler(HotkeyInputPrepHandler);
282 // These two must be called first (i.e. pushed last)
283 // GlobalsInputHandler deals with some important global state,
284 // such as which scancodes are being pressed, mouse buttons pressed, etc.
285 // while HotkeyStateChange updates the map of active hotkeys.
286 in_add_handler(GlobalsInputHandler);
287 in_add_handler(HotkeyStateChange);
291 static void ShutdownPs()
293 SAFE_DELETE(g_GUI);
295 UnloadHotkeys();
298 static void InitSDL()
300 #if OS_LINUX
301 // In fullscreen mode when SDL is compiled with DGA support, the mouse
302 // sensitivity often appears to be unusably wrong (typically too low).
303 // (This seems to be reported almost exclusively on Ubuntu, but can be
304 // reproduced on Gentoo after explicitly enabling DGA.)
305 // Disabling the DGA mouse appears to fix that problem, and doesn't
306 // have any obvious negative effects.
307 setenv("SDL_VIDEO_X11_DGAMOUSE", "0", 0);
308 #endif
310 if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0)
312 LOGERROR("SDL library initialization failed: %s", SDL_GetError());
313 throw PSERROR_System_SDLInitFailed();
315 atexit(SDL_Quit);
317 // Text input is active by default, disable it until it is actually needed.
318 SDL_StopTextInput();
320 #if SDL_VERSION_ATLEAST(2, 0, 9)
321 // SDL2 >= 2.0.9 defaults to 32 pixels (to support touch screens) but that can break our double-clicking.
322 SDL_SetHint(SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS, "1");
323 #endif
325 #if SDL_VERSION_ATLEAST(2, 0, 14) && OS_WIN
326 // SDL2 >= 2.0.14 Before SDL 2.0.14, this defaulted to true. In 2.0.14 they switched to false
327 // breaking the behavior on Windows.
328 // https://github.com/libsdl-org/SDL/commit/1947ca7028ab165cc3e6cbdb0b4b7c4db68d1710
329 // https://github.com/libsdl-org/SDL/issues/5033
330 SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "1");
331 #endif
333 #if OS_MACOSX
334 // Some Mac mice only have one button, so they can't right-click
335 // but SDL2 can emulate that with Ctrl+Click
336 bool macMouse = false;
337 CFG_GET_VAL("macmouse", macMouse);
338 SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, macMouse ? "1" : "0");
339 #endif
342 static void ShutdownSDL()
344 SDL_Quit();
348 void EndGame()
350 SAFE_DELETE(g_NetClient);
351 SAFE_DELETE(g_NetServer);
352 SAFE_DELETE(g_Game);
354 if (CRenderer::IsInitialised())
356 ISoundManager::CloseGame();
357 g_Renderer.GetSceneRenderer().ResetState();
361 void Shutdown(int flags)
363 const bool hasRenderer = CRenderer::IsInitialised();
365 if ((flags & SHUTDOWN_FROM_CONFIG))
366 goto from_config;
368 EndGame();
370 SAFE_DELETE(g_XmppClient);
372 SAFE_DELETE(g_ModIo);
374 ShutdownPs();
376 if (hasRenderer)
378 TIMER_BEGIN(L"shutdown Renderer");
379 g_Renderer.~CRenderer();
380 g_VBMan.Shutdown();
381 TIMER_END(L"shutdown Renderer");
384 g_RenderingOptions.ClearHooks();
386 g_Profiler2.ShutdownGPU();
388 TIMER_BEGIN(L"shutdown SDL");
389 ShutdownSDL();
390 TIMER_END(L"shutdown SDL");
392 if (hasRenderer)
393 g_VideoMode.Shutdown();
395 TIMER_BEGIN(L"shutdown UserReporter");
396 g_UserReporter.Deinitialize();
397 TIMER_END(L"shutdown UserReporter");
399 // Cleanup curl now that g_ModIo and g_UserReporter have been shutdown.
400 curl_global_cleanup();
402 delete &g_L10n;
404 from_config:
405 TIMER_BEGIN(L"shutdown ConfigDB");
406 CConfigDB::Shutdown();
407 TIMER_END(L"shutdown ConfigDB");
409 SAFE_DELETE(g_Console);
411 // This is needed to ensure that no callbacks from the JSAPI try to use
412 // the profiler when it's already destructed
413 g_ScriptContext.reset();
415 // resource
416 // first shut down all resource owners, and then the handle manager.
417 TIMER_BEGIN(L"resource modules");
419 ISoundManager::SetEnabled(false);
421 g_VFS.reset();
423 file_stats_dump();
425 TIMER_END(L"resource modules");
427 TIMER_BEGIN(L"shutdown misc");
428 timer_DisplayClientTotals();
430 CNetHost::Deinitialize();
432 // should be last, since the above use them
433 SAFE_DELETE(g_Logger);
434 delete &g_Profiler;
435 delete &g_ProfileViewer;
437 SAFE_DELETE(g_ScriptStatsTable);
438 TIMER_END(L"shutdown misc");
441 #if OS_UNIX
442 static void FixLocales()
444 #if OS_MACOSX || OS_BSD
445 // OS X requires a UTF-8 locale in LC_CTYPE so that *wprintf can handle
446 // wide characters. Peculiarly the string "UTF-8" seems to be acceptable
447 // despite not being a real locale, and it's conveniently language-agnostic,
448 // so use that.
449 setlocale(LC_CTYPE, "UTF-8");
450 #endif
453 // On misconfigured systems with incorrect locale settings, we'll die
454 // with a C++ exception when some code (e.g. Boost) tries to use locales.
455 // To avoid death, we'll detect the problem here and warn the user and
456 // reset to the default C locale.
459 // For informing the user of the problem, use the list of env vars that
460 // glibc setlocale looks at. (LC_ALL is checked first, and LANG last.)
461 const char* const LocaleEnvVars[] = {
462 "LC_ALL",
463 "LC_COLLATE",
464 "LC_CTYPE",
465 "LC_MONETARY",
466 "LC_NUMERIC",
467 "LC_TIME",
468 "LC_MESSAGES",
469 "LANG"
474 // this constructor is similar to setlocale(LC_ALL, ""),
475 // but instead of returning NULL, it throws runtime_error
476 // when the first locale env variable found contains an invalid value
477 std::locale("");
479 catch (std::runtime_error&)
481 LOGWARNING("Invalid locale settings");
483 for (size_t i = 0; i < ARRAY_SIZE(LocaleEnvVars); i++)
485 if (char* envval = getenv(LocaleEnvVars[i]))
486 LOGWARNING(" %s=\"%s\"", LocaleEnvVars[i], envval);
487 else
488 LOGWARNING(" %s=\"(unset)\"", LocaleEnvVars[i]);
491 // We should set LC_ALL since it overrides LANG
492 if (setenv("LC_ALL", std::locale::classic().name().c_str(), 1))
493 debug_warn(L"Invalid locale settings, and unable to set LC_ALL env variable.");
494 else
495 LOGWARNING("Setting LC_ALL env variable to: %s", getenv("LC_ALL"));
498 #else
499 static void FixLocales()
501 // Do nothing on Windows
503 #endif
505 void EarlyInit()
507 // If you ever want to catch a particular allocation:
508 //_CrtSetBreakAlloc(232647);
510 Threading::SetMainThread();
512 debug_SetThreadName("main");
513 // add all debug_printf "tags" that we are interested in:
514 debug_filter_add("TIMER");
515 debug_filter_add("FILES");
517 timer_Init();
519 // initialise profiler early so it can profile startup,
520 // but only after LatchStartTime
521 g_Profiler2.Initialise();
523 FixLocales();
525 // Because we do GL calls from a secondary thread, Xlib needs to
526 // be told to support multiple threads safely.
527 // This is needed for Atlas, but we have to call it before any other
528 // Xlib functions (e.g. the ones used when drawing the main menu
529 // before launching Atlas)
530 #if MUST_INIT_X11
531 int status = XInitThreads();
532 if (status == 0)
533 debug_printf("Error enabling thread-safety via XInitThreads\n");
534 #endif
536 // Initialise the low-quality rand function
537 srand(time(NULL)); // NOTE: this rand should *not* be used for simulation!
540 bool Autostart(const CmdLineArgs& args);
543 * Returns true if the user has intended to start a visual replay from command line.
545 bool AutostartVisualReplay(const std::string& replayFile);
547 bool Init(const CmdLineArgs& args, int flags)
549 // Do this as soon as possible, because it chdirs
550 // and will mess up the error reporting if anything
551 // crashes before the working directory is set.
552 InitVfs(args, flags);
554 // This must come after VFS init, which sets the current directory
555 // (required for finding our output log files).
556 g_Logger = new CLogger;
558 new CProfileViewer;
559 new CProfileManager; // before any script code
561 g_ScriptStatsTable = new CScriptStatsTable;
562 g_ProfileViewer.AddRootTable(g_ScriptStatsTable);
564 // Set up the console early, so that debugging
565 // messages can be logged to it. (The console's size
566 // and fonts are set later in InitPs())
567 g_Console = new CConsole();
569 // g_ConfigDB, command line args, globals
570 CONFIG_Init(args);
572 // Using a global object for the context is a workaround until Simulation and AI use
573 // their own threads and also their own contexts.
574 const int contextSize = 384 * 1024 * 1024;
575 const int heapGrowthBytesGCTrigger = 20 * 1024 * 1024;
576 g_ScriptContext = ScriptContext::CreateContext(contextSize, heapGrowthBytesGCTrigger);
578 // On the first Init (INIT_MODS), check for command-line arguments
579 // or use the default mods from the config and enable those.
580 // On later engine restarts (e.g. the mod selector), we will skip this path,
581 // to avoid overwriting the newly selected mods.
582 if (flags & INIT_MODS)
584 ScriptInterface modInterface("Engine", "Mod", g_ScriptContext);
585 g_Mods.UpdateAvailableMods(modInterface);
586 std::vector<CStr> mods;
587 if (args.Has("mod"))
588 mods = args.GetMultiple("mod");
589 else
591 CStr modsStr;
592 CFG_GET_VAL("mod.enabledmods", modsStr);
593 boost::split(mods, modsStr, boost::algorithm::is_space(), boost::token_compress_on);
596 if (!g_Mods.EnableMods(mods, flags & INIT_MODS_PUBLIC))
598 // In non-visual mode, fail entirely.
599 if (args.Has("autostart-nonvisual"))
601 LOGERROR("Trying to start with incompatible mods: %s.", boost::algorithm::join(g_Mods.GetIncompatibleMods(), ", "));
602 return false;
606 // If there are incompatible mods, switch to the mod selector so players can resolve the problem.
607 if (g_Mods.GetIncompatibleMods().empty())
608 MountMods(Paths(args), g_Mods.GetEnabledMods());
609 else
610 MountMods(Paths(args), { "mod" });
612 // Special command-line mode to dump the entity schemas instead of running the game.
613 // (This must be done after loading VFS etc, but should be done before wasting time
614 // on anything else.)
615 if (args.Has("dumpSchema"))
617 CSimulation2 sim(NULL, g_ScriptContext, NULL);
618 sim.LoadDefaultScripts();
619 std::ofstream f("entity.rng", std::ios_base::out | std::ios_base::trunc);
620 f << sim.GenerateSchema();
621 std::cout << "Generated entity.rng\n";
622 exit(0);
625 CNetHost::Initialize();
627 #if CONFIG2_AUDIO
628 if (!args.Has("autostart-nonvisual") && !g_DisableAudio)
629 ISoundManager::CreateSoundManager();
630 #endif
632 new L10n;
634 // Optionally start profiler HTTP output automatically
635 // (By default it's only enabled by a hotkey, for security/performance)
636 bool profilerHTTPEnable = false;
637 CFG_GET_VAL("profiler2.autoenable", profilerHTTPEnable);
638 if (profilerHTTPEnable)
639 g_Profiler2.EnableHTTP();
641 // Initialise everything except Win32 sockets (because our networking
642 // system already inits those)
643 curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32);
645 if (!g_Quickstart)
646 g_UserReporter.Initialize(); // after config
648 PROFILE2_EVENT("Init finished");
649 return true;
652 void InitGraphics(const CmdLineArgs& args, int flags, const std::vector<CStr>& installedMods)
654 const bool setup_vmode = (flags & INIT_HAVE_VMODE) == 0;
656 if(setup_vmode)
658 InitSDL();
660 if (!g_VideoMode.InitSDL())
661 throw PSERROR_System_VmodeFailed(); // abort startup
664 RunHardwareDetection();
666 ogl_WarnIfError();
668 // Optionally start profiler GPU timings automatically
669 // (By default it's only enabled by a hotkey, for performance/compatibility)
670 bool profilerGPUEnable = false;
671 CFG_GET_VAL("profiler2.autoenable", profilerGPUEnable);
672 if (profilerGPUEnable)
673 g_Profiler2.EnableGPU();
675 if(!g_Quickstart)
677 WriteSystemInfo();
678 // note: no longer vfs_display here. it's dog-slow due to unbuffered
679 // file output and very rarely needed.
682 if(g_DisableAudio)
683 ISoundManager::SetEnabled(false);
685 g_GUI = new CGUIManager();
687 CStr8 renderPath = "default";
688 CFG_GET_VAL("renderpath", renderPath);
689 if (RenderPathEnum::FromString(renderPath) == FIXED)
691 // It doesn't make sense to continue working here, because we're not
692 // able to display anything.
693 DEBUG_DISPLAY_FATAL_ERROR(
694 L"Your graphics card doesn't appear to be fully compatible with OpenGL shaders."
695 L" The game does not support pre-shader graphics cards."
696 L" You are advised to try installing newer drivers and/or upgrade your graphics card."
697 L" For more information, please see http://www.wildfiregames.com/forum/index.php?showtopic=16734"
701 ogl_WarnIfError();
703 g_RenderingOptions.ReadConfigAndSetupHooks();
705 // create renderer
706 new CRenderer;
708 InitInput();
710 ogl_WarnIfError();
712 // TODO: Is this the best place for this?
713 if (VfsDirectoryExists(L"maps/"))
714 CXeromyces::AddValidator(g_VFS, "map", "maps/scenario.rng");
718 if (!AutostartVisualReplay(args.Get("replay-visual")) && !Autostart(args))
720 const bool setup_gui = ((flags & INIT_NO_GUI) == 0);
721 // We only want to display the splash screen at startup
722 std::shared_ptr<ScriptInterface> scriptInterface = g_GUI->GetScriptInterface();
723 ScriptRequest rq(scriptInterface);
724 JS::RootedValue data(rq.cx);
725 if (g_GUI)
727 Script::CreateObject(rq, &data, "isStartup", true);
728 if (!installedMods.empty())
729 Script::SetProperty(rq, data, "installedMods", installedMods);
731 InitPs(setup_gui, installedMods.empty() ? L"page_pregame.xml" : L"page_modmod.xml", g_GUI->GetScriptInterface().get(), data);
734 catch (PSERROR_Game_World_MapLoadFailed& e)
736 // Map Loading failed
738 // Start the engine so we have a GUI
739 InitPs(true, L"page_pregame.xml", NULL, JS::UndefinedHandleValue);
741 // Call script function to do the actual work
742 // (delete game data, switch GUI page, show error, etc.)
743 CancelLoad(CStr(e.what()).FromUTF8());
747 void InitNonVisual(const CmdLineArgs& args)
749 Autostart(args);
753 * Temporarily loads a scenario map and retrieves the "ScriptSettings" JSON
754 * data from it.
755 * The scenario map format is used for scenario and skirmish map types (random
756 * games do not use a "map" (format) but a small JavaScript program which
757 * creates a map on the fly). It contains a section to initialize the game
758 * setup screen.
759 * @param mapPath Absolute path (from VFS root) to the map file to peek in.
760 * @return ScriptSettings in JSON format extracted from the map.
762 CStr8 LoadSettingsOfScenarioMap(const VfsPath &mapPath)
764 CXeromyces mapFile;
765 const char *pathToSettings[] =
767 "Scenario", "ScriptSettings", "" // Path to JSON data in map
770 Status loadResult = mapFile.Load(g_VFS, mapPath);
772 if (INFO::OK != loadResult)
774 LOGERROR("LoadSettingsOfScenarioMap: Unable to load map file '%s'", mapPath.string8());
775 throw PSERROR_Game_World_MapLoadFailed("Unable to load map file, check the path for typos.");
777 XMBElement mapElement = mapFile.GetRoot();
779 // Select the ScriptSettings node in the map file...
780 for (int i = 0; pathToSettings[i][0]; ++i)
782 int childId = mapFile.GetElementID(pathToSettings[i]);
784 XMBElementList nodes = mapElement.GetChildNodes();
785 auto it = std::find_if(nodes.begin(), nodes.end(), [&childId](const XMBElement& child) {
786 return child.GetNodeName() == childId;
789 if (it != nodes.end())
790 mapElement = *it;
792 // ... they contain a JSON document to initialize the game setup
793 // screen
794 return mapElement.GetText();
798 * Command line options for autostart
799 * (keep synchronized with binaries/system/readme.txt):
801 * -autostart="TYPEDIR/MAPNAME" enables autostart and sets MAPNAME;
802 * TYPEDIR is skirmishes, scenarios, or random
803 * -autostart-seed=SEED sets randomization seed value (default 0, use -1 for random)
804 * -autostart-ai=PLAYER:AI sets the AI for PLAYER (e.g. 2:petra)
805 * -autostart-aidiff=PLAYER:DIFF sets the DIFFiculty of PLAYER's AI
806 * (0: sandbox, 5: very hard)
807 * -autostart-aiseed=AISEED sets the seed used for the AI random
808 * generator (default 0, use -1 for random)
809 * -autostart-player=NUMBER sets the playerID in non-networked games (default 1, use -1 for observer)
810 * -autostart-civ=PLAYER:CIV sets PLAYER's civilisation to CIV
811 * (skirmish and random maps only)
812 * -autostart-team=PLAYER:TEAM sets the team for PLAYER (e.g. 2:2).
813 * -autostart-ceasefire=NUM sets a ceasefire duration NUM
814 * (default 0 minutes)
815 * -autostart-nonvisual disable any graphics and sounds
816 * -autostart-victory=SCRIPTNAME sets the victory conditions with SCRIPTNAME
817 * located in simulation/data/settings/victory_conditions/
818 * (default conquest). When the first given SCRIPTNAME is
819 * "endless", no victory conditions will apply.
820 * -autostart-wonderduration=NUM sets the victory duration NUM for wonder victory condition
821 * (default 10 minutes)
822 * -autostart-relicduration=NUM sets the victory duration NUM for relic victory condition
823 * (default 10 minutes)
824 * -autostart-reliccount=NUM sets the number of relics for relic victory condition
825 * (default 2 relics)
826 * -autostart-disable-replay disable saving of replays
828 * Multiplayer:
829 * -autostart-playername=NAME sets local player NAME (default 'anonymous')
830 * -autostart-host sets multiplayer host mode
831 * -autostart-host-players=NUMBER sets NUMBER of human players for multiplayer
832 * game (default 2)
833 * -autostart-client=IP sets multiplayer client to join host at
834 * given IP address
835 * Random maps only:
836 * -autostart-size=TILES sets random map size in TILES (default 192)
837 * -autostart-players=NUMBER sets NUMBER of players on random map
838 * (default 2)
840 * Examples:
841 * 1) "Bob" will host a 2 player game on the Arcadia map:
842 * -autostart="scenarios/Arcadia" -autostart-host -autostart-host-players=2 -autostart-playername="Bob"
843 * "Alice" joins the match as player 2:
844 * -autostart="scenarios/Arcadia" -autostart-client=127.0.0.1 -autostart-playername="Alice"
845 * The players use the developer overlay to control players.
847 * 2) Load Alpine Lakes random map with random seed, 2 players (Athens and Britons), and player 2 is PetraBot:
848 * -autostart="random/alpine_lakes" -autostart-seed=-1 -autostart-players=2 -autostart-civ=1:athen -autostart-civ=2:brit -autostart-ai=2:petra
850 * 3) Observe the PetraBot on a triggerscript map:
851 * -autostart="random/jebel_barkal" -autostart-seed=-1 -autostart-players=2 -autostart-civ=1:athen -autostart-civ=2:brit -autostart-ai=1:petra -autostart-ai=2:petra -autostart-player=-1
853 bool Autostart(const CmdLineArgs& args)
855 CStr autoStartName = args.Get("autostart");
857 if (autoStartName.empty())
858 return false;
860 g_Game = new CGame(!args.Has("autostart-disable-replay"));
862 ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
863 ScriptRequest rq(scriptInterface);
865 JS::RootedValue attrs(rq.cx);
866 JS::RootedValue settings(rq.cx);
867 JS::RootedValue playerData(rq.cx);
869 Script::CreateObject(rq, &attrs);
870 Script::CreateObject(rq, &settings);
871 Script::CreateArray(rq, &playerData);
873 // The directory in front of the actual map name indicates which type
874 // of map is being loaded. Drawback of this approach is the association
875 // of map types and folders is hard-coded, but benefits are:
876 // - No need to pass the map type via command line separately
877 // - Prevents mixing up of scenarios and skirmish maps to some degree
878 Path mapPath = Path(autoStartName);
879 std::wstring mapDirectory = mapPath.Parent().Filename().string();
880 std::string mapType;
882 if (mapDirectory == L"random")
884 // Random map definition will be loaded from JSON file, so we need to parse it
885 std::wstring scriptPath = L"maps/" + autoStartName.FromUTF8() + L".json";
886 JS::RootedValue scriptData(rq.cx);
887 Script::ReadJSONFile(rq, scriptPath, &scriptData);
888 if (!scriptData.isUndefined() && Script::GetProperty(rq, scriptData, "settings", &settings))
890 // JSON loaded ok - copy script name over to game attributes
891 std::wstring scriptFile;
892 if (!Script::GetProperty(rq, settings, "Script", scriptFile))
894 LOGERROR("Autostart: random map '%s' data has no 'Script' property.", utf8_from_wstring(scriptPath));
895 throw PSERROR_Game_World_MapLoadFailed("Error reading random map script.\nCheck application log for details.");
897 Script::SetProperty(rq, attrs, "script", scriptFile); // RMS filename
899 else
901 // Problem with JSON file
902 LOGERROR("Autostart: Error reading random map script '%s'", utf8_from_wstring(scriptPath));
903 throw PSERROR_Game_World_MapLoadFailed("Error reading random map script.\nCheck application log for details.");
906 // Get optional map size argument (default 192)
907 uint mapSize = 192;
908 if (args.Has("autostart-size"))
910 CStr size = args.Get("autostart-size");
911 mapSize = size.ToUInt();
914 Script::SetProperty(rq, settings, "Size", mapSize); // Random map size (in patches)
916 // Get optional number of players (default 2)
917 size_t numPlayers = 2;
918 if (args.Has("autostart-players"))
920 CStr num = args.Get("autostart-players");
921 numPlayers = num.ToUInt();
924 // Set up player data
925 for (size_t i = 0; i < numPlayers; ++i)
927 JS::RootedValue player(rq.cx);
929 // We could load player_defaults.json here, but that would complicate the logic
930 // even more and autostart is only intended for developers anyway
931 Script::CreateObject(rq, &player, "Civ", "athen");
933 Script::SetPropertyInt(rq, playerData, i, player);
935 mapType = "random";
937 else if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes")
939 // Initialize general settings from the map data so some values
940 // (e.g. name of map) are always present, even when autostart is
941 // partially configured
942 CStr8 mapSettingsJSON = LoadSettingsOfScenarioMap("maps/" + autoStartName + ".xml");
943 Script::ParseJSON(rq, mapSettingsJSON, &settings);
945 // Initialize the playerData array being modified by autostart
946 // with the real map data, so sensible values are present:
947 Script::GetProperty(rq, settings, "PlayerData", &playerData);
949 if (mapDirectory == L"scenarios")
950 mapType = "scenario";
951 else
952 mapType = "skirmish";
954 else
956 LOGERROR("Autostart: Unrecognized map type '%s'", utf8_from_wstring(mapDirectory));
957 throw PSERROR_Game_World_MapLoadFailed("Unrecognized map type.\nConsult readme.txt for the currently supported types.");
960 Script::SetProperty(rq, attrs, "mapType", mapType);
961 Script::SetProperty(rq, attrs, "map", "maps/" + autoStartName);
962 Script::SetProperty(rq, settings, "mapType", mapType);
963 Script::SetProperty(rq, settings, "CheatsEnabled", true);
965 // The seed is used for both random map generation and simulation
966 u32 seed = 0;
967 if (args.Has("autostart-seed"))
969 CStr seedArg = args.Get("autostart-seed");
970 if (seedArg == "-1")
971 seed = rand();
972 else
973 seed = seedArg.ToULong();
975 Script::SetProperty(rq, settings, "Seed", seed);
977 // Set seed for AIs
978 u32 aiseed = 0;
979 if (args.Has("autostart-aiseed"))
981 CStr seedArg = args.Get("autostart-aiseed");
982 if (seedArg == "-1")
983 aiseed = rand();
984 else
985 aiseed = seedArg.ToULong();
987 Script::SetProperty(rq, settings, "AISeed", aiseed);
989 // Set player data for AIs
990 // attrs.settings = { PlayerData: [ { AI: ... }, ... ] }
991 // or = { PlayerData: [ null, { AI: ... }, ... ] } when gaia set
992 int offset = 1;
993 JS::RootedValue player(rq.cx);
994 if (Script::GetPropertyInt(rq, playerData, 0, &player) && player.isNull())
995 offset = 0;
997 // Set teams
998 if (args.Has("autostart-team"))
1000 std::vector<CStr> civArgs = args.GetMultiple("autostart-team");
1001 for (size_t i = 0; i < civArgs.size(); ++i)
1003 int playerID = civArgs[i].BeforeFirst(":").ToInt();
1005 // Instead of overwriting existing player data, modify the array
1006 JS::RootedValue currentPlayer(rq.cx);
1007 if (!Script::GetPropertyInt(rq, playerData, playerID-offset, &currentPlayer) || currentPlayer.isUndefined())
1009 if (mapDirectory == L"skirmishes")
1011 // playerID is certainly bigger than this map player number
1012 LOGWARNING("Autostart: Invalid player %d in autostart-team option", playerID);
1013 continue;
1015 Script::CreateObject(rq, &currentPlayer);
1018 int teamID = civArgs[i].AfterFirst(":").ToInt() - 1;
1019 Script::SetProperty(rq, currentPlayer, "Team", teamID);
1020 Script::SetPropertyInt(rq, playerData, playerID-offset, currentPlayer);
1024 int ceasefire = 0;
1025 if (args.Has("autostart-ceasefire"))
1026 ceasefire = args.Get("autostart-ceasefire").ToInt();
1027 Script::SetProperty(rq, settings, "Ceasefire", ceasefire);
1029 if (args.Has("autostart-ai"))
1031 std::vector<CStr> aiArgs = args.GetMultiple("autostart-ai");
1032 for (size_t i = 0; i < aiArgs.size(); ++i)
1034 int playerID = aiArgs[i].BeforeFirst(":").ToInt();
1036 // Instead of overwriting existing player data, modify the array
1037 JS::RootedValue currentPlayer(rq.cx);
1038 if (!Script::GetPropertyInt(rq, playerData, playerID-offset, &currentPlayer) || currentPlayer.isUndefined())
1040 if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes")
1042 // playerID is certainly bigger than this map player number
1043 LOGWARNING("Autostart: Invalid player %d in autostart-ai option", playerID);
1044 continue;
1046 Script::CreateObject(rq, &currentPlayer);
1049 Script::SetProperty(rq, currentPlayer, "AI", aiArgs[i].AfterFirst(":"));
1050 Script::SetProperty(rq, currentPlayer, "AIDiff", 3);
1051 Script::SetProperty(rq, currentPlayer, "AIBehavior", "balanced");
1052 Script::SetPropertyInt(rq, playerData, playerID-offset, currentPlayer);
1055 // Set AI difficulty
1056 if (args.Has("autostart-aidiff"))
1058 std::vector<CStr> civArgs = args.GetMultiple("autostart-aidiff");
1059 for (size_t i = 0; i < civArgs.size(); ++i)
1061 int playerID = civArgs[i].BeforeFirst(":").ToInt();
1063 // Instead of overwriting existing player data, modify the array
1064 JS::RootedValue currentPlayer(rq.cx);
1065 if (!Script::GetPropertyInt(rq, playerData, playerID-offset, &currentPlayer) || currentPlayer.isUndefined())
1067 if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes")
1069 // playerID is certainly bigger than this map player number
1070 LOGWARNING("Autostart: Invalid player %d in autostart-aidiff option", playerID);
1071 continue;
1073 Script::CreateObject(rq, &currentPlayer);
1076 Script::SetProperty(rq, currentPlayer, "AIDiff", civArgs[i].AfterFirst(":").ToInt());
1077 Script::SetPropertyInt(rq, playerData, playerID-offset, currentPlayer);
1080 // Set player data for Civs
1081 if (args.Has("autostart-civ"))
1083 if (mapDirectory != L"scenarios")
1085 std::vector<CStr> civArgs = args.GetMultiple("autostart-civ");
1086 for (size_t i = 0; i < civArgs.size(); ++i)
1088 int playerID = civArgs[i].BeforeFirst(":").ToInt();
1090 // Instead of overwriting existing player data, modify the array
1091 JS::RootedValue currentPlayer(rq.cx);
1092 if (!Script::GetPropertyInt(rq, playerData, playerID-offset, &currentPlayer) || currentPlayer.isUndefined())
1094 if (mapDirectory == L"skirmishes")
1096 // playerID is certainly bigger than this map player number
1097 LOGWARNING("Autostart: Invalid player %d in autostart-civ option", playerID);
1098 continue;
1100 Script::CreateObject(rq, &currentPlayer);
1103 Script::SetProperty(rq, currentPlayer, "Civ", civArgs[i].AfterFirst(":"));
1104 Script::SetPropertyInt(rq, playerData, playerID-offset, currentPlayer);
1107 else
1108 LOGWARNING("Autostart: Option 'autostart-civ' is invalid for scenarios");
1111 // Add player data to map settings
1112 Script::SetProperty(rq, settings, "PlayerData", playerData);
1114 // Add map settings to game attributes
1115 Script::SetProperty(rq, attrs, "settings", settings);
1117 // Get optional playername
1118 CStrW userName = L"anonymous";
1119 if (args.Has("autostart-playername"))
1120 userName = args.Get("autostart-playername").FromUTF8();
1122 // Add additional scripts to the TriggerScripts property
1123 std::vector<CStrW> triggerScriptsVector;
1124 JS::RootedValue triggerScripts(rq.cx);
1126 if (Script::HasProperty(rq, settings, "TriggerScripts"))
1128 Script::GetProperty(rq, settings, "TriggerScripts", &triggerScripts);
1129 Script::FromJSVal(rq, triggerScripts, triggerScriptsVector);
1132 if (!CRenderer::IsInitialised())
1134 CStr nonVisualScript = "scripts/NonVisualTrigger.js";
1135 triggerScriptsVector.push_back(nonVisualScript.FromUTF8());
1138 std::vector<CStr> victoryConditions(1, "conquest");
1139 if (args.Has("autostart-victory"))
1140 victoryConditions = args.GetMultiple("autostart-victory");
1142 if (victoryConditions.size() == 1 && victoryConditions[0] == "endless")
1143 victoryConditions.clear();
1145 Script::SetProperty(rq, settings, "VictoryConditions", victoryConditions);
1147 for (const CStr& victory : victoryConditions)
1149 JS::RootedValue scriptData(rq.cx);
1150 JS::RootedValue data(rq.cx);
1151 JS::RootedValue victoryScripts(rq.cx);
1153 CStrW scriptPath = L"simulation/data/settings/victory_conditions/" + victory.FromUTF8() + L".json";
1154 Script::ReadJSONFile(rq, scriptPath, &scriptData);
1155 if (!scriptData.isUndefined() && Script::GetProperty(rq, scriptData, "Data", &data) && !data.isUndefined()
1156 && Script::GetProperty(rq, data, "Scripts", &victoryScripts) && !victoryScripts.isUndefined())
1158 std::vector<CStrW> victoryScriptsVector;
1159 Script::FromJSVal(rq, victoryScripts, victoryScriptsVector);
1160 triggerScriptsVector.insert(triggerScriptsVector.end(), victoryScriptsVector.begin(), victoryScriptsVector.end());
1162 else
1164 LOGERROR("Autostart: Error reading victory script '%s'", utf8_from_wstring(scriptPath));
1165 throw PSERROR_Game_World_MapLoadFailed("Error reading victory script.\nCheck application log for details.");
1169 Script::ToJSVal(rq, &triggerScripts, triggerScriptsVector);
1170 Script::SetProperty(rq, settings, "TriggerScripts", triggerScripts);
1172 int wonderDuration = 10;
1173 if (args.Has("autostart-wonderduration"))
1174 wonderDuration = args.Get("autostart-wonderduration").ToInt();
1175 Script::SetProperty(rq, settings, "WonderDuration", wonderDuration);
1177 int relicDuration = 10;
1178 if (args.Has("autostart-relicduration"))
1179 relicDuration = args.Get("autostart-relicduration").ToInt();
1180 Script::SetProperty(rq, settings, "RelicDuration", relicDuration);
1182 int relicCount = 2;
1183 if (args.Has("autostart-reliccount"))
1184 relicCount = args.Get("autostart-reliccount").ToInt();
1185 Script::SetProperty(rq, settings, "RelicCount", relicCount);
1187 if (args.Has("autostart-host"))
1189 InitPsAutostart(true, attrs);
1191 size_t maxPlayers = 2;
1192 if (args.Has("autostart-host-players"))
1193 maxPlayers = args.Get("autostart-host-players").ToUInt();
1195 // Generate a secret to identify the host client.
1196 std::string secret = ps_generate_guid();
1198 g_NetServer = new CNetServer(false, maxPlayers);
1199 g_NetServer->SetControllerSecret(secret);
1200 g_NetServer->UpdateInitAttributes(&attrs, scriptInterface);
1202 bool ok = g_NetServer->SetupConnection(PS_DEFAULT_PORT);
1203 ENSURE(ok);
1205 g_NetClient = new CNetClient(g_Game);
1206 g_NetClient->SetUserName(userName);
1207 g_NetClient->SetupServerData("127.0.0.1", PS_DEFAULT_PORT, false);
1208 g_NetClient->SetControllerSecret(secret);
1209 g_NetClient->SetupConnection(nullptr);
1211 else if (args.Has("autostart-client"))
1213 InitPsAutostart(true, attrs);
1215 g_NetClient = new CNetClient(g_Game);
1216 g_NetClient->SetUserName(userName);
1218 CStr ip = args.Get("autostart-client");
1219 if (ip.empty())
1220 ip = "127.0.0.1";
1222 g_NetClient->SetupServerData(ip, PS_DEFAULT_PORT, false);
1223 ENSURE(g_NetClient->SetupConnection(nullptr));
1225 else
1227 g_Game->SetPlayerID(args.Has("autostart-player") ? args.Get("autostart-player").ToInt() : 1);
1229 g_Game->StartGame(&attrs, "");
1231 if (CRenderer::IsInitialised())
1233 InitPsAutostart(false, attrs);
1235 else
1237 // TODO: Non progressive load can fail - need a decent way to handle this
1238 LDR_NonprogressiveLoad();
1239 ENSURE(g_Game->ReallyStartGame() == PSRETURN_OK);
1243 return true;
1246 bool AutostartVisualReplay(const std::string& replayFile)
1248 if (!FileExists(OsPath(replayFile)))
1249 return false;
1251 g_Game = new CGame(false);
1252 g_Game->SetPlayerID(-1);
1253 g_Game->StartVisualReplay(replayFile);
1255 ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
1256 ScriptRequest rq(scriptInterface);
1257 JS::RootedValue attrs(rq.cx, g_Game->GetSimulation2()->GetInitAttributes());
1259 InitPsAutostart(false, attrs);
1261 return true;
1264 void CancelLoad(const CStrW& message)
1266 std::shared_ptr<ScriptInterface> pScriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface();
1267 ScriptRequest rq(pScriptInterface);
1269 JS::RootedValue global(rq.cx, rq.globalValue());
1271 LDR_Cancel();
1273 if (g_GUI &&
1274 g_GUI->GetPageCount() &&
1275 Script::HasProperty(rq, global, "cancelOnLoadGameError"))
1276 ScriptFunction::CallVoid(rq, global, "cancelOnLoadGameError", message);
1279 bool InDevelopmentCopy()
1281 if (!g_CheckedIfInDevelopmentCopy)
1283 g_InDevelopmentCopy = (g_VFS->GetFileInfo(L"config/dev.cfg", NULL) == INFO::OK);
1284 g_CheckedIfInDevelopmentCopy = true;
1286 return g_InDevelopmentCopy;