LCOV - code coverage report
Current view: top level - source/ps/GameSetup - GameSetup.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 14 508 2.8 %
Date: 2023-01-19 00:18:29 Functions: 4 33 12.1 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       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.
       8             :  *
       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.
      13             :  *
      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/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "ps/GameSetup/GameSetup.h"
      21             : 
      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 "gui/Scripting/JSInterface_GUIManager.h"
      28             : #include "i18n/L10n.h"
      29             : #include "lib/app_hooks.h"
      30             : #include "lib/config2.h"
      31             : #include "lib/external_libraries/libsdl.h"
      32             : #include "lib/file/common/file_stats.h"
      33             : #include "lib/input.h"
      34             : #include "lib/timer.h"
      35             : #include "lobby/IXmppClient.h"
      36             : #include "network/NetServer.h"
      37             : #include "network/NetClient.h"
      38             : #include "network/NetMessage.h"
      39             : #include "network/NetMessages.h"
      40             : #include "network/scripting/JSInterface_Network.h"
      41             : #include "ps/CConsole.h"
      42             : #include "ps/CLogger.h"
      43             : #include "ps/ConfigDB.h"
      44             : #include "ps/Filesystem.h"
      45             : #include "ps/Game.h"
      46             : #include "ps/GameSetup/Atlas.h"
      47             : #include "ps/GameSetup/Paths.h"
      48             : #include "ps/GameSetup/Config.h"
      49             : #include "ps/GameSetup/CmdLineArgs.h"
      50             : #include "ps/GameSetup/HWDetect.h"
      51             : #include "ps/Globals.h"
      52             : #include "ps/GUID.h"
      53             : #include "ps/Hotkey.h"
      54             : #include "ps/Joystick.h"
      55             : #include "ps/Loader.h"
      56             : #include "ps/Mod.h"
      57             : #include "ps/ModIo.h"
      58             : #include "ps/Profile.h"
      59             : #include "ps/ProfileViewer.h"
      60             : #include "ps/Profiler2.h"
      61             : #include "ps/Pyrogenesis.h"   // psSetLogDir
      62             : #include "ps/scripting/JSInterface_Console.h"
      63             : #include "ps/scripting/JSInterface_Game.h"
      64             : #include "ps/scripting/JSInterface_Main.h"
      65             : #include "ps/scripting/JSInterface_VFS.h"
      66             : #include "ps/TouchInput.h"
      67             : #include "ps/UserReport.h"
      68             : #include "ps/Util.h"
      69             : #include "ps/VideoMode.h"
      70             : #include "ps/VisualReplay.h"
      71             : #include "ps/World.h"
      72             : #include "renderer/Renderer.h"
      73             : #include "renderer/SceneRenderer.h"
      74             : #include "renderer/VertexBufferManager.h"
      75             : #include "scriptinterface/FunctionWrapper.h"
      76             : #include "scriptinterface/JSON.h"
      77             : #include "scriptinterface/ScriptInterface.h"
      78             : #include "scriptinterface/ScriptStats.h"
      79             : #include "scriptinterface/ScriptContext.h"
      80             : #include "scriptinterface/ScriptConversions.h"
      81             : #include "simulation2/Simulation2.h"
      82             : #include "simulation2/scripting/JSInterface_Simulation.h"
      83             : #include "soundmanager/scripting/JSInterface_Sound.h"
      84             : #include "soundmanager/ISoundManager.h"
      85             : #include "tools/atlas/GameInterface/GameLoop.h"
      86             : 
      87             : #if !(OS_WIN || OS_MACOSX || OS_ANDROID) // assume all other platforms use X11 for wxWidgets
      88             : #define MUST_INIT_X11 1
      89             : #include <X11/Xlib.h>
      90             : #else
      91             : #define MUST_INIT_X11 0
      92             : #endif
      93             : 
      94             : extern void RestartEngine();
      95             : 
      96             : #include <fstream>
      97             : #include <iostream>
      98             : 
      99             : #include <boost/algorithm/string/classification.hpp>
     100             : #include <boost/algorithm/string/join.hpp>
     101             : #include <boost/algorithm/string/split.hpp>
     102             : 
     103           0 : ERROR_GROUP(System);
     104           0 : ERROR_TYPE(System, SDLInitFailed);
     105           0 : ERROR_TYPE(System, VmodeFailed);
     106             : ERROR_TYPE(System, RequiredExtensionsMissing);
     107             : 
     108           1 : thread_local std::shared_ptr<ScriptContext> g_ScriptContext;
     109             : 
     110             : bool g_InDevelopmentCopy;
     111             : bool g_CheckedIfInDevelopmentCopy = false;
     112             : 
     113           0 : ErrorReactionInternal psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED(flags))
     114             : {
     115             :     // If we're fullscreen, then sometimes (at least on some particular drivers on Linux)
     116             :     // displaying the error dialog hangs the desktop since the dialog box is behind the
     117             :     // fullscreen window. So we just force the game to windowed mode before displaying the dialog.
     118             :     // (But only if we're in the main thread, and not if we're being reentrant.)
     119           0 :     if (Threading::IsMainThread())
     120             :     {
     121             :         static bool reentering = false;
     122           0 :         if (!reentering)
     123             :         {
     124           0 :             reentering = true;
     125           0 :             g_VideoMode.SetFullscreen(false);
     126           0 :             reentering = false;
     127             :         }
     128             :     }
     129             : 
     130             :     // We don't actually implement the error display here, so return appropriately
     131           0 :     return ERI_NOT_IMPLEMENTED;
     132             : }
     133             : 
     134           0 : void MountMods(const Paths& paths, const std::vector<CStr>& mods)
     135             : {
     136           0 :     OsPath modPath = paths.RData()/"mods";
     137           0 :     OsPath modUserPath = paths.UserData()/"mods";
     138             : 
     139           0 :     size_t userFlags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE;
     140           0 :     size_t baseFlags = userFlags|VFS_MOUNT_MUST_EXIST;
     141           0 :     size_t priority = 0;
     142           0 :     for (size_t i = 0; i < mods.size(); ++i)
     143             :     {
     144           0 :         priority = i + 1; // Mods are higher priority than regular mountings, which default to priority 0
     145             : 
     146           0 :         OsPath modName(mods[i]);
     147             :         // Only mount mods from the user path if they don't exist in the 'rdata' path.
     148           0 :         if (DirectoryExists(modPath / modName / ""))
     149           0 :             g_VFS->Mount(L"", modPath / modName / "", baseFlags, priority);
     150             :         else
     151           0 :             g_VFS->Mount(L"", modUserPath / modName / "", userFlags, priority);
     152             :     }
     153             : 
     154             :     // Mount the user mod last. In dev copy, mount it with a low priority. Otherwise, make it writable.
     155           0 :     g_VFS->Mount(L"", modUserPath / "user" / "", userFlags, InDevelopmentCopy() ? 0 : priority + 1);
     156           0 : }
     157             : 
     158           0 : static void InitVfs(const CmdLineArgs& args, int flags)
     159             : {
     160           0 :     TIMER(L"InitVfs");
     161             : 
     162           0 :     const bool setup_error = (flags & INIT_HAVE_DISPLAY_ERROR) == 0;
     163             : 
     164           0 :     const Paths paths(args);
     165             : 
     166           0 :     OsPath logs(paths.Logs());
     167           0 :     CreateDirectories(logs, 0700);
     168             : 
     169           0 :     psSetLogDir(logs);
     170             :     // desired location for crashlog is now known. update AppHooks ASAP
     171             :     // (particularly before the following error-prone operations):
     172           0 :     AppHooks hooks = {0};
     173           0 :     hooks.bundle_logs = psBundleLogs;
     174           0 :     hooks.get_log_dir = psLogDir;
     175           0 :     if (setup_error)
     176           0 :         hooks.display_error = psDisplayError;
     177           0 :     app_hooks_update(&hooks);
     178             : 
     179           0 :     g_VFS = CreateVfs();
     180             : 
     181           0 :     const OsPath readonlyConfig = paths.RData()/"config"/"";
     182             : 
     183             :     // Mount these dirs with highest priority so that mods can't overwrite them.
     184           0 :     g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE, VFS_MAX_PRIORITY);    // (adding XMBs to archive speeds up subsequent reads)
     185           0 :     if (readonlyConfig != paths.Config())
     186           0 :         g_VFS->Mount(L"config/", readonlyConfig, 0, VFS_MAX_PRIORITY-1);
     187           0 :     g_VFS->Mount(L"config/", paths.Config(), 0, VFS_MAX_PRIORITY);
     188           0 :     g_VFS->Mount(L"screenshots/", paths.UserData()/"screenshots"/"", 0, VFS_MAX_PRIORITY);
     189           0 :     g_VFS->Mount(L"saves/", paths.UserData()/"saves"/"", VFS_MOUNT_WATCH, VFS_MAX_PRIORITY);
     190             : 
     191             :     // Engine localization files (regular priority, these can be overwritten).
     192           0 :     g_VFS->Mount(L"l10n/", paths.RData()/"l10n"/"");
     193             : 
     194             :     // Mods will be mounted later.
     195             : 
     196             :     // note: don't bother with g_VFS->TextRepresentation - directories
     197             :     // haven't yet been populated and are empty.
     198           0 : }
     199             : 
     200             : 
     201           0 : static void InitPs(bool setup_gui, const CStrW& gui_page, ScriptInterface* srcScriptInterface, JS::HandleValue initData)
     202             : {
     203             :     {
     204             :         // console
     205           0 :         TIMER(L"ps_console");
     206             : 
     207           0 :         g_Console->Init();
     208             :     }
     209             : 
     210             :     // hotkeys
     211             :     {
     212           0 :         TIMER(L"ps_lang_hotkeys");
     213           0 :         LoadHotkeys(g_ConfigDB);
     214             :     }
     215             : 
     216           0 :     if (!setup_gui)
     217             :     {
     218             :         // We do actually need *some* kind of GUI loaded, so use the
     219             :         // (currently empty) Atlas one
     220           0 :         g_GUI->SwitchPage(L"page_atlas.xml", srcScriptInterface, initData);
     221           0 :         return;
     222             :     }
     223             : 
     224             :     // GUI uses VFS, so this must come after VFS init.
     225           0 :     g_GUI->SwitchPage(gui_page, srcScriptInterface, initData);
     226             : }
     227             : 
     228           1 : void InitInput()
     229             : {
     230           1 :     g_Joystick.Initialise();
     231             : 
     232             :     // register input handlers
     233             :     // This stack is constructed so the first added, will be the last
     234             :     //  one called. This is important, because each of the handlers
     235             :     //  has the potential to block events to go further down
     236             :     //  in the chain. I.e. the last one in the list added, is the
     237             :     //  only handler that can block all messages before they are
     238             :     //  processed.
     239           1 :     in_add_handler(game_view_handler);
     240             : 
     241           1 :     in_add_handler(CProfileViewer::InputThunk);
     242             : 
     243           1 :     in_add_handler(HotkeyInputActualHandler);
     244             : 
     245             :     // gui_handler needs to be registered after (i.e. called before!) the
     246             :     // hotkey handler so that input boxes can be typed in without
     247             :     // setting off hotkeys.
     248           1 :     in_add_handler(gui_handler);
     249             :     // Likewise for the console.
     250           1 :     in_add_handler(conInputHandler);
     251             : 
     252           1 :     in_add_handler(touch_input_handler);
     253             : 
     254             :     // Should be called after scancode map update (i.e. after the global input, but before UI).
     255             :     // This never blocks the event, but it does some processing necessary for hotkeys,
     256             :     // which are triggered later down the input chain.
     257             :     // (by calling this before the UI, we can use 'EventWouldTriggerHotkey' in the UI).
     258           1 :     in_add_handler(HotkeyInputPrepHandler);
     259             : 
     260             :     // These two must be called first (i.e. pushed last)
     261             :     // GlobalsInputHandler deals with some important global state,
     262             :     // such as which scancodes are being pressed, mouse buttons pressed, etc.
     263             :     // while HotkeyStateChange updates the map of active hotkeys.
     264           1 :     in_add_handler(GlobalsInputHandler);
     265           1 :     in_add_handler(HotkeyStateChange);
     266           1 : }
     267             : 
     268             : 
     269           0 : static void ShutdownPs()
     270             : {
     271           0 :     SAFE_DELETE(g_GUI);
     272             : 
     273           0 :     UnloadHotkeys();
     274           0 : }
     275             : 
     276           0 : static void InitSDL()
     277             : {
     278             : #if OS_LINUX
     279             :     // In fullscreen mode when SDL is compiled with DGA support, the mouse
     280             :     // sensitivity often appears to be unusably wrong (typically too low).
     281             :     // (This seems to be reported almost exclusively on Ubuntu, but can be
     282             :     // reproduced on Gentoo after explicitly enabling DGA.)
     283             :     // Disabling the DGA mouse appears to fix that problem, and doesn't
     284             :     // have any obvious negative effects.
     285           0 :     setenv("SDL_VIDEO_X11_DGAMOUSE", "0", 0);
     286             : #endif
     287             : 
     288           0 :     if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0)
     289             :     {
     290           0 :         LOGERROR("SDL library initialization failed: %s", SDL_GetError());
     291           0 :         throw PSERROR_System_SDLInitFailed();
     292             :     }
     293           0 :     atexit(SDL_Quit);
     294             : 
     295             :     // Text input is active by default, disable it until it is actually needed.
     296           0 :     SDL_StopTextInput();
     297             : 
     298             : #if SDL_VERSION_ATLEAST(2, 0, 9)
     299             :     // SDL2 >= 2.0.9 defaults to 32 pixels (to support touch screens) but that can break our double-clicking.
     300           0 :     SDL_SetHint(SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS, "1");
     301             : #endif
     302             : 
     303             : #if SDL_VERSION_ATLEAST(2, 0, 14) && OS_WIN
     304             :     // SDL2 >= 2.0.14 Before SDL 2.0.14, this defaulted to true. In 2.0.14 they switched to false
     305             :     // breaking the behavior on Windows.
     306             :     // https://github.com/libsdl-org/SDL/commit/1947ca7028ab165cc3e6cbdb0b4b7c4db68d1710
     307             :     // https://github.com/libsdl-org/SDL/issues/5033
     308             :     SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "1");
     309             : #endif
     310             : 
     311             : #if OS_MACOSX
     312             :     // Some Mac mice only have one button, so they can't right-click
     313             :     // but SDL2 can emulate that with Ctrl+Click
     314             :     bool macMouse = false;
     315             :     CFG_GET_VAL("macmouse", macMouse);
     316             :     SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, macMouse ? "1" : "0");
     317             : #endif
     318           0 : }
     319             : 
     320           0 : static void ShutdownSDL()
     321             : {
     322           0 :     SDL_Quit();
     323           0 : }
     324             : 
     325             : 
     326           0 : void EndGame()
     327             : {
     328           0 :     SAFE_DELETE(g_NetClient);
     329           0 :     SAFE_DELETE(g_NetServer);
     330           0 :     SAFE_DELETE(g_Game);
     331             : 
     332           0 :     if (CRenderer::IsInitialised())
     333             :     {
     334           0 :         ISoundManager::CloseGame();
     335           0 :         g_Renderer.GetSceneRenderer().ResetState();
     336             :     }
     337           0 : }
     338             : 
     339           0 : void Shutdown(int flags)
     340             : {
     341           0 :     const bool hasRenderer = CRenderer::IsInitialised();
     342             : 
     343           0 :     if ((flags & SHUTDOWN_FROM_CONFIG))
     344           0 :         goto from_config;
     345             : 
     346           0 :     EndGame();
     347             : 
     348           0 :     SAFE_DELETE(g_XmppClient);
     349             : 
     350           0 :     SAFE_DELETE(g_ModIo);
     351             : 
     352           0 :     ShutdownPs();
     353             : 
     354           0 :     if (hasRenderer)
     355             :     {
     356           0 :         TIMER_BEGIN(L"shutdown Renderer");
     357           0 :         g_Renderer.~CRenderer();
     358           0 :         g_VBMan.Shutdown();
     359             :         TIMER_END(L"shutdown Renderer");
     360             :     }
     361             : 
     362           0 :     g_RenderingOptions.ClearHooks();
     363             : 
     364           0 :     g_Profiler2.ShutdownGPU();
     365             : 
     366           0 :     if (hasRenderer)
     367           0 :         g_VideoMode.Shutdown();
     368             : 
     369           0 :     TIMER_BEGIN(L"shutdown SDL");
     370           0 :     ShutdownSDL();
     371             :     TIMER_END(L"shutdown SDL");
     372             : 
     373           0 :     TIMER_BEGIN(L"shutdown UserReporter");
     374           0 :     g_UserReporter.Deinitialize();
     375             :     TIMER_END(L"shutdown UserReporter");
     376             : 
     377             :     // Cleanup curl now that g_ModIo and g_UserReporter have been shutdown.
     378           0 :     curl_global_cleanup();
     379             : 
     380           0 :     delete &g_L10n;
     381             : 
     382           0 : from_config:
     383           0 :     TIMER_BEGIN(L"shutdown ConfigDB");
     384           0 :     CConfigDB::Shutdown();
     385             :     TIMER_END(L"shutdown ConfigDB");
     386             : 
     387           0 :     SAFE_DELETE(g_Console);
     388             : 
     389             :     // This is needed to ensure that no callbacks from the JSAPI try to use
     390             :     // the profiler when it's already destructed
     391           0 :     g_ScriptContext.reset();
     392             : 
     393             :     // resource
     394             :     // first shut down all resource owners, and then the handle manager.
     395           0 :     TIMER_BEGIN(L"resource modules");
     396             : 
     397           0 :         ISoundManager::SetEnabled(false);
     398             : 
     399           0 :         g_VFS.reset();
     400             : 
     401             :         file_stats_dump();
     402             : 
     403             :     TIMER_END(L"resource modules");
     404             : 
     405           0 :     TIMER_BEGIN(L"shutdown misc");
     406           0 :         timer_DisplayClientTotals();
     407             : 
     408           0 :         CNetHost::Deinitialize();
     409             : 
     410             :         // should be last, since the above use them
     411           0 :         SAFE_DELETE(g_Logger);
     412           0 :         delete &g_Profiler;
     413           0 :         delete &g_ProfileViewer;
     414             : 
     415           0 :         SAFE_DELETE(g_ScriptStatsTable);
     416             :     TIMER_END(L"shutdown misc");
     417           0 : }
     418             : 
     419             : #if OS_UNIX
     420           0 : static void FixLocales()
     421             : {
     422             : #if OS_MACOSX || OS_BSD
     423             :     // OS X requires a UTF-8 locale in LC_CTYPE so that *wprintf can handle
     424             :     // wide characters. Peculiarly the string "UTF-8" seems to be acceptable
     425             :     // despite not being a real locale, and it's conveniently language-agnostic,
     426             :     // so use that.
     427             :     setlocale(LC_CTYPE, "UTF-8");
     428             : #endif
     429             : 
     430             : 
     431             :     // On misconfigured systems with incorrect locale settings, we'll die
     432             :     // with a C++ exception when some code (e.g. Boost) tries to use locales.
     433             :     // To avoid death, we'll detect the problem here and warn the user and
     434             :     // reset to the default C locale.
     435             : 
     436             : 
     437             :     // For informing the user of the problem, use the list of env vars that
     438             :     // glibc setlocale looks at. (LC_ALL is checked first, and LANG last.)
     439           0 :     const char* const LocaleEnvVars[] = {
     440             :         "LC_ALL",
     441             :         "LC_COLLATE",
     442             :         "LC_CTYPE",
     443             :         "LC_MONETARY",
     444             :         "LC_NUMERIC",
     445             :         "LC_TIME",
     446             :         "LC_MESSAGES",
     447             :         "LANG"
     448             :     };
     449             : 
     450             :     try
     451             :     {
     452             :         // this constructor is similar to setlocale(LC_ALL, ""),
     453             :         // but instead of returning NULL, it throws runtime_error
     454             :         // when the first locale env variable found contains an invalid value
     455           0 :         std::locale("");
     456             :     }
     457           0 :     catch (std::runtime_error&)
     458             :     {
     459           0 :         LOGWARNING("Invalid locale settings");
     460             : 
     461           0 :         for (size_t i = 0; i < ARRAY_SIZE(LocaleEnvVars); i++)
     462             :         {
     463           0 :             if (char* envval = getenv(LocaleEnvVars[i]))
     464           0 :                 LOGWARNING("  %s=\"%s\"", LocaleEnvVars[i], envval);
     465             :             else
     466           0 :                 LOGWARNING("  %s=\"(unset)\"", LocaleEnvVars[i]);
     467             :         }
     468             : 
     469             :         // We should set LC_ALL since it overrides LANG
     470           0 :         if (setenv("LC_ALL", std::locale::classic().name().c_str(), 1))
     471           0 :             debug_warn(L"Invalid locale settings, and unable to set LC_ALL env variable.");
     472             :         else
     473           0 :             LOGWARNING("Setting LC_ALL env variable to: %s", getenv("LC_ALL"));
     474             :     }
     475           0 : }
     476             : #else
     477             : static void FixLocales()
     478             : {
     479             :     // Do nothing on Windows
     480             : }
     481             : #endif
     482             : 
     483           0 : void EarlyInit()
     484             : {
     485             :     // If you ever want to catch a particular allocation:
     486             :     //_CrtSetBreakAlloc(232647);
     487             : 
     488           0 :     Threading::SetMainThread();
     489             : 
     490           0 :     debug_SetThreadName("main");
     491             :     // add all debug_printf "tags" that we are interested in:
     492           0 :     debug_filter_add("TIMER");
     493           0 :     debug_filter_add("FILES");
     494             : 
     495           0 :     timer_Init();
     496             : 
     497             :     // initialise profiler early so it can profile startup,
     498             :     // but only after LatchStartTime
     499           0 :     g_Profiler2.Initialise();
     500             : 
     501           0 :     FixLocales();
     502             : 
     503             :     // Because we do GL calls from a secondary thread, Xlib needs to
     504             :     // be told to support multiple threads safely.
     505             :     // This is needed for Atlas, but we have to call it before any other
     506             :     // Xlib functions (e.g. the ones used when drawing the main menu
     507             :     // before launching Atlas)
     508             : #if MUST_INIT_X11
     509           0 :     int status = XInitThreads();
     510           0 :     if (status == 0)
     511           0 :         debug_printf("Error enabling thread-safety via XInitThreads\n");
     512             : #endif
     513             : 
     514             :     // Initialise the low-quality rand function
     515           0 :     srand(time(NULL));  // NOTE: this rand should *not* be used for simulation!
     516           0 : }
     517             : 
     518             : bool Autostart(const CmdLineArgs& args);
     519             : 
     520             : /**
     521             :  * Returns true if the user has intended to start a visual replay from command line.
     522             :  */
     523             : bool AutostartVisualReplay(const std::string& replayFile);
     524             : 
     525           0 : bool Init(const CmdLineArgs& args, int flags)
     526             : {
     527             :     // Do this as soon as possible, because it chdirs
     528             :     // and will mess up the error reporting if anything
     529             :     // crashes before the working directory is set.
     530           0 :     InitVfs(args, flags);
     531             : 
     532             :     // This must come after VFS init, which sets the current directory
     533             :     // (required for finding our output log files).
     534           0 :     g_Logger = new CLogger;
     535             : 
     536           0 :     new CProfileViewer;
     537           0 :     new CProfileManager;    // before any script code
     538             : 
     539           0 :     g_ScriptStatsTable = new CScriptStatsTable;
     540           0 :     g_ProfileViewer.AddRootTable(g_ScriptStatsTable);
     541             : 
     542             :     // Set up the console early, so that debugging
     543             :     // messages can be logged to it. (The console's size
     544             :     // and fonts are set later in InitPs())
     545           0 :     g_Console = new CConsole();
     546             : 
     547             :     // g_ConfigDB, command line args, globals
     548           0 :     CONFIG_Init(args);
     549             : 
     550             :     // Using a global object for the context is a workaround until Simulation and AI use
     551             :     // their own threads and also their own contexts.
     552           0 :     const int contextSize = 384 * 1024 * 1024;
     553           0 :     const int heapGrowthBytesGCTrigger = 20 * 1024 * 1024;
     554           0 :     g_ScriptContext = ScriptContext::CreateContext(contextSize, heapGrowthBytesGCTrigger);
     555             : 
     556             :     // On the first Init (INIT_MODS), check for command-line arguments
     557             :     // or use the default mods from the config and enable those.
     558             :     // On later engine restarts (e.g. the mod selector), we will skip this path,
     559             :     // to avoid overwriting the newly selected mods.
     560           0 :     if (flags & INIT_MODS)
     561             :     {
     562           0 :         ScriptInterface modInterface("Engine", "Mod", g_ScriptContext);
     563           0 :         g_Mods.UpdateAvailableMods(modInterface);
     564           0 :         std::vector<CStr> mods;
     565           0 :         if (args.Has("mod"))
     566           0 :             mods = args.GetMultiple("mod");
     567             :         else
     568             :         {
     569           0 :             CStr modsStr;
     570           0 :             CFG_GET_VAL("mod.enabledmods", modsStr);
     571           0 :             boost::split(mods, modsStr, boost::algorithm::is_space(), boost::token_compress_on);
     572             :         }
     573             : 
     574           0 :         if (!g_Mods.EnableMods(mods, flags & INIT_MODS_PUBLIC))
     575             :         {
     576             :             // In non-visual mode, fail entirely.
     577           0 :             if (args.Has("autostart-nonvisual"))
     578             :             {
     579           0 :                 LOGERROR("Trying to start with incompatible mods: %s.", boost::algorithm::join(g_Mods.GetIncompatibleMods(), ", "));
     580           0 :                 return false;
     581             :             }
     582             :         }
     583             :     }
     584             :     // If there are incompatible mods, switch to the mod selector so players can resolve the problem.
     585           0 :     if (g_Mods.GetIncompatibleMods().empty())
     586           0 :         MountMods(Paths(args), g_Mods.GetEnabledMods());
     587             :     else
     588           0 :         MountMods(Paths(args), { "mod" });
     589             : 
     590             :     // Special command-line mode to dump the entity schemas instead of running the game.
     591             :     // (This must be done after loading VFS etc, but should be done before wasting time
     592             :     // on anything else.)
     593           0 :     if (args.Has("dumpSchema"))
     594             :     {
     595           0 :         CSimulation2 sim(NULL, g_ScriptContext, NULL);
     596           0 :         sim.LoadDefaultScripts();
     597           0 :         std::ofstream f("entity.rng", std::ios_base::out | std::ios_base::trunc);
     598           0 :         f << sim.GenerateSchema();
     599           0 :         std::cout << "Generated entity.rng\n";
     600           0 :         exit(0);
     601             :     }
     602             : 
     603           0 :     CNetHost::Initialize();
     604             : 
     605             : #if CONFIG2_AUDIO
     606           0 :     if (!args.Has("autostart-nonvisual") && !g_DisableAudio)
     607           0 :         ISoundManager::CreateSoundManager();
     608             : #endif
     609             : 
     610           0 :     new L10n;
     611             : 
     612             :     // Optionally start profiler HTTP output automatically
     613             :     // (By default it's only enabled by a hotkey, for security/performance)
     614           0 :     bool profilerHTTPEnable = false;
     615           0 :     CFG_GET_VAL("profiler2.autoenable", profilerHTTPEnable);
     616           0 :     if (profilerHTTPEnable)
     617           0 :         g_Profiler2.EnableHTTP();
     618             : 
     619             :     // Initialise everything except Win32 sockets (because our networking
     620             :     // system already inits those)
     621           0 :     curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32);
     622             : 
     623           0 :     if (!g_Quickstart)
     624           0 :         g_UserReporter.Initialize(); // after config
     625             : 
     626           0 :     PROFILE2_EVENT("Init finished");
     627           0 :     return true;
     628             : }
     629             : 
     630           0 : void InitGraphics(const CmdLineArgs& args, int flags, const std::vector<CStr>& installedMods)
     631             : {
     632           0 :     const bool setup_vmode = (flags & INIT_HAVE_VMODE) == 0;
     633             : 
     634           0 :     if(setup_vmode)
     635             :     {
     636           0 :         InitSDL();
     637             : 
     638           0 :         if (!g_VideoMode.InitSDL())
     639           0 :             throw PSERROR_System_VmodeFailed(); // abort startup
     640             :     }
     641             : 
     642           0 :     RunHardwareDetection();
     643             : 
     644             :     // Optionally start profiler GPU timings automatically
     645             :     // (By default it's only enabled by a hotkey, for performance/compatibility)
     646           0 :     bool profilerGPUEnable = false;
     647           0 :     CFG_GET_VAL("profiler2.autoenable", profilerGPUEnable);
     648           0 :     if (profilerGPUEnable)
     649           0 :         g_Profiler2.EnableGPU();
     650             : 
     651           0 :     if(!g_Quickstart)
     652             :     {
     653           0 :         WriteSystemInfo();
     654             :         // note: no longer vfs_display here. it's dog-slow due to unbuffered
     655             :         // file output and very rarely needed.
     656             :     }
     657             : 
     658           0 :     if(g_DisableAudio)
     659           0 :         ISoundManager::SetEnabled(false);
     660             : 
     661           0 :     g_GUI = new CGUIManager();
     662             : 
     663           0 :     CStr8 renderPath = "default";
     664           0 :     CFG_GET_VAL("renderpath", renderPath);
     665           0 :     if (RenderPathEnum::FromString(renderPath) == FIXED)
     666             :     {
     667             :         // It doesn't make sense to continue working here, because we're not
     668             :         // able to display anything.
     669           0 :         DEBUG_DISPLAY_FATAL_ERROR(
     670             :             L"Your graphics card doesn't appear to be fully compatible with OpenGL shaders."
     671             :             L" The game does not support pre-shader graphics cards."
     672             :             L" You are advised to try installing newer drivers and/or upgrade your graphics card."
     673             :             L" For more information, please see http://www.wildfiregames.com/forum/index.php?showtopic=16734"
     674             :         );
     675             :     }
     676             : 
     677           0 :     g_RenderingOptions.ReadConfigAndSetupHooks();
     678             : 
     679             :     // create renderer
     680           0 :     new CRenderer;
     681             : 
     682           0 :     InitInput();
     683             : 
     684             :     // TODO: Is this the best place for this?
     685           0 :     if (VfsDirectoryExists(L"maps/"))
     686           0 :         CXeromyces::AddValidator(g_VFS, "map", "maps/scenario.rng");
     687             : 
     688             :     try
     689             :     {
     690           0 :         if (!AutostartVisualReplay(args.Get("replay-visual")) && !Autostart(args))
     691             :         {
     692           0 :             const bool setup_gui = ((flags & INIT_NO_GUI) == 0);
     693             :             // We only want to display the splash screen at startup
     694           0 :             std::shared_ptr<ScriptInterface> scriptInterface = g_GUI->GetScriptInterface();
     695           0 :             ScriptRequest rq(scriptInterface);
     696           0 :             JS::RootedValue data(rq.cx);
     697           0 :             if (g_GUI)
     698             :             {
     699           0 :                 Script::CreateObject(rq, &data, "isStartup", true);
     700           0 :                 if (!installedMods.empty())
     701           0 :                     Script::SetProperty(rq, data, "installedMods", installedMods);
     702             :             }
     703           0 :             InitPs(setup_gui, installedMods.empty() ? L"page_pregame.xml" : L"page_modmod.xml", g_GUI->GetScriptInterface().get(), data);
     704             :         }
     705             :     }
     706           0 :     catch (PSERROR_Game_World_MapLoadFailed& e)
     707             :     {
     708             :         // Map Loading failed
     709             : 
     710             :         // Start the engine so we have a GUI
     711           0 :         InitPs(true, L"page_pregame.xml", NULL, JS::UndefinedHandleValue);
     712             : 
     713             :         // Call script function to do the actual work
     714             :         //  (delete game data, switch GUI page, show error, etc.)
     715           0 :         CancelLoad(CStr(e.what()).FromUTF8());
     716             :     }
     717           0 : }
     718             : 
     719           0 : bool InitNonVisual(const CmdLineArgs& args)
     720             : {
     721           0 :     return Autostart(args);
     722             : }
     723             : 
     724             : /**
     725             :  * Temporarily loads a scenario map and retrieves the "ScriptSettings" JSON
     726             :  * data from it.
     727             :  * The scenario map format is used for scenario and skirmish map types (random
     728             :  * games do not use a "map" (format) but a small JavaScript program which
     729             :  * creates a map on the fly). It contains a section to initialize the game
     730             :  * setup screen.
     731             :  * @param mapPath Absolute path (from VFS root) to the map file to peek in.
     732             :  * @return ScriptSettings in JSON format extracted from the map.
     733             :  */
     734           0 : CStr8 LoadSettingsOfScenarioMap(const VfsPath &mapPath)
     735             : {
     736           0 :     CXeromyces mapFile;
     737           0 :     const char *pathToSettings[] =
     738             :     {
     739             :         "Scenario", "ScriptSettings", ""  // Path to JSON data in map
     740             :     };
     741             : 
     742           0 :     Status loadResult = mapFile.Load(g_VFS, mapPath);
     743             : 
     744           0 :     if (INFO::OK != loadResult)
     745             :     {
     746           0 :         LOGERROR("LoadSettingsOfScenarioMap: Unable to load map file '%s'", mapPath.string8());
     747           0 :         throw PSERROR_Game_World_MapLoadFailed("Unable to load map file, check the path for typos.");
     748             :     }
     749           0 :     XMBElement mapElement = mapFile.GetRoot();
     750             : 
     751             :     // Select the ScriptSettings node in the map file...
     752           0 :     for (int i = 0; pathToSettings[i][0]; ++i)
     753             :     {
     754           0 :         int childId = mapFile.GetElementID(pathToSettings[i]);
     755             : 
     756           0 :         XMBElementList nodes = mapElement.GetChildNodes();
     757           0 :         auto it = std::find_if(nodes.begin(), nodes.end(), [&childId](const XMBElement& child) {
     758           0 :             return child.GetNodeName() == childId;
     759           0 :         });
     760             : 
     761           0 :         if (it != nodes.end())
     762           0 :             mapElement = *it;
     763             :     }
     764             :     // ... they contain a JSON document to initialize the game setup
     765             :     // screen
     766           0 :     return mapElement.GetText();
     767             : }
     768             : 
     769             : // TODO: this essentially duplicates the CGUI logic to load directory or scripts.
     770             : // NB: this won't make sure to not double-load scripts, unlike the GUI.
     771           0 : void AutostartLoadScript(const ScriptInterface& scriptInterface, const VfsPath& path)
     772             : {
     773           0 :     if (path.IsDirectory())
     774             :     {
     775           0 :         VfsPaths pathnames;
     776           0 :         vfs::GetPathnames(g_VFS, path, L"*.js", pathnames);
     777           0 :         for (const VfsPath& file : pathnames)
     778           0 :             scriptInterface.LoadGlobalScriptFile(file);
     779             :     }
     780             :     else
     781           0 :         scriptInterface.LoadGlobalScriptFile(path);
     782           0 : }
     783             : 
     784             : // TODO: this essentially duplicates the CGUI function
     785           0 : CParamNode GetTemplate(const std::string& templateName)
     786             : {
     787             :     // This is very cheap to create so let's just do it every time.
     788           0 :     CTemplateLoader templateLoader;
     789             : 
     790           0 :     const CParamNode& templateRoot = templateLoader.GetTemplateFileData(templateName).GetOnlyChild();
     791           0 :     if (!templateRoot.IsOk())
     792           0 :         LOGERROR("Invalid template found for '%s'", templateName.c_str());
     793             : 
     794           0 :     return templateRoot;
     795             : }
     796             : 
     797             : /*
     798             :  * Command line options for autostart
     799             :  * (keep synchronized with binaries/system/readme.txt):
     800             :  *
     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 (skirmish and random maps only).
     811             :  *                                 Use random for a random civ.
     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
     827             :  *
     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)
     839             :  *
     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-client=127.0.0.1 -autostart-playername="Alice"
     845             :  * The players use the developer overlay to control players.
     846             :  *
     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
     849             :  *
     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
     852             :  */
     853           0 : bool Autostart(const CmdLineArgs& args)
     854             : {
     855           0 :     if (!args.Has("autostart-client") && !args.Has("autostart"))
     856           0 :         return false;
     857             : 
     858             :     // Get optional playername.
     859           0 :     CStrW userName = L"anonymous";
     860           0 :     if (args.Has("autostart-playername"))
     861           0 :         userName = args.Get("autostart-playername").FromUTF8();
     862             : 
     863             :     // Create some scriptinterface to store the js values for the settings.
     864           0 :     ScriptInterface scriptInterface("Engine", "Game Setup", g_ScriptContext);
     865             : 
     866           0 :     ScriptRequest rq(scriptInterface);
     867             : 
     868             :     // We use the javascript gameSettings to handle options, but that requires running JS.
     869             :     // Since we don't want to use the full Gui manager, we load an entrypoint script
     870             :     // that can run the priviledged "LoadScript" function, and then call the appropriate function.
     871           0 :     ScriptFunction::Register<&AutostartLoadScript>(rq, "LoadScript");
     872             :     // Load the entire folder to allow mods to extend the entrypoint without copying the whole file.
     873           0 :     AutostartLoadScript(scriptInterface, VfsPath(L"autostart/"));
     874             : 
     875             :     // Provide some required functions to the script.
     876           0 :     if (args.Has("autostart-nonvisual"))
     877           0 :         ScriptFunction::Register<&GetTemplate>(rq, "GetTemplate");
     878             :     else
     879             :     {
     880           0 :         JSI_GUIManager::RegisterScriptFunctions(rq);
     881             :         // TODO: this loads pregame, which is hardcoded to exist by various code paths. That ought be changed.
     882           0 :         InitPs(false, L"page_pregame.xml", g_GUI->GetScriptInterface().get(), JS::UndefinedHandleValue);
     883             :     }
     884             : 
     885           0 :     JSI_Game::RegisterScriptFunctions(rq);
     886           0 :     JSI_Main::RegisterScriptFunctions(rq);
     887           0 :     JSI_Simulation::RegisterScriptFunctions(rq);
     888           0 :     JSI_VFS::RegisterScriptFunctions_ReadWriteAnywhere(rq);
     889           0 :     JSI_Network::RegisterScriptFunctions(rq);
     890             : 
     891           0 :     JS::RootedValue sessionInitData(rq.cx);
     892             : 
     893           0 :     if (args.Has("autostart-client"))
     894             :     {
     895           0 :         CStr ip = args.Get("autostart-client");
     896           0 :         if (ip.empty())
     897           0 :             ip = "127.0.0.1";
     898             : 
     899           0 :         Script::CreateObject(
     900             :             rq,
     901             :             &sessionInitData,
     902             :             "playerName", userName,
     903             :             "ip", ip,
     904             :             "port", PS_DEFAULT_PORT,
     905           0 :             "storeReplay", !args.Has("autostart-disable-replay"));
     906             : 
     907           0 :         JS::RootedValue global(rq.cx, rq.globalValue());
     908           0 :         if (!ScriptFunction::CallVoid(rq, global, "autostartClient", sessionInitData, true))
     909           0 :             return false;
     910             : 
     911           0 :         bool shouldQuit = false;
     912           0 :         while (!shouldQuit)
     913             :         {
     914           0 :             g_NetClient->Poll();
     915           0 :             ScriptFunction::Call(rq, global, "onTick", shouldQuit);
     916           0 :             std::this_thread::sleep_for(std::chrono::microseconds(200));
     917             :         }
     918             : 
     919           0 :         if (args.Has("autostart-nonvisual"))
     920             :         {
     921           0 :             LDR_NonprogressiveLoad();
     922           0 :             g_Game->ReallyStartGame();
     923             :         }
     924           0 :         return true;
     925             :     }
     926             : 
     927           0 :     CStr autoStartName = args.Get("autostart");
     928             : 
     929           0 :     if (autoStartName.empty())
     930           0 :         return false;
     931             : 
     932           0 :     JS::RootedValue attrs(rq.cx);
     933           0 :     JS::RootedValue settings(rq.cx);
     934           0 :     JS::RootedValue playerData(rq.cx);
     935             : 
     936           0 :     Script::CreateObject(rq, &attrs);
     937           0 :     Script::CreateObject(rq, &settings);
     938           0 :     Script::CreateArray(rq, &playerData);
     939             : 
     940             :     // The directory in front of the actual map name indicates which type
     941             :     // of map is being loaded. Drawback of this approach is the association
     942             :     // of map types and folders is hard-coded, but benefits are:
     943             :     // - No need to pass the map type via command line separately
     944             :     // - Prevents mixing up of scenarios and skirmish maps to some degree
     945           0 :     Path mapPath = Path(autoStartName);
     946           0 :     std::wstring mapDirectory = mapPath.Parent().Filename().string();
     947           0 :     std::string mapType;
     948             : 
     949           0 :     if (mapDirectory == L"random")
     950             :     {
     951             :         // Get optional map size argument (default 192)
     952           0 :         uint mapSize = 192;
     953           0 :         if (args.Has("autostart-size"))
     954             :         {
     955           0 :             CStr size = args.Get("autostart-size");
     956           0 :             mapSize = size.ToUInt();
     957             :         }
     958             : 
     959           0 :         Script::SetProperty(rq, settings, "Size", mapSize);       // Random map size (in patches)
     960             : 
     961             :         // Get optional number of players (default 2)
     962           0 :         size_t numPlayers = 2;
     963           0 :         if (args.Has("autostart-players"))
     964             :         {
     965           0 :             CStr num = args.Get("autostart-players");
     966           0 :             numPlayers = num.ToUInt();
     967             :         }
     968             : 
     969             :         // Set up player data
     970           0 :         for (size_t i = 0; i < numPlayers; ++i)
     971             :         {
     972           0 :             JS::RootedValue player(rq.cx);
     973             : 
     974             :             // We could load player_defaults.json here, but that would complicate the logic
     975             :             // even more and autostart is only intended for developers anyway
     976           0 :             Script::CreateObject(rq, &player, "Civ", "athen");
     977             : 
     978           0 :             Script::SetPropertyInt(rq, playerData, i, player);
     979             :         }
     980           0 :         mapType = "random";
     981             :     }
     982           0 :     else if (mapDirectory == L"scenarios")
     983           0 :         mapType = "scenario";
     984           0 :     else if (mapDirectory == L"skirmishes")
     985           0 :         mapType = "skirmish";
     986             :     else
     987             :     {
     988           0 :         LOGERROR("Autostart: Unrecognized map type '%s'", utf8_from_wstring(mapDirectory));
     989           0 :         throw PSERROR_Game_World_MapLoadFailed("Unrecognized map type.\nConsult readme.txt for the currently supported types.");
     990             :     }
     991             : 
     992           0 :     Script::SetProperty(rq, attrs, "mapType", mapType);
     993           0 :     Script::SetProperty(rq, attrs, "map", "maps/" + autoStartName);
     994           0 :     Script::SetProperty(rq, settings, "mapType", mapType);
     995           0 :     Script::SetProperty(rq, settings, "CheatsEnabled", true);
     996             : 
     997             :     // The seed is used for both random map generation and simulation
     998           0 :     u32 seed = 0;
     999           0 :     if (args.Has("autostart-seed"))
    1000             :     {
    1001           0 :         CStr seedArg = args.Get("autostart-seed");
    1002           0 :         if (seedArg == "-1")
    1003           0 :             seed = rand();
    1004             :         else
    1005           0 :             seed = seedArg.ToULong();
    1006             :     }
    1007           0 :     Script::SetProperty(rq, settings, "Seed", seed);
    1008             : 
    1009             :     // Set seed for AIs
    1010           0 :     u32 aiseed = 0;
    1011           0 :     if (args.Has("autostart-aiseed"))
    1012             :     {
    1013           0 :         CStr seedArg = args.Get("autostart-aiseed");
    1014           0 :         if (seedArg == "-1")
    1015           0 :             aiseed = rand();
    1016             :         else
    1017           0 :             aiseed = seedArg.ToULong();
    1018             :     }
    1019           0 :     Script::SetProperty(rq, settings, "AISeed", aiseed);
    1020             : 
    1021             :     // Set player data for AIs
    1022             :     //      attrs.settings = { PlayerData: [ { AI: ... }, ... ] }
    1023             :     //                  or = { PlayerData: [ null, { AI: ... }, ... ] } when gaia set
    1024           0 :     int offset = 1;
    1025           0 :     JS::RootedValue player(rq.cx);
    1026           0 :     if (Script::GetPropertyInt(rq, playerData, 0, &player) && player.isNull())
    1027           0 :         offset = 0;
    1028             : 
    1029             :     // Set teams
    1030           0 :     if (args.Has("autostart-team"))
    1031             :     {
    1032           0 :         std::vector<CStr> civArgs = args.GetMultiple("autostart-team");
    1033           0 :         for (size_t i = 0; i < civArgs.size(); ++i)
    1034             :         {
    1035           0 :             int playerID = civArgs[i].BeforeFirst(":").ToInt();
    1036             : 
    1037             :             // Instead of overwriting existing player data, modify the array
    1038           0 :             JS::RootedValue currentPlayer(rq.cx);
    1039           0 :             if (!Script::GetPropertyInt(rq, playerData, playerID-offset, &currentPlayer) || currentPlayer.isUndefined())
    1040           0 :                 Script::CreateObject(rq, &currentPlayer);
    1041             : 
    1042           0 :             int teamID = civArgs[i].AfterFirst(":").ToInt() - 1;
    1043           0 :             Script::SetProperty(rq, currentPlayer, "Team", teamID);
    1044           0 :             Script::SetPropertyInt(rq, playerData, playerID-offset, currentPlayer);
    1045             :         }
    1046             :     }
    1047             : 
    1048           0 :     int ceasefire = 0;
    1049           0 :     if (args.Has("autostart-ceasefire"))
    1050           0 :         ceasefire = args.Get("autostart-ceasefire").ToInt();
    1051           0 :     Script::SetProperty(rq, settings, "Ceasefire", ceasefire);
    1052             : 
    1053           0 :     if (args.Has("autostart-ai"))
    1054             :     {
    1055           0 :         std::vector<CStr> aiArgs = args.GetMultiple("autostart-ai");
    1056           0 :         for (size_t i = 0; i < aiArgs.size(); ++i)
    1057             :         {
    1058           0 :             int playerID = aiArgs[i].BeforeFirst(":").ToInt();
    1059             : 
    1060             :             // Instead of overwriting existing player data, modify the array
    1061           0 :             JS::RootedValue currentPlayer(rq.cx);
    1062           0 :             if (!Script::GetPropertyInt(rq, playerData, playerID-offset, &currentPlayer) || currentPlayer.isUndefined())
    1063           0 :                 Script::CreateObject(rq, &currentPlayer);
    1064             : 
    1065           0 :             Script::SetProperty(rq, currentPlayer, "AI", aiArgs[i].AfterFirst(":"));
    1066           0 :             Script::SetProperty(rq, currentPlayer, "AIDiff", 3);
    1067           0 :             Script::SetProperty(rq, currentPlayer, "AIBehavior", "balanced");
    1068           0 :             Script::SetPropertyInt(rq, playerData, playerID-offset, currentPlayer);
    1069             :         }
    1070             :     }
    1071             :     // Set AI difficulty
    1072           0 :     if (args.Has("autostart-aidiff"))
    1073             :     {
    1074           0 :         std::vector<CStr> civArgs = args.GetMultiple("autostart-aidiff");
    1075           0 :         for (size_t i = 0; i < civArgs.size(); ++i)
    1076             :         {
    1077           0 :             int playerID = civArgs[i].BeforeFirst(":").ToInt();
    1078             : 
    1079             :             // Instead of overwriting existing player data, modify the array
    1080           0 :             JS::RootedValue currentPlayer(rq.cx);
    1081           0 :             if (!Script::GetPropertyInt(rq, playerData, playerID-offset, &currentPlayer) || currentPlayer.isUndefined())
    1082           0 :                 Script::CreateObject(rq, &currentPlayer);
    1083             : 
    1084           0 :             Script::SetProperty(rq, currentPlayer, "AIDiff", civArgs[i].AfterFirst(":").ToInt());
    1085           0 :             Script::SetPropertyInt(rq, playerData, playerID-offset, currentPlayer);
    1086             :         }
    1087             :     }
    1088             :     // Set player data for Civs
    1089           0 :     if (args.Has("autostart-civ"))
    1090             :     {
    1091           0 :         if (mapDirectory != L"scenarios")
    1092             :         {
    1093           0 :             std::vector<CStr> civArgs = args.GetMultiple("autostart-civ");
    1094           0 :             for (size_t i = 0; i < civArgs.size(); ++i)
    1095             :             {
    1096           0 :                 int playerID = civArgs[i].BeforeFirst(":").ToInt();
    1097             : 
    1098             :                 // Instead of overwriting existing player data, modify the array
    1099           0 :                 JS::RootedValue currentPlayer(rq.cx);
    1100           0 :                 if (!Script::GetPropertyInt(rq, playerData, playerID-offset, &currentPlayer) || currentPlayer.isUndefined())
    1101           0 :                     Script::CreateObject(rq, &currentPlayer);
    1102             : 
    1103           0 :                 Script::SetProperty(rq, currentPlayer, "Civ", civArgs[i].AfterFirst(":"));
    1104           0 :                 Script::SetPropertyInt(rq, playerData, playerID-offset, currentPlayer);
    1105             :             }
    1106             :         }
    1107             :         else
    1108           0 :             LOGWARNING("Autostart: Option 'autostart-civ' is invalid for scenarios");
    1109             :     }
    1110             : 
    1111             :     // Add additional scripts to the TriggerScripts property
    1112           0 :     std::vector<CStrW> triggerScriptsVector;
    1113           0 :     JS::RootedValue triggerScripts(rq.cx);
    1114             : 
    1115           0 :     if (Script::HasProperty(rq, settings, "TriggerScripts"))
    1116             :     {
    1117           0 :         Script::GetProperty(rq, settings, "TriggerScripts", &triggerScripts);
    1118           0 :         Script::FromJSVal(rq, triggerScripts, triggerScriptsVector);
    1119             :     }
    1120             : 
    1121           0 :     if (!CRenderer::IsInitialised())
    1122             :     {
    1123           0 :         CStr nonVisualScript = "scripts/NonVisualTrigger.js";
    1124           0 :         triggerScriptsVector.push_back(nonVisualScript.FromUTF8());
    1125             :     }
    1126             : 
    1127           0 :     Script::ToJSVal(rq, &triggerScripts, triggerScriptsVector);
    1128           0 :     Script::SetProperty(rq, settings, "TriggerScripts", triggerScripts);
    1129             : 
    1130           0 :     std::vector<CStr> victoryConditions(1, "conquest");
    1131           0 :     if (args.Has("autostart-victory"))
    1132           0 :         victoryConditions = args.GetMultiple("autostart-victory");
    1133             : 
    1134           0 :     if (victoryConditions.size() == 1 && victoryConditions[0] == "endless")
    1135           0 :         victoryConditions.clear();
    1136             : 
    1137           0 :     Script::SetProperty(rq, settings, "VictoryConditions", victoryConditions);
    1138             : 
    1139           0 :     int wonderDuration = 10;
    1140           0 :     if (args.Has("autostart-wonderduration"))
    1141           0 :         wonderDuration = args.Get("autostart-wonderduration").ToInt();
    1142           0 :     Script::SetProperty(rq, settings, "WonderDuration", wonderDuration);
    1143             : 
    1144           0 :     int relicDuration = 10;
    1145           0 :     if (args.Has("autostart-relicduration"))
    1146           0 :         relicDuration = args.Get("autostart-relicduration").ToInt();
    1147           0 :     Script::SetProperty(rq, settings, "RelicDuration", relicDuration);
    1148             : 
    1149           0 :     int relicCount = 2;
    1150           0 :     if (args.Has("autostart-reliccount"))
    1151           0 :         relicCount = args.Get("autostart-reliccount").ToInt();
    1152           0 :     Script::SetProperty(rq, settings, "RelicCount", relicCount);
    1153             : 
    1154             :     // Add player data to map settings.
    1155           0 :     Script::SetProperty(rq, settings, "PlayerData", playerData);
    1156             : 
    1157             :     // Add map settings to game attributes.
    1158           0 :     Script::SetProperty(rq, attrs, "settings", settings);
    1159             : 
    1160           0 :     if (args.Has("autostart-host"))
    1161             :     {
    1162           0 :         int maxPlayers = 2;
    1163           0 :         if (args.Has("autostart-host-players"))
    1164           0 :             maxPlayers = args.Get("autostart-host-players").ToUInt();
    1165             : 
    1166           0 :         Script::CreateObject(
    1167             :             rq,
    1168             :             &sessionInitData,
    1169             :             "attribs", attrs,
    1170             :             "playerName", userName,
    1171             :             "port", PS_DEFAULT_PORT,
    1172             :             "maxPlayers", maxPlayers,
    1173           0 :             "storeReplay", !args.Has("autostart-disable-replay"));
    1174             : 
    1175           0 :         JS::RootedValue global(rq.cx, rq.globalValue());
    1176           0 :         if (!ScriptFunction::CallVoid(rq, global, "autostartHost", sessionInitData, true))
    1177           0 :             return false;
    1178             : 
    1179             :         // In MP host mode, we need to wait until clients have loaded.
    1180           0 :         bool shouldQuit = false;
    1181           0 :         while (!shouldQuit)
    1182             :         {
    1183           0 :             g_NetClient->Poll();
    1184           0 :             ScriptFunction::Call(rq, global, "onTick", shouldQuit);
    1185           0 :             std::this_thread::sleep_for(std::chrono::microseconds(200));
    1186             :         }
    1187             :     }
    1188             :     else
    1189             :     {
    1190           0 :         JS::RootedValue localPlayer(rq.cx);
    1191           0 :         Script::CreateObject(
    1192             :             rq,
    1193             :             &localPlayer,
    1194           0 :             "player", args.Has("autostart-player") ? args.Get("autostart-player").ToInt() : 1,
    1195             :             "name", userName);
    1196             : 
    1197           0 :         JS::RootedValue playerAssignments(rq.cx);
    1198           0 :         Script::CreateObject(rq, &playerAssignments);
    1199           0 :         Script::SetProperty(rq, playerAssignments, "local", localPlayer);
    1200             : 
    1201           0 :         Script::CreateObject(
    1202             :             rq,
    1203             :             &sessionInitData,
    1204             :             "attribs", attrs,
    1205             :             "playerAssignments", playerAssignments,
    1206           0 :             "storeReplay", !args.Has("autostart-disable-replay"));
    1207             : 
    1208           0 :         JS::RootedValue global(rq.cx, rq.globalValue());
    1209           0 :         if (!ScriptFunction::CallVoid(rq, global, "autostartHost", sessionInitData, false))
    1210           0 :             return false;
    1211             :     }
    1212             : 
    1213           0 :     if (args.Has("autostart-nonvisual"))
    1214             :     {
    1215           0 :         LDR_NonprogressiveLoad();
    1216           0 :         g_Game->ReallyStartGame();
    1217             :     }
    1218             : 
    1219           0 :     return true;
    1220             : }
    1221             : 
    1222           0 : bool AutostartVisualReplay(const std::string& replayFile)
    1223             : {
    1224           0 :     if (!FileExists(OsPath(replayFile)))
    1225           0 :         return false;
    1226             : 
    1227           0 :     g_Game = new CGame(false);
    1228           0 :     g_Game->SetPlayerID(-1);
    1229           0 :     g_Game->StartVisualReplay(replayFile);
    1230             : 
    1231           0 :     ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
    1232           0 :     ScriptRequest rq(scriptInterface);
    1233           0 :     JS::RootedValue attrs(rq.cx, g_Game->GetSimulation2()->GetInitAttributes());
    1234             : 
    1235           0 :     JS::RootedValue playerAssignments(rq.cx);
    1236           0 :     Script::CreateObject(rq, &playerAssignments);
    1237           0 :     JS::RootedValue localPlayer(rq.cx);
    1238           0 :     Script::CreateObject(rq, &localPlayer, "player", g_Game->GetPlayerID());
    1239           0 :     Script::SetProperty(rq, playerAssignments, "local", localPlayer);
    1240             : 
    1241           0 :     JS::RootedValue sessionInitData(rq.cx);
    1242             : 
    1243           0 :     Script::CreateObject(
    1244             :         rq,
    1245             :         &sessionInitData,
    1246             :         "attribs", attrs,
    1247             :         "playerAssignments", playerAssignments);
    1248             : 
    1249           0 :     InitPs(true, L"page_loading.xml", &scriptInterface, sessionInitData);
    1250             : 
    1251           0 :     return true;
    1252             : }
    1253             : 
    1254           0 : void CancelLoad(const CStrW& message)
    1255             : {
    1256           0 :     std::shared_ptr<ScriptInterface> pScriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface();
    1257           0 :     ScriptRequest rq(pScriptInterface);
    1258             : 
    1259           0 :     JS::RootedValue global(rq.cx, rq.globalValue());
    1260             : 
    1261           0 :     LDR_Cancel();
    1262             : 
    1263           0 :     if (g_GUI &&
    1264           0 :         g_GUI->GetPageCount() &&
    1265           0 :         Script::HasProperty(rq, global, "cancelOnLoadGameError"))
    1266           0 :         ScriptFunction::CallVoid(rq, global, "cancelOnLoadGameError", message);
    1267           0 : }
    1268             : 
    1269           0 : bool InDevelopmentCopy()
    1270             : {
    1271           0 :     if (!g_CheckedIfInDevelopmentCopy)
    1272             :     {
    1273           0 :         g_InDevelopmentCopy = (g_VFS->GetFileInfo(L"config/dev.cfg", NULL) == INFO::OK);
    1274           0 :         g_CheckedIfInDevelopmentCopy = true;
    1275             :     }
    1276           0 :     return g_InDevelopmentCopy;
    1277         730 : }

Generated by: LCOV version 1.13