1 /*
2  *  objs.cc - Game objects.
3  *
4  *  Copyright (C) 1998-1999  Jeffrey S. Freedman
5  *  Copyright (C) 2000-2013  The Exult Team
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25 
26 #include <memory>
27 
28 #include "objs.h"
29 #include "chunks.h"
30 #include "objiter.h"
31 #include "egg.h"
32 #include "gamewin.h"
33 #include "gamemap.h"
34 #include "actors.h"
35 #include "ucmachine.h"
36 #include "items.h"
37 #include "dir.h"
38 #include "ordinfo.h"
39 #include "game.h"
40 #include "Gump_manager.h"
41 #include "effects.h"
42 #include "databuf.h"
43 #include "animate.h"
44 #include "Audio.h"
45 #include "ready.h"
46 #include "ammoinf.h"
47 #include "weaponinf.h"
48 #include "frusefun.h"
49 #include "frflags.h"
50 #include "find_nearby.h"
51 #include "usefuns.h"
52 #include "ignore_unused_variable_warning.h"
53 
54 #include <cstring>
55 #include <cstdio>
56 #include <algorithm>       // STL function things
57 
58 #ifdef USE_EXULTSTUDIO
59 #include "cheat.h"
60 #include "server.h"
61 #include "objserial.h"
62 #include "servemsg.h"
63 #endif
64 
65 using std::cout;
66 using std::endl;
67 using std::rand;
68 using std::ostream;
69 using std::string;
70 
71 /*
72  *  Determines the object's usecode function.
73  */
74 
get_usecode() const75 int Game_object::get_usecode() const {
76 	const Shape_info &inf = get_info();
77 	const Frame_usecode_info *useinf = inf.get_frame_usecode(
78 	                                 get_framenum(), inf.has_quality() ? get_quality() : -1);
79 	if (useinf) {
80 		// Shape has frame- or quality-dependent usecode.
81 		std::string ucname = useinf->get_usecode_name();
82 		int ucid = -1;
83 		if (ucname.length())    // Try by name first.
84 			ucid = ucmachine->find_function(ucname.c_str(), true);
85 		if (ucid == -1)         // Now try usecode number.
86 			ucid = useinf->get_usecode();
87 		if (ucid >= 0)          // Have frame usecode.
88 			return ucid;
89 	}
90 	return ucmachine->get_shape_fun(get_shapenum());
91 }
92 
usecode_exists() const93 bool Game_object::usecode_exists() const {
94 	return ucmachine->function_exists(get_usecode());
95 }
96 
97 // Offset to each neighbor, dir=0-7.
98 short Tile_coord::neighbors[16] = {0, -1, 1, -1, 1, 0, 1, 1, 0, 1,
99                                    -1, 1, -1, 0, -1, -1
100                                   };
101 Game_object_shared Game_object::editing;
102 // Bit 5=S, Bit6=reflect. on diag.
103 unsigned char Game_object::rotate[8] = { 0, 0, 48, 48, 16, 16, 32, 32};
104 
105 extern bool combat_trace;
106 
107 /*
108  *  Get chunk coords, or 255.
109  */
get_cx() const110 int Game_object::get_cx() const {
111 	return chunk ? chunk->cx : 255;
112 }
get_cy() const113 int Game_object::get_cy() const {
114 	return chunk ? chunk->cy : 255;
115 }
116 
117 
get_map() const118 Game_map *Game_object::get_map() const { // Map we're on.
119 	return chunk ? chunk->get_map() : nullptr;
120 }
get_map_num() const121 int Game_object::get_map_num() const { // Get map number this is in.
122 	return chunk ? chunk->get_map()->get_num() : -1;
123 }
124 
125 /*
126  *  Get tile.
127  */
128 
get_tile() const129 Tile_coord Game_object::get_tile(
130 ) const {
131 	if (!chunk) {
132 #ifdef DEBUG
133 		cout << "Asking tile for obj. " << get_shapenum()
134 		     << " not on map" << endl;
135 #endif
136 		return Tile_coord(255 * c_tiles_per_chunk, 255 * c_tiles_per_chunk,
137 		                  0);
138 	}
139 	return Tile_coord(chunk->cx * c_tiles_per_chunk + tx,
140 	                  chunk->cy * c_tiles_per_chunk + ty, lift);
141 }
142 
143 /*
144  *  Get tile.
145  */
146 
get_center_tile() const147 Tile_coord Game_object::get_center_tile(
148 ) const {
149 	if (!chunk) {
150 #ifdef DEBUG
151 		cout << "Asking center tile for obj. " << get_shapenum()
152 		     << " not on map" << endl;
153 #endif
154 		return Tile_coord(255 * c_tiles_per_chunk, 255 * c_tiles_per_chunk,
155 		                  0);
156 	}
157 	int frame = get_framenum();
158 	int dx = (get_info().get_3d_xtiles(frame) - 1) >> 1;
159 	int dy = (get_info().get_3d_ytiles(frame) - 1) >> 1;
160 	int dz = (get_info().get_3d_height() * 3) / 4;
161 	int x = chunk->cx * c_tiles_per_chunk + tx - dx;
162 	int y = chunk->cy * c_tiles_per_chunk + ty - dy;
163 	return Tile_coord(x, y, lift + dz);
164 }
165 
get_missile_tile(int dir) const166 Tile_coord Game_object::get_missile_tile(
167     int dir
168 ) const {
169 	ignore_unused_variable_warning(dir);
170 	if (!chunk) {
171 #ifdef DEBUG
172 		cout << "Asking missile tile for obj. " << get_shapenum()
173 		     << " not on map" << endl;
174 #endif
175 		return Tile_coord(255 * c_tiles_per_chunk, 255 * c_tiles_per_chunk,
176 		                  0);
177 	}
178 	int frame = get_framenum();
179 	int dx = get_info().get_3d_xtiles(frame) - 1;
180 	int dy = get_info().get_3d_ytiles(frame) - 1;
181 	int dz = (get_info().get_3d_height() * 3) / 4;
182 	/*switch (dir)
183 	    {
184 	    case south:
185 	        dy = -1;
186 	    case north:
187 	        dx /=2; break;
188 	    case east:
189 	        dx = -1;
190 	    case west:
191 	        dy /= 2; break;
192 	    case southeast:
193 	        dy = -1;
194 	    case northeast:
195 	        dx = -1; break;
196 	    case southwest:
197 	        dy = -1;
198 	        break;
199 	    }*/
200 	int x = chunk->cx * c_tiles_per_chunk + tx - dx / 2;
201 	int y = chunk->cy * c_tiles_per_chunk + ty - dy / 2;
202 	return Tile_coord(x, y, lift + dz);
203 }
204 
delta_check(int delta1,int size1,int size2,short & coord1,short & coord2)205 static inline void delta_check(
206     int delta1,
207     int size1,
208     int size2,
209     short &coord1,
210     short &coord2
211 ) {
212 	if (delta1 < 0) {
213 		if (coord1 + size1 > coord2)
214 			coord1 = coord2;
215 		else
216 			coord1 += size1;
217 	} else if (delta1 > 0) {
218 		if (coord2 + size2 > coord1)
219 			coord2 = coord1;
220 		else
221 			coord2 += size2;
222 	}
223 }
224 
delta_wrap_check(int dir,int size1,int size2,short & coord1,short & coord2)225 static inline void delta_wrap_check(
226     int dir,            // Neg. if coord2 < coord1.
227     int size1,
228     int size2,
229     short &coord1,
230     short &coord2
231 ) {
232 	// NOTE: An obj's tile is it's lower-right corner.
233 	if (dir > 0)            // Coord2 > coord1.
234 		coord2 = (coord2 - size2 + c_num_tiles) % c_num_tiles;
235 	else if (dir < 0)
236 		coord1 = (coord1 - size1 + c_num_tiles) % c_num_tiles;
237 }
238 
239 /*
240  *  Calculate distance to object taking 3D size in consideration.
241  *  U7 & SI verified.
242  */
243 
distance(const Game_object * o2) const244 int Game_object::distance(
245     const Game_object *o2
246 ) const {
247 	Tile_coord t1 = get_tile();
248 	Tile_coord t2 = o2->get_tile();
249 	const Shape_info &info1 = get_info();
250 	const Shape_info &info2 = o2->get_info();
251 	int f1 = get_framenum();
252 	int f2 = o2->get_framenum();
253 	int dx = Tile_coord::delta(t1.tx, t2.tx);
254 	int dy = Tile_coord::delta(t1.ty, t2.ty);
255 	int dz = t1.tz - t2.tz;
256 	delta_wrap_check(dx, info1.get_3d_xtiles(f1) - 1,
257 	                 info2.get_3d_xtiles(f2) - 1, t1.tx, t2.tx);
258 	delta_wrap_check(dy, info1.get_3d_ytiles(f1) - 1,
259 	                 info2.get_3d_ytiles(f2) - 1, t1.ty, t2.ty);
260 	delta_check(dz, info1.get_3d_height(),
261 	            info2.get_3d_height(), t1.tz, t2.tz);
262 	return t1.distance(t2);
263 }
264 /*
265  *  Calculate distance to tile taking 3D size in consideration.
266  *  U7 & SI verified.
267  */
268 
distance(Tile_coord t2) const269 int Game_object::distance(
270     Tile_coord t2
271 ) const {
272 	Tile_coord t1 = get_tile();
273 	const Shape_info &info1 = get_info();
274 	int f1 = get_framenum();
275 	int dx = Tile_coord::delta(t1.tx, t2.tx);
276 	int dy = Tile_coord::delta(t1.ty, t2.ty);
277 	int dz = t1.tz - t2.tz;
278 	delta_wrap_check(dx, info1.get_3d_xtiles(f1) - 1, 0, t1.tx, t2.tx);
279 	delta_wrap_check(dy, info1.get_3d_ytiles(f1) - 1, 0, t1.ty, t2.ty);
280 	delta_check(dz, info1.get_3d_height(), 0, t1.tz, t2.tz);
281 	return t1.distance(t2);
282 }
283 
284 /*
285  *  Get direction to another object.
286  */
287 
get_direction(Game_object * o2) const288 int Game_object::get_direction(
289     Game_object *o2
290 ) const {
291 	Tile_coord t1 = get_center_tile();
292 	Tile_coord t2 = o2->get_center_tile();
293 	// Treat as cartesian coords.
294 	return static_cast<int>(Get_direction(t1.ty - t2.ty, t2.tx - t1.tx));
295 }
296 
297 /*
298  *  Get direction to a given tile.
299  */
300 
get_direction(Tile_coord const & t2) const301 int Game_object::get_direction(
302     Tile_coord const &t2
303 ) const {
304 	Tile_coord t1 = get_center_tile();
305 	// Treat as cartesian coords.
306 	return static_cast<int>(Get_direction(t1.ty - t2.ty, t2.tx - t1.tx));
307 }
308 
309 /*
310  *  Get direction to best face an object.
311  */
312 
get_facing_direction(Game_object * o2) const313 int Game_object::get_facing_direction(
314     Game_object *o2
315 ) const {
316 	Tile_coord t1 = get_tile();
317 	TileRect torect = o2->get_footprint();
318 	if (torect.x + torect.w <= t1.tx &&
319 	        t1.ty >= torect.y && t1.ty < torect.y + torect.h)
320 		return static_cast<int>(west);
321 	else if (t1.tx < torect.x &&
322 	         t1.ty >= torect.y && t1.ty < torect.y + torect.h)
323 		return static_cast<int>(east);
324 	else if (torect.y + torect.h <= t1.ty &&
325 	         t1.tx >= torect.x && t1.tx < torect.w + torect.h)
326 		return static_cast<int>(south);
327 	else if (t1.ty < torect.y &&
328 	         t1.tx >= torect.x && t1.tx < torect.w + torect.h)
329 		return static_cast<int>(north);
330 	else
331 		return get_direction(o2);
332 }
333 
334 /*
335  *  Does a given shape come in quantity.
336  */
Has_quantity(int shnum)337 static bool Has_quantity(
338     int shnum           // Shape number.
339 ) {
340 	const Shape_info &info = ShapeID::get_info(shnum);
341 	return info.has_quantity();
342 }
343 
Has_hitpoints(int shnum)344 static bool Has_hitpoints(int shnum) {
345 	const Shape_info &info = ShapeID::get_info(shnum);
346 	return (info.get_shape_class() == Shape_info::has_hp) ||
347 	       (info.get_shape_class() == Shape_info::container);
348 
349 	// containers have hitpoints too ('resistance')
350 }
351 
352 const int MAX_QUANTITY = 100;       // Highest quantity possible.
353 
354 /*
355  *  Get the quantity.
356  */
357 
get_quantity() const358 int Game_object::get_quantity(
359 ) const {
360 	int shnum = get_shapenum();
361 	if (Has_quantity(shnum)) {
362 		int qual = quality & 0x7f;
363 		return qual ? qual : 1;
364 	} else
365 		return 1;
366 }
367 
368 /*
369  *  Get effective maximum range for weapon.
370  */
get_effective_range(const Weapon_info * winf,int reach) const371 int Game_object::get_effective_range(
372     const Weapon_info *winf,
373     int reach
374 ) const {
375 	if (reach < 0) {
376 		if (!winf)
377 			return 3;
378 		reach = winf->get_range();
379 	}
380 	int uses = winf ? winf->get_uses() : static_cast<int>(Weapon_info::melee);
381 	if (!uses || uses == Weapon_info::ranged)
382 		return reach;
383 	else
384 		return 31;
385 }
386 
387 /*
388  *  Checks to see if the object has ammo for a weapon.
389  *  Output is ammount of ammo needed and -> to ammo
390  *  object, if the argument is not null.
391  */
get_weapon_ammo(int weapon,int family,int proj,bool ranged,Game_object ** ammo,bool recursive)392 int Game_object::get_weapon_ammo(
393     int weapon,
394     int family,
395     int proj,
396     bool ranged,
397     Game_object **ammo,
398     bool recursive
399 ) {
400 	if (ammo)
401 		*ammo = nullptr;
402 	if (weapon < 0)
403 		return false;   // Bare hands.
404 	// See if we need ammo.
405 	const Weapon_info *winf = ShapeID::get_info(weapon).get_weapon_info();
406 	if (!winf)
407 		// No ammo needed.
408 		return 0;
409 	int uses = winf->get_uses();
410 	int need_ammo = 0;
411 	// This seems to match perfectly the originals.
412 	if (family == -1 || !ranged)
413 		need_ammo = !uses && winf->uses_charges();
414 	else
415 		need_ammo = 1;
416 	if (need_ammo && family >= 0 && proj >= 0) {
417 		// BG triple crossbows uses 3x ammo.
418 		const Shape_info &info = ShapeID::get_info(winf->get_projectile());
419 		if (info.get_ready_type() == triple_bolts)
420 			need_ammo = 3;
421 	}
422 
423 	if (ammo)
424 		*ammo = find_weapon_ammo(weapon, need_ammo, recursive);
425 	return need_ammo;
426 }
427 
get_effective_obj_hp(int weapon_shape) const428 int Game_object::get_effective_obj_hp(int weapon_shape) const {
429 	ignore_unused_variable_warning(weapon_shape);
430 	int hps = get_obj_hp();
431 	if (!hps) {
432 		const Shape_info &inf = get_info();
433 		int qual = inf.has_quality() ? get_quality() : -1;
434 		hps = inf.get_effective_hps(get_framenum(), qual);
435 	}
436 	return hps;
437 }
438 
get_obj_hp() const439 int Game_object::get_obj_hp() const {
440 	if (Has_hitpoints(get_shapenum()))
441 		return quality;
442 	else
443 		return 0;
444 }
445 
set_obj_hp(int hp)446 void Game_object::set_obj_hp(int hp) {
447 	int shnum = get_shapenum();
448 	if (Has_hitpoints(shnum))
449 		set_quality(hp);
450 }
451 
452 /*
453  *  Get the volume.
454  */
455 
get_volume() const456 int Game_object::get_volume(
457 ) const {
458 	int vol = get_info().get_volume();
459 	return vol;         // I think U7 ignores quantity!
460 }
461 
462 /*
463  *  Returns true if the object is inside a locked container.
464  */
inside_locked() const465 bool Game_object::inside_locked() const {
466 	const Game_object *top = this;
467 	const Game_object *above;
468 	while ((above = top->get_owner()) != nullptr) {
469 		if (above->get_info().is_container_locked())
470 			return true;
471 		top = above;
472 	}
473 	return false;
474 }
475 
476 
477 /*
478  *  Add or remove from object's 'quantity', and delete if it goes to 0.
479  *  Also, this sets the correct frame, even if delta == 0.
480  *
481  *  Output: Delta decremented/incremented by # added/removed.
482  *      Container's volume_used field is updated.
483  */
484 
modify_quantity(int delta,bool * del)485 int Game_object::modify_quantity(
486     int delta,          // >=0 to add, <0 to remove.
487     bool *del           // If !null, true ret'd if deleted.
488 ) {
489 	if (del)
490 		*del = false;
491 	if (!Has_quantity(get_shapenum())) {
492 		// Can't do quantity here.
493 		if (delta > 0)
494 			return delta;
495 		remove_this();      // Remove from container (or world).
496 		if (del)
497 			*del = true;
498 		return delta + 1;
499 	}
500 	int quant = quality & 0x7f; // Get current quantity.
501 	if (!quant)
502 		quant = 1;      // Might not be set.
503 	int newquant = quant + delta;
504 	if (delta >= 0) {       // Adding?
505 		// Too much?
506 		if (newquant > MAX_QUANTITY)
507 			newquant = MAX_QUANTITY;
508 	} else if (newquant <= 0) { // Subtracting.
509 		remove_this();      // We're done for.
510 		if (del)
511 			*del = true;
512 		return delta + quant;
513 	}
514 	int oldvol = get_volume();  // Get old volume used.
515 	quality = static_cast<char>(newquant);  // Store new value.
516 	// Set appropriate frame.
517 	if (get_info().has_weapon_info())   // Starbursts, serpent(ine) daggers, knives.
518 		change_frame(0);       // (Fixes messed-up games.)
519 	else if (get_info().has_quantity_frames()) {
520 		// This is actually hard-coded in the originals, but doing
521 		// it this way is consistent with musket ammo.
522 		int base = get_info().has_ammo_info() ? 24 : 0;
523 		if (get_info().get_ready_type() == triple_bolts)
524 			base = 24;
525 		// Verified.
526 		int new_frame = newquant > 12 ? 7 : (newquant > 6 ? 6 : newquant - 1);
527 		change_frame(base + new_frame);
528 	}
529 
530 	Container_game_object *owner = get_owner();
531 	if (owner)          // Update owner's volume.
532 		owner->modify_volume_used(get_volume() - oldvol);
533 	return delta - (newquant - quant);
534 }
535 
536 /*
537  *  Based on frame #, get direction (N, S, E, W, 0-7), this (generally an
538  *  NPC) is facing.
539  */
540 
get_dir_facing() const541 int Game_object::get_dir_facing(
542 ) const {
543 	int reflect = get_framenum() & (16 | 32);
544 	switch (reflect) {
545 	case 0:
546 		return static_cast<int>(north);
547 	case 48:
548 		return static_cast<int>(east);
549 	case 16:
550 		return static_cast<int>(south);
551 	case 32:
552 	default:
553 		return static_cast<int>(west);
554 	}
555 }
556 
557 /*
558  *  Move to a new absolute location.  This should work even if the old
559  *  location is invalid (chunk = 0).
560  */
561 
move(int newtx,int newty,int newlift,int newmap)562 void Game_object::move(
563     int newtx,
564     int newty,
565     int newlift,
566     int newmap
567 ) {
568 	// Figure new chunk.
569 	int newcx = newtx / c_tiles_per_chunk;
570 	int newcy = newty / c_tiles_per_chunk;
571 	Game_map *objmap = newmap >= 0 ? gwin->get_map(newmap) : get_map();
572 	if (!objmap) objmap = gmap;
573 	Map_chunk *newchunk = objmap->get_chunk_safely(newcx, newcy);
574 	if (!newchunk)
575 		return;         // Bad loc.
576 	Map_chunk *oldchunk = chunk;    // Remove from old.
577 	Game_object_shared keep = shared_from_this();
578 	if (oldchunk) {
579 		gwin->add_dirty(this);  // Want to repaint old area.
580 		oldchunk->remove(this);
581 	}
582 	set_lift(newlift);      // Set new values.
583 	tx = newtx % c_tiles_per_chunk;
584 	ty = newty % c_tiles_per_chunk;
585 	newchunk->add(this);        // Updates 'chunk'.
586 	gwin->add_dirty(this);      // And repaint new area.
587 }
588 
589 /*
590  *  Change the frame and set to repaint areas.
591  */
592 
change_frame(int frnum)593 void Game_object::change_frame(
594     int frnum
595 ) {
596 	gwin->add_dirty(this);      // Set to repaint old area.
597 	set_frame(frnum);
598 	gwin->add_dirty(this);      // Set to repaint new.
599 }
600 
601 /*
602  *  Swap positions with another object (of the same footprint).
603  *
604  *  Output: true if successful, else false.
605  */
606 
swap_positions(Game_object * obj2)607 bool Game_object::swap_positions(
608     Game_object *obj2
609 ) {
610 	const Shape_info &inf1 = get_info();
611 	const Shape_info &inf2 = obj2->get_info();
612 	int frame1 = get_framenum();
613 	int frame2 = obj2->get_framenum();
614 	if (inf1.get_3d_xtiles(frame1) != inf2.get_3d_xtiles(frame2) ||
615 	        inf1.get_3d_ytiles(frame1) != inf2.get_3d_ytiles(frame2))
616 		return false;       // Not the same size.
617 	Tile_coord p1 = get_tile();
618 	Tile_coord p2 = obj2->get_tile();
619     Game_object_shared keep1;
620     Game_object_shared keep2;
621 	remove_this(&keep1);         // Remove (but don't delete) each.
622 	set_invalid();
623 	obj2->remove_this(&keep2);
624 	obj2->set_invalid();
625 	move(p2.tx, p2.ty, p2.tz);  // Move to new locations.
626 	obj2->move(p1.tx, p1.ty, p1.tz);
627 	return true;
628 }
629 
630 /*
631  *  Remove all dependencies.
632  */
633 
clear_dependencies()634 void Game_object::clear_dependencies(
635 ) {
636 	// First do those we depend on.
637 	for (auto *dependency : dependencies)
638 		dependency->dependors.erase(this);
639 	dependencies.clear();
640 
641 	// Now those who depend on us.
642 	for (auto *dependor : dependors)
643 		dependor->dependencies.erase(this);
644 	dependors.clear();
645 }
646 
647 /*
648  *  Find objects near a given position.
649  *
650  *  Output: # found, appended to vec.
651  */
652 
find_nearby_eggs(Egg_vector & vec,Tile_coord const & pos,int shapenum,int delta,int qual,int frnum)653 int Game_object::find_nearby_eggs(
654     Egg_vector &vec,
655     Tile_coord const &pos,
656     int shapenum,
657     int delta,
658     int qual,
659     int frnum
660 ) {
661 	return Game_object::find_nearby(vec, pos, shapenum, delta, 16,
662 	                                qual, frnum, Egg_cast_functor());
663 }
664 
find_nearby_actors(Actor_vector & vec,Tile_coord const & pos,int shapenum,int delta,int mask)665 int Game_object::find_nearby_actors(
666     Actor_vector &vec,
667     Tile_coord const &pos,
668     int shapenum,
669     int delta,
670     int mask
671 ) {
672 	return Game_object::find_nearby(vec, pos, shapenum, delta, mask | 8,
673 	                                c_any_qual, c_any_framenum, Actor_cast_functor());
674 }
675 
find_nearby(Game_object_vector & vec,Tile_coord const & pos,int shapenum,int delta,int mask,int qual,int frnum,bool exclude_okay_to_take)676 int Game_object::find_nearby(
677     Game_object_vector &vec,
678     Tile_coord const &pos,
679     int shapenum,
680     int delta,
681     int mask,
682     int qual,
683     int frnum,
684     bool exclude_okay_to_take
685 ) {
686 	return Game_object::find_nearby(vec, pos, shapenum, delta, mask,
687 	                                qual, frnum, Game_object_cast_functor(), exclude_okay_to_take);
688 }
689 
find_nearby_eggs(Egg_vector & vec,int shapenum,int delta,int qual,int frnum) const690 int Game_object::find_nearby_eggs(
691     Egg_vector &vec,
692     int shapenum,
693     int delta,
694     int qual,
695     int frnum
696 ) const {
697 	return Game_object::find_nearby(vec, get_tile(), shapenum, delta, 16,
698 	                                qual, frnum, Egg_cast_functor());
699 }
700 
find_nearby_actors(Actor_vector & vec,int shapenum,int delta,int mask) const701 int Game_object::find_nearby_actors(
702     Actor_vector &vec,
703     int shapenum,
704     int delta,
705     int mask
706 ) const {
707 	return Game_object::find_nearby(vec, get_tile(), shapenum, delta, mask | 8,
708 	                                c_any_qual, c_any_framenum, Actor_cast_functor());
709 }
710 
find_nearby(Game_object_vector & vec,int shapenum,int delta,int mask,int qual,int framenum) const711 int Game_object::find_nearby(
712     Game_object_vector &vec,
713     int shapenum,
714     int delta,
715     int mask,
716     int qual,
717     int framenum
718 ) const {
719 	return Game_object::find_nearby(vec, get_tile(), shapenum, delta, mask,
720 	                                qual, framenum, Game_object_cast_functor());
721 }
722 
723 /*
724  *  Convert a vector to weak objects.
725  */
obj_vec_to_weak(std::vector<Game_object_weak> & dest,Game_object_vector & src)726 void Game_object::obj_vec_to_weak(
727     std::vector<Game_object_weak> &dest,
728 	Game_object_vector &src
729 ) {
730 	for (auto *obj : src) {
731 		dest.push_back(weak_from_obj(obj));
732 	}
733 }
734 
735 /*
736  *  For sorting closest to a given spot.
737  */
738 class Object_closest_sorter {
739 	Tile_coord pos;         // Pos. to get closest to.
740 public:
Object_closest_sorter(Tile_coord const & p)741 	Object_closest_sorter(Tile_coord const &p) : pos(p)
742 	{  }
operator ()(const Game_object * o1,const Game_object * o2)743 	bool operator()(const Game_object *o1, const Game_object *o2) {
744 		Tile_coord t1 = o1->get_tile();
745 		Tile_coord t2 = o2->get_tile();
746 		return t1.distance(pos) < t2.distance(pos);
747 	}
748 };
749 
750 /*
751  *  Find the closest nearby objects with a shape in a given list.
752  *
753  *  Output: ->closest object, or 0 if none found.
754  */
755 
find_closest(Game_object_vector & vec,int * shapenums,int num_shapes,int dist)756 Game_object *Game_object::find_closest(
757     Game_object_vector &vec,    // List returned here, closest 1st.
758     int *shapenums,         // Shapes to look for.
759     //   c_any_shapenum=any NPC.
760     int num_shapes,         // Size of shapenums.
761     int dist            // Distance to look (tiles).
762 ) {
763 	int i;
764 	for (i = 0; i < num_shapes; i++)
765 		// 0xb0 mask finds anything.
766 		find_nearby(vec, shapenums[i], dist, 0xb0);
767 	int cnt = vec.size();
768 	if (!cnt)
769 		return nullptr;
770 	if (cnt > 1)
771 		std::sort(vec.begin(), vec.end(),
772 		          Object_closest_sorter(get_tile()));
773 	return *(vec.begin());
774 }
775 
776 /*
777  *  Find the closest nearby object with a shape in a given list.
778  *
779  *  Output: ->object, or nullptr if none found.
780  */
781 
find_closest(Tile_coord const & pos,int * shapenums,int num_shapes,int dist)782 Game_object *Game_object::find_closest(
783     Tile_coord const &pos,  // Where to look from.
784     int *shapenums,         // Shapes to look for.
785     //   c_any_shapenum=any NPC.
786     int num_shapes,         // Size of shapenums.
787     int dist            // Distance to look (tiles).
788 ) {
789 	Game_object_vector vec;         // Gets objects found.
790 	int i;
791 	for (i = 0; i < num_shapes; i++)
792 		// 0xb0 mask finds anything.
793 		find_nearby(vec, pos, shapenums[i], dist, 0xb0);
794 	int cnt = vec.size();
795 	if (!cnt)
796 		return nullptr;
797 	Game_object *closest = nullptr;   // Get closest.
798 	int best_dist = 10000;      // In tiles.
799 	// Get our location.
800 	for (auto *obj : vec) {
801 		int dist = obj->get_tile().distance(pos);
802 		if (dist < best_dist) {
803 			closest = obj;
804 			best_dist = dist;
805 		}
806 	}
807 	return closest;
808 }
809 
810 /*
811  *  Get footprint in absolute tiles.
812  */
813 
get_footprint()814 TileRect Game_object::get_footprint(
815 ) {
816 	const Shape_info &info = get_info();
817 	// Get footprint.
818 	int frame = get_framenum();
819 	int xtiles = info.get_3d_xtiles(frame);
820 	int ytiles = info.get_3d_ytiles(frame);
821 	Tile_coord t = get_tile();
822 	TileRect foot((t.tx - xtiles + 1 + c_num_tiles) % c_num_tiles,
823 	               (t.ty - ytiles + 1 + c_num_tiles) % c_num_tiles,
824 	               xtiles, ytiles);
825 	return foot;
826 }
827 
828 /*
829  *  Get volume in absolute tiles.
830  */
831 
get_block() const832 Block Game_object::get_block(
833 ) const {
834 	const Shape_info &info = get_info();
835 	// Get footprint.
836 	int frame = get_framenum();
837 	int xtiles = info.get_3d_xtiles(frame);
838 	int ytiles = info.get_3d_ytiles(frame);
839 	int ztiles = info.get_3d_height();
840 	Tile_coord t = get_tile();
841 	Block vol((t.tx - xtiles + 1 + c_num_tiles) % c_num_tiles,
842 	          (t.ty - ytiles + 1 + c_num_tiles) % c_num_tiles,
843 	          t.tz,
844 	          xtiles, ytiles, ztiles);
845 	return vol;
846 }
847 
848 /*
849  *  Does this object block a given tile?
850  */
851 
blocks(Tile_coord const & tile) const852 bool Game_object::blocks(
853     Tile_coord const &tile
854 ) const {
855 	Tile_coord t = get_tile();
856 	if (t.tx < tile.tx || t.ty < tile.ty || t.tz > tile.tz)
857 		return false;       // Out of range.
858 	const Shape_info &info = get_info();
859 	int ztiles = info.get_3d_height();
860 	if (!ztiles || !info.is_solid())
861 		return false;       // Skip if not an obstacle.
862 	// Occupies desired tile?
863 	int frame = get_framenum();
864 	return tile.tx > t.tx - info.get_3d_xtiles(frame) &&
865 	        tile.ty > t.ty - info.get_3d_ytiles(frame) &&
866 	        tile.tz < t.tz + ztiles;
867 }
868 
869 /*
870  *  Find the game object that's blocking a given tile.
871  *
872  *  Output: ->object, or nullptr if not found.
873  */
874 
find_blocking(Tile_coord tile)875 Game_object *Game_object::find_blocking(
876     Tile_coord tile         // Tile to check.
877 ) {
878 	tile.fixme();
879 	Map_chunk *chunk = gmap->get_chunk(tile.tx / c_tiles_per_chunk,
880 	                                   tile.ty / c_tiles_per_chunk);
881 	Game_object *obj;
882 	Object_iterator next(chunk->get_objects());
883 	while ((obj = next.get_next()) != nullptr)
884 		if (obj->blocks(tile))
885 			return obj;
886 	return nullptr;
887 }
888 
889 /*
890  *  Find door blocking a given tile.
891  *
892  *  Output: ->door, or nullptr if not found.
893  */
894 
find_door(Tile_coord tile)895 Game_object *Game_object::find_door(
896     Tile_coord tile
897 ) {
898 	tile.fixme();
899 	Map_chunk *chunk = gmap->get_chunk(tile.tx / c_tiles_per_chunk,
900 	                                   tile.ty / c_tiles_per_chunk);
901 	return chunk->find_door(tile);
902 }
903 
904 /*
905  *  Is this a closed door?
906  */
907 
is_closed_door() const908 bool Game_object::is_closed_door(
909 ) const {
910 	const Shape_info &info = get_info();
911 	if (!info.is_door())
912 		return false;
913 	// Get door's footprint.
914 	int frame = get_framenum();
915 	int xtiles = info.get_3d_xtiles(frame);
916 	int ytiles = info.get_3d_ytiles(frame);
917 	// Get its location.
918 	Tile_coord doortile = get_tile();
919 	Tile_coord before;
920 	Tile_coord after;   // Want tiles to both sides.
921 	if (xtiles > ytiles) {      // Horizontal footprint?
922 		before = doortile + Tile_coord(-xtiles, 0, 0);
923 		after = doortile + Tile_coord(1, 0, 0);
924 	} else {            // Vertical footprint.
925 		before = doortile + Tile_coord(0, -ytiles, 0);
926 		after = doortile + Tile_coord(0, 1, 0);
927 	}
928 	// Should be blocked before/after.
929 	return gmap->is_tile_occupied(before) &&
930 	       gmap->is_tile_occupied(after);
931 }
932 
933 /*
934  *  Get the topmost owner of this object.
935  *
936  *  Output: ->topmost owner, or the object itself.
937  */
938 
get_outermost()939 Game_object *Game_object::get_outermost(
940 ) {
941 	Game_object *top = this;
942 	Game_object *above;
943 	while ((above = top->get_owner()) != nullptr)
944 		top = above;
945 	return top;
946 }
947 
948 /*
949  *  Show text by the object on the screen.
950  */
951 
say(const char * text)952 void Game_object::say(
953     const char *text
954 ) {
955 	if (gwin->failed_copy_protection())
956 		text = "Oink!";
957 	eman->add_text(text, this);
958 }
959 
960 /*
961  *  Show a message
962  *  (Msg. #'s start from 0, and are stored from 0x400 in 'text.flx'.)
963  */
964 
say(int msgnum)965 void Game_object::say(
966     int msgnum
967 ) {
968 	say(get_text_msg(msgnum));
969 }
970 
971 /*
972  *  Show a random msg. from 'text.flx' by the object.
973  *  (Msg. #'s start from 0, and are stored from 0x400 in 'text.flx'.)
974  */
975 
say(int from,int to)976 void Game_object::say(
977     int from, int to        // Range (inclusive).
978 ) {
979 	if (from > to) return;
980 	int offset = rand() % (to - from + 1);
981 	if (from + offset < get_num_text_msgs())
982 		say(get_text_msg(from + offset));
983 }
984 
985 /*
986  *  Paint at given spot in world.
987  */
988 
paint()989 void Game_object::paint(
990 ) {
991 	int x;
992 	int y;
993 	gwin->get_shape_location(this, x, y);
994 	paint_shape(x, y);
995 }
996 
997 /*
998  *  Paint outline.
999  */
1000 
paint_outline(Pixel_colors pix)1001 void Game_object::paint_outline(
1002     Pixel_colors pix        // Color to use.
1003 ) {
1004 	int x;
1005 	int y;
1006 	gwin->get_shape_location(this, x, y);
1007 	ShapeID::paint_outline(x, y, pix);
1008 }
1009 
1010 /*
1011  *  Run usecode when double-clicked.
1012  */
1013 
activate(int event)1014 void Game_object::activate(
1015     int event
1016 ) {
1017 	if (edit())
1018 		return;         // Map-editing.
1019 	int gump = get_info().get_gump_shape();
1020 	if (gump == game->get_shape("gumps/spell_scroll")) {
1021 		gumpman->add_gump(this, gump);
1022 		return;
1023 	}
1024 	ucmachine->call_usecode(get_usecode(), this,
1025 	                        static_cast<Usecode_machine::Usecode_events>(event));
1026 }
1027 
1028 /*
1029  *  Edit in ExultStudio.
1030  */
1031 
edit()1032 bool Game_object::edit(
1033 ) {
1034 #ifdef USE_EXULTSTUDIO
1035 	if (client_socket >= 0 &&   // Talking to ExultStudio?
1036 	        cheat.in_map_editor()) {
1037 		editing.reset();
1038 		Tile_coord t = get_tile();
1039 		std::string name = get_name();
1040 		if (Object_out(client_socket, Exult_server::obj,
1041 		               this, t.tx, t.ty, t.tz,
1042 		               get_shapenum(), get_framenum(), get_quality(),
1043 		               name) != -1) {
1044 			cout << "Sent object data to ExultStudio" << endl;
1045 			editing = shared_from_this();
1046 		} else
1047 			cout << "Error sending object to ExultStudio" << endl;
1048 		return true;
1049 	}
1050 #endif
1051 	return false;
1052 }
1053 
1054 /*
1055  *  Message to update from ExultStudio.
1056  */
1057 
update_from_studio(unsigned char * data,int datalen)1058 void Game_object::update_from_studio(
1059     unsigned char *data,
1060     int datalen
1061 ) {
1062 #ifdef USE_EXULTSTUDIO
1063 	Game_object *obj;
1064 	int tx;
1065 	int ty;
1066 	int tz;
1067 	int shape;
1068 	int frame;
1069 	int quality;
1070 	std::string name;
1071 	if (!Object_in(data, datalen, obj, tx, ty, tz, shape, frame,
1072 	               quality, name)) {
1073 		cout << "Error decoding object" << endl;
1074 		return;
1075 	}
1076 	if (!editing || obj != editing.get()) {
1077 		cout << "Obj from ExultStudio is not being edited" << endl;
1078 		return;
1079 	}
1080 	// Keeps NPC alive until end of function
1081 	//Game_object_shared keep = std::move(editing); // He may have chosen 'Apply', so still editing.
1082 	gwin->add_dirty(obj);
1083 	obj->set_shape(shape, frame);
1084 	gwin->add_dirty(obj);
1085 	obj->set_quality(quality);
1086 	Container_game_object *owner = obj->get_owner();
1087 	if (!owner) // See if it moved -- but only if not inside something!
1088 		// Don't skip this even if coords. are the same, since
1089 		//   it will mark the chunk as modified.
1090 		obj->move(tx, ty, tz);
1091 #else
1092 	ignore_unused_variable_warning(data, datalen);
1093 #endif
1094 }
1095 
1096 /*
1097  *  Remove an object from the world.
1098  *  The object is deleted.
1099  */
1100 
remove_this(Game_object_shared * keep)1101 void Game_object::remove_this(
1102     Game_object_shared *keep     // Non-null to not delete.
1103 ) {
1104 	// Do this before all else.
1105 	if (keep)
1106 	    *keep = shared_from_this();
1107 	if (chunk)
1108 		chunk->remove(this);
1109 }
1110 
1111 /*
1112  *  Can this be dragged?
1113  */
1114 
is_dragable() const1115 bool Game_object::is_dragable(
1116 ) const {
1117 	return false;         // Default is 'no'.
1118 }
1119 
1120 /*
1121  *  Static method to get shape's weight in 1/10 stones.  0 means infinite.
1122  */
1123 
get_weight(int shnum,int quant)1124 int Game_object::get_weight(
1125     int shnum,          // Shape #,
1126     int quant           // Quantity.
1127 ) {
1128 	const Shape_info &inf = ShapeID::get_info(shnum);
1129 	int wt = quant * inf.get_weight();
1130 	if (inf.is_lightweight()) {
1131 		// Special case:  reagents, coins.
1132 		wt /= 10;
1133 		if (wt <= 0) wt = 1;
1134 	}
1135 
1136 	if (Has_quantity(shnum))
1137 		if (wt <= 0) wt = 1;
1138 
1139 	return wt;
1140 }
1141 
1142 /*
1143  *  Get weight of object in 1/10 stones.
1144  */
1145 
get_weight()1146 int Game_object::get_weight(
1147 ) {
1148 	return get_weight(get_shapenum(), get_quantity());
1149 }
1150 
1151 /*
1152  *  Get maximum weight in stones that can be held.
1153  *
1154  *  Output: Max. allowed, or 0 if no limit (i.e., not carried by an NPC).
1155  */
1156 
get_max_weight() const1157 int Game_object::get_max_weight(
1158 ) const {
1159 	// Looking outwards for NPC.
1160 	Container_game_object *own = get_owner();
1161 	if (!own) {
1162 		return 0;
1163 	}
1164 	const Shape_info &info = own->get_info();
1165 	if (!info.has_extradimensional_storage()) {
1166 		return own->get_max_weight();
1167 	}
1168 
1169 	return 0;
1170 }
1171 
1172 /*
1173  *  Add an object to this one by combining.
1174  *
1175  *  Output: 1, meaning object is completely combined to this.  Obj. is
1176  *          deleted in this case.
1177  *      0 otherwise, although obj's quantity may be
1178  *          reduced if combine==true.
1179  */
1180 
add(Game_object * obj,bool dont_check,bool combine,bool noset)1181 bool Game_object::add(
1182     Game_object *obj,
1183     bool dont_check,        // 1 to skip volume/recursion check.
1184     bool combine,           // True to try to combine obj.  MAY
1185     //   cause obj to be deleted.
1186     bool noset      // True to prevent actors from setting sched. weapon.
1187 ) {
1188 	ignore_unused_variable_warning(dont_check, noset);
1189 	return combine ? drop(obj) : false;
1190 }
1191 
1192 /*
1193  *  Drop another onto this.
1194  *
1195  *  Output: false to reject, true to accept.
1196  */
1197 
drop(Game_object * obj)1198 bool Game_object::drop(
1199     Game_object *obj        // This may be deleted.
1200 ) {
1201 	const Shape_info &inf = get_info();
1202 	int shapenum = get_shapenum();  // It's possible if shapes match.
1203 	if (obj->get_shapenum() != shapenum || !inf.has_quantity() ||
1204 	        (!inf.has_quantity_frames() && get_framenum() != obj->get_framenum()))
1205 		return false;
1206 	int objq = obj->get_quantity();
1207 	int total_quant = get_quantity() + objq;
1208 	if (total_quant > MAX_QUANTITY) // Too much?
1209 		return false;
1210 	modify_quantity(objq);      // Add to our quantity.
1211 	obj->remove_this();     // It's been used up.
1212 	return true;
1213 }
1214 
1215 //#define DEBUGLT
1216 #ifdef DEBUGLT
1217 static int rx1 = -1, ry1 = -1, rx2 = -1, ry2 = -1;
1218 
Debug_lt(int tx1,int ty1,int tx2,int ty2)1219 static void Debug_lt(
1220     int tx1, int ty1,       // 1st coord.
1221     int tx2, int ty2        // 2nd coord.
1222 ) {
1223 	if (tx1 == rx1 && ty1 == ry1) {
1224 		if (tx2 == rx2 && ty2 == ry2)
1225 			cout << "Debug_lt" << endl;
1226 	}
1227 }
1228 #endif
1229 
1230 //#define DEBUG_COMPARE
1231 #ifdef DEBUG_COMPARE
1232 #include <iomanip>
1233 using std::setw;
operator <<(std::ostream & out,TileRect const & rc)1234 static inline std::ostream &operator<<(std::ostream &out, TileRect const &rc) {
1235 	out << "Rectangle { x = " << setw(4) << rc.x << ", y = " << setw(4) << rc.y << ", w = " << setw(4) << rc.w << ", h = " << setw(4) << rc.h << "}";
1236 	return out;
1237 }
1238 
operator <<(std::ostream & out,Ordering_info const & ord)1239 static inline std::ostream &operator<<(std::ostream &out, Ordering_info const &ord) {
1240 	out << "Ordering_info { area = " << ord.area << "," << endl;
1241 	out << "                tx     = " << setw(4) << ord.tx     << ", ty    = " << setw(4) << ord.ty    << ", tz   = " << setw(4) << ord.tz << "," << endl;
1242 	out << "                xs     = " << setw(4) << ord.xs     << ", ys    = " << setw(4) << ord.ys    << ", zs   = " << setw(4) << ord.zs   << "," << endl;
1243 	out << "                xleft  = " << setw(4) << ord.xleft  << ", yfar  = " << setw(4) << ord.yfar  << ", zbot = " << setw(4) << ord.zbot << "," << endl;
1244 	out << "                xright = " << setw(4) << ord.xright << ", ynear = " << setw(4) << ord.ynear << ", ztop = " << setw(4) << ord.ztop << "}";
1245 	return out;
1246 }
1247 
Trace_Compare(int retv,int xcmp,int ycmp,int zcmp,bool xover,bool yover,bool zover,Ordering_info & inf1,Ordering_info & inf2,char const * file,int line)1248 static int Trace_Compare(int retv, int xcmp, int ycmp, int zcmp,
1249                          bool xover, bool yover, bool zover,
1250                          Ordering_info &inf1, Ordering_info &inf2,
1251                          char const *file, int line) {
1252 	cerr << "Game_object::compare (@" << file << ":" << line << "): " << retv << endl;
1253 	cerr << inf1 << endl;
1254 	cerr << inf2 << endl;
1255 	cerr << "xcmp  = " << xcmp  << ", ycmp  = " << ycmp  << ", zcmp  = " << zcmp  << endl;
1256 	cerr << "xover = " << xover << ", yover = " << yover << ", zover = " << zover << endl;
1257 	return retv;
1258 }
1259 
1260 #   define TRACE_COMPARE(x) Trace_Compare(x, xcmp, ycmp, zcmp, xover, yover, zover, inf1, inf2, __FILE__, __LINE__)
1261 #else
1262 #   define TRACE_COMPARE(x) (x)
1263 #endif
1264 
1265 /*
1266  *  Compare ranges along a given dimension.
1267  */
Compare_ranges(int from1,int to1,int from2,int to2,int & cmp,bool & overlap)1268 inline void Compare_ranges(
1269     int from1, int to1,     // First object's range.
1270     int from2, int to2,     // Second object's range.
1271     // Returns:
1272     int &cmp,           // -1 if 1st < 2nd, 1 if 1st > 2nd,
1273     //   0 if equal.
1274     bool &overlap           // true returned if they overlap.
1275 ) {
1276 	if (to1 < from2) {
1277 		overlap = false;
1278 		cmp = -1;
1279 	} else if (to2 < from1) {
1280 		overlap = false;
1281 		cmp = 1;
1282 	} else {            // coords overlap.
1283 		overlap = true;
1284 		if (from1 < from2)
1285 			cmp = -1;
1286 		else if (from1 > from2)
1287 			cmp = 1;
1288 		// from1 == from2
1289 		else if (to1 < to2)
1290 			cmp = 1;
1291 		else if (to1 > to2)
1292 			cmp = -1;
1293 		else
1294 			cmp = 0;
1295 	}
1296 }
1297 
1298 /*
1299  *  Compare two objects.
1300  *
1301  *  Output: -1 if 1st < 2nd, 0 if dont_care, 1 if 1st > 2nd.
1302  */
1303 
compare(Ordering_info & inf1,Game_object * obj2)1304 int Game_object::compare(
1305     Ordering_info &inf1,        // Info. for object 1.
1306     Game_object *obj2
1307 ) {
1308 	// See if there's no overlap.
1309 	TileRect r2 = gwin->get_shape_rect(obj2);
1310 	if (!inf1.area.intersects(r2))
1311 		return 0;     // No overlap on screen.
1312 	Ordering_info inf2(gwin, obj2, r2);
1313 #ifdef DEBUGLT
1314 	Debug_lt(inf1.tx, inf1.ty, inf2.tx, inf2.ty);
1315 #endif
1316 	int xcmp;
1317 	int ycmp;
1318 	int zcmp;       // Comparisons for a given dimension:
1319 	//   -1 if o1<o2, 0 if o1==o2,
1320 	//    1 if o1>o2.
1321 	bool xover;
1322 	bool yover;
1323 	bool zover;   // True if dim's overlap.
1324 	Compare_ranges(inf1.xleft, inf1.xright, inf2.xleft, inf2.xright,
1325 	               xcmp, xover);
1326 	Compare_ranges(inf1.yfar, inf1.ynear, inf2.yfar, inf2.ynear,
1327 	               ycmp, yover);
1328 	Compare_ranges(inf1.zbot, inf1.ztop, inf2.zbot, inf2.ztop,
1329 	               zcmp, zover);
1330 	if (!xcmp && !ycmp && !zcmp)
1331 		// Same space?
1332 		// Paint biggest area sec. (Fixes
1333 		//   plaque at Penumbra's.)
1334 		return TRACE_COMPARE((inf1.area.w < inf2.area.w  &&
1335 		                      inf1.area.h < inf2.area.h) ? -1 :
1336 		                     (inf1.area.w > inf2.area.w &&
1337 		                      inf1.area.h > inf2.area.h) ? 1 : 0);
1338 //		return 0;       // Equal.
1339 	if (xover && yover && zover) {  // Complete overlap?
1340 		if (!inf1.zs)       // Flat one is always drawn first.
1341 			return TRACE_COMPARE(!inf2.zs ? 0 : -1);
1342 		else if (!inf2.zs)
1343 			return TRACE_COMPARE(1);
1344 	}
1345 	if (xcmp >= 0 && ycmp >= 0 && zcmp >= 0)
1346 		return TRACE_COMPARE(1);        // GTE in all dimensions.
1347 	if (xcmp <= 0 && ycmp <= 0 && zcmp <= 0)
1348 		return TRACE_COMPARE(-1);       // LTE in all dimensions.
1349 	if (yover) {        // Y's overlap.
1350 		if (xover)      // X's too?
1351 			return TRACE_COMPARE(zcmp);
1352 		else if (zover)     // Y's and Z's?
1353 			return TRACE_COMPARE(xcmp);
1354 		// Just Y's overlap.
1355 		else if (!zcmp)     // Z's equal?
1356 			return TRACE_COMPARE(xcmp);
1357 		else if (xcmp == zcmp)      // See if X and Z dirs. agree.
1358 			return TRACE_COMPARE(xcmp);
1359 		// Fixes Trinsic mayor statue-through-roof.
1360 		else if (inf1.ztop / 5 < inf2.zbot / 5 && inf2.info.occludes())
1361 			return TRACE_COMPARE(-1);   // A floor above/below.
1362 		else if (inf2.ztop / 5 < inf1.zbot / 5 && inf1.info.occludes())
1363 			return TRACE_COMPARE(1);
1364 		else
1365 			return TRACE_COMPARE(0);
1366 	} else if (xover) {     // X's overlap.
1367 		if (zover)      // X's and Z's?
1368 			return TRACE_COMPARE(ycmp);
1369 		else if (!zcmp)     // Z's equal?
1370 			return TRACE_COMPARE(ycmp);
1371 		else
1372 			return TRACE_COMPARE(ycmp == zcmp ? ycmp : 0);
1373 	}
1374 	// Neither X nor Y overlap.
1375 	else if (xcmp == -1) {      // o1 X before o2 X?
1376 		if (ycmp == -1)     // o1 Y before o2 Y?
1377 			// If Z agrees or overlaps, it's LT.
1378 			return TRACE_COMPARE((zover || zcmp <= 0) ? -1 : 0);
1379 	} else if (ycmp == 1) { // o1 Y after o2 Y?
1380 		if (zover || zcmp >= 0)
1381 			return TRACE_COMPARE(1);
1382 		// Fixes Brit. museum statue-through-roof.
1383 		else if (inf1.ztop / 5 < inf2.zbot / 5)
1384 			return TRACE_COMPARE(-1);   // A floor above.
1385 		else
1386 			return TRACE_COMPARE(0);
1387 	}
1388 	return TRACE_COMPARE(0);
1389 }
1390 
1391 
1392 /*
1393  *  Should this object be rendered before obj2?
1394  *  NOTE:  This older interface isn't as efficient.
1395  *
1396  *  Output: 1 if so, 0 if not, -1 if cannot compare.
1397  */
lt(Game_object & obj2)1398 int Game_object::lt(
1399     Game_object &obj2
1400 ) {
1401 	Ordering_info ord(gwin, this);
1402 	int cmp = compare(ord, &obj2);
1403 	return cmp == -1 ? 1 : cmp == 1 ? 0 : -1;
1404 }
1405 
1406 
1407 /*
1408  *  Get frame if rotated 1, 2, or 3 quadrants clockwise.  This is to
1409  *  support barges (ship, cart, flying carpet).
1410  */
1411 
get_rotated_frame(int quads) const1412 int Game_object::get_rotated_frame(
1413     int quads           // 1=90, 2=180, 3=270.
1414 ) const {
1415 	int curframe = get_framenum();
1416 	return get_info().get_rotated_frame(curframe, quads);
1417 }
1418 
1419 /*
1420  *  This method should be called to cause damage from traps, attacks.
1421  *
1422  *  Output: Hits taken.
1423  */
apply_damage(Game_object * attacker,int str,int wpoints,int type,int bias,int * exp)1424 int Game_object::apply_damage(
1425     Game_object *attacker,  // Attacker, or null.
1426     int str,        // Attack strength.
1427     int wpoints,    // Weapon bonus.
1428     int type,       // Damage type.
1429     int bias,       // Different combat difficulty.
1430     int *exp
1431 ) {
1432 	ignore_unused_variable_warning(bias);
1433 	if (exp)
1434 		*exp = 0;
1435 	int damage = 0;
1436 	if (wpoints == 127)
1437 		damage = 127;
1438 	else {
1439 		if (type != Weapon_data::lightning_damage && str > 0) {
1440 			int base = str / 3;
1441 			damage = base ? 1 + rand() % base : 0;
1442 		}
1443 		if (wpoints > 0)
1444 			damage += (1 + rand() % wpoints);
1445 	}
1446 
1447 	if (damage <= 0) {
1448 		Object_sfx::Play(this, Audio::game_sfx(5));
1449 		return 0;
1450 	}
1451 	return reduce_health(damage, type, attacker);
1452 }
1453 
1454 /*
1455  *  This method should be called to decrement health directly.
1456  *
1457  *  Output: Hits taken.
1458  */
1459 
reduce_health(int delta,int type,Game_object * attacker,int * exp)1460 int Game_object::reduce_health(
1461     int delta,          // # points to lose.
1462     int type,       // Type of damage.
1463     Game_object *attacker,  // Attacker, or null.
1464     int *exp
1465 ) {
1466 	if (exp)
1467 		*exp = 0;
1468 	// Returns 0 if doesn't have HP's or is indestructible.
1469 	int hp = get_effective_obj_hp();
1470 	if (!hp || // Object is indestructible.
1471 	        // These damage types do not affect objects.
1472 	        type == Weapon_data::lightning_damage ||
1473 	        type == Weapon_data::ethereal_damage)
1474 		return 0;
1475 	if (get_info().is_explosive() &&
1476 	        (type == Weapon_data::fire_damage || delta >= hp)) {
1477 		// Cause chain reaction.
1478 		set_quality(1); // Make it indestructible.
1479 		Tile_coord offset(0, 0, get_info().get_3d_height() / 2);
1480 		eman->add_effect(std::make_unique<Explosion_effect>(get_tile() + offset,
1481 		                                      this, 0, get_shapenum(), -1, attacker));
1482 		return delta;   // Will be destroyed in Explosion_effect.
1483 	}
1484 	if (delta < hp)
1485 		set_obj_hp(hp - delta);
1486 	else {
1487 		// object destroyed
1488 		eman->remove_text_effect(this);
1489 		ucmachine->call_usecode(DestroyObjectsUsecode, this, Usecode_machine::weapon);
1490 	}
1491 	return delta;
1492 }
1493 
1494 /*
1495  *  Being attacked.
1496  *
1497  *  Output: Hits taken or < 0 for explosion.
1498  */
1499 
figure_hit_points(Game_object * attacker,int weapon_shape,int ammo_shape,bool explosion)1500 int Game_object::figure_hit_points(
1501     Game_object *attacker,
1502     int weapon_shape,       // < 0 for readied weapon.
1503     int ammo_shape,         // < 0 for no ammo shape.
1504     bool explosion          // If this is an explosion attacking.
1505 ) {
1506 	const Weapon_info *winf;
1507 	const Ammo_info *ainf;
1508 
1509 	int wpoints = 0;
1510 	if (weapon_shape >= 0)
1511 		winf = ShapeID::get_info(weapon_shape).get_weapon_info();
1512 	else
1513 		winf = nullptr;
1514 	if (ammo_shape >= 0)
1515 		ainf = ShapeID::get_info(ammo_shape).get_ammo_info();
1516 	else
1517 		ainf = nullptr;
1518 	if (!winf && weapon_shape < 0) {
1519 		Actor *npc = attacker ? attacker->as_actor() : nullptr;
1520 		winf = npc ? npc->get_weapon(wpoints) : nullptr;
1521 	}
1522 
1523 	int usefun = -1;
1524 	int type = Weapon_data::normal_damage;
1525 	bool explodes = false;
1526 
1527 	if (winf) {
1528 		wpoints = winf->get_damage();
1529 		usefun = winf->get_usecode();
1530 		type = winf->get_damage_type();
1531 		explodes = winf->explodes();
1532 	} else
1533 		wpoints = 1;    // Give at least one, but only if there's no weapon
1534 	if (ainf) {
1535 		wpoints += ainf->get_damage();
1536 		// Replace damage type.
1537 		if (ainf->get_damage_type() != Weapon_data::normal_damage)
1538 			type = ainf->get_damage_type();
1539 		explodes = explodes || ainf->explodes();
1540 	}
1541 
1542 	if (explodes && !explosion) { // Explosions shouldn't explode again.
1543 		// Time to explode.
1544 		Tile_coord offset(0, 0, get_info().get_3d_height() / 2);
1545 		eman->add_effect(std::make_unique<Explosion_effect>(get_tile() + offset,
1546 		                                      nullptr, 0, weapon_shape, ammo_shape, attacker));
1547 		return -1;
1548 	}
1549 
1550 	int delta = 0;
1551 	int effstr = attacker && attacker->as_actor()
1552 	             ? attacker->as_actor()->get_effective_prop(Actor::strength) : 0;
1553 	if (winf && (winf->get_powers() & Weapon_data::no_damage) == 0)
1554 		delta = apply_damage(attacker, effstr, wpoints, type);
1555 
1556 	// Objects are not affected by weapon powers.
1557 
1558 	// Object may be in the remove list by this point.
1559 	if (usefun >= 0)
1560 		ucmachine->call_usecode(usefun, this,
1561 		                        Usecode_machine::weapon);
1562 	return delta;
1563 }
1564 
play_hit_sfx(int weapon,bool ranged)1565 void Game_object::play_hit_sfx(int weapon, bool ranged) {
1566 	const Weapon_info *winf = weapon >= 0 ?
1567 	                    ShapeID::get_info(weapon).get_weapon_info() : nullptr;
1568 	if (winf && winf->get_damage()) {
1569 		int sfx;
1570 		if (ranged)
1571 			sfx = Audio::game_sfx(1);
1572 		else if (weapon == 604) // Glass sword.
1573 			sfx = Audio::game_sfx(37);
1574 		else
1575 			sfx = Audio::game_sfx(4);
1576 		Object_sfx::Play(this, sfx);
1577 	}
1578 }
1579 
1580 /*
1581  *  Being attacked.
1582  *
1583  *  Output: 0 if destroyed, else object itself.
1584  */
1585 
attacked(Game_object * attacker,int weapon_shape,int ammo_shape,bool explosion)1586 Game_object *Game_object::attacked(
1587     Game_object *attacker,
1588     int weapon_shape,       // < 0 for readied weapon.
1589     int ammo_shape,         // < 0 for no ammo shape.
1590     bool explosion          // If this is an explosion attacking.
1591 ) {
1592 	int shnum = get_shapenum();
1593 
1594 	if (shnum == 735 && ammo_shape == 722) {
1595 		// Arrows hitting archery practice target.
1596 		int frnum = get_framenum();
1597 		int newframe = !frnum ? (3 * (rand() % 8) + 1)
1598 		               : ((frnum % 3) != 0 ? frnum + 1 : frnum);
1599 		change_frame(newframe);
1600 	}
1601 
1602 	int oldhp = get_effective_obj_hp();
1603 	int delta = figure_hit_points(attacker, weapon_shape, ammo_shape, explosion);
1604 	int newhp = get_effective_obj_hp();
1605 
1606 	if (combat_trace) {
1607 		string name = "<trap>";
1608 		if (attacker)
1609 			name = attacker->get_name();
1610 		cout << name << " attacks " << get_name();
1611 		if (oldhp < delta) {
1612 			cout << ", destroying it." << endl;
1613 			return nullptr;
1614 		} else if (!delta || oldhp == newhp) {
1615 			// undamaged/indestructible
1616 			cout << " to no effect." << endl;
1617 			return this;
1618 		} else if (delta < 0)
1619 			cout << " causing an explosion." << endl;
1620 		else
1621 			cout << " for " << delta << " hit points, leaving "
1622 			     << newhp << " remaining." << endl;
1623 	}
1624 	return this;
1625 }
1626 
1627 /*
1628  *  Can't set usecode.
1629  */
set_usecode(int ui,const char * nm)1630 bool Game_object::set_usecode(int ui, const char *nm) {
1631 	ignore_unused_variable_warning(ui, nm);
1632 	return false;
1633 }
1634 
1635 
1636 /*
1637  *  Move to a new absolute location.  This should work even if the old
1638  *  location is invalid (chunk = nullptr).
1639  */
1640 
move(int newtx,int newty,int newlift,int newmap)1641 void Terrain_game_object::move(
1642     int newtx,
1643     int newty,
1644     int newlift,
1645     int newmap
1646 ) {
1647 	bool caching_out = chunk ? chunk->get_map()->is_caching_out() : false;
1648 	if (!caching_out) {
1649 		gwin->get_map()->set_map_modified();
1650 		if (chunk) {
1651 			Chunk_terrain *ter = chunk->get_terrain();
1652 			if (!prev_flat.is_invalid())
1653 				ter->set_flat(get_tx(), get_ty(), prev_flat);
1654 			else
1655 				ter->set_flat(get_tx(), get_ty(), ShapeID(12, 0));
1656 			Game_map::set_chunk_terrains_modified();
1657 		}
1658 	}
1659 	Game_object::move(newtx, newty, newlift, newmap);
1660 	if (chunk && !caching_out) {
1661 		Chunk_terrain *ter = chunk->get_terrain();
1662 		prev_flat = ter->get_flat(get_tx(), get_ty());
1663 		ter->set_flat(get_tx(), get_ty(), *this);
1664 		Game_map::set_chunk_terrains_modified();
1665 	}
1666 }
1667 
1668 /*
1669  *  Remove an object from the world.
1670  *  The object is deleted.
1671  */
1672 
remove_this(Game_object_shared * keep)1673 void Terrain_game_object::remove_this(
1674     Game_object_shared *keep     // Non-null to not delete.
1675 ) {
1676 	if (chunk && !keep && !chunk->get_map()->is_caching_out()) {
1677 		// This code removes the terrain object if the object was deleted.
1678 		// This should NOT be run if the map is being cached out!
1679 		chunk->get_map()->set_map_modified();
1680 		Chunk_terrain *ter = chunk->get_terrain();
1681 		if (!prev_flat.is_invalid())
1682 			ter->set_flat(get_tx(), get_ty(), prev_flat);
1683 		else
1684 			ter->set_flat(get_tx(), get_ty(), ShapeID(12, 0));
1685 		Game_map::set_chunk_terrains_modified();
1686 	}
1687 	Game_object::remove_this(keep);
1688 }
1689 
1690 /*
1691  *  Paint terrain objects only.
1692  */
1693 
paint_terrain()1694 void Terrain_game_object::paint_terrain(
1695 ) {
1696 	paint();
1697 }
1698 
1699 /*
1700  *  Move to a new absolute location.  This should work even if the old
1701  *  location is invalid (chunk = nullptr).
1702  */
1703 
move(int newtx,int newty,int newlift,int newmap)1704 void Ifix_game_object::move(
1705     int newtx,
1706     int newty,
1707     int newlift,
1708     int newmap
1709 ) {
1710 	bool caching_out = gwin->get_map()->is_caching_out();
1711 	if (chunk && !caching_out)  // Mark superchunk as 'modified'.
1712 		get_map()->set_ifix_modified(get_cx(), get_cy());
1713 	Game_object::move(newtx, newty, newlift, newmap);
1714 	if (chunk && !caching_out)
1715 		get_map()->set_ifix_modified(get_cx(), get_cy());
1716 }
1717 
1718 /*
1719  *  Remove an object from the world.
1720  *  The object is deleted.
1721  */
1722 
remove_this(Game_object_shared * keep)1723 void Ifix_game_object::remove_this(
1724     Game_object_shared *keep     // Non-null to not delete.
1725 ) {
1726 	if (chunk) {        // Mark superchunk as 'modified'.
1727 		int cx = get_cx();
1728 		int cy = get_cy();
1729 		get_map()->set_ifix_modified(cx, cy);
1730 	}
1731 	Game_object::remove_this(keep);
1732 }
1733 
1734 /*
1735  *  Write out an IFIX object.
1736  */
1737 
write_ifix(ODataSource * ifix,bool v2)1738 void Ifix_game_object::write_ifix(
1739     ODataSource *ifix,       // Where to write.
1740     bool v2             // More shapes/frames allowed.
1741 ) {
1742 	unsigned char buf[5];
1743 	buf[0] = (tx << 4) | ty;
1744 	buf[1] = lift;
1745 	int shapenum = get_shapenum();
1746 	int framenum = get_framenum();
1747 	if (v2) {
1748 		buf[2] = shapenum & 0xff;
1749 		buf[3] = (shapenum >> 8) & 0xff;
1750 		buf[4] = framenum;
1751 		ifix->write(reinterpret_cast<char *>(buf), 5);
1752 	} else {
1753 		buf[2] = shapenum & 0xff;
1754 		buf[3] = ((shapenum >> 8) & 3) | (framenum << 2);
1755 		ifix->write(reinterpret_cast<char *>(buf), 4);
1756 	}
1757 }
1758 
1759 
1760 
1761 
1762 
1763