/* 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 "xeen/interface.h"
#include "xeen/dialogs_automap.h"
#include "xeen/dialogs_char_info.h"
#include "xeen/dialogs_control_panel.h"
#include "xeen/dialogs_error.h"
#include "xeen/dialogs_fight_options.h"
#include "xeen/dialogs_info.h"
#include "xeen/dialogs_items.h"
#include "xeen/dialogs_query.h"
#include "xeen/dialogs_quests.h"
#include "xeen/dialogs_quick_ref.h"
#include "xeen/dialogs_spells.h"
#include "xeen/resources.h"
#include "xeen/xeen.h"

#include "xeen/dialogs_party.h"

namespace Xeen {

PartyDrawer::PartyDrawer(XeenEngine *vm): _vm(vm) {
	_restoreSprites.load("restorex.icn");
	_hpSprites.load("hpbars.icn");
	_dseFace.load("dse.fac");
	_hiliteChar = -1;
}

void PartyDrawer::drawParty(bool updateFlag) {
	Combat &combat = *_vm->_combat;
	Party &party = *_vm->_party;
	Resources &res = *_vm->_resources;
	Screen &screen = *_vm->_screen;
	bool inCombat = _vm->_mode == MODE_COMBAT;
	_restoreSprites.draw(screen, 0, Common::Point(8, 149));

	// Handle drawing the party faces
	uint partyCount = inCombat ? combat._combatParty.size() : party._activeParty.size();
	for (uint idx = 0; idx < partyCount; ++idx) {
		Character &ps = inCombat ? *combat._combatParty[idx] : party._activeParty[idx];
		Condition charCondition = ps.worstCondition();
		int charFrame = Res.FACE_CONDITION_FRAMES[charCondition];

		SpriteResource *sprites = (charFrame > 4) ? &_dseFace : ps._faceSprites;
		if (charFrame > 4)
			charFrame -= 5;

		sprites->draw(screen, charFrame, Common::Point(Res.CHAR_FACES_X[idx], 150));
	}

	for (uint idx = 0; idx < partyCount; ++idx) {
		Character &ps = inCombat ? *combat._combatParty[idx] : party._activeParty[idx];

		// Draw the Hp bar
		int maxHp = ps.getMaxHP();
		int frame;
		if (ps._currentHp < 1)
			frame = 4;
		else if (ps._currentHp > maxHp)
			frame = 3;
		else if (ps._currentHp == maxHp)
			frame = 0;
		else if (ps._currentHp < (maxHp / 4))
			frame = 2;
		else
			frame = 1;

		_hpSprites.draw(screen, frame, Common::Point(Res.HP_BARS_X[idx], 182));
	}

	if (_hiliteChar != -1)
		res._globalSprites.draw(screen, 8, Common::Point(Res.CHAR_FACES_X[_hiliteChar] - 1, 149));

	if (updateFlag)
		screen._windows[33].update();
}

void PartyDrawer::highlightChar(int charId) {
	Resources &res = *_vm->_resources;
	Screen &screen = *_vm->_screen;

	if (charId != _hiliteChar && _hiliteChar != HILIGHT_CHAR_DISABLED) {
		// Handle deselecting any previusly selected char
		if (_hiliteChar != -1) {
			res._globalSprites.draw(screen, 9 + _hiliteChar,
				Common::Point(Res.CHAR_FACES_X[_hiliteChar] - 1, 149));
		}

		// Highlight new character
		res._globalSprites.draw(screen, 8, Common::Point(Res.CHAR_FACES_X[charId] - 1, 149));
		_hiliteChar = charId;
		screen._windows[33].update();
	}
}

void PartyDrawer::unhighlightChar() {
	Resources &res = *_vm->_resources;
	Screen &screen = *_vm->_screen;

	if (_hiliteChar != -1) {
		res._globalSprites.draw(screen, _hiliteChar + 9,
			Common::Point(Res.CHAR_FACES_X[_hiliteChar] - 1, 149));
		_hiliteChar = -1;
		screen._windows[33].update();
	}
}

void PartyDrawer::resetHighlight() {
	_hiliteChar = -1;
}
/*------------------------------------------------------------------------*/

Interface::Interface(XeenEngine *vm) : ButtonContainer(vm), InterfaceMap(vm),
		PartyDrawer(vm), _vm(vm) {
	_buttonsLoaded = false;
	_intrIndex1 = 0;
	_steppingFX = 0;
	_falling = false;
	_blessedUIFrame = 0;
	_powerShieldUIFrame = 0;
	_holyBonusUIFrame = 0;
	_heroismUIFrame = 0;
	_flipUIFrame = 0;
	_face1UIFrame = 0;
	_face2UIFrame = 0;
	_levitateUIFrame = 0;
	_spotDoorsUIFrame = 0;
	_dangerSenseUIFrame = 0;
	_face1State = _face2State = 0;
	_upDoorText = false;
	_tillMove = 0;
	Common::fill(&_charFX[0], &_charFX[MAX_ACTIVE_PARTY], 0);

	initDrawStructs();
}

void Interface::initDrawStructs() {
	_mainList[0] = DrawStruct(7, 232, 74);
	_mainList[1] = DrawStruct(0, 235, 75);
	_mainList[2] = DrawStruct(2, 260, 75);
	_mainList[3] = DrawStruct(4, 286, 75);
	_mainList[4] = DrawStruct(6, 235, 96);
	_mainList[5] = DrawStruct(8, 260, 96);
	_mainList[6] = DrawStruct(10, 286, 96);
	_mainList[7] = DrawStruct(12, 235, 117);
	_mainList[8] = DrawStruct(14, 260, 117);
	_mainList[9] = DrawStruct(16, 286, 117);
	_mainList[10] = DrawStruct(20, 235, 148);
	_mainList[11] = DrawStruct(22, 260, 148);
	_mainList[12] = DrawStruct(24, 286, 148);
	_mainList[13] = DrawStruct(26, 235, 169);
	_mainList[14] = DrawStruct(28, 260, 169);
	_mainList[15] = DrawStruct(30, 286, 169);
}

void Interface::setup() {
	_borderSprites.load("border.icn");
	_spellFxSprites.load("spellfx.icn");
	_fecpSprites.load("fecp.brd");
	_blessSprites.load("bless.icn");
	_charPowSprites.load("charpow.icn");
	_uiSprites.load("inn.icn");

	Party &party = *_vm->_party;
	party.loadActiveParty();
	party._newDay = party._minutes < 300;
}

void Interface::startup() {
	Resources &res = *_vm->_resources;
	Screen &screen = *_vm->_screen;
	_iconSprites.load("main.icn");

	animate3d();
	if (_vm->_map->_isOutdoors) {
		setIndoorsMonsters();
		setIndoorsObjects();
	} else {
		setOutdoorsMonsters();
		setOutdoorsObjects();
	}
	draw3d(false);

	res._globalSprites.draw(screen._windows[1], 5, Common::Point(232, 9));
	drawParty(false);

	_mainList[0]._sprites = &res._globalSprites;
	for (int i = 1; i < 16; ++i)
		_mainList[i]._sprites = &_iconSprites;

	setMainButtons();

	_tillMove = false;
}

void Interface::mainIconsPrint() {
	Screen &screen = *_vm->_screen;
	screen._windows[38].close();
	screen._windows[12].close();
	screen._windows[0].drawList(_mainList, 16);
	screen._windows[34].update();
}

