mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
In the original interpreter, if the first test following an OR opcode fails, it doesn't execute the second, whereas ScummVM does. This causes a crash in function 19h, where an OR first tests if the noun is an item, and then if the item is takeable. Rather than risking fiddling around with how the OR functions, for now I've simply added an extra check to OPCODE_OBJECT_TAKEABLE to allow for it
857 lines
24 KiB
C++
857 lines
24 KiB
C++
/* 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 "glk/comprehend/game_opcodes.h"
|
|
#include "glk/comprehend/game_data.h"
|
|
#include "glk/comprehend/comprehend.h"
|
|
#include "common/algorithm.h"
|
|
#include "common/textconsole.h"
|
|
|
|
namespace Glk {
|
|
namespace Comprehend {
|
|
|
|
ComprehendGameOpcodes::ComprehendGameOpcodes() {
|
|
Common::fill(&_opcodeMap[0], &_opcodeMap[0x100], OPCODE_UNKNOWN);
|
|
}
|
|
|
|
void ComprehendGameOpcodes::execute_opcode(const Instruction *instr, const Sentence *sentence,
|
|
FunctionState *func_state) {
|
|
byte verb = sentence ? sentence->_formattedWords[0] : 0;
|
|
byte noun = sentence ? sentence->_formattedWords[2] : 0;
|
|
Room *room = get_room(_currentRoom);
|
|
Item *item;
|
|
uint index;
|
|
|
|
byte opcode = getOpcode(instr);
|
|
switch (_opcodeMap[opcode]) {
|
|
case OPCODE_CALL_FUNC:
|
|
index = instr->_operand[0];
|
|
if (instr->_operand[1] == 0x81)
|
|
index += 256;
|
|
if (index >= _functions.size())
|
|
error("Bad function %.4x >= %.4x\n", index, _functions.size());
|
|
|
|
eval_function(index, sentence);
|
|
break;
|
|
|
|
case OPCODE_CLEAR_CAN_TAKE:
|
|
item = get_item_by_noun(noun);
|
|
item->_flags &= ~ITEMF_CAN_TAKE;
|
|
break;
|
|
|
|
case OPCODE_CLEAR_FLAG:
|
|
_flags[instr->_operand[0]] = false;
|
|
break;
|
|
|
|
case OPCODE_CLEAR_FLAG40:
|
|
item = getItem(instr);
|
|
item->_flags &= ~ITEMF_UNKNOWN;
|
|
break;
|
|
|
|
case OPCODE_CLEAR_INVISIBLE:
|
|
item = get_item_by_noun(noun);
|
|
item->_flags &= ~ITEMF_INVISIBLE;
|
|
break;
|
|
|
|
case OPCODE_CLEAR_WORD:
|
|
item = getItem(instr);
|
|
item->_word = 0;
|
|
break;
|
|
|
|
case OPCODE_CURRENT_OBJECT_NOT_VALID:
|
|
func_set_test_result(func_state, !noun);
|
|
break;
|
|
|
|
case OPCODE_CURRENT_IS_OBJECT:
|
|
item = get_item_by_noun(noun);
|
|
func_set_test_result(func_state, item != nullptr);
|
|
break;
|
|
|
|
case OPCODE_ELSE:
|
|
func_state->_testResult = func_state->_elseResult;
|
|
break;
|
|
|
|
case OPCODE_HAVE_CURRENT_OBJECT:
|
|
item = get_item_by_noun(noun);
|
|
func_set_test_result(func_state, item->_room == ROOM_INVENTORY);
|
|
break;
|
|
|
|
case OPCODE_HAVE_OBJECT:
|
|
item = getItem(instr);
|
|
func_set_test_result(func_state, item->_room == ROOM_INVENTORY);
|
|
break;
|
|
|
|
case OPCODE_IN_ROOM:
|
|
func_set_test_result(func_state, _currentRoom == instr->_operand[0]);
|
|
break;
|
|
|
|
case OPCODE_INVENTORY: {
|
|
uint count = num_objects_in_room(ROOM_INVENTORY);
|
|
if (count == 0) {
|
|
console_println(stringLookup(STRING_INVENTORY_EMPTY).c_str());
|
|
break;
|
|
}
|
|
|
|
console_println(stringLookup(STRING_INVENTORY).c_str());
|
|
for (uint i = 0; i < _items.size(); i++) {
|
|
item = &_items[i];
|
|
if (item->_room == ROOM_INVENTORY)
|
|
g_comprehend->print("%s\n",
|
|
stringLookup(item->_stringDesc).c_str());
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OPCODE_MOVE_DEFAULT:
|
|
// Move in the direction dictated by the current verb
|
|
if (verb - 1 >= NR_DIRECTIONS)
|
|
error("Bad verb %d in move", verb);
|
|
|
|
if (room->_direction[verb - 1])
|
|
move_to(room->_direction[verb - 1]);
|
|
else
|
|
console_println(stringLookup(STRING_CANT_GO).c_str());
|
|
break;
|
|
|
|
case OPCODE_MOVE_OBJECT_TO_CURRENT_ROOM:
|
|
item = getItem(instr);
|
|
move_object(item, _currentRoom);
|
|
break;
|
|
|
|
case OPCODE_MOVE_OBJECT_TO_ROOM:
|
|
item = getItem(instr);
|
|
move_object(item, instr->_operand[1]);
|
|
break;
|
|
|
|
case OPCODE_MOVE_TO_ROOM:
|
|
if (instr->_operand[0] != 0xff)
|
|
move_to(instr->_operand[0]);
|
|
break;
|
|
|
|
case OPCODE_OBJECT_IN_ROOM:
|
|
item = getItem(instr);
|
|
func_set_test_result(func_state, item->_room == instr->_operand[1]);
|
|
break;
|
|
|
|
case OPCODE_OBJECT_IS_NOWHERE:
|
|
item = getItem(instr);
|
|
func_set_test_result(func_state, item->_room == ROOM_NOWHERE);
|
|
break;
|
|
|
|
case OPCODE_OBJECT_PRESENT:
|
|
item = getItem(instr);
|
|
func_set_test_result(func_state, item->_room == _currentRoom);
|
|
break;
|
|
|
|
case OPCODE_OR:
|
|
if (func_state->_orCount) {
|
|
func_state->_orCount += 2;
|
|
} else {
|
|
func_state->_testResult = false;
|
|
func_state->_orCount += 3;
|
|
}
|
|
break;
|
|
|
|
case OPCODE_PRINT:
|
|
console_println(instrStringLookup(instr->_operand[0], instr->_operand[1]).c_str());
|
|
break;
|
|
|
|
case OPCODE_RANDOM_MSG: {
|
|
int msgId = (instr->_operand[2] << 8 | instr->_operand[1]) +
|
|
getRandomNumber(instr->_operand[0] - 1);
|
|
console_println(stringLookup(msgId).c_str());
|
|
break;
|
|
}
|
|
|
|
case OPCODE_REMOVE_OBJECT:
|
|
item = getItem(instr);
|
|
move_object(item, ROOM_NOWHERE);
|
|
break;
|
|
|
|
case OPCODE_SAVE_ACTION:
|
|
// Causes the next sentence inputed to re-use the first word of the current one.
|
|
// As far as I'm aware, this is only used for handling responses to questions
|
|
_nounState = NOUNSTATE_QUERY;
|
|
// fall-through
|
|
|
|
case OPCODE_CLEAR_LINE:
|
|
// Resets the input line, removing any pending further actions that were specified
|
|
Common::fill(&_inputLine[0], &_inputLine[INPUT_LINE_SIZE], 0);
|
|
_inputLineIndex = 0;
|
|
break;
|
|
|
|
case OPCODE_SET_CAN_TAKE:
|
|
item = get_item_by_noun(noun);
|
|
item->_flags |= ITEMF_CAN_TAKE;
|
|
break;
|
|
|
|
case OPCODE_SET_FLAG:
|
|
_flags[instr->_operand[0]] = true;
|
|
break;
|
|
|
|
case OPCODE_SET_FLAG40:
|
|
item = getItem(instr);
|
|
item->_flags |= ITEMF_UNKNOWN;
|
|
break;
|
|
|
|
case OPCODE_SET_INVISIBLE:
|
|
item = get_item_by_noun(noun);
|
|
item->_flags |= ITEMF_INVISIBLE;
|
|
break;
|
|
|
|
case OPCODE_SET_OBJECT_DESCRIPTION:
|
|
item = getItem(instr);
|
|
item->_stringDesc = (instr->_operand[2] << 8) | instr->_operand[1];
|
|
break;
|
|
|
|
case OPCODE_SET_ROOM_DESCRIPTION:
|
|
room = get_room(instr->_operand[0]);
|
|
switch (instr->_operand[2]) {
|
|
case 0x80:
|
|
room->_stringDesc = instr->_operand[1];
|
|
break;
|
|
case 0x81:
|
|
room->_stringDesc = instr->_operand[1] + 0x100;
|
|
break;
|
|
case 0x82:
|
|
room->_stringDesc = instr->_operand[1] + 0x200;
|
|
break;
|
|
default:
|
|
error("Bad string desc %.2x:%.2x\n", instr->_operand[1], instr->_operand[2]);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case OPCODE_SET_ROOM_GRAPHIC:
|
|
room = get_room(instr->_operand[0]);
|
|
room->_graphic = instr->_operand[1];
|
|
if (instr->_operand[0] == _currentRoom)
|
|
_updateFlags |= UPDATE_GRAPHICS;
|
|
break;
|
|
|
|
case OPCODE_SET_STRING_REPLACEMENT1:
|
|
_currentReplaceWord = (instr->_operand[0] & 0x80) - 1;
|
|
break;
|
|
|
|
case OPCODE_SET_STRING_REPLACEMENT2:
|
|
_currentReplaceWord = instr->_operand[0] - 1;
|
|
break;
|
|
|
|
case OPCODE_SET_WORD:
|
|
item = getItem(instr);
|
|
item->_word = instr->_operand[1];
|
|
break;
|
|
|
|
case OPCODE_SPECIAL:
|
|
// Game specific opcode
|
|
handleSpecialOpcode(instr->_operand[0]);
|
|
break;
|
|
|
|
case OPCODE_TAKE_CURRENT_OBJECT:
|
|
item = get_item_by_noun(noun);
|
|
if (!item)
|
|
error("Attempt to take object failed\n");
|
|
|
|
move_object(item, ROOM_INVENTORY);
|
|
break;
|
|
|
|
case OPCODE_TAKE_OBJECT:
|
|
item = getItem(instr);
|
|
move_object(item, ROOM_INVENTORY);
|
|
break;
|
|
|
|
case OPCODE_TEST_FLAG:
|
|
func_set_test_result(func_state, _flags[instr->_operand[0]]);
|
|
break;
|
|
|
|
case OPCODE_TEST_ROOM_FLAG:
|
|
func_set_test_result(func_state, room->_flags & instr->_operand[0]);
|
|
break;
|
|
|
|
case OPCODE_TURN_TICK:
|
|
_variables[VAR_TURN_COUNT]++;
|
|
break;
|
|
|
|
case OPCODE_VAR_ADD:
|
|
_variables[instr->_operand[0]] += _variables[instr->_operand[1]];
|
|
break;
|
|
|
|
case OPCODE_VAR_DEC:
|
|
_variables[instr->_operand[0]]--;
|
|
break;
|
|
|
|
case OPCODE_VAR_EQ2:
|
|
func_set_test_result(func_state,
|
|
_variables[instr->_operand[0]] == _variables[instr->_operand[1]]);
|
|
break;
|
|
|
|
case OPCODE_VAR_GT1:
|
|
func_set_test_result(func_state,
|
|
_variables[0] >
|
|
_variables[instr->_operand[0]]);
|
|
break;
|
|
|
|
case OPCODE_VAR_GT2:
|
|
func_set_test_result(func_state, _variables[instr->_operand[0]] >
|
|
_variables[instr->_operand[1]]);
|
|
break;
|
|
|
|
case OPCODE_VAR_GTE1:
|
|
func_set_test_result(func_state,
|
|
_variables[0] >=
|
|
_variables[instr->_operand[0]]);
|
|
break;
|
|
|
|
case OPCODE_VAR_GTE2:
|
|
func_set_test_result(func_state,
|
|
_variables[instr->_operand[0]] >=
|
|
_variables[instr->_operand[1]]);
|
|
break;
|
|
|
|
case OPCODE_VAR_EQ1:
|
|
func_set_test_result(func_state,
|
|
_variables[0] ==
|
|
_variables[instr->_operand[0]]);
|
|
break;
|
|
|
|
case OPCODE_VAR_INC:
|
|
_variables[instr->_operand[0]]++;
|
|
break;
|
|
|
|
case OPCODE_VAR_SUB:
|
|
_variables[instr->_operand[0]] -= _variables[instr->_operand[1]];
|
|
break;
|
|
|
|
default:
|
|
if (instr->_opcode & 0x80) {
|
|
warning("Unhandled command opcode %.2x", opcode);
|
|
} else {
|
|
warning("Unhandled test opcode %.2x - returning false", opcode);
|
|
func_set_test_result(func_state, false);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ComprehendGameOpcodes::func_set_test_result(FunctionState *func_state, bool value) {
|
|
if (func_state->_orCount == 0) {
|
|
/* And */
|
|
if (func_state->_and) {
|
|
if (!value)
|
|
func_state->_testResult = false;
|
|
} else {
|
|
func_state->_testResult = value;
|
|
func_state->_and = true;
|
|
}
|
|
|
|
} else {
|
|
/* Or */
|
|
if (value)
|
|
func_state->_testResult = value;
|
|
}
|
|
}
|
|
|
|
bool ComprehendGameOpcodes::isItemPresent(Item *item) const {
|
|
return item && (
|
|
item->_room == _currentRoom || item->_room == ROOM_INVENTORY
|
|
|| item->_room == ROOM_CONTAINER
|
|
);
|
|
}
|
|
|
|
Item *ComprehendGameOpcodes::getItem(const Instruction *instr) {
|
|
return get_item(instr->_operand[0] - 1);
|
|
}
|
|
|
|
/*-------------------------------------------------------*/
|
|
|
|
ComprehendGameV1::ComprehendGameV1() {
|
|
_opcodeMap[0x01] = OPCODE_HAVE_OBJECT;
|
|
_opcodeMap[0x02] = OPCODE_VAR_GT2;
|
|
_opcodeMap[0x04] = OPCODE_OR;
|
|
_opcodeMap[0x05] = OPCODE_IN_ROOM;
|
|
_opcodeMap[0x06] = OPCODE_VAR_EQ2;
|
|
_opcodeMap[0x08] = OPCODE_CURRENT_IS_OBJECT;
|
|
_opcodeMap[0x09] = OPCODE_OBJECT_PRESENT;
|
|
_opcodeMap[0x0a] = OPCODE_VAR_GTE2;
|
|
_opcodeMap[0x0c] = OPCODE_ELSE;
|
|
_opcodeMap[0x0e] = OPCODE_OBJECT_IN_ROOM;
|
|
_opcodeMap[0x14] = OPCODE_CURRENT_OBJECT_NOT_VALID;
|
|
_opcodeMap[0x18] = OPCODE_INVENTORY_FULL;
|
|
_opcodeMap[0x19] = OPCODE_TEST_FLAG;
|
|
_opcodeMap[0x1d] = OPCODE_CURRENT_OBJECT_IN_ROOM;
|
|
_opcodeMap[0x20] = OPCODE_HAVE_CURRENT_OBJECT;
|
|
_opcodeMap[0x21] = OPCODE_OBJECT_IS_NOT_NOWHERE;
|
|
_opcodeMap[0x24] = OPCODE_CURRENT_OBJECT_PRESENT;
|
|
_opcodeMap[0x25] = OPCODE_VAR_GT1;
|
|
_opcodeMap[0x29] = OPCODE_VAR_EQ1;
|
|
_opcodeMap[0x2d] = OPCODE_VAR_GTE1;
|
|
_opcodeMap[0x31] = OPCODE_TEST_ROOM_FLAG;
|
|
_opcodeMap[0x41] = OPCODE_NOT_HAVE_OBJECT;
|
|
_opcodeMap[0x45] = OPCODE_NOT_IN_ROOM;
|
|
_opcodeMap[0x48] = OPCODE_CURRENT_OBJECT_NOT_PRESENT;
|
|
_opcodeMap[0x49] = OPCODE_OBJECT_NOT_IN_ROOM;
|
|
_opcodeMap[0x4E] = OPCODE_TEST_FALSE;
|
|
_opcodeMap[0x50] = OPCODE_CURRENT_OBJECT_IS_NOWHERE;
|
|
_opcodeMap[0x59] = OPCODE_TEST_NOT_FLAG;
|
|
_opcodeMap[0x5D] = OPCODE_TEST_FALSE;
|
|
_opcodeMap[0x60] = OPCODE_NOT_HAVE_CURRENT_OBJECT;
|
|
_opcodeMap[0x61] = OPCODE_OBJECT_IS_NOWHERE;
|
|
_opcodeMap[0x64] = OPCODE_CURRENT_OBJECT_NOT_IN_ROOM;
|
|
_opcodeMap[0x68] = OPCODE_CURRENT_OBJECT_NOT_TAKEABLE;
|
|
_opcodeMap[0x71] = OPCODE_TEST_NOT_ROOM_FLAG;
|
|
_opcodeMap[0x80] = OPCODE_INVENTORY;
|
|
_opcodeMap[0x81] = OPCODE_TAKE_OBJECT;
|
|
_opcodeMap[0x82] = OPCODE_MOVE_OBJECT_TO_ROOM;
|
|
_opcodeMap[0x83] = OPCODE_RANDOM_MSG;
|
|
_opcodeMap[0x84] = OPCODE_SAVE_ACTION;
|
|
_opcodeMap[0x85] = OPCODE_MOVE_TO_ROOM;
|
|
_opcodeMap[0x86] = OPCODE_VAR_ADD;
|
|
_opcodeMap[0x87] = OPCODE_SET_ROOM_DESCRIPTION;
|
|
_opcodeMap[0x88] = OPCODE_CLEAR_LINE;
|
|
_opcodeMap[0x89] = OPCODE_MOVE_OBJECT_TO_CURRENT_ROOM;
|
|
_opcodeMap[0x8a] = OPCODE_VAR_SUB;
|
|
_opcodeMap[0x8b] = OPCODE_SET_OBJECT_DESCRIPTION;
|
|
_opcodeMap[0x8c] = OPCODE_MOVE_DEFAULT;
|
|
_opcodeMap[0x8d] = OPCODE_SET_CAN_TAKE;
|
|
_opcodeMap[0x8e] = OPCODE_PRINT;
|
|
_opcodeMap[0x91] = OPCODE_CLEAR_CAN_TAKE;
|
|
_opcodeMap[0x95] = OPCODE_REMOVE_OBJECT;
|
|
_opcodeMap[0x99] = OPCODE_SET_FLAG;
|
|
_opcodeMap[0x92] = OPCODE_CALL_FUNC;
|
|
_opcodeMap[0x98] = OPCODE_TURN_TICK;
|
|
_opcodeMap[0x9a] = OPCODE_SET_WORD;
|
|
_opcodeMap[0x9d] = OPCODE_CLEAR_FLAG;
|
|
_opcodeMap[0x9e] = OPCODE_INVENTORY_ROOM;
|
|
_opcodeMap[0xa0] = OPCODE_TAKE_CURRENT_OBJECT;
|
|
_opcodeMap[0xa1] = OPCODE_SPECIAL;
|
|
_opcodeMap[0xa4] = OPCODE_DROP_CURRENT_OBJECT;
|
|
_opcodeMap[0xa2] = OPCODE_SET_ROOM_GRAPHIC;
|
|
_opcodeMap[0xad] = OPCODE_CLEAR_WORD;
|
|
_opcodeMap[0xb0] = OPCODE_REMOVE_CURRENT_OBJECT;
|
|
_opcodeMap[0xb1] = OPCODE_MOVE_DIR;
|
|
_opcodeMap[0xb5] = OPCODE_SET_STRING_REPLACEMENT1;
|
|
_opcodeMap[0xb9] = OPCODE_SET_STRING_REPLACEMENT2;
|
|
_opcodeMap[0xbd] = OPCODE_VAR_INC;
|
|
_opcodeMap[0xc1] = OPCODE_VAR_DEC;
|
|
_opcodeMap[0xc5] = OPCODE_SET_STRING_REPLACEMENT3;
|
|
_opcodeMap[0xc9] = OPCODE_MOVE_CURRENT_OBJECT_TO_ROOM;
|
|
_opcodeMap[0xcd] = OPCODE_CLEAR_INVISIBLE;
|
|
_opcodeMap[0xd1] = OPCODE_SET_INVISIBLE;
|
|
_opcodeMap[0xd5] = OPCODE_CLEAR_FLAG40;
|
|
_opcodeMap[0xd9] = OPCODE_SET_FLAG40;
|
|
}
|
|
|
|
void ComprehendGameV1::execute_opcode(const Instruction *instr, const Sentence *sentence,
|
|
FunctionState *func_state) {
|
|
byte noun = sentence ? sentence->_formattedWords[2] : 0;
|
|
Room *room = get_room(_currentRoom);
|
|
Item *item;
|
|
uint count;
|
|
|
|
switch (_opcodeMap[getOpcode(instr)]) {
|
|
case OPCODE_INVENTORY_FULL:
|
|
item = get_item_by_noun(noun);
|
|
|
|
func_set_test_result(func_state, _variables[VAR_INVENTORY_WEIGHT] +
|
|
(item->_flags & ITEMF_WEIGHT_MASK) > _variables[VAR_INVENTORY_LIMIT]);
|
|
break;
|
|
|
|
case OPCODE_OBJECT_NOT_PRESENT:
|
|
item = getItem(instr);
|
|
func_set_test_result(func_state, !isItemPresent(item));
|
|
break;
|
|
|
|
case OPCODE_SET_STRING_REPLACEMENT3:
|
|
_currentReplaceWord = instr->_operand[0] - 1;
|
|
break;
|
|
|
|
/*--------------------------------------*/
|
|
|
|
case OPCODE_TEST_NOT_ROOM_FLAG:
|
|
func_set_test_result(func_state,
|
|
!(room->_flags & instr->_operand[0]));
|
|
break;
|
|
|
|
case OPCODE_NOT_IN_ROOM:
|
|
func_set_test_result(func_state,
|
|
_currentRoom != instr->_operand[0]);
|
|
break;
|
|
|
|
case OPCODE_OBJECT_NOT_IN_ROOM:
|
|
item = getItem(instr);
|
|
func_set_test_result(func_state, !item || item->_room != _currentRoom);
|
|
break;
|
|
|
|
case OPCODE_CURRENT_OBJECT_NOT_IN_ROOM:
|
|
item = get_item_by_noun(noun);
|
|
func_set_test_result(func_state, !item || item->_room != _currentRoom);
|
|
break;
|
|
|
|
case OPCODE_DESCRIBE_CURRENT_OBJECT:
|
|
/*
|
|
* This opcode is only used in version 2
|
|
* FIXME - unsure what the single operand is for.
|
|
*/
|
|
item = get_item_by_noun(noun);
|
|
g_comprehend->print("%s\n", stringLookup(item->_longString).c_str());
|
|
break;
|
|
|
|
case OPCODE_CURRENT_OBJECT_IN_ROOM: {
|
|
/* FIXME - use common code for these two ops */
|
|
bool test = false;
|
|
|
|
if (noun) {
|
|
for (uint i = 0; i < _items.size(); i++) {
|
|
Item *itemP = &_items[i];
|
|
|
|
if (itemP->_word == noun && itemP->_room == instr->_operand[0]) {
|
|
test = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
func_set_test_result(func_state, test);
|
|
break;
|
|
}
|
|
|
|
case OPCODE_CURRENT_OBJECT_PRESENT:
|
|
item = get_item_by_noun(noun);
|
|
if (item)
|
|
func_set_test_result(func_state,
|
|
item->_room == _currentRoom);
|
|
else
|
|
func_set_test_result(func_state, false);
|
|
break;
|
|
|
|
case OPCODE_NOT_HAVE_CURRENT_OBJECT:
|
|
item = get_item_by_noun(noun);
|
|
func_set_test_result(func_state,
|
|
!item || item->_room != ROOM_INVENTORY);
|
|
break;
|
|
|
|
case OPCODE_NOT_HAVE_OBJECT:
|
|
item = getItem(instr);
|
|
func_set_test_result(func_state,
|
|
item->_room != ROOM_INVENTORY);
|
|
break;
|
|
|
|
case OPCODE_CURRENT_OBJECT_NOT_TAKEABLE:
|
|
item = get_item_by_noun(noun);
|
|
if (!item)
|
|
func_set_test_result(func_state, true);
|
|
else
|
|
func_set_test_result(func_state,
|
|
!(item->_flags & ITEMF_CAN_TAKE));
|
|
break;
|
|
|
|
case OPCODE_CURRENT_OBJECT_IS_NOWHERE:
|
|
item = get_item_by_noun(noun);
|
|
func_set_test_result(func_state, item && item->_room == ROOM_NOWHERE);
|
|
break;
|
|
|
|
case OPCODE_OBJECT_IS_NOT_NOWHERE:
|
|
item = getItem(instr);
|
|
func_set_test_result(func_state, item->_room != ROOM_NOWHERE);
|
|
break;
|
|
|
|
case OPCODE_CURRENT_OBJECT_NOT_PRESENT:
|
|
item = get_item_by_noun(noun);
|
|
func_set_test_result(func_state, !isItemPresent(item));
|
|
break;
|
|
|
|
case OPCODE_REMOVE_CURRENT_OBJECT:
|
|
item = get_item_by_noun(noun);
|
|
move_object(item, ROOM_NOWHERE);
|
|
break;
|
|
|
|
case OPCODE_INVENTORY_ROOM:
|
|
count = num_objects_in_room(instr->_operand[0]);
|
|
if (count == 0) {
|
|
console_println(stringLookup(instr->_operand[1] + 1).c_str());
|
|
break;
|
|
}
|
|
|
|
console_println(stringLookup(instr->_operand[1]).c_str());
|
|
for (uint i = 0; i < _items.size(); i++) {
|
|
item = &_items[i];
|
|
if (item->_room == instr->_operand[0])
|
|
g_comprehend->print("%s\n",
|
|
stringLookup(item->_stringDesc).c_str());
|
|
}
|
|
break;
|
|
|
|
case OPCODE_MOVE_CURRENT_OBJECT_TO_ROOM:
|
|
item = get_item_by_noun(noun);
|
|
if (!item)
|
|
error("Bad current object\n");
|
|
|
|
move_object(item, instr->_operand[0]);
|
|
break;
|
|
|
|
case OPCODE_DROP_OBJECT:
|
|
item = getItem(instr);
|
|
move_object(item, _currentRoom);
|
|
break;
|
|
|
|
case OPCODE_DROP_CURRENT_OBJECT:
|
|
item = get_item_by_noun(noun);
|
|
if (!item)
|
|
error("Attempt to take object failed\n");
|
|
|
|
move_object(item, _currentRoom);
|
|
break;
|
|
|
|
case OPCODE_TEST_NOT_FLAG:
|
|
func_set_test_result(func_state,
|
|
!_flags[instr->_operand[0]]);
|
|
break;
|
|
|
|
case OPCODE_TEST_FALSE:
|
|
// The original had two opcodes mapped to the same code that does
|
|
// a test, but ignores the result, and is always false
|
|
func_set_test_result(func_state, false);
|
|
break;
|
|
|
|
case OPCODE_SET_CURRENT_NOUN_STRING_REPLACEMENT:
|
|
#if 1
|
|
error("TODO: OPCODE_SET_CURRENT_NOUN_STRING_REPLACEMENT");
|
|
#else
|
|
/*
|
|
* FIXME - Not sure what the operand is for,
|
|
* maybe capitalisation?
|
|
*/
|
|
if (noun && (noun->_type & WORD_TYPE_NOUN_PLURAL))
|
|
_currentReplaceWord = 3;
|
|
else if (noun && (noun->_type & WORD_TYPE_FEMALE))
|
|
_currentReplaceWord = 0;
|
|
else if (noun && (noun->_type & WORD_TYPE_MALE))
|
|
_currentReplaceWord = 1;
|
|
else
|
|
_currentReplaceWord = 2;
|
|
#endif
|
|
break;
|
|
|
|
case OPCODE_MOVE_DIR:
|
|
doMovementVerb(instr->_operand[0]);
|
|
break;
|
|
|
|
default:
|
|
ComprehendGameOpcodes::execute_opcode(instr, sentence, func_state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------*/
|
|
|
|
ComprehendGameV2::ComprehendGameV2() {
|
|
_opcodeMap[0x01] = OPCODE_HAVE_OBJECT;
|
|
_opcodeMap[0x02] = OPCODE_VAR_GT2;
|
|
_opcodeMap[0x04] = OPCODE_OR;
|
|
_opcodeMap[0x05] = OPCODE_IN_ROOM;
|
|
_opcodeMap[0x06] = OPCODE_VAR_EQ2;
|
|
_opcodeMap[0x08] = OPCODE_CURRENT_IS_OBJECT;
|
|
_opcodeMap[0x09] = OPCODE_VAR_GT1;
|
|
_opcodeMap[0x0a] = OPCODE_VAR_GTE2;
|
|
_opcodeMap[0x0c] = OPCODE_ELSE;
|
|
_opcodeMap[0x0d] = OPCODE_VAR_EQ1;
|
|
_opcodeMap[0x11] = OPCODE_OBJECT_IS_NOWHERE;
|
|
_opcodeMap[0x14] = OPCODE_CURRENT_OBJECT_NOT_VALID;
|
|
_opcodeMap[0x15] = OPCODE_INVENTORY_FULL_X;
|
|
_opcodeMap[0x19] = OPCODE_TEST_FLAG;
|
|
_opcodeMap[0x1d] = OPCODE_TEST_ROOM_FLAG;
|
|
_opcodeMap[0x20] = OPCODE_HAVE_CURRENT_OBJECT;
|
|
_opcodeMap[0x21] = OPCODE_OBJECT_PRESENT;
|
|
_opcodeMap[0x22] = OPCODE_OBJECT_IN_ROOM;
|
|
_opcodeMap[0x25] = OPCODE_OBJECT_TAKEABLE;
|
|
_opcodeMap[0x29] = OPCODE_INVENTORY_FULL;
|
|
_opcodeMap[0x2d] = OPCODE_OBJECT_CAN_TAKE;
|
|
_opcodeMap[0x80] = OPCODE_INVENTORY;
|
|
_opcodeMap[0x81] = OPCODE_TAKE_OBJECT;
|
|
_opcodeMap[0x83] = OPCODE_RANDOM_MSG;
|
|
_opcodeMap[0x84] = OPCODE_SAVE_ACTION;
|
|
_opcodeMap[0x85] = OPCODE_MOVE_TO_ROOM;
|
|
_opcodeMap[0x86] = OPCODE_VAR_ADD;
|
|
_opcodeMap[0x87] = OPCODE_SET_ROOM_DESCRIPTION;
|
|
_opcodeMap[0x88] = OPCODE_CLEAR_LINE;
|
|
_opcodeMap[0x89] = OPCODE_SPECIAL;
|
|
_opcodeMap[0x8a] = OPCODE_VAR_SUB;
|
|
_opcodeMap[0x8b] = OPCODE_SET_OBJECT_DESCRIPTION;
|
|
_opcodeMap[0x8c] = OPCODE_MOVE_DEFAULT;
|
|
_opcodeMap[0x8e] = OPCODE_PRINT;
|
|
_opcodeMap[0x8f] = OPCODE_SET_OBJECT_LONG_DESCRIPTION;
|
|
_opcodeMap[0x90] = OPCODE_WAIT_KEY;
|
|
_opcodeMap[0x92] = OPCODE_CALL_FUNC;
|
|
_opcodeMap[0x95] = OPCODE_CLEAR_WORD;
|
|
_opcodeMap[0x98] = OPCODE_TURN_TICK;
|
|
_opcodeMap[0x99] = OPCODE_SET_FLAG;
|
|
_opcodeMap[0x9a] = OPCODE_SET_WORD;
|
|
_opcodeMap[0x9d] = OPCODE_CLEAR_FLAG;
|
|
_opcodeMap[0xa0] = OPCODE_TAKE_CURRENT_OBJECT;
|
|
_opcodeMap[0xa1] = OPCODE_CLEAR_FLAG40;
|
|
_opcodeMap[0xa2] = OPCODE_MOVE_OBJECT_TO_ROOM;
|
|
_opcodeMap[0xa5] = OPCODE_SET_FLAG40;
|
|
_opcodeMap[0xa9] = OPCODE_CLEAR_INVISIBLE;
|
|
_opcodeMap[0xad] = OPCODE_SET_INVISIBLE;
|
|
_opcodeMap[0xc1] = OPCODE_VAR_DEC;
|
|
_opcodeMap[0xc2] = OPCODE_SET_ROOM_GRAPHIC;
|
|
_opcodeMap[0xc5] = OPCODE_SET_STRING_REPLACEMENT3;
|
|
_opcodeMap[0xc9] = OPCODE_SET_STRING_REPLACEMENT1;
|
|
_opcodeMap[0xcd] = OPCODE_SET_STRING_REPLACEMENT2;
|
|
_opcodeMap[0xd1] = OPCODE_MOVE_DIR;
|
|
_opcodeMap[0xd5] = OPCODE_DRAW_ROOM;
|
|
_opcodeMap[0xd9] = OPCODE_DRAW_OBJECT;
|
|
_opcodeMap[0xdd] = OPCODE_VAR_INC;
|
|
_opcodeMap[0xe1] = OPCODE_MOVE_OBJECT_TO_CURRENT_ROOM;
|
|
_opcodeMap[0xe5] = OPCODE_SET_CAN_TAKE;
|
|
_opcodeMap[0xe9] = OPCODE_CLEAR_CAN_TAKE;
|
|
_opcodeMap[0xed] = OPCODE_REMOVE_OBJECT;
|
|
|
|
#if 0
|
|
_opcodeMap[0x9e] = OPCODE_INVENTORY_ROOM;
|
|
_opcodeMap[0xc6] = OPCODE_SET_OBJECT_GRAPHIC;
|
|
_opcodeMap[0xf0] = OPCODE_DROP_CURRENT_OBJECT;
|
|
_opcodeMap[0xfc] = OPCODE_REMOVE_CURRENT_OBJECT;
|
|
#endif
|
|
}
|
|
|
|
void ComprehendGameV2::execute_opcode(const Instruction *instr, const Sentence *sentence,
|
|
FunctionState *func_state) {
|
|
Instruction instrCopy;
|
|
byte noun = sentence ? sentence->_formattedWords[2] : 0;
|
|
Room *room = get_room(_currentRoom);
|
|
Item *item;
|
|
|
|
if ((instr->_opcode & 0x30) == 0x30) {
|
|
// First operand comes from entered sentence noun, shifting out existing operands
|
|
instrCopy = *instr;
|
|
instrCopy._operand[2] = instrCopy._operand[1];
|
|
instrCopy._operand[1] = instrCopy._operand[0];
|
|
instrCopy._operand[0] = get_item_id(noun) + 1;
|
|
instr = &instrCopy;
|
|
}
|
|
|
|
func_state->_notComparison = (instr->_opcode & 0x40) != 0;
|
|
|
|
switch (_opcodeMap[getOpcode(instr)]) {
|
|
case OPCODE_CLEAR_INVISIBLE:
|
|
item = get_item_by_noun(noun);
|
|
item->_flags &= ~ITEMF_INVISIBLE;
|
|
break;
|
|
|
|
case OPCODE_DRAW_OBJECT:
|
|
g_comprehend->drawItemPicture(instr->_operand[0] - 1);
|
|
break;
|
|
|
|
case OPCODE_DRAW_ROOM:
|
|
g_comprehend->drawLocationPicture(instr->_operand[0] - 1);
|
|
break;
|
|
|
|
case OPCODE_INVENTORY_FULL:
|
|
item = get_item_by_noun(noun);
|
|
|
|
weighInventory();
|
|
func_set_test_result(func_state, _totalInventoryWeight + (item->_flags & ITEMF_WEIGHT_MASK) >
|
|
_variables[VAR_INVENTORY_LIMIT]);
|
|
break;
|
|
|
|
case OPCODE_INVENTORY_FULL_X:
|
|
item = get_item_by_noun(noun);
|
|
|
|
weighInventory();
|
|
func_set_test_result(func_state, _totalInventoryWeight + (item->_flags & ITEMF_WEIGHT_MASK) >
|
|
_variables[instr->_operand[1]]);
|
|
break;
|
|
|
|
case OPCODE_MOVE_DIR:
|
|
if (room->_direction[instr->_operand[0] - 1])
|
|
move_to(room->_direction[instr->_operand[0] - 1]);
|
|
else
|
|
console_println(stringLookup(STRING_CANT_GO).c_str());
|
|
break;
|
|
|
|
case OPCODE_OBJECT_TAKEABLE:
|
|
// WORKAROUND: Trying to get non-items in OO-Topos
|
|
func_set_test_result(func_state, instr->_operand[0]
|
|
&& (getItem(instr)->_flags & ITEMF_WEIGHT_MASK) != ITEMF_WEIGHT_MASK);
|
|
break;
|
|
|
|
case OPCODE_OBJECT_CAN_TAKE:
|
|
item = getItem(instr);
|
|
func_set_test_result(func_state, item->_flags & ITEMF_CAN_TAKE);
|
|
break;
|
|
|
|
case OPCODE_SET_OBJECT_GRAPHIC:
|
|
item = getItem(instr);
|
|
item->_graphic = instr->_operand[1];
|
|
if (item->_room == _currentRoom)
|
|
_updateFlags |= UPDATE_GRAPHICS;
|
|
break;
|
|
|
|
case OPCODE_SET_OBJECT_LONG_DESCRIPTION:
|
|
item = getItem(instr);
|
|
item->_longString = (instr->_operand[2] << 8) | instr->_operand[1];
|
|
break;
|
|
|
|
case OPCODE_SET_STRING_REPLACEMENT3: {
|
|
int articleNum, bits = _wordFlags;
|
|
for (articleNum = 3; articleNum >= 0; --articleNum, bits <<= 1) {
|
|
if (bits >= 0x100)
|
|
break;
|
|
}
|
|
if (articleNum == -1)
|
|
articleNum = 2;
|
|
|
|
_currentReplaceWord = instr->_operand[0] + articleNum - 1;
|
|
break;
|
|
}
|
|
|
|
case OPCODE_WAIT_KEY:
|
|
console_get_key();
|
|
break;
|
|
|
|
default:
|
|
ComprehendGameOpcodes::execute_opcode(instr, sentence, func_state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
byte ComprehendGameV2::getOpcode(const Instruction *instr) {
|
|
// Special pre-processing for opcodes
|
|
byte opcode = instr->_opcode;
|
|
if (!(opcode & 0x80))
|
|
opcode &= 0x3f;
|
|
if ((opcode & 0x30) == 0x30) {
|
|
opcode = (opcode & ~0x10) + 1;
|
|
}
|
|
|
|
return opcode;
|
|
}
|
|
|
|
void ComprehendGameV2::func_set_test_result(FunctionState *func_state, bool value) {
|
|
ComprehendGameOpcodes::func_set_test_result(func_state, value ^ func_state->_notComparison);
|
|
}
|
|
|
|
|
|
} // namespace Comprehend
|
|
} // namespace Glk
|