scummvm/engines/mohawk/riven_card.cpp
Bastien Bouclet 27a7a67778 MOHAWK: RIVEN: Prevent the card leave script from recursing
Recursion would happen when multiple events are received in the same
frame. This could happen when mashing both the mouse and the keyboard to
move around faster, as noticed by a speedrunner.
2019-02-02 13:30:44 +01:00

1306 lines
37 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "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<RivenHotspot *> 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);
_pressedHotspot = nullptr;
}
if (_hoveredHotspot) {
script += _hoveredHotspot->getScript(kMouseLeaveScript);
_hoveredHotspot = nullptr;
}
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