/* ScummVM - Scumm Interpreter
 * Copyright (C) 2005-2006 The ScummVM project
 *
 * 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.
 *
 * $URL: https://svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-9-0/engines/lure/hotspots.cpp $
 * $Id: hotspots.cpp 22809 2006-05-31 20:05:31Z anotherguest $
 *
 */

#include "lure/hotspots.h"
#include "lure/decode.h"
#include "lure/palette.h"
#include "lure/disk.h"
#include "lure/res.h"
#include "lure/scripts.h"
#include "lure/room.h"
#include "lure/strings.h"
#include "lure/res_struct.h"
#include "lure/events.h"
#include "lure/game.h"

namespace Lure {

Hotspot::Hotspot(HotspotData *res): _pathFinder(this) {
	Resources &resources = Resources::getReference();

	_data = res;
	_anim = NULL;
	_frames = NULL;
	_numFrames = 0;
	_persistant = false;

	_hotspotId = res->hotspotId;
	_roomNumber = res->roomNumber;
	_startX = res->startX;
	_startY = res->startY;
	_destX = res->startX;
	_destY = res->startY;
	_destHotspotId = 0;
	_height = res->height;
	_width = res->width;
	_heightCopy = res->heightCopy;
	_widthCopy = res->widthCopy;
	_yCorrection = res->yCorrection;
	_talkX = res->talkX;
	_talkY = res->talkY;
	_layer = res->layer;
	_sequenceOffset = res->sequenceOffset;
	_tickCtr = res->tickTimeout;
	_actions = res->actions;
	_colourOffset = res->colourOffset;

	_override = resources.getHotspotOverride(res->hotspotId);

	if (_data->animRecordId != 0)
		setAnimation(_data->animRecordId);
	_tickHandler = HotspotTickHandlers::getHandler(_data->tickProcOffset);

	_frameCtr = 0;
	_skipFlag = false;
	_charRectY = 0;
	_actionCtr = 0;
	_blockedOffset = 0;
	_exitCtr = 0;
	_blockedState = BS_NONE;
	_unknownFlag = false;

	if (_data->npcSchedule != 0) {
		CharacterScheduleEntry *entry = resources.charSchedules().getEntry(_data->npcSchedule);
		_currentActions.addFront(DISPATCH_ACTION, entry, _roomNumber);
	}
}

// Special constructor used to create a voice hotspot

Hotspot::Hotspot(Hotspot *character, uint16 objType): _pathFinder(this) {
	_data = NULL;
	_anim = NULL;
	_frames = NULL;
	_numFrames = 0;
	_persistant = false;
	_hotspotId = 0xffff;
	_override = NULL;
	_colourOffset = 0;
	_destHotspotId = character->hotspotId();
	_blockedOffset = 0;
	_exitCtr = 0;
	_blockedState = BS_NONE;
	_unknownFlag = false;

	switch (objType) {
	case VOICE_ANIM_ID:
		_roomNumber = character->roomNumber();
		_destHotspotId = character->hotspotId();
		_startX = character->x() + character->talkX() + 12;
		_startY = character->y() + character->talkY() - 18;
		_destX = _startX;
		_destY = _startY;
		_layer = 1;
		_height = 18;
		_width = 32;
		_heightCopy = character->height() + 14;
		_widthCopy = 24;
		_layer = 2;
	
		_tickHandler = HotspotTickHandlers::getHandler(VOICE_TICK_PROC_ID);
		_tickCtr = 0;

		setAnimation(VOICE_ANIM_ID);
		break;

	default:
		break;
	}
}

Hotspot::~Hotspot() {
	if (_frames) delete _frames;
}

void Hotspot::setAnimation(uint16 newAnimId) {
	Resources &r = Resources::getReference();
	HotspotAnimData *tempAnim;
	if (newAnimId == 0) tempAnim = NULL;
	else tempAnim = r.getAnimation(newAnimId); 
	
	setAnimation(tempAnim);
}

void Hotspot::setAnimation(HotspotAnimData *newRecord) {
	Disk &r = Disk::getReference();
	if (_frames) {
		delete _frames;
		_frames = NULL;
	}
	_anim = NULL;
	_numFrames = 0;
	_frameNumber = 0;
	if (!newRecord) return;
	if (!r.exists(newRecord->animId)) return;

	_anim = newRecord;
	MemoryBlock *src = Disk::getReference().getEntry(_anim->animId);
	
	uint16 *numEntries = (uint16 *) src->data();
	uint16 *headerEntry = (uint16 *) (src->data() + 2);

	if ((*numEntries > 99) || (*numEntries == 0)) {
		// Wobbly, likely something wrong with the resoure
		_width = 1;
		_numFrames = 1;
		_frameNumber = 0;
		_frames = new Surface(1, 1);
		_frames->data().setBytes(_colourOffset, 0, 1);
		return;
	}

	// Calculate total needed size for output and create memory block to hold it
	uint32 totalSize = 0;
	for (uint16 ctr = 0; ctr < *numEntries; ++ctr, ++headerEntry) {
		totalSize += (*headerEntry + 31) / 32;
	}
	totalSize = (totalSize + 0x81) << 4;
	MemoryBlock *dest = Memory::allocate(totalSize);

	uint32 srcStart = (*numEntries + 1) * sizeof(uint16) + 6;
	AnimationDecoder::decode_data(src, dest, srcStart);

	_numFrames = *numEntries;
	_frameNumber = 0;
	
	_frames = new Surface(_width * _numFrames, _height);

	_frames->data().setBytes(_colourOffset, 0, _frames->data().size());

	byte *pSrc = dest->data() + 0x40;
	byte *pDest;
	headerEntry = (uint16 *) (src->data() + 2);
	MemoryBlock &mDest = _frames->data();

	for (uint16 frameNumCtr = 0; frameNumCtr < _numFrames; ++frameNumCtr, ++headerEntry) {

		if ((newRecord->flags & PIXELFLAG_HAS_TABLE) != 0) {
			// For animations with an offset table, set the source point for each frame
			uint16 frameOffset = *((uint16 *) (src->data() + ((frameNumCtr + 1) * sizeof(uint16)))) + 0x40;
			if ((uint32) frameOffset + _height * (_width / 2) > dest->size()) 
				error("Invalid frame offset in animation %x", newRecord->animRecordId);
			pSrc = dest->data() + frameOffset;
		}

		// Copy over the frame, applying the colour offset to each nibble
		for (uint16 yPos = 0; yPos < _height; ++yPos) {
			pDest = mDest.data() + (yPos * _numFrames + frameNumCtr) * _width;

			for (uint16 ctr = 0; ctr < _width / 2; ++ctr) {
				*pDest++ = _colourOffset + (*pSrc >> 4);
				*pDest++ = _colourOffset + (*pSrc & 0xf);
				++pSrc;
			}
		}
	}

	delete src;
	delete dest;
}

void Hotspot::copyTo(Surface *dest) {
	int16 xPos = _startX;
	int16 yPos = _startY;
	uint16 hWidth = _width;
	uint16 hHeight = _height;

	Rect r(_frameNumber * hWidth, 0, (_frameNumber + 1) * hWidth - 1, 
		hHeight - 1);

	// Handle clipping for X position
	if (xPos < 0) {
		if (xPos + hWidth <= 0)
			// Completely off screen, so don't display
			return;

		// Reduce the source rectangle to only the on-screen portion
		r.left += -xPos;
		xPos = 0;
	}
	else if (xPos >= FULL_SCREEN_WIDTH) 
		return;
	else if (xPos + hWidth > FULL_SCREEN_WIDTH)
		r.right = r.left + (FULL_SCREEN_WIDTH - xPos - 1);

	// Handle clipping for Y position
	if (yPos < 0) {
		if (yPos + hHeight <= 0) 
			// Completely off screen, so don't display
			return;

		// Reduce the source rectangle to only the on-screen portion
		r.top += -yPos;
		yPos = 0;
	}
	else if (yPos >= FULL_SCREEN_HEIGHT)
		return;
	else if (yPos + hHeight > FULL_SCREEN_HEIGHT)
		r.bottom = r.top + (FULL_SCREEN_HEIGHT - yPos - 1);

	// Final check to make sure there is anything to display
	if ((r.top >= r.bottom) || (r.left >= r.right))
		return;

	_frames->copyTo(dest, r, (uint16) xPos, (uint16) yPos, _colourOffset);
}

void Hotspot::incFrameNumber() {
	++_frameNumber;
	if (_frameNumber >= _numFrames) 
		_frameNumber = 0;
}

bool Hotspot::isActiveAnimation() {
	return ((_numFrames != 0) && (_layer != 0));
}

uint16 Hotspot::nameId() {
	if (_data == NULL)
		return 0;
	else 
		return _data->nameId;
}

void Hotspot::setPosition(int16 newX, int16 newY) {
	_startX = newX;
	_startY = newY;
	if (_data) {
		_data->startX = newX;
		_data->startY = newY;
	}
}

void Hotspot::setSize(uint16 newWidth, uint16 newHeight) {
	_width = newWidth;
	_height = newHeight;
}

bool Hotspot::executeScript() {
	if (_data->sequenceOffset == 0)
		return false;
	else
		return HotspotScript::execute(this);
}

void Hotspot::tick() {
	_tickHandler(*this);
}

void Hotspot::setTickProc(uint16 newVal) {
	if (_data)
		_data->tickProcOffset = newVal;
	_tickHandler = HotspotTickHandlers::getHandler(newVal);	
}

void Hotspot::walkTo(int16 endPosX, int16 endPosY, uint16 destHotspot) {
	if ((hotspotId() == PLAYER_ID) && (PATHFIND_COUNTDOWN != 0)) {
		// Show the clock cursor whilst pathfinding will be calculated
		Mouse &mouse = Mouse::getReference();
		mouse.setCursorNum(CURSOR_TIME_START, 0, 0);
	}

	_destX = endPosX;
	_destY = endPosY;
	_destHotspotId = destHotspot;
	_currentActions.addFront(START_WALKING, _roomNumber);
}

void Hotspot::stopWalking() {
	// TODO: voiceCtr = 0
	_actionCtr = 0;
	_currentActions.clear();
	Room::getReference().setCursorState(CS_NONE);
}

void Hotspot::endAction() {
	// TODO: voiceCtr = 0
	_actionCtr = 0;
	if (_hotspotId == PLAYER_ID)
		Room::getReference().setCursorState(CS_NONE);

	if (_currentActions.top().hasSupportData()) 
		_currentActions.top().setSupportData(_currentActions.top().supportData().next());
}

void Hotspot::setDirection(Direction dir) {
	_direction = dir;

	switch (dir) {
	case UP:
		setFrameNumber(_anim->upFrame);
		_charRectY = 4;
		break;
	case DOWN:
		setFrameNumber(_anim->downFrame);
		_charRectY = 4;
		break;
	case LEFT:
		setFrameNumber(_anim->leftFrame);
		_charRectY = 0;
		break;
	case RIGHT:
		setFrameNumber(_anim->rightFrame);
		_charRectY = 0;
		break;
	default:
		break;
	}
}

// Makes the character face the given hotspot

void Hotspot::faceHotspot(HotspotData *hotspot) {
	if (hotspot->hotspotId >= START_NONVISUAL_HOTSPOT_ID) {
		// Non visual hotspot
		setDirection(hotspot->nonVisualDirection());

	} else {
		// Visual hotspot
		int xp = x() - hotspot->startX;
		int yp = y() + heightCopy() - (hotspot->startY + hotspot->heightCopy);

		if (ABS(yp) >= ABS(xp)) {
			if (yp < 0) setDirection(DOWN);
			else setDirection(UP);
		} else {
			if (xp < 0) setDirection(RIGHT);
			else setDirection(LEFT);
		}
	}

	if (hotspotId() == PLAYER_ID) {
		Room::getReference().update();
		Screen::getReference().update();
	}
}

// Sets a character walking to a random destination position

void Hotspot::setRandomDest() {
	Resources &res = Resources::getReference();
	RoomData *roomData = res.getRoom(roomNumber());
	Common::Rect &rect = roomData->walkBounds;
	Common::RandomSource _rnd;
	int tryCtr = 0;
	int16 xp, yp;

	if (_currentActions.isEmpty())
		_currentActions.addFront(START_WALKING, roomNumber());
	else
		_currentActions.top().setAction(START_WALKING);
	
	while (tryCtr ++ <= 20) {
		xp = rect.left + _rnd.getRandomNumber(rect.right - rect.left);
		yp = rect.left + _rnd.getRandomNumber(rect.right - rect.left);
		setDestPosition(xp, yp);

		if (!roomData->paths.isOccupied(xp, yp) && !roomData->paths.isOccupied(xp, yp)) 
			break;
	}
}

// Sets or clears the hotspot as occupying an area in its room's pathfinding data

void Hotspot::setOccupied(bool occupiedFlag) {
	int xp = x() >> 3;
	int yp = (y() - 8 + heightCopy() - 4) >> 3;
	int widthVal = MAX((widthCopy() >> 3), 1);

	// Handle cropping for screen left
	if (xp < 0) {
		xp = -xp;
		widthVal -= xp;
		if (widthVal <= 0) return;
		xp = 0;
	}

	// Handle cropping for screen right
	int x2 = xp + widthVal - ROOM_PATHS_WIDTH - 1;
	if (x2 >= 0) {
		widthVal -= (x2 + 1);
		if (widthVal <= 0) return;
	}

	RoomPathsData &paths = Resources::getReference().getRoom(_roomNumber)->paths;
	if (occupiedFlag) {
		paths.setOccupied(xp, yp, widthVal);
	} else {
		paths.clearOccupied(xp, yp, widthVal);
	}
}

// walks the character a single step in a sequence defined by the walking list

bool Hotspot::walkingStep() {
	if (_pathFinder.isEmpty())	return true;

	// Check to see if the end of the next straight walking slice
	if (_pathFinder.stepCtr() >= _pathFinder.top().numSteps()) {
		// Move to next slice in walking sequence
		_pathFinder.stepCtr() = 0;
		_pathFinder.pop();
		if (_pathFinder.isEmpty())	return true;
	}

	if (_pathFinder.stepCtr() == 0)
		// At start of new slice, set the direction
		setDirection(_pathFinder.top().direction());

	MovementDataList *frameSet;
	switch (_pathFinder.top().direction()) {
	case UP:
		frameSet = &_anim->upFrames;
		break;
	case DOWN:
		frameSet = &_anim->downFrames;
		break;
	case LEFT:
		frameSet = &_anim->leftFrames;
		break;
	case RIGHT:
		frameSet = &_anim->rightFrames;
		break;
	default:
		return true;
	}

	int16 _xChange, _yChange;
	uint16 nextFrame;
	frameSet->getFrame(frameNumber(), _xChange, _yChange, nextFrame);
	setFrameNumber(nextFrame);
	setPosition(x() + _xChange, y() + _yChange);

	++_pathFinder.stepCtr();
	return false;
}

void Hotspot::updateMovement() {
	assert(_data != NULL);
	if (_currentActions.action() == EXEC_HOTSPOT_SCRIPT) {
		if (_data->coveredFlag) {
			// Reset position and direction
			resetPosition();
		} else {
			// Make sure the cell occupied by character is covered
			_data->coveredFlag = true;
			setOccupied(true);
		}
	}
}

void Hotspot::updateMovement2(CharacterMode value) {
	setCharacterMode(value);
	updateMovement();
}

void Hotspot::resetPosition() {
	setPosition(x() & 0xf8 | 5, y());
	setDirection(direction());
}

/*-------------------------------------------------------------------------*/
/* Hotspot action handling                                                 */
/*                                                                         */
/*-------------------------------------------------------------------------*/

uint16 validRoomExitHotspots[] = {0x2711, 0x2712, 0x2714, 0x2715, 0x2716, 0x2717,
	0x2718, 0x2719, 0x271A, 0x271E, 0x271F, 0x2720, 0x2721, 0x2722, 0x2725, 0x2726,
	0x2729, 0x272A, 0x272B, 0x272C, 0x272D, 0x272E, 0x272F, 0};

bool Hotspot::isRoomExit(uint16 id) {
	for (uint16 *p = &validRoomExitHotspots[0]; *p != 0; ++p) 
		if (*p == id) return true;
	return false;
}

HotspotPrecheckResult Hotspot::actionPrecheck(HotspotData *hotspot) {
	if ((hotspot->hotspotId == 0x420) || (hotspot->hotspotId == 0x436) ||
		(hotspot->hotspotId == 0x429)) {
		// TODO: figure out specific handling code
		actionPrecheck3(hotspot);
		return PC_EXECUTE;
	} else {
		return actionPrecheck2(hotspot);
	}
}

HotspotPrecheckResult Hotspot::actionPrecheck2(HotspotData *hotspot) {
	ValueTableData fields = Resources::getReference().fieldList();

	if (hotspot->roomNumber != roomNumber()) {
		// Hotspot isn't in same room as character
		if (_actionCtr == 0) 
			Dialog::showMessage(0, hotspotId());
		_actionCtr = 0;
		return PC_NOT_IN_ROOM;
	} else if (_actionCtr != 0) {
		// loc_883
		++_actionCtr;

		if (_actionCtr >= 6) {
			warning("actionCtr exceeded");
			_actionCtr = 0;
			Dialog::showMessage(0xD, hotspotId());
			return PC_EXCESS;
		}

		if (hotspot->hotspotId < FIRST_NONCHARACTER_ID) {
			// TODO: Does other checks on HS[44] -> loc_886
			_actionCtr = 0;
			Dialog::showMessage(0xE, hotspotId());
			return PC_UNKNOWN;
		}
	} else {
		_actionCtr = 1;

		if (hotspot->hotspotId < FIRST_NONCHARACTER_ID) {
			// TODO: Extra Checks
			Dialog::showMessage(5, hotspotId());
			return PC_INITIAL;
		}
	}

	if (characterWalkingCheck(hotspot)) {
		return PC_INITIAL;
	} else {
		actionPrecheck3(hotspot);
		return PC_EXECUTE;
	}
}

void Hotspot::actionPrecheck3(HotspotData *hotspot) {
	_actionCtr = 0;
	if (hotspot->hotspotId < FIRST_NONCHARACTER_ID) {
		// TODO: HS[44]=8, HS[42]=1E, HS[50]=ID
	}
}

bool Hotspot::characterWalkingCheck(HotspotData *hotspot) {
	int16 xp, yp;

	if (hotspot == NULL) {
		// DEBUG for now - hardcoded value for 3E7h (NULL)
		xp = 78; yp = 162;
	}
	else if ((hotspot->walkX == 0) && (hotspot->walkY == 0)) {
		// The hotspot doesn't have any walk co-ordinates
		xp = hotspot->startX;
		yp = hotspot->startY + hotspot->heightCopy - 4;
	} else {
		xp = hotspot->walkX;
		yp = hotspot->walkY & 0x7fff;
		if ((hotspot->walkY & 0x8000) != 0) {
			// Special handling for walking
//			if (((xp >> 3) != (x() >> 3)) ||
//				((((y() + heightCopy()) >> 3) - 1) != (yp >> 3))) {
			if ((ABS(xp - x()) > 8) || 
				(ABS(yp - (y() + heightCopy())) > 8)) {
				walkTo(xp, yp);
				return true;
			} else {
				return false;
			}
		}
	}

	// Default walking handling
	// TODO: ANIM[27h] = 1 if hotspot has walk co-ordinates
	if ((ABS(x() - xp) >= 8) ||
		(ABS(y() + heightCopy() - yp - 1) >= 19)) {
		walkTo(xp, yp);
		return true;
	}

	return false;
}

bool Hotspot::doorCloseCheck(uint16 doorId) {
	Resources &res = Resources::getReference();
	Hotspot *doorHotspot = res.getActiveHotspot(doorId);
	if (!doorHotspot) {
		warning("Hotspot %xh is not currently active", doorId);
		return true;
	}

	Rect bounds(doorHotspot->x(), doorHotspot->y() + doorHotspot->heightCopy() 
		- doorHotspot->yCorrection() - doorHotspot->charRectY(),
		doorHotspot->x() + doorHotspot->widthCopy(),
		doorHotspot->y() + doorHotspot->heightCopy() + doorHotspot->charRectY());

	// Loop through active hotspots
	HotspotList::iterator i;
	HotspotList &lst = res.activeHotspots();
	for (i = lst.begin(); i != lst.end(); ++i) {
		Hotspot *hsCurrent = *i;

		// Skip entry if it's the door or the character
		if ((hsCurrent->hotspotId() == hotspotId()) ||
			(hsCurrent->hotspotId() == doorHotspot->hotspotId())) 
			continue;

		// Skip entry if it doesn't meet certain criteria
		if ((hsCurrent->layer() == 0) ||
			(hsCurrent->roomNumber() != doorHotspot->roomNumber()) ||
			(hsCurrent->hotspotId() < PLAYER_ID) ||
			((hsCurrent->hotspotId() >= 0x408) && (hsCurrent->hotspotId() < 0x2710))) 
			continue;

		// Also skip entry if special Id
		if ((hsCurrent->hotspotId() == 0xfffe) || (hsCurrent->hotspotId() == 0xffff))
			continue;

		// Check the dimensions of the animation
		if ((hsCurrent->x() < bounds.right) &&
			((hsCurrent->x() + hsCurrent->widthCopy()) > bounds.left) &&
			((hsCurrent->y() + hsCurrent->heightCopy() + hsCurrent->charRectY()) >= bounds.top) &&
			((hsCurrent->y() + hsCurrent->heightCopy() - hsCurrent->charRectY()
			- hsCurrent->yCorrection()) > bounds.bottom)) {
			// Return false - the door can't be closed
			return false;
		}
	}

	// No blocking characters, so return true that the door can be closed
	return true;
}

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

typedef void (Hotspot::*ActionProcPtr)(HotspotData *hotspot);

void Hotspot::doAction() {
	CurrentActionEntry &entry = _currentActions.top();
	HotspotData *hotspot = NULL;

	if (!entry.hasSupportData() || (entry.supportData().action() == NONE)) {
		doAction(NONE, NULL);
	} else {
		if (entry.supportData().numParams() > 0)
			hotspot = Resources::getReference().getHotspot(entry.supportData().param(0));
		doAction(entry.supportData().action(), hotspot);
	}
}

void Hotspot::doAction(Action action, HotspotData *hotspot) {
	ActionProcPtr actionProcList[NPC_JUMP_ADDRESS + 1] = {
		&Hotspot::doNothing, 
		&Hotspot::doGet, 
		NULL, 
		&Hotspot::doOperate, 
		&Hotspot::doOperate, 
		&Hotspot::doOperate, 
		&Hotspot::doOpen, 
		&Hotspot::doClose,
		&Hotspot::doLockUnlock, 
		&Hotspot::doLockUnlock, 
		&Hotspot::doUse, 
		&Hotspot::doGive, 
		&Hotspot::doTalkTo, 
		&Hotspot::doTell, 
		NULL,
		&Hotspot::doLook, 
		&Hotspot::doLookAt, 
		&Hotspot::doLookThrough, 
		&Hotspot::doAsk, 
		NULL, 
		&Hotspot::doDrink,
		&Hotspot::doStatus, 
		&Hotspot::doGoto, 
		&Hotspot::doReturn, 
		&Hotspot::doBribe, 
		&Hotspot::doExamine, 
		NULL, NULL,
		&Hotspot::npcSetRoomAndBlockedOffset, 
		&Hotspot::npcUnknown1, 
		&Hotspot::npcExecScript, 
		&Hotspot::npcUnknown2, 
		&Hotspot::npcSetRandomDest,
		&Hotspot::npcWalkingCheck, 
		&Hotspot::npcSetSupportOffset,
		&Hotspot::npcSupportOffsetConditional,
		&Hotspot::npcDispatchAction, 
		&Hotspot::npcUnknown3, 
		&Hotspot::npcUnknown4, 
		&Hotspot::npcStartTalking,
		&Hotspot::npcJumpAddress};

	(this->*actionProcList[action])(hotspot);
}

void Hotspot::doNothing(HotspotData *hotspot) {
	_currentActions.pop();
	if (hotspotId() == PLAYER_ID) {
		Room::getReference().setCursorState(CS_NONE);
	}
}

void Hotspot::doGet(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	HotspotPrecheckResult result = actionPrecheck(hotspot);

	if (result == PC_INITIAL) return;
	else if (result != PC_EXECUTE) {
		endAction();
		return;
	}

	faceHotspot(hotspot);
	endAction();

	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, GET);
	if (sequenceOffset >= 0x8000) {
		Dialog::showMessage(sequenceOffset, hotspotId());
		return;
	} 
	
