/* 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. * * $URL$ * $Id$ * */ #include "mohawk/graphics.h" #include "mohawk/riven.h" #include "mohawk/riven_external.h" #include "mohawk/riven_scripts.h" #include "mohawk/sound.h" #include "mohawk/video.h" #include "common/stream.h" #include "graphics/cursorman.h" namespace Mohawk { RivenScript::RivenScript(MohawkEngine_Riven *vm, Common::SeekableReadStream *stream, uint16 scriptType, uint16 parentStack, uint16 parentCard) : _vm(vm), _stream(stream), _scriptType(scriptType), _parentStack(parentStack), _parentCard(parentCard) { setupOpcodes(); _isRunning = _continueRunning = false; } RivenScript::~RivenScript() { delete _stream; } uint32 RivenScript::calculateCommandSize(Common::SeekableReadStream* script) { uint16 command = script->readUint16BE(); uint32 commandSize = 2; if (command == 8) { if (script->readUint16BE() != 2) // Arg count? warning ("if-then-else unknown value is not 2"); script->readUint16BE(); // variable to check against uint16 logicBlockCount = script->readUint16BE(); // number of logic blocks commandSize += 6; // 2 + variable + logicBlocks for (uint16 i = 0; i < logicBlockCount; i++) { script->readUint16BE(); // Block variable uint16 logicBlockLength = script->readUint16BE(); commandSize += 4; for (uint16 j = 0; j < logicBlockLength; j++) commandSize += calculateCommandSize(script); } } else { uint16 argCount = script->readUint16BE(); commandSize += 2; for (uint16 i = 0; i < argCount; i++) { script->readUint16BE(); commandSize += 2; } } return commandSize; } uint32 RivenScript::calculateScriptSize(Common::SeekableReadStream* script) { uint32 oldPos = script->pos(); uint16 commandCount = script->readUint16BE(); uint16 scriptSize = 2; // 2 for command count for (uint16 i = 0; i < commandCount; i++) scriptSize += calculateCommandSize(script); script->seek(oldPos); return scriptSize; } #define OPCODE(x) { &RivenScript::x, #x } void RivenScript::setupOpcodes() { static const RivenOpcode riven_opcodes[] = { // 0x00 (0 decimal) OPCODE(empty), OPCODE(drawBitmap), OPCODE(switchCard), OPCODE(playScriptSLST), // 0x04 (4 decimal) OPCODE(playSound), OPCODE(empty), // Empty OPCODE(empty), // Complex animation (not used) OPCODE(setVariable), // 0x08 (8 decimal) OPCODE(mohawkSwitch), OPCODE(enableHotspot), OPCODE(disableHotspot), OPCODE(empty), // Empty // 0x0C (12 decimal) OPCODE(clearSLST), OPCODE(changeCursor), OPCODE(delay), OPCODE(empty), // Empty // 0x10 (16 decimal) OPCODE(empty), // Empty OPCODE(runExternalCommand), OPCODE(transition), OPCODE(refreshCard), // 0x14 (20 decimal) OPCODE(disableScreenUpdate), OPCODE(enableScreenUpdate), OPCODE(empty), // Empty OPCODE(empty), // Empty // 0x18 (24 decimal) OPCODE(incrementVariable), OPCODE(empty), // Empty OPCODE(empty), // Empty OPCODE(changeStack), // 0x1C (28 decimal) OPCODE(disableMovie), OPCODE(disableAllMovies), OPCODE(empty), // Set movie rate (not used) OPCODE(enableMovie), // 0x20 (32 decimal) OPCODE(playMovie), OPCODE(playMovieBg), OPCODE(stopMovie), OPCODE(empty), // Start a water effect (not used) // 0x24 (36 decimal) OPCODE(unk_36), // Unknown OPCODE(fadeAmbientSounds), OPCODE(complexPlayMovie), OPCODE(activatePLST), // 0x28 (40 decimal) OPCODE(activateSLST), OPCODE(activateMLSTAndPlay), OPCODE(empty), // Empty OPCODE(activateBLST), // 0x2C (44 decimal) OPCODE(activateFLST), OPCODE(zipMode), OPCODE(activateMLST), OPCODE(empty) // Activate an SLST with a volume parameter (not used) }; _opcodes = riven_opcodes; } static void printTabs(byte tabs) { for (byte i = 0; i < tabs; i++) debugN("\t"); } void RivenScript::dumpScript(Common::StringArray varNames, Common::StringArray xNames, byte tabs) { if (_stream->pos() != 0) _stream->seek(0); printTabs(tabs); debugN("Stream Type %d:\n", _scriptType); dumpCommands(varNames, xNames, tabs + 1); } void RivenScript::dumpCommands(Common::StringArray varNames, Common::StringArray xNames, byte tabs) { uint16 commandCount = _stream->readUint16BE(); for (uint16 i = 0; i < commandCount; i++) { uint16 command = _stream->readUint16BE(); if (command == 8) { // "Switch" Statement if (_stream->readUint16BE() != 2) warning ("if-then-else unknown value is not 2"); uint16 var = _stream->readUint16BE(); printTabs(tabs); debugN("switch (%s) {\n", varNames[var].c_str()); uint16 logicBlockCount = _stream->readUint16BE(); for (uint16 j = 0; j < logicBlockCount; j++) { uint16 varCheck = _stream->readUint16BE(); printTabs(tabs + 1); if (varCheck == 0xFFFF) debugN("default:\n"); else debugN("case %d:\n", varCheck); dumpCommands(varNames, xNames, tabs + 2); printTabs(tabs + 2); debugN("break;\n"); } printTabs(tabs); debugN("}\n"); } else if (command == 7) { // Use the variable name _stream->readUint16BE(); // Skip the opcode var count printTabs(tabs); uint16 var = _stream->readUint16BE(); debugN("%s = %d;\n", varNames[var].c_str(), _stream->readUint16BE()); } else if (command == 17) { // Use the external command name _stream->readUint16BE(); // Skip the opcode var count printTabs(tabs); debugN("%s(", xNames[_stream->readUint16BE()].c_str()); uint16 varCount = _stream->readUint16BE(); for (uint16 j = 0; j < varCount; j++) { debugN("%d", _stream->readUint16BE()); if (j != varCount - 1) debugN(", "); } debugN(");\n"); } else if (command == 24) { // Use the variable name _stream->readUint16BE(); // Skip the opcode var count printTabs(tabs); uint16 var = _stream->readUint16BE(); debugN("%s += %d;\n", varNames[var].c_str(), _stream->readUint16BE()); } else { printTabs(tabs); uint16 varCount = _stream->readUint16BE(); debugN("%s(", _opcodes[command].desc); for (uint16 j = 0; j < varCount; j++) { debugN("%d", _stream->readUint16BE()); if (j != varCount - 1) debugN(", "); } debugN(");\n"); } } } void RivenScript::runScript() { _isRunning = _continueRunning = true; if (_stream->pos() != 0) _stream->seek(0); processCommands(true); _isRunning = false; } void RivenScript::processCommands(bool runCommands) { bool anotherBlockEvaluated = false; bool runBlock = true; uint16 commandCount = _stream->readUint16BE(); for (uint16 j = 0; j < commandCount && !_vm->shouldQuit() && _stream->pos() < _stream->size() && _continueRunning; j++) { uint16 command = _stream->readUint16BE(); if (command == 8) { // Command 8 contains a conditional branch, similar to switch statements if (_stream->readUint16BE() != 2) warning("if-then-else unknown value is not 2"); uint16 var = _stream->readUint16BE(); // variable to check against uint16 logicBlockCount = _stream->readUint16BE(); // number of logic blocks for (uint16 k = 0; k < logicBlockCount; k++) { uint16 checkValue = _stream->readUint16BE(); // variable for this logic block // Run the following block if the block's variable is equal to the variable to check against // Don't run it if the parent block is not executed // And don't run it if another block has already evaluated to true (needed for the default case) runBlock = (*_vm->getLocalVar(var) == checkValue || checkValue == 0xffff) && runCommands && !anotherBlockEvaluated; processCommands(runBlock); if (runBlock) anotherBlockEvaluated = true; } anotherBlockEvaluated = false; } else { uint16 argCount = _stream->readUint16BE(); uint16 *argValues = new uint16[argCount]; for (uint16 k = 0; k < argCount; k++) argValues[k] = _stream->readUint16BE(); if (runCommands) { debug (4, "Running opcode %04x, argument count %d", command, argCount); (this->*(_opcodes[command].proc)) (command, argCount, argValues); } delete[] argValues; } } } //////////////////////////////// // Opcodes //////////////////////////////// // Command 1: draw tBMP resource (tbmp_id, left, top, right, bottom, u0, u1, u2, u3) void RivenScript::drawBitmap(uint16 op, uint16 argc, uint16 *argv) { if (argc < 5) // Copy the image to the whole screen, ignoring the rest of the parameters _vm->_gfx->copyImageToScreen(argv[0], 0, 0, 608, 392); else // Copy the image to a certain part of the screen _vm->_gfx->copyImageToScreen(argv[0], argv[1], argv[2], argv[3], argv[4]); // Now, update the screen _vm->_gfx->updateScreen(); } // Command 2: go to card (card id) void RivenScript::switchCard(uint16 op, uint16 argc, uint16 *argv) { _vm->changeToCard(argv[0]); // WORKAROUND: If we changed card on a mouse down event, // we want to ignore the next mouse up event so we don't // change card when lifting the mouse on the next card. if (_scriptType == kMouseDownScript) _vm->ignoreNextMouseUp(); } // Command 3: play an SLST from the script void RivenScript::playScriptSLST(uint16 op, uint16 argc, uint16 *argv) { SLSTRecord slstRecord; int offset = 0, j = 0; slstRecord.index = 0; // not set by the scripts, so we set it to 0 slstRecord.sound_count = argv[0]; slstRecord.sound_ids = new uint16[slstRecord.sound_count]; offset = slstRecord.sound_count; for (j = 0; j < slstRecord.sound_count; j++) slstRecord.sound_ids[j] = argv[offset++]; slstRecord.fade_flags = argv[offset++]; slstRecord.loop = argv[offset++]; slstRecord.global_volume = argv[offset++]; slstRecord.u0 = argv[offset++]; slstRecord.u1 = argv[offset++]; slstRecord.volumes = new uint16[slstRecord.sound_count]; slstRecord.balances = new int16[slstRecord.sound_count]; slstRecord.u2 = new uint16[slstRecord.sound_count]; for (j = 0; j < slstRecord.sound_count; j++) slstRecord.volumes[j] = argv[offset++]; for (j = 0; j < slstRecord.sound_count; j++) slstRecord.balances[j] = argv[offset++]; // negative = left, 0 = center, positive = right for (j = 0; j < slstRecord.sound_count; j++) slstRecord.u2[j] = argv[offset++]; // Unknown // Play the requested sound list _vm->_sound->playSLST(slstRecord); _vm->_activatedSLST = true; delete[] slstRecord.sound_ids; delete[] slstRecord.volumes; delete[] slstRecord.balances; delete[] slstRecord.u2; } // Command 4: play local tWAV resource (twav_id, volume, block) void RivenScript::playSound(uint16 op, uint16 argc, uint16 *argv) { byte volume = Sound::convertRivenVolume(argv[1]); if (argv[2] == 1) _vm->_sound->playSoundBlocking(argv[0], volume); else _vm->_sound->playSound(argv[0], volume); } // Command 7: set variable value (variable, value) void RivenScript::setVariable(uint16 op, uint16 argc, uint16 *argv) { debug(2, "Setting variable %d to %d", argv[0], argv[1]); *_vm->getLocalVar(argv[0]) = argv[1]; } // Command 8: conditional branch void RivenScript::mohawkSwitch(uint16 op, uint16 argc, uint16 *argv) { // dummy function, this opcode does logic checking in processCommands() } // Command 9: enable hotspot (blst_id) void RivenScript::enableHotspot(uint16 op, uint16 argc, uint16 *argv) { for (uint16 i = 0; i < _vm->getHotspotCount(); i++) { if (_vm->_hotspots[i].blstID == argv[0]) { debug(2, "Enabling hotspot with BLST ID %d", argv[0]); _vm->_hotspots[i].enabled = true; } } } // Command 10: disable hotspot (blst_id) void RivenScript::disableHotspot(uint16 op, uint16 argc, uint16 *argv) { for (uint16 i = 0; i < _vm->getHotspotCount(); i++) { if (_vm->_hotspots[i].blstID == argv[0]) { debug(2, "Disabling hotspot with BLST ID %d", argv[0]); _vm->_hotspots[i].enabled = false; } } } // Command 12: clear slst records (flags) void RivenScript::clearSLST(uint16 op, uint16 argc, uint16 *argv) { warning ("STUB: clearSLST: Fade Out = %s, Fade In = %s", ((argv[0] & 1) != 0) ? "Yes" : "No", ((argv[0] & 2) != 0) ? "Yes" : "No"); //_vm->_sound->clearAllSLST(); } // Command 13: set mouse cursor (cursor_id) void RivenScript::changeCursor(uint16 op, uint16 argc, uint16 *argv) { debug(2, "Change to cursor %d", argv[0]); _vm->_gfx->changeCursor(argv[0]); } // Command 14: pause script execution (delay in ms, u1) void RivenScript::delay(uint16 op, uint16 argc, uint16 *argv) { debug(2, "Delay %dms", argv[0]); if (argv[0] > 0) _vm->delayAndUpdate(argv[0]); } // Command 17: call external command void RivenScript::runExternalCommand(uint16 op, uint16 argc, uint16 *argv) { _vm->_externalScriptHandler->runCommand(argc, argv); } // Command 18: transition // Note that this opcode has 1 or 5 parameters, depending on parameter 0 // Parameter 0: transition type // Parameters 1-4: transition rectangle void RivenScript::transition(uint16 op, uint16 argc, uint16 *argv) { if (argc == 1) _vm->_gfx->scheduleTransition(argv[0]); else _vm->_gfx->scheduleTransition(argv[0], Common::Rect(argv[1], argv[2], argv[3], argv[4])); } // Command 19: reload card void RivenScript::refreshCard(uint16 op, uint16 argc, uint16 *argv) { debug(2, "Refreshing card"); _vm->refreshCard(); } // Command 20: disable screen update void RivenScript::disableScreenUpdate(uint16 op, uint16 argc, uint16 *argv) { debug(2, "Screen update disabled"); _vm->_gfx->_updatesEnabled = false; } // Command 21: enable screen update void RivenScript::enableScreenUpdate(uint16 op, uint16 argc, uint16 *argv) { debug(2, "Screen update enabled"); _vm->_gfx->_updatesEnabled = true; _vm->_gfx->updateScreen(); } // Command 24: increment variable (variable, value) void RivenScript::incrementVariable(uint16 op, uint16 argc, uint16 *argv) { uint32 *localVar = _vm->getLocalVar(argv[0]); *localVar += argv[1]; debug (2, "Incrementing variable %d by %d, variable now is equal to %d", argv[0], argv[1], *localVar); } // Command 27: go to stack (stack_name code_hi code_lo) void RivenScript::changeStack(uint16 op, uint16 argc, uint16 *argv) { Common::String stackName = _vm->getName(StackNames, argv[0]); int8 index = -1; for (byte i = 0; i < 8; i++) if (!scumm_stricmp(_vm->getStackName(i).c_str(), stackName.c_str())) { index = i; break; } if (index == -1) error ("\'%s\' is not a stack name!", stackName.c_str()); _vm->changeToStack(index); uint32 rmapCode = (argv[1] << 16) + argv[2]; uint16 cardID = _vm->matchRMAPToCard(rmapCode); _vm->changeToCard(cardID); } // Command 28: disable a movie void RivenScript::disableMovie(uint16 op, uint16 argc, uint16 *argv) { _vm->_video->disableMovie(argv[0]); } // Command 29: disable all movies void RivenScript::disableAllMovies(uint16 op, uint16 argc, uint16 *argv) { _vm->_video->disableAllMovies(); } // Command 31: enable a movie void RivenScript::enableMovie(uint16 op, uint16 argc, uint16 *argv) { _vm->_video->enableMovie(argv[0]); } // Command 32: play foreground movie - blocking (movie_id) void RivenScript::playMovie(uint16 op, uint16 argc, uint16 *argv) { CursorMan.showMouse(false); // Hide the cursor before playing the video _vm->_video->playMovieBlocking(argv[0]); CursorMan.showMouse(true); // Show the cursor again when we're done ;) } // Command 33: play background movie - nonblocking (movie_id) void RivenScript::playMovieBg(uint16 op, uint16 argc, uint16 *argv) { _vm->_video->playMovie(argv[0]); } // Command 34: stop a movie void RivenScript::stopMovie(uint16 op, uint16 argc, uint16 *argv) { _vm->_video->stopMovie(argv[0]); } // Command 36: unknown void RivenScript::unk_36(uint16 op, uint16 argc, uint16 *argv) { debug(0, "unk_36: Ignoring"); } // Command 37: fade ambient sounds void RivenScript::fadeAmbientSounds(uint16 op, uint16 argc, uint16 *argv) { warning("STUB: fadeAmbientSounds()"); } // Command 38: Play a movie with extra parameters (movie id, delay high, delay low, record type, record id) void RivenScript::complexPlayMovie(uint16 op, uint16 argc, uint16 *argv) { warning("STUB: complexPlayMovie"); debugN("\tMovie ID = %d\n", argv[0]); debugN("\tDelay = %d\n", (argv[1] << 16) + argv[2]); if (argv[3] == 0) { debugN("\tDraw PLST %d\n", argv[4]); } else if (argv[3] == 40) { debugN("\tPlay SLST %d\n", argv[4]); } else { error("Unknown complexPlayMovie record type %d", argv[3]); } } // Command 39: activate PLST record (card picture lists) void RivenScript::activatePLST(uint16 op, uint16 argc, uint16 *argv) { _vm->_gfx->drawPLST(argv[0]); // An update is automatically sent here as long as it's not a load or update script and updates are enabled. if (_scriptType != kCardLoadScript && _scriptType != kCardUpdateScript) _vm->_gfx->updateScreen(); } // Command 40: activate SLST record (card ambient sound lists) void RivenScript::activateSLST(uint16 op, uint16 argc, uint16 *argv) { // WORKAROUND: Disable the SLST that is played during Riven's intro. // Riven X does this too (spoke this over with Jeff) if (_vm->getCurStack() == tspit && _vm->getCurCardRMAP() == 0x6e9a && argv[0] == 2) return; _vm->_sound->playSLST(argv[0], _vm->getCurCard()); _vm->_activatedSLST = true; } // Command 41: activate MLST record and play void RivenScript::activateMLSTAndPlay(uint16 op, uint16 argc, uint16 *argv) { _vm->_video->activateMLST(argv[0], _vm->getCurCard()); _vm->_video->playMovie(argv[0]); } // Command 43: activate BLST record (card hotspot enabling lists) void RivenScript::activateBLST(uint16 op, uint16 argc, uint16 *argv) { Common::SeekableReadStream* blst = _vm->getResource(ID_BLST, _vm->getCurCard()); uint16 recordCount = blst->readUint16BE(); for (uint16 i = 0; i < recordCount; i++) { uint16 index = blst->readUint16BE(); // record index uint16 enabled = blst->readUint16BE(); uint16 hotspotID = blst->readUint16BE(); if (argv[0] == index) for (uint16 j = 0; j < _vm->getHotspotCount(); j++) if (_vm->_hotspots[j].blstID == hotspotID) _vm->_hotspots[j].enabled = (enabled == 1); } delete blst; } // Command 44: activate FLST record (information on which SFXE resource this card should use) void RivenScript::activateFLST(uint16 op, uint16 argc, uint16 *argv) { Common::SeekableReadStream* flst = _vm->getResource(ID_FLST, _vm->getCurCard()); uint16 recordCount = flst->readUint16BE(); for (uint16 i = 0; i < recordCount; i++) { uint16 index = flst->readUint16BE(); uint16 sfxeID = flst->readUint16BE(); if (flst->readUint16BE() != 0) warning("FLST u0 non-zero"); if (index == argv[0]) { _vm->_gfx->scheduleWaterEffect(sfxeID); break; } } delete flst; } // Command 45: do zip mode void RivenScript::zipMode(uint16 op, uint16 argc, uint16 *argv) { // Check the ZIPS records to see if we have a match to the hotspot name Common::String hotspotName = _vm->getHotspotName(_vm->getCurHotspot()); for (uint16 i = 0; i < _vm->_zipModeData.size(); i++) if (_vm->_zipModeData[i].name == hotspotName) { _vm->changeToCard(_vm->_zipModeData[i].id); return; } } // Command 46: activate MLST record (movie lists) void RivenScript::activateMLST(uint16 op, uint16 argc, uint16 *argv) { _vm->_video->activateMLST(argv[0], _vm->getCurCard()); } RivenScriptManager::RivenScriptManager(MohawkEngine_Riven *vm) { _vm = vm; } RivenScriptManager::~RivenScriptManager() { for (uint32 i = 0; i < _currentScripts.size(); i++) delete _currentScripts[i]; } RivenScriptList RivenScriptManager::readScripts(Common::SeekableReadStream *stream, bool garbageCollect) { if (garbageCollect) unloadUnusedScripts(); // Garbage collect! RivenScriptList scriptList; uint16 scriptCount = stream->readUint16BE(); for (uint16 i = 0; i < scriptCount; i++) { uint16 scriptType = stream->readUint16BE(); uint32 scriptSize = RivenScript::calculateScriptSize(stream); RivenScript *script = new RivenScript(_vm, stream->readStream(scriptSize), scriptType, _vm->getCurStack(), _vm->getCurCard()); scriptList.push_back(script); // Only add it to the scripts that we will free later if it is requested. // (ie. we don't want to store scripts from the dumpScript console command) if (garbageCollect) _currentScripts.push_back(script); } return scriptList; } void RivenScriptManager::stopAllScripts() { for (uint32 i = 0; i < _currentScripts.size(); i++) _currentScripts[i]->stopRunning(); } void RivenScriptManager::unloadUnusedScripts() { // Free any scripts that aren't part of the current card and aren't running for (uint32 i = 0; i < _currentScripts.size(); i++) { if ((_vm->getCurStack() != _currentScripts[i]->getParentStack() || _vm->getCurCard() != _currentScripts[i]->getParentCard()) && !_currentScripts[i]->isRunning()) { delete _currentScripts[i]; _currentScripts.remove_at(i); i--; } } } } // End of namespace Mohawk