/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/serializer.h"
#include "xeen/map.h"
#include "xeen/interface.h"
#include "xeen/resources.h"
#include "xeen/saves.h"
#include "xeen/screen.h"
#include "xeen/xeen.h"

namespace Xeen {

const int MAP_GRID_PRIOR_INDEX[] = { 0, 0, 0, 0, 1, 2, 3, 4, 0 };

const int MAP_GRID_PRIOR_DIRECTION[] = { 0, 1, 2, 3, 1, 2, 3, 0, 0 };

const int MAP_GRID_PRIOR_INDEX2[] = { 0, 0, 0, 0, 2, 3, 4, 1, 0 };

const int MAP_GRID_PRIOR_DIRECTION2[] = { 0, 1, 2, 3, 0, 1, 2, 3, 0 };

MonsterStruct::MonsterStruct() {
	_experience = 0;
	_hp = 0;
	_accuracy = 0;
	_speed = 0;
	_numberOfAttacks = 0;
	_hatesClass = CLASS_KNIGHT;
	_strikes = 0;
	_dmgPerStrike = 0;
	_attackType = DT_PHYSICAL;
	_specialAttack = SA_NONE;
	_hitChance = 0;
	_rangeAttack = 0;
	_monsterType = MONSTER_0;
	_fireResistence = 0;
	_electricityResistence = 0;
	_coldResistence = 0;
	_poisonResistence = 0;
	_energyResistence = 0;
	_magicResistence = 0;
	_phsyicalResistence = 0;
	_field29 = 0;
	_gold = 0;
	_gems = 0;
	_itemDrop = 0;
	_flying = 0;
	_imageNumber = 0;
	_loopAnimation = 0;
	_animationEffect = 0;
	_fx = 0;
}

MonsterStruct::MonsterStruct(Common::String name, int experience, int hp, int accuracy,
		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):
		_name(name), _experience(experience), _hp(hp), _accuracy(accuracy),
		_speed(speed), _numberOfAttacks(numberOfAttacks), _hatesClass(hatesClass),
		_strikes(strikes), _dmgPerStrike(dmgPerStrike), _attackType(attackType),
		_specialAttack(specialAttack), _hitChance(hitChance), _rangeAttack(rangeAttack),
		_monsterType(monsterType), _fireResistence(fireResistence),
		_electricityResistence(electricityResistence), _coldResistence(coldResistence),
		_poisonResistence(poisonResistence), _energyResistence(energyResistence),
		_magicResistence(magicResistence), _phsyicalResistence(phsyicalResistence),
		_field29(field29), _gold(gold), _gems(gems), _itemDrop(itemDrop),
		_flying(flying), _imageNumber(imageNumber), _loopAnimation(loopAnimation),
		_animationEffect(animationEffect), _fx(fx), _attackVoc(attackVoc) {
}

void MonsterStruct::synchronize(Common::SeekableReadStream &s) {
	char name[16];
	s.read(name, 16);
	name[15] = '\0';
	_name = Common::String(name);

	_experience = s.readUint32LE();
	_hp = s.readUint16LE();
	_accuracy = s.readByte();
	_speed = s.readByte();
	_numberOfAttacks = s.readByte();
	_hatesClass = (CharacterClass)s.readByte();
	_strikes = s.readUint16LE();
	_dmgPerStrike = s.readByte();
	_attackType = (DamageType)s.readByte();
	_specialAttack = (SpecialAttack)s.readByte();
	_hitChance = s.readByte();
	_rangeAttack = s.readByte();
	_monsterType = (MonsterType)s.readByte();
	_fireResistence = s.readByte();
	_electricityResistence = s.readByte();
	_coldResistence = s.readByte();
	_poisonResistence = s.readByte();
	_energyResistence = s.readByte();
	_magicResistence = s.readByte();
	_phsyicalResistence = s.readByte();
	_field29 = s.readByte();
	_gold = s.readUint16LE();
	_gems = s.readByte();
	_itemDrop = s.readByte();
	_flying = s.readByte() != 0;
	_imageNumber = s.readByte();
	_loopAnimation = s.readByte();
	_animationEffect = s.readByte();
	_fx = s.readByte();

	char attackVoc[10];
	s.read(attackVoc, 9);
	attackVoc[9] = '\0';
	_attackVoc = Common::String(attackVoc);
}

MonsterData::MonsterData() {
	push_back(MonsterStruct("", 0, 0, 0, 0, 0, CLASS_KNIGHT, 1, 1, DT_PHYSICAL,
				   SA_NONE, 1, 0, MONSTER_0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				   0, false, 0, 0, 0, 100, "Slime"));
	push_back(MonsterStruct("Whirlwind", 250000, 1000, 10, 250, 1, CLASS_15, 5,
				   100, DT_PHYSICAL, SA_CONFUSE, 250, 0, MONSTER_0, 100,
				   100, 100, 100, 0, 0, 100, 0, 0, 0, 0, false, 1, 0, 0, 176,
				   "airmon"));
	push_back(MonsterStruct("Annihilator", 1000000, 1500, 40, 200, 12, CLASS_16, 5,
				   50, DT_ENERGY, SA_NONE, 1, 1, MONSTER_0, 80, 80, 100,
				   100, 0, 0, 80, 0, 0, 0, 0, false, 2, 0, 0, 102, "alien1"));
	push_back(MonsterStruct("Autobot", 1000000, 2500, 100, 200, 2, CLASS_16, 5,
				   100, DT_ENERGY, SA_NONE, 1, 0, MONSTER_0, 50, 50, 100,
				   100, 0, 0, 50, 0, 0, 0, 0, true, 3, 0, 0, 101, "alien2"));
	push_back(MonsterStruct("Sewer Stalker", 50000, 250, 30, 25, 1, CLASS_16, 3,
				   100, DT_PHYSICAL, SA_NONE, 50, 0, MONSTER_ANIMAL, 0,
				   0, 50, 50, 0, 0, 0, 0, 0, 0, 0, false, 4, 0, 0, 113,
				   "iguana"));
	push_back(MonsterStruct("Armadillo", 60000, 800, 50, 15, 1, CLASS_16, 100, 6,
				   DT_PHYSICAL, SA_BREAKWEAPON, 60, 0, MONSTER_ANIMAL,
				   50, 0, 80, 80, 50, 0, 50, 0, 0, 0, 0, false, 5, 1, 0, 113,
				   "unnh"));
	push_back(MonsterStruct("Barbarian", 5000, 50, 5, 40, 3, CLASS_SORCERER, 1, 20,
				   DT_PHYSICAL, SA_NONE, 20, 1, MONSTER_HUMANOID, 0, 0,
				   0, 0, 0, 0, 0, 0, 100, 0, 3, false, 6, 0, 0, 100,
				   "barbarch"));
	push_back(MonsterStruct("Electrapede", 10000, 200, 10, 50, 1, CLASS_PALADIN,
				   50, 1, DT_ELECTRICAL, SA_PARALYZE, 1, 0,
				   MONSTER_INSECT, 50, 100, 50, 50, 50, 0, 0, 0, 0, 0, 0,
				   false, 7, 1, 0, 107, "centi"));
	push_back(MonsterStruct("Cleric of Mok", 30000, 125, 10, 40, 1, CLASS_CLERIC,
				   250, 1, DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_HUMANOID,
				   10, 100, 10, 10, 10, 10, 0, 0, 0, 10, 0, false, 8, 0, 0,
				   117, "cleric"));
	push_back(MonsterStruct("Mok Heretic", 50000, 150, 12, 50, 1, CLASS_CLERIC,
				   500, 1, DT_MAGICAL, SA_NONE, 1, 1, MONSTER_HUMANOID, 20, 50,
				   20, 20, 20, 30, 0, 0, 0, 25, 4, false, 8, 0, 0, 117,
				   "cleric"));
	push_back(MonsterStruct("Mantis Ant", 40000, 300, 30, 40, 2, CLASS_16, 2, 100,
				   DT_PHYSICAL, SA_POISON, 30, 0, MONSTER_INSECT, 0, 0,
				   0, 100, 0, 0, 30, 0, 0, 0, 0, false, 10, 0, 0, 104,
				   "spell001"));
	push_back(MonsterStruct("Cloud Dragon", 500000, 2000, 40, 150, 1, CLASS_15,
				   600, 1, DT_COLD, SA_NONE, 1, 1, MONSTER_DRAGON, 0, 50,
				   100, 100, 50, 25, 50, 0, 0, 10, 0, false, 11, 0, 0, 140,
				   "tiger1"));
	push_back(MonsterStruct("Phase Dragon", 2000000, 4000, 80, 200, 1, CLASS_15,
				   750, 1, DT_COLD, SA_NONE, 1, 1, MONSTER_DRAGON, 0, 50,
				   100, 100, 80, 50, 50, 0, 0, 20, 0, false, 11, 0, 10, 140,
				   "Begger"));
	push_back(MonsterStruct("Green Dragon", 500000, 2500, 50, 150, 1, CLASS_15,
				   500, 1, DT_FIRE, SA_NONE, 1, 1, MONSTER_DRAGON, 100,
				   50, 0, 100, 50, 25, 50, 0, 0, 10, 0, false, 13, 0, 0, 140,
				   "tiger1"));
	push_back(MonsterStruct("Energy Dragon", 2000000, 5000, 100, 250, 1, CLASS_15,
				   1000, 1, DT_ENERGY, SA_NONE, 1, 1, MONSTER_DRAGON, 80,
				   80, 60, 100, 100, 30, 50, 0, 0, 20, 0, false, 13, 0, 7,
				   140, "begger"));
	push_back(MonsterStruct("Dragon Mummy", 2000000, 3000, 30, 100, 1,
				   CLASS_CLERIC, 2000, 2, DT_PHYSICAL, SA_DISEASE, 200,
				   0, MONSTER_DRAGON, 0, 80, 100, 100, 0, 10, 90, 0, 0, 0,
				   0, false, 15, 0, 0, 140, "dragmum"));
	push_back(MonsterStruct("Scraps", 2000000, 3000, 30, 100, 1, CLASS_16, 2000, 2,
				   DT_PHYSICAL, SA_NONE, 200, 0, MONSTER_DRAGON, 0, 80,
				   100, 100, 0, 10, 90, 0, 0, 0, 0, false, 15, 0, 0, 140,
				   "dragmum"));
	push_back(MonsterStruct("Earth Blaster", 250000, 1000, 10, 100, 1, CLASS_15, 5,
				   100, DT_PHYSICAL, SA_NONE, 200, 0, MONSTER_0, 100, 90,
				   90, 100, 0, 0, 90, 0, 0, 0, 0, false, 17, 0, 0, 100,
				   "earthmon"));
	push_back(MonsterStruct("Beholder Bat", 10000, 75, 15, 80, 1, CLASS_15, 5, 5,
				   DT_FIRE, SA_NONE, 1, 0, MONSTER_0, 100, 50, 0, 0, 0, 0,
				   0, 0, 0, 0, 0, true, 18, 0, 0, 120, "eyeball"));
	push_back(MonsterStruct("Fire Blower", 250000, 1000, 20, 60, 1, CLASS_15, 5,
				   100, DT_FIRE, SA_NONE, 1, 0, MONSTER_0, 100, 50, 0,
				   100, 50, 0, 50, 0, 0, 0, 0, false, 19, 0, 0, 110, "fire"));
	push_back(MonsterStruct("Hell Hornet", 50000, 250, 30, 50, 2, CLASS_DRUID, 2,
				   250, DT_POISON, SA_WEAKEN, 1, 0, MONSTER_INSECT, 50,
				   50, 50, 100, 50, 0, 50, 0, 0, 0, 0, true, 20, 0, 0, 123,
				   "insect"));
	push_back(MonsterStruct("Gargoyle", 30000, 150, 35, 30, 2, CLASS_16, 5, 50,
				   DT_PHYSICAL, SA_NONE, 60, 0, MONSTER_0, 0, 0, 0, 0, 0,
				   20, 0, 0, 0, 0, 0, false, 21, 0, 10, 100, "gargrwl"));
	push_back(MonsterStruct("Giant", 100000, 500, 25, 45, 2, CLASS_16, 100, 5,
				   DT_PHYSICAL, SA_UNCONSCIOUS, 100, 0, MONSTER_0, 0, 0,
				   0, 0, 0, 0, 0, 0, 1000, 0, 5, false, 22, 0, 0, 100,
				   "giant"));
	push_back(MonsterStruct("Goblin", 1000, 10, 5, 30, 2, CLASS_16, 2, 6,
				   DT_PHYSICAL, SA_NONE, 1, 0, MONSTER_0, 0, 0, 0, 0, 0,
				   0, 0, 0, 0, 0, 0, false, 25, 0, 0, 131, "gremlin"));
	push_back(MonsterStruct("Onyx Golem", 1000000, 10000, 50, 100, 1, CLASS_15, 2,
				   250, DT_MAGICAL, SA_DRAINSP, 1, 0, MONSTER_GOLEM, 100, 100,
				   100, 100, 100, 100, 50, 0, 0, 100, 0, true, 24, 0, 10,
				   100, "golem"));
	push_back(MonsterStruct("Gremlin", 2000, 20, 7, 35, 2, CLASS_16, 2, 10,
				   DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_0, 0, 0, 0, 0, 0,
				   0, 0, 0, 0, 0, 0, false, 26, 0, 0, 101, "gremlink"));
	push_back(MonsterStruct("Gremlin Guard", 3000, 50, 10, 35, 2, CLASS_16, 6, 5,
				   DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_0, 0, 0, 0, 0, 0,
				   0, 0, 0, 0, 0, 0, false, 26, 0, 0, 101, "gremlink"));
	push_back(MonsterStruct("Griffin", 60000, 800, 35, 150, 2, CLASS_KNIGHT, 50, 6,
				   DT_PHYSICAL, SA_NONE, 150, 0, MONSTER_ANIMAL, 0, 0, 0,
				   0, 0, 80, 0, 0, 0, 0, 0, false, 27, 0, 0, 120, "screech"));
	push_back(MonsterStruct("Gamma Gazer", 1000000, 5000, 60, 200, 7, CLASS_16, 10,
				   20, DT_ENERGY, SA_NONE, 1, 0, MONSTER_0, 100, 100, 0,
				   100, 100, 0, 60, 0, 0, 0, 0, false, 28, 0, 0, 140, "hydra"));
	push_back(MonsterStruct("Iguanasaurus", 100000, 2500, 20, 30, 1, CLASS_16, 10,
				   50, DT_PHYSICAL, SA_INSANE, 150, 0, MONSTER_ANIMAL, 50,
				   50, 50, 50, 50, 0, 20, 0, 0, 0, 0, false, 29, 0, 0, 113,
				   "iguana"));
	push_back(MonsterStruct("Slayer Knight", 50000, 500, 30, 50, 1, CLASS_PALADIN,
				   2, 250, DT_PHYSICAL, SA_NONE, 100, 0, MONSTER_HUMANOID,
				   50, 50, 50, 50, 50, 0, 0, 0, 50, 0, 5, false, 30, 0, 0,
				   141, "knight"));
	push_back(MonsterStruct("Death Knight", 100000, 750, 50, 80, 2, CLASS_PALADIN,
				   2, 250, DT_PHYSICAL, SA_NONE, 150, 0, MONSTER_HUMANOID,
				   50, 50, 50, 50, 50, 10, 0, 0, 100, 0, 6, false, 30, 0, 0,
				   141, "knight"));
	push_back(MonsterStruct("Lava Dweller", 500000, 1500, 30, 40, 1, CLASS_15, 5,
				   100, DT_FIRE, SA_NONE, 1, 0, MONSTER_0, 100, 100, 0,
				   100, 50, 0, 50, 0, 0, 0, 0, false, 19, 0, 0, 110, "fire"));
	push_back(MonsterStruct("Lava Roach", 50000, 500, 20, 70, 1, CLASS_16, 5, 50,
				   DT_FIRE, SA_NONE, 1, 0, MONSTER_INSECT, 100, 100, 0,
				   100, 0, 0, 0, 0, 0, 0, 0, false, 33, 0, 0, 131, "Phantom"));
	push_back(MonsterStruct("Power Lich", 200000, 500, 20, 60, 1, CLASS_15, 10, 10,
				   DT_MAGICAL, SA_UNCONSCIOUS, 1, 1, MONSTER_UNDEAD, 0, 0, 0, 0,
				   0, 80, 70, 0, 0, 0, 0, true, 34, 0, 0, 141, "lich"));
	push_back(MonsterStruct("Mystic Mage", 100000, 200, 20, 70, 1, CLASS_15, 10,
				   20, DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_0, 50, 100,
				   50, 50, 50, 30, 0, 0, 0, 50, 0, true, 35, 0, 0, 163,
				   "monsterb"));
	push_back(MonsterStruct("Magic Mage", 200000, 300, 25, 80, 1, CLASS_15, 10, 30,
				   DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_0, 50, 100, 50,
				   50, 50, 50, 0, 0, 0, 75, 0, true, 35, 0, 0, 163,
				   "monsterb"));
	push_back(MonsterStruct("Minotaur", 250000, 3000, 80, 120, 1, CLASS_16, 100, 4,
				   DT_PHYSICAL, SA_AGING, 150, 0, MONSTER_0, 0, 0, 10, 0,
				   0, 50, 60, 0, 0, 0, 0, false, 37, 0, 0, 141, "stonegol"));
	push_back(MonsterStruct("Gorgon", 250000, 4000, 90, 100, 1, CLASS_16, 100, 3,
				   DT_PHYSICAL, SA_STONE, 100, 0, MONSTER_0, 0, 0, 0, 0,
				   0, 60, 70, 0, 0, 0, 0, false, 37, 0, 0, 141, "stonegol"));
	push_back(MonsterStruct("Higher Mummy", 100000, 400, 20, 60, 1, CLASS_CLERIC,
				   10, 40, DT_PHYSICAL, SA_CURSEITEM, 100, 0,
				   MONSTER_UNDEAD, 0, 50, 50, 100, 50, 20, 75, 0, 0, 0, 0,
				   false, 39, 0, 0, 141, "mummy"));
	push_back(MonsterStruct("Orc Guard", 5000, 60, 10, 20, 1, CLASS_12, 3, 10,
				   DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_HUMANOID, 0, 0,
				   0, 0, 0, 0, 0, 0, 50, 0, 2, false, 40, 0, 0, 125, "orc"));
	push_back(MonsterStruct("Octopod", 250000, 2500, 40, 80, 1, CLASS_15, 2, 100,
				   DT_POISON, SA_POISON, 1, 0, MONSTER_ANIMAL, 0, 0, 50,
				   100, 0, 0, 0, 0, 0, 0, 0, true, 41, 0, 0, 101, "photon"));
	push_back(MonsterStruct("Ogre", 10000, 100, 15, 30, 1, CLASS_16, 4, 10,
				   DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 0, 0, 0, 0, 0,
				   0, 0, 0, 100, 0, 0, false, 42, 0, 0, 136, "ogre"));
	push_back(MonsterStruct("Orc Shaman", 10000, 50, 15, 30, 1, CLASS_15, 5, 5,
				   DT_COLD, SA_SLEEP, 1, 1, MONSTER_HUMANOID, 0, 0, 0, 0,
				   0, 10, 0, 0, 75, 10, 2, false, 43, 0, 0, 125, "fx7"));
	push_back(MonsterStruct("Sabertooth", 10000, 100, 20, 60, 3, CLASS_16, 5, 10,
				   DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_ANIMAL, 0, 0, 0,
				   0, 0, 0, 0, 0, 0, 0, 0, false, 44, 1, 0, 101, "saber"));
	push_back(MonsterStruct("Sand Flower", 10000, 100, 10, 50, 5, CLASS_16, 5, 5,
				   DT_PHYSICAL, SA_INLOVE, 50, 0, MONSTER_0, 0, 0, 0, 0,
				   0, 50, 50, 0, 0, 0, 0, false, 45, 0, 0, 106, "sand"));
	push_back(MonsterStruct("Killer Cobra", 25000, 1000, 25, 100, 1, CLASS_16, 2,
				   100, DT_PHYSICAL, SA_AGING, 30, 0, MONSTER_ANIMAL, 0,
				   0, 0, 100, 0, 50, 0, 0, 0, 0, 0, false, 46, 0, 0, 100,
				   "hiss"));
	push_back(MonsterStruct("Sewer Rat", 2000, 40, 5, 35, 1, CLASS_16, 3, 10,
				   DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_ANIMAL, 0, 0, 0,
				   0, 0, 0, 0, 0, 0, 0, 0, false, 47, 0, 0, 136, "rat"));
	push_back(MonsterStruct("Sewer Slug", 1000, 25, 2, 25, 1, CLASS_16, 2, 10,
				   DT_PHYSICAL, SA_NONE, 5, 0, MONSTER_INSECT, 0, 0, 0,
				   100, 0, 0, 0, 0, 0, 0, 0, false, 48, 0, 0, 111, "zombie"));
	push_back(MonsterStruct("Skeletal Lich", 500000, 2000, 30, 200, 1,
				   CLASS_SORCERER, 1000, 1, DT_ENERGY, SA_ERADICATE, 1, 1,
				   MONSTER_UNDEAD, 80, 70, 80, 100, 100, 50, 50, 0, 0, 0,
				   0, false, 49, 0, 0, 140, "elecbolt"));
	push_back(MonsterStruct("Enchantress", 40000, 100, 25, 60, 1, CLASS_CLERIC, 3,
				   150, DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_HUMANOID,
				   10, 100, 10, 10, 10, 20, 0, 0, 0, 20, 0, false, 50, 0, 0,
				   163, "disint"));
	push_back(MonsterStruct("Sorceress", 80000, 200, 30, 80, 1, CLASS_15, 2, 50,
				   DT_MAGICAL, SA_NONE, 1, 1, MONSTER_HUMANOID, 10, 20, 10, 10,
				   10, 80, 0, 0, 0, 50, 5, false, 50, 0, 0, 163, "disint"));
	push_back(MonsterStruct("Arachnoid", 4000, 50, 10, 40, 1, CLASS_16, 3, 5,
				   DT_POISON, SA_POISON, 1, 0, MONSTER_INSECT, 0, 0, 0,
				   100, 0, 0, 0, 0, 0, 0, 0, false, 52, 0, 0, 104, "web"));
	push_back(MonsterStruct("Medusa Sprite", 5000, 30, 5, 30, 1, CLASS_RANGER, 3,
				   3, DT_PHYSICAL, SA_STONE, 10, 0, MONSTER_0, 0, 0, 0,
				   0, 0, 0, 0, 0, 0, 0, 0, true, 53, 0, 0, 42, "hiss"));
	push_back(MonsterStruct("Rogue", 5000, 50, 10, 30, 1, CLASS_ROBBER, 1, 60,
				   DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_HUMANOID, 0, 0,
				   0, 0, 0, 0, 0, 0, 70, 0, 0, false, 54, 0, 0, 100, "thief"));
	push_back(MonsterStruct("Thief", 10000, 100, 15, 40, 1, CLASS_ROBBER, 1, 100,
				   DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_HUMANOID, 0, 0,
				   0, 0, 0, 0, 0, 0, 200, 0, 0, false, 54, 0, 0, 100,
				   "thief"));
	push_back(MonsterStruct("Troll Grunt", 10000, 100, 5, 50, 1, CLASS_16, 2, 25,
				   DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 50, 50, 50,
				   50, 0, 0, 0, 0, 0, 0, 0, false, 56, 0, 0, 136, "troll"));
	push_back(MonsterStruct("Vampire", 200000, 400, 30, 80, 1, CLASS_CLERIC, 10,
				   10, DT_PHYSICAL, SA_WEAKEN, 100, 0, MONSTER_UNDEAD, 50,
				   50, 50, 50, 50, 50, 50, 0, 0, 0, 0, false, 57, 0, 0, 42,
				   "vamp"));
	push_back(MonsterStruct("Vampire Lord", 300000, 500, 35, 100, 1, CLASS_CLERIC,
				   10, 30, DT_PHYSICAL, SA_SLEEP, 120, 0, MONSTER_UNDEAD,
				   50, 50, 50, 50, 50, 50, 70, 0, 0, 0, 0, false, 58, 0, 0,
				   42, "vamp"));
	push_back(MonsterStruct("Vulture Roc", 200000, 2500, 50, 150, 1, CLASS_16, 5,
				   60, DT_PHYSICAL, SA_NONE, 100, 0, MONSTER_ANIMAL, 0, 0,
				   0, 0, 0, 0, 0, 0, 0, 0, 0, true, 59, 0, 0, 120, "vulture"));
	push_back(MonsterStruct("Sewer Hag", 50000, 75, 10, 40, 1, CLASS_PALADIN, 10,
				   25, DT_ELECTRICAL, SA_INSANE, 1, 1, MONSTER_HUMANOID,
				   0, 100, 0, 100, 0, 20, 0, 0, 0, 10, 0, false, 62, 0, 0,
				   108, "elecspel"));
	push_back(MonsterStruct("Tidal Terror", 500000, 1000, 10, 200, 1, CLASS_15, 5,
				   100, DT_COLD, SA_NONE, 1, 0, MONSTER_0, 100, 50, 50,
				   100, 50, 0, 100, 0, 0, 0, 0, true, 61, 0, 0, 101,
				   "splash3"));
	push_back(MonsterStruct("Witch", 80000, 150, 15, 70, 1, CLASS_15, 10, 10,
				   DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_HUMANOID, 0, 100,
				   0, 20, 0, 20, 0, 0, 0, 10, 0, false, 63, 0, 0, 114,
				   "elecspel"));
	push_back(MonsterStruct("Coven Leader", 120000, 250, 20, 100, 1, CLASS_15, 10,
				   15, DT_ENERGY, SA_DRAINSP, 1, 1, MONSTER_HUMANOID, 10,
				   100, 0, 50, 100, 50, 0, 0, 0, 20, 6, false, 63, 0, 10, 114,
				   "elecspel"));
	push_back(MonsterStruct("Master Wizard", 120000, 500, 25, 150, 2, CLASS_KNIGHT,
				   10, 40, DT_FIRE, SA_NONE, 1, 1, MONSTER_HUMANOID, 100,
				   50, 50, 50, 50, 50, 0, 0, 0, 50, 0, false, 64, 0, 0, 163,
				   "boltelec"));
	push_back(MonsterStruct("Wizard", 60000, 250, 20, 125, 1, CLASS_PALADIN, 10,
				   25, DT_MAGICAL, SA_NONE, 1, 1, MONSTER_HUMANOID, 50, 30, 30,
				   30, 30, 30, 0, 0, 0, 20, 0, false, 65, 0, 0, 163, "wizard"));
	push_back(MonsterStruct("Dark Wolf", 10000, 70, 10, 70, 3, CLASS_16, 3, 8,
				   DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_ANIMAL, 0, 0, 0,
				   0, 0, 0, 0, 0, 0, 0, 0, false, 66, 1, 0, 100, "wolf"));
	push_back(MonsterStruct("Screamer", 500000, 3000, 50, 200, 1, CLASS_15, 10, 20,
				   DT_POISON, SA_POISON, 1, 0, MONSTER_0, 0, 0, 0, 100, 0,
				   0, 60, 0, 0, 0, 0, false, 67, 0, 0, 110, "dragon"));
	push_back(MonsterStruct("Cult Leader", 100000, 100, 20, 60, 1, CLASS_15, 10,
				   10, DT_ENERGY, SA_NONE, 1, 1, MONSTER_HUMANOID, 50, 50,
				   50, 50, 100, 50, 0, 0, 0, 100, 6, false, 8, 0, 0, 100,
				   "cleric"));
	push_back(MonsterStruct("Mega Dragon", 100000000, 64000, 100, 200, 1, CLASS_15,
				   10, 200, DT_ENERGY, SA_ERADICATE, 1, 1, MONSTER_DRAGON,
				   100, 100, 100, 100, 100, 100, 90, 0, 0, 232, 0, false, 11,
				   0, 7, 100, "tiger1"));
	push_back(MonsterStruct("Gettlewaithe", 5000, 100, 15, 35, 2, CLASS_16, 5, 5,
				   DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_0, 0, 0, 0, 0, 0,
				   0, 0, 0, 2000, 0, 5, false, 25, 0, 0, 100, "gremlin"));
	push_back(MonsterStruct("Doom Knight", 500000, 1000, 50, 100, 4, CLASS_PALADIN,
				   2, 250, DT_PHYSICAL, SA_DEATH, 150, 0,
				   MONSTER_HUMANOID, 80, 80, 80, 80, 80, 20, 0, 0, 200,
				   0, 7, false, 30, 0, 10, 100, "knight"));
	push_back(MonsterStruct("Sandro", 200000, 1000, 20, 75, 1, CLASS_15, 10, 10,
				   DT_MAGICAL, SA_DEATH, 1, 1, MONSTER_UNDEAD, 0, 0, 0, 0, 0,
				   90, 80, 0, 0, 100, 7, true, 34, 0, 10, 100, "lich"));
	push_back(MonsterStruct("Mega Mage", 500000, 500, 35, 100, 1, CLASS_15, 10, 40,
				   DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_0, 80, 100, 80,
				   80, 80, 80, 0, 0, 0, 100, 6, true, 35, 0, 11, 100,
				   "monsterb"));
	push_back(MonsterStruct("Orc Elite", 15000, 200, 15, 40, 2, CLASS_12, 5, 10,
				   DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_HUMANOID, 0, 0,
				   0, 0, 0, 0, 0, 0, 100, 0, 3, false, 40, 0, 0, 100, "orc"));
	push_back(MonsterStruct("Shaalth", 20000, 300, 15, 50, 1, CLASS_15, 5, 10,
				   DT_COLD, SA_SLEEP, 1, 0, MONSTER_HUMANOID, 0, 0, 0, 0,
				   0, 20, 0, 0, 1000, 50, 5, false, 43, 0, 10, 100, "fx7"));
	push_back(MonsterStruct("Rooka", 5000, 60, 5, 40, 1, CLASS_16, 3, 10,
				   DT_PHYSICAL, SA_DISEASE, 15, 0, MONSTER_ANIMAL, 0, 0,
				   0, 0, 0, 0, 0, 0, 0, 10, 4, false, 47, 0, 0, 100, "rat"));
	push_back(MonsterStruct("Morgana", 200000, 300, 35, 100, 1, CLASS_15, 2, 60,
				   DT_ENERGY, SA_PARALYZE, 1, 1, MONSTER_HUMANOID, 50, 50,
				   50, 50, 100, 80, 0, 0, 0, 100, 6, false, 50, 0, 10, 100,
				   "disint"));
	push_back(MonsterStruct("Master Thief", 20000, 100, 20, 50, 1, CLASS_ROBBER, 1,
				   250, DT_PHYSICAL, SA_NONE, 40, 0, MONSTER_HUMANOID, 0,
				   0, 0, 0, 0, 0, 0, 0, 250, 20, 4, false, 54, 0, 14, 100,
				   "thief"));
	push_back(MonsterStruct("Royal Vampire", 400000, 750, 40, 125, 1, CLASS_CLERIC,
				   10, 50, DT_PHYSICAL, SA_CURSEITEM, 120, 0,
				   MONSTER_UNDEAD, 50, 50, 50, 50, 50, 50, 65, 0, 0, 0, 0,
				   false, 57, 0, 0, 100, "vamp"));
	push_back(MonsterStruct("Ct. Blackfang", 2000000, 1500, 50, 150, 1,
				   CLASS_CLERIC, 10, 100, DT_PHYSICAL, SA_DEATH, 120, 0,
				   MONSTER_UNDEAD, 75, 75, 75, 75, 75, 75, 75, 0, 0, 0, 0,
				   false, 58, 0, 10, 100, "vamp"));
	push_back(MonsterStruct("Troll Guard", 15000, 200, 10, 60, 1, CLASS_16, 2, 35,
				   DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 50, 50, 50,
				   50, 0, 0, 0, 0, 0, 0, 0, false, 56, 0, 0, 100, "troll"));
	push_back(MonsterStruct("Troll Chief", 20000, 300, 15, 65, 1, CLASS_16, 2, 50,
				   DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 50, 50, 50,
				   50, 0, 0, 0, 0, 0, 0, 0, false, 56, 0, 0, 100, "troll"));
	push_back(MonsterStruct("Hobstadt", 25000, 400, 20, 70, 1, CLASS_16, 2, 50,
				   DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 50, 50, 50,
				   50, 0, 0, 0, 0, 1000, 0, 4, false, 56, 0, 0, 100, "troll"));
	push_back(MonsterStruct("Graalg", 20000, 200, 15, 50, 1, CLASS_16, 5, 10,
				   DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 0, 0, 0, 0, 0,
				   0, 0, 0, 1000, 0, 5, false, 42, 0, 0, 100, "ogre"));
	push_back(MonsterStruct("Vampire King", 3000000, 10000, 60, 200, 1,
				   CLASS_CLERIC, 10, 250, DT_PHYSICAL, SA_ERADICATE, 150,
				   0, MONSTER_UNDEAD, 80, 80, 80, 80, 80, 80, 90, 0, 0, 0,
				   0, false, 58, 0, 0, 100, "vamp"));
	push_back(MonsterStruct("Valio", 60000, 150, 15, 60, 1, CLASS_PALADIN, 10, 25,
				   DT_MAGICAL, SA_NONE, 1, 0, MONSTER_HUMANOID, 50, 30, 30, 30,
				   40, 30, 0, 0, 0, 0, 0, false, 65, 0, 0, 100, "wizard"));
	push_back(MonsterStruct("Sky Golem", 200000, 1000, 50, 100, 1, CLASS_15, 2,
				   100, DT_COLD, SA_NONE, 1, 1, MONSTER_GOLEM, 50, 50,
				   100, 50, 50, 50, 50, 0, 0, 0, 0, true, 24, 0, 0, 100,
				   "golem"));
	push_back(MonsterStruct("Gurodel", 100000, 750, 30, 60, 2, CLASS_16, 100, 6,
				   DT_PHYSICAL, SA_UNCONSCIOUS, 110, 0, MONSTER_0, 0, 0,
				   0, 0, 0, 0, 0, 0, 5000, 0, 6, false, 22, 0, 0, 100,
				   "giant"));
	push_back(MonsterStruct("Yog", 25000, 100, 5, 60, 1, CLASS_SORCERER, 1, 30,
				   DT_PHYSICAL, SA_NONE, 25, 0, MONSTER_HUMANOID, 0, 0,
				   0, 0, 0, 0, 0, 0, 200, 0, 4, false, 6, 0, 10, 100,
				   "barbarch"));
	push_back(MonsterStruct("Sharla", 10000, 50, 5, 50, 1, CLASS_RANGER, 3, 4,
				   DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_0, 0, 0, 0, 0, 0,
				   0, 0, 0, 0, 0, 0, true, 53, 0, 0, 100, "hiss"));
	push_back(MonsterStruct("Ghost Mummy", 500000, 500, 35, 175, 1, CLASS_CLERIC,
				   200, 5, DT_PHYSICAL, SA_AGING, 150, 0, MONSTER_UNDEAD,
				   0, 60, 80, 80, 80, 50, 80, 0, 0, 0, 0, false, 40, 0, 6,
				   100, "orc"));
	push_back(MonsterStruct("Phase Mummy", 500000, 500, 35, 175, 1, CLASS_CLERIC,
				   200, 6, DT_PHYSICAL, SA_DRAINSP, 150, 0,
				   MONSTER_UNDEAD, 0, 70, 80, 80, 80, 60, 85, 0, 0, 0, 0,
				   false, 39, 0, 7, 100, "mummy"));
	push_back(MonsterStruct("Xenoc", 250000, 700, 35, 175, 1, CLASS_15, 10, 50,
				   DT_ENERGY, SA_NONE, 1, 0, MONSTER_HUMANOID, 50, 50, 50,
				   50, 100, 50, 0, 0, 0, 100, 6, false, 64, 0, 0, 100,
				   "boltelec"));
	push_back(MonsterStruct("Barkman", 4000000, 40000, 25, 100, 3, CLASS_16, 250,
				   1, DT_FIRE, SA_NONE, 1, 0, MONSTER_0, 100, 50, 0, 100,
				   0, 0, 0, 0, 0, 0, 6, false, 19, 0, 11, 100, "fire"));
}

void MonsterData::load(const Common::String &name) {
	File f(name);
	synchronize(f);
}

void MonsterData::synchronize(Common::SeekableReadStream &s) {
	clear();

	MonsterStruct spr;
	while (!s.eos()) {
		spr.synchronize(s);
		push_back(spr);
	}
}

/*------------------------------------------------------------------------*/

SurroundingMazes::SurroundingMazes() {
	clear();
}

void SurroundingMazes::clear() {
	_north = 0;
	_east = 0;
	_south = 0;
	_west = 0;
}

void SurroundingMazes::synchronize(Common::SeekableReadStream &s) {
	_north = s.readUint16LE();
	_east = s.readUint16LE();
	_south = s.readUint16LE();
	_west = s.readUint16LE();
}

int &SurroundingMazes::operator[](int idx) {
	switch (idx) {
	case DIR_NORTH:
		return _north;
	case DIR_EAST:
		return _east;
	case DIR_SOUTH:
		return _south;
	default:
		return _west;
	}
}

/*------------------------------------------------------------------------*/

MazeDifficulties::MazeDifficulties() {
	_unlockDoor = 0;
	_unlockBox = 0;
	_bashDoor = 0;
	_bashGrate = 0;
	_bashWall = 0;
}

void MazeDifficulties::synchronize(Common::SeekableReadStream &s) {
	_wallNoPass = s.readByte();
	_surfaceNoPass = s.readByte();
	_unlockDoor = s.readByte();
	_unlockBox = s.readByte();
	_bashDoor = s.readByte();
	_bashGrate = s.readByte();
	_bashWall = s.readByte();
	_chance2Run = s.readByte();
}

/*------------------------------------------------------------------------*/

MazeData::MazeData() {
	clear();
}

void MazeData::clear() {
	for (int y = 0; y < MAP_HEIGHT; ++y) {
		for (int x = 0; x < MAP_WIDTH; ++x)
			_wallData[y][x]._data = 0;
		Common::fill(&_seenTiles[y][0], &_seenTiles[y][MAP_WIDTH], false);
		Common::fill(&_steppedOnTiles[y][0], &_steppedOnTiles[y][MAP_WIDTH], false);
		_wallTypes[y] = 0;
		_surfaceTypes[y] = 0;
	}
	_mazeNumber = 0;
	_surroundingMazes.clear();
	_mazeFlags = _mazeFlags2  = 0;
	_floorType = 0;
	_trapDamage = 0;
	_wallKind = 0;
	_tavernTips = 0;
}

void MazeData::synchronize(Common::SeekableReadStream &s) {
	for (int y = 0; y < MAP_HEIGHT; ++y) {
		for (int x = 0; x < MAP_WIDTH; ++x)
			_wallData[y][x]._data = s.readUint16LE();
	}
	for (int y = 0; y < MAP_HEIGHT; ++y) {
		for (int x = 0; x < MAP_WIDTH; ++x) {
			byte b = s.readByte();
			_cells[y][x]._surfaceId = b & 7;
			_cells[y][x]._flags = b & 0xF8;
		}
	}

	_mazeNumber = s.readUint16LE();
	_surroundingMazes.synchronize(s);
	_mazeFlags = s.readUint16LE();
	_mazeFlags2 = s.readUint16LE();

	for (int i = 0; i < 16; ++i)
		_wallTypes[i] = s.readByte();
	for (int i = 0; i < 16; ++i)
		_surfaceTypes[i] = s.readByte();

	_floorType = s.readByte();
	_runPosition.x = s.readByte();
	_difficulties.synchronize(s);
	_runPosition.y = s.readByte();
	_trapDamage = s.readByte();
	_wallKind = s.readByte();
	_tavernTips = s.readByte();

	Common::Serializer ser(&s, nullptr);
	for (int y = 0; y < MAP_HEIGHT; ++y)
		SavesManager::syncBitFlags(ser, &_seenTiles[y][0], &_seenTiles[y][MAP_WIDTH]);
	for (int y = 0; y < MAP_HEIGHT; ++y)
		SavesManager::syncBitFlags(ser, &_steppedOnTiles[y][0], &_steppedOnTiles[y][MAP_WIDTH]);
}

void MazeData::setAllTilesStepped() {
	for (int y = 0; y < MAP_HEIGHT; ++y)
		Common::fill(&_steppedOnTiles[y][0], &_steppedOnTiles[y][MAP_WIDTH], true);
}

void MazeData::clearCellSurfaces() {
	for (int y = 0; y < MAP_HEIGHT; ++y) {
		for (int x = 0; x < MAP_WIDTH; ++x)
			_cells[y][x]._surfaceId = 0;
	}
}

/*------------------------------------------------------------------------*/

MobStruct::MobStruct() {
	_id = 0;
	_direction = DIR_NORTH;
}

bool MobStruct::synchronize(XeenSerializer &s) {
	s.syncAsSint8(_pos.x);
	s.syncAsSint8(_pos.y);
	s.syncAsByte(_id);
	s.syncAsByte(_direction);

	return _id != 0xff || _pos.x != -1 || _pos.y != -1;
}

/*------------------------------------------------------------------------*/

MazeObject::MazeObject() {
	_id = 0;
	_frame = 0;
	_spriteId = 0;
	_direction = DIR_NORTH;
	_flipped = false;
	_sprites = nullptr;
}

/*------------------------------------------------------------------------*/

MazeMonster::MazeMonster() {
	_frame = 0;
	_id = 0;
	_spriteId = 0;
	_isAttacking = false;
	_damageType = DT_PHYSICAL;
	_field9 = 0;
	_fieldA = 0;
	_hp = 0;
	_effect1 = _effect2 = 0;
	_effect3 = 0;
	_sprites = nullptr;
	_attackSprites = nullptr;
	_monsterData = nullptr;
}

int MazeMonster::getTextColor() const {
	if (_hp == _monsterData->_hp)
		return 15;
	else if (_hp >= (_monsterData->_hp / 2))
		return 9;
	else
		return 32;
}

/*------------------------------------------------------------------------*/

MazeWallItem::MazeWallItem() {
	_id = 0;
	_frame = 0;
	_spriteId = 0;
	_direction = DIR_NORTH;
	_sprites = nullptr;
}

/*------------------------------------------------------------------------*/

MonsterObjectData::MonsterObjectData(XeenEngine *vm): _vm(vm) {
}

void MonsterObjectData::synchronize(XeenSerializer &s, MonsterData &monsterData) {
	Common::Array<MobStruct> mobStructs;
	MobStruct mobStruct;
	byte b;

	if (s.isLoading()) {
		_objectSprites.clear();
		_monsterSprites.clear();
		_monsterAttackSprites.clear();
		_wallItemSprites.clear();
		_objects.clear();
		_monsters.clear();
		_wallItems.clear();
	}

	for (uint i = 0; i < 16; ++i) {
		b = (i >= _objectSprites.size()) ? 0xff : _objectSprites[i]._spriteId;
		s.syncAsByte(b);
		if (b != 0xff)
			_objectSprites.push_back(SpriteResourceEntry(b));
	}
	for (uint i = 0; i < 16; ++i) {
		b = (i >= _monsterSprites.size()) ? 0xff : _monsterSprites[i]._spriteId;
		s.syncAsByte(b);
		if (b != 0xff)
			_monsterSprites.push_back(SpriteResourceEntry(b));
	}
	for (uint i = 0; i < 16; ++i) {
		b = (i >= _wallItemSprites.size()) ? 0xff : _wallItemSprites[i]._spriteId;
		s.syncAsByte(b);
		if (b != 0xff)
			_wallItemSprites.push_back(SpriteResourceEntry(b));
	}

	if (s.isSaving()) {
		// Save objects
		for (uint i = 0; i < _objects.size(); ++i) {
			mobStruct._pos = _objects[i]._position;
			mobStruct._id = _objects[i]._id;
			mobStruct._direction = _objects[i]._direction;
			mobStruct.synchronize(s);
		}
		mobStruct._pos.x = mobStruct._pos.y = -1;
		mobStruct._id = 0xff;
		mobStruct.synchronize(s);

		// Save monsters
		for (uint i = 0; i < _monsters.size(); ++i) {
			mobStruct._pos = _monsters[i]._position;
			mobStruct._id = _monsters[i]._id;
			mobStruct._direction = DIR_NORTH;
			mobStruct.synchronize(s);
		}
		mobStruct._pos.x = mobStruct._pos.y = -1;
		mobStruct._id = 0xff;
		mobStruct.synchronize(s);

		// Save wall items
		if (_wallItems.size() == 0) {
			MobStruct nullStruct;
			nullStruct.synchronize(s);
		} else {
			for (uint i = 0; i < _wallItems.size(); ++i) {
				mobStruct._pos = _wallItems[i]._position;
				mobStruct._id = _wallItems[i]._id;
				mobStruct._direction = _wallItems[i]._direction;
				mobStruct.synchronize(s);
			}
		}
		mobStruct._pos.x = mobStruct._pos.y = -1;
		mobStruct._id = 0xff;
		mobStruct.synchronize(s);

	} else {
		// Load monster/obbject data and merge together with sprite Ids
		// Merge together object data
		mobStruct.synchronize(s);
		do {
			MazeObject obj;
			obj._position = mobStruct._pos;
			obj._id = mobStruct._id;
			obj._direction = mobStruct._direction;
			obj._frame = 100;
			obj._spriteId = _objectSprites[obj._id]._spriteId;
			obj._sprites = &_objectSprites[obj._id]._sprites;

			_objects.push_back(obj);
			mobStruct.synchronize(s);
		} while (mobStruct._id != 255 || mobStruct._pos.x != -1);

		// Merge together monster data
		mobStruct.synchronize(s);
		do {
			MazeMonster mon;
			mon._position = mobStruct._pos;
			mon._id = mobStruct._id;
			mon._spriteId = _monsterSprites[mon._id]._spriteId;
			mon._sprites = &_monsterSprites[mon._id]._sprites;
			mon._attackSprites = &_monsterSprites[mon._id]._attackSprites;
			mon._monsterData = &monsterData[mon._spriteId];
			mon._frame = _vm->getRandomNumber(7);

			MonsterStruct &md = *mon._monsterData;
			mon._hp = md._hp;
			mon._effect1 = mon._effect2 = md._animationEffect;
			if (md._animationEffect)
				mon._effect3 = _vm->getRandomNumber(7);

			_monsters.push_back(mon);
			mobStruct.synchronize(s);
		} while (mobStruct._id != 255 || mobStruct._pos.x != -1);

		// Merge together wall item data
		mobStruct.synchronize(s);
		do {
			if (mobStruct._id < (int)_wallItemSprites.size()) {
				MazeWallItem wi;
				wi._position = mobStruct._pos;
				wi._id = mobStruct._id;
				wi._direction = mobStruct._direction;
				wi._spriteId = _wallItemSprites[wi._id]._spriteId;
				wi._sprites = &_wallItemSprites[wi._id]._sprites;

				_wallItems.push_back(wi);
			}

			mobStruct.synchronize(s);
		} while (mobStruct._id != 255 || mobStruct._pos.x != -1);
	}
}

/*------------------------------------------------------------------------*/

HeadData::HeadData() {
	for (int y = 0; y < MAP_HEIGHT; ++y) {
		for (int x = 0; x < MAP_WIDTH; ++x) {
			_data[y][x]._left = _data[y][x]._right = 0;
		}
	}
}

void HeadData::synchronize(Common::SeekableReadStream &s) {
	for (int y = 0; y < MAP_HEIGHT; ++y) {
		for (int x = 0; x < MAP_WIDTH; ++x) {
			_data[y][x]._left = s.readByte();
			_data[y][x]._right = s.readByte();
		}
	}
}

/*------------------------------------------------------------------------*/

void AnimationEntry::synchronize(Common::SeekableReadStream &s) {
	for (int i = 0; i < 4; ++i)
		_frame1._frames[i] = s.readByte();
	for (int i = 0; i < 4; ++i)
		_flipped._flags[i] = s.readByte() != 0;
	for (int i = 0; i < 4; ++i)
		_frame2._frames[i] = s.readByte();
}

void AnimationInfo::synchronize(Common::SeekableReadStream &s) {
	AnimationEntry entry;

	clear();
	while (s.pos() < s.size()) {
		entry.synchronize(s);
		push_back(entry);
	}
}

void AnimationInfo::load(const Common::String &name) {
	File f(name);
	synchronize(f);
	f.close();
}

/*------------------------------------------------------------------------*/

Map::Map(XeenEngine *vm) : _vm(vm), _mobData(vm) {
	_loadDarkSide = false;
	_sideTownPortal = 0;
	_sideObjects = 0;
	_sideMonsters = 0;
	_sidePictures = 0;
	_isOutdoors = false;
	_mazeDataIndex = 0;
	_currentSteppedOn = false;
	_currentSurfaceId = 0;
	_currentWall = 0;
	_currentTile = 0;
	_currentGrateUnlocked = false;
	_currentCantRest = false;
	_currentIsDrain = false;
	_currentIsEvent = false;
	_currentSky = 0;
	_currentMonsterFlags = 0;
}

void Map::load(int mapId) {
	Interface &intf = *_vm->_interface;
	Screen &screen = *_vm->_screen;
	IndoorDrawList &indoorList = _vm->_interface->_indoorList;
	OutdoorDrawList &outdoorList = _vm->_interface->_outdoorList;

	if (intf._falling) {
		Window &w = screen._windows[9];
		w.open();
		w.writeString(Res.OOPS);
	} else {
		PleaseWait::show(_vm);
	}

	_vm->_party->_stepped = true;
	_vm->_party->_mazeId = mapId;
	_vm->_events->clearEvents();

	_sideObjects = 1;
	_sideMonsters = 1;
	_sidePictures = 1;
	if (mapId >= 113 && mapId <= 127) {
		_sideTownPortal = 0;
	} else {
		_sideTownPortal = _loadDarkSide ? 1 : 0;
	}

	if (_vm->getGameID() == GType_WorldOfXeen) {
		if (!_loadDarkSide) {
			_animationInfo.load("clouds.dat");
			_monsterData.load("xeen.mon");
			_wallPicSprites.load("xeenpic.dat");
			_sidePictures = 0;
			_sideMonsters = 0;
			_sideObjects = 0;
		} else {
			switch (mapId) {
			case 113:
			case 114:
			case 115:
			case 116:
			case 128:
				_animationInfo.load("clouds.dat");
				_monsterData.load("dark.mon");
				_wallPicSprites.load("darkpic.dat");
				_sideObjects = 0;
				break;
			case 117:
			case 118:
			case 119:
			case 120:
			case 124:
				_animationInfo.load("clouds.dat");
				_monsterData.load("xeen.mon");
				_wallPicSprites.load("darkpic.dat");
				_sideObjects = 0;
				_sideMonsters = 0;
				break;
			case 125:
			case 126:
			case 127:
				_animationInfo.load("clouds.dat");
				_monsterData.load("dark.mon");
				_wallPicSprites.load("xeenpic.dat");
				_sideObjects = 0;
				_sidePictures = 0;
				break;
			default:
				_animationInfo.load("dark.dat");
				_monsterData.load("ddark.mon");
				_wallPicSprites.load("darkpic.dat");
				break;
			}
		}
	}

	// Load any events for the new map
	loadEvents(mapId);

	// Iterate through loading the given maze as well as the two successive
	// mazes in each of the four cardinal directions
	bool isDarkCc = _vm->getGameID() == GType_DarkSide;
	MazeData *mazeDataP = &_mazeData[0];
	bool textLoaded = false;

	for (int idx = 0; idx < 9; ++idx, ++mazeDataP) {
		mazeDataP->_mazeId = mapId;

		if (mapId == 0) {
			mazeDataP->clear();
		} else {
			// Load in the maze's data file
			Common::String datName = Common::String::format("maze%c%03d.dat",
				(mapId >= 100) ? 'x' : '0', mapId);
			File datFile(datName);
			mazeDataP->synchronize(datFile);
			datFile.close();

			if (isDarkCc && mapId == 50)
				mazeDataP->setAllTilesStepped();
			if (!isDarkCc && _vm->_party->_gameFlags[25] &&
					(mapId == 42 || mapId == 43 || mapId == 4)) {
				mazeDataP->clearCellSurfaces();
			}

			_isOutdoors = (mazeDataP->_mazeFlags2 & FLAG_IS_OUTDOORS) != 0;

			// Handle loading text data
			if (!textLoaded) {
				textLoaded = true;
				Common::String txtName = Common::String::format("%s%c%03d.txt",
					isDarkCc ? "dark" : "xeen", mapId >= 100 ? 'x' : '0', mapId);
				File fText(txtName);
				char mazeName[33];
				fText.read(mazeName, 33);
				mazeName[32] = '\0';

				_mazeName = Common::String(mazeName);
				fText.close();

				// Load the monster/object data
				Common::String mobName = Common::String::format("maze%c%03d.mob",
					(mapId >= 100) ? 'x' : '0', mapId);
				File mobFile(mobName);
				XeenSerializer sMob(&mobFile, nullptr);
				_mobData.synchronize(sMob, _monsterData);
				mobFile.close();

				Common::String headName = Common::String::format("aaze%c%03d.hed",
					(mapId >= 100) ? 'x' : '0', mapId);
				File headFile(headName);
				_headData.synchronize(headFile);
				headFile.close();

				if (!isDarkCc && _vm->_party->_mazeId)
					_mobData._monsters.clear();

				if (!isDarkCc && mapId == 15) {
					if ((_mobData._monsters[0]._position.x > 31 || _mobData._monsters[0]._position.y > 31) &&
						(_mobData._monsters[1]._position.x > 31 || _mobData._monsters[1]._position.y > 31) &&
						(_mobData._monsters[2]._position.x > 31 || _mobData._monsters[2]._position.y > 31)) {
						_vm->_party->_gameFlags[56] = true;
					}
				}
			}
		}

		// Move to next surrounding maze
		MazeData *baseMaze = &_mazeData[MAP_GRID_PRIOR_INDEX[idx]];
		mapId = baseMaze->_surroundingMazes[MAP_GRID_PRIOR_DIRECTION[idx]];
		if (!mapId) {
			baseMaze = &_mazeData[MAP_GRID_PRIOR_INDEX2[idx]];
			mapId = baseMaze->_surroundingMazes[MAP_GRID_PRIOR_DIRECTION2[idx]];
		}
	}

	// TODO: Switch setting flags that don't seem to ever be used

	// Reload the monster data for the main maze that we're loading
	mapId = _vm->_party->_mazeId;
	Common::String filename = Common::String::format("maze%c%03d.mob",
		(mapId >= 100) ? 'x' : '0', mapId);
	File mobFile(filename, *_vm->_saves);
	XeenSerializer sMob(&mobFile, nullptr);
	_mobData.synchronize(sMob, _monsterData);
	mobFile.close();

	// Load sprites for the objects
	for (uint i = 0; i < _mobData._objectSprites.size(); ++i) {
		if (_vm->_party->_cloudsEnd && _mobData._objectSprites[i]._spriteId == 85 &&
				mapId == 27 && isDarkCc) {
			_mobData._objects[29]._spriteId = 0;
			_mobData._objects[29]._id = 8;
			_mobData._objectSprites[i]._sprites.clear();
		} else if (mapId == 12 && _vm->_party->_gameFlags[43] &&
			_mobData._objectSprites[i]._spriteId == 118 && !isDarkCc) {
			filename = "085.obj";
			_mobData._objectSprites[0]._spriteId = 85;
		} else {
			filename = Common::String::format("%03d.%cbj",
				_mobData._objectSprites[i]._spriteId,
				_mobData._objectSprites[i]._spriteId >= 100 ? '0' : 'o');
		}

		// Read in the object sprites
		_mobData._objectSprites[i]._sprites.load(filename,
			_sideObjects ? ALTSIDE_ARCHIVE : GAME_ARCHIVE);
	}

	// Load sprites for the monsters
	for (uint i = 0; i < _mobData._monsterSprites.size(); ++i) {
		ArchiveType archiveType =
			_mobData._monsterSprites[i]._spriteId == 91 && _vm->getGameID() == GType_WorldOfXeen ?
			ALTSIDE_ARCHIVE : GAME_ARCHIVE;

		filename = Common::String::format("%03d.mon", _mobData._monsterSprites[i]._spriteId);
		_mobData._monsterSprites[i]._sprites.load(filename, archiveType);

		filename = Common::String::format("%03d.att", _mobData._monsterSprites[i]._spriteId);
		_mobData._monsterSprites[i]._attackSprites.load(filename, archiveType);
	}

	// Load wall picture sprite resources
	for (uint i = 0; i < _mobData._wallItemSprites.size(); ++i) {
		filename = Common::String::format("%03d.pic", _mobData._wallItems[i]._spriteId);
		_mobData._wallItemSprites[i]._sprites.load(filename,
			_sidePictures ? ALTSIDE_ARCHIVE : GAME_ARCHIVE);
	}

	// Handle loading miscellaneous sprites for the map
	if (_isOutdoors) {
		warning("TODO");	// Sound loading

		_groundSprites.load("water.out");
		_tileSprites.load("outdoor.til");
		outdoorList._sky1._sprites = &_skySprites[0];
		outdoorList._sky2._sprites = &_skySprites[0];
		outdoorList._groundSprite._sprites = &_groundSprites;

		for (int i = 0; i < TOTAL_SURFACES; ++i) {
			_wallSprites._surfaces[i].clear();

			if (_mazeData[0]._wallTypes[i] != 0) {
				_wallSprites._surfaces[i].load(Common::String::format("%s.wal",
					Res.SURFACE_TYPE_NAMES[_mazeData[0]._wallTypes[i]]));
			}

			_surfaceSprites[i].clear();
			if (i != 0 && _mazeData[0]._surfaceTypes[i] != 0)
				_surfaceSprites[i].load(Res.SURFACE_NAMES[_mazeData[0]._surfaceTypes[i]]);
		}
	} else {
		warning("TODO");	// Sound loading

		_skySprites[1].load(Common::String::format("%s.sky",
			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]));
		_groundSprites.load(Common::String::format("%s.gnd",
			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]));
		_tileSprites.load(Common::String::format("%s.til",
			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]));

		for (int i = 0; i < TOTAL_SURFACES; ++i) {
			_surfaceSprites[i].clear();

			if (_mazeData[0]._surfaceTypes[i] != 0 || i == 4)
				_surfaceSprites[i].load(Res.SURFACE_NAMES[_mazeData[0]._surfaceTypes[i]]);
		}

		for (int i = 0; i < TOTAL_SURFACES; ++i)
			_wallSprites._surfaces[i].clear();

		_wallSprites._fwl1.load(Common::String::format("f%s1.fwl",
			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]));
		_wallSprites._fwl2.load(Common::String::format("f%s2.fwl",
			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]));
		_wallSprites._fwl3.load(Common::String::format("f%s3.fwl",
			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]));
		_wallSprites._fwl4.load(Common::String::format("f%s4.fwl",
			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]));
		_wallSprites._swl.load(Common::String::format("s%s.swl",
			Res.TERRAIN_TYPES[_mazeData[0]._wallKind]));

		// Set entries in the indoor draw list to the correct sprites
		// for drawing various parts of the background
		indoorList._swl_0F1R._sprites = &_wallSprites._swl;
		indoorList._swl_0F1L._sprites = &_wallSprites._swl;
		indoorList._swl_1F1R._sprites = &_wallSprites._swl;
		indoorList._swl_1F1L._sprites = &_wallSprites._swl;
		indoorList._swl_2F2R._sprites = &_wallSprites._swl;
		indoorList._swl_2F1R._sprites = &_wallSprites._swl;
		indoorList._swl_2F1L._sprites = &_wallSprites._swl;
		indoorList._swl_2F2L._sprites = &_wallSprites._swl;

		indoorList._swl_3F1R._sprites = &_wallSprites._swl;
		indoorList._swl_3F2R._sprites = &_wallSprites._swl;
		indoorList._swl_3F3R._sprites = &_wallSprites._swl;
		indoorList._swl_3F4R._sprites = &_wallSprites._swl;
		indoorList._swl_3F1L._sprites = &_wallSprites._swl;
		indoorList._swl_3F2L._sprites = &_wallSprites._swl;
		indoorList._swl_3F3L._sprites = &_wallSprites._swl;
		indoorList._swl_3F4L._sprites = &_wallSprites._swl;

		indoorList._swl_4F4R._sprites = &_wallSprites._swl;
		indoorList._swl_4F3R._sprites = &_wallSprites._swl;
		indoorList._swl_4F2R._sprites = &_wallSprites._swl;
		indoorList._swl_4F1R._sprites = &_wallSprites._swl;
		indoorList._swl_4F1L._sprites = &_wallSprites._swl;
		indoorList._swl_4F2L._sprites = &_wallSprites._swl;
		indoorList._swl_4F3L._sprites = &_wallSprites._swl;
		indoorList._swl_4F4L._sprites = &_wallSprites._swl;

		indoorList._fwl_4F4R._sprites = &_wallSprites._fwl4;
		indoorList._fwl_4F3R._sprites = &_wallSprites._fwl4;
		indoorList._fwl_4F2R._sprites = &_wallSprites._fwl4;
		indoorList._fwl_4F1R._sprites = &_wallSprites._fwl4;
		indoorList._fwl_4F._sprites = &_wallSprites._fwl4;
		indoorList._fwl_4F1L._sprites = &_wallSprites._fwl4;
		indoorList._fwl_4F2L._sprites = &_wallSprites._fwl4;
		indoorList._fwl_4F3L._sprites = &_wallSprites._fwl4;
		indoorList._fwl_4F4L._sprites = &_wallSprites._fwl4;

		indoorList._fwl_2F1R._sprites = &_wallSprites._fwl3;
		indoorList._fwl_2F._sprites = &_wallSprites._fwl3;
		indoorList._fwl_2F1L._sprites = &_wallSprites._fwl3;
		indoorList._fwl_3F2R._sprites = &_wallSprites._fwl3;
		indoorList._fwl_3F1R._sprites = &_wallSprites._fwl3;
		indoorList._fwl_3F._sprites = &_wallSprites._fwl3;
		indoorList._fwl_3F1L._sprites = &_wallSprites._fwl3;
		indoorList._fwl_3F2L._sprites = &_wallSprites._fwl3;

		indoorList._fwl_1F._sprites = &_wallSprites._fwl1;
		indoorList._fwl_1F1R._sprites = &_wallSprites._fwl1;
		indoorList._fwl_1F1L._sprites = &_wallSprites._fwl1;
		indoorList._horizon._sprites = &_wallSprites._fwl1;

		indoorList._ground._sprites = &_groundSprites;

		// Don't show horizon for certain maps
		if (_vm->_files->_isDarkCc) {
			if ((mapId >= 89 && mapId <= 112) || mapId == 128 || mapId == 129)
				indoorList._horizon._sprites = nullptr;
		} else {
			if (mapId >= 25 && mapId <= 27)
				indoorList._horizon._sprites = nullptr;
		}
	}

	loadSky();
}