	if (sequenceOffset != 0) {
		uint16 execResult = Script::execute(sequenceOffset);

		if (execResult == 1) return;
		else if (execResult != 0) {
			Dialog::showMessage(execResult, hotspotId());
			return;
		}
	}

	// Move hotspot into characters's inventory
	hotspot->roomNumber = hotspotId();  

	if (hotspot->hotspotId < START_NONVISUAL_HOTSPOT_ID) {
		// Deactive hotspot animation
		Resources::getReference().deactivateHotspot(hotspot->hotspotId);
		// Remove any 'on the ground' description for the hotspot
		hotspot->descId2 = 0;
	}
}

void Hotspot::doOperate(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	Action action = _currentActions.top().supportData().action();

	HotspotPrecheckResult result = actionPrecheck(hotspot);
	if (result == PC_INITIAL) return;
	else if (result != PC_EXECUTE) {
		endAction();
		return;
	}

	_actionCtr = 0;
	faceHotspot(hotspot);
	endAction();

	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, action);
	if (sequenceOffset >= 0x8000) {
		Dialog::showMessage(sequenceOffset, hotspotId());
	} else {
		sequenceOffset = Script::execute(sequenceOffset);
		if (sequenceOffset > 1)
			Dialog::showMessage(sequenceOffset, hotspotId());
	}
}

