diff options
Diffstat (limited to 'engines/sherlock/talk.cpp')
| -rw-r--r-- | engines/sherlock/talk.cpp | 1219 | 
1 files changed, 1219 insertions, 0 deletions
diff --git a/engines/sherlock/talk.cpp b/engines/sherlock/talk.cpp new file mode 100644 index 0000000000..3c6bf44837 --- /dev/null +++ b/engines/sherlock/talk.cpp @@ -0,0 +1,1219 @@ +/* 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/talk.h" +#include "sherlock/sherlock.h" +#include "sherlock/screen.h" +#include "sherlock/scalpel/scalpel.h" +#include "sherlock/scalpel/scalpel_people.h" +#include "sherlock/scalpel/scalpel_talk.h" +#include "sherlock/scalpel/scalpel_user_interface.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#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" + +namespace Sherlock { + +SequenceEntry::SequenceEntry() { +	_objNum = 0; +	_obj = nullptr; +	_seqStack = 0; +	_seqTo = 0; +	_sequenceNumber = _frameNumber = 0; +	_seqCounter = _seqCounter2 = 0; +} + +/*----------------------------------------------------------------*/ + +void Statement::load(Common::SeekableReadStream &s, bool isRoseTattoo) { +	int length; + +	length = s.readUint16LE(); +	for (int idx = 0; idx < length - 1; ++idx) +		_statement += (char)s.readByte(); +	s.readByte();	// Null ending + +	length = s.readUint16LE(); +	for (int idx = 0; idx < length - 1; ++idx) +		_reply += (char)s.readByte(); +	s.readByte();	// Null ending + +	length = s.readUint16LE(); +	for (int idx = 0; idx < length - 1; ++idx) +		_linkFile += (char)s.readByte(); +	s.readByte();	// Null ending + +	length = s.readUint16LE(); +	for (int idx = 0; idx < length - 1; ++idx) +		_voiceFile += (char)s.readByte(); +	s.readByte();	// Null ending + +	_required.resize(s.readByte()); +	_modified.resize(s.readByte()); + +	// Read in flag required/modified data +	for (uint idx = 0; idx < _required.size(); ++idx) +		_required[idx] = s.readSint16LE(); +	for (uint idx = 0; idx < _modified.size(); ++idx) +		_modified[idx] = s.readSint16LE(); + +	_portraitSide = s.readByte(); +	_quotient = s.readUint16LE(); +	_journal = isRoseTattoo ? s.readByte() : 0; +} + +/*----------------------------------------------------------------*/ + +TalkHistoryEntry::TalkHistoryEntry() { +	Common::fill(&_data[0], &_data[16], false); +} + +/*----------------------------------------------------------------*/ + +Talk *Talk::init(SherlockEngine *vm) { +	if (vm->getGameID() == GType_SerratedScalpel) +		return new Scalpel::ScalpelTalk(vm); +	else +		return new Tattoo::TattooTalk(vm); +} + +Talk::Talk(SherlockEngine *vm) : _vm(vm) { +	_talkCounter = 0; +	_talkToAbort = false; +	_openTalkWindow = false; +	_speaker = 0; +	_talkIndex = 0; +	_talkTo = 0; +	_scriptSelect = 0; +	_converseNum = -1; +	_talkStealth = 0; +	_talkToFlag = -1; +	_moreTalkDown = _moreTalkUp = false; +	_scriptMoreFlag = 0; +	_scriptSaveIndex = -1; +	_opcodes = nullptr; +	_opcodeTable = nullptr; +	_3doSpeechIndex = -1; + +	_charCount = 0; +	_line = 0; +	_yp = 0; +	_wait = 0; +	_pauseFlag = false; +	_seqCount = 0; +	_scriptStart = _scriptEnd = nullptr; +	_endStr = _noTextYet = false; + +	_talkHistory.resize(IS_ROSE_TATTOO ? 1500 : 500); +} + +void Talk::talkTo(const Common::String filename) { +	Events &events = *_vm->_events; +	Inventory &inv = *_vm->_inventory; +	Journal &journal = *_vm->_journal; +	People &people = *_vm->_people; +	Scene &scene = *_vm->_scene; +	Screen &screen = *_vm->_screen; +	Sound &sound = *_vm->_sound; +	UserInterface &ui = *_vm->_ui; +	Common::Rect savedBounds = screen.getDisplayBounds(); +	bool abortFlag = false; + +	if (filename.empty()) +		// No filename passed, so exit +		return; + +	// If there any canimations currently running, or a portrait is being cleared, +	// save the filename for later executing when the canimation is done +	bool ongoingAnim = scene._canimShapes.size() > 0; +	if (IS_ROSE_TATTOO) { +		ongoingAnim = static_cast<Tattoo::TattooScene *>(_vm->_scene)->_activeCAnim.active(); +	} +	if (ongoingAnim || people._clearingThePortrait) { +		// Make sure we're not in the middle of a script +		if (!_scriptMoreFlag) { +			_scriptName = filename; +			_scriptSaveIndex = 0; + +			// Flag the selection, since we don't yet know which statement yet +			_scriptSelect = 100; +			_scriptMoreFlag = 3; +		} + +		return; +	} + +	// Save the ui mode temporarily and switch to talk mode +	MenuMode savedMode = ui._menuMode; +	ui._menuMode = TALK_MODE; + +	// Turn on the Exit option +	ui._endKeyActive = true; + +	if (people[HOLMES]._walkCount || (!people[HOLMES]._walkTo.empty() &&  +			(IS_SERRATED_SCALPEL || people._allowWalkAbort))) { +		// Only interrupt if trying to do an action, and not just if player is walking around the scene +		if (people._allowWalkAbort) +			abortFlag = true; + +		people[HOLMES].gotoStand(); +	} + +	if (_talkToAbort) +		return; + +	freeTalkVars(); + +	// If any sequences have changed in the prior talk file, restore them +	if (_savedSequences.size() > 0) { +		for (uint idx = 0; idx < _savedSequences.size(); ++idx) { +			SequenceEntry &ss = _savedSequences[idx]; +			for (uint idx2 = 0; idx2 < ss._sequences.size(); ++idx2) +				scene._bgShapes[ss._objNum]._sequences[idx2] = ss._sequences[idx2]; + +			// Reset the object's frame to the beginning of the sequence +			scene._bgShapes[ss._objNum]._frameNumber = 0; +		} +	} + +	if (IS_ROSE_TATTOO) { +		pullSequence(); +	} else { +		while (!isSequencesEmpty()) +			pullSequence(); +	} + +	if (IS_SERRATED_SCALPEL) { +		// Restore any pressed button +		if (!ui._windowOpen && savedMode != STD_MODE) +			static_cast<Scalpel::ScalpelUserInterface *>(_vm->_ui)->restoreButton((int)(savedMode - 1)); +	} else { +		static_cast<Tattoo::TattooPeople *>(_vm->_people)->pullNPCPaths(); +	} + +	// Clear the ui counter so that anything displayed on the info line +	// before the window was opened isn't cleared +	ui._menuCounter = 0; + +	// Close any previous window before starting the talk +	if (ui._windowOpen) { +		switch (savedMode) { +		case LOOK_MODE: +			events.setCursor(ARROW); + +			if (ui._invLookFlag) { +				screen.resetDisplayBounds(); +				ui.drawInterface(2); +			} + +			ui.banishWindow(); +			ui._windowBounds.top = CONTROLS_Y1; +			ui._temp = ui._oldTemp = ui._lookHelp = 0; +			ui._menuMode = STD_MODE; +			events._pressed = events._released = events._oldButtons = 0; +			ui._invLookFlag = false; +			break; + +		case TALK_MODE: +			if (_speaker < SPEAKER_REMOVE) +				people.clearTalking(); +			if (_talkCounter) +				return; + +			// If we were in inventory mode looking at an object, restore the +			// back buffers before closing the window, so we get the ui restored +			// rather than the inventory again +			if (ui._invLookFlag) { +				screen.resetDisplayBounds(); +				ui.drawInterface(2); +				ui._invLookFlag = ui._lookScriptFlag = false; +			} + +			ui.banishWindow(); +			ui._windowBounds.top = CONTROLS_Y1; +			abortFlag = true; +			break; + +		case INV_MODE: +		case USE_MODE: +		case GIVE_MODE: +			inv.freeInv(); +			if (ui._invLookFlag) { +				screen.resetDisplayBounds(); +				ui.drawInterface(2); +				ui._invLookFlag = ui._lookScriptFlag = false; +			} + +			ui._infoFlag = true; +			ui.clearInfo(); +			ui.banishWindow(false); +			ui._key = -1; +			break; + +		case FILES_MODE: +			ui.banishWindow(true); +			ui._windowBounds.top = CONTROLS_Y1; +			abortFlag = true; +			break; + +		case SETUP_MODE: +			ui.banishWindow(true); +			ui._windowBounds.top = CONTROLS_Y1; +			ui._temp = ui._oldTemp = ui._lookHelp = ui._invLookFlag = false; +			ui._menuMode = STD_MODE; +			events._pressed = events._released = events._oldButtons = 0; +			abortFlag = true; +			break; + +		default: +			break; +		} +	} + +	screen.resetDisplayBounds(); +	events._pressed = events._released = false; +	loadTalkFile(filename); +	ui._selector = ui._oldSelector = ui._key = ui._oldKey = -1; + +	// Find the first statement that has the correct flags +	int select = -1; +	for (uint idx = 0; idx < _statements.size() && select == -1; ++idx) { +		if (_statements[idx]._talkMap == 0) +			select = _talkIndex = idx; +	} + +	// If there's a pending automatic selection to be made, then use it +	if (_scriptMoreFlag && _scriptSelect != 100) +		select = _scriptSelect; + +	if (select == -1) { +		if (IS_ROSE_TATTOO) { +			static_cast<Tattoo::TattooUserInterface *>(&ui)->putMessage( +				"%s", _vm->_fixedText->getText(Tattoo::kFixedText_NoEffect)); +			return; +		} +		error("Couldn't find statement to display"); +	} + +	// Add the statement into the journal and talk history +	if (_talkTo != -1 && !_talkHistory[_converseNum][select]) +		journal.record(_converseNum, select, true); +	_talkHistory[_converseNum][select] = true; + +	// Check if the talk file is meant to be a non-seen comment +	if (filename.size() < 8 || filename[7] != '*') { +		// Should we start in stealth mode? +		if (_statements[select]._statement.hasPrefix("^")) { +			_talkStealth = 2; +		} else { +			// Not in stealth mode, so bring up the ui window +			_talkStealth = 0; +			++_talkToFlag; +			events.setCursor(WAIT); + +			ui._windowBounds.top = CONTROLS_Y; +			ui._infoFlag = true; +			ui.clearInfo(); +		} + +		// Handle replies until there's no further linked file, +		// or the link file isn't a reply first cnversation +		while (!_vm->shouldQuit()) { +			clearSequences(); +			_scriptSelect = select; +			_speaker = _talkTo; + +			// Set up the talk file extension +			if (IS_ROSE_TATTOO && sound._speechOn && _scriptMoreFlag != 1) +				sound._talkSoundFile += Common::String::format("%02dB", select + 1); + +			// Make a copy of the statement (in case the script frees the statement list), and then execute it +			Statement statement = _statements[select]; +			doScript(_statements[select]._reply); + +			if (IS_ROSE_TATTOO) { +				for (int idx = 0; idx < MAX_CHARACTERS; ++idx) +					people[idx]._misc = 0; +			} + +			if (_talkToAbort) +				return; + +			if (!_talkStealth) +				ui.clearWindow(); + +			if (statement._modified.size() > 0) { +				for (uint idx = 0; idx < statement._modified.size(); ++idx) +					_vm->setFlags(statement._modified[idx]); + +				setTalkMap(); +			} + +			// Check for a linked file +			if (!statement._linkFile.empty() && !_scriptMoreFlag) { +				Common::String linkFilename = statement._linkFile; +				freeTalkVars(); +				loadTalkFile(linkFilename); + +				// Scan for the first valid statement in the newly loaded file +				select = -1; +				for (uint idx = 0; idx < _statements.size(); ++idx) { +					if (_statements[idx]._talkMap == 0) { +						select = idx; +						break; +					} +				} + +				if (_talkToFlag == 1) +					pullSequence(); + +				// Set the stealth mode for the new talk file +				Statement &newStatement = _statements[select]; +				_talkStealth = newStatement._statement.hasPrefix("^") ? 2 : 0; + +				// If the new conversion is a reply first, then we don't need +				// to display any choices, since the reply needs to be shown +				if (!newStatement._statement.hasPrefix("*") && !newStatement._statement.hasPrefix("^")) { +					_talkIndex = select; +					showTalk(); + +					// Break out of loop now that we're waiting for player input +					events.setCursor(ARROW); +					break; +				} else { +					// Add the statement into the journal and talk history +					if (_talkTo != -1 && !_talkHistory[_converseNum][select]) +						journal.record(_converseNum, select, true); +					_talkHistory[_converseNum][select] = true; +				} + +				ui._key = ui._oldKey = 'T'; // FIXME: I'm not sure what to do here, I need ScalpelUI->_hotkeyTalk +				ui._temp = ui._oldTemp = 0; +				ui._menuMode = TALK_MODE; +				_talkToFlag = 2; +			} else { +				freeTalkVars(); + +				if (IS_SERRATED_SCALPEL) { +					if (!ui._lookScriptFlag) { +						ui.drawInterface(2); +						ui._menuMode = STD_MODE; +						ui._windowBounds.top = CONTROLS_Y1; +					} +				} else { +					ui._menuMode = static_cast<Tattoo::TattooScene *>(_vm->_scene)->_labTableScene ? LAB_MODE : STD_MODE; +				} + +				ui.banishWindow(); +				break; +			} +		} +	} + +	_talkStealth = 0; +	events._pressed = events._released = events._oldButtons = 0; +	events.clearKeyboard(); + +	if (savedBounds.bottom == SHERLOCK_SCREEN_HEIGHT) +		screen.resetDisplayBounds(); +	else +		screen.setDisplayBounds(savedBounds); + +	_talkToAbort = abortFlag; + +	// If a script was added to the script stack, restore state so that the +	// previous script can continue +	popStack(); + +	events.setCursor(ARROW); +} + +void Talk::initTalk(int objNum) { +	Events &events = *_vm->_events; +	People &people = *_vm->_people; +	Scene &scene = *_vm->_scene; +	UserInterface &ui = *_vm->_ui; + +	ui._windowBounds.top = CONTROLS_Y; +	ui._infoFlag = true; +	_speaker = SPEAKER_REMOVE; + +	Common::String talkFilename = (objNum >= 1000) ? people[objNum - 1000]._npcName : scene._bgShapes[objNum]._name; +	loadTalkFile(talkFilename); + +	// Find the first statement with the correct flags +	int select = -1; +	for (uint idx = 0; idx < _statements.size(); ++idx) { +		if (_statements[idx]._talkMap == 0) { +			select = idx; +			break; +		} +	} + +	if (select == -1) { +		freeTalkVars(); +		if (!scumm_strnicmp(talkFilename.c_str(), "PATH", 4)) +			error("No entries found to execute in path file"); + +		nothingToSay(); +		return; +	} + +	// See if the statement is a stealth mode reply +	Statement &statement = _statements[select]; +	if (statement._statement.hasPrefix("^")) { +		clearSequences(); + +		// Start talk in stealth mode +		_talkStealth = 2; + +		talkTo(talkFilename); +	} else if (statement._statement.hasPrefix("*")) { +		// Character being spoken to will speak first +		if (objNum > 1000) { +			(*static_cast<Tattoo::TattooPeople *>(_vm->_people))[objNum - 1000].walkHolmesToNPC(); +		} else { +			Object &obj = scene._bgShapes[objNum]; +			clearSequences(); +			pushSequence(_talkTo); +			people.setListenSequence(_talkTo, 129); + +			events.setCursor(WAIT); +			if (obj._lookPosition.y != 0) +				// Need to walk to character first +				people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing); +			events.setCursor(ARROW); +		} + +		if (!_talkToAbort) +			talkTo(talkFilename); +	} else { +		// Holmes will be speaking first +		_talkToFlag = false; + +		if (objNum > 1000) { +			(*static_cast<Tattoo::TattooPeople *>(_vm->_people))[objNum - 1000].walkHolmesToNPC(); +		} else { +			Object &obj = scene._bgShapes[objNum]; +			clearSequences(); +			pushSequence(_talkTo); +			people.setListenSequence(_talkTo, 129); + +			events.setCursor(WAIT); +			if (obj._lookPosition.y != 0) +				// Walk over to person to talk to +				people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing); +			events.setCursor(ARROW); +		} + +		if (!_talkToAbort) { +			// See if walking over triggered a conversation +			if (_talkToFlag) { +				if (_talkToFlag == 1) { +					events.setCursor(ARROW); +					// _sequenceStack._count = 1; +					pullSequence(); +				} +			} else { +				_talkIndex = select; +				showTalk(); + +				// Break out of loop now that we're waiting for player input +				events.setCursor(ARROW); +			} + +			_talkToFlag = -1; +		} +	} +} + +void Talk::freeTalkVars() { +	_statements.clear(); +} + +void Talk::loadTalkFile(const Common::String &filename) { +	People &people = *_vm->_people; +	Resources &res = *_vm->_res; +	Sound &sound = *_vm->_sound; + +	// Save a copy of the talk filename +	_scriptName = filename; + +	// Check for an existing person being talked to +	_talkTo = -1; +	for (int idx = 0; idx < (int)people._characters.size(); ++idx) { +		if (!scumm_strnicmp(filename.c_str(), people._characters[idx]._portrait, 4)) { +			_talkTo = idx; +			break; +		} +	} + +	const char *chP = strchr(filename.c_str(), '.'); +	Common::String talkFile = chP ? Common::String(filename.c_str(), chP) + ".tlk" : +		Common::String(filename.c_str(), filename.c_str() + 7) + ".tlk"; + +	// Create the base of the sound filename used for talking in Rose Tattoo +	if (IS_ROSE_TATTOO && _scriptMoreFlag != 1) +		sound._talkSoundFile = Common::String(filename.c_str(), filename.c_str() + 7) + "."; + +	// Open the talk file for reading +	Common::SeekableReadStream *talkStream = res.load(talkFile); +	_converseNum = res.resourceIndex(); +	talkStream->skip(2);	// Skip talk file version num + +	_statements.clear(); +	_statements.resize(talkStream->readByte()); +	for (uint idx = 0; idx < _statements.size(); ++idx) +		_statements[idx].load(*talkStream, IS_ROSE_TATTOO); + +	delete talkStream; + +	if (!sound._voices) +		stripVoiceCommands(); +	setTalkMap(); +} + +void Talk::stripVoiceCommands() { +	for (uint sIdx = 0; sIdx < _statements.size(); ++sIdx) { +		Statement &statement = _statements[sIdx]; + +		// Scan for an sound effect byte, which indicates to play a sound +		for (uint idx = 0; idx < statement._reply.size(); ++idx) { +			if (statement._reply[idx] == (char)_opcodes[OP_SFX_COMMAND]) { +				// Replace instruction character with a space, and delete the +				// rest of the name following it +				statement._reply = Common::String(statement._reply.c_str(), +					statement._reply.c_str() + idx) + " " + +					Common::String(statement._reply.c_str() + idx + 9); +			} +		} + +		// Ensure the last character of the reply is not a space from the prior +		// conversion loop, to avoid any issues with the space ever causing a page +		// wrap, and ending up displaying another empty page +		while (statement._reply.lastChar() == ' ') +			statement._reply.deleteLastChar(); +	} +} + +void Talk::setTalkMap() { +	int statementNum = 0; + +	for (uint sIdx = 0; sIdx < _statements.size(); ++sIdx) { +		Statement &statement = _statements[sIdx]; + +		// Set up talk map entry for the statement +		bool valid = true; +		for (uint idx = 0; idx < statement._required.size(); ++idx) { +			if (!_vm->readFlags(statement._required[idx])) +				valid = false; +		} + +		statement._talkMap = valid ? statementNum++ : -1; +	} +} + +void Talk::pushSequence(int speaker) { +	People &people = *_vm->_people; +	Scene &scene = *_vm->_scene; + +	// Only proceed if a speaker is specified +	if (speaker != -1) { +		int objNum = people.findSpeaker(speaker); +		if (objNum != -1) +			pushSequenceEntry(&scene._bgShapes[objNum]); +	} +} + +void Talk::doScript(const Common::String &script) { +	People &people = *_vm->_people; +	Scene &scene = *_vm->_scene; +	Screen &screen = *_vm->_screen; +	UserInterface &ui = *_vm->_ui; + +	_savedSequences.clear(); + +	_scriptStart = (const byte *)script.c_str(); +	_scriptEnd = _scriptStart + script.size(); +	const byte *str = _scriptStart; +	_charCount = 0; +	_line = 0; +	_wait = 0; +	_pauseFlag = false; +	_seqCount = 0; +	_noTextYet = true; +	_endStr = false; +	_openTalkWindow = false; + +	if (IS_SERRATED_SCALPEL) +		_yp = CONTROLS_Y + 12; +	else +		_yp = (_talkTo == -1) ? 5 : screen.fontHeight() + 11; + +	if (IS_ROSE_TATTOO) { +		for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { +			Tattoo::TattooPerson &p = (*(Tattoo::TattooPeople *)_vm->_people)[idx]; +			p._savedNpcSequence = p._sequenceNumber; +			p._savedNpcFrame = p._frameNumber; +			p._resetNPCPath = true; +		} +	} + +	if (_scriptMoreFlag) { +		_scriptMoreFlag = 0; +		str = _scriptStart + _scriptSaveIndex; +	} + +	// Check if the script begins with a Stealh Mode Active command +	if (str[0] == _opcodes[OP_STEALTH_MODE_ACTIVE] || _talkStealth) { +		_talkStealth = 2; +		_speaker |= SPEAKER_REMOVE; +	} else { +		if (IS_SERRATED_SCALPEL) +			pushSequence(_speaker); +		if (IS_SERRATED_SCALPEL || ui._windowOpen) +			ui.clearWindow(); + +		// Need to switch speakers? +		if (str[0] == _opcodes[OP_SWITCH_SPEAKER]) { +			_speaker = str[1] - 1; + +			if (IS_SERRATED_SCALPEL) { +				str += 2; +				pullSequence(); +				pushSequence(_speaker); +			} else { +				str += 3; +			} + +			people.setTalkSequence(_speaker); +		} else { +			people.setTalkSequence(_speaker); +		} + +		if (IS_SERRATED_SCALPEL) { +			// Assign portrait location? +			if (str[0] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION]) { +				switch (str[1] & 15) { +				case 1: +					people._portraitSide = 20; +					break; +				case 2: +					people._portraitSide = 220; +					break; +				case 3: +					people._portraitSide = 120; +					break; +				default: +					break; + +				} + +				if (str[1] > 15) +					people._speakerFlip = true; +				str += 2; +			} + +			if (IS_SERRATED_SCALPEL) { +				// Remove portrait? +				if ( str[0] == _opcodes[OP_REMOVE_PORTRAIT]) { +					_speaker = -1; +				} else { +					// Nope, so set the first speaker +					((Scalpel::ScalpelPeople *)_vm->_people)->setTalking(_speaker); +				} +			} +		} +	} + +	do { +		Common::String tempString; +		_wait = 0; + +		byte c = str[0]; +		if (!c) { +			_endStr = true; +		} else if (c == '{') { +			// Start of comment, so skip over it +			while (*str++ != '}') +				; +		} else if (isOpcode(c)) { +			// the original interpreter checked for c being >= 0x80 +			// and if that is the case, it tried to process it as opcode, BUT ALSO ALWAYS skipped over it +			// This was done inside the Spanish + German interpreters of Serrated Scalpel, not the original +			// English interpreter (reverse engineered from the binaries). +			// +			// This resulted in special characters not getting shown in case they occurred at the start +			// of sentences like for example the inverted exclamation mark and the inverted question mark. +			// For further study see fonts.cpp +			// +			// We create an inverted exclamation mark for the Spanish version and we show it. +			// +			// Us not skipping over those characters may result in an assert() happening inside fonts.cpp +			// in case more invalid characters exist. +			// More information see bug #6931 +			// + +			// Handle control code +			switch ((this->*_opcodeTable[c - _opcodes[0]])(str)) { +			case RET_EXIT: +				return; +			case RET_CONTINUE: +				continue; +			default: +				break; +			} + +			++str; +		} else { +			// Handle drawing the talk interface with the text +			talkInterface(str); +		} + +		// Open window if it wasn't already open, and text has already been printed +		if ((_openTalkWindow && _wait) || (_openTalkWindow && str[0] >= _opcodes[0] && str[0] != _opcodes[OP_END_TEXT_WINDOW])) { +			if (!ui._slideWindows) { +				screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); +			} else { +				ui.summonWindow(); +			} + +			ui._windowOpen = true; +			_openTalkWindow = false; +		} + +		if (_wait) +			// Handling pausing +			talkWait(str); +	} while (!_vm->shouldQuit() && !_endStr); + +	if (_wait != -1) { +		for (int ssIndex = 0; ssIndex < (int)_savedSequences.size(); ++ssIndex) { +			SequenceEntry &seq = _savedSequences[ssIndex]; +			Object &object = scene._bgShapes[seq._objNum]; + +			for (uint idx = 0; idx < seq._sequences.size(); ++idx) +				object._sequences[idx] = seq._sequences[idx]; +			object._frameNumber = seq._frameNumber; +			object._seqTo = seq._seqTo; +		} + +		pullSequence(); + +		if (IS_SERRATED_SCALPEL) { +			if (_speaker >= 0 && _speaker < SPEAKER_REMOVE) +				people.clearTalking(); +		} else { +			static_cast<Tattoo::TattooPeople *>(_vm->_people)->pullNPCPaths(); +		} +	} +} + +int Talk::waitForMore(int delay) { +	Events &events = *_vm->_events; +	People &people = *_vm->_people; +	Scene &scene = *_vm->_scene; +	Sound &sound = *_vm->_sound; +	UserInterface &ui = *_vm->_ui; +	CursorId oldCursor = events.getCursor(); +	int key2 = 254; +	bool playingSpeech = false; + +	// Unless we're in stealth mode, show the appropriate cursor +	if (!_talkStealth) { +		events.setCursor(ui._lookScriptFlag ? MAGNIFY : ARROW); +	} + +	// Handle playing any speech associated with the text being displayed +	switchSpeaker(); +	if (sound._speechOn && IS_ROSE_TATTOO) { +		sound.playSpeech(sound._talkSoundFile); +		sound._talkSoundFile.setChar(sound._talkSoundFile.lastChar() + 1, sound._talkSoundFile.size() - 1); +	} +	playingSpeech = sound.isSpeechPlaying(); + +	do { +		if (IS_SERRATED_SCALPEL && playingSpeech && !sound.isSpeechPlaying()) +			people._portrait._frameNumber = -1; + +		scene.doBgAnim(); + +		// If talkTo call was done via doBgAnim, abort out of talk quietly +		if (_talkToAbort) { +			key2 = -1; +			events._released = true; +		} else { +			// See if there's been a button press +			events.pollEventsAndWait(); +			events.setButtonState(); + +			if (events.kbHit()) { +				Common::KeyState keyState = events.getKey(); +				if (keyState.keycode == Common::KEYCODE_ESCAPE) { +					if (IS_ROSE_TATTOO && static_cast<Tattoo::TattooEngine *>(_vm)->_runningProlog) { +						// Skip out of the introduction +						_vm->setFlags(-76); +						_vm->setFlags(396); +						scene._goToScene = 1; +					} +					break; + +				} else if (Common::isPrint(keyState.ascii)) +					key2 = keyState.keycode; +			} + +			if (_talkStealth) { +				key2 = 254; +				events._released = false; +			} +		} + +		// Count down the delay +		if ((delay > 0 && !ui._invLookFlag && !ui._lookScriptFlag) || _talkStealth) +			--delay; + +		if (playingSpeech && !sound.isSpeechPlaying()) +			delay = 0; +	} while (!_vm->shouldQuit() && key2 == 254 && (delay || (playingSpeech && sound.isSpeechPlaying())) +		&& !events._released && !events._rightReleased); + +	// If voices was set 2 to indicate a Scalpel voice file was playing, then reset it back to 1 +	if (sound._voices == 2) +		sound._voices = 1; + +	if (delay > 0 && sound.isSpeechPlaying()) +		sound.stopSpeech(); + +	// Adjust _talkStealth mode: +	// mode 1 - It was by a pause without stealth being on before the pause, so reset back to 0 +	// mode 3 - It was set by a pause with stealth being on before the pause, to set it to active +	// mode 0/2 (Inactive/active) No change +	switch (_talkStealth) { +	case 1: +		_talkStealth = 0; +		break; +	case 2: +		_talkStealth = 2; +		break; +	default: +		break; +	} + + +	sound.stopSpeech(); +	events.setCursor(_talkToAbort ? ARROW : oldCursor); +	events._pressed = events._released = false; + +	return key2; +} + +bool Talk::isOpcode(byte checkCharacter) { +	if ((checkCharacter < _opcodes[0]) || (checkCharacter >= (_opcodes[0] + 99))) +		return false; // outside of range +	if (_opcodeTable[checkCharacter - _opcodes[0]]) +		return true; +	return false; +} + +void Talk::popStack() { +	if (!_scriptStack.empty()) { +		ScriptStackEntry scriptEntry = _scriptStack.pop(); +		_scriptName = scriptEntry._name; +		_scriptSaveIndex = scriptEntry._currentIndex; +		_scriptSelect = scriptEntry._select; +		_scriptMoreFlag = 1; +	} +} + +void Talk::synchronize(Serializer &s) { +	for (uint idx = 0; idx < _talkHistory.size(); ++idx) { +		TalkHistoryEntry &he = _talkHistory[idx]; + +		for (int flag = 0; flag < 16; ++flag) +			s.syncAsByte(he._data[flag]); +	} +} + +OpcodeReturn Talk::cmdAddItemToInventory(const byte *&str) { +	Inventory &inv = *_vm->_inventory; +	Common::String tempString; + +	++str; +	for (int idx = 0; idx < str[0]; ++idx) +		tempString += str[idx + 1]; +	str += str[0]; + +	inv.putNameInInventory(tempString); +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdAdjustObjectSequence(const byte *&str) { +	Scene &scene = *_vm->_scene; +	Common::String tempString; + +	// Get the name of the object to adjust +	++str; +	for (int idx = 0; idx < (str[0] & 127); ++idx) +		tempString += str[idx + 2]; + +	// Scan for object +	int objId = -1; +	for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { +		if (tempString.equalsIgnoreCase(scene._bgShapes[idx]._name)) +			objId = idx; +	} +	if (objId == -1) +		error("Could not find object %s to change", tempString.c_str()); + +	// Should the script be overwritten? +	if (str[0] > 0x80) { +		// Save the current sequence +		_savedSequences.push(SequenceEntry()); +		SequenceEntry &seqEntry = _savedSequences.top(); +		seqEntry._objNum = objId; +		seqEntry._seqTo = scene._bgShapes[objId]._seqTo; +		for (uint idx = 0; idx < scene._bgShapes[objId]._seqSize; ++idx) +			seqEntry._sequences.push_back(scene._bgShapes[objId]._sequences[idx]); +	} + +	// Get number of bytes to change +	_seqCount = str[1]; +	str += (str[0] & 127) + 2; + +	// WORKAROUND: Original German Scalpel crash when moving box at Tobacconists +	if (_vm->getLanguage() == Common::DE_DEU && _scriptName == "Alfr30Z") +		_seqCount = 16; + +	// Copy in the new sequence +	for (int idx = 0; idx < _seqCount; ++idx, ++str) +		scene._bgShapes[objId]._sequences[idx] = str[0] - 1; + +	// Reset object back to beginning of new sequence +	scene._bgShapes[objId]._frameNumber = 0; + +	return RET_CONTINUE; +} + +OpcodeReturn Talk::cmdBanishWindow(const byte *&str) { +	People &people = *_vm->_people; +	UserInterface &ui = *_vm->_ui; + +	if (!(_speaker & SPEAKER_REMOVE)) +		people.clearTalking(); +	pullSequence(); + +	if (_talkToAbort) +		return RET_EXIT; + +	_speaker |= SPEAKER_REMOVE; +	ui.banishWindow(); +	ui._menuMode = TALK_MODE; +	_noTextYet = true; + +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdDisableEndKey(const byte *&str) { +	_vm->_ui->_endKeyActive = false; +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdEnableEndKey(const byte *&str) { +	_vm->_ui->_endKeyActive = true; +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdEndTextWindow(const byte *&str) { +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdHolmesOff(const byte *&str) { +	People &people = *_vm->_people; +	people[HOLMES]._type = REMOVE; +	people._holmesOn = false; + +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdHolmesOn(const byte *&str) { +	People &people = *_vm->_people; +	people[HOLMES]._type = CHARACTER; +	people._holmesOn = true; + +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdPause(const byte *&str) { +	_charCount = *++str; +	_wait = _pauseFlag = true; + +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdPauseWithoutControl(const byte *&str) { +	Events &events = *_vm->_events; +	Scene &scene = *_vm->_scene; +	++str; + +	events.incWaitCounter(); + +	for (int idx = 0; idx < (str[0] - 1); ++idx) { +		scene.doBgAnim(); +		if (_talkToAbort) +			return RET_EXIT; + +		// Check for button press +		events.pollEvents(); +		events.setButtonState(); +	} + +	events.decWaitCounter(); + +	_endStr = false; +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdRemoveItemFromInventory(const byte *&str) { +	Inventory &inv = *_vm->_inventory; +	Common::String tempString; + +	++str; +	for (int idx = 0; idx < str[0]; ++idx) +		tempString += str[idx + 1]; +	str += str[0]; + +	inv.deleteItemFromInventory(tempString); + +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdRunCAnimation(const byte *&str) { +	Scene &scene = *_vm->_scene; + +	++str; +	scene.startCAnim((str[0] - 1) & 127, (str[0] & 0x80) ? -1 : 1); +	if (_talkToAbort) +		return RET_EXIT; + +	// Check if next character is changing side or changing portrait +	_wait = 0; +	if (_charCount && (str[1] == _opcodes[OP_SWITCH_SPEAKER] || +			(IS_SERRATED_SCALPEL && str[1] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION]))) +		_wait = 1; + +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdSetFlag(const byte *&str) { +	++str; +	int flag1 = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0); +	int flag = (flag1 & 0x3fff) * (flag1 >= 0x4000 ? -1 : 1); +	_vm->setFlags(flag); +	++str; + +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdSetObject(const byte *&str) { +	Scene &scene = *_vm->_scene; +	Common::String tempString; + +	++str; +	for (int idx = 0; idx < (str[0] & 127); ++idx) +		tempString += str[idx + 1]; + +	// Set comparison state according to if we want to hide or unhide +	bool state = (str[0] >= SPEAKER_REMOVE); +	str += str[0] & 127; + +	for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { +		Object &object = scene._bgShapes[idx]; +		if (tempString.equalsIgnoreCase(object._name)) { +			// Only toggle the object if it's not in the desired state already +			if ((object._type == HIDDEN && state) || (object._type != HIDDEN && !state)) +				object.toggleHidden(); +		} +	} + +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdStealthModeActivate(const byte *&str) { +	_talkStealth = 2; +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdStealthModeDeactivate(const byte *&str) { +	Events &events = *_vm->_events; + +	_talkStealth = 0; +	events.clearKeyboard(); + +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdToggleObject(const byte *&str) { +	Scene &scene = *_vm->_scene; +	Common::String tempString; + +	++str; +	for (int idx = 0; idx < str[0]; ++idx) +		tempString += str[idx + 1]; + +	scene.toggleObject(tempString); +	str += str[0]; + +	return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdWalkToCAnimation(const byte *&str) { +	People &people = *_vm->_people; +	Scene &scene = *_vm->_scene; + +	++str; +	CAnim &animation = scene._cAnim[str[0] - 1]; +	people[HOLMES].walkToCoords(animation._goto[0], animation._goto[0]._facing); +	 +	return _talkToAbort ? RET_EXIT : RET_SUCCESS; +} + +void Talk::talkWait(const byte *&str) { +	if (!_pauseFlag && _charCount < 160) +		_charCount = 160; + +	_wait = waitForMore(_charCount); +	if (_wait == -1) +		_endStr = true; + +	// If a key was pressed to finish the window, see if further voice files should be skipped +	if (IS_SERRATED_SCALPEL && _wait >= 0 && _wait < 254) { +		if (str[0] == _opcodes[OP_SFX_COMMAND]) +			str += 9; +	} + +	_pauseFlag = false; +} + +} // End of namespace Sherlock  | 