void Interface::setMainButtons(bool combatMode) {
	clearButtons();

	addButton(Common::Rect(235,  75, 259,  95),  Common::KEYCODE_s, &_iconSprites);
	addButton(Common::Rect(260,  75, 284,  95),  Common::KEYCODE_c, &_iconSprites);
	addButton(Common::Rect(286,  75, 310,  95),  Common::KEYCODE_r, &_iconSprites);
	addButton(Common::Rect(235,  96, 259, 116),  Common::KEYCODE_b, &_iconSprites);
	addButton(Common::Rect(260,  96, 284, 116),  Common::KEYCODE_d, &_iconSprites);
	addButton(Common::Rect(286,  96, 310, 116),  Common::KEYCODE_v, &_iconSprites);
	addButton(Common::Rect(235, 117, 259, 137),  Common::KEYCODE_m, &_iconSprites);
	addButton(Common::Rect(260, 117, 284, 137),  Common::KEYCODE_i, &_iconSprites);
	addButton(Common::Rect(286, 117, 310, 137),  Common::KEYCODE_q, &_iconSprites);
	addButton(Common::Rect(109, 137, 122, 147), Common::KEYCODE_TAB, &_iconSprites);
	addButton(Common::Rect(235, 148, 259, 168), Common::KEYCODE_LEFT, &_iconSprites);
	addButton(Common::Rect(260, 148, 284, 168), Common::KEYCODE_UP, &_iconSprites);
	addButton(Common::Rect(286, 148, 310, 168), Common::KEYCODE_RIGHT, &_iconSprites);
	addButton(Common::Rect(235, 169, 259, 189), (Common::KBD_CTRL << 16) |Common::KEYCODE_LEFT, &_iconSprites);
	addButton(Common::Rect(260, 169, 284, 189), Common::KEYCODE_DOWN, &_iconSprites);
	addButton(Common::Rect(286, 169, 310, 189), (Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT, &_iconSprites);
	addButton(Common::Rect(236,  11, 308,  69),  Common::KEYCODE_EQUALS);
	addButton(Common::Rect(239,  27, 312,  37),  Common::KEYCODE_1);
	addButton(Common::Rect(239, 37, 312, 47), Common::KEYCODE_2);
	addButton(Common::Rect(239, 47, 312, 57), Common::KEYCODE_3);
	addPartyButtons(_vm);

	if (combatMode) {
		_buttons[0]._value = Common::KEYCODE_f;
		_buttons[1]._value = Common::KEYCODE_c;
		_buttons[2]._value = Common::KEYCODE_a;
		_buttons[3]._value = Common::KEYCODE_u;
		_buttons[4]._value = Common::KEYCODE_r;
		_buttons[5]._value = Common::KEYCODE_b;
		_buttons[6]._value = Common::KEYCODE_o;
		_buttons[7]._value = Common::KEYCODE_i;
		_buttons[16]._value = 0;
	}
}

void Interface::perform() {
	Combat &combat = *_vm->_combat;
	EventsManager &events = *_vm->_events;
	Map &map = *_vm->_map;
	Party &party = *_vm->_party;
	Scripts &scripts = *_vm->_scripts;
	Sound &sound = *_vm->_sound;
	Spells &spells = *_vm->_spells;
	const Common::Rect WAIT_BOUNDS(8, 8, 224, 140);

	events.updateGameCounter();
	draw3d(true);

	// Wait for a frame or a user event
	do {
		events.pollEventsAndWait();
		checkEvents(_vm);

		if (events._leftButton && WAIT_BOUNDS.contains(events._mousePos))
			_buttonValue = Common::KEYCODE_SPACE;
	} while (!_buttonValue && events.timeElapsed() < 1 && !_vm->_party->_partyDead);

	if (!_buttonValue && !_vm->_party->_partyDead)
		return;

	if (_buttonValue == Common::KEYCODE_SPACE) {
		int lookupId = map.mazeLookup(party._mazePosition,
			Res.WALL_SHIFTS[party._mazeDirection][2]);

		bool eventsFlag = true;
		switch (lookupId) {
		case 1:
			if (!map._isOutdoors) {
				scripts.openGrate(13, 1);
				eventsFlag = _buttonValue != 0;
			}

		case 6:
			// Open grate being closed
			if (!map._isOutdoors) {
				scripts.openGrate(9, 0);
				eventsFlag = _buttonValue != 0;
			}
			break;
		case 9:
			// Closed grate being opened
			if (!map._isOutdoors) {
				scripts.openGrate(6, 0);
				eventsFlag = _buttonValue != 0;
			}
			break;
		case 13:
			if (!map._isOutdoors) {
				scripts.openGrate(1, 1);
				eventsFlag = _buttonValue != 0;
			}
			break;
		default:
			break;
		}
		if (eventsFlag) {
			scripts.checkEvents();
			if (_vm->shouldQuit())
				return;
		}
	}

	switch (_buttonValue) {
	case Common::KEYCODE_TAB:
		// Stop mosters doing any movement
		combat._moveMonsters = false;
		if (ControlPanel::show(_vm) == -1) {
			_vm->_quitMode = 2;
		} else {
			combat._moveMonsters = 1;
		}
		break;

	case Common::KEYCODE_SPACE:
	case Common::KEYCODE_w:
		// Wait one turn
		chargeStep();
		combat.moveMonsters();
		_upDoorText = false;
		_flipDefaultGround = !_flipDefaultGround;
		_flipGround = !_flipGround;

		stepTime();
		break;

	case (Common::KBD_CTRL << 16) | Common::KEYCODE_LEFT:
	case Common::KEYCODE_KP4:
		if (checkMoveDirection((Common::KBD_CTRL << 16) | Common::KEYCODE_LEFT)) {
			switch (party._mazeDirection) {
			case DIR_NORTH:
				--party._mazePosition.x;
				break;
			case DIR_SOUTH:
				++party._mazePosition.x;
				break;
			case DIR_EAST:
				++party._mazePosition.y;
				break;
			case DIR_WEST:
				--party._mazePosition.y;
				break;
			default:
				break;
			}

			chargeStep();
			_isAnimReset = true;
			party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
			_flipSky = !_flipSky;
			stepTime();
		}
		break;

	case (Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT:
	case Common::KEYCODE_KP6:
		if (checkMoveDirection((Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT)) {
			switch (party._mazeDirection) {
			case DIR_NORTH:
				++party._mazePosition.x;
				break;
			case DIR_SOUTH:
				--party._mazePosition.x;
				break;
			case DIR_EAST:
				--party._mazePosition.y;
				break;
			case DIR_WEST:
				++party._mazePosition.y;
				break;
			default:
				break;
			}

			chargeStep();
			_isAnimReset = true;
			party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
			_flipSky = !_flipSky;
			stepTime();
		}
		break;

	case Common::KEYCODE_LEFT:
	case Common::KEYCODE_KP7:
		party._mazeDirection = (Direction)((int)party._mazeDirection - 1);
		_isAnimReset = true;
		party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
		_flipSky = !_flipSky;
		stepTime();
		break;

	case Common::KEYCODE_RIGHT:
	case Common::KEYCODE_KP9:
		party._mazeDirection = (Direction)((int)party._mazeDirection + 1);
		_isAnimReset = true;
		party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
		_flipSky = !_flipSky;
		stepTime();
		break;

	case Common::KEYCODE_UP:
	case Common::KEYCODE_KP8:
		if (checkMoveDirection(Common::KEYCODE_UP)) {
			switch (party._mazeDirection) {
			case DIR_NORTH:
				++party._mazePosition.y;
				break;
			case DIR_SOUTH:
				--party._mazePosition.y;
				break;
			case DIR_EAST:
				++party._mazePosition.x;
				break;
			case DIR_WEST:
				--party._mazePosition.x;
				break;
			default:
				break;
			}

			chargeStep();
			stepTime();
		}
		break;

	case Common::KEYCODE_DOWN:
	case Common::KEYCODE_KP2:
		if (checkMoveDirection(Common::KEYCODE_DOWN)) {
			switch (party._mazeDirection) {
			case DIR_NORTH:
				--party._mazePosition.y;
				break;
			case DIR_SOUTH:
				++party._mazePosition.y;
				break;
			case DIR_EAST:
				--party._mazePosition.x;
				break;
			case DIR_WEST:
				++party._mazePosition.x;
				break;
			default:
				break;
			}

			chargeStep();
			stepTime();
		}
		break;

	case Common::KEYCODE_F1:
	case Common::KEYCODE_F2:
	case Common::KEYCODE_F3:
	case Common::KEYCODE_F4:
	case Common::KEYCODE_F5:
	case Common::KEYCODE_F6:
		_buttonValue -= Common::KEYCODE_F1;
		if (_buttonValue < (int)party._activeParty.size()) {
			CharacterInfo::show(_vm, _buttonValue);
			if (party._stepped)
				combat.moveMonsters();
		}
		break;

	case Common::KEYCODE_EQUALS:
	case Common::KEYCODE_KP_EQUALS:
		// Toggle minimap
		combat._moveMonsters = false;
		party._automapOn = !party._automapOn;
		combat._moveMonsters = true;
		break;

	case Common::KEYCODE_b:
		chargeStep();

		if (map.getCell(2) < map.mazeData()._difficulties._wallNoPass
				&& !map._isOutdoors) {
			switch (party._mazeDirection) {
			case DIR_NORTH:
				++party._mazePosition.y;
				break;
			case DIR_EAST:
				++party._mazePosition.x;
				break;
			case DIR_SOUTH:
				--party._mazePosition.y;
				break;
			case DIR_WEST:
				--party._mazePosition.x;
				break;
			default:
				break;
			}
			chargeStep();
			stepTime();
		} else {
			bash(party._mazePosition, party._mazeDirection);
		}
		break;

	case Common::KEYCODE_c: {
		// Cast spell
		if (_tillMove) {
			combat.moveMonsters();
			draw3d(true);
		}

		int result = 0;
		Character *c = &party._activeParty[(spells._lastCaster < 0 ||
			spells._lastCaster >= (int)party._activeParty.size()) ?
			(int)party._activeParty.size() - 1 : spells._lastCaster];
		do {
			int spellId = CastSpell::show(_vm, c);
			if (spellId == -1 || c == nullptr)
				break;

			result = spells.castSpell(c, (MagicSpell)spellId);
		} while (result != -1);

		if (result == 1) {
			chargeStep();
			doStepCode();
		}
		break;
	}

	case Common::KEYCODE_i:
		// Show Info dialog
		combat._moveMonsters = false;
		InfoDialog::show(_vm);
		combat._moveMonsters = true;
		break;

	case Common::KEYCODE_m:
		// Show map dialog
		AutoMapDialog::show(_vm);
		break;

	case Common::KEYCODE_q:
		// Show the quick reference dialog
		QuickReferenceDialog::show(_vm);
		break;

	case Common::KEYCODE_r:
		// Rest
		rest();
		break;

	case Common::KEYCODE_s:
		// Shoot
		if (!party.canShoot()) {
			sound.playFX(21);
		} else {
			if (_tillMove) {
				combat.moveMonsters();
				draw3d(true);
			}

			if (combat._attackMonsters[0] != -1 || combat._attackMonsters[1] != -1
					|| combat._attackMonsters[2] != -1) {
				if ((_vm->_mode == MODE_1 || _vm->_mode == MODE_SLEEPING)
						&& !combat._monstersAttacking && !_charsShooting) {
					doCombat();
				}
			}

			combat.shootRangedWeapon();
			chargeStep();
			doStepCode();
		}
		break;

	case Common::KEYCODE_v:
		// Show the quests dialog
		Quests::show(_vm);
		break;

	case Common::KEYCODE_x:
		// ****DEBUG***
		PartyDialog::show(_vm); //***DEBUG****
	default:
		break;
	}
}

void Interface::chargeStep() {
	if (!_vm->_party->_partyDead) {
		_vm->_party->changeTime(_vm->_map->_isOutdoors ? 10 : 1);
		if (_tillMove) {
			_vm->_combat->moveMonsters();
		}

		_tillMove = 3;
	}
}

void Interface::stepTime() {
	Party &party = *_vm->_party;
	Sound &sound = *_vm->_sound;
	doStepCode();

	if (++party._ctr24 == 24)
		party._ctr24 = 0;

	if (_buttonValue != Common::KEYCODE_SPACE && _buttonValue != Common::KEYCODE_w) {
		_steppingFX ^= 1;
		sound.playFX(_steppingFX + 7);
	}

	_upDoorText = false;
	_flipDefaultGround = !_flipDefaultGround;
	_flipGround = !_flipGround;
}

void Interface::doStepCode() {
	Combat &combat = *_vm->_combat;
	Map &map = *_vm->_map;
	Party &party = *_vm->_party;
	Scripts &scripts = *_vm->_scripts;
	int damage = 0;

	party._stepped = true;
	_upDoorText = false;

	map.getCell(2);
	int surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];

	switch (surfaceId) {
	case SURFTYPE_SPACE:
		// Wheeze.. can't breathe in space! Explosive decompression, here we come
		party._partyDead = true;
		break;
	case SURFTYPE_LAVA:
		// It burns, it burns!
		damage = 100;
		party._damageType = DT_FIRE;
		break;
	case SURFTYPE_SKY:
		// We can fly, we can.. oh wait, we can't!
		damage = 100;
		party._damageType = DT_PHYSICAL;
		_falling = true;
		break;
	case SURFTYPE_DESERT:
		// Without navigation skills, simulate getting lost by adding extra time
		if (map._isOutdoors && !party.checkSkill(NAVIGATOR))
			party.addTime(170);
		break;
	case SURFTYPE_CLOUD:
		if (!party._levitateActive) {
			party._damageType = DT_PHYSICAL;
			_falling = true;
			damage = 100;
		}
		break;
	default:
		break;
	}

	if (_vm->_files->_isDarkCc && party._gameFlags[374]) {
		_falling = false;
	} else {
		if (_falling)
			startFalling(false);

		if ((party._mazePosition.x & 16) || (party._mazePosition.y & 16)) {
			if (map._isOutdoors)
				map.getNewMaze();
		}

		if (damage) {
			_flipGround = !_flipGround;
			draw3d(true);

			int oldVal = scripts._v2;
			scripts._v2 = 0;
			combat.giveCharDamage(damage, combat._damageType, 0);

			scripts._v2 = oldVal;
			_flipGround = !_flipGround;
		} else if (party._partyDead) {
			draw3d(true);
		}
	}
}

void Interface::startFalling(bool flag) {
	Combat &combat = *_vm->_combat;
	Map &map = *_vm->_map;
	Party &party = *_vm->_party;
	Scripts &scripts = *_vm->_scripts;
	bool isDarkCc = _vm->_files->_isDarkCc;

	if (isDarkCc && party._gameFlags[374]) {
		_falling = 0;
		return;
	}

	_falling = false;
	draw3d(true);
	_falling = 2;
	draw3d(false);

	if (flag) {
		if (!isDarkCc || party._fallMaze != 0) {
			party._mazeId = party._fallMaze;
			party._mazePosition = party._fallPosition;
		}
	}

	_falling = true;
	map.load(party._mazeId);
	if (flag) {
		if (((party._mazePosition.x & 16) || (party._mazePosition.y & 16)) &&
				map._isOutdoors) {
			map.getNewMaze();
		}
	}

	if (isDarkCc) {
		switch (party._mazeId - 25) {
		case 0:
		case 26:
		case 27:
		case 28:
		case 29:
			party._mazeId = 24;
			party._mazePosition = Common::Point(11, 9);
			break;
		case 1:
		case 30:
		case 31:
		case 32:
		case 33:
			party._mazeId = 12;
			party._mazePosition = Common::Point(6, 15);
			break;
		case 2:
		case 34:
		case 35:
		case 36:
		case 37:
		case 51:
		case 52:
		case 53:
			party._mazeId = 15;
			party._mazePosition = Common::Point(4, 12);
			party._mazeDirection = DIR_SOUTH;
			break;
		case 40:
		case 41:
			party._mazeId = 14;
			party._mazePosition = Common::Point(8, 3);
			break;
		case 44:
		case 45:
			party._mazeId = 1;
			party._mazePosition = Common::Point(8, 7);
			party._mazeDirection = DIR_NORTH;
			break;
		case 49:
			party._mazeId = 12;
			party._mazePosition = Common::Point(11, 13);
			party._mazeDirection = DIR_SOUTH;
			break;
		case 57:
		case 58:
		case 59:
			party._mazeId = 5;
			party._mazePosition = Common::Point(12, 7);
			party._mazeDirection = DIR_NORTH;
			break;
		case 60:
			party._mazeId = 6;
			party._mazePosition = Common::Point(12, 3);
			party._mazeDirection = DIR_NORTH;
			break;
		default:
			party._mazeId = 23;
			party._mazePosition = Common::Point(12, 10);
			party._mazeDirection = DIR_NORTH;
			break;
		}
	} else {
		if (party._mazeId > 89 && party._mazeId < 113) {
			party._mazeId += 168;
		} else {
			switch (party._mazeId - 25) {
			case 0:
				party._mazeId = 89;
				party._mazePosition = Common::Point(2, 14);
				break;
			case 1:
				party._mazeId = 109;
				party._mazePosition = Common::Point(13, 14);
				break;
			case 2:
				party._mazeId = 112;
				party._mazePosition = Common::Point(13, 3);
				break;
			case 3:
				party._mazeId = 92;
				party._mazePosition = Common::Point(2, 3);
				break;
			case 12:
			case 13:
				party._mazeId = 14;
				party._mazePosition = Common::Point(10, 2);
				break;
			case 16:
			case 17:
			case 18:
				party._mazeId = 4;
				party._mazePosition = Common::Point(5, 14);
				break;
			case 20:
			case 21:
			case 22:
				party._mazeId = 21;
				party._mazePosition = Common::Point(9, 11);
				break;
			case 24:
			case 25:
			case 26:
				party._mazeId = 1;
				party._mazePosition = Common::Point(10, 4);
				break;
			case 28:
			case 29:
			case 30:
			case 31:
				party._mazeId = 26;
				party._mazePosition = Common::Point(12, 10);
				break;
			case 32:
			case 33:
			case 34:
			case 35:
				party._mazeId = 3;
				party._mazePosition = Common::Point(4, 9);
				break;
			case 36:
			case 37:
			case 38:
			case 39:
				party._mazeId = 16;
				party._mazePosition = Common::Point(2, 7);
				break;
			case 40:
			case 41:
			case 42:
			case 43:
				party._mazeId = 23;
				party._mazePosition = Common::Point(10, 9);
				break;
			case 44:
			case 45:
			case 46:
			case 47:
				party._mazeId = 13;
				party._mazePosition = Common::Point(2, 10);
				break;
			case 103:
			case 104:
				map._loadDarkSide = false;
				party._mazeId = 8;
				party._mazePosition = Common::Point(11, 15);
				party._mazeDirection = DIR_NORTH;
				break;
			case 105:
				party._mazeId = 24;
				party._mazePosition = Common::Point(11, 9);
				break;
			case 106:
				party._mazeId = 12;
				party._mazePosition = Common::Point(6, 15);
				break;
			case 107:
				party._mazeId = 15;
				party._mazePosition = Common::Point(4, 12);
				break;
			default:
				party._mazeId = 29;
				party._mazePosition = Common::Point(25, 21);
				party._mazeDirection = DIR_NORTH;
				break;
			}
		}
	}

	_flipGround ^= 1;
	draw3d(true);
	int tempVal = scripts._v2;
	scripts._v2 = 0;
	combat.giveCharDamage(party._fallDamage, DT_PHYSICAL, 0);
	scripts._v2 = tempVal;

	_flipGround ^= 1;
}

bool Interface::checkMoveDirection(int key) {
	Map &map = *_vm->_map;
	Party &party = *_vm->_party;
	Sound &sound = *_vm->_sound;
	Direction dir = party._mazeDirection;

	switch (key) {
	case (Common::KBD_CTRL << 16) | Common::KEYCODE_LEFT:
		party._mazeDirection = (party._mazeDirection == DIR_NORTH) ? DIR_WEST :
			(Direction)(party._mazeDirection - 1);
		break;
	case (Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT:
		party._mazeDirection = (party._mazeDirection == DIR_WEST) ? DIR_NORTH :
			(Direction)(party._mazeDirection + 1);
		break;
	case Common::KEYCODE_DOWN:
		party._mazeDirection = (Direction)((int)party._mazeDirection ^ 2);
		break;
	default:
		break;
	}

	map.getCell(7);
	int startSurfaceId = map._currentSurfaceId;
	int surfaceId;

	if (map._isOutdoors) {
		party._mazeDirection = dir;

		switch (map._currentWall) {
		case 5:
			if (_vm->_files->_isDarkCc)
				goto check;

			// Deliberate FAll-through
		case 0:
		case 2:
		case 4:
		case 8:
		case 11:
		case 13:
		case 14:
			surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];
			if (surfaceId == SURFTYPE_WATER) {
				if (party.checkSkill(SWIMMING) || party._walkOnWaterActive)
					return true;
			} else if (surfaceId == SURFTYPE_DWATER) {
				if (party._walkOnWaterActive)
					return true;
			} else if (surfaceId != SURFTYPE_SPACE) {
				return true;
			}

			sound.playFX(21);
			return false;

		case 1:
		case 7:
		case 9:
		case 10:
		case 12:
			check:
			if (party.checkSkill(MOUNTAINEER))
				return true;

			sound.playFX(21);
			return false;

		default:
			break;
		}
	} else {
		surfaceId = map.getCell(2);
		if (surfaceId >= map.mazeData()._difficulties._wallNoPass) {
			party._mazeDirection = dir;
			sound.playFX(46);
			return false;
		} else {
			party._mazeDirection = dir;

			if (startSurfaceId == SURFTYPE_SWAMP || party.checkSkill(SWIMMING) ||
					party._walkOnWaterActive) {
				sound.playFX(46);
				return false;
			} else {
				if (_buttonValue == Common::KEYCODE_UP && _wo[107]) {
					_openDoor = true;
					sound.playFX(47);
					draw3d(true);
					_openDoor = false;
				}
				return true;
			}
		}
	}

	return true;
}

void Interface::rest() {
	EventsManager &events = *_vm->_events;
	Map &map = *_vm->_map;
	Party &party = *_vm->_party;
	Screen &screen = *_vm->_screen;
	Sound &sound = *_vm->_sound;

	map.cellFlagLookup(party._mazePosition);

	if ((map._currentCantRest || (map.mazeData()._mazeFlags & RESTRICTION_REST))
			&& _vm->_mode != MODE_12) {
		ErrorScroll::show(_vm, Res.TOO_DANGEROUS_TO_REST, WT_NONFREEZED_WAIT);
	} else {
		// Check whether any character is in danger of dying
		bool dangerFlag = false;
		for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
			for (int attrib = MIGHT; attrib <= LUCK; ++attrib) {
				if (party._activeParty[charIdx].getStat((Attribute)attrib) < 1)
					dangerFlag = true;
			}
		}

		if (dangerFlag) {
			if (!Confirm::show(_vm, Res.SOME_CHARS_MAY_DIE))
				return;
		}

		// Mark all the players as being asleep
		for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
			party._activeParty[charIdx]._conditions[ASLEEP] = 1;
		}
		drawParty(true);

		Mode oldMode = _vm->_mode;
		_vm->_mode = MODE_SLEEPING;

		if (oldMode == MODE_12) {
			party.changeTime(8 * 60);
		} else {
			for (int idx = 0; idx < 10; ++idx) {
				chargeStep();
				draw3d(true);

				if (_vm->_mode == MODE_1) {
					_vm->_mode = oldMode;
					return;
				}
			}

			party.changeTime(map._isOutdoors ? 380 : 470);
		}

		if (_vm->getRandomNumber(1, 20) == 1) {
			// Show dream
			screen.saveBackground();
			screen.fadeOut();
			events.hideCursor();

			screen.loadBackground("scene1.raw");
			screen._windows[0].update();
			screen.fadeIn();

			events.updateGameCounter();
			while (!_vm->shouldQuit() && events.timeElapsed() < 7)
				events.pollEventsAndWait();

			sound.playSound("dreams2.voc", 1);
			while (!_vm->shouldQuit() && sound.isPlaying())
				events.pollEventsAndWait();

			sound.playSound("laff1.voc", 1);
			while (!_vm->shouldQuit() && sound.isPlaying())
				events.pollEventsAndWait();

			events.updateGameCounter();
			while (!_vm->shouldQuit() && events.timeElapsed() < 7)
				events.pollEventsAndWait();

			screen.fadeOut();
			events.setCursor(0);
			screen.restoreBackground();
			screen._windows[0].update();

			screen.fadeIn();
		}

		party.resetTemps();

		// Wake up the party
		bool starving = false;
		int foodConsumed = 0;
		for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
			Character &c = party._activeParty[charIdx];
			c._conditions[ASLEEP] = 0;

			if (party._food == 0) {
				starving = true;
			} else {
				party._rested = true;
				Condition condition = c.worstCondition();

				if (condition < DEAD || condition > ERADICATED) {
					--party._food;
					++foodConsumed;
					party._heroism = 0;
					party._holyBonus = 0;
					party._powerShield = 0;
					party._blessed = 0;
					c._conditions[UNCONSCIOUS] = 0;
					c._currentHp = c.getMaxHP();
					c._currentSp = c.getMaxSP();
				}
			}
		}

		drawParty(true);
		_vm->_mode = oldMode;
		doStepCode();
		draw3d(true);

		ErrorScroll::show(_vm, Common::String::format(Res.REST_COMPLETE,
			starving ? Res.PARTY_IS_STARVING : Res.HIT_SPELL_POINTS_RESTORED,
			foodConsumed));
		party.checkPartyDead();
	}
}