void Hotspot::doOpen(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	RoomExitJoinData *joinRec;

	if (isRoomExit(hotspot->hotspotId)) {
		joinRec = res.getExitJoin(hotspot->hotspotId);
		if (!joinRec->blocked) {
			// Room exit is already open
			Dialog::showMessage(4, hotspotId());
			endAction();
			return;
		}
	}

	HotspotPrecheckResult result = actionPrecheck(hotspot);
	if (result == PC_INITIAL) return;
	else if (result != PC_EXECUTE) {
		endAction();
		return;
	}

	faceHotspot(hotspot);
	_actionCtr = 0;
	endAction();

	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, OPEN);
	if (sequenceOffset >= 0x8000) {
		// Message to display
		Dialog::showMessage(sequenceOffset, hotspotId());
		return;
	}

	if (sequenceOffset != 0) {
		sequenceOffset = Script::execute(sequenceOffset);

		if (sequenceOffset == 1) return;
		if (sequenceOffset != 0) {
			if (_exitCtr != 0) 
				_exitCtr = 4;
			Dialog::showMessage(sequenceOffset, hotspotId());
			return;
		}
	}

	joinRec = res.getExitJoin(hotspot->hotspotId);
	if (joinRec->blocked) {
		joinRec->blocked = 0;

		if (hotspotId() != PLAYER_ID) {
			// TODO: HS[44h]=3, HS[42h]W = 4
		}
	}
}

void Hotspot::doClose(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	RoomExitJoinData *joinRec;

	if (isRoomExit(hotspot->hotspotId)) {
		joinRec = res.getExitJoin(hotspot->hotspotId);
		if (joinRec->blocked) {
			// Room exit is already closed/blocked
			Dialog::showMessage(3, hotspotId());
			endAction();
			return;
		}
	}

	HotspotPrecheckResult result = actionPrecheck(hotspot);
	if (result == PC_INITIAL) return;
	else if (result != PC_EXECUTE) {
		endAction();
		return;
	}

	faceHotspot(hotspot);
	_actionCtr = 0;
	endAction();

	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, CLOSE);
	if (sequenceOffset >= 0x8000) {
		// Message to display
		Dialog::showMessage(sequenceOffset, hotspotId());
	} else if (sequenceOffset != 0) {
		// Otherwise handle script
		sequenceOffset = Script::execute(sequenceOffset);

		if (sequenceOffset != 0) {
			Dialog::showMessage(sequenceOffset, hotspotId());
			return;
		}
	}

	joinRec = res.getExitJoin(hotspot->hotspotId);
	if (!joinRec->blocked) {
		// Close the door
		if (!doorCloseCheck(joinRec->hotspot1Id) ||
			!doorCloseCheck(joinRec->hotspot2Id)) {
			// A character is preventing the door from closing
			Dialog::showMessage(2, hotspotId());
		} else {
			// Flag the door as closed
			joinRec->blocked = 1;
		}
	}
}

void Hotspot::doUse(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	uint16 usedId = _currentActions.top().supportData().param(1);
	HotspotData *usedHotspot = res.getHotspot(usedId);
	ValueTableData &fields = res.fieldList();
	fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
	fields.setField(USE_HOTSPOT_ID, usedHotspot->hotspotId);

	if (usedHotspot->roomNumber != hotspotId()) {
		// Item to be used is not in character's inventory - say "What???"
		endAction();
		Dialog::showMessage(0xF, hotspotId());
		return;
	}

	HotspotPrecheckResult result = actionPrecheck(hotspot);
	if (result == PC_INITIAL) return;
	else if (result != PC_EXECUTE) {
		endAction();
		return;
	}

	faceHotspot(hotspot);
	endAction();

	// TODO: If character=3E9h, HS[-1]=28h, HS[1Fh]=50h

	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, USE);

	if (sequenceOffset >= 0x8000) {
		Dialog::showMessage(sequenceOffset, hotspotId());
	} else if (sequenceOffset == 0) {
		Dialog::showMessage(17, hotspotId());
	} else {
		sequenceOffset = Script::execute(sequenceOffset);
		if (sequenceOffset != 0) 
			Dialog::showMessage(sequenceOffset, hotspotId());
	}
}

void Hotspot::doGive(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	uint16 usedId = _currentActions.top().supportData().param(1);
	HotspotData *usedHotspot = res.getHotspot(usedId);
	ValueTableData &fields = res.fieldList();
	fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
	fields.setField(USE_HOTSPOT_ID, usedId);

	if (usedHotspot->roomNumber != hotspotId()) {
		// Item to be used is not in character's inventory - say "What???"
		endAction();
		Dialog::showMessage(0xF, hotspotId());
		return;
	}

	HotspotPrecheckResult result = actionPrecheck(hotspot);
	if (result == PC_INITIAL) return;
	else if (result != PC_EXECUTE) {
		endAction();
		return;
	}

	faceHotspot(hotspot);
	endAction();

	if ((hotspotId() != 0x412) || (usedId != 0x2710)) 
		Dialog::showMessage(7, hotspotId());
	
	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, GIVE);

	if (sequenceOffset >= 0x8000) {
		Dialog::showMessage(sequenceOffset, hotspotId());
	} else if (sequenceOffset != 0) {
		sequenceOffset = Script::execute(sequenceOffset);
		if (sequenceOffset == NOONE_ID) {
			// TODO
		} else if (sequenceOffset == 0) {
			// Move item into character's inventory
			HotspotData *usedItem = res.getHotspot(usedId);
			usedItem->roomNumber = hotspotId();
		} else if (sequenceOffset > 1) {
			Dialog::showMessage(result, hotspotId());
		}
	}
}