int Map::mazeLookup(const Common::Point &pt, int layerShift, int wallMask) {
	Common::Point pos = pt;
	int mapId = _vm->_party->_mazeId;

	if (pt.x < -16 || pt.y < -16 || pt.x >= 32 || pt.y >= 32)
		error("Invalid coordinate");

	// Find the correct maze data out of the set to use
	_mazeDataIndex = 0;
	while (_mazeData[_mazeDataIndex]._mazeId != _vm->_party->_mazeId)
		++_mazeDataIndex;

	// Handle map changing to the north or south as necessary
	if (pos.y & 16) {
		if (pos.y >= 0) {
			pos.y -= 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
		} else {
			pos.y += 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
		}

		if (mapId) {
			// Move to the correct map to north/south
			_mazeDataIndex = 0;
			while (_mazeData[_mazeDataIndex]._mazeId != mapId)
				++_mazeDataIndex;
		} else {
			// No map, so reached outside indoor area or outer space outdoors
			_currentSteppedOn = true;
			return _isOutdoors ? SURFTYPE_SPACE : INVALID_CELL;
		}
	}

	// Handle map changing to the east or west as necessary
	if (pos.x & 16) {
		if (pos.x >= 0) {
			pos.x -= 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
		} else {
			pos.x += 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._west;
		}

		if (mapId) {
			_mazeDataIndex = 0;
			while (_mazeData[_mazeDataIndex]._mazeId != mapId)
				++_mazeDataIndex;
		}
	}

	if (mapId) {
		if (_isOutdoors) {
			_currentSurfaceId = _mazeData[_mazeDataIndex]._wallData[pos.y][pos.x]._outdoors._surfaceId;
		} else {
			_currentSurfaceId = _mazeData[_mazeDataIndex]._cells[pos.y][pos.x]._surfaceId;
		}

		if (_currentSurfaceId == SURFTYPE_SPACE || _currentSurfaceId == SURFTYPE_SKY) {
			_currentSteppedOn = true;
		} else {
			_currentSteppedOn = _mazeData[_mazeDataIndex]._steppedOnTiles[pos.y][pos.x];
		}

		return (_mazeData[_mazeDataIndex]._wallData[pos.y][pos.x]._data >> layerShift) & wallMask;

	} else {
		_currentSteppedOn = _isOutdoors;
		return _isOutdoors ? SURFTYPE_SPACE : INVALID_CELL;
	}
}

