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(&currentEvent, 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