/* Copyright (C) 1994-2004 Revolution Software Ltd
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header: /cvsroot/scummvm/scummvm/sword2/speech.cpp,v 1.67 2004/11/14 15:00:01 eriktorbjorn Exp $
 */

#include "common/stdafx.h"
#include "common/file.h"

#include "sword2/sword2.h"
#include "sword2/console.h"
#include "sword2/controls.h"
#include "sword2/defs.h"
#include "sword2/interpreter.h"
#include "sword2/logic.h"
#include "sword2/maketext.h"
#include "sword2/memory.h"
#include "sword2/resman.h"
#include "sword2/driver/d_draw.h"
#include "sword2/driver/d_sound.h"

namespace Sword2 {

int32 Logic::fnAddSubject(int32 *params) {
	// params:	0 id
	//		1 daves reference number

	if (_scriptVars[IN_SUBJECT] == 0) {
		// This is the start of the new subject list. Set the default
		// repsonse id to zero in case we're never passed one.
		_defaultResponseId = 0;
	}

	if (params[0] == -1) {
		// Id -1 is used for setting the default response, i.e. the
		// response when someone uses an object on a person and he
		// doesn't know anything about it. See fnChoose() below.

		_defaultResponseId = params[1];
	} else {
		debug(5, "fnAddSubject res %d, uid %d", params[0], params[1]);
		_subjectList[_scriptVars[IN_SUBJECT]].res = params[0];
		_subjectList[_scriptVars[IN_SUBJECT]].ref = params[1];
		_scriptVars[IN_SUBJECT]++;
	}

	return IR_CONT;
}

int32 Logic::fnChoose(int32 *params) {
	// params:	none

	// This opcode is used to open the conversation menu. The human is
	// switched off so there will be no normal mouse engine.

	// The player's choice is piggy-backed on the standard opcode return
	// values, to be used with the CP_JUMP_ON_RETURNED opcode. As far as I
	// can tell, this is the only function that uses that feature.

	uint i;

	_scriptVars[AUTO_SELECTED] = 0;

	if (_scriptVars[OBJECT_HELD]) {
		// The player used an object on a person. In this case it
		// triggered a conversation menu. Act as if the user tried to
		// talk to the person about that object. If the person doesn't
		// know anything about it, use the default response.

		uint32 response = _defaultResponseId;

		for (i = 0; i < _scriptVars[IN_SUBJECT]; i++) {
			if (_subjectList[i].res == _scriptVars[OBJECT_HELD]) {
				response = _subjectList[i].ref;
				break;
			}
		}

		// The user won't be holding the object any more, and the
		// conversation menu will be closed.

		_scriptVars[OBJECT_HELD] = 0;
		_scriptVars[IN_SUBJECT] = 0;
		return IR_CONT | (response << 3);
	}

	if (_scriptVars[CHOOSER_COUNT_FLAG] == 0 && _scriptVars[IN_SUBJECT] == 1 && _subjectList[0].res == EXIT_ICON) {
		// This is the first time the chooser is coming up in this
		// conversation, there is only one subject and that's the
		// EXIT icon.
		//
		// In other words, the player doesn't have anything to talk
		// about. Skip it.

		// The conversation menu will be closed. We set AUTO_SELECTED
		// because the speech script depends on it.

		_scriptVars[AUTO_SELECTED] = 1;
		_scriptVars[IN_SUBJECT] = 0;
		return IR_CONT | (_subjectList[0].ref << 3);
	}

	byte *icon;

	if (!_choosing) {
		// This is a new conversation menu.

		if (!_scriptVars[IN_SUBJECT])
			error("fnChoose with no subjects");

		for (i = 0; i < _scriptVars[IN_SUBJECT]; i++) {
			icon = _vm->_resman->openResource(_subjectList[i].res) + sizeof(StandardHeader) + RDMENU_ICONWIDE * RDMENU_ICONDEEP;
			_vm->_graphics->setMenuIcon(RDMENU_BOTTOM, i, icon);
			_vm->_resman->closeResource(_subjectList[i].res);
		}

		for (; i < 15; i++)
			_vm->_graphics->setMenuIcon(RDMENU_BOTTOM, (uint8) i, NULL);

		_vm->_graphics->showMenu(RDMENU_BOTTOM);
		_vm->setMouse(NORMAL_MOUSE_ID);
		_choosing = true;
		return IR_REPEAT;
	}

	// The menu is there - we're just waiting for a click. We only care
	// about left clicks.

	MouseEvent *me = _vm->mouseEvent();

	if (!me || !(me->buttons & RD_LEFTBUTTONDOWN) || _vm->_mouseY < 400)
		return IR_REPEAT;

	// Check for click on a menu.

	int hit = _vm->menuClick(_scriptVars[IN_SUBJECT]);
	if (hit < 0)
		return IR_REPEAT;

	// Hilight the clicked icon by greying the others.

	for (i = 0; i < _scriptVars[IN_SUBJECT]; i++) {
		if ((int) i != hit) {
			icon = _vm->_resman->openResource(_subjectList[i].res) + sizeof(StandardHeader);
			_vm->_graphics->setMenuIcon(RDMENU_BOTTOM, i, icon);
			_vm->_resman->closeResource(_subjectList[i].res);
		}
	}

	// For non-speech scripts that manually call the chooser
	_scriptVars[RESULT] = _subjectList[hit].res;

	// The conversation menu will be closed

	_choosing = false;
	_scriptVars[IN_SUBJECT] = 0;
	_vm->setMouse(0);

	return IR_CONT | (_subjectList[hit].ref << 3);
}

/**
 * Start a conversation.
 *
 * Note that fnStartConversation() might accidentally be called every time the
 * script loops back for another chooser, but we only want to reset the chooser
 * count flag the first time this function is called, i.e. when the talk flag
 * is zero.
 */

int32 Logic::fnStartConversation(int32 *params) {
	// params:	none

	if (_scriptVars[TALK_FLAG] == 0) {
		// See fnChooser & speech scripts
		_scriptVars[CHOOSER_COUNT_FLAG] = 0;
	}

	fnNoHuman(params);
	return IR_CONT;
}

/**
 * End a conversation.
 */

int32 Logic::fnEndConversation(int32 *params) {
	// params:	none

	_vm->_graphics->hideMenu(RDMENU_BOTTOM);

	if (_vm->_mouseY > 399) {
		// Will wait for cursor to move off the bottom menu
		_vm->_mouseMode = MOUSE_holding;
	}

	// In case DC forgets
	_scriptVars[TALK_FLAG] = 0;

	return IR_CONT;
}

// To request the status of a target, we run its 4th script, get-speech-state.
// This will cause RESULT to be set to either 1 (target is waiting) or 0
// (target is busy).

/**
 * Wait for a target to become waiting, i.e. not busy, then send a command to
 * it.
 */

int32 Logic::fnTheyDo(int32 *params) {
	// params:	0 target
	//		1 command
	//		2 ins1
	//		3 ins2
	//		4 ins3
	//		5 ins4
	//		6 ins5

	StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[0]);
	assert (head->fileType == GAME_OBJECT);

