/* 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/locations.h"
#include "xeen/dialogs/dialogs_input.h"
#include "xeen/dialogs/dialogs_items.h"
#include "xeen/dialogs/dialogs_party.h"
#include "xeen/dialogs/dialogs_query.h"
#include "xeen/dialogs/dialogs_spells.h"
#include "xeen/resources.h"
#include "xeen/xeen.h"

#define WAIT(TIME) if (_subtitles.wait(TIME)) goto exit

namespace Xeen {
namespace Locations {

BaseLocation::BaseLocation(LocationAction action) : ButtonContainer(g_vm),
		_locationActionId(action), _ccNum(g_vm->_files->_ccNum),
		_vocName("hello1.voc"), _exitToUi(false) {
	_townMaxId = (action >= SPHINX) ? 0 : Res.TOWN_MAXES[_ccNum][action];
	if (action < NO_ACTION) {
		_songName = Res.TOWN_ACTION_MUSIC[_ccNum][action];
		_townSprites.resize(Res.TOWN_ACTION_FILES[_ccNum][action]);
	}

	_animFrame = 0;
	_drawFrameIndex = 0;
	_farewellTime = 0;
	_drawCtr1 = _drawCtr2 = 0;
	_animPos = Common::Point(8, 8);
}

BaseLocation::~BaseLocation() {
	Interface &intf = *g_vm->_interface;

	for (uint idx = 0; idx < _townSprites.size(); ++idx)
		_townSprites[idx].clear();
	intf.mainIconsPrint();
}

int BaseLocation::show() {
	Map &map = *g_vm->_map;
	Party &party = *g_vm->_party;
	Sound &sound = *g_vm->_sound;
	Windows &windows = *g_vm->_windows;

	// Play the appropriate music
	sound.stopSound();
	sound.playSong(_songName, 223);

	// Load the needed sprite sets for the location
	for (uint idx = 0; idx < _townSprites.size(); ++idx) {
		Common::String shapesName = Common::String::format("%s%d.twn",
			Res.TOWN_ACTION_SHAPES[_locationActionId], idx + 1);
		_townSprites[idx].load(shapesName);
	}

	Character *charP = &party._activeParty[0];

	// Draw the background and the text window
	drawBackground();
	drawWindow();
	drawAnim(true);

	// Play the welcome speech
	sound.playVoice(_vocName);

	do {
		wait();
		charP = doOptions(charP);
		if (_vm->shouldExit() || _exitToUi)
			return 0;

		Common::String msg = createLocationText(*charP);
		windows[10].writeString(msg);
		drawButtons(&windows[0]);
	} while (_buttonValue != Common::KEYCODE_ESCAPE);

	// Handle any farewell message
	farewell();

	int result;
	if (party._mazeId != 0) {
		map.load(party._mazeId);
		_farewellTime += 1440;
		party.addTime(_farewellTime);
		result = 0;
	} else {
		//_vm->_saves->saveChars();
		result = 2;
	}

	return result;
}

void BaseLocation::drawBackground() {
	Interface &intf = *g_vm->_interface;

	intf._face1UIFrame = intf._face2UIFrame = 0;
	intf._dangerSenseUIFrame = 0;
	intf._spotDoorsUIFrame = 0;
	intf._levitateUIFrame = 0;
	_townSprites[_drawFrameIndex / 8].draw(0, _drawFrameIndex % 8, _animPos);
}

void BaseLocation::drawWindow() {
	Interface &intf = *g_vm->_interface;
	Party &party = *g_vm->_party;
	Windows &windows = *g_vm->_windows;

	Character *charP = &party._activeParty[0];
	Common::String title = createLocationText(*charP);

	// Open up the window and write the string
	intf.assembleBorder();
	windows[10].open();
	windows[10].writeString(title);
	drawButtons(&windows[0]);

	windows[0].update();
	intf.highlightChar(0);
}

void BaseLocation::drawAnim(bool flag) {
	Interface &intf = *g_vm->_interface;
	Sound &sound = *g_vm->_sound;
	Windows &windows = *g_vm->_windows;

	// TODO: Figure out a clean way to split method into individual location classes
	if (_locationActionId == BLACKSMITH) {
		if (sound.isSoundPlaying()) {
			if (_ccNum) {
				_townSprites[_drawFrameIndex / 8].draw(0, _drawFrameIndex % 8, _animPos);
				_townSprites[2].draw(0, _vm->getRandomNumber(11) == 1 ? 9 : 10,
					Common::Point(34, 33));
				_townSprites[2].draw(0, _vm->getRandomNumber(5) + 3,
					Common::Point(34, 54));
			}
		} else {
			_townSprites[_drawFrameIndex / 8].draw(0, _drawFrameIndex % 8, _animPos);
			if (_ccNum) {
				_townSprites[2].draw(0, _vm->getRandomNumber(11) == 1 ? 9 : 10,
					Common::Point(34, 33));
			}
		}
	} else if (!_ccNum || _locationActionId != TRAINING) {
		if (!_townSprites[_drawFrameIndex / 8].empty())
			_townSprites[_drawFrameIndex / 8].draw(0, _drawFrameIndex % 8, _animPos);
	}

	switch (_locationActionId) {
	case BANK:
		if (sound.isSoundPlaying() || (_ccNum && _animFrame)) {
			if (_ccNum) {
				if (sound.isSoundPlaying() || _animFrame == 1) {
					_townSprites[4].draw(0, _vm->getRandomNumber(13, 18),
						Common::Point(8, 30));
				} else if (_animFrame > 1) {
					_townSprites[4].draw(0, 13 - _animFrame++,
						Common::Point(8, 30));
					if (_animFrame > 14)
						_animFrame = 0;
				}
			} else {
				_townSprites[2].draw(0, _vm->getRandomNumber(7, 11), Common::Point(8, 8));
			}
		}
		break;

	case GUILD:
		if (!sound.isSoundPlaying()) {
			if (_ccNum) {
				if (_animFrame) {
					_animFrame ^= 1;
					_townSprites[6].draw(0, _animFrame, Common::Point(8, 106));
				} else {
					_townSprites[6].draw(0, _vm->getRandomNumber(3), Common::Point(161, 48));
				}
			}
		}
		break;

	case TAVERN:
		if (sound.isSoundPlaying() && _ccNum) {
			_townSprites[4].draw(0, _vm->getRandomNumber(7), Common::Point(153, 49));
		}
		break;

	case TEMPLE:
		if (sound.isSoundPlaying()) {
			_townSprites[3].draw(0, _vm->getRandomNumber(2, 4), Common::Point(8, 8));

		}
		break;

	case TRAINING:
		if (sound.isSoundPlaying()) {
			if (_ccNum) {
				_townSprites[_drawFrameIndex / 8].draw(0, _drawFrameIndex % 8, _animPos);
			}
		} else {
			if (_ccNum) {
				_townSprites[0].draw(0, ++_animFrame % 8, Common::Point(8, 8));
				_townSprites[5].draw(0, _vm->getRandomNumber(5), Common::Point(61, 74));
			} else {
				_townSprites[1].draw(0, _vm->getRandomNumber(8, 12), Common::Point(8, 8));
			}
		}
		break;

	default:
		break;
	}

	if (flag) {
		intf._face1UIFrame = 0;
		intf._face2UIFrame = 0;
		intf._dangerSenseUIFrame = 0;
		intf._spotDoorsUIFrame = 0;
		intf._levitateUIFrame = 0;

		intf.assembleBorder();
	}

	if (windows[11]._enabled) {
		_drawCtr1 = (_drawCtr1 + 1) % 2;
		int newFrame = _vm->getRandomNumber(3);

		if (!_drawCtr1 || !_drawCtr2) {
			if (--_drawCtr2 <= 0) {
				newFrame = 0;
				_drawCtr2 = 0;
			}
		}
		_drawFrameIndex = newFrame;
	} else {
		_drawFrameIndex = (_drawFrameIndex + 1) % _townMaxId;
	}

	if (_ccNum) {
		if (_locationActionId == BLACKSMITH && (_drawFrameIndex == 4 || _drawFrameIndex == 13))
			sound.playFX(45);

		if (_locationActionId == TRAINING && _drawFrameIndex == 23) {
			sound.playSound("spit1.voc");
		}
	} else {
		if (_townMaxId == 32 && _drawFrameIndex == 0)
			_drawFrameIndex = 17;
		if (_townMaxId == 26 && _drawFrameIndex == 0)
			_drawFrameIndex = 20;
		if (_locationActionId == BLACKSMITH && (_drawFrameIndex == 3 || _drawFrameIndex == 9))
			sound.playFX(45);
	}

	windows[3].update();

	if (_locationActionId == BANK)
		_animFrame = 2;
}

int BaseLocation::wait() {
	EventsManager &events = *g_vm->_events;
	Windows &windows = *g_vm->_windows;

	_buttonValue = 0;
	while (!_vm->shouldExit() && !_buttonValue) {
		events.updateGameCounter();
		while (!_vm->shouldExit() && !_buttonValue && events.timeElapsed() < 3) {
			events.pollEventsAndWait();
			checkEvents(_vm);
		}
		if (!_buttonValue)
			drawAnim(!windows[11]._enabled);
	}

	return _buttonValue;
}

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

BankLocation::BankLocation() : BaseLocation(BANK) {
	_icons1.load("bank.icn");
	_icons2.load("bank2.icn");
	addButton(Common::Rect(234, 108, 259, 128), Common::KEYCODE_d, &_icons1);
	addButton(Common::Rect(261, 108, 285, 128), Common::KEYCODE_w, &_icons1);
	addButton(Common::Rect(288, 108, 312, 128), Common::KEYCODE_ESCAPE, &_icons1);
	_animFrame = 1;

	_vocName = _ccNum ? "bank1.voc" : "banker.voc";
}

Common::String BankLocation::createLocationText(Character &ch) {
	Party &party = *g_vm->_party;
	return Common::String::format(Res.BANK_TEXT,
		XeenEngine::printMil(party._bankGold).c_str(),
		XeenEngine::printMil(party._bankGems).c_str(),
		XeenEngine::printMil(party._gold).c_str(),
		XeenEngine::printMil(party._gems).c_str());
}

void BankLocation::drawBackground() {
	if (_ccNum) {
		_townSprites[4].draw(0, _vm->getRandomNumber(13, 18),
			Common::Point(8, 30));
	}
}

Character *BankLocation::doOptions(Character *c) {
	if (_buttonValue == Common::KEYCODE_d)
		_buttonValue = (int)WHERE_PARTY;
	else if (_buttonValue == Common::KEYCODE_w)
		_buttonValue = (int)WHERE_BANK;
	else
		return c;

	depositWithdrawl((PartyBank)_buttonValue);
	return c;
}

void BankLocation::depositWithdrawl(PartyBank whereId) {
	Party &party = *g_vm->_party;
	Sound &sound = *g_vm->_sound;
	Windows &windows = *g_vm->_windows;
	Window &w = windows[35];
	int gold, gems;

	if (whereId == WHERE_BANK) {
		gold = party._bankGold;
		gems = party._bankGems;
	} else {
		gold = party._gold;
		gems = party._gems;
	}

	for (uint idx = 0; idx < _buttons.size(); ++idx)
		_buttons[idx]._sprites = &_icons2;
	_buttons[0]._value = Common::KEYCODE_o;
	_buttons[1]._value = Common::KEYCODE_e;
	_buttons[2]._value = Common::KEYCODE_ESCAPE;

	Common::String msg = Common::String::format(Res.GOLD_GEMS,
		Res.DEPOSIT_WITHDRAWL[whereId],
		XeenEngine::printMil(gold).c_str(),
		XeenEngine::printMil(gems).c_str());

	w.open();
	w.writeString(msg);
	drawButtons(&w);
	w.update();

	sound.stopSound();
	File voc("coina.voc");
	ConsumableType consType = CONS_GOLD;

	do {
		wait();
		if (_buttonValue == Common::KEYCODE_o) {
			consType = CONS_GOLD;
		} else if (_buttonValue == Common::KEYCODE_e) {
			consType = CONS_GEMS;
		} else if (_buttonValue == Common::KEYCODE_ESCAPE) {
			break;
		} else {
			continue;
		}

		if ((whereId == WHERE_BANK && !party._bankGems && consType == CONS_GEMS) ||
			(whereId == WHERE_BANK && !party._bankGold && consType == CONS_GOLD) ||
			(whereId == WHERE_PARTY && !party._gems && consType == CONS_GEMS) ||
			(whereId == WHERE_PARTY && !party._gold && consType == CONS_GOLD)) {
			party.notEnough(consType, whereId, WHERE_BANK, WT_LOC_WAIT);
		} else {
			w.writeString(Res.AMOUNT);
			int amount = NumericInput::show(_vm, 35, 10, 77);

			if (amount) {
				if (consType == CONS_GEMS) {
					if (party.subtract(CONS_GEMS, amount, whereId, WT_LOC_WAIT)) {
						if (whereId == WHERE_BANK) {
							party._gems += amount;
						} else {
							party._bankGems += amount;
						}
					}
				} else {
					if (party.subtract(CONS_GOLD, amount, whereId, WT_LOC_WAIT)) {
						if (whereId == WHERE_BANK) {
							party._gold += amount;
						} else {
							party._bankGold += amount;
						}
					}
				}
			}

			if (whereId == WHERE_BANK) {
				gold = party._bankGold;
				gems = party._bankGems;
			} else {
				gold = party._gold;
				gems = party._gems;
			}

			sound.playSound(voc);
			msg = Common::String::format(Res.GOLD_GEMS_2, Res.DEPOSIT_WITHDRAWL[whereId],
				XeenEngine::printMil(gold).c_str(), XeenEngine::printMil(gems).c_str());
			w.writeString(msg);
			w.update();
		}
	} while (!g_vm->shouldExit());

	for (uint idx = 0; idx < _buttons.size(); ++idx)
		_buttons[idx]._sprites = &_icons1;
	_buttons[0]._value = Common::KEYCODE_d;
	_buttons[1]._value = Common::KEYCODE_w;
	_buttons[2]._value = Common::KEYCODE_ESCAPE;

	w.close();
	clearEvents();
}

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

BlacksmithLocation::BlacksmithLocation() : BaseLocation(BLACKSMITH) {
	_icons1.load("esc.icn");
	addButton(Common::Rect(261, 108, 285, 128), Common::KEYCODE_ESCAPE, &_icons1);
	addButton(Common::Rect(234, 54, 308, 62), 0);
	addButton(Common::Rect(234, 64, 308, 72), Common::KEYCODE_b);
	addButton(Common::Rect(234, 74, 308, 82), 0);
	addButton(Common::Rect(234, 84, 308, 92), 0);

	_vocName = _ccNum ? "see2.voc" : "whaddayo.voc";
}

Common::String BlacksmithLocation::createLocationText(Character &ch) {
	Party &party = *g_vm->_party;
	return Common::String::format(Res.BLACKSMITH_TEXT,
		ch._name.c_str(), XeenEngine::printMil(party._gold).c_str());
}

Character *BlacksmithLocation::doOptions(Character *c) {
	Interface &intf = *g_vm->_interface;
	Party &party = *g_vm->_party;

	if (_buttonValue >= Common::KEYCODE_F1 && _buttonValue <= Common::KEYCODE_F6) {
		// Switch character
		_buttonValue -= Common::KEYCODE_F1;
		if (_buttonValue < (int)party._activeParty.size()) {
			c = &party._activeParty[_buttonValue];
			intf.highlightChar(_buttonValue);
		}
	} else if (_buttonValue == Common::KEYCODE_b) {
		c = ItemsDialog::show(_vm, c, ITEMMODE_BUY);
		_buttonValue = 0;
	}

	return c;
}

void BlacksmithLocation::farewell() {
	Sound &sound = *g_vm->_sound;

	if (_ccNum) {
		sound.stopSound();
		sound.playVoice("come1.voc", 1);
	}
}

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

GuildLocation::GuildLocation() : BaseLocation(GUILD) {
	loadStrings("spldesc.bin");
	_icons1.load("esc.icn");
	addButton(Common::Rect(261, 108, 285, 128), Common::KEYCODE_ESCAPE, &_icons1);
	addButton(Common::Rect(234, 54, 308, 62), 0);
	addButton(Common::Rect(234, 64, 308, 72), Common::KEYCODE_b);
	addButton(Common::Rect(234, 74, 308, 82), Common::KEYCODE_s);
	addButton(Common::Rect(234, 84, 308, 92), 0);
	g_vm->_mode = MODE_INTERACTIVE7;

	_vocName = _ccNum ? "parrot1.voc" : "guild10.voc";
}

Common::String GuildLocation::createLocationText(Character &ch) {
	Party &party = *g_vm->_party;

	Common::String desc = !ch.guildMember() ? Res.GUILD_NOT_MEMBER_TEXT :
		Common::String::format(Res.GUILD_TEXT, ch._name.c_str());
	return Common::String::format(Res.GUILD_OPTIONS, desc.c_str(),
		g_vm->printMil(party._gold).c_str());
}

Character *GuildLocation::doOptions(Character *c) {
	Interface &intf = *g_vm->_interface;
	Party &party = *g_vm->_party;
	Sound &sound = *g_vm->_sound;

	if (_buttonValue >= Common::KEYCODE_F1 && _buttonValue <= Common::KEYCODE_F6) {
		// Switch character
		_buttonValue -= Common::KEYCODE_F1;
		if (_buttonValue < (int)party._activeParty.size()) {
			c = &party._activeParty[_buttonValue];
			intf.highlightChar(_buttonValue);

			if (!c->guildMember()) {
				sound.stopSound();
				_animFrame = 5;
				sound.playSound(_ccNum ? "skull1.voc" : "guild11.voc", 1);
			}
		}
	} else if (_buttonValue == Common::KEYCODE_s) {
		if (c->guildMember())
			c = SpellsDialog::show(_vm, this, c, SPELLS_DIALOG_INFO);
		_buttonValue = 0;
	} else if (_buttonValue == Common::KEYCODE_b) {
		if (!c->noActions()) {
			if (c->guildMember())
				c = SpellsDialog::show(_vm, this, c, SPELLS_DIALOG_BUY);
			_buttonValue = 0;
		}
	}

	return c;
}

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

TavernLocation::TavernLocation() : BaseLocation(TAVERN) {
	_v21 = 0;
	_v22 = 0;
	_v23 = 0;
	_v24 = 0;

	loadStrings("tavern.bin");
	_icons1.load("tavern.icn");
	addButton(Common::Rect(281, 108, 305, 128), Common::KEYCODE_ESCAPE, &_icons1);
	addButton(Common::Rect(242, 108, 266, 128), Common::KEYCODE_s, &_icons1);
	addButton(Common::Rect(234, 54, 308, 62), Common::KEYCODE_d);
	addButton(Common::Rect(234, 64, 308, 72), Common::KEYCODE_f);
	addButton(Common::Rect(234, 74, 308, 82), Common::KEYCODE_t);
	addButton(Common::Rect(234, 84, 308, 92), Common::KEYCODE_r);
	g_vm->_mode = MODE_INTERACTIVE7;

	_vocName = _ccNum ? "hello1.voc" : "hello.voc";
}

Common::String TavernLocation::createLocationText(Character &ch) {
	Party &party = *g_vm->_party;
	return Common::String::format(Res.TAVERN_TEXT, ch._name.c_str(),
		Res.FOOD_AND_DRINK, XeenEngine::printMil(party._gold).c_str());
}

Character *TavernLocation::doOptions(Character *c) {
	EventsManager &events = *g_vm->_events;
	Interface &intf = *g_vm->_interface;
	Map &map = *g_vm->_map;
	Party &party = *g_vm->_party;
	Sound &sound = *g_vm->_sound;
	Windows &windows = *g_vm->_windows;
	int idx = 0;

	switch (_buttonValue) {
	case Common::KEYCODE_F1:
	case Common::KEYCODE_F2:
	case Common::KEYCODE_F3:
	case Common::KEYCODE_F4:
	case Common::KEYCODE_F5:
	case Common::KEYCODE_F6:
		// Switch character
		_buttonValue -= Common::KEYCODE_F1;
		if (_buttonValue < (int)party._activeParty.size()) {
			c = &party._activeParty[_buttonValue];
			intf.highlightChar(_buttonValue);
			_v21 = 0;
		}
		break;

	case Common::KEYCODE_d:
		// Drink
		if (!c->noActions()) {
			if (party.subtract(CONS_GOLD, 1, WHERE_PARTY, WT_LOC_WAIT)) {
				sound.stopSound();
				sound.playSound("gulp.voc");
				_v21 = 1;

				windows[10].writeString(Common::String::format(Res.TAVERN_TEXT,
					c->_name.c_str(), Res.GOOD_STUFF,
					XeenEngine::printMil(party._gold).c_str()));
				drawButtons(&windows[0]);
				windows[10].update();

				if (_vm->getRandomNumber(100) < 26) {
					++c->_conditions[DRUNK];
					intf.drawParty(true);
					sound.playFX(28);
				}

				wait();
			}
		}
		break;

	case Common::KEYCODE_f: {
		// Food
		if (party._mazeId == (_ccNum ? 29 : 28)) {
			_v22 = party._activeParty.size() * 15;
			_v23 = 10;
			idx = 0;
		} else if (_ccNum && party._mazeId == 31) {
			_v22 = party._activeParty.size() * 60;
			_v23 = 100;
			idx = 1;
		} else if (!_ccNum && party._mazeId == 30) {
			_v22 = party._activeParty.size() * 50;
			_v23 = 50;
			idx = 1;
		} else if (_ccNum) {
			_v22 = party._activeParty.size() * 120;
			_v23 = 250;
			idx = 2;
		} else if (party._mazeId == 49) {
			_v22 = party._activeParty.size() * 120;
			_v23 = 100;
			idx = 2;
		} else {
			_v22 = party._activeParty.size() * 15;
			_v23 = 10;
			idx = 0;
		}

		Common::String msg = _textStrings[(_ccNum ? 60 : 75) + idx];
		windows[10].close();
		windows[12].open();
		windows[12].writeString(msg);
		windows[12].update();

		if (YesNo::show(_vm, false, true)) {
			if (party._food >= _v22) {
				ErrorScroll::show(_vm, Res.FOOD_PACKS_FULL, WT_LOC_WAIT);
			} else if (party.subtract(CONS_GOLD, _v23, WHERE_PARTY, WT_LOC_WAIT)) {
				party._food = _v22;
				sound.stopSound();
				sound.playSound(_ccNum ? "thanks2.voc" : "thankyou.voc", 1);
			}
		}

		windows[12].close();
		windows[10].open();
		_buttonValue = 0;
		break;
	}

	case Common::KEYCODE_r: {
		// Rumors
		if (party._mazeId == (_ccNum ? 29 : 28)) {
			idx = 0;
		} else if (party._mazeId == (_ccNum ? 31 : 30)) {
			idx = 10;
		} else if (_ccNum || party._mazeId == 49) {
			idx = 20;
		}

		Common::String msg = Common::String::format("\x03""c\x0B""012%s",
			_textStrings[(party._day % 10) + idx].c_str());
		Window &w = windows[12];
		w.open();
		w.writeString(msg);
		w.update();

		wait();
		w.close();
		break;
	}

	case Common::KEYCODE_s: {
		// Sign In
		// Set location and position for afterwards
		if (g_vm->getGameID() == GType_Swords) {
			switch (party._mazeId) {
			case 49:
				party._mazePosition = Common::Point(9, 2);
				party._mazeDirection = DIR_WEST;
				break;
			case 53:
				party._mazePosition = Common::Point(13, 1);
				party._mazeDirection = DIR_EAST;
				break;
			case 92:
				party._mazePosition = Common::Point(10, 1);
				party._mazeDirection = DIR_EAST;
				break;
			case 63:
				party._mazePosition = Common::Point(13, 1);
				party._mazeDirection = DIR_SOUTH;
				break;
			default:
				break;
			}
		} else {
			idx = _ccNum ? (party._mazeId - 29) >> 1 : party._mazeId - 28;
			assert(idx >= 0);
			party._mazePosition.x = Res.TAVERN_EXIT_LIST[_ccNum][_locationActionId][idx][0];
			party._mazePosition.y = Res.TAVERN_EXIT_LIST[_ccNum][_locationActionId][idx][1];

			if (!_ccNum || party._mazeId == 29)
				party._mazeDirection = DIR_WEST;
			else if (party._mazeId == 31)
				party._mazeDirection = DIR_EAST;
			else
				party._mazeDirection = DIR_SOUTH;
		}

		party._priorMazeId = party._mazeId;
		for (idx = 0; idx < (int)party._activeParty.size(); ++idx) {
			party._activeParty[idx]._savedMazeId = party._mazeId;
			party._activeParty[idx]._xeenSide = map._loadCcNum;
		}

		g_vm->_mode = MODE_INTERACTIVE7;
		party.addTime(1440);
		party._mazeId = 0;

		// Say farewell
		farewell();
		while (sound.isSoundPlaying())
			events.wait(1);

		// Animate closing a scroll
		doScroll(true, false);
		sound.stopAllAudio();

		// Show the party dialog
		PartyDialog::show(g_vm);

		if (party._mazeId != 0)
			map.load(party._mazeId);
		_exitToUi = true;
		break;
	}

	case Common::KEYCODE_t:
		if (!c->noActions()) {
			if (!_v21) {
				windows[10].writeString(Common::String::format(Res.TAVERN_TEXT,
					c->_name.c_str(), Res.HAVE_A_DRINK,
					XeenEngine::printMil(party._gold).c_str()));
				drawButtons(&windows[0]);
				windows[10].update();
				wait();
			} else {
				_v21 = 0;
				if (c->_conditions[DRUNK]) {
					windows[10].writeString(Common::String::format(Res.TAVERN_TEXT,
						c->_name.c_str(), Res.YOURE_DRUNK,
						XeenEngine::printMil(party._gold).c_str()));
					drawButtons(&windows[0]);
					windows[10].update();
					wait();
				} else if (party.subtract(CONS_GOLD, 1, WHERE_PARTY, WT_LOC_WAIT)) {
					sound.stopSound();
					sound.playSound(_ccNum ? "thanks2.voc" : "thankyou.voc", 1);

					if (party._mazeId == (_ccNum ? 29 : 28)) {
						_v24 = 30;
					} else if (_ccNum && party._mazeId == 31) {
						_v24 = 40;
					} else if (!_ccNum && party._mazeId == 45) {
						_v24 = 45;
					} else if (!_ccNum && party._mazeId == 49) {
						_v24 = 60;
					} else if (_ccNum) {
						_v24 = 50;
					}

					Common::String msg = _textStrings[map.mazeData()._tavernTips + _v24];
					map.mazeData()._tavernTips = (map.mazeData()._tavernTips + 1) /
						(_ccNum ? 10 : 15);

					Window &w = windows[12];
					w.open();
					w.writeString(Common::String::format("\x03""c\x0B""012%s", msg.c_str()));
					w.update();
					wait();
					w.close();
				}
			}
		}
		break;

	default:
		break;
	}

	return c;
}

void TavernLocation::farewell() {
	Map &map = *g_vm->_map;
	Party &party = *g_vm->_party;
	Sound &sound = *g_vm->_sound;

	sound.stopSound();
	sound.playVoice(_ccNum ? "gdluck1.voc" : "goodbye.voc");

	map.mazeData()._mazeNumber = party._mazeId;
}

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

TempleLocation::TempleLocation() : BaseLocation(TEMPLE) {
	_currentCharLevel = 0;
	_donation = 0;
	_healCost = 0;
	_uncurseCost = 0;
	_dayOfWeek = 0;
	_v10 = _v11 = 0;
	_v12 = _v13 = 0;
	_v14 = 0;
	_blessed = false;
	_v5 = _v6 = 0;

	_icons1.load("esc.icn");
	addButton(Common::Rect(261, 108, 285, 128), Common::KEYCODE_ESCAPE, &_icons1);
	addButton(Common::Rect(234, 54, 308, 62), Common::KEYCODE_h);
	addButton(Common::Rect(234, 64, 308, 72), Common::KEYCODE_d);
	addButton(Common::Rect(234, 74, 308, 82), Common::KEYCODE_u);
	addButton(Common::Rect(234, 84, 308, 92), 0);

	_vocName = _ccNum ? "help2.voc" : "maywe2.voc";
}

Common::String TempleLocation::createLocationText(Character &ch) {
	Party &party = *g_vm->_party;
	_donation = 0;
	_uncurseCost = 0;
	_healCost = 0;
	_v5 = _v6 = 0;

	if (party._mazeId == (_ccNum ? 29 : 28)) {
		_v10 = _v11 = _v12 = _v13 = 0;
		_v14 = 10;
	} else if (party._mazeId == (_ccNum ? 31 : 30)) {
		_v13 = 10;
		_v12 = 50;
		_v11 = 500;
		_v10 = 100;
		_v14 = 25;
	} else if (party._mazeId == (_ccNum ? 37 : 73)) {
		_v13 = 20;
		_v12 = 100;
		_v11 = 1000;
		_v10 = 200;
		_v14 = 50;
	} else if (_ccNum || party._mazeId == 49) {
		_v13 = 100;
		_v12 = 500;
		_v11 = 5000;
		_v10 = 300;
		_v14 = 100;
	}

	_currentCharLevel = ch.getCurrentLevel();
	if (ch._currentHp < ch.getMaxHP()) {
		_healCost = _currentCharLevel * 10 + _v13;
	}

	for (int attrib = HEART_BROKEN; attrib <= UNCONSCIOUS; ++attrib) {
		if (ch._conditions[attrib])
			_healCost += _currentCharLevel * 10;
	}

	_v6 = 0;
	if (ch._conditions[DEAD]) {
		_v6 += (_currentCharLevel * 100) + (ch._conditions[DEAD] * 50) + _v12;
	}
	if (ch._conditions[STONED]) {
		_v6 += (_currentCharLevel * 100) + (ch._conditions[STONED] * 50) + _v12;
	}
	if (ch._conditions[ERADICATED]) {
		_v5 = (_currentCharLevel * 1000) + (ch._conditions[ERADICATED] * 500) + _v11;
	}

	bool isCursed = ch._items.hasCursedItems();
	if (isCursed || ch._conditions[CURSED])
		_uncurseCost = (_currentCharLevel * 20) + _v10;

	_donation = _blessed ? 0 : _v14;
	_healCost += _v6 + _v5;

	return Common::String::format(Res.TEMPLE_TEXT, ch._name.c_str(),
		_healCost, _donation, XeenEngine::printK(_uncurseCost).c_str(),
		XeenEngine::printMil(party._gold).c_str());
}

Character *TempleLocation::doOptions(Character *c) {
	Interface &intf = *g_vm->_interface;
	Party &party = *g_vm->_party;
	Sound &sound = *g_vm->_sound;

	switch (_buttonValue) {
	case Common::KEYCODE_F1:
	case Common::KEYCODE_F2:
	case Common::KEYCODE_F3:
	case Common::KEYCODE_F4:
	case Common::KEYCODE_F5:
	case Common::KEYCODE_F6:
		// Switch character
		_buttonValue -= Common::KEYCODE_F1;
		if (_buttonValue < (int)party._activeParty.size()) {
			c = &party._activeParty[_buttonValue];
			intf.highlightChar(_buttonValue);
			_dayOfWeek = 0;
		}
		break;

	case Common::KEYCODE_d:
		if (_donation && party.subtract(CONS_GOLD, _donation, WHERE_PARTY, WT_LOC_WAIT)) {
			sound.stopSound();
			sound.playSound("coina.voc", 1);
			_dayOfWeek = (_dayOfWeek + 1) % 10;

			if (_dayOfWeek == (party._day % 10)) {
				party._clairvoyanceActive = true;
				party._lightCount = 1;

				int amt = _dayOfWeek ? _dayOfWeek : 10;
				party._heroism = amt;
				party._holyBonus = amt;
				party._powerShield = amt;
				party._blessed = amt;

				intf.drawParty(true);
				sound.stopSound();
				sound.playSound("ahh.voc");
				_blessed = true;
				_donation = 0;
			}
		}
		break;

	case Common::KEYCODE_h:
		if (_healCost && party.subtract(CONS_GOLD, _healCost, WHERE_PARTY, WT_LOC_WAIT)) {
			c->_magicResistence._temporary = 0;
			c->_energyResistence._temporary = 0;
			c->_poisonResistence._temporary = 0;
			c->_electricityResistence._temporary = 0;
			c->_coldResistence._temporary = 0;
			c->_fireResistence._temporary = 0;
			c->_ACTemp = 0;
			c->_level._temporary = 0;
			c->_luck._temporary = 0;
			c->_accuracy._temporary = 0;
			c->_speed._temporary = 0;
			c->_endurance._temporary = 0;
			c->_personality._temporary = 0;
			c->_intellect._temporary = 0;
			c->_might._temporary = 0;
			c->_currentHp = c->getMaxHP();
			Common::fill(&c->_conditions[HEART_BROKEN], &c->_conditions[NO_CONDITION], 0);

			_farewellTime = 1440;
			intf.drawParty(true);
			sound.stopSound();
			sound.playSound("ahh.voc", 1);
		}
		break;

	case Common::KEYCODE_u:
		if (_uncurseCost && party.subtract(CONS_GOLD, _uncurseCost, WHERE_PARTY, WT_LOC_WAIT)) {
			c->_items.curseUncurse(false);
			c->_conditions[CURSED] = 0;
			_farewellTime = 1440;
			intf.drawParty(true);
			sound.stopSound();
			sound.playSound("ahh.voc", 1);
		}
		break;

	default:
		break;
	}

	return c;
}

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

TrainingLocation::TrainingLocation() : BaseLocation(TRAINING) {
	Common::fill(&_charsTrained[0], &_charsTrained[6], 0);
	_maxLevel = 0;
	_experienceToNextLevel = 0;
	_charIndex = 0;

	_icons1.load("train.icn");
	addButton(Common::Rect(281, 108, 305, 128), Common::KEYCODE_ESCAPE, &_icons1);
	addButton(Common::Rect(242, 108, 266, 128), Common::KEYCODE_t, &_icons1);

	_vocName = _ccNum ? "youtrn1.voc" : "training.voc";
}

Common::String TrainingLocation::createLocationText(Character &ch) {
	Party &party = *g_vm->_party;
	if (_ccNum) {
		switch (party._mazeId) {
		case 29:
			// Castleview
			_maxLevel = 30;
			break;
		case 31:
			// Sandcaster
			_maxLevel = 50;
			break;
		case 37:
			// Olympus
			_maxLevel = 200;
			break;
		default:
			// Kalindra's Castle
			_maxLevel = 100;
			break;
		}
	} else {
		switch (party._mazeId) {
		case 28:
			// Vertigo
			_maxLevel = 10;
			break;
		case 30:
			// Rivercity
			_maxLevel = 15;
			break;
		default:
			// Newcastle
			_maxLevel = 20;
			break;
		}
	}

	_experienceToNextLevel = ch.experienceToNextLevel();

	Common::String msg;
	if (_experienceToNextLevel && ch._level._permanent < _maxLevel) {
		// Need more experience
		int nextLevel = ch._level._permanent + 1;
		msg = Common::String::format(Res.EXPERIENCE_FOR_LEVEL,
			ch._name.c_str(), _experienceToNextLevel, nextLevel);
	} else if (ch._level._permanent >= _maxLevel) {
		// At maximum level
		_experienceToNextLevel = 1;
		msg = Common::String::format(Res.TRAINING_LEARNED_ALL, ch._name.c_str());
	} else {
		// Eligble for level increase
		uint cost = ch._level._permanent * ch._level._permanent * 10;
		msg = Common::String::format(Res.ELIGIBLE_FOR_LEVEL,
			ch._name.c_str(), ch._level._permanent + 1, cost);
	}

	return Common::String::format(Res.TRAINING_TEXT, msg.c_str(),
		XeenEngine::printMil(party._gold).c_str());
}

Character *TrainingLocation::doOptions(Character *c) {
	Interface &intf = *g_vm->_interface;
	Party &party = *g_vm->_party;
	Sound &sound = *g_vm->_sound;

	switch (_buttonValue) {
	case Common::KEYCODE_F1:
	case Common::KEYCODE_F2:
	case Common::KEYCODE_F3:
	case Common::KEYCODE_F4:
	case Common::KEYCODE_F5:
	case Common::KEYCODE_F6:
		// Switch character
		_buttonValue -= Common::KEYCODE_F1;
		if (_buttonValue < (int)party._activeParty.size()) {
			_charIndex = _buttonValue;
			c = &party._activeParty[_buttonValue];
			intf.highlightChar(_buttonValue);
		}
		break;

	case Common::KEYCODE_t:
		if (_experienceToNextLevel) {
			sound.stopSound();
			_drawFrameIndex = 0;

			Common::String name;
			if (c->_level._permanent >= _maxLevel) {
				name = _ccNum ? "gtlost.voc" : "trainin1.voc";
			} else {
				name = _ccNum ? "gtlost.voc" : "trainin0.voc";
			}

			sound.playSound(name);

		} else if (!c->noActions()) {
			if (party.subtract(CONS_GOLD, (c->_level._permanent * c->_level._permanent) * 10, WHERE_PARTY, WT_LOC_WAIT)) {
				_drawFrameIndex = 0;
				sound.stopSound();
				sound.playSound(_ccNum ? "prtygd.voc" : "trainin2.voc", 1);

				c->_experience -=  c->nextExperienceLevel() -
					(c->getCurrentExperience() - c->_experience);
				c->_level._permanent++;

				if (!_charsTrained[_charIndex]) {
					party.addTime(1440);
					_charsTrained[_charIndex] = true;
				}

				party.resetTemps();
				c->_currentHp = c->getMaxHP();
				c->_currentSp = c->getMaxSP();
				intf.drawParty(true);
			}
		}
		break;

	default:
		break;
	}

	return c;
}

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

ArenaLocation::ArenaLocation() : BaseLocation(ARENA) {
}

int ArenaLocation::show() {
	Map &map = *g_vm->_map;
	Party &party = *g_vm->_party;
	Windows &windows = *g_vm->_windows;
	int level, howMany;
	bool check;
	const char *SUFFIXES[10] = { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" };

	Common::Array<MazeMonster> &monsters = map._mobData._monsters;

	if (monsters.size() > 0) {
		for (uint idx = 0; idx < monsters.size(); ++idx) {
			MazeMonster &monster = monsters[idx];
			if (monster._position.x != 0x80 && monster._position.y != 0x80) {
				LocationMessage::show(27, Res.WARZONE_BATTLE_MASTER,
					map._events._text[4], 300);
				goto exit;
			}
		}

		// Give each character the award
		for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
			party._activeParty[idx]._awards[WARZONE_AWARD]++;
		}

		Common::String format = map._events._text[3];
		int count = party._activeParty[0]._awards[WARZONE_AWARD];
		int suffixNum = (count < 10) ? count : 0;
		Common::String msg = Common::String::format(format.c_str(), count, SUFFIXES[suffixNum]);

		LocationMessage::show(27, Res.WARZONE_BATTLE_MASTER, msg, 1);

		map.load(28);
		goto exit;
	}

	for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
		if (party._activeParty[idx]._awards[WARZONE_AWARD] >= 99) {
			LocationMessage::show(27, Res.WARZONE_BATTLE_MASTER, Res.WARZONE_MAXED, 1);
			map.load(28);
			goto exit;
		}
	}

	check = LocationMessage::show(27, Res.WARZONE_BATTLE_MASTER, map._events._text[0].c_str(), 0);
	if (!check) {
		LocationMessage::show(27, Res.WARZONE_BATTLE_MASTER,
			map._events._text[1].c_str(), 300);
		windows.closeAll();
		map.load(6);
		party._mazePosition = Common::Point(12, 4);
		party._mazeDirection = DIR_WEST;
		return 0;
	}

	do {
		LocationMessage::show(27, Res.WARZONE_BATTLE_MASTER, Res.WARZONE_LEVEL, 2);
		level = NumericInput::show(g_vm, 11, 2, 200);
	} while (!g_vm->shouldExit() && level > 10);
	if (level == 0)
		goto exit;

	do {
		LocationMessage::show(27, Res.WARZONE_BATTLE_MASTER, Res.WARZONE_HOW_MANY, 2);
		howMany = NumericInput::show(g_vm, 11, 2, 200);
	} while (!g_vm->shouldExit() && howMany > 20);
	if (howMany == 0)
		goto exit;

	LocationMessage::show(27, Res.WARZONE_BATTLE_MASTER, map._events._text[2], 1);

	// Clear monsters array
	party._mazeDirection = DIR_EAST;
	map._mobData.clearMonsterSprites();
	monsters.clear();
	monsters.resize(howMany);

	for (uint idx = 0; idx < monsters.size(); ++idx) {
		MazeMonster &mon = monsters[idx];
		mon._spriteId = g_vm->getRandomNumber(1, 7) + (level - 1) * 7;
		if (mon._spriteId > 67)
			mon._spriteId -= 3;
		if (mon._spriteId == 59)
			mon._spriteId = 60;
		if (mon._spriteId == 28)
			mon._spriteId = 29;

		// Set up normal and attack sprites
		map._mobData.addMonsterSprites(mon);

		mon._position.x = g_vm->getRandomNumber(3, 11);
		mon._position.y = g_vm->getRandomNumber(2, 10);
		if ((mon._position.x == 5 || mon._position.x == 10) &&
			(mon._position.y == 8 || mon._position.y == 4))
			mon._position.y = 5;

		mon._id = g_vm->getRandomNumber(4);
		const MonsterStruct &data = map._monsterData[mon._spriteId];
		mon._hp = data._hp;
		mon._frame = g_vm->getRandomNumber(7);
		mon._effect1 = mon._effect2 = data._animationEffect;
		if (data._animationEffect)
			mon._effect3 = g_vm->getRandomNumber(7);
		mon._isAttacking = true;
	}
exit:
	party._mazeDirection = DIR_EAST;
	party.moveToRunLocation();
	windows.closeAll();
	return 0;
}

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

CutsceneLocation::CutsceneLocation(LocationAction action) : BaseLocation(action), _keyFound(false) {
	Party &party = *g_vm->_party;
	_mazeId = party._mazeId;
	_mazePos = party._mazePosition;
	_mazeDir = party._mazeDirection;
}

void CutsceneLocation::setNewLocation() {
	Map &map = *g_vm->_map;
	Party &party = *g_vm->_party;
	map.load(_mazeId);
	party._mazePosition = _mazePos;
	party._mazeDirection = _mazeDir;
}

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

const int16 REAPER_X1[2][14] = {
	{ 0, -10, -20, -30, -40, -49, -49, -49, -49, -49, -49, -49, -49, -49 },
	{ 0, 2, 6, 8, 11, 14, 17, 21, 27, 35, 43, 51, 60, 67 }
};
const int16 REAPER_Y1[2][14] = {
	{ 0, 12, 25, 37, 45, 50, 56, 61, 67, 72, 78, 83, 89, 94 },
	{ 0, 6, 12, 17, 23, 29, 36, 42, 49, 54, 61, 68, 73, 77 }
};
const int16 REAPER_X2[14] = {
	160, 152, 146, 138, 131, 124, 117, 111, 107, 105, 103, 101, 100, 97
};
const int16 REAPER_X3[14] = {
	0, -3, -4, -7, -9, -11, -13, -14, -13, -10, -7, -4, 0, -1
};

ReaperCutscene::ReaperCutscene() : CutsceneLocation(REAPER) {
}

int ReaperCutscene::show() {
	EventsManager &events = *g_vm->_events;
	Interface &intf = *g_vm->_interface;
	Party &party = *g_vm->_party;
	Screen &screen = *g_vm->_screen;
	Sound &sound = *g_vm->_sound;
	Windows &windows = *g_vm->_windows;

	SpriteResource sprites1(_ccNum ? "tower1.zom" : "tower.vga", _ccNum);
	SpriteResource sprites2(_ccNum ? "tower2.zom" : "freap.vga", _ccNum);

	Graphics::ManagedSurface savedBg;
	savedBg.copyFrom(screen);
	getNewLocation();

	for (int idx = 13; idx >= 0; --idx) {
		events.updateGameCounter();
		sprites1.draw(0, 0, Common::Point(REAPER_X1[_ccNum][idx], REAPER_Y1[_ccNum][idx]), 0, idx);
		if (_ccNum) {
			sprites1.draw(0, 1, Common::Point(REAPER_X2[idx], REAPER_Y1[1][idx]), 0, idx);
			sprites1.draw(0, party._isNight ? 3 : 2, Common::Point(REAPER_X3[idx], REAPER_Y1[1][idx]), 0, idx);
		}

		WAIT(1);
	}

	if (_ccNum) {
		for (int idx = -200; idx < 0; idx += 16) {
			events.updateGameCounter();
			sprites1.draw(0, 0, Common::Point(0, 0));
			sprites1.draw(0, 1, Common::Point(160, 0));
			sprites1.draw(0, 2, Common::Point(0, 0));
			sprites2.draw(0, 0, Common::Point(idx, 0), SPRFLAG_800);
			sprites2.draw(0, 5, Common::Point(160 + idx, 0), SPRFLAG_800);

			WAIT(1);
		}
	} else {
		for (int idx = 200; idx >= 0; idx -= 16) {
			events.updateGameCounter();
			sprites1.draw(0, 0, Common::Point(0, 0));
			sprites2.draw(0, 0, Common::Point(idx, 0), SPRFLAG_800);

			WAIT(1);
		}
	}

	sound.setMusicPercent(38);
	sprites1.draw(0, 0, Common::Point(0, 0));
	if (_ccNum) {
		sprites1.draw(0, 1, Common::Point(160, 0));
		sprites1.draw(0, party._isNight ? 3 : 2);
	}

	if (!_ccNum) {
		_subtitles.setLine(_keyFound ? 5 : 6);
		sound.playVoice(_keyFound ? "reaper12.voc" : "reaper14.voc");
	} else if (_keyFound) {
		_subtitles.setLine(2);
		sound.playVoice("howdid1.voc");
	}

	do {
		events.updateGameCounter();
		int frame = g_vm->getRandomNumber(4);
		if (_ccNum) {
			sprites2.draw(0, frame);
			sprites2.draw(0, frame + 5, Common::Point(160, 0));
		} else {
			sprites2.draw(0, 0);
			sprites2.draw(0, frame);
		}

		_subtitles.show();

		WAIT(1);
	} while (sound.isSoundPlaying());

	sprites2.draw(0, 0, Common::Point(0, 0));
	if (_ccNum)
		sprites2.draw(0, 5, Common::Point(160, 0));
	windows[0].update();
	WAIT(7);

	if (_keyFound) {
		sound.playVoice(_ccNum ? "goin1.voc" : "reaper13.voc");
	} else {
		if (_ccNum)
			_subtitles.setLine(3);
		sound.playVoice(_ccNum ? "needkey1.voc" : "reaper15.voc");
	}

	do {
		events.updateGameCounter();
		int frame = g_vm->getRandomNumber(4);
		if (_ccNum) {
			sprites2.draw(0, frame, Common::Point(0, 0));
			sprites2.draw(0, frame + 5, Common::Point(160, 0));
		} else {
			sprites2.draw(0, 0);
			sprites2.draw(0, frame);
		}

		WAIT(1);
	} while (_subtitles.lineActive());

	sprites2.draw(0, 0, Common::Point(0, 0));
	if (_ccNum)
		sprites2.draw(0, 5, Common::Point(160, 0));
	windows[0].update();
	WAIT(1);

	if (_keyFound) {
		for (int idx = 0; idx < 14; ++idx) {
			events.updateGameCounter();
			screen.blitFrom(savedBg);
			sprites1.draw(0, 0, Common::Point(REAPER_X1[_ccNum][idx], REAPER_Y1[_ccNum][idx]), 0, idx);

			if (_ccNum) {
				sprites1.draw(0, 1, Common::Point(REAPER_X2[idx], REAPER_Y1[1][idx]), 0, idx);
				sprites1.draw(0, party._isNight ? 3 : 2, Common::Point(REAPER_X3[idx], REAPER_Y1[1][idx]), 0, idx);
			}

			windows[0].update();
			WAIT(1);
		}

		screen.blitFrom(savedBg);
		windows[0].update();
	}
exit:
	screen.blitFrom(savedBg);
	windows[0].update();

	setNewLocation();

	// Restore game screen
	sound.stopSound();
	sound.setMusicPercent(75);

	screen.loadBackground("back.raw");
	intf.drawParty(false);
	intf.draw3d(false, false);

	events.clearEvents();
	return 0;
}

void ReaperCutscene::getNewLocation() {
	Map &map = *g_vm->_map;
	Party &party = *g_vm->_party;

	if (_ccNum) {
		switch (party._mazeId) {
		case 3:
			if (party._questItems[40]) {
				_mazeId = 57;
				_mazePos = Common::Point(11, 8);
				_mazeDir = DIR_WEST;
				_keyFound = true;
			}
			break;

		case 12:
			if (party._questItems[3]) {
				_mazeId = 55;
				_mazePos = Common::Point(3, 8);
				_mazeDir = DIR_EAST;
				_keyFound = true;
			}
			break;

		case 13:
			if (party._questItems[43]) {
				_mazeId = 69;
				_mazePos = Common::Point(7, 4);
				_mazeDir = DIR_NORTH;
				_keyFound = true;
			}
			break;

		case 16:
			if (party._questItems[41]) {
				_mazeId = 61;
				_mazePos = Common::Point(7, 12);
				_mazeDir = DIR_SOUTH;
				_keyFound = true;
			}
			break;

		case 23:
			if (party._questItems[42]) {
				_mazeId = 65;
				_mazePos = Common::Point(3, 8);
				_mazeDir = DIR_EAST;
				_keyFound = true;
			}
			break;

		case 29:
			if (party._questItems[44]) {
				_mazeId = 53;
				_mazePos = Common::Point(11, 8);
				_mazeDir = DIR_WEST;
				_keyFound = true;
			}
			break;

		default:
			break;
		}
	} else {
		switch (party._mazeId) {
		case 7:
			if (party._questItems[46]) {
				map._loadCcNum = 1;
				_mazeId = 113;
				_mazePos = Common::Point(7, 4);
				_mazeDir = DIR_NORTH;
				_keyFound = true;
			}
			break;

		case 12:
			if (party._questItems[3]) {
				_mazeId = 55;
				_mazePos = Common::Point(3, 8);
				_mazeDir = DIR_EAST;
				_keyFound = true;
			}
			break;

		case 13:
			if (party._questItems[45]) {
				map._loadCcNum = 1;
				_mazeId = 117;
				_mazePos = Common::Point(7, 4);
				_mazeDir = DIR_NORTH;
				_keyFound = true;
			}
			break;

		case 15:
			if (party._questItems[2]) {
				_mazeId = 59;
				_mazePos = Common::Point(11, 8);
				_mazeDir = DIR_WEST;
				_keyFound = true;
			}
			break;

		case 24:
			if (party._questItems[1]) {
				_mazeId = 51;
				_mazePos = Common::Point(7, 12);
				_mazeDir = DIR_SOUTH;
				_keyFound = true;
			}
			break;

		default:
			break;
		}
	}
}

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

const int16 GOLEM_X1[2][12] = {
	{ 0, -5, 0, 6, 10, 13, 17, 20, 23, 26, 29, 31 },
	{ 0, 0, 1, 1, 1, 0, -9, -20, -21, 0, 0, 0 }
};
const int GOLEM_Y1[2][12] = {
	{ 0, 0, 0, 0, 0, 5, 10, 15, 20, 25, 30, 35 },
	{ 0, 6, 12, 18, 24, 30, 29, 23, 25, 0, 0, 0 }
};
const int GOLEM_X2[2][12] = {
	{ 160, 145, 140, 136, 130, 123, 117, 110, 103, 96, 89, 81 },
	{ 160, 150, 141, 131, 121, 110, 91, 70, 57, 0, 0, 0 }
};

GolemCutscene::GolemCutscene() : CutsceneLocation(GOLEM) {
}

int GolemCutscene::show() {
	EventsManager &events = *g_vm->_events;
	Interface &intf = *g_vm->_interface;
	Screen &screen = *g_vm->_screen;
	Sound &sound = *g_vm->_sound;
	Windows &windows = *g_vm->_windows;
	SpriteResource sprites1, sprites2[2];
	sprites1.load(_ccNum ? "dung1.zom" : "golmback.vga");
	sprites2[0].load(_ccNum ? "dung2.zom" : "golem.vga");
	if (_ccNum)
		sprites2[1].load("dung3.zom");

	// Save the screen
	Graphics::ManagedSurface savedBg;
	savedBg.copyFrom(screen);
	getNewLocation();

	for (int idx = (_ccNum ? 8 : 11); idx >= 0; --idx) {
		events.updateGameCounter();
		screen.blitFrom(savedBg);
		sprites1.draw(0, 0,
			Common::Point(GOLEM_X1[_ccNum][idx], GOLEM_Y1[_ccNum][idx]), 0, idx);
		sprites1.draw(0, 1,
			Common::Point(GOLEM_X2[_ccNum][idx], GOLEM_Y1[_ccNum][idx]), 0, idx);

		windows[0].update();
		WAIT(1);
	}

	if (!_ccNum)
		sound.playSound("ogre.voc");

	for (int idx = -200; idx < 0; idx += 16) {
		events.updateGameCounter();
		sprites1.draw(0, 0, Common::Point(0, 0));
		sprites1.draw(0, 1, Common::Point(160, 0));
		sprites2[0].draw(0, 0, Common::Point(idx, 0), SPRFLAG_800);
		sprites2[_ccNum].draw(0, 1, Common::Point(idx + 160, 0), SPRFLAG_800);

		if (!_ccNum)
			sprites2[0].draw(0, 2, Common::Point(idx + g_vm->getRandomNumber(9) - 5,
				g_vm->getRandomNumber(9) - 5), SPRFLAG_800);

		if (!_ccNum && !sound.isSoundPlaying())
			sound.playSound("ogre.voc");

		WAIT(1);
	}

	sprites1.draw(0, 0, Common::Point(0, 0));
	sprites1.draw(0, 1, Common::Point(160, 0));
	sprites2[0].draw(0, 0, Common::Point(0, 0));
	sprites2[_ccNum].draw(0, 1 - _ccNum, Common::Point(160, 0));
	if (!_ccNum)
		sprites2[0].draw(0, 2);

	windows[0].update();
	while (sound.isSoundPlaying()) {
		_subtitles.show();
		WAIT(1);
	}

	sound.setMusicPercent(38);
	if (_ccNum) {
		_subtitles.setLine(_keyFound ? 5 : 4);
		sound.playVoice("what2.voc");
	} else {
		_subtitles.setLine(_keyFound ? 8 : 7);
		sound.playVoice(_keyFound ? "golem15.voc" : "golem13.voc");
	}

	do {
		events.updateGameCounter();
		sprites1.draw(0, 0, Common::Point(0, 0));
		sprites1.draw(0, 1, Common::Point(160, 0));

		if (_ccNum) {
			int frame = g_vm->getRandomNumber(6);
			sprites2[0].draw(0, frame, Common::Point(0, 0));
			sprites2[1].draw(0, frame, Common::Point(160, 0));
		} else {
			sprites2[0].draw(0, 0, Common::Point(0, 0));
			sprites2[0].draw(0, 1, Common::Point(160, 0));
			sprites2[0].draw(0, 2, Common::Point(g_vm->getRandomNumber(5) - 3,
				g_vm->getRandomNumber(9) - 3));
		}

		_subtitles.show();
		WAIT(1);
	} while (sound.isSoundPlaying());

	sprites1.draw(0, 0, Common::Point(0, 0));
	sprites1.draw(0, 1, Common::Point(160, 0));
	sprites2[0].draw(0, 0, Common::Point(0, 0));
	sprites2[_ccNum].draw(0, 1 - _ccNum, Common::Point(160, 0));
	if (!_ccNum)
		sprites2[0].draw(0, 2);

	windows[0].update();
	events.updateGameCounter();
	if (_subtitles.wait(_ccNum ? 10 : 1))
		goto exit;

	if (_ccNum) {
		sound.playVoice(_keyFound ? "go2.voc" : "key2.voc");
	} else {
		sound.playVoice("ogre.voc");
		while (sound.isSoundPlaying())
			events.pollEventsAndWait();

		sound.playVoice(_keyFound ? "golem16.voc" : "golem14.voc");
	}

	do {
		events.updateGameCounter();
		sprites1.draw(0, 0, Common::Point(0, 0));
		sprites1.draw(0, 1, Common::Point(160, 0));

		if (_ccNum) {
			int frame = g_vm->getRandomNumber(6);
			sprites2[0].draw(0, frame, Common::Point(0, 0));
			sprites2[1].draw(0, frame, Common::Point(160, 0));
		} else {
			sprites2[0].draw(0, 0, Common::Point(0, 0));
			sprites2[0].draw(0, 1, Common::Point(160, 0));
			sprites2[0].draw(0, 2, Common::Point(g_vm->getRandomNumber(5) - 3,
				g_vm->getRandomNumber(9) - 3));
		}

		windows[0].update();
		_subtitles.show();
		WAIT(1);
	} while (_subtitles.lineActive());

	sprites1.draw(0, 0, Common::Point(0, 0));
	sprites1.draw(0, 1, Common::Point(160, 0));
	sprites2[0].draw(0, 0, Common::Point(0, 0));
	sprites2[_ccNum].draw(0, 1 - _ccNum, Common::Point(160, 0));
	if (!_ccNum)
		sprites2[0].draw(0, 2);
	windows[0].update();

	sound.setMusicPercent(75);

	if (!_keyFound) {
		for (int idx = 0; !g_vm->shouldExit() && idx < (_ccNum ? 9 : 12); ++idx) {
			events.updateGameCounter();
			screen.blitFrom(savedBg);
			sprites1.draw(0, 0,
				Common::Point(GOLEM_X1[_ccNum][idx], GOLEM_Y1[_ccNum][idx]), 0, idx);
			sprites1.draw(0, 1,
				Common::Point(GOLEM_X2[_ccNum][idx], GOLEM_Y1[_ccNum][idx]), 0, idx);

			windows[0].update();
			WAIT(1);
		}
	}

exit:
	screen.blitFrom(savedBg);
	windows[0].update();

	setNewLocation();

	// Restore game screen
	sound.setMusicPercent(75);
	sound.stopSound();

	screen.loadBackground("back.raw");
	intf.drawParty(false);
	intf.draw3d(false, false);

	events.clearEvents();
	return 0;
}

void GolemCutscene::getNewLocation() {
	Map &map = *g_vm->_map;
	Party &party = *g_vm->_party;

	if (_ccNum) {
		switch (party._mazeId) {
		case 12:
			if (party._questItems[47]) {
				_mazeId = 73;
				_mazePos = Common::Point(0, 7);
				_mazeDir = DIR_NORTH;
				_keyFound = true;
			}
			break;

		case 14:
			if (party._questItems[49]) {
				_mazeId = 83;
				_mazePos = Common::Point(11, 1);
				_mazeDir = DIR_NORTH;
				_keyFound = true;
			}
			break;

		case 19:
			if (party._questItems[50]) {
				_mazeId = 121;
				_mazePos = Common::Point(18, 0);
				_mazeDir = DIR_NORTH;
				_keyFound = true;
			}
			break;

		case 22:
			if (party._questItems[48]) {
				_mazeId = 78;
				_mazePos = Common::Point(8, 14);
				_mazeDir = DIR_SOUTH;
				_keyFound = true;
			}
			break;

		default:
			break;
		}
	} else {
		switch (party._mazeId) {
		case 8:
			if (party._questItems[6]) {
				_mazeId = 81;
				_mazePos = Common::Point(1, 17);
				_mazeDir = DIR_EAST;
				_keyFound = true;
			}
			break;

		case 12:
			if (party._questItems[5]) {
				_mazeId = 80;
				_mazePos = Common::Point(29, 16);
				_mazeDir = DIR_WEST;
				_keyFound = true;
			}
			break;

		case 19:
			if (party._questItems[50]) {
				map._loadCcNum = 1;
				_mazeId = 121;
				_mazePos = Common::Point(18, 0);
				_mazeDir = DIR_NORTH;
				_keyFound = true;
			}
			break;

		case 20:
			if (party._questItems[7]) {
				_mazeId = 79;
				_mazePos = Common::Point(5, 16);
				_mazeDir = DIR_EAST;
				_keyFound = true;
			}
			break;

		default:
			break;
		}
	}
}

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

const int16 DWARF_X0[2][13] = {
	{  0, -5, -7, -8, -11, -9, -3, 1, 6, 10, 15, 18, 23 },
	{ 0, 4, 6, 8, 11, 12, 15, 17, 19, 22, 25, 0, 0 }
};
const int DWARF_X1[2][13] = {
	{ 160, 145, 133, 122, 109, 101, 97, 91, 86, 80, 75, 68, 63 },
	{ 160, 154, 146, 138, 131, 122, 115, 107, 99, 92, 85, 0, 0 }
};
const int DWARF_X2[13] = {
	0, -1, -4, -7, -9, -13, -15, -18, -21, -23, -25, 0, 0
};
const int16 DWARF_Y[2][13] = {
	{ 0, 0, 4, 9, 13, 15, 20, 24, 30, 37, 45, 51, 58 },
	{ 0, 12, 25, 36, 38, 40, 41, 42, 44, 45, 50, 0, 0 }
};
const int16 DWARF2_X[2][16] = {
	{ 0, -2, -4, -6, -8, -10, -12, -14, -16, -18, -20, -20, -20, -20, -20, -20 },
	{ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150 }
};
const int16 DWARF2_Y[2][16] = {
	{ 0, 12, 25, 37, 50, 62, 75, 87, 100, 112, 125, 137, 150, 162, 175, 187 },
	{ 0, 12, 25, 37, 50, 62, 75, 87, 100, 112, 125, 137, 150, 162, 175, 186 }
};

DwarfCutscene::DwarfCutscene() : CutsceneLocation(DWARF_MINE) {}

int DwarfCutscene::show() {
	EventsManager &events = *g_vm->_events;
	Interface &intf = *g_vm->_interface;
	Screen &screen = *g_vm->_screen;
	Sound &sound = *g_vm->_sound;
	Windows &windows = *g_vm->_windows;

	SpriteResource sprites1(_ccNum ? "town1.zom" : "dwarf1.vga");
	SpriteResource sprites2(_ccNum ? "town2.zom" : "dwarf3.vga");
	SpriteResource sprites3(_ccNum ? "town3.zom" : "dwarf2.vga");
	getNewLocation();

	// Save the screen contents
	Graphics::ManagedSurface savedBg;
	savedBg.copyFrom(screen);

	// Zoom in on the mine entrance
	for (int idx = (_ccNum ? 10 : 12); idx >= 0; --idx) {
		events.updateGameCounter();

		screen.blitFrom(savedBg);
		sprites1.draw(0, 0,
			Common::Point(DWARF_X0[_ccNum][idx], DWARF_Y[_ccNum][idx]), 0, idx);
		sprites1.draw(0, 1,
			Common::Point(DWARF_X1[_ccNum][idx], DWARF_Y[_ccNum][idx]), 0, idx);
		if (_ccNum)
			sprites1.draw(0, 2,
				Common::Point(DWARF_X2[idx], DWARF_Y[_ccNum][idx]), 0, idx);

		windows[0].update();
		WAIT(1);
	}

	// Have character rise up from the bottom of the screen
	savedBg.copyFrom(screen);
	for (int idx = 15; idx >= 0; --idx) {
		if (g_vm->shouldExit())
			return 0;
		events.updateGameCounter();

		screen.blitFrom(savedBg);
		sprites2.draw(0, 0, Common::Point(DWARF2_X[_ccNum][idx], DWARF2_Y[_ccNum][idx]), 0, idx);
		windows[0].update();
		WAIT(1);
	}

	sound.setMusicPercent(38);
	screen.blitFrom(savedBg);
	sprites2.draw(0, 0);
	windows[0].update();
	if (_ccNum)
		_subtitles.setLine(_keyFound ? 7 : 8);
	else
		_subtitles.setLine(4);

	for (int idx = 0; idx < (_ccNum ? 2 : 3); ++idx) {
		switch (idx) {
		case 0:
			sound.playSound(_ccNum ? "pass2.voc" : "dwarf10.voc");
			break;

		case 1:
			if (_ccNum) {
				sprites2.draw(0, 0);
				sprites3.draw(0, 0);
				_subtitles.show();

				events.timeMark5();
				while (!g_vm->shouldExit() && events.timeElapsed5() < 7) {
					WAIT(1);
				}

				sound.playSound(_keyFound ? "ok2.voc" : "back2.voc");
			} else {
				sound.playSound("dwarf11.voc");
			}
			break;

		case 2:
			sound.playSound("dwarf12.voc");
			break;

		default:
			break;
		}

		events.updateGameCounter();
		do {
			sprites2.draw(0, 0);
			sprites3.draw(0, g_vm->getRandomNumber(_ccNum ? 8 : 9));
			_subtitles.show();

			events.timeMark5();
			while (events.timeElapsed5() < 2) {
				WAIT(1);
			}
		} while (_subtitles.lineActive());

		WAIT(3);
	}

exit:
	sprites2.draw(0, 0);
	if (!_ccNum)
		sprites3.draw(0, 1);
	windows[0].update();

	setNewLocation();

	// Restore game screen
	sound.setMusicPercent(75);
	sound.stopSound();

	screen.loadBackground("back.raw");
	intf.drawParty(false);
	intf.draw3d(false, false);

	events.clearEvents();
	return 0;
}

void DwarfCutscene::getNewLocation() {
	Party &party = *g_vm->_party;

	if (g_vm->getGameID() == GType_Swords) {
		switch (party._mazeId) {
		case 1:
			if (party._questItems[0]) {
				_mazeId = 53;
				_mazePos = Common::Point(8, 1);
				_mazeDir = DIR_NORTH;
				_keyFound = true;
			}
			break;

		case 7:
			if (party._questItems[1]) {
				_mazeId = 92;
				_mazePos = Common::Point(8, 1);
				_mazeDir = DIR_NORTH;
				_keyFound = true;
			}
			break;

		default:
			break;
		}
	} else if (_ccNum) {
		switch (party._mazeId) {
		case 4:
			if (party._questItems[35]) {
				_mazeId = 29;
				_mazePos = Common::Point(15, 31);
				_mazeDir = DIR_SOUTH;
				_keyFound = true;
			}
			break;

		case 6:
			if (party._questItems[38]) {
				_mazeId = 35;
				_mazePos = Common::Point(15, 8);
				_mazeDir = DIR_WEST;
				_keyFound = true;
			}
			break;

		case 19:
			if (party._questItems[36]) {
				_mazeId = 31;
				_mazePos = Common::Point(31, 16);
				_mazeDir = DIR_WEST;
				_keyFound = true;
			}
			break;

		case 22:
			if (party._questItems[37]) {
				_mazeId = 33;
				_mazePos = Common::Point(0, 3);
				_mazeDir = DIR_EAST;
				_keyFound = true;
			}
			break;

		case 98:
			if (party._questItems[39]) {
				_mazeId = 37;
				_mazePos = Common::Point(7, 0);
				_mazeDir = DIR_NORTH;
				_keyFound = true;
			}
			break;

		default:
			break;
		}
	} else {
		switch (party._mazeId) {
		case 14:
			_mazeId = 37;
			_mazePos = Common::Point(1, 4);
			_mazeDir = DIR_EAST;
			_keyFound = true;
			break;

		case 18:
			if (party._mazePosition.x == 9) {
				_mazeId = 35;
				_mazePos = Common::Point(1, 12);
				_mazeDir = DIR_EAST;
			} else {
				_mazeId = 36;
				_mazePos = Common::Point(7, 1);
				_mazeDir = DIR_NORTH;
			}
			_keyFound = true;
			break;

		case 23:
			if (party._mazePosition.x == 5) {
				_mazeId = 33;
				_mazePos = Common::Point(7, 1);
				_mazeDir = DIR_NORTH;
			} else {
				_mazeId = 34;
				_mazePos = Common::Point(7, 30);
				_mazeDir = DIR_SOUTH;
			}
			_keyFound = true;
			break;

		default:
			break;
		}
	}
}

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

static const int SPHINX_X1[9] = { 0, -5, -10, -15, -20, -17, -12, -7, 0 };
static const int SPHINX_Y1[9] = { 0, 0, 0, 6, 11, 16, 20, 23, 28 };
static const int SPHINX_X2[9] = { 160, 145, 130, 115, 100, 93, 88, 83, 80 };

SphinxCutscene::SphinxCutscene() : CutsceneLocation(SPHINX) {
}

int SphinxCutscene::show() {
	EventsManager &events = *g_vm->_events;
	Interface &intf = *g_vm->_interface;
	Screen &screen = *g_vm->_screen;
	Sound &sound = *g_vm->_sound;
	Windows &windows = *g_vm->_windows;
	SpriteResource sprites1("sphinx.vga");

	getNewLocation();

	// Save background
	Graphics::ManagedSurface bgSurface;
	bgSurface.copyFrom(screen);

	for (int idx = 8; idx >= 0; --idx) {
		events.updateGameCounter();
		screen.blitFrom(bgSurface);
		sprites1.draw(0, 0, Common::Point(SPHINX_X1[idx], SPHINX_Y1[idx]), 0, idx);
		sprites1.draw(0, 1, Common::Point(SPHINX_X2[idx], SPHINX_Y1[idx]), 0, idx);
		windows[0].update();
		WAIT(1);
	}

	sound.setMusicPercent(38);

	for (int idx = 0; idx < (_keyFound ? 3 : 2); ++idx) {
		switch (idx) {
		case 0:
			_subtitles.setLine(_keyFound ? 9 : 10);
			sound.playSound(_keyFound ? "sphinx10.voc" : "sphinx13.voc");
			break;
		case 1:
			sound.playSound(_keyFound ? "sphinx11.voc" : "sphinx14.voc");
			break;
		case 2:
			sound.playSound("sphinx12.voc");
			break;
		default:
			break;
		}

		do {
			sprites1.draw(0, 0, Common::Point(0, 0));
			sprites1.draw(0, 1, Common::Point(160, 0));
			sprites1.draw(0, g_vm->getRandomNumber(2, 10));
			WAIT(1);
		} while (sound.isSoundPlaying());

		sprites1.draw(0, 0, Common::Point(0, 0));
		sprites1.draw(0, 1, Common::Point(160, 0));
	}

	while (_subtitles.lineActive()) {
		WAIT(1);
	}

	sound.setMusicPercent(75);

	if (!_keyFound) {
		for (int idx = 0; idx < 8; ++idx) {
			screen.blitFrom(bgSurface);
			sprites1.draw(0, 0, Common::Point(SPHINX_X1[idx], SPHINX_Y1[idx]), 0, idx);
			sprites1.draw(0, 1, Common::Point(SPHINX_X2[idx], SPHINX_Y1[idx]), 0, idx);
			windows[0].update();
			WAIT(1);
		}

		screen.blitFrom(bgSurface);
		windows[0].update();
	}
exit:
	screen.blitFrom(bgSurface);
	windows[0].update();

	setNewLocation();

	// Restore game screen
	sound.setMusicPercent(75);
	screen.loadBackground("back.raw");
	intf.drawParty(false);
	intf.draw3d(false, false);

	events.clearEvents();
	return 0;
}

void SphinxCutscene::getNewLocation() {
	Map &map = *g_vm->_map;
	Party &party = *g_vm->_party;

	switch (party._mazeId) {
	case 2:
		if (party._questItems[51]) {
			map._loadCcNum = 1;
			_mazeId = 125;
			_mazePos = Common::Point(7, 6);
			_mazeDir = DIR_NORTH;
			_keyFound = true;
		}
		break;

	case 5:
		if (party._questItems[4]) {
			_mazeId = 82;
			_mazePos = Common::Point(7, 5);
			_mazeDir = DIR_NORTH;
			_keyFound = true;
		}
		break;

	default:
		break;
	}
}

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

PyramidLocation::PyramidLocation() : BaseLocation(PYRAMID) {
}

int PyramidLocation::show() {
	EventsManager &events = *g_vm->_events;
	Map &map = *g_vm->_map;
	Party &party = *g_vm->_party;
	Windows &windows = *g_vm->_windows;
	int mapId;
	Direction dir = DIR_NORTH;
	Common::Point pt;

	if (g_vm->getGameID() == GType_WorldOfXeen) {
		if (_ccNum) {
			if (party._mazeId == 52) {
				mapId = 49;
				pt = Common::Point(7, 14);
				dir = DIR_SOUTH;
			} else {
				mapId = 23;
				pt = Common::Point(8, 10);
			}
		} else {
			if (party._mazeId == 49) {
				mapId = 52;
				pt = Common::Point(2, 2);
			} else {
				mapId = 29;
				pt = Common::Point(25, 21);
			}
		}

		// Load the destination map and set position and direction
		map._loadCcNum = _ccNum ? 0 : 1;
		map.load(mapId);
		party._mazePosition = pt;
		party._mazeDirection = dir;
	} else {
		// Playing Clouds or Dark Side on it's own, so can't switch sides
		Window &win = windows[12];
		Common::String msg = Common::String::format(Res.MOONS_NOT_ALIGNED,
			_ccNum ? "Clouds" : "Darkside");
		win.open();
		win.writeString(msg);
		win.update();

		events.waitForPressAnimated();
		win.close();
	}

	return 0;
}

} // End of namespace Locations

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

LocationManager::LocationManager() : _location(nullptr) {
}

int LocationManager::doAction(int actionId) {
	LocationAction action = (g_vm->getGameID() == GType_Swords && actionId > 13 && actionId < 18) ?
		BLACKSMITH : (LocationAction)actionId;

	// Create the desired location
	switch (action) {
	case BANK:
		_location = new Locations::BankLocation();
		break;
	case BLACKSMITH:
		_location = new Locations::BlacksmithLocation();
		break;
	case GUILD:
		_location = new Locations::GuildLocation();
		break;
	case TAVERN:
		_location = new Locations::TavernLocation();
		break;
	case TEMPLE:
		_location = new Locations::TempleLocation();
		break;
	case TRAINING:
		_location = new Locations::TrainingLocation();
		break;
	case ARENA:
		_location = new Locations::ArenaLocation();
		break;
	case REAPER:
		_location = new Locations::ReaperCutscene();
		break;
	case GOLEM:
		_location = new Locations::GolemCutscene();
		break;
	case DWARF_MINE:
	case DWARF_TOWN:
		_location = new Locations::DwarfCutscene();
		break;
	case SPHINX:
		_location = new Locations::SphinxCutscene();
		break;
	case PYRAMID:
		_location = new Locations::PyramidLocation();
		break;
	default:
		return 0;
	}

	// Show the location
	g_vm->_events->clearEvents();
	int result = _location->show();
	delete _location;
	_location = nullptr;

	return result;
}

bool LocationManager::isActive() const {
	return _location != nullptr;
}

void LocationManager::drawAnim(bool flag) {
	if (_location)
		_location->drawAnim(flag);
}

int LocationManager::wait() {
	return _location ? _location->wait() : 0;
}

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

bool LocationMessage::show(int portrait, const Common::String &name,
		const Common::String &text, int confirm) {
	LocationMessage *dlg = new LocationMessage();
	bool result = dlg->execute(portrait, name, text, confirm);
	delete dlg;

	return result;
}

bool LocationMessage::execute(int portrait, const Common::String &name, const Common::String &text,
		int confirm) {
	EventsManager &events = *g_vm->_events;
	Interface &intf = *g_vm->_interface;
	Map &map = *g_vm->_map;
	Party &party = *g_vm->_party;
	Resources &res = *g_vm->_resources;
	Windows &windows = *g_vm->_windows;
	Window &w = windows[11];

	_townMaxId = 4;
	_drawFrameIndex = 0;
	_animPos = Common::Point(23, 22);

	if (!confirm)
		loadButtons();

	_townSprites.resize(2);
	_townSprites[0].load(Common::String::format("face%02d.fac", portrait));
	_townSprites[1].load("frame.fac");

	if (!w._enabled)
		w.open();

	int result = -1;
	Common::String msgText = text;
	do {
		Common::String msg = Common::String::format("\r\v014\x03""c\t125%s\t000\v054%s",
			name.c_str(), msgText.c_str());

		// Count the number of words
		const char *msgEnd = w.writeString(msg);
		int wordCount = 0;

		for (const char *msgP = msg.c_str(); msgP != msgEnd && *msgP; ++msgP) {
			if (*msgP == ' ')
				++wordCount;
		}

		_drawCtr2 = wordCount * 2;	// Set timeout
		_townSprites[1].draw(0, 0, Common::Point(16, 16));
		_townSprites[0].draw(0, _drawFrameIndex, Common::Point(23, 22));
		w.update();

		if (!msgEnd && !confirm) {
			res._globalSprites.draw(0, 7, Common::Point(232, 74));
			drawButtons(&windows[0]);
			windows[34].update();

			intf._face1State = map._headData[party._mazePosition.y][party._mazePosition.x]._left;
			intf._face2State = map._headData[party._mazePosition.y][party._mazePosition.x]._right;
		}

		if (confirm == 2) {
			intf._face1State = intf._face2State = 2;
			return false;
		}

		do {
			events.clearEvents();
			clearEvents();

			do {
				events.updateGameCounter();
				while (!_buttonValue && events.timeElapsed() < 3) {
					events.pollEventsAndWait();
					checkEvents(_vm);
					if (g_vm->shouldExit())
						return false;
				}

				drawAnim(false);
			} while (!_buttonValue);

			if (msgEnd)
				// Another screen of text remaining
				break;

			if (confirm || _buttonValue == Common::KEYCODE_ESCAPE ||
					_buttonValue == Common::KEYCODE_n)
				result = 0;
			else if (_buttonValue == Common::KEYCODE_y)
				result = 1;
		} while (result == -1);

		if (msgEnd) {
			// Text remaining, so cut off already displayed page's
			msgText = Common::String(msgEnd);
			_drawCtr2 = wordCount;
			continue;
		}
	} while (result == -1);

	intf._face1State = intf._face2State = 2;
	if (!confirm)
		intf.mainIconsPrint();

	_townSprites[0].clear();
	_townSprites[1].clear();
	events.clearEvents();
	return result == 1;
}

void LocationMessage::loadButtons() {
	_iconSprites.load("confirm.icn");

	addButton(Common::Rect(235, 75, 259, 95), Common::KEYCODE_y, &_iconSprites);
	addButton(Common::Rect(260, 75, 284, 95), Common::KEYCODE_n, &_iconSprites);
	addButton(Common::Rect(), Common::KEYCODE_ESCAPE);
}

} // End of namespace Xeen