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