	// Run the target's get-speech-state script

	int32 target = params[0];
	char *raw_script_ad = (char *) head;
	uint32 null_pc = 5;

	runScript(raw_script_ad, raw_script_ad, &null_pc);

	_vm->_resman->closeResource(target);

	if (_scriptVars[RESULT] == 1 && !_scriptVars[INS_COMMAND]) {
		// The target is waiting, i.e. not busy, and there is no other
		// command queued. Send the command.

		debug(5, "fnTheyDo: sending command to %d", target);

		_vm->_debugger->_speechScriptWaiting = 0;

		_scriptVars[SPEECH_ID] = params[0];
		_scriptVars[INS_COMMAND] = params[1];
		_scriptVars[INS1] = params[2];
		_scriptVars[INS2] = params[3];
		_scriptVars[INS3] = params[4];
		_scriptVars[INS4] = params[5];
		_scriptVars[INS5] = params[6];

		return IR_CONT;
	}

	// The target is busy. Come back again next cycle.

	_vm->_debugger->_speechScriptWaiting = target;
	return IR_REPEAT;
}

/**
 * Wait for a target to become waiting, i.e. not busy, send a command to it,
 * then wait for it to finish.
 */

int32 Logic::fnTheyDoWeWait(int32 *params) {
	// params:	0 pointer to ob_logic
	//		1 target
	//		2 command
	//		3 ins1
	//		4 ins2
	//		5 ins3
	//		6 ins4
	//		7 ins5

	StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[1]);
	assert(head->fileType == GAME_OBJECT);

	// Run the target's get-speech-state script

	int32 target = params[1];
	char *raw_script_ad = (char *) head;
	uint32 null_pc = 5;

	runScript(raw_script_ad, raw_script_ad, &null_pc);

	_vm->_resman->closeResource(target);

	ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]);

	if (_scriptVars[RESULT] == 1 && !_scriptVars[INS_COMMAND] && ob_logic->looping == 0) {
		// The target is waiting, i.e. not busy, and there is no other
		// command queued. We haven't sent the command yet, so do it.

		debug(5, "fnTheyDoWeWait: sending command to %d", target);

		_vm->_debugger->_speechScriptWaiting = target;
		ob_logic->looping = 1;

		_scriptVars[SPEECH_ID] = params[1];
		_scriptVars[INS_COMMAND] = params[2];
		_scriptVars[INS1] = params[3];
		_scriptVars[INS2] = params[4];
		_scriptVars[INS3] = params[5];
		_scriptVars[INS4] = params[6];
		_scriptVars[INS5] = params[7];

		return IR_REPEAT;
	}

	if (ob_logic->looping == 0) {
		// The command has not been sent yet. Keep waiting.
		_vm->_debugger->_speechScriptWaiting = target;
		return IR_REPEAT;
	}

	if (_scriptVars[RESULT] == 0) {
		// The command has been sent, and the target is busy doing it.
		// Wait for it to finish.

		debug(5, "fnTheyDoWeWait: Waiting for %d to finish", target);

		_vm->_debugger->_speechScriptWaiting = target;
		return IR_REPEAT;
	}

	debug(5, "fnTheyDoWeWait: %d finished", target);

	ob_logic->looping = 0;
	_vm->_debugger->_speechScriptWaiting = 0;
	return IR_CONT;
}

