/* 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. * */ // TODO: Clean up game variable handling and move it to ToltecsEngine #include "common/error.h" #include "graphics/cursorman.h" #include "toltecs/toltecs.h" #include "toltecs/animation.h" #include "toltecs/menu.h" #include "toltecs/movie.h" #include "toltecs/music.h" #include "toltecs/palette.h" #include "toltecs/resource.h" #include "toltecs/script.h" #include "toltecs/segmap.h" #include "toltecs/sound.h" namespace Toltecs { static const VarType varTypes[] = { vtByte, vtWord, vtWord, vtByte, vtWord, // 0 - 4 vtWord, vtWord, vtWord, vtWord, vtWord, // 5 - 9 vtWord, vtWord, vtByte, vtWord, vtWord, // 10 - 14 vtWord, vtWord, vtWord, vtWord, vtWord, // 15 - 19 vtWord, vtWord // 20 - 21 }; static const char *varNames[] = { "mouseDisabled", "mouseY", "mouseX", "mouseButton", "verbLineY", // 0 - 4 "verbLineX", "verbLineWidth", "verbLineCount", "verbLineNum", "talkTextItemNum", // 5 - 9 "talkTextY", "talkTextX", "talkTextFontColor", "cameraY", "cameraX", // 10 - 14 "walkSpeedY", "walkSpeedX", "flag01", "sceneResIndex", "guiHeight", // 15 - 19 "sceneHeight", "sceneWidth" // 20 - 21 }; ScriptInterpreter::ScriptInterpreter(ToltecsEngine *vm) : _vm(vm) { _stack = new byte[kScriptStackSize]; memset(_slots, 0, sizeof(_slots)); _savedSp = 0; _slots[kMaxScriptSlots - 1].size = 1024; _slots[kMaxScriptSlots - 1].data = new byte[_slots[kMaxScriptSlots - 1].size]; setupScriptFunctions(); } ScriptInterpreter::~ScriptInterpreter() { delete[] _stack; for (int i = 0; i < kMaxScriptSlots; i++) delete[] _slots[i].data; for (uint i = 0; i < _scriptFuncs.size(); ++i) delete _scriptFuncs[i]; } typedef Common::Functor0Mem ScriptFunctionF; #define RegisterScriptFunction(x) \ _scriptFuncs.push_back(new ScriptFunctionF(this, &ScriptInterpreter::x)); \ _scriptFuncNames.push_back(#x); void ScriptInterpreter::setupScriptFunctions() { // 0 RegisterScriptFunction(sfNop); RegisterScriptFunction(sfNop); RegisterScriptFunction(sfGetGameVar); RegisterScriptFunction(sfSetGameVar); RegisterScriptFunction(sfUpdateScreen); // 5 RegisterScriptFunction(sfGetRandomNumber); RegisterScriptFunction(sfDrawGuiTextMulti); RegisterScriptFunction(sfUpdateVerbLine); RegisterScriptFunction(sfSetFontColor); RegisterScriptFunction(sfGetTalkTextDuration); // 10 RegisterScriptFunction(sfTalk); RegisterScriptFunction(sfFindPaletteFragment); RegisterScriptFunction(sfClearPaletteFragments); RegisterScriptFunction(sfAddPaletteFragment); RegisterScriptFunction(sfSetDeltaAnimPalette); // 15 RegisterScriptFunction(sfSetUnkPaletteEffect); RegisterScriptFunction(sfBuildColorTransTable); RegisterScriptFunction(sfSetDeltaMainPalette); RegisterScriptFunction(sfLoadScript); RegisterScriptFunction(sfRegisterFont); // 20 RegisterScriptFunction(sfLoadAddPalette); RegisterScriptFunction(sfLoadScene); RegisterScriptFunction(sfSetGuiHeight); RegisterScriptFunction(sfFindMouseInRectIndex1); RegisterScriptFunction(sfFindMouseInRectIndex2); // 25 RegisterScriptFunction(sfDrawGuiImage); RegisterScriptFunction(sfAddAnimatedSpriteNoLoop); RegisterScriptFunction(sfAddAnimatedSprite); RegisterScriptFunction(sfAddStaticSprite); RegisterScriptFunction(sfAddAnimatedSpriteScaled); // 30 RegisterScriptFunction(sfFindPath); RegisterScriptFunction(sfWalk); RegisterScriptFunction(sfScrollCameraUp); RegisterScriptFunction(sfScrollCameraDown); RegisterScriptFunction(sfScrollCameraLeft); // 35 RegisterScriptFunction(sfScrollCameraRight); RegisterScriptFunction(sfScrollCameraUpEx); RegisterScriptFunction(sfScrollCameraDownEx); RegisterScriptFunction(sfScrollCameraLeftEx); RegisterScriptFunction(sfScrollCameraRightEx); // 40 RegisterScriptFunction(sfSetCamera); RegisterScriptFunction(sfGetCameraChanged); RegisterScriptFunction(sfGetRgbModifiertAtPoint); RegisterScriptFunction(sfStartAnim); RegisterScriptFunction(sfAnimNextFrame); // 45 RegisterScriptFunction(sfNop); RegisterScriptFunction(sfGetAnimFrameNumber); RegisterScriptFunction(sfGetAnimStatus); RegisterScriptFunction(sfStartShakeScreen); RegisterScriptFunction(sfStopShakeScreen); // 50 RegisterScriptFunction(sfStartSequence); RegisterScriptFunction(sfEndSequence); RegisterScriptFunction(sfSetSequenceVolume); RegisterScriptFunction(sfPlayPositionalSound); RegisterScriptFunction(sfPlaySound2); // 55 RegisterScriptFunction(sfClearScreen); RegisterScriptFunction(sfNop); RegisterScriptFunction(sfHandleInput); RegisterScriptFunction(sfRunOptionsScreen); RegisterScriptFunction(sfPrecacheSprites); // 60 RegisterScriptFunction(sfPrecacheSounds1); RegisterScriptFunction(sfDeletePrecachedFiles); RegisterScriptFunction(sfPrecacheSounds2); RegisterScriptFunction(sfRestoreStackPtr); RegisterScriptFunction(sfSaveStackPtr); // 65 RegisterScriptFunction(sfPlayMovie); RegisterScriptFunction(sfNop); } void ScriptInterpreter::loadScript(uint resIndex, uint slotIndex) { if (_slots[slotIndex].resIndex && _slots[slotIndex].resIndex != resIndex && _vm->_screen->isTalkTextActive(slotIndex)) { // WORKAROUND: This happens when examining the assembled // pickaxe. It could lead to random characters being printed, // or possibly even crashes, when subtitles are enabled. // // According to johndoe and he said there may be some bug or // missing feature that causes this situation to happen at all, // but he was ok with this workaround for now. warning("Possible script bug: Loading script %d into slot %d that has an active talk text, probably for script %d", resIndex, slotIndex, _slots[slotIndex].resIndex); _vm->_screen->finishTalkTextItem(slotIndex); } delete[] _slots[slotIndex].data; _slots[slotIndex].resIndex = resIndex; Resource *scriptResource = _vm->_res->load(resIndex); _slots[slotIndex].size = scriptResource->size; _slots[slotIndex].data = new byte[_slots[slotIndex].size]; memcpy(_slots[slotIndex].data, scriptResource->data, _slots[slotIndex].size); } void ScriptInterpreter::setMainScript(uint slotIndex) { _switchLocalDataNear = true; _switchLocalDataFar = false; _switchLocalDataToStack = false; _cmpBitTest = false; _regs.reg0 = 0; _regs.reg1 = 0; _regs.reg2 = 0; _regs.reg3 = 0; _regs.reg4 = slotIndex; _regs.reg5 = 0; _regs.reg6 = 0; _regs.sp = 4096; _regs.reg8 = 0; _code = getSlotData(_regs.reg4); } void ScriptInterpreter::runScript() { while (!_vm->shouldQuit()) { if (_vm->_movieSceneFlag) _vm->_mouseButton = 0; if (_vm->_saveLoadRequested != 0) { if (_vm->_saveLoadRequested == 1) _vm->loadGameState(_vm->_saveLoadSlot); else if (_vm->_saveLoadRequested == 2) _vm->saveGameState(_vm->_saveLoadSlot, _vm->_saveLoadDescription); _vm->_saveLoadRequested = 0; } if (_switchLocalDataNear) { _switchLocalDataNear = false; _localData = getSlotData(_regs.reg4); } if (_switchLocalDataFar) { _switchLocalDataFar = false; _localData = getSlotData(_regs.reg5); _switchLocalDataNear = true; } if (_switchLocalDataToStack) { _switchLocalDataToStack = false; _localData = _stack + 2; _switchLocalDataNear = true; } byte opcode = readByte(); execOpcode(opcode); } } byte ScriptInterpreter::readByte() { return *_code++; } int16 ScriptInterpreter::readInt16() { int16 value = READ_LE_UINT16(_code); _code += 2; return value; } void ScriptInterpreter::execOpcode(byte opcode) { int16 ofs; debug(2, "opcode = %d", opcode); switch (opcode) { case 0: { // ok _subCode = _code; byte length = readByte(); if (length == 0) { warning("Opcode length is 0 when calling script function"); return; } debug(2, "length = %d", length); uint16 index = readInt16(); execScriptFunction(index); _code += length - 2; break; } case 1: _regs.reg0 = readInt16(); break; case 2: _regs.reg1 = readInt16(); break; case 3: _regs.reg3 = readInt16(); break; case 4: _regs.reg5 = _regs.reg0; break; case 5: _regs.reg3 = _regs.reg0; break; case 6: _regs.reg1 = _regs.reg0; break; case 7: _regs.reg1 = localRead16(_regs.reg3); break; case 8: localWrite16(_regs.reg3, _regs.reg0); break; case 9: localWrite16(readInt16(), _regs.reg0); break; case 10: localWrite8(readInt16(), _regs.reg0); break; case 11: localWrite16(readInt16(), _regs.reg5); break; case 12: localWrite16(readInt16(), _regs.reg4); break; case 13: localWrite16(readInt16(), _regs.reg3); break; case 14: _regs.reg3 = localRead16(readInt16()); break; case 15: _regs.reg2 = localRead16(_regs.reg1); break; case 16: _regs.reg2 = localRead16(_regs.reg1 + readInt16()); break; case 17: _regs.reg2 = _regs.reg0; break; case 18: _regs.reg0 += readInt16(); break; case 19: localWrite16(_regs.reg3, localRead16(_regs.reg3) + _regs.reg0); break; case 20: _regs.reg0 += _regs.reg2; break; case 21: _regs.reg3 += _regs.sp; break; case 22: _regs.reg1 += _regs.sp; break; case 23: localWrite16(_regs.reg3, localRead16(_regs.reg3) - _regs.reg0); break; case 24: _regs.reg0 /= readInt16(); break; case 25: localWrite16(_regs.reg3, localRead16(_regs.reg3) / _regs.reg0); break; case 26: // NOP break; case 27: _regs.reg0 *= readInt16(); break; case 28: localWrite16(_regs.reg3, localRead16(_regs.reg3) * _regs.reg0); break; case 29: _regs.reg0 *= _regs.reg2; break; case 30: localWrite16(_regs.reg3, localRead16(_regs.reg3) + 1); break; case 31: localWrite16(_regs.reg3, localRead16(_regs.reg3) - 1); break; case 32: _switchLocalDataFar = true; break; case 33: _switchLocalDataToStack = true; break; case 34: pushInt16(_regs.reg0); break; case 35: pushInt16(_regs.reg1); break; case 36: _regs.reg1 = popInt16(); break; case 37: _regs.reg0 = popInt16(); break; case 38: _regs.reg2 = -_regs.reg2; break; case 39: _regs.reg8 = readInt16(); _cmpBitTest = false; break; case 40: _regs.reg8 = _regs.reg0; _cmpBitTest = false; break; case 41: _regs.reg8 = readInt16(); _cmpBitTest = true; break; case 42: _regs.reg8 = _regs.reg0; _cmpBitTest = true; break; case 43: _code = getSlotData(_regs.reg4) + _regs.reg0; break; case 44: _code = getSlotData(_regs.reg5) + _regs.reg0; _regs.reg4 = _regs.reg5; _switchLocalDataNear = true; break; case 45: pushInt16(_code - getSlotData(_regs.reg4)); pushInt16(_regs.reg4); _code = getSlotData(_regs.reg4) + _regs.reg0; break; case 46: pushInt16(_code - getSlotData(_regs.reg4)); pushInt16(_regs.reg4); _code = getSlotData(_regs.reg5) + _regs.reg0; _regs.reg4 = _regs.reg5; _switchLocalDataNear = true; break; case 47: _regs.reg4 = popInt16(); ofs = popInt16(); _code = getSlotData(_regs.reg4) + ofs; _switchLocalDataNear = true; break; case 48: _regs.reg4 = popInt16(); ofs = popInt16(); _code = getSlotData(_regs.reg4) + ofs; _regs.sp += _regs.reg0; _switchLocalDataNear = true; break; case 49: ofs = readByte(); _code += ofs; break; case 50: if (_cmpBitTest) { _regs.reg1 &= _regs.reg8; if (_regs.reg1 == 0) _code += 4; } else { if (_regs.reg1 == _regs.reg8) _code += 4; } _code++; break; case 51: if (_cmpBitTest) { _regs.reg1 &= _regs.reg8; if (_regs.reg1 != 0) _code += 4; } else { if (_regs.reg1 != _regs.reg8) _code += 4; } _code++; break; case 52: if ((uint16)_regs.reg1 >= (uint16)_regs.reg8) _code += 4; _code++; break; case 53: if ((uint16)_regs.reg1 <= (uint16)_regs.reg8) _code += 4; _code++; break; case 54: if ((uint16)_regs.reg1 < (uint16)_regs.reg8) _code += 4; _code++; break; case 55: if ((uint16)_regs.reg1 > (uint16)_regs.reg8) _code += 4; _code++; break; default: // Most likely a script bug. Throw a warning and ignore it. // The original ignores invalid opcodes as well - bug #3604025. warning("Invalid opcode %d", opcode); } } void ScriptInterpreter::execScriptFunction(uint16 index) { if (index >= _scriptFuncs.size()) error("ScriptInterpreter::execScriptFunction() Invalid script function index %d", index); debug(1, "execScriptFunction %s (%d)", _scriptFuncNames[index], index); (*_scriptFuncs[index])(); } int16 ScriptInterpreter::getGameVar(uint variable) { debug(2, "ScriptInterpreter::getGameVar(%d{%s})", variable, varNames[variable]); switch (variable) { case 0: return _vm->_mouseDisabled; case 1: return _vm->_mouseY; case 2: return _vm->_mouseX; case 3: return _vm->_mouseButton; case 4: return _vm->_screen->_verbLineY; case 5: return _vm->_screen->_verbLineX; case 6: return _vm->_screen->_verbLineWidth; case 7: return _vm->_screen->_verbLineCount; case 8: return _vm->_screen->_verbLineNum; case 9: return _vm->_screen->_talkTextItemNum; case 10: return _vm->_screen->_talkTextY; case 11: return _vm->_screen->_talkTextX; case 12: return _vm->_screen->_talkTextFontColor; case 13: return _vm->_cameraY; case 14: return _vm->_cameraX; case 15: return _vm->_walkSpeedY; case 16: return _vm->_walkSpeedX; case 17: return _vm->_flag01; case 18: return _vm->_sceneResIndex; case 19: return _vm->_guiHeight; case 20: return _vm->_sceneHeight; case 21: return _vm->_sceneWidth; default: warning("Getting unimplemented game variable %s (%d)", varNames[variable], variable); return 0; } } void ScriptInterpreter::setGameVar(uint variable, int16 value) { debug(2, "ScriptInterpreter::setGameVar(%d{%s}, %d)", variable, varNames[variable], value); switch (variable) { case 0: _vm->_mouseDisabled = value; CursorMan.showMouse(value == 0); break; case 3: _vm->_mouseButton = value; break; case 4: _vm->_screen->_verbLineY = value; break; case 5: _vm->_screen->_verbLineX = value; break; case 6: _vm->_screen->_verbLineWidth = value; break; case 7: _vm->_screen->_verbLineCount = value; break; case 8: _vm->_screen->_verbLineNum = value; break; case 9: _vm->_screen->_talkTextItemNum = value; break; case 10: _vm->_screen->_talkTextY = value; break; case 11: _vm->_screen->_talkTextX = value; break; case 12: _vm->_screen->_talkTextFontColor = value; break; case 13: _vm->_cameraY = value; break; case 14: _vm->_cameraX = value; break; case 15: _vm->_walkSpeedY = value; break; case 16: _vm->_walkSpeedX = value; break; case 17: _vm->_flag01 = value != 0; break; case 18: _vm->_sceneResIndex = value; break; case 19: _vm->_guiHeight = value; break; case 20: _vm->_sceneHeight = value; break; case 21: _vm->_sceneWidth = value; break; case 1: case 2: default: warning("Setting unimplemented game variable %s (%d) to %d", varNames[variable], variable, value); break; } } byte ScriptInterpreter::arg8(int16 offset) { return _subCode[offset]; } int16 ScriptInterpreter::arg16(int16 offset) { return READ_LE_UINT16(&_subCode[offset]); } void ScriptInterpreter::pushInt16(int16 value) { WRITE_LE_UINT16(_stack + _regs.sp, value); _regs.sp -= 2; } int16 ScriptInterpreter::popInt16() { _regs.sp += 2; return READ_LE_UINT16(_stack + _regs.sp); } void ScriptInterpreter::localWrite8(int16 offset, byte value) { //debug(2, "localWrite8(%d, %d)", offset, value); _localData[offset] = value; } byte ScriptInterpreter::localRead8(int16 offset) { //debug(2, "localRead8(%d) -> %d", offset, _localData[offset]); return _localData[offset]; } void ScriptInterpreter::localWrite16(int16 offset, int16 value) { //debug(2, "localWrite16(%d, %d)", offset, value); WRITE_LE_UINT16(&_localData[offset], value); } int16 ScriptInterpreter::localRead16(int16 offset) { //debug(2, "localRead16(%d) -> %d", offset, (int16)READ_LE_UINT16(&_localData[offset])); return (int16)READ_LE_UINT16(&_localData[offset]); } byte *ScriptInterpreter::localPtr(int16 offset) { //debug(2, "localPtr(%d)", offset); return &_localData[offset]; } void ScriptInterpreter::saveState(Common::WriteStream *out) { // Save registers out->writeUint16LE(_regs.reg0); out->writeUint16LE(_regs.reg1); out->writeUint16LE(_regs.reg2); out->writeUint16LE(_regs.reg3); out->writeUint16LE(_regs.reg4); out->writeUint16LE(_regs.reg5); out->writeUint16LE(_regs.reg6); out->writeUint16LE(_regs.sp); out->writeUint16LE(_regs.reg8); // Save slots for (int slot = 0; slot < kMaxScriptSlots; slot++) { out->writeUint32LE(_slots[slot].size); out->writeUint16LE(_slots[slot].resIndex); if (_slots[slot].size > 0) out->write(_slots[slot].data, _slots[slot].size); } // Save stack out->write(_stack, kScriptStackSize); out->writeUint16LE(_savedSp); // Save IP out->writeUint16LE((int16)(_code - getSlotData(_regs.reg4))); } void ScriptInterpreter::loadState(Common::ReadStream *in) { // Load registers _regs.reg0 = in->readUint16LE(); _regs.reg1 = in->readUint16LE(); _regs.reg2 = in->readUint16LE(); _regs.reg3 = in->readUint16LE(); _regs.reg4 = in->readUint16LE(); _regs.reg5 = in->readUint16LE(); _regs.reg6 = in->readUint16LE(); _regs.sp = in->readUint16LE(); _regs.reg8 = in->readUint16LE(); // Load slots for (int slot = 0; slot < kMaxScriptSlots; slot++) { _slots[slot].size = in->readUint32LE(); _slots[slot].resIndex = in->readUint16LE(); _slots[slot].data = NULL; if (_slots[slot].size > 0) { _slots[slot].data = new byte[_slots[slot].size]; in->read(_slots[slot].data, _slots[slot].size); } } // Load stack in->read(_stack, kScriptStackSize); _savedSp = in->readUint16LE(); // Load IP _code = getSlotData(_regs.reg4) + in->readUint16LE(); } void ScriptInterpreter::sfNop() { // NOP } void ScriptInterpreter::sfGetGameVar() { int16 value = getGameVar(arg16(3)); localWrite16(arg16(5), value); } void ScriptInterpreter::sfSetGameVar() { int16 varIndex = arg16(3); assert(varIndex <= 21); VarType varType = varTypes[varIndex]; int16 value = 0; if (varType == vtByte) value = arg8(5); else if (varType == vtWord) value = arg16(5); setGameVar(varIndex, value); } void ScriptInterpreter::sfUpdateScreen() { _vm->updateScreen(); } void ScriptInterpreter::sfGetRandomNumber() { localWrite16(arg16(5), _vm->_rnd->getRandomNumber(arg16(3) - 1)); } void ScriptInterpreter::sfDrawGuiTextMulti() { _vm->_screen->drawGuiTextMulti((byte *)localPtr(arg16(3))); } void ScriptInterpreter::sfUpdateVerbLine() { _vm->_screen->updateVerbLine(arg16(5), arg16(3)); } void ScriptInterpreter::sfSetFontColor() { _vm->_screen->_fontColor1 = 0; _vm->_screen->_fontColor2 = arg8(3); } void ScriptInterpreter::sfGetTalkTextDuration() { localWrite16(arg16(3), _vm->_screen->getTalkTextDuration()); } void ScriptInterpreter::sfTalk() { _vm->talk(arg16(5), arg16(3)); } void ScriptInterpreter::sfFindPaletteFragment() { localWrite16(arg16(5), _vm->_palette->findFragment(arg16(3))); } void ScriptInterpreter::sfClearPaletteFragments() { _vm->_palette->clearFragments(); } void ScriptInterpreter::sfAddPaletteFragment() { _vm->_palette->addFragment(arg16(3), arg16(5)); } void ScriptInterpreter::sfSetDeltaAnimPalette() { _vm->_palette->setDeltaPalette(_vm->_palette->getAnimPalette(), arg8(6), (char)arg8(5), arg8(4), arg8(3)); } void ScriptInterpreter::sfSetUnkPaletteEffect() { error("ScriptInterpreter::sfSetUnkPaletteEffect called"); // unused } void ScriptInterpreter::sfBuildColorTransTable() { _vm->_palette->buildColorTransTable(arg8(4), (char)arg8(3), arg8(5)); } void ScriptInterpreter::sfSetDeltaMainPalette() { _vm->_palette->setDeltaPalette(_vm->_palette->getMainPalette(), arg8(6), (char)arg8(5), arg8(4), arg8(3)); } void ScriptInterpreter::sfLoadScript() { int16 codeOfs = _code - getSlotData(_regs.reg4); loadScript(arg16(4), arg8(3)); _code = getSlotData(_regs.reg4) + codeOfs; _switchLocalDataNear = true; } void ScriptInterpreter::sfRegisterFont() { _vm->_screen->registerFont(arg8(3), arg16(4)); } void ScriptInterpreter::sfLoadAddPalette() { _vm->_palette->loadAddPalette(arg16(4), arg8(3)); } void ScriptInterpreter::sfLoadScene() { if (arg8(3) == 0) { // FIXME: Originally, this was stopSpeech(). However, we need to stop // ALL sounds here (including sound effects and background sounds) // before purgeCache() is called, otherwise the sound buffers will be // invalidated. This is apparent when moving from a scene that has // background sounds (such as the canyon at the beginning), to another // one that doesn't (such as the map), and does not stop the sounds // already playing. In this case, the engine will either crash or // garbage will be heard through the speakers. // TODO: We should either move purgeCache() elsewhere, or monitor // which resources are still used before purging the cache. _vm->_sound->stopAll(); _vm->_res->purgeCache(); _vm->loadScene(arg16(4)); } else { _vm->_screen->loadMouseCursor(arg16(4)); } } void ScriptInterpreter::sfSetGuiHeight() { _vm->setGuiHeight(arg8(3)); } void ScriptInterpreter::sfFindMouseInRectIndex1() { int16 index = -1; if (_vm->_mouseY < _vm->_cameraHeight) { int16 slotIndex = arg16(5); index = _vm->findRectAtPoint(getSlotData(slotIndex) + arg16(3), _vm->_mouseX + _vm->_cameraX, _vm->_mouseY + _vm->_cameraY, arg16(11) + 1, arg16(7), getSlotData(slotIndex) + _slots[slotIndex].size); } localWrite16(arg16(9), index); } void ScriptInterpreter::sfFindMouseInRectIndex2() { int16 index = -1; if (_vm->_sceneResIndex != 0) { if (_vm->_mouseY < _vm->_cameraHeight) { int16 slotIndex = arg16(5); index = _vm->findRectAtPoint(getSlotData(slotIndex) + arg16(3), _vm->_mouseX + _vm->_cameraX, _vm->_mouseY + _vm->_cameraY, 0, arg16(7), getSlotData(slotIndex) + _slots[slotIndex].size); } } localWrite16(arg16(9), index); } void ScriptInterpreter::sfDrawGuiImage() { _vm->_screen->drawGuiImage(arg16(5), arg16(3), arg16(7)); } void ScriptInterpreter::sfAddAnimatedSpriteNoLoop() { _vm->_screen->addAnimatedSprite(arg16(5), arg16(3), arg16(7), (byte *)localPtr(0), (int16 *)localPtr(arg16(9)), false, 2); } void ScriptInterpreter::sfAddAnimatedSprite() { _vm->_screen->addAnimatedSprite(arg16(5), arg16(3), arg16(7), (byte *)localPtr(0), (int16 *)localPtr(arg16(9)), true, 2); } void ScriptInterpreter::sfAddStaticSprite() { _vm->_screen->addStaticSprite(_subCode + 3); } void ScriptInterpreter::sfAddAnimatedSpriteScaled() { _vm->_screen->addAnimatedSprite(arg16(5), arg16(3), arg16(7), (byte *)localPtr(0), (int16 *)localPtr(arg16(9)), true, 1); } void ScriptInterpreter::sfFindPath() { _vm->_segmap->findPath((int16 *)(getSlotData(arg16(13)) + arg16(11)), arg16(9), arg16(7), arg16(5), arg16(3)); } void ScriptInterpreter::sfWalk() { _vm->walk(getSlotData(arg16(5)) + arg16(3)); } void ScriptInterpreter::sfScrollCameraUp() { _vm->scrollCameraUp(4); } void ScriptInterpreter::sfScrollCameraDown() { _vm->scrollCameraDown(4); } void ScriptInterpreter::sfScrollCameraLeft() { _vm->scrollCameraLeft(4); } void ScriptInterpreter::sfScrollCameraRight() { _vm->scrollCameraRight(4); } void ScriptInterpreter::sfScrollCameraUpEx() { _vm->scrollCameraUp(arg16(3)); } void ScriptInterpreter::sfScrollCameraDownEx() { _vm->scrollCameraDown(arg16(3)); } void ScriptInterpreter::sfScrollCameraLeftEx() { _vm->scrollCameraLeft(arg16(3)); } void ScriptInterpreter::sfScrollCameraRightEx() { _vm->scrollCameraRight(arg16(3)); } void ScriptInterpreter::sfSetCamera() { _vm->setCamera(arg16(5), arg16(3)); } void ScriptInterpreter::sfGetCameraChanged() { localWrite16(arg16(3), _vm->getCameraChanged() ? 1 : 0); } void ScriptInterpreter::sfGetRgbModifiertAtPoint() { byte *rgb = getSlotData(arg16(11)) + arg16(9); _vm->_segmap->getRgbModifiertAtPoint(arg16(5), arg16(3), arg16(7), rgb[0], rgb[1], rgb[2]); } void ScriptInterpreter::sfStartAnim() { _vm->_anim->start(arg16(3)); } void ScriptInterpreter::sfAnimNextFrame() { _vm->_anim->nextFrame(); } void ScriptInterpreter::sfGetAnimFrameNumber() { localWrite16(arg16(3), _vm->_anim->getFrameNumber()); } void ScriptInterpreter::sfGetAnimStatus() { int16 status = _vm->_anim->getStatus(); if (status == 0 || status == 1) { // TODO mov screenFlag01, 0 } localWrite16(arg16(3), status); } void ScriptInterpreter::sfStartShakeScreen() { _vm->_screen->startShakeScreen(arg16(3)); } void ScriptInterpreter::sfStopShakeScreen() { _vm->_screen->stopShakeScreen(); } void ScriptInterpreter::sfStartSequence() { int16 sequenceResIndex = arg16(3); debug(1, "ScriptInterpreter::sfStartSequence(%d)", sequenceResIndex); if (sequenceResIndex >= 0) { //_vm->_arc->dump(sequenceResIndex, "music"); // DEBUG: Dump music so we know what's in there _vm->_music->playSequence(sequenceResIndex); } } void ScriptInterpreter::sfEndSequence() { _vm->_music->stopSequence(); } void ScriptInterpreter::sfSetSequenceVolume() { // TODO //debug("ScriptInterpreter::sfSetSequenceVolume"); } void ScriptInterpreter::sfPlayPositionalSound() { _vm->_sound->playSoundAtPos(arg16(3), arg16(9), arg16(7)); } void ScriptInterpreter::sfPlaySound2() { _vm->_sound->playSound(arg16(3), arg16(5), arg16(7)); } void ScriptInterpreter::sfClearScreen() { // TODO: Occurs on every scene change, but seems unneeded //debug("ScriptInterpreter::sfClearScreen"); } void ScriptInterpreter::sfHandleInput() { int16 varOfs = arg16(3); int16 keyCode = 0; if (_vm->_rightButtonDown) { keyCode = 1; } else { // Convert keyboard scancode to IBM PC scancode. // Only scancodes known to be used (so far) are converted. switch (_vm->_keyState.keycode) { case Common::KEYCODE_ESCAPE: keyCode = 1; break; case Common::KEYCODE_F10: keyCode = 68; break; default: break; } } localWrite16(varOfs, keyCode); } void ScriptInterpreter::sfRunOptionsScreen() { _vm->showMenu(kMenuIdMain); } /** * NOTE: The opcodes sfPrecacheSprites, sfPrecacheSounds1, sfPrecacheSounds2 and * sfDeletePrecachedFiles were used by the original engine to handle precaching * of data so the game doesn't stall while playing (due to the slow speed of * CD-Drives back then). This is not needed in ScummVM since all supported * systems are fast enough to load data in-game. */ void ScriptInterpreter::sfPrecacheSprites() { // See note above } void ScriptInterpreter::sfPrecacheSounds1() { // See note above } void ScriptInterpreter::sfDeletePrecachedFiles() { // See note above } void ScriptInterpreter::sfPrecacheSounds2() { // See note above } void ScriptInterpreter::sfRestoreStackPtr() { _regs.sp = _savedSp; } void ScriptInterpreter::sfSaveStackPtr() { _savedSp = _regs.sp; } void ScriptInterpreter::sfPlayMovie() { CursorMan.showMouse(false); _vm->_moviePlayer->playMovie(arg16(3)); CursorMan.showMouse(true); } } // End of namespace Toltecs