void Map::loadEvents(int mapId) {
	// Load events
	Common::String filename = Common::String::format("maze%c%03d.evt",
		(mapId >= 100) ? 'x' : '0', mapId);
	File fEvents(filename, *_vm->_saves);
	XeenSerializer sEvents(&fEvents, nullptr);
	_events.synchronize(sEvents);
	fEvents.close();

	// Load text data
	filename = Common::String::format("aaze%c%03d.txt",
		(mapId >= 100) ? 'x' : '0', mapId);
	File fText(filename);
	_events._text.clear();
	while (fText.pos() < fText.size())
		_events._text.push_back(fText.readString());
	fText.close();
}

void Map::saveMaze() {
	int mazeNum = _mazeData[0]._mazeNumber;
	if (!mazeNum || (mazeNum == 85 && !_vm->_files->_isDarkCc))
		return;

	// Save the event data
	Common::String filename = Common::String::format("maze%c%03d.evt",
		(mazeNum >= 100) ? 'x' : '0', mazeNum);
	OutFile fEvents(_vm, filename);
	XeenSerializer sEvents(nullptr, &fEvents);
	_events.synchronize(sEvents);
	fEvents.finalize();

	// Save the maze MOB file
	filename = Common::String::format("maze%c%03d.mob",
		(mazeNum >= 100) ? 'x' : '0', mazeNum);
	OutFile fMob(_vm, filename);
	XeenSerializer sMob(nullptr, &fEvents);
	_mobData.synchronize(sMob, _monsterData);
	fEvents.finalize();
}