/**
 * Wait for a target to become waiting, i.e. not busy.
 */

int32 Logic::fnWeWait(int32 *params) {
	// params:	0 target

	StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[0]);
	assert(head->fileType == GAME_OBJECT);

	// Run the target's get-speech-state script

	int32 target = params[0];
	char *raw_script_ad = (char *) head;
	uint32 null_pc = 5;

	runScript(raw_script_ad, raw_script_ad, &null_pc);

	_vm->_resman->closeResource(target);

	if (_scriptVars[RESULT] == 0) {
		// The target is busy. Try again.
		_vm->_debugger->_speechScriptWaiting = target;
		return IR_REPEAT;
	}

	// The target is waiting, i.e. not busy.

	_vm->_debugger->_speechScriptWaiting = 0;
	return IR_CONT;
}

/**
 * Wait for a target to become waiting, i.e. not busy, or until we time out.
 * This is useful when clicking on a target to talk to it, and it doesn't
 * reply. This way, we won't lock up.
 *
 * If the target becomes waiting, RESULT is set to 0. If we time out, RESULT is
 * set to 1.
 */

int32 Logic::fnTimedWait(int32 *params) {
	// params:	0 ob_logic
	//		1 target
	//		2 number of cycles before give up

	StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[1]);
	assert(head->fileType == GAME_OBJECT);

	ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]);

	if (!ob_logic->looping) {
		// This is the first time, so set up the time-out.
		ob_logic->looping = params[2];
	}

	// Run the target's get-speech-state script

	int32 target = params[1];
	char *raw_script_ad = (char *) head;
	uint32 null_pc = 5;

	runScript(raw_script_ad, raw_script_ad, &null_pc);

	_vm->_resman->closeResource(target);

	if (_scriptVars[RESULT] == 1) {
		// The target is waiting, i.e. not busy

		_vm->_debugger->_speechScriptWaiting = 0;

		ob_logic->looping = 0;
		_scriptVars[RESULT] = 0;
		return IR_CONT;
	}

	ob_logic->looping--;

	if (!ob_logic->looping) {
		// Time's up.

		debug(5, "fnTimedWait: Timed out waiting for %d", target);
		_vm->_debugger->_speechScriptWaiting = 0;

		// Clear the event that hasn't been picked up - in theory,
		// none of this should ever happen.

		killAllIdsEvents(target);
		_scriptVars[RESULT] = 1;
		return IR_CONT;
	}

	// Target is busy. Keep trying.

	_vm->_debugger->_speechScriptWaiting = target;
	return IR_REPEAT;
}

enum {
	INS_talk		= 1,
	INS_anim		= 2,
	INS_reverse_anim	= 3,
	INS_walk		= 4,
	INS_turn		= 5,
	INS_face		= 6,
	INS_trace		= 7,
	INS_no_sprite		= 8,
	INS_sort		= 9,
	INS_foreground		= 10,
	INS_background		= 11,
	INS_table_anim		= 12,
	INS_reverse_table_anim	= 13,
	INS_walk_to_anim	= 14,
	INS_set_frame		= 15,
	INS_stand_after_anim	= 16,
	INS_quit		= 42
};

/**
 * Receive and sequence the commands sent from the conversation script. We have
 * to do this in a slightly tweeky manner as we can no longer have generic
 * scripts.
 */