void Interface::bash(const Common::Point &pt, Direction direction) {
	EventsManager &events = *_vm->_events;
	Map &map = *_vm->_map;
	Party &party = *_vm->_party;
	Screen &screen = *_vm->_screen;
	Sound &sound = *_vm->_sound;

	if (map._isOutdoors)
		return;

	sound.playFX(31);

	uint charNum1 = 0, charNum2 = 0;
	for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
		Character &c = party._activeParty[charIdx];
		Condition condition = c.worstCondition();

		if (!(condition == ASLEEP || (condition >= PARALYZED &&
				condition <= ERADICATED))) {
			if (charNum1) {
				charNum2 = charIdx + 1;
				break;
			} else {
				charNum1 = charIdx + 1;
			}
		}
	}

	party._activeParty[charNum1 - 1].subtractHitPoints(2);
	_charPowSprites.draw(screen._windows[0], 0,
		Common::Point(Res.CHAR_FACES_X[charNum1 - 1], 150));
	screen._windows[0].update();

	if (charNum2) {
		party._activeParty[charNum2 - 1].subtractHitPoints(2);
		_charPowSprites.draw(screen._windows[0], 0,
			Common::Point(Res.CHAR_FACES_X[charNum2 - 1], 150));
		screen._windows[0].update();
	}

	int cell = map.mazeLookup(Common::Point(pt.x + Res.SCREEN_POSITIONING_X[direction][7],
		pt.y + Res.SCREEN_POSITIONING_Y[direction][7]), 0, 0xffff);
	if (cell != INVALID_CELL) {
		int v = map.getCell(2);

		if (v == 7) {
			++_wo[207];
			++_wo[267];
			++_wo[287];
		} else if (v == 14) {
			++_wo[267];
			++_wo[287];
		} else if (v == 15) {
			++_wo[287];
		} else {
			int might = party._activeParty[charNum1 - 1].getStat(MIGHT) +
				_vm->getRandomNumber(1, 30);
			if (charNum2)
				might += party._activeParty[charNum2 - 1].getStat(MIGHT);

			int bashThreshold = (v == 9) ? map.mazeData()._difficulties._bashGrate :
				map.mazeData()._difficulties._bashWall;
			if (might >= bashThreshold) {
				// Remove the wall on the current cell, and the reverse wall
				// on the cell we're bashing through to
				map.setWall(pt, direction, 3);
				switch (direction) {
				case DIR_NORTH:
					map.setWall(Common::Point(pt.x, pt.y + 1), DIR_SOUTH, 3);
					break;
				case DIR_EAST:
					map.setWall(Common::Point(pt.x + 1, pt.y), DIR_WEST, 3);
					break;
				case DIR_SOUTH:
					map.setWall(Common::Point(pt.x, pt.y - 1), DIR_NORTH, 3);
					break;
				case DIR_WEST:
					map.setWall(Common::Point(pt.x - 1, pt.y), DIR_EAST, 3);
					break;
				default:
					break;
				}
			}
		}
	}

	party.checkPartyDead();
	events.ipause(2);
	drawParty(true);
}