void Hotspot::doTalkTo(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	uint16 usedId = _currentActions.top().supportData().param(1);
	ValueTableData &fields = res.fieldList();
	fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
	fields.setField(USE_HOTSPOT_ID, usedId);

	if ((hotspot->hotspotId != SKORL_ID) && ((hotspot->roomNumber != 28) || 
		(hotspot->hotspotId != 0x3EB))) {

		HotspotPrecheckResult result = actionPrecheck(hotspot);
		if (result == PC_INITIAL) return;
		else if (result != PC_EXECUTE) {
			endAction();
			return;
		}
	}

	faceHotspot(hotspot);
	endAction();

	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, TALK_TO);

	if (sequenceOffset >= 0x8000) {
		Dialog::showMessage(sequenceOffset, hotspotId());
		return;
	}

	if (sequenceOffset != 0) {
		uint16 result = Script::execute(sequenceOffset);

		if (result != 0) {
			endAction();
			return;
		}
	}

	// Start talking with character
	startTalk(hotspot);
}

void Hotspot::doTell(HotspotData *hotspot) {
	// TODO
}

void Hotspot::doLook(HotspotData *hotspot) {
	endAction();
	Dialog::show(Room::getReference().descId());
}

uint16 hotspotLookAtList[] = {0x411, 0x412, 0x41F, 0x420, 0x421, 0x422, 0x426, 
	0x427, 0x428, 0x429, 0x436, 0x437, 0};

void Hotspot::doLookAt(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, LOOK_AT);

	if (hotspot->hotspotId >= FIRST_NONCHARACTER_ID) {
		// Check if the hotspot appears in the list of hotspots that don't
		// need to be walked to before being looked at
		uint16 *tempId = &hotspotLookAtList[0];
		while ((*tempId != 0) && (*tempId != hotspot->hotspotId)) ++tempId;
		if (!*tempId) {
			// Hotspot wasn't in the list
			HotspotPrecheckResult result = actionPrecheck(hotspot);
			if (result == PC_INITIAL) return;
			else if (result != PC_EXECUTE) {
				endAction();
				return;
			}
		}
	}

	faceHotspot(hotspot);
	_actionCtr = 0;
	endAction();

	if (sequenceOffset >= 0x8000) {
		Dialog::showMessage(sequenceOffset, hotspotId());
	} else {
		if (sequenceOffset != 0) 
			sequenceOffset = Script::execute(sequenceOffset);

		if (sequenceOffset == 0) {
			uint16 descId = (hotspot->descId2 != 0) ? hotspot->descId2 : hotspot->descId;
			Dialog::show(descId);
		}
	}
}

void Hotspot::doLookThrough(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, LOOK_THROUGH);

	if (hotspot->hotspotId >= FIRST_NONCHARACTER_ID) {
		// Check if the hotspot appears in the list of hotspots that don't
		// need to be walked to before being looked at
		uint16 *tempId = &hotspotLookAtList[0];
		while ((*tempId != 0) && (*tempId != hotspot->hotspotId)) ++tempId;
		if (!*tempId) {
			// Hotspot wasn't in the list
			HotspotPrecheckResult result = actionPrecheck(hotspot);
			if (result == PC_INITIAL) return;
			else if (result != PC_EXECUTE) {
				endAction();
				return;
			}
		}
	}

	faceHotspot(hotspot);
	_actionCtr = 0;
	endAction();

	if (sequenceOffset >= 0x8000) {
		Dialog::showMessage(sequenceOffset, hotspotId());
	} else {
		if (sequenceOffset != 0) 
			sequenceOffset = Script::execute(sequenceOffset);

		if (sequenceOffset == 0) {
			uint16 descId = (hotspot->descId2 != 0) ? hotspot->descId2 : hotspot->descId;
			Dialog::show(descId);
		}
	}
}

void Hotspot::doAsk(HotspotData *hotspot) {
	// TODO
}

void Hotspot::doDrink(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	ValueTableData &fields = res.fieldList();
	fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
	fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);

	// Make sure item is in character's inventory
	if (hotspot->hotspotId != hotspotId()) {
		Dialog::showMessage(0xF, hotspotId());
		return;
	}

	endAction();
	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, DRINK);

	if (sequenceOffset >= 0x8000) {
		Dialog::showMessage(sequenceOffset, hotspotId());
	} else if (sequenceOffset == 0) {
		Dialog::showMessage(22, hotspotId());
	} else {
		uint16 result = Script::execute(sequenceOffset);
		if (result == 0) {
			// Item has been drunk, so remove item from game
			hotspot->roomNumber = 0;
		} else if (result != 1) {
			Dialog::showMessage(result, hotspotId());
		}
	}
}

// doStatus
// Handle the status window

void Hotspot::doStatus(HotspotData *hotspot) {
	char buffer[MAX_DESC_SIZE];
	uint16 numItems = 0;
	StringData &strings = StringData::getReference();
	Resources &resources = Resources::getReference();
	Room &room = Room::getReference();
	
	room.update();
	endAction();

	strings.getString(room.roomNumber(), buffer, NULL, NULL);
	strcat(buffer, "\n\nYou are carrying ");

	// Scan through the list and add in any items assigned to the player
	HotspotDataList &list = resources.hotspotData();
	HotspotDataList::iterator i;
	for (i = list.begin(); i != list.end(); ++i) {
		HotspotData *rec = *i;

		if (rec->roomNumber == PLAYER_ID) {
			if (numItems++ == 0) strcat(buffer, ": ");
			else strcat(buffer, ", ");
			strings.getString(rec->nameId, buffer + strlen(buffer), NULL, NULL);
		}
	}

	// If there were no items, add in the word 'nothing'
	if (numItems == 0) strcat(buffer, "nothing.");

	// If the player has money, add it in
	uint16 numGroats = resources.fieldList().numGroats();
	if (numGroats > 0) {
		sprintf(buffer + strlen(buffer), "\n\nYou have %d groat", numGroats);
		if (numGroats > 1) strcat(buffer, "s");
	}

	// Display the dialog
	Screen &screen = Screen::getReference();
	Mouse &mouse = Mouse::getReference();
	mouse.cursorOff();

	Surface *s = Surface::newDialog(INFO_DIALOG_WIDTH, buffer);
	s->copyToScreen(INFO_DIALOG_X, (FULL_SCREEN_HEIGHT-s->height())/2);

	Events::getReference().waitForPress();
	screen.update();
	mouse.cursorOn();
}

// doGoto
// Sets the room for the character to go to

void Hotspot::doGoto(HotspotData *hotspot) {
	_exitCtr = 0;
	_blockedOffset = 0;
	_currentActions.top().setRoomNumber(_currentActions.top().supportData().param(0));
	endAction();
}

void Hotspot::doReturn(HotspotData *hotspot) {
	error("Not yet implemented");
}

uint16 bribe_hotspot_list[] = {0x421, 0x879, 0x3E9, 0x8C7, 0x429, 0x8D1,
	0x422, 0x8D4, 0x420, 0x8D6, 0x42B, 0x956, 0x3F2, 0xBE6, 0};

void Hotspot::doBribe(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	ValueTableData &fields = res.fieldList();
	fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
	fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);

	HotspotPrecheckResult result = actionPrecheck(hotspot);
	if (result == PC_INITIAL) return;
	else if (result != PC_EXECUTE) {
		endAction();
		return;
	}
	
	uint16 *tempId = &bribe_hotspot_list[0];
	uint16 sequenceOffset = 0x14B;     // Default sequence offset
	while (*tempId != 0) {
		if (*tempId++ == hotspotId()) {
			sequenceOffset = *tempId;
			if ((sequenceOffset & 0x8000) != 0)
				sequenceOffset = Script::execute(sequenceOffset & 0x7fff);
			break;
		}
		++tempId;                      // Move over entry's sequence offset
	}
	
	// TODO: call to talk_setup
	faceHotspot(hotspot);
	_actionCtr = 0;
	endAction();

	sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, BRIBE);
	if (sequenceOffset != 0) {
		sequenceOffset = Script::execute(sequenceOffset);
		if (sequenceOffset != 0) return;
	}

	// TODO: handle character message display
}

void Hotspot::doExamine(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	ValueTableData &fields = res.fieldList();
	fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
	fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);

	endAction();
	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, EXAMINE);

	if (sequenceOffset >= 0x8000) {
		Dialog::showMessage(sequenceOffset, hotspotId());
	} else {
		if (sequenceOffset != 0) 
			sequenceOffset = Script::execute(sequenceOffset);

		if (sequenceOffset == 0) {
			Dialog::show(hotspot->descId);
		}
	}
}

void Hotspot::doLockUnlock(HotspotData *hotspot) {
	Action action = _currentActions.top().supportData().action();
	Resources &res = Resources::getReference();
	ValueTableData &fields = res.fieldList();
	fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
	fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);

	HotspotPrecheckResult result = actionPrecheck(hotspot);
	if (result == PC_INITIAL) return;
	else if (result != PC_EXECUTE) {
		endAction();
		return;
	}

	faceHotspot(hotspot);
	endAction();
	
	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, action);

	if (sequenceOffset >= 0x8000) {
		Dialog::showMessage(sequenceOffset, hotspotId());
	} else {
		if (sequenceOffset != 0) 
			Script::execute(sequenceOffset);
	}
}

void Hotspot::npcSetRoomAndBlockedOffset(HotspotData *hotspot) {
	CharacterScheduleEntry &entry = _currentActions.top().supportData();
	_exitCtr = 0;

	_blockedOffset = entry.param(1);
	_currentActions.top().setRoomNumber(entry.param(0));
	endAction();
}

void Hotspot::npcUnknown1(HotspotData *hotspot) {
	error("Not yet implemented");
}

void Hotspot::npcExecScript(HotspotData *hotspot) {
	CharacterScheduleEntry &entry = _currentActions.top().supportData();
	uint16 offset = entry.param(0);
	endAction();
	Script::execute(offset);
}

void Hotspot::npcUnknown2(HotspotData *hotspot) {
	warning("npcUnknown2 - Not yet implemented");
	endAction();
}

void Hotspot::npcSetRandomDest(HotspotData *hotspot) {
	endAction();
	setRandomDest();
}

void Hotspot::npcWalkingCheck(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	ValueTableData &fields = res.fieldList();
	CharacterScheduleEntry &entry = _currentActions.top().supportData();
	uint16 hId = entry.param(0);

	endAction();
	fields.setField(USE_HOTSPOT_ID, hId);
	fields.setField(ACTIVE_HOTSPOT_ID, hId);

	if ((hId < PLAYER_ID) || (hotspot->roomNumber == _roomNumber)) {
		characterWalkingCheck(hotspot);
	}
}

void Hotspot::npcSetSupportOffset(HotspotData *hotspot) {
	CharacterScheduleEntry &entry = _currentActions.top().supportData();
	uint16 entryId = entry.param(0);

	CharacterScheduleEntry *newEntry = Resources::getReference().
		charSchedules().getEntry(entryId, entry.parent());
	_currentActions.top().setSupportData(newEntry);
}

