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

#include "sherlock/tattoo/tattoo_people.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_talk.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "sherlock/tattoo/tattoo.h"

namespace Sherlock {

namespace Tattoo {

#define FACING_PLAYER 16
#define NUM_ADJUSTED_WALKS 21
#define CHARACTERS_INDEX 256

struct AdjustWalk {
	char _vgsName[9];
	int _xAdjust;
	int _flipXAdjust;
	int _yAdjust;
} ;

static const AdjustWalk ADJUST_WALKS[NUM_ADJUSTED_WALKS] = {
	{ "TUPRIGHT", -7, -19, 6 },
	{ "TRIGHT", 8, -14, 0 },
	{ "TDOWNRG", 14, -12, 0 },
	{ "TWUPRIGH", 12, 4, 2 },
	{ "TWRIGHT", 31, -14, 0 },
	{ "TWDOWNRG", 6, -24, 0 },
	{ "HTUPRIGH", 2, -20, 0 },
	{ "HTRIGHT", 28, -20, 0 },
	{ "HTDOWNRG", 8, -2, 0 },
	{ "GTUPRIGH", 4, -12, 0 },
	{ "GTRIGHT", 12, -16, 0 },
	{ "GTDOWNRG", 10, -18, 0 },
	{ "JTUPRIGH", 8, -10, 0 },
	{ "JTRIGHT", 22, -6, 0 },
	{ "JTDOWNRG", 4, -20, 0 },
	{ "CTUPRIGH", 10, 0, 0 },
	{ "CTRIGHT", 26, -22, 0 },
	{ "CTDOWNRI", 16, 4, 0 },
	{ "ITUPRIGH", 0, 0, 0 },
	{ "ITRIGHT", 20, 0, 0 },
	{ "ITDOWNRG", 8, 0, 0 }
};

static const int WALK_SPEED_X[99] = {
	90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 90, 90, 90, 90, 90, 91, 90, 90,
	90, 90,100, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,100, 90,
	90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,103, 90, 90, 90, 90, 90, 90, 90,
	90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,
	90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90
};

static const int WALK_SPEED_Y[99] = {
	28, 28, 28, 28, 28, 28, 28, 28, 28, 32, 32, 32, 28, 28, 28, 28, 28, 26, 28, 28,
	28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
	32, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 31, 28, 28, 28, 28, 28, 28, 28,
	28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
	28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28
};

static const int WALK_SPEED_DIAG_X[99] = {
	50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
	50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
	50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
	50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 90, 50, 50, 50, 50, 50, 50, 50, 50, 50,
	50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50
};

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

SavedNPCPath::SavedNPCPath() {
	Common::fill(&_path[0], &_path[MAX_NPC_PATH], 0);
	_npcIndex = 0;
	_npcPause = 0;
	_npcFacing = 0;
	_lookHolmes = false;
}

SavedNPCPath::SavedNPCPath(byte path[MAX_NPC_PATH], int npcIndex, int npcPause, const Point32 &position,
	int npcFacing, bool lookHolmes) : _npcIndex(npcIndex), _npcPause(npcPause), _position(position),
		_npcFacing(npcFacing), _lookHolmes(lookHolmes) {
	Common::copy(&path[0], &path[MAX_NPC_PATH], &_path[0]);
}

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

TattooPerson::TattooPerson() : Person() {
	Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0);
	_tempX = _tempScaleVal = 0;
	_npcIndex = 0;
	_npcMoved = false;
	_npcFacing = -1;
	_resetNPCPath = true;
	_savedNpcSequence = 0;
	_savedNpcFrame = 0;
	_updateNPCPath = true;
	_npcPause = 0;
	_lookHolmes = false;
}

TattooPerson::~TattooPerson() {
	delete _altImages;
}

void TattooPerson::freeAltGraphics() {
	delete _altImages;
	_altImages = nullptr;
	_altSeq = 0;
}

void TattooPerson::adjustSprite() {
	People &people = *_vm->_people;
	TattooScene &scene = *(TattooScene *)_vm->_scene;
	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;

	if (_type == INVALID)
		return;

	if (_type == CHARACTER && _status) {
		// Sprite waiting to move, so restart walk
		_walkCount = _status;
		_status = 0;

		_walkDest = _walkTo.front();
		setWalking();
	} else if (_type == CHARACTER && _walkCount) {
		if (_walkCount > 10) {
			_walkDest = _nextDest;
			setWalking();
		}

		_position += _delta;
		if (_walkCount)
			--_walkCount;

		if (!_walkCount) {
			// If there are remaining points to walk, move to the next one
			if (!_walkTo.empty()) {
				_walkDest = _walkTo.pop();
				setWalking();
			} else {
				gotoStand();
			}
		}
	}

	if (_type != CHARACTER) {
		if (_position.y > SHERLOCK_SCREEN_HEIGHT)
			_position.y = SHERLOCK_SCREEN_HEIGHT;

		if (_position.y < UPPER_LIMIT)
			_position.y = UPPER_LIMIT;

		if (_position.x < LEFT_LIMIT)
			_position.x = LEFT_LIMIT;

		if (_position.x > RIGHT_LIMIT)
			_position.x = RIGHT_LIMIT;
	}

	int frameNum = _frameNumber;
	if (frameNum == -1)
		frameNum = 0;
	int idx = _walkSequences[_sequenceNumber][frameNum];
	if (idx > _maxFrames)
		idx = 1;

	// Set the image frame
	if (_altSeq)
		_imageFrame = &(*_altImages)[idx - 1];
	else
		_imageFrame = &(*_images)[idx - 1];

	// See if the player has come to a stop after clicking on an Arrow zone to leave the scene.
	// If so, this will set up the exit information for the scene transition
	if (!_walkCount && ui._exitZone != -1 && scene._walkedInScene && scene._goToScene == -1 &&
			!_description.compareToIgnoreCase(people[HOLMES]._description)) { 
		Exit &exit = scene._exits[ui._exitZone];
		scene._goToScene = exit._scene;

		if (exit._newPosition.x != 0) {
			people._savedPos = exit._newPosition;

			if (people._savedPos._facing > 100 && people._savedPos.x < 1)
				people._savedPos.x = 100;
		}
	}
}

void TattooPerson::gotoStand() {
	TattooPeople &people = *(TattooPeople *)_vm->_people;

	// If the misc field is set, then we're running a special talk sequence, so don't interrupt it.
	if (_misc)
		return;

	_walkTo.clear();
	_walkCount = 0;
	int oldFacing = _sequenceNumber;

	// If the person was talking or listening, just return it to the standing sequence 
	// in the direction they were pointing
	if (_sequenceNumber >= TALK_UPRIGHT && _sequenceNumber <= LISTEN_UPLEFT) {
		switch (_sequenceNumber) {
		case TALK_UPRIGHT:
		case LISTEN_UPRIGHT:
			_sequenceNumber = STOP_UPRIGHT;
			break;
		case TALK_RIGHT:
		case LISTEN_RIGHT:
			_sequenceNumber = STOP_RIGHT;
			break;
		case TALK_DOWNRIGHT:
		case LISTEN_DOWNRIGHT:
			_sequenceNumber = STOP_DOWNRIGHT;
			break;
		case TALK_DOWNLEFT:
		case LISTEN_DOWNLEFT:
			_sequenceNumber = STOP_DOWNLEFT;
			break;
		case TALK_LEFT:
		case LISTEN_LEFT:
			_sequenceNumber = STOP_LEFT;
			break;
		case TALK_UPLEFT:
		case LISTEN_UPLEFT:
			_sequenceNumber = STOP_UPLEFT;
			break;
		default:
			break;
		}

		if (_seqTo) {
			// Reset to previous value
			_walkSequences[oldFacing]._sequences[_frameNumber] = _seqTo;
			_seqTo = 0;
		}

		// Set the Frame number to the last frame so we don't move
		_frameNumber = 0;

		checkWalkGraphics();

		_oldWalkSequence = -1;
		people._allowWalkAbort = true;
		return;
	}

	// If the sprite that is stopping is an NPC and he is supposed to face a certain direction 
	// when he stops, set that direction here
	int npc = -1;
	for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
		if (_imageFrame == people[idx]._imageFrame)
			npc = idx;
	}

