Slightly nerf gallic naked fanatic rush.
[0ad.git] / source / main.cpp
blob614887f22616ce7ea88f54e92167dd0f2d694e63
1 /* Copyright (C) 2016 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
20 This module drives the game when running without Atlas (our integrated
21 map editor). It receives input and OS messages via SDL and feeds them
22 into the input dispatcher, where they are passed on to the game GUI and
23 simulation.
24 It also contains main(), which either runs the above controller or
25 that of Atlas depending on commandline parameters.
29 // not for any PCH effort, but instead for the (common) definitions
30 // included there.
31 #define MINIMAL_PCH 2
32 #include "lib/precompiled.h"
34 #include "lib/debug.h"
35 #include "lib/status.h"
36 #include "lib/secure_crt.h"
37 #include "lib/frequency_filter.h"
38 #include "lib/input.h"
39 #include "lib/ogl.h"
40 #include "lib/timer.h"
41 #include "lib/external_libraries/libsdl.h"
43 #include "ps/ArchiveBuilder.h"
44 #include "ps/CConsole.h"
45 #include "ps/CLogger.h"
46 #include "ps/ConfigDB.h"
47 #include "ps/Filesystem.h"
48 #include "ps/Game.h"
49 #include "ps/Globals.h"
50 #include "ps/Hotkey.h"
51 #include "ps/Loader.h"
52 #include "ps/Profile.h"
53 #include "ps/Profiler2.h"
54 #include "ps/Pyrogenesis.h"
55 #include "ps/Replay.h"
56 #include "ps/TouchInput.h"
57 #include "ps/UserReport.h"
58 #include "ps/Util.h"
59 #include "ps/VideoMode.h"
60 #include "ps/World.h"
61 #include "ps/GameSetup/GameSetup.h"
62 #include "ps/GameSetup/Atlas.h"
63 #include "ps/GameSetup/Config.h"
64 #include "ps/GameSetup/CmdLineArgs.h"
65 #include "ps/GameSetup/Paths.h"
66 #include "ps/XML/Xeromyces.h"
67 #include "network/NetClient.h"
68 #include "network/NetServer.h"
69 #include "network/NetSession.h"
70 #include "lobby/IXmppClient.h"
71 #include "graphics/Camera.h"
72 #include "graphics/GameView.h"
73 #include "graphics/TextureManager.h"
74 #include "gui/GUIManager.h"
75 #include "renderer/Renderer.h"
76 #include "scriptinterface/ScriptEngine.h"
77 #include "simulation2/Simulation2.h"
79 #if OS_UNIX
80 #include <unistd.h> // geteuid
81 #endif // OS_UNIX
83 extern bool g_GameRestarted;
85 void kill_mainloop();
87 // to avoid redundant and/or recursive resizing, we save the new
88 // size after VIDEORESIZE messages and only update the video mode
89 // once per frame.
90 // these values are the latest resize message, and reset to 0 once we've
91 // updated the video mode
92 static int g_ResizedW;
93 static int g_ResizedH;
95 // main app message handler
96 static InReaction MainInputHandler(const SDL_Event_* ev)
98 switch(ev->ev.type)
100 case SDL_WINDOWEVENT:
101 switch(ev->ev.window.event)
103 case SDL_WINDOWEVENT_ENTER:
104 RenderCursor(true);
105 break;
106 case SDL_WINDOWEVENT_LEAVE:
107 RenderCursor(false);
108 break;
109 case SDL_WINDOWEVENT_RESIZED:
110 g_ResizedW = ev->ev.window.data1;
111 g_ResizedH = ev->ev.window.data2;
112 break;
113 case SDL_WINDOWEVENT_MOVED:
114 g_VideoMode.UpdatePosition(ev->ev.window.data1, ev->ev.window.data2);
116 break;
118 case SDL_QUIT:
119 kill_mainloop();
120 break;
122 case SDL_HOTKEYDOWN:
123 std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
124 if (hotkey == "exit")
126 kill_mainloop();
127 return IN_HANDLED;
129 else if (hotkey == "screenshot")
131 WriteScreenshot(L".png");
132 return IN_HANDLED;
134 else if (hotkey == "bigscreenshot")
136 WriteBigScreenshot(L".bmp", 10);
137 return IN_HANDLED;
139 else if (hotkey == "togglefullscreen")
141 g_VideoMode.ToggleFullscreen();
142 return IN_HANDLED;
144 else if (hotkey == "profile2.toggle")
146 g_Profiler2.Toggle();
147 return IN_HANDLED;
149 break;
152 return IN_PASS;
156 // dispatch all pending events to the various receivers.
157 static void PumpEvents()
159 JSContext* cx = g_GUI->GetScriptInterface()->GetContext();
160 JSAutoRequest rq(cx);
162 PROFILE3("dispatch events");
164 SDL_Event_ ev;
165 while (in_poll_event(&ev))
167 PROFILE2("event");
168 if (g_GUI)
170 JS::RootedValue tmpVal(cx);
171 ScriptInterface::ToJSVal(cx, &tmpVal, ev);
172 std::string data = g_GUI->GetScriptInterface()->StringifyJSON(&tmpVal);
173 PROFILE2_ATTR("%s", data.c_str());
175 in_dispatch_event(&ev);
178 g_TouchInput.Frame();
182 static int ProgressiveLoad()
184 PROFILE3("progressive load");
186 wchar_t description[100];
187 int progress_percent;
190 Status ret = LDR_ProgressiveLoad(10e-3, description, ARRAY_SIZE(description), &progress_percent);
191 switch(ret)
193 // no load active => no-op (skip code below)
194 case INFO::OK:
195 return 0;
196 // current task didn't complete. we only care about this insofar as the
197 // load process is therefore not yet finished.
198 case ERR::TIMED_OUT:
199 break;
200 // just finished loading
201 case INFO::ALL_COMPLETE:
202 g_Game->ReallyStartGame();
203 wcscpy_s(description, ARRAY_SIZE(description), L"Game is starting..");
204 // LDR_ProgressiveLoad returns L""; set to valid text to
205 // avoid problems in converting to JSString
206 break;
207 // error!
208 default:
209 WARN_RETURN_STATUS_IF_ERR(ret);
210 // can't do this above due to legit ERR::TIMED_OUT
211 break;
214 catch (PSERROR_Game_World_MapLoadFailed& e)
216 // Map loading failed
218 // Call script function to do the actual work
219 // (delete game data, switch GUI page, show error, etc.)
220 CancelLoad(CStr(e.what()).FromUTF8());
223 GUI_DisplayLoadProgress(progress_percent, description);
224 return 0;
228 static void RendererIncrementalLoad()
230 PROFILE3("renderer incremental load");
232 const double maxTime = 0.1f;
234 double startTime = timer_Time();
235 bool more;
236 do {
237 more = g_Renderer.GetTextureManager().MakeProgress();
239 while (more && timer_Time() - startTime < maxTime);
243 static bool quit = false; // break out of main loop
245 static void Frame()
247 g_Profiler2.RecordFrameStart();
248 PROFILE2("frame");
249 g_Profiler2.IncrementFrameNumber();
250 PROFILE2_ATTR("%d", g_Profiler2.GetFrameNumber());
252 ogl_WarnIfError();
254 // get elapsed time
255 const double time = timer_Time();
256 g_frequencyFilter->Update(time);
257 // .. old method - "exact" but contains jumps
258 #if 0
259 static double last_time;
260 const double time = timer_Time();
261 const float TimeSinceLastFrame = (float)(time-last_time);
262 last_time = time;
263 ONCE(return); // first call: set last_time and return
265 // .. new method - filtered and more smooth, but errors may accumulate
266 #else
267 const float realTimeSinceLastFrame = 1.0 / g_frequencyFilter->SmoothedFrequency();
268 #endif
269 ENSURE(realTimeSinceLastFrame > 0.0f);
271 // decide if update/render is necessary
272 bool need_render = !g_app_minimized;
273 bool need_update = true;
275 // If we are not running a multiplayer game, disable updates when the game is
276 // minimized or out of focus and relinquish the CPU a bit, in order to make
277 // debugging easier.
278 if(g_PauseOnFocusLoss && !g_NetClient && !g_app_has_focus)
280 PROFILE3("non-focus delay");
281 need_update = false;
282 // don't use SDL_WaitEvent: don't want the main loop to freeze until app focus is restored
283 SDL_Delay(10);
286 // Throttling: limit update and render frequency to the minimum to 50 FPS
287 // in the "inactive" state, so that other windows get enough CPU time,
288 // (and it's always nice for power+thermal management).
289 // TODO: when the game performance is high enough, implementing a limit for
290 // in-game framerate might be sensible.
291 const float maxFPSMenu = 50.0;
292 bool limit_fps = false;
293 CFG_GET_VAL("gui.menu.limitfps", limit_fps);
294 if (limit_fps && (!g_Game || !g_Game->IsGameStarted()))
296 float remainingFrameTime = (1000.0 / maxFPSMenu) - realTimeSinceLastFrame;
297 if (remainingFrameTime > 0)
298 SDL_Delay(remainingFrameTime);
302 // this scans for changed files/directories and reloads them, thus
303 // allowing hotloading (changes are immediately assimilated in-game).
304 ReloadChangedFiles();
306 ProgressiveLoad();
308 RendererIncrementalLoad();
310 PumpEvents();
312 // if the user quit by closing the window, the GL context will be broken and
313 // may crash when we call Render() on some drivers, so leave this loop
314 // before rendering
315 if (quit)
316 return;
318 // respond to pumped resize events
319 if (g_ResizedW || g_ResizedH)
321 g_VideoMode.ResizeWindow(g_ResizedW, g_ResizedH);
322 g_ResizedW = g_ResizedH = 0;
325 if (g_NetClient)
326 g_NetClient->Poll();
328 ogl_WarnIfError();
330 g_GUI->TickObjects();
332 ogl_WarnIfError();
334 if (g_Game && g_Game->IsGameStarted() && need_update)
336 g_Game->Update(realTimeSinceLastFrame);
338 g_Game->GetView()->Update(float(realTimeSinceLastFrame));
341 // Immediately flush any messages produced by simulation code
342 if (g_NetClient)
343 g_NetClient->Flush();
345 // Keep us connected to any XMPP servers
346 if (g_XmppClient)
347 g_XmppClient->recv();
349 g_UserReporter.Update();
351 g_Console->Update(realTimeSinceLastFrame);
353 ogl_WarnIfError();
354 if(need_render)
356 Render();
358 PROFILE3("swap buffers");
359 SDL_GL_SwapWindow(g_VideoMode.GetWindow());
361 ogl_WarnIfError();
363 g_Profiler.Frame();
365 g_GameRestarted = false;
369 static void MainControllerInit()
371 // add additional input handlers only needed by this controller:
373 // must be registered after gui_handler. Should mayhap even be last.
374 in_add_handler(MainInputHandler);
379 static void MainControllerShutdown()
381 in_reset_handlers();
385 // stop the main loop and trigger orderly shutdown. called from several
386 // places: the event handler (SDL_QUIT and hotkey) and JS exitProgram.
387 void kill_mainloop()
389 quit = true;
393 static bool restart_in_atlas = false;
394 // called by game code to indicate main() should restart in Atlas mode
395 // instead of terminating
396 void restart_mainloop_in_atlas()
398 quit = true;
399 restart_in_atlas = true;
402 static bool restart = false;
403 // trigger an orderly shutdown and restart the game.
404 void restart_engine()
406 quit = true;
407 restart = true;
410 extern CmdLineArgs g_args;
412 // moved into a helper function to ensure args is destroyed before
413 // exit(), which may result in a memory leak.
414 static void RunGameOrAtlas(int argc, const char* argv[])
416 CmdLineArgs args(argc, argv);
418 g_args = args;
420 if (args.Has("version") || args.Has("-version"))
422 debug_printf("Pyrogenesis %s\n", engine_version);
423 return;
426 const bool isReplay = args.Has("replay");
427 const bool isVisualReplay = args.Has("replay-visual");
428 const std::string replayFile = isReplay ? args.Get("replay") : (isVisualReplay ? args.Get("replay-visual") : "");
430 // Ensure the replay file exists
431 if (isReplay || isVisualReplay)
433 if (!FileExists(OsPath(replayFile)))
435 debug_printf("ERROR: The requested replay file '%s' does not exist!\n", replayFile.c_str());
436 return;
438 if (DirectoryExists(OsPath(replayFile)))
440 debug_printf("ERROR: The requested replay file '%s' is a directory!\n", replayFile.c_str());
441 return;
445 // We need to initialize SpiderMonkey and libxml2 in the main thread before
446 // any thread uses them. So initialize them here before we might run Atlas.
447 ScriptEngine scriptEngine;
448 CXeromyces::Startup();
450 if (ATLAS_RunIfOnCmdLine(args, false))
452 CXeromyces::Terminate();
453 return;
456 // run non-visual simulation replay if requested
457 if (isReplay)
459 if (!args.Has("mod"))
461 LOGERROR("At least one mod should be specified! Did you mean to add the argument '-mod=public'?");
462 CXeromyces::Terminate();
463 return;
466 Paths paths(args);
467 g_VFS = CreateVfs(20 * MiB);
468 g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE);
469 MountMods(paths, GetMods(args, INIT_MODS));
472 CReplayPlayer replay;
473 replay.Load(replayFile);
474 replay.Replay(args.Has("serializationtest"), args.Has("ooslog"));
477 g_VFS.reset();
479 CXeromyces::Terminate();
480 return;
483 // run in archive-building mode if requested
484 if (args.Has("archivebuild"))
486 Paths paths(args);
488 OsPath mod(args.Get("archivebuild"));
489 OsPath zip;
490 if (args.Has("archivebuild-output"))
491 zip = args.Get("archivebuild-output");
492 else
493 zip = mod.Filename().ChangeExtension(L".zip");
495 CArchiveBuilder builder(mod, paths.Cache());
497 // Add mods provided on the command line
498 // NOTE: We do not handle mods in the user mod path here
499 std::vector<CStr> mods = args.GetMultiple("mod");
500 for (size_t i = 0; i < mods.size(); ++i)
501 builder.AddBaseMod(paths.RData()/"mods"/mods[i]);
503 builder.Build(zip, args.Has("archivebuild-compress"));
505 CXeromyces::Terminate();
506 return;
509 const double res = timer_Resolution();
510 g_frequencyFilter = CreateFrequencyFilter(res, 30.0);
512 // run the game
513 int flags = INIT_MODS;
516 restart = false;
517 quit = false;
518 if (!Init(args, flags))
520 flags &= ~INIT_MODS;
521 Shutdown(SHUTDOWN_FROM_CONFIG);
522 continue;
524 InitGraphics(args, 0);
525 MainControllerInit();
526 while (!quit)
527 Frame();
528 Shutdown(0);
529 MainControllerShutdown();
530 flags &= ~INIT_MODS;
531 } while (restart);
533 if (restart_in_atlas)
534 ATLAS_RunIfOnCmdLine(args, true);
536 CXeromyces::Terminate();
539 #if OS_ANDROID
540 // In Android we compile the engine as a shared library, not an executable,
541 // so rename main() to a different symbol that the wrapper library can load
542 #undef main
543 #define main pyrogenesis_main
544 extern "C" __attribute__((visibility ("default"))) int main(int argc, char* argv[]);
545 #endif
547 extern "C" int main(int argc, char* argv[])
549 #if OS_UNIX
550 // Don't allow people to run the game with root permissions,
551 // because bad things can happen, check before we do anything
552 if (geteuid() == 0)
554 std::cerr << "********************************************************\n"
555 << "WARNING: Attempted to run the game with root permission!\n"
556 << "This is not allowed because it can alter home directory \n"
557 << "permissions and opens your system to vulnerabilities. \n"
558 << "(You received this message because you were either \n"
559 <<" logged in as root or used e.g. the 'sudo' command.) \n"
560 << "********************************************************\n\n";
561 return EXIT_FAILURE;
563 #endif // OS_UNIX
565 EarlyInit(); // must come at beginning of main
567 RunGameOrAtlas(argc, const_cast<const char**>(argv));
569 // Shut down profiler initialised by EarlyInit
570 g_Profiler2.Shutdown();
572 return EXIT_SUCCESS;