int32 Logic::fnSpeechProcess(int32 *params) {
	// params:	0 pointer to ob_graphic
	//		1 pointer to ob_speech
	//		2 pointer to ob_logic
	//		3 pointer to ob_mega
	//		4 pointer to ob_walkdata

	ObjectSpeech *ob_speech = (ObjectSpeech *) _vm->_memory->decodePtr(params[1]);

	while (1) {
		int32 pars[9];

		// Check which command we're waiting for, and call the
		// appropriate function. Once we're done, clear the command
		// and set wait_state to 1.
		//
		// Note: we could save a var and ditch wait_state and check
		// 'command' for non zero means busy
		//
		// Note: I can't see that we ever check the value of wait_state
		// but perhaps it accesses that memory location directly?

		switch (ob_speech->command) {
		case 0:
			break;
		case INS_talk:
			pars[0] = params[0];		// ob_graphic
			pars[1] = params[1];		// ob_speech
			pars[2] = params[2];		// ob_logic
			pars[3] = params[3];		// ob_mega
			pars[4] = ob_speech->ins1;	// encoded text number
			pars[5] = ob_speech->ins2;	// wav res id
			pars[6] = ob_speech->ins3;	// anim res id
			pars[7] = ob_speech->ins4;	// anim table res id
			pars[8] = ob_speech->ins5;	// animation mode - 0 lip synced, 1 just straight animation

			if (fnISpeak(pars) != IR_REPEAT) {
				ob_speech->command = 0;
				ob_speech->wait_state = 1;
			}

			return IR_REPEAT;
		case INS_turn:
			pars[0] = params[2];		// ob_logic
			pars[1] = params[0];		// ob_graphic
			pars[2] = params[3];		// ob_mega
			pars[3] = params[4];		// ob_walkdata
			pars[4] = ob_speech->ins1;	// direction to turn to

			if (fnTurn(pars) != IR_REPEAT) {
				ob_speech->command = 0;
				ob_speech->wait_state = 1;
			}

			return IR_REPEAT;
		case INS_face:
			pars[0] = params[2];		// ob_logic
			pars[1] = params[0];		// ob_graphic
			pars[2] = params[3];		// ob_mega
			pars[3] = params[4];		// ob_walkdata
			pars[4] = ob_speech->ins1;	// target

			if (fnFaceMega(pars) != IR_REPEAT) {
				ob_speech->command = 0;
				ob_speech->wait_state = 1;
			}

			return IR_REPEAT;
		case INS_anim:
			pars[0] = params[2];		// ob_logic
			pars[1] = params[0];		// ob_graphic
			pars[2] = ob_speech->ins1;	// anim res

			if (fnAnim(pars) != IR_REPEAT) {
				ob_speech->command = 0;
				ob_speech->wait_state = 1;
			}

			return IR_REPEAT;
		case INS_reverse_anim:
			pars[0] = params[2];		// ob_logic
			pars[1] = params[0];		// ob_graphic
			pars[2] = ob_speech->ins1;	// anim res

			if (fnReverseAnim(pars) != IR_REPEAT) {
				ob_speech->command = 0;
				ob_speech->wait_state = 1;
			}

			return IR_REPEAT;
		case INS_table_anim:
			pars[0] = params[2];		// ob_logic
			pars[1] = params[0];		// ob_graphic
			pars[2] = params[3];		// ob_mega
			pars[3] = ob_speech->ins1;	// pointer to anim table

			if (fnMegaTableAnim(pars) != IR_REPEAT) {
				ob_speech->command = 0;
				ob_speech->wait_state = 1;
			}

			return IR_REPEAT;
		case INS_reverse_table_anim:
			pars[0] = params[2];		// ob_logic
			pars[1] = params[0];		// ob_graphic
			pars[2] = params[3];		// ob_mega
			pars[3] = ob_speech->ins1;	// pointer to anim table

			if (fnReverseMegaTableAnim(pars) != IR_REPEAT) {
				ob_speech->command = 0;
				ob_speech->wait_state = 1;
			}

			return IR_REPEAT;
		case INS_no_sprite:
			fnNoSprite(params);		// ob_graphic

			ob_speech->command = 0;
			ob_speech->wait_state = 1;
			return IR_REPEAT ;
		case INS_sort:
			fnSortSprite(params);		// ob_graphic

			ob_speech->command = 0;
			ob_speech->wait_state = 1;
			return IR_REPEAT;
		case INS_foreground:
			fnForeSprite(params);		// ob_graphic

			ob_speech->command = 0;
			ob_speech->wait_state = 1;
			return IR_REPEAT;
		case INS_background:
			fnBackSprite(params);		// ob_graphic

			ob_speech->command = 0;
			ob_speech->wait_state = 1;
			return IR_REPEAT;
		case INS_walk:
			pars[0] = params[2];		// ob_logic
			pars[1] = params[0];		// ob_graphic
			pars[2] = params[3];		// ob_mega
			pars[3] = params[4];		// ob_walkdata
			pars[4] = ob_speech->ins1;	// target x
			pars[5] = ob_speech->ins2;	// target y
			pars[6] = ob_speech->ins3;	// target direction

			if (fnWalk(pars) != IR_REPEAT) {
				ob_speech->command = 0;
				ob_speech->wait_state = 1;
			}

			return IR_REPEAT;
		case INS_walk_to_anim:
			pars[0] = params[2];		// ob_logic
			pars[1] = params[0];		// ob_graphic
			pars[2] = params[3];		// ob_mega
			pars[3] = params[4];		// ob_walkdata
			pars[4] = ob_speech->ins1;	// anim resource

			if (fnWalkToAnim(pars) != IR_REPEAT) {
				ob_speech->command = 0;
				ob_speech->wait_state = 1;
			}

			return IR_REPEAT;
		case INS_stand_after_anim:
			pars[0] = params[0];		// ob_graphic
			pars[1] = params[3];		// ob_mega
			pars[2] = ob_speech->ins1;	// anim resource

			fnStandAfterAnim(pars);

			ob_speech->command = 0;
			ob_speech->wait_state = 1;
			return IR_REPEAT;
		case INS_set_frame:
			pars[0] = params[0];		// ob_graphic
			pars[1] = ob_speech->ins1;	// anim_resource
			pars[2] = ob_speech->ins2;	// FIRST_FRAME or LAST_FRAME
			fnSetFrame(pars);

			ob_speech->command = 0;
			ob_speech->wait_state = 1;
			return IR_REPEAT;
		case INS_quit:
			// That's it - we're finished with this
			ob_speech->command = 0;
			// ob_speech->wait_state = 0;
			return IR_CONT;
		default:
			// Unimplemented command - just cancel
			ob_speech->command = 0;
			ob_speech->wait_state = 1;
			break;
		}

		if (_scriptVars[SPEECH_ID] == _scriptVars[ID]) {
			// There's a new command for us! Grab the command -
			// potentially we only have this cycle to do this - and
			// set things up so that the command will be picked up
			// on the next iteration of the while loop.

			debug(5, "fnSpeechProcess: Received new command %d", _scriptVars[INS_COMMAND]);

			_scriptVars[SPEECH_ID] = 0;

			ob_speech->command = _scriptVars[INS_COMMAND];
			ob_speech->ins1 = _scriptVars[INS1];
			ob_speech->ins2 = _scriptVars[INS2];
			ob_speech->ins3 = _scriptVars[INS3];
			ob_speech->ins4 = _scriptVars[INS4];
			ob_speech->ins5 = _scriptVars[INS5];
			ob_speech->wait_state = 0;

			_scriptVars[INS_COMMAND] = 0;
		} else {
			// No new command. We could run a blink anim (or
			// something) here.

			ob_speech->wait_state = 1;
			return IR_REPEAT;
		}
	}
}