	if (npc != -1 && people[npc]._npcFacing != -1) {
		if (people[npc]._npcFacing == FACING_PLAYER) {
			// See where Holmes is with respect to the NPC (x coords)
			if (people[HOLMES]._position.x < people[npc]._position.x)
				people[npc]._npcFacing = STOP_LEFT;
			else
				people[npc]._npcFacing = STOP_RIGHT;

			// See where Holmes is with respect to the NPC (y coords)
			if (people[HOLMES]._position.y < people[npc]._position.y - (10 * FIXED_INT_MULTIPLIER)) {
				// Holmes is above the NPC so reset the facing to the diagonal ups
				if (people[npc]._npcFacing == STOP_RIGHT)
					people[npc]._npcFacing = STOP_UPRIGHT;
				else
					people[npc]._npcFacing = STOP_UPLEFT;
			} else {
				if (people[HOLMES]._position.y > people[npc]._position.y + (10 * FIXED_INT_MULTIPLIER)) {
					// Holmes is below the NPC so reset the facing to the diagonal downs
					if (people[npc]._npcFacing == STOP_RIGHT)
						people[npc]._npcFacing = STOP_DOWNRIGHT;
					else
						people[npc]._npcFacing = STOP_DOWNLEFT;
				}
			}
		}

		_sequenceNumber = people[npc]._npcFacing;
	} else {
		switch (_sequenceNumber) {
		case WALK_UP: _sequenceNumber = STOP_UP;			break;
		case WALK_UPRIGHT: _sequenceNumber = STOP_UPRIGHT;	break;
		case WALK_RIGHT: _sequenceNumber = STOP_RIGHT;		break;
		case WALK_DOWNRIGHT: _sequenceNumber = STOP_DOWNRIGHT; break;
		case WALK_DOWN: _sequenceNumber = STOP_DOWN;		break;
		case WALK_DOWNLEFT: _sequenceNumber = STOP_DOWNLEFT;break;
		case WALK_LEFT: _sequenceNumber = STOP_LEFT;		break;
		case WALK_UPLEFT: _sequenceNumber = STOP_UPLEFT;	break;
		}
	}

	// Only restart the frame number at 0 if the new sequence is different from the last sequence 
	// so we don't let Holmes repeat standing.
	if (_oldWalkSequence != -1) {
		if (_seqTo) {
			// Reset to previous value
			_walkSequences[oldFacing]._sequences[_frameNumber] = _seqTo;
			_seqTo = 0;
		}

		_frameNumber = 0;
	}

	checkWalkGraphics();

	_oldWalkSequence = -1;
	people._allowWalkAbort = true;
}

