Added different textures for each ptolemaic camel rank
[0ad.git] / source / main.cpp
blobf6f118d770bbd7a0a999be98eae31804241d412e
1 /* Copyright (C) 2013 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/Filesystem.h"
47 #include "ps/Game.h"
48 #include "ps/Globals.h"
49 #include "ps/Hotkey.h"
50 #include "ps/Loader.h"
51 #include "ps/Profile.h"
52 #include "ps/Profiler2.h"
53 #include "ps/Pyrogenesis.h"
54 #include "ps/Replay.h"
55 #include "ps/TouchInput.h"
56 #include "ps/UserReport.h"
57 #include "ps/Util.h"
58 #include "ps/VideoMode.h"
59 #include "ps/World.h"
60 #include "ps/GameSetup/GameSetup.h"
61 #include "ps/GameSetup/Atlas.h"
62 #include "ps/GameSetup/Config.h"
63 #include "ps/GameSetup/CmdLineArgs.h"
64 #include "ps/GameSetup/Paths.h"
65 #include "ps/XML/Xeromyces.h"
66 #include "network/NetClient.h"
67 #include "network/NetServer.h"
68 #include "network/NetSession.h"
69 #include "graphics/Camera.h"
70 #include "graphics/GameView.h"
71 #include "graphics/TextureManager.h"
72 #include "gui/GUIManager.h"
73 #include "renderer/Renderer.h"
74 #include "scripting/ScriptingHost.h"
75 #include "simulation2/Simulation2.h"
77 #if OS_UNIX
78 #include <unistd.h> // geteuid
79 #endif // OS_UNIX
81 extern bool g_GameRestarted;
83 void kill_mainloop();
85 // to avoid redundant and/or recursive resizing, we save the new
86 // size after VIDEORESIZE messages and only update the video mode
87 // once per frame.
88 // these values are the latest resize message, and reset to 0 once we've
89 // updated the video mode
90 static int g_ResizedW;
91 static int g_ResizedH;
93 // main app message handler
94 static InReaction MainInputHandler(const SDL_Event_* ev)
96 switch(ev->ev.type)
98 #if SDL_VERSION_ATLEAST(2, 0, 0)
99 case SDL_WINDOWEVENT:
100 switch(ev->ev.window.event)
102 case SDL_WINDOWEVENT_ENTER:
103 RenderCursor(true);
104 break;
105 case SDL_WINDOWEVENT_LEAVE:
106 RenderCursor(false);
107 break;
108 case SDL_WINDOWEVENT_RESIZED:
109 g_ResizedW = ev->ev.window.data1;
110 g_ResizedH = ev->ev.window.data2;
111 break;
113 break;
114 #else
115 case SDL_ACTIVEEVENT:
116 if (ev->ev.active.state & SDL_APPMOUSEFOCUS)
118 // Tell renderer not to render cursor if mouse focus is lost
119 // this restores system cursor, until/if focus is regained
120 if (!ev->ev.active.gain)
121 RenderCursor(false);
122 else
123 RenderCursor(true);
125 break;
127 case SDL_VIDEORESIZE:
128 g_ResizedW = ev->ev.resize.w;
129 g_ResizedH = ev->ev.resize.h;
130 break;
131 #endif
133 case SDL_QUIT:
134 kill_mainloop();
135 break;
137 case SDL_HOTKEYDOWN:
138 std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
139 if (hotkey == "exit")
141 kill_mainloop();
142 return IN_HANDLED;
144 else if (hotkey == "screenshot")
146 WriteScreenshot(L".png");
147 return IN_HANDLED;
149 else if (hotkey == "bigscreenshot")
151 WriteBigScreenshot(L".bmp", 10);
152 return IN_HANDLED;
154 else if (hotkey == "togglefullscreen")
156 #if !OS_MACOSX
157 // TODO: Fix fullscreen toggling on OS X, see http://trac.wildfiregames.com/ticket/741
158 g_VideoMode.ToggleFullscreen();
159 #else
160 LOGWARNING(L"Toggling fullscreen and resizing are disabled on OS X due to a known bug. Please use the config file to change display settings.");
161 #endif
162 return IN_HANDLED;
164 else if (hotkey == "profile2.enable")
166 g_Profiler2.EnableGPU();
167 g_Profiler2.EnableHTTP();
168 return IN_HANDLED;
170 break;
173 return IN_PASS;
177 // dispatch all pending events to the various receivers.
178 static void PumpEvents()
180 PROFILE3("dispatch events");
182 SDL_Event_ ev;
183 while (in_poll_event(&ev))
185 PROFILE2("event");
186 if (g_GUI)
188 std::string data = g_GUI->GetScriptInterface().StringifyJSON(
189 ScriptInterface::ToJSVal(g_GUI->GetScriptInterface().GetContext(), ev));
190 PROFILE2_ATTR("%s", data.c_str());
192 in_dispatch_event(&ev);
195 g_TouchInput.Frame();
199 static int ProgressiveLoad()
201 PROFILE3("progressive load");
203 wchar_t description[100];
204 int progress_percent;
207 Status ret = LDR_ProgressiveLoad(10e-3, description, ARRAY_SIZE(description), &progress_percent);
208 switch(ret)
210 // no load active => no-op (skip code below)
211 case INFO::OK:
212 return 0;
213 // current task didn't complete. we only care about this insofar as the
214 // load process is therefore not yet finished.
215 case ERR::TIMED_OUT:
216 break;
217 // just finished loading
218 case INFO::ALL_COMPLETE:
219 g_Game->ReallyStartGame();
220 wcscpy_s(description, ARRAY_SIZE(description), L"Game is starting..");
221 // LDR_ProgressiveLoad returns L""; set to valid text to
222 // avoid problems in converting to JSString
223 break;
224 // error!
225 default:
226 WARN_RETURN_STATUS_IF_ERR(ret);
227 // can't do this above due to legit ERR::TIMED_OUT
228 break;
231 catch (PSERROR_Game_World_MapLoadFailed& e)
233 // Map loading failed
235 // Call script function to do the actual work
236 // (delete game data, switch GUI page, show error, etc.)
237 CancelLoad(CStr(e.what()).FromUTF8());
240 GUI_DisplayLoadProgress(progress_percent, description);
241 return 0;
245 static void RendererIncrementalLoad()
247 PROFILE3("renderer incremental load");
249 const double maxTime = 0.1f;
251 double startTime = timer_Time();
252 bool more;
253 do {
254 more = g_Renderer.GetTextureManager().MakeProgress();
256 while (more && timer_Time() - startTime < maxTime);
260 static bool quit = false; // break out of main loop
262 static void Frame()
264 g_Profiler2.RecordFrameStart();
265 PROFILE2("frame");
266 g_Profiler2.IncrementFrameNumber();
267 PROFILE2_ATTR("%d", g_Profiler2.GetFrameNumber());
269 ogl_WarnIfError();
271 // get elapsed time
272 const double time = timer_Time();
273 g_frequencyFilter->Update(time);
274 // .. old method - "exact" but contains jumps
275 #if 0
276 static double last_time;
277 const double time = timer_Time();
278 const float TimeSinceLastFrame = (float)(time-last_time);
279 last_time = time;
280 ONCE(return); // first call: set last_time and return
282 // .. new method - filtered and more smooth, but errors may accumulate
283 #else
284 const float realTimeSinceLastFrame = 1.0 / g_frequencyFilter->SmoothedFrequency();
285 #endif
286 ENSURE(realTimeSinceLastFrame > 0.0f);
288 // decide if update/render is necessary
289 bool need_render = !g_app_minimized;
290 bool need_update = true;
292 // If we are not running a multiplayer game, disable updates when the game is
293 // minimized or out of focus and relinquish the CPU a bit, in order to make
294 // debugging easier.
295 if(g_PauseOnFocusLoss && !g_NetClient && !g_app_has_focus)
297 PROFILE3("non-focus delay");
298 need_update = false;
299 // don't use SDL_WaitEvent: don't want the main loop to freeze until app focus is restored
300 SDL_Delay(10);
303 // TODO: throttling: limit update and render frequency to the minimum.
304 // this is mostly relevant for "inactive" state, so that other windows
305 // get enough CPU time, but it's always nice for power+thermal management.
308 // this scans for changed files/directories and reloads them, thus
309 // allowing hotloading (changes are immediately assimilated in-game).
310 ReloadChangedFiles();
312 ProgressiveLoad();
314 RendererIncrementalLoad();
316 PumpEvents();
318 // if the user quit by closing the window, the GL context will be broken and
319 // may crash when we call Render() on some drivers, so leave this loop
320 // before rendering
321 if (quit)
322 return;
324 // respond to pumped resize events
325 if (g_ResizedW || g_ResizedH)
327 g_VideoMode.ResizeWindow(g_ResizedW, g_ResizedH);
328 g_ResizedW = g_ResizedH = 0;
331 if (g_NetClient)
332 g_NetClient->Poll();
334 ogl_WarnIfError();
336 g_GUI->TickObjects();
338 ogl_WarnIfError();
340 if (g_Game && g_Game->IsGameStarted() && need_update)
342 g_Game->Update(realTimeSinceLastFrame);
344 g_Game->GetView()->Update(float(realTimeSinceLastFrame));
347 // Immediately flush any messages produced by simulation code
348 if (g_NetClient)
349 g_NetClient->Flush();
351 g_UserReporter.Update();
353 g_Console->Update(realTimeSinceLastFrame);
355 ogl_WarnIfError();
356 if(need_render)
358 Render();
360 PROFILE3("swap buffers");
361 #if SDL_VERSION_ATLEAST(2, 0, 0)
362 SDL_GL_SwapWindow(g_VideoMode.GetWindow());
363 #else
364 SDL_GL_SwapBuffers();
365 #endif
367 ogl_WarnIfError();
369 g_Profiler.Frame();
371 g_GameRestarted = false;
375 static void MainControllerInit()
377 // add additional input handlers only needed by this controller:
379 // must be registered after gui_handler. Should mayhap even be last.
380 in_add_handler(MainInputHandler);
385 static void MainControllerShutdown()
390 // stop the main loop and trigger orderly shutdown. called from several
391 // places: the event handler (SDL_QUIT and hotkey) and JS exitProgram.
392 void kill_mainloop()
394 quit = true;
398 static bool restart_in_atlas = false;
399 // called by game code to indicate main() should restart in Atlas mode
400 // instead of terminating
401 void restart_mainloop_in_atlas()
403 quit = true;
404 restart_in_atlas = true;
407 // moved into a helper function to ensure args is destroyed before
408 // exit(), which may result in a memory leak.
409 static void RunGameOrAtlas(int argc, const char* argv[])
411 CmdLineArgs args(argc, argv);
413 // We need to initialise libxml2 in the main thread before
414 // any thread uses it. So initialise it here before we
415 // might run Atlas.
416 CXeromyces::Startup();
418 // run Atlas (if requested via args)
419 bool ran_atlas = ATLAS_RunIfOnCmdLine(args, false);
420 // Atlas handles the whole init/shutdown/etc sequence by itself;
421 // when we get here, it has exited and we're done.
422 if(ran_atlas)
423 return;
425 // run non-visual simulation replay if requested
426 if (args.Has("replay"))
428 // TODO: Support mods
429 Paths paths(args);
430 g_VFS = CreateVfs(20 * MiB);
431 g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE);
432 g_VFS->Mount(L"", paths.RData()/"mods"/"public", VFS_MOUNT_MUST_EXIST);
435 CReplayPlayer replay;
436 replay.Load(args.Get("replay"));
437 replay.Replay();
440 g_VFS.reset();
442 CXeromyces::Terminate();
443 return;
446 // run in archive-building mode if requested
447 if (args.Has("archivebuild"))
449 Paths paths(args);
451 OsPath mod(args.Get("archivebuild"));
452 OsPath zip;
453 if (args.Has("archivebuild-output"))
454 zip = args.Get("archivebuild-output");
455 else
456 zip = mod.Filename().ChangeExtension(L".zip");
458 CArchiveBuilder builder(mod, paths.Cache());
459 builder.Build(zip, args.Has("archivebuild-compress"));
461 CXeromyces::Terminate();
462 return;
465 const double res = timer_Resolution();
466 g_frequencyFilter = CreateFrequencyFilter(res, 30.0);
468 // run the game
469 Init(args, 0);
470 InitGraphics(args, 0);
471 MainControllerInit();
472 while(!quit)
473 Frame();
474 Shutdown(0);
475 ScriptingHost::FinalShutdown(); // this can't go in Shutdown() because that could be called multiple times per process, so stick it here instead
476 MainControllerShutdown();
478 if (restart_in_atlas)
480 ATLAS_RunIfOnCmdLine(args, true);
481 return;
484 // Shut down libxml2 (done here to match the Startup call)
485 CXeromyces::Terminate();
488 #if OS_ANDROID
489 // In Android we compile the engine as a shared library, not an executable,
490 // so rename main() to a different symbol that the wrapper library can load
491 #undef main
492 #define main pyrogenesis_main
493 extern "C" __attribute__((visibility ("default"))) int main(int argc, char* argv[]);
494 #endif
496 extern "C" int main(int argc, char* argv[])
498 #if OS_UNIX
499 // Don't allow people to run the game with root permissions,
500 // because bad things can happen, check before we do anything
501 if (geteuid() == 0)
503 std::cerr << "********************************************************\n"
504 << "WARNING: Attempted to run the game with root permission!\n"
505 << "This is not allowed because it can alter home directory \n"
506 << "permissions and opens your system to vulnerabilities. \n"
507 << "(You received this message because you were either \n"
508 <<" logged in as root or used e.g. the 'sudo' command.) \n"
509 << "********************************************************\n\n";
510 return EXIT_FAILURE;
512 #endif // OS_UNIX
514 EarlyInit(); // must come at beginning of main
516 RunGameOrAtlas(argc, const_cast<const char**>(argv));
518 // Shut down profiler initialised by EarlyInit
519 g_Profiler2.Shutdown();
521 return EXIT_SUCCESS;