void Interface::draw3d(bool updateFlag, bool skipDelay) {
	Combat &combat = *_vm->_combat;
	EventsManager &events = *_vm->_events;
	Party &party = *_vm->_party;
	Screen &screen = *_vm->_screen;
	Scripts &scripts = *_vm->_scripts;

	if (screen._windows[11]._enabled)
		return;

	_flipUIFrame = (_flipUIFrame + 1) % 4;
	if (_flipUIFrame == 0)
		_flipWater = !_flipWater;
	if (_tillMove && (_vm->_mode == MODE_1 || _vm->_mode == MODE_COMBAT) &&
		!combat._monstersAttacking && combat._moveMonsters) {
		if (--_tillMove == 0)
			combat.moveMonsters();
	}

	// Draw the map
	drawMap();

	// Draw the minimap
	drawMiniMap();

	if (_falling == 1)
		handleFalling();

	if (_falling == 2) {
		screen.saveBackground(1);
	}

	assembleBorder();

	// Draw any on-screen text if flagged to do so
	if (_upDoorText && combat._attackMonsters[0] == -1) {
		screen._windows[3].writeString(_screenText);
	}

	if (updateFlag) {
		screen._windows[1].update();
		screen._windows[3].update();
	}

	if (combat._attackMonsters[0] != -1 || combat._attackMonsters[1] != -1
			|| combat._attackMonsters[2] != -1) {
		if ((_vm->_mode == MODE_1 || _vm->_mode == MODE_SLEEPING) &&
				!combat._monstersAttacking && !_charsShooting && combat._moveMonsters) {
			doCombat();
			if (scripts._eventSkipped)
				scripts.checkEvents();
		}
	}

	party._stepped = false;
	if (_vm->_mode == MODE_9) {
		// TODO: Save current scripts data?
	}

	if (!skipDelay)
		events.wait(2, false);
}

