/* 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.
 *
 */

/*
 * This code is based on the original source code of Lord Avalot d'Argent version 1.3.
 * Copyright (c) 1994-1995 Mike: Mark and Thomas Thurman.
 */

/* AVALOT		The kernel of the program. */

#include "avalanche/avalanche.h"

#include "common/math.h"
#include "common/random.h"
#include "common/system.h"
#include "common/config-manager.h"
#include "graphics/palette.h"

namespace Avalanche {

// vv Stairs trap.

/* Explanation: $NSEW.
		Nibble N: North.
		0     = no connection,
		2     = (left,) middle(, right) door with left-hand handle,
		5     = (left,) middle(, right) door with right-hand handle,
		7     = arch,
		8     = arch and 1 north of it,
		9     = arch and 2 north of it,
		D     = no connection + WINDOW,
		E     = no connection + TORCH,
		F     = recessed door (to Geida's room.)

		Nibble S: South.
		0     = no connection,
		1,2,3 = left, middle, right door.

		Nibble E: East.
		0     = no connection (wall),
		1     = no connection (wall + window),
		2     = wall with door,
		3     = wall with door and window,
		6     = wall with candles,
		7     = wall with door and candles,
		F     = straight-through corridor.

		Nibble W: West.
		0     = no connection (wall),
		1     = no connection (wall + shield),
		2     = wall with door,
		3     = wall with door and shield,
		4     = no connection (window),
		5     = wall with door and window,
		6     = wall with candles,
		7     = wall with door and candles,
		F     = straight-through corridor. */

const char AvalancheEngine::kSpludwicksOrder[3] = {kObjectOnion, kObjectInk, kObjectMushroom};
const uint16 AvalancheEngine::kNotes[12] = {196, 220, 247, 262, 294, 330, 350, 392, 440, 494, 523, 587};

Room AvalancheEngine::_whereIs[29] = {
	// The Lads
	kRoomYours, // Avvy
	kRoomSpludwicks, // Spludwick
	kRoomOutsideYours, // Crapulus
	kRoomDucks, // Duck - r__DucksRoom's not defined yet.
	kRoomArgentPub, // Malagauche
	kRoomRobins, // Friar Tuck.
	kRoomDummy, // Robin Hood - can't meet him at the start.
	kRoomBrummieRoad, // Cwytalot
	kRoomLustiesRoom, // Baron du Lustie.
	kRoomOutsideCardiffCastle, // The Duke of Cardiff.
	kRoomArgentPub, // Dogfood
	kRoomOutsideDucks, // Trader
	kRoomArgentPub, // Ibythneth
	kRoomAylesOffice, // Ayles
	kRoomNottsPub, // Port
	kRoomNottsPub, // Spurge
	kRoomMusicRoom, // Jacques
	kRoomNowhere,
	kRoomNowhere,
	kRoomNowhere,
	kRoomNowhere,
	kRoomNowhere,
	kRoomNowhere,
	kRoomNowhere,
	kRoomNowhere,
	// The Lasses
	kRoomYours, // Arkata
	kRoomGeidas, // Geida
	kRoomDummy, // nobody allocated here!
	kRoomWiseWomans  // The Wise Woman.
};



void AvalancheEngine::handleKeyDown(Common::Event &event) {
	_sound->click();

	if ((Common::KEYCODE_F1 <= event.kbd.keycode) && (event.kbd.keycode <= Common::KEYCODE_F15))
		_parser->handleFunctionKey(event);
	else if ((32 <= event.kbd.ascii) && (event.kbd.ascii <= 128) && (event.kbd.ascii != 47))
		_parser->handleInputText(event);
	else
		switch (event.kbd.keycode) { // We can control Avvy with the numpad as well.
		case Common::KEYCODE_KP8:
			event.kbd.keycode = Common::KEYCODE_UP;
			break;
		case Common::KEYCODE_KP2:
			event.kbd.keycode = Common::KEYCODE_DOWN;
			break;
		case Common::KEYCODE_KP6:
			event.kbd.keycode = Common::KEYCODE_RIGHT;
			break;
		case Common::KEYCODE_KP4:
			event.kbd.keycode = Common::KEYCODE_LEFT;
			break;
		case Common::KEYCODE_KP9:
			event.kbd.keycode = Common::KEYCODE_PAGEUP;
			break;
		case Common::KEYCODE_KP3:
			event.kbd.keycode = Common::KEYCODE_PAGEDOWN;
			break;
		case Common::KEYCODE_KP7:
			event.kbd.keycode = Common::KEYCODE_HOME;
			break;
		case Common::KEYCODE_KP1:
			event.kbd.keycode = Common::KEYCODE_END;
			break;
		default:
			break;
	}

	switch (event.kbd.keycode) {
	case Common::KEYCODE_UP:
	case Common::KEYCODE_DOWN:
	case Common::KEYCODE_RIGHT:
	case Common::KEYCODE_LEFT:
	case Common::KEYCODE_PAGEUP:
	case Common::KEYCODE_PAGEDOWN:
	case Common::KEYCODE_HOME:
	case Common::KEYCODE_END:
	case Common::KEYCODE_KP5:
		if (_alive && _avvyIsAwake) {
			_animation->handleMoveKey(event);
			drawDirection();
			return;
		}
		// fall through
	case Common::KEYCODE_BACKSPACE:
		_parser->handleBackspace();
		break;
	case Common::KEYCODE_RETURN:
		_parser->handleReturn();
		break;
	default:
		break;
	}

	drawDirection();
}

void AvalancheEngine::setup() {
	init();

	_dialogs->reset();
	fadeOut();
	_graphics->loadDigits();

	_parser->_inputTextPos = 0;
	_parser->_quote = true;

	_animation->resetAnims();

	_dialogs->setReadyLight(2);

	fadeIn();
	_parser->_cursorState = false;
	_parser->cursorOn();
	_animation->_sprites[0]->_speedX = kWalk;
	_animation->updateSpeed();

	_dropdown->init();

	_graphics->drawSoundLight(_sound->_soundFl);

	drawToolbar();

	int16 loadSlot = ConfMan.instance().getInt("save_slot");
	if (loadSlot >= 0) {
		_thinks = 2; // You always have money.
		thinkAbout(kObjectMoney, kThing);

		loadGame(loadSlot);
	} else {
		// We don't need the MainMenu during the whole game, only at the beginning of it.
		MainMenu *mainmenu = new MainMenu(this);
		mainmenu->run();
		delete mainmenu;
		if (_letMeOut)
			return;

		newGame();

		thinkAbout(kObjectMoney, kThing);

		_dialogs->displayScrollChain('Q', 83); // Info on the game, etc.
	}
}

void AvalancheEngine::runAvalot() {
	setup();

	while (!_letMeOut && !shouldQuit()) {
		uint32 beginLoop = _system->getMillis();

		updateEvents(); // The event handler.

		_clock->update();
		_dropdown->update();
		_background->update();
		_animation->animLink();
		checkClick();
		_timer->updateTimer();

		_graphics->drawDebugLines();
		_graphics->refreshScreen();

		uint32 delay = _system->getMillis() - beginLoop;
		if (delay <= 55)
			_system->delayMillis(55 - delay); // Replaces slowdown(); 55 comes from 18.2 Hz (B Flight).
	};

	_closing->exitGame();
}

void AvalancheEngine::init() {
	for (int i = 0; i < 31; i++) {
		for (int j = 0; j < 2; j++)
			_also[i][j] = nullptr;
	}

	_letMeOut = false;
	_currentMouse = 177;
	_dropsOk = true;
	_mouseText = "";
	_cheat = false;
	_cp = 0;
	_ledStatus = 177;
	for (int i = 0; i < 3; i++)
		_scoreToDisplay[i] = -1; // Impossible digits.
	_holdTheDawn = false;

	_graphics->loadMouse(kCurWait);
	CursorMan.showMouse(true);
}

/**
 * Call a given Verb
 * @remarks	Originally called 'callverb'
 */
void AvalancheEngine::callVerb(VerbCode id) {
	if (id == _parser->kPardon) {
		Common::String tmpStr = Common::String::format("The f5 key lets you do a particular action in certain " \
			"situations. However, at the moment there is nothing assigned to it. You may press alt-A to see " \
			"what the current setting of this key is.");
		_dialogs->displayText(tmpStr);
	} else
		_parser->doVerb(id);
}

/**
 * Check is it's possible to give something to Spludwick
 * @remarks	Originally called 'nextstring'
 */
Common::String AvalancheEngine::readAlsoStringFromFile(Common::File &file) {
	Common::String str;
	byte length = file.readByte();
	for (int i = 0; i < length; i++)
		str += file.readByte();
	return str;
}

void AvalancheEngine::scram(Common::String &str) {
	for (uint i = 0; i < str.size(); i++)
		str.setChar(str[i] ^ 177, i);
}

void AvalancheEngine::unScramble() {
	for (int i = 0; i < 31; i++) {
		for (int j = 0; j < 2; j++) {
			if (_also[i][j] != nullptr)
				scram(*_also[i][j]);
		}
	}
	scram(_listen);
	scram(_flags);
}

void AvalancheEngine::loadAlso(byte num) {
	for (int i = 0; i < 31; i++) {
		for (int j = 0; j < 2; j++) {
			if (_also[i][j] != nullptr)  {
				delete _also[i][j];
				_also[i][j] = nullptr;
			}
		}
	}
	Common::String filename;
	filename = Common::String::format("also%d.avd", num);
	Common::File file;
	if (!file.open(filename))
		error("AVALANCHE: File not found: %s", filename.c_str());

	file.seek(128);

	byte alsoNum = file.readByte();
	Common::String tmpStr;
	for (int i = 0; i <= alsoNum; i++) {
		for (int j = 0; j < 2; j++) {
			_also[i][j] = new Common::String;
			*_also[i][j] = readAlsoStringFromFile(file);
		}
		tmpStr = Common::String::format("\x9D%s\x9D", _also[i][0]->c_str());
		*_also[i][0] = tmpStr;
	}

	memset(_lines, 0xFF, sizeof(_lines));

	_lineNum = file.readByte();
	for (int i = 0; i < _lineNum; i++) {
		LineType *curLine = &_lines[i];
		curLine->_x1 = file.readSint16LE();
		curLine->_y1 = file.readSint16LE();
		curLine->_x2 = file.readSint16LE();
		curLine->_y2 = file.readSint16LE();
		curLine->_color = (Color)file.readByte();
	}

	memset(_peds, 177, sizeof(_peds));
	byte pedNum = file.readByte();
	for (int i = 0; i < pedNum; i++) {
		PedType *curPed = &_peds[i];
		curPed->_x = file.readSint16LE();
		curPed->_y = file.readSint16LE();
		curPed->_direction = (Direction)file.readByte();
	}

	_fieldNum = file.readByte();
	for (int i = 0; i < _fieldNum; i++) {
		FieldType *curField = &_fields[i];
		curField->_x1 = file.readSint16LE();
		curField->_y1 = file.readSint16LE();
		curField->_x2 = file.readSint16LE();
		curField->_y2 = file.readSint16LE();
	}

	for (int i = 0; i < 15; i++) {
		MagicType *magic = &_magics[i];
		magic->_operation = file.readByte();
		magic->_data = file.readUint16LE();
	}

	for (int i = 0; i < 7; i++) {
		MagicType *portal = &_portals[i];
		portal->_operation = file.readByte();
		portal->_data = file.readUint16LE();
	}

	_flags.clear();
	for (int i = 0;  i < 26; i++)
		_flags += file.readByte();

	int16 size = file.readByte();
	_listen.clear();
	for (int i = 0; i < size; i++)
		_listen += file.readByte();

	_graphics->clearAlso();

	CursorMan.showMouse(false);
	for (int i = 0; i < _lineNum; i++) {
		// We had to check if the lines are within the borders of the screen.
		if ((_lines[i]._x1 >= 0) && (_lines[i]._x1 < kScreenWidth) && (_lines[i]._y1 >= 0) && (_lines[i]._y1 < kScreenHeight)
			&& (_lines[i]._x2 >= 0) && (_lines[i]._x2 < kScreenWidth) && (_lines[i]._y2 >= 0) && (_lines[i]._y2 < kScreenHeight))
			_graphics->setAlsoLine(_lines[i]._x1, _lines[i]._y1, _lines[i]._x2, _lines[i]._y2, _lines[i]._color);
	}
	CursorMan.showMouse(true);

	file.close();

	unScramble();
	for (int i = 0; i <= alsoNum; i++) {
		tmpStr = Common::String::format(",%s,", _also[i][0]->c_str());
		*_also[i][0] = tmpStr;
	}
}

void AvalancheEngine::loadBackground(byte num) {
	Common::String filename = Common::String::format("place%d.avd", num);
	Common::File file;
	if (!file.open(filename))
		error("AVALANCHE: File not found: %s", filename.c_str());

	file.seek(146);
	if (!_roomnName.empty())
		_roomnName.clear();
	for (int i = 0; i < 30; i++) {
		char actChar = file.readByte();
		if ((32 <= actChar) && (actChar <= 126))
			_roomnName += actChar;
	}
	// Compression method byte follows this...

	file.seek(177);

	_graphics->loadBackground(file);
	_graphics->refreshBackground();

	file.close();
}

void AvalancheEngine::loadRoom(byte num) {
	CursorMan.showMouse(false);

	loadBackground(num);
	loadAlso(num);
	_background->loadSprites(num);

	CursorMan.showMouse(true);
}

void AvalancheEngine::findPeople(byte room) {
	for (int i = 1; i < 29; i++) {
		if (_whereIs[i] == room) {
			if (i < 25)
				_him = (People)(150 + i);
			else
				_her = (People)(150 + i);
		}
	}
}

void AvalancheEngine::exitRoom(byte x) {
	_sound->stopSound();
	_background->release();
	_animationsEnabled = false;

	switch (x) {
	case kRoomSpludwicks:
		_timer->loseTimer(Timer::kReasonAvariciusTalks);
		 _avariciusTalk = 0;
		// He doesn't HAVE to be talking for this to work. It just deletes it IF it exists.
		break;
	case kRoomBridge:
		if (_drawbridgeOpen > 0) {
			_drawbridgeOpen = 4; // Fully open.
			_timer->loseTimer(Timer::kReasonDrawbridgeFalls);
		}
		break;
	case kRoomOutsideCardiffCastle:
		_timer->loseTimer(Timer::kReasonCardiffsurvey);
		break;
	case kRoomRobins:
		_timer->loseTimer(Timer::kReasonGettingTiedUp);
		break;
	}

	_interrogation = 0; // Leaving the room cancels all the questions automatically.
	_animationsEnabled = true;

	_lastRoom = _room;
	if (_room != kRoomMap)
		_lastRoomNotMap = _room;
}

/**
 * Only when entering a NEW town! Not returning to the last one,
 * but choosing another from the map.
 * @remarks	Originally called 'new_town'
 */
void AvalancheEngine::enterNewTown() {
	_dropdown->setup();

	switch (_room) {
	case kRoomOutsideNottsPub: // Entry into Nottingham.
		if ((_roomCount[kRoomRobins] > 0) && (_beenTiedUp) && (!_takenMushroom))
			_mushroomGrowing = true;
		break;
	case kRoomWiseWomans: // Entry into Argent.
		if (_talkedToCrapulus && (!_lustieIsAsleep)) {
			_spludwickAtHome = !((_roomCount[kRoomWiseWomans] % 3) == 1);
			_crapulusWillTell = !_spludwickAtHome;
		} else {
			_spludwickAtHome = true;
			_crapulusWillTell = false;
		}
		if (_boxContent == kObjectWine)
			_wineState = 3; // Vinegar
		break;
	default:
		break;
	}

	if ((_room != kRoomOutsideDucks) && (_objects[kObjectOnion - 1]) && !(_onionInVinegar))
		_rottenOnion = true; // You're holding the onion
}

void AvalancheEngine::putGeidaAt(byte whichPed, byte ped) {
	if (ped == 0)
		return;
	AnimationType *spr1 = _animation->_sprites[1];

	spr1->init(5, false); // load Geida
	_animation->appearPed(1, whichPed);
	spr1->_callEachStepFl = true;
	spr1->_eachStepProc = Animation::kProcFollowAvvy;
}

void AvalancheEngine::enterRoom(Room roomId, byte ped) {
	_animationsEnabled = false;

	findPeople(roomId);
	_room = roomId;
	if (ped != 0)
		_roomCount[roomId]++;

	loadRoom(roomId);

	if ((_roomCount[roomId] == 0) && (!getFlag('S')))
		incScore(1);

	_whereIs[kPeopleAvalot - 150] = _room;

	if (_geidaFollows)
		_whereIs[kPeopleGeida - 150] = roomId;

	_roomCycles = 0;

	if ((_lastRoom == kRoomMap) && (_lastRoomNotMap != _room))
		enterNewTown();

	_animation->updateSpeed();

	switch (roomId) {
	case kRoomYours:
		if (_avvyInBed) {
			_background->draw(-1, -1, 2);
			_graphics->refreshBackground();
			_timer->addTimer(100, Timer::kProcArkataShouts, Timer::kReasonArkataShouts);
		}
		break;

	case kRoomOutsideYours:
		if (ped > 0) {
			AnimationType *spr1 = _animation->_sprites[1];
			if (!_talkedToCrapulus) {
				_whereIs[kPeopleCrapulus - 150] = kRoomOutsideYours;
				spr1->init(8, false); // load Crapulus

				if (_roomCount[kRoomOutsideYours] == 1) {
					_animation->appearPed(1, 3); // Start on the right-hand side of the screen.
					spr1->walkTo(4); // Walks up to greet you.
				} else {
					_animation->appearPed(1, 4); // Starts where he was before.
					spr1->_facingDir = kDirLeft;
				}

				spr1->_callEachStepFl = true;
				spr1->_eachStepProc = Animation::kProcFaceAvvy; // He always faces Avvy.

			} else
				_whereIs[kPeopleCrapulus - 150] = kRoomNowhere;

			if (_crapulusWillTell) {
				spr1->init(8, false);
				_animation->appearPed(1, 1);
				spr1->walkTo(3);
				_timer->addTimer(20, Timer::kProcCrapulusSpludOut, Timer::kReasonCrapulusSaysSpludwickOut);
				_crapulusWillTell = false;
			}
		}
		break;

	case kRoomOutsideSpludwicks:
		if ((_roomCount[kRoomOutsideSpludwicks] == 1) && (ped == 1)) {
			_timer->addTimer(20, Timer::kProcBang, Timer::kReasonExplosion);
			_spludwickAtHome = true;
		}
		break;

	case kRoomSpludwicks:
		if (_spludwickAtHome) {
			AnimationType *spr1 = _animation->_sprites[1];
			if (ped > 0) {
				spr1->init(2, false); // load Spludwick
				_animation->appearPed(1, 1);
				_whereIs[kPeopleSpludwick - 150] = kRoomSpludwicks;
			}

			spr1->_callEachStepFl = true;
			spr1->_eachStepProc = Animation::kProcFollowAvvy;
		} else
			_whereIs[kPeopleSpludwick - 150] = kRoomNowhere;
		break;

	case kRoomBrummieRoad:
		if (_geidaFollows)
			putGeidaAt(4, ped);
		if (_cwytalotGone) {
			_magics[kColorLightred - 1]._operation = kMagicNothing;
			_whereIs[kPeopleCwytalot - 150] = kRoomNowhere;
		} else if (ped > 0) {
			AnimationType *spr1 = _animation->_sprites[1];
			spr1->init(4, false); // 4 = Cwytalot
			spr1->_callEachStepFl = true;
			spr1->_eachStepProc = Animation::kProcFollowAvvyY;
			_whereIs[kPeopleCwytalot - 150] = kRoomBrummieRoad;

			if (_roomCount[kRoomBrummieRoad] == 1) { // First time here...
				_animation->appearPed(1, 1); // He appears on the right of the screen...
				spr1->walkTo(3); // ...and he walks up...
			} else {
				// You've been here before.
				_animation->appearPed(1, 3); // He's standing in your way straight away...
				spr1->_facingDir = kDirLeft;
			}
		}
		break;

	case kRoomArgentRoad:
		if ((_cwytalotGone) && (!_passedCwytalotInHerts) && (ped == 2) && (_roomCount[kRoomArgentRoad] > 3)) {
			AnimationType *spr1 = _animation->_sprites[1];
			spr1->init(4, false); // 4 = Cwytalot again
			_animation->appearPed(1, 0);
			spr1->walkTo(1);
			spr1->_vanishIfStill = true;
			_passedCwytalotInHerts = true;
			// whereis[#157] = r__Nowhere; // can we fit this in?
			_timer->addTimer(20, Timer::kProcCwytalotInHerts, Timer::kReasonCwytalotInHerts);
		}
		break;

	case kRoomBridge:
		if (_drawbridgeOpen == 4) { // open
			_background->draw(-1, -1, 2); // Position of drawbridge
			_graphics->refreshBackground();
			_magics[kColorGreen - 1]._operation = kMagicNothing; // You may enter the drawbridge.
		}
		if (_geidaFollows)
			putGeidaAt(ped + 2, ped); // load Geida
		break;

	case kRoomRobins:
		if ((ped > 0) && (!_beenTiedUp)) {
			// A welcome party... or maybe not...
			AnimationType *spr1 = _animation->_sprites[1];
			spr1->init(6, false);
			_animation->appearPed(1, 1);
			spr1->walkTo(2);
			_timer->addTimer(36, Timer::kProcGetTiedUp, Timer::kReasonGettingTiedUp);
		}

		if (_beenTiedUp) {
			_whereIs[kPeopleRobinHood - 150] = kRoomNowhere;
			_whereIs[kPeopleFriarTuck - 150] = kRoomNowhere;
		}

		if (_tiedUp)
			_background->draw(-1, -1, 1);

		if (!_mushroomGrowing)
			_background->draw(-1, -1, 2);
		_graphics->refreshBackground();
		break;

	case kRoomOutsideCardiffCastle:
		if (ped > 0) {
			AnimationType *spr1 = _animation->_sprites[1];
			switch (_cardiffQuestionNum) {
			case 0 : // You've answered NONE of his questions.
				spr1->init(9, false);
				_animation->appearPed(1, 1);
				spr1->walkTo(2);
				_timer->addTimer(47, Timer::kProcCardiffSurvey, Timer::kReasonCardiffsurvey);
				break;
			case 5 :
				_magics[1]._operation = kMagicNothing;
				break; // You've answered ALL his questions. => nothing happens.
			default: // You've answered SOME of his questions.
				spr1->init(9, false);
				_animation->appearPed(1, 2);
				spr1->_facingDir = kDirRight;
				_timer->addTimer(3, Timer::kProcCardiffReturn, Timer::kReasonCardiffsurvey);
			}
		}

		if (_cardiffQuestionNum < 5)
			_interrogation = _cardiffQuestionNum;
		else
			_interrogation = 0;
		break;

	case kRoomMap:
		// You're entering the map.
		fadeIn();
		if (ped > 0)
			_graphics->zoomOut(_peds[ped - 1]._x, _peds[ped - 1]._y);

		if ((_objects[kObjectWine - 1]) && (_wineState != 3)) {
			_dialogs->displayScrollChain('Q', 9); // Don't want to waste the wine!
			_objects[kObjectWine - 1] = false;
			refreshObjectList();
		}

		_dialogs->displayScrollChain('Q', 69);
		break;

	case kRoomCatacombs:
		if ((ped == 0) || (ped == 3) || (ped == 5) || (ped == 6)) {

			switch (ped) {
			case 3: // Enter from oubliette
				_catacombX = 8;
				_catacombY = 4;
				break;
			case 5: // Enter from du Lustie's
				_catacombX = 8;
				_catacombY = 7;
				break;
			case 6: // Enter from Geida's
				_catacombX = 4;
				_catacombY = 1;
				break;
			}

			_enterCatacombsFromLustiesRoom = true;
			_animation->catacombMove(ped);
			_enterCatacombsFromLustiesRoom = false;
		}
		break;

	case kRoomArgentPub:
		if (_wonNim)
			_background->draw(-1, -1, 0);   // No lute by the settle.
		_malagauche = 0; // Ready to boot Malagauche
		if (_givenBadgeToIby) {
			_background->draw(-1, -1, 7);
			_background->draw(-1, -1, 8);
		}
		_graphics->refreshBackground();
		break;

	case kRoomLustiesRoom:
		_npcFacing = 1; // du Lustie.
		if (_animation->getAvvyClothes() == 0) // Avvy in his normal clothes
			_timer->addTimer(3, Timer::kProcCallsGuards, Timer::kReasonDuLustieTalks);
		else if (!_enteredLustiesRoomAsMonk) // already
			// Presumably, Avvy dressed as a monk.
			_timer->addTimer(3, Timer::kProcGreetsMonk, Timer::kReasonDuLustieTalks);

		if (_geidaFollows) {
			putGeidaAt(4, ped);
			if (_lustieIsAsleep) {
				_background->draw(-1, -1, 4);
				_graphics->refreshBackground();
			}
		}
		break;

	case kRoomMusicRoom:
		if (_jacquesState > 0) {
			_jacquesState = 5;
			_background->draw(-1, -1, 1);
			_graphics->refreshBackground();
			_background->draw(-1, -1, 3);
			_magics[kColorBrown - 1]._operation = kMagicNothing;
			_whereIs[kPeopleJacques - 150] = kRoomNowhere;
		}
		if (ped != 0) {
			_background->draw(-1, -1, 5);
			_graphics->refreshBackground();
			_sequence->startMusicRoomSeq();
		}
		break;

	case kRoomOutsideNottsPub:
		if (ped == 2) {
			_background->draw(-1, -1, 2);
			_graphics->refreshBackground();
			_sequence->startDuckSeq();
		}
		break;

	case kRoomOutsideArgentPub:
		if (ped == 2)  {
			_background->draw(-1, -1, 5);
			_graphics->refreshBackground();
			_sequence->startMusicRoomSeq();
		}
		break;

	case kRoomWiseWomans: {
		AnimationType *spr1 = _animation->_sprites[1];
		spr1->init(11, false);
		if ((_roomCount[kRoomWiseWomans] == 1) && (ped > 0)) {
			_animation->appearPed(1, 1); // Start on the right-hand side of the screen.
			spr1->walkTo(3); // Walks up to greet you.
		} else {
			_animation->appearPed(1, 3); // Starts where she was before.
			spr1->_facingDir = kDirLeft;
		}

		spr1->_callEachStepFl = true;
		spr1->_eachStepProc = Animation::kProcFaceAvvy; // She always faces Avvy.
		}
		break;

	case kRoomInsideCardiffCastle:
		if (ped > 0) {
			_animation->_sprites[1]->init(10, false); // Define the dart.
			_background->draw(-1, -1, 0);
			_graphics->refreshBackground();
			_sequence->startCardiffSeq2();
		} else {
			_background->draw(-1, -1, 0);
			if (_arrowInTheDoor)
				_background->draw(-1, -1, 2);
			else
				_background->draw(-1, -1, 1);
			_graphics->refreshBackground();
		}
		break;

	case kRoomAvvysGarden:
		if (ped == 1)  {
			_background->draw(-1, -1, 1);
			_graphics->refreshBackground();
			_sequence->startGardenSeq();
		}
		break;

	case kRoomEntranceHall:
	case kRoomInsideAbbey:
	case kRoomYourHall:
		if (ped == 2)  {
#if 0
			// It was the original:
			_celer->show_one(-1, -1, 2);
			_sequence->first_show(1);
			_sequence->then_show(3);
			_sequence->start_to_close();
#endif

			_background->draw(-1, -1, 1);
			_graphics->refreshBackground();
			_sequence->startGardenSeq();
		}
		break;

	case kRoomAylesOffice:
		if (_aylesIsAwake)
			_background->draw(-1, -1, 1);
		_graphics->refreshBackground();
		break; // Ayles awake.

	case kRoomGeidas:
		putGeidaAt(1, ped);
		break; // load Geida

	case kRoomEastHall:
	case kRoomWestHall:
		if (_geidaFollows)
			putGeidaAt(ped + 1, ped);
		break;

	case kRoomLusties:
		if (_geidaFollows)
			putGeidaAt(ped + 5, ped);
		break;

	case kRoomNottsPub:
		if (_sittingInPub)
			_background->draw(-1, -1, 2);
		_npcFacing = 1; // Port.
		break;

	case kRoomOutsideDucks:
		if (ped == 2) {
			// Shut the door
			_background->draw(-1, -1, 2);
			_graphics->refreshBackground();
			_sequence->startDuckSeq();
		}
		break;

	case kRoomDucks:
		_npcFacing = 1; // Duck.
		break;

	default:
		break;
	}

	_animationsEnabled = true;
}

void AvalancheEngine::thinkAbout(byte object, bool type) {
	_thinks = object;
	object--;

	Common::String filename;
	if (type == kThing) {
		filename = "thinks.avd";
	} else { // kPerson
		filename = "folk.avd";

		object -= 149;
		if (object >= 25)
			object -= 8;
		if (object == 20)
			object--; // Last time...
	}

	_graphics->loadMouse(kCurWait);
	CursorMan.showMouse(false);
	_graphics->drawThinkPic(filename, object);
	CursorMan.showMouse(true);

	_thinkThing = type;
}

void AvalancheEngine::drawToolbar() {
	_graphics->drawToolbar();
	_animation->setOldDirection(kDirNone);
	drawDirection();
}

void AvalancheEngine::drawScore() {
	uint16 score = _score;
	int8 numbers[3] = {0, 0, 0};
	for (int i = 0; i < 2; i++) {
		byte divisor = 1;
		for (int j = 0; j < (2 - i); j++)
			divisor *= 10;
		numbers[i] = score / divisor;
		score -= numbers[i] * divisor;
	}
	numbers[2] = score;

	CursorMan.showMouse(false);

	for (int i = 0; i < 3; i++) {
		if (_scoreToDisplay[i] != numbers[i])
			_graphics->drawDigit(numbers[i], 250 + (i + 1) * 15, 177);
	}

	CursorMan.showMouse(true);

	for (int i = 0; i < 3; i++)
		_scoreToDisplay[i] = numbers[i];
}

void AvalancheEngine::incScore(byte num) {
	for (int i = 1; i <= num; i++) {
		_score++;

		if (_soundFx) {
			for (int j = 1; j <= 97; j++)
				// Length of 2 is a guess, the original doesn't have a delay specified
				_sound->playNote(177 + _score * 3, 2);
		}
	}

	drawScore();
}

void AvalancheEngine::useCompass(const Common::Point &cursorPos) {
	byte color = _graphics->getScreenColor(cursorPos);

	switch (color) {
	case kColorGreen:
		_animation->setDirection(kDirUp);
		_animation->setMoveSpeed(0, kDirUp);
		drawDirection();
		break;
	case kColorBrown:
		_animation->setDirection(kDirDown);
		_animation->setMoveSpeed(0, kDirDown);
		drawDirection();
		break;
	case kColorCyan:
		_animation->setDirection(kDirLeft);
		_animation->setMoveSpeed(0, kDirLeft);
		drawDirection();
		break;
	case kColorLightmagenta:
		_animation->setDirection(kDirRight);
		_animation->setMoveSpeed(0, kDirRight);
		drawDirection();
		break;
	case kColorRed:
	case kColorWhite:
	case kColorLightcyan:
	case kColorYellow: // Fall-throughs are intended.
		_animation->stopWalking();
		drawDirection();
		break;
	}
}

void AvalancheEngine::fxToggle() {
	warning("STUB: fxtoggle()");
}

void AvalancheEngine::refreshObjectList() {
	_carryNum = 0;
	if (_thinkThing && !_objects[_thinks - 1])
		thinkAbout(kObjectMoney, kThing); // you always have money

	for (int i = 0; i < kObjectNum; i++) {
		if (_objects[i]) {
			_objectList[_carryNum] = i + 1;
			_carryNum++;
		}
	}
}

/**
 * @remarks	Originally called 'verte'
 */
void AvalancheEngine::guideAvvy(Common::Point cursorPos) {
	if (!_userMovesAvvy)
		return;

	cursorPos.y /= 2;
	byte what;

	// _animation->tr[0] is Avalot.)
	AnimationType *avvy = _animation->_sprites[0];
	if (cursorPos.x < avvy->_x)
		what = 1;
	else if (cursorPos.x > (avvy->_x + avvy->_xLength))
		what = 2;
	else
		what = 0; // On top

	if (cursorPos.y < avvy->_y)
		what += 3;
	else if (cursorPos.y > (avvy->_y + avvy->_yLength))
		what += 6;

	switch (what) {
	case 0:
		_animation->stopWalking();
		break; // Clicked on Avvy: no movement.
	case 1:
		_animation->setMoveSpeed(0, kDirLeft);
		break;
	case 2:
		_animation->setMoveSpeed(0, kDirRight);
		break;
	case 3:
		_animation->setMoveSpeed(0, kDirUp);
		break;
	case 4:
		_animation->setMoveSpeed(0, kDirUpLeft);
		break;
	case 5:
		_animation->setMoveSpeed(0, kDirUpRight);
		break;
	case 6:
		_animation->setMoveSpeed(0, kDirDown);
		break;
	case 7:
		_animation->setMoveSpeed(0, kDirDownLeft);
		break;
	case 8:
		_animation->setMoveSpeed(0, kDirDownRight);
		break;
	}    // No other values are possible.

	drawDirection();
}

void AvalancheEngine::checkClick() {
	Common::Point cursorPos = getMousePos();

	/*if (mrelease > 0)
		after_the_scroll = false;*/

	if ((0 <= cursorPos.y) && (cursorPos.y <= 21))
		_graphics->loadMouse(kCurUpArrow); // up arrow
	else if ((317 <= cursorPos.y) && (cursorPos.y <= 339))
		_graphics->loadMouse(kCurIBeam); //I-beam
	else if ((340 <= cursorPos.y) && (cursorPos.y <= 399))
		_graphics->loadMouse(kCurScrewDriver); // screwdriver
	else if (!_dropdown->isActive()) { // Dropdown can handle its own pointers.
		if (_holdLeftMouse) {
			_graphics->loadMouse(kCurCrosshair); // Mark's crosshairs
			guideAvvy(cursorPos); // Normally, if you click on the picture, you're guiding Avvy around.
		} else
			_graphics->loadMouse(kCurFletch); // fletch
	}

	if (_holdLeftMouse) {
		if ((0 <= cursorPos.y) && (cursorPos.y <= 21)) { // Click on the dropdown menu.
			if (_dropsOk)
				_dropdown->update();
		} else if ((317 <= cursorPos.y) && (cursorPos.y <= 339)) { // Click on the command line.
			_parser->_inputTextPos = (cursorPos.x - 23) / 8;
			if (_parser->_inputTextPos > _parser->_inputText.size() + 1)
				_parser->_inputTextPos = _parser->_inputText.size() + 1;
			if (_parser->_inputTextPos < 1)
				_parser->_inputTextPos = 1;
			_parser->_inputTextPos--;
			_parser->plotText();
		} else if ((340 <= cursorPos.y) && (cursorPos.y <= 399)) { // Check the toolbar.
			if ((137 <= cursorPos.x) && (cursorPos.x <= 207)) { // Control Avvy with the compass.
				if (_alive && _avvyIsAwake)
					useCompass(cursorPos);
			} else if ((208 <= cursorPos.x) && (cursorPos.x <= 260)) { // Examine the _thing.
				do {
					updateEvents();
				} while (_holdLeftMouse);

				if (_thinkThing) {
					_parser->_thing = _thinks;
					_parser->_thing += 49;
					_parser->_person = kPeoplePardon;
				} else {
					_parser->_person = (People)_thinks;
					_parser->_thing = _parser->kPardon;
				}
				callVerb(kVerbCodeExam);
			} else if ((261 <= cursorPos.x) && (cursorPos.x <= 319)) { // Display the score.
				do {
					updateEvents();
				} while (_holdLeftMouse);

				callVerb(kVerbCodeScore);
			} else if ((320 <= cursorPos.x) && (cursorPos.x <= 357)) { // Change speed.
				_animation->_sprites[0]->_speedX = kWalk;
				_animation->updateSpeed();
			} else if ((358 <= cursorPos.x) && (cursorPos.x <= 395)) { // Change speed.
				_animation->_sprites[0]->_speedX = kRun;
				_animation->updateSpeed();
			} else if ((396 <= cursorPos.x) && (cursorPos.x <= 483))
				_sound->toggleSound();
			else if ((535 <= cursorPos.x) && (cursorPos.x <= 640))
				_mouseText.insertChar(kControlNewLine, 0);
		} else if (!_dropsOk)
			_mouseText = Common::String(13) + _mouseText;
	}
}

void AvalancheEngine::errorLed() {
	_dialogs->setReadyLight(0);
	_graphics->drawErrorLight(true);
	for (int i = 177; i >= 1; i--) {
		_sound->playNote(177 + (i * 177177) / 999, 1);
		_system->delayMillis(1);
	}
	_graphics->drawErrorLight(false);
	_dialogs->setReadyLight(2);
}

/**
 * Displays a fade out, full screen.
 * This version is different to the one in the original, which was fading in 3 steps.
 * @remarks	Originally called 'dusk'
 */
void AvalancheEngine::fadeOut() {
	byte pal[3], tmpPal[3];

	_graphics->setBackgroundColor(kColorBlack);
	if (_fxHidden)
		return;
	_fxHidden = true;

	for (int i = 0; i < 16; i++) {
		for (int j = 0; j < 16; j++) {
			g_system->getPaletteManager()->grabPalette((byte *)tmpPal, j, 1);
			_fxPal[i][j][0] = tmpPal[0];
			_fxPal[i][j][1] = tmpPal[1];
			_fxPal[i][j][2] = tmpPal[2];
			if (tmpPal[0] >= 16)
				pal[0] = tmpPal[0] - 16;
			else
				pal[0] = 0;

			if (tmpPal[1] >= 16)
				pal[1] = tmpPal[1] - 16;
			else
				pal[1] = 0;

			if (tmpPal[2] >= 16)
				pal[2] = tmpPal[2] - 16;
			else
				pal[2] = 0;

			g_system->getPaletteManager()->setPalette(pal, j, 1);
		}
		_system->delayMillis(10);
		_graphics->refreshScreen();
	}
}

/**
 * Displays a fade in, full screen.
 * This version is different to the one in the original, which was fading in 3 steps.
 * @remarks	Originally called 'dawn'
 */
void AvalancheEngine::fadeIn() {
	if (_holdTheDawn || !_fxHidden)
		return;

	_fxHidden = false;

	byte pal[3];
	for (int i = 15; i >= 0; i--) {
		for (int j = 0; j < 16; j++) {
			pal[0] = _fxPal[i][j][0];
			pal[1] = _fxPal[i][j][1];
			pal[2] = _fxPal[i][j][2];
			g_system->getPaletteManager()->setPalette(pal, j, 1);
		}
		_system->delayMillis(10);
		_graphics->refreshScreen();
	}

	if ((_room == kRoomYours) && _avvyInBed && _teetotal)
		_graphics->setBackgroundColor(kColorYellow);
}

void AvalancheEngine::drawDirection() { // It's data is loaded in load_digits().
	if (_animation->getOldDirection() == _animation->getDirection())
		return;

	_animation->setOldDirection(_animation->getDirection());

	CursorMan.showMouse(false);
	_graphics->drawDirection(_animation->getDirection(), 0, 161);
	CursorMan.showMouse(true);
}

void AvalancheEngine::gameOver() {
	_userMovesAvvy = false;

	AnimationType *avvy = _animation->_sprites[0];
	int16 sx = avvy->_x;
	int16 sy = avvy->_y;

	avvy->remove();
	avvy->init(12, true); // 12 = Avalot falls
	avvy->_stepNum = 0;
	avvy->appear(sx, sy, kDirUp);

	_timer->addTimer(3, Timer::kProcAvalotFalls, Timer::kReasonFallingOver);
	_alive = false;
}

void AvalancheEngine::minorRedraw() {
	fadeOut();

	enterRoom(_room, 0); // Ped unknown or non-existant.

	for (int i = 0; i < 3; i++)
		_scoreToDisplay[i] = -1; // impossible digits
	drawScore();

	fadeIn();
}

uint16 AvalancheEngine::bearing(byte whichPed) {
	AnimationType *avvy = _animation->_sprites[0];
	PedType *curPed = &_peds[whichPed];

	if (avvy->_x == curPed->_x)
		return 0;

	int16 deltaX = avvy->_x - curPed->_x;
	int16 deltaY = avvy->_y - curPed->_y;
	uint16 result = Common::rad2deg<float,uint16>(atan((float)deltaY / (float)deltaX)); // TODO: Would atan2 be preferable?
	if (avvy->_x < curPed->_x) {
		return result + 90;
	} else {
		return result + 270;
	}
}

/**
 * @remarks	Originally called 'sprite_run'
 */
void AvalancheEngine::spriteRun() {
	_doingSpriteRun = true;
	_animation->animLink();
	_doingSpriteRun = false;
}

// CHECKME: Unused function
void AvalancheEngine::fixFlashers() {
	_ledStatus = 177;
	_animation->setOldDirection(kDirNone);
	_dialogs->setReadyLight(2);
	drawDirection();
}

Common::String AvalancheEngine::intToStr(int32 num) {
	return Common::String::format("%d", num);
}

void AvalancheEngine::resetVariables() {
	_carryNum = 0;
	for (int i = 0; i < kObjectNum; i++)
		_objects[i] = false;

	_score = 0;
	_money = 0;
	_room = kRoomNowhere;
	_saveNum = 0;
	for (int i = 0; i < 100; i++)
		_roomCount[i] = 0;

	_wonNim = false;
	_wineState = 0;
	_cwytalotGone = false;
	_passwordNum = 0;
	_aylesIsAwake = false;
	_drawbridgeOpen = 0;
	_avariciusTalk = 0;
	_rottenOnion = false;
	_onionInVinegar = false;
	_givenToSpludwick = 0;
	_brummieStairs = 0;
	_cardiffQuestionNum = 0;
	_passedCwytalotInHerts = false;
	_avvyIsAwake = false;
	_avvyInBed = false;
	_userMovesAvvy = false;
	_npcFacing = 0;
	_givenBadgeToIby = false;
	_friarWillTieYouUp = false;
	_tiedUp = false;
	_boxContent = 0;
	_talkedToCrapulus = false;
	_jacquesState = 0;
	_bellsAreRinging = false;
	_standingOnDais = false;
	_takenPen = false;
	_arrowInTheDoor = false;
	_favoriteDrink = "";
	_favoriteSong = "";
	_worstPlaceOnEarth = "";
	_spareEvening = "";
	_totalTime = 0;
	_jumpStatus = 0;
	_mushroomGrowing = false;
	_spludwickAtHome = false;
	_lastRoom = kRoomDummy;
	_lastRoomNotMap = kRoomDummy;
	_crapulusWillTell = false;
	_enterCatacombsFromLustiesRoom = false;
	_teetotal = false;
	_malagauche = 0;
	_drinking = 0;
	_enteredLustiesRoomAsMonk = false;
	_catacombX = 0;
	_catacombY = 0;
	_avvysInTheCupboard = false;
	_geidaFollows = false;
	_givenPotionToGeida = false;
	_lustieIsAsleep = false;
	_beenTiedUp = false;
	_sittingInPub = false;
	_spurgeTalkCount = 0;
	_metAvaroid = false;
	_takenMushroom = false;
	_givenPenToAyles = false;
	_askedDogfoodAboutNim = false;
	_startTime = getTimeInSeconds();
}

void AvalancheEngine::resetAllVariables() {
	resetVariables();
	_parser->resetVariables();
	_nim->resetVariables();
	_animation->resetVariables();
	_sequence->resetVariables();
	_background->resetVariables();
	_dropdown->resetVariables();
	_timer->resetVariables();
}

void AvalancheEngine::newGame() {
	for (int i = 0; i < kMaxSprites; i++) {
		AnimationType *spr = _animation->_sprites[i];
		if (spr->_quick)
			spr->remove();
	}
	// Deallocate sprite. Sorry, beta testers!

	AnimationType *avvy = _animation->_sprites[0];
	avvy->init(0, true);

	_alive = true;
	resetAllVariables();

	_dialogs->setBubbleStateNatural();

	_spareEvening = "answer a questionnaire";
	_favoriteDrink = "beer";
	_money = 30; // 2/6
	_animation->setDirection(kDirStopped);
	_parser->_wearing = kObjectClothes;
	_objects[kObjectMoney - 1] = true;
	_objects[kObjectBodkin - 1] = true;
	_objects[kObjectBell - 1] = true;
	_objects[kObjectClothes - 1] = true;

	_thinkThing = true;
	_thinks = 2;
	refreshObjectList();
	_animationsEnabled = true;

	avvy->appear(300, 117, kDirRight); // Needed to initialize Avalot.
	//for (gd = 0; gd <= 30; gd++) for (gm = 0; gm <= 1; gm++) also[gd][gm] = nil;
	// fillchar(previous^,sizeof(previous^),#0); { blank out array }
	_him = kPeoplePardon;
	_her = kPeoplePardon;
	_it = Parser::kPardon;
	_passwordNum = _rnd->getRandomNumber(29) + 1; //Random(30) + 1;
	_userMovesAvvy = false;
	_doingSpriteRun = false;
	_avvyInBed = true;

	_isLoaded = false;

	enterRoom(kRoomYours, 1);
	avvy->_visible = false;
	drawScore();
	_dropdown->setup();
	_clock->update();
	spriteRun();
}

bool AvalancheEngine::getFlag(char x) {
	for (uint16 i = 0; i < _flags.size(); i++) {
		if (_flags[i] == x)
			return true;
	}

	return false;
}

bool AvalancheEngine::decreaseMoney(uint16 amount) {
	_money -= amount;
	if (_money < 0) {
		_dialogs->displayScrollChain('Q', 2); // "You are now denariusless!"
		gameOver();
		return false;
	} else
		return true;
}

Common::String AvalancheEngine::getName(People whose) {
	static const char lads[17][20] = {
		"Avalot",     "Spludwick",  "Crapulus",  "Dr. Duck",  "Malagauche",
		"Friar Tuck", "Robin Hood", "Cwytalot",  "du Lustie", "the Duke of Cardiff",
		"Dogfood",    "A trader",   "Ibythneth", "Ayles",     "Port",
		"Spurge",     "Jacques"
	};

	static const char lasses[4][15] = {"Arkata", "Geida", "\0xB1", "the Wise Woman"};

	if (whose <= kPeopleJacques)
		return Common::String(lads[whose - kPeopleAvalot]);
	else if ((whose >= kPeopleArkata) && (whose <= kPeopleWisewoman))
		return Common::String(lasses[whose - kPeopleArkata]);
	else
		error("getName() - Unexpected character id %d", (byte) whose);
}

Common::String AvalancheEngine::getItem(byte which) {
	static const char items[kObjectNum][18] = {
		"some wine",       "your money-bag", "your bodkin", "a potion",          "a chastity belt",
		"a crossbow bolt", "a crossbow",     "a lute",      "a pilgrim's badge", "a mushroom",
		"a key",           "a bell",         "a scroll",    "a pen",             "some ink",
		"your clothes",    "a habit",        "an onion"
	};

	Common::String result;
	if (which > 150)
		which -= 149;

	switch (which) {
	case kObjectWine:
		switch (_wineState) {
		case 0:
		case 1:
		case 4:
			result = Common::String(items[which - 1]);
			break;
		case 3:
			result = "some vinegar";
			break;
		}
		break;
	case kObjectOnion:
		if (_rottenOnion)
			result = "a rotten onion";
		else if (_onionInVinegar)
			result = "a pickled onion (in the vinegar)";
		else
			result = Common::String(items[which - 1]);
		break;
	default:
		if ((which < kObjectNum) && (which > 0))
			result = Common::String(items[which - 1]);
		else
			result = "";
	}
	return result;
}

Common::String AvalancheEngine::f5Does() {
	switch (_room) {
	case kRoomYours:
		if (!_avvyIsAwake)
			return Common::String::format("%cWWake up", kVerbCodeWake);
		else if (_avvyInBed)
			return Common::String::format("%cGGet up", kVerbCodeStand);
		break;
	case kRoomInsideCardiffCastle:
		if (_standingOnDais)
			return Common::String::format("%cCClimb down", kVerbCodeClimb);
		else
			return Common::String::format("%cCClimb up", kVerbCodeClimb);
		break;
	case kRoomNottsPub:
		if (_sittingInPub)
			return Common::String::format("%cSStand up", kVerbCodeStand);
		else
			return Common::String::format("%cSSit down", kVerbCodeSit);
		break;
	case kRoomMusicRoom:
		if (_animation->inField(5))
			return Common::String::format("%cPPlay the harp", kVerbCodePlay);
		break;
	default:
		break;
	}

	return Common::String::format("%c", kVerbCodePardon); // If all else fails...
}

void AvalancheEngine::flipRoom(Room room, byte ped) {
	assert((ped > 0) && (ped < 15));
	if (!_alive) {
		// You can't leave the room if you're dead.
		_animation->_sprites[0]->_moveX = 0;
		_animation->_sprites[0]->_moveY = 0; // Stop him from moving.
		return;
	}

	if ((room == kRoomDummy) && (_room == kRoomLusties)) {
		_animation->hideInCupboard();
		return;
	}

	if ((_jumpStatus > 0) && (_room == kRoomInsideCardiffCastle)) {
		// You can't *jump* out of Cardiff Castle!
		_animation->_sprites[0]->_moveX = 0;
		return;
	}

	exitRoom(_room);
	fadeOut();

	for (int16 i = 1; i < _animation->kSpriteNumbMax; i++) {
		if (_animation->_sprites[i]->_quick)
			_animation->_sprites[i]->remove();
	} // Deallocate sprite

	if (_room == kRoomLustiesRoom)
		_enterCatacombsFromLustiesRoom = true;

	if (room > kRoomMap)
		return;

	enterRoom(room, ped);
	_animation->appearPed(0, ped - 1);
	_enterCatacombsFromLustiesRoom = false;
	_animation->setOldDirection(_animation->getDirection());
	_animation->setDirection(_animation->_sprites[0]->_facingDir);
	drawDirection();

	fadeIn();
}

/**
 * Open the Door.
 * This slides the door open. The data really ought to be saved in
 * the Also file, and will be next time. However, for now, they're
 * here.
 * @remarks	Originally called 'open_the_door'
 */
void AvalancheEngine::openDoor(Room whither, byte ped, byte magicnum) {
	switch (_room) {
	case kRoomOutsideYours:
	case kRoomOutsideNottsPub:
	case kRoomOutsideDucks:
		_sequence->startOutsideSeq(whither, ped);
		break;
	case kRoomInsideCardiffCastle:
		_sequence->startCardiffSeq(whither, ped);
		break;
	case kRoomAvvysGarden:
	case kRoomEntranceHall:
	case kRoomInsideAbbey:
	case kRoomYourHall:
		_sequence->startHallSeq(whither, ped);
		break;
	case kRoomMusicRoom:
	case kRoomOutsideArgentPub:
		_sequence->startMusicRoomSeq2(whither, ped);
		break;
	case kRoomLusties:
		switch (magicnum) {
		case 14:
			if (_avvysInTheCupboard) {
				_animation->hideInCupboard();
				_sequence->startCupboardSeq();
				return;
			} else {
				_animation->appearPed(0, 5);
				_animation->_sprites[0]->_facingDir = kDirRight;
				_sequence->startLustiesSeq2(whither, ped);
			}
			break;
		case 12:
			_sequence->startLustiesSeq3(whither, ped);
			break;
		}
		break;
	default:
		_sequence->startDummySeq(whither, ped);
	}
}

void AvalancheEngine::setRoom(People persId, Room roomId) {
	_whereIs[persId - kPeopleAvalot] = roomId;
}

Room AvalancheEngine::getRoom(People persId) {
	return _whereIs[persId - kPeopleAvalot];
}
} // End of namespace Avalanche