enum {
	S_OB_GRAPHIC	= 0,
	S_OB_SPEECH	= 1,
	S_OB_LOGIC	= 2,
	S_OB_MEGA	= 3,

	S_TEXT		= 4,
	S_WAV		= 5,
	S_ANIM		= 6,
	S_DIR_TABLE	= 7,
	S_ANIM_MODE	= 8
};

/**
 * It's the super versatile fnSpeak. Text and wavs can be selected in any
 * combination.
 *
 * @note We can assume no human - there should be no human, at least!
 */

int32 Logic::fnISpeak(int32 *params) {
	// params:	0 pointer to ob_graphic
	//		1 pointer to ob_speech
	//		2 pointer to ob_logic
	//		3 pointer to ob_mega
	//		4 encoded text number
	//		5 wav res id
	//		6 anim res id
	//		7 anim table res id
	//		8 animation mode	0 lip synced,
	//					1 just straight animation

	static bool cycle_skip = false;
	static bool speechRunning;

	// Set up the pointers which we know we'll always need

	ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[S_OB_LOGIC]);
	ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->decodePtr(params[S_OB_GRAPHIC]);

	// FIRST TIME ONLY: create the text, load the wav, set up the anim,
	// etc.

	if (!ob_logic->looping) {
		// New fudge to wait for smacker samples to finish
		// since they can over-run into the game

		if (_vm->_sound->getSpeechStatus() != RDSE_SAMPLEFINISHED)
			return IR_REPEAT;
		
		// New fudge for 'fx' subtitles: If subtitles switched off, and
		// we don't want to use a wav for this line either, then just
		// quit back to script right now!

		if (!_vm->_gui->_subtitles && !wantSpeechForLine(params[S_WAV]))
			return IR_CONT;

		// Drop out for 1st cycle to allow walks/anims to end and
		// display last frame before system locks while speech loaded

		if (!cycle_skip) {
			cycle_skip = true;
			return IR_REPEAT;
		}

		cycle_skip = false;

		_vm->_debugger->_textNumber = params[S_TEXT];

		// Pull out the text line to get the official text number
		// (for wav id). Once the wav id's go into all script text
		// commands, we'll only need this for debugging.

		uint32 text_res = params[S_TEXT] / SIZE;
		uint32 local_text = params[S_TEXT] & 0xffff;

		// For testing all text & speech!
		//
		// A script loop can send any text number to fnISpeak and it
		// will only run the valid ones or return with 'result' equal
		// to '1' or '2' to mean 'invalid text resource' and 'text
		// number out of range' respectively
		//
		// See 'testing_routines' object in George's Player Character
		// section of linc

		if (_scriptVars[SYSTEM_TESTING_TEXT]) {
			if (!_vm->_resman->checkValid(text_res)) {
				// Not a valid resource number - invalid (null
				// resource)
				_scriptVars[RESULT] = 1;
				return IR_CONT;
			}

			StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(text_res);

			if (head->fileType != TEXT_FILE) {
				// Invalid - not a text resource
				_vm->_resman->closeResource(text_res);
				_scriptVars[RESULT] = 1;
				return IR_CONT;
			}

			if (!_vm->checkTextLine((byte *) head, local_text)) {
				// Line number out of range
				_vm->_resman->closeResource(text_res);
				_scriptVars[RESULT] = 2;
				return IR_CONT;
			}

			_vm->_resman->closeResource(text_res);
			_scriptVars[RESULT] = 0;
		}

		byte *text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text);
		_officialTextNumber = READ_LE_UINT16(text);
		_vm->_resman->closeResource(text_res);

		// Prevent dud lines from appearing while testing text & speech
		// since these will not occur in the game anyway

		if (_scriptVars[SYSTEM_TESTING_TEXT]) {
			// If actor number is 0 and text line is just a 'dash'
			// character
			if (_officialTextNumber == 0 && text[2] == '-' && text[3] == 0) {
				_scriptVars[RESULT] = 3;
				return IR_CONT;
			}
		}

		// Set the 'looping_flag' and the text-click-delays. We can
		// left-click past the text after half a second, and
		// right-click past it after a quarter of a second.
 
		ob_logic->looping = 1;
		_leftClickDelay = 6;
		_rightClickDelay = 3;

		if (_scriptVars[PLAYER_ID] != CUR_PLAYER_ID)
			debug(5, "(%d) Nico: %s", _officialTextNumber, text + 2);
		else {
			byte buf[NAME_LEN];

			debug(5, "(%d) %s: %s", _officialTextNumber, _vm->fetchObjectName(_scriptVars[ID], buf), text + 2);
		}

		// Set up the speech animation

		if (params[S_ANIM]) {
			// Just a straight anim.
			_animId = params[6];
		} else if (params[S_DIR_TABLE]) {
			// Use this direction table to derive the anim
			// NB. ASSUMES WE HAVE A MEGA OBJECT!!

			ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[S_OB_MEGA]);
			int32 *anim_table = (int32 *) _vm->_memory->decodePtr(params[S_DIR_TABLE]);

			_animId = anim_table[ob_mega->current_dir];
		} else {
			// No animation choosen
			_animId = 0;
		}

		if (_animId) {
			// Set the talker's graphic to the first frame of this
			// speech anim for now.

			_speechAnimType = _scriptVars[SPEECHANIMFLAG];
			ob_graphic->anim_resource = _animId;
			ob_graphic->anim_pc = 0;
		}

		// Default back to looped lip synced anims.
		_scriptVars[SPEECHANIMFLAG] = 0;

		// Set up _textX and _textY for speech panning and/or text
		// sprite position.

		locateTalker(params);

		// Is it to be speech or subtitles or both?

		// Assume not running until know otherwise
		speechRunning = false;

		// New fudge for 'fx' subtitles: If speech is selected, and
		// this line is allowed speech (not if it's an fx subtitle!)

		if (!_vm->_sound->isSpeechMute() && wantSpeechForLine(_officialTextNumber)) {
			// If the wavId parameter is zero because not yet
			// compiled into speech command, we can still get it
			// from the 1st 2 chars of the text line.

			if (!params[S_WAV])
				params[S_WAV] = (int32) _officialTextNumber;

			// Panning goes from -16 (left) to 16 (right)
			int8 speech_pan = ((_textX - 320) * 16) / 320;

			if (speech_pan < -16)
				speech_pan = -16;
			else if (speech_pan > 16)
				speech_pan = 16;

			uint32 rv = _vm->_sound->playCompSpeech(params[S_WAV], 16, speech_pan);

			if (rv == RD_OK) {
				// Ok, we've got something to play. Set it
				// playing now. (We might want to do this the
				// next cycle, don't know yet.)

				speechRunning = true;
				_vm->_sound->unpauseSpeech();
			} else {
				debug(5, "ERROR: PlayCompSpeech(wav=%d (res=%d pos=%d)) returned %.8x", params[S_WAV], text_res, local_text, rv);
			}
		}

		if (_vm->_gui->_subtitles || !speechRunning) {
			// We want subtitles, or the speech failed to load.
			// Either way, we're going to show the text so create
			// the text sprite.

			formText(params);
		}
	}

	// EVERY TIME: run a cycle of animation, if there is one

	if (_animId) {
		// There is an animation - Increment the anim frame number.
		ob_graphic->anim_pc++;

		byte *anim_file = _vm->_resman->openResource(ob_graphic->anim_resource);
		AnimHeader *anim_head = _vm->fetchAnimHeader(anim_file);

		if (!_speechAnimType) {
			// ANIM IS TO BE LIP-SYNC'ED & REPEATING

			if (ob_graphic->anim_pc == (int32) (anim_head->noAnimFrames)) {
				// End of animation - restart from frame 0
				ob_graphic->anim_pc = 0;
			} else if (speechRunning && _vm->_sound->amISpeaking() == RDSE_QUIET) {
				// The speech is running, but we're at a quiet
				// bit. Restart from frame 0 (closed mouth).
				ob_graphic->anim_pc = 0;
			}
		} else {
			// ANIM IS TO PLAY ONCE ONLY
			if (ob_graphic->anim_pc == (int32) (anim_head->noAnimFrames) - 1) {
				// Reached the last frame of the anim. Hold
				// anim on this last frame
				_animId = 0;
			}
		}

		_vm->_resman->closeResource(ob_graphic->anim_resource);
	} else if (_speechAnimType) {
		// Placed here so we actually display the last frame of the
		// anim.
		_speechAnimType = 0;
	}

	// EVERY TIME: FIND OUT IF WE NEED TO STOP THE SPEECH NOW...

	// If there is a wav then we're using that to end the speech naturally

	bool speechFinished = false;

	// If playing a sample

	if (speechRunning) {
		// Has it finished?
		if (_vm->_sound->getSpeechStatus() == RDSE_SAMPLEFINISHED)
			speechFinished = true;
	} else if (!speechRunning && _speechTime) {
		// Counting down text time because there is no sample - this
		// ends the speech

		// if no sample then we're using _speechTime to end speech
		// naturally

		_speechTime--;
		if (!_speechTime)
			speechFinished = true;
	}

	// Ok, all is running along smoothly - but a click means stop
	// unnaturally

	// So that we can go to the options panel while text & speech is
	// being tested
	if (_scriptVars[SYSTEM_TESTING_TEXT] == 0 || _vm->_mouseY > 0) {
		MouseEvent *me = _vm->mouseEvent();

		// Note that we now have TWO click-delays - one for LEFT
		// button, one for RIGHT BUTTON

		if ((!_leftClickDelay && me && (me->buttons & RD_LEFTBUTTONDOWN)) ||
		    (!_rightClickDelay && me && (me->buttons & RD_RIGHTBUTTONDOWN))) {
			// Mouse click, after click_delay has expired -> end
			// the speech.

			// if testing text & speech
			if (_scriptVars[SYSTEM_TESTING_TEXT]) {
				// and RB used to click past text
				if (me->buttons & RD_RIGHTBUTTONDOWN) {
					// then we want the previous line again
					_scriptVars[SYSTEM_WANT_PREVIOUS_LINE] = 1;
				} else {
					// LB just want next line again
					_scriptVars[SYSTEM_WANT_PREVIOUS_LINE] = 0;
				}
			}

			speechFinished = true;

			// if speech sample playing, halt it prematurely
			if (speechRunning)
				_vm->_sound->stopSpeech();
		}
	}

	// If we are finishing the speech this cycle, do the business

	// !speechAnimType, as we want an anim which is playing once to have
	// finished.

	if (speechFinished && !_speechAnimType) {
		// If there is text, kill it
		if (_speechTextBlocNo) {
			_vm->_fontRenderer->killTextBloc(_speechTextBlocNo);
			_speechTextBlocNo = 0;
		}

		// if there is a speech anim, end it on closed mouth frame
		if (_animId) {
			_animId = 0;
			ob_graphic->anim_pc = 0;
		}

		speechRunning = false;

		// no longer in a script function loop
		ob_logic->looping = 0;

		_vm->_debugger->_textNumber = 0;

		// reset to zero, in case text line not even extracted (since
		// this number comes from the text line)
		_officialTextNumber = 0;

		_scriptVars[RESULT] = 0;
		return IR_CONT;
	}

	// Speech still going, so decrement the click_delay if it's still
	// active

	if (_leftClickDelay)
		_leftClickDelay--;

 	if (_rightClickDelay)
		_rightClickDelay--;

	return IR_REPEAT;
}

