diff options
Diffstat (limited to 'engines/scumm/script_v5.cpp')
-rw-r--r-- | engines/scumm/script_v5.cpp | 2898 |
1 files changed, 2898 insertions, 0 deletions
diff --git a/engines/scumm/script_v5.cpp b/engines/scumm/script_v5.cpp new file mode 100644 index 0000000000..7a048d0a01 --- /dev/null +++ b/engines/scumm/script_v5.cpp @@ -0,0 +1,2898 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2001 Ludvig Strigeus + * Copyright (C) 2001-2006 The ScummVM project + * + * 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/stdafx.h" +#include "scumm/actor.h" +#include "scumm/charset.h" +#include "scumm/intern.h" +#include "scumm/object.h" +#include "scumm/scumm.h" +#include "scumm/sound.h" +#include "scumm/util.h" +#include "scumm/verbs.h" + +#include "common/savefile.h" + +namespace Scumm { + +#define OPCODE(x) _OPCODE(ScummEngine_v5, x) + +void ScummEngine_v5::setupOpcodes() { + static const OpcodeEntryV5 opcodes[256] = { + /* 00 */ + OPCODE(o5_stopObjectCode), + OPCODE(o5_putActor), + OPCODE(o5_startMusic), + OPCODE(o5_getActorRoom), + /* 04 */ + OPCODE(o5_isGreaterEqual), + OPCODE(o5_drawObject), + OPCODE(o5_getActorElevation), + OPCODE(o5_setState), + /* 08 */ + OPCODE(o5_isNotEqual), + OPCODE(o5_faceActor), + OPCODE(o5_startScript), + OPCODE(o5_getVerbEntrypoint), + /* 0C */ + OPCODE(o5_resourceRoutines), + OPCODE(o5_walkActorToActor), + OPCODE(o5_putActorAtObject), + OPCODE(o5_getObjectState), + /* 10 */ + OPCODE(o5_getObjectOwner), + OPCODE(o5_animateActor), + OPCODE(o5_panCameraTo), + OPCODE(o5_actorOps), + /* 14 */ + OPCODE(o5_print), + OPCODE(o5_actorFromPos), + OPCODE(o5_getRandomNr), + OPCODE(o5_and), + /* 18 */ + OPCODE(o5_jumpRelative), + OPCODE(o5_doSentence), + OPCODE(o5_move), + OPCODE(o5_multiply), + /* 1C */ + OPCODE(o5_startSound), + OPCODE(o5_ifClassOfIs), + OPCODE(o5_walkActorTo), + OPCODE(o5_isActorInBox), + /* 20 */ + OPCODE(o5_stopMusic), + OPCODE(o5_putActor), + OPCODE(o5_getAnimCounter), + OPCODE(o5_getActorY), + /* 24 */ + OPCODE(o5_loadRoomWithEgo), + OPCODE(o5_pickupObject), + OPCODE(o5_setVarRange), + OPCODE(o5_stringOps), + /* 28 */ + OPCODE(o5_equalZero), + OPCODE(o5_setOwnerOf), + OPCODE(o5_startScript), + OPCODE(o5_delayVariable), + /* 2C */ + OPCODE(o5_cursorCommand), + OPCODE(o5_putActorInRoom), + OPCODE(o5_delay), + OPCODE(o5_ifNotState), + /* 30 */ + OPCODE(o5_matrixOps), + OPCODE(o5_getInventoryCount), + OPCODE(o5_setCameraAt), + OPCODE(o5_roomOps), + /* 34 */ + OPCODE(o5_getDist), + OPCODE(o5_findObject), + OPCODE(o5_walkActorToObject), + OPCODE(o5_startObject), + /* 38 */ + OPCODE(o5_lessOrEqual), + OPCODE(o5_doSentence), + OPCODE(o5_subtract), + OPCODE(o5_getActorScale), + /* 3C */ + OPCODE(o5_stopSound), + OPCODE(o5_findInventory), + OPCODE(o5_walkActorTo), + OPCODE(o5_drawBox), + /* 40 */ + OPCODE(o5_cutscene), + OPCODE(o5_putActor), + OPCODE(o5_chainScript), + OPCODE(o5_getActorX), + /* 44 */ + OPCODE(o5_isLess), + OPCODE(o5_drawObject), + OPCODE(o5_increment), + OPCODE(o5_setState), + /* 48 */ + OPCODE(o5_isEqual), + OPCODE(o5_faceActor), + OPCODE(o5_startScript), + OPCODE(o5_getVerbEntrypoint), + /* 4C */ + OPCODE(o5_soundKludge), + OPCODE(o5_walkActorToActor), + OPCODE(o5_putActorAtObject), + OPCODE(o5_ifState), + /* 50 */ + OPCODE(o5_pickupObjectOld), + OPCODE(o5_animateActor), + OPCODE(o5_actorFollowCamera), + OPCODE(o5_actorOps), + /* 54 */ + OPCODE(o5_setObjectName), + OPCODE(o5_actorFromPos), + OPCODE(o5_getActorMoving), + OPCODE(o5_or), + /* 58 */ + OPCODE(o5_beginOverride), + OPCODE(o5_doSentence), + OPCODE(o5_add), + OPCODE(o5_divide), + /* 5C */ + OPCODE(o5_oldRoomEffect), + OPCODE(o5_setClass), + OPCODE(o5_walkActorTo), + OPCODE(o5_isActorInBox), + /* 60 */ + OPCODE(o5_freezeScripts), + OPCODE(o5_putActor), + OPCODE(o5_stopScript), + OPCODE(o5_getActorFacing), + /* 64 */ + OPCODE(o5_loadRoomWithEgo), + OPCODE(o5_pickupObject), + OPCODE(o5_getClosestObjActor), + OPCODE(o5_getStringWidth), + /* 68 */ + OPCODE(o5_isScriptRunning), + OPCODE(o5_setOwnerOf), + OPCODE(o5_startScript), + OPCODE(o5_debug), + /* 6C */ + OPCODE(o5_getActorWidth), + OPCODE(o5_putActorInRoom), + OPCODE(o5_stopObjectScript), + OPCODE(o5_ifNotState), + /* 70 */ + OPCODE(o5_lights), + OPCODE(o5_getActorCostume), + OPCODE(o5_loadRoom), + OPCODE(o5_roomOps), + /* 74 */ + OPCODE(o5_getDist), + OPCODE(o5_findObject), + OPCODE(o5_walkActorToObject), + OPCODE(o5_startObject), + /* 78 */ + OPCODE(o5_isGreater), + OPCODE(o5_doSentence), + OPCODE(o5_verbOps), + OPCODE(o5_getActorWalkBox), + /* 7C */ + OPCODE(o5_isSoundRunning), + OPCODE(o5_findInventory), + OPCODE(o5_walkActorTo), + OPCODE(o5_drawBox), + /* 80 */ + OPCODE(o5_breakHere), + OPCODE(o5_putActor), + OPCODE(o5_startMusic), + OPCODE(o5_getActorRoom), + /* 84 */ + OPCODE(o5_isGreaterEqual), + OPCODE(o5_drawObject), + OPCODE(o5_getActorElevation), + OPCODE(o5_setState), + /* 88 */ + OPCODE(o5_isNotEqual), + OPCODE(o5_faceActor), + OPCODE(o5_startScript), + OPCODE(o5_getVerbEntrypoint), + /* 8C */ + OPCODE(o5_resourceRoutines), + OPCODE(o5_walkActorToActor), + OPCODE(o5_putActorAtObject), + OPCODE(o5_getObjectState), + /* 90 */ + OPCODE(o5_getObjectOwner), + OPCODE(o5_animateActor), + OPCODE(o5_panCameraTo), + OPCODE(o5_actorOps), + /* 94 */ + OPCODE(o5_print), + OPCODE(o5_actorFromPos), + OPCODE(o5_getRandomNr), + OPCODE(o5_and), + /* 98 */ + OPCODE(o5_systemOps), + OPCODE(o5_doSentence), + OPCODE(o5_move), + OPCODE(o5_multiply), + /* 9C */ + OPCODE(o5_startSound), + OPCODE(o5_ifClassOfIs), + OPCODE(o5_walkActorTo), + OPCODE(o5_isActorInBox), + /* A0 */ + OPCODE(o5_stopObjectCode), + OPCODE(o5_putActor), + OPCODE(o5_getAnimCounter), + OPCODE(o5_getActorY), + /* A4 */ + OPCODE(o5_loadRoomWithEgo), + OPCODE(o5_pickupObject), + OPCODE(o5_setVarRange), + OPCODE(o5_saveLoadVars), + /* A8 */ + OPCODE(o5_notEqualZero), + OPCODE(o5_setOwnerOf), + OPCODE(o5_startScript), + OPCODE(o5_saveRestoreVerbs), + /* AC */ + OPCODE(o5_expression), + OPCODE(o5_putActorInRoom), + OPCODE(o5_wait), + OPCODE(o5_ifNotState), + /* B0 */ + OPCODE(o5_matrixOps), + OPCODE(o5_getInventoryCount), + OPCODE(o5_setCameraAt), + OPCODE(o5_roomOps), + /* B4 */ + OPCODE(o5_getDist), + OPCODE(o5_findObject), + OPCODE(o5_walkActorToObject), + OPCODE(o5_startObject), + /* B8 */ + OPCODE(o5_lessOrEqual), + OPCODE(o5_doSentence), + OPCODE(o5_subtract), + OPCODE(o5_getActorScale), + /* BC */ + OPCODE(o5_stopSound), + OPCODE(o5_findInventory), + OPCODE(o5_walkActorTo), + OPCODE(o5_drawBox), + /* C0 */ + OPCODE(o5_endCutscene), + OPCODE(o5_putActor), + OPCODE(o5_chainScript), + OPCODE(o5_getActorX), + /* C4 */ + OPCODE(o5_isLess), + OPCODE(o5_drawObject), + OPCODE(o5_decrement), + OPCODE(o5_setState), + /* C8 */ + OPCODE(o5_isEqual), + OPCODE(o5_faceActor), + OPCODE(o5_startScript), + OPCODE(o5_getVerbEntrypoint), + /* CC */ + OPCODE(o5_pseudoRoom), + OPCODE(o5_walkActorToActor), + OPCODE(o5_putActorAtObject), + OPCODE(o5_ifState), + /* D0 */ + OPCODE(o5_pickupObjectOld), + OPCODE(o5_animateActor), + OPCODE(o5_actorFollowCamera), + OPCODE(o5_actorOps), + /* D4 */ + OPCODE(o5_setObjectName), + OPCODE(o5_actorFromPos), + OPCODE(o5_getActorMoving), + OPCODE(o5_or), + /* D8 */ + OPCODE(o5_printEgo), + OPCODE(o5_doSentence), + OPCODE(o5_add), + OPCODE(o5_divide), + /* DC */ + OPCODE(o5_oldRoomEffect), + OPCODE(o5_setClass), + OPCODE(o5_walkActorTo), + OPCODE(o5_isActorInBox), + /* E0 */ + OPCODE(o5_freezeScripts), + OPCODE(o5_putActor), + OPCODE(o5_stopScript), + OPCODE(o5_getActorFacing), + /* E4 */ + OPCODE(o5_loadRoomWithEgo), + OPCODE(o5_pickupObject), + OPCODE(o5_getClosestObjActor), + OPCODE(o5_getStringWidth), + /* E8 */ + OPCODE(o5_isScriptRunning), + OPCODE(o5_setOwnerOf), + OPCODE(o5_startScript), + OPCODE(o5_debug), + /* EC */ + OPCODE(o5_getActorWidth), + OPCODE(o5_putActorInRoom), + OPCODE(o5_stopObjectScript), + OPCODE(o5_ifNotState), + /* F0 */ + OPCODE(o5_lights), + OPCODE(o5_getActorCostume), + OPCODE(o5_loadRoom), + OPCODE(o5_roomOps), + /* F4 */ + OPCODE(o5_getDist), + OPCODE(o5_findObject), + OPCODE(o5_walkActorToObject), + OPCODE(o5_startObject), + /* F8 */ + OPCODE(o5_isGreater), + OPCODE(o5_doSentence), + OPCODE(o5_verbOps), + OPCODE(o5_getActorWalkBox), + /* FC */ + OPCODE(o5_isSoundRunning), + OPCODE(o5_findInventory), + OPCODE(o5_walkActorTo), + OPCODE(o5_drawBox) + }; + + _opcodesV5 = opcodes; +} + +#define PARAM_1 0x80 +#define PARAM_2 0x40 +#define PARAM_3 0x20 + +void ScummEngine_v5::executeOpcode(byte i) { + OpcodeProcV5 op = _opcodesV5[i].proc; + (this->*op) (); +} + +const char *ScummEngine_v5::getOpcodeDesc(byte i) { + return _opcodesV5[i].desc; +} + +int ScummEngine_v5::getVar() { + return readVar(fetchScriptWord()); +} + +int ScummEngine_v5::getVarOrDirectByte(byte mask) { + if (_opcode & mask) + return getVar(); + return fetchScriptByte(); +} + +int ScummEngine_v5::getVarOrDirectWord(byte mask) { + if (_opcode & mask) + return getVar(); + return (int16)fetchScriptWord(); +} + +void ScummEngine_v5::o5_actorFollowCamera() { + actorFollowCamera(getVarOrDirectByte(0x80)); +} + +void ScummEngine_v5::o5_actorFromPos() { + int x, y; + getResultPos(); + x = getVarOrDirectWord(PARAM_1); + y = getVarOrDirectWord(PARAM_2); + setResult(getActorFromPos(x, y)); +} + +void ScummEngine_v5::o5_actorOps() { + static const byte convertTable[20] = + { 1, 0, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20 }; + int act = getVarOrDirectByte(PARAM_1); + Actor *a = derefActor(act, "o5_actorOps"); + int i, j; + + while ((_opcode = fetchScriptByte()) != 0xFF) { + if (_features & GF_SMALL_HEADER) + _opcode = (_opcode & 0xE0) | convertTable[(_opcode & 0x1F) - 1]; + + switch (_opcode & 0x1F) { + case 0: /* dummy case */ + getVarOrDirectByte(PARAM_1); + break; + case 1: // SO_COSTUME + a->setActorCostume(getVarOrDirectByte(PARAM_1)); + break; + case 2: // SO_STEP_DIST + i = getVarOrDirectByte(PARAM_1); + j = getVarOrDirectByte(PARAM_2); + a->setActorWalkSpeed(i, j); + break; + case 3: // SO_SOUND + a->_sound[0] = getVarOrDirectByte(PARAM_1); + break; + case 4: // SO_WALK_ANIMATION + a->_walkFrame = getVarOrDirectByte(PARAM_1); + break; + case 5: // SO_TALK_ANIMATION + a->_talkStartFrame = getVarOrDirectByte(PARAM_1); + a->_talkStopFrame = getVarOrDirectByte(PARAM_2); + break; + case 6: // SO_STAND_ANIMATION + a->_standFrame = getVarOrDirectByte(PARAM_1); + break; + case 7: // SO_ANIMATION + getVarOrDirectByte(PARAM_1); + getVarOrDirectByte(PARAM_2); + getVarOrDirectByte(PARAM_3); + break; + case 8: // SO_DEFAULT + a->initActor(0); + break; + case 9: // SO_ELEVATION + a->setElevation(getVarOrDirectWord(PARAM_1)); + break; + case 10: // SO_ANIMATION_DEFAULT + a->_initFrame = 1; + a->_walkFrame = 2; + a->_standFrame = 3; + a->_talkStartFrame = 4; + a->_talkStopFrame = 5; + break; + case 11: // SO_PALETTE + i = getVarOrDirectByte(PARAM_1); + j = getVarOrDirectByte(PARAM_2); + checkRange(31, 0, i, "Illegal palette slot %d"); + a->setPalette(i, j); + break; + case 12: // SO_TALK_COLOR + + // Zak256 (and possibly other games) uses actor 0 to + // indicate that it's the default talk color that is + // to be changed. + + if (act == 0) + _string[0].color = getVarOrDirectByte(PARAM_1); + else + a->_talkColor = getVarOrDirectByte(PARAM_1); + break; + case 13: // SO_ACTOR_NAME + loadPtrToResource(rtActorName, a->_number, NULL); + break; + case 14: // SO_INIT_ANIMATION + a->_initFrame = getVarOrDirectByte(PARAM_1); + break; + case 15: // SO_PALETTE_LIST + error("o5_actorOps:unk not implemented"); +#if 0 + int args[16] = + { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + }; + getWordVararg(args); + for (i = 0; i < 16; i++) + if (args[i] != 0xFF) + a->_palette[i] = args[i]; +#endif + break; + case 16: // SO_ACTOR_WIDTH + a->_width = getVarOrDirectByte(PARAM_1); + break; + case 17: // SO_ACTOR_SCALE + if (_version == 4) { + i = j = getVarOrDirectByte(PARAM_1); + } else { + i = getVarOrDirectByte(PARAM_1); + j = getVarOrDirectByte(PARAM_2); + } + + a->_boxscale = i; + a->setScale(i, j); + break; + case 18: // SO_NEVER_ZCLIP + a->_forceClip = 0; + break; + case 19: // SO_ALWAYS_ZCLIP + a->_forceClip = getVarOrDirectByte(PARAM_1); + break; + case 20: // SO_IGNORE_BOXES + case 21: // SO_FOLLOW_BOXES + a->_ignoreBoxes = !(_opcode & 1); + a->_forceClip = 0; + if (a->isInCurrentRoom()) + a->putActor(a->_pos.x, a->_pos.y, a->_room); + break; + + case 22: // SO_ANIMATION_SPEED + a->setAnimSpeed(getVarOrDirectByte(PARAM_1)); + break; + case 23: // SO_SHADOW + a->_shadowMode = getVarOrDirectByte(PARAM_1); + break; + default: + error("o5_actorOps: default case %d", _opcode & 0x1F); + } + } +} + +void ScummEngine_v5::o5_setClass() { + int obj = getVarOrDirectWord(PARAM_1); + int newClass; + + while ((_opcode = fetchScriptByte()) != 0xFF) { + newClass = getVarOrDirectWord(PARAM_1); + if (newClass == 0) { + // Class '0' means: clean all class data + _classData[obj] = 0; + if ((_features & GF_SMALL_HEADER) && obj <= _numActors) { + Actor *a = derefActor(obj, "o5_setClass"); + a->_ignoreBoxes = false; + a->_forceClip = 0; + } + } else + putClass(obj, newClass, (newClass & 0x80) ? true : false); + } +} + +void ScummEngine_v5::o5_add() { + int a; + getResultPos(); + a = getVarOrDirectWord(PARAM_1); + + // WORKAROUND bug #770065: This works around a script bug in LoomCD. To + // understand the reasoning behind this, compare script 210 and 218 in + // room 20. Apparently they made a mistake when converting the absolute + // delays into relative ones. + if (_gameId == GID_LOOM && _version == 4 && vm.slot[_currentScript].number == 210 && _currentRoom == 20 && _resultVarNumber == 0x4000) { + switch (a) { + // Fix for the Var[250] == 11 case + case 138: + a = 145; + break; + case 324: + a = 324 - 138; + break; + // Fixes for the Var[250] == 14 case + case 130: + a = 170; + break; + case 342: + a = 342 - 130 + 15; // Small extra adjustment for the "OUCH" + break; + case 384: + a -= 342; + break; + case 564: + a -= 384; + break; + } + } + + setResult(readVar(_resultVarNumber) + a); +} + +void ScummEngine_v5::o5_and() { + int a; + getResultPos(); + a = getVarOrDirectWord(PARAM_1); + setResult(readVar(_resultVarNumber) & a); +} + +void ScummEngine_v5::o5_animateActor() { + int act = getVarOrDirectByte(PARAM_1); + int anim = getVarOrDirectByte(PARAM_2); + + // WORKAROUND bug #820357: This seems to be yet another script bug which + // the original engine let slip by. For details, refer to the tracker item. + if (_gameId == GID_INDY4 && vm.slot[_currentScript].number == 206 && _currentRoom == 17 && (act == 31 || act == 86)) { + return; + } + + Actor *a = derefActor(act, "o5_animateActor"); + a->animateActor(anim); +} + +void ScummEngine_v5::o5_breakHere() { + updateScriptPtr(); + _currentScript = 0xFF; +} + +void ScummEngine_v5::o5_chainScript() { + int vars[16]; + int script; + int cur; + + script = getVarOrDirectByte(PARAM_1); + + getWordVararg(vars); + + cur = _currentScript; + + // WORKAROUND bug #743314: Work around a bug in script 33 in Indy3 VGA. + // That script is used for the fist fights in the Zeppelin. It uses + // Local[5], even though that is never set to any value. But script 33 is + // called via chainScript by script 32, and in there Local[5] is defined + // to the actor ID of the opposing soldier. So, we copy that value over + // to the Local[5] variable of script 33. + if (_gameId == GID_INDY3 && vm.slot[cur].number == 32 && script == 33) { + vars[5] = vm.localvar[cur][5]; + } + + vm.slot[cur].number = 0; + vm.slot[cur].status = ssDead; + _currentScript = 0xFF; + + runScript(script, vm.slot[cur].freezeResistant, vm.slot[cur].recursive, vars); +} + +void ScummEngine_v5::o5_cursorCommand() { + int i, j, k; + int table[16]; + switch ((_opcode = fetchScriptByte()) & 0x1F) { + case 1: // SO_CURSOR_ON + _cursor.state = 1; + verbMouseOver(0); + break; + case 2: // SO_CURSOR_OFF + _cursor.state = 0; + verbMouseOver(0); + break; + case 3: // SO_USERPUT_ON + _userPut = 1; + break; + case 4: // SO_USERPUT_OFF + _userPut = 0; + break; + case 5: // SO_CURSOR_SOFT_ON + _cursor.state++; + verbMouseOver(0); + break; + case 6: // SO_CURSOR_SOFT_OFF + _cursor.state--; + verbMouseOver(0); + break; + case 7: // SO_USERPUT_SOFT_ON + _userPut++; + break; + case 8: // SO_USERPUT_SOFT_OFF + _userPut--; + break; + case 10: // SO_CURSOR_IMAGE + i = getVarOrDirectByte(PARAM_1); // Cursor number + j = getVarOrDirectByte(PARAM_2); // Charset letter to use + redefineBuiltinCursorFromChar(i, j); + break; + case 11: // SO_CURSOR_HOTSPOT + i = getVarOrDirectByte(PARAM_1); + j = getVarOrDirectByte(PARAM_2); + k = getVarOrDirectByte(PARAM_3); + redefineBuiltinCursorHotspot(i, j, k); + break; + case 12: // SO_CURSOR_SET + i = getVarOrDirectByte(PARAM_1); + if (i >= 0 && i <= 3) + _currentCursor = i; + else + error("SO_CURSOR_SET: unsupported cursor id %d", i); + break; + case 13: // SO_CHARSET_SET + initCharset(getVarOrDirectByte(PARAM_1)); + break; + case 14: /* unk */ + if (_version == 3) { + /*int a = */ getVarOrDirectByte(PARAM_1); + /*int b = */ getVarOrDirectByte(PARAM_2); + // This is some kind of "init charset" opcode. However, we don't have to do anything + // in here, as our initCharset automatically calls loadCharset for GF_SMALL_HEADER, + // games if needed. + } else { + getWordVararg(table); + for (i = 0; i < 16; i++) + _charsetColorMap[i] = _charsetData[_string[1]._default.charset][i] = (unsigned char)table[i]; + } + break; + } + + if (_version >= 4) { + VAR(VAR_CURSORSTATE) = _cursor.state; + VAR(VAR_USERPUT) = _userPut; + } +} + +void ScummEngine_v5::o5_cutscene() { + int args[16]; + getWordVararg(args); + beginCutscene(args); +} + +void ScummEngine_v5::o5_endCutscene() { + endCutscene(); +} + +void ScummEngine_v5::o5_debug() { + int a = getVarOrDirectWord(PARAM_1); + debugC(DEBUG_GENERAL, "o5_debug(%d)", a); +} + +void ScummEngine_v5::o5_decrement() { + getResultPos(); + setResult(readVar(_resultVarNumber) - 1); +} + +void ScummEngine_v5::o5_delay() { + int delay = fetchScriptByte(); + delay |= fetchScriptByte() << 8; + delay |= fetchScriptByte() << 16; + vm.slot[_currentScript].delay = delay; + vm.slot[_currentScript].status = ssPaused; + o5_breakHere(); +} + +void ScummEngine_v5::o5_delayVariable() { + vm.slot[_currentScript].delay = getVar(); + vm.slot[_currentScript].status = ssPaused; + o5_breakHere(); +} + +void ScummEngine_v5::o5_divide() { + int a; + getResultPos(); + a = getVarOrDirectWord(PARAM_1); + if (a == 0) { + error("Divide by zero"); + setResult(0); + } else + setResult(readVar(_resultVarNumber) / a); +} + +void ScummEngine_v5::o5_doSentence() { + int verb; + SentenceTab *st; + + verb = getVarOrDirectByte(PARAM_1); + if (verb == 0xFE) { + _sentenceNum = 0; + stopScript(VAR(VAR_SENTENCE_SCRIPT)); + clearClickedStatus(); + return; + } + + st = &_sentence[_sentenceNum++]; + + st->verb = verb; + st->objectA = getVarOrDirectWord(PARAM_2); + st->objectB = getVarOrDirectWord(PARAM_3); + st->preposition = (st->objectB != 0); + st->freezeCount = 0; +} + +void ScummEngine_v5::o5_drawBox() { + int x, y, x2, y2, color; + + x = getVarOrDirectWord(PARAM_1); + y = getVarOrDirectWord(PARAM_2); + + _opcode = fetchScriptByte(); + x2 = getVarOrDirectWord(PARAM_1); + y2 = getVarOrDirectWord(PARAM_2); + color = getVarOrDirectByte(PARAM_3); + + drawBox(x, y, x2, y2, color); +} + +void ScummEngine_v5::o5_drawObject() { + int state, obj, idx, i; + ObjectData *od; + uint16 x, y, w, h; + int xpos, ypos; + + state = 1; + xpos = ypos = 255; + obj = getVarOrDirectWord(PARAM_1); + + if (_features & GF_SMALL_HEADER) { + xpos = getVarOrDirectWord(PARAM_2); + ypos = getVarOrDirectWord(PARAM_3); + } else { + _opcode = fetchScriptByte(); + switch (_opcode & 0x1F) { + case 1: /* draw at */ + xpos = getVarOrDirectWord(PARAM_1); + ypos = getVarOrDirectWord(PARAM_2); + break; + case 2: /* set state */ + state = getVarOrDirectWord(PARAM_1); + break; + case 0x1F: /* neither */ + break; + default: + error("o5_drawObject: unknown subopcode %d", _opcode & 0x1F); + } + } + + idx = getObjectIndex(obj); + if (idx == -1) + return; + + od = &_objs[idx]; + if (xpos != 0xFF) { + od->walk_x += (xpos * 8) - od->x_pos; + od->x_pos = xpos * 8; + od->walk_y += (ypos * 8) - od->y_pos; + od->y_pos = ypos * 8; + } + addObjectToDrawQue(idx); + + x = od->x_pos; + y = od->y_pos; + w = od->width; + h = od->height; + + i = _numLocalObjects - 1; + do { + if (_objs[i].obj_nr && _objs[i].x_pos == x && _objs[i].y_pos == y && _objs[i].width == w && _objs[i].height == h) + putState(_objs[i].obj_nr, 0); + } while (--i); + + putState(obj, state); +} + +void ScummEngine_v5::o5_getStringWidth() { + int string, width = 0; + byte *ptr; + + getResultPos(); + string = getVarOrDirectByte(PARAM_1); + ptr = getResourceAddress(rtString, string); + assert(ptr); + + width = _charset->getStringWidth(0, ptr); + + setResult(width); + debug(0, "o5_getStringWidth, result %d", width); +} + +void ScummEngine_v5::o5_saveLoadVars() { + if (fetchScriptByte() == 1) + saveVars(); + else + loadVars(); +} + +void ScummEngine_v5::saveVars() { + int a, b; + + while ((_opcode = fetchScriptByte()) != 0) { + switch (_opcode & 0x1F) { + case 0x01: // write a range of variables + getResultPos(); + a = _resultVarNumber; + getResultPos(); + b = _resultVarNumber; + debug(0, "stub saveVars: vars %d -> %d", a, b); + break; + case 0x02: // write a range of string variables + a = getVarOrDirectByte(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + debug(0, "stub saveVars: strings %d -> %d", a, b); + break; + case 0x03: // open file + a = resStrLen(_scriptPointer); + debug(0, "stub saveVars to %s", _scriptPointer); + _scriptPointer += a + 1; + break; + case 0x04: + return; + break; + case 0x1F: // close file + debug(0, "stub saveVars close file"); + return; + break; + } + } +} + +void ScummEngine_v5::loadVars() { + int a, b; + +// Common::hexdump(_scriptPointer, 64); + while ((_opcode = fetchScriptByte()) != 0) { + switch (_opcode & 0x1F) { + case 0x01: // read a range of variables + getResultPos(); + a = _resultVarNumber; + getResultPos(); + b = _resultVarNumber; + debug(0, "stub loadVars: vars %d -> %d", a, b); + break; + case 0x02: // read a range of string variables + a = getVarOrDirectByte(0x80); + b = getVarOrDirectByte(0x40); + debug(0, "stub loadVars: strings %d -> %d", a, b); + break; + case 0x03: // open file + a = resStrLen(_scriptPointer); + debug(0, "stub loadVars from %s", _scriptPointer); + _scriptPointer += a + 1; + break; + case 0x04: + return; + break; + case 0x1F: // close file + debug(0, "stub loadVars close file"); + return; + break; + } + } +} + +void ScummEngine_v5::o5_expression() { + int dst, i; + + _scummStackPos = 0; + getResultPos(); + dst = _resultVarNumber; + + while ((_opcode = fetchScriptByte()) != 0xFF) { + switch (_opcode & 0x1F) { + case 1: /* varordirect */ + push(getVarOrDirectWord(PARAM_1)); + break; + case 2: /* add */ + i = pop(); + push(i + pop()); + break; + case 3: /* sub */ + i = pop(); + push(pop() - i); + break; + case 4: /* mul */ + i = pop(); + push(i * pop()); + break; + case 5: /* div */ + i = pop(); + if (i == 0) + error("Divide by zero"); + push(pop() / i); + break; + case 6: /* normal opcode */ + _opcode = fetchScriptByte(); + executeOpcode(_opcode); + push(_scummVars[0]); + break; + } + } + + _resultVarNumber = dst; + setResult(pop()); +} + +void ScummEngine_v5::o5_faceActor() { + int act = getVarOrDirectByte(PARAM_1); + int obj = getVarOrDirectWord(PARAM_2); + Actor *a = derefActor(act, "o5_faceActor"); + a->faceToObject(obj); +} + +void ScummEngine_v5::o5_findInventory() { + getResultPos(); + int x = getVarOrDirectByte(PARAM_1); + int y = getVarOrDirectByte(PARAM_2); + setResult(findInventory(x, y)); +} + +void ScummEngine_v5::o5_findObject() { + getResultPos(); + int x = getVarOrDirectByte(PARAM_1); + int y = getVarOrDirectByte(PARAM_2); + setResult(findObject(x, y)); +} + +void ScummEngine_v5::o5_freezeScripts() { + int scr = getVarOrDirectByte(PARAM_1); + + if (scr != 0) + freezeScripts(scr); + else + unfreezeScripts(); +} + +void ScummEngine_v5::o5_getActorCostume() { + getResultPos(); + int act = getVarOrDirectByte(PARAM_1); + Actor *a = derefActor(act, "o5_getActorCostume"); + setResult(a->_costume); +} + +void ScummEngine_v5::o5_getActorElevation() { + getResultPos(); + int act = getVarOrDirectByte(PARAM_1); + Actor *a = derefActor(act, "o5_getActorElevation"); + setResult(a->getElevation()); +} + +void ScummEngine_v5::o5_getActorFacing() { + getResultPos(); + int act = getVarOrDirectByte(PARAM_1); + Actor *a = derefActor(act, "o5_getActorFacing"); + setResult(newDirToOldDir(a->getFacing())); +} + +void ScummEngine_v5::o5_getActorMoving() { + getResultPos(); + int act = getVarOrDirectByte(PARAM_1); + Actor *a = derefActor(act, "o5_getActorMoving"); + setResult(a->_moving); +} + +void ScummEngine_v5::o5_getActorRoom() { + getResultPos(); + int act = getVarOrDirectByte(PARAM_1); + // WORKAROUND bug #746349. This is a really odd bug in either the script + // or in our script engine. Might be a good idea to investigate this + // further by e.g. looking at the FOA engine a bit closer. + if (_gameId == GID_INDY4 && _roomResource == 94 && vm.slot[_currentScript].number == 206 && !isValidActor(act)) { + setResult(0); + return; + } + + Actor *a = derefActor(act, "o5_getActorRoom"); + setResult(a->_room); +} + +void ScummEngine_v5::o5_getActorScale() { + Actor *a; + + // INDY3 uses this opcode for waitForActor + if (_gameId == GID_INDY3) { + const byte *oldaddr = _scriptPointer - 1; + a = derefActor(getVarOrDirectByte(PARAM_1), "o5_getActorScale (wait)"); + if (a->_moving) { + _scriptPointer = oldaddr; + o5_breakHere(); + } + return; + } + + getResultPos(); + int act = getVarOrDirectByte(PARAM_1); + a = derefActor(act, "o5_getActorScale"); + setResult(a->_scalex); +} + +void ScummEngine_v5::o5_getActorWalkBox() { + getResultPos(); + int act = getVarOrDirectByte(PARAM_1); + Actor *a = derefActor(act, "o5_getActorWalkBox"); + setResult(a->_walkbox); +} + +void ScummEngine_v5::o5_getActorWidth() { + getResultPos(); + int act = getVarOrDirectByte(PARAM_1); + Actor *a = derefActor(act, "o5_getActorWidth"); + setResult(a->_width); +} + +void ScummEngine_v5::o5_getActorX() { + int a; + getResultPos(); + + if ((_gameId == GID_INDY3) && !(_platform == Common::kPlatformMacintosh)) + a = getVarOrDirectByte(PARAM_1); + else + a = getVarOrDirectWord(PARAM_1); + + setResult(getObjX(a)); +} + +void ScummEngine_v5::o5_getActorY() { + int a; + getResultPos(); + + if ((_gameId == GID_INDY3) && !(_platform == Common::kPlatformMacintosh)) { + a = getVarOrDirectByte(PARAM_1); + + // WORKAROUND bug #636433 (can't get into Zeppelin) + if (_roomResource == 36) { + setResult(getObjY(a) - 1); + return; + } + } else + a = getVarOrDirectWord(PARAM_1); + + setResult(getObjY(a)); +} + +void ScummEngine_v5::o5_saveLoadGame() { + getResultPos(); + byte a = getVarOrDirectByte(PARAM_1); + byte slot = (a & 0x1F) + 1; + byte result = 0; + + if ((_gameId == GID_MANIAC) && (_version == 1)) { + // Convert older load/save screen + // 1 Load + // 2 Save + slot = 1; + if (a == 1) + _opcode = 0x40; + else if ((a == 2) || (_platform == Common::kPlatformNES)) + _opcode = 0x80; + } else { + _opcode = a & 0xE0; + } + + switch (_opcode) { + case 0x00: // num slots available + result = 100; + break; + case 0x20: // drive + if (_gameId == GID_INDY3) { + // 0 = hard drive + // 1 = disk drive + result = 0; + } else { + // set current drive + result = 1; + } + break; + case 0x40: // load + if (loadState(slot, _saveTemporaryState)) + result = 3; // sucess + else + result = 5; // failed to load + break; + case 0x80: // save + if (saveState(slot, _saveTemporaryState)) + result = 0; + else + result = 2; + break; + case 0xC0: // test if save exists + bool avail_saves[100]; + char filename[256]; + + listSavegames(avail_saves, ARRAYSIZE(avail_saves)); + makeSavegameName(filename, slot, false); + if (avail_saves[slot] && (_saveFileMan->openForLoading(filename))) + result = 6; // save file exists + else + result = 7; // save file does not exist + break; + default: + error("o5_saveLoadGame: unknown subopcode %d", _opcode); + } + + setResult(result); +} + +void ScummEngine_v5::o5_getAnimCounter() { + if (_version == 3) { + o5_saveLoadGame(); + return; + } + + getResultPos(); + + int act = getVarOrDirectByte(PARAM_1); + Actor *a = derefActor(act, "o5_getAnimCounter"); + setResult(a->_cost.animCounter); +} + +void ScummEngine_v5::o5_getClosestObjActor() { + int obj; + int act; + int dist; + + // This code can't detect any actors farther away than 255 units + // (pixels in newer games, characters in older ones.) But this is + // perfectly OK, as it is exactly how the original behaved. + + int closest_obj = 0xFF, closest_dist = 0xFF; + + getResultPos(); + + act = getVarOrDirectWord(PARAM_1); + obj = VAR(VAR_ACTOR_RANGE_MAX); + + do { + dist = getObjActToObjActDist(act, obj); + if (dist < closest_dist) { + closest_dist = dist; + closest_obj = obj; + } + } while (--obj >= VAR(VAR_ACTOR_RANGE_MIN)); + + setResult(closest_obj); +} + +void ScummEngine_v5::o5_getDist() { + int o1, o2; + int r; + getResultPos(); + o1 = getVarOrDirectWord(PARAM_1); + o2 = getVarOrDirectWord(PARAM_2); + r = getObjActToObjActDist(o1, o2); + + // FIXME: MI2 race workaround, see bug #597022. We never quite figured out + // what the real cause of this, or if it maybe occurs in the original, too... + if (_gameId == GID_MONKEY2 && vm.slot[_currentScript].number == 40 && r < 60) + r = 60; + + // WORKAROUND bug #795937 + if ((_gameId == GID_MONKEY_EGA || _gameId == GID_PASS) && o1 == 1 && o2 == 307 && vm.slot[_currentScript].number == 205 && r == 2) + r = 3; + + setResult(r); +} + +void ScummEngine_v5::o5_getInventoryCount() { + getResultPos(); + setResult(getInventoryCount(getVarOrDirectByte(PARAM_1))); +} + +void ScummEngine_v5::o5_getObjectOwner() { + getResultPos(); + setResult(getOwner(getVarOrDirectWord(PARAM_1))); +} + +void ScummEngine_v5::o5_getObjectState() { + if (_features & GF_SMALL_HEADER) { + o5_ifState(); + } else { + getResultPos(); + setResult(getState(getVarOrDirectWord(PARAM_1))); + } +} + +void ScummEngine_v5::o5_ifState() { + int a = getVarOrDirectWord(PARAM_1); + int b = getVarOrDirectByte(PARAM_2); + + if (getState(a) != b) + o5_jumpRelative(); + else + ignoreScriptWord(); +} + +void ScummEngine_v5::o5_ifNotState() { + int a = getVarOrDirectWord(PARAM_1); + int b = getVarOrDirectByte(PARAM_2); + + if (getState(a) == b) + o5_jumpRelative(); + else + ignoreScriptWord(); +} + +void ScummEngine_v5::o5_getRandomNr() { + getResultPos(); + setResult(_rnd.getRandomNumber(getVarOrDirectByte(PARAM_1))); +} + +void ScummEngine_v5::o5_isScriptRunning() { + getResultPos(); + setResult(isScriptRunning(getVarOrDirectByte(PARAM_1))); +} + +void ScummEngine_v5::o5_getVerbEntrypoint() { + int a, b; + getResultPos(); + a = getVarOrDirectWord(PARAM_1); + b = getVarOrDirectWord(PARAM_2); + + setResult(getVerbEntrypoint(a, b)); +} + +void ScummEngine_v5::o5_ifClassOfIs() { + int act, cls, b = 0; + bool cond = true; + + act = getVarOrDirectWord(PARAM_1); + + while ((_opcode = fetchScriptByte()) != 0xFF) { + cls = getVarOrDirectWord(PARAM_1); + + if (!cls) // FIXME: Ender can't remember why this is here, + b = false; // but it fixes an oddball zak256 crash + else + b = getClass(act, cls); + + if (cls & 0x80 && !b || !(cls & 0x80) && b) + cond = false; + } + if (cond) + ignoreScriptWord(); + else + o5_jumpRelative(); +} + +void ScummEngine_v5::o5_increment() { + getResultPos(); + setResult(readVar(_resultVarNumber) + 1); +} + +void ScummEngine_v5::o5_isActorInBox() { + int act = getVarOrDirectByte(PARAM_1); + int box = getVarOrDirectByte(PARAM_2); + Actor *a = derefActor(act, "o5_isActorInBox"); + + if (!checkXYInBoxBounds(box, a->_pos.x, a->_pos.y)) + o5_jumpRelative(); + else + ignoreScriptWord(); +} + +void ScummEngine_v5::o5_isEqual() { + int16 a, b; + int var; + + if (_version <= 2) + var = fetchScriptByte(); + else + var = fetchScriptWord(); + a = readVar(var); + b = getVarOrDirectWord(PARAM_1); + + // HACK: See bug report #602348. The sound effects for Largo's screams + // are only played on type 5 soundcards. However, there is at least one + // other sound effect (the bartender spitting) which is only played on + // type 3 soundcards. + + if (_gameId == GID_MONKEY2 && var == VAR_SOUNDCARD && b == 5) + b = a; + + // HACK: To allow demo script of Maniac Mansion V2 + // The camera x position is only 100, instead of 180, after game title name scrolls. + if (_gameId == GID_MANIAC && _version == 2 && _demoMode && isScriptRunning(173) && b == 180) + b = 100; + + if (b == a) + ignoreScriptWord(); + else + o5_jumpRelative(); + +} + +void ScummEngine_v5::o5_isGreater() { + int16 a = getVar(); + int16 b = getVarOrDirectWord(PARAM_1); + if (b > a) + ignoreScriptWord(); + else + o5_jumpRelative(); +} + +void ScummEngine_v5::o5_isGreaterEqual() { + int16 a = getVar(); + int16 b = getVarOrDirectWord(PARAM_1); + if (b >= a) + ignoreScriptWord(); + else + o5_jumpRelative(); +} + +void ScummEngine_v5::o5_isLess() { + int16 a = getVar(); + int16 b = getVarOrDirectWord(PARAM_1); + if (b < a) + ignoreScriptWord(); + else + o5_jumpRelative(); +} + +void ScummEngine_v5::o5_lessOrEqual() { + int16 a = getVar(); + int16 b = getVarOrDirectWord(PARAM_1); + + // WORKAROUND bug #820507 : Work around a bug in Indy3Town. + if (_gameId == GID_INDY3 && (_platform == Common::kPlatformFMTowns) && + (vm.slot[_currentScript].number == 200 || vm.slot[_currentScript].number == 203) && + _currentRoom == 70 && b == -256) { + o5_jumpRelative(); + return; + } + + if (b <= a) + ignoreScriptWord(); + else + o5_jumpRelative(); +} + +void ScummEngine_v5::o5_isNotEqual() { + int16 a = getVar(); + int16 b = getVarOrDirectWord(PARAM_1); + if (b != a) + ignoreScriptWord(); + else + o5_jumpRelative(); +} + +void ScummEngine_v5::o5_notEqualZero() { + int a = getVar(); + if (a != 0) + ignoreScriptWord(); + else + o5_jumpRelative(); +} + +void ScummEngine_v5::o5_equalZero() { + int a = getVar(); + if (a == 0) + ignoreScriptWord(); + else + o5_jumpRelative(); +} + +void ScummEngine_v5::o5_jumpRelative() { + // Note that calling fetchScriptWord() will also modify _scriptPointer, + // so *don't* do this: _scriptPointer += (int16)fetchScriptWord(); + // + // I'm not enough of a language lawyer to say for certain that this is + // undefined, but I do know that GCC 4.0 doesn't think it means what + // we want it to mean. + + int16 offset = (int16)fetchScriptWord(); + _scriptPointer += offset; +} + +void ScummEngine_v5::o5_lights() { + int a, b, c; + + a = getVarOrDirectByte(PARAM_1); + b = fetchScriptByte(); + c = fetchScriptByte(); + + if (c == 0) + VAR(VAR_CURRENT_LIGHTS) = a; + else if (c == 1) { + _flashlight.xStrips = a; + _flashlight.yStrips = b; + } + _fullRedraw = true; +} + +void ScummEngine_v5::o5_loadRoom() { + int room; + + room = getVarOrDirectByte(PARAM_1); + + if (!_copyProtection) { + // Skip copy protection scheme + if (_gameId == GID_INDY3 && (_features & GF_OLD_BUNDLE) && room == 92) { + VAR(57) = 1; + return; + } + } + + // For small header games, we only call startScene if the room + // actually changed. This avoid unwanted (wrong) fades in Zak256 + // and others. OTOH, it seems to cause a problem in newer games. + if (!(_features & GF_SMALL_HEADER) || room != _currentRoom) + startScene(room, 0, 0); + + _fullRedraw = true; +} + +void ScummEngine_v5::o5_loadRoomWithEgo() { + Actor *a; + int obj, room, x, y; + int x2, y2, dir, oldDir; + + obj = getVarOrDirectWord(PARAM_1); + room = getVarOrDirectByte(PARAM_2); + + a = derefActor(VAR(VAR_EGO), "o5_loadRoomWithEgo"); + + a->putActor(a->_pos.x, a->_pos.y, room); + oldDir = a->getFacing(); + _egoPositioned = false; + + x = (int16)fetchScriptWord(); + y = (int16)fetchScriptWord(); + + VAR(VAR_WALKTO_OBJ) = obj; + startScene(a->_room, a, obj); + VAR(VAR_WALKTO_OBJ) = 0; + + if (_version <= 4) { + if (whereIsObject(obj) != WIO_ROOM) + error("o5_loadRoomWithEgo: Object %d is not in room %d", obj, _currentRoom); + if (!_egoPositioned) { + getObjectXYPos(obj, x2, y2, dir); + a->putActor(x2, y2, _currentRoom); + if (a->getFacing() == oldDir) + a->setDirection(dir + 180); + } + a->_moving = 0; + } + + // This is based on disassembly + camera._cur.x = camera._dest.x = a->_pos.x; + setCameraFollows(a); + + _fullRedraw = true; + + if (x != -1) { + a->startWalkActor(x, y, -1); + } +} + +void ScummEngine_v5::o5_matrixOps() { + int a, b; + + if (_version == 3) { + a = getVarOrDirectByte(PARAM_1); + b = fetchScriptByte(); + setBoxFlags(a, b); + return; + } + + _opcode = fetchScriptByte(); + switch (_opcode & 0x1F) { + case 1: + a = getVarOrDirectByte(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + setBoxFlags(a, b); + break; + case 2: + a = getVarOrDirectByte(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + setBoxScale(a, b); + break; + case 3: + a = getVarOrDirectByte(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + setBoxScale(a, (b - 1) | 0x8000); + break; + case 4: + createBoxMatrix(); + break; + } +} + +void ScummEngine_v5::o5_move() { + getResultPos(); + setResult(getVarOrDirectWord(PARAM_1)); +} + +void ScummEngine_v5::o5_multiply() { + int a; + getResultPos(); + a = getVarOrDirectWord(PARAM_1); + setResult(readVar(_resultVarNumber) * a); +} + +void ScummEngine_v5::o5_or() { + int a; + getResultPos(); + a = getVarOrDirectWord(PARAM_1); + setResult(readVar(_resultVarNumber) | a); +} + +void ScummEngine_v5::o5_beginOverride() { + if (fetchScriptByte() != 0) + beginOverride(); + else + endOverride(); +} + +void ScummEngine_v5::o5_panCameraTo() { + panCameraTo(getVarOrDirectWord(PARAM_1), 0); +} + +void ScummEngine_v5::o5_pickupObject() { + int obj, room; + if (_version == 3 || _version == 4) { + o5_drawObject(); + return; + } + + obj = getVarOrDirectWord(PARAM_1); + room = getVarOrDirectByte(PARAM_2); + if (room == 0) + room = _roomResource; + addObjectToInventory(obj, room); + putOwner(obj, VAR(VAR_EGO)); + putClass(obj, kObjectClassUntouchable, 1); + putState(obj, 1); + markObjectRectAsDirty(obj); + clearDrawObjectQueue(); + runInventoryScript(1); +} + +void ScummEngine_v5::o5_print() { + _actorToPrintStrFor = getVarOrDirectByte(PARAM_1); + decodeParseString(); +} + +void ScummEngine_v5::o5_printEgo() { + _actorToPrintStrFor = (byte)VAR(VAR_EGO); + decodeParseString(); +} + +void ScummEngine_v5::o5_pseudoRoom() { + int i = fetchScriptByte(), j; + while ((j = fetchScriptByte()) != 0) { + if (j >= 0x80) { + _resourceMapper[j & 0x7F] = i; + } + } +} + +void ScummEngine_v5::o5_putActor() { + int x, y; + Actor *a; + + a = derefActor(getVarOrDirectByte(PARAM_1), "o5_putActor"); + x = getVarOrDirectWord(PARAM_2); + y = getVarOrDirectWord(PARAM_3); + a->putActor(x, y, a->_room); +} + +void ScummEngine_v5::o5_putActorAtObject() { + int obj, x, y; + Actor *a; + + a = derefActor(getVarOrDirectByte(PARAM_1), "o5_putActorAtObject"); + obj = getVarOrDirectWord(PARAM_2); + if (whereIsObject(obj) != WIO_NOT_FOUND) + getObjectXYPos(obj, x, y); + else { + x = 240; + y = 120; + } + a->putActor(x, y, a->_room); +} + +void ScummEngine_v5::o5_putActorInRoom() { + Actor *a; + int act = getVarOrDirectByte(PARAM_1); + int room = getVarOrDirectByte(PARAM_2); + + a = derefActor(act, "o5_putActorInRoom"); + + if (a->_visible && _currentRoom != room && getTalkingActor() == a->_number) { + stopTalk(); + } + a->_room = room; + if (!room) + a->putActor(0, 0, 0); +} + +void ScummEngine_v5::o5_systemOps() { + byte subOp = fetchScriptByte(); + switch (subOp) { + case 1: // SO_RESTART + restart(); + break; + case 2: // SO_PAUSE + pauseGame(); + break; + case 3: // SO_QUIT + shutDown(); + break; + default: + error("o5_systemOps: unknown subopcode %d", subOp); + } +} + +void ScummEngine_v5::o5_resourceRoutines() { + const ResTypes resType[4] = { rtScript, rtSound, rtCostume, rtRoom }; + int resid = 0; + int foo, bar; + + _opcode = fetchScriptByte(); + if (_opcode != 17) + resid = getVarOrDirectByte(PARAM_1); + if (!(_platform == Common::kPlatformFMTowns)) { + // FIXME - this probably can be removed eventually, I don't think the following + // check will ever be triggered, but then I could be wrong and it's better + // to play it safe. + if ((_opcode & 0x3F) != (_opcode & 0x1F)) + error("Oops, this shouldn't happen: o5_resourceRoutines opcode %d", _opcode); + } + + int op = _opcode & 0x3F; + + switch (op) { + case 1: // SO_LOAD_SCRIPT + case 2: // SO_LOAD_SOUND + case 3: // SO_LOAD_COSTUME + ensureResourceLoaded(resType[op - 1], resid); + break; + case 4: // SO_LOAD_ROOM + if (_version == 3) { + ensureResourceLoaded(rtRoom, resid); + if (resid > 0x7F) + resid = _resourceMapper[resid & 0x7F]; + + if (_currentRoom != resid) { + res.setResourceCounter(rtRoom, resid, 1); + } + } else + ensureResourceLoaded(rtRoom, resid); + break; + + case 5: // SO_NUKE_SCRIPT + case 6: // SO_NUKE_SOUND + case 7: // SO_NUKE_COSTUME + case 8: // SO_NUKE_ROOM + if (_gameId == GID_ZAK && (_platform == Common::kPlatformFMTowns)) + error("o5_resourceRoutines %d should not occur in Zak256", op); + else + res.setResourceCounter(resType[op-5], resid, 0x7F); + break; + case 9: // SO_LOCK_SCRIPT + if (resid >= _numGlobalScripts) + break; + res.lock(rtScript, resid); + break; + case 10: // SO_LOCK_SOUND + res.lock(rtSound, resid); + break; + case 11: // SO_LOCK_COSTUME + res.lock(rtCostume, resid); + break; + case 12: // SO_LOCK_ROOM + if (resid > 0x7F) + resid = _resourceMapper[resid & 0x7F]; + res.lock(rtRoom, resid); + break; + + case 13: // SO_UNLOCK_SCRIPT + if (resid >= _numGlobalScripts) + break; + res.unlock(rtScript, resid); + break; + case 14: // SO_UNLOCK_SOUND + res.unlock(rtSound, resid); + break; + case 15: // SO_UNLOCK_COSTUME + res.unlock(rtCostume, resid); + break; + case 16: // SO_UNLOCK_ROOM + if (resid > 0x7F) + resid = _resourceMapper[resid & 0x7F]; + res.unlock(rtRoom, resid); + break; + + case 17: // SO_CLEAR_HEAP + //heapClear(0); + //unkHeapProc2(0, 0); + break; + case 18: // SO_LOAD_CHARSET + loadCharset(resid); + break; + case 19: // SO_NUKE_CHARSET + nukeCharset(resid); + break; + case 20: // SO_LOAD_OBJECT + loadFlObject(getVarOrDirectWord(PARAM_2), resid); + break; + + // TODO: For the following see also Hibarnatus' information on bug #805691. + case 32: + // TODO (apparently never used in FM-TOWNS) + debug(0, "o5_resourceRoutines %d not yet handled (script %d)", op, vm.slot[_currentScript].number); + break; + case 33: + // TODO (apparently never used in FM-TOWNS) + debug(0, "o5_resourceRoutines %d not yet handled (script %d)", op, vm.slot[_currentScript].number); + break; + case 35: + // TODO: Might be used to set CD volume in FM-TOWNS Loom + foo = getVarOrDirectByte(PARAM_2); + debug(0, "o5_resourceRoutines %d not yet handled (script %d)", op, vm.slot[_currentScript].number); + break; + case 36: + // TODO: Sets the loudness of a sound resource. Used in Indy3 and Zak. + foo = getVarOrDirectByte(PARAM_2); + bar = fetchScriptByte(); + debug(0, "o5_resourceRoutines %d not yet handled (script %d)", op, vm.slot[_currentScript].number); + break; + case 37: + // TODO: Sets the pitch of a sound resource (pitch = foo - center semitones. + // "center" is at 0x32 in the sfx resource (always 0x3C in zak256, but sometimes different in Indy3). + foo = getVarOrDirectByte(PARAM_2); + debug(0, "o5_resourceRoutines %d not yet handled (script %d)", op, vm.slot[_currentScript].number); + break; + + default: + error("o5_resourceRoutines: default case %d", op); + } +} + +void ScummEngine_v5::o5_roomOps() { + int a = 0, b = 0, c, d, e; + + if (_version == 3) { + a = getVarOrDirectWord(PARAM_1); + b = getVarOrDirectWord(PARAM_2); + } + + _opcode = fetchScriptByte(); + switch (_opcode & 0x1F) { + case 1: // SO_ROOM_SCROLL + if (_version != 3) { + a = getVarOrDirectWord(PARAM_1); + b = getVarOrDirectWord(PARAM_2); + } + if (a < (_screenWidth / 2)) + a = (_screenWidth / 2); + if (b < (_screenWidth / 2)) + b = (_screenWidth / 2); + if (a > _roomWidth - (_screenWidth / 2)) + a = _roomWidth - (_screenWidth / 2); + if (b > _roomWidth - (_screenWidth / 2)) + b = _roomWidth - (_screenWidth / 2); + VAR(VAR_CAMERA_MIN_X) = a; + VAR(VAR_CAMERA_MAX_X) = b; + break; + case 2: // SO_ROOM_COLOR + if (_features & GF_SMALL_HEADER) { + if (_version != 3) { + a = getVarOrDirectWord(PARAM_1); + b = getVarOrDirectWord(PARAM_2); + } + checkRange(256, 0, a, "o5_roomOps: 2: Illegal room color slot (%d)"); + _roomPalette[b] = a; + _fullRedraw = true; + } else { + error("room-color is no longer a valid command"); + } + break; + + case 3: // SO_ROOM_SCREEN + if (_version != 3) { + a = getVarOrDirectWord(PARAM_1); + b = getVarOrDirectWord(PARAM_2); + } + initScreens(a, b); + break; + case 4: // SO_ROOM_PALETTE + if (_features & GF_SMALL_HEADER) { + if (_version != 3) { + a = getVarOrDirectWord(PARAM_1); + b = getVarOrDirectWord(PARAM_2); + } + checkRange(256, 0, a, "o5_roomOps: 2: Illegal room color slot (%d)"); + _shadowPalette[b] = a; + setDirtyColors(b, b); + } else { + a = getVarOrDirectWord(PARAM_1); + b = getVarOrDirectWord(PARAM_2); + c = getVarOrDirectWord(PARAM_3); + _opcode = fetchScriptByte(); + d = getVarOrDirectByte(PARAM_1); + setPalColor(d, a, b, c); /* index, r, g, b */ + } + break; + case 5: // SO_ROOM_SHAKE_ON + setShake(1); + break; + case 6: // SO_ROOM_SHAKE_OFF + setShake(0); + break; + case 7: // SO_ROOM_SCALE + a = getVarOrDirectByte(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + _opcode = fetchScriptByte(); + c = getVarOrDirectByte(PARAM_1); + d = getVarOrDirectByte(PARAM_2); + _opcode = fetchScriptByte(); + e = getVarOrDirectByte(PARAM_2); + setScaleSlot(e - 1, 0, b, a, 0, d, c); + break; + case 8: // SO_ROOM_INTENSITY + if (_features & GF_SMALL_HEADER) { + if (_version != 3) { + a = getVarOrDirectWord(PARAM_1); + b = getVarOrDirectWord(PARAM_2); + } + c = getVarOrDirectWord(PARAM_3); + } else { + a = getVarOrDirectByte(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + c = getVarOrDirectByte(PARAM_3); + } + darkenPalette(a, a, a, b, c); + break; + case 9: // SO_ROOM_SAVEGAME + _saveLoadFlag = getVarOrDirectByte(PARAM_1); + _saveLoadSlot = getVarOrDirectByte(PARAM_2); + _saveLoadSlot = 99; /* use this slot */ + _saveTemporaryState = true; + break; + case 10: // SO_ROOM_FADE + a = getVarOrDirectWord(PARAM_1); + if (a) { + if (_platform == Common::kPlatformFMTowns) { + switch (a) { + case 8: // compose kMainVirtScreen over a screen buffer + case 9: // call 0x110:0x20 _ax=0x601 _edx=2 + case 10: // call 0x110:0x20 _ax=0x601 _edx=3 + case 11: // clear screen 0x1C:0x45000 sizeof(640 * 320) + case 12: // call 0x110:0x20 _ax=0x601 _edx=0 + case 13: // call 0x110:0x20 _ax=0x601 _edx=1 + case 16: // enable clearing of a screen buffer in drawBitmap() + case 17: // disable clearing of a screen buffer in drawBitmap() + case 18: // clear a screen buffer + case 19: // enable palette operations (palManipulate(), cyclePalette() etc.) + case 20: // disable palette operations + case 21: // disable clearing of screen 0x1C:0x5000 sizeof(640 * 320) in initScreens() + case 22: // enable clearing of screen 0x1C:0x5000 sizeof(640 * 320) in initScreens() + case 30: + debug(0, "o5_roomOps: unhandled FM-TOWNS fadeEffect %d", a); + return; + break; + } + } + _switchRoomEffect = (byte)(a & 0xFF); + _switchRoomEffect2 = (byte)(a >> 8); + } else { + fadeIn(_newEffect); + } + break; + case 11: // SO_RGB_ROOM_INTENSITY + a = getVarOrDirectWord(PARAM_1); + b = getVarOrDirectWord(PARAM_2); + c = getVarOrDirectWord(PARAM_3); + _opcode = fetchScriptByte(); + d = getVarOrDirectByte(PARAM_1); + e = getVarOrDirectByte(PARAM_2); + darkenPalette(a, b, c, d, e); + break; + case 12: // SO_ROOM_SHADOW + a = getVarOrDirectWord(PARAM_1); + b = getVarOrDirectWord(PARAM_2); + c = getVarOrDirectWord(PARAM_3); + _opcode = fetchScriptByte(); + d = getVarOrDirectByte(PARAM_1); + e = getVarOrDirectByte(PARAM_2); + setupShadowPalette(a, b, c, d, e, 0, 256); + break; + + case 13: // SO_SAVE_STRING + { + Common::OutSaveFile *file; + char filename[256], *s; + + a = getVarOrDirectByte(PARAM_1); + s = filename; + while ((*s++ = fetchScriptByte())); + + file = _saveFileMan->openForSaving(filename); + if (file != NULL) { + byte *ptr; + ptr = getResourceAddress(rtString, a); + file->write(ptr, resStrLen(ptr) + 1); + delete file; + VAR(VAR_SOUNDRESULT) = 0; + } + break; + } + case 14: // SO_LOAD_STRING + { + Common::InSaveFile *file; + char filename[256], *s; + + a = getVarOrDirectByte(PARAM_1); + s = filename; + while ((*s++ = fetchScriptByte())); + + file = _saveFileMan->openForLoading(filename); + if (file != NULL) { + byte *ptr; + int len = 256, cnt = 0; + ptr = (byte *)malloc(len); + while (ptr) { + int r = file->read(ptr + cnt, len - cnt); + if ((cnt += r) < len) break; + ptr = (byte *)realloc(ptr, len *= 2); + } + ptr[cnt] = '\0'; + loadPtrToResource(rtString, a, ptr); + free(ptr); + delete file; + } + break; + } + case 15: // SO_ROOM_TRANSFORM + a = getVarOrDirectByte(PARAM_1); + _opcode = fetchScriptByte(); + b = getVarOrDirectByte(PARAM_1); + c = getVarOrDirectByte(PARAM_2); + _opcode = fetchScriptByte(); + d = getVarOrDirectByte(PARAM_1); + palManipulateInit(a, b, c, d); + break; + + case 16: // SO_CYCLE_SPEED + a = getVarOrDirectByte(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + checkRange(16, 1, a, "o5_roomOps: 16: color cycle out of range (%d)"); + _colorCycle[a - 1].delay = (b != 0) ? 0x4000 / (b * 0x4C) : 0; + break; + default: + error("o5_roomOps: unknown subopcode %d", _opcode & 0x1F); + } +} + +void ScummEngine_v5::o5_saveRestoreVerbs() { + int a, b, c, slot, slot2; + + _opcode = fetchScriptByte(); + + a = getVarOrDirectByte(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + c = getVarOrDirectByte(PARAM_3); + + switch (_opcode) { + case 1: // SO_SAVE_VERBS + while (a <= b) { + slot = getVerbSlot(a, 0); + if (slot && _verbs[slot].saveid == 0) { + _verbs[slot].saveid = c; + drawVerb(slot, 0); + verbMouseOver(0); + } + a++; + } + break; + case 2: // SO_RESTORE_VERBS + while (a <= b) { + slot = getVerbSlot(a, c); + if (slot) { + slot2 = getVerbSlot(a, 0); + if (slot2) + killVerb(slot2); + slot = getVerbSlot(a, c); + _verbs[slot].saveid = 0; + drawVerb(slot, 0); + verbMouseOver(0); + } + a++; + } + break; + case 3: // SO_DELETE_VERBS + while (a <= b) { + slot = getVerbSlot(a, c); + if (slot) + killVerb(slot); + a++; + } + break; + default: + error("o5_saveRestoreVerbs: unknown subopcode %d", _opcode); + } +} + +void ScummEngine_v5::o5_setCameraAt() { + setCameraAtEx(getVarOrDirectWord(PARAM_1)); +} + +void ScummEngine_v5::o5_setObjectName() { + int obj = getVarOrDirectWord(PARAM_1); + setObjectName(obj); +} + +void ScummEngine_v5::o5_setOwnerOf() { + int obj, owner; + + obj = getVarOrDirectWord(0x80); + owner = getVarOrDirectByte(0x40); + + setOwnerOf(obj, owner); +} + +void ScummEngine_v5::o5_setState() { + int obj, state; + obj = getVarOrDirectWord(PARAM_1); + state = getVarOrDirectByte(PARAM_2); + putState(obj, state); + markObjectRectAsDirty(obj); + if (_bgNeedsRedraw) + clearDrawObjectQueue(); +} + +void ScummEngine_v5::o5_setVarRange() { + int a, b; + + getResultPos(); + a = fetchScriptByte(); + do { + if (_opcode & 0x80) + b = fetchScriptWordSigned(); + else + b = fetchScriptByte(); + + setResult(b); + _resultVarNumber++; + } while (--a); + + // Macintosh verison of indy3ega used different interface, so adjust values. + if (_gameId == GID_INDY3 && _platform == Common::kPlatformMacintosh) { + VAR(68) = 0; + VAR(69) = 0; + VAR(70) = 168; + VAR(71) = 0; + VAR(72) = 168; + VAR(73) = 0; + VAR(74) = 168; + VAR(75) = 0; + VAR(76) = 176; + VAR(77) = 176; + VAR(78) = 184; + VAR(79) = 184; + VAR(80) = 192; + VAR(81) = 192; + } +} + +void ScummEngine_v5::o5_startMusic() { + if (_platform == Common::kPlatformFMTowns && _version == 3) { + // In FM-TOWNS games this is some kind of Audio CD status query function. + // See also bug #762589 (thanks to Hibernatus for providing the information). + getResultPos(); + int b = getVarOrDirectByte(PARAM_1); + int result = 0; + switch (b) { + case 0: + result = _sound->pollCD() == 0; + break; + case 0xFC: + // TODO: Unpause (resume) audio track. We'll have to extend Sound and OSystem for this. + break; + case 0xFD: + // TODO: Pause audio track. We'll have to extend Sound and OSystem for this. + break; + case 0xFE: + result = _sound->getCurrentCDSound(); + break; + case 0xFF: + // TODO: Might return current CD volume in FM-TOWNS Loom. See also bug #805691. + break; + default: + // TODO: return track length in seconds. We'll have to extend Sound and OSystem for this. + // To check scummvm returns the right track length you + // can look at the global script #9 (0x888A in 49.LFL). + break; + } + debugC(DEBUG_GENERAL,"o5_startMusic(%d)", b); + setResult(result); + } else { + _sound->addSoundToQueue(getVarOrDirectByte(PARAM_1)); + } +} + +void ScummEngine_v5::o5_startSound() { + const byte *oldaddr = _scriptPointer - 1; + int sound = getVarOrDirectByte(PARAM_1); + + // WORKAROUND: In the scene where Largo is talking to Mad Marty, the + // Woodtick music often resumes before Largo's theme has finished. As + // far as I can tell, this is a script bug. + + if (_gameId == GID_MONKEY2 && sound == 110 && _sound->isSoundRunning(151)) { + debug(1, "Delaying Woodtick music until Largo's theme has finished\n"); + _scriptPointer = oldaddr; + o5_breakHere(); + return; + } + + if (VAR_MUSIC_TIMER != 0xFF) + VAR(VAR_MUSIC_TIMER) = 0; + _sound->addSoundToQueue(sound); +} + +void ScummEngine_v5::o5_stopMusic() { + _sound->stopAllSounds(); +} + +void ScummEngine_v5::o5_stopSound() { + _sound->stopSound(getVarOrDirectByte(PARAM_1)); +} + +void ScummEngine_v5::o5_isSoundRunning() { + int snd; + getResultPos(); + snd = getVarOrDirectByte(PARAM_1); + if (snd) + snd = _sound->isSoundRunning(snd); + setResult(snd); +} + +void ScummEngine_v5::o5_soundKludge() { + int items[16]; + + if (_features & GF_SMALL_HEADER) { // Is WaitForSentence in SCUMM V3 + if (_sentenceNum) { + if (_sentence[_sentenceNum - 1].freezeCount && !isScriptInUse(VAR(VAR_SENTENCE_SCRIPT))) + return; + } else if (!isScriptInUse(VAR(VAR_SENTENCE_SCRIPT))) + return; + + _scriptPointer--; + o5_breakHere(); + return; + } + + int num = getWordVararg(items); + _sound->soundKludge(items, num); +} + +void ScummEngine_v5::o5_startObject() { + int obj, script; + int data[16]; + + obj = getVarOrDirectWord(PARAM_1); + script = getVarOrDirectByte(PARAM_2); + + getWordVararg(data); + runObjectScript(obj, script, 0, 0, data); +} + +void ScummEngine_v5::o5_startScript() { + int op, script; + int data[16]; + + op = _opcode; + script = getVarOrDirectByte(PARAM_1); + + getWordVararg(data); + + // FIXME: Script 171 loads a complete room resource, instead of the actual script. + // Causing invalid opcode cases, see bug #1290485 + if (_gameId == GID_ZAK && (_platform == Common::kPlatformFMTowns) && script == 171) + return; + + if (!_copyProtection) { + // Method used by original games to skip copy protection scheme + if (_gameId == GID_LOOM && _version == 3 && _currentRoom == 69 && script == 201) + script = 205; + else if ((_gameId == GID_MONKEY_VGA || _gameId == GID_MONKEY_EGA) && script == 152) + return; + } + + runScript(script, (op & 0x20) != 0, (op & 0x40) != 0, data); +} + +void ScummEngine_v5::o5_stopObjectCode() { + stopObjectCode(); +} + +void ScummEngine_v5::o5_stopObjectScript() { + stopObjectScript(getVarOrDirectWord(PARAM_1)); +} + +void ScummEngine_v5::o5_stopScript() { + int script; + + script = getVarOrDirectByte(PARAM_1); + + if (!script) + stopObjectCode(); + else + stopScript(script); +} + +void ScummEngine_v5::o5_stringOps() { + int a, b, c, i; + byte *ptr; + + _opcode = fetchScriptByte(); + switch (_opcode & 0x1F) { + case 1: /* loadstring */ + loadPtrToResource(rtString, getVarOrDirectByte(PARAM_1), NULL); + break; + case 2: /* copystring */ + a = getVarOrDirectByte(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + res.nukeResource(rtString, a); + ptr = getResourceAddress(rtString, b); + if (ptr) + loadPtrToResource(rtString, a, ptr); + break; + case 3: /* set string char */ + a = getVarOrDirectByte(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + c = getVarOrDirectByte(PARAM_3); + ptr = getResourceAddress(rtString, a); + if (!(_gameId == GID_LOOM && _version == 4)) { /* FIXME - LOOM256 */ + if (ptr == NULL) + error("String %d does not exist", a); + ptr[b] = c; + } + + break; + + case 4: /* get string char */ + getResultPos(); + a = getVarOrDirectByte(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + ptr = getResourceAddress(rtString, a); + if (ptr == NULL) + error("String %d does not exist", a); + setResult(ptr[b]); + break; + + case 5: /* create empty string */ + a = getVarOrDirectByte(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + res.nukeResource(rtString, a); + if (b) { + ptr = res.createResource(rtString, a, b); + if (ptr) { + for (i = 0; i < b; i++) + ptr[i] = 0; + } + } + break; + } +} + +void ScummEngine_v5::o5_subtract() { + int a; + getResultPos(); + a = getVarOrDirectWord(PARAM_1); + setResult(readVar(_resultVarNumber) - a); +} + +void ScummEngine_v5::o5_verbOps() { + int verb, slot; + VerbSlot *vs; + int a, b; + byte *ptr; + + verb = getVarOrDirectByte(PARAM_1); + + slot = getVerbSlot(verb, 0); + checkRange(_numVerbs - 1, 0, slot, "Illegal new verb slot %d"); + + vs = &_verbs[slot]; + vs->verbid = verb; + + while ((_opcode = fetchScriptByte()) != 0xFF) { + switch (_opcode & 0x1F) { + case 1: // SO_VERB_IMAGE + a = getVarOrDirectWord(PARAM_1); + if (slot) { + setVerbObject(_roomResource, a, slot); + vs->type = kImageVerbType; + } + break; + case 2: // SO_VERB_NAME + loadPtrToResource(rtVerb, slot, NULL); + if (slot == 0) + res.nukeResource(rtVerb, slot); + vs->type = kTextVerbType; + vs->imgindex = 0; + break; + case 3: // SO_VERB_COLOR + vs->color = getVarOrDirectByte(PARAM_1); + break; + case 4: // SO_VERB_HICOLOR + vs->hicolor = getVarOrDirectByte(PARAM_1); + break; + case 5: // SO_VERB_AT + vs->curRect.left = getVarOrDirectWord(PARAM_1); + vs->curRect.top = getVarOrDirectWord(PARAM_2); + // Macintosh verison of indy3ega used different interface, so adjust values. + if ((_platform == Common::kPlatformMacintosh) && (_gameId == GID_INDY3)) { + switch (verb) { + case 1: + case 2: + case 9: + vs->curRect.left += 16; + break; + case 10: + case 11: + case 12: + vs->curRect.left += 36; + break; + case 4: + case 5: + case 8: + vs->curRect.left += 60; + break; + case 13: + case 32: + case 33: + case 34: + vs->curRect.left += 90; + break; + case 107: + vs->curRect.left -= 54; + vs->curRect.top += 16; + break; + case 108: + vs->curRect.left -= 54; + vs->curRect.top += 8; + break; + } + } else if (_gameId == GID_LOOM && _version == 4) { + // FIXME: hack loom notes into right spot + if ((verb >= 90) && (verb <= 97)) { // Notes + switch (verb) { + case 90: + case 91: + vs->curRect.top -= 7; + break; + case 92: + vs->curRect.top -= 6; + break; + case 93: + vs->curRect.top -= 4; + break; + case 94: + vs->curRect.top -= 3; + break; + case 95: + vs->curRect.top -= 1; + break; + case 97: + vs->curRect.top -= 5; + } + } + } + break; + case 6: // SO_VERB_ON + vs->curmode = 1; + break; + case 7: // SO_VERB_OFF + vs->curmode = 0; + break; + case 8: // SO_VERB_DELETE + killVerb(slot); + break; + case 9: // SO_VERB_NEW + slot = getVerbSlot(verb, 0); + if (slot == 0) { + for (slot = 1; slot < _numVerbs; slot++) { + if (_verbs[slot].verbid == 0) + break; + } + if (slot == _numVerbs) + error("Too many verbs"); + } + vs = &_verbs[slot]; + vs->verbid = verb; + vs->color = 2; + vs->hicolor = (_version == 3) ? 14 : 0; + vs->dimcolor = 8; + vs->type = kTextVerbType; + vs->charset_nr = _string[0]._default.charset; + vs->curmode = 0; + vs->saveid = 0; + vs->key = 0; + vs->center = 0; + vs->imgindex = 0; + break; + + case 16: // SO_VERB_DIMCOLOR + vs->dimcolor = getVarOrDirectByte(PARAM_1); + break; + case 17: // SO_VERB_DIM + vs->curmode = 2; + break; + case 18: // SO_VERB_KEY + vs->key = getVarOrDirectByte(PARAM_1); + break; + case 19: // SO_VERB_CENTER + vs->center = 1; + break; + case 20: // SO_VERB_NAME_STR + ptr = getResourceAddress(rtString, getVarOrDirectWord(PARAM_1)); + if (!ptr) + res.nukeResource(rtVerb, slot); + else { + loadPtrToResource(rtVerb, slot, ptr); + } + if (slot == 0) + res.nukeResource(rtVerb, slot); + vs->type = kTextVerbType; + vs->imgindex = 0; + break; + case 22: /* assign object */ + a = getVarOrDirectWord(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + if (slot && vs->imgindex != a) { + setVerbObject(b, a, slot); + vs->type = kImageVerbType; + vs->imgindex = a; + } + break; + case 23: /* set back color */ + vs->bkcolor = getVarOrDirectByte(PARAM_1); + break; + default: + error("o5_verbOps: unknown subopcode %d", _opcode & 0x1F); + } + } + + // Force redraw of the modified verb slot + drawVerb(slot, 0); + verbMouseOver(0); +} + +void ScummEngine_v5::o5_wait() { + const byte *oldaddr = _scriptPointer - 1; + + if ((_gameId == GID_INDY3) && !(_platform == Common::kPlatformMacintosh)) { + _opcode = 2; + } else + _opcode = fetchScriptByte(); + + switch (_opcode & 0x1F) { + case 1: // SO_WAIT_FOR_ACTOR + { + Actor *a = derefActorSafe(getVarOrDirectByte(PARAM_1), "o5_wait"); + if (a && a->isInCurrentRoom() && a->_moving) + break; + return; + } + case 2: // SO_WAIT_FOR_MESSAGE + if (VAR(VAR_HAVE_MSG)) + break; + return; + case 3: // SO_WAIT_FOR_CAMERA + if (camera._cur.x / 8 != camera._dest.x / 8) + break; + return; + case 4: // SO_WAIT_FOR_SENTENCE + if (_sentenceNum) { + if (_sentence[_sentenceNum - 1].freezeCount && !isScriptInUse(VAR(VAR_SENTENCE_SCRIPT))) + return; + break; + } + if (!isScriptInUse(VAR(VAR_SENTENCE_SCRIPT))) + return; + break; + default: + error("o5_wait: unknown subopcode %d", _opcode & 0x1F); + return; + } + + _scriptPointer = oldaddr; + o5_breakHere(); +} + +void ScummEngine_v5::o5_walkActorTo() { + int x, y; + Actor *a; + + a = derefActor(getVarOrDirectByte(PARAM_1), "o5_walkActorTo"); + x = getVarOrDirectWord(PARAM_2); + y = getVarOrDirectWord(PARAM_3); + a->startWalkActor(x, y, -1); +} + +void ScummEngine_v5::o5_walkActorToActor() { + int x, y; + Actor *a, *a2; + int nr = getVarOrDirectByte(PARAM_1); + int nr2 = getVarOrDirectByte(PARAM_2); + int dist = fetchScriptByte(); + + if (nr == 106 && _gameId == GID_INDY4) { + printf("Bypassing Indy4 bug\n"); + return; + } + + if (_gameId == GID_LOOM && _version == 4 && nr == 1 && nr2 == 0 && + dist == 255 && vm.slot[_currentScript].number == 98) { + // WORKAROUND bug #743615: LoomCD script 98 contains this: + // walkActorToActor(1,0,255) + // Once again this is either a script bug, or there is some hidden + // or unknown meaning to this odd walk request... + return; + } + + if (_gameId == GID_INDY4 && nr == 1 && nr2 == 106 && + dist == 255 && vm.slot[_currentScript].number == 210) { + // WORKAROUND bug: Work around an invalid actor bug when using the + // camel in Fate of Atlantis, the "wits" path. The room-65-210 script + // contains this: + // walkActorToActor(1,106,255) + // Once again this is either a script bug, or there is some hidden + // or unknown meaning to this odd walk request... + return; + } + + a = derefActor(nr, "o5_walkActorToActor"); + if (!a->isInCurrentRoom()) + return; + + a2 = derefActor(nr2, "o5_walkActorToActor(2)"); + if (!a2->isInCurrentRoom()) + return; + + if (_version <= 2) + dist *= 8; + else if (dist == 0xFF) { + dist = a->_scalex * a->_width / 0xFF; + dist += (a2->_scalex * a2->_width / 0xFF) / 2; + } + x = a2->_pos.x; + y = a2->_pos.y; + if (x < a->_pos.x) + x += dist; + else + x -= dist; + + if (_version <= 3) { + AdjustBoxResult abr = a->adjustXYToBeInBox(x, y); + x = abr.x; + y = abr.y; + } + a->startWalkActor(x, y, -1); +} + +void ScummEngine_v5::o5_walkActorToObject() { + int obj; + Actor *a; + + a = derefActor(getVarOrDirectByte(PARAM_1), "o5_walkActorToObject"); + obj = getVarOrDirectWord(PARAM_2); + if (whereIsObject(obj) != WIO_NOT_FOUND) { + int x, y, dir; + getObjectXYPos(obj, x, y, dir); + a->startWalkActor(x, y, dir); + } +} + +int ScummEngine_v5::getWordVararg(int *ptr) { + int i; + + for (i = 0; i < 16; i++) + ptr[i] = 0; + + i = 0; + while ((_opcode = fetchScriptByte()) != 0xFF) { + ptr[i++] = getVarOrDirectWord(PARAM_1); + } + return i; +} + +void ScummEngine_v5::decodeParseString() { + int textSlot; + + switch (_actorToPrintStrFor) { + case 252: + textSlot = 3; + break; + case 253: + textSlot = 2; + break; + case 254: + textSlot = 1; + break; + default: + textSlot = 0; + } + + _string[textSlot].loadDefault(); + + while ((_opcode = fetchScriptByte()) != 0xFF) { + switch (_opcode & 0xF) { + case 0: // SO_AT + _string[textSlot].xpos = getVarOrDirectWord(PARAM_1); + _string[textSlot].ypos = getVarOrDirectWord(PARAM_2); + _string[textSlot].overhead = false; + break; + case 1: // SO_COLOR + _string[textSlot].color = getVarOrDirectByte(PARAM_1); + break; + case 2: // SO_CLIPPED + _string[textSlot].right = getVarOrDirectWord(PARAM_1); + break; + case 3: // SO_ERASE + { + int w = getVarOrDirectWord(PARAM_1); + int h = getVarOrDirectWord(PARAM_2); + // restoreCharsetBg(xpos, xpos + w, ypos, ypos + h) + error("ScummEngine_v5::decodeParseString: Unhandled case 3: %d, %d", w, h); + } + break; + case 4: // SO_CENTER + _string[textSlot].center = true; + _string[textSlot].overhead = false; + break; + case 6: // SO_LEFT + if (_version == 3) { + _string[textSlot].height = getVarOrDirectWord(PARAM_1); + } else { + _string[textSlot].center = false; + _string[textSlot].overhead = false; + } + break; + case 7: // SO_OVERHEAD + _string[textSlot].overhead = true; + break; + case 8:{ // SO_SAY_VOICE + int offset = (uint16)getVarOrDirectWord(PARAM_1); + int delay = (uint16)getVarOrDirectWord(PARAM_2); + + if (_gameId == GID_LOOM && _version == 4) { + if (offset == 0 && delay == 0) { + VAR(VAR_MUSIC_TIMER) = 0; + _sound->stopCD(); + } else { + // Loom specified the offset from the start of the CD; + // thus we have to subtract the length of the first track + // (22500 frames) plus the 2 second = 150 frame leadin. + // I.e. in total 22650 frames. + offset = (int)(offset * 7.5 - 22500 - 2*75); + + // Slightly increase the delay (5 frames = 1/25 of a second). + // This noticably improves the experience in Loom CD. + delay = (int)(delay * 7.5 + 5); + + _sound->playCDTrack(1, 0, offset, delay); + } + } else { + error("ScummEngine_v5::decodeParseString: Unhandled case 8"); + } + } + break; + case 15: // SO_TEXTSTRING + // WORKAROUND: This happens when Chaos introduces + // herself to bishop Mandible. Of all the places to put + // a typo... + if (_gameId == GID_LOOM && strcmp((const char *) _scriptPointer, "I am Choas.") == 0) + printString(textSlot, (const byte *) "I am Chaos."); + else + printString(textSlot, _scriptPointer); + _scriptPointer += resStrLen(_scriptPointer) + 1; + + + // In SCUMM V1-V3, there were no 'default' values for the text slot + // values. Hence to achieve correct behaviour, we have to keep the + // 'default' values in sync with the active values. + // + // Note: This is needed for Indy3 (Grail Diary). It's also needed + // for Loom, or the lines Bobbin speaks during the intro are put + // at position 0,0. + // + // Note: We can't use saveDefault() here because we only want to + // save the position and color. In particular, we do not want to + // save the 'center' flag. See bug #933168. + if (_version <= 3) { + _string[textSlot]._default.xpos = _string[textSlot].xpos; + _string[textSlot]._default.ypos = _string[textSlot].ypos; + _string[textSlot]._default.height = _string[textSlot].height; + _string[textSlot]._default.color = _string[textSlot].color; + } + return; + default: + error("ScummEngine_v5::decodeParseString: Unhandled case %d", _opcode & 0xF); + } + } + + _string[textSlot].saveDefault(); +} + +void ScummEngine_v5::o5_oldRoomEffect() { + int a; + + _opcode = fetchScriptByte(); + if ((_opcode & 0x1F) == 3) { + a = getVarOrDirectWord(PARAM_1); + +#if 1 + if (_platform == Common::kPlatformFMTowns && _version == 3) { + // FIXME / TODO: OK the first thing to note is: at least in Zak256, + // maybe also in other games, this opcode does a bit more. I added + // some stubs here, but somebody with a full IDA or more knowledge + // about this will have to fill in the gaps. At least now we know + // that something is missing here :-) + + if (a == 4) { + printf("o5_oldRoomEffect ODDBALL: _opcode = 0x%x, a = 0x%x\n", _opcode, a); + // No idea what byte_2FCCF is, but it's a globale boolean flag. + // I only add it here as a temporary hack to make the pseudo code compile. + // Maybe it is just there as a reentry protection guard, given + // how it is used? It might also correspond to _screenEffectFlag. + int byte_2FCCF = 0; + + // For now, we force a redraw of the screen background. This + // way the Zak end credits seem to work mostly correct. + VirtScreen *vs = &virtscr[0]; + restoreBG(Common::Rect(0,vs->topline, vs->w, vs->topline + vs->h)); + vs->setDirtyRange(0, vs->h); + updateDirtyScreen(kMainVirtScreen); + + if (byte_2FCCF) { + // Here now "sub_1C44" is called, which sets byte_2FCCF to 0 then + // calls yet another sub (which also reads byte_2FCCF): + + byte_2FCCF = 0; + //call sub_0BB3 + + + // Now sub_085C is called. This is quite simply: it sets + // 0xF000 bytes. starting at 0x40000 to 0. No idea what that + // buffer is, maybe a screen buffer, though. Note that + // 0xF000 = 320*192. + // Maybe this is also the charset mask being cleaned? + + // call sub_085C + + + // And then sub_1C54 is called, which is almost identical to + // the above sub_1C44, only it sets byte_2FCCF to 1: + + byte_2FCCF = 1; + // call sub_0BB3 + + } else { + // Here only sub_085C is called (see comment above) + + // call sub_085C + } + return; + } +#endif + + } + if (a) { + _switchRoomEffect = (byte)(a & 0xFF); + _switchRoomEffect2 = (byte)(a >> 8); + } else { + fadeIn(_newEffect); + } + } +} + +void ScummEngine_v5::o5_pickupObjectOld() { + int obj = getVarOrDirectWord(PARAM_1); + + if (obj < 1) { + error("pickupObjectOld received invalid index %d (script %d)", obj, vm.slot[_currentScript].number); + } + + if (getObjectIndex(obj) == -1) + return; + + if (whereIsObject(obj) == WIO_INVENTORY) /* Don't take an */ + return; /* object twice */ + + // debug(0, "adding %d from %d to inventoryOld", obj, _currentRoom); + addObjectToInventory(obj, _roomResource); + markObjectRectAsDirty(obj); + putOwner(obj, VAR(VAR_EGO)); + putClass(obj, kObjectClassUntouchable, 1); + putState(obj, 1); + clearDrawObjectQueue(); + runInventoryScript(1); +} + +#undef PARAM_1 +#undef PARAM_2 +#undef PARAM_3 + +} // End of namespace Scumm |