1 /*
2
3 Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
4 and the "Aleph One" developers.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 This license is contained in the file "COPYING",
17 which is included with this source code; it is available online at
18 http://www.gnu.org/licenses/gpl.html
19
20 */
21
22 /*
23 * shell.cpp - Main game loop and input handling
24 */
25
26 #include "cseries.h"
27
28 #include "map.h"
29 #include "monsters.h"
30 #include "player.h"
31 #include "render.h"
32 #include "shell.h"
33 #include "interface.h"
34 #include "SoundManager.h"
35 #include "fades.h"
36 #include "screen.h"
37 #include "Music.h"
38 #include "images.h"
39 #include "vbl.h"
40 #include "preferences.h"
41 #include "tags.h" /* for scenario file type.. */
42 #include "network_sound.h"
43 #include "mouse.h"
44 #include "joystick.h"
45 #include "screen_drawing.h"
46 #include "computer_interface.h"
47 #include "game_wad.h" /* yuck... */
48 #include "game_window.h" /* for draw_interface() */
49 #include "extensions.h"
50 #include "items.h"
51 #include "interface_menus.h"
52 #include "weapons.h"
53 #include "lua_script.h"
54
55 #include "Crosshairs.h"
56 #include "OGL_Render.h"
57 #include "OGL_Blitter.h"
58 #include "XML_ParseTreeRoot.h"
59 #include "FileHandler.h"
60 #include "Plugins.h"
61 #include "FilmProfile.h"
62
63 #include "mytm.h" // mytm_initialize(), for platform-specific shell_*.h
64
65 #include <stdlib.h>
66 #include <string.h>
67 #include <ctype.h>
68 #include <vector>
69
70 #include <sstream>
71 #include <boost/lexical_cast.hpp>
72 #include <boost/algorithm/string/predicate.hpp>
73
74 #include "resource_manager.h"
75 #include "sdl_dialogs.h"
76 #include "sdl_fonts.h"
77 #include "sdl_widgets.h"
78
79 #include "DefaultStringSets.h"
80 #include "TextStrings.h"
81
82 #include <ctime>
83 #include <exception>
84 #include <algorithm>
85 #include <vector>
86
87 #ifdef HAVE_UNISTD_H
88 #include <unistd.h>
89 #endif
90
91 #ifdef HAVE_OPENGL
92 #include "OGL_Headers.h"
93 #endif
94
95 #if !defined(DISABLE_NETWORKING)
96 #include <SDL_net.h>
97 #endif
98
99 #ifdef HAVE_PNG
100 #include "IMG_savepng.h"
101 #endif
102
103 #ifdef HAVE_SDL_IMAGE
104 #include <SDL_image.h>
105 #if defined(__WIN32__)
106 #include "alephone32.xpm"
107 #elif !(defined(__APPLE__) && defined(__MACH__))
108 #include "alephone.xpm"
109 #endif
110 #endif
111
112 #ifdef __WIN32__
113 #include <windows.h>
114 #include <shlobj.h>
115 #endif
116
117 #include "alephversion.h"
118
119 #include "Logging.h"
120 #include "network.h"
121 #include "Console.h"
122 #include "Movie.h"
123 #include "HTTP.h"
124 #include "WadImageCache.h"
125
126 // LP addition: whether or not the cheats are active
127 // Defined in shell_misc.cpp
128 extern bool CheatsActive;
129
130 // Data directories
131 vector <DirectorySpecifier> data_search_path; // List of directories in which data files are searched for
132 DirectorySpecifier local_data_dir; // Local (per-user) data file directory
133 DirectorySpecifier default_data_dir; // Default scenario directory
134 DirectorySpecifier bundle_data_dir; // Data inside Mac OS X app bundle
135 DirectorySpecifier preferences_dir; // Directory for preferences
136 DirectorySpecifier saved_games_dir; // Directory for saved games
137 DirectorySpecifier quick_saves_dir; // Directory for auto-named saved games
138 DirectorySpecifier image_cache_dir; // Directory for image cache
139 DirectorySpecifier recordings_dir; // Directory for recordings (except film buffer, which is stored in local_data_dir)
140 DirectorySpecifier screenshots_dir; // Directory for screenshots
141 DirectorySpecifier log_dir; // Directory for Aleph One Log.txt
142 std::string arg_directory;
143 std::vector<std::string> arg_files;
144
145 // Command-line options
146 bool option_nogl = false; // Disable OpenGL
147 bool option_nosound = false; // Disable sound output
148 bool option_nogamma = false; // Disable gamma table effects (menu fades)
149 bool option_debug = false;
150 bool option_nojoystick = false;
151 bool insecure_lua = false;
152 static bool force_fullscreen = false; // Force fullscreen mode
153 static bool force_windowed = false; // Force windowed mode
154
155 // Prototypes
156 static void main_event_loop(void);
157 extern int process_keyword_key(char key);
158 extern void handle_keyword(int type_of_cheat);
159
160 void PlayInterfaceButtonSound(short SoundID);
161
162 // From preprocess_map_sdl.cpp
163 extern bool get_default_music_spec(FileSpecifier &file);
164 extern bool get_default_theme_spec(FileSpecifier& file);
165
166 // From vbl_sdl.cpp
167 void execute_timer_tasks(uint32 time);
168
169 // Prototypes
170 static void initialize_application(void);
171 void shutdown_application(void);
172 static void initialize_marathon_music_handler(void);
173 static void process_event(const SDL_Event &event);
174
175 // cross-platform static variables
176 short vidmasterStringSetID = -1; // can be set with MML
177
usage(const char * prg_name)178 static void usage(const char *prg_name)
179 {
180 char msg[] =
181 #ifdef __WIN32__
182 "Command line switches:\n\n"
183 #else
184 "\nUsage: %s [options] [directory] [file]\n"
185 #endif
186 "\t[-h | --help] Display this help message\n"
187 "\t[-v | --version] Display the game version\n"
188 "\t[-d | --debug] Allow saving of core files\n"
189 "\t (by disabling SDL parachute)\n"
190 "\t[-f | --fullscreen] Run the game fullscreen\n"
191 "\t[-w | --windowed] Run the game in a window\n"
192 #ifdef HAVE_OPENGL
193 "\t[-g | --nogl] Do not use OpenGL\n"
194 #endif
195 "\t[-s | --nosound] Do not access the sound card\n"
196 "\t[-m | --nogamma] Disable gamma table effects (menu fades)\n"
197 "\t[-j | --nojoystick] Do not initialize joysticks\n"
198 // Documenting this might be a bad idea?
199 // "\t[-i | --insecure_lua] Allow Lua netscripts to take over your computer\n"
200 "\tdirectory Directory containing scenario data files\n"
201 "\tfile Saved game to load or film to play\n"
202 "\nYou can also use the ALEPHONE_DATA environment variable to specify\n"
203 "the data directory.\n";
204
205 #ifdef __WIN32__
206 MessageBox(NULL, msg, "Usage", MB_OK | MB_ICONINFORMATION);
207 #else
208 printf(msg, prg_name);
209 #endif
210 exit(0);
211 }
212
213 extern bool handle_open_replay(FileSpecifier& File);
214 extern bool load_and_start_game(FileSpecifier& file);
215
handle_open_document(const std::string & filename)216 bool handle_open_document(const std::string& filename)
217 {
218 bool done = false;
219 FileSpecifier file(filename);
220 switch (file.GetType())
221 {
222 case _typecode_scenario:
223 set_map_file(file);
224 break;
225 case _typecode_savegame:
226 if (load_and_start_game(file))
227 {
228 done = true;
229 }
230 break;
231 case _typecode_film:
232 if (handle_open_replay(file))
233 {
234 done = true;
235 }
236 break;
237 case _typecode_physics:
238 set_physics_file(file);
239 break;
240 case _typecode_shapes:
241 open_shapes_file(file);
242 break;
243 case _typecode_sounds:
244 SoundManager::instance()->OpenSoundFile(file);
245 break;
246 default:
247 break;
248 }
249
250 return done;
251 }
252
253
main(int argc,char ** argv)254 int main(int argc, char **argv)
255 {
256 // Print banner (don't bother if this doesn't appear when started from a GUI)
257 char app_name_version[256];
258 expand_app_variables(app_name_version, "Aleph One $appLongVersion$");
259 printf ("%s\n%s\n\n"
260 "Original code by Bungie Software <http://www.bungie.com/>\n"
261 "Additional work by Loren Petrich, Chris Pruett, Rhys Hill et al.\n"
262 "TCP/IP networking by Woody Zenfell\n"
263 "Expat XML library by James Clark\n"
264 "SDL port by Christian Bauer <Christian.Bauer@uni-mainz.de>\n"
265 #if defined(__MACH__) && defined(__APPLE__)
266 "Mac OS X/SDL version by Chris Lovell, Alexander Strange, and Woody Zenfell\n"
267 #endif
268 "\nThis is free software with ABSOLUTELY NO WARRANTY.\n"
269 "You are welcome to redistribute it under certain conditions.\n"
270 "For details, see the file COPYING.\n"
271 #if defined(__WIN32__)
272 // Windows is statically linked against SDL, so we have to include this:
273 "\nSimple DirectMedia Layer (SDL) Library included under the terms of the\n"
274 "GNU Library General Public License.\n"
275 "For details, see the file COPYING.SDL.\n"
276 #endif
277 #if !defined(DISABLE_NETWORKING)
278 "\nBuilt with network play enabled.\n"
279 #endif
280 #ifdef HAVE_LUA
281 "\nBuilt with Lua scripting enabled.\n"
282 #endif
283 , app_name_version, A1_HOMEPAGE_URL
284 );
285
286 // Parse arguments
287 char *prg_name = argv[0];
288 argc--;
289 argv++;
290 while (argc > 0) {
291 if (strcmp(*argv, "-h") == 0 || strcmp(*argv, "--help") == 0) {
292 usage(prg_name);
293 } else if (strcmp(*argv, "-v") == 0 || strcmp(*argv, "--version") == 0) {
294 printf("%s\n", app_name_version);
295 exit(0);
296 } else if (strcmp(*argv, "-f") == 0 || strcmp(*argv, "--fullscreen") == 0) {
297 force_fullscreen = true;
298 } else if (strcmp(*argv, "-w") == 0 || strcmp(*argv, "--windowed") == 0) {
299 force_windowed = true;
300 } else if (strcmp(*argv, "-g") == 0 || strcmp(*argv, "--nogl") == 0) {
301 option_nogl = true;
302 } else if (strcmp(*argv, "-s") == 0 || strcmp(*argv, "--nosound") == 0) {
303 option_nosound = true;
304 } else if (strcmp(*argv, "-j") == 0 || strcmp(*argv, "--nojoystick") == 0) {
305 option_nojoystick = true;
306 } else if (strcmp(*argv, "-m") == 0 || strcmp(*argv, "--nogamma") == 0) {
307 option_nogamma = true;
308 } else if (strcmp(*argv, "-i") == 0 || strcmp(*argv, "--insecure_lua") == 0) {
309 insecure_lua = true;
310 } else if (strcmp(*argv, "-d") == 0 || strcmp(*argv, "--debug") == 0) {
311 option_debug = true;
312 } else if (*argv[0] != '-') {
313 // if it's a directory, make it the default data dir
314 // otherwise push it and handle it later
315 FileSpecifier f(*argv);
316 if (f.IsDir())
317 {
318 arg_directory = *argv;
319 }
320 else
321 {
322 arg_files.push_back(*argv);
323 }
324 } else {
325 printf("Unrecognized argument '%s'.\n", *argv);
326 usage(prg_name);
327 }
328 argc--;
329 argv++;
330 }
331
332 try {
333
334 // Initialize everything
335 initialize_application();
336
337 for (std::vector<std::string>::iterator it = arg_files.begin(); it != arg_files.end(); ++it)
338 {
339 if (handle_open_document(*it))
340 {
341 break;
342 }
343 }
344
345 // Run the main loop
346 main_event_loop();
347
348 } catch (std::exception &e) {
349 try
350 {
351 logFatal("Unhandled exception: %s", e.what());
352 }
353 catch (...)
354 {
355 }
356 exit(1);
357 } catch (...) {
358 try
359 {
360 logFatal("Unknown exception");
361 }
362 catch (...)
363 {
364 }
365 exit(1);
366 }
367
368 return 0;
369 }
370
char_is_not_filesafe(int c)371 static int char_is_not_filesafe(int c)
372 {
373 return (c != ' ' && !std::isalnum(c));
374 }
375
initialize_application(void)376 static void initialize_application(void)
377 {
378 #if defined(__WIN32__) && defined(__MINGW32__)
379 if (LoadLibrary("exchndl.dll")) option_debug = true;
380 #endif
381
382 #if defined(__WIN32__)
383 SDL_setenv("SDL_AUDIODRIVER", "directsound", 0);
384 #endif
385
386 // Initialize SDL
387 int retval = SDL_Init(SDL_INIT_VIDEO |
388 (option_nosound ? 0 : SDL_INIT_AUDIO) |
389 (option_nojoystick ? 0 : SDL_INIT_JOYSTICK|SDL_INIT_GAMECONTROLLER) |
390 (option_debug ? SDL_INIT_NOPARACHUTE : 0));
391 if (retval < 0)
392 {
393 const char *sdl_err = SDL_GetError();
394 if (sdl_err)
395 fprintf(stderr, "Couldn't initialize SDL (%s)\n", sdl_err);
396 else
397 fprintf(stderr, "Couldn't initialize SDL\n");
398 exit(1);
399 }
400 #if defined(HAVE_SDL_IMAGE)
401 IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG);
402 #endif
403 // We only want text input events at specific times
404 SDL_StopTextInput();
405
406 // See if we had a scenario folder dropped on us
407 if (arg_directory == "") {
408 SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
409 SDL_Event event;
410 while (SDL_PollEvent(&event)) {
411 switch (event.type) {
412 case SDL_DROPFILE:
413 FileSpecifier f(event.drop.file);
414 if (f.IsDir())
415 {
416 arg_directory = event.drop.file;
417 }
418 else
419 {
420 arg_files.push_back(event.drop.file);
421 }
422 SDL_free(event.drop.file);
423 break;
424 }
425 }
426 SDL_EventState(SDL_DROPFILE, SDL_DISABLE);
427 }
428
429 // Find data directories, construct search path
430 InitDefaultStringSets();
431
432 #ifndef SCENARIO_IS_BUNDLED
433 default_data_dir = get_data_path(kPathDefaultData);
434 #endif
435
436 local_data_dir = get_data_path(kPathLocalData);
437 log_dir = get_data_path(kPathLogs);
438 preferences_dir = get_data_path(kPathPreferences);
439 saved_games_dir = get_data_path(kPathSavedGames);
440 quick_saves_dir = get_data_path(kPathQuickSaves);
441 image_cache_dir = get_data_path(kPathImageCache);
442 recordings_dir = get_data_path(kPathRecordings);
443 screenshots_dir = get_data_path(kPathScreenshots);
444
445 if (!get_data_path(kPathBundleData).empty())
446 {
447 bundle_data_dir = get_data_path(kPathBundleData);
448 data_search_path.push_back(bundle_data_dir);
449 }
450
451 // in case we need to redo search path later:
452 size_t dsp_insert_pos = data_search_path.size();
453 size_t dsp_delete_pos = (size_t)-1;
454
455 if (arg_directory != "")
456 {
457 default_data_dir = arg_directory;
458 dsp_delete_pos = data_search_path.size();
459 data_search_path.push_back(arg_directory);
460 }
461
462 const char *data_env = getenv("ALEPHONE_DATA");
463 if (data_env) {
464 // Read colon-separated list of directories
465 string path = data_env;
466 string::size_type pos;
467 char LIST_SEP = get_path_list_separator();
468 while ((pos = path.find(LIST_SEP)) != string::npos) {
469 if (pos) {
470 string element = path.substr(0, pos);
471 data_search_path.push_back(element);
472 }
473 path.erase(0, pos + 1);
474 }
475 if (!path.empty())
476 data_search_path.push_back(path);
477 } else {
478 if (arg_directory == "")
479 {
480 dsp_delete_pos = data_search_path.size();
481 data_search_path.push_back(default_data_dir);
482 }
483
484 string legacy_data_path = get_data_path(kPathLegacyData);
485 if (!legacy_data_path.empty())
486 data_search_path.push_back(DirectorySpecifier(legacy_data_path));
487 data_search_path.push_back(local_data_dir);
488 }
489
490 // Setup resource manager
491 initialize_resources();
492
493 init_physics_wad_data();
494 initialize_fonts(false);
495
496 load_film_profile(FILM_PROFILE_DEFAULT, false);
497
498 // Parse MML files
499 LoadBaseMMLScripts();
500
501 // Check for presence of strings
502 if (!TS_IsPresent(strERRORS) || !TS_IsPresent(strFILENAMES)) {
503 fprintf(stderr, "Can't find required text strings (missing MML?).\n");
504 exit(1);
505 }
506
507 // Check for presence of files (one last chance to change data_search_path)
508 if (!have_default_files()) {
509 char chosen_dir[256];
510 if (alert_choose_scenario(chosen_dir)) {
511 // remove original argument (or fallback) from search path
512 if (dsp_delete_pos < data_search_path.size())
513 data_search_path.erase(data_search_path.begin() + dsp_delete_pos);
514 // add selected directory where command-line argument would go
515 data_search_path.insert(data_search_path.begin() + dsp_insert_pos, chosen_dir);
516
517 default_data_dir = chosen_dir;
518
519 // Parse MML files again, now that we have a new dir to search
520 initialize_fonts(false);
521 LoadBaseMMLScripts();
522 }
523 }
524
525 initialize_fonts(true);
526 Plugins::instance()->enumerate();
527
528 preferences_dir.CreateDirectory();
529 if (!get_data_path(kPathLegacyPreferences).empty())
530 transition_preferences(DirectorySpecifier(get_data_path(kPathLegacyPreferences)));
531
532 // Load preferences
533 initialize_preferences();
534
535 local_data_dir.CreateDirectory();
536 saved_games_dir.CreateDirectory();
537 quick_saves_dir.CreateDirectory();
538 {
539 std::string scen = Scenario::instance()->GetName();
540 if (scen.length())
541 scen.erase(std::remove_if(scen.begin(), scen.end(), char_is_not_filesafe), scen.end());
542 if (!scen.length())
543 scen = "Unknown";
544 quick_saves_dir += scen;
545 quick_saves_dir.CreateDirectory();
546 }
547 image_cache_dir.CreateDirectory();
548 recordings_dir.CreateDirectory();
549 screenshots_dir.CreateDirectory();
550
551 WadImageCache::instance()->initialize_cache();
552
553 #ifndef HAVE_OPENGL
554 graphics_preferences->screen_mode.acceleration = _no_acceleration;
555 #endif
556 if (force_fullscreen)
557 graphics_preferences->screen_mode.fullscreen = true;
558 if (force_windowed) // takes precedence over fullscreen because windowed is safer
559 graphics_preferences->screen_mode.fullscreen = false;
560 write_preferences();
561
562 Plugins::instance()->load_mml();
563
564 // SDL_WM_SetCaption(application_name, application_name);
565
566 // #if defined(HAVE_SDL_IMAGE) && !(defined(__APPLE__) && defined(__MACH__))
567 // SDL_WM_SetIcon(IMG_ReadXPMFromArray(const_cast<char**>(alephone_xpm)), 0);
568 // #endif
569 atexit(shutdown_application);
570
571 #if !defined(DISABLE_NETWORKING)
572 // Initialize SDL_net
573 if (SDLNet_Init () < 0) {
574 fprintf (stderr, "Couldn't initialize SDL_net (%s)\n", SDLNet_GetError());
575 exit(1);
576 }
577 #endif
578
579 if (TTF_Init() < 0) {
580 fprintf (stderr, "Couldn't initialize SDL_ttf (%s)\n", TTF_GetError());
581 exit(1);
582 }
583 HTTPClient::Init();
584
585 // Initialize everything
586 mytm_initialize();
587 // initialize_fonts();
588 SoundManager::instance()->Initialize(*sound_preferences);
589 initialize_marathon_music_handler();
590 initialize_keyboard_controller();
591 initialize_joystick();
592 initialize_gamma();
593 alephone::Screen::instance()->Initialize(&graphics_preferences->screen_mode);
594 initialize_marathon();
595 initialize_screen_drawing();
596 initialize_dialogs();
597 initialize_terminal_manager();
598 initialize_shape_handler();
599 initialize_fades();
600 initialize_images_manager();
601 load_environment_from_preferences();
602 initialize_game_state();
603 }
604
shutdown_application(void)605 void shutdown_application(void)
606 {
607 // ZZZ: seem to be having weird recursive shutdown problems esp. with fullscreen modes...
608 static bool already_shutting_down = false;
609 if(already_shutting_down)
610 return;
611
612 already_shutting_down = true;
613
614 WadImageCache::instance()->save_cache();
615 close_external_resources();
616
617 #if defined(HAVE_SDL_IMAGE) && (SDL_IMAGE_PATCHLEVEL >= 8)
618 IMG_Quit();
619 #endif
620 #if !defined(DISABLE_NETWORKING)
621 SDLNet_Quit();
622 #endif
623 TTF_Quit();
624 SDL_Quit();
625 }
626
networking_available(void)627 bool networking_available(void)
628 {
629 #if !defined(DISABLE_NETWORKING)
630 return true;
631 #else
632 return false;
633 #endif
634 }
635
initialize_marathon_music_handler(void)636 static void initialize_marathon_music_handler(void)
637 {
638 FileSpecifier file;
639 if (get_default_music_spec(file))
640 Music::instance()->SetupIntroMusic(file);
641 }
642
quit_without_saving(void)643 bool quit_without_saving(void)
644 {
645 dialog d;
646 vertical_placer *placer = new vertical_placer;
647 placer->dual_add (new w_static_text("Are you sure you wish to"), d);
648 placer->dual_add (new w_static_text("cancel the game in progress?"), d);
649 placer->add (new w_spacer(), true);
650
651 horizontal_placer *button_placer = new horizontal_placer;
652 w_button *default_button = new w_button("YES", dialog_ok, &d);
653 button_placer->dual_add (default_button, d);
654 button_placer->dual_add (new w_button("NO", dialog_cancel, &d), d);
655 d.activate_widget(default_button);
656 placer->add(button_placer, true);
657 d.set_widget_placer(placer);
658 return d.run() == 0;
659 }
660
661 // ZZZ: moved level-numbers widget into sdl_widgets for a wider audience.
662
663 const int32 AllPlayableLevels = _single_player_entry_point | _multiplayer_carnage_entry_point | _multiplayer_cooperative_entry_point | _kill_the_man_with_the_ball_entry_point | _king_of_hill_entry_point | _rugby_entry_point | _capture_the_flag_entry_point;
664
get_level_number_from_user(void)665 short get_level_number_from_user(void)
666 {
667 // Get levels
668 vector<entry_point> levels;
669 if (!get_entry_points(levels, AllPlayableLevels)) {
670 entry_point dummy;
671 dummy.level_number = 0;
672 strcpy(dummy.level_name, "Untitled Level");
673 levels.push_back(dummy);
674 }
675
676 // Create dialog
677 dialog d;
678 vertical_placer *placer = new vertical_placer;
679 if (vidmasterStringSetID != -1 && TS_IsPresent(vidmasterStringSetID) && TS_CountStrings(vidmasterStringSetID) > 0) {
680 // if we there's a stringset present for it, load the message from there
681 int num_lines = TS_CountStrings(vidmasterStringSetID);
682
683 for (size_t i = 0; i < num_lines; i++) {
684 bool message_font_title_color = true;
685 const char *string = TS_GetCString(vidmasterStringSetID, i);
686 if (!strncmp(string, "[QUOTE]", 7)) {
687 string = string + 7;
688 message_font_title_color = false;
689 }
690 if (!strlen(string))
691 placer->add(new w_spacer(), true);
692 else if (message_font_title_color)
693 placer->dual_add(new w_static_text(string), d);
694 else
695 placer->dual_add(new w_static_text(string), d);
696 }
697
698 } else {
699 // no stringset or no strings in stringset - use default message
700 placer->dual_add(new w_static_text("Before proceeding any further, you"), d);
701 placer->dual_add(new w_static_text ("must take the oath of the vidmaster:"), d);
702 placer->add(new w_spacer(), true);
703 placer->dual_add(new w_static_text("\xd2I pledge to punch all switches,"), d);
704 placer->dual_add(new w_static_text("to never shoot where I could use grenades,"), d);
705 placer->dual_add(new w_static_text("to admit the existence of no level"), d);
706 placer->dual_add(new w_static_text("except Total Carnage,"), d);
707 placer->dual_add(new w_static_text("to never use Caps Lock as my \xd4run\xd5 key,"), d);
708 placer->dual_add(new w_static_text("and to never, ever, leave a single Bob alive.\xd3"), d);
709 }
710
711 placer->add(new w_spacer(), true);
712 placer->dual_add(new w_static_text("Start at level:"), d);
713
714 w_levels *level_w = new w_levels(levels, &d);
715 placer->dual_add(level_w, d);
716 placer->add(new w_spacer(), true);
717 placer->dual_add(new w_button("CANCEL", dialog_cancel, &d), d);
718
719 d.activate_widget(level_w);
720 d.set_widget_placer(placer);
721
722 // Run dialog
723 short level;
724 if (d.run() == 0) // OK
725 // Should do noncontiguous map files OK
726 level = levels[level_w->get_selection()].level_number;
727 else
728 level = NONE;
729
730 // Redraw main menu
731 update_game_window();
732 return level;
733 }
734
735 const uint32 TICKS_BETWEEN_EVENT_POLL = 16; // 60 Hz
main_event_loop(void)736 static void main_event_loop(void)
737 {
738 uint32 last_event_poll = 0;
739 short game_state;
740
741 while ((game_state = get_game_state()) != _quit_game) {
742 uint32 cur_time = SDL_GetTicks();
743 bool yield_time = false;
744 bool poll_event = false;
745
746 switch (game_state) {
747 case _game_in_progress:
748 case _change_level:
749 if (Console::instance()->input_active() || cur_time - last_event_poll >= TICKS_BETWEEN_EVENT_POLL) {
750 poll_event = true;
751 last_event_poll = cur_time;
752 } else {
753 SDL_PumpEvents (); // This ensures a responsive keyboard control
754 }
755 break;
756
757 case _display_intro_screens:
758 case _display_main_menu:
759 case _display_chapter_heading:
760 case _display_prologue:
761 case _display_epilogue:
762 case _begin_display_of_epilogue:
763 case _display_credits:
764 case _display_intro_screens_for_demo:
765 case _display_quit_screens:
766 case _displaying_network_game_dialogs:
767 yield_time = interface_fade_finished();
768 poll_event = true;
769 break;
770
771 case _close_game:
772 case _switch_demo:
773 case _revert_game:
774 yield_time = poll_event = true;
775 break;
776 }
777
778 if (poll_event) {
779 global_idle_proc();
780
781 while (true) {
782 SDL_Event event;
783 bool found_event = SDL_PollEvent(&event);
784
785 if (yield_time) {
786 // The game is not in a "hot" state, yield time to other
787 // processes by calling SDL_Delay() but only try for a maximum
788 // of 30ms
789 int num_tries = 0;
790 while (!found_event && num_tries < 3) {
791 SDL_Delay(10);
792 found_event = SDL_PollEvent(&event);
793 num_tries++;
794 }
795 yield_time = false;
796 } else if (!found_event)
797 break;
798
799 if (found_event)
800 process_event(event);
801 }
802 }
803
804 execute_timer_tasks(SDL_GetTicks());
805 idle_game_state(SDL_GetTicks());
806
807 if (game_state == _game_in_progress && !graphics_preferences->hog_the_cpu && (TICKS_PER_SECOND - (SDL_GetTicks() - cur_time)) > 10)
808 {
809 SDL_Delay(1);
810 }
811 }
812 }
813
has_cheat_modifiers(void)814 static bool has_cheat_modifiers(void)
815 {
816 SDL_Keymod m = SDL_GetModState();
817 #if (defined(__APPLE__) && defined(__MACH__))
818 return ((m & KMOD_SHIFT) && (m & KMOD_CTRL)) || ((m & KMOD_ALT) && (m & KMOD_GUI));
819 #else
820 return (m & KMOD_SHIFT) && (m & KMOD_CTRL) && !(m & KMOD_ALT) && !(m & KMOD_GUI);
821 #endif
822 }
823
event_has_cheat_modifiers(const SDL_Event & event)824 static bool event_has_cheat_modifiers(const SDL_Event &event)
825 {
826 Uint16 m = event.key.keysym.mod;
827 #if (defined(__APPLE__) && defined(__MACH__))
828 return ((m & KMOD_SHIFT) && (m & KMOD_CTRL)) || ((m & KMOD_ALT) && (m & KMOD_GUI));
829 #else
830 return (m & KMOD_SHIFT) && (m & KMOD_CTRL) && !(m & KMOD_ALT) && !(m & KMOD_GUI);
831 #endif
832 }
833
process_screen_click(const SDL_Event & event)834 static void process_screen_click(const SDL_Event &event)
835 {
836 int x = event.button.x, y = event.button.y;
837 alephone::Screen::instance()->window_to_screen(x, y);
838 portable_process_screen_click(x, y, has_cheat_modifiers());
839 }
840
handle_game_key(const SDL_Event & event)841 static void handle_game_key(const SDL_Event &event)
842 {
843 SDL_Keycode key = event.key.keysym.sym;
844 SDL_Scancode sc = event.key.keysym.scancode;
845 bool changed_screen_mode = false;
846 bool changed_prefs = false;
847
848 if (!game_is_networked && (event.key.keysym.mod & KMOD_CTRL) && CheatsActive) {
849 int type_of_cheat = process_keyword_key(key);
850 if (type_of_cheat != NONE)
851 handle_keyword(type_of_cheat);
852 }
853 if (Console::instance()->input_active()) {
854 switch(key) {
855 case SDLK_RETURN:
856 case SDLK_KP_ENTER:
857 Console::instance()->enter();
858 break;
859 case SDLK_ESCAPE:
860 Console::instance()->abort();
861 break;
862 case SDLK_BACKSPACE:
863 Console::instance()->backspace();
864 break;
865 case SDLK_DELETE:
866 Console::instance()->del();
867 break;
868 case SDLK_UP:
869 Console::instance()->up_arrow();
870 break;
871 case SDLK_DOWN:
872 Console::instance()->down_arrow();
873 break;
874 case SDLK_LEFT:
875 Console::instance()->left_arrow();
876 break;
877 case SDLK_RIGHT:
878 Console::instance()->right_arrow();
879 break;
880 case SDLK_HOME:
881 Console::instance()->line_home();
882 break;
883 case SDLK_END:
884 Console::instance()->line_end();
885 break;
886 case SDLK_a:
887 if (event.key.keysym.mod & KMOD_CTRL)
888 Console::instance()->line_home();
889 break;
890 case SDLK_b:
891 if (event.key.keysym.mod & KMOD_CTRL)
892 Console::instance()->left_arrow();
893 break;
894 case SDLK_d:
895 if (event.key.keysym.mod & KMOD_CTRL)
896 Console::instance()->del();
897 break;
898 case SDLK_e:
899 if (event.key.keysym.mod & KMOD_CTRL)
900 Console::instance()->line_end();
901 break;
902 case SDLK_f:
903 if (event.key.keysym.mod & KMOD_CTRL)
904 Console::instance()->right_arrow();
905 break;
906 case SDLK_h:
907 if (event.key.keysym.mod & KMOD_CTRL)
908 Console::instance()->backspace();
909 break;
910 case SDLK_k:
911 if (event.key.keysym.mod & KMOD_CTRL)
912 Console::instance()->forward_clear();
913 break;
914 case SDLK_n:
915 if (event.key.keysym.mod & KMOD_CTRL)
916 Console::instance()->down_arrow();
917 break;
918 case SDLK_p:
919 if (event.key.keysym.mod & KMOD_CTRL)
920 Console::instance()->up_arrow();
921 break;
922 case SDLK_t:
923 if (event.key.keysym.mod & KMOD_CTRL)
924 Console::instance()->transpose();
925 break;
926 case SDLK_u:
927 if (event.key.keysym.mod & KMOD_CTRL)
928 Console::instance()->clear();
929 break;
930 case SDLK_w:
931 if (event.key.keysym.mod & KMOD_CTRL)
932 Console::instance()->delete_word();
933 break;
934 }
935 }
936 else
937 {
938 if (sc == SDL_SCANCODE_ESCAPE || sc == AO_SCANCODE_JOYSTICK_ESCAPE) // (ZZZ) Quit gesture (now safer)
939 {
940 if(!player_controlling_game())
941 do_menu_item_command(mGame, iQuitGame, false);
942 else {
943 if(get_ticks_since_local_player_in_terminal() > 1 * TICKS_PER_SECOND) {
944 if(!game_is_networked) {
945 do_menu_item_command(mGame, iQuitGame, false);
946 }
947 else {
948 #if defined(__APPLE__) && defined(__MACH__)
949 screen_printf("If you wish to quit, press Command-Q");
950 #else
951 screen_printf("If you wish to quit, press Alt+Q.");
952 #endif
953 }
954 }
955 }
956 }
957 else if (input_preferences->shell_key_bindings[_key_volume_up].count(sc))
958 {
959 changed_prefs = SoundManager::instance()->AdjustVolumeUp(Sound_AdjustVolume());
960 }
961 else if (input_preferences->shell_key_bindings[_key_volume_down].count(sc))
962 {
963 changed_prefs = SoundManager::instance()->AdjustVolumeDown(Sound_AdjustVolume());
964 }
965 else if (input_preferences->shell_key_bindings[_key_switch_view].count(sc))
966 {
967 walk_player_list();
968 render_screen(NONE);
969 }
970 else if (input_preferences->shell_key_bindings[_key_zoom_in].count(sc))
971 {
972 if (zoom_overhead_map_in())
973 PlayInterfaceButtonSound(Sound_ButtonSuccess());
974 else
975 PlayInterfaceButtonSound(Sound_ButtonFailure());
976 }
977 else if (input_preferences->shell_key_bindings[_key_zoom_out].count(sc))
978 {
979 if (zoom_overhead_map_out())
980 PlayInterfaceButtonSound(Sound_ButtonSuccess());
981 else
982 PlayInterfaceButtonSound(Sound_ButtonFailure());
983 }
984 else if (input_preferences->shell_key_bindings[_key_inventory_left].count(sc))
985 {
986 if (player_controlling_game()) {
987 PlayInterfaceButtonSound(Sound_ButtonSuccess());
988 scroll_inventory(-1);
989 } else
990 decrement_replay_speed();
991 }
992 else if (input_preferences->shell_key_bindings[_key_inventory_right].count(sc))
993 {
994 if (player_controlling_game()) {
995 PlayInterfaceButtonSound(Sound_ButtonSuccess());
996 scroll_inventory(1);
997 } else
998 increment_replay_speed();
999 }
1000 else if (input_preferences->shell_key_bindings[_key_toggle_fps].count(sc))
1001 {
1002 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1003 extern bool displaying_fps;
1004 displaying_fps = !displaying_fps;
1005 }
1006 else if (input_preferences->shell_key_bindings[_key_activate_console].count(sc))
1007 {
1008 if (game_is_networked) {
1009 #if !defined(DISABLE_NETWORKING)
1010 Console::instance()->activate_input(InGameChatCallbacks::SendChatMessage, InGameChatCallbacks::prompt());
1011 #endif
1012 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1013 }
1014 else if (Console::instance()->use_lua_console())
1015 {
1016 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1017 Console::instance()->activate_input(ExecuteLuaString, ">");
1018 }
1019 else
1020 {
1021 PlayInterfaceButtonSound(Sound_ButtonFailure());
1022 }
1023 }
1024 else if (input_preferences->shell_key_bindings[_key_show_scores].count(sc))
1025 {
1026 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1027 {
1028 extern bool ShowScores;
1029 ShowScores = !ShowScores;
1030 }
1031 }
1032 else if (sc == SDL_SCANCODE_F1) // Decrease screen size
1033 {
1034 if (!graphics_preferences->screen_mode.hud)
1035 {
1036 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1037 graphics_preferences->screen_mode.hud = true;
1038 changed_screen_mode = changed_prefs = true;
1039 }
1040 else
1041 {
1042 int mode = alephone::Screen::instance()->FindMode(get_screen_mode()->width, get_screen_mode()->height);
1043 if (mode < alephone::Screen::instance()->GetModes().size() - 1)
1044 {
1045 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1046 graphics_preferences->screen_mode.width = alephone::Screen::instance()->ModeWidth(mode + 1);
1047 graphics_preferences->screen_mode.height = alephone::Screen::instance()->ModeHeight(mode + 1);
1048 graphics_preferences->screen_mode.auto_resolution = false;
1049 graphics_preferences->screen_mode.hud = false;
1050 changed_screen_mode = changed_prefs = true;
1051 } else
1052 PlayInterfaceButtonSound(Sound_ButtonFailure());
1053 }
1054 }
1055 else if (sc == SDL_SCANCODE_F2) // Increase screen size
1056 {
1057 if (graphics_preferences->screen_mode.hud)
1058 {
1059 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1060 graphics_preferences->screen_mode.hud = false;
1061 changed_screen_mode = changed_prefs = true;
1062 }
1063 else
1064 {
1065 int mode = alephone::Screen::instance()->FindMode(get_screen_mode()->width, get_screen_mode()->height);
1066 int automode = get_screen_mode()->fullscreen ? 0 : 1;
1067 if (mode > automode)
1068 {
1069 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1070 graphics_preferences->screen_mode.width = alephone::Screen::instance()->ModeWidth(mode - 1);
1071 graphics_preferences->screen_mode.height = alephone::Screen::instance()->ModeHeight(mode - 1);
1072 if ((mode - 1) == automode)
1073 graphics_preferences->screen_mode.auto_resolution = true;
1074 graphics_preferences->screen_mode.hud = true;
1075 changed_screen_mode = changed_prefs = true;
1076 } else
1077 PlayInterfaceButtonSound(Sound_ButtonFailure());
1078 }
1079 }
1080 else if (sc == SDL_SCANCODE_F3) // Resolution toggle
1081 {
1082 if (!OGL_IsActive()) {
1083 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1084 graphics_preferences->screen_mode.high_resolution = !graphics_preferences->screen_mode.high_resolution;
1085 changed_screen_mode = changed_prefs = true;
1086 } else
1087 PlayInterfaceButtonSound(Sound_ButtonFailure());
1088 }
1089 else if (sc == SDL_SCANCODE_F4) // Reset OpenGL textures
1090 {
1091 #ifdef HAVE_OPENGL
1092 if (OGL_IsActive()) {
1093 // Play the button sound in advance to get the full effect of the sound
1094 PlayInterfaceButtonSound(Sound_OGL_Reset());
1095 OGL_ResetTextures();
1096 } else
1097 #endif
1098 PlayInterfaceButtonSound(Sound_ButtonInoperative());
1099 }
1100 else if (sc == SDL_SCANCODE_F5) // Make the chase cam switch sides
1101 {
1102 if (ChaseCam_IsActive())
1103 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1104 else
1105 PlayInterfaceButtonSound(Sound_ButtonInoperative());
1106 ChaseCam_SwitchSides();
1107 }
1108 else if (sc == SDL_SCANCODE_F6) // Toggle the chase cam
1109 {
1110 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1111 ChaseCam_SetActive(!ChaseCam_IsActive());
1112 }
1113 else if (sc == SDL_SCANCODE_F7) // Toggle tunnel vision
1114 {
1115 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1116 SetTunnelVision(!GetTunnelVision());
1117 }
1118 else if (sc == SDL_SCANCODE_F8) // Toggle the crosshairs
1119 {
1120 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1121 player_preferences->crosshairs_active = !player_preferences->crosshairs_active;
1122 Crosshairs_SetActive(player_preferences->crosshairs_active);
1123 changed_prefs = true;
1124 }
1125 else if (sc == SDL_SCANCODE_F9) // Screen dump
1126 {
1127 dump_screen();
1128 }
1129 else if (sc == SDL_SCANCODE_F10) // Toggle the position display
1130 {
1131 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1132 {
1133 extern bool ShowPosition;
1134 ShowPosition = !ShowPosition;
1135 }
1136 }
1137 else if (sc == SDL_SCANCODE_F11) // Decrease gamma level
1138 {
1139 if (graphics_preferences->screen_mode.gamma_level) {
1140 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1141 graphics_preferences->screen_mode.gamma_level--;
1142 change_gamma_level(graphics_preferences->screen_mode.gamma_level);
1143 changed_prefs = true;
1144 } else
1145 PlayInterfaceButtonSound(Sound_ButtonFailure());
1146 }
1147 else if (sc == SDL_SCANCODE_F12) // Increase gamma level
1148 {
1149 if (graphics_preferences->screen_mode.gamma_level < NUMBER_OF_GAMMA_LEVELS - 1) {
1150 PlayInterfaceButtonSound(Sound_ButtonSuccess());
1151 graphics_preferences->screen_mode.gamma_level++;
1152 change_gamma_level(graphics_preferences->screen_mode.gamma_level);
1153 changed_prefs = true;
1154 } else
1155 PlayInterfaceButtonSound(Sound_ButtonFailure());
1156 }
1157 else
1158 {
1159 if (get_game_controller() == _demo)
1160 set_game_state(_close_game);
1161 }
1162 }
1163
1164 if (changed_screen_mode) {
1165 screen_mode_data temp_screen_mode = graphics_preferences->screen_mode;
1166 temp_screen_mode.fullscreen = get_screen_mode()->fullscreen;
1167 change_screen_mode(&temp_screen_mode, true);
1168 render_screen(0);
1169 }
1170
1171 if (changed_prefs)
1172 write_preferences();
1173 }
1174
process_game_key(const SDL_Event & event)1175 static void process_game_key(const SDL_Event &event)
1176 {
1177 switch (get_game_state()) {
1178 case _game_in_progress:
1179 #if defined(__APPLE__) && defined(__MACH__)
1180 if ((event.key.keysym.mod & KMOD_GUI))
1181 #else
1182 if ((event.key.keysym.mod & KMOD_ALT) || (event.key.keysym.mod & KMOD_GUI))
1183 #endif
1184 {
1185 int item = -1;
1186 switch (event.key.keysym.sym) {
1187 case SDLK_p:
1188 item = iPause;
1189 break;
1190 case SDLK_s:
1191 item = iSave;
1192 break;
1193 case SDLK_r:
1194 item = iRevert;
1195 break;
1196 case SDLK_q:
1197 // On Mac, this key will trigger the application menu so we ignore it here
1198 #if !defined(__APPLE__) && !defined(__MACH__)
1199 item = iQuitGame;
1200 #endif
1201 break;
1202 case SDLK_RETURN:
1203 item = 0;
1204 toggle_fullscreen();
1205 break;
1206 default:
1207 break;
1208 }
1209 if (item > 0)
1210 do_menu_item_command(mGame, item, event_has_cheat_modifiers(event));
1211 else if (item != 0)
1212 handle_game_key(event);
1213 } else
1214 handle_game_key(event);
1215 break;
1216 case _display_intro_screens:
1217 case _display_chapter_heading:
1218 case _display_prologue:
1219 case _display_epilogue:
1220 case _display_credits:
1221 case _display_quit_screens:
1222 if (interface_fade_finished())
1223 force_game_state_change();
1224 else
1225 stop_interface_fade();
1226 break;
1227
1228 case _display_intro_screens_for_demo:
1229 stop_interface_fade();
1230 display_main_menu();
1231 break;
1232
1233 case _quit_game:
1234 case _close_game:
1235 case _revert_game:
1236 case _switch_demo:
1237 case _change_level:
1238 case _begin_display_of_epilogue:
1239 case _displaying_network_game_dialogs:
1240 break;
1241
1242 case _display_main_menu:
1243 {
1244 if (!interface_fade_finished())
1245 stop_interface_fade();
1246 int item = -1;
1247 switch (event.key.keysym.sym) {
1248 case SDLK_n:
1249 item = iNewGame;
1250 break;
1251 case SDLK_o:
1252 item = iLoadGame;
1253 break;
1254 case SDLK_g:
1255 item = iGatherGame;
1256 break;
1257 case SDLK_j:
1258 item = iJoinGame;
1259 break;
1260 case SDLK_p:
1261 item = iPreferences;
1262 break;
1263 case SDLK_r:
1264 item = iReplaySavedFilm;
1265 break;
1266 case SDLK_c:
1267 item = iCredits;
1268 break;
1269 case SDLK_q:
1270 item = iQuit;
1271 break;
1272 case SDLK_F9:
1273 dump_screen();
1274 break;
1275 case SDLK_RETURN:
1276 #if defined(__APPLE__) && defined(__MACH__)
1277 if ((event.key.keysym.mod & KMOD_GUI))
1278 #else
1279 if ((event.key.keysym.mod & KMOD_GUI) || (event.key.keysym.mod & KMOD_ALT))
1280 #endif
1281 {
1282 toggle_fullscreen();
1283 } else {
1284 process_main_menu_highlight_select(event_has_cheat_modifiers(event));
1285 }
1286 break;
1287 case SDLK_a:
1288 item = iAbout;
1289 break;
1290 case SDLK_UP:
1291 case SDLK_LEFT:
1292 process_main_menu_highlight_advance(true);
1293 break;
1294 case SDLK_DOWN:
1295 case SDLK_RIGHT:
1296 process_main_menu_highlight_advance(false);
1297 break;
1298 case SDLK_TAB:
1299 process_main_menu_highlight_advance(event.key.keysym.mod & KMOD_SHIFT);
1300 break;
1301 case SDLK_UNKNOWN:
1302 switch (static_cast<int>(event.key.keysym.scancode)) {
1303 case AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_DPAD_UP:
1304 case AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_DPAD_LEFT:
1305 process_main_menu_highlight_advance(true);
1306 break;
1307 case AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_DPAD_DOWN:
1308 case AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
1309 process_main_menu_highlight_advance(false);
1310 break;
1311 case AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_A:
1312 process_main_menu_highlight_select(false);
1313 break;
1314 case AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_GUIDE:
1315 process_main_menu_highlight_select(true);
1316 break;
1317 default:
1318 break;
1319 }
1320 break;
1321 default:
1322 break;
1323 }
1324 if (item > 0) {
1325 draw_menu_button_for_command(item);
1326 do_menu_item_command(mInterface, item, event_has_cheat_modifiers(event));
1327 }
1328 break;
1329 }
1330 }
1331 }
1332
process_event(const SDL_Event & event)1333 static void process_event(const SDL_Event &event)
1334 {
1335 switch (event.type) {
1336 case SDL_MOUSEMOTION:
1337 if (get_game_state() == _game_in_progress)
1338 {
1339 mouse_moved(event.motion.xrel, event.motion.yrel);
1340 }
1341 break;
1342 case SDL_MOUSEWHEEL:
1343 if (get_game_state() == _game_in_progress)
1344 {
1345 bool up = (event.wheel.y > 0);
1346 #if SDL_VERSION_ATLEAST(2,0,4)
1347 if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED)
1348 up = !up;
1349 #endif
1350 mouse_scroll(up);
1351 }
1352 break;
1353 case SDL_MOUSEBUTTONDOWN:
1354 if (get_game_state() == _game_in_progress)
1355 {
1356 if (!get_keyboard_controller_status())
1357 {
1358 hide_cursor();
1359 validate_world_window();
1360 set_keyboard_controller_status(true);
1361 }
1362 else
1363 {
1364 SDL_Event e2;
1365 memset(&e2, 0, sizeof(SDL_Event));
1366 e2.type = SDL_KEYDOWN;
1367 e2.key.keysym.sym = SDLK_UNKNOWN;
1368 e2.key.keysym.scancode = (SDL_Scancode)(AO_SCANCODE_BASE_MOUSE_BUTTON + event.button.button - 1);
1369 process_game_key(e2);
1370 }
1371 }
1372 else
1373 process_screen_click(event);
1374 break;
1375
1376 case SDL_CONTROLLERBUTTONDOWN:
1377 joystick_button_pressed(event.cbutton.which, event.cbutton.button, true);
1378 SDL_Event e2;
1379 memset(&e2, 0, sizeof(SDL_Event));
1380 e2.type = SDL_KEYDOWN;
1381 e2.key.keysym.sym = SDLK_UNKNOWN;
1382 e2.key.keysym.scancode = (SDL_Scancode)(AO_SCANCODE_BASE_JOYSTICK_BUTTON + event.cbutton.button);
1383 process_game_key(e2);
1384 break;
1385
1386 case SDL_CONTROLLERBUTTONUP:
1387 joystick_button_pressed(event.cbutton.which, event.cbutton.button, false);
1388 break;
1389
1390 case SDL_CONTROLLERAXISMOTION:
1391 joystick_axis_moved(event.caxis.which, event.caxis.axis, event.caxis.value);
1392 break;
1393
1394 case SDL_JOYDEVICEADDED:
1395 joystick_added(event.jdevice.which);
1396 break;
1397
1398 case SDL_JOYDEVICEREMOVED:
1399 joystick_removed(event.jdevice.which);
1400 break;
1401
1402 case SDL_KEYDOWN:
1403 process_game_key(event);
1404 break;
1405
1406 case SDL_TEXTINPUT:
1407 if (Console::instance()->input_active()) {
1408 Console::instance()->textEvent(event);
1409 }
1410 break;
1411
1412 case SDL_QUIT:
1413 if (get_game_state() == _game_in_progress)
1414 do_menu_item_command(mGame, iQuitGame, false);
1415 else
1416 set_game_state(_quit_game);
1417 break;
1418
1419 case SDL_WINDOWEVENT:
1420 switch (event.window.event) {
1421 case SDL_WINDOWEVENT_FOCUS_LOST:
1422 if (get_game_state() == _game_in_progress && get_keyboard_controller_status() && !Movie::instance()->IsRecording()) {
1423 darken_world_window();
1424 set_keyboard_controller_status(false);
1425 show_cursor();
1426 }
1427 break;
1428 #if (defined(__APPLE__) && defined(__MACH__))
1429 // work around Mojave issue
1430 case SDL_WINDOWEVENT_FOCUS_GAINED:
1431 static bool gFirstWindow = true;
1432 if (gFirstWindow) {
1433 gFirstWindow = false;
1434 SDL_Window *win = SDL_GetWindowFromID(event.window.windowID);
1435 if (!MainScreenIsOpenGL() && (SDL_GetWindowFlags(win) & SDL_WINDOW_FULLSCREEN_DESKTOP)) {
1436 SDL_SetWindowFullscreen(win, 0);
1437 SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN_DESKTOP);
1438 } else {
1439 SDL_Window *w2 = SDL_CreateWindow("Loading", 0, 0, 100, 100, 0);
1440 SDL_RaiseWindow(w2);
1441 SDL_RaiseWindow(win);
1442 SDL_DestroyWindow(w2);
1443 }
1444 }
1445 break;
1446 #endif
1447 case SDL_WINDOWEVENT_EXPOSED:
1448 #if !defined(__APPLE__) && !defined(__MACH__) // double buffering :)
1449 #ifdef HAVE_OPENGL
1450 if (MainScreenIsOpenGL())
1451 MainScreenSwap();
1452 else
1453 #endif
1454 update_game_window();
1455 #endif
1456 break;
1457 }
1458 break;
1459 }
1460
1461 }
1462
1463 #ifdef HAVE_PNG
1464 extern view_data *world_view;
1465 #endif
1466
to_alnum(const std::string & input)1467 std::string to_alnum(const std::string& input)
1468 {
1469 std::string output;
1470 for (std::string::const_iterator it = input.begin(); it != input.end(); ++it)
1471 {
1472 if (isalnum(*it))
1473 {
1474 output += *it;
1475 }
1476 }
1477
1478 return output;
1479 }
1480
dump_screen(void)1481 void dump_screen(void)
1482 {
1483 // Find suitable file name
1484 FileSpecifier file;
1485 int i = 0;
1486 do {
1487 char name[256];
1488 const char* suffix;
1489 #ifdef HAVE_PNG
1490 suffix = "png";
1491 #else
1492 suffix = "bmp";
1493 #endif
1494 if (get_game_state() == _game_in_progress)
1495 {
1496 sprintf(name, "%s_%04d.%s", to_alnum(static_world->level_name).c_str(), i, suffix);
1497 }
1498 else
1499 {
1500 sprintf(name, "Screenshot_%04d.%s", i, suffix);
1501 }
1502
1503 file = screenshots_dir + name;
1504 i++;
1505 } while (file.Exists());
1506
1507 #ifdef HAVE_PNG
1508 // build some nice metadata
1509 std::vector<IMG_PNG_text> texts;
1510 std::map<std::string, std::string> metadata;
1511
1512 metadata["Source"] = expand_app_variables("$appName$ $appVersion$ ($appPlatform$)");
1513
1514 time_t rawtime;
1515 time(&rawtime);
1516
1517 char time_string[32];
1518 strftime(time_string, 32,"%d %b %Y %H:%M:%S +0000", gmtime(&rawtime));
1519 metadata["Creation Time"] = time_string;
1520
1521 if (get_game_state() == _game_in_progress)
1522 {
1523 const float FLOAT_WORLD_ONE = float(WORLD_ONE);
1524 const float AngleConvert = 360/float(FULL_CIRCLE);
1525
1526 metadata["Level"] = static_world->level_name;
1527
1528 char map_file_name[256];
1529 FileSpecifier fs = environment_preferences->map_file;
1530 fs.GetName(map_file_name);
1531 metadata["Map File"] = map_file_name;
1532
1533 if (Scenario::instance()->GetName().size())
1534 {
1535 metadata["Scenario"] = Scenario::instance()->GetName();
1536 }
1537
1538 metadata["Polygon"] = boost::lexical_cast<std::string>(world_view->origin_polygon_index);
1539 metadata["X"] = boost::lexical_cast<std::string>(world_view->origin.x / FLOAT_WORLD_ONE);
1540 metadata["Y"] = boost::lexical_cast<std::string>(world_view->origin.y / FLOAT_WORLD_ONE);
1541 metadata["Z"] = boost::lexical_cast<std::string>(world_view->origin.z / FLOAT_WORLD_ONE);
1542 metadata["Yaw"] = boost::lexical_cast<std::string>(world_view->yaw * AngleConvert);
1543
1544
1545 short pitch = world_view->pitch;
1546 if (pitch > HALF_CIRCLE) pitch -= HALF_CIRCLE;
1547 metadata["Pitch"] = boost::lexical_cast<std::string>(pitch * AngleConvert);
1548 }
1549
1550 for (std::map<std::string, std::string>::const_iterator it = metadata.begin(); it != metadata.end(); ++it)
1551 {
1552 IMG_PNG_text text;
1553 text.key = const_cast<char*>(it->first.c_str());
1554 text.value = const_cast<char*>(it->second.c_str());
1555 texts.push_back(text);
1556 }
1557
1558 IMG_PNG_text* textp = texts.size() ? &texts[0] : 0;
1559 #endif
1560
1561 // Without OpenGL, dumping the screen is easy
1562 if (!MainScreenIsOpenGL()) {
1563 //#ifdef HAVE_PNG
1564 // aoIMG_SavePNG(file.GetPath(), MainScreenSurface(), IMG_COMPRESS_DEFAULT, textp, texts.size());
1565 #ifdef HAVE_SDL_IMAGE
1566 IMG_SavePNG(MainScreenSurface(), file.GetPath());
1567 #else
1568 SDL_SaveBMP(MainScreenSurface(), file.GetPath());
1569 #endif
1570 return;
1571 }
1572
1573 int video_w = MainScreenPixelWidth();
1574 int video_h = MainScreenPixelHeight();
1575
1576 #ifdef HAVE_OPENGL
1577 // Otherwise, allocate temporary surface...
1578 SDL_Surface *t = SDL_CreateRGBSurface(SDL_SWSURFACE, video_w, video_h, 24,
1579 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
1580 0x000000ff, 0x0000ff00, 0x00ff0000, 0);
1581 #else
1582 0x00ff0000, 0x0000ff00, 0x000000ff, 0);
1583 #endif
1584 if (t == NULL)
1585 return;
1586
1587 // ...and pixel buffer
1588 void *pixels = malloc(video_w * video_h * 3);
1589 if (pixels == NULL) {
1590 SDL_FreeSurface(t);
1591 return;
1592 }
1593
1594 // Read OpenGL frame buffer
1595 glPixelStorei(GL_PACK_ALIGNMENT, 1);
1596 glReadPixels(0, 0, video_w, video_h, GL_RGB, GL_UNSIGNED_BYTE, pixels);
1597 glPixelStorei(GL_PACK_ALIGNMENT, 4); // return to default
1598
1599 // Copy pixel buffer (which is upside-down) to surface
1600 for (int y = 0; y < video_h; y++)
1601 memcpy((uint8 *)t->pixels + t->pitch * y, (uint8 *)pixels + video_w * 3 * (video_h - y - 1), video_w * 3);
1602 free(pixels);
1603
1604 // Save surface
1605 //#ifdef HAVE_PNG
1606 // aoIMG_SavePNG(file.GetPath(), t, IMG_COMPRESS_DEFAULT, textp, texts.size());
1607 #ifdef HAVE_SDL_IMAGE
1608 IMG_SavePNG(t, file.GetPath());
1609 #else
1610 SDL_SaveBMP(t, file.GetPath());
1611 #endif
1612 SDL_FreeSurface(t);
1613 #endif
1614 }
1615
_ParseMMLDirectory(DirectorySpecifier & dir)1616 static bool _ParseMMLDirectory(DirectorySpecifier& dir)
1617 {
1618 // Get sorted list of files in directory
1619 vector<dir_entry> de;
1620 if (!dir.ReadDirectory(de))
1621 return false;
1622 sort(de.begin(), de.end());
1623
1624 // Parse each file
1625 vector<dir_entry>::const_iterator i, end = de.end();
1626 for (i=de.begin(); i!=end; i++) {
1627 if (i->is_directory)
1628 continue;
1629 if (i->name[i->name.length() - 1] == '~')
1630 continue;
1631 // people stick Lua scripts in Scripts/
1632 if (boost::algorithm::ends_with(i->name, ".lua"))
1633 continue;
1634
1635 // Construct full path name
1636 FileSpecifier file_name = dir + i->name;
1637
1638 // Parse file
1639 ParseMMLFromFile(file_name);
1640 }
1641
1642 return true;
1643 }
1644
LoadBaseMMLScripts()1645 void LoadBaseMMLScripts()
1646 {
1647 vector <DirectorySpecifier>::const_iterator i = data_search_path.begin(), end = data_search_path.end();
1648 while (i != end) {
1649 DirectorySpecifier path = *i + "MML";
1650 _ParseMMLDirectory(path);
1651 path = *i + "Scripts";
1652 _ParseMMLDirectory(path);
1653 i++;
1654 }
1655 }
1656
expand_symbolic_paths_helper(char * dest,const char * src,int maxlen,const char * symbol,DirectorySpecifier & dir)1657 bool expand_symbolic_paths_helper(char *dest, const char *src, int maxlen, const char *symbol, DirectorySpecifier& dir)
1658 {
1659 int symlen = strlen(symbol);
1660 if (!strncmp(src, symbol, symlen))
1661 {
1662 strncpy(dest, dir.GetPath(), maxlen);
1663 dest[maxlen] = '\0';
1664 strncat(dest, &src[symlen], maxlen-strlen(dest));
1665 return true;
1666 }
1667 return false;
1668 }
1669
expand_symbolic_paths(char * dest,const char * src,int maxlen)1670 char *expand_symbolic_paths(char *dest, const char *src, int maxlen)
1671 {
1672 bool expanded =
1673 #if defined(HAVE_BUNDLE_NAME)
1674 expand_symbolic_paths_helper(dest, src, maxlen, "$bundle$", bundle_data_dir) ||
1675 #endif
1676 expand_symbolic_paths_helper(dest, src, maxlen, "$local$", local_data_dir) ||
1677 expand_symbolic_paths_helper(dest, src, maxlen, "$default$", default_data_dir);
1678 if (!expanded)
1679 {
1680 strncpy(dest, src, maxlen);
1681 dest[maxlen] = '\0';
1682 }
1683 return dest;
1684 }
1685
contract_symbolic_paths_helper(char * dest,const char * src,int maxlen,const char * symbol,DirectorySpecifier & dir)1686 bool contract_symbolic_paths_helper(char *dest, const char *src, int maxlen, const char *symbol, DirectorySpecifier &dir)
1687 {
1688 const char *dpath = dir.GetPath();
1689 int dirlen = strlen(dpath);
1690 if (!strncmp(src, dpath, dirlen))
1691 {
1692 strncpy(dest, symbol, maxlen);
1693 dest[maxlen] = '\0';
1694 strncat(dest, &src[dirlen], maxlen-strlen(dest));
1695 return true;
1696 }
1697 return false;
1698 }
1699
contract_symbolic_paths(char * dest,const char * src,int maxlen)1700 char *contract_symbolic_paths(char *dest, const char *src, int maxlen)
1701 {
1702 bool contracted =
1703 #if defined(HAVE_BUNDLE_NAME)
1704 contract_symbolic_paths_helper(dest, src, maxlen, "$bundle$", bundle_data_dir) ||
1705 #endif
1706 contract_symbolic_paths_helper(dest, src, maxlen, "$local$", local_data_dir) ||
1707 contract_symbolic_paths_helper(dest, src, maxlen, "$default$", default_data_dir);
1708 if (!contracted)
1709 {
1710 strncpy(dest, src, maxlen);
1711 dest[maxlen] = '\0';
1712 }
1713 return dest;
1714 }
1715
1716 // LP: the rest of the code has been moved to Jeremy's shell_misc.file.
1717
PlayInterfaceButtonSound(short SoundID)1718 void PlayInterfaceButtonSound(short SoundID)
1719 {
1720 if (TEST_FLAG(input_preferences->modifiers,_inputmod_use_button_sounds))
1721 SoundManager::instance()->PlaySound(SoundID, (world_location3d *) NULL, NONE);
1722 }
1723