void Hotspot::npcSupportOffsetConditional(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	CharacterScheduleEntry &entry = _currentActions.top().supportData();
	CharacterScheduleEntry *newEntry;
	uint16 scriptOffset = entry.param(0);
	uint16 entryId = entry.param(1);

	if (Script::execute(scriptOffset) == 0) {
		// Not succeeded, get next entry
		newEntry = entry.next();
	} else {
		// Get entry specified by parameter 1
		newEntry = res.charSchedules().getEntry(entryId, entry.parent());
	}

	_currentActions.top().setSupportData(newEntry);
	HotspotData *hotspotData = (newEntry->numParams() == 0) ? NULL : 
		res.getHotspot(newEntry->param(0));
	doAction(newEntry->action(), hotspotData);
}

void Hotspot::npcDispatchAction(HotspotData *hotspot) {
	Resources &res = Resources::getReference();
	ValueTableData &fields = res.fieldList();
	CharacterScheduleEntry &entry = _currentActions.top().supportData();

	fields.setField(USE_HOTSPOT_ID, entry.param(0));
	fields.setField(ACTIVE_HOTSPOT_ID, entry.param(0));

	HotspotPrecheckResult result = actionPrecheck(hotspot);
	if (result == PC_EXECUTE) {
		endAction();
	} else if (result != PC_INITIAL) {
		CharacterScheduleEntry *newEntry = Resources::getReference().
			charSchedules().getEntry(entry.param(0), entry.parent());
		_currentActions.top().setSupportData(newEntry);
		
		HotspotData *hotspotData = (newEntry->numParams() == 0) ? NULL : 
			res.getHotspot(newEntry->param(0));
		doAction(newEntry->action(), hotspotData);
	}
}

void Hotspot::npcUnknown3(HotspotData *hotspot) {
	warning("npcUnknown3: Not yet implemented");
	endAction();
}

void Hotspot::npcUnknown4(HotspotData *hotspot) {
	warning("npcUnknown4: Not yet implemented");
	endAction();
}

void Hotspot::npcStartTalking(HotspotData *hotspot) {
	warning("npcStartTalking: Not yet implemented");
	endAction();
}

void Hotspot::npcJumpAddress(HotspotData *hotspot) {
	warning("npcJumpAddress: Not yet implemented");
	endAction();
}

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

void Hotspot::startTalk(HotspotData *charHotspot) {
	Resources &res = Resources::getReference();
	uint16 talkIndex;

	setTickProc(TALK_TICK_PROC_ID);    // Set for providing talk listing

	// Get offset of talk set to use
	TalkHeaderData *headerEntry = res.getTalkHeader(charHotspot->hotspotId);
	uint16 talkOffset;

	// Calculate talk index to use
	if (charHotspot->nameId == STRANGER_ID)
		talkIndex = 0;
	else
		talkIndex = res.fieldList().getField(TALK_INDEX) + 1;
	talkOffset = headerEntry->getEntry(talkIndex);

	// Set the active talk data
	res.setTalkStartEntry(0);
	res.setTalkData(talkOffset);
	if (!res.getTalkData()) 
		error("Talk failed - invalid offset: Character=%xh, index=%d, offset=%xh",
			charHotspot->hotspotId, talkIndex, talkOffset);
}

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

HandlerMethodPtr HotspotTickHandlers::getHandler(uint16 procOffset) {
	switch (procOffset) {
	case 0x4F82:
		return standardCharacterAnimHandler;
	case 0x7F3A:
		return standardAnimHandler;
	case 0x7207:
		return roomExitAnimHandler;
	case PLAYER_TICK_PROC_ID:
		return playerAnimHandler;
	case 0x7EFA:
		return skorlAnimHandler;
	case 0x7F69:
		return droppingTorchAnimHandler;
	case 0x8009:
		return fireAnimHandler;
	case TALK_TICK_PROC_ID:
		return talkAnimHandler;
	case 0x8241:
		return headAnimationHandler;
	default:
		return defaultHandler;
	}
}

void HotspotTickHandlers::defaultHandler(Hotspot &h) {
	// No handling done
}

void HotspotTickHandlers::standardAnimHandler(Hotspot &h) {
	if (h.tickCtr() > 0) 
		h.setTickCtr(h.tickCtr() - 1);
	else 
		h.executeScript();
}

void HotspotTickHandlers::standardCharacterAnimHandler(Hotspot &h) {
	Resources &res = Resources::getReference();
	RoomPathsData &paths = Resources::getReference().getRoom(h.roomNumber())->paths;
	PathFinder &pathFinder = h.pathFinder();
	CurrentActionStack &actions = h.currentActions();
	uint16 impingingList[MAX_NUM_IMPINGING];
	int numImpinging;
	bool bumpedPlayer;

	// TODO: handle talk dialogs countdown if necessary

	// If a frame countdown is in progress, then decrement and exit
	if (h.frameCtr() > 0) {
		h.decrFrameCtr();
		return;
	}

	numImpinging = Support::findIntersectingCharacters(h, impingingList);
	bumpedPlayer = (numImpinging == 0) ? false :
		Support::isCharacterInList(impingingList, numImpinging, PLAYER_ID);

	// Check for character having just changed room
	if (h.skipFlag()) {
		if (numImpinging > 0) {
			// Scan to check if the character has bumped into player
			Hotspot *player = res.getActiveHotspot(PLAYER_ID);

			if (bumpedPlayer && (player->characterMode() == CHARMODE_IDLE)) {
				// Signal the player to move out of the way automatically
				player->setBlockedState(BS_INITIAL);
				player->setDestHotspot(0);

				Room::getReference().setCursorState(CS_BUMPED);
				player->setRandomDest();
			} else {
				// Signal the character to pause briefly to allow bumped
				// character time to start moving out of the way
				h.setDelayCtr(10);
				h.setCharacterMode(CHARMODE_PAUSED);
			}
			return;
		}

		h.setSkipFlag(false);
	}

	// TODO: Handling of any set Tick Script Offset, as well as certain other
	// as of yet unknown hotspot flags

	if (h.characterMode() != CHARMODE_NONE) {
		if (h.characterMode() == CHARMODE_6) {
			// TODO: Figure out what mode 6 is
			h.updateMovement();
			if (bumpedPlayer) return;

		} else {
			// All other character modes
			if (h.delayCtr() > 0) {
				// There is some countdown left to do
				bool decrementFlag = true; //TODO: = HS[50h] == 0

				if (!decrementFlag) {
					HotspotData *hotspot = res.getHotspot(0); // TODO: HS[50h]
					decrementFlag = (hotspot->roomNumber != h.roomNumber()) ? false :
						Support::charactersIntersecting(hotspot, h.resource());
				}			

				if (decrementFlag) {
					h.setDelayCtr(h.delayCtr() - 1);
					return;
				}
			}
		}

		// TODO: HS[50h]=0
		CharacterMode currentMode = h.characterMode();
		h.setCharacterMode(CHARMODE_NONE);
		h.pathFinder().clear();

		if ((currentMode == CHARMODE_4) || (currentMode == CHARMODE_7)) {
			// TODO: HS[33h]=0
			Dialog::showMessage(1, h.hotspotId());
		}
		return;
	}

	CurrentAction action = actions.action();

	switch (action) {
	case NO_ACTION:
		// TODO: HS[44h]=2, update movement
		break;

	case DISPATCH_ACTION:
		// Dispatch an action
		if (actions.top().roomNumber() == 0)
			actions.top().setRoomNumber(h.roomNumber());
		if (actions.top().roomNumber() == h.roomNumber()) {
			// NPC in correct room for action
			h.setSkipFlag(false);
			h.doAction();
		} else {
			// NPC in wrong room for action
			npcRoomChange(h);
		}
		break;

	case EXEC_HOTSPOT_SCRIPT:
		// A hotspot script is in progress for the player, so don't interrupt
		if (h.executeScript()) {
			// Script is finished
			actions.top().setAction(DISPATCH_ACTION);
		}
		break;

	case START_WALKING:
		// Start the player walking to the given destination
		
		h.setOccupied(false);  
		pathFinder.reset(paths);
		h.currentActions().top().setAction(PROCESSING_PATH);

		// Deliberate fall through to processing walking path

	case PROCESSING_PATH:
		// TODO: Call to sub_105
		if (!pathFinder.process()) break;

		// Pathfinding is now complete 
		actions.top().setAction(WALKING);

		// TODO: Lots of checks to unknown hotspot fields
		break;

	case WALKING:
		// The character is currently moving
		// If the character is walking to an exit hotspot, make sure it's still open
		if ((h.destHotspotId() != 0) && (h.destHotspotId() != 0xffff)) {
			// Player is walking to a room exit hotspot
			RoomExitJoinData *joinRec = res.getExitJoin(h.destHotspotId());
			if (joinRec->blocked) {
				// Exit now blocked, so stop walking
				actions.top().setAction(DISPATCH_ACTION);
				h.setOccupied(true);
				break;
			}
		}

		// TODO: Call to sub_41, exit if returns true

		h.setOccupied(false);
		bool result = h.walkingStep();

		if (result) 
			// Walking done
			h.currentActions().top().setAction(DISPATCH_ACTION);

		if (h.destHotspotId() != 0) {
			// Walking to an exit, check for any required room change
			Support::checkRoomChange(h);
		}

		h.setOccupied(true);
		break;
	}
}

void HotspotTickHandlers::roomExitAnimHandler(Hotspot &h) {
	RoomExitJoinData *rec = Resources::getReference().getExitJoin(h.hotspotId());
	if (!rec) return;
	byte *currentFrame, *destFrame;

	if (rec->hotspot1Id == h.hotspotId()) {
		currentFrame = &rec->h1CurrentFrame; 
		destFrame = &rec->h1DestFrame;
	} else {
		currentFrame = &rec->h2CurrentFrame;
		destFrame = &rec->h2DestFrame;
	}
	
	if ((rec->blocked != 0) && (*currentFrame != *destFrame)) {
		// Closing the door
		h.setOccupied(true);

		++*currentFrame;
		if (*currentFrame == *destFrame) {
			// TODO: play closed door sound
		}
	} else if ((rec->blocked == 0) && (*currentFrame != 0)) {
		// Opening the door
		h.setOccupied(false);

		--*currentFrame;
		if (*currentFrame == *destFrame) {
			//TODO: Check against script val 88 and play sound
		}
	}

	h.setFrameNumber(*currentFrame);
}