void TattooPerson::setWalking() {
	TattooScene &scene = *(TattooScene *)_vm->_scene;
	int oldDirection, oldFrame;
	Common::Point delta;
	_nextDest = _walkDest;

	// Flag that player has now walked in the scene
	scene._walkedInScene = true;

	// Stop any previous walking, since a new dest is being set
	_walkCount = 0;
	oldDirection = _sequenceNumber;
	oldFrame = _frameNumber;

	// Set speed to use horizontal and vertical movement
	int scaleVal = scene.getScaleVal(_position);
	Common::Point speed(MAX(WALK_SPEED_X[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2),
		MAX(WALK_SPEED_Y[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2));
	Common::Point diagSpeed(MAX(WALK_SPEED_DIAG_X[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2),
		MAX((WALK_SPEED_Y[scene._currentScene - 1] - 2) * SCALE_THRESHOLD / scaleVal, 2));

	// If the player is already close to the given destination that no walking is needed, 
	// move to the next  straight line segment in the overall walking route, if there is one
	for (;;) {
		if (_centerWalk || !_walkTo.empty()) {
			// Since we want the player to be centered on the ultimate destination, and the player
			// is drawn from the left side, move the cursor half the width of the player to center it
			delta = Common::Point(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x,
				_position.y / FIXED_INT_MULTIPLIER - _walkDest.y);

			int dir;
			if (ABS(delta.x) > ABS(delta.y))
				dir = (delta.x < 0) ? WALK_LEFT : WALK_RIGHT;
			else
				dir = (delta.y < 0) ? WALK_UP : WALK_DOWN;

			scaleVal = scene.getScaleVal(Point32(_walkDest.x * FIXED_INT_MULTIPLIER,
				_walkDest.y * FIXED_INT_MULTIPLIER));
			_walkDest.x -= _stopFrames[dir]->sDrawXSize(scaleVal) / 2;
		}

		delta = Common::Point(
			ABS(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x),
			ABS(_position.y / FIXED_INT_MULTIPLIER - _walkDest.y)
			);

		// If we're ready to move a sufficient distance, that's it. Otherwise,
		// move onto the next portion of the walk path, if there is one
		if ((delta.x > 3 || delta.y > 0) || _walkTo.empty())
			break;

		// Pop next walk segment off the walk route stack
		_walkDest = _walkTo.pop();
	}

	// If a sufficient move is being done, then start the move
	if (delta.x > 3 || delta.y) {
		// See whether the major movement is horizontal or vertical
		if (delta.x >= delta.y) {
			// Set the initial frame sequence for the left and right, as well
			// as setting the delta x depending on direction
			if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER)) {
				_sequenceNumber = WALK_LEFT;
				_delta.x = speed.x * -(FIXED_INT_MULTIPLIER / 10);
			} else {
				_sequenceNumber = WALK_RIGHT;
				_delta.x = speed.x * (FIXED_INT_MULTIPLIER / 10);
			}

			// See if the x delta is too small to be divided by the speed, since
			// this would cause a divide by zero error
			if ((delta.x * 10) >= speed.x) {
				// Det the delta y
				_delta.y = (delta.y * FIXED_INT_MULTIPLIER) / ((delta.x * 10) / speed.x);
				if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER))
					_delta.y = -_delta.y;

				// Set how many times we should add the delta to the player's position
				_walkCount = (delta.x * 10) / speed.x;
			} else {
				// The delta x was less than the speed (ie. we're really close to
				// the destination). So set delta to 0 so the player won't move
				_delta = Point32(0, 0);
				_position = Point32(_walkDest.x * FIXED_INT_MULTIPLIER, _walkDest.y * FIXED_INT_MULTIPLIER);

				_walkCount = 1;
			}

			// See if the sequence needs to be changed for diagonal walking
			if (_delta.y > 1500) {
				if (_sequenceNumber == WALK_LEFT || _sequenceNumber == WALK_RIGHT) {
					_delta.x = _delta.x / speed.x * diagSpeed.x;
					_delta.y = (delta.y * FIXED_INT_MULTIPLIER) / (delta.x * 10 / diagSpeed.x);

					_walkCount = delta.x * 10 / diagSpeed.x;
				}

				switch (_sequenceNumber) {
				case WALK_LEFT:
					_sequenceNumber = WALK_DOWNLEFT;
					break;
				case WALK_RIGHT:
					_sequenceNumber = WALK_DOWNRIGHT;
					break;
				}
			} else if (_delta.y < -1500) {
				if (_sequenceNumber == WALK_LEFT || _sequenceNumber == WALK_RIGHT) {
					_delta.x = _delta.x / speed.x * diagSpeed.x;
					_delta.y = -1 * (delta.y * FIXED_INT_MULTIPLIER) / (delta.x * 10 / diagSpeed.x);

					_walkCount = (delta.x * 10) / diagSpeed.x;
				}

				switch (_sequenceNumber) {
				case WALK_LEFT:
					_sequenceNumber = WALK_UPLEFT;
					break;
				case WALK_RIGHT:
					_sequenceNumber = WALK_UPRIGHT;
					break;
				}
			}
		} else {
			// Major movement is vertical, so set the sequence for up and down,
			// and set the delta Y depending on the direction
			if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER)) {
				_sequenceNumber = WALK_UP;
				_delta.y = speed.y * -(FIXED_INT_MULTIPLIER / 10);
			} else {
				speed.y = diagSpeed.y;
				_sequenceNumber = WALK_DOWN;
				_delta.y = speed.y * (FIXED_INT_MULTIPLIER / 10);
			}

			// Set the delta x
			if (delta.y * 10 / speed.y)
				_delta.x = (delta.x * FIXED_INT_MULTIPLIER) / (delta.y * 10 / speed.y);
			else
				_delta.x = (delta.x * FIXED_INT_MULTIPLIER) / delta.y;

			if (_walkDest.x < _position.y / FIXED_INT_MULTIPLIER)
				_delta.x = -_delta.x;

			// Set how many times we should add the delta's to the players position
			if (delta.y * 10 / speed.y)
				_walkCount = delta.y * 10 / speed.y;
			else
				_walkCount = delta.y;
		}
	}

	// See if the new walk sequence is the same as the old. If it's a new one,
	// we need to reset the frame number to zero so its animation starts at
	// its beginning. Otherwise, if it's the same sequence, we can leave it
	// as is, so it keeps the animation going at wherever it was up to
	if (_sequenceNumber != _oldWalkSequence) {
		if (_seqTo) {
			// Reset to previous value
			_walkSequences[oldDirection]._sequences[_frameNumber] = _seqTo;
			_seqTo = 0;
		}
		_frameNumber = 0;
	}

	checkWalkGraphics();
	_oldWalkSequence = _sequenceNumber;

	if (!_walkCount && _walkTo.empty())
		gotoStand();

	// If the sequence is the same as when we started, then Holmes was standing still and we're trying 
	// to re-stand him, so reset Holmes' rame to the old frame number from before it was reset to 0
	if (_sequenceNumber == oldDirection)
		_frameNumber = oldFrame;
}