void Interface::handleFalling() {
	Party &party = *_vm->_party;
	Screen &screen = *_vm->_screen;
	Sound &sound = *_vm->_sound;
	Window &w = screen._windows[3];
	saveFall();

	for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
		party._activeParty[idx]._faceSprites->draw(screen._windows[0], 4,
			Common::Point(Res.CHAR_FACES_X[idx], 150));
	}

	screen._windows[33].update();
	sound.playFX(11);
	sound.playSound("scream.voc");

	for (int idx = 0, incr = 2; idx < 133; ++incr, idx += incr) {
		fall(idx);
		assembleBorder();
		w.update();
	}

	fall(132);
	assembleBorder();
	w.update();

	sound.stopSound();
	sound.playSound("unnh.voc");
	sound.playFX(31);

	fall(127);
	assembleBorder();
	w.update();

	fall(132);
	assembleBorder();
	w.update();

	fall(129);
	assembleBorder();
	w.update();

	fall(132);
	assembleBorder();
	w.update();

	shake(10);
}

void Interface::saveFall() {
	// TODO
}

void Interface::fall(int v) {
	// TODO
}

void Interface::shake(int time) {
	// TODO
}

void Interface::drawMiniMap() {
	Map &map = *_vm->_map;
	Party &party = *_vm->_party;
	Resources &res = *_vm->_resources;
	Screen &screen = *_vm->_screen;
	Window &window1 = screen._windows[1];

	if (screen._windows[2]._enabled || screen._windows[10]._enabled)
		return;
	if (!party._automapOn && !party._wizardEyeActive) {
		// Draw the Might & Magic logo
		res._globalSprites.draw(window1, 5, Common::Point(232, 9));
		return;
	}

	int v, frame;
	int frame2 = _overallFrame * 2;
	bool eyeActive = party._wizardEyeActive;
	if (party._automapOn)
		party._wizardEyeActive = false;

	if (map._isOutdoors) {
		res._globalSprites.draw(window1, 15, Common::Point(237, 12));

		for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
			for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
				v = map.mazeLookup(
					Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
					4);
				frame = map.mazeDataCurrent()._surfaceTypes[v];

				if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
					map._tileSprites.draw(window1, frame, Common::Point(xp, yp));
				}
			}
		}

		for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
			for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
				v = map.mazeLookup(
					Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
					4);
				frame = map.mazeData()._wallTypes[v];

				if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
					map._tileSprites.draw(window1, frame + 16, Common::Point(xp, yp));
				}
			}
		}

		for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
			for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
				v = map.mazeLookup(
					Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
					4);

				if (v != -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
					map._tileSprites.draw(window1, v + 32, Common::Point(xp, yp));
				}
			}
		}

		// Draw the direction arrow
		res._globalSprites.draw(window1, party._mazeDirection + 1,
			Common::Point(267, 36));
	} else {
		frame2 = (frame2 + 2) % 8;

		// First draw the default surface bases for each cell to show
		for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
			for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
				v = map.mazeLookup(
					Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
					0, 0xffff);

				if (v != INVALID_CELL && (map._currentSteppedOn || party._wizardEyeActive)) {
					map._tileSprites.draw(window1, 0, Common::Point(xp, yp));
				}
			}
		}

		// Draw correct surface bases for revealed tiles
		for (int rowNum = 0, yp = 17, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
			for (int colNum = 0, xp = 242, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
				v = map.mazeLookup(
					Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
					0, 0xffff);
				int surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];

				if (v != INVALID_CELL && map._currentSurfaceId &&
					(map._currentSteppedOn || party._wizardEyeActive)) {
					map._tileSprites.draw(window1, surfaceId + 36, Common::Point(xp, yp));
				}
			}
		}

		v = map.mazeLookup(Common::Point(party._mazePosition.x - 4, party._mazePosition.y + 4), 0xffff, 0);
		if (v != INVALID_CELL && map._currentSurfaceId &&
			(map._currentSteppedOn || party._wizardEyeActive)) {
			map._tileSprites.draw(window1,
				map.mazeData()._surfaceTypes[map._currentSurfaceId] + 36,
				Common::Point(232, 9));
		}

		// Handle drawing surface sprites partially clipped at the left edge
		for (int rowNum = 0, yp = 17, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, --yDiff, yp += 8) {
			v = map.mazeLookup(
				Common::Point(party._mazePosition.x - 4, party._mazePosition.y + yDiff),
				0, 0xffff);

			if (v != INVALID_CELL && map._currentSurfaceId &&
				(map._currentSteppedOn || party._wizardEyeActive)) {
				map._tileSprites.draw(window1,
					map.mazeData()._surfaceTypes[map._currentSurfaceId] + 36,
					Common::Point(232, yp));
			}
		}

		// Handle drawing surface sprites partially clipped at the top edge
		for (int colNum = 0, xp = 242, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, ++xDiff, xp += 8) {
			v = map.mazeLookup(
				Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + 4),
				0, 0xffff);

			if (v != INVALID_CELL && map._currentSurfaceId &&
				(map._currentSteppedOn || party._wizardEyeActive)) {
				map._tileSprites.draw(window1,
					map.mazeData()._surfaceTypes[map._currentSurfaceId] + 36,
					Common::Point(xp, 9));
			}
		}

		//
		for (int idx = 0, xp = 237, yp = 60, xDiff = -3; idx < MINIMAP_SIZE;
			++idx, ++xDiff, xp += 10, yp -= 8) {
			v = map.mazeLookup(
				Common::Point(party._mazePosition.x - 4, party._mazePosition.y - 3 + idx),
				12, 0xffff);

			switch (v) {
			case 1:
				frame = 18;
				break;
			case 3:
				frame = 22;
				break;
			case 4:
			case 13:
				frame = 16;
				break;
			case 5:
			case 8:
				frame = 2;
				break;
			case 6:
				frame = 30;
				break;
			case 7:
				frame = 32;
				break;
			case 9:
				frame = 24;
				break;
			case 10:
				frame = 28;
				break;
			case 11:
				frame = 14;
				break;
			case 12:
				frame = frame2 + 4;
				break;
			case 14:
				frame = 24;
				break;
			case 15:
				frame = 26;
				break;
			default:
				frame = -1;
				break;
			}

			if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive))
				map._tileSprites.draw(window1, frame, Common::Point(222, yp));

			v = map.mazeLookup(
				Common::Point(party._mazePosition.x - 3 + idx, party._mazePosition.y + 4),
				0);

			switch (v) {
			case 1:
				frame = 19;
				break;
			case 2:
				frame = 35;
				break;
			case 3:
				frame = 23;
				break;
			case 4:
			case 13:
				frame = 17;
				break;
			case 5:
			case 8:
				frame = 3;
				break;
			case 6:
				frame = 31;
				break;
			case 7:
				frame = 33;
				break;
			case 9:
				frame = 21;
				break;
			case 10:
				frame = 29;
				break;
			case 11:
				frame = 15;
				break;
			case 12:
				frame = frame2 + 5;
				break;
			case 14:
				frame = 25;
				break;
			case 15:
				frame = 27;
				break;
			default:
				frame = -1;
				break;
			}

			if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive))
				map._tileSprites.draw(window1, frame, Common::Point(xp, 4));
		}

		// Draw the front/back walls of cells in the minimap
		for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE;
			++rowNum, --yDiff, yp += 8) {
			for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE;
				++colNum, ++xDiff, xp += 10) {
				if (colNum == 4 && rowNum == 4) {
					// Center of the minimap. Draw the direction arrow
					res._globalSprites.draw(window1, party._mazeDirection + 1,
						Common::Point(272, 40));
				}

				v = map.mazeLookup(Common::Point(party._mazePosition.x + xDiff,
					party._mazePosition.y + yDiff), 12, 0xffff);
				switch (v) {
				case 1:
					frame = 18;
					break;
				case 3:
					frame = 22;
					break;
				case 4:
				case 13:
					frame = 16;
					break;
				case 5:
				case 8:
					frame = 2;
					break;
				case 6:
					frame = 30;
					break;
				case 7:
					frame = 32;
					break;
				case 9:
					frame = 20;
					break;
				case 10:
					frame = 28;
					break;
				case 11:
					frame = 14;
					break;
				case 12:
					frame = frame2 + 4;
					break;
				case 14:
					frame = 24;
					break;
				case 15:
					frame = 26;
					break;
				default:
					frame = -1;
					break;
				}

				if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
					map._tileSprites.draw(window1, frame, Common::Point(xp, yp));
				}

				v = map.mazeLookup(Common::Point(party._mazePosition.x + xDiff,
					party._mazePosition.y + yDiff), 12, 0xffff);
				switch (v) {
				case 1:
					frame = 19;
					break;
				case 2:
					frame = 35;
					break;
				case 3:
					frame = 23;
					break;
				case 4:
				case 13:
					frame = 17;
					break;
				case 5:
				case 8:
					frame = 3;
					break;
				case 6:
					frame = 31;
					break;
				case 7:
					frame = 33;
					break;
				case 9:
					frame = 21;
					break;
				case 10:
					frame = 29;
					break;
				case 11:
					frame = 15;
					break;
				case 12:
					frame = frame2 + 5;
					break;
				case 14:
					frame = 25;
					break;
				case 15:
					frame = 27;
					break;
				default:
					frame = -1;
					break;
				}

				if (v == -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
					map._tileSprites.draw(window1, frame, Common::Point(xp, yp));
				}
			}
		}

		// Draw the top of blocked/wall cells on the map
		for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
			for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
				v = map.mazeLookup(
					Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
					0, 0xffff);

				if (v == INVALID_CELL || (!map._currentSteppedOn && !party._wizardEyeActive)) {
					map._tileSprites.draw(window1, 1, Common::Point(xp, yp));
				}
			}
		}
	}

	// Draw outer rectangle around the automap
	res._globalSprites.draw(window1, 6, Common::Point(223, 3));
	party._wizardEyeActive = eyeActive;
}

