diff options
Diffstat (limited to 'engines/wage/script.cpp')
| -rw-r--r-- | engines/wage/script.cpp | 1175 | 
1 files changed, 1175 insertions, 0 deletions
| diff --git a/engines/wage/script.cpp b/engines/wage/script.cpp new file mode 100644 index 0000000000..61336dce88 --- /dev/null +++ b/engines/wage/script.cpp @@ -0,0 +1,1175 @@ +/* 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. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "wage/wage.h" +#include "wage/entities.h" +#include "wage/script.h" +#include "wage/world.h" + +#include "common/stream.h" + +namespace Wage { + +Common::String Script::Operand::toString() { +	switch(_type) { +	case NUMBER: +		return Common::String::format("%d", _value.number); +	case STRING: +	case TEXT_INPUT: +		return *_value.string; +	case OBJ: +		return _value.obj->toString(); +	case CHR: +		return _value.chr->toString(); +	case SCENE: +		return _value.scene->toString(); +	case CLICK_INPUT: +		return _value.inputClick->toString(); +	default: +		error("Unhandled operand type: _type"); +	} +} + +Script::Script(Common::SeekableReadStream *data) : _data(data) { +	_engine = NULL; +	_world = NULL; + +	_loopCount = 0; +	_inputText = NULL; +	_inputClick = NULL; + +	_handled = false; + +	convertToText(); +} + +Script::~Script() { +	for (uint i = 0; i < _scriptText.size(); i++) { +		delete _scriptText[i]; +	} + +	delete _data; +} + +void Script::print() { +	for (uint i = 0; i < _scriptText.size(); i++) { +		debug(4, "%d [%04x]: %s", i, _scriptText[i]->offset, _scriptText[i]->line.c_str()); +	} +} + +void Script::printLine(int offset) { +	for (uint i = 0; i < _scriptText.size(); i++) +		if (_scriptText[i]->offset >= offset) { +			debug(4, "%d [%04x]: %s", i, _scriptText[i]->offset, _scriptText[i]->line.c_str()); +			break; +		} +} + +bool Script::execute(World *world, int loopCount, Common::String *inputText, Designed *inputClick, WageEngine *engine) { +	_world = world; +	_loopCount = loopCount; +	_inputText = inputText; +	_inputClick = inputClick; +	_engine = engine; +	_handled = false; +	Common::String input; + +	if (inputText) +		input = *inputText; + +	_data->seek(12); +	while (_data->pos() < _data->size()) { +		printLine(_data->pos()); + +		byte command = _data->readByte(); + +		switch(command) { +		case 0x80: // IF +			processIf(); +			break; +		case 0x87: // EXIT +			debug(6, "exit at offset %d", _data->pos() - 1); + +			return true; +		case 0x89: // MOVE +			{ +				Scene *currentScene = _world->_player->_currentScene; +				processMove(); +				if (_world->_player->_currentScene != currentScene) +					return true; +				break; +			} +		case 0x8B: // PRINT +			{ +				Operand *op = readOperand(); +				// TODO check op type is string or number, or something good... +				_handled = true; +				_engine->appendText(op->toString().c_str()); +				delete op; +				byte d = _data->readByte(); +				if (d != 0xFD) +					warning("Operand 0x8B (PRINT) End Byte != 0xFD"); +				break; +			} +		case 0x8C: // SOUND +			{ +				Operand *op = readOperand(); +				// TODO check op type is string. +				_handled = true; +				_engine->playSound(op->toString()); +				delete op; +				byte d = _data->readByte(); +				if (d != 0xFD) +					warning("Operand 0x8B (PRINT) End Byte != 0xFD"); +				break; +			} +		case 0x8E: // LET +			processLet(); +			break; +		case 0x95: // MENU +			{ +				Operand *op = readStringOperand(); // allows empty menu +				// TODO check op type is string. +				_engine->setMenu(op->toString()); +				delete op; +				byte d = _data->readByte(); +				if (d != 0xFD) +					warning("Operand 0x8B (PRINT) End Byte != 0xFD"); +			} +		case 0x88: // END +			break; +		default: +			debug(0, "Unknown opcode: %d", _data->pos()); +		} +	} + +	if (_world->_globalScript != this) { +		debug(1, "Executing global script..."); +		bool globalHandled = _world->_globalScript->execute(_world, _loopCount, &input, _inputClick, _engine); +		if (globalHandled) +			_handled = true; +	} else if (!input.empty()) { +		if (input.contains("north")) { +			_handled = _engine->handleMoveCommand(NORTH, "north"); +		} else if (input.contains("east")) { +			_handled = _engine->handleMoveCommand(EAST, "east"); +		} else if (input.contains("south")) { +			_handled = _engine->handleMoveCommand(SOUTH, "south"); +		} else if (input.contains("west")) { +			_handled = _engine->handleMoveCommand(WEST, "west"); +		} else if (input.hasPrefix("take ")) { +			_handled = _engine->handleTakeCommand(&input.c_str()[5]); +		} else if (input.hasPrefix("get ")) { +			_handled = _engine->handleTakeCommand(&input.c_str()[4]); +		} else if (input.hasPrefix("pick up ")) { +			_handled = _engine->handleTakeCommand(&input.c_str()[8]); +		} else if (input.hasPrefix("drop ")) { +			_handled = _engine->handleDropCommand(&input.c_str()[5]); +		} else if (input.hasPrefix("aim ")) { +			_handled = _engine->handleAimCommand(&input.c_str()[4]); +		} else if (input.hasPrefix("wear ")) { +			_handled = _engine->handleWearCommand(&input.c_str()[5]); +		} else if (input.hasPrefix("put on ")) { +			_handled = _engine->handleWearCommand(&input.c_str()[7]); +		} else if (input.hasPrefix("offer ")) { +			_handled = _engine->handleOfferCommand(&input.c_str()[6]); +		} else if (input.contains("look")) { +			_handled = _engine->handleLookCommand(); +		} else if (input.contains("inventory")) { +			_handled = _engine->handleInventoryCommand(); +		} else if (input.contains("status")) { +			_handled = _engine->handleStatusCommand(); +		} else if (input.contains("rest") || input.equals("wait")) { +			_handled = _engine->handleRestCommand(); +		} else if (_engine->getOffer() != NULL && input.contains("accept")) { +			_handled = _engine->handleAcceptCommand(); +		} else { +			Chr *player = _world->_player; +			ObjArray *weapons = player->getWeapons(true); +			for (ObjArray::const_iterator weapon = weapons->begin(); weapon != weapons->end(); ++weapon) { +				if (_engine->tryAttack(*weapon, input)) { +					_handled = _engine->handleAttack(*weapon); +					break; +				} +			} + +			delete weapons; +		} +	// TODO: weapons, offer, etc... +	} else if (_inputClick->_classType == OBJ) { +		Obj *obj = (Obj *)_inputClick; +		if (obj->_type != Obj::IMMOBILE_OBJECT) { +			_engine->takeObj(obj); +		} else { +			_engine->appendText(obj->_clickMessage.c_str()); +		} + +		_handled = true; +	} + +	return _handled; +} + +Script::Operand *Script::readOperand() { +	byte operandType = _data->readByte(); + +	debug(7, "%x: readOperand: 0x%x", _data->pos(), operandType); + +	Context *cont = &_world->_player->_context; +	switch (operandType) { +	case 0xA0: // TEXT$ +		return new Operand(_inputText, TEXT_INPUT); +	case 0xA1: +		return new Operand(_inputClick, CLICK_INPUT); +	case 0xC0: // STORAGE@ +		return new Operand(_world->_storageScene, SCENE); +	case 0xC1: // SCENE@ +		return new Operand(_world->_player->_currentScene, SCENE); +	case 0xC2: // PLAYER@ +		return new Operand(_world->_player, CHR); +	case 0xC3: // MONSTER@ +		return new Operand(_engine->getMonster(), CHR); +	case 0xC4: // RANDOMSCN@ +		return new Operand(_world->_orderedScenes[_engine->_rnd->getRandomNumber(_world->_orderedScenes.size())], SCENE); +	case 0xC5: // RANDOMCHR@ +		return new Operand(_world->_orderedChrs[_engine->_rnd->getRandomNumber(_world->_orderedChrs.size())], CHR); +	case 0xC6: // RANDOMOBJ@ +		return new Operand(_world->_orderedObjs[_engine->_rnd->getRandomNumber(_world->_orderedObjs.size())], OBJ); +	case 0xB0: // VISITS# +		return new Operand(cont->_visits, NUMBER); +	case 0xB1: // RANDOM# for Star Trek, but VISITS# for some other games? +		return new Operand(1 + _engine->_rnd->getRandomNumber(100), NUMBER); +	case 0xB5: // RANDOM# // A random number between 1 and 100. +		return new Operand(1 + _engine->_rnd->getRandomNumber(100), NUMBER); +	case 0xB2: // LOOP# +		return new Operand(_loopCount, NUMBER); +	case 0xB3: // VICTORY# +		return new Operand(cont->_kills, NUMBER); +	case 0xB4: // BADCOPY# +		return new Operand(0, NUMBER); // \?\?\?? +	case 0xFF: +		{ +			// user variable +			int value = _data->readByte(); + +			// TODO: Verify that we're using the right index. +			return new Operand(cont->_userVariables[value - 1], NUMBER); +		} +	case 0xD0: +		return new Operand(cont->_statVariables[PHYS_STR_BAS], NUMBER); +	case 0xD1: +		return new Operand(cont->_statVariables[PHYS_HIT_BAS], NUMBER); +	case 0xD2: +		return new Operand(cont->_statVariables[PHYS_ARM_BAS], NUMBER); +	case 0xD3: +		return new Operand(cont->_statVariables[PHYS_ACC_BAS], NUMBER); +	case 0xD4: +		return new Operand(cont->_statVariables[SPIR_STR_BAS], NUMBER); +	case 0xD5: +		return new Operand(cont->_statVariables[SPIR_HIT_BAS], NUMBER); +	case 0xD6: +		return new Operand(cont->_statVariables[SPIR_ARM_BAS], NUMBER); +	case 0xD7: +		return new Operand(cont->_statVariables[SPIR_ACC_BAS], NUMBER); +	case 0xD8: +		return new Operand(cont->_statVariables[PHYS_SPE_BAS], NUMBER); +	case 0xE0: +		return new Operand(cont->_statVariables[PHYS_STR_CUR], NUMBER); +	case 0xE1: +		return new Operand(cont->_statVariables[PHYS_HIT_CUR], NUMBER); +	case 0xE2: +		return new Operand(cont->_statVariables[PHYS_ARM_CUR], NUMBER); +	case 0xE3: +		return new Operand(cont->_statVariables[PHYS_ACC_CUR], NUMBER); +	case 0xE4: +		return new Operand(cont->_statVariables[SPIR_STR_CUR], NUMBER); +	case 0xE5: +		return new Operand(cont->_statVariables[SPIR_HIT_CUR], NUMBER); +	case 0xE6: +		return new Operand(cont->_statVariables[SPIR_ARM_CUR], NUMBER); +	case 0xE7: +		return new Operand(cont->_statVariables[SPIR_ACC_CUR], NUMBER); +	case 0xE8: +		return new Operand(cont->_statVariables[PHYS_SPE_CUR], NUMBER); +	default: +		if (operandType >= 0x20 && operandType < 0x80) { +			_data->seek(-1, SEEK_CUR); +			return readStringOperand(); +		} else { +			debug("Dunno what %x is (index=%d)!\n", operandType, _data->pos()-1); +		} +		return NULL; +	} +} + +void Script::assign(byte operandType, int uservar, uint16 value) { +	Context *cont = &_world->_player->_context; + +	switch (operandType) { +	case 0xFF: +		cont->_userVariables[uservar - 1] = value; +		break; +	case 0xD0: +		cont->_statVariables[PHYS_STR_BAS] = value; +		break; +	case 0xD1: +		cont->_statVariables[PHYS_HIT_BAS] = value; +		break; +	case 0xD2: +		cont->_statVariables[PHYS_ARM_BAS] = value; +		break; +	case 0xD3: +		cont->_statVariables[PHYS_ACC_BAS] = value; +		break; +	case 0xD4: +		cont->_statVariables[SPIR_STR_BAS] = value; +		break; +	case 0xD5: +		cont->_statVariables[SPIR_HIT_BAS] = value; +		break; +	case 0xD6: +		cont->_statVariables[SPIR_ARM_BAS] = value; +		break; +	case 0xD7: +		cont->_statVariables[SPIR_ACC_BAS] = value; +		break; +	case 0xD8: +		cont->_statVariables[PHYS_SPE_BAS] = value; +		break; +	case 0xE0: +		cont->_statVariables[PHYS_STR_CUR] = value; +		break; +	case 0xE1: +		cont->_statVariables[PHYS_HIT_CUR] = value; +		break; +	case 0xE2: +		cont->_statVariables[PHYS_ARM_CUR] = value; +		break; +	case 0xE3: +		cont->_statVariables[PHYS_ACC_CUR] = value; +		break; +	case 0xE4: +		cont->_statVariables[SPIR_STR_CUR] = value; +		break; +	case 0xE5: +		cont->_statVariables[SPIR_HIT_CUR] = value; +		break; +	case 0xE6: +		cont->_statVariables[SPIR_ARM_CUR] = value; +		break; +	case 0xE7: +		cont->_statVariables[SPIR_ACC_CUR] = value; +		break; +	case 0xE8: +		cont->_statVariables[PHYS_SPE_CUR] = value; +		break; +	default: +		debug("No idea what I'm supposed to assign! (%x at %d)!\n", operandType, _data->pos()-1); +	} +} + +Script::Operand *Script::readStringOperand() { +	Common::String *str; +	bool allDigits = true; + +	str = new Common::String(); + +	while (true) { +		byte c = _data->readByte(); +		if (c >= 0x20 && c < 0x80) +			*str += c; +		else +			break; +		if (c < '0' || c > '9') +			allDigits = false; +	} +	_data->seek(-1, SEEK_CUR); + +	if (allDigits && !str->empty()) { +		int r = atol(str->c_str()); +		delete str; + +		return new Operand(r, NUMBER); +	} else { +		// TODO: This string could be a room name or something like that. +		return new Operand(str, STRING); +	} +} + +const char *Script::readOperator() { +	byte cmd = _data->readByte(); + +	debug(7, "readOperator: 0x%x", cmd); +	switch (cmd) { +	case 0x81: +		return "="; +	case 0x82: +		return "<"; +	case 0x83: +		return ">"; +	case 0x8f: +		return "+"; +	case 0x90: +		return "-"; +	case 0x91: +		return "*"; +	case 0x92: +		return "/"; +	case 0x93: +		return "=="; +	case 0x94: +		return ">>"; +	case 0xfd: +		return ";"; +	default: +		warning("UNKNOWN OP %x", cmd); +	} +	return NULL; +} + +void Script::processIf() { +	int logicalOp = 0; // 0 => initial, 1 => and, 2 => or +	bool result = true; +	bool done = false; + +	do { +		Operand *lhs = readOperand(); +		const char *op = readOperator(); +		Operand *rhs = readOperand(); + +		bool condResult = eval(lhs, op, rhs); + +		delete lhs; +		delete rhs; + +		if (logicalOp == 1) { +			result = (result && condResult); +		} else if (logicalOp == 2) { +			result = (result || condResult); +		} else { // logicalOp == 0 +			result = condResult; +		} + +		byte logical = _data->readByte(); + +		if (logical == 0x84) { +			logicalOp = 1; // and +		} else if (logical == 0x85) { +			logicalOp = 2; // or +		} else if (logical == 0xFE) { +			done = true; // then +		} +	} while (!done); + +	if (result == false) { +		skipBlock(); +	} +} + +void Script::skipIf() { +	do { +		Operand *lhs = readOperand(); +		readOperator(); +		Operand *rhs = readOperand(); + +		delete lhs; +		delete rhs; +	} while (_data->readByte() != 0xFE); +} + +void Script::skipBlock() { +	int nesting = 1; + +	while (true) { +		byte op = _data->readByte(); + +		if (_data->eos()) +			return; + +		if (op == 0x80) { // IF +			nesting++; +			skipIf(); +		} else if (op == 0x88 || op == 0x87) { // END or EXIT +			nesting--; +			if (nesting == 0) { +				return; +			} +		} else switch (op) { +			case 0x8B: // PRINT +			case 0x8C: // SOUND +			case 0x8E: // LET +			case 0x95: // MENU +				while (_data->readByte() != 0xFD) +					; +		} +	} +} + +enum { +	kCompEqNumNum, +	kCompEqObjScene, +	kCompEqChrScene, +	kCompEqObjChr, +	kCompEqChrChr, +	kCompEqSceneScene, +	kCompEqStringTextInput, +	kCompEqTextInputString, +	kCompEqNumberTextInput, +	kCompEqTextInputNumber, +	kCompLtNumNum, +	kCompLtStringTextInput, +	kCompLtTextInputString, +	kCompLtObjChr, +	kCompLtChrObj, +	kCompLtObjScene, +	kCompGtNumNum, +	kCompGtStringString, +	kCompGtChrScene, +	kMoveObjChr, +	kMoveObjScene, +	kMoveChrScene +}; + +static const char *typeNames[] = { +	"OBJ", +	"CHR", +	"SCENE", +	"NUMBER", +	"STRING", +	"CLICK_INPUT", +	"TEXT_INPUT" +}; + +static const char *operandTypeToStr(int type) { +	if (type < 0 || type > 6) +		return "UNKNOWN"; + +	return typeNames[type]; +} + +struct Comparator { +	char op; +	OperandType o1; +	OperandType o2; +	int cmp; +} static comparators[] = { +	{ '=', NUMBER, NUMBER, kCompEqNumNum }, +	{ '=', OBJ, SCENE, kCompEqObjScene }, +	{ '=', CHR, SCENE, kCompEqChrScene }, +	{ '=', OBJ, CHR, kCompEqObjChr }, +	{ '=', CHR, CHR, kCompEqChrChr }, +	{ '=', SCENE, SCENE, kCompEqSceneScene }, +	{ '=', STRING, TEXT_INPUT, kCompEqStringTextInput }, +	{ '=', TEXT_INPUT, STRING, kCompEqTextInputString }, +	{ '=', NUMBER, TEXT_INPUT, kCompEqNumberTextInput }, +	{ '=', TEXT_INPUT, NUMBER, kCompEqTextInputNumber }, + +	{ '<', NUMBER, NUMBER, kCompLtNumNum }, +	{ '<', STRING, TEXT_INPUT, kCompLtStringTextInput }, +	{ '<', TEXT_INPUT, STRING, kCompLtTextInputString }, +	{ '<', OBJ, CHR, kCompLtObjChr }, +	{ '<', CHR, OBJ, kCompLtChrObj }, +	{ '<', OBJ, SCENE, kCompLtObjScene }, +	{ '<', CHR, CHR, kCompEqChrChr }, // Same logic as = +	{ '<', SCENE, SCENE, kCompEqSceneScene }, + +	{ '>', NUMBER, NUMBER, kCompGtNumNum }, +	{ '>', TEXT_INPUT, STRING, kCompLtTextInputString }, // Same logic as < +	//FIXME: this prevents the below cases from working due to exact +	//matches taking precedence over conversions... +	//{ '>', STRING, STRING, kCompGtStringString }, // Same logic as < +	{ '>', OBJ, CHR, kCompLtObjChr }, // Same logic as < +	{ '>', OBJ, SCENE, kCompLtObjScene }, // Same logic as < +	{ '>', CHR, SCENE, kCompGtChrScene }, + +	{ 'M', OBJ, CHR, kMoveObjChr }, +	{ 'M', OBJ, SCENE, kMoveObjScene }, +	{ 'M', CHR, SCENE, kMoveChrScene }, +	{ 0, OBJ, OBJ, 0 } +}; + +bool Script::compare(Operand *o1, Operand *o2, int comparator) { +	switch(comparator) { +	case kCompEqNumNum: +		return o1->_value.number == o2->_value.number; +	case kCompEqObjScene: +		for (ObjList::const_iterator it = o2->_value.scene->_objs.begin(); it != o2->_value.scene->_objs.end(); ++it) +			if (*it == o1->_value.obj) +				return true; +		return false; +	case kCompEqChrScene: +		for (ChrList::const_iterator it = o2->_value.scene->_chrs.begin(); it != o2->_value.scene->_chrs.end(); ++it) +			if (*it == o1->_value.chr) +				return true; +		return false; +	case kCompEqObjChr: +		for (ObjArray::const_iterator it = o2->_value.chr->_inventory.begin(); it != o2->_value.chr->_inventory.end(); ++it) +			if (*it == o1->_value.obj) +				return true; +		return false; +	case kCompEqChrChr: +		return o1->_value.chr == o2->_value.chr; +	case kCompEqSceneScene: +		return o1->_value.scene == o2->_value.scene; +	case kCompEqStringTextInput: +		if (_inputText == NULL) { +			return false; +		} else { +			Common::String s1(*_inputText), s2(*o1->_value.string); +			s1.toLowercase(); +			s2.toLowercase(); + +			return s1.contains(s2); +		} +	case kCompEqTextInputString: +		return compare(o2, o1, kCompEqStringTextInput); +	case kCompEqNumberTextInput: +		if (_inputText == NULL) { +			return false; +		} else { +			Common::String s1(*_inputText), s2(o1->toString()); +			s1.toLowercase(); +			s2.toLowercase(); + +			return s1.contains(s2); +		} +	case kCompEqTextInputNumber: +		if (_inputText == NULL) { +			return false; +		} else { +			Common::String s1(*_inputText), s2(o2->toString()); +			s1.toLowercase(); +			s2.toLowercase(); + +			return s1.contains(s2); +		} +	case kCompLtNumNum: +		return o1->_value.number < o2->_value.number; +	case kCompLtStringTextInput: +		return !compare(o1, o2, kCompEqStringTextInput); +	case kCompLtTextInputString: +		return !compare(o2, o1, kCompEqStringTextInput); +	case kCompLtObjChr: +		return o1->_value.obj->_currentOwner != o2->_value.chr; +	case kCompLtChrObj: +		return compare(o2, o1, kCompLtObjChr); +	case kCompLtObjScene: +		return o1->_value.obj->_currentScene != o2->_value.scene; +	case kCompGtNumNum: +		return o1->_value.number > o2->_value.number; +	case kCompGtStringString: +		return o1->_value.string == o2->_value.string; +	case kCompGtChrScene: +		return (o1->_value.chr != NULL && o1->_value.chr->_currentScene != o2->_value.scene); +	case kMoveObjChr: +		if (o1->_value.obj->_currentOwner != o2->_value.chr) { +			_world->move(o1->_value.obj, o2->_value.chr); +			_handled = true;  // TODO: Is this correct? +		} +		break; +	case kMoveObjScene: +		if (o1->_value.obj->_currentScene != o2->_value.scene) { +			_world->move(o1->_value.obj, o2->_value.scene); +			// Note: This shouldn't call setHandled() - see +			// Sultan's Palace 'Food and Drink' scene. +		} +		break; +	case kMoveChrScene: +		_world->move(o1->_value.chr, o2->_value.scene); +		_handled = true;  // TODO: Is this correct? +		break; +	} + +	return false; +} + +bool Script::evaluatePair(Operand *lhs, const char *op, Operand *rhs) { +	debug(7, "HANDLING CASE: [lhs=%s/%s, op=%s rhs=%s/%s]", +		operandTypeToStr(lhs->_type), lhs->toString().c_str(), op, operandTypeToStr(rhs->_type), rhs->toString().c_str()); + +	for (int cmp = 0; comparators[cmp].op != 0; cmp++) { +		if (comparators[cmp].op != op[0]) +			continue; + +		if (comparators[cmp].o1 == lhs->_type && comparators[cmp].o2 == rhs->_type) +			return compare(lhs, rhs, comparators[cmp].cmp); +	} + +	// Now, try partial matches. +	Operand *c1, *c2; +	for (int cmp = 0; comparators[cmp].op != 0; cmp++) { +		if (comparators[cmp].op != op[0]) +			continue; + +		if (comparators[cmp].o1 == lhs->_type && +				(c2 = convertOperand(rhs, comparators[cmp].o2)) != NULL) { +			bool res = compare(lhs, c2, comparators[cmp].cmp); +			delete c2; +			return res; +		} else if (comparators[cmp].o2 == rhs->_type && +				(c1 = convertOperand(lhs, comparators[cmp].o1)) != NULL) { +			bool res = compare(c1, rhs, comparators[cmp].cmp); +			delete c1; +			return res; +		} +	} + +	// Now, try double conversion. +	for (int cmp = 0; comparators[cmp].op != 0; cmp++) { +		if (comparators[cmp].op != op[0]) +			continue; + +		if (comparators[cmp].o1 == lhs->_type || comparators[cmp].o2 == rhs->_type) +			continue; + +		if ((c1 = convertOperand(lhs, comparators[cmp].o1)) != NULL) { +			if ((c2 = convertOperand(rhs, comparators[cmp].o2)) != NULL) { +				bool res = compare(c1, c2, comparators[cmp].cmp); +				delete c1; +				delete c2; +				return res; +			} +			delete c1; +		} +	} + +	warning("UNHANDLED CASE: [lhs=%s/%s, op=%s rhs=%s/%s]", +		operandTypeToStr(lhs->_type), lhs->toString().c_str(), op, operandTypeToStr(rhs->_type), rhs->toString().c_str()); + +	return false; +} + +bool Script::eval(Operand *lhs, const char *op, Operand *rhs) { +	bool result = false; + +	if (lhs->_type == CLICK_INPUT || rhs->_type == CLICK_INPUT) { +		return evalClickCondition(lhs, op, rhs); +	} else if (!strcmp(op, "==") || !strcmp(op, ">>")) { +		// TODO: check if >> can be used for click inputs and if == can be used for other things +		// exact string match +		if (lhs->_type == TEXT_INPUT) { +			if ((rhs->_type != STRING && rhs->_type != NUMBER) || _inputText == NULL) { +				result = false; +			} else { +				result = _inputText->equalsIgnoreCase(rhs->toString()); +			} +		} else if (rhs->_type == TEXT_INPUT) { +			if ((lhs->_type != STRING && lhs->_type != NUMBER) || _inputText == NULL) { +				result = false; +			} else { +				result = _inputText->equalsIgnoreCase(lhs->toString()); +			} +		} else { +			error("UNHANDLED CASE: [lhs=%s/%s, rhs=%s/%s]", +				operandTypeToStr(lhs->_type), lhs->toString().c_str(), operandTypeToStr(rhs->_type), rhs->toString().c_str()); +		} +		if (!strcmp(op, ">>")) { +			result = !result; +		} + +		return result; +	} else { +		return evaluatePair(lhs, op, rhs); +	} + +	return false; +} + +Script::Operand *Script::convertOperand(Operand *operand, int type) { +	if (operand->_type == type) +		error("Incorrect conversion to type %d", type); + +	if (type == SCENE) { +		if (operand->_type == STRING || operand->_type == NUMBER) { +			Common::String key(operand->toString()); +			key.toLowercase(); +			if (_world->_scenes.contains(key)) +				return new Operand(_world->_scenes[key], SCENE); +		} +	} else if (type == OBJ) { +		if (operand->_type == STRING || operand->_type == NUMBER) { +			Common::String key = operand->toString(); +			key.toLowercase(); +			if (_world->_objs.contains(key)) +				return new Operand(_world->_objs[key], OBJ); +		} else if (operand->_type == CLICK_INPUT) { +			if (_inputClick->_classType == OBJ) +				return new Operand(_inputClick, OBJ); +		} +	} else if (type == CHR) { +		if (operand->_type == STRING || operand->_type == NUMBER) { +			Common::String key = operand->toString(); +			key.toLowercase(); +			if (_world->_chrs.contains(key)) +				return new Operand(_world->_chrs[key], CHR); +		} else if (operand->_type == CLICK_INPUT) { +			if (_inputClick->_classType == CHR) +				return new Operand(_inputClick, CHR); +		} +	} + +	return NULL; +} + +bool Script::evalClickEquality(Operand *lhs, Operand *rhs, bool partialMatch) { +	bool result = false; +	if (lhs->_value.obj == NULL || rhs->_value.obj == NULL) { +		result = false; +	} else if (lhs->_value.obj == rhs->_value.obj) { +		result = true; +	} else if (rhs->_type == STRING) { +		Common::String str = rhs->toString(); +		str.toLowercase(); + +		debug(9, "evalClickEquality(%s, %s, %d)", lhs->_value.designed->_name.c_str(), rhs->_value.designed->_name.c_str(), partialMatch); +		debug(9, "l: %s r: %s)", operandTypeToStr(lhs->_type), operandTypeToStr(rhs->_type)); +		debug(9, "class: %d", lhs->_value.inputClick->_classType); + +		if (lhs->_value.inputClick->_classType == CHR || lhs->_value.inputClick->_classType == OBJ) { +			Common::String name = lhs->_value.designed->_name; +			name.toLowercase(); + +			if (partialMatch) +				result = name.contains(str); +			else +				result = name.equals(str); +		} + +		debug(9, "result: %d", result); +	} +	return result; +} + +bool Script::evalClickCondition(Operand *lhs, const char *op, Operand *rhs) { +	// TODO: check if >> can be used for click inputs +	if (strcmp(op, "==") && strcmp(op, "=") && strcmp(op, "<") && strcmp(op, ">")) { +		error("Unknown operation '%s' for Script::evalClickCondition", op); +	} + +	bool partialMatch = strcmp(op, "=="); +	bool result; +	if (lhs->_type == CLICK_INPUT) { +		result = evalClickEquality(lhs, rhs, partialMatch); +	} else { +		result = evalClickEquality(rhs, lhs, partialMatch); +	} +	if (!strcmp(op, "<") || !strcmp(op, ">")) { +		// CLICK$<FOO only matches if there was a click +		if (_inputClick == NULL) { +			result = false; +		} else { +			result = !result; +		} +	} +	return result; +} + +void Script::processMove() { +	Operand *what = readOperand(); +	byte skip = _data->readByte(); +	if (skip != 0x8a) +		error("Incorrect operator for MOVE: %02x", skip); + +	Operand *to = readOperand(); + +	skip = _data->readByte(); +	if (skip != 0xfd) +		error("No end for MOVE: %02x", skip); + +	evaluatePair(what, "M", to); + +	delete what; +	delete to; +} + +void Script::processLet() { +	const char *lastOp = NULL; +	int16 result = 0; +	int operandType = _data->readByte(); +	int uservar = 0; + +	if (operandType == 0xff) { +		uservar = _data->readByte(); +	} + +	byte eq = _data->readByte(); // skip "=" operator + +	debug(7, "processLet: 0x%x, uservar: 0x%x, eq: 0x%x", operandType, uservar, eq); + +	do { +		Operand *operand = readOperand(); +		// TODO assert that value is NUMBER +		int16 value = operand->_value.number; +		delete operand; +		if (lastOp != NULL) { +			if (lastOp[0] == '+') +				result += value; +			else if (lastOp[0] == '-') +				result -= value; +			else if (lastOp[0] == '/') +				result = (int16)(value == 0 ? 0 : result / value); +			else if (lastOp[0] == '*') +				result *= value; +		} else { +			result = value; +		} +		lastOp = readOperator(); + +		if (lastOp[0] == ';') +			break; +	} while (true); +	//System.out.println("processLet " + buildStringFromOffset(oldIndex - 1, index - oldIndex + 1) + "}"); + +	assign(operandType, uservar, result); +} + +enum { +	BLOCK_START, +	BLOCK_END, +	STATEMENT, +	OPERATOR, +	OPCODE +}; + +struct Mapping { +	const char *cmd; +	int type; +} static const mapping[] = { +	{ "IF{", STATEMENT }, // 0x80 +	{ "=", OPERATOR }, +	{ "<", OPERATOR }, +	{ ">", OPERATOR }, +	{ "}AND{", OPCODE }, +	{ "}OR{", OPCODE }, +	{ "\?\?\?(0x86)", OPCODE }, +	{ "EXIT\n", BLOCK_END }, +	{ "END\n", BLOCK_END }, // 0x88 +	{ "MOVE{", STATEMENT }, +	{ "}TO{", OPCODE }, +	{ "PRINT{", STATEMENT }, +	{ "SOUND{", STATEMENT }, +	{ "\?\?\?(0x8d)", OPCODE }, +	{ "LET{", STATEMENT }, +	{ "+", OPERATOR }, +	{ "-", OPERATOR }, // 0x90 +	{ "*", OPERATOR }, +	{ "/", OPERATOR }, +	{ "==", OPERATOR }, +	{ ">>", OPERATOR }, +	{ "MENU{", STATEMENT }, +	{ "\?\?\?(0x96)", OPCODE }, +	{ "\?\?\?(0x97)", OPCODE }, +	{ "\?\?\?(0x98)", OPCODE }, // 0x98 +	{ "\?\?\?(0x99)", OPCODE }, +	{ "\?\?\?(0x9a)", OPCODE }, +	{ "\?\?\?(0x9b)", OPCODE }, +	{ "\?\?\?(0x9c)", OPCODE }, +	{ "\?\?\?(0x9d)", OPCODE }, +	{ "\?\?\?(0x9e)", OPCODE }, +	{ "\?\?\?(0x9f)", OPCODE }, +	{ "TEXT$", OPCODE }, // 0xa0 +	{ "CLICK$", OPCODE }, +	{ "\?\?\?(0xa2)", OPCODE }, +	{ "\?\?\?(0xa3)", OPCODE }, +	{ "\?\?\?(0xa4)", OPCODE }, +	{ "\?\?\?(0xa5)", OPCODE }, +	{ "\?\?\?(0xa6)", OPCODE }, +	{ "\?\?\?(0xa7)", OPCODE }, +	{ "\?\?\?(0xa8)", OPCODE }, // 0xa8 +	{ "\?\?\?(0xa9)", OPCODE }, +	{ "\?\?\?(0xaa)", OPCODE }, +	{ "\?\?\?(0xab)", OPCODE }, +	{ "\?\?\?(0xac)", OPCODE }, +	{ "\?\?\?(0xad)", OPCODE }, +	{ "\?\?\?(0xae)", OPCODE }, +	{ "\?\?\?(0xaf)", OPCODE }, +	{ "VISITS#", OPCODE }, // 0xb0 // The number of scenes the player has visited, including repeated visits. +	{ "RANDOM#", OPCODE }, // RANDOM# for Star Trek, but VISITS# for some other games? +	{ "LOOP#", OPCODE },   // The number of commands the player has given in the current scene. +	{ "VICTORY#", OPCODE }, // The number of characters killed. +	{ "BADCOPY#", OPCODE }, +	{ "RANDOM#", OPCODE }, // A random number between 1 and 100. +	{ "\?\?\?(0xb6)", OPCODE }, +	{ "\?\?\?(0xb7)", OPCODE }, +	{ "\?\?\?(0xb8)", OPCODE }, // 0xb8 +	{ "\?\?\?(0xb9)", OPCODE }, +	{ "\?\?\?(0xba)", OPCODE }, +	{ "\?\?\?(0xbb)", OPCODE }, +	{ "\?\?\?(0xbc)", OPCODE }, +	{ "\?\?\?(0xbd)", OPCODE }, +	{ "\?\?\?(0xbe)", OPCODE }, +	{ "\?\?\?(0xbf)", OPCODE }, +	{ "STORAGE@", OPCODE }, // 0xc0 +	{ "SCENE@", OPCODE }, +	{ "PLAYER@", OPCODE }, +	{ "MONSTER@", OPCODE }, +	{ "RANDOMSCN@", OPCODE }, +	{ "RANDOMCHR@", OPCODE }, +	{ "RANDOMOBJ@", OPCODE }, +	{ "\?\?\?(0xc7)", OPCODE }, +	{ "\?\?\?(0xc8)", OPCODE }, // 0xc8 +	{ "\?\?\?(0xc9)", OPCODE }, +	{ "\?\?\?(0xca)", OPCODE }, +	{ "\?\?\?(0xcb)", OPCODE }, +	{ "\?\?\?(0xcc)", OPCODE }, +	{ "\?\?\?(0xcd)", OPCODE }, +	{ "\?\?\?(0xce)", OPCODE }, +	{ "\?\?\?(0xcf)", OPCODE }, +	{ "PHYS.STR.BAS#", OPCODE }, // 0xd0 +	{ "PHYS.HIT.BAS#", OPCODE }, +	{ "PHYS.ARM.BAS#", OPCODE }, +	{ "PHYS.ACC.BAS#", OPCODE }, +	{ "SPIR.STR.BAS#", OPCODE }, +	{ "SPIR.HIT.BAS#", OPCODE }, +	{ "SPIR.ARM.BAS#", OPCODE }, +	{ "SPIR.ACC.BAS#", OPCODE }, +	{ "PHYS.SPE.BAS#", OPCODE }, // 0xd8 +	{ "\?\?\?(0xd9)", OPCODE }, +	{ "\?\?\?(0xda)", OPCODE }, +	{ "\?\?\?(0xdb)", OPCODE }, +	{ "\?\?\?(0xdc)", OPCODE }, +	{ "\?\?\?(0xdd)", OPCODE }, +	{ "\?\?\?(0xde)", OPCODE }, +	{ "\?\?\?(0xdf)", OPCODE }, +	{ "PHYS.STR.CUR#", OPCODE }, // 0xe0 +	{ "PHYS.HIT.CUR#", OPCODE }, +	{ "PHYS.ARM.CUR#", OPCODE }, +	{ "PHYS.ACC.CUR#", OPCODE }, +	{ "SPIR.STR.CUR#", OPCODE }, +	{ "SPIR.HIT.CUR#", OPCODE }, +	{ "SPIR.ARM.CUR#", OPCODE }, +	{ "SPIR.ACC.CUR#", OPCODE }, +	{ "PHYS.SPE.CUR#", OPCODE }, // 0xe8 +	{ "\?\?\?(0xe9)", OPCODE }, +	{ "\?\?\?(0xea)", OPCODE }, +	{ "\?\?\?(0xeb)", OPCODE }, +	{ "\?\?\?(0xec)", OPCODE }, +	{ "\?\?\?(0xed)", OPCODE }, +	{ "\?\?\?(0xee)", OPCODE }, +	{ "\?\?\?(0xef)", OPCODE }, +	{ "\?\?\?(0xf0)", OPCODE }, +	{ "\?\?\?(0xf1)", OPCODE }, +	{ "\?\?\?(0xf2)", OPCODE }, +	{ "\?\?\?(0xf3)", OPCODE }, +	{ "\?\?\?(0xf4)", OPCODE }, +	{ "\?\?\?(0xf5)", OPCODE }, +	{ "\?\?\?(0xf6)", OPCODE }, +	{ "\?\?\?(0xf7)", OPCODE }, +	{ "\?\?\?(0xf8)", OPCODE }, // 0xf8 +	{ "\?\?\?(0xf9)", OPCODE }, +	{ "\?\?\?(0xfa)", OPCODE }, +	{ "\?\?\?(0xfb)", OPCODE }, +	{ "\?\?\?(0xfc)", OPCODE }, +	{ "}\n", OPCODE }, +	{ "}THEN\n", BLOCK_START }, +	{ "\?\?\?(0xff)", OPCODE } // Uservar +}; + +void Script::convertToText() { +	_data->seek(12); + +	int indentLevel = 0; +	ScriptText *scr = new ScriptText; +	scr->offset = _data->pos(); + +	while(true) { +		int c = _data->readByte(); + +		if (_data->eos()) +			break; + +		if (c < 0x80) { +			if (c < 0x20) +				error("convertToText: Unknown code 0x%02x at %d", c, _data->pos()); + +			do { +				scr->line += c; +				c = _data->readByte(); +			} while (c < 0x80); + +			_data->seek(-1, SEEK_CUR); +		} else if (c == 0xff) { +			int value = _data->readByte(); +			value -= 1; +			scr->line += (char)('A' + (value / 9)); +			scr->line += (char)('0' + (value % 9) + 1); +			scr->line += '#'; +		} else { +			const char *cmd = mapping[c - 0x80].cmd; +			int type = mapping[c - 0x80].type; + +			if (type == STATEMENT) { +				for (int i = 0; i < indentLevel; i++) +					scr->line += ' '; +			} else if (type == BLOCK_START) { +				indentLevel += 2; +			} else if (type == BLOCK_END) { +				indentLevel -= 2; +				for (int i = 0; i < indentLevel; i++) +					scr->line += ' '; +			} + +			scr->line += cmd; + +			if (strchr(cmd, '\n')) { +				scr->line.deleteLastChar(); + +				_scriptText.push_back(scr); + +				scr = new ScriptText; +				scr->offset = _data->pos(); +			} +		} +	} + +	if (!scr->line.empty()) +		_scriptText.push_back(scr); +	else +		delete scr; +} + +} // End of namespace Wage | 