// Distance kept above talking sprite
#define GAP_ABOVE_HEAD 20

/**
 * Sets _textX and _textY for position of text sprite. Note that _textX is
 * also used to calculate speech pan.
 */

void Logic::locateTalker(int32 *params) {
	// params:	0 pointer to ob_graphic
	//		1 pointer to ob_speech
	//		2 pointer to ob_logic
	//		3 pointer to ob_mega
	//		4 encoded text number
	//		5 wav res id
	//		6 anim res id
	//		7 pointer to anim table
	//		8 animation mode	0 lip synced,
	//					1 just straight animation

	if (!_animId) {
		// There is no animation. Assume it's voice-over text, and put
		// it at the bottom of the screen.

		_textX = 320;
		_textY = 400;
		return;
	}

	byte *file = _vm->_resman->openResource(_animId);

	// '0' means 1st frame

	CdtEntry *cdt_entry = _vm->fetchCdtEntry(file, 0);
	FrameHeader *frame_head = _vm->fetchFrameHeader(file, 0);

	// Note: This part of the code is quite similar to registerFrame().

	if (cdt_entry->frameType & FRAME_OFFSET) {
		// The frame has offsets, i.e. it's a scalable mega frame
		ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[S_OB_MEGA]);

		// Calculate scale at which to print the sprite, based on feet
		// y-coord and scaling constants (NB. 'scale' is actually
		// 256 * true_scale, to maintain accuracy)

		// Ay+B gives 256 * scale ie. 256 * 256 * true_scale for even
		// better accuracy, ie. scale = (Ay + B) / 256

		uint16 scale = (uint16) ((ob_mega->scale_a * ob_mega->feet_y + ob_mega->scale_b) / 256);

		// Calc suitable centre point above the head, based on scaled
		// height

		// just use 'feet_x' as centre
		_textX = (int16) ob_mega->feet_x;

		// Add scaled y-offset to feet_y coord to get top of sprite
		_textY = (int16) (ob_mega->feet_y + (cdt_entry->y * scale) / 256);
	} else {
		// It's a non-scaling anim - calc suitable centre point above
		// the head, based on scaled width

		// x-coord + half of width
		_textX = cdt_entry->x + (frame_head->width) / 2;
		_textY = cdt_entry->y;
	}

	_vm->_resman->closeResource(_animId);

	// Leave space above their head
	_textY -= GAP_ABOVE_HEAD;
			
	// Adjust the text coords for RDSPR_DISPLAYALIGN

	_textX -= _vm->_thisScreen.scroll_offset_x;
	_textY -= _vm->_thisScreen.scroll_offset_y;
}

