1 
2 // main.cpp
3 
4 // Copyright 2004-2006 Jasmine Langridge, jas@jareiko.net
5 // License: GPL version 2 (see included gpl.txt)
6 
7 
8 
9 #include "main.h"
10 #include "physfs_utils.h"
11 
12 #include <SDL2/SDL_main.h>
13 #include <SDL2/SDL_thread.h>
14 
15 #include <cctype>
16 #include <regex>
17 
18 GLfloat MainApp::cfg_anisotropy = 1.0f;
19 bool MainApp::cfg_foliage = true;
20 bool MainApp::cfg_roadsigns = true;
21 bool MainApp::cfg_weather = true;
22 
config()23 void MainApp::config()
24 {
25     PUtil::setDebugLevel(DEBUGLEVEL_DEVELOPER);
26 
27     loadConfig();
28     setScreenMode(cfg_video_cx, cfg_video_cy, cfg_video_fullscreen);
29     calcScreenRatios();
30 
31     if (cfg_datadirs.empty())
32         throw MakePException("Data directory paths are empty: check your trigger-rally.config file.");
33 
34     for (const std::string &datadir: cfg_datadirs)
35         if (PHYSFS_mount(datadir.c_str(), NULL, 1) == 0)
36         {
37             PUtil::outLog() << "Failed to add PhysFS search directory \"" << datadir << "\"" << std::endl
38                 << "PhysFS: " << physfs_getErrorString() << std::endl;
39         }
40         else
41         {
42             PUtil::outLog() << "Main game data directory datadir=\"" << datadir << "\"" << std::endl;
43             break;
44         }
45 
46     if (cfg_copydefplayers)
47         copyDefaultPlayers();
48 
49     best_times.loadAllTimes();
50     player_unlocks = best_times.getUnlockData();
51 
52 #ifndef NDEBUG
53     PUtil::outLog() << "Player \"" cfg_playername << "\" unlocks:\n";
54 
55     for (const auto &s: player_unlocks)
56         PUtil::outLog() << '\t' << s << '\n';
57 #endif
58 }
59 
load()60 void MainApp::load()
61 {
62   psys_dirt = nullptr;
63 
64   audinst_engine = nullptr;
65   audinst_wind = nullptr;
66   audinst_gravel = nullptr;
67   game = nullptr;
68 
69   // use PUtil, not boost
70   //std::string buff = boost::str(boost::format("textures/splash/splash%u.jpg") % ((rand() % 3) + 1));
71   //if (!(tex_splash_screen = getSSTexture().loadTexture(buff))) return false;
72 
73   if (!(tex_loading_screen = getSSTexture().loadTexture("/textures/splash/loading.png")))
74     throw MakePException("Failed to load the Loading screen");
75 
76   if (!(tex_splash_screen = getSSTexture().loadTexture("/textures/splash/splash.jpg")))
77     throw MakePException("Failed to load the Splash screen");
78 
79   appstate = AS_LOAD_1;
80 
81   loadscreencount = 3;
82 
83   splashtimeout = 0.0f;
84 
85   // Check that controls are available where requested
86   // (can't be done in config because joy info not available)
87 
88   for (int i = 0; i < ActionCount; i++) {
89 
90     switch(ctrl.map[i].type) {
91     case UserControl::TypeUnassigned:
92       break;
93 
94     case UserControl::TypeKey:
95       if (ctrl.map[i].key.sym <= 0 /* || ctrl.map[i].key.sym >= SDLK_LAST */) // `SDLK_LAST` unavailable in SDL2
96         ctrl.map[i].type = UserControl::TypeUnassigned;
97       break;
98 
99     case UserControl::TypeJoyButton:
100       if (0 >= getNumJoysticks() || ctrl.map[i].joybutton.button >= getJoyNumButtons(0))
101         ctrl.map[i].type = UserControl::TypeUnassigned;
102       break;
103 
104     case UserControl::TypeJoyAxis:
105       if (0 >= getNumJoysticks() || ctrl.map[i].joyaxis.axis >= getJoyNumAxes(0))
106         ctrl.map[i].type = UserControl::TypeUnassigned;
107       break;
108     }
109   }
110 }
111 
112 namespace
113 {
114 
115 ///
116 /// @brief X-macro defining supported SDL keymaps.
117 /// @see http://wiki.libsdl.org/SDL_Keycode
118 ///
119 #define STRING_TO_SDL_KEYMAP    \
120     X(SDLK_UNKNOWN)             \
121     X(SDLK_BACKSPACE)           \
122     X(SDLK_TAB)                 \
123     X(SDLK_RETURN)              \
124     X(SDLK_ESCAPE)              \
125     X(SDLK_SPACE)               \
126     X(SDLK_EXCLAIM)             \
127     X(SDLK_QUOTEDBL)            \
128     X(SDLK_HASH)                \
129     X(SDLK_DOLLAR)              \
130     X(SDLK_PERCENT)             \
131     X(SDLK_AMPERSAND)           \
132     X(SDLK_QUOTE)               \
133     X(SDLK_LEFTPAREN)           \
134     X(SDLK_RIGHTPAREN)          \
135     X(SDLK_ASTERISK)            \
136     X(SDLK_PLUS)                \
137     X(SDLK_COMMA)               \
138     X(SDLK_MINUS)               \
139     X(SDLK_PERIOD)              \
140     X(SDLK_SLASH)               \
141     X(SDLK_0)                   \
142     X(SDLK_1)                   \
143     X(SDLK_2)                   \
144     X(SDLK_3)                   \
145     X(SDLK_4)                   \
146     X(SDLK_5)                   \
147     X(SDLK_6)                   \
148     X(SDLK_7)                   \
149     X(SDLK_8)                   \
150     X(SDLK_9)                   \
151     X(SDLK_COLON)               \
152     X(SDLK_SEMICOLON)           \
153     X(SDLK_LESS)                \
154     X(SDLK_EQUALS)              \
155     X(SDLK_GREATER)             \
156     X(SDLK_QUESTION)            \
157     X(SDLK_AT)                  \
158     X(SDLK_LEFTBRACKET)         \
159     X(SDLK_BACKSLASH)           \
160     X(SDLK_RIGHTBRACKET)        \
161     X(SDLK_CARET)               \
162     X(SDLK_UNDERSCORE)          \
163     X(SDLK_BACKQUOTE)           \
164     X(SDLK_a)                   \
165     X(SDLK_b)                   \
166     X(SDLK_c)                   \
167     X(SDLK_d)                   \
168     X(SDLK_e)                   \
169     X(SDLK_f)                   \
170     X(SDLK_g)                   \
171     X(SDLK_h)                   \
172     X(SDLK_i)                   \
173     X(SDLK_j)                   \
174     X(SDLK_k)                   \
175     X(SDLK_l)                   \
176     X(SDLK_m)                   \
177     X(SDLK_n)                   \
178     X(SDLK_o)                   \
179     X(SDLK_p)                   \
180     X(SDLK_q)                   \
181     X(SDLK_r)                   \
182     X(SDLK_s)                   \
183     X(SDLK_t)                   \
184     X(SDLK_u)                   \
185     X(SDLK_v)                   \
186     X(SDLK_w)                   \
187     X(SDLK_x)                   \
188     X(SDLK_y)                   \
189     X(SDLK_z)                   \
190     X(SDLK_DELETE)              \
191     X(SDLK_CAPSLOCK)            \
192     X(SDLK_F1)                  \
193     X(SDLK_F2)                  \
194     X(SDLK_F3)                  \
195     X(SDLK_F4)                  \
196     X(SDLK_F5)                  \
197     X(SDLK_F6)                  \
198     X(SDLK_F7)                  \
199     X(SDLK_F8)                  \
200     X(SDLK_F9)                  \
201     X(SDLK_F10)                 \
202     X(SDLK_F11)                 \
203     X(SDLK_F12)                 \
204     X(SDLK_PRINTSCREEN)         \
205     X(SDLK_SCROLLLOCK)          \
206     X(SDLK_PAUSE)               \
207     X(SDLK_INSERT)              \
208     X(SDLK_HOME)                \
209     X(SDLK_PAGEUP)              \
210     X(SDLK_END)                 \
211     X(SDLK_PAGEDOWN)            \
212     X(SDLK_RIGHT)               \
213     X(SDLK_LEFT)                \
214     X(SDLK_DOWN)                \
215     X(SDLK_UP)                  \
216     X(SDLK_NUMLOCKCLEAR)        \
217     X(SDLK_KP_DIVIDE)           \
218     X(SDLK_KP_MULTIPLY)         \
219     X(SDLK_KP_MINUS)            \
220     X(SDLK_KP_PLUS)             \
221     X(SDLK_KP_ENTER)            \
222     X(SDLK_KP_1)                \
223     X(SDLK_KP_2)                \
224     X(SDLK_KP_3)                \
225     X(SDLK_KP_4)                \
226     X(SDLK_KP_5)                \
227     X(SDLK_KP_6)                \
228     X(SDLK_KP_7)                \
229     X(SDLK_KP_8)                \
230     X(SDLK_KP_9)                \
231     X(SDLK_KP_0)                \
232     X(SDLK_KP_PERIOD)           \
233     X(SDLK_APPLICATION)         \
234     X(SDLK_POWER)               \
235     X(SDLK_KP_EQUALS)           \
236     X(SDLK_F13)                 \
237     X(SDLK_F14)                 \
238     X(SDLK_F15)                 \
239     X(SDLK_F16)                 \
240     X(SDLK_F17)                 \
241     X(SDLK_F18)                 \
242     X(SDLK_F19)                 \
243     X(SDLK_F20)                 \
244     X(SDLK_F21)                 \
245     X(SDLK_F22)                 \
246     X(SDLK_F23)                 \
247     X(SDLK_F24)                 \
248     X(SDLK_EXECUTE)             \
249     X(SDLK_HELP)                \
250     X(SDLK_MENU)                \
251     X(SDLK_SELECT)              \
252     X(SDLK_STOP)                \
253     X(SDLK_AGAIN)               \
254     X(SDLK_UNDO)                \
255     X(SDLK_CUT)                 \
256     X(SDLK_COPY)                \
257     X(SDLK_PASTE)               \
258     X(SDLK_FIND)                \
259     X(SDLK_MUTE)                \
260     X(SDLK_VOLUMEUP)            \
261     X(SDLK_VOLUMEDOWN)          \
262     X(SDLK_KP_COMMA)            \
263     X(SDLK_KP_EQUALSAS400)      \
264     X(SDLK_ALTERASE)            \
265     X(SDLK_SYSREQ)              \
266     X(SDLK_CANCEL)              \
267     X(SDLK_CLEAR)               \
268     X(SDLK_PRIOR)               \
269     X(SDLK_RETURN2)             \
270     X(SDLK_SEPARATOR)           \
271     X(SDLK_OUT)                 \
272     X(SDLK_OPER)                \
273     X(SDLK_CLEARAGAIN)          \
274     X(SDLK_CRSEL)               \
275     X(SDLK_EXSEL)               \
276     X(SDLK_KP_00)               \
277     X(SDLK_KP_000)              \
278     X(SDLK_THOUSANDSSEPARATOR)  \
279     X(SDLK_DECIMALSEPARATOR)    \
280     X(SDLK_CURRENCYUNIT)        \
281     X(SDLK_CURRENCYSUBUNIT)     \
282     X(SDLK_KP_LEFTPAREN)        \
283     X(SDLK_KP_RIGHTPAREN)       \
284     X(SDLK_KP_LEFTBRACE)        \
285     X(SDLK_KP_RIGHTBRACE)       \
286     X(SDLK_KP_TAB)              \
287     X(SDLK_KP_BACKSPACE)        \
288     X(SDLK_KP_A)                \
289     X(SDLK_KP_B)                \
290     X(SDLK_KP_C)                \
291     X(SDLK_KP_D)                \
292     X(SDLK_KP_E)                \
293     X(SDLK_KP_F)                \
294     X(SDLK_KP_XOR)              \
295     X(SDLK_KP_POWER)            \
296     X(SDLK_KP_PERCENT)          \
297     X(SDLK_KP_LESS)             \
298     X(SDLK_KP_GREATER)          \
299     X(SDLK_KP_AMPERSAND)        \
300     X(SDLK_KP_DBLAMPERSAND)     \
301     X(SDLK_KP_VERTICALBAR)      \
302     X(SDLK_KP_DBLVERTICALBAR)   \
303     X(SDLK_KP_COLON)            \
304     X(SDLK_KP_HASH)             \
305     X(SDLK_KP_SPACE)            \
306     X(SDLK_KP_AT)               \
307     X(SDLK_KP_EXCLAM)           \
308     X(SDLK_KP_MEMSTORE)         \
309     X(SDLK_KP_MEMRECALL)        \
310     X(SDLK_KP_MEMCLEAR)         \
311     X(SDLK_KP_MEMADD)           \
312     X(SDLK_KP_MEMSUBTRACT)      \
313     X(SDLK_KP_MEMMULTIPLY)      \
314     X(SDLK_KP_MEMDIVIDE)        \
315     X(SDLK_KP_PLUSMINUS)        \
316     X(SDLK_KP_CLEAR)            \
317     X(SDLK_KP_CLEARENTRY)       \
318     X(SDLK_KP_BINARY)           \
319     X(SDLK_KP_OCTAL)            \
320     X(SDLK_KP_DECIMAL)          \
321     X(SDLK_KP_HEXADECIMAL)      \
322     X(SDLK_LCTRL)               \
323     X(SDLK_LSHIFT)              \
324     X(SDLK_LALT)                \
325     X(SDLK_LGUI)                \
326     X(SDLK_RCTRL)               \
327     X(SDLK_RSHIFT)              \
328     X(SDLK_RALT)                \
329     X(SDLK_RGUI)                \
330     X(SDLK_MODE)                \
331     X(SDLK_AUDIONEXT)           \
332     X(SDLK_AUDIOPREV)           \
333     X(SDLK_AUDIOSTOP)           \
334     X(SDLK_AUDIOPLAY)           \
335     X(SDLK_AUDIOMUTE)           \
336     X(SDLK_MEDIASELECT)         \
337     X(SDLK_WWW)                 \
338     X(SDLK_MAIL)                \
339     X(SDLK_CALCULATOR)          \
340     X(SDLK_COMPUTER)            \
341     X(SDLK_AC_SEARCH)           \
342     X(SDLK_AC_HOME)             \
343     X(SDLK_AC_BACK)             \
344     X(SDLK_AC_FORWARD)          \
345     X(SDLK_AC_STOP)             \
346     X(SDLK_AC_REFRESH)          \
347     X(SDLK_AC_BOOKMARKS)        \
348     X(SDLK_BRIGHTNESSDOWN)      \
349     X(SDLK_BRIGHTNESSUP)        \
350     X(SDLK_DISPLAYSWITCH)       \
351     X(SDLK_KBDILLUMTOGGLE)      \
352     X(SDLK_KBDILLUMDOWN)        \
353     X(SDLK_KBDILLUMUP)          \
354     X(SDLK_EJECT)               \
355     X(SDLK_SLEEP)
356 
357 ///
358 /// @brief Converts the string to a SDL keycode.
359 /// @param [in] s   The string to be converted.
360 /// @returns The keycode.
361 ///
getSdlKeySym(const std::string & s)362 SDL_Keycode getSdlKeySym(const std::string &s)
363 {
364 #define X(SdlKey)   if (s == #SdlKey) return SdlKey;
365     STRING_TO_SDL_KEYMAP
366 #undef X
367     return SDLK_HELP;
368 }
369 
370 }
371 
372 ///
373 /// @brief Copies default players from data to user directory.
374 ///
copyDefaultPlayers() const375 void MainApp::copyDefaultPlayers() const
376 {
377     const std::string dppsearchdir = "/defplayers"; // Default Player Profiles Search Directory
378     const std::string dppdestdir = "/players"; // Default Player Profiles Destination Directory
379 
380     char **rc = PHYSFS_enumerateFiles(dppsearchdir.c_str());
381 
382     for (char **fname = rc; *fname != nullptr; ++fname)
383     {
384         // reject files that are already in the user directory
385         if (PHYSFS_exists((dppdestdir + '/' + *fname).c_str()))
386         {
387             PUtil::outLog() << "Skipping copy of default player \"" << *fname << "\"" << std::endl;
388             continue;
389         }
390 
391         // reject files without .PLAYER extension (lowercase)
392         std::smatch mr; // Match Results
393         std::regex pat(R"(^([\s\w]+)(\.player)$)"); // Pattern
394         std::string fn(*fname); // Filename
395 
396         if (!std::regex_search(fn, mr, pat))
397             continue;
398 
399         if (!PUtil::copyFile(dppsearchdir + '/' + *fname, dppdestdir + '/' + *fname))
400             PUtil::outLog() << "Couldn't copy default player \"" << *fname << "\"" << std::endl;
401     }
402 
403     PHYSFS_freeList(rc);
404 }
405 
406 ///
407 /// @brief Load configurations from files
408 /// @todo Since C++11 introduced default members initializers, the defaults could
409 ///  be set in the class declaration directly rather than in this function.
410 ///
loadConfig()411 void MainApp::loadConfig()
412 {
413   PUtil::outLog() << "Loading game configuration" << std::endl;
414 
415   // Set defaults
416 
417   cfg_playername = "Player";
418   cfg_copydefplayers = true;
419 
420   cfg_video_cx = 640;
421   cfg_video_cy = 480;
422   cfg_video_fullscreen = false;
423 
424   cfg_drivingassist = 1.0f;
425   cfg_enable_sound = true;
426   cfg_enable_codriversigns = true;
427   cfg_skip_saves = 5;
428   cfg_volume_engine = 0.33f;
429   cfg_volume_sfx = 1.0f;
430   cfg_volume_codriver = 1.0f;
431   cfg_anisotropy = 1.0f;
432   cfg_foliage = true;
433   cfg_roadsigns = true;
434   cfg_weather = true;
435   cfg_speed_unit = mph;
436   cfg_speed_style = analogue;
437   cfg_snowflaketype = SnowFlakeType::point;
438   cfg_dirteffect = true;
439 
440   cfg_datadirs.clear();
441 
442   hud_speedo_start_deg = MPH_ZERO_DEG;
443   hud_speedo_mps_deg_mult = MPS_MPH_DEG_MULT;
444   hud_speedo_mps_speed_mult = MPS_MPH_SPEED_MULT;
445 
446   ctrl.action_name[ActionForward] = std::string("forward");
447   ctrl.action_name[ActionBack] = std::string("back");
448   ctrl.action_name[ActionLeft] = std::string("left");
449   ctrl.action_name[ActionRight] = std::string("right");
450   ctrl.action_name[ActionHandbrake] = std::string("handbrake");
451   ctrl.action_name[ActionRecover] = std::string("recover");
452   ctrl.action_name[ActionRecoverAtCheckpoint] = std::string("recoveratcheckpoint");
453   ctrl.action_name[ActionCamMode] = std::string("cammode");
454   ctrl.action_name[ActionCamLeft] = std::string("camleft");
455   ctrl.action_name[ActionCamRight] = std::string("camright");
456   ctrl.action_name[ActionShowMap] = std::string("showmap");
457   ctrl.action_name[ActionPauseRace] = std::string("pauserace");
458   ctrl.action_name[ActionShowUi] = std::string("showui");
459   ctrl.action_name[ActionShowCheckpoint] = std::string("showcheckpoint");
460   ctrl.action_name[ActionNext] = std::string("next");
461 
462   for (int i = 0; i < ActionCount; i++) {
463     ctrl.map[i].type = UserControl::TypeUnassigned;
464     ctrl.map[i].value = 0.0f;
465   }
466 
467   // Do config file management
468 
469   std::string cfgfilename = "trigger-rally-" PACKAGE_VERSION ".config";
470 
471   if (!PHYSFS_exists(cfgfilename.c_str())) {
472 #ifdef UNIX
473     const std::vector<std::string> cfghidingplaces {
474         "/usr/local/share/games/trigger-rally/"
475     };
476 
477     for (const std::string &cfgpath: cfghidingplaces)
478         if (PHYSFS_mount(cfgpath.c_str(), NULL, 1) == 0)
479         {
480             PUtil::outLog() << "Failed to add PhysFS search directory \"" <<
481                 cfgpath << "\"\nPhysFS: " << physfs_getErrorString() << std::endl;
482         }
483 #endif
484     PUtil::outLog() << "No user config file, copying over defaults" << std::endl;
485 
486     std::string cfgdefaults = "trigger-rally.config.defs";
487 
488     if (!PUtil::copyFile(cfgdefaults, cfgfilename)) {
489 
490       PUtil::outLog() << "Couldn't create user config file. Proceeding with defaults." << std::endl;
491 
492       cfgfilename = cfgdefaults;
493     }
494   }
495 
496   // Load actual settings from file
497 
498   XMLDocument xmlfile;
499 
500   XMLElement *rootelem = PUtil::loadRootElement(xmlfile, cfgfilename, "config");
501   if (!rootelem) {
502     PUtil::outLog() << "Error: Couldn't load configuration file" << std::endl;
503     PUtil::outLog() << "TinyXML: " << xmlfile.ErrorStr() << std::endl;
504     PUtil::outLog() << "Your data paths are probably not set up correctly" << std::endl;
505     throw MakePException ("Boink");
506   }
507 
508   const char *val;
509 
510   for (XMLElement *walk = rootelem->FirstChildElement();
511     walk; walk = walk->NextSiblingElement()) {
512 
513     if (strcmp(walk->Value(), "player") == 0)
514     {
515         val = walk->Attribute("name");
516 
517         if (val != nullptr)
518         {
519             cfg_playername = val;
520             best_times.setPlayerName(val);
521         }
522 
523         val = walk->Attribute("copydefplayers");
524 
525         if (val != nullptr && std::string(val) == "no")
526             cfg_copydefplayers = false;
527         else
528             cfg_copydefplayers = true;
529 
530         val = walk->Attribute("skipsaves");
531 
532         if (val != nullptr)
533             cfg_skip_saves = std::stol(val);
534 
535         best_times.setSkipSaves(cfg_skip_saves);
536     }
537     else
538     if (!strcmp(walk->Value(), "video")) {
539 
540         val = walk->Attribute("automatic");
541 
542         if (val != nullptr && std::string(val) == "yes")
543             automaticVideoMode(true);
544         else
545             automaticVideoMode(false);
546 
547       val = walk->Attribute("width");
548       if (val) cfg_video_cx = atoi(val);
549 
550       val = walk->Attribute("height");
551       if (val) cfg_video_cy = atoi(val);
552 
553       val = walk->Attribute("fullscreen");
554       if (val) {
555         if (!strcmp(val, "yes"))
556           cfg_video_fullscreen = true;
557         else if (!strcmp(val, "no"))
558           cfg_video_fullscreen = false;
559       }
560 
561       val = walk->Attribute("requirergb");
562       if (val) {
563         if (!strcmp(val, "yes"))
564           requireRGB(true);
565         else if (!strcmp(val, "no"))
566           requireRGB(false);
567       }
568 
569       val = walk->Attribute("requirealpha");
570       if (val) {
571         if (!strcmp(val, "yes"))
572           requireAlpha(true);
573         else if (!strcmp(val, "no"))
574           requireAlpha(false);
575       }
576 
577       val = walk->Attribute("requiredepth");
578       if (val) {
579         if (!strcmp(val, "yes"))
580           requireDepth(true);
581         else if (!strcmp(val, "no"))
582           requireDepth(false);
583       }
584 
585       val = walk->Attribute("requirestencil");
586       if (val) {
587         if (!strcmp(val, "yes"))
588           requireStencil(true);
589         else if (!strcmp(val, "no"))
590           requireStencil(false);
591       }
592 
593       val = walk->Attribute("stereo");
594       if (val) {
595         if (!strcmp(val, "none"))
596           setStereoMode(PApp::StereoNone);
597         else if (!strcmp(val, "quadbuffer"))
598           setStereoMode(PApp::StereoQuadBuffer);
599         else if (!strcmp(val, "red-blue"))
600           setStereoMode(PApp::StereoRedBlue);
601         else if (!strcmp(val, "red-green"))
602           setStereoMode(PApp::StereoRedGreen);
603         else if (!strcmp(val, "red-cyan"))
604           setStereoMode(PApp::StereoRedCyan);
605         else if (!strcmp(val, "yellow-blue"))
606           setStereoMode(PApp::StereoYellowBlue);
607       }
608 
609       float sepMult = 1.0f;
610       val = walk->Attribute("stereoswapeyes");
611       if (val && !strcmp(val, "yes"))
612         sepMult = -1.0f;
613 
614       val = walk->Attribute("stereoeyeseparation");
615       if (val) {
616         setStereoEyeSeperation(atof(val) * sepMult);
617       }
618     }
619     else
620     if (!strcmp(walk->Value(), "audio"))
621     {
622         val = walk->Attribute("enginevolume");
623 
624         if (val != nullptr)
625             cfg_volume_engine = atof(val);
626 
627         val = walk->Attribute("sfxvolume");
628 
629         if (val != nullptr)
630             cfg_volume_sfx = atof(val);
631 
632         val = walk->Attribute("codrivervolume");
633 
634         if (val != nullptr)
635             cfg_volume_codriver = atof(val);
636     }
637     else
638     if (!strcmp(walk->Value(), "graphics"))
639     {
640         val = walk->Attribute("anisotropy");
641 
642         if (val)
643         {
644             if (!strcmp(val, "off"))
645             {
646                 cfg_anisotropy = 1.0f;
647             }
648             else
649             if (!strcmp(val, "max"))
650             {
651                 glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &cfg_anisotropy);
652             }
653             else // TODO: listen to the user, but don't trust him
654             {
655                 cfg_anisotropy = atof(val);
656                 CLAMP_LOWER(cfg_anisotropy, 1.0f);
657             }
658         }
659 
660         val = walk->Attribute("foliage");
661 
662         if (val)
663         {
664             if (!strcmp(val, "no"))
665                 cfg_foliage = false;
666             else // "yes"
667                 cfg_foliage = true;
668         }
669 
670         val = walk->Attribute("roadsigns");
671 
672         if (val != nullptr)
673         {
674             if (strcmp(val, "no") == 0)
675                 cfg_roadsigns = false;
676             else // yes
677                 cfg_roadsigns = true;
678         }
679 
680         val = walk->Attribute("weather");
681 
682         if (val)
683         {
684             if (!strcmp(val, "no"))
685                 cfg_weather = false;
686             else // "yes"
687                 cfg_weather = true;
688         }
689 
690         val = walk->Attribute("snowflaketype");
691 
692         if (val)
693         {
694             if (!strcmp(val, "square"))
695                 cfg_snowflaketype = SnowFlakeType::square;
696             else
697             if (!strcmp(val, "textured"))
698                 cfg_snowflaketype = SnowFlakeType::textured;
699             else // default
700                 cfg_snowflaketype = SnowFlakeType::point;
701         }
702 
703         val = walk->Attribute("dirteffect");
704 
705         if (val)
706         {
707             if (!strcmp(val, "yes"))
708                 cfg_dirteffect = true;
709             else
710                 cfg_dirteffect = false;
711         }
712     }
713     else
714     if (!strcmp(walk->Value(), "datadirectory"))
715     {
716         for (XMLElement *walk2 = walk->FirstChildElement(); walk2; walk2 = walk2->NextSiblingElement())
717             if (!strcmp(walk2->Value(), "data"))
718                 cfg_datadirs.push_back(walk2->Attribute("path"));
719     }
720     else if (!strcmp(walk->Value(), "parameters")) {
721 
722       val = walk->Attribute("drivingassist");
723       if (val) cfg_drivingassist = atof(val);
724 
725       val = walk->Attribute("enablesound");
726       if (val) {
727         if (!strcmp(val, "yes"))
728           cfg_enable_sound = true;
729         else if (!strcmp(val, "no"))
730           cfg_enable_sound = false;
731       }
732 
733         val = walk->Attribute("enablecodriversigns");
734 
735         if (val != nullptr)
736         {
737             if (strcmp(val, "yes") == 0)
738                 cfg_enable_codriversigns = true;
739             else
740             if (strcmp(val, "no") == 0)
741                 cfg_enable_codriversigns = false;
742         }
743 
744       val = walk->Attribute("speedunit");
745       if (val) {
746         if (!strcmp(val, "mph")) {
747             cfg_speed_unit = mph;
748             hud_speedo_start_deg = MPH_ZERO_DEG;
749             hud_speedo_mps_deg_mult = MPS_MPH_DEG_MULT;
750             hud_speedo_mps_speed_mult = MPS_MPH_SPEED_MULT;
751           }
752         else if (!strcmp(val, "kph")) {
753            cfg_speed_unit = kph;
754            hud_speedo_start_deg = KPH_ZERO_DEG;
755            hud_speedo_mps_deg_mult = MPS_KPH_DEG_MULT;
756            hud_speedo_mps_speed_mult = MPS_KPH_SPEED_MULT;
757          }
758       }
759 
760       val = walk->Attribute("codriver");
761 
762       if (val != nullptr)
763         cfg_codrivername = val;
764 
765       val = walk->Attribute("codriversigns");
766 
767         if (val != nullptr)
768             cfg_codriversigns = val;
769 
770         val = walk->Attribute("codriversignslife");
771 
772         if (val != nullptr)
773             cfg_codriveruserconfig.life = std::stof(val);
774 
775         val = walk->Attribute("codriversignsposx");
776 
777         if (val != nullptr)
778             cfg_codriveruserconfig.posx = std::stof(val);
779 
780         val = walk->Attribute("codriversignsposy");
781 
782         if (val != nullptr)
783             cfg_codriveruserconfig.posy = std::stof(val);
784 
785         val = walk->Attribute("codriversignsscale");
786 
787         if (val != nullptr)
788             cfg_codriveruserconfig.scale = std::stof(val);
789 
790     } else if (!strcmp(walk->Value(), "controls")) {
791 
792       for (XMLElement *walk2 = walk->FirstChildElement();
793         walk2; walk2 = walk2->NextSiblingElement()) {
794 
795         if (!strcmp(walk2->Value(), "keyboard")) {
796 
797           val = walk2->Attribute("enable");
798           if (val && !strcmp(val, "no"))
799             continue;
800 
801           for (XMLElement *walk3 = walk2->FirstChildElement();
802             walk3; walk3 = walk3->NextSiblingElement()) {
803 
804             if (!strcmp(walk3->Value(), "key")) {
805 
806               val = walk3->Attribute("action");
807 
808               int a;
809               for (a = 0; a < ActionCount; a++)
810                 if (ctrl.action_name[a] == val) break;
811 
812               if (a >= ActionCount) {
813                 PUtil::outLog() << "Config ctrls: Unknown action \"" << val << "\"" << std::endl;
814                 continue;
815               }
816               /*
817               // TODO: implement string to keycode mapping
818               val = walk3->Attribute("code");
819               if (!val) {
820                 PUtil::outLog() << "Config ctrls: Key has no code" << std::endl;
821                 continue;
822               }
823               */
824 
825               val = walk3->Attribute("id");
826 
827               if (!val)
828               {
829                   PUtil::outLog() << "Config ctrls: Key has no ID" << std::endl;
830                   continue;
831               }
832 
833               ctrl.map[a].type = UserControl::TypeKey;
834               //ctrl.map[a].key.sym = (SDLKey) atoi(val);
835               ctrl.map[a].key.sym = getSdlKeySym(val);
836             }
837           }
838 
839         } else if (!strcmp(walk2->Value(), "joystick")) {
840 
841           val = walk2->Attribute("enable");
842           if (val && !strcmp(val, "no"))
843             continue;
844 
845           for (XMLElement *walk3 = walk2->FirstChildElement();
846             walk3; walk3 = walk3->NextSiblingElement()) {
847 
848             if (!strcmp(walk3->Value(), "button")) {
849 
850               val = walk3->Attribute("action");
851 
852               int a;
853               for (a = 0; a < ActionCount; a++)
854                 if (ctrl.action_name[a] == val) break;
855 
856               if (a >= ActionCount) {
857                 PUtil::outLog() << "Config ctrls: Unknown action \"" << val << "\"" << std::endl;
858                 continue;
859               }
860 
861               val = walk3->Attribute("index");
862               if (!val) {
863                 PUtil::outLog() << "Config ctrls: Joy button has no index" << std::endl;
864                 continue;
865               }
866 
867               ctrl.map[a].type = UserControl::TypeJoyButton;
868               ctrl.map[a].joybutton.button = atoi(val);
869 
870             } else if (!strcmp(walk3->Value(), "axis")) {
871 
872               val = walk3->Attribute("action");
873 
874               int a;
875               for (a = 0; a < ActionCount; a++)
876                 if (ctrl.action_name[a] == val) break;
877 
878               if (a >= ActionCount) {
879                 PUtil::outLog() << "Config ctrls: Unknown action \"" << val << "\"" << std::endl;
880                 continue;
881               }
882 
883               val = walk3->Attribute("index");
884               if (!val) {
885                 PUtil::outLog() << "Config ctrls: Joy axis has no index" << std::endl;
886                 continue;
887               }
888 
889               int index = atoi(val);
890 
891               bool positive;
892 
893               val = walk3->Attribute("direction");
894               if (!val) {
895                 PUtil::outLog() << "Config ctrls: Joy axis has no direction" << std::endl;
896                 continue;
897               }
898               if (!strcmp(val, "+"))
899                 positive = true;
900               else if (!strcmp(val, "-"))
901                 positive = false;
902               else {
903                 PUtil::outLog() << "Config ctrls: Joy axis direction \"" << val <<
904                   "\" is neither \"+\" nor \"-\"" << std::endl;
905                 continue;
906               }
907 
908               ctrl.map[a].type = UserControl::TypeJoyAxis;
909               ctrl.map[a].joyaxis.axis = index;
910               ctrl.map[a].joyaxis.sign = positive ? 1.0f : -1.0f;
911               ctrl.map[a].joyaxis.deadzone = 0.0f;
912               ctrl.map[a].joyaxis.maxrange = 1.0f;
913 
914               val = walk3->Attribute("deadzone");
915               if (val) ctrl.map[a].joyaxis.deadzone = atof(val);
916 
917               val = walk3->Attribute("maxrange");
918               if (val) ctrl.map[a].joyaxis.maxrange = atof(val);
919             }
920           }
921         }
922       }
923     }
924   }
925 }
926 
loadLevel(TriggerLevel & tl)927 bool MainApp::loadLevel(TriggerLevel &tl)
928 {
929   tl.name = "Untitled";
930   tl.description = "(no description)";
931   tl.comment = "";
932   tl.author = "";
933   tl.targettime = "";
934   tl.targettimeshort = "";
935   tl.targettimefloat = 0.0f;
936   tl.tex_minimap = nullptr;
937   tl.tex_screenshot = nullptr;
938 
939   XMLDocument xmlfile;
940   XMLElement *rootelem = PUtil::loadRootElement(xmlfile, tl.filename, "level");
941   if (!rootelem) {
942     PUtil::outLog() << "Couldn't read level \"" << tl.filename << "\"" << std::endl;
943     return false;
944   }
945 
946   const char *val;
947 
948   val = rootelem->Attribute("name");
949   if (val) tl.name = val;
950 
951   val = rootelem->Attribute("description");
952 
953   if (val != nullptr)
954     tl.description = val;
955 
956   val = rootelem->Attribute("comment");
957   if (val) tl.comment = val;
958   val = rootelem->Attribute("author");
959   if (val) tl.author = val;
960 
961   val = rootelem->Attribute("screenshot");
962 
963   if (val != nullptr)
964     tl.tex_screenshot = getSSTexture().loadTexture(PUtil::assemblePath(val, tl.filename));
965 
966   val = rootelem->Attribute("minimap");
967 
968   if (val != nullptr)
969     tl.tex_minimap = getSSTexture().loadTexture(PUtil::assemblePath(val, tl.filename));
970 
971   for (XMLElement *walk = rootelem->FirstChildElement();
972     walk; walk = walk->NextSiblingElement()) {
973 
974     if (!strcmp(walk->Value(), "race")) {
975       val = walk->Attribute("targettime");
976       if (val)
977       {
978         tl.targettime = PUtil::formatTime(atof(val));
979         tl.targettimeshort = PUtil::formatTimeShort(atof(val));
980         tl.targettimefloat = atof(val);
981       }
982     }
983   }
984 
985   return true;
986 }
987 
loadLevelsAndEvents()988 bool MainApp::loadLevelsAndEvents()
989 {
990   PUtil::outLog() << "Loading levels and events" << std::endl;
991 
992   // Find levels
993 
994   std::list<std::string> results = PUtil::findFiles("/maps", ".level");
995 
996   for (std::list<std::string>::iterator i = results.begin();
997     i != results.end(); ++i) {
998 
999     TriggerLevel tl;
1000     tl.filename = *i;
1001 
1002     if (!loadLevel(tl)) continue;
1003 
1004     // Insert level in alphabetical order
1005     std::vector<TriggerLevel>::iterator j = levels.begin();
1006     while (j != levels.end() && j->name < tl.name) ++j;
1007     levels.insert(j, tl);
1008   }
1009 
1010   // Find events
1011 
1012   results = PUtil::findFiles("/events", ".event");
1013 
1014   for (std::list<std::string>::iterator i = results.begin();
1015     i != results.end(); ++i) {
1016 
1017     TriggerEvent te;
1018 
1019     te.filename = *i;
1020 
1021     XMLDocument xmlfile;
1022     XMLElement *rootelem = PUtil::loadRootElement(xmlfile, *i, "event");
1023     if (!rootelem) {
1024       PUtil::outLog() << "Couldn't read event \"" << *i << "\"" << std::endl;
1025       continue;
1026     }
1027 
1028     const char *val;
1029 
1030     val = rootelem->Attribute("name");
1031     if (val) te.name = val;
1032     val = rootelem->Attribute("comment");
1033     if (val) te.comment = val;
1034     val = rootelem->Attribute("author");
1035     if (val) te.author = val;
1036 
1037     val = rootelem->Attribute("locked");
1038 
1039     if (val != nullptr && strcmp(val, "yes") == 0)
1040         te.locked = true;
1041     else
1042         te.locked = false; // FIXME: redundant but clearer?
1043 
1044     float evtotaltime = 0.0f;
1045 
1046     for (XMLElement *walk = rootelem->FirstChildElement();
1047       walk; walk = walk->NextSiblingElement()) {
1048 
1049       if (strcmp(walk->Value(), "unlocks") == 0)
1050       {
1051           val = walk->Attribute("file");
1052 
1053           if (val == nullptr)
1054           {
1055               PUtil::outLog() << "Warning: Event has empty unlock" << std::endl;
1056               continue;
1057           }
1058 
1059           te.unlocks.insert(val);
1060       }
1061       else
1062       if (!strcmp(walk->Value(), "level")) {
1063 
1064         TriggerLevel tl;
1065 
1066         val = walk->Attribute("file");
1067         if (!val) {
1068           PUtil::outLog() << "Warning: Event level has no filename" << std::endl;
1069           continue;
1070         }
1071         tl.filename = PUtil::assemblePath(val, *i);
1072 
1073         if (loadLevel(tl))
1074         {
1075           te.levels.push_back(tl);
1076           evtotaltime += tl.targettimefloat;
1077         }
1078 
1079         PUtil::outLog() << tl.filename << std::endl;
1080       }
1081     }
1082 
1083     if (te.levels.size() <= 0) {
1084       PUtil::outLog() << "Warning: Event has no levels" << std::endl;
1085       continue;
1086     }
1087 
1088     te.totaltime = PUtil::formatTimeShort(evtotaltime);
1089 
1090     // Insert event in alphabetical order
1091     std::vector<TriggerEvent>::iterator j = events.begin();
1092     while (j != events.end() && j->name < te.name) ++j;
1093     events.insert(j, te);
1094   }
1095 
1096   return true;
1097 }
1098 
1099 ///
1100 /// @TODO: should also load all vehicles here, then if needed filter which
1101 ///  of them should be made available to the player -- it makes no sense
1102 ///  to reload vehicles for each race, over and over again
1103 ///
loadAll()1104 bool MainApp::loadAll()
1105 {
1106   if (!(tex_fontSourceCodeBold = getSSTexture().loadTexture("/textures/font-SourceCodeProBold.png")))
1107     return false;
1108 
1109   if (!(tex_fontSourceCodeOutlined = getSSTexture().loadTexture("/textures/font-SourceCodeProBoldOutlined.png")))
1110     return false;
1111 
1112   if (!(tex_fontSourceCodeShadowed = getSSTexture().loadTexture("/textures/font-SourceCodeProBoldShadowed.png")))
1113     return false;
1114 
1115   if (!(tex_end_screen = getSSTexture().loadTexture("/textures/splash/endgame.jpg"))) return false;
1116 
1117   if (!(tex_hud_life = getSSTexture().loadTexture("/textures/life_helmet.png"))) return false;
1118 
1119   if (!(tex_detail = getSSTexture().loadTexture("/textures/detail.jpg"))) return false;
1120   if (!(tex_dirt = getSSTexture().loadTexture("/textures/dust.png"))) return false;
1121   if (!(tex_shadow = getSSTexture().loadTexture("/textures/shadow.png", true, true))) return false;
1122 
1123   if (!(tex_hud_revneedle = getSSTexture().loadTexture("/textures/rev_needle.png"))) return false;
1124 
1125   if (!(tex_hud_revs = getSSTexture().loadTexture("/textures/dial_rev.png"))) return false;
1126 
1127   if (!(tex_hud_offroad = getSSTexture().loadTexture("/textures/offroad.png"))) return false;
1128 
1129   if (!(tex_race_no_screenshot = getSSTexture().loadTexture("/textures/no_screenshot.png"))) return false;
1130 
1131   if (!(tex_race_no_minimap = getSSTexture().loadTexture("/textures/no_minimap.png"))) return false;
1132 
1133   if (!(tex_button_next = getSSTexture().loadTexture("/textures/button_next.png"))) return false;
1134   if (!(tex_button_prev = getSSTexture().loadTexture("/textures/button_prev.png"))) return false;
1135 
1136   if (!(tex_waterdefault = getSSTexture().loadTexture("/textures/water/default.png"))) return false;
1137 
1138   if (!(tex_snowflake = getSSTexture().loadTexture("/textures/snowflake.png"))) return false;
1139 
1140     if (cfg_enable_codriversigns && !cfg_codriversigns.empty())
1141     {
1142         const std::string origdir(std::string("/textures/CodriverSigns/") + cfg_codriversigns);
1143 
1144         char **rc = PHYSFS_enumerateFiles(origdir.c_str());
1145 
1146         for (char **fname = rc; *fname != nullptr; ++fname)
1147         {
1148             PTexture *tex_cdsign = getSSTexture().loadTexture(origdir + '/' + *fname);
1149 
1150             if (tex_cdsign != nullptr) // failed loads are ignored
1151             {
1152                 // remove the extension from the filename
1153                 std::smatch mr; // Match Results
1154                 std::regex pat(R"(^(\w+)(\..+)$)"); // Pattern
1155                 std::string fn(*fname); // Filename
1156 
1157                 if (!std::regex_search(fn, mr, pat))
1158                     continue;
1159 
1160                 std::string basefname = mr[1];
1161 
1162                 // make the base filename lowercase
1163                 for (char &c: basefname)
1164                     c = std::tolower(static_cast<unsigned char> (c));
1165 
1166                 tex_codriversigns[basefname] = tex_cdsign;
1167                 //PUtil::outLog() << "Loaded codriver sign for: \"" << basefname << '"' << std::endl;
1168             }
1169         }
1170 
1171         PHYSFS_freeList(rc);
1172     }
1173 
1174   if (cfg_enable_sound) {
1175     if (!(aud_engine = getSSAudio().loadSample("/sounds/engine.wav", false))) return false;
1176     if (!(aud_wind = getSSAudio().loadSample("/sounds/wind.wav", false))) return false;
1177     if (!(aud_gearchange = getSSAudio().loadSample("/sounds/gear.wav", false))) return false;
1178     if (!(aud_gravel = getSSAudio().loadSample("/sounds/gravel.wav", false))) return false;
1179     if (!(aud_crash1 = getSSAudio().loadSample("/sounds/bang.wav", false))) return false;
1180 
1181     if (!cfg_codrivername.empty() && cfg_codrivername != "mime")
1182     {
1183         const std::string origdir(std::string("/sounds/codriver/") + cfg_codrivername);
1184 
1185         char **rc = PHYSFS_enumerateFiles(origdir.c_str());
1186 
1187         for (char **fname = rc; *fname != nullptr; ++fname)
1188         {
1189             PAudioSample *aud_cdword = getSSAudio().loadSample(origdir + '/' + *fname, false);
1190 
1191             if (aud_cdword != nullptr) // failed loads are ignored
1192             {
1193                 // remove the extension from the filename
1194                 std::smatch mr; // Match Results
1195                 std::regex pat(R"(^(\w+)(\..+)$)"); // Pattern
1196                 std::string fn(*fname); // Filename
1197 
1198                 if (!std::regex_search(fn, mr, pat))
1199                     continue;
1200 
1201                 std::string basefname = mr[1];
1202 
1203                 // make the base filename lowercase
1204                 for (char &c: basefname)
1205                     c = std::tolower(static_cast<unsigned char> (c));
1206 
1207                 aud_codriverwords[basefname] = aud_cdword;
1208                 //PUtil::outLog() << "Loaded codriver word for: \"" << basefname << '"' << std::endl;
1209             }
1210         }
1211 
1212         PHYSFS_freeList(rc);
1213     }
1214   }
1215 
1216   if (!gui.loadColors("/menu.colors"))
1217     PUtil::outLog() << "Couldn't load (all) menu colors, continuing with defaults" << std::endl;
1218 
1219   if (!loadLevelsAndEvents()) {
1220     PUtil::outLog() << "Couldn't load levels/events" << std::endl;
1221     return false;
1222   }
1223 
1224   //quatf tempo;
1225   //tempo.fromThreeAxisAngle(vec3f(-0.38, -0.38, 0.0));
1226   //vehic->getBody().setOrientation(tempo);
1227 
1228   campos = campos_prev = vec3f(-15.0,0.0,30.0);
1229   //camori.fromThreeAxisAngle(vec3f(-1.0,0.0,1.5));
1230   camori = quatf::identity();
1231 
1232   camvel = vec3f::zero();
1233 
1234   cloudscroll = 0.0f;
1235 
1236   cprotate = 0.0f;
1237 
1238   cameraview = 0;
1239   camera_user_angle = 0.0f;
1240 
1241   showmap = true;
1242 
1243   pauserace = false;
1244 
1245   showui = true;
1246 
1247   showcheckpoint = true;
1248 
1249   crashnoise_timeout = 0.0f;
1250 
1251     if (cfg_dirteffect)
1252     {
1253         psys_dirt = new DirtParticleSystem();
1254         psys_dirt->setColorStart(0.5f, 0.4f, 0.2f, 1.0f);
1255         psys_dirt->setColorEnd(0.5f, 0.4f, 0.2f, 0.0f);
1256         psys_dirt->setSize(0.1f, 0.5f);
1257         psys_dirt->setDecay(6.0f);
1258         psys_dirt->setTexture(tex_dirt);
1259         psys_dirt->setBlend(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1260     }
1261     else
1262         psys_dirt = nullptr;
1263 
1264   //
1265 
1266   choose_type = 0;
1267 
1268   choose_spin = 0.0f;
1269 
1270   return true;
1271 }
1272 
unload()1273 void MainApp::unload()
1274 {
1275   endGame(Gamefinish::not_finished);
1276 
1277   delete psys_dirt;
1278 }
1279 
1280 ///
1281 /// @brief Prepare to start a new game (a race)
1282 /// @param filename = filename of the level (track) to load
1283 ///
startGame(const std::string & filename)1284 bool MainApp::startGame(const std::string &filename)
1285 {
1286   PUtil::outLog() << "Starting level \"" << filename << "\"" << std::endl;
1287 
1288   // mouse is grabbed during the race
1289   grabMouse(true);
1290 
1291   // the game
1292   game = new TriggerGame(this);
1293 
1294   // load vehicles
1295   if (!game->loadVehicles())
1296   {
1297       PUtil::outLog() << "Error: failed to load vehicles" << std::endl;
1298       return false;
1299   }
1300 
1301   // load the vehicle
1302   if (!game->loadLevel(filename)) {
1303     PUtil::outLog() << "Error: failed to load level" << std::endl;
1304     return false;
1305   }
1306 
1307   // useful datas
1308   race_data.playername  = cfg_playername; // TODO: move to a better place
1309   race_data.mapname     = filename;
1310   choose_type = 0;
1311 
1312   // if there is more than a vehicle to choose from, choose it
1313   if (game->vehiclechoices.size() > 1) {
1314     appstate = AS_CHOOSE_VEHICLE;
1315   } else {
1316     game->chooseVehicle(game->vehiclechoices[choose_type]);
1317 
1318     if (lss.state == AM_TOP_LVL_PREP)
1319     {
1320         const float bct = best_times.getBestClassTime(
1321             filename,
1322             game->vehicle.front()->type->proper_class);
1323 
1324         if (bct >= 0.0f)
1325             game->targettime = bct;
1326     }
1327 
1328     startGame2();
1329     appstate = AS_IN_GAME;
1330   }
1331 
1332   // load the sky texture
1333   tex_sky[0] = nullptr;
1334 
1335   if (game->weather.cloud.texname.length() > 0)
1336     tex_sky[0] = getSSTexture().loadTexture(game->weather.cloud.texname);
1337 
1338   // if there is none load default
1339   if (tex_sky[0] == nullptr) {
1340     tex_sky[0] = getSSTexture().loadTexture("/textures/sky/blue.jpg");
1341 
1342     if (tex_sky[0] == nullptr) tex_sky[0] = tex_detail; // last fallback...
1343   }
1344 
1345   // load water texture
1346   tex_water = nullptr;
1347 
1348   if (!game->water.texname.empty())
1349     tex_water = getSSTexture().loadTexture(game->water.texname);
1350 
1351   // if there is none load water default
1352   if (tex_water == nullptr)
1353     tex_water = tex_waterdefault;
1354 
1355   return true;
1356 }
1357 
1358 ///
1359 /// @brief Turns game sound effects on or off.
1360 /// @note Codriver voice unaffected.
1361 /// @param to       State to switch to (true is on, false is off).
1362 ///
toggleSounds(bool to)1363 void MainApp::toggleSounds(bool to)
1364 {
1365     if (cfg_enable_sound)
1366     {
1367         if (audinst_engine != nullptr)
1368         {
1369             if (!to)
1370             {
1371                 audinst_engine->setGain(0.0f);
1372                 audinst_engine->play();
1373             }
1374         }
1375 
1376         if (audinst_wind != nullptr)
1377         {
1378             if (!to)
1379             {
1380                 audinst_wind->setGain(0.0f);
1381                 audinst_wind->play();
1382             }
1383         }
1384 
1385         if (audinst_gravel != nullptr)
1386         {
1387             if (!to)
1388             {
1389                 audinst_gravel->setGain(0.0f);
1390                 audinst_gravel->play();
1391             }
1392         }
1393     }
1394 }
1395 
1396 ///
1397 /// @brief Initialize game sounds instances
1398 ///
startGame2()1399 void MainApp::startGame2()
1400 {
1401   if (cfg_enable_sound) {
1402     audinst_engine = new PAudioInstance(aud_engine, true);
1403     audinst_engine->setGain(0.0);
1404     audinst_engine->play();
1405 
1406     audinst_wind = new PAudioInstance(aud_wind, true);
1407     audinst_wind->setGain(0.0);
1408     audinst_wind->play();
1409 
1410     audinst_gravel = new PAudioInstance(aud_gravel, true);
1411     audinst_gravel->setGain(0.0);
1412     audinst_gravel->play();
1413   }
1414 }
1415 
endGame(Gamefinish state)1416 void MainApp::endGame(Gamefinish state)
1417 {
1418   float coursetime = (state == Gamefinish::not_finished) ? 0.0f :
1419     game->coursetime + game->uservehicle->offroadtime_total * game->offroadtime_penalty_multiplier;
1420 
1421     if (state != Gamefinish::not_finished && lss.state != AM_TOP_EVT_PREP)
1422     {
1423         race_data.carname   = game->vehicle.front()->type->proper_name;
1424         race_data.carclass  = game->vehicle.front()->type->proper_class;
1425         race_data.totaltime = game->coursetime + game->uservehicle->offroadtime_total * game->offroadtime_penalty_multiplier;
1426         race_data.maxspeed  = 0.0f; // TODO: measure this too
1427         //PUtil::outLog() << race_data;
1428         current_times = best_times.insertAndGetCurrentTimesHL(race_data);
1429         best_times.skipSavePlayer();
1430 
1431         // show the best times
1432         if (lss.state == AM_TOP_LVL_PREP)
1433             lss.state = AM_TOP_LVL_TIMES;
1434         else
1435         if (lss.state == AM_TOP_PRAC_SEL_PREP)
1436             lss.state = AM_TOP_PRAC_TIMES;
1437     }
1438 
1439   if (audinst_engine) {
1440     delete audinst_engine;
1441     audinst_engine = nullptr;
1442   }
1443 
1444   if (audinst_wind) {
1445     delete audinst_wind;
1446     audinst_wind = nullptr;
1447   }
1448 
1449   if (audinst_gravel) {
1450     delete audinst_gravel;
1451     audinst_gravel = nullptr;
1452   }
1453 
1454   for (unsigned int i=0; i<audinst.size(); i++) {
1455     delete audinst[i];
1456   }
1457   audinst.clear();
1458 
1459   if (game) {
1460     delete game;
1461     game = nullptr;
1462   }
1463 
1464   finishRace(state, coursetime);
1465 }
1466 
1467 ///
1468 /// @brief Calculate screen ratios from the current screen width and height.
1469 /// @details Sets `hratio` and `vratio` member data in accordance to the values of
1470 ///  `getWidth()` (screen width) and `getHeight()` (screen height).
1471 ///  This data is important for proper scaling on widescreen monitors.
1472 ///
calcScreenRatios()1473 void MainApp::calcScreenRatios()
1474 {
1475     const int cx = getWidth();
1476     const int cy = getHeight();
1477 
1478     if (cx > cy)
1479     {
1480         hratio = static_cast<double> (cx) / cy;
1481         vratio = 1.0;
1482     }
1483     else
1484     if (cx < cy)
1485     {
1486         hratio = 1.0;
1487         vratio = static_cast<double> (cy) / cx;
1488     }
1489     else
1490     {
1491         hratio = 1.0;
1492         vratio = 1.0;
1493     }
1494 }
1495 
tick(float delta)1496 void MainApp::tick(float delta)
1497 {
1498     getSSAudio().tick();
1499 
1500   switch (appstate) {
1501   case AS_LOAD_1:
1502     splashtimeout -= delta;
1503     if (--loadscreencount <= 0)
1504       appstate = AS_LOAD_2;
1505     break;
1506   case AS_LOAD_2:
1507     splashtimeout -= delta;
1508     if (!loadAll()) {
1509       requestExit();
1510       return;
1511     }
1512     appstate = AS_LOAD_3;
1513     break;
1514   case AS_LOAD_3:
1515     splashtimeout -= delta;
1516     if (splashtimeout <= 0.0f)
1517       levelScreenAction(AA_INIT, 0);
1518     break;
1519 
1520   case AS_LEVEL_SCREEN:
1521     tickStateLevel(delta);
1522     break;
1523 
1524   case AS_CHOOSE_VEHICLE:
1525     tickStateChoose(delta);
1526     break;
1527 
1528   case AS_IN_GAME:
1529       if (!pauserace)
1530         tickStateGame(delta);
1531     break;
1532 
1533   case AS_END_SCREEN:
1534     splashtimeout += delta * 0.04f;
1535     if (splashtimeout >= 1.0f)
1536       requestExit();
1537     break;
1538   }
1539 }
1540 
tickStateChoose(float delta)1541 void MainApp::tickStateChoose(float delta)
1542 {
1543   choose_spin += delta * 2.0f;
1544 }
1545 
tickStateGame(float delta)1546 void MainApp::tickStateGame(float delta)
1547 {
1548   PVehicle *vehic = game->vehicle[0];
1549 
1550   if (game->isFinished())
1551   {
1552     endGame(game->getFinishState());
1553     return;
1554   }
1555 
1556   cloudscroll = fmodf(cloudscroll + delta * game->weather.cloud.scrollrate, 1.0f);
1557 
1558   cprotate = fmodf(cprotate + delta * 1.0f, 1000.0f);
1559 
1560   // Do input/control processing
1561 
1562   for (int a = 0; a < ActionCount; a++) {
1563 
1564     switch(ctrl.map[a].type) {
1565     case UserControl::TypeUnassigned:
1566       break;
1567 
1568     case UserControl::TypeKey:
1569       ctrl.map[a].value = keyDown(SDL_GetScancodeFromKey(ctrl.map[a].key.sym)) ? 1.0f : 0.0f;
1570       break;
1571 
1572     case UserControl::TypeJoyButton:
1573       ctrl.map[a].value = getJoyButton(0, ctrl.map[a].joybutton.button) ? 1.0f : 0.0f;
1574       break;
1575 
1576     case UserControl::TypeJoyAxis:
1577       ctrl.map[a].value = ctrl.map[a].joyaxis.sign *
1578         getJoyAxis(0, ctrl.map[a].joyaxis.axis);
1579 
1580       RANGEADJUST(ctrl.map[a].value, ctrl.map[a].joyaxis.deadzone, ctrl.map[a].joyaxis.maxrange, 0.0f, 1.0f);
1581 
1582       CLAMP_LOWER(ctrl.map[a].value, 0.0f);
1583       break;
1584     }
1585   }
1586 
1587   // Bit of a hack for turning, because you simply can't handle analogue
1588   // and digital steering the same way, afaics
1589 
1590   if (ctrl.map[ActionLeft].type == UserControl::TypeJoyAxis ||
1591     ctrl.map[ActionRight].type == UserControl::TypeJoyAxis) {
1592 
1593     // Analogue mode
1594 
1595     vehic->ctrl.turn.z = 0.0f;
1596     vehic->ctrl.turn.z -= ctrl.map[ActionLeft].value;
1597     vehic->ctrl.turn.z += ctrl.map[ActionRight].value;
1598 
1599   } else {
1600 
1601     // Digital mode
1602 
1603     static float turnaccel = 0.0f;
1604 
1605     if (ctrl.map[ActionLeft].value > 0.0f) {
1606       if (turnaccel > -0.0f) turnaccel = -0.0f;
1607       turnaccel -= 8.0f * delta;
1608       vehic->ctrl.turn.z += turnaccel * delta;
1609     } else if (ctrl.map[ActionRight].value > 0.0f) {
1610       if (turnaccel < 0.0f) turnaccel = 0.0f;
1611       turnaccel += 8.0f * delta;
1612       vehic->ctrl.turn.z += turnaccel * delta;
1613     } else {
1614       PULLTOWARD(turnaccel, 0.0f, delta * 5.0f);
1615       PULLTOWARD(vehic->ctrl.turn.z, 0.0f, delta * 5.0f);
1616     }
1617   }
1618 
1619   // Computer aided steering
1620   if (vehic->forwardspeed > 1.0f)
1621     vehic->ctrl.turn.z -= vehic->body->getAngularVel().z * cfg_drivingassist / (1.0f + vehic->forwardspeed);
1622 
1623 
1624   float throttletarget = 0.0f;
1625   float braketarget = 0.0f;
1626 
1627   if (ctrl.map[ActionForward].value > 0.0f) {
1628     if (vehic->wheel_angvel > -10.0f)
1629       throttletarget = ctrl.map[ActionForward].value;
1630     else
1631       braketarget = ctrl.map[ActionForward].value;
1632   }
1633   if (ctrl.map[ActionBack].value > 0.0f) {
1634     if (vehic->wheel_angvel < 10.0f)
1635       throttletarget = -ctrl.map[ActionBack].value;
1636     else
1637       braketarget = ctrl.map[ActionBack].value;
1638   }
1639 
1640   PULLTOWARD(vehic->ctrl.throttle, throttletarget, delta * 15.0f);
1641   PULLTOWARD(vehic->ctrl.brake1, braketarget, delta * 25.0f);
1642 
1643   vehic->ctrl.brake2 = ctrl.map[ActionHandbrake].value;
1644 
1645 
1646   //PULLTOWARD(vehic->ctrl.aim.x, 0.0, delta * 2.0);
1647   //PULLTOWARD(vehic->ctrl.aim.y, 0.0, delta * 2.0);
1648 
1649   game->tick(delta);
1650 
1651     if (cfg_dirteffect)
1652     {
1653 
1654 #define BRIGHTEN_ADD        0.20f
1655 
1656   for (unsigned int i=0; i<game->vehicle.size(); i++) {
1657     for (unsigned int j=0; j<game->vehicle[i]->part.size(); j++) {
1658       //const vec3f bodydirtpos = game->vehicle[i]->part[j].ref_world.getPosition();
1659       const vec3f bodydirtpos = game->vehicle[i]->body->getPosition();
1660       const dirtinfo bdi = PUtil::getDirtInfo(game->terrain->getRoadSurface(bodydirtpos));
1661 
1662     if (bdi.startsize >= 0.30f && game->vehicle[i]->forwardspeed > 23.0f)
1663     {
1664         if (game->vehicle[i]->canHaveDustTrail())
1665         {
1666             const float sizemult = game->vehicle[i]->forwardspeed * 0.035f;
1667             const vec3f bodydirtvec = {0, 0, 1}; // game->vehicle[i]->body->getLinearVelAtPoint(bodydirtpos);
1668             vec3f bodydirtcolor = game->terrain->getCmapColor(bodydirtpos);
1669 
1670             bodydirtcolor.x += BRIGHTEN_ADD;
1671             bodydirtcolor.y += BRIGHTEN_ADD;
1672             bodydirtcolor.z += BRIGHTEN_ADD;
1673 
1674             CLAMP(bodydirtcolor.x, 0.0f, 1.0f);
1675             CLAMP(bodydirtcolor.y, 0.0f, 1.0f);
1676             CLAMP(bodydirtcolor.z, 0.0f, 1.0f);
1677             psys_dirt->setColorStart(bodydirtcolor.x, bodydirtcolor.y, bodydirtcolor.z, 1.0f);
1678             psys_dirt->setColorEnd(bodydirtcolor.x, bodydirtcolor.y, bodydirtcolor.z, 0.0f);
1679             psys_dirt->setSize(bdi.startsize * sizemult, bdi.endsize * sizemult);
1680             psys_dirt->setDecay(bdi.decay);
1681             psys_dirt->addParticle(bodydirtpos, bodydirtvec);
1682         }
1683     }
1684     else
1685       for (unsigned int k=0; k<game->vehicle[i]->part[j].wheel.size(); k++) {
1686         if (rand01 * 20.0f < game->vehicle[i]->part[j].wheel[k].dirtthrow)
1687         {
1688             const vec3f dirtpos = game->vehicle[i]->part[j].wheel[k].dirtthrowpos;
1689             const vec3f dirtvec = game->vehicle[i]->part[j].wheel[k].dirtthrowvec;
1690             const dirtinfo di = PUtil::getDirtInfo(game->terrain->getRoadSurface(dirtpos));
1691             vec3f dirtcolor = game->terrain->getCmapColor(dirtpos);
1692 
1693             dirtcolor.x += BRIGHTEN_ADD;
1694             dirtcolor.y += BRIGHTEN_ADD;
1695             dirtcolor.z += BRIGHTEN_ADD;
1696             CLAMP(dirtcolor.x, 0.0f, 1.0f);
1697             CLAMP(dirtcolor.y, 0.0f, 1.0f);
1698             CLAMP(dirtcolor.z, 0.0f, 1.0f);
1699             psys_dirt->setColorStart(dirtcolor.x, dirtcolor.y, dirtcolor.z, 1.0f);
1700             psys_dirt->setColorEnd(dirtcolor.x, dirtcolor.y, dirtcolor.z, 0.0f);
1701             psys_dirt->setSize(di.startsize, di.endsize);
1702             psys_dirt->setDecay(di.decay);
1703             psys_dirt->addParticle(dirtpos, dirtvec /*+ vec3f::rand() * 10.0f*/);
1704         }
1705       }
1706     }
1707   }
1708 
1709   #undef BRIGHTEN_ADD
1710 
1711     }
1712 
1713   float angtarg = 0.0f;
1714   angtarg -= ctrl.map[ActionCamLeft].value;
1715   angtarg += ctrl.map[ActionCamRight].value;
1716   angtarg *= PI*0.75f;
1717 
1718   PULLTOWARD(camera_user_angle, angtarg, delta * 4.0f);
1719 
1720   quatf tempo;
1721   //tempo.fromThreeAxisAngle(vec3f(-1.3,0.0,0.0));
1722 
1723   // allow temporary camera view changes for this frame
1724   int cameraview_mod = cameraview;
1725 
1726   if (game->gamestate == Gamestate::finished) {
1727     cameraview_mod = 0;
1728     static float spinner = 0.0f;
1729     spinner += 1.4f * delta;
1730     tempo.fromThreeAxisAngle(vec3f(-PI*0.5f,0.0f,spinner));
1731   } else {
1732     tempo.fromThreeAxisAngle(vec3f(-PI*0.5f,0.0f,0.0f));
1733   }
1734 
1735   renderowncar = (cameraview_mod != 1);
1736 
1737   campos_prev = campos;
1738 
1739   //PReferenceFrame *rf = &vehic->part[2].ref_world;
1740   PReferenceFrame *rf = &vehic->getBody();
1741 
1742   vec3f forw = makevec3f(rf->getOrientationMatrix().row[0]);
1743   vec3f nose = makevec3f(rf->getOrientationMatrix().row[1]);
1744   float forwangle = atan2(forw.y, forw.x);
1745   float noseangle = atan2(nose.z, nose.y);
1746 
1747   mat44f cammat;
1748 
1749   switch (cameraview_mod) {
1750       // Chase
1751     default:
1752   case 0: {
1753     quatf temp2;
1754     temp2.fromZAngle(forwangle + camera_user_angle);
1755 
1756     quatf target = tempo * temp2;
1757 
1758     if (target.dot(camori) < 0.0f) target = target * -1.0f;
1759 
1760     PULLTOWARD(camori, target, delta * 3.0f);
1761 
1762     camori.normalize();
1763 
1764     cammat = camori.getMatrix();
1765     cammat = cammat.transpose();
1766     //campos = rf->getPosition() + makevec3f(cammat.row[2]) * 100.0;
1767     campos = rf->getPosition() +
1768       makevec3f(cammat.row[1]) * 1.6f +
1769       makevec3f(cammat.row[2]) * 5.0f;
1770     } break;
1771 
1772     // Bumper
1773   case 1: {
1774     quatf temp2;
1775     temp2.fromZAngle(camera_user_angle);
1776 
1777     quatf target = tempo * temp2 * rf->ori;
1778 
1779     if (target.dot(camori) < 0.0f) target = target * -1.0f;
1780 
1781     PULLTOWARD(camori, target, delta * 25.0f);
1782 
1783     camori.normalize();
1784 
1785     cammat = camori.getMatrix();
1786     cammat = cammat.transpose();
1787     const mat44f &rfmat = rf->getInverseOrientationMatrix();
1788     //campos = rf->getPosition() + makevec3f(cammat.row[2]) * 100.0;
1789     campos = rf->getPosition() +
1790       makevec3f(rfmat.row[1]) * 1.7f +
1791       makevec3f(rfmat.row[2]) * 0.4f;
1792     } break;
1793 
1794     // Side (right wheel)
1795   case 2: {
1796     quatf temp2;
1797     temp2.fromZAngle(camera_user_angle);
1798 
1799     quatf target = tempo * temp2 * rf->ori;
1800 
1801     if (target.dot(camori) < 0.0f) target = target * -1.0f;
1802 
1803     //PULLTOWARD(camori, target, delta * 25.0f);
1804     camori = target;
1805 
1806     camori.normalize();
1807 
1808     cammat = camori.getMatrix();
1809     cammat = cammat.transpose();
1810     const mat44f &rfmat = rf->getInverseOrientationMatrix();
1811     //campos = rf->getPosition() + makevec3f(cammat.row[2]) * 100.0;
1812     campos = rf->getPosition() +
1813       makevec3f(rfmat.row[0]) * 1.1f +
1814       makevec3f(rfmat.row[1]) * 0.3f +
1815       makevec3f(rfmat.row[2]) * 0.1f;
1816     } break;
1817 
1818     // Hood
1819   case 3: {
1820     quatf temp2;
1821     temp2.fromZAngle(camera_user_angle);
1822 
1823     quatf target = tempo * temp2 * rf->ori;
1824 
1825     if (target.dot(camori) < 0.0f) target = target * -1.0f;
1826 
1827     //PULLTOWARD(camori, target, delta * 25.0f);
1828     camori = target;
1829 
1830     camori.normalize();
1831 
1832     cammat = camori.getMatrix();
1833     cammat = cammat.transpose();
1834     const mat44f &rfmat = rf->getInverseOrientationMatrix();
1835     //campos = rf->getPosition() + makevec3f(cammat.row[2]) * 100.0;
1836     campos = rf->getPosition() +
1837       makevec3f(rfmat.row[1]) * 0.50f +
1838       makevec3f(rfmat.row[2]) * 0.85f;
1839     } break;
1840 
1841     // Periscope view
1842   case 4:{
1843     quatf temp2;
1844     temp2.fromZAngle(camera_user_angle);
1845 
1846     quatf target = tempo * temp2 * rf->ori;
1847 
1848     if (target.dot(camori) < 0.0f) target = target * -1.0f;
1849 
1850     PULLTOWARD(camori, target, delta * 25.0f);
1851 
1852     camori.normalize();
1853 
1854     cammat = camori.getMatrix();
1855     cammat = cammat.transpose();
1856     const mat44f &rfmat = rf->getInverseOrientationMatrix();
1857     //campos = rf->getPosition() + makevec3f(cammat.row[2]) * 100.0;
1858     campos = rf->getPosition() +
1859       makevec3f(rfmat.row[1]) * 1.7f +
1860       makevec3f(rfmat.row[2]) * 5.0f;
1861     } break;
1862 
1863     // Piggyback (fixed chase)
1864     //
1865     // TODO: broken because of "world turns upside down" bug
1866     //
1867   case -1:{
1868     quatf temp2, temp3, temp4;
1869     temp2.fromZAngle(forwangle + camera_user_angle);
1870     temp3.fromXAngle(noseangle);
1871 
1872     //if (tempo.dot(temp2) < 0.0f) tempo = tempo * -1.0f;
1873 
1874     temp4 = temp3 * temp2;
1875 
1876     quatf target = tempo * temp4;
1877 
1878     if (target.dot(camori) < 0.0f) target = target * -1.0f;
1879     //if (camori.dot(target) < 0.0f) camori = camori * -1.0f;
1880 
1881     PULLTOWARD(camori, target, delta * 3.0f);
1882 
1883     camori.normalize();
1884 
1885     cammat = camori.getMatrix();
1886     cammat = cammat.transpose();
1887     //campos = rf->getPosition() + makevec3f(cammat.row[2]) * 100.0;
1888     campos = rf->getPosition() +
1889       makevec3f(cammat.row[1]) * 1.6f +
1890       makevec3f(cammat.row[2]) * 6.0f;
1891     }
1892     break;
1893   }
1894 
1895   forw = makevec3f(cammat.row[0]);
1896   camera_angle = atan2(forw.y, forw.x);
1897 
1898   vec2f diff = makevec2f(game->checkpt[vehic->nextcp].pt) - makevec2f(vehic->body->getPosition());
1899   nextcpangle = -atan2(diff.y, diff.x) - forwangle + PI*0.5f;
1900 
1901   if (cfg_enable_sound) {
1902     audinst_engine->setGain(cfg_volume_engine);
1903     audinst_engine->setPitch(vehic->getEngineRPM() / 9000.0f);
1904 
1905     float windlevel = fabsf(vehic->forwardspeed) * 0.6f;
1906 
1907     audinst_wind->setGain(windlevel * 0.03f * cfg_volume_sfx);
1908     audinst_wind->setPitch(windlevel * 0.02f + 0.9f);
1909 
1910     audinst_gravel->setGain(vehic->getSkidLevel() * 0.1f * cfg_volume_sfx);
1911     audinst_gravel->setPitch(1.0f);//vehic->getEngineRPM() / 7500.0f);
1912 
1913     if (vehic->getFlagGearChange()) {
1914       audinst.push_back(new PAudioInstance(aud_gearchange));
1915       audinst.back()->setPitch(1.0f + randm11*0.02f);
1916       audinst.back()->setGain(0.3f * cfg_volume_sfx);
1917       audinst.back()->play();
1918     }
1919 
1920     if (crashnoise_timeout <= 0.0f) {
1921       float crashlevel = vehic->getCrashNoiseLevel();
1922       if (crashlevel > 0.0f) {
1923         audinst.push_back(new PAudioInstance(aud_crash1));
1924         audinst.back()->setPitch(1.0f + randm11*0.02f);
1925         audinst.back()->setGain(logf(1.0f + crashlevel) * cfg_volume_sfx);
1926         audinst.back()->play();
1927       }
1928       crashnoise_timeout = rand01 * 0.1f + 0.01f;
1929     } else {
1930       crashnoise_timeout -= delta;
1931     }
1932 
1933     for (unsigned int i=0; i<audinst.size(); i++) {
1934       if (!audinst[i]->isPlaying()) {
1935         delete audinst[i];
1936         audinst.erase(audinst.begin() + i);
1937         i--;
1938         continue;
1939       }
1940     }
1941   }
1942 
1943   if (psys_dirt != nullptr)
1944     psys_dirt->tick(delta);
1945 
1946 #define RAIN_START_LIFE         0.6f
1947 #define RAIN_POS_RANDOM         15.0f
1948 #define RAIN_VEL_RANDOM         2.0f
1949 
1950   vec3f camvel = (campos - campos_prev) * (1.0f / delta);
1951 
1952   {
1953   const vec3f def_drop_vect(2.5f,0.0f,17.0f);
1954 
1955   // randomised number of drops calculation
1956   float numdrops = game->weather.precip.rain * delta;
1957   int inumdrops = (int)numdrops;
1958   if (rand01 < numdrops - inumdrops) inumdrops++;
1959   for (int i=0; i<inumdrops; i++) {
1960     rain.push_back(RainDrop());
1961     rain.back().drop_pt = vec3f(campos.x,campos.y,0);
1962     rain.back().drop_pt += camvel * RAIN_START_LIFE;
1963     rain.back().drop_pt += vec3f::rand() * RAIN_POS_RANDOM;
1964     rain.back().drop_pt.z = game->terrain->getHeight(rain.back().drop_pt.x, rain.back().drop_pt.y);
1965 
1966     if (game->water.enabled && rain.back().drop_pt.z < game->water.height)
1967         rain.back().drop_pt.z = game->water.height;
1968 
1969     rain.back().drop_vect = def_drop_vect + vec3f::rand() * RAIN_VEL_RANDOM;
1970     rain.back().life = RAIN_START_LIFE;
1971   }
1972 
1973   // update life and delete dead raindrops
1974   unsigned int j=0;
1975   for (unsigned int i = 0; i < rain.size(); i++) {
1976     if (rain[i].life <= 0.0f) continue;
1977     rain[j] = rain[i];
1978     rain[j].prevlife = rain[j].life;
1979     rain[j].life -= delta;
1980     if (rain[j].life < 0.0f)
1981       rain[j].life = 0.0f; // will be deleted next time round
1982     j++;
1983   }
1984   rain.resize(j);
1985   }
1986 
1987 #define SNOWFALL_START_LIFE     6.5f
1988 #define SNOWFALL_POS_RANDOM     110.0f
1989 #define SNOWFALL_VEL_RANDOM     0.8f
1990 
1991   // snowfall logic; this is rain logic CPM'd (Copied, Pasted and Modified) -- A.B.
1992   {
1993     const vec3f def_drop_vect(1.3f, 0.0f, 6.0f);
1994 
1995   // randomised number of flakes calculation
1996   float numflakes = game->weather.precip.snowfall * delta;
1997   int inumflakes = (int)numflakes;
1998   if (rand01 < numflakes - inumflakes) inumflakes++;
1999   for (int i=0; i<inumflakes; i++) {
2000     snowfall.push_back(SnowFlake());
2001     snowfall.back().drop_pt = vec3f(campos.x,campos.y,0);
2002     snowfall.back().drop_pt += camvel * SNOWFALL_START_LIFE / 2;
2003     snowfall.back().drop_pt += vec3f::rand() * SNOWFALL_POS_RANDOM;
2004     snowfall.back().drop_pt.z = game->terrain->getHeight(snowfall.back().drop_pt.x, snowfall.back().drop_pt.y);
2005 
2006     if (game->water.enabled && snowfall.back().drop_pt.z < game->water.height)
2007         snowfall.back().drop_pt.z = game->water.height;
2008 
2009     snowfall.back().drop_vect = def_drop_vect + vec3f::rand() * SNOWFALL_VEL_RANDOM;
2010     snowfall.back().life = SNOWFALL_START_LIFE * rand01;
2011   }
2012 
2013   // update life and delete dead snowflakes
2014   unsigned int j=0;
2015   for (unsigned int i = 0; i < snowfall.size(); i++) {
2016     if (snowfall[i].life <= 0.0f) continue;
2017     snowfall[j] = snowfall[i];
2018     snowfall[j].prevlife = snowfall[j].life;
2019     snowfall[j].life -= delta;
2020     if (snowfall[j].life < 0.0f)
2021       snowfall[j].life = 0.0f; // will be deleted next time round
2022     j++;
2023   }
2024   snowfall.resize(j);
2025   }
2026 
2027   // update stuff for SSRender
2028 
2029   cam_pos = campos;
2030   cam_orimat = cammat;
2031   cam_linvel = camvel;
2032 }
2033 
2034 // TODO: mark instant events with flags, deal with them in tick()
2035 // this will get rid of the silly doubling up between keyEvent and joyButtonEvent
2036 // and possibly mouseButtonEvent in future
2037 
keyEvent(const SDL_KeyboardEvent & ke)2038 void MainApp::keyEvent(const SDL_KeyboardEvent &ke)
2039 {
2040   if (ke.type == SDL_KEYDOWN) {
2041 
2042     if (ke.keysym.sym == SDLK_F12) {
2043       saveScreenshot();
2044       return;
2045     }
2046 
2047     switch (appstate) {
2048     case AS_LOAD_1:
2049     case AS_LOAD_2:
2050       // no hitting escape allowed... end screen not loaded!
2051       return;
2052     case AS_LOAD_3:
2053       levelScreenAction(AA_INIT, 0);
2054       return;
2055     case AS_LEVEL_SCREEN:
2056       handleLevelScreenKey(ke);
2057       return;
2058     case AS_CHOOSE_VEHICLE:
2059 
2060       if (ctrl.map[ActionLeft].type == UserControl::TypeKey &&
2061         ctrl.map[ActionLeft].key.sym == ke.keysym.sym) {
2062         if (--choose_type < 0)
2063           choose_type = (int)game->vehiclechoices.size()-1;
2064         return;
2065       }
2066       if ((ctrl.map[ActionRight].type == UserControl::TypeKey &&
2067         ctrl.map[ActionRight].key.sym == ke.keysym.sym) ||
2068         (ctrl.map[ActionNext].type == UserControl::TypeKey &&
2069         ctrl.map[ActionNext].key.sym == ke.keysym.sym)) {
2070         if (++choose_type >= (int)game->vehiclechoices.size())
2071           choose_type = 0;
2072         return;
2073       }
2074 
2075       switch (ke.keysym.sym) {
2076       case SDLK_RETURN:
2077       case SDLK_KP_ENTER:
2078       {
2079         startGame2();
2080         game->chooseVehicle(game->vehiclechoices[choose_type]);
2081 
2082         if (lss.state == AM_TOP_LVL_PREP)
2083         {
2084             const float bct = best_times.getBestClassTime(
2085                 race_data.mapname,
2086                 game->vehicle.front()->type->proper_class);
2087 
2088             if (bct >= 0.0f)
2089                 game->targettime = bct;
2090         }
2091 
2092         appstate = AS_IN_GAME;
2093         return;
2094       }
2095       case SDLK_ESCAPE:
2096         endGame(Gamefinish::not_finished);
2097         return;
2098       default:
2099         break;
2100       }
2101       break;
2102     case AS_IN_GAME:
2103 
2104       if (ctrl.map[ActionRecover].type == UserControl::TypeKey &&
2105         ctrl.map[ActionRecover].key.sym == ke.keysym.sym) {
2106         game->vehicle[0]->doReset();
2107         return;
2108       }
2109       if (ctrl.map[ActionRecoverAtCheckpoint].type == UserControl::TypeKey &&
2110         ctrl.map[ActionRecoverAtCheckpoint].key.sym == ke.keysym.sym)
2111       {
2112           game->resetAtCheckpoint(game->vehicle[0]);
2113           return;
2114       }
2115       if (ctrl.map[ActionCamMode].type == UserControl::TypeKey &&
2116         ctrl.map[ActionCamMode].key.sym == ke.keysym.sym) {
2117         cameraview = (cameraview + 1) % 5;
2118         camera_user_angle = 0.0f;
2119         return;
2120       }
2121       if (ctrl.map[ActionShowMap].type == UserControl::TypeKey &&
2122         ctrl.map[ActionShowMap].key.sym == ke.keysym.sym) {
2123         showmap = !showmap;
2124         return;
2125       }
2126       if (ctrl.map[ActionPauseRace].type == UserControl::TypeKey &&
2127         ctrl.map[ActionPauseRace].key.sym == ke.keysym.sym)
2128       {
2129           toggleSounds(pauserace);
2130           pauserace = !pauserace;
2131           return;
2132       }
2133       if (ctrl.map[ActionShowUi].type == UserControl::TypeKey &&
2134         ctrl.map[ActionShowUi].key.sym == ke.keysym.sym) {
2135         showui = !showui;
2136         return;
2137       }
2138 
2139       if (ctrl.map[ActionShowCheckpoint].type == UserControl::TypeKey &&
2140         ctrl.map[ActionShowCheckpoint].key.sym == ke.keysym.sym) {
2141             showcheckpoint = !showcheckpoint;
2142             return;
2143       }
2144 
2145 
2146       switch (ke.keysym.sym) {
2147       case SDLK_ESCAPE:
2148           endGame(game->getFinishState());
2149           pauserace = false;
2150 /*
2151           if (game->getFinishState() == GF_PASS)
2152             endGame(GF_PASS);
2153           else // GF_FAIL or GF_NOT_FINISHED
2154             endGame(GF_FAIL);
2155 */
2156         return;
2157       default:
2158         break;
2159       }
2160       break;
2161     case AS_END_SCREEN:
2162       requestExit();
2163       return;
2164     }
2165 
2166     switch (ke.keysym.sym) {
2167     case SDLK_ESCAPE:
2168       quitGame();
2169       return;
2170     default:
2171       break;
2172     }
2173   }
2174 }
2175 
mouseMoveEvent(int dx,int dy)2176 void MainApp::mouseMoveEvent(int dx, int dy)
2177 {
2178   //PVehicle *vehic = game->vehicle[0];
2179 
2180   //vehic->ctrl.tank.turret_turn.x += dx * -0.002;
2181   //vehic->ctrl.tank.turret_turn.y += dy * 0.002;
2182 
2183   //vehic->ctrl.turn.x += dy * 0.005;
2184   //vehic->ctrl.turn.y += dx * -0.005;
2185 
2186   dy = dy;
2187 
2188   if (appstate == AS_IN_GAME) {
2189     PVehicle *vehic = game->vehicle[0];
2190     vehic->ctrl.turn.z += dx * 0.01f;
2191   }
2192 }
2193 
joyButtonEvent(int which,int button,bool down)2194 void MainApp::joyButtonEvent(int which, int button, bool down)
2195 {
2196   if (which == 0 && down) {
2197 
2198     switch (appstate) {
2199     case AS_CHOOSE_VEHICLE:
2200 
2201       if (ctrl.map[ActionLeft].type == UserControl::TypeJoyButton &&
2202         ctrl.map[ActionLeft].joybutton.button == button) {
2203         if (--choose_type < 0)
2204           choose_type = (int)game->vehiclechoices.size()-1;
2205         return;
2206       }
2207       if ((ctrl.map[ActionRight].type == UserControl::TypeJoyButton &&
2208         ctrl.map[ActionRight].joybutton.button == button) ||
2209         (ctrl.map[ActionNext].type == UserControl::TypeJoyButton &&
2210         ctrl.map[ActionNext].joybutton.button == button)) {
2211         if (++choose_type >= (int)game->vehiclechoices.size())
2212           choose_type = 0;
2213         return;
2214       }
2215 
2216       break;
2217 
2218     case AS_IN_GAME:
2219 
2220       if (ctrl.map[ActionRecover].type == UserControl::TypeJoyButton &&
2221         ctrl.map[ActionRecover].joybutton.button == button) {
2222         game->vehicle[0]->doReset();
2223         return;
2224       }
2225       if (ctrl.map[ActionRecoverAtCheckpoint].type == UserControl::TypeJoyButton &&
2226         ctrl.map[ActionRecoverAtCheckpoint].joybutton.button == button)
2227       {
2228           game->resetAtCheckpoint(game->vehicle[0]);
2229           return;
2230       }
2231       if (ctrl.map[ActionCamMode].type == UserControl::TypeJoyButton &&
2232         ctrl.map[ActionCamMode].joybutton.button == button) {
2233         cameraview = (cameraview + 1) % 5;
2234         // current camera views: Chase, Bumper, Side, Hood, Periscope, [Piggyback - disabled]
2235         camera_user_angle = 0.0f;
2236         return;
2237       }
2238       if (ctrl.map[ActionShowMap].type == UserControl::TypeJoyButton &&
2239         ctrl.map[ActionShowMap].joybutton.button == button) {
2240         showmap = !showmap;
2241         return;
2242       }
2243       if (ctrl.map[ActionPauseRace].type == UserControl::TypeJoyButton &&
2244         ctrl.map[ActionPauseRace].joybutton.button == button)
2245         {
2246             toggleSounds(pauserace);
2247             pauserace = !pauserace;
2248             return;
2249         }
2250       if (ctrl.map[ActionShowUi].type == UserControl::TypeJoyButton &&
2251         ctrl.map[ActionShowUi].joybutton.button == button) {
2252         showui = !showui;
2253         return;
2254       }
2255     }
2256   }
2257 }
2258 
main(int argc,char * argv[])2259 int main(int argc, char *argv[])
2260 {
2261     return MainApp("Trigger Rally", ".trigger-rally").run(argc, argv);
2262 }
2263