1 /**
2  ** Exult.cc - Multiplatform Ultima 7 game engine
3  **
4  ** Written: 7/22/98 - JSF
5  **/
6 /*
7  *  Copyright (C) 1998-1999  Jeffrey S. Freedman
8  *  Copyright (C) 2000-2013  The Exult Team
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  (at your option) any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program; if not, write to the Free Software
22  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #  include <config.h>
27 #endif
28 
29 #include <cstdlib>
30 #include <cctype>
31 #include <cmath>
32 
33 #include <SDL.h>
34 
35 #define Font _XFont_
36 #include <SDL_syswm.h>
37 #undef Font
38 
39 #ifdef USE_EXULTSTUDIO  /* Only needed for communication with exult studio */
40 #if HAVE_SYS_TIME_H
41 #include <sys/time.h>
42 #endif
43 #ifdef _WIN32
44 #include "windrag.h"
45 #endif
46 #include "servemsg.h"
47 #include "objserial.h"
48 #include "server.h"
49 #include "chunks.h"
50 #include "chunkter.h"
51 #endif
52 
53 #if (defined(USECODE_DEBUGGER) && defined(XWIN))
54 #include <csignal>
55 #endif
56 
57 #include "Audio.h"
58 #include "Configuration.h"
59 #include "Gump_manager.h"
60 #include "Scroll_gump.h"
61 #include "actors.h"
62 #include "args.h"
63 #include "cheat.h"
64 #include "effects.h"
65 #include "exult.h"
66 #include "exultmenu.h"
67 #include "fnames.h"
68 #include "font.h"
69 #include "game.h"
70 #include "gamewin.h"
71 #include "gamemap.h"
72 #include "gump_utils.h"
73 #include "keyactions.h"
74 #include "keys.h"
75 #include "mouse.h"
76 #include "ucmachine.h"
77 #include "utils.h"
78 #include "version.h"
79 #include "u7drag.h"
80 #include "drag.h"
81 #include "palette.h"
82 #include "combat_opts.h"
83 #include "U7file.h"
84 #include "U7fileman.h"
85 #include "party.h"
86 #include "array_size.h"
87 
88 #include "exult_flx.h"
89 #include "exult_bg_flx.h"
90 #include "exult_si_flx.h"
91 #include "crc.h"
92 #include "items.h"
93 #include "gamemgr/modmgr.h"
94 #include "AudioMixer.h"
95 #include "VideoOptions_gump.h"
96 #include "Gump_button.h"
97 #include "ShortcutBar_gump.h"
98 #include "ignore_unused_variable_warning.h"
99 #include "touchui.h"
100 #include "verify.h"
101 using namespace Pentagram;
102 
103 #ifdef __IPHONEOS__
104 #  include "ios_utils.h"
105 #endif
106 
107 using std::atof;
108 using std::cerr;
109 using std::cout;
110 using std::endl;
111 using std::atexit;
112 using std::exit;
113 using std::toupper;
114 using std::string;
115 using std::vector;
116 
117 #if (defined(_WIN32) || (defined(MACOSX) && defined(USE_EXULTSTUDIO)))
118 
SDL_putenv(const char * _var)119 static int SDLCALL SDL_putenv(const char *_var) {
120     char *ptr = nullptr;
121     char *var = SDL_strdup(_var);
122     if (var == nullptr) {
123         return -1;  /* we don't set errno. */
124     }
125 
126     ptr = SDL_strchr(var, '=');
127     if (ptr == nullptr) {
128         SDL_free(var);
129         return -1;
130     }
131 
132     *ptr = '\0';  /* split the string into name and value. */
133     SDL_setenv(var, ptr + 1, 1);
134     SDL_free(var);
135     return 0;
136 }
137 #endif
138 
139 Configuration *config = nullptr;
140 KeyBinder *keybinder = nullptr;
141 GameManager *gamemanager = nullptr;
142 
143 /*
144  *  Globals:
145  */
146 Game_window *gwin = nullptr;
147 quitting_time_enum quitting_time = QUIT_TIME_NO;
148 
149 bool intrinsic_trace = false;       // Do we trace Usecode-intrinsics?
150 int usecode_trace = 0;      // Do we trace Usecode-instructions?
151 // 0 = no, 1 = short, 2 = long
152 bool combat_trace = false; // show combat messages?
153 
154 // Save game compression level
155 int save_compression = 1;
156 bool ignore_crc = false;
157 
158 TouchUI *touchui = nullptr;
159 
160 bool g_waiting_for_click = false;
161 ShortcutBar_gump *g_shortcutBar = nullptr;
162 
163 #ifdef USECODE_DEBUGGER
164 bool    usecode_debugging = false;  // Do we enable the usecode debugger?
165 extern void initialise_usecode_debugger();
166 #endif
167 
168 struct resolution {
169 	int x;
170 	int y;
171 	int scale;
172 } res_list[] = {
173 	{ 320, 200, 1 },
174 	{ 320, 240, 1 },
175 	{ 400, 300, 1 },
176 	{ 320, 200, 2 },
177 	{ 320, 240, 2 },
178 	{ 400, 300, 2 },
179 	{ 512, 384, 1 },
180 	{ 640, 480, 1 },
181 	{ 800, 600, 1 }
182 };
183 int num_res = array_size(res_list);
184 int current_res = 0;
185 int current_scaleval = 1;
186 
187 #ifdef _WIN32
188 static HWND hgwin;
189 static Windnd *windnd = nullptr;
190 #endif
191 
192 
193 /*
194  *  Local functions:
195  */
196 static int exult_main(const char * runpath);
197 static void Init();
198 static int Play();
199 static bool Get_click(int &x, int &y, char *chr, bool drag_ok, bool rotate_colors = false);
200 static int find_resolution(int w, int h, int s);
201 static void set_scaleval(int new_scaleval);
202 #ifdef USE_EXULTSTUDIO
203 static void Move_dragged_shape(int shape, int frame, int x, int y,
204                                int prevx, int prevy, bool show);
205 #ifdef _WIN32
206 static void Move_dragged_combo(int xtiles, int ytiles, int tiles_right,
207                                int tiles_below, int x, int y, int prevx, int prevy, bool show);
208 #endif
209 static void Drop_dragged_shape(int shape, int frame, int x, int y);
210 static void Drop_dragged_chunk(int chunknum, int x, int y);
211 static void Drop_dragged_npc(int npcnum, int x, int y);
212 static void Drop_dragged_combo(int cnt, U7_combo_data *combo, int x, int y);
213 #endif
214 static void BuildGameMap(BaseGameInfo *game, int mapnum);
215 static void Handle_events();
216 static void Handle_event(SDL_Event &event);
217 
218 /*
219  *  Statics:
220  */
221 static bool run_bg = false;     // skip menu and run bg
222 static bool run_si = false;     // skip menu and run si
223 static bool run_fov = false;        // skip menu and run fov
224 static bool run_ss = false;     // skip menu and run ss
225 static bool run_sib = false;        // skip menu and run sib
226 static string arg_gamename = "default"; // cmdline arguments
227 static string arg_modname = "default";  // cmdline arguments
228 static string arg_configfile;
229 static int arg_buildmap = -1;
230 static int arg_mapnum = -1;
231 static bool arg_nomenu = false;
232 static bool arg_edit_mode = false;  // Start up ExultStudio.
233 static bool arg_write_xml = false;  // Write out game's config. as XML.
234 static bool arg_reset_video = false;    // Resets the video setings.
235 static bool arg_verify_files = false;    // Verify a game's files.
236 
237 static bool dragging = false;       // Object or gump being moved.
238 static bool dragged = false;        // Flag for when obj. moved.
239 static bool right_on_gump = false;  // Right clicked on gump?
240 static int show_items_x = 0, show_items_y = 0;
241 static unsigned int show_items_time = 0;
242 static bool show_items_clicked = false;
243 static int left_down_x = 0, left_down_y = 0;
244 static int joy_aim_x = 0, joy_aim_y = 0;
245 Mouse::Avatar_Speed_Factors joy_speed_factor = Mouse::medium_speed_factor;
246 
247 #if defined _WIN32
do_cleanup_output()248 void do_cleanup_output() {
249 	cleanup_output("std");
250 }
251 #endif
252 
253 /*
254  *  Main program.
255  */
256 
main(int argc,char * argv[])257 int main(
258     int argc,
259     char *argv[]
260 ) {
261 	bool    needhelp = false;
262 	bool    showversion = false;
263 	int     result;
264 	Args    parameters;
265 
266 	// Declare everything from the commandline that we're interested in.
267 	parameters.declare("-h", &needhelp, true);
268 	parameters.declare("--help", &needhelp, true);
269 	parameters.declare("/?", &needhelp, true);
270 	parameters.declare("/h", &needhelp, true);
271 	parameters.declare("--bg", &run_bg, true);
272 	parameters.declare("--si", &run_si, true);
273 	parameters.declare("--fov", &run_fov, true);
274 	parameters.declare("--ss", &run_ss, true);
275 	parameters.declare("--sib", &run_sib, true);
276 	parameters.declare("--nomenu", &arg_nomenu, true);
277 	parameters.declare("-v", &showversion, true);
278 	parameters.declare("--version", &showversion, true);
279 	parameters.declare("--game", &arg_gamename, "default");
280 	parameters.declare("--mod", &arg_modname, "default");
281 	parameters.declare("--buildmap", &arg_buildmap, -1);
282 	parameters.declare("--mapnum", &arg_mapnum, -1);
283 	parameters.declare("--nocrc", &ignore_crc, true);
284 	parameters.declare("-c", &arg_configfile, "");
285 	parameters.declare("--edit", &arg_edit_mode, true);
286 	parameters.declare("--write-xml", &arg_write_xml, true);
287 	parameters.declare("--reset-video", &arg_reset_video, true);
288 	parameters.declare("--verify-files", &arg_verify_files, true);
289 #if defined _WIN32
290 	bool portable = false;
291 	parameters.declare("-p", &portable, true);
292 #endif
293 	// Process the args
294 	parameters.process(argc, argv);
295 	add_system_path("<alt_cfg>", arg_configfile);
296 #if defined _WIN32
297 	if (portable)
298 		add_system_path("<HOME>", ".");
299 	setup_program_paths();
300 	redirect_output("std");
301 	std::atexit(do_cleanup_output);
302 #endif
303 
304 	if (needhelp) {
305 		cerr << "Usage: exult [--help|-h] [-v|--version] [-c configfile]" << endl
306 		     << "             [--bg|--fov|--si|--ss|--sib|--game <game>] [--mod <mod>]" << endl
307 		     << "             [--nomenu] [--buildmap 0|1|2] [--mapnum <num>]" << endl
308 		     << "             [--nocrc] [--edit] [--write-xml] [--reset-video]" << endl
309 		     << "--help\t\tShow this information" << endl
310 		     << "--version\tShow version info" << endl
311 		     << " -c configfile\tSpecify alternate config file" << endl
312 		     << "--bg\t\tSkip menu and run Black Gate (prefers original game)" << endl
313 		     << "--fov\t\tSkip menu and run Black Gate with Forge of Virtue expansion" << endl
314 		     << "--si\t\tSkip menu and run Serpent Isle (prefers original game)" << endl
315 		     << "--ss\t\tSkip menu and run Serpent Isle with Silver Seed expansion" << endl
316 		     << "--sib\t\tSkip menu and run Serpent Isle Beta" << endl
317 		     << "--nomenu\tSkip BG/SI game menu" << endl
318 		     << "--game <game>\tSkip menu and run the game with key '<game>' in Exult.cfg" << endl
319 		     << "--mod <mod>\tMust be used together with '--bg', '--fov', '--si', '--ss', '--sib' or" << endl
320 		     << "\t\t'--game <game>'; runs the specified game using the mod with" << endl
321 		     << "\t\ttitle equal to '<mod>'" << endl
322 		     << "--buildmap <N>\tCreate a fullsize map of the game world in u7map??.pcx" << endl
323 		     << "\t\t(N=0: all roofs, 1: no level 2 roofs, 2: no roofs)" << endl
324 		     << "\t\tOnly valid if used together with '--bg', '--fov', '--si', '--ss', '--sib'" << endl
325 		     << "\t\tor '--game <game>'; you may optionally specify a mod with" << endl
326 		     << "\t\t'--mod <mod>' (WARNING: requires big amounts of RAM, HD" << endl
327 		     << "\t\tspace and time!)" << endl
328 		     << "--mapnum <N>\tThis must be used with '--buildmap'. Selects which map" << endl
329 		     << "\t\t(for multimap games or mods) whose map is desired" << endl
330 		     << "--nocrc\t\tDon't check crc's of .flx files" << endl
331 		     << "--verify-files\tVerifies that the files in static dir are not corrupt" << endl
332 		     << "\t\tOnly valid if used together with '--bg', '--fov', '--si', '--ss', '--sib'" << endl
333 		     << "\t\tor '--game <game>'; you cannot specify a mod with this flag" << endl
334 		     << "--edit\t\tStart in map-edit mode" << endl;
335 #if defined _WIN32
336 		cerr << " -p\t\tMakes the home path the Exult directory (old Windows way)" << endl;
337 #endif
338 		cerr << "--write-xml\tWrite 'patch/exultgame.xml'" << endl
339 		     << "--reset-video\tResets to the default video settings" << endl;
340 
341 		exit(1);
342 	}
343 	unsigned gameparam = static_cast<unsigned>(run_bg)
344 	                     + static_cast<unsigned>(run_si)
345 	                     + static_cast<unsigned>(run_fov)
346 	                     + static_cast<unsigned>(run_ss)
347 	                     + static_cast<unsigned>(run_sib)
348 	                     + static_cast<unsigned>(arg_gamename != "default");
349 	if (gameparam > 1) {
350 		cerr << "Error: You may only specify one of --bg, --fov, --si, --ss, --sib or --game!" <<
351 		     endl;
352 		exit(1);
353 	} else if (arg_buildmap >= 0 && gameparam == 0) {
354 		cerr << "Error: --buildmap requires one of --bg, --fov, --si, --ss, --sib or --game!" <<
355 		     endl;
356 		exit(1);
357 	} else if (arg_verify_files && gameparam == 0) {
358 		cerr << "Error: --verify-files requires one of --bg, --fov, --si, --ss, --sib or --game!" <<
359 		     endl;
360 		exit(1);
361 	}
362 
363 	if (arg_mapnum >= 0 && arg_buildmap < 0) {
364 		cerr << "Error: '--mapnum' requires '--buildmap'!" << endl;
365 		exit(1);
366 	} else if (arg_mapnum < 0)
367 		arg_mapnum = 0;     // Sane default.
368 
369 	if (arg_modname != "default" && !gameparam) {
370 		cerr << "Error: You must also specify the game to be used!" << endl;
371 		exit(1);
372 	} else if (arg_verify_files && arg_modname != "default") {
373 		cerr << "Error: You cannot combine --mod with --verify-files!" << endl;
374 		exit(1);
375 	}
376 
377 	if (showversion) {
378 		getVersionInfo(cerr);
379 		return 0;
380 	}
381 
382 	try {
383 		result = exult_main(argv[0]);
384 	} catch (const quit_exception & /*e*/) {
385 		// Your basic "shutup Valgrind" code.
386 		Free_text();
387 		fontManager.reset();
388 		delete gamemanager;
389 		delete Game_window::get_instance();
390 		delete game;
391 		Audio::Destroy();   // Deinit the sound system.
392 		delete config;
393 		SDL_VideoQuit();
394 		SDL_Quit();
395 		result = 0;
396 	} catch (const exult_exception &e) {
397 		cerr << "============================" << endl <<
398 		     "An exception occured: " << endl <<
399 		     e.what() << endl <<
400 		     "errno: " << e.get_errno() << endl;
401 		if (e.get_errno() != 0)
402 			perror("Error Description");
403 		cerr << "============================" << endl;
404 		result = e.get_errno();
405 	}
406 
407 	return result;
408 }
409 
410 /*
411  *  Main program.
412  */
413 
exult_main(const char * runpath)414 int exult_main(const char *runpath) {
415 	string music_path;
416 	// output version info
417 	getVersionInfo(cout);
418 
419 #ifndef _WIN32
420 	setup_program_paths();
421 #endif
422 	// Read in configuration file
423 	config = new Configuration;
424 
425 	if (!arg_configfile.empty()) {
426 		config->read_abs_config_file(arg_configfile);
427 	} else {
428 		config->read_config_file(USER_CONFIGURATION_FILE);
429 	}
430 
431 	// reset-video command line option
432 	if (arg_reset_video) {
433 		config->set("config/video/display/width", 640, false);
434 		config->set("config/video/display/height", 480, false);
435 		config->set("config/video/game/width", 320, false);
436 		config->set("config/video/game/height", 200, false);
437 		config->set("config/video/scale", 2, false);
438 		config->set("config/video/scale_method", "2xSaI" , false);
439 		config->set("config/video/fill_mode", "center", false);
440 		config->set("config/video/fill_scaler", "Bilinear", false);
441 		config->set("config/video/share_video_settings", "yes", false);
442 		config->set("config/video/fullscreen", "no", false);
443 		config->set("config/video/force_bpp", 0, false);
444 
445 		config->write_back();
446 	}
447 	if (config->key_exists("config/gameplay/allow_double_right_move")) {
448 		string str;
449 		config->value("config/gameplay/allow_double_right_move", str, "yes");
450 		if (str == "no") {
451 			config->value("config/gameplay/allow_right_pathfind", str, "no");
452 			config->set("config/gameplay/allow_right_pathfind", str, false);
453 		}
454 		config->remove("config/gameplay/allow_double_right_move", false);
455 	}
456 
457 	// Setup virtual directories
458 	string data_path;
459 	config->value("config/disk/data_path", data_path, EXULT_DATADIR);
460 	setup_data_dir(data_path, runpath);
461 
462 	std::string default_music = get_system_path("<DATA>/music");
463 	config->value("config/disk/music_path", music_path, default_music.c_str());
464 
465 	add_system_path("<MUSIC>", music_path);
466 	add_system_path("<STATIC>", "static");
467 	add_system_path("<GAMEDAT>", "gamedat");
468 	add_system_path("<PATCH>", "patch");
469 //	add_system_path("<SAVEGAME>", "savegame");
470 	add_system_path("<SAVEGAME>", ".");
471 	add_system_path("<MODS>", "mods");
472 
473 	std::cout << "Exult path settings:" << std::endl;
474 #ifdef __IPHONEOS__
475 	std::cout << "Bundle        : " << get_system_path("<BUNDLE>") << std::endl;
476 #endif
477 	std::cout << "Data          : " << get_system_path("<DATA>") << std::endl;
478 	std::cout << "Digital music : " << get_system_path("<MUSIC>") << std::endl;
479 	std::cout << std::endl;
480 
481 	// Check CRCs of our .flx files
482 	bool crc_ok = true;
483 	const char *flexname = BUNDLE_CHECK(BUNDLE_EXULT_FLX, EXULT_FLX);
484 	uint32 crc = crc32(flexname);
485 	if (crc != EXULT_FLX_CRC32) {
486 		crc_ok = false;
487 		cerr << "exult.flx has a wrong checksum!" << endl;
488 	}
489 	flexname = BUNDLE_CHECK(BUNDLE_EXULT_BG_FLX, EXULT_BG_FLX);
490 	if (crc32(flexname) != EXULT_BG_FLX_CRC32) {
491 		crc_ok = false;
492 		cerr << "exult_bg.flx has a wrong checksum!" << endl;
493 	}
494 	flexname = BUNDLE_CHECK(BUNDLE_EXULT_SI_FLX, EXULT_SI_FLX);
495 	if (crc32(flexname) != EXULT_SI_FLX_CRC32) {
496 		crc_ok = false;
497 		cerr << "exult_si.flx has a wrong checksum!" << endl;
498 	}
499 
500 	bool config_ignore_crc;
501 	config->value("config/disk/no_crc", config_ignore_crc);
502 	ignore_crc |= config_ignore_crc;
503 
504 	if (!ignore_crc && !crc_ok) {
505 		cerr << "This usually means the file(s) mentioned above are "
506 		     << "from a different version" << endl
507 		     << "of Exult than this one. Please re-install Exult" << endl
508 		     << endl
509 		     << "(Note: if you modified the .flx files yourself, "
510 		     << "you can skip this check" << endl
511 		     << "by passing the --nocrc parameter.)" << endl;
512 
513 		return 1;
514 	}
515 
516 
517 	// Convert from old format if needed
518 	vector<string> vs = config->listkeys("config/disk/game", false);
519 	if (vs.empty() && config->key_exists("config/disk/u7path")) {
520 		// Convert from the older format
521 		string data_directory;
522 		config->value("config/disk/u7path", data_directory, "./blackgate");
523 		config->remove("config/disk/u7path", false);
524 		config->set("config/disk/game/blackgate/path", data_directory, true);
525 	}
526 
527 	// Enable tracing of intrinsics?
528 	config->value("config/debug/trace/intrinsics", intrinsic_trace);
529 
530 	// Enable tracing of UC-instructions?
531 	string uctrace;
532 	config->value("config/debug/trace/usecode", uctrace, "no");
533 	to_uppercase(uctrace);
534 	if (uctrace == "YES")
535 		usecode_trace = 1;
536 	else if (uctrace == "VERBOSE")
537 		usecode_trace = 2;
538 	else
539 		usecode_trace = 0;
540 
541 	config->value("config/debug/trace/combat", combat_trace);
542 
543 	// Save game compression level
544 	config->value("config/disk/save_compression_level", save_compression, 1);
545 	if (save_compression < 0 || save_compression > 2) save_compression = 1;
546 	config->set("config/disk/save_compression_level", save_compression, false);
547 #ifdef USECODE_DEBUGGER
548 	// Enable usecode debugger
549 	config->value("config/debug/debugger/enable", usecode_debugging);
550 	initialise_usecode_debugger();
551 #endif
552 
553 #if (defined(USECODE_DEBUGGER) && defined(XWIN))
554 	signal(SIGUSR1, SIG_IGN);
555 #endif
556 
557 	cheat.init();
558 
559 #ifdef __IPHONEOS__
560 	touchui = new TouchUI_iOS();
561 #endif
562 	Init();             // Create main window.
563 
564 	cheat.finish_init();
565 	cheat.set_map_editor(arg_edit_mode);    // Start in map-edit mode?
566 	if (arg_write_xml)
567 		game->write_game_xml();
568 
569 	Mouse::mouse = new Mouse(gwin);
570 	Mouse::mouse->set_shape(Mouse::hand);
571 
572 	if (touchui != nullptr) {
573 		touchui->showButtonControls();
574 		// TODO(Marzo): This is probably the wrong place for this
575 		Usecode_machine *usecode = Game_window::get_instance()->get_usecode();
576 		if (!usecode->get_global_flag(Usecode_machine::did_first_scene) && GAME_BG) {
577 			touchui->hideGameControls();
578 		} else {
579 			touchui->showGameControls();
580 		}
581 	}
582 
583 	int result = Play();        // start game
584 
585 #ifdef USE_EXULTSTUDIO
586 	// Currently, leaving the game results in destruction of the window.
587 	//  Maybe sometime in the future, there is an option like "return to
588 	//  main menu and select another scenario". Becaule DnD isn't registered until
589 	//  you really enter the game, we remove it here to prevent possible bugs
590 	//  invilved with registering DnD a second time over an old variable.
591 #if defined(_WIN32)
592 	RevokeDragDrop(hgwin);
593 	windnd->Release();
594 #endif
595 	Server_close();
596 #endif
597 
598 	return result;
599 }
600 
601 namespace ExultIcon {
602 #include "exulticon.h"
603 }
604 
SetIcon()605 static void SetIcon() {
606 #ifndef MACOSX      // Don't set icon on OS X; the external icon is *much* nicer
607 	SDL_Color iconpal[256];
608 	for (int i = 0; i < 256; ++i) {
609 		iconpal[i].r = ExultIcon::header_data_cmap[i][0];
610 		iconpal[i].g = ExultIcon::header_data_cmap[i][1];
611 		iconpal[i].b = ExultIcon::header_data_cmap[i][2];
612 	}
613 	SDL_Surface *iconsurface = SDL_CreateRGBSurface(0,
614 				ExultIcon::width,
615 				ExultIcon::height,
616 				32,
617 				0, 0, 0, 0);
618 	if (iconsurface == nullptr)
619 		cout << "Error creating icon surface: " << SDL_GetError() << std::endl;
620 	for (int y = 0; y < static_cast<int>(ExultIcon::height); ++y)
621 	{
622 		for (int x = 0; x < static_cast<int>(ExultIcon::width); ++x)
623 		{
624 			int idx = ExultIcon::header_data[(y*ExultIcon::height)+x];
625 			Uint32 pix = SDL_MapRGB(iconsurface->format,
626 					iconpal[idx].r,
627 					iconpal[idx].g,
628 					iconpal[idx].b);
629 			SDL_Rect destRect = {x, y, 1, 1};
630 			SDL_FillRect(iconsurface, &destRect, pix);
631 		}
632 	}
633 	SDL_SetColorKey(iconsurface, SDL_TRUE,
634 		SDL_MapRGB(iconsurface->format,
635 			iconpal[0].r, iconpal[0].g, iconpal[0].b));
636 	SDL_SetWindowIcon(gwin->get_win()->get_screen_window(), iconsurface);
637 	SDL_FreeSurface(iconsurface);
638 #endif
639 }
640 
Open_game_controller(int joystick_index)641 void Open_game_controller(int joystick_index) {
642 	SDL_GameController *input_device = SDL_GameControllerOpen(joystick_index);
643 	if (input_device) {
644 		SDL_GameControllerGetJoystick(input_device);
645 		std::cout << "Game controller attached and open: \""
646 		          << SDL_GameControllerName(input_device) << '"' << std::endl;
647 	} else {
648 		std::cout << "Game controller attached, but it failed to open. Error: \""
649 		          << SDL_GetError() << '"' << std::endl;
650 	}
651 }
652 
Handle_device_connection_event(void * userdata,SDL_Event * event)653 int Handle_device_connection_event(void *userdata, SDL_Event *event) {
654 	ignore_unused_variable_warning(userdata);
655 	// Make sure that game-controllers are opened and closed, as they
656 	// become connected or disconnected.
657 	switch (event->type) {
658 		case SDL_CONTROLLERDEVICEADDED: {
659 			SDL_JoystickID joystick_id = SDL_JoystickGetDeviceInstanceID(event->cdevice.which);
660 			if (!SDL_GameControllerFromInstanceID(joystick_id)) {
661 				Open_game_controller(event->cdevice.which);
662 			}
663 			break;
664 		}
665 		case SDL_CONTROLLERDEVICEREMOVED: {
666 			SDL_GameController *input_device = SDL_GameControllerFromInstanceID(event->cdevice.which);
667 			if (input_device) {
668 				SDL_GameControllerClose(input_device);
669 				input_device = nullptr;
670 				std::cout << "Game controller detached and closed." << std::endl;
671 			}
672 			break;
673 		}
674 	}
675 
676 	// Returning 1 will tell SDL2, which can invoke this via a callback
677 	// setup through SDL_AddEventWatch, to make sure the event gets posted
678 	// to its event-queue (rather than dropping it).
679 	return 1;
680 }
681 
682 /*
683  *  Initialize and create main window.
684  */
Init()685 static void Init(
686 ) {
687 	Uint32 init_flags = SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER;
688 #ifdef NO_SDL_PARACHUTE
689 	init_flags |= SDL_INIT_NOPARACHUTE;
690 #endif
691 #ifdef _WIN32
692 	SDL_putenv("SDL_AUDIODRIVER=DirectSound");
693 #elif defined(MACOSX) && defined(XWIN) && defined(USE_EXULTSTUDIO)
694 	// Exult Studio drag'n'drop with SDL2 < 2.0.15 requires Exult
695 	// to use X11. Hence, we force the issue.
696 	SDL_putenv("SDL_VIDEODRIVER=x11");
697 #endif
698 	SDL_SetHint(SDL_HINT_ORIENTATIONS, "Landscape");
699 #if 0
700 	init_flags |= SDL_INIT_JOYSTICK;
701 #endif
702 #ifdef __IPHONEOS__
703 	Mouse::use_touch_input = true;
704 	SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "2");
705 #endif
706 	if (SDL_Init(init_flags) < 0) {
707 		cerr << "Unable to initialize SDL: " << SDL_GetError() << endl;
708 		exit(-1);
709 	}
710 	std::atexit(SDL_Quit);
711 
712 	SDL_SysWMinfo info;     // Get system info.
713 
714 	// KBD repeat should be nice.
715 	SDL_ShowCursor(0);
716 	SDL_VERSION(&info.version);
717 
718 	// Open any connected game controllers.
719 	for (int i = 0, n = SDL_NumJoysticks(); i < n; ++i) {
720 		if (SDL_IsGameController(i)) {
721 			Open_game_controller(i);
722 		}
723 	}
724 	// Listen for game controller device connection and disconnection
725 	// events. Registering a listener allows these events to be received
726 	// and processed via any event-processing loop, of which Exult has
727 	// many, without needing to modify each individual loop, and to
728 	// make sure that SDL_GameController objects are always ready.
729 	SDL_AddEventWatch(Handle_device_connection_event, nullptr);
730 
731 	// Load games and mods; also stores system paths:
732 	gamemanager = new GameManager();
733 
734 	if (arg_buildmap < 0 && arg_verify_files == false) {
735 		string gr;
736 		string gg;
737 		string gb;
738 		config->value("config/video/gamma/red", gr, "1.0");
739 		config->value("config/video/gamma/green", gg, "1.0");
740 		config->value("config/video/gamma/blue", gb, "1.0");
741 		Image_window8::set_gamma(atof(gr.c_str()),
742 		                         atof(gg.c_str()),
743 		                         atof(gb.c_str()));
744 		string  fullscreenstr;      // Check config. for fullscreen mode.
745 		config->value("config/video/fullscreen", fullscreenstr, "no");
746 		bool    fullscreen = (fullscreenstr == "yes");
747 		config->set("config/video/fullscreen", fullscreen ? "yes" : "no", false);
748 
749 		int border_red;
750 		int border_green;
751 		int border_blue;
752 		config->value("config/video/game/border/red", border_red, 0);
753 		if (border_red < 0) border_red = 0;
754 		else if (border_red > 255) border_red = 255;
755 		config->set("config/video/game/border/red", border_red, false);
756 
757 		config->value("config/video/game/border/green", border_green, 0);
758 		if (border_green < 0) border_green = 0;
759 		else if (border_green > 255) border_green = 255;
760 		config->set("config/video/game/border/green", border_green, false);
761 
762 		config->value("config/video/game/border/blue", border_blue, 0);
763 		if (border_blue < 0) border_blue = 0;
764 		else if (border_blue > 255) border_blue = 255;
765 		config->set("config/video/game/border/blue", border_blue, false);
766 
767 		Palette::set_border(border_red, border_green, border_blue);
768 		bool disable_fades;
769 		config->value("config/video/disable_fades", disable_fades, false);
770 
771 		setup_video(fullscreen, VIDEO_INIT);
772 		SetIcon();
773 		Audio::Init();
774 		gwin->get_pal()->set_fades_enabled(!disable_fades);
775 		gwin->set_in_exult_menu(false);
776 	}
777 
778 	SDL_SetEventFilter(nullptr, nullptr);
779 	// Show the banner
780 	game = nullptr;
781 
782 	do {
783 		reset_system_paths();
784 		fontManager.reset();
785 		U7FileManager::get_ptr()->reset();
786 
787 		if (game) {
788 			delete game;
789 			game = nullptr;
790 		}
791 
792 		ModManager *basegame = nullptr;
793 		if (run_bg) {
794 			basegame = gamemanager->get_bg();
795 			arg_gamename = CFG_BG_NAME;
796 			run_bg = false;
797 		} else if (run_fov) {
798 			basegame = gamemanager->get_fov();
799 			arg_gamename = CFG_FOV_NAME;
800 			run_fov = false;
801 		} else if (run_si) {
802 			basegame = gamemanager->get_si();
803 			arg_gamename = CFG_SI_NAME;
804 			run_si = false;
805 		} else if (run_ss) {
806 			basegame = gamemanager->get_ss();
807 			arg_gamename = CFG_SS_NAME;
808 			run_ss = false;
809 		} else if (run_sib) {
810 			basegame = gamemanager->get_sib();
811 			arg_gamename = CFG_SIB_NAME;
812 			run_sib = false;
813 		}
814 		BaseGameInfo *newgame = nullptr;
815 		if (basegame || arg_gamename != "default") {
816 			if (!basegame)
817 				basegame = gamemanager->find_game(arg_gamename);
818 			if (basegame) {
819 				if (arg_modname != "default")
820 					// Prints error messages:
821 					newgame = basegame->get_mod(arg_modname);
822 				else
823 					newgame = basegame;
824 				arg_modname = "default";
825 			} else {
826 				cerr << "Game '" << arg_gamename << "' not found." << endl;
827 				newgame = nullptr;
828 			}
829 			// Prevent game from being reloaded in case the player
830 			// tries to return to the main menu:
831 			arg_gamename = "default";
832 		}
833 		if (!newgame) {
834 			ExultMenu exult_menu(gwin);
835 			newgame = exult_menu.run();
836 		}
837 		assert(newgame != nullptr);
838 
839 		if (arg_buildmap >= 0) {
840 			BuildGameMap(newgame, arg_mapnum);
841 			exit(0);
842 		}
843 		if (arg_verify_files) {
844 			newgame->setup_game_paths();
845 			exit(verify_files(newgame));
846 		}
847 
848 		Game::create_game(newgame);
849 		Audio *audio = Audio::get_ptr();
850 		audio->Init_sfx();
851 		MyMidiPlayer *midi = audio->get_midi();
852 
853 		Setup_text(GAME_SI, Game::has_expansion(), GAME_SIB);
854 
855 		// Skip splash screen?
856 		bool skip_splash;
857 		config->value("config/gameplay/skip_splash", skip_splash);
858 
859 		// Make sure we have a proper palette before playing the intro.
860 		gwin->get_pal()->load(BUNDLE_CHECK(BUNDLE_EXULT_FLX, EXULT_FLX),
861 		                      EXULT_FLX_EXULT0_PAL);
862 		gwin->get_pal()->apply();
863 		if (!skip_splash && (Game::get_game_type() != EXULT_DEVEL_GAME
864 		                     || U7exists(INTRO_DAT))) {
865 			if (midi) midi->set_timbre_lib(MyMidiPlayer::TIMBRE_LIB_INTRO);
866 			game->play_intro();
867 			std::cout << "played intro" << std::endl;
868 		}
869 
870 		if (midi) {
871 			if (arg_nomenu)
872 				midi->set_timbre_lib(MyMidiPlayer::TIMBRE_LIB_INTRO);
873 			else
874 				midi->set_timbre_lib(MyMidiPlayer::TIMBRE_LIB_MAINMENU);
875 		}
876 	} while (!game || !game->show_menu(arg_nomenu));
877 
878 	// Should not be needed anymore:
879 	delete gamemanager;
880 	gamemanager = nullptr;
881 
882 	Audio *audio = Audio::get_ptr();
883 	MyMidiPlayer *midi = audio->get_midi();
884 	if (midi) midi->set_timbre_lib(MyMidiPlayer::TIMBRE_LIB_GAME);
885 
886 	gwin->init_files();
887 	gwin->read_gwin();
888 	gwin->setup_game(arg_edit_mode);    // This will start the scene.
889 	// Get scale factor for mouse.
890 #ifdef USE_EXULTSTUDIO
891 #ifndef _WIN32
892 	SDL_GetWindowWMInfo(gwin->get_win()->get_screen_window(), &info);
893 	Server_init();          // Initialize server (for map-editor).
894 	SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
895 #else
896 	SDL_GetWindowWMInfo(gwin->get_win()->get_screen_window(), &info);
897 	hgwin = info.info.win.window;
898 	Server_init();          // Initialize server (for map-editor).
899 	OleInitialize(nullptr);
900 	windnd = new Windnd(hgwin,
901 	                    Move_dragged_shape, Move_dragged_combo,
902 	                    Drop_dragged_shape, Drop_dragged_chunk,
903 	                    Drop_dragged_npc, Drop_dragged_combo);
904 	if (FAILED(RegisterDragDrop(hgwin, windnd))) {
905 		cout << "Something's wrong with OLE2 ..." << endl;
906 	};
907 #endif
908 #endif
909 }
910 
911 /*
912  *  Play game.
913  */
914 
Play()915 static int Play() {
916 	do {
917 		quitting_time = QUIT_TIME_NO;
918 		Handle_events();
919 		if (quitting_time == QUIT_TIME_RESTART) {
920 			Mouse::mouse->hide();   // Turn off mouse.
921 			gwin->read();   // Restart
922 			/////gwin->setup_game();
923 			//// setup_game is already being called from inside
924 			//// of gwin->read(), so no need to call it here, I hope...
925 		}
926 	} while (quitting_time == QUIT_TIME_RESTART);
927 
928 	delete gwin;
929 	delete Mouse::mouse;
930 
931 	Audio::Destroy();   // Deinit the sound system.
932 	delete keybinder;
933 	delete config;
934 	Free_text();
935 	fontManager.reset();
936 	delete game;
937 	return 0;
938 }
939 
940 #ifdef USE_EXULTSTUDIO          // Shift-click means 'paint'.
941 /*
942  *  Add a shape while map-editing.
943  */
944 
Paint_with_shape(SDL_Event & event,bool dragging)945 static void Paint_with_shape(
946     SDL_Event &event,
947     bool dragging           // Painting terrain.
948 ) {
949 	static int lasttx = -1;
950 	static int lastty = -1;
951 	//int scale = gwin->get_win()->get_scale();
952 	//int x = event.button.x/scale, y = event.button.y/scale;
953 	int x;
954 	int y;
955 	gwin->get_win()->screen_to_game(event.button.x, event.button.y, false, x, y);
956 
957 	int tx = (gwin->get_scrolltx() + x / c_tilesize);
958 	int ty = (gwin->get_scrollty() + y / c_tilesize);
959 	if (dragging) {         // See if moving to a new tile.
960 		if (tx == lasttx && ty == lastty)
961 			return;
962 	}
963 	lasttx = tx;
964 	lastty = ty;
965 	int shnum = cheat.get_edit_shape();
966 	int frnum;
967 	SDL_Keymod mod = SDL_GetModState();
968 	if (mod & KMOD_ALT) {   // ALT?  Pick random frame.
969 		ShapeID id(shnum, 0);
970 		frnum = std::rand() % id.get_num_frames();
971 	} else if (mod & KMOD_CTRL) { // Cycle through frames.
972 		frnum = cheat.get_edit_frame();
973 		ShapeID id(shnum, 0);
974 		int nextframe = (frnum + 1) % id.get_num_frames();
975 		cheat.set_edit_shape(shnum, nextframe);
976 	} else
977 		frnum = cheat.get_edit_frame();
978 	Drop_dragged_shape(shnum, frnum, event.button.x, event.button.y);
979 }
980 
981 /*
982  *  Set a complete chunk while map-editing.
983  */
984 
Paint_with_chunk(SDL_Event & event,bool dragging)985 static void Paint_with_chunk(
986     SDL_Event &event,
987     bool dragging           // Painting terrain.
988 ) {
989 	static int lastcx = -1;
990 	static int lastcy = -1;
991 	int x;
992 	int y;
993 	gwin->get_win()->screen_to_game(event.button.x, event.button.y, false, x, y);
994 	int cx = (gwin->get_scrolltx() + x / c_tilesize) / c_tiles_per_chunk;
995 	int cy = (gwin->get_scrollty() + y / c_tilesize) / c_tiles_per_chunk;
996 	if (dragging) {         // See if moving to a new chunk.
997 		if (cx == lastcx && cy == lastcy)
998 			return;
999 	}
1000 	lastcx = cx;
1001 	lastcy = cy;
1002 	int chnum = cheat.get_edit_chunknum();
1003 	Drop_dragged_chunk(chnum, event.button.x, event.button.y);
1004 }
1005 
1006 /*
1007  *  Select chunks.
1008  */
1009 
Select_chunks(SDL_Event & event,bool dragging,bool toggle)1010 static void Select_chunks(
1011     SDL_Event &event,
1012     bool dragging,          // Painting terrain.
1013     bool toggle
1014 ) {
1015 	static int lastcx = -1;
1016 	static int lastcy = -1;
1017 	int x;
1018 	int y;
1019 	gwin->get_win()->screen_to_game(event.button.x, event.button.y, false, x, y);
1020 	int cx = (gwin->get_scrolltx() + x / c_tilesize) / c_tiles_per_chunk;
1021 	int cy = (gwin->get_scrollty() + y / c_tilesize) / c_tiles_per_chunk;
1022 	if (dragging) {         // See if moving to a new chunk.
1023 		if (cx == lastcx && cy == lastcy)
1024 			return;
1025 	}
1026 	lastcx = cx;
1027 	lastcy = cy;
1028 	Map_chunk *chunk = gwin->get_map()->get_chunk(cx, cy);
1029 	if (toggle) {
1030 		if (!chunk->is_selected())
1031 			cheat.add_chunksel(chunk);
1032 		else
1033 			chunk->set_selected(false);
1034 	} else
1035 		cheat.add_chunksel(chunk);
1036 	gwin->set_all_dirty();
1037 }
1038 
1039 /*
1040  *  Select for combo.
1041  */
Select_for_combo(SDL_Event & event,bool dragging,bool toggle)1042 static void Select_for_combo(
1043     SDL_Event &event,
1044     bool dragging,          // Dragging to select.
1045     bool toggle
1046 ) {
1047 	static Game_object *last_obj = nullptr;
1048 	int x;
1049 	int y;
1050 	gwin->get_win()->screen_to_game(event.button.x, event.button.y, false, x, y);
1051 	//int tx = (gwin->get_scrolltx() + x/c_tilesize)%c_num_tiles;
1052 	//int ty = (gwin->get_scrollty() + y/c_tilesize)%c_num_tiles;
1053 	Game_object *obj = gwin->find_object(x, y);
1054 	if (obj) {
1055 		if (dragging && obj == last_obj)
1056 			return;
1057 		last_obj = obj;
1058 	} else
1059 		return;
1060 	ShapeID id = *obj;
1061 	Tile_coord t = obj->get_tile();
1062 	std::string name = get_item_name(id.get_shapenum());
1063 	if (toggle)
1064 		cheat.toggle_selected(obj);
1065 	else if (!cheat.is_selected(obj)) {
1066 		cheat.append_selected(obj);
1067 		gwin->add_dirty(obj);
1068 	}
1069 	if (Object_out(client_socket,
1070 	               toggle ? Exult_server::combo_toggle : Exult_server::combo_pick,
1071 	               nullptr, t.tx, t.ty, t.tz, id.get_shapenum(),
1072 	               id.get_framenum(), 0, name) == -1)
1073 		cout << "Error sending shape to ExultStudio" << endl;
1074 }
1075 
1076 #endif
1077 
1078 /*
1079  *  Handle events until a flag is set.
1080  */
1081 
Handle_events()1082 static void Handle_events(
1083 ) {
1084 	uint32 last_repaint = 0;    // For insuring animation repaints.
1085 	uint32 last_rotate = 0;
1086 	uint32 last_rest = 0;
1087 #ifdef DEBUG
1088 	uint32 last_fps = 0;
1089 #endif
1090 	/*
1091 	 *  Main event loop.
1092 	 */
1093 	int last_x = -1;
1094 	int last_y = -1;
1095 	while (!quitting_time) {
1096 #ifdef USE_EXULTSTUDIO
1097 		Server_delay();     // Handle requests.
1098 #else
1099 		Delay();        // Wait a fraction of a second.
1100 #endif
1101 		// Mouse scale factor
1102 		//int scale = gwin->get_fastmouse() ? 1 :
1103 		//              gwin->get_win()->get_scale();
1104 
1105 		Mouse::mouse->hide();       // Turn off mouse.
1106 		Mouse::mouse_update = false;
1107 
1108 		// Get current time.
1109 		uint32 ticks = SDL_GetTicks();
1110 #if defined(_WIN32) && defined(USE_EXULTSTUDIO)
1111 		if (ticks - Game::get_ticks() < 10) {
1112 			// Reducing processor usage with a slight delay.
1113 			SDL_Delay(10 - (ticks - Game::get_ticks()));
1114 			continue;
1115 		}
1116 #endif
1117 		Game::set_ticks(ticks);
1118 #ifdef DEBUG
1119 		if (last_fps == 0 || ticks >= last_fps + 10000) {
1120 			double fps = (gwin->blits * 1000.0) / (ticks - last_fps);
1121 			cerr << "***#ticks = " << ticks - last_fps <<
1122 			     ", blits = " << gwin->blits << ", ";
1123 			cerr << "FPS:  " << fps << endl;
1124 			last_fps = ticks;
1125 			gwin->blits = 0;
1126 		}
1127 #endif
1128 
1129 		SDL_Event event;
1130 		while (!quitting_time && SDL_PollEvent(&event))
1131 			Handle_event(event);
1132 
1133 		// Animate unless dormant.
1134 		if (gwin->have_focus() && !dragging)
1135 			gwin->get_tqueue()->activate(ticks);
1136 
1137 		// Moved this out of the animation loop, since we want movement to be
1138 		// more responsive. Also, if the step delta is only 1 tile,
1139 		// always check every loop
1140 		if ((!gwin->is_moving() || gwin->get_step_tile_delta() == 1) &&
1141 		        gwin->main_actor_can_act_charmed()) {
1142 			int x;
1143 			int y;// Check for 'stuck' Avatar.
1144 			int ms = SDL_GetMouseState(&x, &y);
1145 			//mouse movement needs to be adjusted for HighDPI
1146 			gwin->get_win()->screen_to_game_hdpi(x, y, gwin->get_fastmouse(), x, y);
1147 			if ((SDL_BUTTON(3) & ms) && !right_on_gump)
1148 				gwin->start_actor(x, y,
1149 				                  Mouse::mouse->avatar_speed);
1150 			else if (ticks > last_rest) {
1151 				int resttime = ticks - last_rest;
1152 				gwin->get_main_actor()->resting(resttime);
1153 
1154 				Party_manager *party_man = gwin->get_party_man();
1155 				int cnt = party_man->get_count();
1156 				for (int i = 0; i < cnt; i++) {
1157 					int party_member = party_man->get_member(i);
1158 					Actor *person = gwin->get_npc(party_member);
1159 					if (!person)
1160 						continue;
1161 					person->resting(resttime);
1162 				}
1163 				last_rest = ticks;
1164 			}
1165 		}
1166 
1167 		// handle delayed showing of clicked items (wait for possible dblclick)
1168 		if (show_items_clicked && ticks > show_items_time) {
1169 			gwin->show_items(show_items_x, show_items_y, false);
1170 			show_items_clicked = false;
1171 		}
1172 
1173 		if (joy_aim_x != 0 || joy_aim_y != 0) {
1174 			// Calculate the player speed
1175 			const int speed = 200 * gwin->get_std_delay() / static_cast<int>(joy_speed_factor);
1176 
1177 			// [re]start moving
1178 			gwin->start_actor(joy_aim_x, joy_aim_y, speed);
1179 		}
1180 
1181 		// Lerping stuff...
1182 		int lerp = gwin->is_lerping_enabled();
1183 		bool didlerp = false;
1184 		if (lerp) {
1185 			// Always repaint,
1186 			Actor *act = gwin->get_camera_actor();
1187 			int mswait = (act->get_frame_time() * lerp) / 100;
1188 			if (mswait <= 0) mswait = (gwin->get_std_delay() * lerp) / 100;
1189 
1190 			// Force a reset if position changed
1191 			if (last_x != gwin->get_scrolltx() || last_y != gwin->get_scrollty()) {
1192 				//printf ("%i: %i -> %i, %i -> %i\n", ticks, last_x, gwin->get_scrolltx(), last_y, gwin->get_scrollty());
1193 				gwin->lerp_reset();
1194 				last_repaint = ticks;
1195 			}
1196 			last_x = gwin->get_scrolltx();
1197 			last_y = gwin->get_scrollty();
1198 
1199 			// Is lerping (smooth scrolling) enabled
1200 			if (mswait && ticks < (last_repaint + mswait * 2)) {
1201 				gwin->paint_lerped(((ticks - last_repaint) * 0x10000) / mswait);
1202 				didlerp = true;
1203 			}
1204 		}
1205 		if (!lerp || !didlerp) { // No lerping
1206 			if (gwin->is_dirty())   // Note the ending else in the above #if!
1207 				gwin->paint_dirty();
1208 			// Reset it for lerping.
1209 			last_x = last_y = -1;
1210 		}
1211 		Mouse::mouse->show();   // Re-display mouse.
1212 		// Rotate less often if scaling and
1213 		//   not paletized.
1214 		int rot_speed = 100 << (gwin->get_win()->fast_palette_rotate() ? 0 : 1);
1215 
1216 		if (ticks > last_rotate + rot_speed) {
1217 			// (Blits in simulated 8-bit mode.)
1218 			gwin->get_win()->rotate_colors(0xfc, 3, 0);
1219 			gwin->get_win()->rotate_colors(0xf8, 4, 0);
1220 			gwin->get_win()->rotate_colors(0xf4, 4, 0);
1221 			gwin->get_win()->rotate_colors(0xf0, 4, 0);
1222 			gwin->get_win()->rotate_colors(0xe8, 8, 0);
1223 			gwin->get_win()->rotate_colors(0xe0, 8, 1);
1224 			while (ticks > last_rotate + rot_speed)
1225 				last_rotate += rot_speed;
1226 			// Non palettized needs explicit blit.
1227 			if (!gwin->get_win()->is_palettized())
1228 				gwin->set_painted();
1229 		}
1230 		if (!gwin->show() &&    // Blit to screen if necessary.
1231 		        Mouse::mouse_update)    // If not, did mouse change?
1232 			Mouse::mouse->blit_dirty();
1233 	}
1234 }
1235 
EnteredWindow(SDL_Event & event)1236 static inline bool EnteredWindow(SDL_Event& event) {
1237 	return event.window.event == SDL_WINDOWEVENT_ENTER;
1238 }
1239 
GainedFocus(SDL_Event & event)1240 static inline bool GainedFocus(SDL_Event& event) {
1241 	return event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED;
1242 }
1243 
LostFocus(SDL_Event & event)1244 static inline bool LostFocus(SDL_Event& event) {
1245 	return event.window.event == SDL_WINDOWEVENT_FOCUS_LOST;
1246 }
1247 
1248 /*
1249  *  Handle an event.  This should work for all platforms, and should only
1250  *  be called in 'normal' and 'gump' modes.
1251  */
1252 
Handle_event(SDL_Event & event)1253 static void Handle_event(
1254     SDL_Event &event
1255 ) {
1256 
1257 	// Mouse scale factor
1258 	bool dont_move_mode = gwin->main_actor_dont_move();
1259 	bool avatar_can_act = gwin->main_actor_can_act();
1260 
1261 	// We want this
1262 	Gump_manager *gump_man = gwin->get_gump_man();
1263 	Gump *gump = nullptr;
1264 
1265 	// For detecting double-clicks.
1266 	static uint32 last_b1_click = 0;
1267 	static uint32 last_b3_click = 0;
1268 	static uint32 last_b1down_click = 0;
1269 	//cout << "Event " << (int) event.type << " received"<<endl;
1270 	switch (event.type) {
1271 
1272 	// Quick saving to make sure no game progress gets lost
1273 	// when the app goes into background
1274 	case SDL_APP_WILLENTERBACKGROUND: {
1275 		Game_window *gwin = Game_window::get_instance();
1276 		try {
1277 			gwin->write();
1278 		} catch (exult_exception &/*e*/) {
1279 			break;
1280 		}
1281 		break;
1282 	}
1283 	case SDL_USEREVENT: {
1284 		if (!dragged) {
1285 			switch (event.user.code) {
1286 				case SHORTCUT_BAR_USER_EVENT: {
1287 					if(g_shortcutBar) // just in case
1288 						g_shortcutBar->onUserEvent(&event);
1289 					break;
1290 				}
1291 				default:
1292 					break;
1293 			}
1294 		}
1295 		dragging = dragged = false;
1296 		break;
1297 	}
1298 	case SDL_CONTROLLERAXISMOTION: {
1299 		// Ignore axis changes on anything but a specific thumb-stick
1300 		// on the game-controller.
1301 		if (!(event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTX ||
1302 			  event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTY)) {
1303 			break;
1304 		}
1305 
1306 		SDL_GameController *input_device = SDL_GameControllerFromInstanceID(event.caxis.which);
1307 		if (input_device && !dont_move_mode && avatar_can_act &&
1308 			gwin->main_actor_can_act_charmed()) {
1309 			auto get_normalized_axis = [input_device](SDL_GameControllerAxis axis){
1310 				return SDL_GameControllerGetAxis(input_device, axis) / static_cast<float>(SDL_JOYSTICK_AXIS_MAX);
1311 			};
1312 			// Collect both of the controller thumb-stick's axis values.
1313 			// The input-event only carries one axis, and each thumb-stick
1314 			// has two axes.  Both axes' values are needed in order to
1315 			// call start_actor.
1316 			float axis_x = get_normalized_axis(SDL_CONTROLLER_AXIS_LEFTX);
1317 			float axis_y = get_normalized_axis(SDL_CONTROLLER_AXIS_LEFTY);
1318 
1319 			// Dead-zone is applied to each axis, X and Y, on the game's 2d plane.
1320 			// All axis readings below this are ignored
1321 			constexpr const float axis_dead_zone = 0.25f;
1322 			// Medium-speed threashold; below this, move slow
1323 			constexpr const float axis_length_move_medium = 0.60f;
1324 			// Fast-speed threashold; above this, move fast
1325 			constexpr const float axis_length_move_fast = 0.90f;
1326 
1327 			// Many analog game-controllers report non-zero axis values,
1328 			// even when the controller isn't moving.  These non-zero
1329 			// values can change over time, as the thumb-stick is moved
1330 			// by the player.
1331 			//
1332 			// In order to prevent idle controller sticks from leading
1333 			// to unwanted movements, axis-values that are small will
1334 			// be ignored.  This is sometimes referred to as a
1335 			// "dead zone".
1336 			if (axis_dead_zone >= std::fabs(axis_x)) {
1337 				axis_x = 0;
1338 			}
1339 			if (axis_dead_zone >= std::fabs(axis_y)) {
1340 				axis_y = 0;
1341 			}
1342 
1343 			// Pick a player's speed-factor, depending on how much the input-stick
1344 			// is being pushed in a direction.
1345 			const float joy_axis_length = std::sqrt((axis_x * axis_x) + (axis_y * axis_y));
1346 			joy_speed_factor = Mouse::fast_speed_factor;
1347 			if (joy_axis_length < axis_length_move_medium) {
1348 				joy_speed_factor = Mouse::slow_speed_factor;
1349 			} else if (joy_axis_length < axis_length_move_fast) {
1350 				joy_speed_factor = Mouse::medium_speed_factor;
1351 			}
1352 
1353 			// Depending on axis values, we're either going to start moving,
1354 			// restarting moving (which looks the same as starting, to us),
1355 			// or stopping.
1356 			if (axis_x == 0 && axis_y == 0) {
1357 				// Both axes are zero, so we'll stop.
1358 				gwin->stop_actor();
1359 				joy_aim_x = 0;
1360 				joy_aim_y = 0;
1361 			} else {
1362 				// At least one axis is non-zero, so we'll [re]start moving.
1363 
1364 				// Declare a position to aim for, in window coordinates, relative
1365 				// to the center of the window (where the player is).
1366 				const float aim_distance = 50.f;
1367 				const int aim_dx = static_cast<int>(std::lround(aim_distance * axis_x));
1368 				const int aim_dy = static_cast<int>(std::lround(aim_distance * axis_y));
1369 				joy_aim_x = (gwin->get_width() / 2) + aim_dx;
1370 				joy_aim_y = (gwin->get_height() / 2) + aim_dy;
1371 			}
1372 		}
1373 		break;
1374 	}
1375 	case SDL_FINGERDOWN: {
1376 		if ((!Mouse::use_touch_input) && (event.tfinger.fingerId != 0)) {
1377 			Mouse::use_touch_input = true;
1378 			gwin->set_painted();
1379 		}
1380 		break;
1381 	}
1382 	case SDL_MOUSEBUTTONDOWN: {
1383 		SDL_SetWindowGrab(gwin->get_win()->get_screen_window(), SDL_TRUE);
1384 		if (dont_move_mode)
1385 			break;
1386 		last_b1down_click = SDL_GetTicks();
1387 		if (g_shortcutBar && g_shortcutBar->handle_event(&event))
1388 			break;
1389 		int x;
1390 		int y;
1391 		gwin->get_win()->screen_to_game(event.button.x, event.button.y, gwin->get_fastmouse(), x, y);
1392 		if (event.button.button == 1) {
1393 			Gump_button *button;
1394 			// Allow dragging only either if the avatar can act or if map edit
1395 			// or hackmove is on.
1396 			if (avatar_can_act || cheat.in_hack_mover() ||
1397 			        ((gump = gump_man->find_gump(x, y, false)) && (button = gump->on_button(x, y)) &&
1398 			         button->is_checkmark())) { // also allow closing parent gump when clicking on checkmark
1399 #ifdef USE_EXULTSTUDIO
1400 				if (cheat.in_map_editor()) {
1401 					// Paint if shift-click.
1402 					if (cheat.get_edit_shape() >= 0 &&
1403 					        // But always if painting.
1404 					        (cheat.get_edit_mode() == Cheat::paint ||
1405 					         (SDL_GetModState() & KMOD_SHIFT))) {
1406 						Paint_with_shape(event, false);
1407 						break;
1408 					} else if (cheat.get_edit_chunknum() >= 0 &&
1409 					           cheat.get_edit_mode() == Cheat::paint_chunks) {
1410 						Paint_with_chunk(event, false);
1411 						break;
1412 					} else if (cheat.get_edit_mode() ==
1413 					           Cheat::select_chunks) {
1414 						Select_chunks(event, false,
1415 						              (SDL_GetModState()&KMOD_CTRL) != 0);
1416 						break;
1417 					} else if (cheat.get_edit_mode() ==
1418 					           Cheat::combo_pick) {
1419 						Select_for_combo(event, false,
1420 						                 (SDL_GetModState()&KMOD_CTRL) != 0);
1421 						break;
1422 					}
1423 					// Don't drag if not in 'move' mode.
1424 					else if (cheat.get_edit_mode() != Cheat::move)
1425 						break;
1426 				}
1427 #endif
1428 				dragging = gwin->start_dragging(x, y);
1429 				//Mouse::mouse->set_shape(Mouse::hand);
1430 				dragged = false;
1431 			}
1432 			left_down_x = x;
1433 			left_down_y = y;
1434 		}
1435 
1436 		// Middle click, use targetting crosshairs.
1437 		if (gwin->get_mouse3rd())
1438 			if (event.button.button == 2)
1439 				ActionTarget(nullptr);
1440 
1441 		// Right click. Only walk if avatar can act.
1442 		if (event.button.button == 3) {
1443 			if (!dragging &&    // Causes crash if dragging.
1444 			        gump_man->can_right_click_close() &&
1445 			        gump_man->gump_mode() &&
1446 			        gump_man->find_gump(x, y, false)) {
1447 				gump = nullptr;
1448 				right_on_gump = true;
1449 			} else if (avatar_can_act && gwin->main_actor_can_act_charmed()) {
1450 				// Try removing old queue entry.
1451 				gwin->get_tqueue()->remove(gwin->get_main_actor());
1452 				gwin->start_actor(x, y, Mouse::mouse->avatar_speed);
1453 			}
1454 		}
1455 		break;
1456 	}
1457 	// two-finger scrolling of view port with SDL2.
1458 	case SDL_FINGERMOTION: {
1459 		if (!cheat() || !gwin->can_scroll_with_mouse()) break;
1460 		static int numFingers = 0;
1461 		SDL_Finger* finger0 = SDL_GetTouchFinger(event.tfinger.touchId, 0);
1462 		if (finger0) {
1463 			numFingers = SDL_GetNumTouchFingers(event.tfinger.touchId);
1464 		}
1465 		if (numFingers > 1) {
1466 			if(event.tfinger.dy < 0) {
1467 				ActionScrollUp(nullptr);
1468 			}
1469 			else if(event.tfinger.dy > 0) {
1470 				ActionScrollDown(nullptr);
1471 			}
1472 			if(event.tfinger.dx > 0) {
1473 				ActionScrollRight(nullptr);
1474 			}
1475 			else if(event.tfinger.dx < 0) {
1476 				ActionScrollLeft(nullptr);
1477 			}
1478 		}
1479 		break;
1480 	}
1481 	// Mousewheel scrolling of view port with SDL2.
1482 	case SDL_MOUSEWHEEL: {
1483 		if (!cheat() || !gwin->can_scroll_with_mouse()) break;
1484 		SDL_Keymod mod = SDL_GetModState();
1485 		if(event.wheel.y > 0) {
1486 			if (mod & KMOD_ALT)
1487 				ActionScrollLeft(nullptr);
1488 			else
1489 				ActionScrollUp(nullptr);
1490 		}
1491 		else if(event.wheel.y < 0) {
1492 			if (mod & KMOD_ALT)
1493 				ActionScrollRight(nullptr);
1494 			else
1495 				ActionScrollDown(nullptr);
1496 		}
1497 		if(event.wheel.x > 0) {
1498 			ActionScrollRight(nullptr);
1499 		}
1500 		else if(event.wheel.x < 0) {
1501 			ActionScrollLeft(nullptr);
1502 		}
1503 		break;
1504 	}
1505 	case SDL_MOUSEBUTTONUP: {
1506 		SDL_SetWindowGrab(gwin->get_win()->get_screen_window(), SDL_FALSE);
1507 		if (dont_move_mode)
1508 			break;
1509 		int x ;
1510 		int y;
1511 		gwin->get_win()->screen_to_game(event.button.x, event.button.y, gwin->get_fastmouse(), x, y);
1512 
1513 		if (event.button.button == 3) {
1514 			uint32 curtime = SDL_GetTicks();
1515 			// If showing gumps, ignore all double-right-click results
1516 			if (gump_man->gump_mode()) {
1517 				if (right_on_gump &&
1518 				        (gump = gump_man->find_gump(x, y, false))) {
1519 					TileRect dirty = gump->get_dirty();
1520 					gwin->add_dirty(dirty);
1521 					gump_man->close_gump(gump);
1522 					gump = nullptr;
1523 					right_on_gump = false;
1524 				}
1525 			} else if (avatar_can_act) {
1526 				// Last right click not within .5 secs (not a doubleclick or rapid right clicking)?
1527 				if (gwin->get_allow_right_pathfind() == 1 && curtime - last_b3_click > 500 && gwin->main_actor_can_act_charmed())
1528 					gwin->start_actor_along_path(x, y,
1529 					                             Mouse::mouse->avatar_speed);
1530 
1531 				// Last right click within .5 secs (doubleclick)?
1532 				else if (gwin->get_allow_right_pathfind() == 2 && curtime - last_b3_click < 500 && gwin->main_actor_can_act_charmed())
1533 					gwin->start_actor_along_path(x, y,
1534 					                             Mouse::mouse->avatar_speed);
1535 
1536 				else {
1537 					gwin->stop_actor();
1538 					if (Combat::is_paused() && gwin->in_combat())
1539 						gwin->paused_combat_select(x, y);
1540 				}
1541 			}
1542 			last_b3_click = curtime;
1543 		} else if (event.button.button == 1) {
1544 			uint32 curtime = SDL_GetTicks();
1545 			bool click_handled = false;
1546 			if (dragging) {
1547 				click_handled = gwin->drop_dragged(x, y, dragged);
1548 				Mouse::mouse->set_speed_cursor();
1549 			}
1550 			if (g_shortcutBar && g_shortcutBar->handle_event(&event))
1551 				break;
1552 			// Last click within .5 secs?
1553 			if (curtime - last_b1_click < 500 &&
1554 			        left_down_x - 1 <= x && x <= left_down_x + 1 &&
1555 			        left_down_y - 1 <= y && y <= left_down_y + 1) {
1556 				dragging = dragged = false;
1557 				// This function handles the trouble of deciding what to
1558 				// do when the avatar cannot act.
1559 				gwin->double_clicked(x, y);
1560 				Mouse::mouse->set_speed_cursor();
1561 				show_items_clicked = false;
1562 				break;
1563 			}
1564 			if (!dragging || !dragged)
1565 				last_b1_click = curtime;
1566 
1567 		    if (gwin->get_touch_pathfind() && !click_handled &&
1568 			        (curtime - last_b1down_click > 500) && avatar_can_act &&
1569 			        gwin->main_actor_can_act_charmed() && !dragging &&
1570 			        !gump_man->find_gump(x, y, false)) {
1571 				gwin->start_actor_along_path(x, y, Mouse::mouse->avatar_speed);
1572 				dragging = dragged = false;
1573 				break;
1574 			}
1575 
1576 			if (!click_handled && avatar_can_act &&
1577 			        left_down_x - 1 <= x && x <= left_down_x + 1 &&
1578 			        left_down_y - 1 <= y && y <= left_down_y + 1) {
1579 				show_items_x = x;
1580 				show_items_y = y;
1581 				// Identify item(s) clicked on.
1582 				if (cheat.in_map_editor())
1583 					gwin->show_items(x, y, (SDL_GetModState() & KMOD_CTRL) != 0);
1584 				else {
1585 					show_items_time = curtime + 500;
1586 					show_items_clicked = true;
1587 				}
1588 			}
1589 			dragging = dragged = false;
1590 		}
1591 		break;
1592 	}
1593 	case SDL_MOUSEMOTION: {
1594 		int mx ;
1595 		int my;
1596 		if ((Mouse::use_touch_input == true) && (event.motion.which != SDL_TOUCH_MOUSEID))
1597 			Mouse::use_touch_input = false;
1598 		gwin->get_win()->screen_to_game(event.motion.x, event.motion.y, gwin->get_fastmouse(), mx, my);
1599 
1600 		Mouse::mouse->move(mx, my);
1601 		if (!dragging)
1602 			Mouse::mouse->set_speed_cursor();
1603 		Mouse::mouse_update = true; // Need to blit mouse.
1604 		if (right_on_gump &&
1605 		        !(gump_man->can_right_click_close() &&
1606 		          gump_man->gump_mode() &&
1607 		          gump_man->find_gump(mx, my, false))) {
1608 			right_on_gump = false;
1609 		}
1610 
1611 		// Dragging with left button?
1612 		if (event.motion.state & SDL_BUTTON(1)) {
1613 #ifdef USE_EXULTSTUDIO          // Painting?
1614 			if (cheat.in_map_editor()) {
1615 				if (cheat.get_edit_shape() >= 0 &&
1616 				        (cheat.get_edit_mode() == Cheat::paint ||
1617 				         (SDL_GetModState() & KMOD_SHIFT))) {
1618 					Paint_with_shape(event, true);
1619 					break;
1620 				} else if (cheat.get_edit_chunknum() >= 0 &&
1621 				           cheat.get_edit_mode() == Cheat::paint_chunks) {
1622 					Paint_with_chunk(event, true);
1623 					break;
1624 				} else if (cheat.get_edit_mode() ==
1625 				           Cheat::select_chunks) {
1626 					Select_chunks(event, true,
1627 					              (SDL_GetModState()&KMOD_CTRL) != 0);
1628 					break;
1629 				} else if (cheat.get_edit_mode() ==
1630 				           Cheat::combo_pick) {
1631 					Select_for_combo(event, true,
1632 					                 (SDL_GetModState()&KMOD_CTRL) != 0);
1633 					break;
1634 				}
1635 			}
1636 #endif
1637 			dragged = gwin->drag(mx, my);
1638 		}
1639 		// Dragging with right?
1640 		else if ((event.motion.state & SDL_BUTTON(3)) && !right_on_gump) {
1641 			if (avatar_can_act && gwin->main_actor_can_act_charmed())
1642 				gwin->start_actor(mx, my, Mouse::mouse->avatar_speed);
1643 		}
1644 #ifdef USE_EXULTSTUDIO          // Painting?
1645 		else if (cheat.in_map_editor() &&
1646 		         cheat.get_edit_shape() >= 0 &&
1647 		         (cheat.get_edit_mode() == Cheat::paint ||
1648 		          (SDL_GetModState() & KMOD_SHIFT))) {
1649 			static int prevx = -1;
1650 			static int prevy = -1;
1651 			Move_dragged_shape(cheat.get_edit_shape(),
1652 			                   cheat.get_edit_frame(),
1653 			                   event.motion.x, event.motion.y,
1654 			                   prevx, prevy, false);
1655 			prevx = event.motion.x;
1656 			prevy = event.motion.y;
1657 		}
1658 #endif
1659 		break;
1660 	}
1661 	case SDL_WINDOWEVENT:
1662 		if (EnteredWindow(event)) {
1663 			int x;
1664 			int y;
1665 			SDL_GetMouseState(&x, &y);
1666 			gwin->get_win()->screen_to_game(x, y, gwin->get_fastmouse(), x, y);
1667 			Mouse::mouse->set_location(x, y);
1668 			gwin->set_painted();
1669 		}
1670 
1671 		if (GainedFocus(event))
1672 			gwin->get_focus();
1673 		else if (LostFocus(event))
1674 			gwin->lose_focus();
1675 		break;
1676 	case SDL_QUIT:
1677 		gwin->get_gump_man()->okay_to_quit();
1678 		break;
1679 	case SDL_KEYDOWN:       // Keystroke.
1680 	case SDL_KEYUP:
1681 		if (!dragging &&    // ESC while dragging causes crashes.
1682 		        !gwin->get_gump_man()->handle_kbd_event(&event))
1683 			keybinder->HandleEvent(event);
1684 		break;
1685 	case SDL_DROPFILE: {
1686 #ifdef USE_EXULTSTUDIO
1687 #ifndef _WIN32
1688 		int x;
1689 		int y;
1690 		SDL_GetMouseState(&x, &y);
1691 #ifdef DEBUG
1692 		cout << "(EXULT) SDL_DROPFILE Event, type = " << event.drop.type
1693 		     << ", file (" << strlen(event.drop.file) << ") = '" << event.drop.file
1694 		     << "', at x = " << x << ", y = " << y << endl;
1695 #endif
1696 		const unsigned char *data = reinterpret_cast<const unsigned char *>(event.drop.file);
1697 		if (Is_u7_shapeid(data) == true) {
1698 			// Get shape info.
1699 			int file, shape, frame;
1700 			Get_u7_shapeid(data, file, shape, frame);
1701 			cout << "(EXULT) SDL_DROPFILE Event, Shape: file = " << file
1702 			     << ", shape = " << shape << ", frame = " << frame << endl;
1703 			if (shape >= 0) {   // Dropping a shape?
1704 				if (file == U7_SHAPE_SHAPES)
1705 					// For now, just allow "shapes.vga".
1706 					Drop_dragged_shape(shape, frame, x, y);
1707 			}
1708 		} else if (Is_u7_chunkid(data) == true) {
1709 			// A whole chunk.
1710 			int chunknum;
1711 			Get_u7_chunkid(data, chunknum);
1712 			cout << "(EXULT) SDL_DROPFILE Event, Chunk: num = " << chunknum << endl;
1713 			if (chunknum >= 0) { // A whole chunk.
1714 				Drop_dragged_chunk(chunknum, x, y);
1715 			}
1716 		} else if (Is_u7_npcid(data) == true) {
1717 			int npcnum;
1718 			Get_u7_npcid(data, npcnum);
1719 			cout << "(EXULT) SDL_DROPFILE Event, Npc: num = " << npcnum << endl;
1720 			if (npcnum >= 0) { // An NPC.
1721 				Drop_dragged_npc(npcnum, x, y);
1722 			}
1723 		} else if (Is_u7_comboid(data) == true) {
1724 			int combo_xtiles, combo_ytiles, combo_tiles_right, combo_tiles_below, combo_cnt;
1725 			U7_combo_data *combo;
1726 			Get_u7_comboid(data, combo_xtiles, combo_ytiles,
1727 			               combo_tiles_right, combo_tiles_below, combo_cnt, combo);
1728 			cout << "(EXULT) SDL_DROPFILE Event, Combo: xtiles = " << combo_xtiles
1729 			     << ", ytiles = " << combo_ytiles << ", tiles_right = " << combo_tiles_right
1730 			     << ", tiles_below = " << combo_tiles_below
1731 			     << ", count = " << combo_cnt << endl;
1732 			if (combo_cnt >= 0 && combo) {
1733 				Drop_dragged_combo(combo_cnt, combo, x, y);
1734 			}
1735 			delete[] combo;
1736 		}
1737 #ifdef DEBUG
1738 		cout << "(EXULT) SDL_DROPFILE Event complete" << endl;
1739 #endif
1740 #endif
1741 #endif
1742 		break;
1743 	}
1744 	}
1745 }
1746 
1747 
1748 /*
1749  *  Wait for a click, or optionally, a kbd. chr.
1750  *
1751  *  Output: false if user hit ESC.
1752  */
Get_click(int & x,int & y,char * chr,bool drag_ok,bool rotate_colors)1753 static bool Get_click(
1754     int &x, int &y,
1755     char *chr,          // Char. returned if not null.
1756     bool drag_ok,           // Okay to drag/close while here.
1757     bool rotate_colors      // If the palette colors should rotate.
1758 ) {
1759 	dragging = false;       // Init.
1760 	uint32 last_rotate = 0;
1761 	g_waiting_for_click = true;
1762 	while (true) {
1763 		SDL_Event event;
1764 		Delay();        // Wait a fraction of a second.
1765 
1766 		uint32 ticks = SDL_GetTicks();
1767 		Game::set_ticks(ticks);
1768 		Mouse::mouse->hide();       // Turn off mouse.
1769 		Mouse::mouse_update = false;
1770 
1771 		if (rotate_colors) {
1772 			int rot_speed = 100 << (gwin->get_win()->fast_palette_rotate() ? 0 : 1);
1773 			if (ticks > last_rotate + rot_speed) {
1774 				// (Blits in simulated 8-bit mode.)
1775 				gwin->get_win()->rotate_colors(0xfc, 3, 0);
1776 				gwin->get_win()->rotate_colors(0xf8, 4, 0);
1777 				gwin->get_win()->rotate_colors(0xf4, 4, 0);
1778 				gwin->get_win()->rotate_colors(0xf0, 4, 0);
1779 				gwin->get_win()->rotate_colors(0xe8, 8, 0);
1780 				gwin->get_win()->rotate_colors(0xe0, 8, 1);
1781 				while (ticks > last_rotate + rot_speed)
1782 					last_rotate += rot_speed;
1783 				// Non palettized needs explicit blit.
1784 				if (!gwin->get_win()->is_palettized())
1785 					gwin->set_painted();
1786 			}
1787 		}
1788 
1789 		// Mouse scale factor
1790 		static bool rightclick;
1791 		while (SDL_PollEvent(&event))
1792 			switch (event.type) {
1793 			case SDL_MOUSEBUTTONDOWN:
1794 				SDL_SetWindowGrab(gwin->get_win()->get_screen_window(), SDL_TRUE);
1795 				if (g_shortcutBar && g_shortcutBar->handle_event(&event))
1796 					break;
1797 				if (event.button.button == 3)
1798 					rightclick = true;
1799 				else if (drag_ok && event.button.button == 1) {
1800 					gwin->get_win()->screen_to_game(event.button.x, event.button.y, gwin->get_fastmouse(), x, y);
1801 					dragging = gwin->start_dragging(x, y);
1802 					dragged = false;
1803 				}
1804 				break;
1805 			case SDL_MOUSEBUTTONUP:
1806 				SDL_SetWindowGrab(gwin->get_win()->get_screen_window(), SDL_FALSE);
1807 				if (g_shortcutBar && g_shortcutBar->handle_event(&event))
1808 					break;
1809 				if (event.button.button == 1) {
1810 					gwin->get_win()->screen_to_game(event.button.x, event.button.y, gwin->get_fastmouse(), x, y);
1811 					bool drg = dragging;
1812 					bool drged = dragged;
1813 					dragging = dragged = false;
1814 					if (!drg ||
1815 					        !gwin->drop_dragged(x, y, drged)) {
1816 						if (chr) *chr = 0;
1817 						g_waiting_for_click = false;
1818 						return true;
1819 					}
1820 				} else if (event.button.button == 3) {
1821 					// Just stop.  Don't get followers!
1822 					gwin->get_main_actor()->stop();
1823 					if (gwin->get_mouse3rd() && rightclick) {
1824 						rightclick = false;
1825 						g_waiting_for_click = false;
1826 						return false;
1827 					}
1828 				}
1829 				break;
1830 			case SDL_MOUSEMOTION: {
1831 				int mx;
1832 				int my;
1833 				gwin->get_win()->screen_to_game(event.motion.x, event.motion.y, gwin->get_fastmouse(), mx, my);
1834 
1835 				Mouse::mouse->move(mx, my);
1836 				Mouse::mouse_update = true;
1837 				if (drag_ok &&
1838 				        (event.motion.state & SDL_BUTTON(1)))
1839 					dragged = gwin->drag(mx, my);
1840 				break;
1841 			}
1842 			case SDL_KEYDOWN: {
1843 				//+++++ convert to unicode first?
1844 				int c = event.key.keysym.sym;
1845 				switch (c) {
1846 				case SDLK_ESCAPE:
1847 					g_waiting_for_click = false;
1848 					return false;
1849 				case SDLK_RSHIFT:
1850 				case SDLK_LSHIFT:
1851 				case SDLK_RCTRL:
1852 				case SDLK_LCTRL:
1853 				case SDLK_RALT:
1854 				case SDLK_LALT:
1855 				case SDLK_RGUI:
1856 				case SDLK_LGUI:
1857 				case SDLK_NUMLOCKCLEAR:
1858 				case SDLK_CAPSLOCK:
1859 				case SDLK_SCROLLLOCK:
1860 					break;
1861 				default:
1862 					if (keybinder->IsMotionEvent(event))
1863 						break;
1864 					if ((c == 's') &&
1865 					        (event.key.keysym.mod & KMOD_ALT) &&
1866 					        (event.key.keysym.mod & KMOD_CTRL)) {
1867 						make_screenshot(true);
1868 						break;
1869 					}
1870 					if (chr) { // Looking for a character?
1871 						*chr = (event.key.keysym.mod &
1872 						        KMOD_SHIFT)
1873 						       ? toupper(c) : c;
1874 						g_waiting_for_click = false;
1875 						return true;
1876 					}
1877 					break;
1878 				}
1879 				break;
1880 			}
1881 			case SDL_WINDOWEVENT:
1882 				if (GainedFocus(event))
1883 					gwin->get_focus();
1884 				else if (LostFocus(event))
1885 					gwin->lose_focus();
1886 			}
1887 		if (dragging)
1888 			gwin->paint_dirty();
1889 		Mouse::mouse->show();       // Turn on mouse.
1890 		if (!gwin->show() &&    // Blit to screen if necessary.
1891 		        Mouse::mouse_update)
1892 			Mouse::mouse->blit_dirty();
1893 	}
1894 	g_waiting_for_click = false;
1895 	return false;         // Shouldn't get here.
1896 }
1897 
1898 /*
1899  *  Get a click, or, optionally, a keyboard char.
1900  *
1901  *  Output: false if user hit ESC.
1902  *      Chr gets keyboard char., or 0 if it's was a mouse click.
1903  */
1904 
Get_click(int & x,int & y,Mouse::Mouse_shapes shape,char * chr,bool drag_ok,Paintable * paint,bool rotate_colors)1905 bool Get_click(
1906     int &x, int &y,         // Location returned (if not ESC).
1907     Mouse::Mouse_shapes shape,  // Mouse shape to use.
1908     char *chr,          // Char. returned if not null.
1909     bool drag_ok,           // Okay to drag/close while here.
1910     Paintable *paint,       // Paint this over everything else.
1911     bool rotate_colors      // If the palette colors should rotate.
1912 ) {
1913 	if (chr)
1914 		*chr = 0;       // Init.
1915 	Mouse::Mouse_shapes saveshape = Mouse::mouse->get_shape();
1916 	if (shape != Mouse::dontchange)
1917 		Mouse::mouse->set_shape(shape);
1918 	if (paint)
1919 		paint->paint();
1920 	Mouse::mouse->show();
1921 	gwin->show(true);          // Want to see new mouse.
1922 	gwin->get_tqueue()->pause(Game::get_ticks());
1923 	bool ret = Get_click(x, y, chr, drag_ok, rotate_colors);
1924 	gwin->get_tqueue()->resume(Game::get_ticks());
1925 	Mouse::mouse->set_shape(saveshape);
1926 	return ret;
1927 }
1928 
1929 /*
1930  *  Wait for someone to stop walking.  If a timeout is given, at least
1931  *  one animation cycle will still always occur.
1932  */
1933 
Wait_for_arrival(Actor * actor,const Tile_coord & dest,long maxticks)1934 void Wait_for_arrival(
1935     Actor *actor,           // Whom to wait for.
1936     const Tile_coord& dest,        // Where he's going.
1937     long maxticks           // Max. # msecs. to wait, or 0.
1938 ) {
1939 	// Mouse scale factor
1940 	int mx;
1941 	int my;
1942 
1943 	bool os = Mouse::mouse->is_onscreen();
1944 	uint32 last_repaint = 0;    // For insuring animation repaints.
1945 	Actor_action *orig_action = actor->get_action();
1946 	uint32 stop_time = SDL_GetTicks() + maxticks;
1947 	bool timeout = false;
1948 	while (actor->is_moving() && actor->get_action() == orig_action &&
1949 	        actor->get_tile() != dest && !timeout) {
1950 		Delay();        // Wait a fraction of a second.
1951 
1952 		Mouse::mouse->hide();       // Turn off mouse.
1953 		Mouse::mouse_update = false;
1954 
1955 		SDL_Event event;
1956 		while (SDL_PollEvent(&event))
1957 			switch (event.type) {
1958 			case SDL_MOUSEMOTION:
1959 				gwin->get_win()->screen_to_game(event.motion.x, event.motion.y, gwin->get_fastmouse(), mx, my);
1960 
1961 				Mouse::mouse->move(mx, my);
1962 				Mouse::mouse_update = true;
1963 				break;
1964 			}
1965 		// Get current time, & animate.
1966 		uint32 ticks = SDL_GetTicks();
1967 		Game::set_ticks(ticks);
1968 		if (maxticks && ticks > stop_time)
1969 			timeout = true;
1970 		if (gwin->have_focus())
1971 			gwin->get_tqueue()->activate(ticks);
1972 		// Show animation every 1/20 sec.
1973 		if (ticks > last_repaint + 50 || gwin->was_painted()) {
1974 			gwin->paint_dirty();
1975 			while (ticks > last_repaint + 50)last_repaint += 50;
1976 		}
1977 
1978 		Mouse::mouse->show();       // Re-display mouse.
1979 		if (!gwin->show() &&    // Blit to screen if necessary.
1980 		        Mouse::mouse_update)    // If not, did mouse change?
1981 			Mouse::mouse->blit_dirty();
1982 	}
1983 
1984 	if (!os)
1985 		Mouse::mouse->hide();
1986 
1987 }
1988 
1989 /*
1990  *  Shift 'wizard's view' according to mouse position.
1991  */
1992 
Shift_wizards_eye(int mx,int my)1993 static void Shift_wizards_eye(
1994     int mx, int my
1995 ) {
1996 	// Figure dir. from center.
1997 	int cx = gwin->get_width() / 2;
1998 	int cy = gwin->get_height() / 2;
1999 	int dy = cy - my;
2000 	int dx = mx - cx;
2001 	Direction dir = Get_direction_NoWrap(dy, dx);
2002 	static int deltas[16] = {0, -1, 1, -1, 1, 0, 1, 1, 0, 1,
2003 	                         -1, 1, -1, 0, -1, -1
2004 	                        };
2005 	int dirx = deltas[2 * dir];
2006 	int diry = deltas[2 * dir + 1];
2007 	if (dirx == 1)
2008 		gwin->view_right();
2009 	else if (dirx == -1)
2010 		gwin->view_left();
2011 	if (diry == 1)
2012 		gwin->view_down();
2013 	else if (diry == -1)
2014 		gwin->view_up();
2015 }
2016 
2017 /*
2018  *  Do the 'wizard's eye' spell by letting the user browse around.
2019  */
2020 
Wizard_eye(long msecs)2021 void Wizard_eye(
2022     long msecs          // Length of time in milliseconds.
2023 ) {
2024 	// Center of screen.
2025 	int cx = gwin->get_width() / 2;
2026 	int cy = gwin->get_height() / 2;
2027 
2028 	bool os = Mouse::mouse->is_onscreen();
2029 	uint32 last_repaint = 0;    // For insuring animation repaints.
2030 	uint32 stop_time = SDL_GetTicks() + msecs;
2031 	bool timeout = false;
2032 	while (!timeout) {
2033 		Delay();        // Wait a fraction of a second.
2034 
2035 		Mouse::mouse->hide();       // Turn off mouse.
2036 		Mouse::mouse_update = false;
2037 		if (touchui != nullptr) {
2038 			touchui->hideGameControls();
2039 		}
2040 		SDL_Event event;
2041 		while (SDL_PollEvent(&event))
2042 			switch (event.type) {
2043 			case SDL_FINGERMOTION: {
2044 				if(event.tfinger.dy > 0) {
2045 					gwin->view_down();
2046 				}
2047 				else if(event.tfinger.dy < 0) {
2048 					gwin->view_up();
2049 				}
2050 				if(event.tfinger.dx > 0) {
2051 					gwin->view_right();
2052 				}
2053 				else if(event.tfinger.dx < 0) {
2054 					gwin->view_left();
2055 				}
2056 				break;
2057 			}
2058 			case SDL_MOUSEMOTION: {
2059 				int mx;
2060 				int my;
2061 				gwin->get_win()->screen_to_game(event.motion.x, event.motion.y, gwin->get_fastmouse(), mx, my);
2062 
2063 				Mouse::mouse->move(mx, my);
2064 				Mouse::mouse->set_shape(
2065 				    Mouse::mouse->get_short_arrow(
2066 				        Get_direction_NoWrap(cy - my, mx - cx)));
2067 				Mouse::mouse_update = true;
2068 				break;
2069 			}
2070 			case SDL_KEYDOWN:
2071 				if (event.key.keysym.sym == SDLK_ESCAPE)
2072 					timeout = true;
2073 			}
2074 		// Get current time, & animate.
2075 		uint32 ticks = SDL_GetTicks();
2076 		Game::set_ticks(ticks);
2077 		if (ticks > stop_time)
2078 			timeout = true;
2079 		if (gwin->have_focus())
2080 			gwin->get_tqueue()->activate(ticks);
2081 		// Show animation every 1/20 sec.
2082 		if (ticks > last_repaint + 50 || gwin->was_painted()) {
2083 			// Right mouse button down?
2084 			int x;
2085 			int y;
2086 			int ms = SDL_GetMouseState(&x, &y);
2087 			int mx;
2088 			int my;
2089 			//mouse movement of the eye needs to adjust for HighDPI
2090 			gwin->get_win()->screen_to_game_hdpi(x, y, gwin->get_fastmouse(), mx, my);
2091 			if (SDL_BUTTON(3) & ms)
2092 				Shift_wizards_eye(mx, my);
2093 			gwin->set_all_dirty();
2094 			gwin->paint_dirty();
2095 			// Paint sprite over view.
2096 			ShapeID eye(10, 0, SF_SPRITES_VGA);
2097 			Shape_frame *spr = eye.get_shape();
2098 			// Center it.
2099 			int w = gwin->get_width();
2100 			int h = gwin->get_height();
2101 			int sw = spr->get_width();
2102 			int sh = spr->get_height();
2103 			int topx = (w - sw) / 2;
2104 			int topy = (h - sh) / 2;
2105 			eye.paint_shape(topx + spr->get_xleft(),
2106 			                topy + spr->get_yabove());
2107 			int sizex = (w - 320) / 2;
2108 			int sizey = (h - 200) / 2;
2109 			if (sizey) { // Black-fill area outside original resolution.
2110 				gwin->get_win()->fill8(0, w, sizey, 0, 0);
2111 				gwin->get_win()->fill8(0, w, sizey, 0, h - sizey);
2112 			}
2113 			if (sizex) {
2114 				gwin->get_win()->fill8(0, sizex, 200, 0, sizey);
2115 				gwin->get_win()->fill8(0, sizex, 200, w - sizex, sizey);
2116 			}
2117 			while (ticks > last_repaint + 50)last_repaint += 50;
2118 		}
2119 
2120 		Mouse::mouse->show();       // Re-display mouse.
2121 		if (!gwin->show() &&    // Blit to screen if necessary.
2122 		        Mouse::mouse_update)    // If not, did mouse change?
2123 			Mouse::mouse->blit_dirty();
2124 		if (touchui != nullptr) {
2125 			touchui->showGameControls();
2126 		}
2127 	}
2128 
2129 	if (!os)
2130 		Mouse::mouse->hide();
2131 	gwin->center_view(gwin->get_main_actor()->get_tile());
2132 }
2133 
2134 
find_resolution(int w,int h,int s)2135 int find_resolution(int w, int h, int s) {
2136 	int res = 0;
2137 	for (int i = 0; i < num_res; i++) {
2138 		if (res_list[i].x == w && res_list[i].y == h && res_list[i].scale == s)
2139 			res = i;
2140 	}
2141 	return res;
2142 }
2143 
increase_scaleval()2144 void increase_scaleval() {
2145 	if (!cheat()) return;
2146 
2147 	current_scaleval++;
2148 	if (current_scaleval >= 9)
2149 		current_scaleval = 1;
2150 	set_scaleval(current_scaleval);
2151 }
2152 
decrease_scaleval()2153 void decrease_scaleval() {
2154 	if (!cheat()) return;
2155 
2156 	current_scaleval--;
2157 	if (current_scaleval < 1)
2158 		current_scaleval = 8;
2159 	set_scaleval(current_scaleval);
2160 }
2161 
set_scaleval(int new_scaleval)2162 void set_scaleval(int new_scaleval) {
2163 	int scaler = gwin->get_win()->get_scaler();
2164 	bool fullscreen = gwin->get_win()->is_fullscreen();
2165 	if (new_scaleval >= 1 && !fullscreen && scaler == Image_window::point && cheat.in_map_editor()) {
2166 		current_scaleval = new_scaleval;
2167 		bool share_settings;
2168 		config->value("config/video/share_video_settings", share_settings, true);
2169 		const string &vidStr = (fullscreen || share_settings) ?
2170 		                       "config/video" : "config/video/window";
2171 		int resx;
2172 		int resy;
2173 		config->value(vidStr + "/display/width", resx);
2174 		config->value(vidStr + "/display/height", resy);
2175 
2176 		// for Studio zooming we set game area to auto, fill quality to point,
2177 		// fill mode to fill and increase/decrease the scale value
2178 		gwin->resized(resx,
2179 		              resy,
2180 		              fullscreen,
2181 		              0, 0,
2182 		              current_scaleval, scaler,
2183 		              Image_window::Fill,
2184 		              Image_window::point);
2185 	}
2186 }
2187 
make_screenshot(bool silent)2188 void make_screenshot(bool silent) {
2189 	// TODO: Maybe use <SAVEGAME>/exult%03i.pcx instead.
2190 	// Or maybe some form or "My Pictures" on Windows.
2191 	string homepath = get_system_path("<HOME>");
2192 	size_t strsize = homepath.size() + 20;
2193 	char *fn = new char[strsize];
2194 	bool namefound = false;
2195 	Effects_manager *eman = gwin->get_effects();
2196 
2197 	// look for the next available exult???.pcx file
2198 	for (int i = 0; i < 1000 && !namefound; i++) {
2199 		snprintf(fn, strsize, "%s/exult%03i.pcx", homepath.c_str(), i);
2200 		FILE *f = fopen(fn, "rb");
2201 		if (f) {
2202 			fclose(f);
2203 		} else {
2204 			namefound = true;
2205 		}
2206 	}
2207 
2208 	if (!namefound) {
2209 		if (!silent) eman->center_text("Too many screenshots");
2210 	} else {
2211 		SDL_RWops *dst = SDL_RWFromFile(fn, "wb");
2212 
2213 		if (gwin->get_win()->screenshot(dst)) {
2214 			cout << "Screenshot saved in " << fn << endl;
2215 			if (!silent) eman->center_text("Screenshot");
2216 		} else {
2217 			if (!silent) eman->center_text("Screenshot failed");
2218 		}
2219 	}
2220 	delete [] fn;
2221 }
2222 
change_gamma(bool down)2223 void change_gamma(bool down) {
2224 	double r;
2225 	double g;
2226 	double b;
2227 	char text[256];
2228 	const double delta = down ? 0.05 : -0.05;
2229 	Image_window8::get_gamma(r, g, b);
2230 	Image_window8::set_gamma(r + delta, g + delta, b + delta);
2231 	gwin->get_pal()->apply(true);   // So new brightness applies.
2232 
2233 	// Message
2234 	Image_window8::get_gamma(r, g, b);
2235 	snprintf(text, 256, "Gamma Set to R: %01.2f G: %01.2f B: %01.2f", r, g, b);
2236 	gwin->get_effects()->center_text(text);
2237 
2238 	int igam = std::lround(r * 10000);
2239 	snprintf(text, 256, "%d.%04d", igam / 10000, igam % 10000);
2240 	config->set("config/video/gamma/red", text, true);
2241 
2242 	igam = std::lround(b * 10000);
2243 	snprintf(text, 256, "%d.%04d", igam / 10000, igam % 10000);
2244 	config->set("config/video/gamma/green", text, true);
2245 
2246 	igam = std::lround(g * 10000);
2247 	snprintf(text, 256, "%d.%04d", igam / 10000, igam % 10000);
2248 	config->set("config/video/gamma/blue", text, true);
2249 }
2250 
BuildGameMap(BaseGameInfo * game,int mapnum)2251 void BuildGameMap(BaseGameInfo *game, int mapnum) {
2252 	// create 2048x2048 screenshots of the full Ultima 7 map.
2253 	// WARNING!! Takes up lots of memory and diskspace!
2254 	if (arg_buildmap >= 0) {
2255 		int maplift = 16;
2256 		switch (arg_buildmap) {
2257 		case 0:
2258 			maplift = 16;
2259 			break;
2260 		case 1:
2261 			maplift = 10;
2262 			break;
2263 		case 2:
2264 			maplift = 5;
2265 			break;
2266 		}
2267 		int w;
2268 		int h;
2269 		int sc;
2270 		int sclr;
2271 		h = w = c_tilesize * c_tiles_per_schunk;
2272 		sc = 1;
2273 		sclr = Image_window::point;
2274 		Image_window8::set_gamma(1, 1, 1);
2275 		Image_window::FillMode fillmode = Image_window::Fit;
2276 
2277 		//string    fullscreenstr;      // Check config. for fullscreen mode.
2278 		//config->value("config/video/fullscreen",fullscreenstr,"no");
2279 		// set windowed mode
2280 		//config->set("config/video/fullscreen","no",false);
2281 		gwin = new Game_window(w, h, false, w, h, sc, sclr, fillmode, sclr);
2282 		// restore original fullscreen setting
2283 		//config->set("config/video/fullscreen",fullscreenstr,true);
2284 		Audio::Init();
2285 		current_res = find_resolution(w, h, sc);
2286 		Game::create_game(game);
2287 		gwin->init_files(false); //init, but don't show plasma
2288 		gwin->get_map()->init();// +++++Got to clean this up.
2289 		gwin->set_map(mapnum);
2290 		gwin->get_pal()->set(0);
2291 		for (int x = 0; x < c_num_chunks / c_chunks_per_schunk; x++) {
2292 			for (int y = 0; y < c_num_chunks / c_chunks_per_schunk; y++) {
2293 				gwin->paint_map_at_tile(0, 0, w, h, x * c_tiles_per_schunk, y * c_tiles_per_schunk, maplift);
2294 				char fn[15];
2295 				snprintf(fn, 15, "u7map%02x.pcx", (12*y)+x);
2296 				SDL_RWops *dst = SDL_RWFromFile(fn, "wb");
2297 				cerr << x << "," << y << ": ";
2298 				gwin->get_win()->screenshot(dst);
2299 			}
2300 		}
2301 		Audio::Destroy();
2302 		exit(0);
2303 	}
2304 }
2305 
2306 /*
2307  *  Most of the game setable video configuration stuff is stored here so
2308  *  it isn't duplicated all over the place. fullscreen is determined
2309  *  before coming here. config->write_back() is done after (if needed).
2310  *  video_init does save before trying to open the Game_window just in case
2311  */
setup_video(bool fullscreen,int setup_video_type,int resx,int resy,int gw,int gh,int scaleval,int scaler,Image_window::FillMode fillmode,int fill_scaler)2312 void setup_video(bool fullscreen, int setup_video_type, int resx, int resy,
2313                  int gw , int gh, int scaleval, int scaler,
2314                  Image_window::FillMode fillmode, int fill_scaler) {
2315 	string fmode_string;
2316 	string sclr;
2317 	string scalerName;
2318 	string fillScalerName;
2319 	bool video_init = false;
2320 	bool set_config = false;
2321 	bool change_gwin = false;
2322 	bool menu_init = false;
2323 	bool read_config = false;
2324 	if (setup_video_type == VIDEO_INIT)
2325 		read_config = video_init = set_config = true;
2326 	else if (setup_video_type == TOGGLE_FULLSCREEN)
2327 		read_config = change_gwin = true;
2328 	else if (setup_video_type == MENU_INIT)
2329 		read_config = menu_init = true;
2330 	else if (setup_video_type == SET_CONFIG)
2331 		set_config = true;
2332 	bool share_settings;
2333 	config->value("config/video/share_video_settings", share_settings, true);
2334 	const string vidStr((fullscreen || share_settings) ?
2335 	                       "config/video" : "config/video/window");
2336 	bool high_dpi;
2337 	config->value("config/video/highdpi", high_dpi, true);
2338 	if (read_config) {
2339 #ifdef DEBUG
2340 		cout << "Reading video menu adjustable configuration options" << endl;
2341 #endif
2342 		// Default resolution is now 320x240 with 2x scaling
2343 		int w = 320;
2344 		int h = 240;
2345 #ifdef __IPHONEOS__
2346 		SDL_DisplayMode dispmode;
2347 		if (SDL_GetDesktopDisplayMode(0, &dispmode) == 0) {
2348 			w = dispmode.w;
2349 			h = dispmode.h;
2350 		}
2351 		int sc = 1;
2352 		string default_scaler = "point";
2353 		string default_fill_scaler = "point";
2354 		string default_fmode = "Fill";
2355 		fullscreen = true;
2356 		config->set("config/video/force_bpp", 32, true);
2357 #else
2358 		int sc = 2;
2359 		string default_scaler = "2xSaI";
2360 		string default_fill_scaler = "bilinear";
2361 		string default_fmode = "Centre";
2362 #endif
2363 		string fill_scaler_str;
2364 		if (video_init) {
2365 			// Convert from old video dims to new
2366 			if (config->key_exists("config/video/width")) {
2367 				config->value("config/video/width", resx, w);
2368 				config->remove("config/video/width", false);
2369 			} else
2370 				resx = w;
2371 			if (config->key_exists("config/video/height")) {
2372 				config->value("config/video/height", resy, h);
2373 				config->remove("config/video/height", false);
2374 			} else
2375 				resy = h;
2376 		} else {
2377 			resx = w;
2378 			resy = h;
2379 		}
2380 		config->value(vidStr + "/scale_method", sclr, default_scaler.c_str());
2381 		config->value(vidStr + "/scale", scaleval, sc);
2382 		scaler = Image_window::get_scaler_for_name(sclr.c_str());
2383 		// Ensure a default scaler if a wrong scaler name is set
2384 		if (scaler == Image_window::NoScaler)
2385 			scaler = Image_window::get_scaler_for_name(default_scaler.c_str());
2386 		// Ensure proper values for scaleval based on scaler.
2387 		if (scaler == Image_window::Hq3x || scaler == Image_window::_3xBR)
2388 			scaleval = 3;
2389 		else if (scaler == Image_window::Hq4x || scaler == Image_window::_4xBR)
2390 			scaleval = 4;
2391 		else if (scaler != Image_window::point &&
2392 		         scaler != Image_window::interlaced &&
2393 		         scaler != Image_window::bilinear)
2394 			scaleval = 2;
2395 		config->value(vidStr + "/display/width", resx, resx * scaleval);
2396 		config->value(vidStr + "/display/height", resy, resy * scaleval);
2397 		config->value(vidStr + "/game/width", gw, 320);
2398 		config->value(vidStr + "/game/height", gh, 200);
2399 		SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, high_dpi ? "0" : "1");
2400 		config->value(vidStr + "/fill_mode", fmode_string, default_fmode);
2401 		fillmode = Image_window::string_to_fillmode(fmode_string.c_str());
2402 		if (fillmode == 0)
2403 			fillmode = Image_window::AspectCorrectCentre;
2404 		config->value(vidStr + "/fill_scaler", fill_scaler_str, default_fill_scaler);
2405 		fill_scaler = Image_window::get_scaler_for_name(fill_scaler_str.c_str());
2406 		if (fill_scaler == Image_window::NoScaler)
2407 			fill_scaler = Image_window::bilinear;
2408 	}
2409 	if (set_config) {
2410 		scalerName = Image_window::get_name_for_scaler(scaler);
2411 		fillScalerName = Image_window::get_name_for_scaler(fill_scaler);
2412 		Image_window::fillmode_to_string(fillmode, fmode_string);
2413 #ifdef DEBUG
2414 		cout << "Setting video menu adjustable configuration options " <<
2415 		     resx << " resX, " << resy <<
2416 		     " resY, " << gw << " gameW, " << gh << " gameH, " << scaleval <<
2417 		     " scale, " << scalerName << " scaler, " << fmode_string <<
2418 		     " fill mode, " << fillScalerName << " fill scaler, " <<
2419 		     (fullscreen ? "full screen" : "window") << endl;
2420 #endif
2421 		config->set((vidStr + "/display/width").c_str(), resx, false);
2422 		config->set((vidStr + "/display/height").c_str(), resy, false);
2423 		config->set((vidStr + "/game/width").c_str(), gw, false);
2424 		config->set((vidStr + "/game/height").c_str(), gh, false);
2425 		config->set((vidStr + "/scale").c_str(), scaleval, false);
2426 		config->set((vidStr + "/scale_method").c_str(), scalerName , false);
2427 		config->set((vidStr + "/fill_mode").c_str(), fmode_string, false);
2428 		config->set((vidStr + "/fill_scaler").c_str(), fillScalerName, false);
2429 		config->set("config/video/highdpi", high_dpi ?
2430 		            "yes" : "no", false);
2431 	}
2432 	if (video_init) {
2433 #ifdef DEBUG
2434 		cout << "Initializing Game_window to " << resx << " resX, " << resy <<
2435 		     " resY, " << gw << " gameW, " << gh << " gameH, " << scaleval <<
2436 		     " scale, " << scalerName << " scaler, " << fmode_string <<
2437 		     " fill mode, " << fillScalerName << " fill scaler, " <<
2438 		     (fullscreen ? "full screen" : "window") << endl;
2439 #endif
2440 		config->set("config/video/share_video_settings", share_settings ?
2441 		            "yes" : "no", false);
2442 		config->write_back();
2443 		gwin = new Game_window(resx, resy, fullscreen, gw , gh, scaleval, scaler,
2444 		                       fillmode, fill_scaler);
2445 		// Ensure proper clipping:
2446 		gwin->get_win()->clear_clip();
2447 		// is find_resolution outdated?
2448 		current_res = find_resolution(resx, resy, scaleval);
2449 	} else if (change_gwin) {
2450 #ifdef DEBUG
2451 		if (!set_config) {
2452 			Image_window::fillmode_to_string(fillmode, fmode_string);
2453 			scalerName = Image_window::get_name_for_scaler(scaler);
2454 			fillScalerName = Image_window::get_name_for_scaler(fill_scaler);
2455 		}
2456 		cout << "Changing Game_window to " << resx << " resX, " << resy <<
2457 		     " resY, " << gw << " gameW, " << gh << " gameH, " << scaleval <<
2458 		     " scale, " << scalerName << " scaler, " << fmode_string <<
2459 		     " fill mode, " << fillScalerName << " fill scaler, " <<
2460 		     (fullscreen ? "full screen" : "window") << endl;
2461 #endif
2462 		gwin->resized(resx, resy, fullscreen, gw, gh, scaleval, scaler,
2463 		              fillmode, fill_scaler);
2464 	}
2465 	if (menu_init) {
2466 #ifdef DEBUG
2467 		Image_window::fillmode_to_string(fillmode, fmode_string);
2468 		scalerName = Image_window::get_name_for_scaler(scaler);
2469 		fillScalerName = Image_window::get_name_for_scaler(fill_scaler);
2470 		cout << "Initializing video options menu settings " << resx << " resX, " <<
2471 		     resy << " resY, " << gw << " gameW, " << gh << " gameH, " << scaleval <<
2472 		     " scale, " << scalerName << " scaler, " << fmode_string <<
2473 		     " fill mode, " << fillScalerName << " fill scaler, " <<
2474 		     (fullscreen ? "full screen" : "window") << endl;
2475 #endif
2476 		VideoOptions_gump *videoGump = VideoOptions_gump::get_instance();
2477 		videoGump->set_scaling(scaleval - 1);
2478 		videoGump->set_scaler(scaler);
2479 		videoGump->set_resolution(resx << 16 | resy);
2480 		videoGump->set_game_resolution(gw << 16 | gh);
2481 		videoGump->set_fill_scaler(fill_scaler == Image_window::bilinear ? 1 : 0);
2482 		videoGump->set_fill_mode(fillmode);
2483 	}
2484 }
2485 
2486 #ifdef USE_EXULTSTUDIO
2487 
2488 /*
2489  *  Show a grid being dragged.
2490  */
2491 
Move_grid(int x,int y,int prevx,int prevy,bool ireg,int xtiles,int ytiles,int tiles_right,int tiles_below)2492 static void Move_grid(
2493     int x, int y,           // Mouse coords. within window.
2494     int prevx, int prevy,       // Prev. coords, or -1.
2495     bool ireg,          // A single IREG object?
2496     int xtiles, int ytiles,     // Dimension of grid to show.
2497     int tiles_right, int tiles_below// # tiles to show to right of and
2498     //   below (x, y).
2499 ) {
2500 	//int scale = gwin->get_win()->get_scale();
2501 	//x /= scale;           // Watch for scaled window.
2502 	//y /= scale;
2503 	gwin->get_win()->screen_to_game(x, y, false, x, y);
2504 
2505 	int lift = cheat.get_edit_lift();
2506 	x += lift * 4 - 1;      // Take lift into account, round.
2507 	y += lift * 4 - 1;
2508 	int tx = x / c_tilesize;    // Figure tile on ground.
2509 	int ty = y / c_tilesize;
2510 	tx += tiles_right;
2511 	ty += tiles_below;
2512 	if (prevx != -1) {      // See if moved to a new tile.
2513 		gwin->get_win()->screen_to_game(prevx, prevy, false, prevx, prevy);
2514 		prevx += lift * 4 - 1;  // Take lift into account, round.
2515 		prevy += lift * 4 - 1;
2516 		int ptx = prevx / c_tilesize;
2517 		int pty = prevy / c_tilesize;
2518 		ptx += tiles_right;
2519 		pty += tiles_below;
2520 		if (tx == ptx && ty == pty)
2521 			return;     // Will be in same tile.
2522 		// Repaint over old area.
2523 		const int pad = 8;
2524 		TileRect r((ptx - xtiles + 1)*c_tilesize - pad,
2525 		           (pty - ytiles + 1)*c_tilesize - pad,
2526 		           xtiles * c_tilesize + 2 * pad,
2527 		           ytiles * c_tilesize + 2 * pad);
2528 		r = gwin->clip_to_win(r);
2529 		gwin->add_dirty(r);
2530 		gwin->paint_dirty();
2531 	}
2532 	// First see if it's a gump.
2533 	if (ireg && gwin->get_gump_man()->find_gump(x, y))
2534 		return;         // Skip if so.
2535 	tx -= xtiles - 1;       // Get top-left of footprint.
2536 	ty -= ytiles - 1;
2537 	// Let's try a green outline.
2538 	int pix = Shape_manager::get_instance()->get_special_pixel(
2539 	              POISON_PIXEL);
2540 	Image_window8 *win = gwin->get_win();
2541 	win->set_clip(0, 0, win->get_game_width(), win->get_game_height());
2542 	for (int Y = 0; Y <= ytiles; Y++)
2543 		win->fill8(pix, xtiles * c_tilesize, 1,
2544 		           tx * c_tilesize, (ty + Y)*c_tilesize);
2545 	for (int X = 0; X <= xtiles; X++)
2546 		win->fill8(pix, 1, ytiles * c_tilesize,
2547 		           (tx + X)*c_tilesize, ty * c_tilesize);
2548 	win->clear_clip();
2549 	gwin->set_painted();
2550 }
2551 
2552 /*
2553  *  Show where a shape dragged from a shape-chooser will go.
2554  *  ALSO, this is called with shape==-1 to just force a repaint.
2555  */
2556 
Move_dragged_shape(int shape,int frame,int x,int y,int prevx,int prevy,bool show)2557 static void Move_dragged_shape(
2558     int shape, int frame,       // What to create, OR -1 to just
2559     //   repaint window.
2560     int x, int y,           // Mouse coords. within window.
2561     int prevx, int prevy,       // Prev. coords, or -1.
2562     bool show           // Blit window.
2563 ) {
2564 	if (shape == -1) {
2565 		gwin->set_all_dirty();
2566 		return;
2567 	}
2568 	const Shape_info &info = ShapeID::get_info(shape);
2569 	// Get footprint in tiles.
2570 	int xtiles = info.get_3d_xtiles(frame);
2571 	int ytiles = info.get_3d_ytiles(frame);
2572 	int sclass = info.get_shape_class();
2573 	// Is it an ireg (changeable) obj?
2574 	bool ireg = (sclass != Shape_info::unusable &&
2575 	             sclass != Shape_info::building);
2576 	Move_grid(x, y, prevx, prevy, ireg, xtiles, ytiles, 0, 0);
2577 	if (show)
2578 		gwin->show();
2579 }
2580 
2581 #ifdef _WIN32
2582 /*
2583  *  Show where a shape dragged from a shape-chooser will go.
2584  */
2585 
Move_dragged_combo(int xtiles,int ytiles,int tiles_right,int tiles_below,int x,int y,int prevx,int prevy,bool show)2586 static void Move_dragged_combo(
2587     int xtiles, int ytiles,     // Dimensions in tiles.
2588     int tiles_right,        // Tiles right of & below hot-spot.
2589     int tiles_below,
2590     int x, int y,           // Mouse coords. within window.
2591     int prevx, int prevy,       // Prev. coords, or -1.
2592     bool show           // Blit window.
2593 ) {
2594 	Move_grid(x, y, prevx, prevy, false, xtiles, ytiles, tiles_right,
2595 	          tiles_below);
2596 	if (show)
2597 		gwin->show();
2598 }
2599 #endif
2600 
2601 /*
2602  *  Create an object as moveable (IREG) or fixed.
2603  */
2604 
Create_object(int shape,int frame,bool & ireg)2605 static Game_object_shared Create_object(
2606     int shape, int frame,       // What to create.
2607     bool &ireg          // Rets. TRUE if ireg (moveable).
2608 ) {
2609 	const Shape_info &info = ShapeID::get_info(shape);
2610 	int sclass = info.get_shape_class();
2611 	// Is it an ireg (changeable) obj?
2612 	ireg = (sclass != Shape_info::unusable &&
2613 	        sclass != Shape_info::building);
2614 	Game_object_shared newobj;
2615 	if (ireg)
2616 		newobj = gwin->get_map()->create_ireg_object(
2617 		             info, shape, frame, 0, 0, 0);
2618 	else
2619 		newobj = gwin->get_map()->create_ifix_object(shape, frame);
2620 	return newobj;
2621 }
2622 
2623 /*
2624  *  Drop a shape dragged from a shape-chooser via drag-and-drop.
2625  */
2626 
Drop_dragged_shape(int shape,int frame,int x,int y)2627 static void Drop_dragged_shape(
2628     int shape, int frame,   // What to create.
2629     int x, int y            // Mouse coords. within window.
2630 ) {
2631 	if (!cheat.in_map_editor()) // Get into editing mode.
2632 		cheat.toggle_map_editor();
2633 	cheat.clear_selected();     // Remove old selected.
2634 	gwin->get_map()->set_map_modified();
2635 	gwin->get_win()->screen_to_game(x, y, false, x, y);
2636 	ShapeID sid(shape, frame);
2637 	if (gwin->skip_lift == 0) { // Editing terrain?
2638 		int tx = (gwin->get_scrolltx() + x / c_tilesize) % c_num_tiles;
2639 		int ty = (gwin->get_scrollty() + y / c_tilesize) % c_num_tiles;
2640 		int cx = tx / c_tiles_per_chunk;
2641 		int cy = ty / c_tiles_per_chunk;
2642 		Map_chunk *chunk = gwin->get_map()->get_chunk(cx, cy);
2643 		Chunk_terrain *ter = chunk->get_terrain();
2644 		tx %= c_tiles_per_chunk;
2645 		ty %= c_tiles_per_chunk;
2646 		ShapeID curid = ter->get_flat(tx, ty);
2647 		if (sid.get_shapenum() != curid.get_shapenum() ||
2648 		        sid.get_framenum() != curid.get_framenum()) {
2649 			ter->set_flat(tx, ty, sid);
2650 			Game_map::set_chunk_terrains_modified();
2651 			gwin->set_all_dirty();  // ++++++++For now.++++++++++
2652 		}
2653 		return;
2654 	}
2655 	Shape_frame *sh = sid.get_shape();
2656 	if (!sh || !sh->is_rle())   // Flats are only for terrain.
2657 		return;         // Shouldn't happen.
2658 	cout << "Last drag pos: (" << x << ", " << y << ')' << endl;
2659 	cout << "Create shape (" << shape << '/' << frame << ')' <<
2660 	     endl;
2661 	bool ireg;          // Create object.
2662 	Game_object_shared newobj = Create_object(shape, frame, ireg);
2663 	Dragging_info drag(newobj);
2664 	drag.drop(x, y, true);      // (Dels if it fails.)
2665 }
2666 
2667 /*
2668  *  Drop a chunk dragged from a chunk-chooser via drag-and-drop.
2669  */
2670 
Drop_dragged_chunk(int chunknum,int x,int y)2671 static void Drop_dragged_chunk(
2672     int chunknum,           // Index in 'u7chunks'.
2673     int x, int y            // Mouse coords. within window.
2674 ) {
2675 	if (!cheat.in_map_editor()) // Get into editing mode.
2676 		cheat.toggle_map_editor();
2677 	gwin->get_win()->screen_to_game(x, y, false, x, y);
2678 	cout << "Last drag pos: (" << x << ", " << y << ')' << endl;
2679 	cout << "Set chunk (" << chunknum << ')' << endl;
2680 	// Need chunk-coordinates.
2681 	int tx = (gwin->get_scrolltx() + x / c_tilesize) % c_num_tiles;
2682 	int ty = (gwin->get_scrollty() + y / c_tilesize) % c_num_tiles;
2683 	int cx = tx / c_tiles_per_chunk;
2684 	int cy = ty / c_tiles_per_chunk;
2685 	gwin->get_map()->set_chunk_terrain(cx, cy, chunknum);
2686 	gwin->paint();
2687 }
2688 
2689 /*
2690  *  Drop a npc dragged from a npc-chooser via drag-and-drop.
2691  */
2692 
Drop_dragged_npc(int npcnum,int x,int y)2693 static void Drop_dragged_npc(
2694     int npcnum,
2695     int x, int y            // Mouse coords. within window.
2696 ) {
2697 	if (!cheat.in_map_editor()) // Get into editing mode.
2698 		cheat.toggle_map_editor();
2699 	gwin->get_win()->screen_to_game(x, y, false, x, y);
2700 	cout << "Last drag pos: (" << x << ", " << y << ')' << endl;
2701 	cout << "Set npc (" << npcnum << ')' << endl;
2702 	Actor *npc = gwin->get_npc(npcnum);
2703 	if (!npc)
2704 		return;
2705 	Game_object_shared safe_npc = npc->shared_from_this();
2706 	Game_object_shared npckeep;
2707 	if (npc->is_pos_invalid())  // Brand new?
2708 		npc->clear_flag(Obj_flags::dead);
2709 	else
2710 		npc->remove_this(&npckeep); // Remove if already on map.
2711 	Dragging_info drag(safe_npc);
2712 	if (drag.drop(x, y))
2713 		npc->set_unused(false);
2714 	gwin->paint();
2715 }
2716 
2717 /*
2718  *  Drop a combination object dragged from ExultStudio.
2719  */
2720 
Drop_dragged_combo(int cnt,U7_combo_data * combo,int x,int y)2721 void Drop_dragged_combo(
2722     int cnt,            // # shapes.
2723     U7_combo_data *combo,       // The shapes.
2724     int x, int y            // Mouse coords. within window.
2725 ) {
2726 	if (!cheat.in_map_editor()) // Get into editing mode.
2727 		cheat.toggle_map_editor();
2728 	cheat.clear_selected();     // Remove old selected.
2729 	gwin->get_win()->screen_to_game(x, y, false, x, y);
2730 	int at_lift = cheat.get_edit_lift();
2731 	x += at_lift * 4 - 1;   // Take lift into account, round.
2732 	y += at_lift * 4 - 1;
2733 	// Figure tile at mouse pos.
2734 	int tx = (gwin->get_scrolltx() + x / c_tilesize) % c_num_tiles;
2735 	int ty = (gwin->get_scrollty() + y / c_tilesize) % c_num_tiles;
2736 	for (int i = 0; i < cnt; i++) {
2737 		// Drop each shape.
2738 		U7_combo_data &elem = combo[i];
2739 		// Figure new tile coord.
2740 		int ntx = (tx + elem.tx) % c_num_tiles;
2741 		int nty = (ty + elem.ty) % c_num_tiles;
2742 		int ntz = at_lift + elem.tz;
2743 		if (ntz < 0)
2744 			ntz = 0;
2745 		ShapeID sid(elem.shape, elem.frame);
2746 		if (gwin->skip_lift == 0) { // Editing terrain?
2747 			int cx = ntx / c_tiles_per_chunk;
2748 			int cy = nty / c_tiles_per_chunk;
2749 			Map_chunk *chunk = gwin->get_map()->get_chunk(cx, cy);
2750 			Chunk_terrain *ter = chunk->get_terrain();
2751 			ntx %= c_tiles_per_chunk;
2752 			nty %= c_tiles_per_chunk;
2753 			ter->set_flat(ntx, nty, sid);
2754 			Game_map::set_chunk_terrains_modified();
2755 			continue;
2756 		}
2757 		bool ireg;      // Create object.
2758 		Game_object_shared newobj = Create_object(elem.shape,
2759 		                                    elem.frame, ireg);
2760 		newobj->set_invalid();  // Not in world.
2761 		newobj->move(ntx, nty, ntz);
2762 		// Add to selection.
2763 		cheat.append_selected(newobj.get());
2764 	}
2765 	gwin->set_all_dirty();      // For now, until we clear out grid.
2766 }
2767 
2768 #endif
2769