/**
 * This function is called the first time to build the text, if we need one. If
 * If necessary it also brings in the wav and sets up the animation.
 *
 * If there is an animation it can be repeating lip-sync or run-once.
 *
 * If there is no wav, then the text comes up instead. There can be any
 * combination of text/wav playing.
 */

void Logic::formText(int32 *params) {
	// params	0 pointer to ob_graphic
	// 		1 pointer to ob_speech
	//		2 pointer to ob_logic
	//		3 pointer to ob_mega
	//		4 encoded text number
	//		5 wav res id
	//		6 anim res id
	//		7 pointer to anim table
	//		8 animation mode	0 lip synced,
	//					1 just straight animation

	// There should always be a text line, as all text is derived from it.
	// If there is none, that's bad...

	if (!params[S_TEXT]) {
		warning("No text line for speech wav %d", params[S_WAV]);
		return;
	}

	ObjectSpeech *ob_speech = (ObjectSpeech *) _vm->_memory->decodePtr(params[S_OB_SPEECH]);

	// Establish the max width allowed for this text sprite.
	uint32 textWidth = ob_speech->width ? ob_speech->width : 400;

	// Pull out the text line, and make the sprite and text block

	uint32 text_res = params[S_TEXT] / SIZE;
	uint32 local_text = params[S_TEXT] & 0xffff;
	byte *text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text);

	// 'text + 2' to skip the first 2 bytes which form the line reference
	// number

	_speechTextBlocNo = _vm->_fontRenderer->buildNewBloc(
		text + 2, _textX, _textY,
		textWidth, ob_speech->pen,
		RDSPR_TRANS | RDSPR_DISPLAYALIGN,
		_vm->_speechFontId, POSITION_AT_CENTRE_OF_BASE);

	_vm->_resman->closeResource(text_res);

	// Set speech duration, in case not using a wav.
	_speechTime = strlen((char *) text) + 30;
}

