/* 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 3 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, see . * */ #include "engines/stark/resources/script.h" #include "engines/stark/formats/xrc.h" #include "engines/stark/resources/anim.h" #include "engines/stark/resources/command.h" #include "engines/stark/resources/item.h" #include "engines/stark/resources/scroll.h" #include "engines/stark/resources/sound.h" #include "engines/stark/resources/speech.h" #include "engines/stark/services/dialogplayer.h" #include "engines/stark/services/global.h" #include "engines/stark/services/services.h" #include "engines/stark/services/stateprovider.h" #include "engines/stark/services/userinterface.h" #include "engines/stark/tools/decompiler.h" namespace Stark { namespace Resources { Script::~Script() { } Script::Script(Object *parent, byte subType, uint16 index, const Common::String &name) : Object(parent, subType, index, name), _scriptType(0), _runEvent(0), _minChapter(0), _maxChapter(999), _shouldResetGameSpeed(false), _enabled(false), _nextCommand(nullptr), _pauseTimeLeft(-1), _suspendingResource(nullptr), _resumeStatus(kResumeSuspend) { _type = TYPE; } void Script::readData(Formats::XRCReadStream *stream) { uint32 type = stream->readUint32LE(); _runEvent = stream->readUint32LE(); _minChapter = stream->readUint32LE(); _maxChapter = stream->readUint32LE(); _shouldResetGameSpeed = stream->readBool(); _enabled = type == 0; switch (_subType) { case kSubTypeGameEvent: _scriptType = type == 2 ? kScriptTypePassiveDialog : kScriptTypeOnGameEvent; break; case kSubTypePlayerAction: _scriptType = kScriptTypeOnPlayerAction; break; case kSubTypeDialog: _scriptType = kScriptType4; break; default: error("Unknown script subtype %d for script %s", _subType, getName().c_str()); } } void Script::onAllLoaded() { Object::onAllLoaded(); reset(); } void Script::onGameLoop() { Object::onGameLoop(); execute(kCallModeGameLoop); } void Script::reset() { if (_suspendingResource && _suspendingResource->getType() == Type::kItem) { Item *item = _suspendingResource->cast(_suspendingResource); item->setMovement(nullptr); } _suspendingResource = nullptr; _resumeStatus = kResumeSuspend; _pauseTimeLeft = -1; _nextCommand = getBeginCommand(); } bool Script::isOnBegin() { return _nextCommand && _nextCommand->getSubType() == Command::kCommandBegin; } bool Script::isOnEnd() { return _nextCommand && _nextCommand->getSubType() == Command::kCommandEnd; } Command *Script::getBeginCommand() { return findChildWithSubtype(Command::kCommandBegin, false); } bool Script::isEnabled() { switch (_scriptType) { case kScriptTypeOnGameEvent: case kScriptTypeOnPlayerAction: return _enabled; case kScriptType3: return false; case kScriptTypePassiveDialog: case kScriptType4: return true; default: error("Unknown script type %d for script %s", _scriptType, getName().c_str()); } } void Script::enable(bool value) { if (_scriptType == kScriptTypeOnGameEvent || _scriptType == kScriptTypeOnPlayerAction) { _enabled = value; } } bool Script::shouldExecute(uint32 callMode) { if ((!isEnabled() && isOnBegin()) || !_nextCommand) { return false; // Don't execute disabled scripts } if (callMode == kCallModeGameLoop && !isOnBegin()) { return true; // Continue previously running script } if (_scriptType == kScriptTypeOnGameEvent) { if (_runEvent == kGameEventOnGameLoop && callMode != kCallModeGameLoop) { return false; // Wrong call mode for this script } if (_runEvent == kGameEventOnEnterLocation && callMode != kCallModeEnterLocation) { return false; // Wrong call mode for this script } if (_runEvent == kGameEventOnExitLocation && callMode != kCallModeExitLocation) { return false; // Wrong call mode for this script } Item *parentItem = findParent(); if (parentItem && !parentItem->isEnabled()) { return false; // Disabled parent } } else if (_scriptType == kScriptTypePassiveDialog) { if (callMode != kCallModeDialogCreateSelections && callMode != kCallModeDialogAnswer) { return false; // Wrong call mode for this script } } else if (_scriptType == kScriptTypeOnPlayerAction) { if (callMode != kCallModePlayerAction) { return false; // Wrong call mode for this script } } else { return false; // Wrong script type } uint32 currentChapter = StarkGlobal->getCurrentChapter(); if (currentChapter < _minChapter || currentChapter >= _maxChapter) { return false; // Wrong chapter } return true; } bool Script::isSuspended() { return _pauseTimeLeft >= 0 || _suspendingResource; } Object *Script::getSuspendingResource() const { return _suspendingResource; } void Script::updateSuspended() { if (_pauseTimeLeft >= 0) { // Decrease the remaining pause time _pauseTimeLeft -= StarkGlobal->getMillisecondsPerGameloop(); } else { _pauseTimeLeft = -1; } if (_nextCommand->getSubType() == Command::kScriptPauseSkippable && (StarkUserInterface->wasInteractionDenied() || _pauseTimeLeft < 0)) { StarkUserInterface->setInteractive(true); _pauseTimeLeft = -1; } bool commandChanged = false; if (_suspendingResource) { // Check if the suspending resource is still active switch (_suspendingResource->getType().get()) { case Type::kDialog: { if (!StarkDialogPlayer->isRunning()) { // Resume the script execution if the dialog is complete _suspendingResource = nullptr; } break; } case Type::kFMV: { // Scripts are not running during an FMV, if we are here, then it has stopped playing _suspendingResource = nullptr; break; } case Type::kSoundItem: { Sound *soundItem = Object::cast(_suspendingResource); if (!soundItem->isPlaying()) { // Resume the script execution once the sound has stopped playing _suspendingResource = nullptr; } break; } case Type::kSpeech: { Speech *speech = Object::cast(_suspendingResource); if (!StarkDialogPlayer->isSpeechReady(speech) && !speech->isPlaying()) { // Resume the script execution once the speech has stopped playing _suspendingResource = nullptr; } break; } case Type::kScroll: { Scroll *scroll = Object::cast(_suspendingResource); if (!scroll->isActive()) { // Resume the script execution once the scroll target position is reached _suspendingResource = nullptr; } break; } case Type::kItem: { if (_nextCommand->getSubType() == Command::kWalkTo) { if (_resumeStatus == kResumeComplete) { // Resume the script execution once the item has stopped its movement _suspendingResource = nullptr; _nextCommand = _nextCommand->nextCommandIf(false); commandChanged = true; } else if (_resumeStatus == kResumeAbort) { // Resume the script execution once the item has stopped its movement _suspendingResource = nullptr; _nextCommand = _nextCommand->nextCommandIf(true); commandChanged = true; } } else { if (_resumeStatus != kResumeSuspend) { // Resume the script execution once the item has stopped its movement _suspendingResource = nullptr; } } break; } case Type::kAnim: { Anim *anim = Object::cast(_suspendingResource); if (anim->isDone()) { anim->resetItem(); // Resume the script execution once the animation is complete _suspendingResource = nullptr; } break; } default: error("Unhandled suspending resource type %s", _suspendingResource->getType().getName()); } } if (_nextCommand->getSubType() == Command::kItemSetActivity && !_suspendingResource) { _nextCommand->resumeItemSetActivity(); } if (!isSuspended() && _shouldResetGameSpeed) { StarkGlobal->setNormalSpeed(); } if (!isSuspended() && !commandChanged) { // Resume to the next command goToNextCommand(); } } void Script::stop() { reset(); _enabled = false; _returnObjects.clear(); } void Script::pause(int32 msecs) { _pauseTimeLeft = msecs; } void Script::suspend(Object *cause) { _suspendingResource = cause; _resumeStatus = kResumeSuspend; } void Script::setResumeStatus(ResumeStatus status) { _resumeStatus = status; } void Script::goToNextCommand() { _nextCommand = _nextCommand->nextCommand(); } void Script::execute(uint32 callMode) { if (!shouldExecute(callMode)) { return; } if (isSuspended()) { // If the script is suspended, check if it can be resumed updateSuspended(); } uint32 executedCommands = 0; while (1) { if (isSuspended()) { break; } if (!_nextCommand) { break; // No next command, stop here } if (isOnEnd()) { break; // Reached the end of the script } _nextCommand = _nextCommand->execute(callMode, this); executedCommands++; if (executedCommands > 50) { break; // Too many consecutive commands } } if (isOnEnd() || !_nextCommand) { // Reset ended scripts so they can be started again reset(); // Check if we should return to some caller script if (!_returnObjects.empty()) { Object *callerObject = _returnObjects.back(); _returnObjects.pop_back(); // Resume execution of the caller object resumeCallerExecution(callerObject); } } } void Script::resumeCallerExecution(Object *callerObject) { switch (callerObject->getType().get()) { case Type::kCommand: { Command *callerCommand = Object::cast(callerObject); _nextCommand = callerCommand->nextCommand(); break; } case Type::kDialog: { Dialog *callerDialog = Object::cast(callerObject); StarkDialogPlayer->resume(callerDialog); break; } default: error("Unhandled caller object type %s", callerObject->getType().getName()); } } void Script::addReturnObject(Object *object) { _returnObjects.push_back(object); } void Script::print(uint depth) { printDescription(depth); printData(); // Print anything that is not a command for (uint i = 0; i < _children.size(); i++) { if (_children[i]->getType() != Type::kCommand) { _children[i]->print(depth + 1); } } Tools::Decompiler *decompiler = new Tools::Decompiler(this); // Print the decompiled output printWithDepth(depth + 1, "Decompiled output"); if (decompiler->getError() == "") { decompiler->printDecompiled(); } else { debug("Decompilation failure: %s", decompiler->getError().c_str()); } delete decompiler; } void Script::printData() { debug("scriptType: %d", _scriptType); debug("runEvent: %d", _runEvent); debug("minChapter: %d", _minChapter); debug("maxChapter: %d", _maxChapter); debug("shouldResetGameSpeed: %d", _shouldResetGameSpeed); } void Script::saveLoad(ResourceSerializer *serializer) { serializer->syncAsSint32LE(_enabled); } void Script::saveLoadCurrent(ResourceSerializer *serializer) { bool isStarted = !isOnBegin(); serializer->syncAsUint32LE(isStarted); if (isStarted) { serializer->syncAsResourceReference(&_nextCommand); serializer->syncArraySize(_returnObjects); for (uint i = 0; i < _returnObjects.size(); i++) { serializer->syncAsResourceReference(&_returnObjects[i]); } serializer->syncAsSint32LE(_pauseTimeLeft); serializer->syncAsResourceReference(&_suspendingResource); serializer->syncAsSint32LE(_resumeStatus); } } } // End of namespace Resources } // End of namespace Stark