void Map::cellFlagLookup(const Common::Point &pt) {
	Common::Point pos = pt;
	int mapId = _vm->_party->_mazeId;
	_mazeDataIndex = 0;
	while (_mazeData[_mazeDataIndex]._mazeId != mapId)
		++_mazeDataIndex;

	// Handle map changing to the north or south as necessary
	if (pos.y & 16) {
		if (pos.y >= 0) {
			pos.y -= 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
		} else {
			pos.y += 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
		}

		_mazeDataIndex = 0;
		while (_mazeData[_mazeDataIndex]._mazeId != mapId)
			++_mazeDataIndex;
	}

	// Handle map changing to the east or west as necessary
	if (pos.x & 16) {
		if (pos.x >= 0) {
			pos.x -= 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
		} else {
			pos.x += 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._west;
		}

		_mazeDataIndex = 0;
		while (_mazeData[_mazeDataIndex]._mazeId != mapId)
			++_mazeDataIndex;
	}

	// Get the cell flags
	const MazeCell &cell = _mazeData[_mazeDataIndex]._cells[pos.y][pos.x];
	_currentGrateUnlocked = cell._flags & OUTFLAG_GRATE;
	_currentCantRest = cell._flags & RESTRICTION_REST;
	_currentIsDrain = cell._flags & OUTFLAG_DRAIN;
	_currentIsEvent = cell._flags & FLAG_AUTOEXECUTE_EVENT;
	_currentSky = (cell._flags & OUTFLAG_OBJECT_EXISTS) ? 1 : 0;
	_currentMonsterFlags = cell._flags & 7;
}

