/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

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

namespace Sherlock {

namespace Tattoo {

static const uint8 DIRECTION_CONVERSION[] = {
	WALK_RIGHT, WALK_DOWN, WALK_LEFT, WALK_UP, STOP_RIGHT, STOP_DOWN, STOP_LEFT, STOP_UP,
	WALK_UPRIGHT, WALK_DOWNRIGHT, WALK_UPLEFT, WALK_DOWNLEFT, STOP_UPRIGHT, STOP_UPLEFT,
	STOP_DOWNRIGHT, STOP_DOWNLEFT
};

const byte TATTOO_OPCODES[] = {
	170,	// OP_SWITCH_SPEAKER
	171,	// OP_RUN_CANIMATION
	0,	// OP_ASSIGN_PORTRAIT_LOCATION
	173,	// OP_PAUSE
	0,	// OP_REMOVE_PORTRAIT
	0,	// OP_CLEAR_WINDOW
	176,	// OP_ADJUST_OBJ_SEQUENCE
	177,	// OP_WALK_HOlMES_TO_COORDS
	178,	// OP_PAUSE_WITHOUT_CONTROL
	179,	// OP_BANISH_WINDOW
	0,		// OP_SUMMON_WINDOW
	181,	// OP_SET_FLAG
	0,		// OP_SFX_COMMAND
	183,	// OP_TOGGLE_OBJECT
	184,	// OP_STEALTH_MODE_ACTIVE
	0,		// OP_IF_STATEMENT
	0,		// OP_ELSE_STATEMENT
	0,		// OP_END_IF_STATEMENT
	188,	// OP_STEALTH_MODE_DEACTIVATE
	189,	// OP_TURN_HOLMES_OFF
	190,	// OP_TURN_HOLMES_ON
	191,	// OP_GOTO_SCENE
	0,		// OP_PLAY_PROLOGUE
	193,	// OP_ADD_ITEM_TO_INVENTORY
	194,	// OP_SET_OBJECT
	172,	// OP_CALL_TALK_FILE
	0,		// OP_MOVE_MOUSE
	0,		// OP_DISPLAY_INFO_LINE
	0,		// OP_CLEAR_INFO_LINE
	199,	// OP_WALK_TO_CANIMATION
	200,	// OP_REMOVE_ITEM_FROM_INVENTORY
	201,	// OP_ENABLE_END_KEY
	202,	// OP_DISABLE_END_KEY
	203,	// OP_END_TEXT_WINDOW
	174,	// OP_MOUSE_ON_OFF
	175,	// OP_SET_WALK_CONTROL
	180,	// OP_SET_TALK_SEQUENCE
	182,	// OP_PLAY_SONG
	187,	// OP_WALK_HOLMES_AND_NPC_TO_CANIM
	192,	// OP_SET_NPC_PATH_DEST
	195,	// OP_NEXT_SONG
	196,	// OP_SET_NPC_PATH_PAUSE
	197,	// OP_PASSWORD
	198,	// OP_SET_SCENE_ENTRY_FLAG
	185,	// OP_WALK_NPC_TO_CANIM
	186,	// OP_WALK_NPC_TO_COORDS
	204,	// OP_WALK_HOLMES_AND_NPC_TO_COORDS
	205,	// OP_SET_NPC_TALK_FILE
	206,	// OP_TURN_NPC_OFF
	207,	// OP_TURN_NPC_ON
	208,	// OP_NPC_DESC_ON_OFF
	209,	// OP_NPC_PATH_PAUSE_TAKING_NOTES
	210,	// OP_NPC_PATH_PAUSE_LOOKING_HOLMES
	211,	// OP_ENABLE_TALK_INTERRUPTS
	212,	// OP_DISABLE_TALK_INTERRUPTS
	213,	// OP_SET_NPC_INFO_LINE
	214,	// OP_SET_NPC_POSITION
	215,	// OP_NPC_PATH_LABEL
	216,	// OP_PATH_GOTO_LABEL
	217,	// OP_PATH_IF_FLAG_GOTO_LABEL
	218,	// OP_NPC_WALK_GRAPHICS
	220,	// OP_NPC_VERB
	221,	// OP_NPC_VERB_CANIM
	222,	// OP_NPC_VERB_SCRIPT
	224,	// OP_RESTORE_PEOPLE_SEQUENCE
	226,	// OP_NPC_VERB_TARGET
	227,	// OP_TURN_SOUNDS_OFF
	225		// OP_NULL
};

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

TattooTalk::TattooTalk(SherlockEngine *vm) : Talk(vm), _talkWidget(vm), _passwordWidget(vm) {
	static OpcodeMethod OPCODE_METHODS[] = {
		(OpcodeMethod)&TattooTalk::cmdSwitchSpeaker,

		(OpcodeMethod)&TattooTalk::cmdRunCAnimation,
		(OpcodeMethod)&TattooTalk::cmdCallTalkFile,
		(OpcodeMethod)&TattooTalk::cmdPause,
		(OpcodeMethod)&TattooTalk::cmdMouseOnOff,
		(OpcodeMethod)&TattooTalk::cmdSetWalkControl,
		(OpcodeMethod)&TattooTalk::cmdAdjustObjectSequence,
		(OpcodeMethod)&TattooTalk::cmdWalkHolmesToCoords,
		(OpcodeMethod)&TattooTalk::cmdPauseWithoutControl,
		(OpcodeMethod)&TattooTalk::cmdBanishWindow,
		(OpcodeMethod)&TattooTalk::cmdSetTalkSequence,

		(OpcodeMethod)&TattooTalk::cmdSetFlag,
		(OpcodeMethod)&TattooTalk::cmdPlaySong,
		(OpcodeMethod)&TattooTalk::cmdToggleObject,
		(OpcodeMethod)&TattooTalk::cmdStealthModeActivate,
		(OpcodeMethod)&TattooTalk::cmdWalkNPCToCAnimation,
		(OpcodeMethod)&TattooTalk::cmdWalkNPCToCoords,
		(OpcodeMethod)&TattooTalk::cmdWalkHomesAndNPCToCoords,
		(OpcodeMethod)&TattooTalk::cmdStealthModeDeactivate,
		(OpcodeMethod)&TattooTalk::cmdHolmesOff,
		(OpcodeMethod)&TattooTalk::cmdHolmesOn,

		(OpcodeMethod)&TattooTalk::cmdGotoScene,
		(OpcodeMethod)&TattooTalk::cmdSetNPCPathDest,
		(OpcodeMethod)&TattooTalk::cmdAddItemToInventory,
		(OpcodeMethod)&TattooTalk::cmdSetObject,
		(OpcodeMethod)&TattooTalk::cmdNextSong,
		(OpcodeMethod)&TattooTalk::cmdSetNPCPathPause,
		(OpcodeMethod)&TattooTalk::cmdPassword,
		(OpcodeMethod)&TattooTalk::cmdSetSceneEntryFlag,
		(OpcodeMethod)&TattooTalk::cmdWalkToCAnimation,
		(OpcodeMethod)&TattooTalk::cmdRemoveItemFromInventory,

		(OpcodeMethod)&TattooTalk::cmdEnableEndKey,
		(OpcodeMethod)&TattooTalk::cmdDisableEndKey,
		(OpcodeMethod)&TattooTalk::cmdEndTextWindow,
		(OpcodeMethod)&TattooTalk::cmdWalkHomesAndNPCToCoords,
		(OpcodeMethod)&TattooTalk::cmdSetNPCTalkFile,
		(OpcodeMethod)&TattooTalk::cmdSetNPCOff,
		(OpcodeMethod)&TattooTalk::cmdSetNPCOn,
		(OpcodeMethod)&TattooTalk::cmdSetNPCDescOnOff,
		(OpcodeMethod)&TattooTalk::cmdSetNPCPathPauseTakingNotes,
		(OpcodeMethod)&TattooTalk::cmdSetNPCPathPauseLookingHolmes,

		(OpcodeMethod)&TattooTalk::cmdTalkInterruptsEnable,
		(OpcodeMethod)&TattooTalk::cmdTalkInterruptsDisable,
		(OpcodeMethod)&TattooTalk::cmdSetNPCInfoLine,
		(OpcodeMethod)&TattooTalk::cmdSetNPCPosition,
		(OpcodeMethod)&TattooTalk::cmdNPCLabelSet,
		(OpcodeMethod)&TattooTalk::cmdNPCLabelGoto,
		(OpcodeMethod)&TattooTalk::cmdNPCLabelIfFlagGoto,
		(OpcodeMethod)&TattooTalk::cmdSetNPCWalkGraphics,
		nullptr,
		(OpcodeMethod)&TattooTalk::cmdSetNPCVerb,

		(OpcodeMethod)&TattooTalk::cmdSetNPCVerbCAnimation,
		(OpcodeMethod)&TattooTalk::cmdSetNPCVerbScript,
		nullptr,
		(OpcodeMethod)&TattooTalk::cmdRestorePeopleSequence,
		nullptr,
		(OpcodeMethod)&TattooTalk::cmdSetNPCVerbTarget,
		(OpcodeMethod)&TattooTalk::cmdTurnSoundsOff
	};

	_opcodes = TATTOO_OPCODES;
	_opcodeTable = OPCODE_METHODS;
}

void TattooTalk::talkTo(const Common::String &filename) {
	Events &events = *_vm->_events;
	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;

	// WORKAROUND: Keep wait cursor active until very end of the cutscene of the monkey
	// stealing the cap, which is finished by calling the 30cuend script
	if (filename == "wilb29a")
		events.incWaitCounter();

	Talk::talkTo(filename);

	if (filename == "wilb29a")
		ui._menuMode = TALK_MODE;
	if (filename == "30cuend") {
		events.decWaitCounter();
		events.setCursor(ARROW);
	}
}

void TattooTalk::talkInterface(const byte *&str) {
	TattooEngine &vm = *(TattooEngine *)_vm;
	Sound &sound = *_vm->_sound;
	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
	const byte *s = str;

	// Move to past the end of the text string
	_wait = 1;
	_charCount = 0;
	while ((*str < TATTOO_OPCODES[0] || *str == TATTOO_OPCODES[OP_NULL]) && *str) {
		++_charCount;
		++str;
	}

	// If speech is on, and text windows (subtitles) are off, then don't show the text window
	if (!vm._textWindowsOn && sound._speechOn && _speaker != -1)
		return;

	// Display the text window
	ui.banishWindow();
	ui._textWidget.load(Common::String((const char *)s, (const char *)str), _speaker);
	ui._textWidget.summonWindow();
}

void TattooTalk::nothingToSay() {
	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
	ui.putMessage("%s", FIXED(NothingToSay));
}

void TattooTalk::showTalk() {
	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;

	_talkWidget.load();
	_talkWidget.summonWindow();
	_talkWidget.refresh();

	if (ui._menuMode != MESSAGE_MODE)
		ui._menuMode = TALK_MODE;
}

OpcodeReturn TattooTalk::cmdSwitchSpeaker(const byte *&str) {
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	Screen &screen = *_vm->_screen;
	UserInterface &ui = *_vm->_ui;

	if (_talkToAbort)
		return RET_EXIT;

	ui.clearWindow();

	_yp = screen.fontHeight() + 11;
	_charCount = _line = 0;

	people.setListenSequence(_speaker, 129);
	_speaker = *++str - 1;
	++str;

	people.setTalkSequence(_speaker, 1);

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdMouseOnOff(const byte *&str) {
	Events &events = *_vm->_events;
	bool mouseOn = *++str == 2;
	if (mouseOn)
		events.showCursor();
	else
		events.hideCursor();
	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdWalkHolmesToCoords(const byte *&str) {
	People &people = *_vm->_people;
	++str;

	int xp = (str[0] - 1) * 256 + str[1] - 1;
	if (xp > 16384)
		// Negative X
		xp = -1 * (xp - 16384);
	int yp = (str[2] - 1) * 256 + str[3] - 1;

	people[HOLMES].walkToCoords(Point32(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER),
		DIRECTION_CONVERSION[str[4] - 1]);

	if (_talkToAbort)
		return RET_EXIT;

	str += 4;
	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdGotoScene(const byte *&str) {
	Map &map = *_vm->_map;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	Scene &scene = *_vm->_scene;
	scene._goToScene = str[1] - 1;

	if (scene._goToScene != OVERHEAD_MAP) {
		// Not going to the map overview
		map._oldCharPoint = scene._goToScene;

		// Run a canimation?
		if (str[2] > 100) {
			people._savedPos = PositionFacing(160, 100, str[2]);
		} else {
			int posX = (str[3] - 1) * 256 + str[4] - 1;
			if (posX > 16384)
				posX = -1 * (posX - 16384);
			int posY = (str[5] - 1) * 256 + str[6] - 1;
			people._savedPos = PositionFacing(posX, posY, str[2] - 1);
		}

		_scriptMoreFlag = 1;
	}

	str += 7;
	if (scene._goToScene != OVERHEAD_MAP)
		_scriptSaveIndex = str - _scriptStart;

	_endStr = true;
	_wait = 0;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdNextSong(const byte *&str) {
	Music &music = *_vm->_music;

	// Get the name of the next song to play
	++str;
	music._nextSongName = "";
	for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx)
		music._nextSongName += str[idx];
	str += 7;

	// WORKAROUND: Original game set wrong music name at the end of the introduction sequence
	if (_scriptName == "prol80p" && music._nextSongName == "default")
		music._nextSongName = "01cue90";

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdNPCLabelGoto(const byte *&str) {
	int npcNum = *++str;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];

	if (person._resetNPCPath) {
		person._npcIndex = person._npcPause = 0;
		person._resetNPCPath = false;
		memset(person._npcPath, 0, 100);
	}

	person._npcPath[person._npcIndex] = 8;
	person._npcPath[person._npcIndex + 1] = str[1];
	person._npcIndex += 2;
	str++;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdNPCLabelIfFlagGoto(const byte *&str) {
	int npcNum = *++str;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];

	if (person._resetNPCPath) {
		person._npcIndex = person._npcPause = 0;
		person._resetNPCPath = false;
		memset(person._npcPath, 0, 100);
	}

	person._npcPath[person._npcIndex] = 9;
	for (int i = 1; i <= 3; i++)
		person._npcPath[person._npcIndex + i] = str[i];

	person._npcIndex += 4;
	str += 3;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdNPCLabelSet(const byte *&str) {
	int npcNum = *++str;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];

	if (person._resetNPCPath) {
		person._npcIndex = person._npcPause = 0;
		person._resetNPCPath = false;
		memset(person._npcPath, 0, 100);
	}

	person._npcPath[person._npcIndex] = 7;
	person._npcPath[person._npcIndex + 1] = str[1];
	person._npcIndex += 2;
	str++;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdPassword(const byte *&str) {
	_vm->_ui->clearWindow();
	_passwordWidget.show();
	return RET_EXIT;
}

OpcodeReturn TattooTalk::cmdPlaySong(const byte *&str) {
	Music &music = *_vm->_music;
	Common::String currentSong = music._currentSongName;

	// Get the name of the song to play
	music._currentSongName = "";
	str++;
	for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx)
		music._currentSongName += str[idx];
	str += 7;

	// Play the song
	music.loadSong(music._currentSongName);

	// Copy the old song name to _nextSongName so that when the new song is finished, the old song will restart
	music._nextSongName = currentSong;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdRestorePeopleSequence(const byte *&str) {
	int npcNum = *++str - 1;
	// WORKAROUND: Fix script error talking to woman in Tailor shop
	if (npcNum == 111 && _vm->getLanguage() == Common::ES_ESP)
		npcNum = 5;

	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];
	person._misc = 0;

	if (person._seqTo) {
		person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo;
		person._seqTo = 0;
	}
	person._sequenceNumber = person._savedNpcSequence;
	person._frameNumber = person._savedNpcFrame;
	person.checkWalkGraphics();

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCDescOnOff(const byte *&str) {
	int npcNum = *++str;
	++str;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	Person &person = people[npcNum];

	// Copy over the NPC examine text until we reach a stop marker, which is
	// the same as a start marker, or we reach the end of the file
	person._examine = "";
	while (*str && *str != _opcodes[OP_NPC_DESC_ON_OFF])
		person._examine += *str++;

	// Move past any leftover text till we reach a stop marker
	while (*str && *str != _opcodes[OP_NPC_DESC_ON_OFF])
		str++;

	if (!*str)
		// Reached end of file, so decrement pointer so outer loop will terminate on NULL
		--str;
	else
		// Move past the ending OP_NPC_DEST_ON_OFF opcode
		++str;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCInfoLine(const byte *&str) {
	int npcNum = *++str;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];

	person._description = "";
	int len = *++str;
	for (int idx = 0; idx < len; ++idx)
		person._description += str[idx + 1];

	str += len;
	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCOff(const byte *&str) {
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	int npcNum = *++str;
	people[npcNum]._type = REMOVE;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCOn(const byte *&str) {
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	int npcNum = *++str;
	people[npcNum]._type = CHARACTER;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCPathDest(const byte *&str) {
	int npcNum = *++str;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];

	if (person._resetNPCPath) {
		person._npcIndex = person._npcPause = 0;
		person._resetNPCPath = false;
		memset(person._npcPath, 0, 100);
	}

	person._npcPath[person._npcIndex] = 1;
	for (int i = 1; i <= 4; i++)
		person._npcPath[person._npcIndex + i] = str[i];
	person._npcPath[person._npcIndex + 5] = DIRECTION_CONVERSION[str[5] - 1] + 1;

	person._npcIndex += 6;
	str += 5;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCPathPause(const byte *&str) {
	int npcNum = *++str;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];

	if (person._resetNPCPath) {
		person._npcIndex = person._npcPause = 0;
		person._resetNPCPath = false;
		memset(person._npcPath, 0, 100);
	}

	person._npcPath[person._npcIndex] = 2;
	for (int i = 1; i <= 2; i++)
		person._npcPath[person._npcIndex + i] = str[i];

	person._npcIndex += 3;
	str += 2;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCPathPauseTakingNotes(const byte *&str) {
	int npcNum = *++str;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];

	if (person._resetNPCPath) {
		person._npcIndex = person._npcPause = 0;
		person._resetNPCPath = false;
		memset(person._npcPath, 0, 100);
	}

	person._npcPath[person._npcIndex] = 5;
	for (int i = 1; i <= 2; i++)
		person._npcPath[person._npcIndex + i] = str[i];

	person._npcIndex += 3;
	str += 2;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCPathPauseLookingHolmes(const byte *&str) {
	int npcNum = *++str;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];

	if (person._resetNPCPath) {
		person._npcIndex = person._npcPause = 0;
		person._resetNPCPath = false;
		memset(person._npcPath, 0, 100);
	}

	person._npcPath[person._npcIndex] = 6;
	for (int i = 1; i <= 2; i++)
		person._npcPath[person._npcIndex + i] = str[i];

	person._npcIndex += 3;
	str += 2;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCPosition(const byte *&str) {
	int npcNum = *++str - 1;
	++str;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];
	int posX = (str[0] - 1) * 256 + str[1] - 1;
	if (posX > 16384)
		posX = -1 * (posX - 16384);
	int posY = (str[2] - 1) * 256 + str[3] - 1;

	person._position = Point32(posX * FIXED_INT_MULTIPLIER, posY * FIXED_INT_MULTIPLIER);
	if (person._seqTo && person._walkLoaded) {
		person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo;
		person._seqTo = 0;
	}

	assert(str[4] - 1 < 16);
	person._sequenceNumber = DIRECTION_CONVERSION[str[4] - 1];
	person._frameNumber = 0;

	if (person._walkLoaded)
		person.checkWalkGraphics();

	if (person._walkLoaded && person._type == CHARACTER &&
		person._sequenceNumber >= STOP_UP && person._sequenceNumber <= STOP_UPLEFT) {
		bool done = false;
		do {
			person.checkSprite();
			for (int x = 0; x < person._frameNumber; x++) {
				if (person._walkSequences[person._sequenceNumber]._sequences[x] == 0) {
					done = true;
					break;
				}
			}
		} while (!done);
	}

	str += 4;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCTalkFile(const byte *&str) {
	int npcNum = *++str;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];

	if (person._resetNPCPath) {
		person._npcIndex = person._npcPause = 0;
		person._resetNPCPath = false;
		memset(person._npcPath, 0, 100);
	}

	person._npcPath[person._npcIndex] = NPCPATH_SET_TALK_FILE;
	for (int i = 1; i <= 8; i++)
		person._npcPath[person._npcIndex + i] = str[i];

	person._npcIndex += 9;
	str += 8;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCVerb(const byte *&str) {
	int npcNum = *++str;
	int verbNum = *++str - 1;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	Common::String &verb = people[npcNum]._use[verbNum]._verb;

	// Get the verb name
	verb = "";
	for (int idx = 0; idx < 12 && str[idx + 1] != '~'; ++idx)
		verb += str[idx + 1];

	// Strip off any trailing whitespace
	while (verb.hasSuffix(" "))
		verb.deleteLastChar();

	str += 12;
	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCVerbCAnimation(const byte *&str) {
	int npcNum = *++str;
	int verbNum = *++str - 1;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	UseType &useType = people[npcNum]._use[verbNum];

	useType._cAnimNum = (str[1] - 1) & 127;
	useType._cAnimSpeed = 1 + 128 * (str[1] >= 128);
	str++;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCVerbScript(const byte *&str) {
	int npcNum = *++str;
	int verbNum = *++str - 1;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	UseType &useType = people[npcNum]._use[verbNum];

	Common::String &name = useType._names[0];
	name = "*C";

	for (int idx = 0; idx < 8 && str[idx + 1] != '~'; ++idx)
		name += str[idx + 1];

	useType._cAnimNum = 99;
	useType._cAnimSpeed = 1;
	str += 8;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCVerbTarget(const byte *&str) {
	int npcNum = *++str;
	int verbNum = *++str - 1;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	Common::String &target = people[npcNum]._use[verbNum]._target;

	target = "";
	for (int idx = 0; idx < 12 && str[idx + 1] != '~'; ++idx)
		target += str[idx + 1];

	while (target.hasSuffix(" "))
		target.deleteLastChar();

	str += 12;
	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetNPCWalkGraphics(const byte *&str) {
	int npcNum = *++str - 1;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	Person &person = people[npcNum];

	// Build up walk library name for the given NPC
	Common::String walkVGSName;
	for (int idx = 0; idx < 8 && str[idx + 1] != '~'; ++idx)
		walkVGSName += str[idx + 1];

	walkVGSName += ".VGS";
	person._walkVGSName = Common::Path(walkVGSName);
	people._forceWalkReload = true;
	str += 8;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetSceneEntryFlag(const byte *&str) {
	TattooScene &scene = *(TattooScene *)_vm->_scene;
	++str;
	int flag = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1);

	int flag1 = flag & 16383;
	if (flag > 16383)
		flag1 *= -1;

	str += 2;

	// Make sure that this instance is not already being tracked
	bool found = false;
	for (uint idx = 0; idx < scene._sceneTripCounters.size() && !found; ++idx) {
		SceneTripEntry &entry = scene._sceneTripCounters[idx];
		if (entry._flag == flag1 && entry._sceneNumber == str[0] - 1)
			found = true;
	}

	// Only add it if it's not being tracked already
	if (!found)
		scene._sceneTripCounters.push_back(SceneTripEntry(flag1, str[0] - 1, str[1] - 1));

	++str;
	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetTalkSequence(const byte *&str) {
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	int speaker = str[1] - 1;
	int sequenceNumber = str[2];

	if (sequenceNumber < 128)
		people.setTalkSequence(speaker, sequenceNumber);
	else
		people.setListenSequence(speaker, sequenceNumber);

	str += 2;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdSetWalkControl(const byte *&str) {
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	++str;
	people._walkControl = str[0] - 1;

	return RET_SUCCESS;
}

// Dummy opcode
OpcodeReturn TattooTalk::cmdTalkInterruptsDisable(const byte *&str) { error("Dummy opcode cmdTalkInterruptsDisable called"); }

// Dummy opcode
OpcodeReturn TattooTalk::cmdTalkInterruptsEnable(const byte *&str) { error("Dummy opcode cmdTalkInterruptsEnable called"); }

OpcodeReturn TattooTalk::cmdTurnSoundsOff(const byte *&str) {
	_vm->_sound->stopSound();
	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdWalkHolmesAndNPCToCAnimation(const byte *&str) {
	int npcNum = *++str;
	int cAnimNum = *++str;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];
	Scene &scene = *_vm->_scene;
	CAnim &anim = scene._cAnim[cAnimNum];

	if (person._pathStack.empty())
		person.pushNPCPath();
	person._npcMoved = true;

	person.walkToCoords(anim._goto[1], anim._goto[1]._facing);

	if (_talkToAbort)
		return RET_EXIT;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdWalkNPCToCAnimation(const byte *&str) {
	int npcNum = *++str;
	int cAnimNum = *++str;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];
	Scene &scene = *_vm->_scene;
	CAnim &anim = scene._cAnim[cAnimNum];

	if (person._pathStack.empty())
		person.pushNPCPath();
	person._npcMoved = true;

	person.walkToCoords(anim._goto[1], anim._goto[1]._facing);

	if (_talkToAbort)
		return RET_EXIT;

	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdWalkNPCToCoords(const byte *&str) {
	int npcNum = *++str;
	str++;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];

	if (person._pathStack.empty())
		person.pushNPCPath();
	person._npcMoved = true;

	int xp = (str[0] - 1) * 256 + str[1] - 1;
	if (xp > 16384)
		xp = -1 * (xp - 16384);
	int yp = (str[2] - 1) * 256 + str[3] - 1;

	person.walkToCoords(Point32(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER),
		DIRECTION_CONVERSION[str[4] - 1]);
	if (_talkToAbort)
		return RET_EXIT;

	str += 4;
	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdWalkHomesAndNPCToCoords(const byte *&str) {
	int npcNum = *++str;
	str++;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooPerson &person = people[npcNum];

	if (person._pathStack.empty())
		person.pushNPCPath();
	person._npcMoved = true;

	// Get destination position and facing for Holmes
	int xp = (str[0] - 1) * 256 + str[1] - 1;
	if (xp > 16384)
		xp = -1 * (xp - 16384);
	int yp = (str[2] - 1) * 256 + str[3] - 1;
	PositionFacing holmesDest(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER, DIRECTION_CONVERSION[str[4] - 1]);

	// Get destination position and facing for specified NPC
	xp = (str[5] - 1) * 256 + str[6] - 1;
	if (xp > 16384)
		xp = -1 * (xp - 16384);
	yp = (str[7] - 1) * 256 + str[8] - 1;
	PositionFacing npcDest(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER, DIRECTION_CONVERSION[str[9] - 1]);

	person.walkBothToCoords(holmesDest, npcDest);

	if (_talkToAbort)
		return RET_EXIT;

	str += 9;
	return RET_SUCCESS;
}

OpcodeReturn TattooTalk::cmdCallTalkFile(const byte *&str) {
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	Common::String tempString;

	int npc = *++str;
	assert(npc >= 1 && npc < MAX_CHARACTERS);
	TattooPerson &person = people[npc];

	if (person._resetNPCPath) {
		person._npcIndex = person._npcPause = 0;
		person._resetNPCPath = false;
		Common::fill(&person._npcPath[0], &person._npcPath[100], 0);
	}

	// Set the path control code and copy the filename
	person._npcPath[person._npcIndex] = 4;
	for (int idx = 1; idx <= 8; ++idx)
		person._npcPath[person._npcIndex + idx] = str[idx];

	person._npcIndex += 9;
	str += 8;

	return RET_SUCCESS;
}

void TattooTalk::pushSequenceEntry(Object *obj) {
	// Check if the shape is already on the stack
	for (uint idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) {
		if (_sequenceStack[idx]._obj == obj)
			return;
	}

	// Find a free slot and save the details in it
	for (uint idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) {
		SequenceEntry &seq = _sequenceStack[idx];
		if (seq._obj == nullptr) {
			seq._obj = obj;
			seq._frameNumber = obj->_frameNumber;
			seq._sequenceNumber = obj->_sequenceNumber;
			seq._seqStack = obj->_seqStack;
			seq._seqTo = obj->_seqTo;
			seq._seqCounter = obj->_seqCounter;
			seq._seqCounter2 = obj->_seqCounter2;
			return;
		}
	}

	error("Ran out of talk sequence stack space");
}

void TattooTalk::pullSequence(int slot) {
	People &people = *_vm->_people;

	for (int idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) {
		SequenceEntry &seq = _sequenceStack[idx];
		if (slot != -1 && idx != slot)
			continue;

		// Check for an entry in this slot
		if (seq._obj) {
			Object &o = *seq._obj;

			// See if we're not supposed to restore it until an Allow Talk Interrupt
			if (slot == -1 && seq._obj->hasAborts()) {
				seq._obj->_gotoSeq = -1;
				seq._obj->_restoreSlot = idx;
			} else {
				// Restore the object's sequence information immediately
				o._frameNumber = seq._frameNumber;
				o._sequenceNumber = seq._sequenceNumber;
				o._seqStack = seq._seqStack;
				o._seqTo = seq._seqTo;
				o._seqCounter = seq._seqCounter;
				o._seqCounter2 = seq._seqCounter2;
				o._gotoSeq = 0;
				o._talkSeq = 0;

				// Flag the slot as free again
				seq._obj = nullptr;
			}
		}
	}

	// Handle restoring any character positioning
	for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
		Person &person = people[idx];

		if (person._type == CHARACTER && !person._walkSequences.empty() && person._sequenceNumber >= TALK_UPRIGHT
				&& person._sequenceNumber <= LISTEN_UPLEFT) {
			person.gotoStand();

			bool done = false;
			do {
				person.checkSprite();
				for (int frameNum = 0; frameNum < person._frameNumber; ++frameNum) {
					if (person._walkSequences[person._sequenceNumber]._sequences[frameNum] == 0)
						done = true;
				}
			} while (!done);
		}
	}
}

bool TattooTalk::isSequencesEmpty() const {
	for (int idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) {
		if (_sequenceStack[idx]._obj)
			return false;
	}

	return true;
}

void TattooTalk::clearSequences() {
	for (int idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) {
		_sequenceStack[idx]._obj = nullptr;
	}
}

} // End of namespace Tattoo

} // End of namespace Sherlock