void TattooPerson::walkToCoords(const Point32 &destPos, int destDir) {
	TattooEngine &vm = *(TattooEngine *)_vm;
	Events &events = *_vm->_events;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooScene &scene = *(TattooScene *)_vm->_scene;
	Talk &talk = *_vm->_talk;

	CursorId oldCursor = events.getCursor();
	events.setCursor(WAIT);

	_walkDest = Common::Point(destPos.x / FIXED_INT_MULTIPLIER, destPos.y / FIXED_INT_MULTIPLIER);

	bool isHolmes = this == &people[HOLMES];
	if (isHolmes) {
		people._allowWalkAbort = true;
	} else {
		// Clear the path Variables
		_npcIndex = _npcPause = 0;
		Common::fill(_npcPath, _npcPath + 100, 0);
		_npcFacing = destDir;
	}

	_centerWalk = false;

	// Only move the person if they're going an appreciable distance
	if (ABS(_walkDest.x - (_position.x / FIXED_INT_MULTIPLIER)) > 8 || 
			ABS(_walkDest.y - (_position.y / FIXED_INT_MULTIPLIER)) > 4) {
		goAllTheWay();

		do {
			// Keep doing animations whilst walk is in progress
			events.wait(1);
			scene.doBgAnim();

			if (events.kbHit()) {
				Common::KeyState keyState = events.getKey();

				if (keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog) {
					vm.setFlags(-76);
					vm.setFlags(396);
					scene._goToScene = 1;
					talk._talkToAbort = true;
				}
			}
		} while (!_vm->shouldQuit() && _walkCount && !talk._talkToAbort);
	}

	_centerWalk = true;
	if (!isHolmes)
		_updateNPCPath = true;

	if (!talk._talkToAbort) {
		// put character exactly on right spot
		_position = destPos;

		if (_sequenceNumber != destDir) {
			// Facing character to correct ending direction
			_sequenceNumber = destDir;
			gotoStand();
		}

		if (!isHolmes)
			_updateNPCPath = false;

		// Secondary walking wait loop
		bool done = false;
		while (!done && !_vm->shouldQuit()) {
			events.wait(1);
			scene.doBgAnim();

			// See if we're past the initial goto stand sequence
			for (int idx = 0; idx < _frameNumber; ++idx) {
				if (_walkSequences[_sequenceNumber][idx] == 0) {
					done = true;
					break;
				}
			}

			if (events.kbHit()) {
				Common::KeyState keyState = events.getKey();

				if (keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog) {
					vm.setFlags(-76);
					vm.setFlags(396);
					scene._goToScene = 1;
					talk._talkToAbort = true;
				}
			}
		}

		if (!isHolmes)
			_updateNPCPath = true;

		if (!talk._talkToAbort)
			events.setCursor(oldCursor);
	}
}

void TattooPerson::clearNPC() {
	Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0);
	_npcIndex = 0;
	_pathStack.clear();
	_npcName = "";
}

void TattooPerson::updateNPC() {
	People &people = *_vm->_people;
	Talk &talk = *_vm->_talk;

	// If the NPC isn't on, or it's in Talk or Listen Mode, then return without doing anything
	if (_type != CHARACTER || _sequenceNumber >= TALK_UPRIGHT)
		return;

	// If the NPC is paused, just decrement his pause counter and exit
	if (_npcPause) {
		// Decrement counter
		--_npcPause;

		// Now see if we need to update the NPC's frame sequence so that he faces Holmes
		if (_lookHolmes) {
			// See where Holmes is with respect to the NPC (x coordinate)			
			_npcFacing =  (people[HOLMES]._position.x < _position.x) ? STOP_LEFT : STOP_RIGHT;

			// See where Holmes is with respect to the NPC (y coordinate)
			if (people[HOLMES]._position.y < (_position.y - 10 * FIXED_INT_MULTIPLIER)) {
				// Holmes is above the NPC so reset the facing to a diagonal up
				_npcFacing = (_npcFacing == STOP_RIGHT) ? STOP_UPRIGHT : STOP_UPLEFT;
			} else  if (people[HOLMES]._position.y > (_position.y + 10 * FIXED_INT_MULTIPLIER)) {
				// Holmes is below the NPC so reset the facing to a diagonal down
				_npcFacing = (_npcFacing == STOP_RIGHT) ? STOP_DOWNRIGHT : STOP_DOWNLEFT;
			}

			// See if we need to set the old_walk_sequence so the NPC will put his arms 
			// up if he turns another way
			if (_sequenceNumber != _npcFacing)
				_oldWalkSequence = _sequenceNumber;

			gotoStand();
		}
	} else {
		// Reset the look flag so the NPC won't face Holmes anymore
		_lookHolmes = false;

		// See if the NPC is stopped or not. Don't do anything if he's moving
		if (!_walkCount) {
			// If there is no new command, reset the path back to the beginning
			if (!_npcPath[_npcIndex])
				_npcIndex = 0;

			// The NPC is stopped and any pause he was doing is done. We can now see what 
			// the next command in the NPC path is.

			// Scan Past any NPC Path Labels since they do nothing except mark places for If's and Goto's
			while (_npcPath[_npcIndex] == NPCPATH_PATH_LABEL)
				_npcIndex += 2;

			if (_npcPath[_npcIndex]) {
				_npcFacing = -1;

				switch (_npcPath[_npcIndex]) {
				case NPCPATH_SET_DEST: {
					// Set the NPC's new destination
					int xp = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1;
					if (xp > 16384)
						xp = -1 * (xp - 16384);
					_walkDest.x = xp;
					_walkDest.y = (_npcPath[_npcIndex + 3] - 1) * 256 + _npcPath[_npcIndex + 4] - 1;
					_npcFacing = _npcPath[_npcIndex + 5] - 1;

					goAllTheWay();
					_npcIndex += 6;
					break;
				}

				case NPCPATH_PAUSE:
					// Set the NPC to pause where he is
					_npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1;
					_npcIndex += 3;
					break;

				case NPCPATH_SET_TALK_FILE: {
					// Set the NPC's Talk File to use if Holmes talks to them
					++_npcIndex;

					_npcName = "";
					for (int idx = 0; idx < 8; ++idx) {
						if (_npcPath[_npcIndex + idx] != '~')
							_npcName += _npcPath[_npcIndex + idx];
						else
							break;
					}

					_npcIndex += 8;
					break;
				}

				case NPCPATH_CALL_TALK_FILE: {
					// Call a Talk File
					++_npcIndex;

					Common::String name;
					for (int idx = 0; idx < 8; ++idx) {
						if (_npcPath[_npcIndex + idx] != '~')
							name += _npcPath[_npcIndex + idx];
						else
							break;
					}

					_npcIndex += 8;
					talk.talkTo(name);
					break;
				}

				case NPCPATH_TAKE_NOTES:
					// Set the NPC to Pause where he is and set his frame sequences 
					// so he takes notes while he's paused
					_npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1;
					_npcIndex += 3;
					break;

				case NPCPATH_FACE_HOLMES:
					// Set the NPC to Pause where he is and set his look flag so he will always face Holmes
					// while he is paused
					_npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1;
					_lookHolmes = true;
					_npcIndex += 3;
					break;

				//case NPCPATH_PATH_LABEL:		// No implementation needed here

				case NPCPATH_GOTO_LABEL: {
					// Goto NPC Path Label
					int label = _npcPath[_npcIndex + 1];
					_npcIndex = 0;

					// Scan through NPC path data to find the label
					bool found = false;
					while (!found) {
						switch (_npcPath[_npcIndex]) {
						case NPCPATH_SET_DEST:
							_npcIndex += 6;
							break;
						case NPCPATH_PAUSE:
						case NPCPATH_TAKE_NOTES:
						case NPCPATH_FACE_HOLMES:
							_npcIndex += 3;
							break;
						case NPCPATH_SET_TALK_FILE:
						case NPCPATH_CALL_TALK_FILE:
							_npcIndex += 8;
							break;
						case NPCPATH_PATH_LABEL:
							if (_npcPath[_npcIndex + 1] == label)
								found = true;
							_npcIndex += 2;
							break;
						case NPCPATH_GOTO_LABEL:
							_npcIndex += 2;
							break;
						case NPCPATH_IFFLAG_GOTO_LABEL:
							_npcIndex += 4;
							break;
						}
					}
					break;
				}

				case NPCPATH_IFFLAG_GOTO_LABEL: {
					// If FLAG then Goto Label
					int flag = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1 - (_npcPath[_npcIndex + 2] == 1 ? 1 : 0);

					// Set the value the flag should be for the if statement to succeed
					bool flagVal = flag < 16384;

					int label = _npcPath[_npcIndex + 3];
					_npcIndex += 4;

					// If the flag is set Correctly, move the NPC Index to the given label
					if (_vm->readFlags(flag & 16383) == flagVal) {
						_npcIndex = 0;
						bool found = false;
						while (!found)
						{
							switch (_npcPath[_npcIndex])
							{
							case NPCPATH_SET_DEST:
								_npcIndex += 6;
								break;
							case NPCPATH_PAUSE:
							case NPCPATH_TAKE_NOTES:
							case NPCPATH_FACE_HOLMES:
								_npcIndex += 3;
								break;
							case NPCPATH_SET_TALK_FILE:
							case NPCPATH_CALL_TALK_FILE:
								_npcIndex += 8;
								break;
							case NPCPATH_PATH_LABEL:
								if (_npcPath[_npcIndex + 1] == label)
									found = true;
								_npcIndex += 2;
								break;
							case NPCPATH_GOTO_LABEL:
								_npcIndex += 2;
								break;
							case NPCPATH_IFFLAG_GOTO_LABEL:
								_npcIndex += 4;
								break;
							}
						}
					}

					break;
				}

				default:
					break;
				}
			}
		}
	}
}