void HotspotTickHandlers::playerAnimHandler(Hotspot &h) {
	Resources &res = Resources::getReference();
	Mouse &mouse = Mouse::getReference();
	RoomPathsData &paths = Resources::getReference().getRoom(h.roomNumber())->paths;
	PathFinder &pathFinder = h.pathFinder();
	CurrentActionStack &actions = h.currentActions();
	uint16 impingingList[MAX_NUM_IMPINGING];
	int numImpinging;
	Action hsAction;
	uint16 hotspotId;
	HotspotData *hotspot;

	// TODO: handle talk dialogs countdown if necessary
	
	numImpinging = Support::findIntersectingCharacters(h, impingingList);
	if (h.skipFlag()) {
		if (numImpinging > 0) 
			return;
		h.setSkipFlag(false);
	}

	// If a frame countdown is in progress, then decrement and exit
	if (h.frameCtr() > 0) {
		h.decrFrameCtr();
		return;
	}

	CurrentAction action = actions.action();

	switch (action) {
	case NO_ACTION:
		// Make sure there is no longer any destination
		h.setDestHotspot(0);
		h.updateMovement2(CHARMODE_IDLE);
		break;

	case DISPATCH_ACTION:
		// Dispatch an action
		h.setDestHotspot(0);

		if (actions.top().hasSupportData()) {
			hsAction = actions.top().supportData().action();
			hotspotId = actions.top().supportData().param(0);
			hotspot = res.getHotspot(hotspotId);
		} else {
			hsAction = NONE;
			hotspot = NULL;
		}

		h.doAction(hsAction, hotspot);
		break;

	case EXEC_HOTSPOT_SCRIPT:
		// A hotspot script is in progress for the player, so don't interrupt
		if (h.executeScript()) {
			// Script is finished, so pop of the execution action
			actions.pop();
		}
		break;

	case START_WALKING:
		// Start the player walking to the given destination
		h.setOccupied(false);  

		// Reset the path finder / walking sequence
		pathFinder.reset(paths);

		// Set current action to processing walking path
		actions.pop();
		h.currentActions().addFront(PROCESSING_PATH, h.roomNumber());
		// Deliberate fall through to processing walking path

	case PROCESSING_PATH:
		h.setCharacterMode(CHARMODE_NONE);
		if (!pathFinder.process()) break;

		// Pathfinding is now complete 
/*
		if ((pathFinder.result() != PF_OK) && (h.unknownFlag() ||
			(pathFinder.result() != PF_DEST_OCCUPIED))) {
			// TODO: occupiedFlag
			
		}
*/
		actions.pop();

		if (pathFinder.isEmpty()) {
		mouse.setCursorNum(CURSOR_ARROW);
			break;
		}

		if (mouse.getCursorNum() != CURSOR_CAMERA)
			mouse.setCursorNum(CURSOR_ARROW);
		h.currentActions().addFront(WALKING, h.roomNumber());
		h.setPosition(h.x(), h.y() & 0xFFF8);
		
		// Deliberate fall through to walking

	case WALKING:
		// The character is currently moving
		if ((h.destHotspotId() != 0) && (h.destHotspotId() != 0xffff)) {
			// Player is walking to a room exit hotspot
			RoomExitJoinData *joinRec = res.getExitJoin(h.destHotspotId());
			if (joinRec->blocked) {
				// Exit now blocked, so stop walking
				actions.pop();
				break;
			}
		}

		if (h.walkingStep()) {
			// Walking done
			Room &room = Room::getReference();
			if (room.cursorState() == CS_BUMPED)
				room.setCursorState(CS_NONE);
			h.currentActions().pop();
		}
	
		// Check for whether need to change room
		Support::checkRoomChange(h);
		break;
	}
}

void HotspotTickHandlers::skorlAnimHandler(Hotspot &h) {
	Resources &res = Resources::getReference();
	ValueTableData &fields = res.fieldList();
	HotspotData *player = res.getHotspot(PLAYER_ID);

	if ((fields.getField(11) != 0) && !h.skipFlag() && 
		(h.roomNumber() == player->roomNumber)) {
		// TODO: Need extra check on data_669

		if (Support::charactersIntersecting(h.resource(), player)) {
			// Skorl has caught the player
			Game::getReference().setState(GS_RESTORE_RESTART | GS_CAUGHT);
		}
	}

	standardCharacterAnimHandler(h);
}

void HotspotTickHandlers::droppingTorchAnimHandler(Hotspot &h) {
	if (h.tickCtr() > 0) 
		h.setTickCtr(h.tickCtr() - 1);
	else {
		bool result = h.executeScript();
		if (result) {
			// Changeover to the fire on the straw
			Resources &res = Resources::getReference();
			res.deactivateHotspot(h.hotspotId());
			res.activateHotspot(0x41C);

			// Enable the fire and activate its animation
			HotspotData *fire = res.getHotspot(0x418);
			fire->flags |= 0x80;
			fire->loadOffset = 0x7172; 
			res.activateHotspot(0x418);
		}
 	}
}

void HotspotTickHandlers::fireAnimHandler(Hotspot &h) {
	standardAnimHandler(h);
	h.setOccupied(true);
}

// Special variables used across multiple calls to talkAnimHandler
static TalkEntryData *_talkResponse;

void HotspotTickHandlers::talkAnimHandler(Hotspot &h) {
	// Talk handler
	Resources &res = Resources::getReference();
	Room &room = Room::getReference();
	ValueTableData &fields = res.fieldList();
	StringData &strings = StringData::getReference();
	Screen &screen = Screen::getReference();
	Mouse &mouse = Mouse::getReference();
	TalkSelections &talkSelections = res.getTalkSelections();
	TalkData *data = res.getTalkData();
	TalkEntryList &entries = data->entries; 
	char buffer[MAX_DESC_SIZE];
	Rect r;
	int lineNum, numLines;
	int selectedLine, responseNumber;
	bool showSelections, keepTalkingFlag;
	TalkEntryList::iterator i;
	TalkEntryData *entry;
	uint16 result, descId, charId;

	switch (res.getTalkState()) {
	case TALK_NONE:
		// Handle initial setup of talking options
		// Reset talk entry pointer list
		for (lineNum = 0; lineNum < MAX_TALK_SELECTIONS; ++lineNum)
			talkSelections[lineNum] = NULL;
		
		// Loop through list to find entries to display
		_talkResponse = NULL;
		numLines = 0;
		showSelections = false;

		i = entries.begin();
		for (lineNum = 0; lineNum < res.getTalkStartEntry(); ++lineNum)
			if (i != entries.end()) ++i;

		for (; i != entries.end(); ++i) {
			entry = *i;
			uint8 flags = (uint8) (entry->descId >> 14);
			if (flags == 3) 
				// Skip the entry
				continue;

			uint16 sequenceOffset = entry->preSequenceId & 0x3fff;
			bool showLine = sequenceOffset == 0;
			if (!showLine) 
				showLine = Script::execute(sequenceOffset) != 0;

			if (showLine) {
				talkSelections[numLines++] = entry;
				showSelections |= (entry->descId & 0x3fff) != TALK_MAGIC_ID;
			}

			if ((entry->preSequenceId & 0x8000) != 0) break;
		}

		if (showSelections && (numLines > 1))
			res.setTalkState(TALK_SELECT);
		else {
			res.setTalkState(TALK_RESPOND);
			res.setTalkSelection(1);
		}
		break;

	case TALK_SELECT:
		r.left = 0; r.right = FULL_SCREEN_WIDTH - 1;
		selectedLine = mouse.y() / MENUBAR_Y_SIZE;
		if ((selectedLine > MAX_TALK_SELECTIONS) || ((selectedLine != 0) && 
			!talkSelections[selectedLine-1]))
			selectedLine = 0;

		for (lineNum = 0; lineNum < MAX_TALK_SELECTIONS; ++lineNum) {
			if (!talkSelections[lineNum]) break;
			entry = talkSelections[lineNum];

			strings.getString(entry->descId & 0x3fff, buffer, NULL, NULL);

			// Clear line
			r.top = (lineNum + 1) * MENUBAR_Y_SIZE;
			r.bottom = r.top + MENUBAR_Y_SIZE - 1;
			screen.screen().fillRect(r, 0);

			// Display line
			byte colour = (lineNum+1 == selectedLine) ?
				DIALOG_WHITE_COLOUR : DIALOG_TEXT_COLOUR;
			screen.screen().writeString(r.left, r.top, buffer, false, colour);
		}

		if ((!mouse.lButton() && !mouse.rButton()) || (selectedLine == 0))
			break;

		// Set the talk response index to use
		res.setTalkSelection(selectedLine);
		res.setTalkState(TALK_RESPOND);
		break;

	case TALK_RESPOND:
		// Handle initial response to show the question in a talk dialog if needed
		selectedLine = res.getTalkSelection();
		entry = talkSelections[selectedLine-1];
		descId = entry->descId & 0x3fff;
		entry->descId |= 0x4000;

		if (descId != TALK_MAGIC_ID) {
			// Set up to display the question in a talk dialog
			room.setTalkDialog(PLAYER_ID, descId);
			res.setTalkState(TALK_RESPONSE_WAIT);
		} else {
			res.setTalkState(TALK_RESPOND_2);
		}
		break;

	case TALK_RESPOND_2:
		if (!_talkResponse) {
			// Handles bringing up the response talk dialog
			charId = fields.getField(ACTIVE_HOTSPOT_ID);
			selectedLine = res.getTalkSelection();
			entry = talkSelections[selectedLine-1];

			responseNumber = entry->postSequenceId;
			if ((responseNumber & 0x8000) != 0) 
				responseNumber = Script::execute(responseNumber & 0x7fff);

			do {
				_talkResponse = res.getTalkData()->getResponse(responseNumber);
				if (!_talkResponse->preSequenceId) break;
				responseNumber = Script::execute(_talkResponse->preSequenceId);
			} while (responseNumber != TALK_RESPONSE_MAGIC_ID);

			descId = _talkResponse->descId;
			if ((descId & 0x8000) != 0)
				descId = Script::execute(descId & 0x7fff);

			if (descId != TALK_MAGIC_ID) {
				room.setTalkDialog(charId, descId);
				res.setTalkState(TALK_RESPONSE_WAIT);
				return;
			} 
		}

		// Handle checking whether to keep talking
		result = _talkResponse->postSequenceId;
		if (result == 0xffff)
			keepTalkingFlag = false;
		else {
			if ((result & 0x8000) == 0) 
				keepTalkingFlag = true;
			else {
				result = Script::execute(result & 0x7fff);
				keepTalkingFlag = result != 0xffff;
			}
		}
			
		if (keepTalkingFlag) {
			// Reset for loading the next set of talking options
			res.setTalkStartEntry(result);
			res.setTalkState(TALK_NONE);
		} else {
			// End the conversation
			res.getActiveHotspot(PLAYER_ID)->setTickProc(PLAYER_TICK_PROC_ID);
			res.setTalkData(0);
			res.setCurrentAction(NONE);
			res.setTalkState(TALK_NONE);
		}
		break;

	case TALK_RESPONSE_WAIT:
		// Keeps waiting while a talk dialog is active
		break;
	}
}

