scummvm/engines/scumm/script_v6.cpp
Torbjörn Andersson f74ba29753 SCUMM: Enable Day of the Tentacle easter egg
Instead of returning to the launcher, a game may now specify a list
of "chained" games and optional save slots. The first game is popped
from the list and started. Quitting still quits the entire ScummVM.
It seemed like the sensible thing to do.
2014-12-30 03:45:14 +01:00

3094 lines
69 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "common/config-manager.h"
#include "common/system.h"
#include "scumm/actor.h"
#include "scumm/charset.h"
#include "scumm/file.h"
#include "scumm/imuse/imuse.h"
#include "scumm/imuse_digi/dimuse.h"
#include "scumm/insane/insane.h"
#include "scumm/object.h"
#include "scumm/resource.h"
#include "scumm/scumm.h"
#include "scumm/scumm_v6.h"
#include "scumm/scumm_v7.h"
#include "scumm/smush/smush_player.h"
#include "scumm/sound.h"
#include "scumm/util.h"
#include "scumm/verbs.h"
namespace Scumm {
#define OPCODE(i, x) _opcodes[i]._OPCODE(ScummEngine_v6, x)
void ScummEngine_v6::setupOpcodes() {
/* 00 */
OPCODE(0x00, o6_pushByte);
OPCODE(0x01, o6_pushWord);
OPCODE(0x02, o6_pushByteVar);
OPCODE(0x03, o6_pushWordVar);
/* 04 */
OPCODE(0x06, o6_byteArrayRead);
OPCODE(0x07, o6_wordArrayRead);
/* 08 */
OPCODE(0x0a, o6_byteArrayIndexedRead);
OPCODE(0x0b, o6_wordArrayIndexedRead);
/* 0C */
OPCODE(0x0c, o6_dup);
OPCODE(0x0d, o6_not);
OPCODE(0x0e, o6_eq);
OPCODE(0x0f, o6_neq);
/* 10 */
OPCODE(0x10, o6_gt);
OPCODE(0x11, o6_lt);
OPCODE(0x12, o6_le);
OPCODE(0x13, o6_ge);
/* 14 */
OPCODE(0x14, o6_add);
OPCODE(0x15, o6_sub);
OPCODE(0x16, o6_mul);
OPCODE(0x17, o6_div);
/* 18 */
OPCODE(0x18, o6_land);
OPCODE(0x19, o6_lor);
OPCODE(0x1a, o6_pop);
/* 1C */
/* 20 */
/* 24 */
/* 28 */
/* 2C */
/* 30 */
/* 34 */
/* 38 */
/* 3C */
/* 40 */
OPCODE(0x42, o6_writeByteVar);
OPCODE(0x43, o6_writeWordVar);
/* 44 */
OPCODE(0x46, o6_byteArrayWrite);
OPCODE(0x47, o6_wordArrayWrite);
/* 48 */
OPCODE(0x4a, o6_byteArrayIndexedWrite);
OPCODE(0x4b, o6_wordArrayIndexedWrite);
/* 4C */
OPCODE(0x4e, o6_byteVarInc);
OPCODE(0x4f, o6_wordVarInc);
/* 50 */
OPCODE(0x52, o6_byteArrayInc);
OPCODE(0x53, o6_wordArrayInc);
/* 54 */
OPCODE(0x56, o6_byteVarDec);
OPCODE(0x57, o6_wordVarDec);
/* 58 */
OPCODE(0x5a, o6_byteArrayDec);
OPCODE(0x5b, o6_wordArrayDec);
/* 5C */
OPCODE(0x5c, o6_if);
OPCODE(0x5d, o6_ifNot);
OPCODE(0x5e, o6_startScript);
OPCODE(0x5f, o6_startScriptQuick);
/* 60 */
OPCODE(0x60, o6_startObject);
OPCODE(0x61, o6_drawObject);
OPCODE(0x62, o6_drawObjectAt);
OPCODE(0x63, o6_drawBlastObject);
/* 64 */
OPCODE(0x64, o6_setBlastObjectWindow);
OPCODE(0x65, o6_stopObjectCode);
OPCODE(0x66, o6_stopObjectCode);
OPCODE(0x67, o6_endCutscene);
/* 68 */
OPCODE(0x68, o6_cutscene);
OPCODE(0x69, o6_stopMusic);
OPCODE(0x6a, o6_freezeUnfreeze);
OPCODE(0x6b, o6_cursorCommand);
/* 6C */
OPCODE(0x6c, o6_breakHere);
OPCODE(0x6d, o6_ifClassOfIs);
OPCODE(0x6e, o6_setClass);
OPCODE(0x6f, o6_getState);
/* 70 */
OPCODE(0x70, o6_setState);
OPCODE(0x71, o6_setOwner);
OPCODE(0x72, o6_getOwner);
OPCODE(0x73, o6_jump);
/* 74 */
OPCODE(0x74, o6_startSound);
OPCODE(0x75, o6_stopSound);
OPCODE(0x76, o6_startMusic);
OPCODE(0x77, o6_stopObjectScript);
/* 78 */
OPCODE(0x78, o6_panCameraTo);
OPCODE(0x79, o6_actorFollowCamera);
OPCODE(0x7a, o6_setCameraAt);
OPCODE(0x7b, o6_loadRoom);
/* 7C */
OPCODE(0x7c, o6_stopScript);
OPCODE(0x7d, o6_walkActorToObj);
OPCODE(0x7e, o6_walkActorTo);
OPCODE(0x7f, o6_putActorAtXY);
/* 80 */
OPCODE(0x80, o6_putActorAtObject);
OPCODE(0x81, o6_faceActor);
OPCODE(0x82, o6_animateActor);
OPCODE(0x83, o6_doSentence);
/* 84 */
OPCODE(0x84, o6_pickupObject);
OPCODE(0x85, o6_loadRoomWithEgo);
OPCODE(0x87, o6_getRandomNumber);
/* 88 */
OPCODE(0x88, o6_getRandomNumberRange);
OPCODE(0x8a, o6_getActorMoving);
OPCODE(0x8b, o6_isScriptRunning);
/* 8C */
OPCODE(0x8c, o6_getActorRoom);
OPCODE(0x8d, o6_getObjectX);
OPCODE(0x8e, o6_getObjectY);
OPCODE(0x8f, o6_getObjectOldDir);
/* 90 */
OPCODE(0x90, o6_getActorWalkBox);
OPCODE(0x91, o6_getActorCostume);
OPCODE(0x92, o6_findInventory);
OPCODE(0x93, o6_getInventoryCount);
/* 94 */
OPCODE(0x94, o6_getVerbFromXY);
OPCODE(0x95, o6_beginOverride);
OPCODE(0x96, o6_endOverride);
OPCODE(0x97, o6_setObjectName);
/* 98 */
OPCODE(0x98, o6_isSoundRunning);
OPCODE(0x99, o6_setBoxFlags);
OPCODE(0x9a, o6_createBoxMatrix);
OPCODE(0x9b, o6_resourceRoutines);
/* 9C */
OPCODE(0x9c, o6_roomOps);
OPCODE(0x9d, o6_actorOps);
OPCODE(0x9e, o6_verbOps);
OPCODE(0x9f, o6_getActorFromXY);
/* A0 */
OPCODE(0xa0, o6_findObject);
OPCODE(0xa1, o6_pseudoRoom);
OPCODE(0xa2, o6_getActorElevation);
OPCODE(0xa3, o6_getVerbEntrypoint);
/* A4 */
OPCODE(0xa4, o6_arrayOps);
OPCODE(0xa5, o6_saveRestoreVerbs);
OPCODE(0xa6, o6_drawBox);
OPCODE(0xa7, o6_pop);
/* A8 */
OPCODE(0xa8, o6_getActorWidth);
OPCODE(0xa9, o6_wait);
OPCODE(0xaa, o6_getActorScaleX);
OPCODE(0xab, o6_getActorAnimCounter);
/* AC */
OPCODE(0xac, o6_soundKludge);
OPCODE(0xad, o6_isAnyOf);
OPCODE(0xae, o6_systemOps);
OPCODE(0xaf, o6_isActorInBox);
/* B0 */
OPCODE(0xb0, o6_delay);
OPCODE(0xb1, o6_delaySeconds);
OPCODE(0xb2, o6_delayMinutes);
OPCODE(0xb3, o6_stopSentence);
/* B4 */
OPCODE(0xb4, o6_printLine);
OPCODE(0xb5, o6_printText);
OPCODE(0xb6, o6_printDebug);
OPCODE(0xb7, o6_printSystem);
/* B8 */
OPCODE(0xb8, o6_printActor);
OPCODE(0xb9, o6_printEgo);
OPCODE(0xba, o6_talkActor);
OPCODE(0xbb, o6_talkEgo);
/* BC */
OPCODE(0xbc, o6_dimArray);
OPCODE(0xbd, o6_dummy);
OPCODE(0xbe, o6_startObjectQuick);
OPCODE(0xbf, o6_startScriptQuick2);
/* C0 */
OPCODE(0xc0, o6_dim2dimArray);
/* C4 */
OPCODE(0xc4, o6_abs);
OPCODE(0xc5, o6_distObjectObject);
OPCODE(0xc6, o6_distObjectPt);
OPCODE(0xc7, o6_distPtPt);
/* C8 */
OPCODE(0xc8, o6_kernelGetFunctions);
OPCODE(0xc9, o6_kernelSetFunctions);
OPCODE(0xca, o6_delayFrames);
OPCODE(0xcb, o6_pickOneOf);
/* CC */
OPCODE(0xcc, o6_pickOneOfDefault);
OPCODE(0xcd, o6_stampObject);
/* D0 */
OPCODE(0xd0, o6_getDateTime);
OPCODE(0xd1, o6_stopTalking);
OPCODE(0xd2, o6_getAnimateVariable);
/* D4 */
OPCODE(0xd4, o6_shuffle);
OPCODE(0xd5, o6_jumpToScript);
OPCODE(0xd6, o6_band);
OPCODE(0xd7, o6_bor);
/* D8 */
OPCODE(0xd8, o6_isRoomScriptRunning);
/* DC */
OPCODE(0xdd, o6_findAllObjects);
/* E0 */
OPCODE(0xe1, o6_getPixel);
OPCODE(0xe3, o6_pickVarRandom);
/* E4 */
OPCODE(0xe4, o6_setBoxSet);
/* E8 */
/* EC */
OPCODE(0xec, o6_getActorLayer);
OPCODE(0xed, o6_getObjectNewDir);
}
int ScummEngine_v6::popRoomAndObj(int *room) {
int obj;
if (_game.version >= 7) {
obj = pop();
*room = getObjectRoom(obj);
} else {
*room = pop();
obj = pop();
}
return obj;
}
byte *ScummEngine_v6::defineArray(int array, int type, int dim2, int dim1) {
int id;
int size;
ArrayHeader *ah;
assert(0 <= type && type <= 5);
if (_game.heversion >= 61) {
if (type == kBitArray || type == kNibbleArray)
type = kByteArray;
} else {
// NOTE: The following code turns all arrays except string arrays into
// integer arrays. There seems to be no reason for this, and it wastes
// space. However, we can't just remove this either, as that would
// break savegame compatibility. So do not touch this unless you are
// also adding code which updates old savegames, too. And of course
// readArray() and writeArray() would have to be updated, too...
if (type != kStringArray)
type = kIntArray;
}
nukeArray(array);
id = findFreeArrayId();
if (_game.version == 8) {
if (array & 0x40000000) {
}
if (array & 0x80000000) {
error("Can't define bit variable as array pointer");
}
size = (type == kIntArray) ? 4 : 1;
} else {
if (array & 0x4000) {
}
if (array & 0x8000) {
error("Can't define bit variable as array pointer");
}
size = (type == kIntArray) ? 2 : 1;
}
writeVar(array, id);
size *= dim2 + 1;
size *= dim1 + 1;
ah = (ArrayHeader *)_res->createResource(rtString, id, size + sizeof(ArrayHeader));
ah->type = TO_LE_16(type);
ah->dim1 = TO_LE_16(dim1 + 1);
ah->dim2 = TO_LE_16(dim2 + 1);
return ah->data;
}
void ScummEngine_v6::nukeArray(int a) {
int data;
data = readVar(a);
if (_game.heversion >= 80)
data &= ~0x33539000;
if (data)
_res->nukeResource(rtString, data);
if (_game.heversion >= 60)
_arraySlot[data] = 0;
writeVar(a, 0);
}
int ScummEngine_v6::findFreeArrayId() {
const ResourceManager::ResTypeData &rtd = _res->_types[rtString];
int i;
for (i = 1; i < _numArray; i++) {
if (!rtd[i]._address)
return i;
}
error("Out of array pointers, %d max", _numArray);
return -1;
}
#define SWAP16(x) x = SWAP_BYTES_16(x)
ScummEngine_v6::ArrayHeader *ScummEngine_v6::getArray(int array) {
ArrayHeader *ah = (ArrayHeader *)getResourceAddress(rtString, readVar(array));
if (!ah)
return 0;
if (_game.heversion == 0) {
// Workaround for a long standing bug where we saved array headers in native
// endianness, instead of a fixed endianness. We now always store the
// dimensions in little endian byte order. But to stay compatible with older
// savegames, we try to detect savegames which were created on a big endian
// system and convert them to the proper little endian format on the fly.
if ((FROM_LE_16(ah->dim1) & 0xF000) || (FROM_LE_16(ah->dim2) & 0xF000) || (FROM_LE_16(ah->type) & 0xFF00)) {
SWAP16(ah->dim1);
SWAP16(ah->dim2);
SWAP16(ah->type);
}
}
return ah;
}
int ScummEngine_v6::readArray(int array, int idx, int base) {
ArrayHeader *ah = getArray(array);
if (!ah)
error("readArray: invalid array %d (%d)", array, readVar(array));
// WORKAROUND bug #645711. This is clearly a script bug, as this script
// excerpt shows nicely:
// ...
// [03A7] (5D) if (isAnyOf(array-447[localvar13][localvar14],[0,4])) {
// [03BD] (5D) if ((localvar13 != -1) && (localvar14 != -1)) {
// [03CF] (B6) printDebug.begin()
// ...
// So it checks for invalid array indices only *after* using them to access
// the array. Ouch.
if (_game.id == GID_FT && array == 447 && _currentRoom == 95 && vm.slot[_currentScript].number == 2010 && idx == -1 && base == -1) {
return 0;
}
const int offset = base + idx * FROM_LE_16(ah->dim1);
if (offset < 0 || offset >= FROM_LE_16(ah->dim1) * FROM_LE_16(ah->dim2)) {
error("readArray: array %d out of bounds: [%d,%d] exceeds [%d,%d]",
array, base, idx, FROM_LE_16(ah->dim1), FROM_LE_16(ah->dim2));
}
int val;
if (FROM_LE_16(ah->type) != kIntArray) {
val = ah->data[offset];
} else if (_game.version == 8) {
val = (int32)READ_LE_UINT32(ah->data + offset * 4);
} else {
val = (int16)READ_LE_UINT16(ah->data + offset * 2);
}
return val;
}
void ScummEngine_v6::writeArray(int array, int idx, int base, int value) {
ArrayHeader *ah = getArray(array);
if (!ah)
return;
const int offset = base + idx * FROM_LE_16(ah->dim1);
if (offset < 0 || offset >= FROM_LE_16(ah->dim1) * FROM_LE_16(ah->dim2)) {
error("writeArray: array %d out of bounds: [%d,%d] exceeds [%d,%d]",
array, base, idx, FROM_LE_16(ah->dim1), FROM_LE_16(ah->dim2));
}
if (FROM_LE_16(ah->type) != kIntArray) {
ah->data[offset] = value;
} else if (_game.version == 8) {
WRITE_LE_UINT32(ah->data + offset * 4, value);
} else {
WRITE_LE_UINT16(ah->data + offset * 2, value);
}
}
void ScummEngine_v6::readArrayFromIndexFile() {
int num;
int a, b, c;
while ((num = _fileHandle->readUint16LE()) != 0) {
a = _fileHandle->readUint16LE();
b = _fileHandle->readUint16LE();
c = _fileHandle->readUint16LE();
if (c == kBitArray)
defineArray(num, kBitArray, a, b);
else
defineArray(num, kIntArray, a, b);
}
}
int ScummEngine_v6::getStackList(int *args, uint maxnum) {
uint num, i;
for (i = 0; i < maxnum; i++)
args[i] = 0;
num = pop();
if (num > maxnum)
error("Too many items %d in stack list, max %d", num, maxnum);
i = num;
while (i--) {
args[i] = pop();
}
return num;
}
void ScummEngine_v6::o6_pushByte() {
push(fetchScriptByte());
}
void ScummEngine_v6::o6_pushWord() {
push(fetchScriptWordSigned());
}
void ScummEngine_v6::o6_pushByteVar() {
push(readVar(fetchScriptByte()));
}
void ScummEngine_v6::o6_pushWordVar() {
push(readVar(fetchScriptWord()));
}
void ScummEngine_v6::o6_byteArrayRead() {
int base = pop();
push(readArray(fetchScriptByte(), 0, base));
}
void ScummEngine_v6::o6_wordArrayRead() {
int base = pop();
push(readArray(fetchScriptWord(), 0, base));
}
void ScummEngine_v6::o6_byteArrayIndexedRead() {
int base = pop();
int idx = pop();
push(readArray(fetchScriptByte(), idx, base));
}
void ScummEngine_v6::o6_wordArrayIndexedRead() {
int base = pop();
int idx = pop();
push(readArray(fetchScriptWord(), idx, base));
}
void ScummEngine_v6::o6_dup() {
int a = pop();
push(a);
push(a);
}
void ScummEngine_v6::o6_not() {
push(pop() == 0);
}
void ScummEngine_v6::o6_eq() {
int a = pop();
int b = pop();
// WORKAROUND: Forces the game version string set via script 1 to be used in both Macintosh and Windows versions,
// when checking for save game compatibility. Allows saved games to be shared between Macintosh and Windows versions.
// The scripts check VAR_PLATFORM (b) against the value (2) of the Macintosh platform (a).
if (_game.id == GID_BASEBALL2001 && (vm.slot[_currentScript].number == 291 || vm.slot[_currentScript].number == 292) &&
a == 2 && b == 1) {
push(1);
} else {
push(a == b);
}
}
void ScummEngine_v6::o6_neq() {
push(pop() != pop());
}
void ScummEngine_v6::o6_gt() {
int a = pop();
push(pop() > a);
}
void ScummEngine_v6::o6_lt() {
int a = pop();
push(pop() < a);
}
void ScummEngine_v6::o6_le() {
int a = pop();
push(pop() <= a);
}
void ScummEngine_v6::o6_ge() {
int a = pop();
push(pop() >= a);
}
void ScummEngine_v6::o6_add() {
int a = pop();
push(pop() + a);
}
void ScummEngine_v6::o6_sub() {
int a = pop();
push(pop() - a);
}
void ScummEngine_v6::o6_mul() {
int a = pop();
push(pop() * a);
}
void ScummEngine_v6::o6_div() {
int a = pop();
if (a == 0)
error("division by zero");
push(pop() / a);
}
void ScummEngine_v6::o6_land() {
int a = pop();
push(pop() && a);
}
void ScummEngine_v6::o6_lor() {
int a = pop();
push(pop() || a);
}
void ScummEngine_v6::o6_bor() {
int a = pop();
push(pop() | a);
}
void ScummEngine_v6::o6_band() {
int a = pop();
push(pop() & a);
}
void ScummEngine_v6::o6_pop() {
pop();
}
void ScummEngine_v6::o6_writeByteVar() {
writeVar(fetchScriptByte(), pop());
}
void ScummEngine_v6::o6_writeWordVar() {
writeVar(fetchScriptWord(), pop());
}
void ScummEngine_v6::o6_byteArrayWrite() {
int a = pop();
writeArray(fetchScriptByte(), 0, pop(), a);
}
void ScummEngine_v6::o6_wordArrayWrite() {
int a = pop();
writeArray(fetchScriptWord(), 0, pop(), a);
}
void ScummEngine_v6::o6_byteArrayIndexedWrite() {
int val = pop();
int base = pop();
writeArray(fetchScriptByte(), pop(), base, val);
}
void ScummEngine_v6::o6_wordArrayIndexedWrite() {
int val = pop();
int base = pop();
writeArray(fetchScriptWord(), pop(), base, val);
}
void ScummEngine_v6::o6_byteVarInc() {
int var = fetchScriptByte();
writeVar(var, readVar(var) + 1);
}
void ScummEngine_v6::o6_wordVarInc() {
int var = fetchScriptWord();
writeVar(var, readVar(var) + 1);
}
void ScummEngine_v6::o6_byteArrayInc() {
int var = fetchScriptByte();
int base = pop();
writeArray(var, 0, base, readArray(var, 0, base) + 1);
}
void ScummEngine_v6::o6_wordArrayInc() {
int var = fetchScriptWord();
int base = pop();
writeArray(var, 0, base, readArray(var, 0, base) + 1);
}
void ScummEngine_v6::o6_byteVarDec() {
int var = fetchScriptByte();
writeVar(var, readVar(var) - 1);
}
void ScummEngine_v6::o6_wordVarDec() {
int var = fetchScriptWord();
writeVar(var, readVar(var) - 1);
}
void ScummEngine_v6::o6_byteArrayDec() {
int var = fetchScriptByte();
int base = pop();
writeArray(var, 0, base, readArray(var, 0, base) - 1);
}
void ScummEngine_v6::o6_wordArrayDec() {
int var = fetchScriptWord();
int base = pop();
writeArray(var, 0, base, readArray(var, 0, base) - 1);
}
void ScummEngine_v6::o6_if() {
if (pop())
o6_jump();
else
fetchScriptWord();
}
void ScummEngine_v6::o6_ifNot() {
if (!pop())
o6_jump();
else
fetchScriptWord();
}
void ScummEngine_v6::o6_jump() {
int offset = fetchScriptWordSigned();
// WORKAROUND bug #2826144: Talking to the guard at the bigfoot party, after
// he's let you inside, will cause the game to hang, if you end the conversation.
// This is a script bug, due to a missing jump in one segment of the script.
if (_game.id == GID_SAMNMAX && vm.slot[_currentScript].number == 101 && readVar(0x8000 + 97) == 1 && offset == 1) {
offset = -18;
}
_scriptPointer += offset;
}
void ScummEngine_v6::o6_startScript() {
int args[25];
int script, flags;
getStackList(args, ARRAYSIZE(args));
script = pop();
flags = pop();
// WORKAROUND bug #556558: At Dino Bungee National Memorial, the buttons for
// the Wally and Rex dinosaurs will always restart their speech, instead of
// stopping and starting their speech. This was a script bug in the original
// game.
if (_game.id == GID_SAMNMAX && _roomResource == 59 &&
vm.slot[_currentScript].number == 201 && script == 48) {
o6_breakHere();
}
// WORKAROUND bug #903223: In Puerto Pollo, if you have Guybrush examine
// the church clock, he'll read out the current time. Nice touch, only that
// it sounds crap in the german version (and maybe others, too). It seems
// the original engine of the german version played just a simple fixed
// text in this spot, for the above reason. Since the data files are
// unchanged, it must have been an engine hack job. No idea how they did
// it exactly, but this here is how we do it :-)
if (_game.id == GID_CMI && script == 204 &&
_currentRoom == 15 && vm.slot[_currentScript].number == 421 &&
_language == Common::DE_DEU) {
_actorToPrintStrFor = 1;
_string[0].loadDefault();
actorTalk((const byte *)"/VDSO325/Whoa! Look at the time. Gotta scoot.");
return;
}
// WORKAROUND bug #1878514: When turning pages in the recipe book
// (found on Blood Island), there is a brief moment where it displays
// text from two different pages at the same time.
//
// The content of the books is drawing (in an endless loop) by local
// script 2007. Changing the page is handled by script 2006, which
// first stops script 2007; then switches the page; then restarts
// script 2007. But it fails to clear the blast texts beforehand.
// Hence, the next time blast text is drawn, both the old one (from
// the old instance of script 2007) and the new text (from the new
// instance) are briefly drawn simultaneously.
//
// This looks like a script bug to me (a missing call to clearTextQueue).
// But this could also hint at a subtle bug in ScummVM; we should check
// whether this bug occurs with the original engine or not.
if (_game.id == GID_CMI && script == 2007 &&
_currentRoom == 62 && vm.slot[_currentScript].number == 2006) {
removeBlastTexts();
}
runScript(script, (flags & 1) != 0, (flags & 2) != 0, args);
}
void ScummEngine_v6::o6_jumpToScript() {
int args[25];
int script, flags;
getStackList(args, ARRAYSIZE(args));
script = pop();
flags = pop();
stopObjectCode();
runScript(script, (flags & 1) != 0, (flags & 2) != 0, args);
}
void ScummEngine_v6::o6_startScriptQuick() {
int args[25];
int script;
getStackList(args, ARRAYSIZE(args));
script = pop();
runScript(script, 0, 0, args);
}
void ScummEngine_v6::o6_startScriptQuick2() {
int args[25];
int script;
getStackList(args, ARRAYSIZE(args));
script = pop();
runScript(script, 0, 1, args);
}
void ScummEngine_v6::o6_startObject() {
int args[25];
int script, entryp;
int flags;
getStackList(args, ARRAYSIZE(args));
entryp = pop();
script = pop();
flags = pop();
runObjectScript(script, entryp, (flags & 1) != 0, (flags & 2) != 0, args);
}
void ScummEngine_v6::o6_startObjectQuick() {
int args[25];
int script, entryp;
getStackList(args, ARRAYSIZE(args));
entryp = pop();
script = pop();
runObjectScript(script, entryp, 0, 1, args);
}
void ScummEngine_v6::o6_drawObject() {
int state = pop();
int obj = pop();
// This is based on disassembly
if (state == 0)
state = 1;
setObjectState(obj, state, -1, -1);
}
void ScummEngine_v6::o6_drawObjectAt() {
int y = pop();
int x = pop();
int obj = pop();
// WORKAROUND bug #1846746 : Adjust x and y position of
// objects in credits sequence, to match other ports
if (_game.id == GID_PUTTMOON && _game.platform == Common::kPlatform3DO &&
_roomResource == 38 && vm.slot[_currentScript].number == 206) {
x = y = -1;
}
setObjectState(obj, 1, x, y);
}
void ScummEngine_v6::o6_stopObjectCode() {
stopObjectCode();
}
void ScummEngine_v6::o6_endCutscene() {
endCutscene();
}
void ScummEngine_v6::o6_cutscene() {
int args[25];
getStackList(args, ARRAYSIZE(args));
beginCutscene(args);
}
void ScummEngine_v6::o6_stopMusic() {
_sound->stopAllSounds();
}
void ScummEngine_v6::o6_freezeUnfreeze() {
int a = pop();
if (a)
freezeScripts(a);
else
unfreezeScripts();
}
void ScummEngine_v6::o6_cursorCommand() {
int a, i;
int args[16];
byte subOp = fetchScriptByte();
switch (subOp) {
case 0x90: // SO_CURSOR_ON Turn cursor on
_cursor.state = 1;
verbMouseOver(0);
break;
case 0x91: // SO_CURSOR_OFF Turn cursor off
_cursor.state = 0;
verbMouseOver(0);
break;
case 0x92: // SO_USERPUT_ON
_userPut = 1;
break;
case 0x93: // SO_USERPUT_OFF
_userPut = 0;
break;
case 0x94: // SO_CURSOR_SOFT_ON Turn soft cursor on
_cursor.state++;
if (_cursor.state > 1)
error("Cursor state greater than 1 in script");
verbMouseOver(0);
break;
case 0x95: // SO_CURSOR_SOFT_OFF Turn soft cursor off
_cursor.state--;
verbMouseOver(0);
break;
case 0x96: // SO_USERPUT_SOFT_ON
_userPut++;
break;
case 0x97: // SO_USERPUT_SOFT_OFF
_userPut--;
break;
case 0x99: // SO_CURSOR_IMAGE Set cursor image
{
int room, obj;
if (_game.heversion >= 70) {
obj = pop();
room = getObjectRoom(obj);
} else {
obj = popRoomAndObj(&room);
}
setCursorFromImg(obj, room, 1);
break;
}
case 0x9A: // SO_CURSOR_HOTSPOT Set cursor hotspot
a = pop();
setCursorHotspot(pop(), a);
updateCursor();
break;
case 0x9C: // SO_CHARSET_SET
initCharset(pop());
break;
case 0x9D: // SO_CHARSET_COLOR
getStackList(args, ARRAYSIZE(args));
for (i = 0; i < 16; i++)
_charsetColorMap[i] = _charsetData[_string[1]._default.charset][i] = (unsigned char)args[i];
break;
case 0xD6: // SO_CURSOR_TRANSPARENT Set cursor transparent color
setCursorTransparency(pop());
break;
default:
error("o6_cursorCommand: default case %x", subOp);
}
VAR(VAR_CURSORSTATE) = _cursor.state;
VAR(VAR_USERPUT) = _userPut;
}
void ScummEngine_v6::o6_breakHere() {
updateScriptPtr();
_currentScript = 0xFF;
}
void ScummEngine_v6::o6_ifClassOfIs() {
int args[16];
int num, obj, cls;
bool b;
int cond = 1;
num = getStackList(args, ARRAYSIZE(args));
obj = pop();
if (_game.heversion >= 80 && num == 0) {
push(_classData[obj]);
return;
}
while (--num >= 0) {
cls = args[num];
b = getClass(obj, cls);
if ((cls & 0x80 && !b) || (!(cls & 0x80) && b))
cond = 0;
}
push(cond);
}
void ScummEngine_v6::o6_setClass() {
int args[16];
int num, obj, cls;
num = getStackList(args, ARRAYSIZE(args));
obj = pop();
while (--num >= 0) {
cls = args[num];
if (cls == 0)
_classData[num] = 0;
else if (cls & 0x80)
putClass(obj, cls, 1);
else
putClass(obj, cls, 0);
}
}
void ScummEngine_v6::o6_getState() {
push(getState(pop()));
}
void ScummEngine_v6::o6_setState() {
int state = pop();
int obj = pop();
putState(obj, state);
markObjectRectAsDirty(obj);
if (_bgNeedsRedraw)
clearDrawObjectQueue();
}
void ScummEngine_v6::o6_setOwner() {
int owner = pop();
int obj = pop();
setOwnerOf(obj, owner);
}
void ScummEngine_v6::o6_getOwner() {
push(getOwner(pop()));
}
void ScummEngine_v6::o6_startSound() {
int offset = 0;
// In Fatty Bear's Birthday Surprise the piano uses offsets 1 - 23 to
// indicate which note to play, but only when using the standard piano
// sound. See also o60_soundOps()
if (_game.heversion >= 60 && (_game.id != GID_PUTTDEMO))
offset = pop();
#ifdef ENABLE_SCUMM_7_8
if (_game.version >= 7)
_imuseDigital->startSfx(pop(), 64);
else
#endif
_sound->addSoundToQueue(pop(), offset);
}
void ScummEngine_v6::o6_stopSound() {
_sound->stopSound(pop());
}
void ScummEngine_v6::o6_startMusic() {
if (_game.version >= 7)
error("o6_startMusic() It shouldn't be called here for imuse digital");
_sound->addSoundToQueue(pop());
}
void ScummEngine_v6::o6_stopObjectScript() {
stopObjectScript(pop());
}
void ScummEngine_v6::o6_panCameraTo() {
if (_game.version >= 7) {
int y = pop();
int x = pop();
panCameraTo(x, y);
} else {
panCameraTo(pop(), 0);
}
}
void ScummEngine_v6::o6_actorFollowCamera() {
if (_game.version >= 7)
setCameraFollows(derefActor(pop(), "actorFollowCamera"));
else
actorFollowCamera(pop());
}
void ScummEngine_v6::o6_setCameraAt() {
if (_game.version >= 7) {
int x, y;
camera._follows = 0;
VAR(VAR_CAMERA_FOLLOWED_ACTOR) = 0;
y = pop();
x = pop();
setCameraAt(x, y);
} else {
setCameraAtEx(pop());
}
}
void ScummEngine_v6::o6_loadRoom() {
int room = pop();
startScene(room, 0, 0);
if (_game.heversion >= 61) {
setCameraAt(camera._cur.x, 0);
}
_fullRedraw = true;
}
void ScummEngine_v6::o6_stopScript() {
int script = pop();
if (script == 0)
stopObjectCode();
else
stopScript(script);
}
void ScummEngine_v6::o6_walkActorToObj() {
int act, obj, dist;
Actor *a, *a2;
int x, y;
dist = pop();
obj = pop();
act = pop();
a = derefActor(act, "o6_walkActorToObj");
if (obj >= _numActors) {
int wio = whereIsObject(obj);
if (wio != WIO_FLOBJECT && wio != WIO_ROOM)
return;
int dir;
getObjectXYPos(obj, x, y, dir);
a->startWalkActor(x, y, dir);
} else {
a2 = derefActorSafe(obj, "o6_walkActorToObj(2)");
if (_game.id == GID_SAMNMAX && a2 == 0) {
// WORKAROUND bug #742676 SAM: Fish Farm. Note quite sure why it
// happens, whether it's normal or due to a bug in the ScummVM code.
debug(0, "o6_walkActorToObj: invalid actor %d", obj);
return;
}
if (!a->isInCurrentRoom() || !a2->isInCurrentRoom())
return;
if (dist == 0) {
dist = a2->_scalex * a2->_width / 0xFF;
dist += dist / 2;
}
x = a2->getPos().x;
y = a2->getPos().y;
if (x < a->getPos().x)
x += dist;
else
x -= dist;
a->startWalkActor(x, y, -1);
}
}
void ScummEngine_v6::o6_walkActorTo() {
int x, y;
y = pop();
x = pop();
Actor *a = derefActor(pop(), "o6_walkActorTo");
a->startWalkActor(x, y, -1);
}
void ScummEngine_v6::o6_putActorAtXY() {
int room, x, y, act;
Actor *a;
room = pop();
y = pop();
x = pop();
act = pop();
a = derefActor(act, "o6_putActorAtXY");
if (room == 0xFF || room == 0x7FFFFFFF) {
room = a->_room;
} else {
if (a->_visible && _currentRoom != room && getTalkingActor() == a->_number) {
stopTalk();
}
if (room != 0)
a->_room = room;
}
a->putActor(x, y, room);
}
void ScummEngine_v6::o6_putActorAtObject() {
int room, obj, x, y;
Actor *a;
obj = popRoomAndObj(&room);
a = derefActor(pop(), "o6_putActorAtObject");
if (whereIsObject(obj) != WIO_NOT_FOUND) {
getObjectXYPos(obj, x, y);
} else {
x = 160;
y = 120;
}
if (room == 0xFF)
room = a->_room;
a->putActor(x, y, room);
}
void ScummEngine_v6::o6_faceActor() {
int obj = pop();
Actor *a = derefActor(pop(), "o6_faceActor");
a->faceToObject(obj);
}
void ScummEngine_v6::o6_animateActor() {
int anim = pop();
int act = pop();
if (_game.id == GID_TENTACLE && _roomResource == 57 &&
vm.slot[_currentScript].number == 19 && act == 593) {
// WORKAROUND bug #743363: This very odd case (animateActor(593,250))
// occurs in DOTT, in the cutscene after George cuts down the "cherry
// tree" and the tree Laverne is trapped in vanishes...
// Not sure if this means animateActor somehow also must work for objects
// (593 is the time machine in room 57), or if this is simply a script bug.
act = 6;
}
if (_game.id == GID_SAMNMAX && _roomResource == 35 &&
vm.slot[_currentScript].number == 202 && act == 4 && anim == 14) {
// WORKAROUND bug #1223621 (Animation glitch at World of Fish).
// Before starting animation 14 of the fisherman, make sure he isn't
// talking anymore. This appears to be a bug in the original game as well.
if (getTalkingActor() == 4) {
stopTalk();
}
}
Actor *a = derefActor(act, "o6_animateActor");
a->animateActor(anim);
}
void ScummEngine_v6::o6_doSentence() {
int verb, objectA, objectB;
objectB = pop();
if (_game.version < 8)
pop(); // dummy pop (in Sam&Max, seems to be always 0 or 130)
objectA = pop();
verb = pop();
doSentence(verb, objectA, objectB);
}
void ScummEngine_v6::o6_pickupObject() {
int obj, room;
int i;
obj = popRoomAndObj(&room);
if (room == 0)
room = _roomResource;
for (i = 0; i < _numInventory; i++) {
if (_inventory[i] == (uint16)obj) {
putOwner(obj, VAR(VAR_EGO));
runInventoryScript(obj);
return;
}
}
addObjectToInventory(obj, room);
putOwner(obj, VAR(VAR_EGO));
putClass(obj, kObjectClassUntouchable, 1);
putState(obj, 1);
markObjectRectAsDirty(obj);
clearDrawObjectQueue();
runInventoryScript(obj);
}
void ScummEngine_v6::o6_loadRoomWithEgo() {
Actor *a;
int obj, room, x, y;
y = pop();
x = pop();
obj = popRoomAndObj(&room);
a = derefActor(VAR(VAR_EGO), "o6_loadRoomWithEgo");
a->putActor(0, 0, room);
_egoPositioned = false;
VAR(VAR_WALKTO_OBJ) = obj;
startScene(a->_room, a, obj);
VAR(VAR_WALKTO_OBJ) = 0;
if (_game.version == 6) {
camera._cur.x = camera._dest.x = a->getPos().x;
setCameraFollows(a, (_game.heversion >= 60));
}
_fullRedraw = true;
if (x != -1 && x != 0x7FFFFFFF) {
a->startWalkActor(x, y, -1);
}
}
void ScummEngine_v6::o6_getRandomNumber() {
int rnd;
rnd = _rnd.getRandomNumber(ABS(pop()));
if (VAR_RANDOM_NR != 0xFF)
VAR(VAR_RANDOM_NR) = rnd;
push(rnd);
}
void ScummEngine_v6::o6_getRandomNumberRange() {
int max = pop();
int min = pop();
int rnd = _rnd.getRandomNumberRng(min, max);
if (VAR_RANDOM_NR != 0xFF)
VAR(VAR_RANDOM_NR) = rnd;
push(rnd);
}
void ScummEngine_v6::o6_isScriptRunning() {
push(isScriptRunning(pop()));
}
void ScummEngine_v6::o6_isRoomScriptRunning() {
push(isRoomScriptRunning(pop()));
}
void ScummEngine_v6::o6_getActorMoving() {
Actor *a = derefActor(pop(), "o6_getActorMoving");
push(a->_moving);
}
void ScummEngine_v6::o6_getActorRoom() {
int act = pop();
if (act == 0) {
// This case occurs at the very least in COMI. That's because in COMI's script 28,
// there is a check which looks as follows:
// if (((VAR_TALK_ACTOR != 0) && (VAR_HAVE_MSG == 1)) &&
// (getActorRoom(VAR_TALK_ACTOR) == VAR_ROOM))
// Due to the way this is represented in bytecode, the engine cannot
// short circuit. Hence, even though this would be perfectly fine code
// in C/C++, here it can (and does) lead to getActorRoom(0) being
// invoked. We silently ignore this.
push(0);
return;
}
if (act == 255) {
// This case also occurs in COMI...
push(0);
return;
}
Actor *a = derefActor(act, "o6_getActorRoom");
push(a->_room);
}
void ScummEngine_v6::o6_getActorWalkBox() {
Actor *a = derefActor(pop(), "o6_getActorWalkBox");
push(a->_ignoreBoxes ? 0 : a->_walkbox);
}
void ScummEngine_v6::o6_getActorCostume() {
Actor *a = derefActor(pop(), "o6_getActorCostume");
push(a->_costume);
}
void ScummEngine_v6::o6_getActorElevation() {
Actor *a = derefActor(pop(), "o6_getActorElevation");
push(a->getElevation());
}
void ScummEngine_v6::o6_getActorWidth() {
Actor *a = derefActor(pop(), "o6_getActorWidth");
push(a->_width);
}
void ScummEngine_v6::o6_getActorScaleX() {
Actor *a = derefActor(pop(), "o6_getActorScaleX");
push(a->_scalex);
}
void ScummEngine_v6::o6_getActorAnimCounter() {
Actor *a = derefActor(pop(), "o6_getActorAnimCounter");
push(a->_cost.animCounter);
}
void ScummEngine_v6::o6_getAnimateVariable() {
int var = pop();
Actor *a = derefActor(pop(), "o6_getAnimateVariable");
push(a->getAnimVar(var));
}
void ScummEngine_v6::o6_isActorInBox() {
int box = pop();
Actor *a = derefActor(pop(), "o6_isActorInBox");
push(checkXYInBoxBounds(box, a->getPos().x, a->getPos().y));
}
void ScummEngine_v6::o6_getActorLayer() {
Actor *a = derefActor(pop(), "getActorLayer");
push(a->_layer);
}
void ScummEngine_v6::o6_getObjectX() {
push(getObjX(pop()));
}
void ScummEngine_v6::o6_getObjectY() {
push(getObjY(pop()));
}
void ScummEngine_v6::o6_getObjectOldDir() {
push(getObjOldDir(pop()));
}
void ScummEngine_v6::o6_getObjectNewDir() {
push(getObjNewDir(pop()));
}
void ScummEngine_v6::o6_findInventory() {
int idx = pop();
int owner = pop();
push(findInventory(owner, idx));
}
void ScummEngine_v6::o6_getInventoryCount() {
push(getInventoryCount(pop()));
}
void ScummEngine_v6::o6_getVerbFromXY() {
int y = pop();
int x = pop();
int over = findVerbAtPos(x, y);
if (over)
over = _verbs[over].verbid;
push(over);
}
void ScummEngine_v6::o6_beginOverride() {
beginOverride();
_skipVideo = 0;
}
void ScummEngine_v6::o6_endOverride() {
endOverride();
}
void ScummEngine_v6::o6_setObjectName() {
int obj = pop();
setObjectName(obj);
}
void ScummEngine_v6::o6_isSoundRunning() {
int snd = pop();
if (snd)
snd = _sound->isSoundRunning(snd);
push(snd);
}
void ScummEngine_v6::o6_setBoxFlags() {
int table[65];
int num, value;
value = pop();
num = getStackList(table, ARRAYSIZE(table));
while (--num >= 0) {
setBoxFlags(table[num], value);
}
}
void ScummEngine_v6::o6_createBoxMatrix() {
createBoxMatrix();
if ((_game.id == GID_DIG) || (_game.id == GID_CMI))
putActors();
}
void ScummEngine_v6::o6_resourceRoutines() {
int resid;
byte subOp = fetchScriptByte();
switch (subOp) {
case 100: // SO_LOAD_SCRIPT
resid = pop();
if (_game.version >= 7)
if (resid >= _numGlobalScripts)
break;
ensureResourceLoaded(rtScript, resid);
break;
case 101: // SO_LOAD_SOUND
resid = pop();
ensureResourceLoaded(rtSound, resid);
break;
case 102: // SO_LOAD_COSTUME
resid = pop();
ensureResourceLoaded(rtCostume, resid);
break;
case 103: // SO_LOAD_ROOM
resid = pop();
ensureResourceLoaded(rtRoom, resid);
break;
case 104: // SO_NUKE_SCRIPT
resid = pop();
if (_game.version >= 7)
if (resid >= _numGlobalScripts)
break;
_res->setResourceCounter(rtScript, resid, 0x7F);
break;
case 105: // SO_NUKE_SOUND
resid = pop();
_res->setResourceCounter(rtSound, resid, 0x7F);
break;
case 106: // SO_NUKE_COSTUME
resid = pop();
_res->setResourceCounter(rtCostume, resid, 0x7F);
break;
case 107: // SO_NUKE_ROOM
resid = pop();
_res->setResourceCounter(rtRoom, resid, 0x7F);
break;
case 108: // SO_LOCK_SCRIPT
resid = pop();
if (resid >= _numGlobalScripts)
break;
_res->lock(rtScript, resid);
break;
case 109: // SO_LOCK_SOUND
resid = pop();
_res->lock(rtSound, resid);
break;
case 110: // SO_LOCK_COSTUME
resid = pop();
_res->lock(rtCostume, resid);
break;
case 111: // SO_LOCK_ROOM
resid = pop();
if (resid > 0x7F)
resid = _resourceMapper[resid & 0x7F];
_res->lock(rtRoom, resid);
break;
case 112: // SO_UNLOCK_SCRIPT
resid = pop();
if (resid >= _numGlobalScripts)
break;
_res->unlock(rtScript, resid);
break;
case 113: // SO_UNLOCK_SOUND
resid = pop();
_res->unlock(rtSound, resid);
break;
case 114: // SO_UNLOCK_COSTUME
resid = pop();
_res->unlock(rtCostume, resid);
break;
case 115: // SO_UNLOCK_ROOM
resid = pop();
if (resid > 0x7F)
resid = _resourceMapper[resid & 0x7F];
_res->unlock(rtRoom, resid);
break;
case 116: // SO_CLEAR_HEAP
/* this is actually a scumm message */
error("clear heap not working yet");
break;
case 117: // SO_LOAD_CHARSET
resid = pop();
loadCharset(resid);
break;
case 118: // SO_NUKE_CHARSET
resid = pop();
nukeCharset(resid);
break;
case 119: // SO_LOAD_OBJECT
{
int room, obj = popRoomAndObj(&room);
loadFlObject(obj, room);
break;
}
default:
error("o6_resourceRoutines: default case %d", subOp);
}
}
void ScummEngine_v6::o6_roomOps() {
int a, b, c, d, e;
byte subOp = fetchScriptByte();
switch (subOp) {
case 172: // SO_ROOM_SCROLL
b = pop();
a = pop();
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 174: // SO_ROOM_SCREEN
b = pop();
a = pop();
initScreens(a, b);
break;
case 175: // SO_ROOM_PALETTE
d = pop();
c = pop();
b = pop();
a = pop();
setPalColor(d, a, b, c);
break;
case 176: // SO_ROOM_SHAKE_ON
setShake(1);
break;
case 177: // SO_ROOM_SHAKE_OFF
setShake(0);
break;
case 179: // SO_ROOM_INTENSITY
c = pop();
b = pop();
a = pop();
darkenPalette(a, a, a, b, c);
break;
case 180: // SO_ROOM_SAVEGAME
_saveTemporaryState = true;
_saveLoadSlot = pop();
_saveLoadFlag = pop();
if (_game.id == GID_TENTACLE)
_saveSound = (_saveLoadSlot != 0);
break;
case 181: // SO_ROOM_FADE
a = pop();
if (a) {
_switchRoomEffect = (byte)(a & 0xFF);
_switchRoomEffect2 = (byte)(a >> 8);
} else {
fadeIn(_newEffect);
}
break;
case 182: // SO_RGB_ROOM_INTENSITY
e = pop();
d = pop();
c = pop();
b = pop();
a = pop();
darkenPalette(a, b, c, d, e);
break;
case 183: // SO_ROOM_SHADOW
e = pop();
d = pop();
c = pop();
b = pop();
a = pop();
setShadowPalette(a, b, c, d, e, 0, 256);
break;
case 184: // SO_SAVE_STRING
error("save string not implemented");
break;
case 185: // SO_LOAD_STRING
error("load string not implemented");
break;
case 186: // SO_ROOM_TRANSFORM
d = pop();
c = pop();
b = pop();
a = pop();
palManipulateInit(a, b, c, d);
break;
case 187: // SO_CYCLE_SPEED
b = pop();
a = pop();
assertRange(1, a, 16, "o6_roomOps: 187: color cycle");
_colorCycle[a - 1].delay = (b != 0) ? 0x4000 / (b * 0x4C) : 0;
break;
case 213: // SO_ROOM_NEW_PALETTE
a = pop();
// This opcode is used when turning off noir mode in Sam & Max,
// but since our implementation of this feature doesn't change
// the original palette there's no need to reload it. Doing it
// this way, we avoid some graphics glitches that the original
// interpreter had.
if (_game.id == GID_SAMNMAX && vm.slot[_currentScript].number == 64)
setDirtyColors(0, 255);
else
setCurrentPalette(a);
break;
default:
error("o6_roomOps: default case %d", subOp);
}
}
void ScummEngine_v6::o6_actorOps() {
Actor *a;
int i, j, k;
int args[8];
byte subOp = fetchScriptByte();
if (subOp == 197) {
_curActor = pop();
return;
}
a = derefActorSafe(_curActor, "o6_actorOps");
if (!a)
return;
switch (subOp) {
case 76: // SO_COSTUME
a->setActorCostume(pop());
break;
case 77: // SO_STEP_DIST
j = pop();
i = pop();
a->setActorWalkSpeed(i, j);
break;
case 78: // SO_SOUND
k = getStackList(args, ARRAYSIZE(args));
for (i = 0; i < k; i++)
a->_sound[i] = args[i];
break;
case 79: // SO_WALK_ANIMATION
a->_walkFrame = pop();
break;
case 80: // SO_TALK_ANIMATION
a->_talkStopFrame = pop();
a->_talkStartFrame = pop();
break;
case 81: // SO_STAND_ANIMATION
a->_standFrame = pop();
break;
case 82: // SO_ANIMATION
// dummy case in scumm6
pop();
pop();
pop();
break;
case 83: // SO_DEFAULT
a->initActor(0);
break;
case 84: // SO_ELEVATION
a->setElevation(pop());
break;
case 85: // SO_ANIMATION_DEFAULT
a->_initFrame = 1;
a->_walkFrame = 2;
a->_standFrame = 3;
a->_talkStartFrame = 4;
a->_talkStopFrame = 5;
break;
case 86: // SO_PALETTE
j = pop();
i = pop();
assertRange(0, i, 255, "o6_actorOps: palette slot");
a->setPalette(i, j);
break;
case 87: // SO_TALK_COLOR
a->_talkColor = pop();
break;
case 88: // SO_ACTOR_NAME
loadPtrToResource(rtActorName, a->_number, NULL);
break;
case 89: // SO_INIT_ANIMATION
a->_initFrame = pop();
break;
case 91: // SO_ACTOR_WIDTH
a->_width = pop();
break;
case 92: // SO_SCALE
i = pop();
a->setScale(i, i);
break;
case 93: // SO_NEVER_ZCLIP
a->_forceClip = 0;
break;
case 225: // SO_ALWAYS_ZCLIP
case 94: // SO_ALWAYS_ZCLIP
a->_forceClip = pop();
break;
case 95: // SO_IGNORE_BOXES
a->_ignoreBoxes = 1;
a->_forceClip = (_game.version >= 7) ? 100 : 0;
if (a->isInCurrentRoom())
a->putActor();
break;
case 96: // SO_FOLLOW_BOXES
a->_ignoreBoxes = 0;
a->_forceClip = (_game.version >= 7) ? 100 : 0;
if (a->isInCurrentRoom())
a->putActor();
break;
case 97: // SO_ANIMATION_SPEED
a->setAnimSpeed(pop());
break;
case 98: // SO_SHADOW
a->_shadowMode = pop();
break;
case 99: // SO_TEXT_OFFSET
a->_talkPosY = pop();
a->_talkPosX = pop();
break;
case 198: // SO_ACTOR_VARIABLE
i = pop();
a->setAnimVar(pop(), i);
break;
case 215: // SO_ACTOR_IGNORE_TURNS_ON
a->_ignoreTurns = true;
break;
case 216: // SO_ACTOR_IGNORE_TURNS_OFF
a->_ignoreTurns = false;
break;
case 217: // SO_ACTOR_NEW
a->initActor(2);
break;
case 227: // SO_ACTOR_DEPTH
a->_layer = pop();
break;
case 228: // SO_ACTOR_WALK_SCRIPT
a->_walkScript = pop();
break;
case 229: // SO_ACTOR_STOP
a->stopActorMoving();
a->startAnimActor(a->_standFrame);
break;
case 230: /* set direction */
a->_moving &= ~MF_TURN;
a->setDirection(pop());
break;
case 231: /* turn to direction */
a->turnToDirection(pop());
break;
case 233: // SO_ACTOR_WALK_PAUSE
a->_moving |= MF_FROZEN;
break;
case 234: // SO_ACTOR_WALK_RESUME
a->_moving &= ~MF_FROZEN;
break;
case 235: // SO_ACTOR_TALK_SCRIPT
a->_talkScript = pop();
break;
default:
error("o6_actorOps: default case %d", subOp);
}
}
void ScummEngine_v6::o6_verbOps() {
int slot, a, b;
VerbSlot *vs;
byte subOp = fetchScriptByte();
if (subOp == 196) {
_curVerb = pop();
_curVerbSlot = getVerbSlot(_curVerb, 0);
assertRange(0, _curVerbSlot, _numVerbs - 1, "new verb slot");
return;
}
vs = &_verbs[_curVerbSlot];
slot = _curVerbSlot;
switch (subOp) {
case 124: // SO_VERB_IMAGE
a = pop();
if (_curVerbSlot) {
setVerbObject(_roomResource, a, slot);
vs->type = kImageVerbType;
if (_game.heversion >= 61)
vs->imgindex = a;
}
break;
case 125: // SO_VERB_NAME
loadPtrToResource(rtVerb, slot, NULL);
vs->type = kTextVerbType;
vs->imgindex = 0;
break;
case 126: // SO_VERB_COLOR
vs->color = pop();
break;
case 127: // SO_VERB_HICOLOR
vs->hicolor = pop();
break;
case 128: // SO_VERB_AT
vs->curRect.top = pop();
vs->curRect.left = pop();
break;
case 129: // SO_VERB_ON
vs->curmode = 1;
break;
case 130: // SO_VERB_OFF
vs->curmode = 0;
break;
case 131: // SO_VERB_DELETE
if (_game.heversion >= 60) {
slot = getVerbSlot(pop(), 0);
}
killVerb(slot);
break;
case 132: // SO_VERB_NEW
slot = getVerbSlot(_curVerb, 0);
if (slot == 0) {
for (slot = 1; slot < _numVerbs; slot++) {
if (_verbs[slot].verbid == 0)
break;
}
if (slot == _numVerbs)
error("Too many verbs");
_curVerbSlot = slot;
}
vs = &_verbs[slot];
vs->verbid = _curVerb;
vs->color = 2;
vs->hicolor = 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 133: // SO_VERB_DIMCOLOR
vs->dimcolor = pop();
break;
case 134: // SO_VERB_DIM
vs->curmode = 2;
break;
case 135: // SO_VERB_KEY
vs->key = pop();
break;
case 136: // SO_VERB_CENTER
vs->center = 1;
break;
case 137: // SO_VERB_NAME_STR
a = pop();
if (a == 0) {
loadPtrToResource(rtVerb, slot, (const byte *)"");
} else {
loadPtrToResource(rtVerb, slot, getStringAddress(a));
}
vs->type = kTextVerbType;
vs->imgindex = 0;
break;
case 139: // SO_VERB_IMAGE_IN_ROOM
b = pop();
a = pop();
if (slot && a != vs->imgindex) {
setVerbObject(b, a, slot);
vs->type = kImageVerbType;
vs->imgindex = a;
}
break;
case 140: // SO_VERB_BAKCOLOR
vs->bkcolor = pop();
break;
case 255:
drawVerb(slot, 0);
verbMouseOver(0);
break;
default:
error("o6_verbops: default case %d", subOp);
}
}
void ScummEngine_v6::o6_getActorFromXY() {
int y = pop();
int x = pop();
int r = getActorFromPos(x, y);
push(r);
}
void ScummEngine_v6::o6_findObject() {
int y = pop();
int x = pop();
int r = findObject(x, y);
push(r);
}
void ScummEngine_v6::o6_pseudoRoom() {
int list[100];
int num, a, value;
num = getStackList(list, ARRAYSIZE(list));
value = pop();
while (--num >= 0) {
a = list[num];
if (a > 0x7F)
_resourceMapper[a & 0x7F] = value;
}
}
void ScummEngine_v6::o6_getVerbEntrypoint() {
int e = pop();
int v = pop();
push(getVerbEntrypoint(v, e));
}
void ScummEngine_v6::o6_arrayOps() {
byte subOp = fetchScriptByte();
int array = fetchScriptWord();
int b, c, d, len;
byte *data;
int list[128];
switch (subOp) {
case 205: // SO_ASSIGN_STRING
b = pop();
len = resStrLen(_scriptPointer);
data = defineArray(array, kStringArray, 0, len + 1);
copyScriptString(data + b);
break;
case 208: // SO_ASSIGN_INT_LIST
b = pop();
c = pop();
d = readVar(array);
if (d == 0) {
defineArray(array, kIntArray, 0, b + c);
}
while (c--) {
writeArray(array, 0, b + c, pop());
}
break;
case 212: // SO_ASSIGN_2DIM_LIST
b = pop();
len = getStackList(list, ARRAYSIZE(list));
d = readVar(array);
if (d == 0)
error("Must DIM a two dimensional array before assigning");
c = pop();
while (--len >= 0) {
writeArray(array, c, b + len, list[len]);
}
break;
default:
error("o6_arrayOps: default case %d (array %d)", subOp, array);
}
}
void ScummEngine_v6::o6_saveRestoreVerbs() {
int a, b, c;
int slot, slot2;
c = pop();
b = pop();
a = pop();
byte subOp = fetchScriptByte();
if (_game.version == 8) {
subOp = (subOp - 141) + 0xB4;
}
switch (subOp) {
case 141: // 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 142: // 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 143: // SO_DELETE_VERBS
while (a <= b) {
slot = getVerbSlot(a, c);
if (slot)
killVerb(slot);
a++;
}
break;
default:
error("o6_saveRestoreVerbs: default case");
}
}
void ScummEngine_v6::o6_drawBox() {
int x, y, x2, y2, color;
color = pop();
y2 = pop();
x2 = pop();
y = pop();
x = pop();
drawBox(x, y, x2, y2, color);
}
void ScummEngine_v6::o6_wait() {
int actnum;
int offs = -2;
Actor *a;
byte subOp = fetchScriptByte();
switch (subOp) {
case 168: // SO_WAIT_FOR_ACTOR Wait for actor
offs = fetchScriptWordSigned();
actnum = pop();
a = derefActor(actnum, "o6_wait:168");
if (_game.version >= 7) {
if (a->isInCurrentRoom() && a->_moving)
break;
} else {
if (a->_moving)
break;
}
return;
case 169: // SO_WAIT_FOR_MESSAGE Wait for message
if (VAR(VAR_HAVE_MSG))
break;
return;
case 170: // SO_WAIT_FOR_CAMERA Wait for camera
if (_game.version >= 7) {
if (camera._dest != camera._cur)
break;
} else {
if (camera._cur.x / 8 != camera._dest.x / 8)
break;
}
return;
case 171: // 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;
case 226: // SO_WAIT_FOR_ANIMATION
offs = fetchScriptWordSigned();
actnum = pop();
a = derefActor(actnum, "o6_wait:226");
if (a->isInCurrentRoom() && a->_needRedraw)
break;
return;
case 232: // SO_WAIT_FOR_TURN
// WORKAROUND for bug #744441: An angle will often be received as the
// actor number due to script bugs in The Dig. In all cases where this
// occurs, _curActor is set just before it, so we can use it instead.
//
// For now, if the value passed in is divisible by 45, assume it is an
// angle, and use _curActor as the actor to wait for.
offs = fetchScriptWordSigned();
actnum = pop();
if (actnum % 45 == 0) {
actnum = _curActor;
}
a = derefActor(actnum, "o6_wait:232b");
if (a->isInCurrentRoom() && a->_moving & MF_TURN)
break;
return;
default:
error("o6_wait: default case 0x%x", subOp);
}
_scriptPointer += offs;
o6_breakHere();
}
void ScummEngine_v6::o6_soundKludge() {
int list[16];
int num = getStackList(list, ARRAYSIZE(list));
_sound->soundKludge(list, num);
// WORKAROUND for bug #1398195: The room-11-2016 script contains a
// slight bug causing it to busy-wait for a sound to finish. Even under
// the best of circumstances, this will cause the game to hang briefly.
// On platforms where threading is cooperative, it will cause the game
// to hang indefinitely. We identify the buggy part of the script by
// looking for a soundKludge() opcode immediately followed by a jump.
if (_game.id == GID_CMI && _roomResource == 11 && vm.slot[_currentScript].number == 2016 && *_scriptPointer == 0x66) {
debug(3, "Working around script bug in room-11-2016");
o6_breakHere();
}
}
void ScummEngine_v6::o6_isAnyOf() {
int list[100];
int num;
int32 val;
num = getStackList(list, ARRAYSIZE(list));
val = pop();
while (--num >= 0) {
if (list[num] == val) {
push(1);
return;
}
}
push(0);
}
void ScummEngine_v6::o6_systemOps() {
byte subOp = fetchScriptByte();
switch (subOp) {
case 158: // SO_RESTART
restart();
break;
case 159: // SO_PAUSE
pauseGame();
break;
case 160: // SO_QUIT
quitGame();
break;
default:
error("o6_systemOps invalid case %d", subOp);
}
}
void ScummEngine_v6::o6_delay() {
uint32 delay = (uint16)pop();
vm.slot[_currentScript].delay = delay;
vm.slot[_currentScript].status = ssPaused;
o6_breakHere();
}
void ScummEngine_v6::o6_delaySeconds() {
uint32 delay = (uint32)pop();
delay = delay * 60;
vm.slot[_currentScript].delay = delay;
vm.slot[_currentScript].status = ssPaused;
o6_breakHere();
}
void ScummEngine_v6::o6_delayMinutes() {
uint32 delay = (uint16)pop() * 3600;
vm.slot[_currentScript].delay = delay;
vm.slot[_currentScript].status = ssPaused;
o6_breakHere();
}
void ScummEngine_v6::o6_stopSentence() {
_sentenceNum = 0;
stopScript(VAR(VAR_SENTENCE_SCRIPT));
clearClickedStatus();
}
void ScummEngine_v6::o6_printLine() {
_actorToPrintStrFor = 0xFF;
decodeParseString(0, 0);
}
void ScummEngine_v6::o6_printText() {
decodeParseString(1, 0);
}
void ScummEngine_v6::o6_printDebug() {
decodeParseString(2, 0);
}
void ScummEngine_v6::o6_printSystem() {
decodeParseString(3, 0);
}
void ScummEngine_v6::o6_printActor() {
decodeParseString(0, 1);
}
void ScummEngine_v6::o6_printEgo() {
push(VAR(VAR_EGO));
decodeParseString(0, 1);
}
void ScummEngine_v6::o6_talkActor() {
int offset = _scriptPointer - _scriptOrgPointer;
// WORKAROUND for bug #896489: see below for detailed description
if (_forcedWaitForMessage) {
if (VAR(VAR_HAVE_MSG)) {
_scriptPointer--;
o6_breakHere();
return;
}
_forcedWaitForMessage = false;
_scriptPointer += resStrLen(_scriptPointer) + 1;
return;
}
_actorToPrintStrFor = pop();
// WORKAROUND for bug #2016521: "DOTT: Bernard impersonating LaVerne"
// Original script did not check for VAR_EGO == 2 before executing
// a talkActor opcode.
if (_game.id == GID_TENTACLE && vm.slot[_currentScript].number == 307
&& VAR(VAR_EGO) != 2 && _actorToPrintStrFor == 2) {
_scriptPointer += resStrLen(_scriptPointer) + 1;
return;
}
_string[0].loadDefault();
actorTalk(_scriptPointer);
// WORKAROUND for bug #896489: "DIG: Missing subtitles when talking to Brink"
// Original script does not have wait.waitForMessage() after several messages:
//
// [011A] (5D) if (getActorCostume(VAR_EGO) == 1) {
// [0126] (BA) talkActor("/STOP.008/Low out.",3)
// [013D] (A9) wait.waitForMessage()
// [013F] (5D) } else if (var227 == 0) {
// [014C] (BA) talkActor("/STOP.009/Never mind.",3)
// [0166] (73) } else {
//
// Here we simulate that opcode.
if (_game.id == GID_DIG && vm.slot[_currentScript].number == 88) {
if (offset == 0x158 || offset == 0x214 || offset == 0x231 || offset == 0x278) {
_forcedWaitForMessage = true;
_scriptPointer--;
return;
}
}
_scriptPointer += resStrLen(_scriptPointer) + 1;
}
void ScummEngine_v6::o6_talkEgo() {
push(VAR(VAR_EGO));
o6_talkActor();
}
void ScummEngine_v6::o6_dimArray() {
int data;
byte subOp = fetchScriptByte();
switch (subOp) {
case 199: // SO_INT_ARRAY
data = kIntArray;
break;
case 200: // SO_BIT_ARRAY
data = kBitArray;
break;
case 201: // SO_NIBBLE_ARRAY
data = kNibbleArray;
break;
case 202: // SO_BYTE_ARRAY
data = kByteArray;
break;
case 203: // SO_STRING_ARRAY
data = kStringArray;
break;
case 204: // SO_UNDIM_ARRAY
nukeArray(fetchScriptWord());
return;
default:
error("o6_dimArray: default case %d", subOp);
}
defineArray(fetchScriptWord(), data, 0, pop());
}
void ScummEngine_v6::o6_dummy() {
}
void ScummEngine_v6::o6_dim2dimArray() {
int a, b, data;
byte subOp = fetchScriptByte();
switch (subOp) {
case 199: // SO_INT_ARRAY
data = kIntArray;
break;
case 200: // SO_BIT_ARRAY
data = kBitArray;
break;
case 201: // SO_NIBBLE_ARRAY
data = kNibbleArray;
break;
case 202: // SO_BYTE_ARRAY
data = kByteArray;
break;
case 203: // SO_STRING_ARRAY
data = kStringArray;
break;
default:
error("o6_dim2dimArray: default case %d", subOp);
}
b = pop();
a = pop();
defineArray(fetchScriptWord(), data, a, b);
}
void ScummEngine_v6::o6_abs() {
int a = pop();
push(ABS(a));
}
void ScummEngine_v6::o6_distObjectObject() {
int a, b;
b = pop();
a = pop();
push(getDistanceBetween(true, a, 0, true, b, 0));
}
void ScummEngine_v6::o6_distObjectPt() {
int a, b, c;
c = pop();
b = pop();
a = pop();
push(getDistanceBetween(true, a, 0, false, b, c));
}
void ScummEngine_v6::o6_distPtPt() {
int a, b, c, d;
d = pop();
c = pop();
b = pop();
a = pop();
push(getDistanceBetween(false, a, b, false, c, d));
}
void ScummEngine_v6::o6_drawBlastObject() {
int args[16];
int a, b, c, d, e;
getStackList(args, ARRAYSIZE(args));
e = pop();
d = pop();
c = pop();
b = pop();
a = pop();
enqueueObject(a, b, c, d, e, 0xFF, 0xFF, 1, 0);
}
// Set BOMP processing window
void ScummEngine_v6::o6_setBlastObjectWindow() {
pop();
pop();
pop();
pop();
// None of the scripts of The Dig and Full Throttle use this opcode.
// Sam & Max only uses it at the beginning of the highway subgame. In
// the original interpreter pop'ed arguments are just ignored and the
// clipping blastObject window is defined with (0, 0, 320, 200)...
// which matches the screen dimensions and thus, doesn't require
// another clipping operation.
// So, we just handle this as no-op opcode.
}
#ifdef ENABLE_SCUMM_7_8
void ScummEngine_v7::o6_kernelSetFunctions() {
int args[30];
int num;
Actor *a;
num = getStackList(args, ARRAYSIZE(args));
switch (args[0]) {
case 4:
grabCursor(args[1], args[2], args[3], args[4]);
break;
case 6: {
// SMUSH movie playback
if (args[1] == 0 && !_skipVideo) {
const char *videoname = (const char *)getStringAddressVar(VAR_VIDEONAME);
assert(videoname);
// Correct incorrect smush filename in Macintosh FT demo
if ((_game.id == GID_FT) && (_game.features & GF_DEMO) && (_game.platform == Common::kPlatformMacintosh) &&
(!strcmp(videoname, "jumpgorge.san")))
_splayer->play("jumpgorg.san", _smushFrameRate);
// WORKAROUND: A faster frame rate is required, to keep audio/video in sync in this video
else if (_game.id == GID_DIG && !strcmp(videoname, "sq3.san"))
_splayer->play(videoname, 14);
else
_splayer->play(videoname, _smushFrameRate);
if (_game.id == GID_DIG) {
_disableFadeInEffect = true;
}
} else if (_game.id == GID_FT && !_skipVideo) {
const int insaneVarNum = ((_game.features & GF_DEMO) && (_game.platform == Common::kPlatformDOS))
? 232 : 233;
_insane->setSmushParams(_smushFrameRate);
_insane->runScene(insaneVarNum);
}
}
break;
case 12:
setCursorFromImg(args[1], (uint) - 1, args[2]);
break;
case 13:
derefActor(args[1], "o6_kernelSetFunctions:13")->remapActorPalette(args[2], args[3], args[4], -1);
break;
case 14:
derefActor(args[1], "o6_kernelSetFunctions:14")->remapActorPalette(args[2], args[3], args[4], args[5]);
break;
case 15:
_smushFrameRate = args[1];
break;
case 16:
case 17:
enqueueText(getStringAddressVar(VAR_STRING2DRAW), args[3], args[4], args[2], args[1], (args[0] == 16));
break;
case 20:
_imuseDigital->setRadioChatterSFX(args[1]);
break;
case 107:
a = derefActor(args[1], "o6_kernelSetFunctions: 107");
a->setScale((unsigned char)args[2], -1);
break;
case 108:
setShadowPalette(args[1], args[2], args[3], args[4], args[5], args[6]);
break;
case 109:
setShadowPalette(0, args[1], args[2], args[3], args[4], args[5]);
break;
case 114:
error("o6_kernelSetFunctions: stub114()");
break;
case 117:
freezeScripts(2);
break;
case 118:
enqueueObject(args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], 3);
break;
case 119:
enqueueObject(args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], 0);
break;
case 124:
_saveSound = args[1];
break;
case 215:
ConfMan.setBool("subtitles", args[1] != 0);
break;
default:
error("o6_kernelSetFunctions: default case %d (param count %d)", args[0], num);
break;
}
}
#endif
void ScummEngine_v6::o6_kernelSetFunctions() {
int args[30];
int num;
Actor *a;
num = getStackList(args, ARRAYSIZE(args));
switch (args[0]) {
case 3:
// Dummy case
break;
case 4:
grabCursor(args[1], args[2], args[3], args[4]);
break;
case 5:
fadeOut(args[1]);
break;
case 6:
_fullRedraw = true;
redrawBGAreas();
setActorRedrawFlags();
processActors();
fadeIn(args[1]);
break;
case 8:
if (startManiac()) {
// This is so that the surprised exclamation happens
// after we return to the game again, not before.
o6_breakHere();
}
break;
case 9:
killAllScriptsExceptCurrent();
break;
case 104: /* samnmax */
nukeFlObjects(args[2], args[3]);
break;
case 107: /* set actor scale */
a = derefActor(args[1], "o6_kernelSetFunctions: 107");
a->setScale((unsigned char)args[2], -1);
break;
case 108: /* create proc_special_palette */
case 109:
// Case 108 and 109 share the same function
if (num != 6)
error("o6_kernelSetFunctions sub op %d: expected 6 params but got %d", args[0], num);
setShadowPalette(args[3], args[4], args[5], args[1], args[2], 0, 256);
break;
case 110:
clearCharsetMask();
break;
case 111:
a = derefActor(args[1], "o6_kernelSetFunctions: 111");
a->_shadowMode = args[2] + args[3];
break;
case 112: /* palette shift? */
setShadowPalette(args[3], args[4], args[5], args[1], args[2], args[6], args[7]);
break;
case 114:
// Sam & Max film noir mode
if (_game.id == GID_SAMNMAX) {
// At this point ScummVM will already have set
// variable 0x8000 to indicate that the game is
// in film noir mode. All we have to do here is
// to mark the palette as "dirty", because
// updatePalette() will desaturate the colors
// as they are uploaded to the backend.
//
// This actually works better than the original
// interpreter, where actors would sometimes
// still be drawn in color.
setDirtyColors(0, 255);
} else
error("stub o6_kernelSetFunctions_114()");
break;
case 117:
// Sam & Max uses this opcode in script-43, right
// before a screensaver is selected.
//
// Sam & Max uses variable 132 to specify the number of
// minutes of inactivity (no mouse movements) before
// starting the screensaver, so setting it to 0 will
// help in debugging.
freezeScripts(0x80);
break;
case 119:
enqueueObject(args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], 0);
break;
case 120:
swapPalColors(args[1], args[2]);
break;
case 122:
VAR(VAR_SOUNDRESULT) =
(short)_imuse->doCommand (num - 1, &args[1]);
break;
case 123:
copyPalColor(args[2], args[1]);
break;
case 124:
_saveSound = args[1];
break;
default:
error("o6_kernelSetFunctions: default case %d (param count %d)", args[0], num);
break;
}
}
void ScummEngine_v6::o6_kernelGetFunctions() {
int args[30];
int i;
int slot;
Actor *a;
VirtScreen *vs = &_virtscr[kMainVirtScreen];
getStackList(args, ARRAYSIZE(args));
switch (args[0]) {
case 113:
// WORKAROUND for bug #899249: The scripts used for screen savers
// in Sam & Max use hard coded values for the maximum height and width.
// This causes problems in rooms (ie. Credits) where their values are
// lower, so we set result to zero if out of bounds.
if (args[1] >= 0 && args[1] <= vs->w && args[2] >= 0 && args[2] <= vs->h) {
byte pixel = *vs->getPixels(args[1], args[2]);
push(pixel);
} else {
push(0);
}
break;
case 115:
push(getSpecialBox(args[1], args[2]));
break;
case 116:
push(checkXYInBoxBounds(args[3], args[1], args[2]));
break;
case 206:
push(remapPaletteColor(args[1], args[2], args[3], -1));
break;
case 207:
i = getObjectIndex(args[1]);
assert(i);
push(_objs[i].x_pos);
break;
case 208:
i = getObjectIndex(args[1]);
assert(i);
push(_objs[i].y_pos);
break;
case 209:
i = getObjectIndex(args[1]);
assert(i);
push(_objs[i].width);
break;
case 210:
i = getObjectIndex(args[1]);
assert(i);
push(_objs[i].height);
break;
case 211:
/*
13 = thrust
336 = thrust
328 = thrust
27 = abort
97 = left
331 = left
115 = right
333 = right
*/
push(getKeyState(args[1]));
break;
case 212:
a = derefActor(args[1], "o6_kernelGetFunctions:212");
// This is used by walk scripts
push(a->_frame);
break;
case 213:
slot = getVerbSlot(args[1], 0);
push(_verbs[slot].curRect.left);
break;
case 214:
slot = getVerbSlot(args[1], 0);
push(_verbs[slot].curRect.top);
break;
case 215:
if ((_extraBoxFlags[args[1]] & 0x00FF) == 0x00C0) {
push(_extraBoxFlags[args[1]]);
} else {
push(getBoxFlags(args[1]));
}
break;
default:
error("o6_kernelGetFunctions: default case %d", args[0]);
}
}
int ScummEngine::getKeyState(int key) {
switch (key) {
case 0x147: // Home
// FIXME: There seems to be a mistake in the code here ("insert" vs. "home")
return (_keyDownMap[Common::KEYCODE_KP7] ||
_keyDownMap[Common::KEYCODE_INSERT]) ? 1 : 0;
case 0x148: // Up
return (_keyDownMap[Common::KEYCODE_KP8] ||
_keyDownMap[Common::KEYCODE_UP] ||
_keyDownMap[Common::KEYCODE_8]) ? 1 : 0;
case 0x149: // PgUp
return (_keyDownMap[Common::KEYCODE_KP9] ||
_keyDownMap[Common::KEYCODE_PAGEUP]) ? 1 : 0;
case 0x14B: // Left
return (_keyDownMap[Common::KEYCODE_KP4] ||
_keyDownMap[Common::KEYCODE_LEFT] ||
_keyDownMap[Common::KEYCODE_4]) ? 1 : 0;
case 0x14D: // Right
return (_keyDownMap[Common::KEYCODE_KP6] ||
_keyDownMap[Common::KEYCODE_RIGHT] ||
_keyDownMap[Common::KEYCODE_6]) ? 1 : 0;
case 0x14F: // End
return (_keyDownMap[Common::KEYCODE_KP1] ||
_keyDownMap[Common::KEYCODE_END]) ? 1 : 0;
case 0x150: // Down
return (_keyDownMap[Common::KEYCODE_KP2] ||
_keyDownMap[Common::KEYCODE_DOWN] ||
_keyDownMap[Common::KEYCODE_2]) ? 1 : 0;
case 0x151: // PgDn
return (_keyDownMap[Common::KEYCODE_KP3] ||
_keyDownMap[Common::KEYCODE_PAGEDOWN]) ? 1 : 0;
default:
return (_keyDownMap[key]) ? 1 : 0;
}
}
void ScummEngine_v6::o6_delayFrames() {
ScriptSlot *ss = &vm.slot[_currentScript];
if (ss->delayFrameCount == 0) {
ss->delayFrameCount = pop();
} else {
ss->delayFrameCount--;
}
if (ss->delayFrameCount) {
_scriptPointer--;
o6_breakHere();
}
}
void ScummEngine_v6::o6_pickOneOf() {
int args[100];
int i, num;
num = getStackList(args, ARRAYSIZE(args));
i = pop();
if (i < 0 || i > num)
error("o6_pickOneOf: %d out of range (0, %d)", i, num - 1);
push(args[i]);
}
void ScummEngine_v6::o6_pickOneOfDefault() {
int args[100];
int i, num, def;
def = pop();
num = getStackList(args, ARRAYSIZE(args));
i = pop();
if (i < 0 || i >= num)
i = def;
else
i = args[i];
push(i);
}
void ScummEngine_v6::o6_stampObject() {
int object, x, y, state;
state = pop();
y = pop();
x = pop();
object = pop();
if (_game.version >= 7 && object < 30) {
if (state == 0)
state = 255;
Actor *a = derefActor(object, "o6_stampObject");
a->_scalex = state;
a->_scaley = state;
a->putActor(x, y, _currentRoom);
a->_drawToBackBuf = true;
a->drawActorCostume();
a->_drawToBackBuf = false;
a->drawActorCostume();
return;
}
if (state == 0)
state = 1;
int objnum = getObjectIndex(object);
if (objnum == -1)
return;
if (x != -1) {
_objs[objnum].x_pos = x * 8;
_objs[objnum].y_pos = y * 8;
}
putState(object, state);
drawObject(objnum, 0);
}
void ScummEngine_v6::o6_stopTalking() {
stopTalk();
}
void ScummEngine_v6::o6_findAllObjects() {
int room = pop();
int i = 1;
if (room != _currentRoom)
error("o6_findAllObjects: current room is not %d", room);
writeVar(0, 0);
defineArray(0, kIntArray, 0, _numLocalObjects + 1);
writeArray(0, 0, 0, _numLocalObjects);
while (i < _numLocalObjects) {
writeArray(0, 0, i, _objs[i].obj_nr);
i++;
}
push(readVar(0));
}
void ScummEngine_v6::shuffleArray(int num, int minIdx, int maxIdx) {
int range = maxIdx - minIdx;
int count = range * 2;
// Shuffle the array 'num'
while (count--) {
// Determine two random elements...
int rand1 = _rnd.getRandomNumber(range) + minIdx;
int rand2 = _rnd.getRandomNumber(range) + minIdx;
// ...and swap them
int val1 = readArray(num, 0, rand1);
int val2 = readArray(num, 0, rand2);
writeArray(num, 0, rand1, val2);
writeArray(num, 0, rand2, val1);
}
}
void ScummEngine_v6::o6_shuffle() {
int b = pop();
int a = pop();
shuffleArray(fetchScriptWord(), a, b);
}
void ScummEngine_v6::o6_pickVarRandom() {
int num;
int args[100];
int dim1;
num = getStackList(args, ARRAYSIZE(args));
int value = fetchScriptWord();
if (readVar(value) == 0) {
defineArray(value, kIntArray, 0, num);
if (num > 0) {
int16 counter = 0;
do {
writeArray(value, 0, counter + 1, args[counter]);
} while (++counter < num);
}
shuffleArray(value, 1, num);
writeArray(value, 0, 0, 2);
push(readArray(value, 0, 1));
return;
}
num = readArray(value, 0, 0);
ArrayHeader *ah = getArray(value);
dim1 = FROM_LE_16(ah->dim1) - 1;
if (dim1 < num) {
int16 var_2 = readArray(value, 0, num - 1);
shuffleArray(value, 1, dim1);
if (readArray(value, 0, 1) == var_2) {
num = 2;
} else {
num = 1;
}
}
writeArray(value, 0, 0, num + 1);
push(readArray(value, 0, num));
}
void ScummEngine_v6::o6_getDateTime() {
TimeDate t;
_system->getTimeAndDate(t);
VAR(VAR_TIMEDATE_YEAR) = t.tm_year;
VAR(VAR_TIMEDATE_MONTH) = t.tm_mon;
VAR(VAR_TIMEDATE_DAY) = t.tm_mday;
VAR(VAR_TIMEDATE_HOUR) = t.tm_hour;
VAR(VAR_TIMEDATE_MINUTE) = t.tm_min;
if (_game.version == 8)
VAR(VAR_TIMEDATE_SECOND) = t.tm_sec;
}
void ScummEngine_v6::o6_getPixel() {
int x, y;
if (_game.heversion >= 61 && _game.heversion <= 62) {
x = pop();
y = pop();
} else {
y = pop();
x = pop();
}
VirtScreen *vs = findVirtScreen(y);
if (vs == NULL || x > _screenWidth - 1 || x < 0) {
push(-1);
return;
}
byte pixel = *vs->getPixels(x, y - vs->topline);
push(pixel);
}
void ScummEngine_v6::o6_setBoxSet() {
int arg = pop() - 1;
const byte *room = getResourceAddress(rtRoom, _roomResource);
const byte *boxd = NULL, *boxm = NULL;
int32 dboxSize, mboxSize;
int i;
ResourceIterator boxds(room, false);
for (i = 0; i < arg; i++)
boxd = boxds.findNext(MKTAG('B','O','X','D'));
if (!boxd)
error("ScummEngine_v6::o6_setBoxSet: Can't find dboxes for set %d", arg);
dboxSize = READ_BE_UINT32(boxd + 4) - 8;
byte *matrix = _res->createResource(rtMatrix, 2, dboxSize);
assert(matrix);
memcpy(matrix, boxd + 8, dboxSize);
ResourceIterator boxms(room, false);
for (i = 0; i < arg; i++)
boxm = boxms.findNext(MKTAG('B','O','X','M'));
if (!boxm)
error("ScummEngine_v6::o6_setBoxSet: Can't find mboxes for set %d", arg);
mboxSize = READ_BE_UINT32(boxm + 4) - 8;
matrix = _res->createResource(rtMatrix, 1, mboxSize);
assert(matrix);
memcpy(matrix, boxm + 8, mboxSize);
if (_game.version == 7)
putActors();
}
void ScummEngine_v6::decodeParseString(int m, int n) {
byte b = fetchScriptByte();
switch (b) {
case 65: // SO_AT
_string[m].ypos = pop();
_string[m].xpos = pop();
_string[m].overhead = false;
break;
case 66: // SO_COLOR
_string[m].color = pop();
break;
case 67: // SO_CLIPPED
_string[m].right = pop();
break;
case 69: // SO_CENTER
_string[m].center = true;
_string[m].overhead = false;
break;
case 71: // SO_LEFT
_string[m].center = false;
_string[m].overhead = false;
break;
case 72: // SO_OVERHEAD
_string[m].overhead = true;
_string[m].no_talk_anim = false;
break;
case 74: // SO_MUMBLE
_string[m].no_talk_anim = true;
break;
case 75: // SO_TEXTSTRING
printString(m, _scriptPointer);
_scriptPointer += resStrLen(_scriptPointer) + 1;
break;
case 0xFE:
_string[m].loadDefault();
if (n)
_actorToPrintStrFor = pop();
break;
case 0xFF:
_string[m].saveDefault();
break;
default:
error("decodeParseString: default case 0x%x", b);
}
}
} // End of namespace Scumm