void Map::setCellSurfaceFlags(const Common::Point &pt, int bits) {
	mazeLookup(pt, 0);

	Common::Point mapPos(pt.x & 15, pt.y & 15);
	MazeCell &cell = _mazeData[_mazeDataIndex]._cells[mapPos.y][mapPos.x];
	cell._flags |= bits & 0xF8;
}

void Map::setWall(const Common::Point &pt, Direction dir, int v) {
	const int XOR_MASKS[4] = { 0xFFF, 0xF0FF, 0xFF0F, 0xFFF0 };
	mazeLookup(pt, 0, 0);

	Common::Point mapPos(pt.x & 15, pt.y & 15);
	MazeWallLayers &wallLayer = _mazeData[_mazeDataIndex]._wallData[mapPos.y][mapPos.x];
	wallLayer._data &= XOR_MASKS[dir];
	wallLayer._data |= v << Res.WALL_SHIFTS[dir][2];
}

int Map::getCell(int idx) {
	int mapId = _vm->_party->_mazeId;
	Direction dir = _vm->_party->_mazeDirection;
	Common::Point pt(
		_vm->_party->_mazePosition.x + Res.SCREEN_POSITIONING_X[_vm->_party->_mazeDirection][idx],
		_vm->_party->_mazePosition.y + Res.SCREEN_POSITIONING_Y[_vm->_party->_mazeDirection][idx]
	);

	if (pt.x > 31 || pt.y > 31) {
		if (_vm->_files->_isDarkCc) {
			if ((mapId >= 53 && mapId <= 88 && mapId != 73) || (mapId >= 74 && mapId <= 120) ||
					mapId == 125 || mapId == 126 || mapId == 128 || mapId == 129) {
				_currentSurfaceId = SURFTYPE_DESERT;
			} else {
				_currentSurfaceId = 0;
			}
		} else {
			_currentSurfaceId = (mapId >= 25 && mapId <= 27) ? 7 : 0;
		}
		_currentWall = INVALID_CELL;
		return INVALID_CELL;
	}

	_mazeDataIndex = 0;
	while (_mazeData[_mazeDataIndex]._mazeId != mapId)
		++_mazeDataIndex;

	if (pt.y & 16) {
		if (pt.y >= 0) {
			pt.y -= 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
		} else {
			pt.y += 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
		}

		if (!mapId) {
			if (_isOutdoors) {
				_currentSurfaceId = SURFTYPE_SPACE;
				_currentWall = 0;
				return 0;
			} else {
				if (_vm->_files->_isDarkCc) {
					if ((mapId >= 53 && mapId <= 88 && mapId != 73) || (mapId >= 74 && mapId <= 120) ||
						mapId == 125 || mapId == 126 || mapId == 128 || mapId == 129) {
						_currentSurfaceId = 6;
					} else {
						_currentSurfaceId = 0;
					}
				} else {
					_currentSurfaceId = (mapId >= 25 && mapId <= 27) ? SURFTYPE_ROAD : SURFTYPE_DEFAULT;
				}

				_currentWall = INVALID_CELL;
				return INVALID_CELL;
			}

			_mazeDataIndex = 0;
			while (_mazeData[_mazeDataIndex]._mazeId != mapId)
				++_mazeDataIndex;
		}
	}

	if (pt.x & 16) {
		if (pt.x >= 0) {
			pt.x -= 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
		} else {
			pt.x += 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
		}

		if (!mapId) {
			if (_isOutdoors) {
				_currentSurfaceId = SURFTYPE_SPACE;
				_currentWall = 0;
				return 0;
			} else {
				if (_vm->_files->_isDarkCc) {
					if ((mapId >= 53 && mapId <= 88 && mapId != 73) || (mapId >= 74 && mapId <= 120) ||
						mapId == 125 || mapId == 126 || mapId == 128 || mapId == 129) {
						_currentSurfaceId = 6;
					} else {
						_currentSurfaceId = 0;
					}
				} else {
					_currentSurfaceId = (mapId >= 25 && mapId <= 27) ? SURFTYPE_ROAD : SURFTYPE_DEFAULT;
				}

				_currentWall = INVALID_CELL;
				return INVALID_CELL;
			}
		}

		_mazeDataIndex = 0;
		while (_mazeData[_mazeDataIndex]._mazeId != mapId)
			++_mazeDataIndex;
	}

	int wallData = _mazeData[_mazeDataIndex]._wallData[pt.y][pt.x]._data;
	if (_isOutdoors) {
		if (mapId) {
			// TODO: tile is set to word of (wallLayers >> 8) && 0xff? Makes no sense
			_currentTile = (wallData >> 8) & 0xFF;
			_currentWall = (wallData >> 4) & 0xF;
			_currentSurfaceId = wallData & 0xF;
		} else {
			_currentSurfaceId = SURFTYPE_DEFAULT;
			_currentWall = 0;
			_currentTile = 0;
		}
	} else {
		if (!mapId)
			return 0;

		if (pt.x > 31 || pt.y > 31)
			_currentSurfaceId = 7;
		else
			_currentSurfaceId = _mazeData[_mazeDataIndex]._cells[pt.y][pt.x]._surfaceId;

		_currentWall = wallData;
		return (_currentWall >> Res.WALL_SHIFTS[dir][idx]) & 0xF;
	}

	return _currentWall;
}

