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