void Interface::assembleBorder() {
	Combat &combat = *_vm->_combat;
	Resources &res = *_vm->_resources;
	Screen &screen = *_vm->_screen;

	// Draw the outer frame
	res._globalSprites.draw(screen._windows[0], 0, Common::Point(8, 8));

	// Draw the animating bat character on the left screen edge to indicate
	// that the party is being levitated
	_borderSprites.draw(screen._windows[0], _vm->_party->_levitateActive ? _levitateUIFrame + 16 : 16,
		Common::Point(0, 82));
	_levitateUIFrame = (_levitateUIFrame + 1) % 12;

	// Draw UI element to indicate whether can spot hidden doors
	_borderSprites.draw(screen,
		(_thinWall && _vm->_party->checkSkill(SPOT_DOORS)) ? _spotDoorsUIFrame + 28 : 28,
		Common::Point(194, 91));
	_spotDoorsUIFrame = (_spotDoorsUIFrame + 1) % 12;

	// Draw UI element to indicate whether can sense danger
	_borderSprites.draw(screen,
		(combat._dangerPresent && _vm->_party->checkSkill(DANGER_SENSE)) ? _spotDoorsUIFrame + 40 : 40,
		Common::Point(107, 9));
	_dangerSenseUIFrame = (_dangerSenseUIFrame + 1) % 12;

	// Handle the face UI elements for indicating clairvoyance status
	_face1UIFrame = (_face1UIFrame + 1) % 4;
	if (_face1State == 0)
		_face1UIFrame += 4;
	else if (_face1State == 2)
		_face1UIFrame = 0;

	_face2UIFrame = (_face2UIFrame + 1) % 4 + 12;
	if (_face2State == 0)
		_face2UIFrame += 252;
	else if (_face2State == 2)
		_face2UIFrame = 0;

	if (!_vm->_party->_clairvoyanceActive) {
		_face1UIFrame = 0;
		_face2UIFrame = 8;
	}

	_borderSprites.draw(screen, _face1UIFrame, Common::Point(0, 32));
	_borderSprites.draw(screen,
		screen._windows[10]._enabled || screen._windows[2]._enabled ?
		52 : _face2UIFrame,
		Common::Point(215, 32));

	// Draw resistence indicators
	if (!screen._windows[10]._enabled && !screen._windows[2]._enabled
		&& screen._windows[38]._enabled) {
		_fecpSprites.draw(screen, _vm->_party->_fireResistence ? 1 : 0,
			Common::Point(2, 2));
		_fecpSprites.draw(screen, _vm->_party->_electricityResistence ? 3 : 2,
			Common::Point(219, 2));
		_fecpSprites.draw(screen, _vm->_party->_coldResistence ? 5 : 4,
			Common::Point(2, 134));
		_fecpSprites.draw(screen, _vm->_party->_poisonResistence ? 7 : 6,
			Common::Point(219, 134));
	} else {
		_fecpSprites.draw(screen, _vm->_party->_fireResistence ? 9 : 8,
			Common::Point(8, 8));
		_fecpSprites.draw(screen, _vm->_party->_electricityResistence ? 10 : 11,
			Common::Point(219, 8));
		_fecpSprites.draw(screen, _vm->_party->_coldResistence ? 12 : 13,
			Common::Point(8, 134));
		_fecpSprites.draw(screen, _vm->_party->_poisonResistence ? 14 : 15,
			Common::Point(219, 134));
	}

	// Draw UI element for blessed
	_blessSprites.draw(screen, 16, Common::Point(33, 137));
	if (_vm->_party->_blessed) {
		_blessedUIFrame = (_blessedUIFrame + 1) % 4;
		_blessSprites.draw(screen, _blessedUIFrame, Common::Point(33, 137));
	}

	// Draw UI element for power shield
	if (_vm->_party->_powerShield) {
		_powerShieldUIFrame = (_powerShieldUIFrame + 1) % 4;
		_blessSprites.draw(screen, _powerShieldUIFrame + 4,
			Common::Point(55, 137));
	}

	// Draw UI element for holy bonus
	if (_vm->_party->_holyBonus) {
		_holyBonusUIFrame = (_holyBonusUIFrame + 1) % 4;
		_blessSprites.draw(screen, _holyBonusUIFrame + 8, Common::Point(160, 137));
	}

	// Draw UI element for heroism
	if (_vm->_party->_heroism) {
		_heroismUIFrame = (_heroismUIFrame + 1) % 4;
		_blessSprites.draw(screen, _heroismUIFrame + 12, Common::Point(182, 137));
	}

	// Draw direction character if direction sense is active
	if (_vm->_party->checkSkill(DIRECTION_SENSE) && !_vm->_noDirectionSense) {
		const char *dirText = Res.DIRECTION_TEXT_UPPER[_vm->_party->_mazeDirection];
		Common::String msg = Common::String::format(
			"\002""08\003""c\013""139\011""116%c\014""d\001", *dirText);
		screen._windows[0].writeString(msg);
	}

	// Draw view frame
	if (screen._windows[12]._enabled)
		screen._windows[12].frame();
}

