1 /**
2  ** Gamewin.cc - X-windows Ultima7 map browser.
3  **
4  ** Written: 7/22/98 - JSF
5  **/
6 
7 /*
8  *
9  *  Copyright (C) 1998-1999  Jeffrey S. Freedman
10  *  Copyright (C) 2000-2013  The Exult Team
11  *
12  *  This program is free software; you can redistribute it and/or modify
13  *  it under the terms of the GNU General Public License as published by
14  *  the Free Software Foundation; either version 2 of the License, or
15  *  (at your option) any later version.
16  *
17  *  This program is distributed in the hope that it will be useful,
18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  *  GNU General Public License for more details.
21  *
22  *  You should have received a copy of the GNU General Public License
23  *  along with this program; if not, write to the Free Software
24  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25  */
26 
27 #ifdef HAVE_CONFIG_H
28 #  include <config.h>
29 #endif
30 
31 #include <cstdlib>
32 #include <cstring>
33 #include <cstdarg>
34 #include <cstdio>
35 #include <memory>
36 
37 #include "Astar.h"
38 #include "Audio.h"
39 #include "Configuration.h"
40 #include "chunks.h"
41 #include "gamemap.h"
42 #include "Face_stats.h"
43 #include "Flex.h"
44 #include "Gump.h"
45 #include "Gump_manager.h"
46 #include "ShortcutBar_gump.h"
47 #include "actions.h"
48 #include "monsters.h"
49 #include "animate.h"
50 #include "barge.h"
51 #include "cheat.h"
52 #include "chunkter.h"
53 #include "combat_opts.h"
54 #include "dir.h"
55 #include "effects.h"
56 #include "egg.h"
57 #include "exult.h"
58 #include "files/U7file.h"
59 #include "flic/playfli.h"
60 #include "fnames.h"
61 #include "game.h"
62 #include "gamewin.h"
63 #include "gameclk.h"
64 #include "gamerend.h"
65 #include "items.h"
66 //#include "jawbone.h" // CHECKME: this doesn't seem to be needed
67 #include "keys.h"
68 #include "mouse.h"
69 #include "npcnear.h"
70 #include "objiter.h"
71 #include "paths.h"
72 #include "schedule.h"
73 #include "spellbook.h"
74 #include "ucmachine.h"
75 #include "ucsched.h"            /* Only used to flush objects. */
76 #include "utils.h"
77 #include "virstone.h"
78 #include "mappatch.h"
79 #include "version.h"
80 #include "drag.h"
81 #include "party.h"
82 #include "Notebook_gump.h"
83 #include "AudioMixer.h"
84 #include "combat.h"
85 #include "keyactions.h"
86 #include "monstinf.h"
87 #include "usefuns.h"
88 #include "audio/midi_drivers/XMidiFile.h"
89 #include "array_size.h"
90 
91 #ifdef USE_EXULTSTUDIO
92 #include "server.h"
93 #include "servemsg.h"
94 #endif
95 #include "ItemMenu_gump.h"
96 
97 using std::cerr;
98 using std::cout;
99 using std::endl;
100 using std::istream;
101 using std::ifstream;
102 using std::ios;
103 using std::make_unique;
104 using std::memset;
105 using std::ofstream;
106 using std::rand;
107 using std::string;
108 using std::srand;
109 using std::unique_ptr;
110 using std::vector;
111 
112 // THE game window:
113 Game_window *Game_window::game_window = nullptr;
114 
115 /*
116  *  Provide chirping birds.
117  */
118 class Background_noise : public Time_sensitive {
119 	int repeats;            // Repeats in quick succession.
120 	int last_sound;         // # of last sound played.
121 	Game_window *gwin;
122 	enum Song_states {
123 	    Invalid,
124 	    Outside,
125 	    Dungeon,
126 	    Nighttime,
127 	    RainStorm,
128 	    SnowStorm,
129 	    DangerNear
130 	} laststate;            // Last state for SFX music tracks,
131 	// 1 outside, 2 dungeon, 3 nighttime, 4 rainstorm,
132 	// 5 snowstorm, 6 for danger nearby
133 public:
Background_noise(Game_window * gw)134 	Background_noise(Game_window *gw) : repeats(0), last_sound(-1),
135 		gwin(gw), laststate(Invalid) {
136 		gwin->get_tqueue()->add(5000, this);
137 	}
~Background_noise()138 	~Background_noise() override {
139 		gwin->get_tqueue()->remove(this);
140 	}
141 	void handle_event(unsigned long curtime, uintptr udata) override;
is_combat_music(int num)142 	static bool is_combat_music(int num) {
143 		// Lumping music 16 as if it were a combat music in order to simplify
144 		// the check.
145 		return (num >= Audio::game_music(9) && num <= Audio::game_music(12)) ||
146 		       (num >= Audio::game_music(15) && num <= Audio::game_music(18));
147 	}
148 };
149 
150 /*
151  *  Play background sound.
152  */
153 
handle_event(unsigned long curtime,uintptr udata)154 void Background_noise::handle_event(
155     unsigned long curtime,
156     uintptr udata
157 ) {
158 #ifndef COLOURLESS_REALLY_HATES_THE_BG_SFX
159 
160 	unsigned long delay = 8000;
161 	Song_states currentstate;
162 	int bghour = gwin->get_clock()->get_hour();
163 	int weather = gwin->get_effects()->get_weather();
164 	bool nighttime = bghour < 6 || bghour > 20;
165 	bool nearby_hostile = gwin->is_hostile_nearby();
166 	if (nearby_hostile && !gwin->in_combat())
167 		currentstate = DangerNear;
168 	else if (gwin->is_in_dungeon())
169 		currentstate = Dungeon;
170 	else if (weather == 2)
171 		currentstate = RainStorm;
172 	else if (weather == 1)
173 		currentstate = SnowStorm;
174 	else if (nighttime)
175 		currentstate = Nighttime;   //Night time
176 	else
177 		currentstate = Outside;
178 
179 	MyMidiPlayer *player = Audio::get_ptr()->get_midi();
180 	// Lets allow this for Digital Muisc and MT32Emu only,
181 	// for MT32/FakeMT32 conversion as well.
182 	// if (player) {
183 	//if (player && player->get_ogg_enabled()){
184 	if (player && (player->get_ogg_enabled() || player->is_mt32() || player->is_adlib())) {
185 		delay = 1000;   //Quickly get back to this function check
186 		//We've got OGG so play the background SFX tracks
187 
188 		int curr_track = player->get_current_track();
189 
190 		// Testing. Original seems to allow crickets for all songs at night,
191 		// except when in a dungeon. Also, only do it sometimes.
192 		if (nighttime && currentstate != Dungeon && rand() % 6 == 0) {
193 			//Play the cricket sounds at night
194 			Audio::get_ptr()->play_sound_effect(Audio::game_sfx(61),
195 			                                    AUDIO_MAX_VOLUME - 30);
196 		}
197 
198 		if ((curr_track == -1 || laststate != currentstate) &&
199 		        Audio::get_ptr()->is_music_enabled()) {
200 			// Don't override bee cave music with dungeon music.
201 			bool notbees = !GAME_BG || curr_track != 54;
202 			// ++++ TODO: Need to come up with a way to replace repeating songs
203 			// here, just so they don't loop forever.
204 			// Conditions: not playing music, playing a background music
205 			if (curr_track == -1 || gwin->is_bg_track(curr_track) ||
206 			        (((currentstate == Dungeon && notbees) ||
207 			          currentstate == DangerNear) && !is_combat_music(curr_track))) {
208 				//Not already playing music
209 				int tracknum = 255;
210 
211 				//Get the relevant track number.
212 				if (nearby_hostile && !gwin->in_combat()) {
213 					tracknum = Audio::game_music(10);
214 					laststate = DangerNear;
215 				} else if (gwin->is_in_dungeon()) {
216 					//Start the SFX music track then
217 					tracknum = Audio::game_music(52);
218 					laststate = Dungeon;
219 				} else if (weather == 1) { // Snowstorm
220 					tracknum = Audio::game_music(5);
221 					laststate = SnowStorm;
222 				} else if (weather == 2) { // Rainstorm
223 					tracknum = Audio::game_music(4);
224 					laststate = RainStorm;
225 				} else if (bghour < 6 || bghour > 20) {
226 					tracknum = Audio::game_music(7);
227 					laststate = Nighttime;
228 				} else {
229 					//Start the SFX music track then
230 					tracknum = Audio::game_music(6);
231 					laststate = Outside;
232 				}
233 				Audio::get_ptr()->start_music(tracknum, true);
234 			}
235 		}
236 	}else {
237 		Main_actor *ava = gwin->get_main_actor();
238 		//Tests to see if track is playing the SFX tracks, possible
239 		//when the game has been restored
240 		//and the Audio option was changed from OGG to something else
241 		if (player && player->get_current_track() >= Audio::game_music(4) &&
242 		        player->get_current_track() <= Audio::game_music(8))
243 			player->stop_music();
244 
245 		//Not OGG so play the SFX sounds manually
246 		// Only if outside.
247 		if (ava && !gwin->is_main_actor_inside()  &&
248 		        // +++++SI SFX's don't sound right.
249 		        Game::get_game_type() == BLACK_GATE) {
250 			int sound;      // BG SFX #.
251 			static unsigned char bgnight[] = {61, 61, 255};
252 			static unsigned char bgday[] = {82, 85, 85};
253 			if (repeats > 0)    // Repeating?
254 				sound = last_sound;
255 			else {
256 				int hour = gwin->get_clock()->get_hour();
257 				if (hour < 6 || hour > 20)
258 					sound = bgnight[rand() % sizeof(bgnight)];
259 				else
260 					sound = bgday[rand() % sizeof(bgday)];
261 				// Translate BG to SI #'s.
262 				sound = Audio::game_sfx(sound);
263 				last_sound = sound;
264 			}
265 			Audio::get_ptr()->play_sound_effect(sound);
266 			repeats++;      // Count it.
267 			if (rand() % (repeats + 1) == 0)
268 				// Repeat.
269 				delay = 500 + rand() % 1000;
270 			else {
271 				delay = 4000 + rand() % 3000;
272 				repeats = 0;
273 			}
274 		}
275 	}
276 
277 	gwin->get_tqueue()->add(curtime + delay, this, udata);
278 #endif
279 }
280 
281 /*
282  *  Create game window.
283  */
284 
Game_window(int width,int height,bool fullscreen,int gwidth,int gheight,int scale,int scaler,Image_window::FillMode fillmode,unsigned int fillsclr)285 Game_window::Game_window(
286     int width, int height, bool fullscreen, int gwidth, int gheight, int scale, int scaler, Image_window::FillMode fillmode, unsigned int fillsclr      // Window dimensions.
287 ) :
288 	dragging(nullptr), effects(new Effects_manager(this)), map(new Game_map(0)),
289 	render(new Game_render), gump_man(new Gump_manager),
290 	party_man(new Party_manager), win(nullptr),
291 	npc_prox(new Npc_proximity_handler(this)), pal(nullptr),
292 	tqueue(new Time_queue()), background_noise(new Background_noise(this)),
293 	usecode(nullptr), combat(false), focus(true), ice_dungeon(false),
294 	painted(false), ambient_light(false), infravision_active(false),
295 	skip_above_actor(31), in_dungeon(0), num_npcs1(0),
296 	std_delay(c_std_delay), time_stopped(0), special_light(0),
297 	theft_warnings(0), theft_cx(255), theft_cy(255),
298 	moving_barge(nullptr), main_actor(nullptr), camera_actor(nullptr), npcs(0), bodies(0),
299 	scrolltx(0), scrollty(0), dirty(0, 0, 0, 0),
300 	mouse3rd(false), fastmouse(false), double_click_closes_gumps(false),
301 	text_bg(false), step_tile_delta(8), allow_right_pathfind(2),
302 	scroll_with_mouse(false), alternate_drop(false), allow_autonotes(false), in_exult_menu(false),
303 	extended_intro(false),load_palette_timer(0), plasma_start_color(0), plasma_cycle_range(0),
304 	skip_lift(255), paint_eggs(false), armageddon(false),
305 	walk_in_formation(false), debug(0), blits(0),
306 	scrolltx_l(0), scrollty_l(0), scrolltx_lp(0), scrollty_lp(0),
307 	scrolltx_lo(0), scrollty_lo(0), avposx_ld(0), avposy_ld(0),
308 	lerping_enabled(0) {
309 	memset(save_names, 0, sizeof(save_names));
310 	game_window = this;     // Set static ->.
311 	clock = new Game_clock(tqueue);
312 	shape_man = new Shape_manager();// Create the single instance.
313 	maps.push_back(map);        // Map #0.
314 	// Create window.
315 	win = new Image_window8(width, height, gwidth, gheight, scale, fullscreen, scaler, fillmode, fillsclr);
316 	win->set_title("Exult Ultima7 Engine");
317 	pal = new Palette();
318 	Game_singletons::init(this);    // Everything but 'usecode' exists.
319 	Shape_frame::set_to_render(win->get_ib8());
320 
321 	string str;
322 	config->value("config/gameplay/textbackground", text_bg, -1);
323 	config->value("config/gameplay/mouse3rd", str, "no");
324 	if (str == "yes")
325 		mouse3rd = true;
326 	config->set("config/gameplay/mouse3rd", str, false);
327 	config->value("config/gameplay/fastmouse", str, "no");
328 	if (str == "yes")
329 		fastmouse = true;
330 	config->set("config/gameplay/fastmouse", str, false);
331 	config->value("config/gameplay/double_click_closes_gumps", str, "no");
332 	if (str == "yes")
333 		double_click_closes_gumps = true;
334 	config->set("config/gameplay/double_click_closes_gumps", str, false);
335 	config->value("config/gameplay/combat/difficulty",
336 	              Combat::difficulty, 0);
337 	config->set("config/gameplay/combat/difficulty",
338 	            Combat::difficulty, false);
339 	config->value("config/gameplay/combat/charmDifficulty", str, "normal");
340 	Combat::charmed_more_difficult = (str == "hard");
341 	config->set("config/gameplay/combat/charmDifficulty", str, false);
342 	config->value("config/gameplay/combat/mode", str, "original");
343 	if (str == "keypause")
344 		Combat::mode = Combat::keypause;
345 	else
346 		Combat::mode = Combat::original;
347 	config->set("config/gameplay/combat/mode", str, false);
348 	config->value("config/gameplay/combat/show_hits", str, "no");
349 	Combat::show_hits = (str == "yes");
350 	config->set("config/gameplay/combat/show_hits", str, false);
351 	config->value("config/audio/disablepause", str, "no");
352 	config->set("config/audio/disablepause", str, false);
353 
354 	config->value("config/gameplay/step_tile_delta", step_tile_delta, 8);
355 	if (step_tile_delta < 1) step_tile_delta = 1;
356 	config->set("config/gameplay/step_tile_delta", step_tile_delta, false);
357 
358 	config->value("config/gameplay/allow_right_pathfind", str, "double");
359 	if (str == "no")
360 		allow_right_pathfind = 0;
361 	else if (str == "single")
362 		allow_right_pathfind = 1;
363 	config->set("config/gameplay/allow_right_pathfind", str, false);
364 
365 	// New 'formation' walking?
366 	config->value("config/gameplay/formation", str, "yes");
367 	// Assume "yes" on anything but "no".
368 	walk_in_formation = str != "no";
369 	config->set("config/gameplay/formation", walk_in_formation ? "yes" : "no",
370 	            false);
371 
372 	config->value("config/gameplay/smooth_scrolling", lerping_enabled, 0);
373 	config->set("config/gameplay/smooth_scrolling", lerping_enabled, false);
374 	config->value("config/gameplay/alternate_drop", str, "no");
375 	alternate_drop = str == "yes";
376 	config->set("config/gameplay/alternate_drop", alternate_drop ? "yes" : "no", false);
377 	config->value("config/gameplay/allow_autonotes", str, "no");
378 	allow_autonotes = str == "yes";
379 	config->set("config/gameplay/allow_autonotes", allow_autonotes ? "yes" : "no", false);
380 #ifdef __IPHONEOS__
381 	string default_scroll_with_mouse = "no";
382 #else
383 	string default_scroll_with_mouse = "yes";
384 #endif
385 	config->value("config/gameplay/scroll_with_mouse", str, default_scroll_with_mouse);
386 	scroll_with_mouse = str == "yes";
387 	config->set("config/gameplay/scroll_with_mouse",
388 	            scroll_with_mouse ? "yes" : "no", false);
389 #ifdef __IPHONEOS__
390 	const string default_item_menu = "yes";
391 	const string default_dpad_location = "right";
392 	const string default_shortcutbar = "translucent";
393 	const string default_touch_pathfind = "yes";
394 	const string default_outline = "black";
395 #else
396 	const string default_item_menu = "no";
397 	const string default_dpad_location = "no";
398 	const string default_shortcutbar = "no";
399 	const string default_touch_pathfind = "no";
400 	const string default_outline = "no";
401 #endif
402 
403 	// Item menu
404 	config->value("config/touch/item_menu", str, default_item_menu);
405 	item_menu = str == "yes";
406 	config->set("config/touch/item_menu", item_menu ? "yes" : "no", false);
407 
408 	// DPad location
409 	config->value("config/touch/dpad_location", str, default_dpad_location);
410 	if (str == "no")
411 		dpad_location = 0;
412 	else if (str == "left")
413 		dpad_location = 1;
414 	else {
415 		str = "right";
416 		dpad_location = 2;
417 	}
418 	config->set("config/touch/dpad_location", str, false);
419 
420 	// Touch pathfind
421 	config->value("config/touch/touch_pathfind", str, default_touch_pathfind);
422 	touch_pathfind = str == "yes";
423 	config->set("config/touch/touch_pathfind", touch_pathfind ? "yes" : "no", false);
424 
425 	// Shortcut bar
426 	config->value("config/shortcutbar/use_shortcutbar", str, default_shortcutbar);
427 	if(str == "no") {
428 		use_shortcutbar = 0;
429 	} else if(str == "yes") {
430 		use_shortcutbar = 2;
431 	} else {
432 		str = "translucent";
433 		use_shortcutbar = 1;
434 	}
435 	config->set("config/shortcutbar/use_shortcutbar", str, false);
436 
437 	config->value("config/shortcutbar/use_outline_color", str, default_outline);
438 	if(str == "no") {
439 		outline_color = NPIXCOLORS;
440 	} else if(str == "green") {
441 		outline_color = POISON_PIXEL;
442 	} else if(str == "white") {
443 		outline_color = PROTECT_PIXEL;
444 	} else if(str == "yellow") {
445 		outline_color = CURSED_PIXEL;
446 	} else if(str == "blue") {
447 		outline_color = CHARMED_PIXEL;
448 	} else if(str == "red") {
449 		outline_color = HIT_PIXEL;
450 	} else if(str == "purple") {
451 		outline_color = PARALYZE_PIXEL;
452 	} else {
453 		str = "black";
454 		outline_color = BLACK_PIXEL;
455 	}
456 	config->set("config/shortcutbar/use_outline_color", str, false);
457 
458 	config->value("config/shortcutbar/hide_missing_items", str, "yes");
459 	sb_hide_missing = str != "no";
460 	config->set("config/shortcutbar/hide_missing_items", sb_hide_missing ? "yes" : "no", false);
461 	config->write_back();
462 
463 	// default to SI extended intro
464 	config->value("config/gameplay/extended_intro", str, "yes");
465 	extended_intro = str == "yes";
466 	config->set("config/gameplay/extended_intro", extended_intro ? "yes" : "no", false);
467 }
468 
469 /*
470  *  Blank out screen.
471  */
clear_screen(bool update)472 void Game_window::clear_screen(bool update) {
473 	win->fill8(0, win->get_full_width(), win->get_full_height(), win->get_start_x(), win->get_start_y());
474 
475 	// update screen
476 	if (update)
477 		show(true);
478 }
479 
480 
481 
482 /*
483  *  Deleting game window.
484  */
485 
~Game_window()486 Game_window::~Game_window(
487 ) {
488 	gump_man->close_all_gumps(true);
489 	clear_world(false);         // Delete all objects, chunks.
490 	for (size_t i = 0; i < array_size(save_names); i++)
491 		delete [] save_names[i];
492 	delete shape_man;
493 	delete gump_man;
494 	delete party_man;
495 	delete background_noise;
496 	delete tqueue;
497 	tqueue = nullptr;
498 	delete win;
499 	delete dragging;
500 	delete pal;
501 	for (auto *map : maps)
502 		delete map;
503 	delete usecode;
504 	delete clock;
505 	delete npc_prox;
506 	delete effects;
507 	delete render;
508 }
509 
510 /*
511  *  Abort.
512  */
513 
abort(const char * msg,...)514 void Game_window::abort(
515     const char *msg,
516     ...
517 ) {
518 	std::va_list ap;
519 	va_start(ap, msg);
520 	char buf[512];
521 	vsprintf(buf, msg, ap);     // Format the message.
522 	cerr << "Exult (fatal): " << buf << endl;
523 	delete this;
524 	throw quit_exception(-1);
525 }
526 
is_bg_track(int num) const527 bool Game_window::is_bg_track(int num) const { // ripped out of Background_noise
528 	// Have to do it this way because of SI.
529 	return num == Audio::game_music(4) || num == Audio::game_music(5) ||
530 			num == Audio::game_music(6) || num == Audio::game_music(7) ||
531 			num == Audio::game_music(8) || num == Audio::game_music(52);
532 }
533 
init_files(bool cycle)534 void Game_window::init_files(bool cycle) {
535 	// Display red plasma during load...
536 	if (cycle)
537 		setup_load_palette();
538 
539 	usecode = Usecode_machine::create();
540 	Game_singletons::init(this);    // Everything should exist here.
541 
542 	cycle_load_palette();
543 	shape_man->load();      // All the .vga files!
544 	cycle_load_palette();
545 
546 	unsigned long timer = SDL_GetTicks();
547 	srand(timer);           // Use time to seed rand. generator.
548 	// Force clock to start.
549 	tqueue->add(timer, clock, this);
550 	// Go to starting chunk
551 	scrolltx_lp = scrolltx_l = scrolltx = game->get_start_tile_x();
552 	scrollty_lp = scrollty_l = scrollty = game->get_start_tile_y();
553 	scrolltx_lo = scrollty_lo = 0;
554 	avposx_ld = avposy_ld = 0;
555 
556 	// initialize keybinder
557 	delete keybinder;
558 	keybinder = new KeyBinder();
559 
560 	std::string d;
561 	std::string keyfilename;
562 	d = "config/disk/game/" + Game::get_gametitle() + "/keys";
563 	config->value(d.c_str(), keyfilename, "(default)");
564 	if (keyfilename == "(default)") {
565 		config->set(d.c_str(), keyfilename, true);
566 		keybinder->LoadDefaults();
567 	} else {
568 		try {
569 			keybinder->LoadFromFile(keyfilename.c_str());
570 		} catch (file_open_exception &err) {
571 			cerr << "Key mappings file '" << keyfilename << "' not found, falling back to default mappings." << endl;
572 			keybinder->LoadDefaults();
573 		}
574 	}
575 	keybinder->LoadFromPatch();
576 	cycle_load_palette();
577 
578 	int fps;            // Init. animation speed.
579 	config->value("config/video/fps", fps, 5);
580 	if (fps <= 0)
581 		fps = 5;
582 	std_delay = 1000 / fps;     // Convert to msecs. between frames.
583 }
584 
585 /*
586  *  Read any map. (This is for "multimap" games, not U7.)
587  */
588 
get_map(int num)589 Game_map *Game_window::get_map(
590     int num             // Should be > 0.
591 ) {
592 	if (num >= static_cast<int>(maps.size()))
593 		maps.resize(num + 1);
594 	if (maps[num] == nullptr) {
595 		auto *newmap = new Game_map(num);
596 		maps[num] = newmap;
597 		maps[num]->init();
598 	}
599 	return maps[num];
600 }
601 
602 /*
603  *  Set current map to given #.
604  */
605 
set_map(int num)606 void Game_window::set_map(
607     int num
608 ) {
609 	map = get_map(num);
610 	if (!map)
611 		abort("Map %d doesn't exist", num);
612 	Game_singletons::gmap = map;
613 }
614 
615 /*
616  *  Get map patch list.
617  */
get_map_patches()618 Map_patch_collection& Game_window::get_map_patches() {
619 	return map->get_map_patches();
620 }
621 
622 /*
623  *  Set/unset barge mode.
624  */
625 
set_moving_barge(Barge_object * b)626 void Game_window::set_moving_barge(
627     Barge_object *b
628 ) {
629 	if (b && b != moving_barge) {
630 		b->gather();        // Will 'gather' on next move.
631 		if (!b->contains(main_actor))
632 			b->set_to_gather();
633 	} else if (!b && moving_barge)
634 		moving_barge->done();   // No longer 'barging'.
635 	moving_barge = b;
636 }
637 
638 /*
639  *  Is character moving?
640  */
641 
is_moving() const642 bool Game_window::is_moving(
643 ) const {
644 	return moving_barge ? moving_barge->is_moving()
645 	       : main_actor->is_moving();
646 }
647 
648 /*
649  *  Are we in dont_move mode?
650  */
651 
main_actor_dont_move() const652 bool Game_window::main_actor_dont_move() const {
653 	return !cheat.in_map_editor() && main_actor != nullptr && // Not if map-editing.
654 	       ((main_actor->get_flag(Obj_flags::dont_move)) ||
655 	        (main_actor->get_flag(Obj_flags::dont_render)));
656 }
657 
658 /*
659  *  Are we in dont_move mode?
660  */
661 
main_actor_can_act() const662 bool Game_window::main_actor_can_act() const {
663 	return main_actor->can_act();
664 }
665 
main_actor_can_act_charmed() const666 bool Game_window::main_actor_can_act_charmed() const {
667 	return main_actor->can_act_charmed();
668 }
669 
in_infravision() const670 bool Game_window::in_infravision() const {
671 	return infravision_active || cheat.in_infravision();
672 }
673 
674 /*
675  *  Add time for a light spell.
676  */
677 
add_special_light(int units)678 void Game_window::add_special_light(
679     int units           // Light=500, GreatLight=5000.
680 ) {
681 	if (!special_light) {   // Nothing in effect now?
682 		special_light = clock->get_total_minutes();
683 		clock->set_palette();
684 	}
685 	special_light += units / 20; // Figure ending time.
686 }
687 
688 /*
689  *  Set 'stop time' value.
690  */
691 
set_time_stopped(long delay)692 void Game_window::set_time_stopped(
693     long delay          // Delay in ticks (1/1000 secs.),
694     // -1 to stop indefinitely, or 0
695     // to end.
696 ) {
697 	if (delay == -1)
698 		time_stopped = -1;
699 	else if (!delay)
700 		time_stopped = 0;
701 	else {
702 		long new_expire = Game::get_ticks() + delay;
703 		if (new_expire > time_stopped)  // Set expiration time.
704 			time_stopped = new_expire;
705 	}
706 }
707 
708 /*
709  *  Return delay to expiration (or 3000 if indefinite).
710  */
711 
check_time_stopped()712 long Game_window::check_time_stopped(
713 ) {
714 	if (time_stopped == -1)
715 		return 3000;
716 	long delay = time_stopped - Game::get_ticks();
717 	if (delay > 0)
718 		return delay;
719 	time_stopped = 0;       // Done.
720 	return 0;
721 }
722 
723 /*
724  *  Toggle combat mode.
725  */
726 
toggle_combat()727 void Game_window::toggle_combat(
728 ) {
729 	combat = !combat;
730 	// Change party member's schedules.
731 	int newsched = combat ? Schedule::combat : Schedule::follow_avatar;
732 	int cnt = party_man->get_count();
733 	for (int i = 0; i < cnt; i++) {
734 		int party_member = party_man->get_member(i);
735 		Actor *person = get_npc(party_member);
736 		if (!person)
737 			continue;
738 		if (!person->can_act_charmed())
739 			newsched = Schedule::combat;
740 		int sched = person->get_schedule_type();
741 		if (sched != newsched && sched != Schedule::wait &&
742 		        sched != Schedule::loiter)
743 			person->set_schedule_type(newsched);
744 	}
745 	if (!main_actor_can_act_charmed())
746 		newsched = Schedule::combat;
747 	if (main_actor->get_schedule_type() != newsched)
748 		main_actor->set_schedule_type(newsched);
749 	if (combat) {       // Get rid of flee modes.
750 		main_actor->ready_best_weapon();
751 		set_moving_barge(nullptr);    // And get out of barge mode.
752 		Actor *all[9];
753 		int cnt = get_party(all, 1);
754 		for (int i = 0; i < cnt; i++) {
755 			// Did Usecode set to flee?
756 			Actor *act = all[i];
757 			if (act->get_attack_mode() == Actor::flee &&
758 			        !act->did_user_set_attack())
759 				act->set_attack_mode(Actor::nearest);
760 			// And avoid attacking party members,
761 			//  in case of Usecode bug.
762 			const Game_object *targ = act->get_target();
763 			if (targ && targ->get_flag(Obj_flags::in_party))
764 				act->set_target(nullptr);
765 		}
766 	} else              // Ending combat.
767 		Combat::resume();   // Make sure not still paused.
768 	if (g_shortcutBar)
769 		g_shortcutBar->set_changed();
770 }
771 
772 /*
773  *  Add an NPC.
774  */
775 
add_npc(Actor * npc,int num)776 void Game_window::add_npc(
777     Actor *npc,
778     int num             // Number. Has to match npc->num.
779 ) {
780 	assert(num == npc->get_npc_num());
781 	assert(num <= static_cast<int>(npcs.size()));
782 	if (num == static_cast<int>(npcs.size())) {      // Add at end.
783 		npcs.push_back(std::static_pointer_cast<Actor>(npc->shared_from_this()));
784 	} else {
785 		// Better be unused.
786 		assert(!npcs[num] || npcs[num]->is_unused());
787 		npcs[num] = std::static_pointer_cast<Actor>(npc->shared_from_this());
788 	}
789 }
790 
791 /*  Get desired NPD.
792  */
get_npc(long npc_num) const793 Actor *Game_window::get_npc(long npc_num) const {
794 	if (npc_num < 0 || npc_num >= static_cast<int>(npcs.size()))
795 		return nullptr;
796 	else {
797 		return npcs[npc_num].get();
798 	}
799 }
800 
801 /*
802  *  Show desired NPC.
803  */
804 
locate_npc(int npc_num)805 void Game_window::locate_npc(
806     int npc_num
807 ) {
808 	char msg[80];
809 	Actor *npc = get_npc(npc_num);
810 	if (!npc) {
811 		snprintf(msg, 80, "NPC %d does not exist.", npc_num);
812 		effects->center_text(msg);
813 	} else if (npc->is_pos_invalid()) {
814 		snprintf(msg, 80, "NPC %d is not on the map.", npc_num);
815 		effects->center_text(msg);
816 	} else {
817 		// ++++WHAT IF on a different map???
818 		Tile_coord pos = npc->get_tile();
819 		center_view(pos);
820 		cheat.clear_selected();
821 		cheat.append_selected(npc);
822 		snprintf(msg, 80, "NPC %d: '%s'.", npc_num,
823 		         npc->get_npc_name().c_str());
824 		int above = pos.tz + npc->get_info().get_3d_height() - 1;
825 		if (skip_above_actor > above)
826 			set_above_main_actor(above);
827 		effects->center_text(msg);
828 	}
829 }
830 
831 /*
832  *  Find first unused NPC #.
833  */
834 
get_unused_npc()835 int Game_window::get_unused_npc(
836 ) {
837 	int cnt = npcs.size();      // Get # in list.
838 	int i = 0;
839 	for (; i < cnt; i++) {
840 		if (i >= 356 && i <= 359)
841 			continue;   // Never return these.
842 		if (!npcs[i] || npcs[i]->is_unused())
843 			break;
844 	}
845 	if (i >= 356 && i <= 359) {
846 		// Special, 'reserved' cases.
847 		i = 360;
848 		do {
849 			npcs.push_back(std::make_shared<Npc_actor>("Reserved", 0));
850 			auto& npc = npcs[i];
851 			npc->set_schedule_type(Schedule::wait);
852 			npc->set_unused(true);
853 		} while (++cnt < 360);
854 	}
855 	return i;           // First free one is past the end.
856 }
857 
858 /*
859  *  Resize event occurred.
860  */
861 
resized(unsigned int neww,unsigned int newh,bool newfs,unsigned int newgw,unsigned int newgh,unsigned int newsc,unsigned int newsclr,Image_window::FillMode newfill,unsigned int newfillsclr)862 void Game_window::resized(
863     unsigned int neww,
864     unsigned int newh,
865     bool newfs,
866     unsigned int newgw,
867     unsigned int newgh,
868     unsigned int newsc,
869     unsigned int newsclr,
870     Image_window::FillMode newfill,
871     unsigned int newfillsclr
872 ) {
873 	win->resized(neww, newh, newfs, newgw, newgh, newsc, newsclr, newfill, newfillsclr);
874 	pal->apply(false);
875 	Shape_frame::set_to_render(win->get_ib8());
876 	if (!main_actor)        // In case we're before start.
877 		return;
878 	center_view(main_actor->get_tile());
879 	paint();
880 	// Do the following only if in game (not for menus)
881 	if (!gump_man->gump_mode()) {
882 		char msg[80];
883 		snprintf(msg, 80, "%ux%ux%u", neww, newh, newsc);
884 		effects->center_text(msg);
885 	}
886 	if(g_shortcutBar)
887 		g_shortcutBar->set_changed();
888 	Face_stats::UpdateButtons();
889 }
890 
891 /*
892  *  Clear out world's contents. Should be used during a 'restore'.
893  */
894 
clear_world(bool restoremapedit)895 void Game_window::clear_world(
896     bool restoremapedit
897 ) {
898 	bool edit = cheat.in_map_editor();
899 	cheat.set_map_editor(false);
900 	Combat::resume();
901 	tqueue->clear();        // Remove all entries.
902 	clear_dirty();
903 	Usecode_script::clear();    // Clear out all scheduled usecode.
904 	// Most NPCs were deleted when the map is cleared; we have to deal with some stragglers.
905 	for (auto& npc : npcs) {
906 		if (npc && npc->is_unused())
907 			npc.reset();
908 	}
909 	for (auto *map : maps)
910 		map->clear();
911 	try {
912 		set_map(0);         // Back to main map.
913 	} catch (const quit_exception & /*f*/) {
914 		// Lets not allow this to go up.
915 	}
916 	Monster_actor::delete_all();    // To be safe, del. any still around.
917 	Notebook_gump::clear();
918 	main_actor = nullptr;
919 	camera_actor = nullptr;
920 	num_npcs1 = 0;
921 	theft_cx = theft_cy = -1;
922 	combat = false;
923 	npcs.resize(0);         // NPC's already deleted above.
924 	bodies.resize(0);
925 	moving_barge = nullptr;       // Get out of barge mode.
926 	special_light = 0;      // Clear out light spells.
927 	ambient_light = false;  // And ambient lighting.
928 	infravision_active = false;
929 	effects->remove_all_effects(false);
930 	Schedule_change::clear();
931 	cheat.set_map_editor(edit && restoremapedit);
932 }
933 
934 /*
935  *  Look throughout the map for a given shape. The search starts at
936  *  the first currently-selected shape, if possible.
937  *
938  *  Output: true if found, else 0.
939  */
940 
locate_shape(int shapenum,bool upwards,int frnum,int qual)941 bool Game_window::locate_shape(
942     int shapenum,           // Desired shape.
943     bool upwards,           // If true, search upwards.
944     int frnum,          // Frame.
945     int qual            // Quality/quantity.
946 ) {
947 	// Get (first) selected object.
948 	const std::vector<Game_object_shared> &sel = cheat.get_selected();
949 	Game_object *start = !sel.empty() ? (sel[0]).get() : nullptr;
950 	char msg[80];
951 	snprintf(msg, sizeof(msg), "Searching for shape %d", shapenum);
952 	effects->center_text(msg);
953 	paint();
954 	show();
955 	Game_object *obj = map->locate_shape(shapenum, upwards, start, frnum, qual);
956 	if (!obj) {
957 		effects->center_text("Not found");
958 		return false;       // Not found.
959 	}
960 	effects->remove_text_effects();
961 	cheat.clear_selected();     // Select obj.
962 	cheat.append_selected(obj);
963 	//++++++++Got to show it.
964 	Game_object *owner = obj->get_outermost(); //+++++TESTING
965 	if (owner != obj)
966 		cheat.append_selected(owner);
967 	center_view(owner->get_tile());
968 	return true;
969 }
970 
971 /*
972  *  Set location to ExultStudio.
973  */
974 
Send_location(Game_window * gwin)975 inline void Send_location(
976     Game_window *gwin
977 ) {
978 #ifdef USE_EXULTSTUDIO
979 	if (client_socket >= 0 &&   // Talking to ExultStudio?
980 	        cheat.in_map_editor()) {
981 		unsigned char data[50];
982 		unsigned char *ptr = &data[0];
983 		Write4(ptr, gwin->get_scrolltx());
984 		Write4(ptr, gwin->get_scrollty());
985 		Write4(ptr, gwin->get_width() / c_tilesize);
986 		Write4(ptr, gwin->get_height() / c_tilesize);
987 		Write4(ptr, gwin->get_win()->get_scale_factor());
988 		Exult_server::Send_data(client_socket, Exult_server::view_pos,
989 		                        &data[0], ptr - data);
990 	}
991 #else
992 	ignore_unused_variable_warning(gwin);
993 #endif
994 }
995 
996 /*
997  *  Send current location to ExultStudio.
998  */
999 
send_location()1000 void Game_window::send_location(
1001 ) {
1002 	Send_location(this);
1003 }
1004 
1005 /*
1006  *  Set the scroll position to a given tile.
1007  */
1008 
set_scrolls(int newscrolltx,int newscrollty)1009 void Game_window::set_scrolls(
1010     int newscrolltx, int newscrollty
1011 ) {
1012 	scrolltx = newscrolltx;
1013 	scrollty = newscrollty;
1014 	// Set scroll box.
1015 	// Let's try 2x2 tiles.
1016 	scroll_bounds.w = scroll_bounds.h = 2;
1017 	scroll_bounds.x = scrolltx +
1018 	                  (get_width() / c_tilesize - scroll_bounds.w) / 2;
1019 	// OFFSET HERE
1020 	scroll_bounds.y = scrollty +
1021 	                  ((get_height()) / c_tilesize - scroll_bounds.h) / 2;
1022 
1023 	Barge_object *old_active_barge = moving_barge;
1024 	map->read_map_data();       // This pulls in objects.
1025 	// Found active barge?
1026 	if (!old_active_barge && moving_barge) {
1027 		// Do it right.
1028 		Barge_object *b = moving_barge;
1029 		moving_barge = nullptr;
1030 		set_moving_barge(b);
1031 	}
1032 	// Set where to skip rendering.
1033 	int cx = camera_actor->get_cx();
1034 	int cy = camera_actor->get_cy();
1035 	Map_chunk *nlist = map->get_chunk(cx, cy);
1036 	nlist->setup_cache();
1037 	int tx = camera_actor->get_tx();
1038 	int ty = camera_actor->get_ty();
1039 	set_above_main_actor(nlist->is_roof(tx, ty,
1040 	                                    camera_actor->get_lift()));
1041 	set_in_dungeon(nlist->has_dungeon() ? nlist->is_dungeon(tx, ty) : 0);
1042 	set_ice_dungeon(nlist->is_ice_dungeon(tx, ty));
1043 	send_location();        // Tell ExultStudio.
1044 }
1045 
1046 /*
1047  *  Set the scroll position so that a given tile is centered. (Used by
1048  *  center_view.)
1049  */
1050 
set_scrolls(Tile_coord cent)1051 void Game_window::set_scrolls(
1052     Tile_coord cent         // Want center here.
1053 ) {
1054 	// Figure in tiles.
1055 	// OFFSET HERE
1056 	int tw = get_width() / c_tilesize;
1057 	int th = (get_height()) / c_tilesize;
1058 	set_scrolls(DECR_TILE(cent.tx, tw / 2), DECR_TILE(cent.ty, th / 2));
1059 }
1060 
1061 /*
1062  *  Center view around a given tile. This is called during a 'restore'
1063  *  to init. stuff as well.
1064  */
1065 
center_view(Tile_coord const & t)1066 void Game_window::center_view(
1067     Tile_coord const &t
1068 ) {
1069 	set_scrolls(t);
1070 	set_all_dirty();
1071 }
1072 
1073 /*
1074  *  Set actor to center view around.
1075  */
1076 
set_camera_actor(Actor * a)1077 void Game_window::set_camera_actor(
1078     Actor *a
1079 ) {
1080 	if (a == main_actor &&      // Setting back to main actor?
1081 	        camera_actor &&     // Change in chunk?
1082 	        (camera_actor->get_cx() != main_actor->get_cx() ||
1083 	         camera_actor->get_cy() != main_actor->get_cy()))
1084 		// Cache out temp. objects.
1085 		emulate_cache(camera_actor->get_chunk(),
1086 		              main_actor->get_chunk());
1087 	camera_actor = a;
1088 	Tile_coord t = a->get_tile();
1089 	set_scrolls(t);         // Set scrolling around position,
1090 	// and read in map there.
1091 	set_all_dirty();
1092 }
1093 
1094 /*
1095  *  Scroll if necessary.
1096  *
1097  *  Output: 1 if scrolled (screen updated).
1098  */
1099 
scroll_if_needed(Tile_coord t)1100 bool Game_window::scroll_if_needed(
1101     Tile_coord t
1102 ) {
1103 	bool scrolled = false;
1104 	// 1 lift = 1/2 tile.
1105 	int tx = t.tx - t.tz / 2;
1106 	int ty = t.ty - t.tz / 2;
1107 	if (Tile_coord::gte(DECR_TILE(scroll_bounds.x), tx)) {
1108 		view_left();
1109 		scrolled = true;
1110 	} else if (Tile_coord::gte(tx,
1111 	                           (scroll_bounds.x + scroll_bounds.w) % c_num_tiles)) {
1112 		view_right();
1113 		scrolled = true;
1114 	}
1115 	if (Tile_coord::gte(DECR_TILE(scroll_bounds.y), ty)) {
1116 		view_up();
1117 		scrolled = true;
1118 	} else if (Tile_coord::gte(ty,
1119 	                           (scroll_bounds.y + scroll_bounds.h) % c_num_tiles)) {
1120 		view_down();
1121 		scrolled = true;
1122 	}
1123 	return scrolled;
1124 }
1125 
1126 /*
1127  *  Show the absolute game location, where each coordinate is of the
1128  *  8x8 tiles clicked on.
1129  */
1130 
show_game_location(int x,int y)1131 void Game_window::show_game_location(
1132     int x, int y            // Point on screen.
1133 ) {
1134 	x = get_scrolltx() + x / c_tilesize;
1135 	y = get_scrollty() + y / c_tilesize;
1136 	cout << "Game location is (" << x << ", " << y << ")" << endl;
1137 }
1138 
1139 /*
1140  *  Get screen area used by object.
1141  */
1142 
get_shape_rect(const Game_object * obj) const1143 TileRect Game_window::get_shape_rect(const Game_object *obj) const {
1144 	if (!obj->get_chunk()) {    // Not on map?
1145 		Gump *gump = gump_man->find_gump(obj);
1146 		if (gump)
1147 			return gump->get_shape_rect(obj);
1148 		else
1149 			return TileRect(0, 0, 0, 0);
1150 	}
1151 	Shape_frame *s = obj->get_shape();
1152 	if (!s) {
1153 		// This is probably fatal.
1154 #ifdef DEBUG
1155 		std::cerr << "DEATH! get_shape() returned a nullptr pointer: " << __FILE__ << ":" << __LINE__ << std::endl;
1156 		std::cerr << "Betcha it's a little doggie." << std::endl;
1157 #endif
1158 		return TileRect(0, 0, 0, 0);
1159 	}
1160 	Tile_coord t = obj->get_tile(); // Get tile coords.
1161 	int lftpix = (c_tilesize * t.tz) / 2;
1162 	t.tx += 1 - get_scrolltx();
1163 	t.ty += 1 - get_scrollty();
1164 	// Watch for wrapping.
1165 	if (t.tx < -c_num_tiles / 2)
1166 		t.tx += c_num_tiles;
1167 	if (t.ty < -c_num_tiles / 2)
1168 		t.ty += c_num_tiles;
1169 	return get_shape_rect(s,
1170 	                      t.tx * c_tilesize - 1 - lftpix,
1171 	                      t.ty * c_tilesize - 1 - lftpix);
1172 }
1173 
1174 /*
1175  *  Get screen location of given tile.
1176  */
1177 
Get_shape_location(Tile_coord t,int scrolltx,int scrollty,int & x,int & y)1178 inline void Get_shape_location(
1179     Tile_coord t,
1180     int scrolltx, int scrollty,
1181     int &x, int &y
1182 ) {
1183 	int lft = 4 * t.tz;
1184 	t.tx += 1 - scrolltx;
1185 	t.ty += 1 - scrollty;
1186 	// Watch for wrapping.
1187 	if (t.tx < -c_num_tiles / 2)
1188 		t.tx += c_num_tiles;
1189 	if (t.ty < -c_num_tiles / 2)
1190 		t.ty += c_num_tiles;
1191 	x = t.tx * c_tilesize - 1 - lft;
1192 	y = t.ty * c_tilesize - 1 - lft;
1193 }
1194 
1195 /*
1196  *  Get screen loc. of object which MUST be on the map (no owner).
1197  */
1198 
get_shape_location(const Game_object * obj,int & x,int & y)1199 void Game_window::get_shape_location(const Game_object *obj, int &x, int &y) {
1200 	Get_shape_location(obj->get_tile(), scrolltx, scrollty, x, y);
1201 	// Smooth scroll the avatar as well, if possible
1202 	if (obj == get_camera_actor()) {
1203 		x += avposx_ld;
1204 		y += avposy_ld;
1205 	}
1206 	x -= scrolltx_lo;
1207 	y -= scrollty_lo;
1208 }
get_shape_location(Tile_coord const & t,int & x,int & y)1209 void Game_window::get_shape_location(Tile_coord const &t, int &x, int &y) {
1210 	Get_shape_location(t, scrolltx, scrollty, x, y);
1211 	x -= scrolltx_lo;
1212 	y -= scrollty_lo;
1213 }
1214 
1215 /*
1216  *  Put the actor(s) in the world.
1217  */
1218 
init_actors()1219 void Game_window::init_actors(
1220 ) {
1221 	if (main_actor) {       // Already done?
1222 		Game::clear_avname();
1223 		Game::clear_avsex();
1224 		Game::clear_avskin();
1225 		return;
1226 	}
1227 	read_npcs();            // Read in all U7 NPC's.
1228 	int avsched = combat ? Schedule::combat : Schedule::follow_avatar;
1229 	if (!main_actor_can_act_charmed())
1230 		avsched = Schedule::combat;
1231 	main_actor->set_schedule_type(avsched);
1232 
1233 	// Was a name, sex or skincolor set in Game
1234 	// this bascially detects
1235 	bool    changed = false;
1236 
1237 
1238 	if (Game::get_avsex() == 0 || Game::get_avsex() == 1 || Game::get_avname()
1239 	        || (Game::get_avskin() >= 0 && Game::get_avskin() <= 2))
1240 		changed = true;
1241 
1242 	Game::clear_avname();
1243 	Game::clear_avsex();
1244 	Game::clear_avskin();
1245 
1246 	// Update gamedat if there was a change
1247 	if (changed) {
1248 		schedule_npcs(6, false);
1249 		write_npcs();
1250 	}
1251 }
1252 
1253 // In gamemgr/modmgr.cc because it is also needed by ES.
1254 string get_game_identity(const char *savename, const string &title);
1255 
1256 /*
1257  *  Create initial 'gamedat' directory if needed
1258  *
1259  */
1260 
init_gamedat(bool create)1261 bool Game_window::init_gamedat(bool create) {
1262 	// Create gamedat files 1st time.
1263 	if (create) {
1264 		cout << "Creating 'gamedat' files." << endl;
1265 		if (is_system_path_defined("<PATCH>") &&
1266 		        U7exists(PATCH_INITGAME))
1267 			restore_gamedat(PATCH_INITGAME);
1268 		else {
1269 			// Flag that we're reading U7 file.
1270 			Game::set_new_game();
1271 			restore_gamedat(INITGAME);
1272 		}
1273 		ofstream out;
1274 		// Editing, and no IDENTITY?
1275 		if (Game::is_editing() && !U7exists(IDENTITY)) {
1276 			U7open(out, IDENTITY);
1277 			out << Game::get_gametitle() << endl;
1278 			out.close();
1279 		}
1280 
1281 		// log version of exult that was used to start this game
1282 		U7open(out, GNEWGAMEVER);
1283 		getVersionInfo(out);
1284 		out.close();
1285 	}
1286 	//++++Maybe just test for IDENTITY+++:
1287 	else if ((U7exists(U7NBUF_DAT) || !U7exists(NPC_DAT)) &&
1288 	         !Game::is_editing()) {
1289 		return false;
1290 	} else {
1291 		ifstream identity_file;
1292 		U7open(identity_file, IDENTITY);
1293 		char gamedat_identity[256];
1294 		identity_file.read(gamedat_identity, 256);
1295 		char *ptr = gamedat_identity;
1296 		for (; (*ptr != 0x1a && *ptr != 0x0d &&
1297 		        *ptr != 0x0a); ptr++)
1298 			;
1299 		*ptr = 0;
1300 		cout << "Gamedat identity " << gamedat_identity << endl;
1301 		string static_identity = get_game_identity(INITGAME, Game::get_gametitle());
1302 		if (static_identity != gamedat_identity) {
1303 			return false;
1304 		}
1305 		// scroll coords.
1306 	}
1307 	read_save_names();      // Read in saved-game names.
1308 	return true;
1309 }
1310 
1311 /*
1312  *  Save game by writing out to the 'gamedat' directory.
1313  *
1314  *  Output: 0 if error, already reported.
1315  */
1316 
write()1317 void Game_window::write(
1318 ) {
1319 	// Lets just show a nice message on screen first
1320 
1321 	int width = get_width();
1322 	int centre_x = width / 2;
1323 	int height = get_height();
1324 	int centre_y = height / 2;
1325 	int text_height = shape_man->get_text_height(0);
1326 	int text_width = shape_man->get_text_width(0, "Saving Game");
1327 
1328 	win->fill_translucent8(0, width, height, 0, 0,
1329 	                       shape_man->get_xform(8));
1330 	shape_man->paint_text(0, "Saving Game", centre_x - text_width / 2,
1331 	                      centre_y - text_height);
1332 	show(true);
1333 	for (auto *map : maps)
1334 		map->write_ireg();    // Write ireg files.
1335 	write_npcs();           // Write out npc.dat.
1336 	usecode->write();       // Usecode.dat (party, global flags).
1337 	Notebook_gump::write();     // Write out journal.
1338 	write_gwin();           // Write our data.
1339 	write_saveinfo();
1340 }
1341 
1342 /*
1343  *  Restore game by reading in 'gamedat'.
1344  *
1345  *  Output: 0 if error, already reported.
1346  */
1347 
read()1348 void Game_window::read(
1349 ) {
1350 	Audio::get_ptr()->cancel_streams();
1351 	// Display red plasma during load...
1352 	setup_load_palette();
1353 
1354 	clear_world(true);      // Wipe clean.
1355 	read_gwin();            // Read our data.
1356 	// DON'T do anything that might paint()
1357 	// before calling read_npcs!!
1358 	setup_game(cheat.in_map_editor());  // Read NPC's, usecode.
1359 	Mouse::mouse->set_speed_cursor();
1360 }
1361 
1362 /*
1363  *  Write data for the game.
1364  *
1365  *  Output: 0 if error.
1366  */
1367 
write_gwin()1368 void Game_window::write_gwin(
1369 ) {
1370 	OFileDataSource gout(GWINDAT);
1371 	// Start with scroll coords (in tiles).
1372 	gout.write2(get_scrolltx());
1373 	gout.write2(get_scrollty());
1374 	// Write clock.
1375 	gout.write2(clock->get_day());
1376 	gout.write2(clock->get_hour());
1377 	gout.write2(clock->get_minute());
1378 	gout.write4(special_light); // Write spell expiration minute.
1379 	MyMidiPlayer *player = Audio::get_ptr()->get_midi();
1380 	if (player) {
1381 		gout.write4(static_cast<uint32>(player->get_current_track()));
1382 		gout.write4(static_cast<uint32>(player->is_repeating()));
1383 	} else {
1384 		gout.write4(static_cast<uint32>(-1));
1385 		gout.write4(0);
1386 	}
1387 	gout.write1(armageddon ? 1 : 0);
1388 	gout.write1(ambient_light ? 1 : 0);
1389 	gout.write1(combat ? 1 : 0);
1390 	gout.write1(infravision_active ? 1 : 0);
1391 	if (!gout.good())
1392 		throw file_write_exception(GWINDAT);
1393 }
1394 
1395 /*
1396  *  Read data for the game.
1397  *
1398  *  Output: 0 if error.
1399  */
1400 
read_gwin()1401 void Game_window::read_gwin(
1402 ) {
1403 	if (!clock->in_queue())     // Be sure clock is running.
1404 		tqueue->add(Game::get_ticks(), clock, this);
1405 
1406 	IFileDataSource gin(GWINDAT);
1407 	if (!gin.good()) {
1408 		return;
1409 	}
1410 
1411 	// Start with scroll coords (in tiles).
1412 	scrolltx_lp = scrolltx_l = scrolltx = gin.read2();
1413 	scrollty_lp = scrollty_l = scrollty = gin.read2();
1414 	scrolltx_lo = scrollty_lo = 0;
1415 	avposx_ld = avposy_ld = 0;
1416 	// Read clock.
1417 	clock->reset();
1418 	clock->set_day(gin.read2());
1419 	clock->set_hour(gin.read2());
1420 	clock->set_minute(gin.read2());
1421 	if (!gin.good())     // Next ones were added recently.
1422 		throw file_read_exception(GWINDAT);
1423 	special_light = gin.read4();
1424 	armageddon = false;     // Old saves may not have this yet.
1425 
1426 	if (!gin.good()) {
1427 		special_light = 0;
1428 		return;
1429 	}
1430 
1431 	int track_num = gin.read4();
1432 	int repeat = gin.read4();
1433 	if (!gin.good()) {
1434 		Audio::get_ptr()->stop_music();
1435 		return;
1436 	}
1437 	MyMidiPlayer *midi = Audio::get_ptr()->get_midi();
1438 	if(!is_bg_track(track_num) || (midi && (midi->get_ogg_enabled() || midi->is_mt32())))
1439 		Audio::get_ptr()->start_music(track_num, repeat != 0);
1440 	armageddon = gin.read1() == 1;
1441 	if (!gin.good())
1442 		armageddon = false;
1443 
1444 	ambient_light = gin.read1() == 1;
1445 	if (!gin.good())
1446 		ambient_light = false;
1447 
1448 	combat = gin.read1() == 1;
1449 	if (!gin.good())
1450 		combat = false;
1451 
1452 	infravision_active = gin.read1() == 1;
1453 	if (!gin.good())
1454 		infravision_active = false;
1455 }
1456 
1457 /*
1458  *  Was any map modified?
1459  */
1460 
was_map_modified()1461 bool Game_window::was_map_modified(
1462 ) {
1463 	if (Game_map::was_chunk_terrain_modified())
1464 		return true;
1465 	for (auto *map : maps) {
1466 		if (map && map->was_map_modified())
1467 			return true;
1468 	}
1469 	return false;
1470 }
1471 
1472 /*
1473  *  Write out map data (IFIXxx, U7CHUNKS, U7MAP) to static, and also
1474  *  save 'gamedat' to <PATCH>/initgame.dat.
1475  *
1476  *  Note: This is for map-editing.
1477  *
1478  *  Output: Errors are reported.
1479  */
1480 
write_map()1481 void Game_window::write_map(
1482 ) {
1483 	for (auto *map : maps) {
1484 		if (map && map->was_map_modified())
1485 			map->write_static();    // Write ifix, map files.
1486 	}
1487 	write();            // Write out to 'gamedat' too.
1488 	save_gamedat(PATCH_INITGAME, "Saved map");
1489 }
1490 
1491 /*
1492  *  Reinitialize game from map.
1493  */
1494 
read_map()1495 void Game_window::read_map(
1496 ) {
1497 	init_gamedat(true);     // Unpack 'initgame.dat'.
1498 	read();             // This does the whole restore.
1499 }
1500 
1501 /*
1502  *  Reload (patched) usecode.
1503  */
1504 
reload_usecode()1505 void Game_window::reload_usecode(
1506 ) {
1507 	// Get custom usecode functions.
1508 	if (is_system_path_defined("<PATCH>") && U7exists(PATCH_USECODE)) {
1509 		ifstream file;
1510 		U7open(file, PATCH_USECODE);
1511 		usecode->read_usecode(file, true);
1512 		file.close();
1513 	}
1514 }
1515 
1516 /*
1517  *  Shift view by one tile.
1518  */
1519 
view_right()1520 void Game_window::view_right(
1521 ) {
1522 	int w = get_width();
1523 	int h = get_height();
1524 	// Get current rightmost chunk.
1525 	int old_rcx = ((scrolltx + (w - 1) / c_tilesize) / c_tiles_per_chunk) %
1526 	              c_num_chunks;
1527 	scrolltx = INCR_TILE(scrolltx);
1528 	scroll_bounds.x = INCR_TILE(scroll_bounds.x);
1529 	if (gump_man->showing_gumps()) {    // Gump on screen?
1530 		paint();
1531 		return;
1532 	}
1533 	map->read_map_data();       // Be sure objects are present.
1534 	// Shift image to left.
1535 	win->copy(c_tilesize, 0, w - c_tilesize, h, 0, 0);
1536 	// Paint 1 column to right.
1537 	paint(w - c_tilesize, 0, c_tilesize, h);
1538 	dirty.x -= c_tilesize;  // Shift dirty rect.
1539 	dirty = clip_to_win(dirty);
1540 	// New chunk?
1541 	int new_rcx = ((scrolltx + (w - 1) / c_tilesize) / c_tiles_per_chunk) %
1542 	              c_num_chunks;
1543 	if (new_rcx != old_rcx)
1544 		Send_location(this);
1545 }
view_left()1546 void Game_window::view_left(
1547 ) {
1548 	int old_lcx = (scrolltx / c_tiles_per_chunk) % c_num_chunks;
1549 	// Want to wrap.
1550 	scrolltx = DECR_TILE(scrolltx);
1551 	scroll_bounds.x = DECR_TILE(scroll_bounds.x);
1552 	if (gump_man->showing_gumps()) {        // Gump on screen?
1553 		paint();
1554 		return;
1555 	}
1556 	map->read_map_data();       // Be sure objects are present.
1557 	win->copy(0, 0, get_width() - c_tilesize, get_height(),
1558 				c_tilesize, 0);
1559 	int h = get_height();
1560 	paint(0, 0, c_tilesize, h);
1561 	dirty.x += c_tilesize;      // Shift dirty rect.
1562 	dirty = clip_to_win(dirty);
1563 	// New chunk?
1564 	int new_lcx = (scrolltx / c_tiles_per_chunk) % c_num_chunks;
1565 	if (new_lcx != old_lcx)
1566 		Send_location(this);
1567 }
view_down()1568 void Game_window::view_down(
1569 ) {
1570 	int w = get_width();
1571 	int h = get_height();
1572 	// Get current bottomost chunk.
1573 	int old_bcy = ((scrollty + (h - 1) / c_tilesize) / c_tiles_per_chunk) %
1574 	              c_num_chunks;
1575 	scrollty = INCR_TILE(scrollty);
1576 	scroll_bounds.y = INCR_TILE(scroll_bounds.y);
1577 	if (gump_man->showing_gumps()) {        // Gump on screen?
1578 		paint();
1579 		return;
1580 	}
1581 	map->read_map_data();       // Be sure objects are present.
1582 	win->copy(0, c_tilesize, w, h - c_tilesize, 0, 0);
1583 	paint(0, h - c_tilesize, w, c_tilesize);
1584 	dirty.y -= c_tilesize;      // Shift dirty rect.
1585 	dirty = clip_to_win(dirty);
1586 	// New chunk?
1587 	int new_bcy = ((scrollty + (h - 1) / c_tilesize) / c_tiles_per_chunk) %
1588 	              c_num_chunks;
1589 	if (new_bcy != old_bcy)
1590 		Send_location(this);
1591 }
view_up()1592 void Game_window::view_up(
1593 ) {
1594 	int old_tcy = (scrollty / c_tiles_per_chunk) % c_num_chunks;
1595 	// Want to wrap.
1596 	scrollty = DECR_TILE(scrollty);
1597 	scroll_bounds.y = DECR_TILE(scroll_bounds.y);
1598 	if (gump_man->showing_gumps()) {    // Gump on screen?
1599 		paint();
1600 		return;
1601 	}
1602 	map->read_map_data();       // Be sure objects are present.
1603 	int w = get_width();
1604 	win->copy(0, 0, w, get_height() - c_tilesize, 0, c_tilesize);
1605 	paint(0, 0, w, c_tilesize);
1606 	dirty.y += c_tilesize;      // Shift dirty rect.
1607 	dirty = clip_to_win(dirty);
1608 	// New chunk?
1609 	int new_tcy = (scrollty / c_tiles_per_chunk) % c_num_chunks;
1610 	if (new_tcy != old_tcy)
1611 		Send_location(this);
1612 }
1613 
1614 /*
1615  *  Get gump being dragged.
1616  */
1617 
get_dragging_gump()1618 Gump *Game_window::get_dragging_gump(
1619 ) {
1620 	return dragging ? dragging->gump : nullptr;
1621 }
1622 
1623 /*
1624  *  Alternative start actor function
1625  *  Placed in an alternative function to prevent breaking barges
1626  */
start_actor_alt(int winx,int winy,int speed)1627 void Game_window::start_actor_alt(
1628     int winx, int winy, // Mouse position to aim for.
1629     int speed           // Msecs. between frames.
1630 ) {
1631 	int ax;
1632 	int ay;
1633 	bool blocked[8];
1634 	get_shape_location(main_actor, ax, ay);
1635 
1636 	Tile_coord start = main_actor->get_tile();
1637 	int dir;
1638 	bool checkdrop = (main_actor->get_type_flags() & MOVE_LEVITATE) == 0;
1639 
1640 	for (dir = 0; dir < 8; dir++) {
1641 		Tile_coord dest = start.get_neighbor(dir);
1642 		blocked[dir] = main_actor->is_blocked(dest, &start,
1643 		                                      main_actor->get_type_flags());
1644 		if (checkdrop && abs(start.tz - dest.tz) > 1)
1645 			blocked[dir] = true;
1646 	}
1647 
1648 	dir = Get_direction_NoWrap(ay - winy, winx - ax);
1649 
1650 	if (blocked[dir] && !blocked[(dir + 1) % 8])
1651 		dir = (dir + 1) % 8;
1652 	else if (blocked[dir] && !blocked[(dir + 7) % 8])
1653 		dir = (dir + 7) % 8;
1654 	else if (blocked[dir]) {
1655 		Game_object *block = main_actor->is_moving() ? nullptr
1656 		                     : main_actor->find_blocking(start.get_neighbor(dir), dir);
1657 		// We already know the blocking object isn't the avatar, so don't
1658 		// double check it here.
1659 		if (!block || !block->move_aside(main_actor, dir)) {
1660 			stop_actor();
1661 			if (main_actor->get_lift() % 5) { // Up on something?
1662 				// See if we're stuck in the air.
1663 				int savetz = start.tz;
1664 				if (!Map_chunk::is_blocked(start, 1,
1665 				                           MOVE_WALK, 100) &&
1666 				        start.tz < savetz)
1667 					main_actor->move(start.tx, start.ty,
1668 					                 start.tz);
1669 			}
1670 			return;
1671 		}
1672 	}
1673 
1674 	const int delta = step_tile_delta * c_tilesize; // Bigger # here avoids jerkiness,
1675 	// but causes probs. with followers.
1676 	switch (dir) {
1677 	case north:
1678 		//cout << "NORTH" << endl;
1679 		ay -= delta;
1680 		break;
1681 
1682 	case northeast:
1683 		//cout << "NORTH EAST" << endl;
1684 		ay -= delta;
1685 		ax += delta;
1686 		break;
1687 
1688 	case east:
1689 		//cout << "EAST" << endl;
1690 		ax += delta;
1691 		break;
1692 
1693 	case southeast:
1694 		//cout << "SOUTH EAST" << endl;
1695 		ay += delta;
1696 		ax += delta;
1697 		break;
1698 
1699 	case south:
1700 		//cout << "SOUTH" << endl;
1701 		ay += delta;
1702 		break;
1703 
1704 	case southwest:
1705 		//cout << "SOUTH WEST" << endl;
1706 		ay += delta;
1707 		ax -= delta;
1708 		break;
1709 
1710 	case west:
1711 		//cout << "WEST" << endl;
1712 		ax -= delta;
1713 		break;
1714 
1715 	case northwest:
1716 		//cout << "NORTH WEST" << endl;
1717 		ay -= delta;
1718 		ax -= delta;
1719 		break;
1720 	}
1721 
1722 	int lift = main_actor->get_lift();
1723 	int liftpixels = 4 * lift;  // Figure abs. tile.
1724 	int tx = get_scrolltx() + (ax + liftpixels) / c_tilesize;
1725 	int ty = get_scrollty() + (ay + liftpixels) / c_tilesize;
1726 	// Wrap:
1727 	tx = (tx + c_num_tiles) % c_num_tiles;
1728 	ty = (ty + c_num_tiles) % c_num_tiles;
1729 	main_actor->walk_to_tile(tx, ty, lift, speed, 0);
1730 	if (walk_in_formation && main_actor->get_action())
1731 		//++++++In this case, may need to set schedules back to
1732 		// follow_avatar after, say, sitting.++++++++++
1733 		main_actor->get_action()->set_get_party(true);
1734 	else                // "Traditional" Exult walk:-)
1735 		main_actor->get_followers();
1736 }
1737 
1738 /*
1739  *  Start the actor.
1740  */
1741 
start_actor(int winx,int winy,int speed)1742 void Game_window::start_actor(
1743     int winx, int winy, // Mouse position to aim for.
1744     int speed           // Msecs. between frames.
1745 ) {
1746 	if (main_actor->Actor::get_flag(Obj_flags::asleep))
1747 		return;         // Zzzzz....
1748 	if (!cheat.in_map_editor() &&
1749 	        (main_actor->in_usecode_control() ||
1750 	         main_actor->get_flag(Obj_flags::paralyzed)))
1751 		return;
1752 	if (gump_man->gump_mode() && !gump_man->gumps_dont_pause_game())
1753 		return;
1754 //	teleported = 0;
1755 	if (moving_barge) {
1756 		// Want to move center there.
1757 		int lift = main_actor->get_lift();
1758 		int liftpixels = 4 * lift;  // Figure abs. tile.
1759 		int tx = get_scrolltx() + (winx + liftpixels) / c_tilesize;
1760 		int ty = get_scrollty() + (winy + liftpixels) / c_tilesize;
1761 		// Wrap:
1762 		tx = (tx + c_num_tiles) % c_num_tiles;
1763 		ty = (ty + c_num_tiles) % c_num_tiles;
1764 		Tile_coord atile = moving_barge->get_center();
1765 		Tile_coord btile = moving_barge->get_tile();
1766 		// Go faster than walking.
1767 		moving_barge->travel_to_tile(
1768 		    Tile_coord(tx + btile.tx - atile.tx,
1769 		               ty + btile.ty - atile.ty, btile.tz),
1770 		    speed / 2);
1771 	} else {
1772 		/*
1773 		main_actor->walk_to_tile(tx, ty, lift, speed, 0);
1774 		main_actor->get_followers();
1775 		*/
1776 		// Set schedule.
1777 		int sched = main_actor->get_schedule_type();
1778 		if (sched != Schedule::follow_avatar &&
1779 		        sched != Schedule::combat &&
1780 		        !main_actor->get_flag(Obj_flags::asleep))
1781 			main_actor->set_schedule_type(Schedule::follow_avatar);
1782 		// Going to use the alternative function for this at the moment
1783 		start_actor_alt(winx, winy, speed);
1784 	}
1785 }
1786 
1787 /*
1788  *  Find path to where user double-right-clicked.
1789  */
1790 
start_actor_along_path(int winx,int winy,int speed)1791 void Game_window::start_actor_along_path(
1792     int winx, int winy, // Mouse position to aim for.
1793     int speed           // Msecs. between frames.
1794 ) {
1795 	if (main_actor->Actor::get_flag(Obj_flags::asleep) ||
1796 	        main_actor->Actor::get_flag(Obj_flags::paralyzed) ||
1797 	        main_actor->get_schedule_type() == Schedule::sleep ||
1798 	        moving_barge)       // For now, don't do barges.
1799 		return;         // Zzzzz....
1800 	// Animation in progress?
1801 	if (!cheat.in_map_editor() && main_actor->in_usecode_control())
1802 		return;
1803 //	teleported = 0;
1804 	int lift = main_actor->get_lift();
1805 	int liftpixels = 4 * lift;  // Figure abs. tile.
1806 	Tile_coord dest(get_scrolltx() + (winx + liftpixels) / c_tilesize,
1807 	                get_scrollty() + (winy + liftpixels) / c_tilesize, lift);
1808 	if (!main_actor->walk_path_to_tile(dest, speed)) {
1809 		cout << "Couldn't find path for Avatar." << endl;
1810 		if (touch_pathfind)
1811 			Mouse::mouse->flash_shape(Mouse::blocked);
1812 	} else {
1813 		if (touch_pathfind)
1814 			get_effects()->add_effect(std::make_unique<Sprites_effect>(18, dest, 0, 0, 0, 0));
1815 		main_actor->get_followers();
1816 	}
1817 }
1818 
1819 /*
1820  *  Stop the actor.
1821  */
1822 
stop_actor()1823 void Game_window::stop_actor(
1824 ) {
1825 	if (moving_barge)
1826 		moving_barge->stop();
1827 	else {
1828 		main_actor->stop(); // Stop and set resting state.
1829 		if (!gump_man->gump_mode() || gump_man->gumps_dont_pause_game())
1830 //+++++++The following line is for testing:
1831 //			if (!walk_in_formation)
1832 			main_actor->get_followers();
1833 	}
1834 }
1835 
1836 /*
1837  *  Teleport the party.
1838  */
1839 
teleport_party(Tile_coord const & t,bool skip_eggs,int newmap,bool no_status_check)1840 void Game_window::teleport_party(
1841     Tile_coord const &t,    // Where to go.
1842     bool skip_eggs,         // Don't activate eggs at dest.
1843     int newmap,         // New map #, or -1 for same map.
1844     bool no_status_check
1845 ) {
1846 	Tile_coord oldpos = main_actor->get_tile();
1847 	main_actor->set_action(nullptr);  // Definitely need this, or you may
1848 	//   step back to where you came from.
1849 	moving_barge = nullptr;       // Calling 'done()' could be risky...
1850 	int i;
1851 	int cnt = party_man->get_count();
1852 	if (newmap != -1)
1853 		set_map(newmap);
1854 	main_actor->move(t.tx, t.ty, t.tz, newmap); // Move Avatar.
1855 	// Fixes a rare crash when moving between maps and teleporting:
1856 	newmap = main_actor->get_map_num();
1857 	center_view(t);         // Bring pos. into view, and insure all
1858 	clock->reset_palette();
1859 	//   objs. exist.
1860 	for (i = 0; i < cnt; i++) {
1861 		int party_member = party_man->get_member(i);
1862 		Actor *person = get_npc(party_member);
1863 		if (person && !person->is_dead() &&
1864 		        person->get_schedule_type() != Schedule::wait
1865 		        && (person->can_act() || no_status_check)) {
1866 			person->set_action(nullptr);
1867 			Tile_coord t1 = Map_chunk::find_spot(t, 8,
1868 			                                     person->get_shapenum(), person->get_framenum(),
1869 			                                     1);
1870 			if (t1.tx != -1) {
1871 				person->move(t1, newmap);
1872 				person->change_frame(person->get_dir_framenum(Actor::standing));
1873 			}
1874 		}
1875 	}
1876 	main_actor->get_followers();
1877 	if (!skip_eggs)         // Check all eggs around new spot.
1878 		Map_chunk::try_all_eggs(main_actor, t.tx, t.ty, t.tz,
1879 		                        oldpos.tx, oldpos.ty);
1880 //	teleported = 1;
1881 	// generate mousemotion event
1882 	int x;
1883 	int y;
1884 	SDL_GetMouseState(&x, &y);
1885 	SDL_WarpMouseInWindow(win->get_screen_window(), x, y);
1886 }
1887 
1888 /*
1889  *  Get party members.
1890  *
1891  *  Output: Number returned in 'list'.
1892  */
1893 
get_party(Actor ** a_list,int avatar_too)1894 int Game_window::get_party(
1895     Actor **a_list,         // Room for 9.
1896     int avatar_too          // 1 to include Avatar too.
1897 ) {
1898 	int n = 0;
1899 	if (avatar_too && main_actor)
1900 		a_list[n++] = main_actor;
1901 	int cnt = party_man->get_count();
1902 	for (int i = 0; i < cnt; i++) {
1903 		int party_member = party_man->get_member(i);
1904 		Actor *person = get_npc(party_member);
1905 		if (person)
1906 			a_list[n++] = person;
1907 	}
1908 	return n;           // Return # actually stored.
1909 }
1910 
1911 /*
1912  *  Find a given shaped item amongst the party, and 'activate' it.  This
1913  *  is used, for example, by the 'f' command to feed.
1914  *
1915  *  Output: True if the item was found, else false.
1916  */
1917 
activate_item(int shnum,int frnum,int qual)1918 bool Game_window::activate_item(
1919     int shnum,          // Desired shape.
1920     int frnum,          // Desired frame
1921     int qual            // Desired quality
1922 ) {
1923 	Actor *party[9];        // Get party.
1924 	int cnt = get_party(party, 1);
1925 	for (int i = 0; i < cnt; i++) {
1926 		Actor *person = party[i];
1927 		Game_object *obj = person->find_item(shnum, qual, frnum);
1928 		if (obj) {
1929 			obj->activate();
1930 			return true;
1931 		}
1932 	}
1933 	return false;
1934 }
1935 /*
1936  *  Find the top object that can be selected, dragged, or activated.
1937  *  The one returned is the 'highest'.
1938  *
1939  *  Output: ->object, or null if none.
1940  */
1941 
find_object(int x,int y)1942 Game_object *Game_window::find_object(
1943     int x, int y            // Pos. on screen.
1944 ) {
1945 #ifdef DEBUG
1946 	cout << "Clicked at tile (" << get_scrolltx() + x / c_tilesize << ", " <<
1947 	     get_scrollty() + y / c_tilesize << ")" << endl;
1948 #endif
1949 	int not_above = get_render_skip_lift();
1950 	// Figure chunk #'s.
1951 	int start_cx = ((scrolltx +
1952 	                 x / c_tilesize) / c_tiles_per_chunk) % c_num_chunks;
1953 	int start_cy = ((scrollty +
1954 	                 y / c_tilesize) / c_tiles_per_chunk) % c_num_chunks;
1955 	// Check 1 chunk down & right too.
1956 	int stop_cx = (2 + (scrolltx +
1957 	                    (x + 4 * not_above) / c_tilesize) / c_tiles_per_chunk) % c_num_chunks;
1958 	int stop_cy = (2 + (scrollty +
1959 	                    (y + 4 * not_above) / c_tilesize) / c_tiles_per_chunk) % c_num_chunks;
1960 
1961 	Game_object *best = nullptr;      // Find 'best' one.
1962 	bool trans = true;      // Try to avoid 'transparent' objs.
1963 	// Go through them.
1964 	for (int cy = start_cy; cy != stop_cy; cy = INCR_CHUNK(cy))
1965 		for (int cx = start_cx; cx != stop_cx; cx = INCR_CHUNK(cx)) {
1966 			Map_chunk *olist = map->get_chunk(cx, cy);
1967 			if (!olist)
1968 				continue;
1969 			Object_iterator next(olist->get_objects());
1970 			Game_object *obj;
1971 			while ((obj = next.get_next()) != nullptr) {
1972 				if (obj->get_lift() >= not_above ||
1973 				        !get_shape_rect(obj).has_world_point(x, y) ||
1974 				        !obj->is_findable())
1975 					continue;
1976 				// Check the shape itself.
1977 				Shape_frame *s = obj->get_shape();
1978 				int ox;
1979 				int oy;
1980 				get_shape_location(obj, ox, oy);
1981 				if (!s->has_point(x - ox, y - oy))
1982 					continue;
1983 				// Fixes key under rock in BG at [915, 2434, 0]; need to
1984 				// know if there are side effects.
1985 				if (!best || best->lt(*obj) != 0 || trans) {
1986 					bool ftrans = obj->get_info().is_transparent();
1987 					// TODO: Fix this properly, instead of with an ugly hack.
1988 					// This fixes clicking the Y shapes instead of the Y
1989 					// depression in SI. This has the effect of also making
1990 					// the Y depressions not draggable.
1991 					if (GAME_SI && obj->get_shapenum() == 0xd1
1992 					    && obj->get_framenum() == 17) {
1993 						ftrans = true;  // Pretend it is transparent
1994 					}
1995 					if (!ftrans || trans) {
1996 						best = obj;
1997 						trans = ftrans;
1998 					}
1999 				}
2000 			}
2001 		}
2002 	return best;
2003 }
2004 
find_nearby_objects(Game_object_map_xy & mobjxy,int x,int y,Gump * gump)2005 void Game_window::find_nearby_objects(Game_object_map_xy& mobjxy, int x, int y, Gump *gump) {
2006 	// Find object at each pixel
2007 	for (int iy = y - 10; iy < (y + 10); iy++) {
2008 		for (int ix = x - 10; ix < (x + 10); ix++) {
2009 			Game_object *iobj;
2010 			if (gump) {
2011 				iobj = gump->find_object(ix, iy);
2012 			} else {
2013 				iobj = find_object(ix, iy);
2014 			}
2015 
2016 			if (iobj) {
2017 				const Shape_info &info = iobj->get_info();
2018 				auto cl = info.get_shape_class();
2019 				// Filter list a bit.
2020 				if (cl == Shape_info::unusable || cl == Shape_info::building) {
2021 					continue;
2022 				}
2023 				if ((cl == Shape_info::hatchable || cl == Shape_info::barge || info.is_barge_part()) && !cheat.in_map_editor()) {
2024 					continue;
2025 				}
2026 				if (info.is_transparent()) {
2027 					continue;
2028 				}
2029 				mobjxy[iobj] = Position2d{ix, iy};
2030 			}
2031 		}
2032 	}
2033 }
2034 
Get_object_name(const Game_object * obj)2035 static inline string Get_object_name(const Game_object *obj) {
2036 	if (obj == Game_window::get_instance()->get_main_actor()) {
2037 		if (GAME_BG)
2038 			return get_misc_name(0x42);
2039 		else if (GAME_SI)
2040 			return get_misc_name(0x4e);
2041 		else
2042 			return obj->get_name();
2043 	}
2044 	return obj->get_name();
2045 }
2046 
2047 /*
2048  *  Show the name of the item the mouse is clicked on.
2049  */
2050 
show_items(int x,int y,bool ctrl)2051 void Game_window::show_items(
2052     int x, int y,           // Coords. in window.
2053     bool ctrl           // Control key is pressed.
2054 ) {
2055 	// Look for obj. in open gump.
2056 	Gump *gump = gump_man->find_gump(x, y);
2057 	Game_object *obj;       // What we find.
2058 	if (gump) {
2059 		obj = gump->find_object(x, y);
2060 		if (!obj) obj = gump->get_cont_or_actor(x, y);
2061 	} else {            // Search rest of world.
2062 		obj = find_object(x, y);
2063 	}
2064 
2065 	if (item_menu){
2066 		Game_object_map_xy mobjxy;
2067 		find_nearby_objects(mobjxy, x, y, gump);
2068 		if (!mobjxy.empty() && Notebook_gump::get_instance() == nullptr) {
2069 			// Make sure menu is visible on the screen
2070 			Itemmenu_gump itemgump(&mobjxy, x, y);
2071 			Game_window::get_instance()->get_gump_man()->do_modal_gump(&itemgump, Mouse::hand);
2072 			obj = nullptr;
2073 		}
2074 	}
2075 
2076 	// Map-editing?
2077 	if (obj && cheat.in_map_editor()) {
2078 		if (ctrl)       // Control?  Toggle.
2079 			cheat.toggle_selected(obj);
2080 		else {
2081 			// In normal mode, sel. just this one.
2082 			cheat.clear_selected();
2083 			if (cheat.get_edit_mode() == Cheat::move)
2084 				cheat.append_selected(obj);
2085 		}
2086 	} else              // All other cases:  unselect.
2087 		cheat.clear_selected();
2088 
2089 	// Do we have an NPC?
2090 	Actor *npc = obj ? obj->as_actor() : nullptr;
2091 	if (npc && cheat.number_npcs() &&
2092 	        (npc->get_npc_num() > 0 || npc == main_actor)) {
2093 		char str[64];
2094 		std::string namestr = Get_object_name(obj);
2095 		snprintf(str, sizeof(str)-1, "(%i) %s", npc->get_npc_num(),
2096 		         namestr.c_str());
2097 		str[sizeof(str)-1] = 0;
2098 		effects->add_text(str, obj);
2099 	} else if (obj) {
2100 		// Show name.
2101 		std::string namestr = Get_object_name(obj);
2102 		if (Game_window::get_instance()->failed_copy_protection() &&
2103 		        (npc == main_actor || !npc)) {  // Avatar and items
2104 			namestr = "Oink!";
2105 		}
2106 		// Combat and an NPC?
2107 		if (in_combat() && Combat::mode != Combat::original && npc) {
2108 			char buf[128];
2109 			snprintf(buf, sizeof(buf)-1, " (%d)",
2110 			        npc->get_property(Actor::health));
2111 			buf[sizeof(buf)-1] = 0;
2112 			namestr += buf;
2113 		}
2114 		effects->add_text(namestr.c_str(), obj);
2115 	} else if (cheat.in_map_editor() && skip_lift > 0) {
2116 		// Show flat, but not when editing ter.
2117 		ShapeID id = get_flat(x, y);
2118 		char str[20];
2119 		snprintf(str, sizeof(str)-1, "Flat %d:%d",
2120 		         id.get_shapenum(), id.get_framenum());
2121 		str[sizeof(str)-1] = 0;
2122 		effects->add_text(str, x, y);
2123 	}
2124 	// If it's an actor and we want to grab the actor, grab it.
2125 	if (npc && cheat.grabbing_actor() &&
2126 	        (npc->get_npc_num() || npc == main_actor))
2127 		cheat.set_grabbed_actor(npc);
2128 
2129 #ifdef DEBUG
2130 	int shnum;
2131 	if (obj) {
2132 		shnum = obj->get_shapenum();
2133 		int frnum = obj->get_framenum();
2134 		const Shape_info &info = obj->get_info();
2135 		cout << "Object " << shnum << ':' << frnum <<
2136 		     " has 3d tiles (x, y, z): " <<
2137 		     info.get_3d_xtiles(frnum) << ", " <<
2138 		     info.get_3d_ytiles(frnum) << ", " <<
2139 		     info.get_3d_height();
2140 		Actor *npc = obj->as_actor();
2141 		if (npc)
2142 			cout  << ", sched = " <<
2143 			      npc->get_schedule_type() << ", real align = " <<
2144 			      npc->get_alignment() << ", eff. align = " <<
2145 			      npc->get_effective_alignment() << ", npcnum = " <<
2146 			      npc->get_npc_num();
2147 		cout << endl;
2148 		if (obj->get_chunk()) {
2149 			Tile_coord t = obj->get_tile();
2150 			cout << "tx = " << t.tx << ", ty = " << t.ty <<
2151 			     ", tz = " << t.tz << ", ";
2152 		}
2153 		cout << "quality = " <<
2154 		     obj->get_quality() <<
2155 		     ", okay_to_take = " <<
2156 		     obj->get_flag(Obj_flags::okay_to_take) <<
2157 		     ", flag0x1d = " << obj->get_flag(0x1d) <<
2158 		     ", hp = " << obj->get_obj_hp() << ", weight = " << obj->get_weight()
2159 		     << ", volume = " << obj->get_volume()
2160 		     << endl;
2161 		cout << "obj = " << obj << endl;
2162 		if (obj->get_flag(Obj_flags::asleep))
2163 			cout << "ASLEEP" << endl;
2164 		if (obj->is_egg())  // Show egg info. here.
2165 			obj->as_egg()->print_debug();
2166 	} else {            // Obj==0
2167 		ShapeID id = get_flat(x, y);
2168 		shnum = id.get_shapenum();
2169 		cout << "Clicked on flat shape " <<
2170 		     shnum << ':' << id.get_framenum() << endl;
2171 
2172 #ifdef CHUNK_OBJ_DUMP
2173 		Map_chunk *chunk = map->get_chunk_safely(x / c_tiles_per_chunk, y / c_tiles_per_chunk);
2174 		if (chunk) {
2175 			Object_iterator it(chunk->get_objects());
2176 			Game_object *each;
2177 			cout << "Chunk Contents: " << endl;
2178 			while ((each = it.get_next()) != nullptr)
2179 				cout << "    " << each->get_name() << ":" << each->get_shapenum() << ":" << each->get_framenum() << endl;
2180 		}
2181 #endif
2182 		if (id.is_invalid())
2183 			return;
2184 	}
2185 	const Shape_info &info = ShapeID::get_info(shnum);
2186 	cout << "TFA[1][0-6]= " << (info.get_tfa(1) & 127) << endl;
2187 	cout << "TFA[0][0-1]= " << (info.get_tfa(0) & 3) << endl;
2188 	cout << "TFA[0][3-4]= " << ((info.get_tfa(0) >> 3) & 3) << endl;
2189 	if (info.is_animated())
2190 		cout << "Object is ANIMATED" << endl;
2191 	if (info.has_translucency())
2192 		cout << "Object has TRANSLUCENCY" << endl;
2193 	if (info.is_transparent())
2194 		cout << "Object is TRANSPARENT" << endl;
2195 	if (info.is_light_source())
2196 		cout << "Object is LIGHT_SOURCE" << endl;
2197 	if (info.is_door())
2198 		cout << "Object is a DOOR" << endl;
2199 	if (info.is_solid())
2200 		cout << "Object is SOLID" << endl;
2201 #endif
2202 }
2203 
2204 /*
2205  *  Handle right click when combat is paused.  The user can right-click
2206  *  on a party member, then on an enemy to attack.
2207  */
2208 
paused_combat_select(int x,int y)2209 void Game_window::paused_combat_select(
2210     int x, int y            // Coords in window.
2211 ) {
2212 	Gump *gump = gump_man->find_gump(x, y);
2213 	if (gump)
2214 		return;         // Ignore if clicked on gump.
2215 	Game_object *obj = find_object(x, y);
2216 	Actor *npc = obj ? obj->as_actor() : nullptr;
2217 	if (!npc || !npc->is_in_party() ||
2218 	        npc->get_flag(Obj_flags::asleep) || npc->is_dead() ||
2219 	        npc->get_flag(Obj_flags::paralyzed) ||
2220 	        !npc->can_act_charmed())
2221 		return;         // Want an active party member.
2222 	npc->paint_outline(PROTECT_PIXEL);
2223 	show(true);         // Flash white outline.
2224 	SDL_Delay(100);
2225 	npc->add_dirty();
2226 	paint_dirty();
2227 	show();
2228 	// Pick a spot.
2229 	if (!Get_click(x, y, Mouse::greenselect, nullptr, true))
2230 		return;
2231 	obj = find_object(x, y);    // Find it.
2232 	if (!obj) {         // Nothing?  Walk there.
2233 		// Needs work if lift > 0.
2234 		int lift = npc->get_lift();
2235 		int liftpixels = 4 * lift;
2236 		Tile_coord dest(scrolltx + (x + liftpixels) / c_tilesize,
2237 		                scrollty + (y + liftpixels) / c_tilesize, lift);
2238 		// Aim within 1 tile.
2239 		if (!npc->walk_path_to_tile(dest, std_delay, 0, 1))
2240 			Mouse::mouse->flash_shape(Mouse::blocked);
2241 		else            // Make sure he's in combat mode.
2242 			npc->set_target(nullptr, true);
2243 		return;
2244 	}
2245 	Actor *target = obj->as_actor();
2246 	// Don't attack party or body.
2247 	if ((target && target->is_in_party()) ||
2248 	        obj->get_info().is_body_shape()) {
2249 		Mouse::mouse->flash_shape(Mouse::redx);
2250 		return;
2251 	}
2252 	npc->set_target(obj, true);
2253 	obj->paint_outline(HIT_PIXEL);  // Flash red outline.
2254 	show(true);
2255 	SDL_Delay(100);
2256 	add_dirty(obj);
2257 	paint_dirty();
2258 	show();
2259 	if (npc->get_schedule_type() != Schedule::combat)
2260 		npc->set_schedule_type(Schedule::combat);
2261 }
2262 
2263 /*
2264  *  Get the 'flat' that a screen point is in.
2265  */
2266 
get_flat(int x,int y)2267 ShapeID Game_window::get_flat(
2268     int x, int y            // Window point.
2269 ) {
2270 	int tx = (get_scrolltx() + x / c_tilesize) % c_num_tiles;
2271 	int ty = (get_scrollty() + y / c_tilesize) % c_num_tiles;
2272 	int cx = tx / c_tiles_per_chunk;
2273 	int cy = ty / c_tiles_per_chunk;
2274 	tx = tx % c_tiles_per_chunk;
2275 	ty = ty % c_tiles_per_chunk;
2276 	Map_chunk *chunk = map->get_chunk(cx, cy);
2277 	ShapeID id = chunk->get_flat(tx, ty);
2278 	return id;
2279 }
2280 
2281 /*
2282  *  Handle a double-click.
2283  */
2284 
double_clicked(int x,int y)2285 void Game_window::double_clicked(
2286     int x, int y            // Coords in window.
2287 ) {
2288 	// Animation in progress?
2289 	if (main_actor_dont_move())
2290 		return;
2291 	// Nothing going on?
2292 	// Look for obj. in open gump.
2293 	Game_object *obj = nullptr;
2294 	bool gump = gump_man->double_clicked(x, y, obj);
2295 	bool avatar_can_act = main_actor_can_act();
2296 
2297 	// If gump manager didn't handle it, we search the world for an object
2298 	if (!gump) {
2299 		obj = find_object(x, y);
2300 		if (!avatar_can_act && obj && obj->as_actor()
2301 		        && obj->as_actor() == main_actor->as_actor()) {
2302 			ActionFileGump(nullptr);
2303 			return;
2304 		}
2305 		// Check path, except if an NPC, sign, or if editing.
2306 		if (obj && !obj->as_actor() &&
2307 		        !cheat.in_hack_mover() &&
2308 		        //!Is_sign(obj->get_shapenum()) &&
2309 		        !Fast_pathfinder_client::is_grabable(main_actor, obj)) {
2310 			Mouse::mouse->flash_shape(Mouse::blocked);
2311 			return;
2312 		}
2313 	}
2314 	if (!obj || !avatar_can_act)
2315 		return;         // Nothing found or avatar disabled.
2316 	if (combat && !gump &&      // In combat?
2317 	        !Combat::is_paused() && main_actor_can_act_charmed() &&
2318 	        (!gump_man->gump_mode() || gump_man->gumps_dont_pause_game())) {
2319 		Actor *npc = obj->as_actor();
2320 		// But don't attack party members.
2321 		if ((!npc || !npc->is_in_party()) &&
2322 		        // Or bodies.
2323 		        !obj->get_info().is_body_shape()) {
2324 			// In combat mode.
2325 			// Want everyone to be in combat.
2326 			combat = false;
2327 			main_actor->set_target(obj);
2328 			toggle_combat();
2329 			return;
2330 		}
2331 	}
2332 	effects->remove_text_effects(); // Remove text msgs. from screen.
2333 #ifdef DEBUG
2334 	cout << "Object name is " << obj->get_name() << endl;
2335 #endif
2336 	usecode->init_conversation();
2337 	obj->activate();
2338 	npc_prox->wait(4);      // Delay "barking" for 4 secs.
2339 }
2340 
2341 
2342 /*
2343  *  Add an NPC to the 'nearby' list.
2344  */
2345 
add_nearby_npc(Npc_actor * npc)2346 void Game_window::add_nearby_npc(
2347     Npc_actor *npc
2348 ) {
2349 	if (!npc->is_nearby()) {
2350 		npc->set_nearby();
2351 		npc_prox->add(Game::get_ticks(), npc);
2352 	}
2353 }
2354 
2355 /*
2356  *  Remove an NPC from the nearby list.
2357  */
2358 
remove_nearby_npc(Npc_actor * npc)2359 void Game_window::remove_nearby_npc(
2360     Npc_actor *npc
2361 ) {
2362 	if (npc->is_nearby())
2363 		npc_prox->remove(npc);
2364 }
2365 
2366 /*
2367  *  Add all nearby NPC's to the given list.
2368  */
2369 
get_nearby_npcs(Actor_vector & a_list) const2370 void Game_window::get_nearby_npcs(
2371     Actor_vector &a_list
2372 ) const {
2373 	npc_prox->get_all(a_list);
2374 }
2375 
2376 /*
2377  *  Tell all npc's to update their schedules at a new 3-hour period.
2378  */
2379 
schedule_npcs(int hour,bool repaint)2380 void Game_window::schedule_npcs(
2381     int hour,           // 24 hour
2382     bool repaint
2383 ) {
2384 	// Go through npc's, skipping Avatar.
2385 	for (auto& npc : npcs) {
2386 		if (!npc)
2387 			continue;
2388 		// Don't want companions leaving.
2389 		if (npc->get_schedule_type() != Schedule::wait &&
2390 		        (npc->get_schedule_type() != Schedule::combat ||
2391 		         npc->get_target() == nullptr))
2392 			npc->update_schedule(hour / 3, hour % 3 == 0 ? -1 : 0);
2393 	}
2394 
2395 	if (repaint)
2396 		paint();            // Repaint all.
2397 }
2398 
2399 /*
2400  *  Tell all npc's to restore some of their HP's and/or mana on the hour.
2401  */
2402 
mend_npcs()2403 void Game_window::mend_npcs(
2404 ) {
2405 	// Go through npc's.
2406 	for (auto& npc : npcs) {
2407 		if (npc)
2408 			npc->mend_wounds(true);
2409 	}
2410 }
2411 
2412 /*
2413  *  Get guard shape.
2414  */
2415 
get_guard_shape()2416 int Game_window::get_guard_shape(
2417 ) {
2418 	// Base on avatar's position.
2419 	Tile_coord const pos = Game_window::get_instance()->main_actor->get_tile();
2420 	if (!GAME_SI)           // Default (BG).
2421 		return 0x3b2;
2422 	// Moonshade?
2423 	if (pos.tx >= 2054 && pos.ty >= 1698 &&
2424 	        pos.tx < 2590 && pos.ty < 2387)
2425 		return 0x103;       // Ranger.
2426 	// Fawn?
2427 	if (pos.tx >= 895 && pos.ty >= 1604 &&
2428 	        pos.tx < 1173 && pos.ty < 1960)
2429 		return 0x17d;       // Fawn guard.
2430 	if (pos.tx >= 670 && pos.ty >= 2430 &&
2431 	        pos.tx < 1135 && pos.ty < 2800)
2432 		return 0xe4;        // Pikeman.
2433 	return -1;          // No local guard.
2434 }
2435 
2436 /*
2437  *  Find a witness to the Avatar's thievery.
2438  *
2439  *  Output: ->witness, or nullptr.
2440  *      closest_npc = closest one that's nearby.
2441  */
2442 
find_witness(Actor * & closest_npc,int align)2443 Actor *Game_window::find_witness(
2444     Actor  *&closest_npc,       // Closest one returned.
2445     int align                   // Desired alignment.
2446 ) {
2447 	Actor_vector nearby;          // See if someone is nearby.
2448 	main_actor->find_nearby_actors(nearby, c_any_shapenum, 12, 0x28);
2449 	closest_npc = nullptr;        // Look for closest NPC.
2450 	int closest_dist = 5000;
2451 	Actor *witness = nullptr;     // And closest facing us.
2452 	int closest_witness_dist = 5000;
2453 	int gshape = get_guard_shape();
2454 	for (auto& npc : nearby) {
2455 		// Want non-party intelligent and not disabled NPCs only.
2456 		if (npc->is_in_party() || !npc->is_sentient() || !npc->can_act())
2457 			continue;
2458 		// Witnesses must either match desired alignment or they must be
2459 		// local guards and the alignment must be neutral. This makes guards
2460 		// assist neutral and chaotic NPCs.
2461 		if (npc->get_effective_alignment() != align ||
2462 		        (npc->get_shapenum() == gshape && align != Actor::neutral))
2463 			continue;
2464 		int dist = npc->distance(main_actor);
2465 		if (dist >= closest_witness_dist ||
2466 		        !Fast_pathfinder_client::is_grabable(npc, main_actor))
2467 			continue;
2468 		// Looking toward Avatar?
2469 		auto sched =
2470 		    static_cast<Schedule::Schedule_types>(npc->get_schedule_type());
2471 		int dir = npc->get_direction(main_actor);
2472 		int facing = npc->get_dir_facing();
2473 		int dirdiff = (dir - facing + 8) % 8;
2474 		if (dirdiff < 3 || dirdiff > 5 || sched == Schedule::hound) {
2475 			// Yes.
2476 			witness = npc;
2477 			closest_witness_dist = dist;
2478 		} else if (dist < closest_dist) {
2479 			closest_npc = npc;
2480 			closest_dist = dist;
2481 		}
2482 	}
2483 	return witness;
2484 }
2485 
2486 /*
2487  *  Handle theft.
2488  */
2489 
theft()2490 void Game_window::theft(
2491 ) {
2492 	// See if in a new location.
2493 	int cx = main_actor->get_cx();
2494 	int cy = main_actor->get_cy();
2495 	if (cx != theft_cx || cy != theft_cy) {
2496 		theft_cx = cx;
2497 		theft_cy = cy;
2498 		theft_warnings = 0;
2499 	}
2500 	Actor *closest_npc;
2501 	Actor *witness = find_witness(closest_npc, Actor::neutral);
2502 	if (!witness) {
2503 		if (closest_npc && rand() % 2) {
2504 			if (closest_npc->is_goblin())
2505 				closest_npc->say(goblin_heard_something);
2506 			else if (closest_npc->can_speak())
2507 				closest_npc->say(heard_something);
2508 		}
2509 		return;         // Didn't get caught.
2510 	}
2511 	bool avainvisible = main_actor->get_flag(Obj_flags::invisible);
2512 	if (avainvisible && !witness->can_see_invisible()) {
2513 		if (witness->is_goblin())
2514 			witness->say(goblin_invis_theft);
2515 		else if (witness->can_speak())
2516 			witness->say(first_invis_theft, last_invis_theft);
2517 		return;         // Didn't get caught because was invisible.
2518 	}
2519 	int dir = witness->get_direction(main_actor);
2520 	// Face avatar.
2521 	witness->change_frame(witness->get_dir_framenum(dir,
2522 	                      Actor::standing));
2523 	// If not in combat, change to hound schedule.
2524 	auto sched =
2525 	    static_cast<Schedule::Schedule_types>(witness->get_schedule_type());
2526 	if (sched != Schedule::combat && sched != Schedule::hound)
2527 		witness->set_schedule_type(Schedule::hound);
2528 	theft_warnings++;
2529 	if (theft_warnings < 2 + rand() % 3) {
2530 		// Just a warning this time.
2531 		if (witness->is_goblin())
2532 			witness->say(goblin_theft);
2533 		else if (witness->can_speak())
2534 			witness->say(first_theft, last_theft);
2535 		return;
2536 	}
2537 	gump_man->close_all_gumps();    // Get gumps off screen.
2538 	call_guards(witness, true);
2539 }
2540 
2541 /*
2542  *  Create a guard to arrest the Avatar.
2543  */
2544 
call_guards(Actor * witness,bool theft)2545 void Game_window::call_guards(
2546     Actor *witness,         // ->witness, or nullptr to find one.
2547     bool theft              // called from Game_window::theft
2548 ) {
2549 	Actor *closest;
2550 	if (armageddon || in_dungeon)
2551 		return;
2552 	int gshape = get_guard_shape();
2553 	int align = witness ? witness->get_effective_alignment() : Actor::neutral;
2554 	if (witness || (witness = find_witness(closest, align)) != nullptr) {
2555 		if (witness->is_goblin()) {
2556 			if (gshape < 0)
2557 				witness->say(goblin_need_help);
2558 			else if (theft)
2559 				witness->say(goblin_call_guards_theft);
2560 			else
2561 				witness->say(first_goblin_call_guards, last_goblin_call_guards);
2562 		} else if (witness->can_speak()) {
2563 			if (gshape < 0)
2564 				witness->say(first_need_help, last_need_help);
2565 			else if (theft)
2566 				witness->say(first_call_guards_theft, last_call_guards_theft);
2567 			else
2568 				witness->say(first_call_guards, last_call_guards);
2569 		}
2570 	}
2571 	if (gshape < 0) { // No local guards; lets forward to attack_avatar.
2572 		attack_avatar(0, align);
2573 		return;
2574 	}
2575 
2576 	Tile_coord dest = Map_chunk::find_spot(main_actor->get_tile(), 5, gshape, 0, 1);
2577 	if (dest.tx != -1) {
2578 		// Show guard running up.
2579 		// Create it off-screen.
2580 		int numguards = 1 + rand() % 3;
2581 		Tile_coord offscreen(main_actor->get_tile() + Tile_coord(128, 128, 0));
2582 		// Start in combat if avatar is fighting.
2583 		// FIXME: Disabled for now, as causes guards to attack
2584 		// avatar if you break glass, when they should arrest
2585 		// instead.
2586 		Schedule::Schedule_types sched = /*combat ? Schedule::combat
2587                                                 :*/ Schedule::arrest_avatar;
2588 		for (int i = 0; i < numguards; i++) {
2589 			Game_object_shared new_guard = Monster_actor::create(
2590 							   gshape, offscreen, sched, Actor::chaotic);
2591 			auto *guard = static_cast<Monster_actor *>(
2592 													new_guard.get());
2593 			add_nearby_npc(guard);
2594 			guard->approach_another(main_actor);
2595 		}
2596 		// Guaranteed way to do it.
2597 		MyMidiPlayer *player = Audio::get_ptr()->get_midi();
2598 		if (player &&
2599 		        !Background_noise::is_combat_music(player->get_current_track()))
2600 			Audio::get_ptr()->start_music(Audio::game_music(10), true);
2601 	}
2602 }
2603 
2604 /*
2605  *  Makes guards stop trying to arrest the avatar. Based on what it seems to
2606  *  do in SI, as well as what usecode seems to want to do.
2607  */
2608 
stop_arresting()2609 void Game_window::stop_arresting(
2610 ) {
2611 	if (armageddon || in_dungeon)
2612 		return;
2613 	int gshape = get_guard_shape();
2614 	if (gshape < 0) { // No one to calm down.
2615 		return;
2616 	}
2617 
2618 	Actor_vector nearby;      // See if someone is nearby.
2619 	main_actor->find_nearby_actors(nearby, gshape, 20, 0x28);
2620 	for (auto& npc : nearby) {
2621 		if (!npc->is_in_party() && npc->get_schedule_type() == Schedule::arrest_avatar) {
2622 			npc->set_schedule_type(Schedule::wander);
2623 			// Prevent guard from becoming hostile.
2624 			npc->set_alignment(Actor::neutral);
2625 		}
2626 	}
2627 }
2628 
2629 /*
2630  *  Have nearby residents attack the Avatar.
2631  */
2632 
attack_avatar(int create_guards,int align)2633 void Game_window::attack_avatar(
2634     int create_guards,      // # of extra guards to create.
2635     int align
2636 ) {
2637 	if (armageddon)
2638 		return;
2639 	int gshape = get_guard_shape();
2640 	if (gshape >= 0 && !in_dungeon) {
2641 		while (create_guards--) {
2642 			// Create it off-screen.
2643 			Game_object_shared new_guard = Monster_actor::create(gshape,
2644 			                  main_actor->get_tile() + Tile_coord(128, 128, 0),
2645 			                  Schedule::combat, Actor::chaotic);
2646 			auto *guard = static_cast<Monster_actor *>(
2647 													new_guard.get());
2648 			add_nearby_npc(guard);
2649 			guard->set_target(main_actor, true);
2650 			guard->approach_another(main_actor);
2651 		}
2652 	}
2653 
2654 	int numhelpers = 0;
2655 	int const maxhelpers = 3;
2656 	Actor_vector nearby;      // See if someone is nearby.
2657 	main_actor->find_nearby_actors(nearby, c_any_shapenum, 20, 0x28);
2658 	for (auto& npc : nearby) {
2659 		if (npc->can_act() && !npc->is_in_party() && npc->is_sentient() &&
2660 		        ((npc->get_shapenum() == gshape && !in_dungeon) ||
2661 		         align == npc->get_effective_alignment()) &&
2662 		        // Only if can get there.
2663 		        Fast_pathfinder_client::is_grabable(npc, main_actor)) {
2664 			npc->set_target(main_actor, true);
2665 			if (++numhelpers >= maxhelpers) {
2666 				break;
2667 			}
2668 		}
2669 	}
2670 	// Guaranteed way to do it.
2671 	MyMidiPlayer *player = Audio::get_ptr()->get_midi();
2672 	if (player &&
2673 	        !Background_noise::is_combat_music(player->get_current_track()))
2674 		Audio::get_ptr()->start_music(Audio::game_music(10), true);
2675 }
2676 
2677 /*
2678  *  Gain/lose focus.
2679  */
2680 
get_focus()2681 void Game_window::get_focus(
2682 ) {
2683 	cout << "Game resumed" << endl;
2684 	Audio::get_ptr()->resume_audio();
2685 	focus = true;
2686 	tqueue->resume(Game::get_ticks());
2687 }
lose_focus()2688 void Game_window::lose_focus(
2689 ) {
2690 	if (!focus)
2691 		return;         // Fixes SDL bug.
2692 	cout << "Game paused" << endl;
2693 
2694 	string str;
2695 	config->value("config/audio/disablepause", str, "no");
2696 	if (str == "no")
2697 		Audio::get_ptr()->pause_audio();
2698 
2699 	focus = false;
2700 	tqueue->pause(Game::get_ticks());
2701 }
2702 
2703 
2704 /*
2705  *  Prepare for game
2706  */
2707 
setup_game(bool map_editing)2708 void Game_window::setup_game(
2709     bool map_editing
2710 ) {
2711 	// Map 0 always exists.
2712 	get_map(0)->init();
2713 	if (is_system_path_defined("<PATCH>"))
2714 		// There is a patch dir; search for other maps.
2715 		for (int i = 1; i <= 0xFF; i++) {
2716 			char fname[128];
2717 			if (U7exists(Get_mapped_name(PATCH_U7MAP, i, fname)))
2718 				get_map(i)->init();
2719 		}
2720 	// Init. current 'tick'.
2721 	Game::set_ticks(SDL_GetTicks());
2722 	Face_stats::RemoveGump(); // it tries to update when reading actors so delete it until finished loading
2723 	init_actors();      // Set up actors if not already done.
2724 	// This also sets up initial
2725 	// schedules and positions.
2726 
2727 	cycle_load_palette();
2728 
2729 	Notebook_gump::initialize();        // Read in journal.
2730 
2731 	//read autonotes
2732 	std::string d;
2733 	std::string autonotesfilename;
2734 	d = "config/disk/game/" + Game::get_gametitle() + "/autonotes";
2735 	config->value(d.c_str(), autonotesfilename, "(default)");
2736 	if (autonotesfilename == "(default)") {
2737 		config->set(d.c_str(), autonotesfilename, true);
2738 		Notebook_gump::read_auto_text();
2739 	} else {
2740 		try {
2741 			Notebook_gump::read_auto_text_file(autonotesfilename.c_str());
2742 		} catch (file_open_exception &err) {
2743 			cerr << "Autonotes file '" << autonotesfilename << "' not found, falling back to default autonotes." << endl;
2744 			Notebook_gump::read_auto_text();
2745 		}
2746 	}
2747 
2748 	usecode->read();        // Read the usecode flags
2749 	cycle_load_palette();
2750 
2751 	if (Game::get_game_type() == BLACK_GATE && !map_editing) {
2752 		string yn;      // Override from config. file.
2753 		// Skip intro. scene?
2754 		config->value("config/gameplay/skip_intro", yn, "no");
2755 		if (yn == "yes")
2756 			usecode->set_global_flag(
2757 			    Usecode_machine::did_first_scene, 1);
2758 
2759 		// Should Avatar be visible?
2760 		if (usecode->get_global_flag(Usecode_machine::did_first_scene))
2761 			main_actor->clear_flag(Obj_flags::bg_dont_render);
2762 		else
2763 			main_actor->set_flag(Obj_flags::bg_dont_render);
2764 	}
2765 
2766 	cycle_load_palette();
2767 
2768 	// Fade out & clear screen before palette change
2769 	pal->fade_out(c_fade_out_time);
2770 	clear_screen(true);
2771 	load_palette_timer = 0;
2772 
2773 	// note: we had to stop the plasma here already, because init_readied
2774 	// and activate_eggs may update the screen through usecode functions
2775 	// (Helm of Light, for example)
2776 	Actor *party[9];
2777 	int cnt = get_party(party, 1);  // Get entire party.
2778 	for (int i = 0; i < cnt; i++) { // Init. rings.
2779 		party[i]->init_readied();
2780 	}
2781 	// Ensure time is stopped if map-editing.
2782 	time_stopped = map_editing ? -1 : 0;
2783 //+++++The below wasn't prev. done by ::read(), so maybe it should be
2784 //+++++controlled by a 'first-time' flag.
2785 	// Want to activate first egg.
2786 	Map_chunk *olist = main_actor->get_chunk();
2787 	olist->setup_cache();
2788 
2789 	Tile_coord t = main_actor->get_tile();
2790 	// Do them immediately.
2791 	if (!map_editing)       // (Unless map-editing.)
2792 		olist->activate_eggs(main_actor, t.tx, t.ty, t.tz, -1, -1, true);
2793 	// Force entire repaint.
2794 	set_all_dirty();
2795 	painted = true;         // Main loop uses this.
2796 	gump_man->close_all_gumps(true);        // Kill gumps.
2797 	Face_stats::load_config(config);
2798 	if(using_shortcutbar())
2799 		g_shortcutBar = new ShortcutBar_gump(0,0);
2800 
2801 	// Set palette for time-of-day.
2802 	clock->reset_palette();
2803 	pal->fade(6, 1, -1);        // Fade back in.
2804 }
2805 
2806 
2807 
plasma(int w,int h,int x,int y,int startc,int endc)2808 void Game_window::plasma(int w, int h, int x, int y, int startc, int endc) {
2809 	Image_buffer8 *ibuf = get_win()->get_ib8();
2810 
2811 	ibuf->fill8(startc, w, h, x, y);
2812 
2813 	for (int i = 0; i < w * h; i++) {
2814 		Uint8 pc = startc + rand() % (endc - startc + 1);
2815 		int px = x + rand() % w;
2816 		int py = y + rand() % h;
2817 
2818 		for (int j = 0; j < 6; j++) {
2819 			int px2 = px + rand() % 17 - 8;
2820 			int py2 = py + rand() % 17 - 8;
2821 			ibuf->fill8(pc, 3, 1, px2 - 1, py2);
2822 			ibuf->fill8(pc, 1, 3, px2, py2 - 1);
2823 		}
2824 	}
2825 	painted = true;
2826 }
2827 
2828 /*
2829  *  Chunk caching emulation: swap out chunks which are now at least
2830  *  3 chunks away.
2831  */
emulate_cache(Map_chunk * olist,Map_chunk * nlist)2832 void Game_window::emulate_cache(Map_chunk *olist, Map_chunk *nlist) {
2833 	if (!olist)
2834 		return;         // Seems like there's nothing to do.
2835 	// Cancel weather from eggs that are
2836 	// far away.
2837 	effects->remove_weather_effects(120);
2838 	int newx = nlist->get_cx();
2839 	int newy = nlist->get_cy();
2840 	int oldx = olist->get_cx();
2841 	int oldy = olist->get_cy();
2842 	Game_map *omap = olist->get_map();
2843 	Game_map *nmap = nlist->get_map();
2844 	// Cancel scripts 4 chunks from this.
2845 	Usecode_script::purge(Tile_coord(newx * c_tiles_per_chunk,
2846 	                                 newy * c_tiles_per_chunk, 0), 4 * c_tiles_per_chunk);
2847 	int nearby[5][5];       // Chunks within 3.
2848 	int x;
2849 	int y;
2850 	// Figure old range.
2851 	int old_minx = c_num_chunks + oldx - 2;
2852 	int old_maxx = c_num_chunks + oldx + 2;
2853 	int old_miny = c_num_chunks + oldy - 2;
2854 	int old_maxy = c_num_chunks + oldy + 2;
2855 	if (nmap == omap) {     // Same map?
2856 		// Set to 0
2857 		memset(nearby, 0, sizeof(nearby));
2858 		// Figure new range.
2859 		int new_minx = c_num_chunks + newx - 2;
2860 		int new_maxx = c_num_chunks + newx + 2;
2861 		int new_miny = c_num_chunks + newy - 2;
2862 		int new_maxy = c_num_chunks + newy + 2;
2863 		// Now we write what we are now near
2864 		for (y = new_miny; y <= new_maxy; y++) {
2865 			if (y > old_maxy)
2866 				break;      // Beyond the end.
2867 			int dy = y - old_miny;
2868 			if (dy < 0)
2869 				continue;
2870 			assert(dy < 5);
2871 			for (x = new_minx; x <= new_maxx; x++) {
2872 				if (x > old_maxx)
2873 					break;
2874 				int dx = x - old_minx;
2875 				if (dx >= 0) {
2876 					assert(dx < 5);
2877 					nearby[dx][dy] = 1;
2878 				}
2879 			}
2880 		}
2881 	} else              // New map, so cache out all of old.
2882 		memset(nearby, 0, sizeof(nearby));
2883 
2884 	// Swap out chunks no longer nearby (0).
2885 	Game_object_vector removes;
2886 	for (y = 0; y < 5; y++)
2887 		for (x = 0; x < 5; x++) {
2888 			if (nearby[x][y] != 0)
2889 				continue;
2890 			Map_chunk *list = omap->get_chunk_safely(
2891 			                      (old_minx + x) % c_num_chunks,
2892 			                      (old_miny + y) % c_num_chunks);
2893 			if (!list) continue;
2894 			Object_iterator it(list->get_objects());
2895 			Game_object *each;
2896 			while ((each = it.get_next()) != nullptr) {
2897 				if (each->is_egg())
2898 					each->as_egg()->reset();
2899 				else if (each->get_flag(Obj_flags::is_temporary))
2900 					removes.push_back(each);
2901 			}
2902 		}
2903 	for (auto *remove : removes) {
2904 #ifdef DEBUG
2905 		Tile_coord t = remove->get_tile();
2906 		cout << "Culling object: " << remove->get_name() <<
2907 		     '(' << remove << ")@" <<
2908 		     t.tx << "," << t.ty << "," << t.tz << endl;
2909 #endif
2910 		remove->delete_contents();  // first delete item's contents
2911 		remove->remove_this(nullptr);
2912 	}
2913 
2914 	if (omap == nmap)
2915 		omap->cache_out(newx, newy);
2916 	else                    // Going to new map?
2917 		omap->cache_out(-1, -1);    // Cache out whole of old map.
2918 }
2919 
2920 // Tests to see if a move goes out of range of the actors superchunk
emulate_is_move_allowed(int tx,int ty)2921 bool Game_window::emulate_is_move_allowed(int tx, int ty) {
2922 	int ax = camera_actor->get_cx() / c_chunks_per_schunk;
2923 	int ay = camera_actor->get_cy() / c_chunks_per_schunk;
2924 	tx /= c_tiles_per_schunk;
2925 	ty /= c_tiles_per_schunk;
2926 
2927 	int difx = ax - tx;
2928 	int dify = ay - ty;
2929 
2930 	if (difx < 0) difx = -difx;
2931 	if (dify < 0) dify = -dify;
2932 
2933 	// Is it within 1 superchunk range?
2934 	return (!difx || difx == 1 || difx == c_num_schunks || difx == c_num_schunks - 1) &&
2935 	        (!dify || dify == 1 || dify == c_num_schunks || dify == c_num_schunks - 1);
2936 }
2937 
2938 //create mini-screenshot (96x60) for use in savegames
create_mini_screenshot()2939 unique_ptr<Shape_file> Game_window::create_mini_screenshot() {
2940 	set_all_dirty();
2941 	render->paint_map(0, 0, get_width(), get_height());
2942 
2943 	unique_ptr<unsigned char[]> img(win->mini_screenshot());
2944 	unique_ptr<Shape_file> sh;
2945 	if (img) {
2946 		sh = make_unique<Shape_file>(make_unique<Shape_frame>(std::move(img),
2947 		                             96, 60, 0, 0, true));
2948 	}
2949 
2950 	set_all_dirty();
2951 	paint();
2952 	return sh;
2953 }
2954 
2955 #define BG_PLASMA_START_COLOR   128
2956 #define BG_PLASMA_CYCLE_RANGE   80
2957 
2958 #define SI_PLASMA_START_COLOR   16
2959 #define SI_PLASMA_CYCLE_RANGE   96
2960 
setup_load_palette()2961 void Game_window::setup_load_palette() {
2962 	if (load_palette_timer != 0)
2963 		return;
2964 
2965 	if (Game::get_game_type() == BLACK_GATE) {
2966 		plasma_start_color = BG_PLASMA_START_COLOR;
2967 		plasma_cycle_range = BG_PLASMA_CYCLE_RANGE;
2968 	} else { // Default: if (Game::get_game_type()==SERPENT_ISLE)
2969 		plasma_start_color = SI_PLASMA_START_COLOR;
2970 		plasma_cycle_range = SI_PLASMA_CYCLE_RANGE;
2971 	}
2972 
2973 	// Put up the plasma to the screen
2974 	plasma(win->get_full_width(), win->get_full_height(), win->get_start_x(), win->get_start_y(), plasma_start_color, plasma_start_color + plasma_cycle_range - 1);
2975 
2976 	// Load the palette
2977 	if (Game::get_game_type() == BLACK_GATE)
2978 		pal->load(INTROPAL_DAT, PATCH_INTROPAL, 2);
2979 	else if (Game::get_game_type() == SERPENT_ISLE)
2980 		pal->load(MAINSHP_FLX, PATCH_MAINSHP, 1);
2981 
2982 	pal->apply();
2983 	load_palette_timer = SDL_GetTicks();
2984 }
2985 
cycle_load_palette()2986 void Game_window::cycle_load_palette() {
2987 	if (load_palette_timer == 0)
2988 		return;
2989 	uint32 ticks = SDL_GetTicks();
2990 	if (ticks > load_palette_timer + 75) {
2991 		for (int i = 0; i < 4; ++i)
2992 			get_win()->rotate_colors(plasma_start_color, plasma_cycle_range, 1);
2993 		show(true);
2994 
2995 		// We query the timer here again, as the blit can take easily 50 ms and more
2996 		// depending on the chosen scaler and the overall system speed
2997 		load_palette_timer = SDL_GetTicks();
2998 	}
2999 }
3000 
is_hostile_nearby() const3001 bool Game_window::is_hostile_nearby() const {
3002 	/* If there is a hostile NPC nearby, the avatar isn't allowed to
3003 	 * move very fast
3004 	 * Note that the range at which this occurs in the original is
3005 	 * less than the "potential target" range- that is, if I go into
3006 	 * combat mode, even when I'm allowed to run at full speed,
3007 	 * I'll sometime charge off to kill someone "too far away"
3008 	 * to affect a speed limit.
3009 	 * I don't know whether this is taken into account by
3010 	 * get_nearby_npcs, but on the other hand, its a negligible point.
3011 	 */
3012 	Actor_vector nearby;
3013 	if (!cheat.in_god_mode())
3014 		get_nearby_npcs(nearby);
3015 
3016 	bool nearby_hostile = false;
3017 	for (auto& actor : nearby) {
3018 		if (!actor->is_dead() && actor->get_schedule() &&
3019 		        actor->get_effective_alignment() >= Actor::evil &&
3020 		        ((actor->get_schedule_type() == Schedule::combat &&
3021 		          static_cast<Combat_schedule *>(actor->get_schedule())->has_started_battle()) ||
3022 		         actor->get_schedule_type() == Schedule::arrest_avatar)) {
3023 			/* TODO- I think invisibles still trigger the
3024 			 * slowdown, verify this. */
3025 			nearby_hostile = true;
3026 			break; /* No need to bother checking the rest :P */
3027 		}
3028 	}
3029 	return nearby_hostile;
3030 }
3031 
failed_copy_protection()3032 bool Game_window::failed_copy_protection() {
3033 	bool confused = main_actor->get_flag(Obj_flags::confused);
3034 	bool failureFlag = usecode->get_global_flag(56);
3035 	return (GAME_SI && confused) || (GAME_BG && failureFlag);
3036 }
3037 
got_bad_feeling(int odds)3038 void Game_window::got_bad_feeling(int odds) {
3039 	if (!(GAME_BG || GAME_SI))
3040 		return;
3041 	if ((rand() % odds) == 0) {
3042 		char const *badfeeling = get_misc_name(GAME_BG ? 0x44 : 0x50);
3043 		effects->remove_text_effect(main_actor);
3044 		effects->add_text(badfeeling, main_actor);
3045 	}
3046 }
3047 
set_shortcutbar(uint8 s)3048 void Game_window::set_shortcutbar(uint8 s) {
3049 	if(use_shortcutbar == s)
3050 		return;
3051 	if(using_shortcutbar() && s > 0) {
3052 		use_shortcutbar = s;
3053 		return;
3054 	}
3055 	use_shortcutbar = s;
3056 	if(using_shortcutbar()) {
3057 		g_shortcutBar = new ShortcutBar_gump(0,0);
3058 	} else {
3059 		gump_man->close_gump(g_shortcutBar);
3060 		g_shortcutBar = nullptr;
3061 	}
3062 }
3063