void TattooPerson::pushNPCPath() {
	assert(_pathStack.size() < 2);
	SavedNPCPath savedPath(_npcPath, _npcIndex, _npcPause, _position, _sequenceNumber, _lookHolmes);
	_pathStack.push(savedPath);
}

void TattooPerson::pullNPCPath() {
	// Pop the stack entry and restore the fields
	SavedNPCPath path = _pathStack.pop();
	Common::copy(&path._path[0], &path._path[MAX_NPC_PATH], &_npcPath[0]);
	_npcIndex = path._npcIndex;
	_npcPause = path._npcPause;

	// Handle the first case if the NPC was paused
	if (_npcPause) {
		_npcFacing = path._npcFacing;
		_lookHolmes = path._lookHolmes;

		// See if the NPC has moved from where they originally were
		if (path._position != _position) {
			_walkDest = Point32(path._position.x / FIXED_INT_MULTIPLIER, path._position.y / FIXED_INT_MULTIPLIER);
			goAllTheWay();
			_npcPause = 0;
			_npcIndex -= 3;
		} else {
			// See if we need to set the old walk sequence so the NPC will put his arms up if he turns another way
			if (_npcFacing != _sequenceNumber)
				_oldWalkSequence = _sequenceNumber;

			gotoStand();
		}
	} else {
		// Handle the second case if the NPC was in motion
		_npcIndex -= 6;
	}
}

Common::Point TattooPerson::getSourcePoint() const {
	TattooScene &scene = *(TattooScene *)_vm->_scene;
	int scaleVal = scene.getScaleVal(_position);

	return Common::Point(_position.x / FIXED_INT_MULTIPLIER + 
		(_imageFrame ? _imageFrame->sDrawXSize(scaleVal) / 2 : 0), _position.y / FIXED_INT_MULTIPLIER);
}

void TattooPerson::setObjTalkSequence(int seq) {
	assert(seq != -1 && _type == CHARACTER);

	if (_seqTo) {
		// reset to previous value
		_walkSequences[_sequenceNumber]._sequences[_frameNumber] = _seqTo;
		_seqTo = 0;
	}

	_sequenceNumber = _gotoSeq;
	_frameNumber = 0;
	checkWalkGraphics();
}

void TattooPerson::checkWalkGraphics() {
	People &people = *_vm->_people;

	if (_images == nullptr) {
		freeAltGraphics();
		return;
	}

	Common::String filename = Common::String::format("%s.vgs", _walkSequences[_sequenceNumber]._vgsName.c_str());

	// Set the adjust depending on if we have to fine tune the x position of this particular graphic
	_adjust.x = _adjust.y = 0;

	for (int idx = 0; idx < NUM_ADJUSTED_WALKS; ++idx) {
		if (!scumm_strnicmp(_walkSequences[_sequenceNumber]._vgsName.c_str(), ADJUST_WALKS[idx]._vgsName,
			strlen(ADJUST_WALKS[idx]._vgsName))) {
			if (_walkSequences[_sequenceNumber]._horizFlip)
				_adjust.x = ADJUST_WALKS[idx]._flipXAdjust;
			else
				_adjust.x = ADJUST_WALKS[idx]._xAdjust;

			_adjust.y = ADJUST_WALKS[idx]._yAdjust;
			break;
		}
	}

	// See if we're already using Alternate Graphics
	if (_altSeq) {
		// See if the VGS file called for is different than the alternate graphics already loaded
		if (_walkSequences[_sequenceNumber]._vgsName.compareToIgnoreCase(_walkSequences[_altSeq - 1]._vgsName)) {
			// Different AltGraphics, Free the old ones
			freeAltGraphics();
		}
	}

	// If there is no Alternate Sequence set, see if we need to load a new one
	if (!_altSeq) {
		int npcNum = -1;
		// Find which NPC this is so we can check the name of the graphics loaded
		for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
			if (this == &people[idx]) {
				npcNum = idx;
				break;
			}
		}

		if (npcNum != -1) {
			// See if the VGS file called for is different than the main graphics which are already loaded
			if (filename.compareToIgnoreCase(people[npcNum]._walkVGSName) != 0) {
				// See if this is one of the more used Walk Graphics stored in WALK.LIB
				for (int idx = 0; idx < NUM_IN_WALK_LIB; ++idx) {
					if (!scumm_stricmp(filename.c_str(), WALK_LIB_NAMES[idx])) {
						people._useWalkLib = true;
						break;
					}
				}

				_altImages = new ImageFile(filename);
				people._useWalkLib = false;

				_altSeq = _sequenceNumber + 1;
			}
		}
	}

	// If this is a different seqeunce from the current sequence, reset the appropriate variables
	if (_sequences != &_walkSequences[_sequenceNumber]._sequences[0]) {
		_seqTo = _seqCounter = _seqCounter2 = _seqStack = _startSeq = 0;
		_sequences = &_walkSequences[_sequenceNumber]._sequences[0];
		_seqSize = _walkSequences[_sequenceNumber]._sequences.size();

		// WORKAROUND: Occassionally when switching to a new walk sequence the existing frame number may be outside
		// the allowed range for the new sequence. In such cases, reset the frame number
		if (_frameNumber < 0 || _frameNumber >= (int)_seqSize || _walkSequences[_sequenceNumber][_frameNumber] == 0)
			_frameNumber = 0;
	}

	setImageFrame();
}

