scummvm/engines/glk/comprehend/game_opcodes.cpp
2021-12-26 18:48:43 +01:00

871 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 3 of the License, or
* 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 "glk/comprehend/debugger.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:
case OPCODE_CALL_FUNC2:
// Note: CALL_FUNC2 in the original did some extra backing of data which is
// redundant in the ScummVM version, so it can be handled the same as 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
_specialOpcode = 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);
if (g_debugger->_invLimit)
func_set_test_result(func_state, _variables[VAR_INVENTORY_WEIGHT] +
(item->_flags & ITEMF_WEIGHT_MASK) > _variables[VAR_INVENTORY_LIMIT]);
else
// Allow for an unlimited number of items in inventory
func_set_test_result(func_state, false);
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[0x96] = OPCODE_CALL_FUNC2;
_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;
// In case a single opcode is being executed outside of a function, use a dummy function state
FunctionState dummyState;
if (!func_state)
func_state = &dummyState;
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);
g_comprehend->readChar();
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