/**
 * There are some hard-coded cases where speech is used to illustrate a sound
 * effect. In this case there is no sound associated with the speech itself.
 */

bool Logic::wantSpeechForLine(uint32 wavId) {
	switch (wavId) {
	case 1328:	// AttendantSpeech
			//	SFX(Phone71);
			//	FX <Telephone rings>
	case 2059:	// PabloSpeech
			//	SFX (2059);
			//	FX <Sound of sporadic gunfire from below>
	case 4082:	// DuaneSpeech
			//	SFX (4082);
			//	FX <Pffffffffffft! Frp. (Unimpressive, flatulent noise.)>
	case 4214:	// cat_52
			//	SFX (4214);
			//	4214FXMeow!
	case 4568:	// trapdoor_13
 			//	SFX (4568);
			//	4568fx<door slamming>
	case 4913:	// LobineauSpeech
			//	SFX (tone2);
			//	FX <Lobineau hangs up>
	case 5120:	// bush_66
			//	SFX (5120);
			//	5120FX<loud buzzing>
	case 528:	// PresidentaSpeech
			//	SFX (528);
			//	FX <Nearby Crash of Collapsing Masonry>
	case 920:	// Zombie Island forest maze (bird)
	case 923:	// Zombie Island forest maze (monkey)
	case 926:	// Zombie Island forest maze (zombie)
		// Don't want speech for these lines!
		return false;
	default:
		// Ok for all other lines
		return true;
	}
}

} // End of namespace Sword2
