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