1 /**
2  ** Gamemap.cc - X-windows Ultima7 map browser.
3  **/
4 
5 /*
6  *
7  *  Copyright (C) 1998-1999  Jeffrey S. Freedman
8  *  Copyright (C) 2000-2013  The Exult Team
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  (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 <cstdlib>
30 #include <cstring>
31 #include <cstdarg>
32 #include <cstdio>
33 
34 #include "gamemap.h"
35 #include "objs.h"
36 #include "chunks.h"
37 #include "mappatch.h"
38 #include "fnames.h"
39 #include "utils.h"
40 #include "shapeinf.h"
41 #include "objiter.h"
42 #include "Flex.h"
43 #include "exceptions.h"
44 #include "animate.h"
45 #include "barge.h"
46 #include "spellbook.h"
47 #include "virstone.h"
48 #include "egg.h"
49 #include "jawbone.h"
50 #include "actors.h" /* For Dead_body, which should be moved. */
51 #include "ucsched.h"
52 #include "gamewin.h"    /* With some work, could get rid of this. */
53 #include "game.h"
54 #include "effects.h"
55 #include "objiter.cc"   /* Yes we #include the .cc here on purpose! Please don't "fix" this */
56 #include "databuf.h"
57 #include "weaponinf.h"
58 #include <fstream>
59 #include <memory>
60 #include <sstream>
61 #include "ios_state.hpp"
62 
63 using std::cerr;
64 using std::cout;
65 using std::endl;
66 using std::istream;
67 using std::ifstream;
68 using std::istringstream;
69 using std::ios;
70 using std::memcpy;
71 using std::ofstream;
72 using std::ostringstream;
73 using std::string;
74 using std::strlen;
75 using std::vector;
76 using std::pair;
77 
78 vector<Chunk_terrain *> *Game_map::chunk_terrains = nullptr;
79 std::ifstream *Game_map::chunks = nullptr;
80 bool Game_map::v2_chunks = false;
81 bool Game_map::read_all_terrain = false;
82 bool Game_map::chunk_terrains_modified = false;
83 
84 const int V2_CHUNK_HDR_SIZE = 4 + 4 + 2; // 0xffff, "exlt", vers.
85 static char v2hdr[] = {static_cast<char>(0xff), static_cast<char>(0xff),
86                        static_cast<char>(0xff), static_cast<char>(0xff),
87                        'e', 'x', 'l', 't', 0, 0
88                       };
89 
90 /*
91  *  Create a chunk.
92  */
93 
create_chunk(int cx,int cy)94 Map_chunk *Game_map::create_chunk(
95     int cx, int cy
96 ) {
97 	objects[cx][cy] = std::make_unique<Map_chunk>(this, cx, cy);
98 	return get_chunk_unsafe(cx, cy);
99 }
100 
101 /*
102  *  Read in a terrain chunk.
103  */
104 
read_terrain(int chunk_num)105 Chunk_terrain *Game_map::read_terrain(
106     int chunk_num           // Want this one from u7chunks.
107 ) {
108 	const int ntiles = c_tiles_per_chunk * c_tiles_per_chunk;
109 	assert(chunk_num >= 0 && static_cast<unsigned>(chunk_num) < chunk_terrains->size());
110 	unsigned char buf[ntiles * 3];
111 	if (v2_chunks) {
112 		chunks->seekg(V2_CHUNK_HDR_SIZE + chunk_num * ntiles * 3);
113 		chunks->read(reinterpret_cast<char *>(buf), ntiles * 3);
114 	} else {
115 		chunks->seekg(chunk_num * ntiles * 2);
116 		chunks->read(reinterpret_cast<char *>(buf), ntiles * 2);
117 	}
118 	auto *ter = new Chunk_terrain(&buf[0], v2_chunks);
119 	if (static_cast<unsigned>(chunk_num) >= chunk_terrains->size())
120 		chunk_terrains->resize(chunk_num + 1);
121 	(*chunk_terrains)[chunk_num] = ter;
122 	return ter;
123 }
124 
125 /*
126  *  Create game window.
127  */
128 
Game_map(int n)129 Game_map::Game_map(
130     int n
131 ) :
132 	num(n), didinit(false),
133 	map_modified(false), caching_out(0),
134 	map_patches(std::make_unique<Map_patch_collection>()) {
135 }
136 
137 /*
138  *  Deleting map.
139  */
140 
~Game_map()141 Game_map::~Game_map(
142 ) {
143 	clear();            // Delete all objects, chunks.
144 	delete chunks;
145 }
146 
147 /*
148  *  Init. the static data.
149  */
150 
init_chunks()151 void Game_map::init_chunks(
152 ) {
153 	delete chunks;
154 	chunks = new ifstream;
155 	int num_chunk_terrains;
156 	bool patch_exists = is_system_path_defined("<PATCH>");
157 	if (patch_exists && U7exists(PATCH_U7CHUNKS))
158 		U7open(*chunks, PATCH_U7CHUNKS);
159 	else try {
160 			U7open(*chunks, U7CHUNKS);
161 		} catch (const file_exception &) {
162 			if (!Game::is_editing() ||  // Ok if map-editing.
163 			        !patch_exists)  // But only if patch exists.
164 				throw;
165 			ofstream ochunks;   // Create one in 'patch'.
166 			U7open(ochunks, PATCH_U7CHUNKS);
167 			ochunks.write(v2hdr, sizeof(v2hdr));
168 			unsigned char buf[16 * 16 * 3]{};
169 			ochunks.write(reinterpret_cast<char *>(buf), sizeof(buf));
170 			ochunks.close();
171 			U7open(*chunks, PATCH_U7CHUNKS);
172 		}
173 	char v2buf[V2_CHUNK_HDR_SIZE];  // Check for V2.
174 	chunks->read(v2buf, sizeof(v2buf));
175 	int hdrsize = 0;
176 	int chunksz = c_tiles_per_chunk * c_tiles_per_chunk * 2;
177 	if (memcmp(v2hdr, v2buf, sizeof(v2buf)) == 0) {
178 		v2_chunks = true;
179 		hdrsize = V2_CHUNK_HDR_SIZE;
180 		chunksz = c_tiles_per_chunk * c_tiles_per_chunk * 3;
181 	}
182 	// Get to end so we can get length.
183 	chunks->seekg(0, ios::end);
184 	// 2 bytes/tile.
185 	num_chunk_terrains = (static_cast<int>(chunks->tellg()) - hdrsize) / chunksz;
186 	if (!chunk_terrains)
187 		chunk_terrains = new vector<Chunk_terrain *>();
188 	// Resize list to hold all.
189 	chunk_terrains->resize(num_chunk_terrains);
190 	read_all_terrain = false;
191 }
192 
193 /*
194  *  Initialize for new/restored game.
195  */
196 
init()197 void Game_map::init(
198 ) {
199 	char fname[128];
200 
201 	if (num == 0)
202 		init_chunks();
203 	map_modified = false;
204 	std::ifstream u7map;        // Read in map.
205 	bool nomap = false;
206 	if (is_system_path_defined("<PATCH>") &&
207 	        U7exists(get_mapped_name(PATCH_U7MAP, fname)))
208 		U7open(u7map, fname);
209 	else try {
210 			U7open(u7map, get_mapped_name(U7MAP, fname));
211 		} catch (const file_exception & /*f*/) {
212 			if (!Game::is_editing())    // Ok if map-editing.
213 				cerr << "Map file '" << fname << "' not found." <<
214 				     endl;
215 			nomap = true;
216 		}
217 	for (int schunk = 0; schunk < c_num_schunks * c_num_schunks; schunk++) {
218 		// Read in the chunk #'s.
219 		unsigned char buf[16 * 16 * 2];
220 		if (nomap)
221 			std::fill(std::begin(buf), std::end(buf), 0);
222 		else
223 			u7map.read(reinterpret_cast<char *>(buf), sizeof(buf));
224 		int scy = 16 * (schunk / 12); // Get abs. chunk coords.
225 		int scx = 16 * (schunk % 12);
226 		const uint8 *mapdata = buf;
227 		// Go through chunks.
228 		for (int cy = 0; cy < 16; cy++)
229 			for (int cx = 0; cx < 16; cx++)
230 				terrain_map[scx + cx][scy + cy] = Read2(mapdata);
231 	}
232 	u7map.close();
233 	// Clear object lists, flags.
234 	for (auto& row : objects) {
235 		for (auto& obj : row) {
236 			obj.reset();
237 		}
238 	}
239 	std::fill(std::begin(schunk_read), std::end(schunk_read), false);
240 	std::fill(std::begin(schunk_modified), std::end(schunk_modified), false);
241 	std::fill(std::begin(schunk_cache), std::end(schunk_cache), nullptr);
242 	std::fill(std::begin(schunk_cache_sizes), std::end(schunk_cache_sizes), -1);
243 
244 	didinit = true;
245 }
246 
247 /*
248  *  Clear the static data.
249  */
250 
clear_chunks()251 void Game_map::clear_chunks(
252 ) {
253 	if (chunk_terrains) {
254 		int cnt = chunk_terrains->size();
255 		for (int i = 0; i < cnt; i++)
256 			delete (*chunk_terrains)[i];
257 		delete chunk_terrains;
258 		chunk_terrains = nullptr;
259 	}
260 	delete chunks;          // Close 'u7chunks'.
261 	chunks = nullptr;
262 	read_all_terrain = false;
263 }
264 
265 /*
266  *  Clear out world's contents.  Should be used during a 'restore'.
267  */
268 
clear()269 void Game_map::clear(
270 ) {
271 	if (num == 0)
272 		clear_chunks();
273 
274 	if (didinit) {
275 		// Delete all chunks (& their objs).
276 		for (auto *i : schunk_cache) {
277 			delete [] i;
278 		}
279 	}
280 	for (auto& row : objects) {
281 		for (auto& obj : row) {
282 			obj.reset();
283 		}
284 	}
285 	didinit = false;
286 	map_modified = false;
287 	// Clear 'read' flags.
288 	std::fill(std::begin(schunk_read), std::end(schunk_read), false);
289 	std::fill(std::begin(schunk_modified), std::end(schunk_modified), false);
290 	std::fill(std::begin(schunk_cache), std::end(schunk_cache), nullptr);
291 	std::fill(std::begin(schunk_cache_sizes), std::end(schunk_cache_sizes), -1);
292 }
293 
294 /*
295  *  Read in superchunk data to cover the screen.
296  */
297 
read_map_data()298 void Game_map::read_map_data(
299 ) {
300 	Game_window *gwin = Game_window::get_instance();
301 	int scrolltx = gwin->get_scrolltx();
302 	int scrollty = gwin->get_scrollty();
303 	int w = gwin->get_width();
304 	int h = gwin->get_height();
305 	// Start one tile to left.
306 	int firstsx = (scrolltx - 1) / c_tiles_per_schunk;
307 	int firstsy = (scrollty - 1) / c_tiles_per_schunk;
308 	// End 8 tiles to right.
309 	int lastsx = (scrolltx + (w + c_tilesize - 2) / c_tilesize +
310 	              c_tiles_per_chunk / 2) / c_tiles_per_schunk;
311 	int lastsy = (scrollty + (h + c_tilesize - 2) / c_tilesize +
312 	              c_tiles_per_chunk / 2) / c_tiles_per_schunk;
313 	// Watch for wrapping.
314 	int stopsx = (lastsx + 1) % c_num_schunks;
315 	int stopsy = (lastsy + 1) % c_num_schunks;
316 	// Read in "map", "ifix" objects for
317 	//  all visible superchunks.
318 	for (int sy = firstsy; sy != stopsy; sy = (sy + 1) % c_num_schunks)
319 		for (int sx = firstsx; sx != stopsx;
320 		        sx = (sx + 1) % c_num_schunks) {
321 			// Figure superchunk #.
322 			int schunk = 12 * sy + sx;
323 			// Read it if necessary.
324 			if (!schunk_read[schunk])
325 				get_superchunk_objects(schunk);
326 		}
327 }
328 
329 /*
330  *  Get the map objects and scenery for a superchunk.
331  */
332 
get_map_objects(int schunk)333 void Game_map::get_map_objects(
334     int schunk          // Superchunk # (0-143).
335 ) {
336 	int scy = 16 * (schunk / 12); // Get abs. chunk coords.
337 	int scx = 16 * (schunk % 12);
338 	// Go through chunks.
339 	for (int cy = 0; cy < 16; cy++)
340 		for (int cx = 0; cx < 16; cx++)
341 			get_chunk_objects(scx + cx, scy + cy);
342 }
343 
344 /*
345  *  Read in terrain graphics data into window's image.  (May also be
346  *  called during map-editing if the chunknum changes.)
347  */
348 
get_chunk_objects(int cx,int cy)349 void Game_map::get_chunk_objects(
350     int cx, int cy          // Chunk index within map.
351 ) {
352 	// Get list we'll store into.
353 	Map_chunk *chunk = get_chunk(cx, cy);
354 	int chunk_num = terrain_map[cx][cy];
355 	Chunk_terrain *ter = get_terrain(chunk_num);
356 	chunk->set_terrain(ter);
357 }
358 
359 /*
360  *  Read in all terrain chunks (for editing).
361  */
362 
get_all_terrain()363 void Game_map::get_all_terrain(
364 ) {
365 	if (read_all_terrain)
366 		return;         // Already done.
367 	int num_chunk_terrains = chunk_terrains->size();
368 	for (int i = 0; i < num_chunk_terrains; i++)
369 		if (!(*chunk_terrains)[i])
370 			read_terrain(i);
371 	read_all_terrain = true;
372 }
373 
374 /*
375  *  Set a chunk to a new terrain (during map-editing).
376  */
377 
set_chunk_terrain(int cx,int cy,int chunknum)378 void Game_map::set_chunk_terrain(
379     int cx, int cy,         // Coords. of chunk to change.
380     int chunknum            // New chunk #.
381 ) {
382 	terrain_map[cx][cy] = chunknum; // Set map.
383 	get_chunk_objects(cx, cy);  // Set chunk to it.
384 	map_modified = true;
385 }
386 
387 /*
388  *  Build a file name with the map directory before it; ie,
389  *      get_mapped_name("<GAMEDAT>/ireg, 3, to) will store
390  *          "<GAMEDAT>/map03/ireg".
391  */
392 
get_mapped_name(const char * from,char * to)393 char *Game_map::get_mapped_name(
394     const char *from,
395     char *to
396 ) {
397 	return Get_mapped_name(from, num, to);
398 }
399 
400 /*
401  *  Get the name of an ireg or ifix file.
402  *
403  *  Output: ->fname, where name is stored.
404  */
405 
get_schunk_file_name(const char * prefix,int schunk,char * fname)406 char *Game_map::get_schunk_file_name(
407     const char *prefix,     // "ireg" or "ifix".
408     int schunk,         // Superchunk # (0-143).
409     char *fname         // Name is stored here.
410 ) {
411 	get_mapped_name(prefix, fname);
412 	int len = strlen(fname);
413 	constexpr static const char hexLUT[] = "0123456789abcdef";
414 	fname[len] = hexLUT[schunk / 16];
415 	fname[len + 1] = hexLUT[schunk % 16];
416 	fname[len + 2] = 0;
417 	return fname;
418 }
419 
420 /*
421  *  Have shapes been added?
422  */
423 
New_shapes()424 static bool New_shapes() {
425 	int u7nshapes = GAME_SI ? 1036 : 1024;
426 	int nshapes =
427 	    Shape_manager::get_instance()->get_shapes().get_num_shapes();
428 	return nshapes > u7nshapes;
429 }
430 
431 /*
432  *  Write out the chunks descriptions.
433  */
434 
write_chunk_terrains()435 void Game_map::write_chunk_terrains(
436 ) {
437 	const int ntiles = c_tiles_per_chunk * c_tiles_per_chunk;
438 	int cnt = chunk_terrains->size();
439 	int i;              // Any terrains modified?
440 	for (i = 0; i < cnt; i++)
441 		if ((*chunk_terrains)[i] &&
442 		        (*chunk_terrains)[i]->is_modified())
443 			break;
444 	if (i < cnt) {          // Got to update.
445 		get_all_terrain();  // IMPORTANT:  Get all in memory.
446 		ofstream ochunks;   // Open file for chunks data.
447 		// This truncates the file.
448 		U7open(ochunks, PATCH_U7CHUNKS);
449 		v2_chunks = New_shapes();
450 		int nbytes = v2_chunks ? 3 : 2;
451 		if (v2_chunks)
452 			ochunks.write(v2hdr, sizeof(v2hdr));
453 		for (i = 0; i < cnt; i++) {
454 			Chunk_terrain *ter = (*chunk_terrains)[i];
455 			unsigned char data[ntiles * 3];
456 			if (ter) {
457 				ter->write_flats(data, v2_chunks);
458 				ter->set_modified(false);
459 			} else {
460 				std::fill_n(data, ntiles * nbytes, 0);
461 				cerr << "nullptr terrain.  U7chunks may be bad."
462 				     << endl;
463 			}
464 			ochunks.write(reinterpret_cast<char *>(data),
465 			              ntiles * nbytes);
466 		}
467 		if (!ochunks.good())
468 			throw file_write_exception(U7CHUNKS);
469 		ochunks.close();
470 	}
471 	chunk_terrains_modified = false;
472 }
473 
474 /*
475  *  Write out the 'static' map files.
476  */
477 
write_static()478 void Game_map::write_static(
479 ) {
480 	char fname[128];
481 	U7mkdir("<PATCH>", 0755);   // Create dir if not already there.
482 	//  Don't use PATCHDAT define cause
483 	//  it has a trailing slash
484 
485 	int schunk;         // Write each superchunk to 'static'.
486 	for (schunk = 0; schunk < c_num_schunks * c_num_schunks; schunk++)
487 		// Only write what we've modified.
488 		if (schunk_modified[schunk])
489 			write_ifix_objects(schunk);
490 	if (chunk_terrains_modified)
491 		write_chunk_terrains();
492 	std::ofstream u7map;        // Write out map.
493 	U7open(u7map, get_mapped_name(PATCH_U7MAP, fname));
494 	for (schunk = 0; schunk < c_num_schunks * c_num_schunks; schunk++) {
495 		int scy = 16 * (schunk / 12); // Get abs. chunk coords.
496 		int scx = 16 * (schunk % 12);
497 		uint8 buf[16 * 16 * 2];
498 		uint8 *mapdata = buf;
499 		// Go through chunks.
500 		for (int cy = 0; cy < 16; cy++)
501 			for (int cx = 0; cx < 16; cx++)
502 				Write2(mapdata, terrain_map[scx + cx][scy + cy]);
503 		u7map.write(reinterpret_cast<char *>(buf), sizeof(buf));
504 	}
505 	if (!u7map.good())
506 		throw file_write_exception(U7MAP);
507 	u7map.close();
508 	map_modified = false;
509 }
510 
511 /*
512  *  Write out one of the "u7ifix" files.
513  *
514  *  Output: Errors reported.
515  */
516 
write_ifix_objects(int schunk)517 void Game_map::write_ifix_objects(
518     int schunk          // Superchunk # (0-143).
519 ) {
520 	char fname[128];        // Set up name.
521 	OFileDataSource ifix(get_schunk_file_name(PATCH_U7IFIX, schunk, fname));
522 	// +++++Use game title.
523 	const int count = c_chunks_per_schunk * c_chunks_per_schunk;
524 	Flex_header::Flex_vers vers = !New_shapes() ? Flex_header::orig : Flex_header::exult_v2;
525 	bool v2 = vers == Flex_header::exult_v2;
526 	Flex_writer writer(ifix, "Exult",  count, vers);
527 	int scy = 16 * (schunk / 12); // Get abs. chunk coords.
528 	int scx = 16 * (schunk % 12);
529 	// Go through chunks.
530 	for (int cy = 0; cy < 16; cy++) {
531 		for (int cx = 0; cx < 16; cx++) {
532 			writer.write_object(get_chunk(scx + cx, scy + cy), v2);
533 		}
534 	}
535 	schunk_modified[schunk] = false;
536 }
537 
538 /*
539  *  Read in the objects for a superchunk from one of the "u7ifix" files.
540  */
541 
get_ifix_objects(int schunk)542 void Game_map::get_ifix_objects(
543     int schunk          // Superchunk # (0-143).
544 ) {
545 	char fname[128];        // Set up name.
546 	if (!is_system_path_defined("<PATCH>") ||
547 	        // First check for patch.
548 	        !U7exists(get_schunk_file_name(PATCH_U7IFIX, schunk, fname))) {
549 		get_schunk_file_name(U7IFIX, schunk, fname);
550 	}
551 	IFileDataSource ifix(fname);
552 	if (!ifix.good()) {
553 		if (!Game::is_editing())    // Ok if map-editing.
554 			cerr << "Ifix file '" << fname << "' not found." << endl;
555 		return;
556 	}
557 	FlexFile flex(fname);
558 	int vers = static_cast<int>(flex.get_vers());
559 	int scy = 16 * (schunk / 12); // Get abs. chunk coords.
560 	int scx = 16 * (schunk % 12);
561 	// Go through chunks.
562 	for (int cy = 0; cy < 16; cy++) {
563 		for (int cx = 0; cx < 16; cx++) {
564 			// Get to index entry for chunk.
565 			int chunk_num = cy * 16 + cx;
566 			size_t len;
567 			uint32 offset = flex.get_entry_info(chunk_num, len);
568 			if (len)
569 				get_ifix_chunk_objects(&ifix, vers, offset,
570 				                       len, scx + cx, scy + cy);
571 		}
572 	}
573 }
574 
575 /*
576  *  Get the objects from one ifix chunk entry onto the screen.
577  */
578 
get_ifix_chunk_objects(IDataSource * ifix,int vers,long filepos,int len,int cx,int cy)579 void Game_map::get_ifix_chunk_objects(
580     IDataSource *ifix,
581     int vers,           // Flex file vers.
582     long filepos,           // Offset in file.
583     int len,            // Length of data.
584     int cx, int cy          // Absolute chunk #'s.
585 ) {
586 	Game_object_shared obj;
587 	ifix->seek(filepos);        // Get to actual shape.
588 	// Get buffer to hold entries' indices.
589 	auto *entries = new unsigned char[len];
590 	unsigned char *ent = entries;   // Read them in.
591 	ifix->read(reinterpret_cast<char *>(entries), len);
592 	// Get object list for chunk.
593 	Map_chunk *olist = get_chunk(cx, cy);
594 	if (static_cast<Flex_header::Flex_vers>(vers) == Flex_header::orig) {
595 		int cnt = len / 4;
596 		for (int i = 0; i < cnt; i++, ent += 4) {
597 			int tx = (ent[0] >> 4) & 0xf;
598 			int ty = ent[0] & 0xf;
599 			int tz = ent[1] & 0xf;
600 			int shnum = ent[2] + 256 * (ent[3] & 3);
601 			int frnum = ent[3] >> 2;
602 			const Shape_info &info = ShapeID::get_info(shnum);
603 			obj = (info.is_animated() || info.has_sfx()) ?
604 			     std::make_shared<Animated_ifix_object>(shnum, frnum,
605 				  												tx, ty, tz)
606 			     : std::make_shared<Ifix_game_object>(shnum, frnum, tx, ty, tz);
607 			olist->add(obj.get());
608 		}
609 	} else if (static_cast<Flex_header::Flex_vers>(vers) == Flex_header::exult_v2) {
610 		// b0 = tx,ty, b1 = lift, b2-3 = shnum, b4=frnum
611 		int cnt = len / 5;
612 		for (int i = 0; i < cnt; i++, ent += 5) {
613 			int tx = (ent[0] >> 4) & 0xf;
614 			int ty = ent[0] & 0xf;
615 			int tz = ent[1] & 0xf;
616 			int shnum = ent[2] + 256 * ent[3];
617 			int frnum = ent[4];
618 			const Shape_info &info = ShapeID::get_info(shnum);
619 			obj = (info.is_animated() || info.has_sfx()) ?
620 			   std::make_shared<Animated_ifix_object>(shnum, frnum, tx, ty, tz)
621 			   : std::make_shared<Ifix_game_object>(shnum, frnum, tx, ty, tz);
622 			olist->add(obj.get());
623 		}
624 	} else
625 		assert(0);
626 	delete[] entries;       // Done with buffer.
627 	olist->setup_dungeon_levels();  // Should have all dungeon pieces now.
628 }
629 
630 /*
631  *  Constants for IREG files:
632  */
633 #define IREG_SPECIAL    255     // Precedes special entries.
634 #define IREG_UCSCRIPT   1       // Saved Usecode_script for object.
635 #define IREG_ENDMARK    2       // Just an 'end' mark.
636 #define IREG_ATTS   3       // Attribute/value pairs.
637 #define IREG_STRING 4       // A string; ie, function name.
638 
639 /*
640  *  Write out attributes for an object.
641  */
642 
write_attributes(ODataSource * ireg,vector<pair<const char *,int>> & attlist)643 void Game_map::write_attributes(
644     ODataSource *ireg,
645     vector<pair<const char *, int> > &attlist
646 ) {
647 	int len = 0;            // Figure total length.
648 	int i;
649 	int cnt = attlist.size();
650 	if (!cnt)
651 		return;
652 	for (i = 0; i < cnt; ++i) {
653 		const char *att = attlist[i].first;
654 		len += strlen(att) + 1 + 2; // Name, nullptr, val.
655 	}
656 	ireg->write1(IREG_SPECIAL);
657 	ireg->write1(IREG_ATTS);
658 	ireg->write2(len);
659 	for (i = 0; i < cnt; ++i) {
660 		const char *att = attlist[i].first;
661 		int val = attlist[i].second;
662 		ireg->write(att, strlen(att) + 1);
663 		ireg->write2(val);
664 	}
665 }
666 
667 /*
668  *  Write out scheduled usecode for an object.
669  */
670 
write_scheduled(ODataSource * ireg,Game_object * obj,bool write_mark)671 void Game_map::write_scheduled(
672     ODataSource *ireg,
673     Game_object *obj,
674     bool write_mark         // Write an IREG_ENDMARK if true.
675 ) {
676 	for (Usecode_script *scr = Usecode_script::find(obj); scr;
677 	        scr = Usecode_script::find(obj, scr)) {
678 		ostringstream outbuf(ios::out);
679 		OStreamDataSource nbuf(&outbuf);
680 		int len = scr->save(&nbuf);
681 		if (len < 0)
682 			cerr << "Error saving Usecode script" << endl;
683 		else if (len > 0) {
684 			ireg->write1(IREG_SPECIAL);
685 			ireg->write1(IREG_UCSCRIPT);
686 			ireg->write2(len);  // Store length.
687 			ireg->write(outbuf.str());
688 		}
689 	}
690 	if (write_mark) {
691 		ireg->write1(IREG_SPECIAL);
692 		ireg->write1(IREG_ENDMARK);
693 	}
694 }
695 
696 /*
697  *  Write string entry and/or return length of what's written.
698  */
write_string(ODataSource * ireg,const char * str)699 int Game_map::write_string(
700     ODataSource *ireg,       // Null if we just want length.
701     const char *str
702 ) {
703 	int len = 1 + strlen(str);
704 	if (ireg) {
705 		ireg->write1(IREG_SPECIAL);
706 		ireg->write1(IREG_STRING);
707 		ireg->write2(len);
708 		ireg->write(str, len);
709 	}
710 	return len + 4;
711 }
712 
713 /*
714  *  Write modified 'u7ireg' files.
715  */
716 
write_ireg()717 void Game_map::write_ireg(
718 ) {
719 	// Write each superchunk to Iregxx.
720 	for (int schunk = 0; schunk < c_num_schunks * c_num_schunks; schunk++)
721 		// Only write what we've read.
722 		if (schunk_cache[schunk] && schunk_cache_sizes[schunk] >= 0) {
723 			// It's loaded in a memory buffer
724 			char fname[128];        // Set up name.
725 			ofstream ireg_stream;
726 			U7open(ireg_stream, get_schunk_file_name(U7IREG, schunk, fname));
727 			ireg_stream.write(schunk_cache[schunk], schunk_cache_sizes[schunk]);
728 		} else if (schunk_read[schunk]) {
729 			// It's active
730 			write_ireg_objects(schunk);
731 		}
732 }
733 
734 /*
735  *  Write out one of the "u7ireg" files.
736  *
737  *  Output: 0 if error, which is reported.
738  */
739 
write_ireg_objects(int schunk)740 void Game_map::write_ireg_objects(
741     int schunk          // Superchunk # (0-143).
742 ) {
743 	char fname[128];        // Set up name.
744 	OFileDataSource ireg(get_schunk_file_name(U7IREG, schunk, fname));
745 	write_ireg_objects(schunk, &ireg);
746 	ireg.flush();
747 }
748 
749 
750 /*
751  *  Write out one of the "u7ireg" files.
752  *
753  *  Output: 0 if error, which is reported.
754  */
755 
write_ireg_objects(int schunk,ODataSource * ireg)756 void Game_map::write_ireg_objects(
757     int schunk,         // Superchunk # (0-143).
758     ODataSource *ireg
759 ) {
760 	int scy = 16 * (schunk / 12); // Get abs. chunk coords.
761 	int scx = 16 * (schunk % 12);
762 	// Go through chunks.
763 	for (int cy = 0; cy < 16; cy++)
764 		for (int cx = 0; cx < 16; cx++) {
765 			Map_chunk *chunk = get_chunk(scx + cx,
766 			                             scy + cy);
767 			Game_object *obj;
768 			// Restore original order (sort of).
769 			Object_iterator_backwards next(chunk);
770 			while ((obj = next.get_next()) != nullptr)
771 				obj->write_ireg(ireg);
772 			ireg->write2(0);// End with 2 0's.
773 		}
774 }
775 
776 /*
777  *  Read in the objects for a superchunk from one of the "u7ireg" files.
778  *  (These are the moveable objects.)
779  */
780 
get_ireg_objects(int schunk)781 void Game_map::get_ireg_objects(
782     int schunk          // Superchunk # (0-143).
783 ) {
784 	char fname[128];        // Set up name.
785 	std::unique_ptr<IDataSource> ireg;
786 
787 	if (schunk_cache[schunk] && schunk_cache_sizes[schunk] >= 0) {
788 		// No items
789 		if (schunk_cache_sizes[schunk] == 0) {
790 			return;
791 		}
792 		ireg = std::make_unique<IBufferDataView>(schunk_cache[schunk], schunk_cache_sizes[schunk]);
793 #ifdef DEBUG
794 		std::cout << "Reading " << get_schunk_file_name(U7IREG, schunk, fname) << " from memory" << std::endl;
795 #endif
796 	} else {
797 		ireg = std::make_unique<IFileDataSource>(get_schunk_file_name(U7IREG, schunk, fname));
798 		if (!ireg->good()) {
799 			return;         // Just don't show them.
800 		}
801 	}
802 	int scy = 16 * (schunk / 12); // Get abs. chunk coords.
803 	int scx = 16 * (schunk % 12);
804 	read_ireg_objects(ireg.get(), scx, scy);
805 	// A fixup:
806 	if (schunk == 10 * 12 + 11 && Game::get_game_type() == SERPENT_ISLE) {
807 		// Lever in SilverSeed:
808 		Game_object_vector vec;
809 		if (Game_object::find_nearby(vec, Tile_coord(2936, 2726, 0),
810 		                             787, 0, 0, c_any_qual, 5))
811 			vec[0]->move(2937, 2727, 2);
812 	}
813 	if (schunk_cache[schunk]) {
814 		delete [] schunk_cache[schunk];
815 		schunk_cache[schunk] = nullptr;
816 		schunk_cache_sizes[schunk] = -1;
817 	}
818 }
819 
820 /*
821  *  Read in a 'special' IREG entry (one starting with 255).
822  */
823 
Read_special_ireg(IDataSource * ireg,Game_object * obj)824 void Read_special_ireg(
825     IDataSource *ireg,
826     Game_object *obj        // Last object read.
827 ) {
828 	int type = ireg->read1();       // Get type.
829 	int len = ireg->read2();        // Length of rest.
830 	auto *buf = new unsigned char[len];
831 	ireg->read(reinterpret_cast<char *>(buf), len);
832 	if (type == IREG_UCSCRIPT) { // Usecode script?
833 		IBufferDataView nbuf(buf, len);
834 		Usecode_script *scr = Usecode_script::restore(obj, &nbuf);
835 		if (scr) {
836 			scr->start(scr->get_delay());
837 		}
838 	} else if (type == IREG_ATTS) // Attribute/value pairs?
839 		obj->read_attributes(buf, len);
840 	else if (type == IREG_STRING) { // IE, Usecode egg function name?
841 		if (obj->is_egg())
842 			static_cast<Egg_object *>(obj)->set_str1(
843 			    reinterpret_cast<char *>(buf));
844 	} else {
845 		cerr << "Unknown special IREG entry: " << type << endl;
846 	}
847 	delete [] buf;
848 }
849 
850 /*
851  *  Read in a 'special' IREG entry (one starting with 255).
852  */
853 
read_special_ireg(IDataSource * ireg,Game_object * obj)854 void Game_map::read_special_ireg(
855     IDataSource *ireg,
856     Game_object *obj        // Last object read.
857 ) {
858 	while (ireg->peek() == IREG_SPECIAL && !ireg->eof()) {
859 		ireg->read1();      // Eat the IREG_SPECIAL.
860 		unsigned char type = ireg->peek();
861 		if (type == IREG_ENDMARK) {
862 			// End of list.
863 			ireg->read1();
864 			return;
865 		}
866 		Read_special_ireg(ireg, obj);
867 	}
868 }
869 
870 /*
871  *  Containers and items classed as 'quality_flags' have a byte of flags.
872  *  This routine returns them converted into Object_flags.
873  */
Get_quality_flags(unsigned char qualbyte)874 inline unsigned long Get_quality_flags(
875     unsigned char qualbyte      // Quality byte containing flags.
876 ) {
877 	return ((qualbyte & 1) << Obj_flags::invisible) |
878 	       (((qualbyte >> 3) & 1) << Obj_flags::okay_to_take);
879 }
880 
881 /*
882  *  Read a list of ireg objects.  They are either placed in the desired
883  *  game chunk, or added to their container.
884  */
885 
read_ireg_objects(IDataSource * ireg,int scx,int scy,Game_object * container,unsigned long flags)886 void Game_map::read_ireg_objects(
887     IDataSource *ireg,           // File to read from.
888     int scx, int scy,       // Abs. chunk coords. of superchunk.
889     Game_object *container,     // Container, or null.
890     unsigned long flags     // Usecode item flags.
891 ) {
892 	unsigned char entbuf[20];
893 	int entlen;         // Gets entry length.
894 	sint8 index_id = -1;
895 	Game_object *last_obj = nullptr;  // Last one read in this call.
896 	Game_window *gwin = Game_window::get_instance();
897 	// Go through entries.
898 	for (entlen = ireg->read1(); !ireg->eof(); entlen = ireg->read1()) {
899 		int extended = 0;   // 1 for 2-byte shape #'s.
900 		bool extended_lift = false;
901 
902 		// Skip 0's & ends of containers.
903 
904 		if (!entlen || entlen == 1) {
905 			if (container)
906 				return; // Skip 0's & ends of containers.
907 			else
908 				continue;
909 		}
910 		// Detect the 2 byte index id
911 		else if (entlen == 2) {
912 			index_id = static_cast<sint8>(ireg->read2());
913 			continue;
914 		} else if (entlen == IREG_SPECIAL) {
915 			Read_special_ireg(ireg, last_obj);
916 			continue;
917 		} else if (entlen == IREG_EXTENDED || entlen == IREG_EXTENDED2) {
918 			if (entlen == IREG_EXTENDED) {
919 				extended = 1;
920 			}
921 			extended_lift = true;
922 			entlen = ireg->read1();
923 		}
924 		// Get copy of flags.
925 		unsigned long oflags = flags & ~(1 << Obj_flags::is_temporary);
926 		int testlen = entlen - extended;
927 		if (testlen != 6 && testlen != 10 && testlen != 12 &&
928 		        testlen != 13 && testlen != 14 && testlen != 18) {
929 			long pos = ireg->getPos();
930 			cout << "Unknown entlen " << testlen << " at pos. " <<
931 			     pos << endl;
932 			ireg->seek(pos + entlen);
933 			continue;   // Only know these two types.
934 		}
935 		unsigned char *entry = &entbuf[0];  // Get entry.
936 		ireg->read(reinterpret_cast<char *>(entry), entlen);
937 		int cx = entry[0] >> 4; // Get chunk indices within schunk.
938 		int cy = entry[1] >> 4;
939 		// Get coord. #'s where shape goes.
940 		int tilex;
941 		int tiley;
942 		if (container) {    // In container?  Get gump coords.
943 			tilex = entry[0];
944 			tiley = entry[1];
945 		} else {
946 			tilex = entry[0] & 0xf;
947 			tiley = entry[1] & 0xf;
948 		}
949 		int shnum;
950 		int frnum;   // Get shape #, frame #.
951 		if (extended) {
952 			shnum = entry[2] + 256 * entry[3];
953 			frnum = entry[4];
954 			++entry;    // So the rest is in the right place.
955 		} else {
956 			shnum = entry[2] + 256 * (entry[3] & 3);
957 			frnum = entry[3] >> 2;
958 		}
959 		const Shape_info &info = ShapeID::get_info(shnum);
960 		unsigned int quality;
961 		Ireg_game_object_shared obj;
962 		int is_egg = 0;     // Fields are eggs.
963 
964 		// Has flag byte(s)
965 		if (testlen == 10) {
966 			// Temporary
967 			if (entry[6] & 1) oflags |= 1 << Obj_flags::is_temporary;
968 		}
969 
970 		auto read_lift = [extended_lift](unsigned char val) {
971 			unsigned int lift = nibble_swap(val);
972 			if (extended_lift) {
973 				return lift;
974 			}
975 			return lift & 0xfU;
976 		};
977 		// An "egg"?
978 		if (info.get_shape_class() == Shape_info::hatchable) {
979 			bool anim = info.is_animated() || info.has_sfx();
980 			const unsigned int lift = read_lift(entry[9]);
981 			Egg_object_shared egg = Egg_object::create_egg(entry, entlen,
982 			                  anim, shnum, frnum, tilex, tiley, lift);
983 			get_chunk(scx + cx, scy + cy)->add_egg(egg.get());
984 			last_obj = egg.get();
985 			continue;
986 		} else if (testlen == 6 || testlen == 10) { // Simple entry?
987 			const unsigned int lift = read_lift(entry[4]);
988 			quality = entry[5];
989 			obj = create_ireg_object(info, shnum, frnum,
990 			                         tilex, tiley, lift);
991 			is_egg = obj->is_egg();
992 
993 			// Wierd use of flag:
994 			if (info.has_quantity()) {
995 				if (quality & 0x80) {
996 					oflags |= (1 << Obj_flags::okay_to_take);
997 					quality &= 0x7f;
998 				} else
999 					oflags &= ~(1 << Obj_flags::okay_to_take);
1000 			} else if (info.has_quality_flags()) {
1001 				// Use those flags instead of deflt.
1002 				oflags = Get_quality_flags(quality);
1003 				quality = 0;
1004 			}
1005 		} else if (info.is_body_shape()) {
1006 			// NPC's body.
1007 			int extbody = testlen == 13 ? 1 : 0;
1008 			const unsigned int type = entry[4] + 256 * entry[5];
1009 			const unsigned int lift = read_lift(entry[9 + extbody]);
1010 			quality = entry[7];
1011 			oflags =    // Override flags (I think).
1012 			    Get_quality_flags(entry[11 + extbody]);
1013 			int npc_num;
1014 			if (quality == 1 && (extbody || (entry[8] >= 0x80 ||
1015 			                                 Game::get_game_type() == SERPENT_ISLE)))
1016 				npc_num = extbody ? (entry[8] + 256 * entry[9]) :
1017 				          ((entry[8] - 0x80) & 0xFF);
1018 			else
1019 				npc_num = -1;
1020 			if (!npc_num)   // Avatar has no body.
1021 				npc_num = -1;
1022 			Dead_body_shared b = std::make_shared<Dead_body>(shnum, frnum,
1023 			                             tilex, tiley, lift, npc_num);
1024 			obj = b;
1025 			if (npc_num > 0)
1026 				gwin->set_body(npc_num, b.get());
1027 			if (type) { // (0 if empty.)
1028 				// Don't pass along invisibility!
1029 				read_ireg_objects(ireg, scx, scy, obj.get(),
1030 				                  oflags & ~(1 << Obj_flags::invisible));
1031 				obj->elements_read();
1032 			}
1033 		} else if (testlen == 12) { // Container?
1034 			unsigned int type = entry[4] + 256 * entry[5];
1035 			const unsigned int lift = read_lift(entry[9]);
1036 			quality = entry[7];
1037 			oflags =    // Override flags (I think).
1038 			    Get_quality_flags(entry[11]);
1039 			if (info.get_shape_class() == Shape_info::virtue_stone) {
1040 				// Virtue stone?
1041 				std::shared_ptr<Virtue_stone_object> v =
1042 				    std::make_shared<Virtue_stone_object>(shnum, frnum, tilex,
1043 				                            tiley, lift);
1044 				v->set_target_pos(entry[4], entry[5], entry[6],
1045 				                  entry[7]);
1046 				v->set_target_map(entry[10]);
1047 				obj = v;
1048 				type = 0;
1049 			} else if (info.get_shape_class() == Shape_info::barge) {
1050 				std::shared_ptr<Barge_object> b =
1051 					std::make_shared<Barge_object>(
1052 				        shnum, frnum, tilex, tiley, lift,
1053 				    	entry[4], entry[5],
1054 				    	(quality >> 1) & 3);
1055 				obj = b;
1056 				if (!gwin->get_moving_barge() &&
1057 				        (quality & (1 << 3)))
1058 					gwin->set_moving_barge(b.get());
1059 			} else if (info.is_jawbone()) { // serpent jawbone
1060 				obj = std::make_shared<Jawbone_object>(shnum, frnum,
1061 				                         tilex, tiley, lift, entry[10]);
1062 			} else
1063 				obj = std::make_shared<Container_game_object>(
1064 				    shnum, frnum, tilex, tiley, lift,
1065 				    entry[10]);
1066 			// Read container's objects.
1067 			if (type) { // (0 if empty.)
1068 				// Don't pass along invisibility!
1069 				read_ireg_objects(ireg, scx, scy, obj.get(),
1070 				                  oflags & ~(1 << Obj_flags::invisible));
1071 				obj->elements_read();
1072 			}
1073 		} else if (info.get_shape_class() == Shape_info::spellbook) {
1074 			// Length 18 means it's a spellbook.
1075 			// Get all 9 spell bytes.
1076 			quality = 0;
1077 			unsigned char circles[9];
1078 			memcpy(&circles[0], &entry[4], 5);
1079 			const unsigned int lift = read_lift(entry[9]);
1080 			memcpy(&circles[5], &entry[10], 4);
1081 			uint8 *ptr = &entry[14];
1082 			// 3 unknowns, then bookmark.
1083 			unsigned char bmark = ptr[3];
1084 			obj = std::make_shared<Spellbook_object>(
1085 			    shnum, frnum, tilex, tiley, lift,
1086 			    &circles[0], bmark);
1087 		} else {
1088 			// Just to shut up spurious warnings by compilers and static
1089 			// analyzers.
1090 			boost::io::ios_flags_saver sflags(cerr);
1091 			boost::io::ios_fill_saver sfill(cerr);
1092 			std::cerr << "Error: Invalid IREG entry on chunk (" << scx << ", "
1093 			          << scy << "): extended = " << extended
1094 			          << ", extended_lift = " << extended_lift << ", entlen = "
1095 			          << entlen << ", shnum = " << shnum << ", frnum = "
1096 			          << frnum << std::endl;
1097 			std::cerr << "Entry data:" << std::hex;
1098 			std::cerr << std::setfill('0');
1099 			for (int i = 0; i < entlen; i++)
1100 				std::cerr << " " << std::setw(2)
1101 				          << static_cast<int>(entry[i]);
1102 			std::cerr << std::endl;
1103 			continue;
1104 		}
1105 		obj->set_quality(quality);
1106 		obj->set_flags(oflags);
1107 		last_obj = obj.get();     // Save as last read.
1108 		// Add, but skip volume check.
1109 		if (container) {
1110 			if (index_id != -1 &&
1111 			        container->add_readied(obj.get(), index_id, true, true))
1112 				continue;
1113 			else if (container->add(obj.get(), true))
1114 				continue;
1115 			else        // Fix tx, ty.
1116 				obj->set_shape_pos(obj->get_tx() & 0xf,
1117 				                   obj->get_ty() & 0xf);
1118 		}
1119 		Map_chunk *chunk = get_chunk(scx + cx, scy + cy);
1120 		if (is_egg)
1121 			chunk->add_egg(obj->as_egg());
1122 		else
1123 			chunk->add(obj.get());
1124 	}
1125 }
1126 
1127 /*
1128  *  Create non-container IREG objects.
1129  */
1130 
create_ireg_object(const Shape_info & info,int shnum,int frnum,int tilex,int tiley,int lift)1131 Ireg_game_object_shared Game_map::create_ireg_object(
1132     const Shape_info &info,       // Info. about shape.
1133     int shnum, int frnum,       // Shape, frame.
1134     int tilex, int tiley,       // Tile within chunk.
1135     int lift            // Desired lift.
1136 ) {
1137     Ireg_game_object_shared newobj;
1138 	// (These are all animated.)
1139 	if (info.is_field() && info.get_field_type() >= 0)
1140 		newobj = std::make_shared<Field_object>(shnum, frnum, tilex, tiley,
1141 		               lift, Egg_object::fire_field + info.get_field_type());
1142 	else if (info.is_animated() || info.has_sfx())
1143 		newobj = std::make_shared<Animated_ireg_object>(
1144 		           shnum, frnum, tilex, tiley, lift);
1145 	else if (shnum == 607)      // Path.
1146 		newobj = std::make_shared<Egglike_game_object>(
1147 		           shnum, frnum, tilex, tiley, lift);
1148 	else if (info.is_mirror())  // Mirror
1149 		newobj = std::make_shared<Mirror_object>(shnum, frnum,
1150 			   	 									   tilex, tiley, lift);
1151 	else if (info.is_body_shape())
1152 		newobj = std::make_shared<Dead_body>(shnum, frnum,
1153 			   	 								   	tilex, tiley, lift, -1);
1154 	else if (info.get_shape_class() == Shape_info::virtue_stone)
1155 		newobj = std::make_shared<Virtue_stone_object>(
1156 		           shnum, frnum, tilex, tiley, lift);
1157 	else if (info.get_shape_class() == Shape_info::spellbook) {
1158 		static unsigned char circles[9] = {0};
1159 		newobj = std::make_shared<Spellbook_object>(
1160 		           shnum, frnum, tilex, tiley, lift,
1161 		           &circles[0], 0);
1162 	} else if (info.get_shape_class() == Shape_info::barge) {
1163 		newobj = std::make_shared<Barge_object>(
1164 				    shnum, frnum, tilex, tiley, lift,
1165 					// FOR NOW: 8x16 tiles, North.
1166 				    8, 16, 0);
1167 	} else if (info.get_shape_class() == Shape_info::container) {
1168 		if (info.is_jawbone())
1169 			newobj = std::make_shared<Jawbone_object>(shnum, frnum,
1170 				   	 								tilex, tiley, lift);
1171 		else
1172 			newobj = std::make_shared<Container_game_object>(shnum, frnum,
1173 			                                 tilex, tiley, lift);
1174 	} else {
1175 	    newobj = std::make_shared<Ireg_game_object>(shnum, frnum,
1176 														  tilex, tiley, lift);
1177 	}
1178     return newobj;
1179 }
1180 
1181 /*
1182  *  Create non-container IREG objects.
1183  */
1184 
create_ireg_object(int shnum,int frnum)1185 Ireg_game_object_shared Game_map::create_ireg_object(
1186     int shnum, int frnum        // Shape, frame.
1187 ) {
1188 	return create_ireg_object(ShapeID::get_info(shnum),
1189 	                          shnum, frnum, 0, 0, 0);
1190 }
1191 
1192 /*
1193  *  Create 'fixed' (landscape, building) objects.
1194  */
1195 
create_ifix_object(int shnum,int frnum)1196 Ifix_game_object_shared Game_map::create_ifix_object(
1197     int shnum, int frnum        // Shape, frame.
1198 ) {
1199 	const Shape_info &info = ShapeID::get_info(shnum);
1200 	return (info.is_animated() || info.has_sfx())
1201 	       ? std::make_shared<Animated_ifix_object>(shnum, frnum, 0, 0, 0)
1202 	       : std::make_shared<Ifix_game_object>(shnum, frnum, 0, 0, 0);
1203 }
1204 
1205 /*
1206  *  Read in the objects in a superchunk.
1207  */
1208 
get_superchunk_objects(int schunk)1209 void Game_map::get_superchunk_objects(
1210     int schunk          // Superchunk #.
1211 ) {
1212 	get_map_objects(schunk);    // Get map objects/scenery.
1213 	get_ifix_objects(schunk);   // Get objects from ifix.
1214 	get_ireg_objects(schunk);   // Get moveable objects.
1215 	schunk_read[schunk] = true;    // Done this one now.
1216 	map_patches->apply(schunk); // Move/delete objects.
1217 }
1218 
1219 /*
1220  *  Just see if a tile is occupied by something.
1221  */
1222 
is_tile_occupied(Tile_coord const & tile)1223 bool Game_map::is_tile_occupied(
1224     Tile_coord const &tile
1225 ) {
1226 	Map_chunk *chunk = get_chunk_safely(
1227 	                       tile.tx / c_tiles_per_chunk, tile.ty / c_tiles_per_chunk);
1228 	if (!chunk)         // Outside the world?
1229 		return false;       // Then it's not blocked.
1230 	return chunk->is_tile_occupied(tile.tx % c_tiles_per_chunk,
1231 	                               tile.ty % c_tiles_per_chunk, tile.tz);
1232 }
1233 
1234 /*
1235  *  Locate a chunk with a given terrain # and center the view on it.
1236  *
1237  *  Output: true if found, else 0.
1238  */
1239 
locate_terrain(int tnum,int & cx,int & cy,bool upwards)1240 bool Game_map::locate_terrain(
1241     int tnum,           // # in u7chunks.
1242     int &cx, int &cy,       // Chunk to start at, or (-1,-1).
1243     //   Updated with chunk found.
1244     bool upwards            // If true, search upwards.
1245 ) {
1246 	int cnum;           // Chunk #, counting L-R, T-B.
1247 	int cstop;          // Stop when cnum == cstop.
1248 	int dir;
1249 	if (upwards) {
1250 		cstop = -1;
1251 		dir = -1;
1252 		if (cx == -1)       // Start at end?
1253 			cnum = c_num_chunks * c_num_chunks - 1;
1254 		else
1255 			cnum = cy * c_num_chunks + cx - 1;
1256 	} else {
1257 		cstop = c_num_chunks * c_num_chunks;
1258 		dir = 1;
1259 		cnum = (cx == -1) ? 0 : cy * c_num_chunks + cx + 1;
1260 	}
1261 	while (cnum != cstop) {
1262 		int chunky = cnum / c_num_chunks;
1263 		int chunkx = cnum % c_num_chunks;
1264 		if (terrain_map[chunkx][chunky] == tnum) {
1265 			// Return chunk # found.
1266 			cx = chunkx;
1267 			cy = chunky;
1268 			// Center window over chunk found.
1269 			Game_window::get_instance()->center_view(Tile_coord(
1270 			            cx * c_tiles_per_chunk + c_tiles_per_chunk / 2,
1271 			            cy * c_tiles_per_chunk + c_tiles_per_chunk / 2,
1272 			            0));
1273 			return true;
1274 		}
1275 		cnum += dir;
1276 	}
1277 	return false;           // Failed.
1278 }
1279 
1280 /*
1281  *  Swap two adjacent terrain #'s, keeping the map looking the same.
1282  *
1283  *  Output: false if unsuccessful.
1284  */
1285 
swap_terrains(int tnum)1286 bool Game_map::swap_terrains(
1287     int tnum            // Swap tnum and tnum + 1.
1288 ) {
1289 	if (tnum < 0 || static_cast<unsigned>(tnum) >= chunk_terrains->size() - 1)
1290 		return false;       // Out of bounds.
1291 	// Swap in list.
1292 	Chunk_terrain *tmp = get_terrain(tnum);
1293 	tmp->set_modified();
1294 	(*chunk_terrains)[tnum] = get_terrain(tnum + 1);
1295 	(*chunk_terrains)[tnum]->set_modified();
1296 	(*chunk_terrains)[tnum + 1] = tmp;
1297 	chunk_terrains_modified = true;
1298 	// Update terrain maps.
1299 	Game_window *gwin = Game_window::get_instance();
1300 	const vector<Game_map *> &maps = gwin->get_maps();
1301 	for (auto *map : maps) {
1302 		if (!map)
1303 			continue;
1304 		for (auto& row : map->terrain_map) {
1305 			for (short& terrain : row) {
1306 				if (terrain == tnum)
1307 					terrain++;
1308 				else if (terrain == tnum + 1)
1309 					terrain--;
1310 			}
1311 		}
1312 		map->map_modified = true;
1313 	}
1314 	gwin->set_all_dirty();
1315 	return true;
1316 }
1317 
1318 /*
1319  *  Insert a new terrain after a given one, and push all the others up
1320  *  so the map looks the same.  The new terrain is filled with
1321  *  (shape, frame) == (0, 0) unless 'dup' is passed 'true'.
1322  *
1323  *  Output: False if unsuccessful.
1324  */
1325 
insert_terrain(int tnum,bool dup)1326 bool Game_map::insert_terrain(
1327     int tnum,           // Insert after this one (may be -1).
1328     bool dup            // If true, duplicate #tnum.
1329 ) {
1330 	const int ntiles = c_tiles_per_chunk * c_tiles_per_chunk;
1331 	const int nbytes = v2_chunks ? 3 : 2;
1332 	if (tnum < -1 || tnum >= static_cast<int>(chunk_terrains->size()))
1333 		return false;       // Invalid #.
1334 	get_all_terrain();      // Need all of 'u7chunks' read in.
1335 	unsigned char buf[ntiles * 3];  // Set up buffer with shape #'s.
1336 	if (dup && tnum >= 0) {
1337 		// Want to duplicate given terrain.
1338 		Chunk_terrain *ter = (*chunk_terrains)[tnum];
1339 		unsigned char *data = &buf[0];
1340 		for (int ty = 0; ty < c_tiles_per_chunk; ty++)
1341 			for (int tx = 0; tx < c_tiles_per_chunk; tx++) {
1342 				ShapeID id = ter->get_flat(tx, ty);
1343 				int shnum = id.get_shapenum();
1344 				int frnum = id.get_framenum();
1345 				if (v2_chunks) {
1346 					*data++ = shnum & 0xff;
1347 					*data++ = (shnum >> 8) & 0xff;
1348 					*data++ = frnum;
1349 				} else {
1350 					*data++ = id.get_shapenum() & 0xff;
1351 					*data++ = ((id.get_shapenum() >> 8) & 3) |
1352 					          (id.get_framenum() << 2);
1353 				}
1354 			}
1355 	} else
1356 		std::fill_n(buf, ntiles * nbytes, 0);
1357 	auto *new_terrain = new Chunk_terrain(&buf[0], v2_chunks);
1358 	// Insert in list.
1359 	chunk_terrains->insert(chunk_terrains->begin() + tnum + 1, new_terrain);
1360 	// Indicate terrains are modified.
1361 	int num_chunk_terrains = chunk_terrains->size();
1362 	for (int i = tnum + 1; i < num_chunk_terrains; i++)
1363 		(*chunk_terrains)[i]->set_modified();
1364 	chunk_terrains_modified = true;
1365 	if (tnum + 1 == num_chunk_terrains - 1)
1366 		return true;        // Inserted at end of list.
1367 	// Update terrain map.
1368 	Game_window *gwin = Game_window::get_instance();
1369 	const vector<Game_map *> &maps = gwin->get_maps();
1370 	for (auto *map : maps) {
1371 		if (!map)
1372 			continue;
1373 		for (auto& row : map->terrain_map) {
1374 			for (short& terrain : row) {
1375 				if (terrain > tnum)
1376 					terrain++;
1377 			}
1378 		}
1379 		map->map_modified = true;
1380 	}
1381 	gwin->set_all_dirty();
1382 	return true;
1383 }
1384 
1385 /*
1386  *  Remove a terrain, updating the map.
1387  *
1388  *  Output: false if unsuccessful.
1389  */
1390 
delete_terrain(int tnum)1391 bool Game_map::delete_terrain(
1392     int tnum
1393 ) {
1394 	if (tnum < 0 || static_cast<unsigned>(tnum) >= chunk_terrains->size())
1395 		return false;       // Out of bounds.
1396 	int sz = chunk_terrains->size();
1397 	delete (*chunk_terrains)[tnum];
1398 	for (int i = tnum + 1; i < sz; i++) {
1399 		// Move the rest downwards.
1400 		Chunk_terrain *tmp = get_terrain(i);
1401 		tmp->set_modified();
1402 		(*chunk_terrains)[i - 1] = tmp;
1403 	}
1404 	chunk_terrains->resize(sz - 1);
1405 	chunk_terrains_modified = true;
1406 	// Update terrain map.
1407 	Game_window *gwin = Game_window::get_instance();
1408 	const vector<Game_map *> &maps = gwin->get_maps();
1409 	for (auto *map : maps) {
1410 		if (!map)
1411 			continue;
1412 		for (auto& row : map->terrain_map) {
1413 			for (short& terrain : row) {
1414 				if (terrain >= tnum)
1415 					terrain--;
1416 			}
1417 		}
1418 		map->map_modified = true;
1419 	}
1420 	gwin->set_all_dirty();
1421 	return true;
1422 }
1423 
1424 /*
1425  *  Commit edits made to terrain chunks.
1426  */
1427 
commit_terrain_edits()1428 void Game_map::commit_terrain_edits(
1429 ) {
1430 	int num_terrains = chunk_terrains->size();
1431 	// Create list of flags.
1432 	auto *ters = new unsigned char[num_terrains]{};
1433 	// Commit edits.
1434 	for (int i = 0; i < num_terrains; i++)
1435 		if ((*chunk_terrains)[i] &&
1436 		        (*chunk_terrains)[i]->commit_edits())
1437 			ters[i] = 1;
1438 	// Update terrain map.
1439 	Game_window *gwin = Game_window::get_instance();
1440 	const vector<Game_map *> &maps = gwin->get_maps();
1441 	for (auto *map : maps) {
1442 		if (!map)
1443 			continue;
1444 		for (int cy = 0; cy < c_num_chunks; cy++)
1445 			for (int cx = 0; cx < c_num_chunks; cx++) {
1446 				Map_chunk *chunk = map->get_chunk_unsafe(cx, cy);
1447 				if (chunk && ters[map->terrain_map[cx][cy]]
1448 				        != 0 && chunk->get_terrain())
1449 					// Reload objects.
1450 					chunk->set_terrain(
1451 					    chunk->get_terrain());
1452 			}
1453 	}
1454 	delete [] ters;
1455 }
1456 
1457 /*
1458  *  Abort edits made to terrain chunks.
1459  */
1460 
abort_terrain_edits()1461 void Game_map::abort_terrain_edits(
1462 ) {
1463 	int num_terrains = chunk_terrains->size();
1464 	// Abort edits.
1465 	for (int i = 0; i < num_terrains; i++)
1466 		if ((*chunk_terrains)[i])
1467 			(*chunk_terrains)[i]->abort_edits();
1468 }
1469 
1470 /*
1471  *  Find all unused shapes in game.  This can take a while!!
1472  */
1473 
find_unused_shapes(unsigned char * found,int foundlen)1474 void Game_map::find_unused_shapes(
1475     unsigned char *found,       // Bits set for shapes found.
1476     int foundlen            // # bytes.
1477 ) {
1478 	std::fill_n(found, foundlen, 0);
1479 	Game_window *gwin = Game_window::get_instance();
1480 	Shape_manager *sman = Shape_manager::get_instance();
1481 	cout << "Reading all chunks";
1482 	// Read in EVERYTHING!
1483 	for (int sc = 0; sc < c_num_schunks * c_num_schunks; sc++) {
1484 		cout << '.';
1485 		cout.flush();
1486 		char msg[80];
1487 		snprintf(msg, sizeof(msg), "Scanning superchunk %d", sc);
1488 		gwin->get_effects()->center_text(msg);
1489 		gwin->paint();
1490 		gwin->show();
1491 		if (!schunk_read[sc])
1492 			get_superchunk_objects(sc);
1493 	}
1494 	cout << endl;
1495 	int maxbits = foundlen * 8; // Total #bits in 'found'.
1496 	int nshapes = sman->get_shapes().get_num_shapes();
1497 	if (maxbits > nshapes)
1498 		maxbits = nshapes;
1499 	// Go through chunks.
1500 	for (int cy = 0; cy < c_num_chunks; cy++)
1501 		for (int cx = 0; cx < c_num_chunks; cx++) {
1502 			Map_chunk *chunk = get_chunk_unchecked(cx, cy);
1503 			Recursive_object_iterator all(chunk->get_objects());
1504 			Game_object *obj;
1505 			while ((obj = all.get_next()) != nullptr) {
1506 				int shnum = obj->get_shapenum();
1507 				if (shnum >= 0 && shnum < maxbits)
1508 					found[shnum / 8] |= (1 << (shnum % 8));
1509 			}
1510 		}
1511 	int i;
1512 	for (i = 0; i < maxbits; i++) { // Add all possible monsters.
1513 		const Shape_info &info = ShapeID::get_info(i);
1514 		const Monster_info *minf = info.get_monster_info();
1515 		if (minf)
1516 			found[i / 8] |= (1 << (i % 8));
1517 		const Weapon_info *winf = info.get_weapon_info();
1518 		if (winf) {     // Get projectiles for weapons.
1519 			int proj = winf->get_projectile();
1520 			if (proj > 0 && proj < maxbits)
1521 				found[proj / 8] |= (1 << (proj % 8));
1522 		}
1523 	}
1524 	for (i = c_first_obj_shape; i < maxbits; i++) // Ignore flats (<0x96).
1525 		if (!(found[i / 8] & (1 << (i % 8))))
1526 			cout << "Shape " << i << " not found in game" << endl;
1527 }
1528 
1529 /*
1530  *  Look throughout the map for a given shape.  The search starts at
1531  *  the first currently-selected shape, if possible.
1532  *
1533  *  Output: ->object if found, else 0.
1534  */
1535 
locate_shape(int shapenum,bool upwards,Game_object * start,int frnum,int qual)1536 Game_object *Game_map::locate_shape(
1537     int shapenum,           // Desired shape.
1538     bool upwards,           // If true, search upwards.
1539     Game_object *start,     // Start here if !0.
1540     int frnum,          // Frame, or c_any_frame
1541     int qual            // Quality, or c_any_qual
1542 ) {
1543 	int cx = -1;
1544 	int cy = 0;        // Before chunk to search.
1545 	int dir = 1;            // Direction to increment.
1546 	int stop = c_num_chunks;
1547 	if (upwards) {
1548 		dir = -1;
1549 		stop = -1;
1550 		cx = c_num_chunks;  // Past last chunk.
1551 		cy = c_num_chunks - 1;
1552 	}
1553 	Game_object *obj = nullptr;
1554 	if (start) {        // Start here.
1555 		Game_object *owner = start->get_outermost();
1556 		cx = owner->get_cx();
1557 		cy = owner->get_cy();
1558 		if (upwards) {
1559 			Recursive_object_iterator_backwards next(start);
1560 			while ((obj = next.get_next()) != nullptr)
1561 				if (obj->get_shapenum() == shapenum &&
1562 				        (frnum == c_any_framenum ||
1563 				         obj->get_framenum() == frnum) &&
1564 				        (qual == c_any_qual ||
1565 				         obj->get_quality() == qual))
1566 					break;
1567 		} else {
1568 			Recursive_object_iterator next(start);
1569 			while ((obj = next.get_next()) != nullptr)
1570 				if (obj->get_shapenum() == shapenum &&
1571 				        (frnum == c_any_framenum ||
1572 				         obj->get_framenum() == frnum) &&
1573 				        (qual == c_any_qual ||
1574 				         obj->get_quality() == qual))
1575 					break;
1576 		}
1577 	}
1578 	while (!obj) {          // Not found yet?
1579 		cx += dir;      // Next chunk.
1580 		if (cx == stop) {   // Past (either) end?
1581 			cy += dir;
1582 			if (cy == stop)
1583 				break;  // All done.
1584 			cx -= dir * c_num_chunks;
1585 		}
1586 		Map_chunk *chunk = get_chunk(cx, cy);
1587 		// Make sure objs. are read.
1588 		ensure_chunk_read(cx, cy);
1589 		if (upwards) {
1590 			Recursive_object_iterator_backwards next(
1591 			    chunk->get_objects());
1592 			while ((obj = next.get_next()) != nullptr)
1593 				if (obj->get_shapenum() == shapenum &&
1594 				        (frnum == c_any_framenum ||
1595 				         obj->get_framenum() == frnum) &&
1596 				        (qual == c_any_qual ||
1597 				         obj->get_quality() == qual))
1598 					break;
1599 		} else {
1600 			Recursive_object_iterator next(chunk->get_objects());
1601 			while ((obj = next.get_next()) != nullptr)
1602 				if (obj->get_shapenum() == shapenum &&
1603 				        (frnum == c_any_framenum ||
1604 				         obj->get_framenum() == frnum) &&
1605 				        (qual == c_any_qual ||
1606 				         obj->get_quality() == qual))
1607 					break;
1608 		}
1609 	}
1610 	return obj;
1611 }
1612 
1613 /*
1614  *  Create a 192x192 map shape.
1615  */
1616 
create_minimap(Shape * minimaps,const unsigned char * chunk_pixels)1617 void Game_map::create_minimap(Shape *minimaps, const unsigned char *chunk_pixels) {
1618 	int cx;
1619 	int cy;
1620 	auto *pixels = new unsigned char[c_num_chunks * c_num_chunks];
1621 
1622 	for (cy = 0; cy < c_num_chunks; ++cy) {
1623 		int yoff = cy * c_num_chunks;
1624 		for (cx = 0; cx < c_num_chunks; ++cx) {
1625 			int chunk_num = terrain_map[cx][cy];
1626 			pixels[yoff + cx] = chunk_pixels[chunk_num];
1627 		}
1628 	}
1629 	if (num >= minimaps->get_num_frames())
1630 		minimaps->resize(num + 1);
1631 	minimaps->set_frame(std::make_unique<Shape_frame>(pixels,
1632 	                                     c_num_chunks, c_num_chunks, 0, 0, true), num);
1633 	delete [] pixels;
1634 }
1635 
1636 /*
1637  *  Create a 192x192 map shape for each map and write out
1638  *  "patch/minimaps.vga".
1639  *
1640  *  Output: false if failed.
1641  */
1642 
write_minimap()1643 bool Game_map::write_minimap() {
1644 	char msg[80];
1645 	// A pixel for each possible chunk.
1646 	int num_chunks = chunk_terrains->size();
1647 	auto chunk_pixels = std::make_unique<unsigned char[]>(num_chunks);
1648 	unsigned char *ptr = chunk_pixels.get();
1649 	Game_window *gwin = Game_window::get_instance();
1650 	Palette pal;
1651 	// Ensure that all terrain is loaded:
1652 	get_all_terrain();
1653 	pal.set(PALETTE_DAY, 100, false);
1654 	Effects_manager *eman = gwin->get_effects();
1655 	eman->center_text("Encoding chunks");
1656 	gwin->paint();
1657 	gwin->show();
1658 	for (auto *ter : *chunk_terrains) {
1659 		Image_buffer8 *ibuf = ter->get_rendered_flats();
1660 		unsigned char *terbits = ibuf->get_bits();
1661 		int w = ibuf->get_width();
1662 		int h = ibuf->get_height();
1663 		unsigned long r = 0;
1664 		unsigned long g = 0;
1665 		unsigned long b = 0;
1666 		for (int y = 0; y < h; ++y) {
1667 			for (int x = 0; x < w; ++x) {
1668 				r += pal.get_red(*terbits);
1669 				g += pal.get_green(*terbits);
1670 				b += pal.get_blue(*terbits);
1671 				++terbits;
1672 			}
1673 		}
1674 		r /= w * h;
1675 		b /= w * h;
1676 		g /= w * h;
1677 		*ptr++ = pal.find_color(r, g, b);
1678 	}
1679 	eman->remove_text_effects();
1680 	const vector<Game_map *> &maps = gwin->get_maps();
1681 	int nmaps = maps.size();
1682 	Shape shape;
1683 	for (int i = 0; i < nmaps; ++i) {
1684 		snprintf(msg, sizeof(msg), "Creating minimap %d", i);
1685 		eman->center_text(msg);
1686 		gwin->paint();
1687 		gwin->show();
1688 		maps[i]->create_minimap(&shape, chunk_pixels.get());
1689 		eman->remove_text_effects();
1690 	}
1691 	OFileDataSource mfile(PATCH_MINIMAPS);  // May throw exception.
1692 	Flex_writer writer(mfile, "Written by Exult", 1);
1693 	writer.write_object(shape);
1694 	gwin->set_all_dirty();
1695 	return true;
1696 }
1697 
1698 /*
1699  *  Do a cache out. (x, y) is the center.
1700  *  If x == -1, cache out whole map.
1701  */
1702 
cache_out(int cx,int cy)1703 void Game_map::cache_out(int cx, int cy) {
1704 	int sx = cx / c_chunks_per_schunk;
1705 	int sy = cy / c_chunks_per_schunk;
1706 	bool chunk_flags[12][12]{};
1707 
1708 #ifdef DEBUG
1709 	if (cx == -1)
1710 		std::cout << "Want to cache out entire map #" <<
1711 		          get_num() << std::endl;
1712 	else
1713 		std::cout << "Want to cache out around super chunk: " <<
1714 		          (sy * 12 + sx) << " = "  << sx << ", " << sy << std::endl;
1715 #endif
1716 
1717 	// We cache out all but the 9 directly around the pov
1718 	if (cx != -1 && cy != -1) {
1719 		chunk_flags[(sy + 11) % 12][(sx + 11) % 12] = true;
1720 		chunk_flags[(sy + 11) % 12][sx] = true;
1721 		chunk_flags[(sy + 11) % 12][(sx + 1) % 12] = true;
1722 
1723 		chunk_flags[sy][(sx + 11) % 12] = true;
1724 		chunk_flags[sy][sx] = true;
1725 		chunk_flags[sy][(sx + 1) % 12] = true;
1726 
1727 		chunk_flags[(sy + 1) % 12][(sx + 11) % 12] = true;
1728 		chunk_flags[(sy + 1) % 12][sx] = true;
1729 		chunk_flags[(sy + 1) % 12][(sx + 1) % 12] = true;
1730 	}
1731 	for (sy = 0; sy < 12; sy++) for (sx = 0; sx < 12; sx++) {
1732 		if (chunk_flags[sy][sx]) continue;
1733 
1734 		int schunk = sy * 12 + sx;
1735 		if (schunk_read[schunk] && !schunk_modified[schunk]) cache_out_schunk(schunk);
1736 	}
1737 }
1738 
cache_out_schunk(int schunk)1739 void Game_map::cache_out_schunk(int schunk) {
1740 	// Get abs. chunk coords.
1741 	const int scy = 16 * (schunk / 12);
1742 	const int scx = 16 * (schunk % 12);
1743 	int cy;
1744 	int cx;
1745 	bool save_map_modified = map_modified;
1746 	bool save_terrain_modified = chunk_terrains_modified;
1747 
1748 	if (schunk_modified[schunk])
1749 		return;         // NEVER cache out modified chunks.
1750 	// Our vectors
1751 	Game_object_vector removes;
1752 	Actor_vector actors;
1753 
1754 	int buf_size = 0;
1755 
1756 #ifdef DEBUG
1757 	std::cout << "Killing superchunk: " << schunk << std::endl;
1758 #endif
1759 	// Go through chunks and get all the items
1760 	for (cy = 0; cy < 16; cy++) {
1761 		for (cx = 0; cx < 16; cx++) {
1762 			int size = get_chunk_unsafe(scx + cx, scy + cy)->get_obj_actors(removes, actors);
1763 
1764 			if (size < 0) {
1765 #ifdef DEBUG
1766 				std::cerr << "Failed attempting to kill superchunk" << std::endl;
1767 #endif
1768 				return;
1769 			}
1770 			buf_size += size + 2;
1771 		}
1772 	}
1773 
1774 	schunk_read[schunk] = false;
1775 	++caching_out;
1776 
1777 #ifdef DEBUG
1778 	std::cout << "Buffer size of " << buf_size << " bytes required to store super chunk" << std::endl;
1779 #endif
1780 
1781 	// Clear old (this shouldn't happen)
1782 	if (schunk_cache[schunk]) {
1783 		delete [] schunk_cache[schunk];
1784 		schunk_cache[schunk] = nullptr;
1785 		schunk_cache_sizes[schunk] = -1;
1786 	}
1787 
1788 	// Create new
1789 	schunk_cache[schunk] = new char[buf_size];
1790 	schunk_cache_sizes[schunk] = buf_size;
1791 
1792 	OBufferDataSpan ds(schunk_cache[schunk], schunk_cache_sizes[schunk]);
1793 
1794 	write_ireg_objects(schunk, &ds);
1795 
1796 #ifdef DEBUG
1797 	std::cout << "Wrote " << ds.getPos() << " bytes" << std::endl;
1798 #endif
1799 
1800 	// Now remove the objects
1801 	for (auto *remove : removes) {
1802 		remove->delete_contents();
1803 		remove->remove_this();
1804 	}
1805 
1806 	// Now disable the actors
1807 	for (auto *actor : actors) {
1808 		actor->cache_out();
1809 	}
1810 
1811 	// Go through chunks and finish up
1812 	for (cy = 0; cy < 16; cy++) {
1813 		for (cx = 0; cx < 16; cx++) {
1814 			get_chunk_unsafe(scx + cx, scy + cy)->kill_cache();
1815 		}
1816 	}
1817 	// Removing objs. sets these flags.
1818 	schunk_modified[schunk] = false;
1819 	map_modified = save_map_modified;
1820 	chunk_terrains_modified = save_terrain_modified;
1821 	--caching_out;
1822 }
1823