void Interface::doCombat() {
	Combat &combat = *_vm->_combat;
	EventsManager &events = *_vm->_events;
	Map &map = *_vm->_map;
	Party &party = *_vm->_party;
	Screen &screen = *_vm->_screen;
	Scripts &scripts = *_vm->_scripts;
	Spells &spells = *_vm->_spells;
	Sound &sound = *_vm->_sound;
	bool upDoorText = _upDoorText;
	bool reloadMap = false;

	_upDoorText = false;
	combat._combatMode = COMBATMODE_2;
	_vm->_mode = MODE_COMBAT;

	_iconSprites.load("combat.icn");
	for (int idx = 1; idx < 16; ++idx)
		_mainList[idx]._sprites = &_iconSprites;

	// Set the combat buttons
	setMainButtons(true);
	mainIconsPrint();

	combat._combatParty.clear();
	combat._charsGone.clear();
	combat._charsBlocked.clear();
	combat._charsArray1[0] = 0;
	combat._charsArray1[1] = 0;
	combat._charsArray1[2] = 0;
	combat._monstersAttacking = 0;
	combat._partyRan = false;

	// Set up the combat party
	combat.setupCombatParty();
	combat.setSpeedTable();

	// Initialize arrays for character/monster states
	combat._charsGone.resize(combat._speedTable.size());
	combat._charsBlocked.resize(combat._speedTable.size());
	Common::fill(&combat._charsGone[0], &combat._charsGone[0] + combat._speedTable.size(), 0);
	Common::fill(&combat._charsBlocked[0], &combat._charsBlocked[0] + combat._speedTable.size(), false);

	combat._whosSpeed = -1;
	combat._whosTurn = -1;
	resetHighlight();

	nextChar();

	if (!party._dead) {
		combat.setSpeedTable();

		if (_tillMove) {
			combat.moveMonsters();
			draw3d(true);
		}

		Window &w = screen._windows[2];
		w.open();
		bool breakFlag = false;

		while (!_vm->shouldQuit() && !breakFlag) {
			highlightChar(combat._whosTurn);
			combat.setSpeedTable();

			// Write out the description of the monsters being battled
			w.writeString(combat.getMonsterDescriptions());
			_iconSprites.draw(screen, 32, Common::Point(233, combat._monsterIndex * 10 + 27),
				0x8010000);
			w.update();

			// Wait for keypress
			int index = 0;
			do {
				events.updateGameCounter();
				draw3d(true);

				if (++index == 5 && combat._attackMonsters[0] != -1) {
					MazeMonster &monster = map._mobData._monsters[combat._monster2Attack];
					MonsterStruct &monsterData = *monster._monsterData;
					sound.playFX(monsterData._fx);
				}

				do {
					events.pollEventsAndWait();
					checkEvents(_vm);
				} while (!_vm->shouldQuit() && events.timeElapsed() < 1 && !_buttonValue);
			} while (!_vm->shouldQuit() && !_buttonValue);
			if (_vm->shouldQuit())
				return;

			switch (_buttonValue) {
			case Common::KEYCODE_TAB:
				// Show the control panel
				if (ControlPanel::show(_vm) == 2) {
					reloadMap = true;
					breakFlag = true;
				} else {
					highlightChar(combat._whosTurn);
				}
				break;

			case Common::KEYCODE_1:
			case Common::KEYCODE_2:
			case Common::KEYCODE_3:
				_buttonValue -= Common::KEYCODE_1;
				if (combat._attackMonsters[_buttonValue] != -1) {
					combat._monster2Attack = combat._attackMonsters[_buttonValue];
					combat._monsterIndex = _buttonValue;
				}
				break;

			case Common::KEYCODE_a:
				// Attack
				combat.attack(*combat._combatParty[combat._whosTurn], RT_SINGLE);
				nextChar();
				break;

			case Common::KEYCODE_b:
				// Block
				combat.block();
				nextChar();
				break;

			case Common::KEYCODE_c: {
				// Cast spell
				int spellId = CastSpell::show(_vm);
				if (spellId != -1) {
					Character *c = combat._combatParty[combat._whosTurn];
					spells.castSpell(c, (MagicSpell)spellId);
					nextChar();
				} else {
					highlightChar(combat._combatParty[combat._whosTurn]->_rosterId);
				}
				break;
			}

			case Common::KEYCODE_f:
				// Quick Fight
				combat.quickFight();
				nextChar();
				break;

			case Common::KEYCODE_i:
				// Info dialog
				InfoDialog::show(_vm);
				highlightChar(combat._whosTurn);
				break;

			case Common::KEYCODE_o:
				// Fight Options
				FightOptions::show(_vm);
				highlightChar(combat._whosTurn);
				break;

			case Common::KEYCODE_q:
				// Quick Reference dialog
				QuickReferenceDialog::show(_vm);
				highlightChar(combat._whosTurn);
				break;

			case Common::KEYCODE_r:
				// Run from combat
				combat.run();
				nextChar();

				if (_vm->_mode == MODE_1) {
					warning("TODO: loss of treasure");
					party.moveToRunLocation();
					breakFlag = true;
				}
				break;

			case Common::KEYCODE_u: {
				int whosTurn = combat._whosTurn;
				ItemsDialog::show(_vm, combat._combatParty[combat._whosTurn], ITEMMODE_COMBAT);
				if (combat._whosTurn == whosTurn) {
					highlightChar(combat._whosTurn);
				} else {
					combat._whosTurn = whosTurn;
					nextChar();
				}
				break;
			}

			case Common::KEYCODE_F1:
			case Common::KEYCODE_F2:
			case Common::KEYCODE_F3:
			case Common::KEYCODE_F4:
			case Common::KEYCODE_F5:
			case Common::KEYCODE_F6:
				// Show character info
				_buttonValue -= Common::KEYCODE_F1;
				if (_buttonValue < (int)combat._combatParty.size()) {
					CharacterInfo::show(_vm, _buttonValue);
				}
				highlightChar(combat._whosTurn);
				break;

			case Common::KEYCODE_LEFT:
			case Common::KEYCODE_RIGHT:
				// Rotate party direction left or right
				if (_buttonValue == Common::KEYCODE_LEFT) {
					party._mazeDirection = (party._mazeDirection == DIR_NORTH) ?
						DIR_WEST : (Direction)((int)party._mazeDirection - 1);
				} else {
					party._mazeDirection = (party._mazeDirection == DIR_WEST) ?
					DIR_NORTH : (Direction)((int)party._mazeDirection + 1);
				}

				_flipSky ^= 1;
				if (_tillMove)
					combat.moveMonsters();
				party._stepped = true;
				break;
			}

			// Handling for if the combat turn is complete
			if (combat.allHaveGone()) {
				Common::fill(&combat._charsGone[0], &combat._charsGone[combat._charsGone.size()], false);
				Common::fill(&combat._charsBlocked[0], &combat._charsBlocked[combat._charsBlocked.size()], false);
				combat.setSpeedTable();
				combat._whosTurn = -1;
				combat._whosSpeed = -1;
				nextChar();

				for (uint idx = 0; idx < map._mobData._monsters.size(); ++idx) {
					MazeMonster &monster = map._mobData._monsters[idx];
					if (monster._spriteId == 53) {
						warning("TODO: Monster 53's HP is altered here?!");
					}
				}

				combat.moveMonsters();
				setIndoorsMonsters();
				party.changeTime(1);
			}

			if (combat._attackMonsters[0] == -1 && combat._attackMonsters[1] == -1
					&& combat._attackMonsters[2] == -1) {
				party.changeTime(1);
				draw3d(true);

				if (combat._attackMonsters[0] == -1 && combat._attackMonsters[1] == -1
						&& combat._attackMonsters[2] == -1)
					break;
			}

			party.checkPartyDead();
			if (party._dead || _vm->_mode != MODE_COMBAT)
				break;
		}

		_vm->_mode = MODE_1;
		if (combat._partyRan && (combat._attackMonsters[0] != -1 ||
				combat._attackMonsters[1] != -1 || combat._attackMonsters[2] != -1)) {
			party.checkPartyDead();
			if (!party._dead) {
				party.moveToRunLocation();

				for (uint idx = 0; idx < combat._combatParty.size(); ++idx) {
					Character &c = *combat._combatParty[idx];
					if (c.isDisabled())
						c._conditions[DEAD] = 1;
				}
			}
		}

		w.close();
		events.clearEvents();

		_vm->_mode = MODE_COMBAT;
		draw3d(true);
		party.giveTreasure();
		_vm->_mode = MODE_1;
		party._stepped = true;
		unhighlightChar();

		combat.setupCombatParty();
		drawParty(true);
	}

	_iconSprites.load("main.icn");
	for (int idx = 1; idx < 16; ++idx)
		_mainList[idx]._sprites = &_iconSprites;

	setMainButtons();
	mainIconsPrint();
	combat._monster2Attack = -1;

	if (upDoorText) {
		map.cellFlagLookup(party._mazePosition);
		if (map._currentIsEvent)
			scripts.checkEvents();
	}

	if (reloadMap) {
		sound.playFX(51);
		map._loadDarkSide = _vm->getGameID() != GType_WorldOfXeen;
		map.load(_vm->getGameID() == GType_WorldOfXeen ? 28 : 29);
		party._mazeDirection = _vm->getGameID() == GType_WorldOfXeen ?
			DIR_EAST : DIR_SOUTH;
	}

	combat._combatMode = COMBATMODE_1;
}

