/* 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" namespace Scumm { #define OPCODE(x) _OPCODE(ScummEngine_v2, x) void ScummEngine_v2::setupOpcodes() { static const OpcodeEntryV2 opcodes[256] = { /* 00 */ OPCODE(o5_stopObjectCode), OPCODE(o2_putActor), OPCODE(o5_startMusic), OPCODE(o5_getActorRoom), /* 04 */ OPCODE(o2_isGreaterEqual), OPCODE(o2_drawObject), OPCODE(o2_getActorElevation), OPCODE(o2_setState08), /* 08 */ OPCODE(o5_isNotEqual), OPCODE(o5_faceActor), OPCODE(o2_assignVarWordIndirect), OPCODE(o2_setObjPreposition), /* 0C */ OPCODE(o2_resourceRoutines), OPCODE(o5_walkActorToActor), OPCODE(o2_putActorAtObject), OPCODE(o2_ifNotState08), /* 10 */ OPCODE(o5_getObjectOwner), OPCODE(o5_animateActor), OPCODE(o2_panCameraTo), OPCODE(o2_actorOps), /* 14 */ OPCODE(o5_print), OPCODE(o2_actorFromPos), OPCODE(o5_getRandomNr), OPCODE(o2_clearState02), /* 18 */ OPCODE(o5_jumpRelative), OPCODE(o2_doSentence), OPCODE(o5_move), OPCODE(o2_setBitVar), /* 1C */ OPCODE(o5_startSound), OPCODE(o2_ifClassOfIs), OPCODE(o2_walkActorTo), OPCODE(o2_ifState02), /* 20 */ OPCODE(o5_stopMusic), OPCODE(o2_putActor), OPCODE(o5_saveLoadGame), OPCODE(o2_getActorY), /* 24 */ OPCODE(o2_loadRoomWithEgo), OPCODE(o2_drawObject), OPCODE(o5_setVarRange), OPCODE(o2_setState04), /* 28 */ OPCODE(o5_equalZero), OPCODE(o2_setOwnerOf), OPCODE(o2_addIndirect), OPCODE(o5_delayVariable), /* 2C */ OPCODE(o2_assignVarByte), OPCODE(o2_putActorInRoom), OPCODE(o2_delay), OPCODE(o2_ifNotState04), /* 30 */ OPCODE(o2_setBoxFlags), OPCODE(o2_getBitVar), OPCODE(o2_setCameraAt), OPCODE(o2_roomOps), /* 34 */ OPCODE(o5_getDist), OPCODE(o2_findObject), OPCODE(o2_walkActorToObject), OPCODE(o2_setState01), /* 38 */ OPCODE(o2_isLessEqual), OPCODE(o2_doSentence), OPCODE(o2_subtract), OPCODE(o2_waitForActor), /* 3C */ OPCODE(o5_stopSound), OPCODE(o2_setActorElevation), OPCODE(o2_walkActorTo), OPCODE(o2_ifNotState01), /* 40 */ OPCODE(o2_cutscene), OPCODE(o2_putActor), OPCODE(o2_startScript), OPCODE(o2_getActorX), /* 44 */ OPCODE(o2_isLess), OPCODE(o2_drawObject), OPCODE(o5_increment), OPCODE(o2_clearState08), /* 48 */ OPCODE(o5_isEqual), OPCODE(o5_faceActor), OPCODE(o2_chainScript), OPCODE(o2_setObjPreposition), /* 4C */ OPCODE(o2_waitForSentence), OPCODE(o5_walkActorToActor), OPCODE(o2_putActorAtObject), OPCODE(o2_ifState08), /* 50 */ OPCODE(o2_pickupObject), OPCODE(o5_animateActor), OPCODE(o5_actorFollowCamera), OPCODE(o2_actorOps), /* 54 */ OPCODE(o5_setObjectName), OPCODE(o2_actorFromPos), OPCODE(o5_getActorMoving), OPCODE(o2_setState02), /* 58 */ OPCODE(o2_beginOverride), OPCODE(o2_doSentence), OPCODE(o2_add), OPCODE(o2_setBitVar), /* 5C */ OPCODE(o2_dummy), OPCODE(o2_ifClassOfIs), OPCODE(o2_walkActorTo), OPCODE(o2_ifNotState02), /* 60 */ OPCODE(o2_cursorCommand), OPCODE(o2_putActor), OPCODE(o2_stopScript), OPCODE(o5_getActorFacing), /* 64 */ OPCODE(o2_loadRoomWithEgo), OPCODE(o2_drawObject), OPCODE(o5_getClosestObjActor), OPCODE(o2_clearState04), /* 68 */ OPCODE(o5_isScriptRunning), OPCODE(o2_setOwnerOf), OPCODE(o2_subIndirect), OPCODE(o2_dummy), /* 6C */ OPCODE(o2_getObjPreposition), OPCODE(o2_putActorInRoom), OPCODE(o2_dummy), OPCODE(o2_ifState04), /* 70 */ OPCODE(o2_lights), OPCODE(o5_getActorCostume), OPCODE(o5_loadRoom), OPCODE(o2_roomOps), /* 74 */ OPCODE(o5_getDist), OPCODE(o2_findObject), OPCODE(o2_walkActorToObject), OPCODE(o2_clearState01), /* 78 */ OPCODE(o2_isGreater), OPCODE(o2_doSentence), OPCODE(o2_verbOps), OPCODE(o2_getActorWalkBox), /* 7C */ OPCODE(o5_isSoundRunning), OPCODE(o2_setActorElevation), OPCODE(o2_walkActorTo), OPCODE(o2_ifState01), /* 80 */ OPCODE(o5_breakHere), OPCODE(o2_putActor), OPCODE(o5_startMusic), OPCODE(o5_getActorRoom), /* 84 */ OPCODE(o2_isGreaterEqual), OPCODE(o2_drawObject), OPCODE(o2_getActorElevation), OPCODE(o2_setState08), /* 88 */ OPCODE(o5_isNotEqual), OPCODE(o5_faceActor), OPCODE(o2_assignVarWordIndirect), OPCODE(o2_setObjPreposition), /* 8C */ OPCODE(o2_resourceRoutines), OPCODE(o5_walkActorToActor), OPCODE(o2_putActorAtObject), OPCODE(o2_ifNotState08), /* 90 */ OPCODE(o5_getObjectOwner), OPCODE(o5_animateActor), OPCODE(o2_panCameraTo), OPCODE(o2_actorOps), /* 94 */ OPCODE(o5_print), OPCODE(o2_actorFromPos), OPCODE(o5_getRandomNr), OPCODE(o2_clearState02), /* 98 */ OPCODE(o2_restart), OPCODE(o2_doSentence), OPCODE(o5_move), OPCODE(o2_setBitVar), /* 9C */ OPCODE(o5_startSound), OPCODE(o2_ifClassOfIs), OPCODE(o2_walkActorTo), OPCODE(o2_ifState02), /* A0 */ OPCODE(o5_stopObjectCode), OPCODE(o2_putActor), OPCODE(o5_saveLoadGame), OPCODE(o2_getActorY), /* A4 */ OPCODE(o2_loadRoomWithEgo), OPCODE(o2_drawObject), OPCODE(o5_setVarRange), OPCODE(o2_setState04), /* A8 */ OPCODE(o5_notEqualZero), OPCODE(o2_setOwnerOf), OPCODE(o2_addIndirect), OPCODE(o2_switchCostumeSet), /* AC */ OPCODE(o2_drawSentence), OPCODE(o2_putActorInRoom), OPCODE(o2_waitForMessage), OPCODE(o2_ifNotState04), /* B0 */ OPCODE(o2_setBoxFlags), OPCODE(o2_getBitVar), OPCODE(o2_setCameraAt), OPCODE(o2_roomOps), /* B4 */ OPCODE(o5_getDist), OPCODE(o2_findObject), OPCODE(o2_walkActorToObject), OPCODE(o2_setState01), /* B8 */ OPCODE(o2_isLessEqual), OPCODE(o2_doSentence), OPCODE(o2_subtract), OPCODE(o2_waitForActor), /* BC */ OPCODE(o5_stopSound), OPCODE(o2_setActorElevation), OPCODE(o2_walkActorTo), OPCODE(o2_ifNotState01), /* C0 */ OPCODE(o2_endCutscene), OPCODE(o2_putActor), OPCODE(o2_startScript), OPCODE(o2_getActorX), /* C4 */ OPCODE(o2_isLess), OPCODE(o2_drawObject), OPCODE(o5_decrement), OPCODE(o2_clearState08), /* C8 */ OPCODE(o5_isEqual), OPCODE(o5_faceActor), OPCODE(o2_chainScript), OPCODE(o2_setObjPreposition), /* CC */ OPCODE(o5_pseudoRoom), OPCODE(o5_walkActorToActor), OPCODE(o2_putActorAtObject), OPCODE(o2_ifState08), /* D0 */ OPCODE(o2_pickupObject), OPCODE(o5_animateActor), OPCODE(o5_actorFollowCamera), OPCODE(o2_actorOps), /* D4 */ OPCODE(o5_setObjectName), OPCODE(o2_actorFromPos), OPCODE(o5_getActorMoving), OPCODE(o2_setState02), /* D8 */ OPCODE(o5_printEgo), OPCODE(o2_doSentence), OPCODE(o2_add), OPCODE(o2_setBitVar), /* DC */ OPCODE(o2_dummy), OPCODE(o2_ifClassOfIs), OPCODE(o2_walkActorTo), OPCODE(o2_ifNotState02), /* E0 */ OPCODE(o2_cursorCommand), OPCODE(o2_putActor), OPCODE(o2_stopScript), OPCODE(o5_getActorFacing), /* E4 */ OPCODE(o2_loadRoomWithEgo), OPCODE(o2_drawObject), OPCODE(o5_getClosestObjActor), OPCODE(o2_clearState04), /* E8 */ OPCODE(o5_isScriptRunning), OPCODE(o2_setOwnerOf), OPCODE(o2_subIndirect), OPCODE(o2_dummy), /* EC */ OPCODE(o2_getObjPreposition), OPCODE(o2_putActorInRoom), OPCODE(o2_dummy), OPCODE(o2_ifState04), /* F0 */ OPCODE(o2_lights), OPCODE(o5_getActorCostume), OPCODE(o5_loadRoom), OPCODE(o2_roomOps), /* F4 */ OPCODE(o5_getDist), OPCODE(o2_findObject), OPCODE(o2_walkActorToObject), OPCODE(o2_clearState01), /* F8 */ OPCODE(o2_isGreater), OPCODE(o2_doSentence), OPCODE(o2_verbOps), OPCODE(o2_getActorWalkBox), /* FC */ OPCODE(o5_isSoundRunning), OPCODE(o2_setActorElevation), OPCODE(o2_walkActorTo), OPCODE(o2_ifState01) }; _opcodesV2 = opcodes; } #define SENTENCE_SCRIPT 2 #define PARAM_1 0x80 #define PARAM_2 0x40 #define PARAM_3 0x20 void ScummEngine_v2::executeOpcode(byte i) { OpcodeProcV2 op = _opcodesV2[i].proc; (this->*op) (); } const char *ScummEngine_v2::getOpcodeDesc(byte i) { return _opcodesV2[i].desc; } int ScummEngine_v2::getVar() { return readVar(fetchScriptByte()); } void ScummEngine_v2::decodeParseString() { byte buffer[512]; byte *ptr = buffer; byte c; bool insertSpace = false; while ((c = fetchScriptByte())) { insertSpace = (c & 0x80) != 0; c &= 0x7f; if (c < 8) { // Special codes as seen in CHARSET_1 etc. My guess is that they // have a similar function as the corresponding embedded stuff in modern // games. Hence for now we convert them to the modern format. // This might allow us to reuse the existing code. *ptr++ = 0xFF; *ptr++ = c; if (c > 3) { *ptr++ = fetchScriptByte(); *ptr++ = 0; } } else *ptr++ = c; if (insertSpace) *ptr++ = ' '; } *ptr = 0; int textSlot = 0; _string[textSlot].xpos = 0; _string[textSlot].ypos = 0; _string[textSlot].right = _screenWidth - 1; _string[textSlot].center = false; _string[textSlot].overhead = false; if (_game.id == GID_MANIAC && _actorToPrintStrFor == 0xFF) { if (_game.platform == Common::kPlatformC64) { _string[textSlot].color = 14; } else if (_game.features & GF_DEMO) { _string[textSlot].color = (_game.version == 2) ? 15 : 1; } } actorTalk(buffer); } int ScummEngine_v2::readVar(uint var) { if (var >= 14 && var <= 16) var = _scummVars[var]; assertRange(0, var, _numVariables - 1, "variable (reading)"); debugC(DEBUG_VARS, "readvar(%d) = %d", var, _scummVars[var]); return _scummVars[var]; } void ScummEngine_v2::writeVar(uint var, int value) { assertRange(0, var, _numVariables - 1, "variable (writing)"); debugC(DEBUG_VARS, "writeVar(%d) = %d", var, value); if (VAR_CUTSCENEEXIT_KEY != 0xFF && var == VAR_CUTSCENEEXIT_KEY) { // Remap the cutscene exit key in earlier games if (value == 4 || value == 13 || value == 64) value = 27; } _scummVars[var] = value; } void ScummEngine_v2::getResultPosIndirect() { _resultVarNumber = _scummVars[fetchScriptByte()]; } void ScummEngine_v2::getResultPos() { _resultVarNumber = fetchScriptByte(); } void ScummEngine_v2::setStateCommon(byte type) { int obj = getVarOrDirectWord(PARAM_1); putState(obj, getState(obj) | type); } void ScummEngine_v2::clearStateCommon(byte type) { int obj = getVarOrDirectWord(PARAM_1); putState(obj, getState(obj) & ~type); } void ScummEngine_v2::o2_setState08() { int obj = getVarOrDirectWord(PARAM_1); putState(obj, getState(obj) | kObjectState_08); markObjectRectAsDirty(obj); clearDrawObjectQueue(); } void ScummEngine_v2::o2_clearState08() { int obj = getVarOrDirectWord(PARAM_1); putState(obj, getState(obj) & ~kObjectState_08); markObjectRectAsDirty(obj); clearDrawObjectQueue(); } void ScummEngine_v2::o2_setState04() { setStateCommon(kObjectStateLocked); } void ScummEngine_v2::o2_clearState04() { clearStateCommon(kObjectStateLocked); } void ScummEngine_v2::o2_setState02() { setStateCommon(kObjectStateUntouchable); } void ScummEngine_v2::o2_clearState02() { clearStateCommon(kObjectStateUntouchable); } void ScummEngine_v2::o2_setState01() { setStateCommon(kObjectStatePickupable); } void ScummEngine_v2::o2_clearState01() { clearStateCommon(kObjectStatePickupable); } void ScummEngine_v2::o2_assignVarWordIndirect() { getResultPosIndirect(); setResult(getVarOrDirectWord(PARAM_1)); } void ScummEngine_v2::o2_assignVarByte() { getResultPos(); setResult(fetchScriptByte()); } void ScummEngine_v2::o2_setObjPreposition() { int obj = getVarOrDirectWord(PARAM_1); int unk = fetchScriptByte(); if (_game.platform == Common::kPlatformNES) return; if (whereIsObject(obj) != WIO_NOT_FOUND) { // FIXME: this might not work properly the moment we save and restore the game. byte *ptr = getOBCDFromObject(obj) + 12; *ptr &= 0x1F; *ptr |= unk << 5; } } void ScummEngine_v2::o2_getObjPreposition() { getResultPos(); int obj = getVarOrDirectWord(PARAM_1); if (whereIsObject(obj) != WIO_NOT_FOUND) { byte *ptr = getOBCDFromObject(obj) + 12; setResult(*ptr >> 5); } else { setResult(0xFF); } } void ScummEngine_v2::o2_setBitVar() { int var = fetchScriptWord(); byte a = getVarOrDirectByte(PARAM_1); int bit_var = var + a; int bit_offset = bit_var & 0x0f; bit_var >>= 4; if (getVarOrDirectByte(PARAM_2)) _scummVars[bit_var] |= (1 << bit_offset); else _scummVars[bit_var] &= ~(1 << bit_offset); } void ScummEngine_v2::o2_getBitVar() { getResultPos(); int var = fetchScriptWord(); byte a = getVarOrDirectByte(PARAM_1); int bit_var = var + a; int bit_offset = bit_var & 0x0f; bit_var >>= 4; setResult((_scummVars[bit_var] & (1 << bit_offset)) ? 1 : 0); } void ScummEngine_v2::ifStateCommon(byte type) { int obj = getVarOrDirectWord(PARAM_1); if ((getState(obj) & type) != 0) ignoreScriptWord(); else o5_jumpRelative(); } void ScummEngine_v2::ifNotStateCommon(byte type) { int obj = getVarOrDirectWord(PARAM_1); if ((getState(obj) & type) == 0) ignoreScriptWord(); else o5_jumpRelative(); } void ScummEngine_v2::o2_ifState08() { ifStateCommon(kObjectState_08); } void ScummEngine_v2::o2_ifNotState08() { ifNotStateCommon(kObjectState_08); } void ScummEngine_v2::o2_ifState04() { ifStateCommon(kObjectStateLocked); } void ScummEngine_v2::o2_ifNotState04() { ifNotStateCommon(kObjectStateLocked); } void ScummEngine_v2::o2_ifState02() { ifStateCommon(kObjectStateUntouchable); } void ScummEngine_v2::o2_ifNotState02() { ifNotStateCommon(kObjectStateUntouchable); } void ScummEngine_v2::o2_ifState01() { ifStateCommon(kObjectStatePickupable); } void ScummEngine_v2::o2_ifNotState01() { ifNotStateCommon(kObjectStatePickupable); } void ScummEngine_v2::o2_addIndirect() { int a; getResultPosIndirect(); a = getVarOrDirectWord(PARAM_1); _scummVars[_resultVarNumber] += a; } void ScummEngine_v2::o2_subIndirect() { int a; getResultPosIndirect(); a = getVarOrDirectWord(PARAM_1); _scummVars[_resultVarNumber] -= a; } void ScummEngine_v2::o2_add() { int a; getResultPos(); a = getVarOrDirectWord(PARAM_1); _scummVars[_resultVarNumber] += a; } void ScummEngine_v2::o2_subtract() { int a; getResultPos(); a = getVarOrDirectWord(PARAM_1); _scummVars[_resultVarNumber] -= a; } void ScummEngine_v2::o2_waitForActor() { Actor *a = derefActor(getVarOrDirectByte(PARAM_1), "o2_waitForActor"); if (a->_moving) { _scriptPointer -= 2; o5_breakHere(); } } void ScummEngine_v2::o2_waitForMessage() { if (VAR(VAR_HAVE_MSG)) { _scriptPointer--; o5_breakHere(); } } void ScummEngine_v2::o2_waitForSentence() { if (!_sentenceNum && !isScriptInUse(SENTENCE_SCRIPT)) return; _scriptPointer--; o5_breakHere(); } void ScummEngine_v2::o2_actorOps() { int act = getVarOrDirectByte(PARAM_1); int arg = getVarOrDirectByte(PARAM_2); Actor *a; int i; _opcode = fetchScriptByte(); if (act == 0 && _opcode == 5) { // This case happens in the Zak/MM bootscripts, to set the default talk color (9). _string[0].color = arg; return; } a = derefActor(act, "actorOps"); switch (_opcode) { case 1: // SO_SOUND a->_sound[0] = arg; break; case 2: // SO_PALETTE if (_game.version == 1) i = act; else i = fetchScriptByte(); a->setPalette(i, arg); break; case 3: // SO_ACTOR_NAME loadPtrToResource(rtActorName, a->_number, NULL); break; case 4: // SO_COSTUME a->setActorCostume(arg); break; case 5: // SO_TALK_COLOR if (_game.id == GID_MANIAC && _game.version == 2 && (_game.features & GF_DEMO) && arg == 1) a->_talkColor = 15; else a->_talkColor = arg; break; default: error("o2_actorOps: opcode %d not yet supported", _opcode); } } void ScummEngine_v2::o2_restart() { restart(); } void ScummEngine_v2::o2_drawObject() { int obj, idx, i; ObjectData *od; uint16 x, y, w, h; int xpos, ypos; obj = getVarOrDirectWord(PARAM_1); xpos = getVarOrDirectByte(PARAM_2); ypos = getVarOrDirectByte(PARAM_3); 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; while (i--) { 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, getState(_objs[i].obj_nr) & ~kObjectState_08); } putState(obj, getState(od->obj_nr) | kObjectState_08); } void ScummEngine_v2::o2_resourceRoutines() { const ResTypes resTypes[] = { rtNumTypes, // Invalid rtNumTypes, // Invalid rtCostume, rtRoom, rtNumTypes, // Invalid rtScript, rtSound }; int resid = getVarOrDirectByte(PARAM_1); int opcode = fetchScriptByte(); ResTypes type = rtNumTypes; if (0 <= (opcode >> 4) && (opcode >> 4) < (int)ARRAYSIZE(resTypes)) type = resTypes[opcode >> 4]; if ((opcode & 0x0f) == 0 || type == rtNumTypes) return; // HACK V2 Maniac Mansion tries to load an invalid sound resource in demo script. if (_game.id == GID_MANIAC && _game.version == 2 && vm.slot[_currentScript].number == 9 && type == rtSound && resid == 1) return; if ((opcode & 0x0f) == 1) { ensureResourceLoaded(type, resid); } else { if (opcode & 1) _res->lock(type, resid); else _res->unlock(type, resid); } } void ScummEngine_v2::o2_verbOps() { int verb = fetchScriptByte(); int slot, state; switch (verb) { case 0: // SO_DELETE_VERBS slot = getVarOrDirectByte(PARAM_1) + 1; assert(0 < slot && slot < _numVerbs); //printf("o2_verbOps delete slot = %d\n", slot); killVerb(slot); break; case 0xFF: // Verb On/Off verb = fetchScriptByte(); state = fetchScriptByte(); slot = getVerbSlot(verb, 0); //printf("o2_verbOps Verb On/Off: verb = %d, slot = %d, state = %d\n", verb, slot, state); _verbs[slot].curmode = state; break; default: { // New Verb // FIXME: maybe use V12_X_MULTIPLIER here... maybe not. Depends // on whether verbs can be handled separately from objects and // actors or not when it comes to handling coordinates. int x = fetchScriptByte() * 8; int y = fetchScriptByte() * 8; slot = getVarOrDirectByte(PARAM_1) + 1; int prep = fetchScriptByte(); // Only used in V1? // V1 Maniac verbs are relative to the 'verb area' - under the sentence if (_game.platform == Common::kPlatformNES) x += 8; else if ((_game.id == GID_MANIAC) && (_game.version == 1)) y += 8; //printf("o2_verbOps: verb = %d, slot = %d, x = %d, y = %d, unk = %d, name = %s\n", // verb, slot, x, y, prep, _scriptPointer); VerbSlot *vs; assert(0 < slot && slot < _numVerbs); vs = &_verbs[slot]; vs->verbid = verb; if (_game.platform == Common::kPlatformNES) { vs->color = 1; vs->hicolor = 1; vs->dimcolor = 1; } else if (_game.version == 1) { vs->color = (_game.id == GID_MANIAC && (_game.features & GF_DEMO)) ? 16 : 5; vs->hicolor = 7; vs->dimcolor = 11; } else { vs->color = (_game.id == GID_MANIAC && (_game.features & GF_DEMO)) ? 13 : 2; vs->hicolor = 14; vs->dimcolor = 8; } vs->type = kTextVerbType; vs->charset_nr = _string[0]._default.charset; vs->curmode = 1; vs->saveid = 0; vs->key = 0; vs->center = 0; vs->imgindex = 0; vs->prep = prep; vs->curRect.left = x; vs->curRect.top = y; // FIXME: again, this map depends on the language of the game. // E.g. a german keyboard has 'z' and 'y' swapped, while a french // keyboard starts with "awert", etc. const char keyboard[] = { 'q','w','e','r','t', 'a','s','d','f','g', 'z','x','c','v','b' }; if (1 <= slot && slot <= ARRAYSIZE(keyboard)) vs->key = keyboard[slot - 1]; // It follows the verb name loadPtrToResource(rtVerb, slot, NULL); } break; } // Force redraw of the modified verb slot drawVerb(slot, 0); verbMouseOver(0); } void ScummEngine_v2::o2_doSentence() { int a; SentenceTab *st; a = getVarOrDirectByte(PARAM_1); if (a == 0xFC) { _sentenceNum = 0; stopScript(SENTENCE_SCRIPT); return; } if (a == 0xFB) { resetSentence(); return; } assert(_sentenceNum < NUM_SENTENCE); st = &_sentence[_sentenceNum++]; st->verb = a; st->objectA = getVarOrDirectWord(PARAM_2); st->objectB = getVarOrDirectWord(PARAM_3); st->preposition = (st->objectB != 0); st->freezeCount = 0; // Execute or print the sentence _opcode = fetchScriptByte(); switch (_opcode) { case 0: // Do nothing (besides setting up the sentence above) break; case 1: // Execute the sentence _sentenceNum--; if (st->verb == 254) { ScummEngine::stopObjectScript(st->objectA); } else { bool isBackgroundScript; bool isSpecialVerb; if (st->verb != 253 && st->verb != 250) { VAR(VAR_ACTIVE_VERB) = st->verb; VAR(VAR_ACTIVE_OBJECT1) = st->objectA; VAR(VAR_ACTIVE_OBJECT2) = st->objectB; isBackgroundScript = false; isSpecialVerb = false; } else { isBackgroundScript = (st->verb == 250); isSpecialVerb = true; st->verb = 253; } // Check if an object script for this object is already running. If // so, reuse its script slot. Note that we abuse two script flags: // freezeResistant and recursive. We use them to track two // script flags used in V1/V2 games. The main reason we do it this // ugly evil way is to avoid having to introduce yet another save // game revision. int slot = -1; ScriptSlot *ss; int i; ss = vm.slot; for (i = 0; i < NUM_SCRIPT_SLOT; i++, ss++) { if (st->objectA == ss->number && ss->freezeResistant == isBackgroundScript && ss->recursive == isSpecialVerb && (ss->where == WIO_ROOM || ss->where == WIO_INVENTORY || ss->where == WIO_FLOBJECT)) { slot = i; break; } } runObjectScript(st->objectA, st->verb, isBackgroundScript, isSpecialVerb, NULL, slot); } break; case 2: // Print the sentence _sentenceNum--; VAR(VAR_SENTENCE_VERB) = st->verb; VAR(VAR_SENTENCE_OBJECT1) = st->objectA; VAR(VAR_SENTENCE_OBJECT2) = st->objectB; o2_drawSentence(); break; default: error("o2_doSentence: unknown subopcode %d", _opcode); } } void ScummEngine_v2::o2_drawSentence() { Common::Rect sentenceline; const byte *temp; int slot = getVerbSlot(VAR(VAR_SENTENCE_VERB), 0); if (!((_userState & 32) || (_game.platform == Common::kPlatformNES && _userState & 0xe0))) return; if (getResourceAddress(rtVerb, slot)) strcpy(_sentenceBuf, (char*)getResourceAddress(rtVerb, slot)); else return; if (VAR(VAR_SENTENCE_OBJECT1) > 0) { temp = getObjOrActorName(VAR(VAR_SENTENCE_OBJECT1)); if (temp) { strcat(_sentenceBuf, " "); strcat(_sentenceBuf, (const char*)temp); } // For V1 games, the engine must compute the preposition. // In all other Scumm versions, this is done by the sentence script. if ((_game.id == GID_MANIAC && _game.version == 1 && !(_game.platform == Common::kPlatformNES)) && (VAR(VAR_SENTENCE_PREPOSITION) == 0)) { if (_verbs[slot].prep == 0xFF) { byte *ptr = getOBCDFromObject(VAR(VAR_SENTENCE_OBJECT1)); assert(ptr); VAR(VAR_SENTENCE_PREPOSITION) = (*(ptr + 12) >> 5); } else VAR(VAR_SENTENCE_PREPOSITION) = _verbs[slot].prep; } } if (0 < VAR(VAR_SENTENCE_PREPOSITION) && VAR(VAR_SENTENCE_PREPOSITION) <= 4) { // The prepositions, like the fonts, were hard code in the engine. Thus // we have to do that, too, and provde localized versions for all the // languages MM/Zak are available in. const char *prepositions[][5] = { { " ", " in", " with", " on", " to" }, // English { " ", " mit", " mit", " mit", " zu" }, // German { " ", " dans", " avec", " sur", " <" }, // French { " ", " in", " con", " su", " a" }, // Italian { " ", " en", " con", " en", " a" }, // Spanish }; int lang; switch (_language) { case Common::DE_DEU: lang = 1; break; case Common::FR_FRA: lang = 2; break; case Common::IT_ITA: lang = 3; break; case Common::ES_ESP: lang = 4; break; default: lang = 0; // Default to english } if (_game.platform == Common::kPlatformNES) { strcat(_sentenceBuf, (const char *)(getResourceAddress(rtCostume, 78) + VAR(VAR_SENTENCE_PREPOSITION) * 8 + 2)); } else strcat(_sentenceBuf, prepositions[lang][VAR(VAR_SENTENCE_PREPOSITION)]); } if (VAR(VAR_SENTENCE_OBJECT2) > 0) { temp = getObjOrActorName(VAR(VAR_SENTENCE_OBJECT2)); if (temp) { strcat(_sentenceBuf, " "); strcat(_sentenceBuf, (const char*)temp); } } _string[2].charset = 1; _string[2].ypos = virtscr[kVerbVirtScreen].topline; _string[2].xpos = 0; _string[2].right = virtscr[kVerbVirtScreen].w - 1; if (_game.platform == Common::kPlatformNES) { _string[2].xpos = 16; _string[2].color = 0; } else if (_game.version == 1) _string[2].color = 16; else _string[2].color = 13; byte string[80]; char *ptr = _sentenceBuf; int i = 0, len = 0; // Maximum length of printable characters int maxChars = (_game.platform == Common::kPlatformNES) ? 60 : 40; while (*ptr) { if (*ptr != '@') len++; if (len > maxChars) { break; } string[i++] = *ptr++; if (_game.platform == Common::kPlatformNES && len == 30) { string[i++] = 0xFF; string[i++] = 8; } } string[i] = 0; if (_game.platform == Common::kPlatformNES) { sentenceline.top = virtscr[kVerbVirtScreen].topline; sentenceline.bottom = virtscr[kVerbVirtScreen].topline + 16; sentenceline.left = 16; sentenceline.right = virtscr[kVerbVirtScreen].w - 1; } else { sentenceline.top = virtscr[kVerbVirtScreen].topline; sentenceline.bottom = virtscr[kVerbVirtScreen].topline + 8; sentenceline.left = 0; sentenceline.right = virtscr[kVerbVirtScreen].w - 1; } restoreBackground(sentenceline); drawString(2, (byte*)string); } void ScummEngine_v2::o2_ifClassOfIs() { int obj = getVarOrDirectWord(PARAM_1); int clsop = getVarOrDirectByte(PARAM_2); byte *obcd = getOBCDFromObject(obj); if (obcd == 0) { o5_jumpRelative(); return; } byte cls = *(obcd + 6); if ((cls & clsop) != clsop) { o5_jumpRelative(); return; } ignoreScriptWord(); } void ScummEngine_v2::o2_walkActorTo() { int x, y; Actor *a; int act = getVarOrDirectByte(PARAM_1); // WORKAROUND bug #1252606 if (_game.id == GID_ZAK && _game.version == 1 && vm.slot[_currentScript].number == 115 && act == 249) { act = VAR(VAR_EGO); } a = derefActor(act, "o2_walkActorTo"); x = getVarOrDirectByte(PARAM_2) * V12_X_MULTIPLIER; y = getVarOrDirectByte(PARAM_3) * V12_Y_MULTIPLIER; a->startWalkActor(x, y, -1); } void ScummEngine_v2::o2_putActor() { int act = getVarOrDirectByte(PARAM_1); int x, y; Actor *a; a = derefActor(act, "o2_putActor"); x = getVarOrDirectByte(PARAM_2) * V12_X_MULTIPLIER; y = getVarOrDirectByte(PARAM_3) * V12_Y_MULTIPLIER; if (_game.id == GID_MANIAC && _game.version <= 1 && _game.platform != Common::kPlatformNES) a->setFacing(180); a->putActor(x, y); } void ScummEngine_v2::o2_startScript() { int script = getVarOrDirectByte(PARAM_1); if (!_copyProtection) { // The enhanced version of Zak McKracken included in the // SelectWare Classic Collection bundle used CD check instead // of the usual key code check at airports. if ((_game.id == GID_ZAK) && (script == 15) && (_roomResource == 45)) return; } runScript(script, 0, 0, 0); } void ScummEngine_v2::o2_stopScript() { int script; script = getVarOrDirectByte(PARAM_1); if (_game.id == GID_MANIAC && _roomResource == 26 && vm.slot[_currentScript].number == 10001) { // FIXME: Nasty hack for bug #915575 // Don't let the exit script for room 26 stop the script (116), when // switching to the dungeon (script 89) if ((script == 116) && isScriptRunning(89)) return; } if (script == 0) script = vm.slot[_currentScript].number; if (_currentScript != 0 && vm.slot[_currentScript].number == script) stopObjectCode(); else stopScript(script); } void ScummEngine_v2::o2_panCameraTo() { panCameraTo(getVarOrDirectByte(PARAM_1) * V12_X_MULTIPLIER, 0); } void ScummEngine_v2::o2_walkActorToObject() { int obj; Actor *a; a = derefActor(getVarOrDirectByte(PARAM_1), "o2_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); } } void ScummEngine_v2::o2_putActorAtObject() { int obj, x, y; Actor *a; a = derefActor(getVarOrDirectByte(PARAM_1), "o2_putActorAtObject"); obj = getVarOrDirectWord(PARAM_2); if (whereIsObject(obj) != WIO_NOT_FOUND) getObjectXYPos(obj, x, y); else { x = 240; y = 120; } a->putActor(x, y); } void ScummEngine_v2::o2_putActorInRoom() { Actor *a; int act = getVarOrDirectByte(PARAM_1); int room = getVarOrDirectByte(PARAM_2); a = derefActor(act, "o2_putActorInRoom"); a->_room = room; if (!room) { if (_game.id == GID_MANIAC && _game.version <= 1 && _game.platform != Common::kPlatformNES) a->setFacing(180); a->putActor(0, 0, 0); } } void ScummEngine_v2::o2_getActorElevation() { getResultPos(); int act = getVarOrDirectByte(PARAM_1); Actor *a = derefActor(act, "o2_getActorElevation"); setResult(a->getElevation() / V12_Y_MULTIPLIER); } void ScummEngine_v2::o2_setActorElevation() { int act = getVarOrDirectByte(PARAM_1); int elevation = (int8)getVarOrDirectByte(PARAM_2); Actor *a = derefActor(act, "o2_setActorElevation"); a->setElevation(elevation * V12_Y_MULTIPLIER); } void ScummEngine_v2::o2_actorFromPos() { int x, y; getResultPos(); x = getVarOrDirectByte(PARAM_1) * V12_X_MULTIPLIER; y = getVarOrDirectByte(PARAM_2) * V12_Y_MULTIPLIER; setResult(getActorFromPos(x, y)); } void ScummEngine_v2::o2_findObject() { int obj; getResultPos(); int x = getVarOrDirectByte(PARAM_1) * V12_X_MULTIPLIER; int y = getVarOrDirectByte(PARAM_2) * V12_Y_MULTIPLIER; obj = findObject(x, y); if (obj == 0 && (_game.platform == Common::kPlatformNES) && (_userState & 0x40)) { if (_mouseOverBoxV2 >= 0 && _mouseOverBoxV2 < 4) obj = findInventory(VAR(VAR_EGO), _mouseOverBoxV2 + _inventoryOffset + 1); } setResult(obj); } void ScummEngine_v2::o2_getActorX() { int a; getResultPos(); a = getVarOrDirectByte(PARAM_1); setResult(getObjX(a) / V12_X_MULTIPLIER); } void ScummEngine_v2::o2_getActorY() { int a; getResultPos(); a = getVarOrDirectByte(PARAM_1); setResult(getObjY(a) / V12_Y_MULTIPLIER); } void ScummEngine_v2::o2_isGreater() { uint16 a = getVar(); uint16 b = getVarOrDirectWord(PARAM_1); if (b > a) ignoreScriptWord(); else o5_jumpRelative(); } void ScummEngine_v2::o2_isGreaterEqual() { uint16 a = getVar(); uint16 b = getVarOrDirectWord(PARAM_1); if (b >= a) ignoreScriptWord(); else o5_jumpRelative(); } void ScummEngine_v2::o2_isLess() { uint16 a = getVar(); uint16 b = getVarOrDirectWord(PARAM_1); if (b < a) ignoreScriptWord(); else o5_jumpRelative(); } void ScummEngine_v2::o2_isLessEqual() { uint16 a = getVar(); uint16 b = getVarOrDirectWord(PARAM_1); if (b <= a) ignoreScriptWord(); else o5_jumpRelative(); } void ScummEngine_v2::o2_lights() { int a, b, c; a = getVarOrDirectByte(PARAM_1); b = fetchScriptByte(); c = fetchScriptByte(); if (c == 0) { if (_game.id == GID_MANIAC && _game.version == 1 && !(_game.platform == Common::kPlatformNES)) { // Convert older light mode values into // equivalent values of later games. // 0 Darkness // 1 Flashlight // 2 Lighted area if (a == 2) VAR(VAR_CURRENT_LIGHTS) = 11; else if (a == 1) VAR(VAR_CURRENT_LIGHTS) = 4; else VAR(VAR_CURRENT_LIGHTS) = 0; } else VAR(VAR_CURRENT_LIGHTS) = a; } else if (c == 1) { _flashlight.xStrips = a; _flashlight.yStrips = b; } _fullRedraw = true; } void ScummEngine_v2::o2_loadRoomWithEgo() { Actor *a; int obj, room, x, y, x2, y2, dir; obj = getVarOrDirectWord(PARAM_1); room = getVarOrDirectByte(PARAM_2); a = derefActor(VAR(VAR_EGO), "o2_loadRoomWithEgo"); a->putActor(0, 0, room); _egoPositioned = false; x = (int8)fetchScriptByte() * V12_X_MULTIPLIER; y = (int8)fetchScriptByte() * V12_Y_MULTIPLIER; startScene(a->_room, a, obj); getObjectXYPos(obj, x2, y2, dir); a->putActor(x2, y2, _currentRoom); a->setDirection(dir + 180); camera._dest.x = camera._cur.x = a->getPos().x; setCameraAt(a->getPos().x, a->getPos().y); setCameraFollows(a); _fullRedraw = true; resetSentence(); if (x >= 0 && y >= 0) { a->startWalkActor(x, y, -1); } runScript(5, 0, 0, 0); } void ScummEngine_v2::o2_setOwnerOf() { int obj, owner; obj = getVarOrDirectWord(PARAM_1); owner = getVarOrDirectByte(PARAM_2); setOwnerOf(obj, owner); } void ScummEngine_v2::o2_delay() { int delay = fetchScriptByte(); delay |= fetchScriptByte() << 8; delay |= fetchScriptByte() << 16; delay = 0xFFFFFF - delay; vm.slot[_currentScript].delay = delay; vm.slot[_currentScript].status = ssPaused; o5_breakHere(); } void ScummEngine_v2::o2_setBoxFlags() { int a, b; a = getVarOrDirectByte(PARAM_1); b = fetchScriptByte(); setBoxFlags(a, b); } void ScummEngine_v2::o2_setCameraAt() { setCameraAtEx(getVarOrDirectByte(PARAM_1) * V12_X_MULTIPLIER); } void ScummEngine_v2::o2_roomOps() { int a = getVarOrDirectByte(PARAM_1); int b = getVarOrDirectByte(PARAM_2); _opcode = fetchScriptByte(); switch (_opcode & 0x1F) { case 1: // SO_ROOM_SCROLL // FIXME: Use V12_X_MULTIPLIER... ? a *= 8; b *= 8; 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 (_game.version == 1) { // V1 zak needs to know when room color is changed _roomPalette[0] = 255; _roomPalette[1] = a; _roomPalette[2] = b; } else { _roomPalette[b] = a; } _fullRedraw = true; break; } } void ScummEngine_v2::o2_cutscene() { vm.cutSceneData[0] = _userState | (_userPut ? 16 : 0); vm.cutSceneData[1] = (int16)VAR(VAR_CURSORSTATE); vm.cutSceneData[2] = _currentRoom; vm.cutSceneData[3] = camera._mode; VAR(VAR_CURSORSTATE) = 200; // Hide inventory, freeze scripts, hide cursor setUserState(15); _sentenceNum = 0; stopScript(SENTENCE_SCRIPT); resetSentence(); vm.cutScenePtr[0] = 0; } void ScummEngine_v2::o2_endCutscene() { vm.cutSceneStackPointer = 0; VAR(VAR_OVERRIDE) = 0; vm.cutSceneScript[0] = 0; vm.cutScenePtr[0] = 0; VAR(VAR_CURSORSTATE) = vm.cutSceneData[1]; // Reset user state to values before cutscene setUserState(vm.cutSceneData[0] | 7); if ((_game.id == GID_MANIAC) && !(_game.platform == Common::kPlatformNES)) { camera._mode = (byte) vm.cutSceneData[3]; if (camera._mode == kFollowActorCameraMode) { actorFollowCamera(VAR(VAR_EGO)); } else if (vm.cutSceneData[2] != _currentRoom) { startScene(vm.cutSceneData[2], 0, 0); } } else { actorFollowCamera(VAR(VAR_EGO)); } } void ScummEngine_v2::o2_beginOverride() { vm.cutScenePtr[0] = _scriptPointer - _scriptOrgPointer; vm.cutSceneScript[0] = _currentScript; // Skip the jump instruction following the override instruction fetchScriptByte(); fetchScriptWord(); } void ScummEngine_v2::o2_chainScript() { int data = getVarOrDirectByte(PARAM_1); stopScript(vm.slot[_currentScript].number); _currentScript = 0xFF; runScript(data, 0, 0, 0); } void ScummEngine_v2::o2_pickupObject() { int obj = getVarOrDirectWord(PARAM_1); if (obj < 1) { error("pickupObject 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 */ addObjectToInventory(obj, _roomResource); markObjectRectAsDirty(obj); putOwner(obj, VAR(VAR_EGO)); putState(obj, getState(obj) | kObjectState_08 | kObjectStateUntouchable); clearDrawObjectQueue(); runInventoryScript(1); if (_game.platform == Common::kPlatformNES) _sound->addSoundToQueue(51); // play 'pickup' sound } void ScummEngine_v2::o2_cursorCommand() { // TODO: Define the magic numbers uint16 cmd = getVarOrDirectWord(PARAM_1); byte state = cmd >> 8; if (cmd & 0xFF) { VAR(VAR_CURSORSTATE) = cmd & 0xFF; } setUserState(state); } void ScummEngine_v2::setUserState(byte state) { if (state & 4) { // Userface if (_game.platform == Common::kPlatformNES) _userState = (_userState & ~0xE0) | (state & 0xE0); else _userState = state & (32 | 64 | 128); } if (state & 1) { // Freeze if (state & 8) freezeScripts(0); else unfreezeScripts(); } if (state & 2) { // Cursor Show/Hide if (_game.platform == Common::kPlatformNES) _userState = (_userState & ~0x10) | (state & 0x10); if (state & 16) { _userPut = 1; _cursor.state = 1; } else { _userPut = 0; _cursor.state = 0; } } // Hide all verbs and inventory Common::Rect rect; rect.top = virtscr[kVerbVirtScreen].topline; rect.bottom = virtscr[kVerbVirtScreen].topline + 8 * 88; rect.right = virtscr[kVerbVirtScreen].w - 1; if (_game.platform == Common::kPlatformNES) { rect.left = 16; } else { rect.left = 0; } restoreBackground(rect); // Draw all verbs and inventory redrawVerbs(); runInventoryScript(1); } void ScummEngine_v2::o2_getActorWalkBox() { Actor *a; getResultPos(); a = derefActor(getVarOrDirectByte(PARAM_1), "o2_getActorWalkbox"); setResult(a->_walkbox); } void ScummEngine_v2::o2_dummy() { // Opcode 238 is used in maniac and zak but has no purpose if (_opcode != 238) warning("o2_dummy invoked (opcode %d)", _opcode); } void ScummEngine_v2::o2_switchCostumeSet() { // NES version of maniac uses this to switch between the two // groups of costumes it has if (_game.platform == Common::kPlatformNES) NES_loadCostumeSet(fetchScriptByte()); else if (_game.platform == Common::kPlatformC64) fetchScriptByte(); else o2_dummy(); } void ScummEngine_v2::resetSentence() { VAR(VAR_SENTENCE_VERB) = VAR(VAR_BACKUP_VERB); VAR(VAR_SENTENCE_OBJECT1) = 0; VAR(VAR_SENTENCE_OBJECT2) = 0; VAR(VAR_SENTENCE_PREPOSITION) = 0; } void ScummEngine_v2::runInventoryScript(int i) { redrawV2Inventory(); } #undef PARAM_1 #undef PARAM_2 #undef PARAM_3 } // End of namespace Scumm