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, ¤tPlayer) || currentPlayer.isUndefined())
1040 0 : Script::CreateObject(rq, ¤tPlayer);
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, ¤tPlayer) || currentPlayer.isUndefined())
1063 0 : Script::CreateObject(rq, ¤tPlayer);
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, ¤tPlayer) || currentPlayer.isUndefined())
1082 0 : Script::CreateObject(rq, ¤tPlayer);
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, ¤tPlayer) || currentPlayer.isUndefined())
1101 0 : Script::CreateObject(rq, ¤tPlayer);
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 : }
|