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