void TattooPerson::synchronize(Serializer &s) {
	if (s.isSaving()) {
		SpriteType type = (_type == INVALID && _walkLoaded) ? HIDDEN_CHARACTER : _type;
		s.syncAsSint16LE(type);
	} else {
		if (_walkCount)
			gotoStand();

		s.syncAsSint16LE(_type);
	}

	s.syncAsSint32LE(_position.x);
	s.syncAsSint32LE(_position.y);
	s.syncString(_walkVGSName);
	s.syncString(_description);
	s.syncString(_examine);

	// NPC specific properties
	s.syncBytes(&_npcPath[0], MAX_NPC_PATH);
	s.syncString(_npcName);
	s.syncAsSint32LE(_npcPause);
	s.syncAsByte(_lookHolmes);
	s.syncAsByte(_updateNPCPath);
	if (s.isLoading())
		_npcIndex = 0;

	// Verbs
	for (int idx = 0; idx < 2; ++idx)
		_use[idx].synchronize(s);
}

void TattooPerson::walkHolmesToNPC() {
	Events &events = *_vm->_events;
	TattooScene &scene = *(TattooScene *)_vm->_scene;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	Screen &screen = *_vm->_screen;
	Talk &talk = *_vm->_talk;
	TattooPerson &holmes = people[HOLMES];

	// Save the character's details
	pushNPCPath();

	// If the NPC is moving, stop him at his current position
	if (_walkCount) {
		// Reset the facing so the NPC will stop facing the direction he was going,
		// rather than the direction he was supposed to when he finished wlaking
		_npcFacing = -1;
		gotoStand();
	}

	int scaleVal = scene.getScaleVal(_position);
	ImageFrame &imgFrame = (*holmes._images)[0];

	// Clear the path variables
	memset(_npcPath, 0, 100);
	
	// Set the NPC path so he pauses for 250 while looking at Holmes
	_npcPath[0] = 6;
	_npcPath[1] = 1;
	_npcPath[2] = 251;
	_npcIndex = 0;
	_npcPause = 250;
	_lookHolmes = true;

	// See where Holmes is with respect to the NPC (x coords)
	if (holmes._position.x < _position.x) {
		holmes._walkDest.x = MAX(_position.x / FIXED_INT_MULTIPLIER - imgFrame.sDrawXSize(scaleVal), 0);
	} else {
		holmes._walkDest.x = MIN(_position.x / FIXED_INT_MULTIPLIER + imgFrame.sDrawXSize(scaleVal) * 2,
			screen._backBuffer1.w() - 1);
	}

	// See where Holmes is with respect to the NPC (y coords)
	if (holmes._position.y < (_position.y - imgFrame.sDrawXSize(scaleVal) * 500)) {
		holmes._walkDest.y = MAX(_position.y / FIXED_INT_MULTIPLIER - imgFrame.sDrawXSize(scaleVal) / 2, 0);
	} else {
		if (holmes._position.y > (_position.y + imgFrame.sDrawXSize(scaleVal) * 500)) {
			// Holmes is below the NPC
			holmes._walkDest.y = MIN(_position.y / FIXED_INT_MULTIPLIER + imgFrame.sDrawXSize(scaleVal) / 2,
				SHERLOCK_SCREEN_HEIGHT - 1);
		} else {
			// Holmes is roughly on the same Y as the NPC
			holmes._walkDest.y = _position.y / FIXED_INT_MULTIPLIER;
		}
	}

	events.setCursor(WAIT);

	_walkDest.x += 10;
	people._allowWalkAbort = true;
	holmes.goAllTheWay();

	// Do doBgAnim should be called over and over until walk is done
	do {
		events.wait(1);
		scene.doBgAnim();
	} while (holmes._walkCount);

	if (!talk._talkToAbort) {
		// Setup correct direction for Holmes to face
		// See where Holmes is with respect to the NPC (x coords)
		int facing = (holmes._position.x < _position.x) ? STOP_RIGHT : STOP_LEFT;

		// See where Holmes is with respect to the NPC (y coords)
		if (holmes._position.y < (_position.y - (10 * FIXED_INT_MULTIPLIER))) {
			// Holmes is above the NPC. Reset the facing to the diagonal downs
			facing = (facing == STOP_RIGHT) ? STOP_DOWNRIGHT : STOP_DOWNLEFT;
		} else {
			if (holmes._position.y > (_position.y + 10 * FIXED_INT_MULTIPLIER)) {
				// Holmes is below the NPC. Reset the facing to the diagonal ups
				facing = (facing == STOP_RIGHT) ? STOP_UPRIGHT : STOP_UPLEFT;
			}
		}

		holmes._sequenceNumber = facing;
		holmes.gotoStand();

		events.setCursor(ARROW);
	}
}

