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