void HotspotTickHandlers::headAnimationHandler(Hotspot &h) {
	Resources &res = Resources::getReference();
	Hotspot *character = res.getActiveHotspot(PLAYER_ID);
	uint16 frameNumber = 0;

	if (character->y() < 79) {
		//character = res.getActiveHotspot(RATPOUCH_ID);
		frameNumber = 1;
	} else {
		if (character->x() < 72) frameNumber = 0;
		else if (character->x() < 172) frameNumber = 1;
		else frameNumber = 2;
	}

	h.setFrameNumber(frameNumber);
}

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

// support method for the standard character tick proc routine - it gets called
// when the character is in the wrong room designated for an action, and is
// responsible for starting the character walking to the correct exit

void HotspotTickHandlers::npcRoomChange(Hotspot &h) {
	Resources &res = Resources::getReference();

	// Increment number of times an exit has been attempted
	h.setExitCtr(h.exitCtr() + 1);
	if (h.exitCtr() >= 5) {
		// Failed to exit room too many times
		h.setExitCtr(0);
		if (h.currentActions().size() > 1) {
			// Pending items on stack
			// TODO: Check on HS[4Eh]
			if (h.currentActions().top().supportData().id() != RETURN_SUPPORT_ID) {
				h.currentActions().top().supportData().setDetails(RETURN, 0);
			}

			h.currentActions().top().setRoomNumber(h.roomNumber());

		} else if (h.blockedOffset() != 0) {
			// Only current action on stack - and there is a block handler 
			CharacterScheduleEntry *entry = res.charSchedules().getEntry(h.blockedOffset());
			h.currentActions().top().setSupportData(entry);
			h.currentActions().top().setRoomNumber(h.roomNumber());
		}

		return;
	}

	// Get room exit coordinates
	RoomExitCoordinateData &exitData = res.coordinateList().getEntry(
		h.roomNumber()).getData(h.currentActions().top().roomNumber());
	
	if (h.hotspotId() != RATPOUCH_ID) {
		// Count up the number of characters in the room
		HotspotList &list = res.activeHotspots();
		HotspotList::iterator i;
		int numCharacters = 0;

		for (i = list.begin(); i != list.end(); ++i) {
			if ((h.roomNumber() == (exitData.roomNumber & 0xff)) && (h.layer() != 0) &&
				(h.hotspotId() >= PLAYER_ID) && (h.hotspotId() < FIRST_NONCHARACTER_ID)) 
				++numCharacters;
		}

		if (numCharacters >= 4) {
warning("XYZZY npcChangeRoom - too many characters - yet to be tested");
			uint16 dataId = res.getCharOffset(0);
			CharacterScheduleEntry *entry = res.charSchedules().getEntry(dataId);
			h.currentActions().addFront(DISPATCH_ACTION, entry, h.roomNumber());
			
			return;
		}
	}

	h.setDestPosition(exitData.x, exitData.y);
	h.setDestHotspot(res.exitHotspots().getHotspot(h.roomNumber(), exitData.hotspotIndexId));

	if (h.destHotspotId() != 0xffff) {
		RoomExitJoinData *joinRec = res.getExitJoin(h.destHotspotId());

		if (joinRec->blocked) {
			// The room exit is blocked - so add an opening action
			CharacterScheduleEntry &entry = h.npcSupportData();
			entry.setDetails(OPEN, h.destHotspotId());
			h.currentActions().addFront(DISPATCH_ACTION, &entry, h.roomNumber());
			return;
		}
	}

	// No exit hotspot, or it has one that's not blocked. So start the walking
	h.currentActions().top().setAction(START_WALKING);
}

/*-------------------------------------------------------------------------*/
/* Miscellaneous classes                                                   */
/*                                                                         */
/*-------------------------------------------------------------------------*/

// WalkingActionEntry class

// This method performs rounding of the number of steps depending on direciton

int WalkingActionEntry::numSteps() {
	switch (_direction) {
	case UP:
	case DOWN:
		return (_numSteps + 1) >> 1;

	case LEFT:
	case RIGHT:
		return (_numSteps + 3) >> 2;
	default:
		return 0;
	}
}

// PathFinder class

PathFinder::PathFinder(Hotspot *h) { 
	_hotspot = h;
	_list.clear(); 
	_stepCtr = 0; 
}

void PathFinder::clear() {
	_stepCtr = 0;
	_list.clear();
	_inProgress = false;
	_countdownCtr = PATHFIND_COUNTDOWN;
}

void PathFinder::reset(RoomPathsData &src) {
	clear();
	src.decompress(_layer, _hotspot->widthCopy());
}

// Does the next stage of processing to figure out a path to take to a given
// destination. Returns true if the path finding has been completed

bool PathFinder::process() {
	bool returnFlag = _inProgress;
	// Check whether the pathfinding can be broken by the countdown counter
	bool breakFlag = (PATHFIND_COUNTDOWN != 0);
	_countdownCtr = PATHFIND_COUNTDOWN;
	int v;
	uint16 *pTemp;
	bool scanFlag = false;
	Direction currDirection = NO_DIRECTION;
	Direction newDirection;
	uint16 numSteps = 0, savedSteps = 0;
	bool altFlag;
	uint16 *pCurrent;

	if (!_inProgress) {
		// Following code only done during first call to method
		_inProgress = true;
		initVars();

		_xCurrent >>= 3; _yCurrent >>= 3;
		_xDestCurrent >>= 3; _yDestCurrent >>= 3;
		if ((_xCurrent == _xDestCurrent) && (_yCurrent == _yDestCurrent)) {
			// Very close move
			if (_xDestPos > 0) 
				add(RIGHT, _xDestPos);
			else if (_xDestPos < 0) 
				add(LEFT, -_xDestPos);

			goto final_step;
		}

		// Path finding

		_destX >>= 3;
		_destY >>= 3;
		_pSrc = &_layer[(_yCurrent + 1) * DECODED_PATHS_WIDTH + 1 + _xCurrent];
		_pDest = &_layer[(_yDestCurrent + 1) * DECODED_PATHS_WIDTH + 1 + _xDestCurrent];

		// Flag starting/ending cells
		*_pSrc = 1;
		_destOccupied = *_pDest != 0;
		_result = _destOccupied ? PF_DEST_OCCUPIED : PF_OK;
		*_pDest = 0;

		// Set up the current pointer, adjusting away from edges if necessary 

		if (_xCurrent >= _xDestCurrent) {
			_xChangeInc = -1;
			_xChangeStart = ROOM_PATHS_WIDTH;
		} else {
			_xChangeInc = 1;
			_xChangeStart = 1;
		}

		if (_yCurrent >= _yDestCurrent) {
			_yChangeInc = -1;
			_yChangeStart = ROOM_PATHS_HEIGHT;
		} else {
			_yChangeInc = 1;
			_yChangeStart = 1;
		}
	}

	// Major loop to populate data
	_cellPopulated = false;

	while (1) {
		// Loop through to process cells in the given area
		if (!returnFlag) _yCtr = 0;
		while (returnFlag || (_yCtr < ROOM_PATHS_HEIGHT)) {
			if (!returnFlag) _xCtr = 0;

			while (returnFlag || (_xCtr < ROOM_PATHS_WIDTH)) {
				if (!returnFlag) {
					processCell(&_layer[(_yChangeStart + _yCtr * _yChangeInc) * DECODED_PATHS_WIDTH +
						(_xChangeStart + _xCtr * _xChangeInc)]);
					if (breakFlag && (_countdownCtr <= 0)) return false;
				} else {
					returnFlag = false;
				}
				++_xCtr;
			}
			++_yCtr;
		}

		// If the destination cell has been filled in, then break out of loop
		if (*_pDest != 0) break;

		if (_cellPopulated) {
			// At least one cell populated, so go repeat loop
			_cellPopulated = false;
		} else {
			_result = PF_NO_PATH;
			scanFlag = true;
			break;
		}
	} 

	if (scanFlag || _destOccupied) {
		// Adjust the end point if necessary to stop character walking into occupied area

		// Restore destination's occupied state if necessary
		if (_destOccupied) {
			*_pDest = 0xffff;
			_destOccupied = false;
		}

		// Scan through lines
		v = 0xff;
		pTemp = _pDest;
		scanLine(_destX, -1, pTemp, v);
		scanLine(ROOM_PATHS_WIDTH - _destX, 1, pTemp, v);
		scanLine(_destY, -DECODED_PATHS_WIDTH, pTemp, v);
		scanLine(ROOM_PATHS_HEIGHT - _destY, DECODED_PATHS_WIDTH, pTemp, v); 

		if (pTemp == _pDest) {
			_result = PF_NO_WALK;
			clear();
			return true;
		}

		_pDest = pTemp;
	}

	// ****DEBUG****
	int ctr;
	for (ctr = 0; ctr < DECODED_PATHS_WIDTH * DECODED_PATHS_HEIGHT; ++ctr)
		Room::getReference().tempLayer[ctr] = _layer[ctr];

	// Determine the walk path by working backwards from the destination, adding in the 
	// walking steps in reverse order until source is reached
	int stageCtr;
	for (stageCtr = 0; stageCtr < 3; ++stageCtr) {
		altFlag = stageCtr == 1;
		pCurrent = _pDest;

		numSteps = 0;
		currDirection = NO_DIRECTION;
		while (1) {
			v = *pCurrent - 1;
			if (v == 0) break;
			
			newDirection = NO_DIRECTION;
			if (!altFlag && (currDirection != LEFT) && (currDirection != RIGHT)) {
				// Standard order direction checking
				if (*(pCurrent - DECODED_PATHS_WIDTH) == v) newDirection = DOWN;
				else if (*(pCurrent + DECODED_PATHS_WIDTH) == v) newDirection = UP;
				else if (*(pCurrent + 1) == v) newDirection = LEFT;
				else if (*(pCurrent - 1) == v) newDirection = RIGHT;
			} else {
				// Alternate order direction checking
				if (*(pCurrent + 1) == v) newDirection = LEFT;
				else if (*(pCurrent - 1) == v) newDirection = RIGHT;
				else if (*(pCurrent - DECODED_PATHS_WIDTH) == v) newDirection = DOWN;
				else if (*(pCurrent + DECODED_PATHS_WIDTH) == v) newDirection = UP;
			}
			if (newDirection == NO_DIRECTION) 
				error("Path finding process failed");

			// Process for the specified direction
			if (newDirection != currDirection) add(newDirection, 0);

			switch (newDirection) {
			case UP:
				pCurrent += DECODED_PATHS_WIDTH;
				break;

			case DOWN:
				pCurrent -= DECODED_PATHS_WIDTH;
				break;

			case LEFT:
				++pCurrent;
				break;

			case RIGHT:
				--pCurrent;
				break;

			default:
				break;
			}

			++numSteps;
			top().rawSteps() += 8;
			currDirection = newDirection;
		}

		if (stageCtr == 0) 
			// Save the number of steps needed
			savedSteps = numSteps;
		if ((stageCtr == 1) && (numSteps <= savedSteps))
			// Less steps were needed, so break out
			break;

		// Clear out any previously determined directions
		clear();
	}

	// Add a final move if necessary

	if (_result == PF_OK) {
		if (_xDestPos < 0) 
			addBack(LEFT, -_xDestPos);
		else if (_xDestPos > 0) 
			addBack(RIGHT, _xDestPos);
	}
	
final_step:
	if (_xPos < 0) add(LEFT, -_xPos);
	else if (_xPos > 0) add(RIGHT, _xPos);

	return true;
}