void TattooPerson::walkBothToCoords(const PositionFacing &holmesDest, const PositionFacing &npcDest) {
	Events &events = *_vm->_events;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	Scene &scene = *_vm->_scene;
	Talk &talk = *_vm->_talk;
	TattooPerson &holmes = people[HOLMES];
	bool holmesStopped = false, npcStopped = false;

	// Save the current cursor and change to the wait cursor
	CursorId oldCursor = events.getCursor();
	events.setCursor(WAIT);

	holmes._centerWalk = false;
	_centerWalk = false;

	// Start Holmes walking to his dest
	holmes._walkDest = Common::Point(holmesDest.x / FIXED_INT_MULTIPLIER + 10, holmesDest.y / FIXED_INT_MULTIPLIER);
	people._allowWalkAbort = true;
	holmes.goAllTheWay();

	// Start the NPC walking to their dest
	_walkDest = Common::Point(npcDest.x / FIXED_INT_MULTIPLIER + 10, npcDest.y / FIXED_INT_MULTIPLIER);
	goAllTheWay();

	// Clear the path variables
	_npcIndex = _npcPause = 0;
	Common::fill(&_npcPath[0], &_npcPath[100], 0);
	_npcFacing = npcDest._facing;

	// Now loop until both stop walking
	do {
		events.pollEvents();
		scene.doBgAnim();

		if (!holmes._walkCount && !holmesStopped) {
			// Holmes finished walking
			holmesStopped = true;

			// Ensure Holmes is on the exact destination spot
			holmes._position = holmesDest;
			holmes._sequenceNumber = holmesDest._facing;
			holmes.gotoStand();
		}

		if (!_walkCount && !npcStopped) {
			// NPC finished walking
			npcStopped = true;

			// Ensure NPC is on the exact destination spot
			_position = npcDest;
			_sequenceNumber = npcDest._facing;
			gotoStand();
		}

	} while (!_vm->shouldQuit() && (holmes._walkCount || _walkCount));

	holmes._centerWalk = true;
	_centerWalk = true;

	// Do one last frame draw so that the lsat person to stop will be drawn in their final position
	scene.doBgAnim();

	_updateNPCPath = true;

	if (!talk._talkToAbort)
		// Restore original mouse cursor
		events.setCursor(oldCursor);
}

void TattooPerson::centerScreenOnPerson() {
	Screen &screen = *_vm->_screen;
	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;

	ui._targetScroll.x = CLIP(_position.x / FIXED_INT_MULTIPLIER - SHERLOCK_SCREEN_WIDTH / 2,
		0, screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH);
	screen._currentScroll = ui._targetScroll;

	// Reset the default look position to the center of the screen
	ui._lookPos = screen._currentScroll + Common::Point(SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2);
}

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

TattooPeople::TattooPeople(SherlockEngine *vm) : People(vm) {
	for (int idx = 0; idx < 6; ++idx)
		_data.push_back(new TattooPerson());
}

void TattooPeople::setListenSequence(int speaker, int sequenceNum) {
	Scene &scene = *_vm->_scene;

	// If no speaker is specified, then nothing needs to be done
	if (speaker == -1)
		return;

	int objNum = findSpeaker(speaker);
	if (objNum < CHARACTERS_INDEX && objNum != -1) {
		// See if the Object has to wait for an Abort Talk Code
		Object &obj = scene._bgShapes[objNum];
		if (obj.hasAborts())
			obj._gotoSeq = sequenceNum;
		else
			obj.setObjTalkSequence(sequenceNum);
	} else if (objNum != -1) {
		objNum -= CHARACTERS_INDEX;
		TattooPerson &person = (*this)[objNum];

		int newDir = person._sequenceNumber;
		switch (person._sequenceNumber) {
		case WALK_UP:
		case STOP_UP:
		case WALK_UPRIGHT:
		case STOP_UPRIGHT:
		case TALK_UPRIGHT:
		case LISTEN_UPRIGHT:
			newDir = LISTEN_UPRIGHT;
			break;
		case WALK_RIGHT:
		case STOP_RIGHT:
		case TALK_RIGHT:
		case LISTEN_RIGHT:
			newDir = LISTEN_RIGHT;
			break;
		case WALK_DOWNRIGHT:
		case STOP_DOWNRIGHT:
		case TALK_DOWNRIGHT:
		case LISTEN_DOWNRIGHT:
			newDir = LISTEN_DOWNRIGHT;
			break;
		case WALK_DOWN:
		case STOP_DOWN:
		case WALK_DOWNLEFT:
		case STOP_DOWNLEFT:
		case TALK_DOWNLEFT:
		case LISTEN_DOWNLEFT:
			newDir = LISTEN_DOWNLEFT;
			break;
		case WALK_LEFT:
		case STOP_LEFT:
		case TALK_LEFT:
		case LISTEN_LEFT:
			newDir = LISTEN_LEFT;
			break;
		case WALK_UPLEFT:
		case STOP_UPLEFT:
		case TALK_UPLEFT:
		case LISTEN_UPLEFT:
			newDir = LISTEN_UPLEFT;
			break;

		default:
			break;
		}

		// See if the NPC's Seq has to wait for an Abort Talk Code
		if (person.hasAborts()) {
			person._gotoSeq = newDir;
		}  else {
			if (person._seqTo) {
				// Reset to previous value
				person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo;
				person._seqTo = 0;
			}

			person._sequenceNumber = newDir;
			person._frameNumber = 0;
			person.checkWalkGraphics();
		}
	}
}

void TattooPeople::setTalkSequence(int speaker, int sequenceNum) {
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	Scene &scene = *_vm->_scene;
	TattooTalk &talk = *(TattooTalk *)_vm->_talk;

	// If no speaker is specified, then nothing needs to be done
	if (speaker == -1)
		return;

	int objNum = people.findSpeaker(speaker);
	if (objNum != -1 && objNum < CHARACTERS_INDEX) {
		Object &obj = scene._bgShapes[objNum];

		// See if the Object has to wait for an Abort Talk Code
		if (obj.hasAborts()) {
			talk.pushSequenceEntry(&obj);
			obj._gotoSeq = sequenceNum;
		} else {
			obj.setObjTalkSequence(sequenceNum);
		}
	} else if (objNum != -1) {
		objNum -= CHARACTERS_INDEX;
		TattooPerson &person = people[objNum];
		int newDir = person._sequenceNumber;

		switch (newDir) {
		case WALK_UP:
		case STOP_UP:
		case WALK_UPRIGHT:
		case STOP_UPRIGHT:
		case TALK_UPRIGHT:
		case LISTEN_UPRIGHT:
			newDir = TALK_UPRIGHT;
			break;
		case WALK_RIGHT:
		case STOP_RIGHT:
		case TALK_RIGHT:
		case LISTEN_RIGHT:
			newDir = TALK_RIGHT;
			break;
		case WALK_DOWNRIGHT:
		case STOP_DOWNRIGHT:
		case TALK_DOWNRIGHT:
		case LISTEN_DOWNRIGHT:
			newDir = TALK_DOWNRIGHT;
			break;
		case WALK_DOWN:
		case STOP_DOWN:
		case WALK_DOWNLEFT:
		case STOP_DOWNLEFT:
		case TALK_DOWNLEFT:
		case LISTEN_DOWNLEFT:
			newDir = TALK_DOWNLEFT;
			break;
		case WALK_LEFT:
		case STOP_LEFT:
		case TALK_LEFT:
		case LISTEN_LEFT:
			newDir = TALK_LEFT;
			break;
		case WALK_UPLEFT:
		case STOP_UPLEFT:
		case TALK_UPLEFT:
		case LISTEN_UPLEFT:
			newDir = TALK_UPLEFT;
			break;
		default:
			break;
		}

		// See if the NPC's sequence has to wait for an Abort Talk Code
		if (person.hasAborts()) {
			person._gotoSeq = newDir;
		} else {
			if (person._seqTo) {
				// Reset to previous value
				person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo;
				person._seqTo = 0;
			}

			person._sequenceNumber = newDir;
			person._frameNumber = 0;
			person.checkWalkGraphics();
		}
	}
}