void Interface::nextChar() {
	Combat &combat = *_vm->_combat;
	Party &party = *_vm->_party;

	if (combat.allHaveGone())
		return;
	if ((combat._attackMonsters[0] == -1 && combat._attackMonsters[1] == -1 &&
		combat._attackMonsters[2] == -1) || combat._combatParty.size() == 0) {
		_vm->_mode = MODE_1;
		return;
	}

	// Loop for potentially multiple monsters attacking until it's time
	// for one of the party's turn
	for (;;) {
		// Check if party is dead
		party.checkPartyDead();
		if (party._dead) {
			_vm->_mode = MODE_1;
			break;
		}

		int idx;
		for (idx = 0; idx < (int)combat._speedTable.size(); ++idx) {
			if (combat._whosTurn != -1) {
				combat._charsGone[combat._whosTurn] = true;
			}

			combat._whosSpeed = (combat._whosSpeed + 1) % combat._speedTable.size();
			combat._whosTurn = combat._speedTable[combat._whosSpeed];
			if (combat.allHaveGone()) {
				idx = -1;
				break;
			}

			if (combat._whosTurn < (int)combat._combatParty.size()) {
				// If it's a party member, only allow them to become active if
				// they're still conscious
				if (combat._combatParty[idx]->isDisabledOrDead())
					continue;
			}

			break;
		}

		if (idx == -1) {
			if (!combat.charsCantAct())
				return;

			combat.setSpeedTable();
			combat._whosTurn = -1;
			combat._whosSpeed = -1;
			Common::fill(&combat._charsGone[0], &combat._charsGone[0] + combat._charsGone.size(), 0);
			continue;
		}

		if (combat._whosTurn < (int)combat._combatParty.size()) {
			// It's a party character's turn now, so highlight the character
			if (!combat.allHaveGone()) {
				highlightChar(combat._whosTurn);
			}
			break;
		} else {
			// It's a monster's turn to attack
			combat.doMonsterTurn(0);
			if (!party._dead) {
				party.checkPartyDead();
				if (party._dead)
					break;
			}
		}
	}
}

void Interface::spellFX(Character *c) {
	Combat &combat = *_vm->_combat;
	EventsManager &events = *_vm->_events;
	Party &party = *_vm->_party;
	Screen &screen = *_vm->_screen;
	Sound &sound = *_vm->_sound;

	// Ensure there's no alraedy running effect for the given character
	uint charIndex;
	for (charIndex = 0; charIndex < party._activeParty.size(); ++charIndex) {
		if (&party._activeParty[charIndex] == c)
			break;
	}
	if (charIndex == party._activeParty.size() || _charFX[charIndex])
		return;

	if (screen._windows[12]._enabled)
		screen._windows[12].close();

	if (combat._combatMode == COMBATMODE_2) {
		for (uint idx = 0; idx < combat._combatParty.size(); ++idx) {
			if (combat._combatParty[idx]->_rosterId == c->_rosterId) {
				charIndex = idx;
				break;
			}
		}
	}

	int tillMove = _tillMove;
	_tillMove = 0;
	sound.playFX(20);

	for (int frameNum = 0; frameNum < 4; ++frameNum) {
		events.updateGameCounter();
		_spellFxSprites.draw(screen, frameNum, Common::Point(
			Res.CHAR_FACES_X[charIndex], 150));

		if (!screen._windows[11]._enabled)
			draw3d(false);

		screen._windows[0].update();
		events.wait(screen._windows[11]._enabled ? 2 : 1,false);
	}

	drawParty(true);
	_tillMove = tillMove;
}


} // End of namespace Xeen