/* 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 "mohawk/riven_card.h" #include "mohawk/cursors.h" #include "mohawk/riven_graphics.h" #include "mohawk/riven_stack.h" #include "mohawk/riven_stacks/aspit.h" #include "mohawk/riven_video.h" #include "mohawk/resource.h" #include "mohawk/riven.h" #include "common/memstream.h" namespace Mohawk { RivenCard::RivenCard(MohawkEngine_Riven *vm, uint16 id) : _vm(vm), _id(id), _hoveredHotspot(nullptr), _pressedHotspot(nullptr) { loadCardResource(id); loadHotspots(id); loadCardPictureList(id); loadCardSoundList(id); loadCardMovieList(id); loadCardHotspotEnableList(id); loadCardWaterEffectList(id); applyPatches(id); } RivenCard::~RivenCard() { for (uint i = 0; i < _hotspots.size(); i++) { delete _hotspots[i]; } _vm->_gfx->clearWaterEffect(); _vm->_gfx->clearFliesEffect(); _vm->_video->closeVideos(); } void RivenCard::loadCardResource(uint16 id) { Common::SeekableReadStream *inStream = _vm->getResource(ID_CARD, id); _name = inStream->readSint16BE(); _zipModePlace = inStream->readUint16BE(); _scripts = _vm->_scriptMan->readScripts(inStream); delete inStream; } void RivenCard::applyPatches(uint16 id) { uint32 globalId = _vm->getStack()->getCardGlobalId(id); applyPropertiesPatch8EB7(globalId); applyPropertiesPatch2E76(globalId); // Apply script patches for (uint i = 0; i < _scripts.size(); i++) { _scripts[i].script->applyCardPatches(_vm, globalId, _scripts[i].type, 0xFFFF); } applyPropertiesPatch22118(globalId); applyPropertiesPatchE2E(globalId); applyPropertiesPatch1518D(globalId); } void RivenCard::applyPropertiesPatch8EB7(uint32 globalId) { // On Jungle Island on the back side of the "beetle" gate, the forward hotspot // is always enabled, preventing keyboard navigation from automatically opening // the gate. // We patch the card so that the forward opcode is enabled only when the gate is open. // // New hotspot enable entries: // == Hotspot enable 5 == // hotspotId: 3 // enabled: 1 // // == Hotspot enable 6 == // hotspotId: 3 // enabled: 0 // // Additional load script fragment: // switch (jgate) { // case 0: // activateBLST(6); // break; // case 1: // activateBLST(5); // break; if (globalId == 0x8EB7) { HotspotEnableRecord forwardEnabled; forwardEnabled.index = _hotspotEnableList.back().index + 1; forwardEnabled.hotspotId = 3; forwardEnabled.enabled = 1; _hotspotEnableList.push_back(forwardEnabled); HotspotEnableRecord forwardDisabled; forwardDisabled.index = _hotspotEnableList.back().index + 1; forwardDisabled.hotspotId = 3; forwardDisabled.enabled = 0; _hotspotEnableList.push_back(forwardDisabled); uint16 jGateVariable = _vm->getStack()->getIdFromName(kVariableNames, "jgate"); uint16 patchData[] = { 1, // Command count in script kRivenCommandSwitch, 2, // Unused jGateVariable, 2, // Branches count 0, // jgate == 0 branch (gate closed) 1, // Command count in sub-script kRivenCommandActivateBLST, 1, // Argument count forwardDisabled.index, 1, // jgate == 1 branch (gate open) 1, // Command count in sub-script kRivenCommandActivateBLST, 1, // Argument count forwardEnabled.index }; RivenScriptPtr patchScript = _vm->_scriptMan->readScriptFromData(patchData, ARRAYSIZE(patchData)); // Append the patch to the existing script RivenScriptPtr loadScript = getScript(kCardLoadScript); loadScript += patchScript; debugC(kRivenDebugPatches, "Applied fix always enabled forward hotspot in card %x", globalId); } } void RivenCard::applyPropertiesPatch2E76(uint32 globalId) { // In Gehn's office, after having encountered him once before and coming back // with the trap book, the draw update script of card 1 tries to switch to // card 2 while still loading card 1. Switching cards is not allowed during // draw update scripts, resulting in an use after free crash. // // Here we backport the fix that has been made in the DVD version to the CD version. // // Script before patch: // == Script 1 == // type: CardUpdate // switch (agehn) { // case 1: // switch (atrapbook) { // case 1: // obutton = 1; // transition(16); // switchCard(2); // break; // } // break; // case 2: // activatePLST(5); // break; // case 3: // activatePLST(5); // break; // } // // // Script after patch: // == Script 1 == // type: CardUpdate // switch (agehn) { // case 1: // switch (atrapbook) { // case 1: // obutton = 1; // activatePLST(6); // break; // } // break; // case 2: // activatePLST(5); // break; // case 3: // activatePLST(5); // break; // } // // == Script 2 == // type: CardEnter // switch (agehn) { // case 1: // switch (atrapbook) { // case 1: // transition(16); // switchCard(2); // break; // } // break; // } if (globalId == 0x2E76 && !(_vm->getFeatures() & GF_DVD)) { uint16 aGehnVariable = _vm->getStack()->getIdFromName(kVariableNames, "agehn"); uint16 aTrapBookVariable = _vm->getStack()->getIdFromName(kVariableNames, "atrapbook"); uint16 patchData[] = { 1, // Command count in script kRivenCommandSwitch, 2, // Unused aGehnVariable, 1, // Branches count 1, // agehn == 1 branch 1, // Command count in sub-script kRivenCommandSwitch, 2, // Unused aTrapBookVariable, 1, // Branches count 1, // atrapbook == 1 branch 2, // Command count in sub-script kRivenCommandTransition, 1, // Argument count kRivenTransitionBlend, kRivenCommandChangeCard, 1, // Argument count 2 // Card id }; // Add the new script to the list RivenTypedScript patchScript; patchScript.type = kCardEnterScript; patchScript.script = _vm->_scriptMan->readScriptFromData(patchData, ARRAYSIZE(patchData)); _scripts.push_back(patchScript); // Add a black picture to the card's list to be able to use it in the second part of the patch Picture blackPicture; blackPicture.index = 6; blackPicture.id = 117; blackPicture.rect = Common::Rect(608, 392); _pictureList.push_back(blackPicture); debugC(kRivenDebugPatches, "Applied invalid card change during screen update (1/2) to card %x", globalId); // The second part of this patch is in the script patches } } void RivenCard::applyPropertiesPatch22118(uint32 globalId) { // On Temple Island, near the steam valve closest to the bridge to Boiler island, // the background sound on the view offering a view to the bridge does // not properly reflect the valve's position. // // The sound is always that of steam going through the pipe when the bridge is // down. When the valve points up, the sound should be that of steam escaping // through the top of the pipe. // // Script before patch: // == Script 0 == // type: CardLoad // switch (bbigbridge) { // case 0: // switch (tbookvalve) { // case 0: // activatePLST(2); // activateSLST(1); // break; // } // break; // } // switch (bbigbridge) { // case 0: // switch (tbookvalve) { // case 1: // activatePLST(2); // activateSLST(2); // break; // } // break; // } // switch (bbigbridge) { // case 1: // switch (tbookvalve) { // case 0: // activatePLST(1); // activateSLST(2); // break; // } // break; // } // switch (bbigbridge) { // case 1: // switch (tbookvalve) { // case 1: // activatePLST(1); // activateSLST(2); // break; // } // break; // } // // // Script after patch: // == Script 0 == // type: CardLoad // switch (bbigbridge) { // case 0: // switch (tbookvalve) { // case 0: // activatePLST(2); // break; // } // break; // } // switch (bbigbridge) { // case 0: // switch (tbookvalve) { // case 1: // activatePLST(2); // break; // } // break; // } // switch (bbigbridge) { // case 1: // switch (tbookvalve) { // case 0: // activatePLST(1); // break; // } // break; // } // switch (bbigbridge) { // case 1: // switch (tbookvalve) { // case 1: // activatePLST(1); // break; // } // break; // } // switch (tbookvalve) { // case 0: // activateSLST(1); // break; // case 1: // activateSLST(2); // break; // } if (globalId == 0x22118) { uint16 tBookValveVariable = _vm->getStack()->getIdFromName(kVariableNames, "tbookvalve"); uint16 patchData[] = { 1, // Command count in script kRivenCommandSwitch, 2, // Unused tBookValveVariable, 2, // Branches count 0, // tbookvalve == 0 branch (steam escaping at the top of the pipe) 1, // Command count in sub-script kRivenCommandActivateSLST, 1, // Argument count 1, // Steam leaking sound id 1, // tbookvalve == 1 branch (steam going to the left pipe) 1, // Command count in sub-script kRivenCommandActivateSLST, 1, // Argument count 2, // Steam in pipe sound id }; RivenScriptPtr patchScript = _vm->_scriptMan->readScriptFromData(patchData, ARRAYSIZE(patchData)); // Append the patch to the existing script RivenScriptPtr loadScript = getScript(kCardLoadScript); loadScript += patchScript; debugC(kRivenDebugPatches, "Applied incorrect steam sounds (2/2) to card %x", globalId); } } void RivenCard::applyPropertiesPatchE2E(uint32 globalId) { if (!(_vm->getFeatures() & GF_25TH)) return; // The main menu in the Myst 25th anniversary version is patched to include new items: // - Save game if (globalId == 0xE2E) { moveHotspot( 22, Common::Rect(470, 175, 602, 190)); // Setup moveHotspot( 16, Common::Rect(470, 201, 602, 216)); // New game addMenuHotspot(23, Common::Rect(470, 227, 602, 242), 3, RivenStacks::ASpit::kExternalRestoreGame, "xarestoregame"); addMenuHotspot(24, Common::Rect(470, 256, 602, 271), 4, RivenStacks::ASpit::kExternalSaveGame, "xaSaveGame"); addMenuHotspot(25, Common::Rect(470, 283, 602, 300), 5, RivenStacks::ASpit::kExternalResume, "xaResumeGame"); addMenuHotspot(26, Common::Rect(470, 309, 602, 326), 6, RivenStacks::ASpit::kExternalOptions, "xaOptions"); addMenuHotspot(27, Common::Rect(470, 335, 602, 352), 7, RivenStacks::ASpit::kExternalQuit, "xademoquit"); _vm->getStack()->registerName(kExternalCommandNames, RivenStacks::ASpit::kExternalNewGame, "xaNewGame"); } } void RivenCard::applyPropertiesPatch1518D(uint32 globalId) { // Inside Jungle Island's dome, when looking at the open book, // stepping back from the stand and then looking at the book // again, the book closing animation would play again. // // Comparing the scripts for the Jungle dome and the other domes // shows a small portion of script is missing. // The following patch adds it back so the jungle dome script // matches the other domes. // // Added script part: // == Script 2 == // [...] // type: CardEnter // switch (jbook) { // case 2: // playMovieBlocking(1); // jbook = 0; // refreshCard(); // break; // } if (globalId == 0x1518D) { uint16 jBookVariable = _vm->getStack()->getIdFromName(kVariableNames, "jbook"); uint16 patchData[] = { 1, // Command count in script kRivenCommandSwitch, 2, // Unused jBookVariable, 1, // Branches count 2, // jbook == 2 branch 3, // Command count in sub-script kRivenCommandPlayMovieBlocking, 1, // Argument count 1, // Video id kRivenCommandSetVariable, 2, // Argument count jBookVariable, 0, // Variable value kRivenCommandRefreshCard, 0 // Argument count }; RivenScriptPtr patchScript = _vm->_scriptMan->readScriptFromData(patchData, ARRAYSIZE(patchData)); // Append the patch to the existing script RivenScriptPtr loadScript = getScript(kCardEnterScript); loadScript += patchScript; debugC(kRivenDebugPatches, "Applied jungle book close loop to card %x", globalId); } } void RivenCard::moveHotspot(uint16 blstId, const Common::Rect &position) { RivenHotspot *hotspot = getHotspotByBlstId(blstId); if (!hotspot) { warning("Could not find hotspot with blstId %d", blstId); return; } hotspot->setRect(position); } void RivenCard::addMenuHotspot(uint16 blstId, const Common::Rect &position, uint16 index, uint16 externalCommandNameId, const char *externalCommandName) { RivenHotspot *existingHotspot = getHotspotByBlstId(blstId); if (existingHotspot) { moveHotspot(blstId, position); return; // Don't add the hotspot if it already exists } // Add the external command id => name mapping if it is missing int16 existingCommandNameId = _vm->getStack()->getIdFromName(kExternalCommandNames, externalCommandName); if (existingCommandNameId < 0) { _vm->getStack()->registerName(kExternalCommandNames, externalCommandNameId, externalCommandName); } else { externalCommandNameId = existingCommandNameId; } uint16 patchData[] = { blstId, 0xFFFF, // name (uint16) position.left, (uint16) position.top, (uint16) position.right, (uint16) position.bottom, 0, // u0 kRivenMainCursor, // cursor index, 0xFFFF, // transition offset 0, // flags 2, // script count kMouseDownScript, // script type 1, // command count kRivenCommandRunExternal, // command type 2, // argument count externalCommandNameId, 0, // external argument count kMouseInsideScript, // script type 1, // command count kRivenCommandChangeCursor, // command type 1, // argument count kRivenOpenHandCursor // cursor }; // Script data is expected to be in big endian for (uint i = 0; i < ARRAYSIZE(patchData); i++) { patchData[i] = TO_BE_16(patchData[i]); } // Add the new hotspot to the existing ones Common::MemoryReadStream patchStream((const byte *)(patchData), ARRAYSIZE(patchData) * sizeof(uint16)); RivenHotspot *newHotspot = new RivenHotspot(_vm, &patchStream); _hotspots.push_back(newHotspot); } void RivenCard::enter(bool unkMovies) { setCurrentCardVariable(); _vm->_activatedPLST = false; _vm->_activatedSLST = false; _vm->_gfx->beginScreenUpdate(); runScript(kCardLoadScript); defaultLoadScript(); initializeZipMode(); _vm->_gfx->applyScreenUpdate(true); if (_vm->_showHotspots) { drawHotspotRects(); } runScript(kCardEnterScript); } void RivenCard::initializeZipMode() { if (_zipModePlace) { _vm->addZipVisitedCard(_id, _name); } // Check if a zip mode hotspot is enabled by checking the name/id against the ZIPS records. for (uint32 i = 0; i < _hotspots.size(); i++) { if (_hotspots[i]->isZip()) { if (_vm->_vars["azip"] != 0) { // Check if a zip mode hotspot is enabled by checking the name/id against the ZIPS records. Common::String hotspotName = _hotspots[i]->getName(); bool visited = _vm->isZipVisitedCard(hotspotName); _hotspots[i]->enable(visited); } else // Disable the hotspot if zip mode is disabled _hotspots[i]->enable(false); } } } RivenScriptPtr RivenCard::getScript(uint16 scriptType) const { for (uint16 i = 0; i < _scripts.size(); i++) if (_scripts[i].type == scriptType) { return _scripts[i].script; } return RivenScriptPtr(); } void RivenCard::runScript(uint16 scriptType) { RivenScriptPtr script = getScript(scriptType); _vm->_scriptMan->runScript(script, false); } uint16 RivenCard::getId() const { return _id; } void RivenCard::defaultLoadScript() { // Activate the first picture list if none have been activated if (!_vm->_activatedPLST) drawPicture(1); // Activate the first sound list if none have been activated if (!_vm->_activatedSLST) playSound(1); } void RivenCard::loadCardPictureList(uint16 id) { Common::SeekableReadStream* plst = _vm->getResource(ID_PLST, id); uint16 recordCount = plst->readUint16BE(); _pictureList.resize(recordCount); for (uint16 i = 0; i < recordCount; i++) { Picture &picture = _pictureList[i]; picture.index = plst->readUint16BE(); picture.id = plst->readUint16BE(); picture.rect.left = plst->readUint16BE(); picture.rect.top = plst->readUint16BE(); picture.rect.right = plst->readUint16BE(); picture.rect.bottom = plst->readUint16BE(); } delete plst; } void RivenCard::drawPicture(uint16 index, bool queue) { if (index > 0 && index <= _pictureList.size()) { RivenScriptPtr script = _vm->_scriptMan->createScriptFromData(1, kRivenCommandActivatePLST, 1, index); _vm->_scriptMan->runScript(script, queue); } } RivenCard::Picture RivenCard::getPicture(uint16 index) const { for (uint16 i = 0; i < _pictureList.size(); i++) { if (_pictureList[i].index == index) { return _pictureList[i]; } } error("Could not find picture %d in card %d", index, _id); } void RivenCard::loadCardSoundList(uint16 id) { Common::SeekableReadStream *slstStream = _vm->getResource(ID_SLST, id); uint16 recordCount = slstStream->readUint16BE(); _soundList.resize(recordCount); for (uint16 i = 0; i < recordCount; i++) { SLSTRecord &slstRecord = _soundList[i]; slstRecord.index = slstStream->readUint16BE(); uint16 soundCount = slstStream->readUint16BE(); slstRecord.soundIds.resize(soundCount); for (uint16 j = 0; j < soundCount; j++) slstRecord.soundIds[j] = slstStream->readUint16BE(); slstRecord.fadeFlags = slstStream->readUint16BE(); slstRecord.loop = slstStream->readUint16BE(); slstRecord.globalVolume = slstStream->readUint16BE(); slstRecord.u0 = slstStream->readUint16BE(); // Unknown if (slstRecord.u0 > 1) warning("slstRecord.u0: %d non-boolean", slstRecord.u0); slstRecord.suspend = slstStream->readUint16BE(); if (slstRecord.suspend != 0) warning("slstRecord.suspend: %d non-zero", slstRecord.suspend); slstRecord.volumes.resize(soundCount); slstRecord.balances.resize(soundCount); slstRecord.u2.resize(soundCount); for (uint16 j = 0; j < soundCount; j++) slstRecord.volumes[j] = slstStream->readUint16BE(); for (uint16 j = 0; j < soundCount; j++) slstRecord.balances[j] = slstStream->readSint16BE(); // negative = left, 0 = center, positive = right for (uint16 j = 0; j < soundCount; j++) { slstRecord.u2[j] = slstStream->readUint16BE(); // Unknown if (slstRecord.u2[j] != 255 && slstRecord.u2[j] != 256) warning("slstRecord.u2[%d]: %d not 255 or 256", j, slstRecord.u2[j]); } } delete slstStream; } void RivenCard::playSound(uint16 index, bool queue) { if (index > 0 && index <= _soundList.size()) { RivenScriptPtr script = _vm->_scriptMan->createScriptFromData(1, kRivenCommandActivateSLST, 1, index); _vm->_scriptMan->runScript(script, queue); } } SLSTRecord RivenCard::getSound(uint16 index) const { for (uint16 i = 0; i < _soundList.size(); i++) { if (_soundList[i].index == index) { return _soundList[i]; } } error("Could not find sound %d in card %d", index, _id); } void RivenCard::overrideSound(uint16 index, uint16 withIndex) { _soundList[index].soundIds = _soundList[withIndex].soundIds; } void RivenCard::loadHotspots(uint16 id) { Common::SeekableReadStream *inStream = _vm->getResource(ID_HSPT, id); uint16 hotspotCount = inStream->readUint16BE(); _hotspots.resize(hotspotCount); uint32 globalId = _vm->getStack()->getCardGlobalId(id); for (uint16 i = 0; i < hotspotCount; i++) { _hotspots[i] = new RivenHotspot(_vm, inStream); _hotspots[i]->applyPropertiesPatches(globalId); _hotspots[i]->applyScriptPatches(globalId); } delete inStream; } void RivenCard::drawHotspotRects() { for (uint16 i = 0; i < _hotspots.size(); i++) _vm->_gfx->drawRect(_hotspots[i]->getRect(), _hotspots[i]->isEnabled()); } RivenHotspot *RivenCard::getHotspotContainingPoint(const Common::Point &point) const { RivenHotspot *hotspot = nullptr; for (uint16 i = 0; i < _hotspots.size(); i++) if (_hotspots[i]->isEnabled() && _hotspots[i]->containsPoint(point)) { hotspot = _hotspots[i]; } return hotspot; } Common::Array RivenCard::getHotspots() const { return _hotspots; } RivenHotspot *RivenCard::getHotspotByName(const Common::String &name, bool optional) const { int16 nameId = _vm->getStack()->getIdFromName(kHotspotNames, name); for (uint i = 0; i < _hotspots.size(); i++) { if (_hotspots[i]->getNameId() == nameId && nameId != -1) { return _hotspots[i]; } } if (optional) { return nullptr; } else { error("Card %d does not have an hotspot named %s", _id, name.c_str()); } } RivenHotspot *RivenCard::getHotspotByBlstId(const uint16 blstId) const { for (uint i = 0; i < _hotspots.size(); i++) { if (_hotspots[i]->getBlstId() == blstId) { return _hotspots[i]; } } return nullptr; } void RivenCard::loadCardHotspotEnableList(uint16 id) { Common::SeekableReadStream *blst = _vm->getResource(ID_BLST, id); uint16 recordCount = blst->readUint16BE(); _hotspotEnableList.resize(recordCount); for (uint16 i = 0; i < recordCount; i++) { HotspotEnableRecord &record = _hotspotEnableList[i]; record.index = blst->readUint16BE(); record.enabled = blst->readUint16BE(); record.hotspotId = blst->readUint16BE(); } delete blst; } void RivenCard::activateHotspotEnableRecord(uint16 index) { for (uint16 i = 0; i < _hotspotEnableList.size(); i++) { const HotspotEnableRecord &record = _hotspotEnableList[i]; if (record.index == index) { RivenHotspot *hotspot = getHotspotByBlstId(record.hotspotId); hotspot->enable(record.enabled == 1); break; } } } void RivenCard::loadCardWaterEffectList(uint16 id) { Common::SeekableReadStream *flst = _vm->getResource(ID_FLST, id); uint16 recordCount = flst->readUint16BE(); _waterEffectList.resize(recordCount); for (uint16 i = 0; i < recordCount; i++) { WaterEffectRecord &record = _waterEffectList[i]; record.index = flst->readUint16BE(); record.sfxeId = flst->readUint16BE(); record.u0 = flst->readUint16BE(); if (record.u0 != 0) { warning("FLST u0 non-zero"); } } delete flst; } void RivenCard::activateWaterEffect(uint16 index) { for (uint16 i = 0; i < _waterEffectList.size(); i++) { const WaterEffectRecord &record = _waterEffectList[i]; if (record.index == index) { _vm->_gfx->scheduleWaterEffect(record.sfxeId); break; } } } RivenHotspot *RivenCard::getCurHotspot() const { return _hoveredHotspot; } RivenScriptPtr RivenCard::onMouseDown(const Common::Point &mouse) { RivenScriptPtr script = onMouseMove(mouse); _pressedHotspot = _hoveredHotspot; if (_pressedHotspot) { script += _pressedHotspot->getScript(kMouseDownScript); } return script; } RivenScriptPtr RivenCard::onMouseUp(const Common::Point &mouse) { RivenScriptPtr script = onMouseMove(mouse); if (_pressedHotspot && _pressedHotspot == _hoveredHotspot) { script += _pressedHotspot->getScript(kMouseUpScript); } _pressedHotspot = nullptr; return script; } RivenScriptPtr RivenCard::onMouseMove(const Common::Point &mouse) { RivenHotspot *hotspot = getHotspotContainingPoint(mouse); RivenScriptPtr script = RivenScriptPtr(new RivenScript()); // Detect hotspot exit if (_hoveredHotspot && (!hotspot || hotspot != _hoveredHotspot)) { script += _hoveredHotspot->getScript(kMouseLeaveScript); } // Detect hotspot entry if (hotspot && hotspot != _hoveredHotspot) { _hoveredHotspot = hotspot; script += _hoveredHotspot->getScript(kMouseEnterScript); } if (!hotspot) { _hoveredHotspot = nullptr; } return script; } RivenScriptPtr RivenCard::onMouseDragUpdate() { RivenScriptPtr script; if (_pressedHotspot) { script = _pressedHotspot->getScript(kMouseDragScript); } return script; } RivenScriptPtr RivenCard::onFrame() { return getScript(kCardFrameScript); } RivenScriptPtr RivenCard::onMouseUpdate() { RivenScriptPtr script; if (_hoveredHotspot) { script = _hoveredHotspot->getScript(kMouseInsideScript); } if (!script || script->empty()) { updateMouseCursor(); } return script; } void RivenCard::updateMouseCursor() { uint16 cursor; if (_hoveredHotspot) { cursor = _hoveredHotspot->getMouseCursor(); } else { cursor = kRivenMainCursor; } _vm->_cursor->setCursor(cursor); } void RivenCard::leave() { RivenScriptPtr script(new RivenScript()); if (_pressedHotspot) { script += _pressedHotspot->getScript(kMouseUpScript); } if (_hoveredHotspot) { script += _hoveredHotspot->getScript(kMouseLeaveScript); } script += getScript(kCardLeaveScript); _vm->_scriptMan->runScript(script, false); } void RivenCard::setCurrentCardVariable() { _vm->_vars["currentcardid"] = _id; } void RivenCard::dump() const { debug("== Card =="); debug("id: %d", _id); if (_name >= 0) { debug("name: %s", _vm->getStack()->getName(kCardNames, _name).c_str()); } else { debug("name: [no name]"); } debug("zipModePlace: %d", _zipModePlace); debug("globalId: %x", _vm->getStack()->getCardGlobalId(_id)); debugN("\n"); for (uint i = 0; i < _scripts.size(); i++) { debug("== Script %d ==", i); debug("type: %s", RivenScript::getTypeName(_scripts[i].type)); _scripts[i].script->dumpScript(0); debugN("\n"); } for (uint i = 0; i < _hotspots.size(); i++) { debug("== Hotspot %d ==", i); _hotspots[i]->dump(); } for (uint i = 0; i < _pictureList.size(); i++) { const Common::Rect &rect = _pictureList[i].rect; debug("== Picture %d ==", _pictureList[i].index); debug("pictureId: %d", _pictureList[i].id); debug("rect: (%d, %d, %d, %d)", rect.left, rect.top, rect.right, rect.bottom); debugN("\n"); } for (uint i = 0; i < _waterEffectList.size(); i++) { debug("== Effect %d ==", _waterEffectList[i].index); debug("sfxeId: %d", _waterEffectList[i].sfxeId); debug("u0: %d", _waterEffectList[i].u0); debugN("\n"); } for (uint i = 0; i < _hotspotEnableList.size(); i++) { debug("== Hotspot enable %d ==", _hotspotEnableList[i].index); debug("hotspotId: %d", _hotspotEnableList[i].hotspotId); debug("enabled: %d", _hotspotEnableList[i].enabled); debugN("\n"); } for (uint i = 0; i < _soundList.size(); i++) { debug("== Ambient sound list %d ==", _soundList[i].index); debug("globalVolume: %d", _soundList[i].globalVolume); debug("fadeFlags: %d", _soundList[i].fadeFlags); debug("loop: %d", _soundList[i].loop); debug("suspend: %d", _soundList[i].suspend); debug("u0: %d", _soundList[i].u0); for (uint j = 0; j < _soundList[i].soundIds.size(); j++) { debug("sound[%d].id: %d", j, _soundList[i].soundIds[j]); debug("sound[%d].volume: %d", j, _soundList[i].volumes[j]); debug("sound[%d].balance: %d", j, _soundList[i].balances[j]); debug("sound[%d].u2: %d", j, _soundList[i].u2[j]); } debugN("\n"); } for (uint i = 0; i < _movieList.size(); i++) { debug("== Movie %d ==", _movieList[i].index); debug("movieID: %d", _movieList[i].movieID); debug("playbackSlot: %d", _movieList[i].playbackSlot); debug("left: %d", _movieList[i].left); debug("top: %d", _movieList[i].top); debug("lowBoundTime: %d", _movieList[i].lowBoundTime); debug("startTime: %d", _movieList[i].startTime); debug("highBoundTime: %d", _movieList[i].highBoundTime); debug("loop: %d", _movieList[i].loop); debug("volume: %d", _movieList[i].volume); debug("rate: %d", _movieList[i].rate); debugN("\n"); } } void RivenCard::loadCardMovieList(uint16 id) { Common::SeekableReadStream *mlstStream = _vm->getResource(ID_MLST, id); uint16 recordCount = mlstStream->readUint16BE(); _movieList.resize(recordCount); for (uint16 i = 0; i < recordCount; i++) { MLSTRecord &mlstRecord = _movieList[i]; mlstRecord.index = mlstStream->readUint16BE(); mlstRecord.movieID = mlstStream->readUint16BE(); mlstRecord.playbackSlot = mlstStream->readUint16BE(); mlstRecord.left = mlstStream->readUint16BE(); mlstRecord.top = mlstStream->readUint16BE(); mlstRecord.lowBoundTime = mlstStream->readUint16BE(); mlstRecord.startTime = mlstStream->readUint16BE(); mlstRecord.highBoundTime = mlstStream->readUint16BE(); mlstRecord.loop = mlstStream->readUint16BE(); mlstRecord.volume = mlstStream->readUint16BE(); mlstRecord.rate = mlstStream->readUint16BE(); if (mlstRecord.lowBoundTime != 0) warning("lowBoundTime in MLST not 0"); if (mlstRecord.startTime != 0) warning("startTime in MLST not 0"); if (mlstRecord.highBoundTime != 0xFFFF) warning("highBoundTime in MLST not 0xFFFF"); if (mlstRecord.rate != 1) warning("mlstRecord.rate not 1"); } delete mlstStream; } MLSTRecord RivenCard::getMovie(uint16 index) const { for (uint16 i = 0; i < _movieList.size(); i++) { if (_movieList[i].index == index) { return _movieList[i]; } } error("Could not find movie %d in card %d", index, _id); } void RivenCard::playMovie(uint16 index, bool queue) { if (index > 0 && index <= _movieList.size()) { RivenScriptPtr script = _vm->_scriptMan->createScriptFromData(1, kRivenCommandActivateMLSTAndPlay, 1, index); _vm->_scriptMan->runScript(script, queue); } } RivenScriptPtr RivenCard::onKeyAction(RivenKeyAction keyAction) { static const char *forwardNames[] = { "forward", "forward1", "forward2", "forward3", "opendoor", "openhatch", "opentrap", "opengate", "opengrate", "open", "door", "drop", "go", "enterprison", "exit", "forwardleft", "forwardright", nullptr }; static const char *forwardLeftNames [] = { "forwardleft", nullptr }; static const char *forwardRightNames[] = { "forwardright", nullptr }; static const char *leftNames [] = { "left", "afl", "prevpage", nullptr }; static const char *rightNames [] = { "right", "afr", "nextpage", nullptr }; static const char *backNames [] = { "back", nullptr }; static const char *upNames [] = { "up", nullptr }; static const char *downNames [] = { "down", nullptr }; const char **hotspotNames = nullptr; switch (keyAction) { case kKeyActionMoveForward: hotspotNames = forwardNames; break; case kKeyActionMoveForwardLeft: hotspotNames = forwardLeftNames; break; case kKeyActionMoveForwardRight: hotspotNames = forwardRightNames; break; case kKeyActionMoveLeft: hotspotNames = leftNames; break; case kKeyActionMoveRight: hotspotNames = rightNames; break; case kKeyActionMoveBack: hotspotNames = backNames; break; case kKeyActionLookUp: hotspotNames = upNames; break; case kKeyActionLookDown: hotspotNames = downNames; break; default: break; } if (!hotspotNames) { return RivenScriptPtr(new RivenScript()); } RivenHotspot *directionHotspot = findEnabledHotspotByName(hotspotNames); if (!directionHotspot) { return RivenScriptPtr(new RivenScript()); } _hoveredHotspot = directionHotspot; RivenScriptPtr clickScript = directionHotspot->getScript(kMouseDownScript); if (!clickScript || clickScript->empty()) { clickScript = directionHotspot->getScript(kMouseUpScript); } if (!clickScript || clickScript->empty()) { clickScript = RivenScriptPtr(new RivenScript()); } return clickScript; } RivenHotspot *RivenCard::findEnabledHotspotByName(const char **names) const { for (uint i = 0; names[i] != nullptr; i++) { RivenHotspot *hotspot = getHotspotByName(names[i], true); if (hotspot && hotspot->isEnabled()) { return hotspot; } } return nullptr; } RivenHotspot::RivenHotspot(MohawkEngine_Riven *vm, Common::ReadStream *stream) : _vm(vm) { loadFromStream(stream); } void RivenHotspot::loadFromStream(Common::ReadStream *stream) { _flags = kFlagEnabled; _blstID = stream->readUint16BE(); _nameResource = stream->readSint16BE(); int16 left = stream->readSint16BE(); int16 top = stream->readSint16BE(); int16 right = stream->readSint16BE(); int16 bottom = stream->readSint16BE(); // Riven has some invalid rects, disable them here // Known weird hotspots: // - tspit 371 (DVD: 377), hotspot 4 if (left >= right || top >= bottom) { warning("Invalid hotspot: (%d, %d, %d, %d)", left, top, right, bottom); left = top = right = bottom = 0; enable(false); } _rect = Common::Rect(left, top, right, bottom); _u0 = stream->readUint16BE(); _mouseCursor = stream->readUint16BE(); _index = stream->readUint16BE(); _transitionOffset = stream->readSint16BE(); _flags |= stream->readUint16BE(); // Read in the scripts now _scripts = _vm->_scriptMan->readScripts(stream); } RivenScriptPtr RivenHotspot::getScript(uint16 scriptType) const { for (uint16 i = 0; i < _scripts.size(); i++) if (_scripts[i].type == scriptType) { return _scripts[i].script; } return RivenScriptPtr(); } void RivenHotspot::applyPropertiesPatches(uint32 cardGlobalId) { // In Jungle island, one of the bridge hotspots does not have a name // This breaks keyboard navigation. Set the proper name. if (cardGlobalId == 0x214a0 && _blstID == 9) { _nameResource = _vm->getStack()->getIdFromName(kHotspotNames, "forward"); debugC(kRivenDebugPatches, "Applied missing hotspot name patch to card %x", cardGlobalId); } // In the lab in Book Making island the card showing one of the doors has // two "forward" hotspots. One of them goes backwards. Disable it, and make sure // it cannot be found by the keyboard navigation code. if (cardGlobalId == 0x1fa79 && _blstID == 3) { enable(false); _nameResource = -1; debugC(kRivenDebugPatches, "Applied disable buggy forward hotspot to card %x", cardGlobalId); } // On Temple Island, in front of the back door to the rotating room, // change the name of the hotspot to look at the bottom of the door to // "down" instead of "forwardleft". That way the keyboard navigation // does not spoil that you can go below the door. // Also make sure the forward keyboard action plays the try to open // door animation. if (cardGlobalId == 0x87ac && _blstID == 10) { _nameResource = _vm->getStack()->getIdFromName(kHotspotNames, "down"); debugC(kRivenDebugPatches, "Applied change hotspot name to 'down' patch to card %x", cardGlobalId); } if (cardGlobalId == 0x87ac && _blstID == 12) { _nameResource = _vm->getStack()->getIdFromName(kHotspotNames, "opendoor"); debugC(kRivenDebugPatches, "Applied change hotspot name to 'opendoor' patch to card %x", cardGlobalId); } } void RivenHotspot::applyScriptPatches(uint32 cardGlobalId) { for (uint16 i = 0; i < _scripts.size(); i++) { _scripts[i].script->applyCardPatches(_vm, cardGlobalId, _scripts[i].type, _blstID); } } bool RivenHotspot::isEnabled() const { return (_flags & kFlagEnabled) != 0; } void RivenHotspot::enable(bool e) { if (e) { _flags |= kFlagEnabled; } else { _flags &= ~kFlagEnabled; } } bool RivenHotspot::isZip() const { return (_flags & kFlagZip) != 0; } Common::Rect RivenHotspot::getRect() const { return _rect; } bool RivenHotspot::containsPoint(const Common::Point &point) const { return _rect.contains(point); } uint16 RivenHotspot::getMouseCursor() const { return _mouseCursor; } Common::String RivenHotspot::getName() const { if (_nameResource < 0) return Common::String(); return _vm->getStack()->getName(kHotspotNames, _nameResource); } uint16 RivenHotspot::getIndex() const { return _index; } uint16 RivenHotspot::getBlstId() const { return _blstID; } void RivenHotspot::setRect(const Common::Rect &rect) { _rect = rect; } int16 RivenHotspot::getNameId() const { return _nameResource; } int16 RivenHotspot::getTransitionOffset() const { return _transitionOffset; } void RivenHotspot::dump() const { debug("index: %d", _index); debug("blstId: %d", _blstID); debug("name: %s", getName().c_str()); debug("rect: (%d, %d, %d, %d)", _rect.left, _rect.top, _rect.right, _rect.bottom); debug("flags: %d", _flags); debug("mouseCursor: %d", _mouseCursor); debug("transitionOffset: %d", _transitionOffset); debug("u0: %d", _u0); debugN("\n"); for (uint i = 0; i < _scripts.size(); i++) { debug("=== Hotspot script %d ===", i); debug("type: %s", RivenScript::getTypeName(_scripts[i].type)); _scripts[i].script->dumpScript(0); debugN("\n"); } } } // End of namespace Mohawk