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