void PathFinder::list() {
	printf("Pathfinder::list\n");
	ManagedList<WalkingActionEntry *>::iterator i;
	for (i = _list.begin(); i != _list.end(); ++i) {
		WalkingActionEntry *e = *i;
		printf("Direction=%d, numSteps=%d\n", e->direction(), e->numSteps());
	}
}

void PathFinder::processCell(uint16 *p) {
	// Only process cells that are still empty
	if (*p == 0) {
		uint16 vMax = 0xffff;
		uint16 vTemp;

		// Check the surrounding cells (up,down,left,right) for values
		// Up
		vTemp = *(p - DECODED_PATHS_WIDTH);
		if ((vTemp != 0) && (vTemp < vMax)) vMax = vTemp;
		// Down
		vTemp = *(p + DECODED_PATHS_WIDTH);
		if ((vTemp != 0) && (vTemp < vMax)) vMax = vTemp;
		// Left
		vTemp = *(p - 1);
		if ((vTemp != 0) && (vTemp < vMax)) vMax = vTemp;
		// Right
		vTemp = *(p + 1);
		if ((vTemp != 0) && (vTemp < vMax)) vMax = vTemp;

		if (vMax != 0xffff) {
			// A surrounding cell with a value was found
			++vMax;
			*p = vMax;
			_cellPopulated = true;
		}

		_countdownCtr -= 3;

	} else {
		--_countdownCtr;
	}
}

void PathFinder::scanLine(int numScans, int changeAmount, uint16 *&pEnd, int &v) {
	uint16 *pTemp = _pDest;

	for (int ctr = 1; ctr <= numScans; ++ctr) {
		pTemp += changeAmount;
		if ((*pTemp != 0) && (*pTemp != 0xffff)) {
			if ((v < ctr) || ((v == ctr) && (*pTemp >= *pEnd))) return;
			pEnd = pTemp;
			v = ctr;
			break;
		}
	}
}

void PathFinder::initVars() {
	int16 xRight;

	// Set up dest position, adjusting for walking off screen if necessary
	_destX = _hotspot->destX();
	_destY = _hotspot->destY();

	if (_destX < 10) _destX -= 50;
	if (_destX >= FULL_SCREEN_WIDTH-10) _destX += 50;

	_xPos = 0; _yPos = 0;
	_xDestPos = 0; _yDestPos = 0;

	_xCurrent = _hotspot->x();
	if (_xCurrent < 0) {
		_xPos = _xCurrent;
		_xCurrent = 0;
	}
	xRight = FULL_SCREEN_WIDTH - _hotspot->widthCopy() - 1;
	if (_xCurrent >= xRight) {
		_xPos = _xCurrent - xRight;
		_xCurrent = xRight;
	}

	_yCurrent = (_hotspot->y() & 0xf8) + _hotspot->heightCopy() - MENUBAR_Y_SIZE - 4;
	if (_yCurrent < 0) {
		_yPos = _yCurrent;
		_yCurrent = 0;
	}
	if (_yCurrent >= (FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE)) {
		_yPos = _yCurrent - (FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE);
		_yCurrent = FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE;
	}

	_xDestCurrent = _destX;
	if (_xDestCurrent < 0) {
		_xDestPos = _xDestCurrent;
		_xDestCurrent = 0;
	}
	xRight = FULL_SCREEN_WIDTH - _hotspot->widthCopy();
	if (_xDestCurrent >= xRight) {
		_xDestPos = _xDestCurrent - xRight;
		_xDestCurrent = xRight;
	}

	_yDestCurrent = _destY - 8;
	if (_yDestCurrent < 0)
		_yDestCurrent = 0;
	if (_yDestCurrent >= (FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE))
		_yDestCurrent = FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE - 1;

	// Subtract an amount from the countdown counter to compensate for
	// the time spent decompressing the walkable areas set for the room
	_countdownCtr -= 700;
}

// Current action stack class

void CurrentActionStack::list() {
	ManagedList<CurrentActionEntry *>::iterator i;

	printf("CurrentActionStack::list num_actions=%d\n", size());

	for (i = _actions.begin(); i != _actions.end(); ++i) {
		CurrentActionEntry *entry = *i;
		printf("style=%d room#=%d", entry->action(), entry->roomNumber());
		if (entry->hasSupportData()) {
			CharacterScheduleEntry &rec = entry->supportData();

			printf(", action=%d params=", rec.action());
			if (rec.numParams() == 0) 
				printf("none");
			else {
				for (int ctr = 0; ctr < rec.numParams(); ++ctr) {
					if (ctr != 0) printf(", ");
					printf("%d", rec.param(ctr));
				}
			}
		}
		printf("\n");
	}
}

/*-------------------------------------------------------------------------*/
/* Support methods                                                         */
/*                                                                         */
/*-------------------------------------------------------------------------*/

// finds a list of character animations whose base area are impinging 
// that of the specified character (ie. are bumping into them)

int Support::findIntersectingCharacters(Hotspot &h, uint16 *charList) {
	int numImpinging = 0;
	Resources &res = Resources::getReference();
	Rect r;
	uint16 hotspotY;

	r.left = h.x();
	r.right = h.x() + h.widthCopy();
	r.top = h.y() + h.heightCopy() - h.yCorrection() - h.charRectY();
	r.bottom = h.y() + h.heightCopy() + h.charRectY();

	HotspotList::iterator i;
	for (i = res.activeHotspots().begin(); i != res.activeHotspots().end(); ++i) {
		Hotspot &hotspot = **i;
		
		// Check for basic reasons to skip checking the animation
		if ((h.hotspotId() == hotspot.hotspotId()) || (hotspot.layer() == 0) ||
			(h.roomNumber() != hotspot.roomNumber()) || 
			(hotspot.hotspotId() >= FIRST_NONCHARACTER_ID) ||
			hotspot.skipFlag()) continue;
		// TODO: See why si+ANIM_HOTSPOT_OFFSET compared aganst di+ANIM_VOICE_CTR

		hotspotY = hotspot.y() + hotspot.heightCopy();

		if ((hotspot.x() > r.right) || (hotspot.x() + hotspot.widthCopy() <= r.left) ||
			(hotspotY + hotspot.charRectY() < r.top) ||
			(hotspotY - hotspot.charRectY() - hotspot.yCorrection() >= r.bottom))
			continue;

		// Add hotspot Id to list
		if (numImpinging == MAX_NUM_IMPINGING)
			error("Exceeded maximum allowable number of impinging characters");
		*charList++ = hotspot.hotspotId();
		++numImpinging;
	}

	return numImpinging;
}

// Returns true if any other characters are intersecting the specified one

bool Support::checkForIntersectingCharacter(Hotspot &h) {
	uint16 tempList[MAX_NUM_IMPINGING];
	return findIntersectingCharacters(h, tempList) != 0;
}

// Check whether a character needs to change the room they're in

void Support::checkRoomChange(Hotspot &h) {
	int16 x = h.x() + (h.widthCopy() >> 1);
	int16 y = h.y() + h.heightCopy() - (h.yCorrection() >> 1);

	RoomData *roomData = Resources::getReference().getRoom(h.roomNumber());
	RoomExitData *exitRec = roomData->exits.checkExits(x, y);

	if (exitRec) {
		if (exitRec->sequenceOffset != 0xffff) {
			Script::execute(exitRec->sequenceOffset);
		} else {
			Support::characterChangeRoom(h, exitRec->roomNumber, 
				exitRec->x, exitRec->y, exitRec->direction);
		}
	}
}

void Support::characterChangeRoom(Hotspot &h, uint16 roomNumber, 
								  int16 newX, int16 newY, Direction dir) {
	Resources &res = Resources::getReference();
	ValueTableData &fields = res.fieldList();

	if (h.hotspotId() == PLAYER_ID) {
		// Room change code for the player

		h.setDirection(dir);
		PlayerNewPosition &p = fields.playerNewPos();
		p.roomNumber = roomNumber;
		p.position.x = newX;
		p.position.y = newY - 48;

		// TODO: Call sub_136, and if !ZF reset new room number back to 0 
	} else {
		// Any other character changing room

		if (checkForIntersectingCharacter(h)) {
			// Character is blocked, so add a handler for handling it
			uint16 dataId = res.getCharOffset(0);
			CharacterScheduleEntry *entry = res.charSchedules().getEntry(dataId);
			h.currentActions().addFront(DISPATCH_ACTION, entry, h.roomNumber());
		} else {
			// Handle character room change
			h.setRoomNumber(roomNumber);
			h.setPosition((newX & 0xfff8) | 5, (newY - h.heightCopy()) & 0xfff8);
			h.setSkipFlag(true);
			h.setDirection(dir);

			h.setExitCtr(0);
			h.currentActions().top().setAction(DISPATCH_ACTION);
		}
	}
}

bool Support::charactersIntersecting(HotspotData *hotspot1, HotspotData *hotspot2) {
	return !((hotspot1->startX + hotspot1->widthCopy + 4 < hotspot2->startX) ||
		(hotspot2->startX + hotspot2->widthCopy + 4 < hotspot1->startX) ||
		(hotspot2->startY + hotspot2->heightCopy - hotspot2->yCorrection - 2 >=
			hotspot1->startY + hotspot1->heightCopy + 2) ||
		(hotspot2->startY + hotspot2->heightCopy + 2 < 
			hotspot1->startY + hotspot1->heightCopy - hotspot1->yCorrection - 2));
}

bool Support::isCharacterInList(uint16 *lst, int numEntries, uint16 charId) {
	while (numEntries-- > 0) 
		if (*lst++ == charId) return true;
	return false;
}

} // end of namespace Lure
