1 /*
2 * This file is part of the Colobot: Gold Edition source code
3 * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam
4 * http://epsitec.ch; http://colobot.info; http://github.com/colobot
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 * See the GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see http://gnu.org/licenses
18 */
19
20 #include "app/app.h"
21
22 #include "app/controller.h"
23 #include "app/input.h"
24 #include "app/modman.h"
25 #include "app/pathman.h"
26
27 #include "common/config_file.h"
28 #include "common/image.h"
29 #include "common/key.h"
30 #include "common/logger.h"
31 #include "common/make_unique.h"
32 #include "common/profiler.h"
33 #include "common/stringutils.h"
34 #include "common/version.h"
35
36 #include "common/resources/resourcemanager.h"
37
38 #include "common/system/system.h"
39
40 #include "common/thread/thread.h"
41
42 #include "graphics/core/nulldevice.h"
43
44 #include "graphics/opengl/glutil.h"
45
46 #include "level/robotmain.h"
47
48 #include "object/object_manager.h"
49
50 #include "sound/sound.h"
51 #ifdef OPENAL_SOUND
52 #include "sound/oalsound/alsound.h"
53 #endif
54
55 #include <boost/tokenizer.hpp>
56
57 #include <SDL.h>
58 #include <SDL_image.h>
59
60 #include <stdlib.h>
61 #include <libintl.h>
62 #include <getopt.h>
63 #include <localename.h>
64
65 char CApplication::m_languageLocale[] = { 0 };
66
67
68 //! Interval of timer called to update joystick state
69 const int JOYSTICK_TIMER_INTERVAL = 1000/30;
70
71 //! Function called by the timer
72 Uint32 JoystickTimerCallback(Uint32 interval, void *);
73
74
75 /**
76 * \struct ApplicationPrivate
77 * \brief Private data of CApplication class
78 *
79 * Contains SDL-specific variables that should not be visible outside application module.
80 */
81 struct ApplicationPrivate
82 {
83 //! Main game window
84 SDL_Window *window;
85 //! Main game OpenGL context
86 SDL_GLContext glcontext;
87 //! Currently handled event
88 SDL_Event currentEvent;
89 //! Mouse motion event to be handled
90 SDL_Event lastMouseMotionEvent;
91 //! Joystick
92 SDL_Joystick *joystick;
93 //! Id of joystick timer
94 SDL_TimerID joystickTimer;
95 //! Haptic subsystem for the joystick
96 SDL_Haptic *haptic;
97
ApplicationPrivateApplicationPrivate98 ApplicationPrivate()
99 {
100 SDL_memset(¤tEvent, 0, sizeof(SDL_Event));
101 SDL_memset(&lastMouseMotionEvent, 0, sizeof(SDL_Event));
102 window = nullptr;
103 glcontext = nullptr;
104 joystick = nullptr;
105 joystickTimer = 0;
106 haptic = nullptr;
107 }
108 };
109
110
111
CApplication(CSystemUtils * systemUtils)112 CApplication::CApplication(CSystemUtils* systemUtils)
113 : m_systemUtils(systemUtils),
114 m_private(MakeUnique<ApplicationPrivate>()),
115 m_configFile(MakeUnique<CConfigFile>()),
116 m_input(MakeUnique<CInput>()),
117 m_pathManager(MakeUnique<CPathManager>(systemUtils)),
118 m_modManager(MakeUnique<CModManager>(this, m_pathManager.get()))
119 {
120 m_exitCode = 0;
121 m_active = false;
122 m_debugModes = 0;
123
124 m_windowTitle = "Colobot: Gold Edition";
125
126 m_simulationSuspended = false;
127
128 m_simulationSpeed = 1.0f;
129
130 m_realAbsTimeBase = 0LL;
131 m_realAbsTime = 0LL;
132 m_realRelTime = 0LL;
133
134 m_absTimeBase = 0LL;
135 m_exactAbsTime = 0LL;
136 m_exactRelTime = 0LL;
137
138 m_absTime = 0.0f;
139 m_relTime = 0.0f;
140
141 m_baseTimeStamp = m_systemUtils->CreateTimeStamp();
142 m_curTimeStamp = m_systemUtils->CreateTimeStamp();
143 m_lastTimeStamp = m_systemUtils->CreateTimeStamp();
144
145 m_manualFrameLast = m_systemUtils->CreateTimeStamp();
146 m_manualFrameTime = m_systemUtils->CreateTimeStamp();
147
148
149 m_joystickEnabled = false;
150
151 m_mouseMode = MOUSE_SYSTEM;
152
153 m_runSceneCategory = LevelCategory::Max;
154 m_runSceneRank = 0;
155
156 m_sceneTest = false;
157 m_headless = false;
158 m_resolutionOverride = false;
159
160 m_language = LANGUAGE_ENV;
161 }
162
~CApplication()163 CApplication::~CApplication()
164 {
165 m_systemUtils->DestroyTimeStamp(m_baseTimeStamp);
166 m_systemUtils->DestroyTimeStamp(m_curTimeStamp);
167 m_systemUtils->DestroyTimeStamp(m_lastTimeStamp);
168
169 m_systemUtils->DestroyTimeStamp(m_manualFrameLast);
170 m_systemUtils->DestroyTimeStamp(m_manualFrameTime);
171
172 m_joystickEnabled = false;
173
174 m_controller.reset();
175 m_sound.reset();
176
177 if (m_engine != nullptr)
178 {
179 m_engine->Destroy();
180
181 m_engine.reset();
182 }
183
184 if (m_device != nullptr)
185 {
186 m_device->Destroy();
187
188 m_device.reset();
189 }
190
191 if (m_private->joystick != nullptr)
192 {
193 SDL_JoystickClose(m_private->joystick);
194 m_private->joystick = nullptr;
195 }
196
197 if (m_private->glcontext != nullptr)
198 {
199 SDL_GL_DeleteContext(m_private->glcontext);
200 m_private->glcontext = nullptr;
201 }
202
203 if (m_private->window != nullptr)
204 {
205 SDL_DestroyWindow(m_private->window);
206 m_private->window = nullptr;
207 }
208
209 IMG_Quit();
210
211 if (SDL_WasInit(0))
212 SDL_Quit();
213 }
214
GetEventQueue()215 CEventQueue* CApplication::GetEventQueue()
216 {
217 return m_eventQueue.get();
218 }
219
GetSound()220 CSoundInterface* CApplication::GetSound()
221 {
222 return m_sound.get();
223 }
224
GetModManager()225 CModManager* CApplication::GetModManager()
226 {
227 return m_modManager.get();
228 }
229
LoadEnvironmentVariables()230 void CApplication::LoadEnvironmentVariables()
231 {
232 auto dataDir = m_systemUtils->GetEnvVar("COLOBOT_DATA_DIR");
233 if (!dataDir.empty())
234 {
235 m_pathManager->SetDataPath(dataDir);
236 GetLogger()->Info("Using data dir (based on environment variable): '%s'\n", dataDir.c_str());
237 }
238
239 auto langDir = m_systemUtils->GetEnvVar("COLOBOT_LANG_DIR");
240 if (!langDir.empty())
241 {
242 m_pathManager->SetLangPath(langDir);
243 GetLogger()->Info("Using lang dir (based on environment variable): '%s'\n", langDir.c_str());
244 }
245
246 auto saveDir = m_systemUtils->GetEnvVar("COLOBOT_SAVE_DIR");
247 if (!saveDir.empty())
248 {
249 m_pathManager->SetSavePath(saveDir);
250 GetLogger()->Info("Using save dir (based on environment variable): '%s'\n", saveDir.c_str());
251 }
252 }
253
ParseArguments(int argc,char * argv[])254 ParseArgsStatus CApplication::ParseArguments(int argc, char *argv[])
255 {
256 enum OptionType
257 {
258 OPT_HELP = 1,
259 OPT_DEBUG,
260 OPT_RUNSCENE,
261 OPT_SCENETEST,
262 OPT_LOGLEVEL,
263 OPT_LANGDIR,
264 OPT_DATADIR,
265 OPT_SAVEDIR,
266 OPT_MOD,
267 OPT_RESOLUTION,
268 OPT_HEADLESS,
269 OPT_DEVICE,
270 OPT_OPENGL_VERSION,
271 OPT_OPENGL_PROFILE
272 };
273
274 option options[] =
275 {
276 { "help", no_argument, nullptr, OPT_HELP },
277 { "debug", required_argument, nullptr, OPT_DEBUG },
278 { "runscene", required_argument, nullptr, OPT_RUNSCENE },
279 { "scenetest", no_argument, nullptr, OPT_SCENETEST },
280 { "loglevel", required_argument, nullptr, OPT_LOGLEVEL },
281 { "langdir", required_argument, nullptr, OPT_LANGDIR },
282 { "datadir", required_argument, nullptr, OPT_DATADIR },
283 { "savedir", required_argument, nullptr, OPT_SAVEDIR },
284 { "mod", required_argument, nullptr, OPT_MOD },
285 { "resolution", required_argument, nullptr, OPT_RESOLUTION },
286 { "headless", no_argument, nullptr, OPT_HEADLESS },
287 { "graphics", required_argument, nullptr, OPT_DEVICE },
288 { "glversion", required_argument, nullptr, OPT_OPENGL_VERSION },
289 { "glprofile", required_argument, nullptr, OPT_OPENGL_PROFILE },
290 { nullptr, 0, nullptr, 0}
291 };
292
293 opterr = 0;
294 optind = 1;
295
296 int c = 0;
297 int index = -1;
298 while ((c = getopt_long_only(argc, argv, "", options, &index)) != -1)
299 {
300 if (c == '?')
301 {
302 if (optopt == 0)
303 GetLogger()->Error("Invalid argument: %s\n", argv[optind-1]);
304 else
305 GetLogger()->Error("Expected argument for option: %s\n", argv[optind-1]);
306
307 m_exitCode = 1;
308 return PARSE_ARGS_FAIL;
309 }
310
311 index = -1;
312
313 switch (c)
314 {
315 case OPT_HELP:
316 {
317 GetLogger()->Message("\n");
318 GetLogger()->Message("%s\n", COLOBOT_FULLNAME);
319 GetLogger()->Message("\n");
320 GetLogger()->Message("List of available options and environment variables:\n");
321 GetLogger()->Message(" -help this help\n");
322 GetLogger()->Message(" -debug modes enable debug modes (more info printed in logs; see code for reference of modes)\n");
323 GetLogger()->Message(" -runscene sceneNNN run given scene on start\n");
324 GetLogger()->Message(" -scenetest win every mission right after it's loaded\n");
325 GetLogger()->Message(" -loglevel level set log level to level (one of: trace, debug, info, warn, error, none)\n");
326 GetLogger()->Message(" -langdir path set custom language directory path\n");
327 GetLogger()->Message(" environment variable: COLOBOT_LANG_DIR\n");
328 GetLogger()->Message(" -datadir path set custom data directory path\n");
329 GetLogger()->Message(" environment variable: COLOBOT_DATA_DIR\n");
330 GetLogger()->Message(" -savedir path set custom save directory path (must be writable)\n");
331 GetLogger()->Message(" environment variable: COLOBOT_SAVE_DIR\n");
332 GetLogger()->Message(" -mod path load datadir mod from given path\n");
333 GetLogger()->Message(" -resolution WxH set resolution\n");
334 GetLogger()->Message(" -headless headless mode - disables graphics, sound and user interaction\n");
335 GetLogger()->Message(" -graphics changes graphics device (one of: default, auto, opengl, gl14, gl21, gl33\n");
336 GetLogger()->Message(" -glversion sets OpenGL context version to use (either default or version in format #.#)\n");
337 GetLogger()->Message(" -glprofile sets OpenGL context profile to use (one of: default, core, compatibility, opengles)\n");
338 return PARSE_ARGS_HELP;
339 }
340 case OPT_DEBUG:
341 {
342 if (optarg == nullptr)
343 {
344 m_debugModes = DEBUG_ALL;
345 GetLogger()->Info("All debug modes active\n");
346 }
347 else
348 {
349 int debugModes;
350 if (! ParseDebugModes(optarg, debugModes))
351 {
352 return PARSE_ARGS_FAIL;
353 }
354
355 m_debugModes = debugModes;
356 GetLogger()->Info("Active debug modes: %s\n", optarg);
357 }
358 break;
359 }
360 case OPT_RUNSCENE:
361 {
362 std::string file = optarg;
363 std::string cat = file.substr(0, file.size()-3);
364 m_runSceneCategory = GetLevelCategoryFromDir(cat);
365 m_runSceneRank = StrUtils::FromString<int>(file.substr(file.size()-3, 3));
366 if(m_runSceneCategory != LevelCategory::Max)
367 {
368 GetLogger()->Info("Running scene '%s%d' on start\n", cat.c_str(), m_runSceneRank);
369 }
370 else
371 {
372 GetLogger()->Error("Requested to run scene from unknown category '%s'\n", cat.c_str());
373 return PARSE_ARGS_FAIL;
374 }
375 break;
376 }
377 case OPT_SCENETEST:
378 {
379 m_sceneTest = true;
380 break;
381 }
382 case OPT_LOGLEVEL:
383 {
384 LogLevel logLevel;
385 if (! CLogger::ParseLogLevel(optarg, logLevel))
386 {
387 GetLogger()->Error("Invalid log level: '%s'\n", optarg);
388 return PARSE_ARGS_FAIL;
389 }
390
391 GetLogger()->Message("[*****] Log level changed to %s\n", optarg);
392 GetLogger()->SetLogLevel(logLevel);
393 break;
394 }
395 case OPT_DATADIR:
396 {
397 m_pathManager->SetDataPath(optarg);
398 GetLogger()->Info("Using data dir: '%s'\n", optarg);
399 break;
400 }
401 case OPT_LANGDIR:
402 {
403 m_pathManager->SetLangPath(optarg);
404 GetLogger()->Info("Using language dir: '%s'\n", optarg);
405 break;
406 }
407 case OPT_SAVEDIR:
408 {
409 m_pathManager->SetSavePath(optarg);
410 GetLogger()->Info("Using save dir: '%s'\n", optarg);
411 break;
412 }
413 case OPT_MOD:
414 {
415 m_pathManager->AddMod(optarg);
416 break;
417 }
418 case OPT_RESOLUTION:
419 {
420 std::istringstream resolution(optarg);
421 std::string w, h;
422 std::getline(resolution, w, 'x');
423 std::getline(resolution, h, 'x');
424
425 m_deviceConfig.size.x = atoi(w.c_str());
426 m_deviceConfig.size.y = atoi(h.c_str());
427 m_resolutionOverride = true;
428 break;
429 }
430 case OPT_HEADLESS:
431 {
432 m_headless = true;
433 break;
434 }
435 case OPT_DEVICE:
436 {
437 m_graphics = optarg;
438 m_graphicsOverride = true;
439 break;
440 }
441 case OPT_OPENGL_VERSION:
442 {
443 if (strcmp(optarg, "default") == 0)
444 {
445 m_glMajor = -1;
446 m_glMinor = -1;
447 m_glVersionOverride = true;
448 }
449 else
450 {
451 int major = 1, minor = 1;
452
453 int parsed = sscanf(optarg, "%d.%d", &major, &minor);
454
455 if (parsed < 2)
456 {
457 GetLogger()->Error("Invalid OpenGL version: %s\n", optarg);
458 return PARSE_ARGS_FAIL;
459 }
460
461 m_glMajor = major;
462 m_glMinor = minor;
463 m_glVersionOverride = true;
464 }
465 break;
466 }
467 case OPT_OPENGL_PROFILE:
468 {
469 if (strcmp(optarg, "default") == 0)
470 {
471 m_glProfile = 0;
472 m_glProfileOverride = true;
473 }
474 else if (strcmp(optarg, "core") == 0)
475 {
476 m_glProfile = SDL_GL_CONTEXT_PROFILE_CORE;
477 m_glProfileOverride = true;
478 }
479 else if (strcmp(optarg, "compatibility") == 0)
480 {
481 m_glProfile = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
482 m_glProfileOverride = true;
483 }
484 else if (strcmp(optarg, "opengles") == 0)
485 {
486 m_glProfile = SDL_GL_CONTEXT_PROFILE_ES;
487 m_glProfileOverride = true;
488 }
489 else
490 {
491 GetLogger()->Error("Invalid OpenGL profile: %s\n", optarg);
492 return PARSE_ARGS_FAIL;
493 }
494 break;
495 }
496 default:
497 assert(false); // should never get here
498 }
499 }
500
501 return PARSE_ARGS_OK;
502 }
503
Create()504 bool CApplication::Create()
505 {
506 std::string path;
507
508 GetLogger()->Info("Creating CApplication\n");
509
510 m_errorMessage = m_pathManager->VerifyPaths();
511 if (!m_errorMessage.empty())
512 {
513 m_exitCode = 1;
514 return false;
515 }
516 m_pathManager->InitPaths();
517
518 if (!GetConfigFile().Init())
519 {
520 GetLogger()->Warn("Config could not be loaded. Default values will be used!\n");
521 }
522
523 m_modManager->FindMods();
524 m_modManager->SaveMods();
525 m_modManager->MountAllMods();
526
527 // Create the sound instance.
528 #ifdef OPENAL_SOUND
529 if (!m_headless)
530 {
531 m_sound = MakeUnique<CALSound>();
532 }
533 else
534 {
535 m_sound = MakeUnique<CSoundInterface>();
536 }
537 #else
538 GetLogger()->Info("No sound support.\n");
539 m_sound = MakeUnique<CSoundInterface>();
540 #endif
541
542 m_sound->Create();
543
544 GetLogger()->Info("CApplication created successfully\n");
545
546 std::string standardInfoMessage =
547 "\nPlease see the console output or log file\n"
548 "to get more information on the source of error";
549
550 /* SDL initialization sequence */
551
552 // Creating the m_engine now because it holds the vsync flag
553 m_engine = MakeUnique<Gfx::CEngine>(this, m_systemUtils);
554
555 Uint32 initFlags = SDL_INIT_VIDEO | SDL_INIT_TIMER;
556
557 if (SDL_Init(initFlags) < 0)
558 {
559 m_errorMessage = std::string("SDL initialization error:\n") +
560 std::string(SDL_GetError());
561 GetLogger()->Error(m_errorMessage.c_str());
562 m_exitCode = 2;
563 return false;
564 }
565
566 // This is non-fatal and besides seems to fix some memory leaks
567 if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0)
568 {
569 GetLogger()->Warn("Joystick subsystem init failed\nJoystick(s) will not be available\n");
570 }
571 if (SDL_InitSubSystem(SDL_INIT_HAPTIC) < 0)
572 {
573 GetLogger()->Warn("Joystick haptic subsystem init failed\nForce feedback will not be available\n");
574 }
575
576 if ((IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG) == 0)
577 {
578 m_errorMessage = std::string("SDL_Image initialization error:\n") +
579 std::string(IMG_GetError());
580 GetLogger()->Error(m_errorMessage.c_str());
581 m_exitCode = 3;
582 return false;
583 }
584
585 if (!m_headless)
586 {
587 // load settings from profile
588 int iValue;
589 std::string sValue;
590
591 // GetVideoResolutionList() has to be called here because it is responsible
592 // for list of resolutions in options menu, not calling it results in empty list
593 std::vector<Math::IntPoint> modes;
594 GetVideoResolutionList(modes);
595
596 if ( GetConfigFile().GetStringProperty("Setup", "Resolution", sValue) && !m_resolutionOverride )
597 {
598 std::istringstream resolution(sValue);
599 std::string ws, hs;
600 std::getline(resolution, ws, 'x');
601 std::getline(resolution, hs, 'x');
602 int w = 800, h = 600;
603 if (!ws.empty() && !hs.empty())
604 {
605 w = atoi(ws.c_str());
606 h = atoi(hs.c_str());
607 }
608
609 // Why not just set m_deviceConfig.size to w,h? Because this way if the resolution is no longer supported (e.g. changimg monitor) defaults will be used instead
610 for (auto it = modes.begin(); it != modes.end(); ++it)
611 {
612 if (it->x == w && it->y == h)
613 {
614 m_deviceConfig.size = *it;
615 break;
616 }
617 }
618 }
619
620 if ( GetConfigFile().GetIntProperty("Setup", "Fullscreen", iValue) && !m_resolutionOverride )
621 {
622 m_deviceConfig.fullScreen = (iValue == 1);
623 }
624
625 if (! CreateVideoSurface())
626 return false; // dialog is in function
627
628 if (m_private->window == nullptr)
629 {
630 m_errorMessage = std::string("SDL error while setting video mode:\n") +
631 std::string(SDL_GetError());
632 GetLogger()->Error(m_errorMessage.c_str());
633 m_exitCode = 4;
634 return false;
635 }
636 }
637
638 // Don't generate joystick events
639 SDL_JoystickEventState(SDL_IGNORE);
640
641 // Report joystick list to log, since we still don't have a GUI for them so you have to set the ID manually in the config
642 auto joysticks = GetJoystickList();
643 bool first = true;
644 for (const auto& joystick : joysticks)
645 {
646 if (first)
647 {
648 ChangeJoystick(joystick);
649 first = false;
650 }
651 GetLogger()->Info("Detected joystick: %s [ID %d]\n", joystick.name.c_str(), joystick.index);
652 }
653 if (first)
654 {
655 GetLogger()->Info("No joysticks detected\n");
656 }
657
658 if (!m_headless)
659 {
660 std::string graphics = "default";
661 std::string value;
662
663 if (m_graphicsOverride)
664 {
665 graphics = m_graphics;
666 }
667 else if (GetConfigFile().GetStringProperty("Experimental", "GraphicsDevice", value))
668 {
669 graphics = value;
670 }
671
672 m_device = Gfx::CreateDevice(m_deviceConfig, graphics.c_str());
673
674 if (m_device == nullptr)
675 {
676 GetLogger()->Error("Unknown graphics device: %s\n", graphics.c_str());
677 GetLogger()->Info("Changing to default device\n");
678 m_systemUtils->SystemDialog(SDT_ERROR, "Graphics initialization error", "You have selected invalid graphics device with -graphics switch. Game will use default OpenGL device instead.");
679 m_device = Gfx::CreateDevice(m_deviceConfig, "opengl");
680 }
681 }
682 else
683 {
684 m_device = MakeUnique<Gfx::CNullDevice>();
685 }
686
687 if (! m_device->Create() )
688 {
689 m_errorMessage = std::string("Error in CDevice::Create()\n")
690 + "\n\n"
691 + m_device->GetError()
692 + standardInfoMessage;
693 m_exitCode = 5;
694 return false;
695 }
696
697 // Create the 3D engine
698 m_engine->SetDevice(m_device.get());
699
700 if (! m_engine->Create() )
701 {
702 m_errorMessage = std::string("Error in CEngine::Init()\n") + standardInfoMessage;
703 m_exitCode = 6;
704 return false;
705 }
706
707 m_eventQueue = MakeUnique<CEventQueue>();
708
709 // Create the robot application.
710 m_controller = MakeUnique<CController>();
711
712 StartLoadingMusic();
713
714 if (m_runSceneCategory == LevelCategory::Max)
715 m_controller->StartApp();
716 else
717 {
718 m_controller->GetRobotMain()->UpdateCustomLevelList(); // To load the userlevels
719 m_controller->GetRobotMain()->SetExitAfterMission(true);
720 m_controller->StartGame(m_runSceneCategory, m_runSceneRank/100, m_runSceneRank%100);
721 }
722
723 return true;
724 }
725
ReloadResources()726 void CApplication::ReloadResources()
727 {
728 GetLogger()->Info("Reloading resources\n");
729 m_engine->ReloadAllTextures();
730 StartLoadingMusic();
731 m_controller->GetRobotMain()->UpdateCustomLevelList();
732 }
733
734
CreateVideoSurface()735 bool CApplication::CreateVideoSurface()
736 {
737 Uint32 videoFlags = SDL_WINDOW_OPENGL;
738
739 if (m_deviceConfig.fullScreen)
740 videoFlags |= SDL_WINDOW_FULLSCREEN;
741
742 if (m_deviceConfig.resizeable)
743 videoFlags |= SDL_WINDOW_RESIZABLE;
744
745 // Set OpenGL attributes
746
747 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, m_deviceConfig.redSize);
748 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, m_deviceConfig.greenSize);
749 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, m_deviceConfig.blueSize);
750 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, m_deviceConfig.alphaSize);
751
752 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, m_deviceConfig.depthSize);
753 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, m_deviceConfig.stencilSize);
754
755 if (m_deviceConfig.doubleBuf)
756 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
757
758 std::string value;
759
760 // set OpenGL context version
761 // -glversion switch overrides config settings
762 if (m_glVersionOverride)
763 {
764 if ((m_glMajor >= 0) && (m_glMinor >= 0))
765 {
766 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, m_glMajor);
767 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, m_glMinor);
768
769 GetLogger()->Info("Requesting OpenGL context version %d.%d\n", m_glMajor, m_glMinor);
770 }
771 }
772 else if (GetConfigFile().GetStringProperty("Experimental", "OpenGLVersion", value))
773 {
774 int major = 1, minor = 1;
775
776 sscanf(value.c_str(), "%d.%d", &major, &minor);
777
778 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major);
779 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor);
780
781 GetLogger()->Info("Requesting OpenGL context version %d.%d\n", major, minor);
782 }
783
784 // set OpenGL context profile
785 // -glprofile switch overrides config settings
786 int profile = 0;
787
788 if (m_glProfileOverride)
789 {
790 profile = m_glProfile;
791 }
792 else if (GetConfigFile().GetStringProperty("Experimental", "OpenGLProfile", value))
793 {
794 if (value == "core")
795 {
796 profile = SDL_GL_CONTEXT_PROFILE_CORE;
797 }
798 else if (value == "compatibility")
799 {
800 profile = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
801 }
802 else if (value == "opengles")
803 {
804 profile = SDL_GL_CONTEXT_PROFILE_ES;
805 }
806 }
807
808 if (profile != 0)
809 {
810 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile);
811
812 switch (profile)
813 {
814 case SDL_GL_CONTEXT_PROFILE_CORE:
815 GetLogger()->Info("Requesting OpenGL core profile\n");
816 break;
817 case SDL_GL_CONTEXT_PROFILE_COMPATIBILITY:
818 GetLogger()->Info("Requesting OpenGL compatibility profile\n");
819 break;
820 case SDL_GL_CONTEXT_PROFILE_ES:
821 GetLogger()->Info("Requesting OpenGL ES profile\n");
822 break;
823 }
824 }
825
826 int msaa = 0;
827 if (GetConfigFile().GetIntProperty("Experimental", "MSAA", msaa))
828 {
829 if (msaa > 1)
830 {
831 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
832 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, msaa);
833
834 GetLogger()->Info("Using MSAA on default framebuffer (%d samples)\n", msaa);
835 }
836 }
837
838 /* If hardware acceleration specifically requested, this will force the hw accel
839 and fail with error if not available */
840 if (m_deviceConfig.hardwareAccel)
841 SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
842
843 m_private->window = SDL_CreateWindow(m_windowTitle.c_str(),
844 SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
845 m_deviceConfig.size.x, m_deviceConfig.size.y,
846 videoFlags);
847
848 m_private->glcontext = SDL_GL_CreateContext(m_private->window);
849
850 int vsync = 0;
851 if (GetConfigFile().GetIntProperty("Setup", "VSync", vsync))
852 {
853 m_engine->SetVSync(vsync);
854 TryToSetVSync();
855 vsync = m_engine->GetVSync();
856 GetConfigFile().SetIntProperty("Setup", "VSync", vsync);
857
858 GetLogger()->Info("Using Vsync: %s\n", (vsync == -1 ? "adaptive" : (vsync ? "true" : "false")));
859 }
860
861 return true;
862 }
863
TryToSetVSync()864 void CApplication::TryToSetVSync()
865 {
866 int vsync = m_engine->GetVSync();
867 int result = SDL_GL_SetSwapInterval(vsync);
868 if (result == -1)
869 {
870 switch (vsync)
871 {
872 case -1:
873 GetLogger()->Warn("Adaptive sync not supported: %s\n", SDL_GetError());
874 m_engine->SetVSync(1);
875 TryToSetVSync();
876 break;
877 case 1:
878 GetLogger()->Warn("Couldn't enable VSync: %s\n", SDL_GetError());
879 m_engine->SetVSync(0);
880 TryToSetVSync();
881 break;
882 case 0:
883 GetLogger()->Warn("Couldn't disable VSync: %s\n", SDL_GetError());
884 m_engine->SetVSync(SDL_GL_GetSwapInterval());
885 break;
886 }
887 }
888 }
889
ChangeVideoConfig(const Gfx::DeviceConfig & newConfig)890 bool CApplication::ChangeVideoConfig(const Gfx::DeviceConfig &newConfig)
891 {
892 m_deviceConfig = newConfig;
893
894 // TODO: Somehow this doesn't work for maximized windows (at least on Ubuntu)
895 SDL_SetWindowSize(m_private->window, m_deviceConfig.size.x, m_deviceConfig.size.y);
896 SDL_SetWindowFullscreen(m_private->window, m_deviceConfig.fullScreen ? SDL_WINDOW_FULLSCREEN : 0);
897
898 TryToSetVSync();
899
900 m_device->ConfigChanged(m_deviceConfig);
901
902 m_eventQueue->AddEvent(Event(EVENT_RESOLUTION_CHANGED));
903
904 return true;
905 }
906
OpenJoystick()907 bool CApplication::OpenJoystick()
908 {
909 if ( (m_joystick.index < 0) || (m_joystick.index >= SDL_NumJoysticks()) )
910 return false;
911
912 assert(m_private->joystick == nullptr);
913 GetLogger()->Info("Opening joystick %d\n", m_joystick.index);
914
915 m_private->joystick = SDL_JoystickOpen(m_joystick.index);
916 if (m_private->joystick == nullptr)
917 return false;
918
919 m_joystick.axisCount = SDL_JoystickNumAxes(m_private->joystick);
920 m_joystick.buttonCount = SDL_JoystickNumButtons(m_private->joystick);
921
922 // Create the vectors with joystick axis & button states to exactly the required size
923 m_joyAxeState = std::vector<int>(m_joystick.axisCount, 0);
924 m_joyButtonState = std::vector<bool>(m_joystick.buttonCount, false);
925
926 // Create a timer for polling joystick state
927 m_private->joystickTimer = SDL_AddTimer(JOYSTICK_TIMER_INTERVAL, JoystickTimerCallback, nullptr);
928
929 // Initialize haptic subsystem
930 m_private->haptic = SDL_HapticOpenFromJoystick(m_private->joystick);
931 if (m_private->haptic == nullptr)
932 {
933 GetLogger()->Warn("Haptic subsystem open failed: %s\n", SDL_GetError());
934 return true;
935 }
936
937 if (SDL_HapticRumbleInit(m_private->haptic) != 0)
938 {
939 GetLogger()->Warn("Haptic rumble effect init failed: %s\n", SDL_GetError());
940 return true;
941 }
942
943 return true;
944 }
945
CloseJoystick()946 void CApplication::CloseJoystick()
947 {
948 // Timer will remove itself automatically
949
950 GetLogger()->Info("Closing joystick\n");
951
952 StopForceFeedbackEffect();
953
954 SDL_HapticClose(m_private->haptic);
955 m_private->haptic = nullptr;
956
957 SDL_JoystickClose(m_private->joystick);
958 m_private->joystick = nullptr;
959 }
960
ChangeJoystick(const JoystickDevice & newJoystick)961 bool CApplication::ChangeJoystick(const JoystickDevice &newJoystick)
962 {
963 if ( (newJoystick.index < 0) || (newJoystick.index >= SDL_NumJoysticks()) )
964 return false;
965
966 m_joystick = newJoystick;
967
968 if (m_private->joystick != nullptr)
969 CloseJoystick();
970
971 if (m_joystickEnabled)
972 return OpenJoystick();
973 else
974 return true;
975 }
976
JoystickTimerCallback(Uint32 interval,void *)977 Uint32 JoystickTimerCallback(Uint32 interval, void *)
978 {
979 CApplication *app = CApplication::GetInstancePointer();
980 if ((app == nullptr) || (! app->GetJoystickEnabled()))
981 return 0; // don't run the timer again
982
983 app->UpdateJoystick();
984
985 return interval; // run for the same interval again
986 }
987
988 /** Updates the state info in CApplication and on change, creates SDL events and pushes them to SDL event queue.
989 This way, the events get handled properly in the main event loop and besides, SDL_PushEvent() ensures thread-safety. */
UpdateJoystick()990 void CApplication::UpdateJoystick()
991 {
992 if (! m_joystickEnabled)
993 return;
994
995 SDL_JoystickUpdate();
996
997 for (int axis = 0; axis < static_cast<int>( m_joyAxeState.size() ); ++axis)
998 {
999 int newValue = SDL_JoystickGetAxis(m_private->joystick, axis);
1000
1001 if (m_joyAxeState[axis] != newValue)
1002 {
1003 m_joyAxeState[axis] = newValue;
1004
1005 SDL_Event joyAxisEvent;
1006
1007 joyAxisEvent.jaxis.type = SDL_JOYAXISMOTION;
1008 joyAxisEvent.jaxis.which = 0;
1009 joyAxisEvent.jaxis.axis = axis;
1010 joyAxisEvent.jaxis.value = newValue;
1011
1012 SDL_PushEvent(&joyAxisEvent);
1013 }
1014 }
1015
1016 for (int button = 0; button < static_cast<int>( m_joyButtonState.size() ); ++button)
1017 {
1018 bool newValue = SDL_JoystickGetButton(m_private->joystick, button) == 1;
1019
1020 if (m_joyButtonState[button] != newValue)
1021 {
1022 m_joyButtonState[button] = newValue;
1023
1024 SDL_Event joyButtonEvent;
1025
1026 if (newValue)
1027 {
1028 joyButtonEvent.jbutton.type = SDL_JOYBUTTONDOWN;
1029 joyButtonEvent.jbutton.state = SDL_PRESSED;
1030 }
1031 else
1032 {
1033 joyButtonEvent.jbutton.type = SDL_JOYBUTTONUP;
1034 joyButtonEvent.jbutton.state = SDL_RELEASED;
1035 }
1036 joyButtonEvent.jbutton.which = 0;
1037 joyButtonEvent.jbutton.button = button;
1038
1039 SDL_PushEvent(&joyButtonEvent);
1040 }
1041 }
1042 }
1043
UpdateMouse()1044 void CApplication::UpdateMouse()
1045 {
1046 Math::IntPoint pos;
1047 SDL_GetMouseState(&pos.x, &pos.y);
1048 m_input->MouseMove(pos);
1049 }
1050
Run()1051 int CApplication::Run()
1052 {
1053 m_active = true;
1054
1055 m_systemUtils->GetCurrentTimeStamp(m_baseTimeStamp);
1056 m_systemUtils->GetCurrentTimeStamp(m_lastTimeStamp);
1057 m_systemUtils->GetCurrentTimeStamp(m_curTimeStamp);
1058
1059 MoveMouse(Math::Point(0.5f, 0.5f)); // center mouse on start
1060
1061 SystemTimeStamp *previousTimeStamp = m_systemUtils->CreateTimeStamp();
1062 SystemTimeStamp *currentTimeStamp = m_systemUtils->CreateTimeStamp();
1063 SystemTimeStamp *interpolatedTimeStamp = m_systemUtils->CreateTimeStamp();
1064
1065 while (true)
1066 {
1067 if (m_active)
1068 {
1069 CProfiler::StartPerformanceCounter(PCNT_ALL);
1070 CProfiler::StartPerformanceCounter(PCNT_EVENT_PROCESSING);
1071 }
1072
1073 // To be sure no old event remains
1074 m_private->currentEvent.type = SDL_LASTEVENT;
1075
1076 // Call SDL_PumpEvents() only once here
1077 // (SDL_PeepEvents() doesn't call it)
1078 if (m_active)
1079 SDL_PumpEvents();
1080
1081 m_private->lastMouseMotionEvent.type = SDL_LASTEVENT;
1082
1083 bool haveEvent = true;
1084 while (haveEvent)
1085 {
1086 haveEvent = false;
1087
1088 int count = 0;
1089 // Use SDL_PeepEvents() if the app is active, so we can use idle time to
1090 // render the scene. Else, use SDL_WaitEvent() to avoid eating CPU time.
1091 if (m_active)
1092 count = SDL_PeepEvents(&m_private->currentEvent, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
1093 else
1094 count = SDL_WaitEvent(&m_private->currentEvent);
1095
1096 // If received an event
1097 if (count > 0)
1098 {
1099 haveEvent = true;
1100
1101 // Skip mouse motion events, for now
1102 if (m_private->currentEvent.type == SDL_MOUSEMOTION)
1103 {
1104 m_private->lastMouseMotionEvent = m_private->currentEvent;
1105 continue;
1106 }
1107
1108 Event event = ProcessSystemEvent();
1109
1110 if (event.type == EVENT_SYS_QUIT)
1111 goto end; // exit the loop
1112
1113 Event virtualEvent = CreateVirtualEvent(event);
1114
1115 if (event.type != EVENT_NULL)
1116 m_eventQueue->AddEvent(std::move(event));
1117
1118 if (virtualEvent.type != EVENT_NULL)
1119 m_eventQueue->AddEvent(std::move(virtualEvent));
1120 }
1121 }
1122
1123 // Now, process the last received mouse motion
1124 if (m_private->lastMouseMotionEvent.type != SDL_LASTEVENT)
1125 {
1126 m_private->currentEvent = m_private->lastMouseMotionEvent;
1127
1128 Event event = ProcessSystemEvent();
1129
1130 if (event.type == EVENT_SYS_QUIT)
1131 goto end; // exit the loop
1132
1133 if (event.type != EVENT_NULL)
1134 m_eventQueue->AddEvent(std::move(event));
1135 }
1136
1137 // Enter game update & frame rendering only if active
1138 if (m_active)
1139 {
1140 while (! m_eventQueue->IsEmpty())
1141 {
1142 Event event = m_eventQueue->GetEvent();
1143
1144 if (event.type == EVENT_SYS_QUIT || event.type == EVENT_QUIT)
1145 goto end; // exit both loops
1146
1147 LogEvent(event);
1148
1149 m_input->EventProcess(event);
1150
1151 bool passOn = true;
1152 if (m_engine != nullptr)
1153 passOn = m_engine->ProcessEvent(event);
1154
1155 if (passOn && m_controller != nullptr)
1156 m_controller->ProcessEvent(event);
1157 }
1158
1159 CProfiler::StopPerformanceCounter(PCNT_EVENT_PROCESSING);
1160
1161 CProfiler::StartPerformanceCounter(PCNT_UPDATE_ALL);
1162
1163 // Prepare and process step simulation event(s)
1164 // If game speed is increased then we do extra ticks per loop iteration to improve physics accuracy.
1165 int numTickSlices = static_cast<int>(GetSimulationSpeed());
1166 if(numTickSlices < 1) numTickSlices = 1;
1167 m_systemUtils->CopyTimeStamp(previousTimeStamp, m_curTimeStamp);
1168 m_systemUtils->GetCurrentTimeStamp(currentTimeStamp);
1169 for(int tickSlice = 0; tickSlice < numTickSlices; tickSlice++)
1170 {
1171 m_systemUtils->InterpolateTimeStamp(interpolatedTimeStamp, previousTimeStamp, currentTimeStamp, (tickSlice+1)/static_cast<float>(numTickSlices));
1172 Event event = CreateUpdateEvent(interpolatedTimeStamp);
1173 if (event.type != EVENT_NULL && m_controller != nullptr)
1174 {
1175 LogEvent(event);
1176
1177 m_sound->FrameMove(m_relTime);
1178
1179 CProfiler::StartPerformanceCounter(PCNT_UPDATE_GAME);
1180 m_controller->ProcessEvent(event);
1181 CProfiler::StopPerformanceCounter(PCNT_UPDATE_GAME);
1182
1183 CProfiler::StartPerformanceCounter(PCNT_UPDATE_ENGINE);
1184 m_engine->FrameUpdate();
1185 CProfiler::StopPerformanceCounter(PCNT_UPDATE_ENGINE);
1186 }
1187 }
1188
1189 CProfiler::StopPerformanceCounter(PCNT_UPDATE_ALL);
1190
1191 /* Update mouse position explicitly right before rendering
1192 * because mouse events are usually way behind */
1193 UpdateMouse();
1194
1195 Render();
1196
1197 CProfiler::StopPerformanceCounter(PCNT_ALL);
1198 }
1199 }
1200
1201 end:
1202 m_systemUtils->DestroyTimeStamp(previousTimeStamp);
1203 m_systemUtils->DestroyTimeStamp(currentTimeStamp);
1204 m_systemUtils->DestroyTimeStamp(interpolatedTimeStamp);
1205
1206 return m_exitCode;
1207 }
1208
GetExitCode() const1209 int CApplication::GetExitCode() const
1210 {
1211 return m_exitCode;
1212 }
1213
GetErrorMessage() const1214 const std::string& CApplication::GetErrorMessage() const
1215 {
1216 return m_errorMessage;
1217 }
1218
1219 /** The SDL event parsed is stored internally.
1220 If event is not available or is not understood, returned event is of type EVENT_NULL. */
ProcessSystemEvent()1221 Event CApplication::ProcessSystemEvent()
1222 {
1223 Event event;
1224
1225 if (m_private->currentEvent.type == SDL_QUIT)
1226 {
1227 event.type = EVENT_SYS_QUIT;
1228 }
1229 else if (m_private->currentEvent.type == SDL_WINDOWEVENT)
1230 {
1231 if (m_private->currentEvent.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
1232 {
1233 Gfx::DeviceConfig newConfig = m_deviceConfig;
1234 newConfig.size.x = m_private->currentEvent.window.data1;
1235 newConfig.size.y = m_private->currentEvent.window.data2;
1236 if (newConfig.size != m_deviceConfig.size)
1237 ChangeVideoConfig(newConfig);
1238 }
1239
1240 if (m_private->currentEvent.window.event == SDL_WINDOWEVENT_ENTER)
1241 {
1242 event.type = EVENT_MOUSE_ENTER;
1243 }
1244
1245 if (m_private->currentEvent.window.event == SDL_WINDOWEVENT_LEAVE)
1246 {
1247 event.type = EVENT_MOUSE_LEAVE;
1248 }
1249
1250 if (m_private->currentEvent.window.event == SDL_WINDOWEVENT_FOCUS_GAINED)
1251 {
1252 event.type = EVENT_FOCUS_GAINED;
1253 }
1254
1255 if (m_private->currentEvent.window.event == SDL_WINDOWEVENT_FOCUS_LOST)
1256 {
1257 event.type = EVENT_FOCUS_LOST;
1258 }
1259 }
1260 else if ( (m_private->currentEvent.type == SDL_KEYDOWN) ||
1261 (m_private->currentEvent.type == SDL_KEYUP) )
1262 {
1263 if (m_private->currentEvent.type == SDL_KEYDOWN)
1264 event.type = EVENT_KEY_DOWN;
1265 else
1266 event.type = EVENT_KEY_UP;
1267
1268 auto data = MakeUnique<KeyEventData>();
1269
1270 data->virt = false;
1271 data->key = m_private->currentEvent.key.keysym.sym;
1272 event.kmodState = m_private->currentEvent.key.keysym.mod;
1273
1274 // Some keyboards return numerical enter keycode instead of normal enter
1275 // See issue #427 for details
1276 if (data->key == KEY(KP_ENTER))
1277 data->key = KEY(RETURN);
1278
1279 if (data->key == KEY(TAB) && ((event.kmodState & KEY_MOD(ALT)) != 0))
1280 {
1281 GetLogger()->Debug("Minimize to taskbar\n");
1282 SDL_MinimizeWindow(m_private->window);
1283 event.type = EVENT_NULL;
1284 }
1285
1286 event.data = std::move(data);
1287 }
1288 else if (m_private->currentEvent.type == SDL_TEXTINPUT)
1289 {
1290 event.type = EVENT_TEXT_INPUT;
1291 auto data = MakeUnique<TextInputData>();
1292 data->text = m_private->currentEvent.text.text;
1293 event.data = std::move(data);
1294 }
1295 else if (m_private->currentEvent.type == SDL_MOUSEWHEEL)
1296 {
1297 event.type = EVENT_MOUSE_WHEEL;
1298
1299 auto data = MakeUnique<MouseWheelEventData>();
1300 data->y = m_private->currentEvent.wheel.y;
1301 data->x = m_private->currentEvent.wheel.x;
1302
1303 event.data = std::move(data);
1304 }
1305 else if ( (m_private->currentEvent.type == SDL_MOUSEBUTTONDOWN) ||
1306 (m_private->currentEvent.type == SDL_MOUSEBUTTONUP) )
1307 {
1308 auto data = MakeUnique<MouseButtonEventData>();
1309
1310 if (m_private->currentEvent.type == SDL_MOUSEBUTTONDOWN)
1311 event.type = EVENT_MOUSE_BUTTON_DOWN;
1312 else
1313 event.type = EVENT_MOUSE_BUTTON_UP;
1314
1315 data->button = static_cast<MouseButton>(1 << m_private->currentEvent.button.button);
1316
1317 event.data = std::move(data);
1318 }
1319 else if (m_private->currentEvent.type == SDL_MOUSEMOTION)
1320 {
1321 event.type = EVENT_MOUSE_MOVE;
1322
1323 m_input->MouseMove(Math::IntPoint(m_private->currentEvent.button.x, m_private->currentEvent.button.y));
1324 }
1325 else if (m_private->currentEvent.type == SDL_JOYAXISMOTION)
1326 {
1327 event.type = EVENT_JOY_AXIS;
1328
1329 auto data = MakeUnique<JoyAxisEventData>();
1330 data->axis = m_private->currentEvent.jaxis.axis;
1331 data->value = m_private->currentEvent.jaxis.value;
1332 event.data = std::move(data);
1333 }
1334 else if ( (m_private->currentEvent.type == SDL_JOYBUTTONDOWN) ||
1335 (m_private->currentEvent.type == SDL_JOYBUTTONUP) )
1336 {
1337 if (m_private->currentEvent.type == SDL_JOYBUTTONDOWN)
1338 event.type = EVENT_JOY_BUTTON_DOWN;
1339 else
1340 event.type = EVENT_JOY_BUTTON_UP;
1341
1342 auto data = MakeUnique<JoyButtonEventData>();
1343 data->button = m_private->currentEvent.jbutton.button;
1344 event.data = std::move(data);
1345 }
1346
1347 return event;
1348 }
1349
LogEvent(const Event & event)1350 void CApplication::LogEvent(const Event &event)
1351 {
1352 CLogger *l = GetLogger();
1353
1354 auto PrintEventDetails = [&]()
1355 {
1356 l->Trace(" rTime = %f\n", event.rTime);
1357 l->Trace(" kmodState = %04x\n", event.kmodState);
1358 l->Trace(" mousePos = %f, %f\n", event.mousePos.x, event.mousePos.y);
1359 l->Trace(" mouseButtonsState = %02x\n", event.mouseButtonsState);
1360 l->Trace(" customParam = %d\n", event.customParam);
1361 };
1362
1363 // Print the events in debug mode to test the code
1364 if (IsDebugModeActive(DEBUG_SYS_EVENTS) || IsDebugModeActive(DEBUG_UPDATE_EVENTS) || IsDebugModeActive(DEBUG_APP_EVENTS))
1365 {
1366 std::string eventType = ParseEventType(event.type);
1367
1368 if (IsDebugModeActive(DEBUG_UPDATE_EVENTS) && event.type == EVENT_FRAME)
1369 {
1370 l->Trace("Update event: %s\n", eventType.c_str());
1371 PrintEventDetails();
1372 }
1373
1374 if (IsDebugModeActive(DEBUG_SYS_EVENTS) && (event.type <= EVENT_SYS_MAX && event.type != EVENT_FRAME))
1375 {
1376 l->Trace("System event %s:\n", eventType.c_str());
1377 switch (event.type)
1378 {
1379 case EVENT_KEY_DOWN:
1380 case EVENT_KEY_UP:
1381 {
1382 auto data = event.GetData<KeyEventData>();
1383 l->Trace(" virt = %s\n", data->virt ? "true" : "false");
1384 l->Trace(" key = %d\n", data->key);
1385 break;
1386 }
1387 case EVENT_TEXT_INPUT:
1388 {
1389 auto data = event.GetData<TextInputData>();
1390 l->Trace(" text = %s\n", data->text.c_str());
1391 break;
1392 }
1393 case EVENT_MOUSE_BUTTON_DOWN:
1394 case EVENT_MOUSE_BUTTON_UP:
1395 {
1396 auto data = event.GetData<MouseButtonEventData>();
1397 l->Trace(" button = %d\n", data->button);
1398 break;
1399 }
1400 case EVENT_MOUSE_WHEEL:
1401 {
1402 auto data = event.GetData<MouseWheelEventData>();
1403 l->Trace(" y = %d\n", data->y);
1404 l->Trace(" x = %d\n", data->x);
1405 break;
1406 }
1407 case EVENT_JOY_AXIS:
1408 {
1409 auto data = event.GetData<JoyAxisEventData>();
1410 l->Trace(" axis = %d\n", data->axis);
1411 l->Trace(" value = %d\n", data->value);
1412 break;
1413 }
1414 case EVENT_JOY_BUTTON_DOWN:
1415 case EVENT_JOY_BUTTON_UP:
1416 {
1417 auto data = event.GetData<JoyButtonEventData>();
1418 l->Trace(" button = %d\n", data->button);
1419 break;
1420 }
1421 default:
1422 break;
1423 }
1424
1425 PrintEventDetails();
1426 }
1427
1428 if (IsDebugModeActive(DEBUG_APP_EVENTS) && event.type > EVENT_SYS_MAX)
1429 {
1430 l->Trace("App event %s:\n", eventType.c_str());
1431 PrintEventDetails();
1432 }
1433 }
1434 }
1435
1436
CreateVirtualEvent(const Event & sourceEvent)1437 Event CApplication::CreateVirtualEvent(const Event& sourceEvent)
1438 {
1439 Event virtualEvent;
1440
1441 if ((sourceEvent.type == EVENT_KEY_DOWN) || (sourceEvent.type == EVENT_KEY_UP))
1442 {
1443 auto sourceData = sourceEvent.GetData<KeyEventData>();
1444 auto virtualKey = GetVirtualKey(sourceData->key);
1445
1446 if (virtualKey == sourceData->key)
1447 {
1448 virtualEvent.type = EVENT_NULL;
1449 }
1450 else
1451 {
1452 virtualEvent.type = sourceEvent.type;
1453
1454 auto data = sourceData->Clone();
1455 auto keyData = static_cast<KeyEventData*>(data.get());
1456 keyData->key = virtualKey;
1457 keyData->virt = true;
1458 virtualEvent.data = std::move(data);
1459 }
1460 }
1461 else if ((sourceEvent.type == EVENT_JOY_BUTTON_DOWN) || (sourceEvent.type == EVENT_JOY_BUTTON_UP))
1462 {
1463 if (sourceEvent.type == EVENT_JOY_BUTTON_DOWN)
1464 virtualEvent.type = EVENT_KEY_DOWN;
1465 else
1466 virtualEvent.type = EVENT_KEY_UP;
1467
1468 auto sourceData = sourceEvent.GetData<JoyButtonEventData>();
1469
1470 auto data = MakeUnique<KeyEventData>();
1471 data->virt = true;
1472 data->key = VIRTUAL_JOY(sourceData->button);
1473 virtualEvent.data = std::move(data);
1474 }
1475 else
1476 {
1477 virtualEvent.type = EVENT_NULL;
1478 }
1479
1480 return virtualEvent;
1481 }
1482
1483 /** Renders the frame and swaps buffers as necessary */
Render()1484 void CApplication::Render()
1485 {
1486 CProfiler::StartPerformanceCounter(PCNT_RENDER_ALL);
1487 m_engine->Render();
1488 CProfiler::StopPerformanceCounter(PCNT_RENDER_ALL);
1489
1490 CProfiler::StartPerformanceCounter(PCNT_SWAP_BUFFERS);
1491 if (m_deviceConfig.doubleBuf)
1492 SDL_GL_SwapWindow(m_private->window);
1493 CProfiler::StopPerformanceCounter(PCNT_SWAP_BUFFERS);
1494 }
1495
RenderIfNeeded(int updateRate)1496 void CApplication::RenderIfNeeded(int updateRate)
1497 {
1498 m_systemUtils->GetCurrentTimeStamp(m_manualFrameTime);
1499 long long diff = m_systemUtils->TimeStampExactDiff(m_manualFrameLast, m_manualFrameTime);
1500 if (diff < 1e9f / updateRate)
1501 {
1502 return;
1503 }
1504 m_systemUtils->CopyTimeStamp(m_manualFrameLast, m_manualFrameTime);
1505
1506 Render();
1507 }
1508
SuspendSimulation()1509 void CApplication::SuspendSimulation()
1510 {
1511 m_simulationSuspended = true;
1512 GetLogger()->Info("Suspend simulation\n");
1513 }
1514
ResumeSimulation()1515 void CApplication::ResumeSimulation()
1516 {
1517 m_simulationSuspended = false;
1518 InternalResumeSimulation();
1519
1520 GetLogger()->Info("Resume simulation\n");
1521 }
1522
ResetTimeAfterLoading()1523 void CApplication::ResetTimeAfterLoading()
1524 {
1525 InternalResumeSimulation();
1526
1527 GetLogger()->Trace("Resume simulation on loading\n");
1528 }
1529
InternalResumeSimulation()1530 void CApplication::InternalResumeSimulation()
1531 {
1532 m_systemUtils->GetCurrentTimeStamp(m_baseTimeStamp);
1533 m_systemUtils->CopyTimeStamp(m_curTimeStamp, m_baseTimeStamp);
1534 m_realAbsTimeBase = m_realAbsTime;
1535 m_absTimeBase = m_exactAbsTime;
1536 }
1537
StartLoadingMusic()1538 void CApplication::StartLoadingMusic()
1539 {
1540 CThread musicLoadThread([this]()
1541 {
1542 GetLogger()->Debug("Cache sounds...\n");
1543 SystemTimeStamp* musicLoadStart = m_systemUtils->CreateTimeStamp();
1544 m_systemUtils->GetCurrentTimeStamp(musicLoadStart);
1545
1546 m_sound->Reset();
1547 m_sound->CacheAll();
1548
1549 SystemTimeStamp* musicLoadEnd = m_systemUtils->CreateTimeStamp();
1550 m_systemUtils->GetCurrentTimeStamp(musicLoadEnd);
1551 float musicLoadTime = m_systemUtils->TimeStampDiff(musicLoadStart, musicLoadEnd, STU_MSEC);
1552 GetLogger()->Debug("Sound loading took %.2f ms\n", musicLoadTime);
1553 },
1554 "Sound loading thread");
1555 musicLoadThread.Start();
1556 }
1557
GetSimulationSuspended() const1558 bool CApplication::GetSimulationSuspended() const
1559 {
1560 return m_simulationSuspended;
1561 }
1562
SetSimulationSpeed(float speed)1563 void CApplication::SetSimulationSpeed(float speed)
1564 {
1565 m_simulationSpeed = speed;
1566
1567 m_systemUtils->CopyTimeStamp(m_baseTimeStamp, m_curTimeStamp);
1568 m_realAbsTimeBase = m_realAbsTime;
1569 m_absTimeBase = m_exactAbsTime;
1570
1571 GetLogger()->Info("Simulation speed = %.2f\n", speed);
1572 }
1573
CreateUpdateEvent(SystemTimeStamp * newTimeStamp)1574 Event CApplication::CreateUpdateEvent(SystemTimeStamp *newTimeStamp)
1575 {
1576 if (m_simulationSuspended)
1577 return Event(EVENT_NULL);
1578
1579 m_systemUtils->CopyTimeStamp(m_lastTimeStamp, m_curTimeStamp);
1580 m_systemUtils->CopyTimeStamp(m_curTimeStamp, newTimeStamp);
1581
1582 long long absDiff = m_systemUtils->TimeStampExactDiff(m_baseTimeStamp, m_curTimeStamp);
1583 long long newRealAbsTime = m_realAbsTimeBase + absDiff;
1584 long long newRealRelTime = m_systemUtils->TimeStampExactDiff(m_lastTimeStamp, m_curTimeStamp);
1585
1586 if (newRealAbsTime < m_realAbsTime || newRealRelTime < 0)
1587 {
1588 GetLogger()->Error("Fatal error: got negative system counter difference!\n");
1589 GetLogger()->Error("This should never happen. Please report this error.\n");
1590 m_eventQueue->AddEvent(Event(EVENT_SYS_QUIT));
1591 return Event(EVENT_NULL);
1592 }
1593 else
1594 {
1595 m_realAbsTime = newRealAbsTime;
1596 // m_baseTimeStamp is updated on simulation speed change, so this is OK
1597 m_exactAbsTime = m_absTimeBase + m_simulationSpeed * absDiff;
1598 m_absTime = (m_absTimeBase + m_simulationSpeed * absDiff) / 1e9f;
1599
1600 m_realRelTime = newRealRelTime;
1601 m_exactRelTime = m_simulationSpeed * m_realRelTime;
1602 m_relTime = (m_simulationSpeed * m_realRelTime) / 1e9f;
1603 }
1604
1605 Event frameEvent(EVENT_FRAME);
1606 frameEvent.rTime = m_relTime;
1607 m_input->EventProcess(frameEvent);
1608
1609 return frameEvent;
1610 }
1611
GetSimulationSpeed() const1612 float CApplication::GetSimulationSpeed() const
1613 {
1614 return m_simulationSpeed;
1615 }
1616
GetAbsTime() const1617 float CApplication::GetAbsTime() const
1618 {
1619 return m_absTime;
1620 }
1621
GetExactAbsTime() const1622 long long CApplication::GetExactAbsTime() const
1623 {
1624 return m_exactAbsTime;
1625 }
1626
GetRealAbsTime() const1627 long long CApplication::GetRealAbsTime() const
1628 {
1629 return m_realAbsTime;
1630 }
1631
GetRelTime() const1632 float CApplication::GetRelTime() const
1633 {
1634 return m_relTime;
1635 }
1636
GetExactRelTime() const1637 long long CApplication::GetExactRelTime() const
1638 {
1639 return m_exactRelTime;
1640 }
1641
GetRealRelTime() const1642 long long CApplication::GetRealRelTime() const
1643 {
1644 return m_realRelTime;
1645 }
1646
GetVideoConfig() const1647 Gfx::DeviceConfig CApplication::GetVideoConfig() const
1648 {
1649 return m_deviceConfig;
1650 }
1651
GetVideoResolutionList(std::vector<Math::IntPoint> & resolutions,int display) const1652 void CApplication::GetVideoResolutionList(std::vector<Math::IntPoint> &resolutions, int display) const
1653 {
1654 resolutions.clear();
1655
1656 for(int i = 0; i < SDL_GetNumDisplayModes(display); i++)
1657 {
1658 SDL_DisplayMode mode;
1659 SDL_GetDisplayMode(display, i, &mode);
1660 Math::IntPoint resolution = Math::IntPoint(mode.w, mode.h);
1661
1662 if (std::find(resolutions.begin(), resolutions.end(), resolution) == resolutions.end())
1663 resolutions.push_back(resolution);
1664 }
1665 }
1666
SetDebugModeActive(DebugMode mode,bool active)1667 void CApplication::SetDebugModeActive(DebugMode mode, bool active)
1668 {
1669 if (active)
1670 m_debugModes |= mode;
1671 else
1672 m_debugModes &= (~mode);
1673 }
1674
IsDebugModeActive(DebugMode mode) const1675 bool CApplication::IsDebugModeActive(DebugMode mode) const
1676 {
1677 return (m_debugModes & mode) != 0;
1678 }
1679
ParseDebugModes(const std::string & str,int & debugModes)1680 bool CApplication::ParseDebugModes(const std::string& str, int& debugModes)
1681 {
1682 debugModes = 0;
1683
1684 boost::char_separator<char> sep(",");
1685 boost::tokenizer<boost::char_separator<char>> tokens(str, sep);
1686 for (const auto& modeToken : tokens)
1687 {
1688 if (modeToken == "sys_events")
1689 {
1690 debugModes |= DEBUG_SYS_EVENTS;
1691 }
1692 else if (modeToken == "update_events")
1693 {
1694 debugModes |= DEBUG_UPDATE_EVENTS;
1695 }
1696 else if (modeToken == "app_events")
1697 {
1698 debugModes |= DEBUG_APP_EVENTS;
1699 }
1700 else if (modeToken == "events")
1701 {
1702 debugModes |= DEBUG_EVENTS;
1703 }
1704 else if (modeToken == "models")
1705 {
1706 debugModes |= DEBUG_MODELS;
1707 }
1708 else if (modeToken == "all")
1709 {
1710 debugModes = DEBUG_ALL;
1711 }
1712 else
1713 {
1714 GetLogger()->Error("Invalid debug mode: '%s'\n", modeToken.c_str());
1715 return false;
1716 }
1717 }
1718
1719 return true;
1720 }
1721
SetMouseMode(MouseMode mode)1722 void CApplication::SetMouseMode(MouseMode mode)
1723 {
1724 m_mouseMode = mode;
1725 if ((m_mouseMode == MOUSE_SYSTEM) || (m_mouseMode == MOUSE_BOTH))
1726 SDL_ShowCursor(SDL_ENABLE);
1727 else
1728 SDL_ShowCursor(SDL_DISABLE);
1729 }
1730
GetMouseMode() const1731 MouseMode CApplication::GetMouseMode() const
1732 {
1733 return m_mouseMode;
1734 }
1735
MoveMouse(Math::Point pos)1736 void CApplication::MoveMouse(Math::Point pos)
1737 {
1738 Math::IntPoint windowPos = m_engine->InterfaceToWindowCoords(pos);
1739 m_input->MouseMove(windowPos);
1740 SDL_WarpMouseInWindow(m_private->window, windowPos.x, windowPos.y);
1741 }
1742
GetJoystickList() const1743 std::vector<JoystickDevice> CApplication::GetJoystickList() const
1744 {
1745 std::vector<JoystickDevice> result;
1746
1747 int count = SDL_NumJoysticks();
1748
1749 for (int index = 0; index < count; ++index)
1750 {
1751 JoystickDevice device;
1752 device.index = index;
1753 device.name = SDL_JoystickNameForIndex(index);
1754 result.push_back(device);
1755 }
1756
1757 return result;
1758 }
1759
GetJoystick() const1760 JoystickDevice CApplication::GetJoystick() const
1761 {
1762 return m_joystick;
1763 }
1764
SetJoystickEnabled(bool enable)1765 void CApplication::SetJoystickEnabled(bool enable)
1766 {
1767 m_joystickEnabled = enable;
1768
1769 if (m_joystickEnabled)
1770 {
1771 if (! OpenJoystick())
1772 {
1773 m_joystickEnabled = false;
1774 }
1775 }
1776 else
1777 {
1778 CloseJoystick();
1779 }
1780 }
1781
GetJoystickEnabled() const1782 bool CApplication::GetJoystickEnabled() const
1783 {
1784 return m_joystickEnabled;
1785 }
1786
GetLanguage() const1787 Language CApplication::GetLanguage() const
1788 {
1789 return m_language;
1790 }
1791
GetLanguageChar() const1792 char CApplication::GetLanguageChar() const
1793 {
1794 char langChar = 'E';
1795 switch (m_language)
1796 {
1797 default:
1798 case LANGUAGE_ENV:
1799 case LANGUAGE_ENGLISH:
1800 langChar = 'E';
1801 break;
1802
1803 case LANGUAGE_CZECH:
1804 langChar = 'C';
1805 break;
1806
1807 case LANGUAGE_GERMAN:
1808 langChar = 'D';
1809 break;
1810
1811 case LANGUAGE_FRENCH:
1812 langChar = 'F';
1813 break;
1814
1815 case LANGUAGE_POLISH:
1816 langChar = 'P';
1817 break;
1818
1819 case LANGUAGE_RUSSIAN:
1820 langChar = 'R';
1821 break;
1822
1823 case LANGUAGE_PORTUGUESE_BRAZILIAN:
1824 langChar = 'B';
1825 break;
1826
1827 }
1828 return langChar;
1829 }
1830
SetLanguage(Language language)1831 void CApplication::SetLanguage(Language language)
1832 {
1833 m_language = language;
1834
1835 /* Gettext initialization */
1836
1837 static char envLang[50] = { 0 };
1838 if (envLang[0] == 0)
1839 {
1840 // Get this only at the first call, since this code modifies it
1841 const char* currentEnvLang = gl_locale_name(LC_MESSAGES, "LC_MESSAGES");
1842 if (currentEnvLang != nullptr)
1843 {
1844 strcpy(envLang, currentEnvLang);
1845 }
1846 }
1847
1848 if (language == LANGUAGE_ENV)
1849 {
1850 if (envLang[0] == 0)
1851 {
1852 GetLogger()->Error("Failed to get language from environment, setting default language\n");
1853 m_language = LANGUAGE_ENGLISH;
1854 }
1855 else
1856 {
1857 GetLogger()->Trace("gl_locale_name: '%s'\n", envLang);
1858
1859 if (strncmp(envLang,"en",2) == 0)
1860 {
1861 m_language = LANGUAGE_ENGLISH;
1862 }
1863 else if (strncmp(envLang,"cs",2) == 0)
1864 {
1865 m_language = LANGUAGE_CZECH;
1866 }
1867 else if (strncmp(envLang,"de",2) == 0)
1868 {
1869 m_language = LANGUAGE_GERMAN;
1870 }
1871 else if (strncmp(envLang,"fr",2) == 0)
1872 {
1873 m_language = LANGUAGE_FRENCH;
1874 }
1875 else if (strncmp(envLang,"pl",2) == 0)
1876 {
1877 m_language = LANGUAGE_POLISH;
1878 }
1879 else if (strncmp(envLang,"ru",2) == 0)
1880 {
1881 m_language = LANGUAGE_RUSSIAN;
1882 }
1883 else if (strncmp(envLang,"pt",2) == 0)
1884 {
1885 m_language = LANGUAGE_PORTUGUESE_BRAZILIAN;
1886 }
1887 else
1888 {
1889 GetLogger()->Warn("Enviromnent locale ('%s') is not supported, setting default language\n", envLang);
1890 m_language = LANGUAGE_ENGLISH;
1891 }
1892 }
1893 }
1894
1895 std::string locale = "";
1896 switch (m_language)
1897 {
1898 default:
1899 locale = "";
1900 break;
1901
1902 case LANGUAGE_CZECH:
1903 locale = "cs_CZ.utf8";
1904 break;
1905
1906 case LANGUAGE_ENGLISH:
1907 locale = "en_US.utf8";
1908 break;
1909
1910 case LANGUAGE_GERMAN:
1911 locale = "de_DE.utf8";
1912 break;
1913
1914 case LANGUAGE_FRENCH:
1915 locale = "fr_FR.utf8";
1916 break;
1917
1918 case LANGUAGE_POLISH:
1919 locale = "pl_PL.utf8";
1920 break;
1921
1922 case LANGUAGE_RUSSIAN:
1923 locale = "ru_RU.utf8";
1924 break;
1925
1926 case LANGUAGE_PORTUGUESE_BRAZILIAN:
1927 locale = "pt_BR.utf8";
1928 break;
1929 }
1930
1931 std::string langStr = "LANGUAGE=";
1932 langStr += locale;
1933 strcpy(m_languageLocale, langStr.c_str());
1934 putenv(m_languageLocale);
1935 GetLogger()->Trace("SetLanguage: Set LANGUAGE=%s in environment\n", locale.c_str());
1936
1937 char* defaultLocale = setlocale(LC_ALL, ""); // Load system locale
1938 GetLogger()->Debug("Default system locale: %s\n", defaultLocale);
1939 if (!locale.empty()) // Override system locale?
1940 {
1941 setlocale(LC_ALL, locale.c_str());
1942 }
1943 setlocale(LC_NUMERIC, "C"); // Force numeric locale to "C" (fixes decimal point problems)
1944 std::string systemLocale = setlocale(LC_ALL, nullptr); // Get current locale configuration
1945 GetLogger()->Debug("Setting locale: %s\n", systemLocale.c_str());
1946 // Update C++ locale
1947 try
1948 {
1949 #if defined(_MSC_VER) && defined(_DEBUG)
1950 // Avoids failed assertion in VS debugger
1951 throw -1;
1952 #else
1953 std::locale::global(std::locale(systemLocale.c_str()));
1954 #endif
1955 }
1956 catch (...)
1957 {
1958 GetLogger()->Warn("Failed to update locale, possibly incorect system configuration. Will fallback to classic locale.\n");
1959 try
1960 {
1961 std::locale::global(std::locale::classic());
1962 }
1963 catch (...)
1964 {
1965 GetLogger()->Warn("Failed to set classic locale. Something is really messed up in your system configuration. Translations might not work.\n");
1966 }
1967
1968 // C locale might still work correctly
1969 setlocale(LC_ALL, "");
1970 setlocale(LC_NUMERIC, "C");
1971 }
1972
1973 bindtextdomain("colobot", m_pathManager->GetLangPath().c_str());
1974 bind_textdomain_codeset("colobot", "UTF-8");
1975 textdomain("colobot");
1976
1977 GetLogger()->Debug("SetLanguage: Test gettext translation: '%s'\n", gettext("Colobot rules!"));
1978 }
1979
GetSceneTestMode()1980 bool CApplication::GetSceneTestMode()
1981 {
1982 return m_sceneTest;
1983 }
1984
SetTextInput(bool textInputEnabled,int id)1985 void CApplication::SetTextInput(bool textInputEnabled, int id)
1986 {
1987 m_textInputEnabled[id] = textInputEnabled;
1988 if (std::any_of(m_textInputEnabled.begin(), m_textInputEnabled.end(), [](std::pair<int, bool> v) { return v.second; }))
1989 {
1990 SDL_StartTextInput();
1991 }
1992 else
1993 {
1994 SDL_StopTextInput();
1995 }
1996 }
1997
PlayForceFeedbackEffect(float strength,int length)1998 void CApplication::PlayForceFeedbackEffect(float strength, int length)
1999 {
2000 if (m_private->haptic == nullptr) return;
2001
2002 GetLogger()->Trace("Force feedback! length = %d ms, strength = %.2f\n", length, strength);
2003 if (SDL_HapticRumblePlay(m_private->haptic, strength, length) != 0)
2004 {
2005 GetLogger()->Debug("Failed to play haptic effect: %s\n", SDL_GetError());
2006 }
2007 }
2008
StopForceFeedbackEffect()2009 void CApplication::StopForceFeedbackEffect()
2010 {
2011 if (m_private->haptic == nullptr) return;
2012 SDL_HapticRumbleStop(m_private->haptic);
2013 }
2014