void Map::loadSky() {
	Party &party = *_vm->_party;

	party._isNight = party._minutes < (5 * 60) || party._minutes >= (21 * 60);
	_skySprites[0].load(((party._mazeId >= 89 && party._mazeId <= 112) ||
		party._mazeId == 128 || party._mazeId == 129) || !party._isNight
		? "sky.sky" : "night.sky");
}

void Map::getNewMaze() {
	Party &party = *_vm->_party;
	Common::Point pt = party._mazePosition;
	int mapId = party._mazeId;

	// Get the correct map to use from the cached list
	_mazeDataIndex = 0;
	while (_mazeData[_mazeDataIndex]._mazeId == mapId)
		++_mazeDataIndex;

	// Adjust Y and X to be in the 0-15 range, and on the correct surrounding
	// map if either value is < 0 or >= 16
	if (pt.y & 16) {
		if (pt.y >= 0) {
			pt.y -= 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
		} else {
			pt.y += 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
		}

		if (mapId) {
			_mazeDataIndex = 0;
			while (_mazeData[_mazeDataIndex]._mazeId == mapId)
				++_mazeDataIndex;
		}
	}

	if (pt.x & 16) {
		if (pt.x >= 0) {
			pt.x -= 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
		} else {
			pt.x += 16;
			mapId = _mazeData[_mazeDataIndex]._surroundingMazes._west;
		}

		if (mapId) {
			_mazeDataIndex = 0;
			while (_mazeData[_mazeDataIndex]._mazeId == mapId)
				++_mazeDataIndex;
		}
	}

	// Save the adjusted (0,0)-(15,15) position and load the given map.
	// This will make it the new center, with it's own surrounding mazees loaded
	party._mazePosition = pt;
	if (mapId)
		load(mapId);
}

} // End of namespace Xeen