1 /**
2 ** Chunks.cc - Chunks (16x16 tiles) on the map.
3 **
4 ** Written: 10/1/98 - JSF
5 **/
6
7 /*
8 Copyright (C) 2000 The Exult Team
9
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 */
24
25 #ifdef HAVE_CONFIG_H
26 # include <config.h>
27 #endif
28
29 #include "chunks.h"
30 #include "chunkter.h"
31 #include "gamewin.h"
32 #include "gamemap.h"
33 #include "shapeinf.h"
34 #include "citerate.h"
35 #include "databuf.h"
36 #include "egg.h"
37 #include "objiter.h"
38 #include "objs.h"
39 #include "ordinfo.h"
40 #include "game.h"
41 #include "animate.h"
42 #include "dir.h"
43 #include "actors.h"
44 #include "miscinf.h"
45 #include "ignore_unused_variable_warning.h"
46
47 using std::memset;
48 using std::rand;
49 using std::vector;
50
51 /*
52 * Create the cached data storage for a chunk.
53 */
54
Chunk_cache()55 Chunk_cache::Chunk_cache(
56 ) : egg_objects(4), eggs{} {
57 }
58
59 /*
60 * This mask gives the low bits (b0) for a given # of ztiles.
61 */
62 unsigned long tmasks[8] = {
63 0x0L,
64 0x1L,
65 0x5L,
66 0x15L,
67 0x55L,
68 0x155L,
69 0x555L,
70 0x1555L
71 };
72
73 /*
74 * Set (actually, increment count) for a given tile.
75 * Want: 00 => 01, 01 => 10,
76 * 10 => 11, 11 => 11.
77 * So: newb0 = !b0 OR b1,
78 * newb1 = b1 OR b0
79 */
Set_blocked_tile(Chunk_cache::blocked8z & blocked,int tx,int ty,int lift,int ztiles)80 inline void Set_blocked_tile(
81 Chunk_cache::blocked8z &blocked, // 16x16 flags,
82 int tx, int ty, // Tile #'s (0-15).
83 int lift, // Starting lift to set.
84 int ztiles // # tiles along z-axis.
85 ) {
86 uint16 val = blocked[ty * c_tiles_per_chunk + tx];
87 // Get mask for the bit0's:
88 auto mask0 = static_cast<uint16>(tmasks[ztiles] << 2 * lift);
89 uint16 mask1 = mask0 << 1; // Mask for the bit1's.
90 uint16 val0s = val & mask0;
91 uint16 Nval0s = (~val)&mask0;
92 uint16 val1s = val & mask1;
93 uint16 newval = val1s | (val0s << 1) | Nval0s | (val1s >> 1);
94 // Replace old values with new.
95 blocked[ty * c_tiles_per_chunk + tx] = (val&~(mask0 | mask1)) | newval;
96 }
97
98 /*
99 * Clear (actually, decrement count) for a given tile.
100 * Want: 00 => 00, 01 => 00,
101 * 10 => 01, 11 => 10.
102 * So: newb0 = b1 AND !b0
103 * newb1 = b1 AND b0
104 */
Clear_blocked_tile(Chunk_cache::blocked8z & blocked,int tx,int ty,int lift,int ztiles)105 inline void Clear_blocked_tile(
106 Chunk_cache::blocked8z &blocked, // 16x16 flags,
107 int tx, int ty, // Tile #'s (0-15).
108 int lift, // Starting lift to set.
109 int ztiles // # tiles along z-axis.
110 ) {
111 uint16 val = blocked[ty * c_tiles_per_chunk + tx];
112 // Get mask for the bit0's:
113 auto mask0 = static_cast<uint16>(tmasks[ztiles] << 2 * lift);
114 uint16 mask1 = mask0 << 1; // Mask for the bit1's.
115 uint16 val0s = val & mask0;
116 uint16 Nval0s = (~val)&mask0;
117 uint16 val1s = val & mask1;
118 uint16 newval = (val1s & (val0s << 1)) | ((val1s >> 1) & Nval0s);
119 // Replace old values with new.
120 blocked[ty * c_tiles_per_chunk + tx] = (val&~(mask0 | mask1)) | newval;
121 }
122
123 /*
124 * Create new blocked flags for a given z-level, where each level
125 * covers 8 lifts.
126 */
127
new_blocked_level(int zlevel)128 Chunk_cache::blocked8z &Chunk_cache::new_blocked_level(
129 int zlevel
130 ) {
131 if (static_cast<unsigned>(zlevel) >= blocked.size())
132 blocked.resize(zlevel + 1);
133 blocked[zlevel] = std::make_unique<uint16[]>(256);
134 return blocked[zlevel];
135 }
136
137 /*
138 * Set/unset the blocked flags in a region.
139 */
140
set_blocked(int startx,int starty,int endx,int endy,int lift,int ztiles)141 void Chunk_cache::set_blocked(
142 int startx, int starty, // Starting tile #'s.
143 int endx, int endy, // Ending tile #'s.
144 int lift, int ztiles // Lift, height info.
145 ) {
146 int z = lift;
147 while (ztiles) {
148 int zlevel = z / 8;
149 int thisz = z % 8;
150 int zcnt = 8 - thisz;
151 if (ztiles < zcnt)
152 zcnt = ztiles;
153 auto &block = need_blocked_level(zlevel);
154 for (int y = starty; y <= endy; y++)
155 for (int x = startx; x <= endx; x++)
156 Set_blocked_tile(block, x, y, thisz, zcnt);
157 z += zcnt;
158 ztiles -= zcnt;
159 }
160 }
161
clear_blocked(int startx,int starty,int endx,int endy,int lift,int ztiles)162 void Chunk_cache::clear_blocked(
163 int startx, int starty, // Starting tile #'s.
164 int endx, int endy, // Ending tile #'s.
165 int lift, int ztiles // Lift, height info.
166 ) {
167 int z = lift;
168 while (ztiles) {
169 unsigned int zlevel = z / 8;
170 int thisz = z % 8;
171 int zcnt = 8 - thisz;
172 if (zlevel >= blocked.size())
173 break; // All done.
174 if (ztiles < zcnt)
175 zcnt = ztiles;
176 auto &block = blocked[zlevel];
177 if (block) {
178 for (int y = starty; y <= endy; y++)
179 for (int x = startx; x <= endx; x++)
180 Clear_blocked_tile(block, x, y,
181 thisz, zcnt);
182 }
183 z += zcnt;
184 ztiles -= zcnt;
185 }
186 }
187
188 /*
189 * Add/remove an object to/from the cache.
190 */
191
update_object(Map_chunk * chunk,Game_object * obj,bool add)192 void Chunk_cache::update_object(
193 Map_chunk *chunk,
194 Game_object *obj,
195 bool add // 1 to add, 0 to remove.
196 ) {
197 ignore_unused_variable_warning(chunk);
198 const Shape_info &info = obj->get_info();
199 if (info.is_door()) { // Special door list.
200 if (add)
201 doors.insert(obj);
202 else
203 doors.erase(obj);
204 }
205 int ztiles = info.get_3d_height();
206 if (!ztiles || !info.is_solid())
207 return; // Skip if not an obstacle.
208 // Get lower-right corner of obj.
209 int endx = obj->get_tx();
210 int endy = obj->get_ty();
211 int frame = obj->get_framenum();// Get footprint dimensions.
212 int xtiles = info.get_3d_xtiles(frame);
213 int ytiles = info.get_3d_ytiles(frame);
214 int lift = obj->get_lift();
215 // Simplest case?
216 if (xtiles == 1 && ytiles == 1 && ztiles <= 8 - lift % 8) {
217 if (add)
218 Set_blocked_tile(need_blocked_level(lift / 8),
219 endx, endy, lift % 8, ztiles);
220 else if (blocked[lift / 8])
221 Clear_blocked_tile(blocked[lift / 8],
222 endx, endy, lift % 8, ztiles);
223 return;
224 }
225 TileRect footprint = obj->get_footprint();
226 // Go through interesected chunks.
227 Chunk_intersect_iterator next_chunk(footprint);
228 TileRect tiles;
229 int cx;
230 int cy;
231 while (next_chunk.get_next(tiles, cx, cy))
232 gmap->get_chunk(cx, cy)->set_blocked(tiles.x, tiles.y,
233 tiles.x + tiles.w - 1, tiles.y + tiles.h - 1, lift,
234 ztiles, add);
235 }
236
237 /*
238 * Set a rectangle of tiles within this chunk to be under the influence
239 * of a given egg, or clear it.
240 */
241
set_egged(Egg_object * egg,TileRect & tiles,bool add)242 void Chunk_cache::set_egged(
243 Egg_object *egg,
244 TileRect &tiles, // Range of tiles within chunk.
245 bool add // 1 to add, 0 to remove.
246 ) {
247 // Egg already there?
248 int eggnum = -1;
249 int spot = -1;
250 for (auto it = egg_objects.begin(); it != egg_objects.end(); ++it) {
251 if (*it == egg) {
252 eggnum = it - egg_objects.begin();
253 break;
254 } else if (*it == nullptr && spot == -1)
255 spot = it - egg_objects.begin();
256 }
257 if (add) {
258 if (eggnum < 0) { // No, so add it.
259 eggnum = spot >= 0 ? spot : egg_objects.size();
260 if (spot >= 0)
261 egg_objects[spot] = egg;
262 else
263 egg_objects.push_back(egg);
264 }
265 if (eggnum > 15) // We only have 16 bits.
266 eggnum = 15;
267 short mask = (1 << eggnum);
268 int stopx = tiles.x + tiles.w;
269 int stopy = tiles.y + tiles.h;
270 for (int ty = tiles.y; ty < stopy; ++ty)
271 for (int tx = tiles.x; tx < stopx; ++tx)
272 eggs[ty * c_tiles_per_chunk + tx] |= mask;
273 } else { // Remove.
274 if (eggnum < 0)
275 return; // Not there.
276 egg_objects[eggnum] = nullptr;
277 if (eggnum >= 15) { // We only have 16 bits.
278 // Last one at 15 or above?
279 for (auto it = egg_objects.begin() + 15; it != egg_objects.end(); ++it)
280 if (*it != nullptr)
281 // No, so leave bits alone.
282 return;
283 eggnum = 15;
284 }
285 short mask = ~(1 << eggnum);
286 int stopx = tiles.x + tiles.w;
287 int stopy = tiles.y + tiles.h;
288 for (int ty = tiles.y; ty < stopy; ty++)
289 for (int tx = tiles.x; tx < stopx; tx++)
290 eggs[ty * c_tiles_per_chunk + tx] &= mask;
291 }
292 }
293
294 /*
295 * Add/remove an egg to the cache.
296 */
297
update_egg(Map_chunk * chunk,Egg_object * egg,bool add)298 void Chunk_cache::update_egg(
299 Map_chunk *chunk,
300 Egg_object *egg,
301 bool add // 1 to add, 0 to remove.
302 ) {
303 ignore_unused_variable_warning(chunk);
304 // Get footprint with abs. tiles.
305 TileRect foot = egg->get_area();
306 if (!foot.w)
307 return; // Empty (probability = 0).
308 TileRect crect; // Gets tiles within each chunk.
309 int cx;
310 int cy;
311 if (egg->is_solid_area()) {
312 // Do solid rectangle.
313 Chunk_intersect_iterator all(foot);
314 while (all.get_next(crect, cx, cy))
315 gmap->get_chunk(cx, cy)->set_egged(egg, crect, add);
316 return;
317 }
318 // Just do the perimeter.
319 TileRect top(foot.x, foot.y, foot.w, 1);
320 TileRect bottom(foot.x, foot.y + foot.h - 1, foot.w, 1);
321 TileRect left(foot.x, foot.y + 1, 1, foot.h - 2);
322 TileRect right(foot.x + foot.w - 1, foot.y + 1, 1, foot.h - 2);
323 // Go through intersected chunks.
324 Chunk_intersect_iterator tops(top);
325 while (tops.get_next(crect, cx, cy))
326 gmap->get_chunk(cx, cy)->set_egged(egg, crect, add);
327 Chunk_intersect_iterator bottoms(bottom);
328 while (bottoms.get_next(crect, cx, cy))
329 gmap->get_chunk(cx, cy)->set_egged(egg, crect, add);
330 Chunk_intersect_iterator lefts(left);
331 while (lefts.get_next(crect, cx, cy))
332 gmap->get_chunk(cx, cy)->set_egged(egg, crect, add);
333 Chunk_intersect_iterator rights(right);
334 while (rights.get_next(crect, cx, cy))
335 gmap->get_chunk(cx, cy)->set_egged(egg, crect, add);
336
337 }
338
339 /*
340 * Create the cached data for a chunk.
341 */
342
setup(Map_chunk * chunk)343 void Chunk_cache::setup(
344 Map_chunk *chunk
345 ) {
346 Game_object *obj; // Set 'blocked' tiles.
347 Object_iterator next(chunk->get_objects());
348 while ((obj = next.get_next()) != nullptr)
349 if (obj->is_egg())
350 update_egg(chunk, obj->as_egg(), true);
351 else
352 update_object(chunk, obj, true);
353
354 obj_list = chunk;
355 }
356
357 // Temp. storage for 'blocked' flags for a single tile.
358 static uint16 tflags[256 / 8];
359 static int tflags_maxz;
360 // Test for given z-coord. (lift)
361 #define TEST_TFLAGS(i) (tflags[(i)/8] & (3<<(2*((i)%8))))
362
set_tflags(int tx,int ty,int maxz)363 inline void Chunk_cache::set_tflags(
364 int tx, int ty,
365 int maxz
366 ) {
367 int zlevel = maxz / 8;
368 int bsize = blocked.size();
369 if (zlevel >= bsize) {
370 memset(tflags + bsize, 0, (zlevel - bsize + 1)*sizeof(uint16));
371 zlevel = bsize - 1;
372 }
373 while (zlevel >= 0) {
374 auto &block = blocked[zlevel];
375 tflags[zlevel--] = block ? block[ty * c_tiles_per_chunk + tx] : 0;
376 }
377 tflags_maxz = maxz;
378 }
379
380 /*
381 * Get highest blocked lift below a given level for a given tile.
382 *
383 * Output: Highest lift that's blocked by an object, or -1 if none.
384 */
385
get_highest_blocked(int lift)386 inline int Chunk_cache::get_highest_blocked(
387 int lift // Look below this lift.
388 ) {
389 int i; // Look downwards.
390 for (i = lift - 1; i >= 0 && !TEST_TFLAGS(i); i--)
391 ;
392 return i;
393 }
394
395 /*
396 * Get highest blocked lift below a given level for a given tile.
397 *
398 * Output: Highest lift that's blocked by an object, or -1 if none.
399 */
400
get_highest_blocked(int lift,int tx,int ty)401 int Chunk_cache::get_highest_blocked(
402 int lift, // Look below this lift.
403 int tx, int ty // Square to test.
404 ) {
405 set_tflags(tx, ty, lift);
406 return get_highest_blocked(lift);
407 }
408
409 /*
410 * Get lowest blocked lift above a given level for a given tile.
411 *
412 * Output: Highest lift that's blocked by an object, or -1 if none.
413 */
414
get_lowest_blocked(int lift)415 inline int Chunk_cache::get_lowest_blocked(
416 int lift // Look above this lift.
417 ) {
418 int i; // Look upward.
419 for (i = lift; i <= tflags_maxz && !TEST_TFLAGS(i); i++)
420 ;
421 if (i > tflags_maxz) return -1;
422 return i;
423 }
424
425 /*
426 * Get lowest blocked lift above a given level for a given tile.
427 *
428 * Output: Lowest lift that's blocked by an object, or -1 if none.
429 */
430
get_lowest_blocked(int lift,int tx,int ty)431 int Chunk_cache::get_lowest_blocked(
432 int lift, // Look above this lift.
433 int tx, int ty // Square to test.
434 ) {
435 set_tflags(tx, ty, 255); // FOR NOW, look up to max.
436 return get_lowest_blocked(lift);
437 }
438
439 /*
440 * See if a tile is water or land.
441 */
442
Check_terrain(Map_chunk * nlist,int tx,int ty,int & terrain)443 inline void Check_terrain(
444 Map_chunk *nlist, // Chunk.
445 int tx, int ty, // Tile within chunk.
446 int &terrain // Sets: bit0 if land, bit1 if water,
447 // bit2 if solid.
448 ) {
449 ShapeID flat = nlist->get_flat(tx, ty);
450 if (!flat.is_invalid()) {
451 if (flat.get_info().is_water())
452 terrain |= 2;
453 else if (flat.get_info().is_solid())
454 terrain |= 4;
455 else
456 terrain |= 1;
457 }
458
459 }
460
461 /*
462 * Is a given square occupied at a given lift?
463 *
464 * Output: true if so, else false.
465 * If false (tile is free), new_lift contains the new height that
466 * an actor will be at if he walks onto the tile.
467 */
468
is_blocked(int height,int lift,int tx,int ty,int & new_lift,const int move_flags,int max_drop,int max_rise)469 bool Chunk_cache::is_blocked(
470 int height, // Height (in tiles) of obj. being
471 // tested.
472 int lift, // Given lift.
473 int tx, int ty, // Square to test.
474 int &new_lift, // New lift returned.
475 const int move_flags,
476 int max_drop, // Max. drop/rise allowed.
477 int max_rise // Max. rise, or -1 to use old beha-
478 // viour (max_drop if FLY, else 1).
479 ) {
480
481 // Ethereal beings always return not blocked
482 // and can only move horizontally
483 if (move_flags & MOVE_ETHEREAL) {
484 new_lift = lift;
485 return false;
486 }
487 // Figure max lift allowed.
488 if (max_rise == -1) {
489 if ((move_flags & (MOVE_FLY | MOVE_MAPEDIT)) != 0) {
490 max_rise = max_drop;
491 } else if ((move_flags & MOVE_WALK)) {
492 max_rise = 1;
493 } else {
494 // Swim.
495 max_rise = 0;
496 }
497 }
498 int max_lift = lift + max_rise;
499 if (max_lift > 255)
500 max_lift = 255; // As high as we can go.
501 set_tflags(tx, ty, max_lift + height);
502 for (new_lift = lift; new_lift <= max_lift; new_lift++) {
503 if (!TEST_TFLAGS(new_lift)) {
504 // Not blocked?
505 int new_high = get_lowest_blocked(new_lift);
506 // Not blocked above?
507 if (new_high == -1 || new_high >= (new_lift + height))
508 break; // Okay.
509 }
510 }
511 if (new_lift > max_lift) { // Spot not found at lift or higher?
512 // Look downwards.
513 new_lift = get_highest_blocked(lift) + 1;
514 if (new_lift >= lift) // Couldn't drop?
515 return true;
516 int new_high = get_lowest_blocked(new_lift);
517 if (new_high != -1 && new_high < new_lift + height)
518 return true; // Still blocked above.
519 }
520 if (new_lift <= lift) { // Not going up? See if falling.
521 new_lift = (move_flags & MOVE_LEVITATE) ? lift :
522 get_highest_blocked(lift) + 1;
523 // Don't allow fall of > max_drop.
524 if (lift - new_lift > max_drop) {
525 // Map-editing? Suspend in air there.
526 if (move_flags & MOVE_MAPEDIT)
527 new_lift = lift - max_drop;
528 else
529 return true;
530 }
531 int new_high = get_lowest_blocked(new_lift);
532
533 // Make sure that where we want to go is tall enough for us
534 if (new_high != -1 && new_high < (new_lift + height))
535 return true;
536 }
537
538 // Found a new place to go, lets test if we can actually move there
539
540 // Lift 0 tests
541 if (new_lift == 0) {
542 if (move_flags & MOVE_MAPEDIT)
543 return false; // Map-editor, so anything is okay.
544 int ter = 0;
545 Check_terrain(obj_list, tx, ty, ter);
546 bool const can_walk = (move_flags & MOVE_WALK) != 0;
547 bool const can_swim = (move_flags & MOVE_SWIM) != 0;
548 bool const can_fly = (move_flags & MOVE_FLY ) != 0;
549 if (can_swim && !can_walk && !can_fly && (ter & 2) == 0) {
550 // Can only swim; do not allow to move outside of water.
551 return true;
552 }
553 if (can_walk && !can_swim && !can_fly && (ter & 2) != 0) {
554 // Can only walk; do not allow to move into water.
555 return true;
556 }
557 if (!can_swim && !can_fly && (ter & 4) != 0) {
558 // Can only walk and terrain is solid (and 0-height).
559 return true;
560 }
561 return false;
562 } else if (move_flags & (MOVE_FLY | MOVE_WALK))
563 return false;
564
565 return true;
566 }
567
568 /*
569 * Activate nearby eggs.
570 */
571
activate_eggs(Game_object * obj,Map_chunk * chunk,int tx,int ty,int tz,int from_tx,int from_ty,unsigned short eggbits,bool now)572 void Chunk_cache::activate_eggs(
573 Game_object *obj, // Object (actor) that's near.
574 Map_chunk *chunk, // Chunk this is attached to.
575 int tx, int ty, int tz, // Tile (absolute).
576 int from_tx, int from_ty, // Tile walked from.
577 unsigned short eggbits, // Eggs[tile].
578 bool now // Do them immediately.
579 ) {
580 // Ensure we exist until the end of the function.
581 auto ownHandle = shared_from_this();
582 size_t i; // Go through eggs.
583 for (i = 0; i < 8 * sizeof(eggbits) - 1 && eggbits;
584 i++, eggbits = eggbits >> 1) {
585 Egg_object *egg;
586 if ((eggbits & 1) && i < egg_objects.size() &&
587 (egg = egg_objects[i]) &&
588 egg->is_active(obj, tx, ty, tz, from_tx, from_ty)) {
589 egg->hatch(obj, now);
590 if (chunk->get_cache() != this)
591 return; // A teleport could have deleted us!
592 }
593 }
594 if (eggbits) { // Check 15th bit.
595 // DON'T use an iterator here, since
596 // the list can change as eggs are
597 // activated, causing a CRASH!
598 size_t sz = egg_objects.size();
599 for (; i < sz; i++) {
600 Egg_object *egg = egg_objects[i];
601 if (egg && egg->is_active(obj, tx, ty, tz, from_tx, from_ty)) {
602 egg->hatch(obj, now);
603 if (chunk->get_cache() != this)
604 return; // A teleport could have deleted us!
605 }
606 }
607 }
608 }
609
610 /*
611 * Find door blocking a given tile.
612 */
613
find_door(const Tile_coord & tile)614 Game_object *Chunk_cache::find_door(
615 const Tile_coord& tile
616 ) {
617 for (auto *door : doors)
618 if (door->blocks(tile))
619 return door; // Found it.
620 return nullptr;
621 }
622
623 /*
624 * Create list for a given chunk.
625 */
626
Map_chunk(Game_map * m,int chunkx,int chunky)627 Map_chunk::Map_chunk(
628 Game_map *m, // Map we'll belong to.
629 int chunkx, int chunky // Absolute chunk coords.
630 ) : map(m),
631 terrain(nullptr), objects(nullptr), first_nonflat(nullptr), from_below(0),
632 from_right(0), from_below_right(0), ice_dungeon(0x00),
633 dungeon_levels(nullptr), cache(nullptr), roof(0),
634 cx(chunkx), cy(chunky), selected(false) {
635 }
636
637 /*
638 * Set terrain. Even if the terrain is the same, it still reloads the
639 * 'flat' objects.
640 */
641
set_terrain(Chunk_terrain * ter)642 void Map_chunk::set_terrain(
643 Chunk_terrain *ter
644 ) {
645 if (terrain) {
646 terrain->remove_client();
647 // Remove objs. from terrain.
648 Game_object_vector removes;
649 {
650 // Separate scope for Object_iterator.
651 Object_iterator it(get_objects());
652 Game_object *each;
653 while ((each = it.get_next()) != nullptr)
654 // Kind of nasty, I know:
655 if (each->as_terrain())
656 removes.push_back(each);
657 }
658 for (auto *remove : removes)
659 // We don't want to edit the chunks here:
660 remove->Game_object::remove_this();
661 }
662 terrain = ter;
663 terrain->add_client();
664 // Get RLE objects in chunk.
665 for (int tiley = 0; tiley < c_tiles_per_chunk; tiley++)
666 for (int tilex = 0; tilex < c_tiles_per_chunk; tilex++) {
667 ShapeID id = ter->get_flat(tilex, tiley);
668 Shape_frame *shape = id.get_shape();
669 if (shape && shape->is_rle()) {
670 int shapenum = id.get_shapenum();
671 int framenum = id.get_framenum();
672 const Shape_info &info = id.get_info();
673 Game_object_shared obj = info.is_animated() ?
674 std::make_shared<Animated_object>(shapenum,
675 framenum, tilex, tiley)
676 : std::make_shared<Terrain_game_object>(shapenum,
677 framenum, tilex, tiley);
678 add(obj.get());
679 }
680 }
681 }
682
683 /*
684 * Add rendering dependencies for a new object.
685 */
686
add_dependencies(Game_object * newobj,Ordering_info & newinfo)687 void Map_chunk::add_dependencies(
688 Game_object *newobj, // Object to add.
689 Ordering_info &newinfo // Info. for new object's ordering.
690 ) {
691 Game_object *obj; // Figure dependencies.
692 Nonflat_object_iterator next(this);
693 while ((obj = next.get_next()) != nullptr) {
694 //cout << "Here " << __LINE__ << " " << obj << endl;
695 /* Compare returns -1 if lt, 0 if dont_care, 1 if gt. */
696 int cmp = Game_object::compare(newinfo, obj);
697 // TODO: Fix this properly, instead of with an ugly hack.
698 // This fixes relative ordering between the Y depression and the Y
699 // shapes in SI. Done so in a way that the depression is not clickable.
700 if (!cmp && GAME_SI && newobj->get_shapenum() == 0xd1
701 && obj->get_shapenum() == 0xd1 && obj->get_framenum() == 17) {
702 cmp = 1;
703 }
704 if (cmp == 1) { // Bigger than this object?
705 newobj->dependencies.insert(obj);
706 obj->dependors.insert(newobj);
707 } else if (cmp == -1) { // Smaller than?
708 obj->dependencies.insert(newobj);
709 newobj->dependors.insert(obj);
710 }
711 }
712 }
713
714 /*
715 * Add rendering dependencies for a new object to another chunk.
716 * NOTE: This is static.
717 *
718 * Output: ->chunk that was checked.
719 */
720
add_outside_dependencies(int cx,int cy,Game_object * newobj,Ordering_info & newinfo)721 inline Map_chunk *Map_chunk::add_outside_dependencies(
722 int cx, int cy, // Chunk to check.
723 Game_object *newobj, // Object to add.
724 Ordering_info &newinfo // Info. for new object's ordering.
725 ) {
726 Map_chunk *chunk = gmap->get_chunk(cx, cy);
727 chunk->add_dependencies(newobj, newinfo);
728 return chunk;
729 }
730
731 /*
732 * Add a game object to a chunk's list.
733 *
734 * Newobj's chunk field is set to this chunk.
735 */
736
add(Game_object * newobj)737 void Map_chunk::add(
738 Game_object *newobj // Object to add.
739 ) {
740 newobj->chunk = this; // Set object's chunk.
741 Ordering_info ord(gwin, newobj);
742 Game_object_shared newobj_shared = newobj->shared_from_this();
743 // Put past flats.
744 if (first_nonflat)
745 objects.insert_before(newobj_shared, first_nonflat);
746 else
747 objects.append(newobj_shared);
748 // Not flat?
749 if (newobj->get_lift() || ord.info.get_3d_height()) {
750 // Deal with dependencies.
751 // First this chunk.
752 add_dependencies(newobj, ord);
753 if (from_below) // Overlaps from below?
754 add_outside_dependencies(cx, INCR_CHUNK(cy),
755 newobj, ord);
756 if (from_right) // Overlaps from right?
757 add_outside_dependencies(INCR_CHUNK(cx), cy,
758 newobj, ord);
759 if (from_below_right)
760 add_outside_dependencies(INCR_CHUNK(cx),
761 INCR_CHUNK(cy), newobj, ord);
762 // See if newobj extends outside.
763 /* Let's try boundary. YES. This helps with statues through roofs!*/
764 bool ext_left = (newobj->get_tx() - ord.xs) < 0 && cx > 0;
765 bool ext_above = (newobj->get_ty() - ord.ys) < 0 && cy > 0;
766 if (ext_left) {
767 add_outside_dependencies(DECR_CHUNK(cx), cy,
768 newobj, ord)->from_right++;
769 if (ext_above)
770 add_outside_dependencies(DECR_CHUNK(cx),
771 DECR_CHUNK(cy),
772 newobj, ord)->from_below_right++;
773 }
774 if (ext_above)
775 add_outside_dependencies(cx, DECR_CHUNK(cy),
776 newobj, ord)->from_below++;
777 first_nonflat = newobj; // Inserted before old first_nonflat.
778 }
779 if (cache) // Add to cache.
780 cache->update_object(this, newobj, true);
781 if (ord.info.is_light_source()) { // Count light sources.
782 if (dungeon_levels && is_dungeon(newobj->get_tx(),
783 newobj->get_ty()))
784 dungeon_lights.insert(newobj);
785 else
786 non_dungeon_lights.insert(newobj);
787 }
788 if (newobj->get_lift() >= 5) { // Looks like a roof?
789 if (ord.info.get_shape_class() == Shape_info::building)
790 roof = 1;
791 }
792 }
793
794 /*
795 * Add an egg.
796 */
797
add_egg(Egg_object * egg)798 void Map_chunk::add_egg(
799 Egg_object *egg
800 ) {
801 add(egg); // Add it normally.
802 egg->set_area();
803 // Messed up Moonshade after Banes if (cache) // Add to cache.
804 need_cache()->update_egg(this, egg, true);
805 }
806
807 /*
808 * Remove an egg.
809 */
810
remove_egg(Egg_object * egg)811 void Map_chunk::remove_egg(
812 Egg_object *egg
813 ) {
814 if (cache) // Remove from cache.
815 cache->update_egg(this, egg, false);
816 remove(egg); // Remove it normally.
817 }
818
819 /*
820 * Remove a game object from this list. The object's 'chunk' field
821 * is set to nullptr.
822 */
823
remove(Game_object * remove)824 void Map_chunk::remove(
825 Game_object *remove
826 ) {
827 assert(remove->get_chunk() == this);
828 if (cache) // Remove from cache.
829 cache->update_object(this, remove, false);
830 remove->clear_dependencies(); // Remove all dependencies.
831 Game_map *gmap = gwin->get_map();
832 const Shape_info &info = remove->get_info();
833 // See if it extends outside.
834 int frame = remove->get_framenum();
835 int tx = remove->get_tx();
836 int ty = remove->get_ty();
837 /* Let's try boundary. YES. Helps with statues through roofs. */
838 bool ext_left = (tx - info.get_3d_xtiles(frame)) < 0 && cx > 0;
839 bool ext_above = (ty - info.get_3d_ytiles(frame)) < 0 && cy > 0;
840 if (ext_left) {
841 gmap->get_chunk(cx - 1, cy)->from_below_right--;
842 if (ext_above)
843 gmap->get_chunk(cx - 1, cy - 1)->from_below_right--;
844 }
845 if (ext_above)
846 gmap->get_chunk(cx, cy - 1)->from_below--;
847 if (info.is_light_source()) { // Count light sources.
848 if (dungeon_levels && is_dungeon(tx, ty))
849 dungeon_lights.erase(remove);
850 else
851 non_dungeon_lights.erase(remove);
852 }
853 if (remove == first_nonflat) { // First nonflat?
854 // Update.
855 first_nonflat = remove->get_next();
856 if (first_nonflat == objects.get_first())
857 first_nonflat = nullptr;
858 }
859 remove->set_invalid(); // No longer part of world.
860 objects.remove(remove); // Remove from list.
861 }
862
863 /*
864 * Is a given rectangle of tiles blocked at a given lift?
865 *
866 * Output: true if so, else false.
867 * If false (tile is free), new_lift contains the new height that
868 * an actor will be at if he walks onto the tile.
869 */
870
is_blocked(int height,int lift,int startx,int starty,int xtiles,int ytiles,int & new_lift,const int move_flags,int max_drop,int max_rise)871 bool Map_chunk::is_blocked(
872 int height, // Height (along lift) to check.
873 int lift, // Starting lift.
874 int startx, int starty, // Starting tile coords.
875 int xtiles, int ytiles, // Width, height in tiles.
876 int &new_lift, // New lift returned.
877 const int move_flags,
878 int max_drop, // Max. drop/rise allowed.
879 int max_rise // Max. rise, or -1 to use old beha-
880 // viour (max_drop if FLY, else 1).
881 ) {
882 Game_map *gmap = gwin->get_map();
883 int tx;
884 int ty;
885 new_lift = 0;
886 startx = (startx + c_num_tiles) % c_num_tiles; // Watch for wrapping.
887 starty = (starty + c_num_tiles) % c_num_tiles;
888 int stopy = (starty + ytiles) % c_num_tiles;
889 int stopx = (startx + xtiles) % c_num_tiles;
890 for (ty = starty; ty != stopy; ty = INCR_TILE(ty)) {
891 // Get y chunk, tile-in-chunk.
892 int cy = ty / c_tiles_per_chunk;
893 int rty = ty % c_tiles_per_chunk;
894 for (tx = startx; tx != stopx; tx = INCR_TILE(tx)) {
895 int this_lift;
896 Map_chunk *olist = gmap->get_chunk(
897 tx / c_tiles_per_chunk, cy);
898 olist->setup_cache();
899 if (olist->is_blocked(height, lift,
900 tx % c_tiles_per_chunk,
901 rty, this_lift, move_flags, max_drop, max_rise))
902 return true;
903 // Take highest one.
904 new_lift = this_lift > new_lift ?
905 this_lift : new_lift;
906 }
907 }
908 return false;
909 }
910
911 /*
912 * Check an absolute tile position.
913 *
914 * Output: true if blocked, false otherwise.
915 * Tile.tz may be updated for stepping onto square.
916 */
917
is_blocked(Tile_coord & tile,int height,const int move_flags,int max_drop,int max_rise)918 bool Map_chunk::is_blocked(
919 Tile_coord &tile,
920 int height, // Height in tiles to check.
921 const int move_flags,
922 int max_drop, // Max. drop/rise allowed.
923 int max_rise // Max. rise, or -1 to use old beha-
924 // viour (max_drop if FLY, else 1).
925 ) {
926 // Get chunk tile is in.
927 Game_map *gmap = gwin->get_map();
928 Map_chunk *chunk = gmap->get_chunk_safely(
929 tile.tx / c_tiles_per_chunk, tile.ty / c_tiles_per_chunk);
930 if (!chunk) // Outside the world?
931 return false; // Then it's not blocked.
932 chunk->setup_cache(); // Be sure cache is present.
933 int new_lift; // Check it within chunk.
934 if (chunk->is_blocked(height, tile.tz, tile.tx % c_tiles_per_chunk,
935 tile.ty % c_tiles_per_chunk, new_lift, move_flags, max_drop,
936 max_rise))
937 return true;
938 tile.tz = new_lift;
939 return false;
940 }
941
942 /*
943 * This one is used to see if an object of dims. possibly > 1X1 can
944 * step onto an adjacent square.
945 */
946
is_blocked(int xtiles,int ytiles,int ztiles,Tile_coord const & from,Tile_coord & to,const int move_flags,int max_drop,int max_rise)947 bool Map_chunk::is_blocked(
948 // Object dims:
949 int xtiles, int ytiles, int ztiles,
950 Tile_coord const &from, // Stepping from here.
951 Tile_coord &to, // Stepping to here. Tz updated.
952 const int move_flags,
953 int max_drop, // Max drop/rise allowed.
954 int max_rise // Max. rise, or -1 to use old beha-
955 // viour (max_drop if FLY, else 1).
956 ) {
957 int vertx0;
958 int vertx1; // Get x-coords. of vert. block
959 // to right/left.
960 int horizx0;
961 int horizx1; // Get x-coords of horiz. block
962 // above/below.
963 int verty0;
964 int verty1; // Get y-coords of horiz. block
965 // above/below.
966 int horizy0;
967 int horizy1; // Get y-coords of vert. block
968 // to right/left.
969 // !Watch for wrapping.
970 horizx0 = (to.tx + 1 - xtiles + c_num_tiles) % c_num_tiles;
971 horizx1 = INCR_TILE(to.tx);
972 if (Tile_coord::gte(to.tx, from.tx)) { // Moving right?
973 // Start to right of hot spot.
974 vertx0 = INCR_TILE(from.tx);
975 vertx1 = INCR_TILE(to.tx); // Stop past dest.
976 } else { // Moving left?
977 vertx0 = (to.tx + 1 - xtiles + c_num_tiles) % c_num_tiles;
978 vertx1 = (from.tx + 1 - xtiles + c_num_tiles) % c_num_tiles;
979 }
980 verty0 = (to.ty + 1 - ytiles + c_num_tiles) % c_num_tiles;
981 verty1 = INCR_TILE(to.ty);
982 if (Tile_coord::gte(to.ty, from.ty)) { // Moving down?
983 // Start below hot spot.
984 horizy0 = INCR_TILE(from.ty);
985 horizy1 = INCR_TILE(to.ty); // End past dest.
986 if (to.ty != from.ty) // Includes bottom of vert. area.
987 verty1 = DECR_TILE(verty1);
988 } else { // Moving up?
989 horizy0 = (to.ty + 1 - ytiles + c_num_tiles) % c_num_tiles;
990 horizy1 = (from.ty + 1 - ytiles + c_num_tiles) % c_num_tiles;
991 // Includes top of vert. area.
992 verty0 = INCR_TILE(verty0);
993 }
994 int x;
995 int y; // Go through horiz. part.
996 int new_lift = from.tz;
997 int new_lift0 = -1; // All lift changes must be same.
998 #ifdef DEBUG
999 assert(Tile_coord::gte(horizy1, horizy0));
1000 assert(Tile_coord::gte(horizx1, horizx0));
1001 assert(Tile_coord::gte(verty1, verty0));
1002 assert(Tile_coord::gte(vertx1, vertx0));
1003 #endif
1004 for (y = horizy0; y != horizy1; y = INCR_TILE(y)) {
1005 // Get y chunk, tile-in-chunk.
1006 int cy = y / c_tiles_per_chunk;
1007 int rty = y % c_tiles_per_chunk;
1008 for (x = horizx0; x != horizx1; x = INCR_TILE(x)) {
1009 Map_chunk *olist = gmap->get_chunk(
1010 x / c_tiles_per_chunk, cy);
1011 olist->setup_cache();
1012 int rtx = x % c_tiles_per_chunk;
1013 if (olist->is_blocked(ztiles, from.tz, rtx, rty,
1014 new_lift, move_flags, max_drop, max_rise))
1015 return true;
1016 if (new_lift != from.tz) {
1017 if (new_lift0 == -1)
1018 new_lift0 = new_lift;
1019 else if (new_lift != new_lift0)
1020 return true;
1021 }
1022 }
1023 }
1024 // Do vert. block.
1025 for (x = vertx0; x != vertx1; x = INCR_TILE(x)) {
1026 // Get x chunk, tile-in-chunk.
1027 int cx = x / c_tiles_per_chunk;
1028 int rtx = x % c_tiles_per_chunk;
1029 for (y = verty0; y != verty1; y = INCR_TILE(y)) {
1030 Map_chunk *olist = gmap->get_chunk(
1031 cx, y / c_tiles_per_chunk);
1032 olist->setup_cache();
1033 int rty = y % c_tiles_per_chunk;
1034 if (olist->is_blocked(ztiles, from.tz, rtx, rty,
1035 new_lift, move_flags, max_drop, max_rise))
1036 return true;
1037 if (new_lift != from.tz) {
1038 if (new_lift0 == -1)
1039 new_lift0 = new_lift;
1040 else if (new_lift != new_lift0)
1041 return true;
1042 }
1043 }
1044 }
1045 to.tz = new_lift;
1046 return false; // All clear.
1047 }
1048
1049 /*
1050 * Get the list of tiles in a square perimeter around a given tile.
1051 *
1052 * Output: List (8*dist) of tiles, starting in Northwest corner and going
1053 * clockwise. List is on heap.
1054 */
1055
Get_square(Tile_coord & pos,int dist)1056 static auto Get_square(
1057 Tile_coord &pos, // Center of square.
1058 int dist // Distance to perimeter (>0)
1059 ) {
1060 std::vector<Tile_coord> square;
1061 square.reserve(8 * dist);
1062 // Upper left corner:
1063 square.emplace_back(DECR_TILE(pos.tx, dist),
1064 DECR_TILE(pos.ty, dist), pos.tz);
1065 const int len = 2 * dist + 1;
1066 int out = 1;
1067 for (int i = 1; i < len; i++, out++) {
1068 const auto &back = square.back();
1069 square.emplace_back(INCR_TILE(back.tx), back.ty, pos.tz);
1070 }
1071 // Down right side.
1072 for (int i = 1; i < len; i++, out++) {
1073 const auto &back = square.back();
1074 square.emplace_back(back.tx, INCR_TILE(back.ty), pos.tz);
1075 }
1076 // Bottom, going back to left.
1077 for (int i = 1; i < len; i++, out++) {
1078 const auto &back = square.back();
1079 square.emplace_back(DECR_TILE(back.tx), back.ty, pos.tz);
1080 }
1081 // Left side, going up.
1082 for (int i = 1; i < len - 1; i++, out++) {
1083 const auto &back = square.back();
1084 square.emplace_back(back.tx, DECR_TILE(back.ty), pos.tz);
1085 }
1086 return square;
1087 }
1088
1089 /*
1090 * Check a spot against the 'where' paramater to find_spot.
1091 *
1092 * Output: true if it passes.
1093 */
1094
Check_spot(Map_chunk::Find_spot_where where,int tx,int ty,int tz)1095 inline bool Check_spot(
1096 Map_chunk::Find_spot_where where,
1097 int tx, int ty, int tz
1098 ) {
1099 Game_map *gmap = Game_window::get_instance()->get_map();
1100 int cx = tx / c_tiles_per_chunk;
1101 int cy = ty / c_tiles_per_chunk;
1102 Map_chunk *chunk = gmap->get_chunk_safely(cx, cy);
1103 if (!chunk)
1104 return false;
1105 return (where == Map_chunk::inside) ==
1106 (chunk->is_roof(tx % c_tiles_per_chunk,
1107 ty % c_tiles_per_chunk, tz) < 31);
1108 }
1109
1110 /*
1111 * Find a free area for an object of a given shape, looking outwards.
1112 *
1113 * Output: Tile if successful, else (-1, -1, -1).
1114 */
1115
find_spot(Tile_coord pos,int dist,int shapenum,int framenum,int max_drop,int dir,Find_spot_where where)1116 Tile_coord Map_chunk::find_spot(
1117 Tile_coord pos, // Starting point.
1118 int dist, // Distance to look outwards. (0 means
1119 // only check 'pos'.
1120 int shapenum, // Shape, frame to find spot for.
1121 int framenum,
1122 int max_drop, // Allow to drop by this much.
1123 int dir, // Preferred direction (0-7), or -1 for
1124 // random.
1125 Find_spot_where where // Inside/outside.
1126 ) {
1127 const Shape_info &info = ShapeID::get_info(shapenum);
1128 int xs = info.get_3d_xtiles(framenum);
1129 int ys = info.get_3d_ytiles(framenum);
1130 int zs = info.get_3d_height();
1131 // The 'MOVE_FLY' flag really means
1132 // we can look upwards by max_drop.
1133 const int mflags = MOVE_WALK | MOVE_FLY;
1134 int new_lift;
1135 // Start with original position.
1136 if (!Map_chunk::is_blocked(zs, pos.tz, pos.tx - xs + 1,
1137 pos.ty - ys + 1, xs, ys, new_lift, mflags, max_drop))
1138 return Tile_coord(pos.tx, pos.ty, new_lift);
1139 if (dir < 0)
1140 dir = rand() % 8; // Choose dir. randomly.
1141 dir = (dir + 1) % 8; // Make NW the 0 point.
1142 for (int d = 1; d <= dist; d++) { // Look outwards.
1143 int square_cnt = 8 * d ; // # tiles in square's perim.
1144 // Get square (starting in NW).
1145 const auto square = Get_square(pos, d);
1146 int index = dir * d; // Get index of preferred spot.
1147 // Get start of preferred range.
1148 index = (index - d / 2 + square_cnt) % square_cnt;
1149 for (int cnt = square_cnt; cnt; cnt--, index++) {
1150 const Tile_coord &p = square[index % square_cnt];
1151 if (!Map_chunk::is_blocked(zs, p.tz, p.tx - xs + 1,
1152 p.ty - ys + 1, xs, ys, new_lift, mflags,
1153 max_drop) &&
1154 (where == anywhere ||
1155 Check_spot(where, p.tx, p.ty, new_lift))) {
1156 // Use tile before deleting.
1157 return Tile_coord(p.tx, p.ty, new_lift);
1158 }
1159 }
1160 }
1161 return Tile_coord(-1, -1, -1);
1162 }
1163
1164 /*
1165 * Find a free area for an object (usually an NPC) that we want to
1166 * approach a given position.
1167 *
1168 * Output: Tile if successful, else (-1, -1, -1).
1169 */
1170
find_spot(Tile_coord const & pos,int dist,Game_object * obj,int max_drop,Find_spot_where where)1171 Tile_coord Map_chunk::find_spot(
1172 Tile_coord const &pos, // Starting point.
1173 int dist, // Distance to look outwards. (0 means
1174 // only check 'pos'.
1175 Game_object *obj, // Object that we want to move.
1176 int max_drop, // Allow to drop by this much.
1177 Find_spot_where where // Inside/outside.
1178 ) {
1179 Tile_coord t2 = obj->get_tile();
1180 // Get direction from pos. to object.
1181 int dir = static_cast<int>(Get_direction(pos.ty - t2.ty, t2.tx - pos.tx));
1182 return find_spot(pos, dist, obj->get_shapenum(), obj->get_framenum(),
1183 max_drop, dir, where);
1184 }
1185
1186 /*
1187 * Find all desired objects within a given rectangle.
1188 *
1189 * Output: # found, appended to vec.
1190 */
1191
find_in_area(Game_object_vector & vec,TileRect const & area,int shapenum,int framenum)1192 int Map_chunk::find_in_area(
1193 Game_object_vector &vec, // Returned here.
1194 TileRect const &area, // Area to search.
1195 int shapenum,
1196 int framenum
1197 ) {
1198 int savesize = vec.size();
1199 // Go through interesected chunks.
1200 Chunk_intersect_iterator next_chunk(area);
1201 TileRect tiles; // (Tiles within intersected chunk).
1202 int eachcx;
1203 int eachcy;
1204 Game_map *gmap = gwin->get_map();
1205 while (next_chunk.get_next(tiles, eachcx, eachcy)) {
1206 Map_chunk *chunk = gmap->get_chunk_safely(eachcx, eachcy);
1207 if (!chunk)
1208 continue;
1209 Object_iterator next(chunk->objects);
1210 Game_object *each;
1211 while ((each = next.get_next()) != nullptr)
1212 if (each->get_shapenum() == shapenum &&
1213 each->get_framenum() == framenum &&
1214 tiles.has_world_point(each->get_tx(), each->get_ty()))
1215 vec.push_back(each);
1216 }
1217 return vec.size() - savesize;
1218 }
1219
1220
1221
1222 /*
1223 * Test all nearby eggs when you've teleported in.
1224 */
1225
try_all_eggs(Game_object * obj,int tx,int ty,int tz,int from_tx,int from_ty)1226 void Map_chunk::try_all_eggs(
1227 Game_object *obj, // Object (actor) that's near.
1228 int tx, int ty, int tz, // Tile (absolute).
1229 int from_tx, int from_ty // Tile walked from.
1230 ) {
1231 static int norecurse = 0; // NO recursion here.
1232 if (norecurse)
1233 return;
1234 norecurse++;
1235 Game_map *gmap = gwin->get_map();
1236 Tile_coord pos = obj->get_tile();
1237 const int dist = 32; // See if this works okay.
1238 TileRect area(pos.tx - dist, pos.ty - dist, 2 * dist, 2 * dist);
1239 // Go through interesected chunks.
1240 Chunk_intersect_iterator next_chunk(area);
1241 TileRect tiles; // (Ignored).
1242 int eachcx;
1243 int eachcy;
1244 Egg_vector eggs; // Get them here first, as activating
1245 eggs.reserve(40);
1246 // an egg could affect chunk's list.
1247 while (next_chunk.get_next(tiles, eachcx, eachcy)) {
1248 Map_chunk *chunk = gmap->get_chunk_safely(eachcx, eachcy);
1249 if (!chunk)
1250 continue;
1251 chunk->setup_cache(); // I think we should do this.
1252 Object_iterator next(chunk->objects);
1253 Game_object *each;
1254 while ((each = next.get_next()) != nullptr)
1255 if (each->is_egg()) {
1256 Egg_object *egg = each->as_egg();
1257 // Music eggs are causing problems.
1258 if (egg->get_type() != Egg_object::jukebox &&
1259 // And don't teleport a 2nd time.
1260 egg->get_type() != Egg_object::teleport &&
1261 egg->is_active(obj, tx, ty, tz, from_tx, from_ty))
1262 eggs.push_back(egg);
1263 }
1264 }
1265 for (auto *egg : eggs)
1266 egg->hatch(obj);
1267 norecurse--;
1268 }
1269
1270 /*
1271 * Add a rectangle of dungeon tiles (but only if higher!).
1272 */
1273
add_dungeon_levels(TileRect & tiles,unsigned int lift)1274 void Map_chunk::add_dungeon_levels(
1275 TileRect &tiles, unsigned int lift
1276 ) {
1277 if (!dungeon_levels) {
1278 // First one found.
1279 dungeon_levels = std::make_unique<unsigned char[]>(256);
1280 }
1281 int endy = tiles.y + tiles.h;
1282 int endx = tiles.x + tiles.w;
1283 for (int ty = tiles.y; ty < endy; ty++) {
1284 for (int tx = tiles.x; tx < endx; tx++) {
1285 if (GAME_SI) {
1286 // SI has roofs at random levels!!
1287 lift = 5;
1288 }
1289 dungeon_levels[ty * c_tiles_per_chunk + tx] = lift;
1290 }
1291 }
1292 }
1293
1294 /*
1295 * Set up the dungeon levels (after IFIX objects read).
1296 */
1297
setup_dungeon_levels()1298 void Map_chunk::setup_dungeon_levels(
1299 ) {
1300 Game_map *gmap = gwin->get_map();
1301
1302 Object_iterator next(objects);
1303 Game_object *each;
1304 while ((each = next.get_next()) != nullptr) {
1305 // Test for mountain-tops.
1306 const Shape_info &shinf = each->get_info();
1307 if (shinf.get_shape_class() == Shape_info::building &&
1308 shinf.get_mountain_top_type() == Shape_info::normal_mountain_top) {
1309 // SI shape 941, frame 0 => do whole chunk (I think).
1310 TileRect area =
1311 (shinf.has_translucency()
1312 && each->get_framenum() == 0)
1313 ? TileRect(cx * c_tiles_per_chunk,
1314 cy * c_tiles_per_chunk,
1315 c_tiles_per_chunk,
1316 c_tiles_per_chunk)
1317 : each->get_footprint();
1318
1319 // Go through interesected chunks.
1320 Chunk_intersect_iterator next_chunk(area);
1321 TileRect tiles;// Rel. tiles.
1322 int cx;
1323 int cy;
1324 while (next_chunk.get_next(tiles, cx, cy))
1325 gmap->get_chunk(cx, cy)->add_dungeon_levels(
1326 tiles, each->get_lift());
1327 } // Ice Dungeon Pieces in SI
1328 else if (shinf.get_shape_class() == Shape_info::building &&
1329 shinf.get_mountain_top_type() == Shape_info::snow_mountain_top) {
1330 // HACK ALERT! This gets 320x200 to work, but it is a hack
1331 // This is not exactly accurate.
1332 ice_dungeon |= 1 << ((each->get_tx() >> 3) + 2 * (each->get_ty() >> 3));
1333
1334 TileRect area = each->get_footprint();
1335
1336 // Go through interesected chunks.
1337 Chunk_intersect_iterator next_chunk(area);
1338 TileRect tiles;// Rel. tiles.
1339 int cx;
1340 int cy;
1341 while (next_chunk.get_next(tiles, cx, cy))
1342 gmap->get_chunk(cx, cy)->add_dungeon_levels(
1343 tiles, each->get_lift());
1344 }
1345 }
1346 if (dungeon_levels) { // Recount lights.
1347 dungeon_lights.clear();
1348 non_dungeon_lights.clear();
1349 next.reset();
1350 while ((each = next.get_next()) != nullptr)
1351 if (each->get_info().is_light_source()) {
1352 if (is_dungeon(each->get_tx(), each->get_ty()))
1353 dungeon_lights.insert(each);
1354 else
1355 non_dungeon_lights.insert(each);
1356 }
1357 }
1358 }
1359
1360 /*
1361 * Recursively apply gravity over a given rectangle that is known to be
1362 * unblocked below a given lift.
1363 */
1364
gravity(TileRect const & area,int lift)1365 void Map_chunk::gravity(
1366 TileRect const &area, // Unblocked tiles (in abs. coords).
1367 int lift // Lift where tiles are free.
1368 ) {
1369 Game_object_vector dropped; // Gets list of objs. that dropped.
1370 dropped.reserve(20);
1371 // Go through interesected chunks.
1372 Chunk_intersect_iterator next_chunk(area);
1373 TileRect tiles; // Rel. tiles. Not used.
1374 int cx;
1375 int cy;
1376 int new_lift;
1377 while (next_chunk.get_next(tiles, cx, cy)) {
1378 Map_chunk *chunk = gmap->get_chunk(cx, cy);
1379 Object_iterator objs(chunk->objects);
1380 Game_object *obj;
1381 while ((obj = objs.get_next()) != nullptr) {
1382 // We DO want NPC's to fall.
1383 if (!obj->is_dragable() &&
1384 !obj->get_info().is_npc())
1385 continue;
1386 Tile_coord t = obj->get_tile();
1387 // Get footprint.
1388 TileRect foot = obj->get_footprint();
1389 // Above area?
1390 if (t.tz >= lift && foot.intersects(area) &&
1391 // Unblocked below itself?
1392 !is_blocked(1, t.tz - 1, foot.x, foot.y,
1393 foot.w, foot.h, new_lift,
1394 MOVE_ALL_TERRAIN, 0) &&
1395 new_lift < t.tz)
1396 dropped.push_back(obj);
1397 }
1398 }
1399 // Drop each one found.
1400 for (auto *obj : dropped) {
1401 Tile_coord t = obj->get_tile();
1402 // Get footprint.
1403 TileRect foot = obj->get_footprint();
1404 // Let drop as far as possible.
1405 if (!is_blocked(1, t.tz - 1, foot.x, foot.y,
1406 foot.w, foot.h, new_lift, MOVE_ALL_TERRAIN, 100) && new_lift < t.tz) {
1407 // Drop & recurse.
1408 obj->move(t.tx, t.ty, new_lift);
1409 gravity(foot, obj->get_lift() +
1410 obj->get_info().get_3d_height());
1411 }
1412 }
1413 }
1414
1415 /*
1416 * Finds if there is a 'roof' above lift in tile (tx, ty)
1417 * of the chunk. Point is taken 4 above lift
1418 *
1419 * Roof can be any object, not just a literal roof
1420 *
1421 * Output: height of the roof.
1422 * A return of 31 means no roof
1423 *
1424 */
is_roof(int tx,int ty,int lift)1425 int Map_chunk::is_roof(int tx, int ty, int lift) {
1426 int height = get_lowest_blocked(lift + 4, tx, ty);
1427 if (height == -1) return 255;
1428 return height;
1429 }
1430
kill_cache()1431 void Map_chunk::kill_cache() {
1432 // Get rid of terrain
1433 if (terrain) {
1434 terrain->remove_client();
1435 }
1436 terrain = nullptr;
1437
1438 // Now remove the cachce
1439 cache.reset();
1440
1441 // Delete dungeon bits
1442 dungeon_levels.reset();
1443 }
1444
get_obj_actors(vector<Game_object * > & removes,vector<Actor * > & actors)1445 int Map_chunk::get_obj_actors(vector<Game_object *> &removes,
1446 vector<Actor *> &actors) {
1447 int buf_size = 0;
1448 bool failed = false;
1449
1450 // Separate scope for Object_iterator.
1451 Object_iterator it(get_objects());
1452 Game_object *each;
1453 while ((each = it.get_next()) != nullptr) {
1454 Actor *actor = each->as_actor();
1455
1456 // Normal objects and monsters
1457 if (actor == nullptr || (each->is_monster() && each->get_flag(Obj_flags::is_temporary))) {
1458 removes.push_back(each);
1459 int ireg_size = each->get_ireg_size();
1460
1461 if (ireg_size < 0) failed = true;
1462 else buf_size += ireg_size;
1463 }
1464 // Actors/NPCs here
1465 else {
1466 actors.push_back(actor);
1467 }
1468 }
1469
1470 return failed ? -1 : buf_size;
1471 }
1472
write(ODataSource & out,bool v2)1473 void Map_chunk::write(ODataSource& out, bool v2) {
1474 // Restore original order (sort of).
1475 Object_iterator_backwards next(this);
1476 Game_object *obj;
1477 while ((obj = next.get_next()) != nullptr)
1478 obj->write_ifix(&out, v2);
1479 }
1480