aboutsummaryrefslogtreecommitdiff
path: root/engines/agi/op_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/agi/op_test.cpp')
-rw-r--r--engines/agi/op_test.cpp435
1 files changed, 249 insertions, 186 deletions
diff --git a/engines/agi/op_test.cpp b/engines/agi/op_test.cpp
index 0660a614b6..a44c68e0fc 100644
--- a/engines/agi/op_test.cpp
+++ b/engines/agi/op_test.cpp
@@ -23,18 +23,180 @@
#include "agi/agi.h"
#include "agi/opcodes.h"
+#include "common/endian.h"
namespace Agi {
-#define ip (_game.logics[lognum].cIP)
-#define code (_game.logics[lognum].data)
+#define ip (state->_curLogic->cIP)
+#define code (state->_curLogic->data)
+
+#define getvar(a) state->_vm->getvar(a)
+#define getflag(a) state->_vm->getflag(a)
#define testEqual(v1, v2) (getvar(v1) == (v2))
#define testLess(v1, v2) (getvar(v1) < (v2))
#define testGreater(v1, v2) (getvar(v1) > (v2))
#define testIsSet(flag) (getflag(flag))
-#define testHas(obj) (objectGetLocation(obj) == EGO_OWNED)
-#define testObjInRoom(obj, v) (objectGetLocation(obj) == getvar(v))
+#define testHas(obj) (state->_vm->objectGetLocation(obj) == EGO_OWNED)
+#define testHasV1(obj) (state->_vm->objectGetLocation(obj) == EGO_OWNED_V1)
+#define testObjInRoom(obj, v) (state->_vm->objectGetLocation(obj) == getvar(v))
+
+void condEqual(AgiGame *state, uint8 *p) {
+ if (p[0] == 11)
+ state->_vm->_timerHack++;
+ state->testResult = testEqual(p[0], p[1]);
+}
+
+void condEqualV(AgiGame *state, uint8 *p) {
+ if (p[0] == 11 || p[1] == 11)
+ state->_vm->_timerHack++;
+ state->testResult = testEqual(p[0], getvar(p[1]));
+}
+
+void condLess(AgiGame *state, uint8 *p) {
+ if (p[0] == 11)
+ state->_vm->_timerHack++;
+ state->testResult = testLess(p[0], p[1]);
+}
+
+void condLessV(AgiGame *state, uint8 *p) {
+ if (p[0] == 11 || p[1] == 11)
+ state->_vm->_timerHack++;
+ state->testResult = testLess(p[0], getvar(p[1]));
+}
+
+void condGreater(AgiGame *state, uint8 *p) {
+ if (p[0] == 11)
+ state->_vm->_timerHack++;
+ state->testResult = testGreater(p[0], p[1]);
+}
+
+void condGreaterV(AgiGame *state, uint8 *p) {
+ if (p[0] == 11 || p[1] == 11)
+ state->_vm->_timerHack++;
+ state->testResult = testGreater(p[0], getvar(p[1]));
+}
+
+void condIsSet(AgiGame *state, uint8 *p) {
+ state->testResult = testIsSet(p[0]);
+}
+
+void condIsSetV(AgiGame *state, uint8 *p) {
+ state->testResult = testIsSet(getvar(p[1]));
+}
+
+void condIsSetV1(AgiGame *state, uint8 *p) {
+ state->testResult = getvar(p[0]) > 0;
+}
+
+void condHas(AgiGame *state, uint8 *p) {
+ state->testResult = testHas(p[0]);
+}
+
+void condHasV1(AgiGame *state, uint8 *p) {
+ state->testResult = testHasV1(p[0]);
+}
+
+void condObjInRoom(AgiGame *state, uint8 *p) {
+ state->testResult = testObjInRoom(p[0], p[1]);
+}
+
+void condPosn(AgiGame *state, uint8 *p) {
+ state->testResult = state->_vm->testPosn(p[0], p[1], p[2], p[3], p[4]);
+}
+
+void condController(AgiGame *state, uint8 *p) {
+ state->testResult = state->_vm->testController(p[0]);
+}
+
+void condHaveKey(AgiGame *state, uint8 *p) {
+ state->testResult = state->_vm->testKeypressed();
+}
+
+void condSaid(AgiGame *state, uint8 *p) {
+ int ec = state->_vm->testSaid(p[0], p + 1);
+ state->testResult = ec;
+}
+
+void condSaid1(AgiGame *state, uint8 *p) {
+ state->testResult = false;
+
+ if (!getflag(fEnteredCli))
+ return;
+
+ int id0 = READ_LE_UINT16(p);
+
+ if ((id0 == 1 || id0 == state->egoWords[0].id))
+ state->testResult = true;
+}
+
+void condSaid2(AgiGame *state, uint8 *p) {
+ state->testResult = false;
+
+ if (!getflag(fEnteredCli))
+ return;
+
+ int id0 = READ_LE_UINT16(p);
+ int id1 = READ_LE_UINT16(p + 2);
+
+ if ((id0 == 1 || id0 == state->egoWords[0].id) &&
+ (id1 == 1 || id1 == state->egoWords[1].id))
+ state->testResult = true;
+}
+
+void condSaid3(AgiGame *state, uint8 *p) {
+ state->testResult = false;
+
+ if (!getflag(fEnteredCli))
+ return;
+
+ int id0 = READ_LE_UINT16(p);
+ int id1 = READ_LE_UINT16(p + 2);
+ int id2 = READ_LE_UINT16(p + 4);
+
+ if ((id0 == 1 || id0 == state->egoWords[0].id) &&
+ (id1 == 1 || id1 == state->egoWords[1].id) &&
+ (id2 == 1 || id2 == state->egoWords[2].id))
+ state->testResult = true;
+}
+
+void condBit(AgiGame *state, uint8 *p) {
+ state->testResult = (getvar(p[1]) >> p[0]) & 1;
+}
+
+void condCompareStrings(AgiGame *state, uint8 *p) {
+ debugC(7, kDebugLevelScripts, "comparing [%s], [%s]", state->strings[p[0]], state->strings[p[1]]);
+ state->testResult = state->_vm->testCompareStrings(p[0], p[1]);
+}
+
+void condObjInBox(AgiGame *state, uint8 *p) {
+ state->testResult = state->_vm->testObjInBox(p[0], p[1], p[2], p[3], p[4]);
+}
+
+void condCenterPosn(AgiGame *state, uint8 *p) {
+ state->testResult = state->_vm->testObjCenter(p[0], p[1], p[2], p[3], p[4]);
+}
+
+void condRightPosn(AgiGame *state, uint8 *p) {
+ state->testResult = state->_vm->testObjRight(p[0], p[1], p[2], p[3], p[4]);
+}
+
+void condUnknown13(AgiGame *state, uint8 *p) {
+ // My current theory is that this command checks whether the ego is currently moving
+ // and that that movement has been caused using the mouse and not using the keyboard.
+ // I base this theory on the game's behavior on an Amiga emulator, not on disassembly.
+ // This command is used at least in the Amiga version of Gold Rush! v2.05 1989-03-09
+ // (AGI 2.316) in logics 1, 3, 5, 6, 137 and 192 (Logic.192 revealed this command's nature).
+ // TODO: Check this command's implementation using disassembly just to be sure.
+ int ec = state->viewTable[0].flags & fAdjEgoXY;
+ debugC(7, kDebugLevelScripts, "op_test: in.motion.using.mouse = %s (Amiga-specific testcase 19)", ec ? "true" : "false");
+ state->testResult = ec;
+}
+
+void condUnknown(AgiGame *state, uint8 *p) {
+ warning("Skipping unknown test command %2X", *(code + ip - 1));
+ state->testResult = false;
+}
uint8 AgiEngine::testCompareStrings(uint8 s1, uint8 s2) {
char ms1[MAX_STRINGLEN];
@@ -147,6 +309,7 @@ uint8 AgiEngine::testObjRight(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) {
// When player has entered something, it is parsed elsewhere
uint8 AgiEngine::testSaid(uint8 nwords, uint8 *cc) {
+ AgiGame *state = &_game;
int c, n = _game.numEgoWords;
int z = 0;
@@ -202,210 +365,110 @@ uint8 AgiEngine::testSaid(uint8 nwords, uint8 *cc) {
}
int AgiEngine::testIfCode(int lognum) {
- int ec = true;
- int retval = true;
- uint8 op = 0;
- uint8 notTest = false;
- uint8 orTest = false;
- uint16 lastIp = ip;
- uint8 p[16] = { 0 };
- bool end_test = false;
-
- while (retval && !(shouldQuit() || _restartGame) && !end_test) {
+ AgiGame *state = &_game;
+ uint8 op;
+ uint8 p[16];
+
+ int notMode = false;
+ int orMode = false;
+ int endTest = false;
+ int result = true;
+
+ while (!(shouldQuit() || _restartGame) && !endTest) {
if (_debug.enabled && (_debug.logic0 || lognum))
debugConsole(lognum, lTEST_MODE, NULL);
- lastIp = ip;
op = *(code + ip++);
memmove(p, (code + ip), 16);
switch (op) {
- case 0xFF: // END IF, TEST true
- end_test = true;
- break;
- case 0xFD:
- notTest = !notTest;
- continue;
- case 0xFC: // OR
- // if or_test is ON and we hit 0xFC, end of OR, then
- // or is STILL false so break.
- if (orTest) {
- ec = false;
- retval = false;
- end_test = true;
+ case 0xFC:
+ if (orMode) {
+ // We have reached the end of an OR expression without
+ // a single test command evaluating as true. Thus the OR
+ // expression evalutes as false which means the whole
+ // expression evaluates as false. So skip until the
+ // ending 0xFF and return.
+ skipInstructionsUntil(0xFF);
+ result = false;
+ endTest = true;
+ } else {
+ orMode = true;
}
-
- orTest = true;
continue;
-
+ case 0xFD:
+ notMode = true;
+ continue;
case 0x00:
- // return true?
- end_test = true;
- break;
- case 0x01:
- ec = testEqual(p[0], p[1]);
- if (p[0] == 11)
- _timerHack++;
- break;
- case 0x02:
- ec = testEqual(p[0], getvar(p[1]));
- if (p[0] == 11 || p[1] == 11)
- _timerHack++;
- break;
- case 0x03:
- ec = testLess(p[0], p[1]);
- if (p[0] == 11)
- _timerHack++;
- break;
- case 0x04:
- ec = testLess(p[0], getvar(p[1]));
- if (p[0] == 11 || p[1] == 11)
- _timerHack++;
- break;
- case 0x05:
- ec = testGreater(p[0], p[1]);
- if (p[0] == 11)
- _timerHack++;
- break;
- case 0x06:
- ec = testGreater(p[0], getvar(p[1]));
- if (p[0] == 11 || p[1] == 11)
- _timerHack++;
- break;
- case 0x07:
- ec = testIsSet(p[0]);
- break;
- case 0x08:
- ec = testIsSet(getvar(p[0]));
- break;
- case 0x09:
- ec = testHas(p[0]);
- break;
- case 0x0A:
- ec = testObjInRoom(p[0], p[1]);
- break;
- case 0x0B:
- ec = testPosn(p[0], p[1], p[2], p[3], p[4]);
- break;
- case 0x0C:
- ec = testController(p[0]);
- break;
- case 0x0D:
- ec = testKeypressed();
- break;
- case 0x0E:
- ec = testSaid(p[0], (uint8 *) code + (ip + 1));
- ip = lastIp;
- ip++; // skip opcode
- ip += p[0] * 2; // skip num_words * 2
- ip++; // skip num_words opcode
- break;
- case 0x0F:
- debugC(7, kDebugLevelScripts, "comparing [%s], [%s]", _game.strings[p[0]], _game.strings[p[1]]);
- ec = testCompareStrings(p[0], p[1]);
- break;
- case 0x10:
- ec = testObjInBox(p[0], p[1], p[2], p[3], p[4]);
- break;
- case 0x11:
- ec = testObjCenter(p[0], p[1], p[2], p[3], p[4]);
- break;
- case 0x12:
- ec = testObjRight(p[0], p[1], p[2], p[3], p[4]);
- break;
- case 0x13: // Unknown test command 19
- // My current theory is that this command checks whether the ego is currently moving
- // and that that movement has been caused using the mouse and not using the keyboard.
- // I base this theory on the game's behavior on an Amiga emulator, not on disassembly.
- // This command is used at least in the Amiga version of Gold Rush! v2.05 1989-03-09
- // (AGI 2.316) in logics 1, 3, 5, 6, 137 and 192 (Logic.192 revealed this command's nature).
- // TODO: Check this command's implementation using disassembly just to be sure.
- ec = _game.viewTable[0].flags & ADJ_EGO_XY;
- debugC(7, kDebugLevelScripts, "op_test: in.motion.using.mouse = %s (Amiga-specific testcase 19)", ec ? "true" : "false");
- break;
+ case 0xFF:
+ endTest = true;
+ continue;
+
default:
- ec = false;
- end_test = true;
- }
-
- if (!end_test) {
- if (op <= 0x12)
- ip += logicNamesTest[op].numArgs;
-
- // exchange ec value
- if (notTest)
- ec = !ec;
-
- // not is only enabled for 1 test command
- notTest = false;
-
- if (orTest && ec) {
- // a true inside an OR statement passes
- // ENTIRE statement scan for end of OR
-
- // CM: test for opcode < 0xfc changed from 'op' to
- // '*(code+ip)', to avoid problem with the 0xfd (NOT)
- // opcode byte. Changed a bad ip += ... ip++ construct.
- // This should fix the crash with Larry's logic.0 code:
- //
- // if ((isset(4) ||
- // !isset(2) ||
- // v30 == 2 ||
- // v30 == 1)) {
- // goto Label1;
- // }
- //
- // The bytecode is:
- // ff fc 07 04 fd 07 02 01 1e 02 01 1e 01 fc ff
-
- // find end of OR
- while (*(code + ip) != 0xFC) {
- if (*(code + ip) == 0x0E) { // said
- ip++;
-
- // cover count + ^words
- ip += 1 + ((*(code + ip)) * 2);
- continue;
- }
-
- if (*(code + ip) < 0xFC)
- ip += logicNamesTest[*(code + ip)].numArgs;
- ip++;
+ // Evaluate the command and skip the rest of the instruction
+ _agiCondCommands[op](state, p);
+ skipInstruction(op);
+
+ // NOT mode is enabled only for one instruction
+ if (notMode)
+ state->testResult = !state->testResult;
+ notMode = false;
+
+ if (orMode) {
+ if (state->testResult) {
+ // We are in OR mode and the last test command evaluated
+ // as true, thus the whole OR expression evaluates as
+ // true. So skip the rest of the OR expression and
+ // continue normally.
+ skipInstructionsUntil(0xFC);
+ orMode = false;
+ continue;
}
- ip++;
-
- orTest = false;
- retval = true;
} else {
- retval = orTest ? retval || ec : retval && ec;
+ result &= state->testResult;
+ if (!result) {
+ // Since we are in AND mode and the last test command
+ // evaluated as false, the whole expression also evaluates
+ // as false. So skip until the ending 0xFF and return.
+ skipInstructionsUntil(0xFF);
+ endTest = true;
+ continue;
+ }
}
+ break;
}
}
- // if false, scan for end of IP?
- if (retval)
+ // Skip the following IF block if the condition evaluates as false
+ if (result)
ip += 2;
- else {
- ip = lastIp;
- while (*(code + ip) != 0xff) {
- if (*(code + ip) == 0x0e) {
- ip++;
- ip += (*(code + ip)) * 2 + 1;
- } else if (*(code + ip) < 0xfc) {
- ip += logicNamesTest[*(code + ip)].numArgs;
- ip++;
- } else {
- ip++;
- }
- }
- ip++; // skip over 0xFF
+ else
ip += READ_LE_UINT16(code + ip) + 2;
- }
if (_debug.enabled && (_debug.logic0 || lognum))
- debugConsole(lognum, 0xFF, retval ? "=true" : "=false");
+ debugConsole(lognum, 0xFF, result ? "=true" : "=false");
+
+ return result;
+}
- return retval;
+void AgiEngine::skipInstruction(byte op) {
+ AgiGame *state = &_game;
+ if (op >= 0xFC)
+ return;
+ if (op == 0x0E && state->_vm->getVersion() >= 0x2000) // said
+ ip += *(code + ip) * 2 + 1;
+ else
+ ip += logicNamesTest[op].argumentsLength();
+}
+
+void AgiEngine::skipInstructionsUntil(byte v) {
+ AgiGame *state = &_game;
+ while (1) {
+ byte op = *(code + ip++);
+ if (op == v)
+ return;
+ skipInstruction(op);
+ }
}
} // End of namespace Agi