int TattooPeople::findSpeaker(int speaker) {
	speaker &= 0x7f;
	int result = People::findSpeaker(speaker);
	const char *portrait = _characters[speaker]._portrait;

	// Fallback that Rose Tattoo uses if no speaker was found
	if (result == -1) {
		bool flag = _vm->readFlags(FLAG_PLAYER_IS_HOLMES);

		if (_data[HOLMES]->_type == CHARACTER && ((speaker == HOLMES && flag) || (speaker == WATSON && !flag)))
			// Return the offset index for the first character
			return 0 + CHARACTERS_INDEX;

		// Otherwise, scan through the list of the remaining characters to find a name match
		for (uint idx = 1; idx < _data.size(); ++idx) {
			TattooPerson &p = (*this)[idx];

			if (p._type == CHARACTER) {
				Common::String name(p._name.c_str(), p._name.c_str() + 4);

				if (name.equalsIgnoreCase(portrait) && p._npcName[4] >= '0' && p._npcName[4] <= '9')
					return idx + CHARACTERS_INDEX;
			}
		}
	}

	return result;
}

void TattooPeople::synchronize(Serializer &s) {
	s.syncAsByte(_holmesOn);

	for (uint idx = 0; idx < _data.size(); ++idx)
		(*this)[idx].synchronize(s);

	s.syncAsSint16LE(_holmesQuotient);

	if (s.isLoading()) {
		_savedPos.x = _data[HOLMES]->_position.x;
		_savedPos.y = _data[HOLMES]->_position.y;
		_savedPos._facing = _data[HOLMES]->_sequenceNumber;
	}
}

bool TattooPeople::loadWalk() {
	Resources &res = *_vm->_res;
	bool result = false;

	for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
		Person &person = *_data[idx];

		if (!person._walkLoaded && (person._type == CHARACTER || person._type == HIDDEN_CHARACTER)) {
			if (person._type == HIDDEN_CHARACTER)
				person._type = INVALID;

			// See if this is one of the more used Walk Graphics stored in WALK.LIB
			for (int libNum = 0; libNum < NUM_IN_WALK_LIB; ++libNum) {
				if (!person._walkVGSName.compareToIgnoreCase(WALK_LIB_NAMES[libNum])) {
					_useWalkLib = true;
					break;
				}
			}

			// Load the images for the character
			person._images = new ImageFile(person._walkVGSName, false);
			person._maxFrames = person._images->size();

			// Load walk sequence data
			Common::String fname = Common::String(person._walkVGSName.c_str(), strchr(person._walkVGSName.c_str(), '.'));
			fname += ".SEQ";

			// Load the walk sequence data
			Common::SeekableReadStream *stream = res.load(fname, _useWalkLib ? "walk.lib" : "vgs.lib");
				
			person._walkSequences.resize(stream->readByte());

			for (uint seqNum = 0; seqNum < person._walkSequences.size(); ++seqNum)
				person._walkSequences[seqNum].load(*stream);

			// Close the sequences resource
			delete stream;
			_useWalkLib = false;

			person._sequences = &person._walkSequences[person._sequenceNumber]._sequences[0];
			person._seqSize = person._walkSequences[person._sequenceNumber]._sequences.size();
			person._frameNumber = 0;
			person.setImageFrame();

			// Set the stop Frames pointers
			for (int dirNum = 0; dirNum < 8; ++dirNum) {
				int count = 0;
				while (person._walkSequences[dirNum + 8][count] != 0)
					++count;
				count += 2;
				count = person._walkSequences[dirNum + 8][count] - 1;
				person._stopFrames[dirNum] = &(*person._images)[count];
			}

			result = true;
			person._walkLoaded = true;
		} else if (person._type != CHARACTER) {
			person._walkLoaded = false;
		}
	}

	_forceWalkReload = false;
	return result;
}


void TattooPeople::pullNPCPaths() {
	for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
		TattooPerson &p = (*this)[idx];
		if (p._npcMoved) {
			while (!p._pathStack.empty())
				p.pullNPCPath();
		}
	}
}

const Common::Point TattooPeople::restrictToZone(int zoneId, const Common::Point &destPos) {
	Scene &scene = *_vm->_scene;
	Screen &screen = *_vm->_screen;
	Common::Rect &r = scene._zones[zoneId];

	if (destPos.x < 0 || destPos.x > screen._backBuffer1.w())
		return destPos;
	else if (destPos.y < r.top && r.left < destPos.x && destPos.x < r.right)
		return Common::Point(destPos.x, r.top);
	else if (destPos.y > r.bottom && r.left < destPos.x && destPos.x < r.right)
		return Common::Point(destPos.x, r.bottom);
	else if (destPos.x < r.left && r.top < destPos.y && destPos.y < r.bottom)
		return Common::Point(r.left, destPos.y);
	else if (destPos.x > r.right && r.top < destPos.y && destPos.y < r.bottom)
		return Common::Point(r.right, destPos.y);

	// Find which corner of the zone the point is closet to
	if (destPos.x <= r.left) {
		return Common::Point(r.left, (destPos.y <= r.top) ? r.top : r.bottom);
	} else {
		return Common::Point(r.right, (destPos.y <= r.top) ? r.top : r.bottom);
	}
}


} // End of namespace Tattoo

} // End of namespace Sherlock
