1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "common/serializer.h"
24 #include "xeen/map.h"
25 #include "xeen/interface.h"
26 #include "xeen/resources.h"
27 #include "xeen/saves.h"
28 #include "xeen/screen.h"
29 #include "xeen/xeen.h"
30 #include "xeen/dialogs/please_wait.h"
31 
32 namespace Xeen {
33 
34 const int MAP_GRID_PRIOR_INDEX[] = { 0, 0, 0, 0, 1, 2, 3, 4, 0 };
35 
36 const int MAP_GRID_PRIOR_DIRECTION[] = { 0, 1, 2, 3, 1, 2, 3, 0, 0 };
37 
38 const int MAP_GRID_PRIOR_INDEX2[] = { 0, 0, 0, 0, 2, 3, 4, 1, 0 };
39 
40 const int MAP_GRID_PRIOR_DIRECTION2[] = { 0, 1, 2, 3, 0, 1, 2, 3, 0 };
41 
MonsterStruct()42 MonsterStruct::MonsterStruct() {
43 	_experience = 0;
44 	_hp = 0;
45 	_armorClass = 0;
46 	_speed = 0;
47 	_numberOfAttacks = 0;
48 	_hatesClass = CLASS_KNIGHT;
49 	_strikes = 0;
50 	_dmgPerStrike = 0;
51 	_attackType = DT_PHYSICAL;
52 	_specialAttack = SA_NONE;
53 	_hitChance = 0;
54 	_rangeAttack = 0;
55 	_monsterType = MONSTER_MONSTERS;
56 	_fireResistence = 0;
57 	_electricityResistence = 0;
58 	_coldResistence = 0;
59 	_poisonResistence = 0;
60 	_energyResistence = 0;
61 	_magicResistence = 0;
62 	_phsyicalResistence = 0;
63 	_field29 = 0;
64 	_gold = 0;
65 	_gems = 0;
66 	_itemDrop = 0;
67 	_flying = 0;
68 	_imageNumber = 0;
69 	_loopAnimation = 0;
70 	_animationEffect = 0;
71 	_fx = 0;
72 }
73 
MonsterStruct(Common::String name,int experience,int hp,int armorClass,int speed,int numberOfAttacks,CharacterClass hatesClass,int strikes,int dmgPerStrike,DamageType attackType,SpecialAttack specialAttack,int hitChance,int rangeAttack,MonsterType monsterType,int fireResistence,int electricityResistence,int coldResistence,int poisonResistence,int energyResistence,int magicResistence,int phsyicalResistence,int field29,int gold,int gems,int itemDrop,bool flying,int imageNumber,int loopAnimation,int animationEffect,int fx,Common::String attackVoc)74 MonsterStruct::MonsterStruct(Common::String name, int experience, int hp, int armorClass,
75 		int speed, int numberOfAttacks, CharacterClass hatesClass, int strikes,
76 		int dmgPerStrike, DamageType attackType, SpecialAttack specialAttack,
77 		int hitChance, int rangeAttack, MonsterType monsterType,
78 		int fireResistence, int electricityResistence, int coldResistence,
79 		int poisonResistence, int energyResistence, int magicResistence,
80 		int phsyicalResistence, int field29, int gold, int gems, int itemDrop,
81 		bool flying, int imageNumber, int loopAnimation, int animationEffect,
82 		int fx, Common::String attackVoc):
83 		_name(name), _experience(experience), _hp(hp), _armorClass(armorClass),
84 		_speed(speed), _numberOfAttacks(numberOfAttacks), _hatesClass(hatesClass),
85 		_strikes(strikes), _dmgPerStrike(dmgPerStrike), _attackType(attackType),
86 		_specialAttack(specialAttack), _hitChance(hitChance), _rangeAttack(rangeAttack),
87 		_monsterType(monsterType), _fireResistence(fireResistence),
88 		_electricityResistence(electricityResistence), _coldResistence(coldResistence),
89 		_poisonResistence(poisonResistence), _energyResistence(energyResistence),
90 		_magicResistence(magicResistence), _phsyicalResistence(phsyicalResistence),
91 		_field29(field29), _gold(gold), _gems(gems), _itemDrop(itemDrop),
92 		_flying(flying), _imageNumber(imageNumber), _loopAnimation(loopAnimation),
93 		_animationEffect(animationEffect), _fx(fx), _attackVoc(attackVoc) {
94 }
95 
synchronize(Common::SeekableReadStream & s)96 void MonsterStruct::synchronize(Common::SeekableReadStream &s) {
97 	char name[16];
98 	Common::fill(name, name + 16, '\0');
99 	s.read(name, 16);
100 	name[15] = '\0';
101 	_name = Common::String(name);
102 
103 	_experience = s.readUint32LE();
104 	_hp = s.readUint16LE();
105 	_armorClass = s.readByte();
106 	_speed = s.readByte();
107 	_numberOfAttacks = s.readByte();
108 	_hatesClass = (CharacterClass)s.readByte();
109 	_strikes = s.readUint16LE();
110 	_dmgPerStrike = s.readByte();
111 	_attackType = (DamageType)s.readByte();
112 	_specialAttack = (SpecialAttack)s.readByte();
113 	_hitChance = s.readByte();
114 	_rangeAttack = s.readByte();
115 	_monsterType = (MonsterType)s.readByte();
116 	_fireResistence = s.readByte();
117 	_electricityResistence = s.readByte();
118 	_coldResistence = s.readByte();
119 	_poisonResistence = s.readByte();
120 	_energyResistence = s.readByte();
121 	_magicResistence = s.readByte();
122 	_phsyicalResistence = s.readByte();
123 	_field29 = s.readByte();
124 	_gold = s.readUint16LE();
125 	_gems = s.readByte();
126 	_itemDrop = s.readByte();
127 	_flying = s.readByte() != 0;
128 	_imageNumber = s.readByte();
129 	_loopAnimation = s.readByte();
130 	_animationEffect = s.readByte();
131 	_fx = s.readByte();
132 
133 	char attackVoc[10];
134 	Common::fill(attackVoc, attackVoc + 10, '\0');
135 	s.read(attackVoc, 9);
136 	attackVoc[9] = '\0';
137 	_attackVoc = Common::String(attackVoc);
138 }
139 
MonsterData()140 MonsterData::MonsterData() {
141 }
142 
load(const Common::String & name)143 void MonsterData::load(const Common::String &name) {
144 	File f(name);
145 	synchronize(f);
146 }
147 
synchronize(Common::SeekableReadStream & s)148 void MonsterData::synchronize(Common::SeekableReadStream &s) {
149 	clear();
150 
151 	MonsterStruct spr;
152 	while (!s.eos()) {
153 		spr.synchronize(s);
154 		push_back(spr);
155 	}
156 }
157 
158 /*------------------------------------------------------------------------*/
159 
SurroundingMazes()160 SurroundingMazes::SurroundingMazes() {
161 	clear();
162 }
163 
clear()164 void SurroundingMazes::clear() {
165 	_north = 0;
166 	_east = 0;
167 	_south = 0;
168 	_west = 0;
169 }
170 
synchronize(XeenSerializer & s)171 void SurroundingMazes::synchronize(XeenSerializer &s) {
172 	s.syncAsUint16LE(_north);
173 	s.syncAsUint16LE(_east);
174 	s.syncAsUint16LE(_south);
175 	s.syncAsUint16LE(_west);
176 }
177 
operator [](int idx)178 int &SurroundingMazes::operator[](int idx) {
179 	switch (idx) {
180 	case DIR_NORTH:
181 		return _north;
182 	case DIR_EAST:
183 		return _east;
184 	case DIR_SOUTH:
185 		return _south;
186 	default:
187 		return _west;
188 	}
189 }
190 
191 /*------------------------------------------------------------------------*/
192 
MazeDifficulties()193 MazeDifficulties::MazeDifficulties() {
194 	_unlockDoor = 0;
195 	_unlockBox = 0;
196 	_bashDoor = 0;
197 	_bashGrate = 0;
198 	_bashWall = 0;
199 	_wallNoPass = -1;
200 	_surfaceNoPass = -1;
201 	_chance2Run = -1;
202 }
203 
synchronize(XeenSerializer & s)204 void MazeDifficulties::synchronize(XeenSerializer &s) {
205 	s.syncAsByte(_wallNoPass);
206 	s.syncAsByte(_surfaceNoPass);
207 	s.syncAsByte(_unlockDoor);
208 	s.syncAsByte(_unlockBox);
209 	s.syncAsByte(_bashDoor);
210 	s.syncAsSint8(_bashGrate);
211 	s.syncAsSint8(_bashWall);
212 	s.syncAsSint8(_chance2Run);
213 }
214 
215 /*------------------------------------------------------------------------*/
216 
MazeData()217 MazeData::MazeData() {
218 	clear();
219 }
220 
clear()221 void MazeData::clear() {
222 	for (int y = 0; y < MAP_HEIGHT; ++y) {
223 		for (int x = 0; x < MAP_WIDTH; ++x)
224 			_wallData[y][x]._data = 0;
225 		Common::fill(&_seenTiles[y][0], &_seenTiles[y][MAP_WIDTH], false);
226 		Common::fill(&_steppedOnTiles[y][0], &_steppedOnTiles[y][MAP_WIDTH], false);
227 		_wallTypes[y] = 0;
228 		_surfaceTypes[y] = 0;
229 	}
230 	_mazeNumber = 0;
231 	_surroundingMazes.clear();
232 	_mazeFlags = _mazeFlags2  = 0;
233 	_floorType = 0;
234 	_trapDamage = 0;
235 	_wallKind = 0;
236 	_tavernTips = 0;
237 	_mazeId = 0;
238 }
239 
synchronize(XeenSerializer & s)240 void MazeData::synchronize(XeenSerializer &s) {
241 	byte b;
242 
243 	for (int y = 0; y < MAP_HEIGHT; ++y) {
244 		for (int x = 0; x < MAP_WIDTH; ++x)
245 			s.syncAsUint16LE(_wallData[y][x]._data);
246 	}
247 	for (int y = 0; y < MAP_HEIGHT; ++y) {
248 		for (int x = 0; x < MAP_WIDTH; ++x) {
249 			if (s.isLoading()) {
250 				s.syncAsByte(b);
251 				_cells[y][x]._surfaceId = b & 7;
252 				_cells[y][x]._flags = b & 0xF8;
253 			} else {
254 				b = (_cells[y][x]._surfaceId & 7) | (_cells[y][x]._flags & 0xf8);
255 				s.syncAsByte(b);
256 			}
257 		}
258 	}
259 
260 	s.syncAsUint16LE(_mazeNumber);
261 	_surroundingMazes.synchronize(s);
262 	s.syncAsUint16LE(_mazeFlags);
263 	s.syncAsUint16LE(_mazeFlags2);
264 
265 	for (int i = 0; i < 16; ++i)
266 		s.syncAsByte(_wallTypes[i]);
267 	for (int i = 0; i < 16; ++i)
268 		s.syncAsByte(_surfaceTypes[i]);
269 
270 	s.syncAsByte(_floorType);
271 	s.syncAsByte(_runPosition.x);
272 	_difficulties.synchronize(s);
273 	s.syncAsByte(_runPosition.y);
274 	s.syncAsByte(_trapDamage);
275 	s.syncAsByte(_wallKind);
276 	s.syncAsByte(_tavernTips);
277 
278 	for (int y = 0; y < MAP_HEIGHT; ++y)
279 		File::syncBitFlags(s, &_seenTiles[y][0], &_seenTiles[y][MAP_WIDTH]);
280 	for (int y = 0; y < MAP_HEIGHT; ++y)
281 		File::syncBitFlags(s, &_steppedOnTiles[y][0], &_steppedOnTiles[y][MAP_WIDTH]);
282 }
283 
setAllTilesStepped()284 void MazeData::setAllTilesStepped() {
285 	for (int y = 0; y < MAP_HEIGHT; ++y)
286 		Common::fill(&_steppedOnTiles[y][0], &_steppedOnTiles[y][MAP_WIDTH], true);
287 }
288 
clearCellSurfaces()289 void MazeData::clearCellSurfaces() {
290 	for (int y = 0; y < MAP_HEIGHT; ++y) {
291 		for (int x = 0; x < MAP_WIDTH; ++x)
292 			_cells[y][x]._surfaceId = 0;
293 	}
294 }
295 
296 /*------------------------------------------------------------------------*/
297 
MobStruct()298 MobStruct::MobStruct() {
299 	_id = 0;
300 	_direction = DIR_NORTH;
301 }
302 
synchronize(XeenSerializer & s)303 bool MobStruct::synchronize(XeenSerializer &s) {
304 	s.syncAsSint8(_pos.x);
305 	s.syncAsSint8(_pos.y);
306 	s.syncAsByte(_id);
307 	s.syncAsByte(_direction);
308 
309 	return _id != 0xff || _pos.x != -1 || _pos.y != -1;
310 }
311 
endOfList()312 void MobStruct::endOfList() {
313 	_pos.x = _pos.y = -1;
314 	_id = 0xff;
315 	_direction = (Direction)-1;
316 }
317 
318 /*------------------------------------------------------------------------*/
319 
MazeObject()320 MazeObject::MazeObject() {
321 	_id = 0;
322 	_frame = 0;
323 	_spriteId = 0;
324 	_direction = DIR_NORTH;
325 	_flipped = false;
326 	_sprites = nullptr;
327 }
328 
329 /*------------------------------------------------------------------------*/
330 
MazeMonster()331 MazeMonster::MazeMonster() {
332 	_frame = 0;
333 	_id = 0;
334 	_spriteId = 0;
335 	_isAttacking = false;
336 	_damageType = DT_PHYSICAL;
337 	_field9 = 0;
338 	_postAttackDelay = 0;
339 	_hp = 0;
340 	_effect1 = _effect2 = 0;
341 	_effect3 = 0;
342 	_sprites = nullptr;
343 	_attackSprites = nullptr;
344 	_monsterData = nullptr;
345 }
346 
getTextColor() const347 int MazeMonster::getTextColor() const {
348 	if (_hp == _monsterData->_hp)
349 		return 15;
350 	else if (_hp >= (_monsterData->_hp / 2))
351 		return 9;
352 	else
353 		return 32;
354 }
355 
356 /*------------------------------------------------------------------------*/
357 
MazeWallItem()358 MazeWallItem::MazeWallItem() {
359 	_id = 0;
360 	_frame = 0;
361 	_spriteId = 0;
362 	_direction = DIR_NORTH;
363 	_sprites = nullptr;
364 }
365 
366 /*------------------------------------------------------------------------*/
367 
MonsterObjectData(XeenEngine * vm)368 MonsterObjectData::MonsterObjectData(XeenEngine *vm): _vm(vm) {
369 }
370 
synchronize(XeenSerializer & s,MonsterData & monsterData)371 void MonsterObjectData::synchronize(XeenSerializer &s, MonsterData &monsterData) {
372 	Common::Array<MobStruct> mobStructs;
373 	MobStruct mobStruct;
374 	byte b;
375 
376 	if (s.isLoading()) {
377 		_objectSprites.clear();
378 		_monsterSprites.clear();
379 		_monsterAttackSprites.clear();
380 		_wallItemSprites.clear();
381 		_objects.clear();
382 		_monsters.clear();
383 		_wallItems.clear();
384 	}
385 
386 	byte objSprites[16];
387 	int maxSprite = 0;
388 	for (int i = 0; i < 16; ++i) {
389 		objSprites[i] = (i >= (int)_objectSprites.size()) ? 0xff : _objectSprites[i]._spriteId;
390 		s.syncAsByte(objSprites[i]);
391 		if (s.isLoading() && objSprites[i] != 0xff)
392 			maxSprite = i;
393 	}
394 
395 	if (s.isLoading()) {
396 		for (int i = 0; i <= maxSprite; ++i) {
397 			if (objSprites[i] == 0xff)
398 				_objectSprites.push_back(SpriteResourceEntry());
399 			else
400 				_objectSprites.push_back(SpriteResourceEntry(objSprites[i]));
401 		}
402 	}
403 
404 	for (uint i = 0; i < 16; ++i) {
405 		b = (i >= _monsterSprites.size()) ? 0xff : _monsterSprites[i]._spriteId;
406 		s.syncAsByte(b);
407 		if (s.isLoading() && b != 0xff)
408 			_monsterSprites.push_back(SpriteResourceEntry(b));
409 	}
410 	for (uint i = 0; i < 16; ++i) {
411 		b = (i >= _wallItemSprites.size()) ? 0xff : _wallItemSprites[i]._spriteId;
412 		s.syncAsByte(b);
413 		if (s.isLoading() && b != 0xff)
414 			_wallItemSprites.push_back(SpriteResourceEntry(b));
415 	}
416 
417 	if (s.isSaving()) {
418 		// Save objects
419 		if (_objects.empty()) {
420 			mobStruct.endOfList();
421 			mobStruct.synchronize(s);
422 		} else {
423 			for (uint i = 0; i < _objects.size(); ++i) {
424 				mobStruct._pos = _objects[i]._position;
425 				mobStruct._id = _objects[i]._id;
426 				mobStruct._direction = _objects[i]._direction;
427 				mobStruct.synchronize(s);
428 			}
429 		}
430 		mobStruct.endOfList();
431 		mobStruct.synchronize(s);
432 
433 		// Save monsters
434 		if (_monsters.empty()) {
435 			mobStruct.endOfList();
436 			mobStruct.synchronize(s);
437 		} else {
438 			for (uint i = 0; i < _monsters.size(); ++i) {
439 				mobStruct._pos = _monsters[i]._position;
440 				mobStruct._id = _monsters[i]._id;
441 				mobStruct._direction = DIR_NORTH;
442 				mobStruct.synchronize(s);
443 			}
444 		}
445 		mobStruct.endOfList();
446 		mobStruct.synchronize(s);
447 
448 		// Save wall items
449 		for (uint i = 0; i < _wallItems.size(); ++i) {
450 			mobStruct._pos = _wallItems[i]._position;
451 			mobStruct._id = _wallItems[i]._id;
452 			mobStruct._direction = _wallItems[i]._direction;
453 			mobStruct.synchronize(s);
454 		}
455 		mobStruct.endOfList();
456 		mobStruct.synchronize(s);
457 
458 	} else {
459 		// Load monster/obbject data and merge together with sprite Ids
460 		// Load objects
461 		mobStruct.synchronize(s);
462 		if (mobStruct._id == -1 && mobStruct._pos.x == -1)
463 			// Empty array has a blank entry
464 			mobStruct.synchronize(s);
465 
466 		do {
467 			MazeObject obj;
468 			obj._position = mobStruct._pos;
469 			obj._id = mobStruct._id;
470 			obj._direction = mobStruct._direction;
471 			obj._frame = 100;
472 
473 			if (obj._id < (int)_objectSprites.size()) {
474 				obj._spriteId = _objectSprites[obj._id]._spriteId;
475 				obj._sprites = &_objectSprites[obj._id]._sprites;
476 			}
477 
478 			_objects.push_back(obj);
479 			mobStruct.synchronize(s);
480 		} while (mobStruct._id != 255 || mobStruct._pos.x != -1);
481 
482 		// Load monsters
483 		mobStruct.synchronize(s);
484 		if (mobStruct._id == -1 && mobStruct._pos.x == -1)
485 			// Empty array has a blank entry
486 			mobStruct.synchronize(s);
487 
488 		while (mobStruct._id != 255 || mobStruct._pos.x != -1) {
489 			MazeMonster mon;
490 			mon._position = mobStruct._pos;
491 			mon._id = mobStruct._id;
492 			mon._frame = _vm->getRandomNumber(7);
493 
494 			if (mon._id < (int)_monsterSprites.size()) {
495 				mon._spriteId = _monsterSprites[mon._id]._spriteId;
496 				mon._sprites = &_monsterSprites[mon._id]._sprites;
497 				mon._attackSprites = &_monsterSprites[mon._id]._attackSprites;
498 				mon._monsterData = &monsterData[mon._spriteId];
499 
500 				MonsterStruct &md = *mon._monsterData;
501 				mon._hp = md._hp;
502 				mon._effect1 = mon._effect2 = md._animationEffect;
503 				if (md._animationEffect)
504 					mon._effect3 = _vm->getRandomNumber(7);
505 
506 				_monsters.push_back(mon);
507 			} else {
508 				assert(!mon._id);
509 			}
510 
511 			mobStruct.synchronize(s);
512 		}
513 
514 		// Load wall items. Unlike the previous two arrays, this has no dummy entry for an empty array
515 		mobStruct.synchronize(s);
516 		while (mobStruct._id != 255 || mobStruct._pos.x != -1) {
517 			if (mobStruct._id < (int)_wallItemSprites.size()) {
518 				MazeWallItem wi;
519 				wi._position = mobStruct._pos;
520 				wi._id = mobStruct._id;
521 				wi._direction = mobStruct._direction;
522 				wi._spriteId = _wallItemSprites[wi._id]._spriteId;
523 				wi._sprites = &_wallItemSprites[wi._id]._sprites;
524 
525 				_wallItems.push_back(wi);
526 			}
527 
528 			mobStruct.synchronize(s);
529 		}
530 	}
531 }
532 
clearMonsterSprites()533 void MonsterObjectData::clearMonsterSprites() {
534 	_monsterSprites.clear();
535 	_monsterAttackSprites.clear();
536 }
537 
addMonsterSprites(MazeMonster & monster)538 void MonsterObjectData::addMonsterSprites(MazeMonster &monster) {
539 	Map &map = *g_vm->_map;
540 	monster._monsterData = &map._monsterData[monster._spriteId];
541 	int imgNumber = monster._monsterData->_imageNumber;
542 	uint idx;
543 
544 	// Find the sprites for the monster, loading them in if necessary
545 	for (idx = 0; idx < _monsterSprites.size(); ++idx) {
546 		if (_monsterSprites[idx]._spriteId == monster._spriteId) {
547 			monster._sprites = &_monsterSprites[idx]._sprites;
548 			break;
549 		}
550 	}
551 	if (idx == _monsterSprites.size()) {
552 		_monsterSprites.push_back(SpriteResourceEntry(monster._spriteId));
553 		_monsterSprites.back()._sprites.load(Common::String::format("%03u.mon", imgNumber));
554 		monster._sprites = &_monsterSprites.back()._sprites;
555 	}
556 
557 	// Find the attack sprites for the monster, loading them in if necessary
558 	for (idx = 0; idx < _monsterAttackSprites.size(); ++idx) {
559 		if (_monsterAttackSprites[idx]._spriteId == monster._spriteId) {
560 			monster._attackSprites = &_monsterAttackSprites[idx]._sprites;
561 			break;
562 		}
563 	}
564 	if (idx == _monsterAttackSprites.size()) {
565 		_monsterAttackSprites.push_back(SpriteResourceEntry(monster._spriteId));
566 		_monsterAttackSprites.back()._sprites.load(Common::String::format("%03u.att", imgNumber));
567 		monster._attackSprites = &_monsterAttackSprites.back()._sprites;
568 	}
569 }
570 
571 /*------------------------------------------------------------------------*/
572 
HeadData()573 HeadData::HeadData() {
574 	for (int y = 0; y < MAP_HEIGHT; ++y) {
575 		for (int x = 0; x < MAP_WIDTH; ++x) {
576 			_data[y][x]._left = _data[y][x]._right = 0;
577 		}
578 	}
579 }
580 
synchronize(Common::SeekableReadStream & s)581 void HeadData::synchronize(Common::SeekableReadStream &s) {
582 	for (int y = 0; y < MAP_HEIGHT; ++y) {
583 		for (int x = 0; x < MAP_WIDTH; ++x) {
584 			_data[y][x]._left = s.readByte();
585 			_data[y][x]._right = s.readByte();
586 		}
587 	}
588 }
589 
590 /*------------------------------------------------------------------------*/
591 
synchronize(Common::SeekableReadStream & s)592 void AnimationEntry::synchronize(Common::SeekableReadStream &s) {
593 	for (int i = 0; i < 4; ++i)
594 		_frame1._frames[i] = s.readByte();
595 	for (int i = 0; i < 4; ++i)
596 		_flipped._flags[i] = s.readByte() != 0;
597 	for (int i = 0; i < 4; ++i)
598 		_frame2._frames[i] = s.readByte();
599 }
600 
synchronize(Common::SeekableReadStream & s)601 void AnimationInfo::synchronize(Common::SeekableReadStream &s) {
602 	AnimationEntry entry;
603 
604 	clear();
605 	while (s.pos() < s.size()) {
606 		entry.synchronize(s);
607 		push_back(entry);
608 	}
609 }
610 
load(const Common::String & name)611 void AnimationInfo::load(const Common::String &name) {
612 	File f(name);
613 	synchronize(f);
614 	f.close();
615 }
616 
617 /*------------------------------------------------------------------------*/
618 
Map(XeenEngine * vm)619 Map::Map(XeenEngine *vm) : _vm(vm), _mobData(vm) {
620 	_loadCcNum = 0;
621 	_sideTownPortal = 0;
622 	_sideObjects = 0;
623 	_sideMonsters = 0;
624 	_sidePictures = 0;
625 	_isOutdoors = false;
626 	_mazeDataIndex = 0;
627 	_currentSteppedOn = false;
628 	_currentSurfaceId = 0;
629 	_currentWall = 0;
630 	_currentTile = 0;
631 	_currentGrateUnlocked = false;
632 	_currentCantRest = false;
633 	_currentIsDrain = false;
634 	_currentIsEvent = false;
635 	_currentSky = 0;
636 	_currentMonsterFlags = 0;
637 }
638 
load(int mapId)639 void Map::load(int mapId) {
640 	EventsManager &events = *g_vm->_events;
641 	FileManager &files = *g_vm->_files;
642 	Interface &intf = *g_vm->_interface;
643 	Party &party = *g_vm->_party;
644 	Patcher &patcher = *g_vm->_patcher;
645 	Sound &sound = *g_vm->_sound;
646 	IndoorDrawList &indoorList = intf._indoorList;
647 	OutdoorDrawList &outdoorList = intf._outdoorList;
648 
649 	PleaseWait waitMsg(intf._falling);
650 	waitMsg.show();
651 
652 	intf._objNumber = -1;
653 	party._stepped = true;
654 	party._mazeId = mapId;
655 	saveMaze();
656 	events.clearEvents();
657 
658 	_sideObjects = 1;
659 	_sideMonsters = 1;
660 	_sidePictures = 1;
661 	if (mapId >= 113 && mapId <= 127) {
662 		_sideTownPortal = 0;
663 	} else {
664 		_sideTownPortal = _loadCcNum;
665 	}
666 
667 	if (_vm->getGameID() == GType_Swords || _vm->getGameID() == GType_DarkSide) {
668 		_animationInfo.load("dark.dat");
669 		_monsterData.load((_vm->getGameID() == GType_Swords) ? "monsters.swd" : "dark.mon");
670 		_wallPicSprites.load("darkpic.dat");
671 	} else if (_vm->getGameID() == GType_Clouds) {
672 		_animationInfo.load("animinfo.cld");
673 		_monsterData.load("monsters.cld");
674 		_wallPicSprites.load("wallpics.cld");
675 	} else if (_vm->getGameID() == GType_WorldOfXeen) {
676 		files.setGameCc(1);
677 
678 		if (!_loadCcNum) {
679 			_animationInfo.load("clouds.dat");
680 			_monsterData.load("xeen.mon");
681 			_wallPicSprites.load("xeenpic.dat");
682 			_sidePictures = 0;
683 			_sideMonsters = 0;
684 			_sideObjects = 0;
685 		} else {
686 			switch (mapId) {
687 			case 113:
688 			case 114:
689 			case 115:
690 			case 116:
691 			case 128:
692 				_animationInfo.load("clouds.dat");
693 				_monsterData.load("dark.mon");
694 				_wallPicSprites.load("darkpic.dat");
695 				_sideObjects = 0;
696 				break;
697 			case 117:
698 			case 118:
699 			case 119:
700 			case 120:
701 			case 124:
702 				_animationInfo.load("clouds.dat");
703 				_monsterData.load("xeen.mon");
704 				_wallPicSprites.load("darkpic.dat");
705 				_sideObjects = 0;
706 				_sideMonsters = 0;
707 				break;
708 			case 125:
709 			case 126:
710 			case 127:
711 				_animationInfo.load("clouds.dat");
712 				_monsterData.load("dark.mon");
713 				_wallPicSprites.load("xeenpic.dat");
714 				_sideObjects = 0;
715 				_sidePictures = 0;
716 				break;
717 			default:
718 				_animationInfo.load("dark.dat");
719 				_monsterData.load("dark.mon");
720 				_wallPicSprites.load("darkpic.dat");
721 				break;
722 			}
723 		}
724 
725 		files.setGameCc(_loadCcNum);
726 	}
727 
728 	// Load any events for the new map
729 	loadEvents(mapId, _loadCcNum);
730 
731 	// Iterate through loading the given maze as well as the two successive
732 	// mazes in each of the four cardinal directions
733 	int ccNum = files._ccNum;
734 	MazeData *mazeDataP = &_mazeData[0];
735 	bool mapDataLoaded = false;
736 
737 	for (int idx = 0; idx < 9; ++idx, ++mazeDataP) {
738 		mazeDataP->_mazeId = mapId;
739 
740 		if (mapId == 0) {
741 			mazeDataP->clear();
742 		} else {
743 			// Load in the maze's data file
744 			Common::String datName = Common::String::format("maze%c%03d.dat",
745 				(mapId >= 100) ? 'x' : '0', mapId);
746 			File datFile(datName);
747 			XeenSerializer datSer(&datFile, nullptr);
748 			mazeDataP->synchronize(datSer);
749 			datFile.close();
750 
751 			if (ccNum && mapId == 50)
752 				mazeDataP->setAllTilesStepped();
753 			if (!ccNum && party._gameFlags[0][25] &&
754 					(mapId == 42 || mapId == 43 || mapId == 4)) {
755 				mazeDataP->clearCellSurfaces();
756 			}
757 
758 			_isOutdoors = (mazeDataP->_mazeFlags2 & FLAG_IS_OUTDOORS) != 0;
759 
760 			Common::String mobName = Common::String::format("maze%c%03d.mob", (mapId >= 100) ? 'x' : '0', mapId);
761 
762 			if (!mapDataLoaded) {
763 				// Called once for the main map being loaded
764 				mapDataLoaded = true;
765 				_mazeName = getMazeName(mapId, ccNum);
766 
767 				// Load the monster/object data
768 				File mobFile(mobName);
769 				XeenSerializer sMob(&mobFile, nullptr);
770 				_mobData.synchronize(sMob, _monsterData);
771 				mobFile.close();
772 
773 				Common::String headName = Common::String::format("aaze%c%03d.hed",
774 					(mapId >= 100) ? 'x' : '0', mapId);
775 				File headFile(headName);
776 				_headData.synchronize(headFile);
777 				headFile.close();
778 
779 				if (!ccNum && mapId == 15) {
780 					if ((_mobData._monsters[0]._position.x > 31 || _mobData._monsters[0]._position.y > 31) &&
781 						(_mobData._monsters[1]._position.x > 31 || _mobData._monsters[1]._position.y > 31) &&
782 						(_mobData._monsters[2]._position.x > 31 || _mobData._monsters[2]._position.y > 31)) {
783 						party._gameFlags[0][56] = true;
784 					}
785 				}
786 			} else if (File::exists(mobName)) {
787 				// For surrounding maps, set up flags for whether objects are present
788 
789 				// WORKAROUND: In WOX Map 120, one of the maps for Deep Mine Alpha,
790 				// has invalid monster data. So to work around it, we just ignore it
791 				if (!(mapId == 120 && g_vm->getGameID() == GType_WorldOfXeen)) {
792 					// Load the monster/object data
793 					File mobFile(mobName);
794 					XeenSerializer sMob(&mobFile, nullptr);
795 					MonsterObjectData mobData(_vm);
796 					mobData.synchronize(sMob, _monsterData);
797 					mobFile.close();
798 
799 					mazeDataP->_objectsPresent.resize(mobData._objects.size());
800 					for (uint objIndex = 0; objIndex < mobData._objects.size(); ++objIndex) {
801 						const Common::Point &pt = mobData._objects[objIndex]._position;
802 						mazeDataP->_objectsPresent[objIndex] = ABS(pt.x) != 128 && ABS(pt.y) != 128;
803 					}
804 				}
805 			}
806 		}
807 
808 		// Move to next surrounding maze
809 		MazeData *baseMaze = &_mazeData[MAP_GRID_PRIOR_INDEX[idx]];
810 		mapId = baseMaze->_surroundingMazes[MAP_GRID_PRIOR_DIRECTION[idx]];
811 		if (!mapId) {
812 			baseMaze = &_mazeData[MAP_GRID_PRIOR_INDEX2[idx]];
813 			mapId = baseMaze->_surroundingMazes[MAP_GRID_PRIOR_DIRECTION2[idx]];
814 		}
815 	}
816 
817 	// Reload the monster data for the main maze that we're loading
818 	mapId = party._mazeId;
819 	Common::String filename = Common::String::format("maze%c%03d.mob",
820 		(mapId >= 100) ? 'x' : '0', mapId);
821 	File mobFile(filename);
822 	XeenSerializer sMob(&mobFile, nullptr);
823 	_mobData.synchronize(sMob, _monsterData);
824 	mobFile.close();
825 
826 	// Load sprites for the objects
827 	for (uint i = 0; i < _mobData._objectSprites.size(); ++i) {
828 		files.setGameCc(_sideObjects);
829 
830 		if (party._cloudsCompleted && _mobData._objectSprites[i]._spriteId == 85 &&
831 				mapId == 27 && ccNum) {
832 			_mobData._objects[29]._spriteId = 0;
833 			_mobData._objects[29]._id = 8;
834 			_mobData._objectSprites[i]._sprites.clear();
835 		} else if (mapId == 12 && party._gameFlags[0][43] &&
836 			_mobData._objectSprites[i]._spriteId == 118 && !ccNum) {
837 			filename = "085.obj";
838 			_mobData._objectSprites[0]._spriteId = 85;
839 		} else {
840 			filename = Common::String::format("%03d.%cbj",
841 				_mobData._objectSprites[i]._spriteId,
842 				_mobData._objectSprites[i]._spriteId >= 100 ? '0' : 'o');
843 		}
844 
845 		// Read in the object sprites
846 		if (!_mobData._objectSprites[i].isEmpty())
847 			_mobData._objectSprites[i]._sprites.load(filename);
848 	}
849 
850 	// Load sprites for the monsters
851 	for (uint i = 0; i < _mobData._monsterSprites.size(); ++i) {
852 		MonsterObjectData::SpriteResourceEntry &spr = _mobData._monsterSprites[i];
853 		uint imgNumber = _monsterData[spr._spriteId]._imageNumber;
854 
855 		files.setGameCc((spr._spriteId == 91 && _vm->getGameID() == GType_WorldOfXeen) ?
856 			0 : _sideMonsters);
857 		filename = Common::String::format("%03u.mon", imgNumber);
858 		_mobData._monsterSprites[i]._sprites.load(filename);
859 
860 		filename = Common::String::format("%03u.att", imgNumber);
861 		_mobData._monsterSprites[i]._attackSprites.load(filename);
862 	}
863 
864 	// Load wall picture sprite resources
865 	for (uint i = 0; i < _mobData._wallItemSprites.size(); ++i) {
866 		filename = Common::String::format("%03d.pic", _mobData._wallItemSprites[i]._spriteId);
867 		_mobData._wallItemSprites[i]._sprites.load(filename, _sidePictures);
868 	}
869 
870 	files.setGameCc(ccNum);
871 
872 	// Handle loading miscellaneous sprites for the map
873 	if (_isOutdoors) {
874 		// Start playing relevant music
875 		sound._musicSide = ccNum;
876 		Common::String musName;
877 
878 		if (_vm->_files->_ccNum) {
879 			int randIndex = _vm->getRandomNumber(6);
880 			musName = Res.MUSIC_FILES2[_mazeData->_wallKind][randIndex];
881 		} else {
882 			musName = "outdoors.m";
883 		}
884 		if (musName != sound._currentMusic)
885 			sound.playSong(musName, 207);
886 
887 		// Load sprite sets needed for scene rendering
888 		_groundSprites.load("water.out");
889 		_tileSprites.load("outdoor.til");
890 		outdoorList._sky1._sprites = &_skySprites[0];
891 		outdoorList._sky2._sprites = &_skySprites[0];
892 		outdoorList._groundSprite._sprites = &_groundSprites;
893 
894 		for (int i = 0; i < TOTAL_SURFACES; ++i) {
895 			_wallSprites._surfaces[i].clear();
896 
897 			if (_mazeData[0]._wallTypes[i] != 0) {
898 				_wallSprites._surfaces[i].load(Common::String::format("%s.wal",
899 					Res.OUTDOORS_WALL_TYPES[_mazeData[0]._wallTypes[i]]));
900 			}
901 
902 			_surfaceSprites[i].clear();
903 			if (i != 0 && _mazeData[0]._surfaceTypes[i] != 0)
904 				_surfaceSprites[i].load(Res.SURFACE_NAMES[_mazeData[0]._surfaceTypes[i]]);
905 		}
906 	} else {
907 		if (files._ccNum && (mapId == 125 || mapId == 126 || mapId == 127))
908 			files.setGameCc(0);
909 		sound._musicSide = files._ccNum;
910 
911 		// Start playing relevant music
912 		const int MUS_INDEXES[] = { 1, 2, 3, 4, 3, 5 };
913 		Common::String musName;
914 
915 		if (files._ccNum) {
916 			int randIndex = _vm->getRandomNumber(6);
917 			musName = Res.MUSIC_FILES2[MUS_INDEXES[_mazeData->_wallKind]][randIndex];
918 		} else {
919 			musName = Res.MUSIC_FILES1[MUS_INDEXES[_mazeData->_wallKind]];
920 		}
921 		if (musName != sound._currentMusic)
922 			sound.playSong(musName, 207);
923 
924 		// Load sprite sets needed for scene rendering
925 		_skySprites[1].load(Common::String::format("%s.sky",
926 			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]));
927 		_groundSprites.load(Common::String::format("%s.gnd",
928 			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]));
929 		_tileSprites.load(Common::String::format("%s.til",
930 			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]));
931 
932 		for (int i = 0; i < TOTAL_SURFACES; ++i) {
933 			_surfaceSprites[i].clear();
934 
935 			if (_mazeData[0]._surfaceTypes[i] != 0 || i == 4)
936 				_surfaceSprites[i].load(Res.SURFACE_NAMES[_mazeData[0]._surfaceTypes[i]]);
937 		}
938 
939 		for (int i = 0; i < TOTAL_SURFACES; ++i)
940 			_wallSprites._surfaces[i].clear();
941 
942 		_wallSprites._fwl1.load(Common::String::format("f%s1.fwl",
943 			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]), _sidePictures);
944 		_wallSprites._fwl2.load(Common::String::format("f%s2.fwl",
945 			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]), _sidePictures);
946 		_wallSprites._fwl3.load(Common::String::format("f%s3.fwl",
947 			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]), _sidePictures);
948 		_wallSprites._fwl4.load(Common::String::format("f%s4.fwl",
949 			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]), _sidePictures);
950 		_wallSprites._swl.load(Common::String::format("s%s.swl",
951 			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]), _sidePictures);
952 
953 		// Set entries in the indoor draw list to the correct sprites
954 		// for drawing various parts of the background
955 		indoorList._swl_0F1R._sprites = &_wallSprites._swl;
956 		indoorList._swl_0F1L._sprites = &_wallSprites._swl;
957 		indoorList._swl_1F1R._sprites = &_wallSprites._swl;
958 		indoorList._swl_1F1L._sprites = &_wallSprites._swl;
959 		indoorList._swl_2F2R._sprites = &_wallSprites._swl;
960 		indoorList._swl_2F1R._sprites = &_wallSprites._swl;
961 		indoorList._swl_2F1L._sprites = &_wallSprites._swl;
962 		indoorList._swl_2F2L._sprites = &_wallSprites._swl;
963 
964 		indoorList._swl_3F1R._sprites = &_wallSprites._swl;
965 		indoorList._swl_3F2R._sprites = &_wallSprites._swl;
966 		indoorList._swl_3F3R._sprites = &_wallSprites._swl;
967 		indoorList._swl_3F4R._sprites = &_wallSprites._swl;
968 		indoorList._swl_3F1L._sprites = &_wallSprites._swl;
969 		indoorList._swl_3F2L._sprites = &_wallSprites._swl;
970 		indoorList._swl_3F3L._sprites = &_wallSprites._swl;
971 		indoorList._swl_3F4L._sprites = &_wallSprites._swl;
972 
973 		indoorList._swl_4F4R._sprites = &_wallSprites._swl;
974 		indoorList._swl_4F3R._sprites = &_wallSprites._swl;
975 		indoorList._swl_4F2R._sprites = &_wallSprites._swl;
976 		indoorList._swl_4F1R._sprites = &_wallSprites._swl;
977 		indoorList._swl_4F1L._sprites = &_wallSprites._swl;
978 		indoorList._swl_4F2L._sprites = &_wallSprites._swl;
979 		indoorList._swl_4F3L._sprites = &_wallSprites._swl;
980 		indoorList._swl_4F4L._sprites = &_wallSprites._swl;
981 
982 		indoorList._fwl_4F4R._sprites = &_wallSprites._fwl4;
983 		indoorList._fwl_4F3R._sprites = &_wallSprites._fwl4;
984 		indoorList._fwl_4F2R._sprites = &_wallSprites._fwl4;
985 		indoorList._fwl_4F1R._sprites = &_wallSprites._fwl4;
986 		indoorList._fwl_4F._sprites = &_wallSprites._fwl4;
987 		indoorList._fwl_4F1L._sprites = &_wallSprites._fwl4;
988 		indoorList._fwl_4F2L._sprites = &_wallSprites._fwl4;
989 		indoorList._fwl_4F3L._sprites = &_wallSprites._fwl4;
990 		indoorList._fwl_4F4L._sprites = &_wallSprites._fwl4;
991 
992 		indoorList._fwl_2F1R._sprites = &_wallSprites._fwl3;
993 		indoorList._fwl_2F._sprites = &_wallSprites._fwl3;
994 		indoorList._fwl_2F1L._sprites = &_wallSprites._fwl3;
995 		indoorList._fwl_3F2R._sprites = &_wallSprites._fwl3;
996 		indoorList._fwl_3F1R._sprites = &_wallSprites._fwl3;
997 		indoorList._fwl_3F._sprites = &_wallSprites._fwl3;
998 		indoorList._fwl_3F1L._sprites = &_wallSprites._fwl3;
999 		indoorList._fwl_3F2L._sprites = &_wallSprites._fwl3;
1000 
1001 		indoorList._fwl_1F._sprites = &_wallSprites._fwl1;
1002 		indoorList._fwl_1F1R._sprites = &_wallSprites._fwl1;
1003 		indoorList._fwl_1F1L._sprites = &_wallSprites._fwl1;
1004 		indoorList._horizon._sprites = &_wallSprites._fwl1;
1005 
1006 		indoorList._ground._sprites = &_groundSprites;
1007 
1008 		// Don't show horizon for certain maps
1009 		if (_vm->_files->_ccNum) {
1010 			if ((mapId >= 89 && mapId <= 112) || mapId == 128 || mapId == 129)
1011 				indoorList._horizon._sprites = nullptr;
1012 		} else {
1013 			if (mapId >= 25 && mapId <= 27)
1014 				indoorList._horizon._sprites = nullptr;
1015 		}
1016 	}
1017 
1018 	patcher.patch();
1019 	loadSky();
1020 
1021 	files.setGameCc(ccNum);
1022 }
1023 
findMap(int mapId)1024 void Map::findMap(int mapId) {
1025 	if (mapId == -1)
1026 		mapId = _vm->_party->_mazeId;
1027 
1028 	_mazeDataIndex = 0;
1029 	while (_mazeDataIndex < 9 && _mazeData[_mazeDataIndex]._mazeId != mapId)
1030 		++_mazeDataIndex;
1031 	if (_mazeDataIndex == 9)
1032 		error("Could not find map %d", mapId);
1033 }
1034 
mazeLookup(const Common::Point & pt,int layerShift,int wallMask)1035 int Map::mazeLookup(const Common::Point &pt, int layerShift, int wallMask) {
1036 	Common::Point pos = pt;
1037 	int mapId = _vm->_party->_mazeId;
1038 
1039 	if (pt.x < -16 || pt.y < -16 || pt.x >= 32 || pt.y >= 32) {
1040 		_currentWall = INVALID_CELL;
1041 		return INVALID_CELL;
1042 	}
1043 
1044 	// Find the correct maze data out of the set to use
1045 	findMap();
1046 
1047 	// Handle map changing to the north or south as necessary
1048 	if (pos.y & 16) {
1049 		if (pos.y >= 0) {
1050 			pos.y -= 16;
1051 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
1052 		} else {
1053 			pos.y += 16;
1054 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
1055 		}
1056 
1057 		if (mapId) {
1058 			// Move to the correct map to north/south
1059 			findMap(mapId);
1060 		} else {
1061 			// No map, so reached outside indoor area or outer space outdoors
1062 			_currentSteppedOn = true;
1063 			return _isOutdoors ? SURFTYPE_SPACE : INVALID_CELL;
1064 		}
1065 	}
1066 
1067 	// Handle map changing to the east or west as necessary
1068 	if (pos.x & 16) {
1069 		if (pos.x >= 0) {
1070 			pos.x -= 16;
1071 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
1072 		} else {
1073 			pos.x += 16;
1074 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._west;
1075 		}
1076 
1077 		if (mapId)
1078 			// Move to the correct map to east/west
1079 			findMap(mapId);
1080 	}
1081 
1082 	if (mapId) {
1083 		if (_isOutdoors) {
1084 			_currentSurfaceId = _mazeData[_mazeDataIndex]._wallData[pos.y][pos.x]._outdoors._surfaceId;
1085 		} else {
1086 			_currentSurfaceId = _mazeData[_mazeDataIndex]._cells[pos.y][pos.x]._surfaceId;
1087 		}
1088 
1089 		if (mazeData()._surfaceTypes[_currentSurfaceId] == SURFTYPE_SPACE ||
1090 				mazeData()._surfaceTypes[_currentSurfaceId] == SURFTYPE_SKY) {
1091 			_currentSteppedOn = true;
1092 		} else {
1093 			_currentSteppedOn = _mazeData[_mazeDataIndex]._steppedOnTiles[pos.y][pos.x];
1094 		}
1095 
1096 		return (_mazeData[_mazeDataIndex]._wallData[pos.y][pos.x]._data >> layerShift) & wallMask;
1097 
1098 	} else {
1099 		_currentSteppedOn = _isOutdoors;
1100 		return _isOutdoors ? SURFTYPE_SPACE : INVALID_CELL;
1101 	}
1102 }
1103 
loadEvents(int mapId,int ccNum)1104 void Map::loadEvents(int mapId, int ccNum) {
1105 	// Load events
1106 	Common::String filename = Common::String::format("maze%c%03d.evt",
1107 		(mapId >= 100) ? 'x' : '0', mapId);
1108 	File fEvents(filename, ccNum);
1109 	XeenSerializer sEvents(&fEvents, nullptr);
1110 	_events.synchronize(sEvents);
1111 	fEvents.close();
1112 
1113 	// Load text data
1114 	filename = Common::String::format("aaze%c%03d.txt",
1115 		(mapId >= 100) ? 'x' : '0', mapId);
1116 	File fText(filename, ccNum);
1117 	_events._text.clear();
1118 	while (fText.pos() < fText.size())
1119 		_events._text.push_back(fText.readString());
1120 	fText.close();
1121 }
1122 
saveEvents()1123 void Map::saveEvents() {
1124 	// Save eents
1125 	int mapId = _mazeData[0]._mazeId;
1126 	Common::String filename = Common::String::format("maze%c%03d.evt",
1127 		(mapId >= 100) ? 'x' : '0', mapId);
1128 	OutFile fEvents(filename);
1129 	XeenSerializer sEvents(nullptr, &fEvents);
1130 	_events.synchronize(sEvents);
1131 	fEvents.finalize();
1132 }
1133 
saveMap()1134 void Map::saveMap() {
1135 	FileManager &files = *g_vm->_files;
1136 	Party &party = *g_vm->_party;
1137 	int mapId = _mazeData[0]._mazeId;
1138 	if (!files._ccNum && mapId == 85)
1139 		return;
1140 
1141 	// Save the primary maze
1142 	Common::String datName = Common::String::format("maze%c%03d.dat", (mapId >= 100) ? 'x' : '0', mapId);
1143 	OutFile datFile(datName);
1144 	XeenSerializer datSer(nullptr, &datFile);
1145 	_mazeData[0].synchronize(datSer);
1146 	datFile.finalize();
1147 
1148 	if (!files._ccNum && mapId == 15) {
1149 		for (uint idx = 0; idx < MIN(_mobData._monsters.size(), (uint)3); ++idx) {
1150 			MazeMonster &mon = _mobData._monsters[idx];
1151 			if (mon._position.x > 31 || mon._position.y > 31) {
1152 				party._gameFlags[0][56] = true;
1153 				break;
1154 			}
1155 		}
1156 	}
1157 
1158 	if (!_isOutdoors) {
1159 		// Iterate through the surrounding mazes
1160 		for (int mazeIndex = 1; mazeIndex < 9; ++mazeIndex) {
1161 			mapId = _mazeData[mazeIndex]._mazeId;
1162 			if (mapId == 0)
1163 				continue;
1164 
1165 			datName = Common::String::format("maze%c%03d.dat", (mapId >= 100) ? 'x' : '0', mapId);
1166 			OutFile datFile2(datName);
1167 			XeenSerializer datSer2(nullptr, &datFile2);
1168 			_mazeData[mazeIndex].synchronize(datSer2);
1169 			datFile2.finalize();
1170 		}
1171 	}
1172 }
1173 
saveMonsters()1174 void Map::saveMonsters() {
1175 	int mapId = _mazeData[0]._mazeId;
1176 	Common::String filename = Common::String::format("maze%c%03d.mob",
1177 		(mapId >= 100) ? 'x' : '0', mapId);
1178 	OutFile fMob(filename);
1179 	XeenSerializer sMob(nullptr, &fMob);
1180 	_mobData.synchronize(sMob, _monsterData);
1181 	fMob.finalize();
1182 }
1183 
saveMaze()1184 void Map::saveMaze() {
1185 	int mazeNum = _mazeData[0]._mazeNumber;
1186 	if (!mazeNum || (mazeNum == 85 && !_vm->_files->_ccNum))
1187 		return;
1188 
1189 	saveEvents();
1190 	saveMap();
1191 	saveMonsters();
1192 }
1193 
clearMaze()1194 void Map::clearMaze() {
1195 	_mazeData[0]._mazeNumber = 0;
1196 }
1197 
cellFlagLookup(const Common::Point & pt)1198 void Map::cellFlagLookup(const Common::Point &pt) {
1199 	Common::Point pos = pt;
1200 	findMap();
1201 
1202 	int mapId = _vm->_party->_mazeId;
1203 	findMap(mapId);
1204 
1205 	// Handle map changing to the north or south as necessary
1206 	if (pos.y & 16) {
1207 		if (pos.y >= 0) {
1208 			pos.y -= 16;
1209 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
1210 		} else {
1211 			pos.y += 16;
1212 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
1213 		}
1214 
1215 		findMap(mapId);
1216 	}
1217 
1218 	// Handle map changing to the east or west as necessary
1219 	if (pos.x & 16) {
1220 		if (pos.x >= 0) {
1221 			pos.x -= 16;
1222 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
1223 		} else {
1224 			pos.x += 16;
1225 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._west;
1226 		}
1227 
1228 		findMap(mapId);
1229 	}
1230 
1231 	// Get the cell flags
1232 	const MazeCell &cell = _mazeData[_mazeDataIndex]._cells[pos.y][pos.x];
1233 	_currentGrateUnlocked = cell._flags & OUTFLAG_GRATE;
1234 	_currentCantRest = cell._flags & RESTRICTION_REST;
1235 	_currentIsDrain = cell._flags & OUTFLAG_DRAIN;
1236 	_currentIsEvent = cell._flags & FLAG_AUTOEXECUTE_EVENT;
1237 	_currentSky = (cell._flags & OUTFLAG_OBJECT_EXISTS) ? 1 : 0;
1238 	_currentMonsterFlags = cell._flags & 7;
1239 }
1240 
setCellSurfaceFlags(const Common::Point & pt,int bits)1241 void Map::setCellSurfaceFlags(const Common::Point &pt, int bits) {
1242 	mazeLookup(pt, 0);
1243 
1244 	Common::Point mapPos(pt.x & 15, pt.y & 15);
1245 	MazeCell &cell = _mazeData[_mazeDataIndex]._cells[mapPos.y][mapPos.x];
1246 	cell._flags |= bits & 0xF8;
1247 }
1248 
setWall(const Common::Point & pt,Direction dir,int v)1249 void Map::setWall(const Common::Point &pt, Direction dir, int v) {
1250 	const int XOR_MASKS[4] = { 0xFFF, 0xF0FF, 0xFF0F, 0xFFF0 };
1251 	mazeLookup(pt, 0, 0);
1252 
1253 	Common::Point mapPos(pt.x & 15, pt.y & 15);
1254 	MazeWallLayers &wallLayer = _mazeData[_mazeDataIndex]._wallData[mapPos.y][mapPos.x];
1255 	wallLayer._data &= XOR_MASKS[dir];
1256 	wallLayer._data |= v << Res.WALL_SHIFTS[dir][2];
1257 }
1258 
getCell(int idx)1259 int Map::getCell(int idx) {
1260 	Party &party = *g_vm->_party;
1261 	int mapId = party._mazeId;
1262 	Direction dir = _vm->_party->_mazeDirection;
1263 	Common::Point pt(
1264 		_vm->_party->_mazePosition.x + Res.SCREEN_POSITIONING_X[_vm->_party->_mazeDirection][idx],
1265 		_vm->_party->_mazePosition.y + Res.SCREEN_POSITIONING_Y[_vm->_party->_mazeDirection][idx]
1266 	);
1267 
1268 	if (pt.x > 31 || pt.y > 31) {
1269 		if (_vm->_files->_ccNum) {
1270 			if ((mapId >= 53 && mapId <= 88 && mapId != 73) || (mapId >= 74 && mapId <= 120) ||
1271 					mapId == 125 || mapId == 126 || mapId == 128 || mapId == 129) {
1272 				_currentSurfaceId = SURFTYPE_DESERT;
1273 			} else {
1274 				_currentSurfaceId = 0;
1275 			}
1276 		} else {
1277 			_currentSurfaceId = (mapId >= 25 && mapId <= 27) ? 7 : 0;
1278 		}
1279 		_currentWall = INVALID_CELL;
1280 		return INVALID_CELL;
1281 	}
1282 
1283 	findMap(mapId);
1284 
1285 	if (pt.y & 16) {
1286 		if (pt.y >= 0) {
1287 			pt.y -= 16;
1288 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
1289 		} else {
1290 			pt.y += 16;
1291 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
1292 		}
1293 
1294 		if (!mapId) {
1295 			mapId = party._mazeId;
1296 
1297 			if (_isOutdoors) {
1298 				_currentSurfaceId = SURFTYPE_SPACE;
1299 				_currentWall = 0;
1300 				return 0;
1301 			} else {
1302 				if (_vm->_files->_ccNum) {
1303 					if ((mapId >= 53 && mapId <= 88 && mapId != 73) || (mapId >= 74 && mapId <= 120) ||
1304 						mapId == 125 || mapId == 126 || mapId == 128 || mapId == 129) {
1305 						_currentSurfaceId = 6;
1306 					} else {
1307 						_currentSurfaceId = 0;
1308 					}
1309 				} else {
1310 					_currentSurfaceId = (mapId >= 25 && mapId <= 27) ? SURFTYPE_ROAD : SURFTYPE_DEFAULT;
1311 				}
1312 
1313 				_currentWall = INVALID_CELL;
1314 				return INVALID_CELL;
1315 			}
1316 		}
1317 
1318 		findMap(mapId);
1319 	}
1320 
1321 	if (pt.x & 16) {
1322 		if (pt.x >= 0) {
1323 			pt.x -= 16;
1324 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
1325 		} else {
1326 			pt.x += 16;
1327 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._west;
1328 		}
1329 
1330 		if (!mapId) {
1331 			mapId = party._mazeId;
1332 
1333 			if (_isOutdoors) {
1334 				_currentSurfaceId = SURFTYPE_SPACE;
1335 				_currentWall = 0;
1336 				return 0;
1337 			} else {
1338 				if (_vm->_files->_ccNum) {
1339 					if ((mapId >= 53 && mapId <= 88 && mapId != 73) || (mapId >= 74 && mapId <= 120) ||
1340 						mapId == 125 || mapId == 126 || mapId == 128 || mapId == 129) {
1341 						_currentSurfaceId = 6;
1342 					} else {
1343 						_currentSurfaceId = 0;
1344 					}
1345 				} else {
1346 					_currentSurfaceId = (mapId >= 25 && mapId <= 27) ? SURFTYPE_ROAD : SURFTYPE_DEFAULT;
1347 				}
1348 
1349 				_currentWall = INVALID_CELL;
1350 				return INVALID_CELL;
1351 			}
1352 		}
1353 
1354 		findMap(mapId);
1355 	}
1356 
1357 	assert(pt.x >= 0 && pt.x < 16 && pt.y >= 0 && pt.y < 16);
1358 	int wallData = _mazeData[_mazeDataIndex]._wallData[pt.y][pt.x]._data;
1359 	if (_isOutdoors) {
1360 		if (mapId) {
1361 			_currentTile = (wallData >> 8) & 0xFF;
1362 			_currentWall = (wallData >> 4) & 0xF;
1363 			_currentSurfaceId = wallData & 0xF;
1364 		} else {
1365 			_currentSurfaceId = SURFTYPE_DEFAULT;
1366 			_currentWall = 0;
1367 			_currentTile = 0;
1368 		}
1369 	} else {
1370 		if (!mapId)
1371 			return 0;
1372 
1373 		_currentSurfaceId = _mazeData[_mazeDataIndex]._cells[pt.y][pt.x]._surfaceId;
1374 		_currentWall = wallData;
1375 		return (_currentWall >> Res.WALL_SHIFTS[dir][idx]) & 0xF;
1376 	}
1377 
1378 	return _currentWall;
1379 }
1380 
loadSky()1381 void Map::loadSky() {
1382 	Party &party = *_vm->_party;
1383 
1384 	party._isNight = party._minutes < (5 * 60) || party._minutes >= (21 * 60);
1385 	_skySprites[0].load(((party._mazeId >= 89 && party._mazeId <= 112) ||
1386 		party._mazeId == 128 || party._mazeId == 129) || !party._isNight
1387 		? "sky.sky" : "night.sky");
1388 }
1389 
getNewMaze()1390 void Map::getNewMaze() {
1391 	Party &party = *_vm->_party;
1392 	Common::Point pt = party._mazePosition;
1393 	int mapId = party._mazeId;
1394 
1395 	// Get the correct map to use from the cached list
1396 	findMap(mapId);
1397 
1398 	// Adjust Y and X to be in the 0-15 range, and on the correct surrounding
1399 	// map if either value is < 0 or >= 16
1400 	if (pt.y & 16) {
1401 		if (pt.y >= 0) {
1402 			pt.y -= 16;
1403 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
1404 		} else {
1405 			pt.y += 16;
1406 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
1407 		}
1408 
1409 		if (mapId)
1410 			findMap(mapId);
1411 	}
1412 
1413 	if (pt.x & 16) {
1414 		if (pt.x >= 0) {
1415 			pt.x -= 16;
1416 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
1417 		} else {
1418 			pt.x += 16;
1419 			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._west;
1420 		}
1421 
1422 		if (mapId)
1423 			findMap(mapId);
1424 	}
1425 
1426 	// Save the adjusted (0,0)-(15,15) position and load the given map.
1427 	// This will make it the new center, with it's own surrounding mazees loaded
1428 	party._mazePosition = pt;
1429 	if (mapId)
1430 		load(mapId);
1431 }
1432 
getMazeName(int mapId,int ccNum)1433 Common::String Map::getMazeName(int mapId, int ccNum) {
1434 	if (ccNum == -1)
1435 		ccNum = g_vm->_files->_ccNum;
1436 
1437 	if (g_vm->getGameID() == GType_Clouds) {
1438 		return Res._cloudsMapNames[mapId];
1439 	} else {
1440 		Common::String txtName = Common::String::format("%s%c%03d.txt",
1441 			ccNum ? "dark" : "xeen", mapId >= 100 ? 'x' : '0', mapId);
1442 		File fText(txtName, 1);
1443 		char mazeName[33];
1444 		fText.read(mazeName, 33);
1445 		mazeName[32] = '\0';
1446 
1447 		Common::String name = Common::String(mazeName);
1448 		fText.close();
1449 		return name;
1450 	}
1451 }
1452 
1453 } // End of namespace Xeen
1454