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