diff options
Diffstat (limited to 'engines/pegasus')
182 files changed, 53956 insertions, 0 deletions
diff --git a/engines/pegasus/ai/ai_action.cpp b/engines/pegasus/ai/ai_action.cpp new file mode 100644 index 0000000000..38d639038f --- /dev/null +++ b/engines/pegasus/ai/ai_action.cpp @@ -0,0 +1,78 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/ai/ai_action.h" +#include "pegasus/ai/ai_area.h" + +namespace Pegasus { + +AICompoundAction::~AICompoundAction() { + for (AIActionList::iterator it = _compoundActions.begin(); it != _compoundActions.end(); it++) + delete *it; +} + +void AICompoundAction::performAIAction(AIRule *rule) { + for (AIActionList::iterator it = _compoundActions.begin(); it != _compoundActions.end(); it++) + (*it)->performAIAction(rule); +} + +AIPlayMessageAction::AIPlayMessageAction(const Common::String &movieName, bool keepLastFrame, const InputBits interruptionFilter) { + _movieName = movieName; + _keepLastFrame = keepLastFrame; + _interruptionFilter = interruptionFilter; +} + +void AIPlayMessageAction::performAIAction(AIRule *) { + if (g_AIArea) { + g_AIArea->checkMiddleArea(); + g_AIArea->playAIMovie(kRightAreaSignature, _movieName, _keepLastFrame, _interruptionFilter); + } +} + +AIStartTimerAction::AIStartTimerAction(AITimerCondition *timerCondition) { + _timerCondition = timerCondition; +} + +void AIStartTimerAction::performAIAction(AIRule *) { + _timerCondition->startTimer(); +} + +AIActivateRuleAction::AIActivateRuleAction(AIRule *rule) { + _rule = rule; +} + +void AIActivateRuleAction::performAIAction(AIRule *) { + _rule->activateRule(); +} + +AIDeactivateRuleAction::AIDeactivateRuleAction(AIRule *rule) { + _rule = rule; +} + +void AIDeactivateRuleAction::performAIAction(AIRule *) { + _rule->deactivateRule(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/ai/ai_action.h b/engines/pegasus/ai/ai_action.h new file mode 100644 index 0000000000..6eac976f9c --- /dev/null +++ b/engines/pegasus/ai/ai_action.h @@ -0,0 +1,136 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_AI_AIACTION_H +#define PEGASUS_AI_AIACTION_H + +#include "common/list.h" + +#include "pegasus/input.h" +#include "pegasus/types.h" + +namespace Pegasus { + +class AIRule; +class AITimerCondition; + +///////////////////////////////////////////// +// +// AIAction + +class AIAction { +friend class AIRule; +public: + AIAction() { _actionCount = 1; } + virtual ~AIAction() {} + + virtual void performAIAction(AIRule *) = 0; + + void setActionCount(const uint32 count) { _actionCount = count; } + +protected: + uint32 _actionCount; +}; + +typedef Common::List<AIAction *> AIActionList; + +///////////////////////////////////////////// +// +// AICompoundAction + +class AICompoundAction : public AIAction { +public: + AICompoundAction() {} + virtual ~AICompoundAction(); + + void addAction(AIAction *action) { _compoundActions.push_back(action); } + + virtual void performAIAction(AIRule *); + +protected: + AIActionList _compoundActions; +}; + +///////////////////////////////////////////// +// +// AIPlayMessageAction + +class AIPlayMessageAction : public AIAction { +public: + AIPlayMessageAction(const Common::String &movieName, bool keepLastFrame, const InputBits = kWarningInterruption); + + virtual void performAIAction(AIRule *); + +protected: + Common::String _movieName; + InputBits _interruptionFilter; + bool _keepLastFrame; +}; + +///////////////////////////////////////////// +// +// AIStartTimerAction + +class AIStartTimerAction : public AIAction { +public: + AIStartTimerAction(AITimerCondition *); + + virtual void performAIAction(AIRule *); + +protected: + AITimerCondition *_timerCondition; +}; + +///////////////////////////////////////////// +// +// AIActivateRuleAction + +class AIActivateRuleAction : public AIAction { +public: + AIActivateRuleAction(AIRule *); + + virtual void performAIAction(AIRule *); + +protected: + AIRule *_rule; +}; + +///////////////////////////////////////////// +// +// AIDeactivateRuleAction + +class AIDeactivateRuleAction : public AIAction { +public: + AIDeactivateRuleAction(AIRule *rule); + + virtual void performAIAction(AIRule *); + +protected: + AIRule *_rule; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/ai/ai_area.cpp b/engines/pegasus/ai/ai_area.cpp new file mode 100644 index 0000000000..e4d31049f2 --- /dev/null +++ b/engines/pegasus/ai/ai_area.cpp @@ -0,0 +1,613 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/memstream.h" + +#include "pegasus/cursor.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/aichip.h" +#include "pegasus/items/biochips/biochipitem.h" +#include "pegasus/items/biochips/opticalchip.h" +#include "pegasus/items/biochips/pegasuschip.h" +#include "pegasus/items/inventory/airmask.h" +#include "pegasus/items/inventory/inventoryitem.h" + +namespace Pegasus { + +AIArea *g_AIArea = 0; + +AIArea::AIArea(InputHandler *nextHandler) : InputHandler(nextHandler), _leftAreaMovie(kAILeftAreaID), + _middleAreaMovie(kAIMiddleAreaID), _rightAreaMovie(kAIRightAreaID), _AIMovie(kAIMovieID) { + g_AIArea = this; + _leftAreaOwner = kNoClientSignature; + _middleAreaOwner = kNoClientSignature; + _rightAreaOwner = kNoClientSignature; + _leftInventoryTime = 0xffffffff; + _middleInventoryTime = 0xffffffff; + _middleBiochipTime = 0xffffffff; + _rightBiochipTime = 0xffffffff; + _lockCount = 0; + startIdling(); +} + +AIArea::~AIArea() { + if (_middleAreaOwner == kBiochipSignature) { + BiochipItem *currentBiochip = ((PegasusEngine *)g_engine)->getCurrentBiochip(); + if (currentBiochip && currentBiochip->isSelected()) + currentBiochip->giveUpSharedArea(); + } else if (_middleAreaOwner == kInventorySignature) { + InventoryItem *currentItem = ((PegasusEngine *)g_engine)->getCurrentInventoryItem(); + if (currentItem && currentItem->isSelected()) + currentItem->giveUpSharedArea(); + } + + stopIdling(); + + for (AIRuleList::iterator it = _AIRules.begin(); it != _AIRules.end(); it++) + delete *it; + + g_AIArea = 0; +} + +// Save last state of AI rules... +void AIArea::saveAIState() { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + delete vm->_aiSaveStream; + + Common::MemoryWriteStreamDynamic out; + writeAIRules(&out); + + vm->_aiSaveStream = new Common::MemoryReadStream(out.getData(), out.size(), DisposeAfterUse::YES); +} + +void AIArea::restoreAIState() { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + if (vm->_aiSaveStream) + readAIRules(vm->_aiSaveStream); +} + +void AIArea::writeAIRules(Common::WriteStream *stream) { + _AIRules.writeAIRules(stream); +} + +void AIArea::readAIRules(Common::ReadStream *stream) { + _AIRules.readAIRules(stream); +} + +void AIArea::initAIArea() { + allocateSurface(Common::Rect(0, 0, 384, 96)); + + _leftAreaMovie.shareSurface(this); + _leftAreaMovie.initFromMovieFile("Images/Items/Left Area Movie"); + _leftAreaMovie.moveElementTo(kAILeftAreaLeft, kAILeftAreaTop); + _leftAreaMovie.setDisplayOrder(kAILeftAreaOrder); + _leftAreaMovie.startDisplaying(); + _leftAreaMovie.setVolume(((PegasusEngine *)g_engine)->getSoundFXLevel()); + + _middleAreaMovie.shareSurface(this); + _middleAreaMovie.initFromMovieFile("Images/Items/Middle Area Movie"); + _middleAreaMovie.moveElementTo(kAIMiddleAreaLeft, kAIMiddleAreaTop); + _middleAreaMovie.moveMovieBoxTo(kAIMiddleAreaLeft - kAILeftAreaLeft, 0); + _middleAreaMovie.setDisplayOrder(kAIMiddleAreaOrder); + _middleAreaMovie.startDisplaying(); + _middleAreaMovie.setVolume(((PegasusEngine *)g_engine)->getSoundFXLevel()); + + _rightAreaMovie.shareSurface(this); + _rightAreaMovie.initFromMovieFile("Images/Items/Right Area Movie"); + _rightAreaMovie.moveElementTo(kAIRightAreaLeft, kAIRightAreaTop); + _rightAreaMovie.moveMovieBoxTo(kAIRightAreaLeft - kAILeftAreaLeft, 0); + _rightAreaMovie.setDisplayOrder(kAIRightAreaOrder); + _rightAreaMovie.startDisplaying(); + _rightAreaMovie.setVolume(((PegasusEngine *)g_engine)->getSoundFXLevel()); + + _AIMovie.setDisplayOrder(kAIMovieOrder); +} + +void AIArea::setAIVolume(const uint16 volume) { + _leftAreaMovie.setVolume(volume); + _middleAreaMovie.setVolume(volume); + _rightAreaMovie.setVolume(volume); +} + +// There are only so many legal combinations of client/area. +// Here is the list of supported pairs: +// kInventorySignature kLeftAreaSignature +// kInventorySignature kMiddleAreaSignature +// kBiochipSignature kMiddleAreaSignature +// kBiochipSignature kRightAreaSignature +// kAISignature kLeftAreaSignature +// Further, the kAISignature never sets a static frame time in the left area, +// but only plays a sequence. + +// If this function is called while a sequence is playing, it will just "remember" +// the time value, so that when the sequence finishes, the new time is asserted. + +void AIArea::setAIAreaToTime(const LowerClientSignature client, const LowerAreaSignature area, const TimeValue time) { + switch (area) { + case kLeftAreaSignature: + // Only support kInventorySignature client, since AI never calls SetAIAreaToTime. + _leftAreaMovie.setSegment(0, _leftAreaMovie.getDuration()); + + if (time == 0xffffffff) { + _leftAreaMovie.hide(); + _leftAreaOwner = kNoClientSignature; + } else { + setLeftMovieTime(time); + } + break; + case kMiddleAreaSignature: + // Only support kInventorySignature and kBiochipSignature clients. + _middleAreaMovie.stop(); + _middleAreaMovie.setFlags(0); + _middleAreaMovie.setSegment(0, _middleAreaMovie.getDuration()); + + if (time == 0xffffffff) { + if (client == kInventorySignature) { + if (_middleBiochipTime != 0xffffffff) { + setMiddleMovieTime(kBiochipSignature, _middleBiochipTime); + } else { + _middleAreaMovie.hide(); + _middleAreaOwner = kNoClientSignature; + } + } else { // client == kBiochipSignature + if (_middleInventoryTime != 0xffffffff) { + setMiddleMovieTime(kInventorySignature, _middleInventoryTime); + } else { + _middleAreaMovie.hide(); + _middleAreaOwner = kNoClientSignature; + } + } + } else { + setMiddleMovieTime(client, time); + } + break; + case kRightAreaSignature: + // Only support kBiochipSignature client. + _rightAreaMovie.setSegment(0, _rightAreaMovie.getDuration()); + + if (time == 0xffffffff) { + _rightAreaMovie.hide(); + _rightAreaOwner = kNoClientSignature; + } else { + setRightMovieTime(time); + } + break; + } +} + +// Plays a sequence on an area. When the sequence ends, the previous image +// is restored. +// Also, is input disabled or not? +// Easy answer: yes. + +// There are only so many legal combinations of client/area. +// Here is the list of supported pairs: +// kBiochipSignature kMiddleAreaSignature +// kBiochipSignature kRightAreaSignature +// kInventorySignature kMiddleAreaSignature + +void AIArea::playAIAreaSequence(const LowerClientSignature, const LowerAreaSignature area, const TimeValue start, const TimeValue stop) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + lockAIOut(); + + switch (area) { + case kLeftAreaSignature: + break; + case kMiddleAreaSignature: + if (_middleAreaOwner == kInventorySignature) + _middleInventoryTime = _middleAreaMovie.getTime(); + else if (_middleAreaOwner == kBiochipSignature) + _middleBiochipTime = _middleAreaMovie.getTime(); + + _middleAreaMovie.stop(); + _middleAreaMovie.setFlags(0); + _middleAreaMovie.setSegment(start, stop); + _middleAreaMovie.setTime(start); + _middleAreaMovie.show(); + _middleAreaMovie.start(); + vm->_cursor->hide(); + + while (_middleAreaMovie.isRunning()) { + vm->checkCallBacks(); + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + _middleAreaMovie.stop(); + vm->_cursor->hideUntilMoved(); + + if (_middleAreaOwner == kInventorySignature) + setAIAreaToTime(_middleAreaOwner, kMiddleAreaSignature, _middleInventoryTime); + else if (_middleAreaOwner == kBiochipSignature) + setAIAreaToTime(_middleAreaOwner, kMiddleAreaSignature, _middleBiochipTime); + else + setAIAreaToTime(_middleAreaOwner, kMiddleAreaSignature, 0xffffffff); + break; + case kRightAreaSignature: + _rightBiochipTime = _rightAreaMovie.getTime(); + _rightAreaMovie.setSegment(start, stop); + _rightAreaMovie.setTime(start); + _rightAreaMovie.show(); + _rightAreaMovie.start(); + vm->_cursor->hide(); + + while (_rightAreaMovie.isRunning()) { + vm->checkCallBacks(); + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + _rightAreaMovie.stop(); + vm->_cursor->hideUntilMoved(); + setAIAreaToTime(_rightAreaOwner, kRightAreaSignature, _rightBiochipTime); + break; + } + + unlockAI(); +} + +bool AIArea::playAIMovie(const LowerAreaSignature area, const Common::String &movieName, bool keepLastFrame, const InputBits interruptFilter) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + lockAIOut(); + + InputDevice.waitInput(interruptFilter); + if (_AIMovie.isMovieValid()) + _AIMovie.releaseMovie(); + + _AIMovie.shareSurface(this); + _AIMovie.initFromMovieFile(movieName); + + if (area == kLeftAreaSignature) { + _AIMovie.moveElementTo(kAILeftAreaLeft, kAILeftAreaTop); + _leftAreaMovie.hide(); + } else { + _AIMovie.moveElementTo(kAIRightAreaLeft, kAIRightAreaTop); + _AIMovie.moveMovieBoxTo(kAIRightAreaLeft - kAILeftAreaLeft, 0); + _rightAreaMovie.hide(); + } + + _AIMovie.setTime(0); + _AIMovie.startDisplaying(); + _AIMovie.show(); + _AIMovie.redrawMovieWorld(); + _AIMovie.setVolume(vm->getSoundFXLevel()); + _AIMovie.start(); + vm->_cursor->hide(); + + bool result = true; + bool saveAllowed = vm->swapSaveAllowed(false); + bool openAllowed = vm->swapLoadAllowed(false); + + while (_AIMovie.isRunning()) { + Input input; + InputDevice.getInput(input, interruptFilter); + + if (input.anyInput() || vm->shouldQuit() || vm->saveRequested() || vm->loadRequested()) { + result = false; + break; + } + + vm->checkCallBacks(); + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + _AIMovie.stop(); + + vm->swapSaveAllowed(saveAllowed); + vm->swapLoadAllowed(openAllowed); + + // This used to keep the last frame up even if the movie was interrupted. + // However, this only occurs in the recalibration, where interruption means skip the + // whole thing, so skipping causes the AI to go away even when keepLastFrame is true. + + if (!(result && keepLastFrame)) { + _AIMovie.stopDisplaying(); + _AIMovie.releaseMovie(); + + if (area == kLeftAreaSignature) { + _leftAreaMovie.setTime(_leftInventoryTime); + _leftAreaMovie.show(); + _leftAreaMovie.redrawMovieWorld(); + } else { + _rightAreaMovie.setTime(_rightBiochipTime); + _rightAreaMovie.show(); + _rightAreaMovie.redrawMovieWorld(); + } + } + + vm->_cursor->hideUntilMoved(); + unlockAI(); + return result; +} + +// Only implemented for kMiddleAreaSignature, kInventorySignature +void AIArea::loopAIAreaSequence(const LowerClientSignature owner, const LowerAreaSignature area, const TimeValue start, const TimeValue stop) { + if (area == kMiddleAreaSignature && owner == kInventorySignature && owner == _middleAreaOwner) { + _middleAreaMovie.stop(); + _middleAreaMovie.setFlags(0); + _middleAreaMovie.setSegment(start, stop); + _middleAreaMovie.setFlags(kLoopTimeBase); + _middleAreaMovie.setTime(start); + _middleAreaMovie.show(); + _middleAreaMovie.start(); + } +} + +// Only called by kInventorySignature. +void AIArea::setLeftMovieTime(const TimeValue time) { + if (!_AIMovie.isSurfaceValid()) { + _leftAreaMovie.setTime(time); + _leftAreaMovie.show(); + _leftAreaMovie.redrawMovieWorld(); + } + + _leftAreaOwner = kInventorySignature; + _leftInventoryTime = time; +} + +void AIArea::setMiddleMovieTime(const LowerClientSignature client, const TimeValue time) { + if (client == kInventorySignature) { + _middleInventoryTime = time; + if (_middleAreaOwner == kBiochipSignature) { + BiochipItem *currentBiochip = ((PegasusEngine *)g_engine)->getCurrentBiochip(); + if (currentBiochip && currentBiochip->isSelected()) + currentBiochip->giveUpSharedArea(); + } + } else { + _middleBiochipTime = time; + if (_middleAreaOwner == kInventorySignature) { + InventoryItem *currentItem = ((PegasusEngine *)g_engine)->getCurrentInventoryItem(); + if (currentItem && currentItem->isSelected()) + currentItem->giveUpSharedArea(); + } + } + + _middleAreaMovie.setSegment(0, _middleAreaMovie.getDuration()); + _middleAreaMovie.stop(); + _middleAreaMovie.setFlags(0); + _middleAreaMovie.setTime(time); + _middleAreaMovie.show(); + _middleAreaMovie.redrawMovieWorld(); + _middleAreaOwner = client; +} + +// Only called by kBiochipSignature. +void AIArea::setRightMovieTime(const TimeValue time) { + if (!_AIMovie.isSurfaceValid()) { + // Can't do it when the AI movie is up... + _rightAreaMovie.setTime(time); + _rightAreaMovie.show(); + _rightAreaMovie.redrawMovieWorld(); + } + + _rightAreaOwner = kBiochipSignature; + _rightBiochipTime = time; +} + +void AIArea::handleInput(const Input &input, const Hotspot *cursorSpot) { + if (JMPPPInput::isToggleAIMiddleInput(input)) + toggleMiddleAreaOwner(); + else + InputHandler::handleInput(input, cursorSpot); +} + +void AIArea::toggleMiddleAreaOwner() { + if (_middleAreaOwner == kInventorySignature) { + BiochipItem *currentBiochip = ((PegasusEngine *)g_engine)->getCurrentBiochip(); + if (currentBiochip) { + setMiddleMovieTime(kBiochipSignature, currentBiochip->getSharedAreaTime()); + currentBiochip->takeSharedArea(); + } + } else if (_middleAreaOwner == kBiochipSignature) { + InventoryItem *currentItem = ((PegasusEngine *)g_engine)->getCurrentInventoryItem(); + if (currentItem) { + setMiddleMovieTime(kInventorySignature, currentItem->getSharedAreaTime()); + currentItem->takeSharedArea(); + } + } +} + +void AIArea::activateHotspots() { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + if (_middleAreaOwner == kBiochipSignature) { + BiochipItem *currentBiochip = ((PegasusEngine *)g_engine)->getCurrentBiochip(); + if (currentBiochip) + switch (currentBiochip->getObjectID()) { + case kAIBiochip: + ((AIChip *)currentBiochip)->activateAIHotspots(); + break; + case kPegasusBiochip: + if (!vm->isDemo()) + ((PegasusChip *)currentBiochip)->activatePegasusHotspots(); + break; + case kOpticalBiochip: + ((OpticalChip *)currentBiochip)->activateOpticalHotspots(); + break; + } + } else if (_middleAreaOwner == kInventorySignature) { + InventoryItem *currentItem = ((PegasusEngine *)g_engine)->getCurrentInventoryItem(); + if (currentItem && currentItem->getObjectID() == kAirMask) + ((AirMask *)currentItem)->activateAirMaskHotspots(); + } + + InputHandler::activateHotspots(); +} + +void AIArea::clickInHotspot(const Input &input, const Hotspot *hotspot) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + bool handled = false; + + if (_middleAreaOwner == kBiochipSignature) { + BiochipItem *currentBiochip = ((PegasusEngine *)g_engine)->getCurrentBiochip(); + + if (currentBiochip) { + switch (currentBiochip->getObjectID()) { + case kAIBiochip: + if ((hotspot->getHotspotFlags() & kAIBiochipSpotFlag) != 0) { + ((AIChip *)currentBiochip)->clickInAIHotspot(hotspot->getObjectID()); + handled = true; + } + break; + case kPegasusBiochip: + if (!vm->isDemo() && ((hotspot->getHotspotFlags() & kPegasusBiochipSpotFlag) != 0)) { + ((PegasusChip *)currentBiochip)->clickInPegasusHotspot(); + handled = true; + } + break; + case kOpticalBiochip: + if ((hotspot->getHotspotFlags() & kOpticalBiochipSpotFlag) != 0) { + ((OpticalChip *)currentBiochip)->clickInOpticalHotspot(hotspot->getObjectID()); + handled = true; + } + break; + } + } + } else if (_middleAreaOwner == kInventorySignature) { + InventoryItem *currentItem = ((PegasusEngine *)g_engine)->getCurrentInventoryItem(); + + if (currentItem) { + switch (currentItem->getObjectID()) { + case kAirMask: + if ((hotspot->getHotspotFlags() & kAirMaskSpotFlag) != 0) { + ((AirMask *)currentItem)->clickInAirMaskHotspot(); + handled = true; + } + break; + } + } + } + + if (!handled) + InputHandler::clickInHotspot(input, hotspot); +} + +void AIArea::lockAIOut() { + if (_lockCount == 0) + stopIdling(); + + _lockCount++; +} + +void AIArea::unlockAI() { + if (_lockCount > 0) { + _lockCount--; + if (_lockCount == 0) + startIdling(); + } +} + +void AIArea::forceAIUnlocked() { + if (_lockCount > 0) { + _lockCount = 1; + unlockAI(); + } +} + +void AIArea::checkRules() { + if (_lockCount == 0 && ((PegasusEngine *)g_engine)->playerAlive()) + for (AIRuleList::iterator it = _AIRules.begin(); it != _AIRules.end(); it++) + if ((*it)->fireRule()) + break; +} + +void AIArea::useIdleTime() { + checkRules(); +} + +void AIArea::addAIRule(AIRule *rule) { + _AIRules.push_back(rule); +} + +void AIArea::removeAllRules() { + for (AIRuleList::iterator it = _AIRules.begin(); it != _AIRules.end(); it++) + delete *it; + + _AIRules.clear(); +} + +void AIArea::checkMiddleArea() { + BiochipItem *currentBiochip = ((PegasusEngine *)g_engine)->getCurrentBiochip(); + + if (currentBiochip) { + if (_middleAreaOwner == kBiochipSignature) { + switch (currentBiochip->getObjectID()) { + case kAIBiochip: + ((AIChip *)currentBiochip)->setUpAIChip(); + break; + case kPegasusBiochip: + ((PegasusChip *)currentBiochip)->setUpPegasusChip(); + break; + } + } else { + switch (currentBiochip->getObjectID()) { + case kAIBiochip: + ((AIChip *)currentBiochip)->setUpAIChipRude(); + break; + case kPegasusBiochip: + ((PegasusChip *)currentBiochip)->setUpPegasusChipRude(); + break; + } + } + } +} + +TimeValue AIArea::getBigInfoTime() { + if (_middleAreaOwner == kInventorySignature) { + InventoryItem *currentItem = ((PegasusEngine *)g_engine)->getCurrentInventoryItem(); + return currentItem->getInfoLeftTime(); + } else if (_middleAreaOwner == kBiochipSignature) { + BiochipItem *currentBiochip = ((PegasusEngine *)g_engine)->getCurrentBiochip(); + return currentBiochip->getInfoLeftTime(); + } + + return 0xffffffff; +} + +void AIArea::getSmallInfoSegment(TimeValue &start, TimeValue &stop) { + if (_middleAreaOwner == kInventorySignature) { + InventoryItem *currentItem = ((PegasusEngine *)g_engine)->getCurrentInventoryItem(); + currentItem->getInfoRightTimes(start, stop); + } else if (_middleAreaOwner == kBiochipSignature) { + BiochipItem *currentBiochip = ((PegasusEngine *)g_engine)->getCurrentBiochip(); + currentBiochip->getInfoRightTimes(start, stop); + } else { + start = 0xffffffff; + stop = 0xffffffff; + } +} + +LowerClientSignature AIArea::getMiddleAreaOwner() { + return _middleAreaOwner; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/ai/ai_area.h b/engines/pegasus/ai/ai_area.h new file mode 100644 index 0000000000..10bfc933a1 --- /dev/null +++ b/engines/pegasus/ai/ai_area.h @@ -0,0 +1,172 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_AI_AIAREA_H +#define PEGASUS_AI_AIAREA_H + +#include "pegasus/input.h" +#include "pegasus/movie.h" +#include "pegasus/timers.h" +#include "pegasus/ai/ai_rule.h" + +namespace Common { + class ReadStream; + class WriteStream; +} + +/* + + The AI area is the area at the bottom of the screen. There are three areas within + the AI area: + 1) the inventory/AI help area + 2) the middle area + 3) the biochip display area + + Area 1 is used for displaying the current inventory item. When the player changes the + current item, either by selecting a new one in the inventory list or by picking + up a new item, area 1 updates to show the new item. + + If the AI decides to give a message, the AI's head temporarily takes over area 1 + for the duration of the message, then goes away, returning the area to the current + inventory item. + + Area 2 is used to display the current inventory item's state, the current biochip's + state, and any extra information from the AI. The contention for this area is + resolved as follows: + -- If the AI needs to use the area while giving a message in area 1, it takes over + area 2 for the duration of its message. +*** This is not true. + -- If the player selects a new inventory item, the inventory item's state gets + displayed immediately. + -- If the player selects a new biochip, the biochip's state info gets displayed + immediately. + -- If any auto drawing is to occur, it seizes the area as soon as the drawing is + to occur. For example, the mapping biochip does auto drawing every time the + player takes a step. The only exception to this rule is if the AI is presenting + a warning. When the AI seizes areas 1 and 2, it preempts all other uses. + Some inventory items and biochips can cause arbitrary drawing to occur in this area + at arbitrary times. The map biochip is one example which causes drawing when the + player takes a step. Another example is the poison gas canister, which flashes in + this area to indicate a dangerous compound. + + Area 3 is used to display the current biochip. When the player changes the current + biochip, either by selecting a new one from the biochip list or by picking up a + new one, area 3 updates to show the new item. In addition, some biochips can play + animation in this area. + +*/ + +namespace Pegasus { + +class AIArea : public Surface, public Idler, public InputHandler { +public: + AIArea(InputHandler *); + virtual ~AIArea(); + + void writeAIRules(Common::WriteStream *stream); + void readAIRules(Common::ReadStream *stream); + + void initAIArea(); + + void saveAIState(); + void restoreAIState(); + + void handleInput(const Input &, const Hotspot *); + void activateHotspots(); + void clickInHotspot(const Input &, const Hotspot *); + + void setAIVolume(const uint16); + + // There are only so many legal combinations of client/area. + // Here is the list of supported pairs: + // kInventorySignature kLeftAreaSignature + // kInventorySignature kMiddleAreaSignature + // kBiochipSignature kMiddleAreaSignature + // kBiochipSignature kRightAreaSignature + // kAISignature kLeftAreaSignature + // Further, the kAISignature never sets a static frame time in the left area, + // but only plays a sequence from the AI movie. + void setAIAreaToTime(const LowerClientSignature, const LowerAreaSignature, const TimeValue); + + // The "Play" functions play the requested sequence synchronously. + void playAIAreaSequence(const LowerClientSignature, const LowerAreaSignature, const TimeValue, const TimeValue); + + // For PlayAIMovie, it is assumed that the client is the AI itself. + // This is used to play AI messages as well as Optical Memory video. + // Returns true if the movie played all the way through, false if it was interrupted. + bool playAIMovie(const LowerAreaSignature, const Common::String &movieName, bool keepLastFrame, const InputBits); + + // Loop the requested sequence indefinitely. + void loopAIAreaSequence(const LowerClientSignature, const LowerAreaSignature, const TimeValue, const TimeValue); + + void addAIRule(AIRule *); + + // Remove and delete all rules. + void removeAllRules(); + + void lockAIOut(); + void unlockAI(); + void forceAIUnlocked(); + + void checkMiddleArea(); + void checkRules(); + + LowerClientSignature getMiddleAreaOwner(); + void toggleMiddleAreaOwner(); + + TimeValue getBigInfoTime(); + void getSmallInfoSegment(TimeValue &, TimeValue &); + +protected: + void useIdleTime(); + + void setLeftMovieTime(const TimeValue); + void setMiddleMovieTime(const LowerClientSignature, const TimeValue); + void setRightMovieTime(const TimeValue); + + Movie _leftAreaMovie; + Movie _middleAreaMovie; + Movie _rightAreaMovie; + Movie _AIMovie; + + LowerClientSignature _leftAreaOwner; + LowerClientSignature _middleAreaOwner; + LowerClientSignature _rightAreaOwner; + + TimeValue _leftInventoryTime; + TimeValue _middleInventoryTime; + TimeValue _middleBiochipTime; + TimeValue _rightBiochipTime; + + AIRuleList _AIRules; + + uint _lockCount; +}; + +extern AIArea *g_AIArea; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/ai/ai_condition.cpp b/engines/pegasus/ai/ai_condition.cpp new file mode 100644 index 0000000000..df6d3227e5 --- /dev/null +++ b/engines/pegasus/ai/ai_condition.cpp @@ -0,0 +1,290 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/stream.h" + +#include "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_condition.h" +#include "pegasus/items/itemlist.h" +#include "pegasus/items/biochips/biochipitem.h" +#include "pegasus/items/inventory/inventoryitem.h" + +namespace Pegasus { + +AIOneChildCondition::AIOneChildCondition(AICondition *child) { + _child = child; +} + +AIOneChildCondition::~AIOneChildCondition() { + delete _child; +} + +void AIOneChildCondition::writeAICondition(Common::WriteStream *stream) { + if (_child) + _child->writeAICondition(stream); +} + +void AIOneChildCondition::readAICondition(Common::ReadStream *stream) { + if (_child) + _child->readAICondition(stream); +} + +AITwoChildrenCondition::AITwoChildrenCondition(AICondition *leftChild, AICondition *rightChild) { + _leftChild = leftChild; + _rightChild = rightChild; +} + +AITwoChildrenCondition::~AITwoChildrenCondition() { + delete _leftChild; + delete _rightChild; +} + +void AITwoChildrenCondition::writeAICondition(Common::WriteStream *stream) { + if (_leftChild) + _leftChild->writeAICondition(stream); + + if (_rightChild) + _rightChild->writeAICondition(stream); +} + +void AITwoChildrenCondition::readAICondition(Common::ReadStream *stream) { + if (_leftChild) + _leftChild->readAICondition(stream); + + if (_rightChild) + _rightChild->readAICondition(stream); +} + +AINotCondition::AINotCondition(AICondition* child) : AIOneChildCondition(child) { +} + +bool AINotCondition::fireCondition() { + return _child && !_child->fireCondition(); +} + +AIAndCondition::AIAndCondition(AICondition *leftChild, AICondition *rightChild) : AITwoChildrenCondition(leftChild, rightChild) { +} + +bool AIAndCondition::fireCondition() { + return _leftChild && _leftChild->fireCondition() && _rightChild && _rightChild->fireCondition(); +} + +AIOrCondition::AIOrCondition(AICondition *leftChild, AICondition *rightChild) : AITwoChildrenCondition(leftChild, rightChild) { +} + +bool AIOrCondition::fireCondition() { + return (_leftChild && _leftChild->fireCondition()) || (_rightChild && _rightChild->fireCondition()); +} + +AITimerCondition::AITimerCondition(const TimeValue time, const TimeScale scale, const bool shouldStartTimer) { + _timerFuse.primeFuse(time, scale); + _timerFuse.setFunctionPtr((tFunctionPtr)&AITimerFunction, (void *)this); + _fired = false; + + if (shouldStartTimer) + startTimer(); +} + +void AITimerCondition::startTimer() { + _fired = false; + _timerFuse.lightFuse(); +} + +void AITimerCondition::stopTimer() { + _timerFuse.stopFuse(); +} + +void AITimerCondition::writeAICondition(Common::WriteStream *stream) { + stream->writeByte(_timerFuse.isFuseLit()); + stream->writeByte(_fired); + stream->writeUint32BE(_timerFuse.getTimeRemaining()); + stream->writeUint32BE(_timerFuse.getFuseScale()); +} + +void AITimerCondition::readAICondition(Common::ReadStream *stream) { + bool running = stream->readByte(); + _fired = stream->readByte(); + TimeValue time = stream->readUint32BE(); + TimeScale scale = stream->readUint32BE(); + + _timerFuse.stopFuse(); + _timerFuse.primeFuse(time, scale); + + if (running) + _timerFuse.lightFuse(); +} + +void AITimerCondition::AITimerFunction(FunctionPtr *, AITimerCondition *condition) { + condition->_fired = true; +} + +bool AITimerCondition::fireCondition() { + return _fired; +} + +AILocationCondition::AILocationCondition(uint32 maxLocations) { + _numLocations = 0; + _maxLocations = maxLocations; + _locations = new RoomViewID[maxLocations]; +} + +AILocationCondition::~AILocationCondition() { + delete[] _locations; +} + +void AILocationCondition::addLocation(const RoomViewID location) { + if (_numLocations < _maxLocations) + _locations[_numLocations++] = location; +} + +bool AILocationCondition::fireCondition() { + RoomViewID test = GameState.getCurrentRoomAndView(), *p; + uint32 i; + + for (i = 0, p = _locations; i < _numLocations; i++, p++) { + if (test == *p) { + *p = MakeRoomView(kNoRoomID, kNoDirection); + return true; + } + } + + return false; +} + +void AILocationCondition::writeAICondition(Common::WriteStream *stream) { + stream->writeUint32BE(_maxLocations); + stream->writeUint32BE(_numLocations); + + uint32 i; + RoomViewID *p; + for (i = 0, p = _locations; i < _numLocations; i++, p++) + stream->writeUint32BE(*p); +} + +void AILocationCondition::readAICondition(Common::ReadStream *stream) { + uint32 maxLocations = stream->readUint32BE(); + + if (_maxLocations != maxLocations) { + delete[] _locations; + _locations = new RoomViewID[maxLocations]; + _maxLocations = maxLocations; + } + + _numLocations = stream->readUint32BE(); + + uint32 i; + RoomViewID *p; + for (i = 0, p = _locations; i < _numLocations; i++, p++) + *p = stream->readUint32BE(); +} + +AIDoorOpenedCondition::AIDoorOpenedCondition(RoomViewID doorLocation) { + _doorLocation = doorLocation; +} + +bool AIDoorOpenedCondition::fireCondition() { + return GameState.getCurrentRoomAndView() == _doorLocation && GameState.isCurrentDoorOpen(); +} + +AIHasItemCondition::AIHasItemCondition(const ItemID item) { + _item = item; +} + +bool AIHasItemCondition::fireCondition() { + return _item == kNoItemID || GameState.isTakenItemID(_item); +} + +AIDoesntHaveItemCondition::AIDoesntHaveItemCondition(const ItemID item) { + _item = item; +} + +bool AIDoesntHaveItemCondition::fireCondition() { + return _item == kNoItemID || !GameState.isTakenItemID(_item); +} + +AICurrentItemCondition::AICurrentItemCondition(const ItemID item) { + _item = item; +} + +bool AICurrentItemCondition::fireCondition() { + InventoryItem *item = ((PegasusEngine *)g_engine)->getCurrentInventoryItem(); + + if (_item == kNoItemID) + return item == 0; + + return item != 0 && item->getObjectID() == _item; +} + +AICurrentBiochipCondition::AICurrentBiochipCondition(const ItemID biochip) { + _biochip = biochip; +} + +bool AICurrentBiochipCondition::fireCondition() { + BiochipItem *biochip = ((PegasusEngine *)g_engine)->getCurrentBiochip(); + + if (_biochip == kNoItemID) + return biochip == 0; + + return biochip != 0 && biochip->getObjectID() == _biochip; +} + +AIItemStateCondition::AIItemStateCondition(const ItemID item, const ItemState state) { + _item = item; + _state = state; +} + +bool AIItemStateCondition::fireCondition() { + Item *item = g_allItems.findItemByID(_item); + return item != 0 && item->getItemState() == _state; +} + +AIEnergyMonitorCondition::AIEnergyMonitorCondition(const int32 energyThreshold) { + _energyThreshold = energyThreshold; +} + +bool AIEnergyMonitorCondition::fireCondition() { + return g_energyMonitor != 0 && g_energyMonitor->getCurrentEnergy() < _energyThreshold; +} + +AILastExtraCondition::AILastExtraCondition(const ExtraID lastExtra) { + _lastExtra = lastExtra; +} + +bool AILastExtraCondition::fireCondition() { + return g_neighborhood && (ExtraID)g_neighborhood->getLastExtra() == _lastExtra; +} + +AICondition *makeLocationAndDoesntHaveItemCondition(const RoomID room, const DirectionConstant direction, const ItemID item) { + AILocationCondition *location = new AILocationCondition(1); + location->addLocation(MakeRoomView(room, direction)); + + AIDoesntHaveItemCondition *doesntHaveItem = new AIDoesntHaveItemCondition(item); + + return new AIAndCondition(location, doesntHaveItem); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/ai/ai_condition.h b/engines/pegasus/ai/ai_condition.h new file mode 100644 index 0000000000..2d93a52eab --- /dev/null +++ b/engines/pegasus/ai/ai_condition.h @@ -0,0 +1,287 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_AI_AICONDITION_H +#define PEGASUS_AI_AICONDITION_H + +#include "pegasus/timers.h" + +namespace Common { + class ReadStream; + class WriteStream; +} + +namespace Pegasus { + +///////////////////////////////////////////// +// +// AICondition + +class AICondition { +public: + AICondition() {} + virtual ~AICondition() {} + + virtual bool fireCondition() = 0; + + // Only need these for conditions that are dynamic, like timer conditions... + // other conditions, like item related conditions, which don't change during + // the run of an environment, are completely initted when the environment + // is created. + virtual void writeAICondition(Common::WriteStream *) {} + virtual void readAICondition(Common::ReadStream *) {} +}; + +///////////////////////////////////////////// +// +// AIOneChildCondition + +class AIOneChildCondition : public AICondition { +public: + AIOneChildCondition(AICondition *); + virtual ~AIOneChildCondition(); + + virtual void writeAICondition(Common::WriteStream *); + virtual void readAICondition(Common::ReadStream *); + +protected: + AICondition *_child; +}; + +///////////////////////////////////////////// +// +// AITwoChildrenCondition + +class AITwoChildrenCondition : public AICondition { +public: + AITwoChildrenCondition(AICondition *, AICondition *); + virtual ~AITwoChildrenCondition(); + + virtual void writeAICondition(Common::WriteStream *); + virtual void readAICondition(Common::ReadStream *); + +protected: + AICondition *_leftChild, *_rightChild; +}; + +///////////////////////////////////////////// +// +// AINotCondition + +class AINotCondition : public AIOneChildCondition { +public: + AINotCondition(AICondition *); + + virtual bool fireCondition(); +}; + +///////////////////////////////////////////// +// +// AIAndCondition + +class AIAndCondition : public AITwoChildrenCondition { +public: + AIAndCondition(AICondition *, AICondition *); + + virtual bool fireCondition(); +}; + +///////////////////////////////////////////// +// +// AIOrCondition + +class AIOrCondition : public AITwoChildrenCondition { +public: + AIOrCondition(AICondition *, AICondition *); + + virtual bool fireCondition(); +}; + +///////////////////////////////////////////// +// +// AITimerCondition + +class AITimerCondition : public AICondition { +public: + AITimerCondition(const TimeValue, const TimeScale, const bool); + + void startTimer(); + void stopTimer(); + + virtual bool fireCondition(); + + virtual void writeAICondition(Common::WriteStream *); + virtual void readAICondition(Common::ReadStream *); + +protected: + static void AITimerFunction(FunctionPtr *, AITimerCondition *); + + FuseFunction _timerFuse; + bool _fired; +}; + +///////////////////////////////////////////// +// +// AILocationCondition + +class AILocationCondition : public AICondition { +public: + AILocationCondition(uint32); + virtual ~AILocationCondition(); + + void addLocation(RoomViewID); + virtual bool fireCondition(); + + virtual void writeAICondition(Common::WriteStream *); + virtual void readAICondition(Common::ReadStream *); + +protected: + uint32 _numLocations, _maxLocations; + RoomViewID *_locations; +}; + +///////////////////////////////////////////// +// +// AIDoorOpenedCondition + +class AIDoorOpenedCondition : public AICondition { +public: + AIDoorOpenedCondition(RoomViewID); + virtual ~AIDoorOpenedCondition() {} + + virtual bool fireCondition(); + +protected: + RoomViewID _doorLocation; +}; + +///////////////////////////////////////////// +// +// AIHasItemCondition + +class AIHasItemCondition : public AICondition { +public: + AIHasItemCondition(const ItemID); + + virtual bool fireCondition(); + +protected: + ItemID _item; +}; + +///////////////////////////////////////////// +// +// AIDoesntHaveItemCondition + +class AIDoesntHaveItemCondition : public AICondition { +public: + AIDoesntHaveItemCondition(const ItemID); + + virtual bool fireCondition(); + +protected: + ItemID _item; +}; + +///////////////////////////////////////////// +// +// AICurrentItemCondition + +class AICurrentItemCondition : public AICondition { +public: + AICurrentItemCondition(const ItemID); + + virtual bool fireCondition(); + +protected: + ItemID _item; +}; + +///////////////////////////////////////////// +// +// AICurrentBiochipCondition + +class AICurrentBiochipCondition : public AICondition { +public: + AICurrentBiochipCondition(const ItemID); + + virtual bool fireCondition(); + +protected: + ItemID _biochip; +}; + +///////////////////////////////////////////// +// +// AIItemStateCondition + +class AIItemStateCondition : public AICondition { +public: + AIItemStateCondition(const ItemID, const ItemState); + + virtual bool fireCondition(); + +protected: + ItemID _item; + ItemState _state; +}; + +///////////////////////////////////////////// +// +// AIEnergyMonitorCondition + +class AIEnergyMonitorCondition : public AICondition { +public: + AIEnergyMonitorCondition(const int32); + + virtual bool fireCondition(); + +protected: + int32 _energyThreshold; +}; + +///////////////////////////////////////////// +// +// AILastExtraCondition + +class AILastExtraCondition : public AICondition { +public: + AILastExtraCondition(const ExtraID); + + virtual bool fireCondition(); + +protected: + ExtraID _lastExtra; +}; + +///////////////////////////////////////////// +// +// Helper functions + +AICondition *makeLocationAndDoesntHaveItemCondition(const RoomID room, const DirectionConstant direction, const ItemID item); + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/ai/ai_rule.cpp b/engines/pegasus/ai/ai_rule.cpp new file mode 100644 index 0000000000..3aaa530a4a --- /dev/null +++ b/engines/pegasus/ai/ai_rule.cpp @@ -0,0 +1,78 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/stream.h" + +#include "pegasus/ai/ai_action.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/ai/ai_condition.h" +#include "pegasus/ai/ai_rule.h" + +namespace Pegasus { + +bool AIRule::fireRule() { + if (_ruleActive && _ruleCondition && _ruleAction && _ruleCondition->fireCondition()) { + if (g_AIArea) + g_AIArea->lockAIOut(); + + _ruleAction->performAIAction(this); + + if (--_ruleAction->_actionCount <= 0) + deactivateRule(); + + if (g_AIArea) + g_AIArea->unlockAI(); + + return true; + } + + return false; +} + +void AIRule::writeAIRule(Common::WriteStream *stream) { + stream->writeByte(_ruleActive); + + if (_ruleCondition) + _ruleCondition->writeAICondition(stream); +} + +void AIRule::readAIRule(Common::ReadStream *stream) { + _ruleActive = stream->readByte(); + + if (_ruleCondition) + _ruleCondition->readAICondition(stream); +} + +void AIRuleList::writeAIRules(Common::WriteStream *stream) { + for (AIRuleList::iterator it = begin(); it != end(); it++) + (*it)->writeAIRule(stream); +} + +void AIRuleList::readAIRules(Common::ReadStream *stream) { + for (AIRuleList::iterator it = begin(); it != end(); it++) + (*it)->readAIRule(stream); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/ai/ai_rule.h b/engines/pegasus/ai/ai_rule.h new file mode 100644 index 0000000000..bccd4ecb06 --- /dev/null +++ b/engines/pegasus/ai/ai_rule.h @@ -0,0 +1,86 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_AI_AIRULE_H +#define PEGASUS_AI_AIRULE_H + +#include "common/list.h" + +#include "pegasus/ai/ai_action.h" +#include "pegasus/ai/ai_condition.h" + +namespace Common { + class ReadStream; + class WriteStream; +} + +namespace Pegasus { + +class AICondition; +class AIAction; + +class AIRule { +public: + AIRule(AICondition *condition, AIAction *rule) { + _ruleCondition = condition; + _ruleAction = rule; + _ruleActive = true; + } + + ~AIRule() { + if (_ruleCondition) + delete _ruleCondition; + + if (_ruleAction) + delete _ruleAction; + } + + bool fireRule(); + + void activateRule() { _ruleActive = true; } + void deactivateRule() { _ruleActive = false; } + bool isRuleActive() { return _ruleActive; } + + void writeAIRule(Common::WriteStream *); + void readAIRule(Common::ReadStream *); + +protected: + AICondition *_ruleCondition; + AIAction *_ruleAction; + bool _ruleActive; +}; + +class AIRuleList : public Common::List<AIRule *> { +public: + AIRuleList() {} + ~AIRuleList() {} + + void writeAIRules(Common::WriteStream *); + void readAIRules(Common::ReadStream *); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/compass.cpp b/engines/pegasus/compass.cpp new file mode 100644 index 0000000000..6ae4e11a0d --- /dev/null +++ b/engines/pegasus/compass.cpp @@ -0,0 +1,82 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/compass.h" + +namespace Pegasus { + +Compass *g_compass = 0; + +Compass::Compass() : FaderAnimation(kCompassID) { + // Initialize it to east... + setFaderValue(90); + g_compass = this; +} + +Compass::~Compass() { + g_compass = 0; +} + +void Compass::initCompass() { + if (!isCompassValid()) { + Common::Rect r; + _compassImage.initFromPICTFile("Images/Compass/Compass"); + _compassImage.getSurfaceBounds(r); + r.right = kCompassWidth; + setBounds(r); + } +} + +void Compass::deallocateCompass() { + _compassImage.deallocateSurface(); +} + +void Compass::setFaderValue(const int32 angle) { + int16 a = angle % 360; + + if (a < 0) + a += 360; + + FaderAnimation::setFaderValue(a); +} + +void Compass::draw(const Common::Rect &r1) { + if (_compassImage.isSurfaceValid()) { + Common::Rect bounds; + getBounds(bounds); + + Common::Rect r2; + _compassImage.getSurfaceBounds(r2); + + CoordType width = r2.width(); + CoordType offsetH = width / 10 - bounds.width() / 2 + (getFaderValue() * width) / 450 - bounds.left; + CoordType offsetV = -bounds.top; + r2 = r1; + r2.translate(offsetH, offsetV); + _compassImage.drawImage(r2, r1); + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/compass.h b/engines/pegasus/compass.h new file mode 100644 index 0000000000..d66b432a38 --- /dev/null +++ b/engines/pegasus/compass.h @@ -0,0 +1,58 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_COMPASS_H +#define PEGASUS_COMPASS_H + +#include "pegasus/fader.h" +#include "pegasus/surface.h" + +namespace Pegasus { + +// Compass is defined with 0 as north, 90 east, 180 south, 270 west. +// Clockwise rotation increases the angle, counterclockwise rotation decreases the angle. + +class Compass : public FaderAnimation { +public: + Compass(); + virtual ~Compass(); + + void initCompass(); + void deallocateCompass(); + bool isCompassValid() const { return _compassImage.isSurfaceValid(); } + + void setFaderValue(const int32); + +protected: + void draw(const Common::Rect &); + + Frame _compassImage; +}; + +extern Compass *g_compass; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/console.cpp b/engines/pegasus/console.cpp new file mode 100644 index 0000000000..64bd0ba5f2 --- /dev/null +++ b/engines/pegasus/console.cpp @@ -0,0 +1,102 @@ +/* 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 "pegasus/console.h" +#include "pegasus/interface.h" +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/neighborhood.h" + +namespace Pegasus { + +PegasusConsole::PegasusConsole(PegasusEngine *vm) : GUI::Debugger(), _vm(vm) { + DCmd_Register("die", WRAP_METHOD(PegasusConsole, Cmd_Die)); + + // These functions are non-demo specific + if (!_vm->isDemo()) + DCmd_Register("jump", WRAP_METHOD(PegasusConsole, Cmd_Jump)); +} + +PegasusConsole::~PegasusConsole() { +} + +bool PegasusConsole::Cmd_Die(int argc, const char **argv) { + if (argc == 1) { + DebugPrintf("Usage: die <death reason>\n"); + return true; + } + + int reason = atoi(argv[1]); + + bool invalidReason = (reason == 0 || reason > kPlayerWonGame); + + if (!invalidReason && _vm->isDemo()) + invalidReason = (reason != kDeathFallOffCliff) && (reason != kDeathEatenByDinosaur) && + (reason != kDeathStranded) && (reason != kPlayerWonGame); + + + if (invalidReason) { + DebugPrintf("Invalid death reason %d\n", reason); + return true; + } + + _vm->die(atoi(argv[1])); + return false; +} + +bool PegasusConsole::Cmd_Jump(int argc, const char **argv) { + if (!g_interface) { + // TODO + DebugPrintf("Cannot jump without interface set up\n"); + return true; + } + + // TODO: Default room/direction for each neighborhood + + if (argc < 4) { + DebugPrintf("Usage: jump <neighborhood> <room> <direction>\n"); + return true; + } + + NeighborhoodID neighborhood = (NeighborhoodID)atoi(argv[1]); + RoomID room = (RoomID)atoi(argv[2]); + DirectionConstant direction = (DirectionConstant)atoi(argv[3]); + + if ((neighborhood < kCaldoriaID || neighborhood > kNoradDeltaID || neighborhood == kFinalTSAID) && + neighborhood != kNoradSubChaseID) { + DebugPrintf("Invalid neighborhood %d", neighborhood); + return true; + } + + // No real way to check room validity at this point + + if (direction > kWest) { + DebugPrintf("Invalid direction %d", direction); + return true; + } + + // Here we go! + // TODO: Can't clear menu since the engine is paused + _vm->jumpToNewEnvironment(neighborhood, room, direction); + return false; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/console.h b/engines/pegasus/console.h new file mode 100644 index 0000000000..f00ee25294 --- /dev/null +++ b/engines/pegasus/console.h @@ -0,0 +1,46 @@ +/* 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. + * + */ + +#ifndef PEGASUS_CONSOLE_H +#define PEGASUS_CONSOLE_H + +#include "gui/debugger.h" + +namespace Pegasus { + +class PegasusEngine; + +class PegasusConsole : public GUI::Debugger { +public: + PegasusConsole(PegasusEngine *vm); + virtual ~PegasusConsole(); + +private: + bool Cmd_Die(int argc, const char **argv); + bool Cmd_Jump(int argc, const char **argv); + + PegasusEngine *_vm; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/constants.h b/engines/pegasus/constants.h new file mode 100644 index 0000000000..77a80256ac --- /dev/null +++ b/engines/pegasus/constants.h @@ -0,0 +1,729 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_CONSTANTS_H +#define PEGASUS_CONSTANTS_H + +#include "common/endian.h" +#include "common/rect.h" + +#include "pegasus/types.h" + +namespace Pegasus { + +// TODO: Organize these + +static const GameID kGameIDNothing = -1; + +static const ActorID kNoActorID = kGameIDNothing; +static const ActorID kPlayerID = 0; +static const ItemID kNoItemID = kGameIDNothing; +static const RoomID kNoRoomID = kGameIDNothing; +static const ExtraID kNoExtraID = 0xFFFFFFFF; +static const NeighborhoodID kNoNeighborhoodID = kGameIDNothing; +static const AlternateID kNoAlternateID = 0; +static const GameMenuCommand kMenuCmdNoCommand = 0; + +static const HotSpotActivationID kActivateHotSpotAlways = 0; +static const HotSpotActivationID kActivateHotSpotNever = -1; + +static const ItemState kNoItemState = -1; + +static const DirectionConstant kNoDirection = 0xFF; +static const DirectionConstant kNorth = 0; +static const DirectionConstant kSouth = 1; +static const DirectionConstant kEast = 2; +static const DirectionConstant kWest = 3; + +static const TurnDirection kNoTurn = 0xFF; +static const TurnDirection kTurnLeft = 0; +static const TurnDirection kTurnRight = 1; +static const TurnDirection kTurnUp = 2; +static const TurnDirection kTurnDown = 3; +static const TurnDirection kMaxTurns = 4; + +static const GameMode kNoMode = -1; +static const GameMode kModeNavigation = 0; +static const GameMode kLastGameShellMode = kModeNavigation; + +static const CanMoveForwardReason kCanMoveForward = 0; +static const CanMoveForwardReason kCantMoveBlocked = kCanMoveForward + 1; +static const CanMoveForwardReason kCantMoveDoorClosed = kCantMoveBlocked + 1; +static const CanMoveForwardReason kCantMoveDoorLocked = kCantMoveDoorClosed + 1; +static const CanMoveForwardReason kCantMoveLastReason = kCantMoveDoorLocked; + +static const CanTurnReason kCanTurn = 0; +static const CanTurnReason kCantTurnNoTurn = kCanTurn + 1; +static const CanTurnReason kCantTurnLastReason = kCantTurnNoTurn; + +static const CanOpenDoorReason kCanOpenDoor = 0; +static const CanOpenDoorReason kCantOpenNoDoor = kCanOpenDoor + 1; +static const CanOpenDoorReason kCantOpenLocked = kCantOpenNoDoor + 1; +static const CanOpenDoorReason kCantOpenAlreadyOpen = kCantOpenLocked + 1; +static const CanOpenDoorReason kCantOpenLastReason = kCantOpenAlreadyOpen; + +static const DisplayElementID kNoDisplayElement = -1; +static const DisplayElementID kHighestReservedElementID = -2; + +static const DisplayElementID kCursorID = kHighestReservedElementID; +static const DisplayElementID kLoadScreenID = kCursorID - 1; + +static const DisplayOrder kMinAvailableOrder = 0; +static const DisplayOrder kMaxAvailableOrder = 999998; +static const DisplayOrder kLoadScreenOrder = 900000; +static const DisplayOrder kCursorOrder = 1000000; + +static const HotSpotID kNoHotSpotID = -1; +static const HotSpotFlags kNoHotSpotFlags = 0; +static const HotSpotFlags kAllHotSpotFlags = ~kNoHotSpotFlags; + +static const NotificationFlags kNoNotificationFlags = 0; + +static const DisplayElementID kCurrentDragSpriteID = 1000; + +static const TimeScale kDefaultTimeScale = 600; + +// Ticks per second. + +static const TimeScale kOneTickPerSecond = 1; +static const TimeScale kTwoTicksPerSecond = 2; +static const TimeScale kFifteenTicksPerSecond = 15; +static const TimeScale kThirtyTicksPerSecond = 30; +static const TimeScale kSixtyTicksPerSecond = 60; +static const TimeScale kMovieTicksPerSecond = 600; + +// These times are in seconds. + +static const TimeValue kOneSecond = 1; +static const TimeValue kTwoSeconds = 2; +static const TimeValue kThreeSeconds = 3; +static const TimeValue kFourSeconds = 4; +static const TimeValue kFiveSeconds = 5; +static const TimeValue kSixSeconds = 6; +static const TimeValue kSevenSeconds = 7; +static const TimeValue kEightSeconds = 8; +static const TimeValue kNineSeconds = 9; +static const TimeValue kTenSeconds = 10; +static const TimeValue kElevenSeconds = 11; +static const TimeValue kTwelveSeconds = 12; +static const TimeValue kThirteenSeconds = 13; +static const TimeValue kFourteenSeconds = 14; +static const TimeValue kFifteenSeconds = 15; +static const TimeValue kSixteenSeconds = 16; +static const TimeValue kSeventeenSeconds = 17; +static const TimeValue kEighteenSeconds = 18; +static const TimeValue kNineteenSeconds = 19; +static const TimeValue kTwentySeconds = 20; +static const TimeValue kThirtySeconds = 30; +static const TimeValue kFortySeconds = 40; +static const TimeValue kFiftySeconds = 50; +static const TimeValue kSixtySeconds = 60; +static const TimeValue kOneMinute = 60; +static const TimeValue kTwoMinutes = kOneMinute * 2; +static const TimeValue kThreeMinutes = kOneMinute * 3; +static const TimeValue kFourMinutes = kOneMinute * 4; +static const TimeValue kFiveMinutes = kOneMinute * 5; +static const TimeValue kSixMinutes = kOneMinute * 6; +static const TimeValue kSevenMinutes = kOneMinute * 7; +static const TimeValue kEightMinutes = kOneMinute * 8; +static const TimeValue kNineMinutes = kOneMinute * 9; +static const TimeValue kTenMinutes = kOneMinute * 10; +static const TimeValue kElevenMinutes = kOneMinute * 11; +static const TimeValue kTwelveMinutes = kOneMinute * 12; +static const TimeValue kThirteenMinutes = kOneMinute * 13; +static const TimeValue kFourteenMinutes = kOneMinute * 14; +static const TimeValue kFifteenMinutes = kOneMinute * 15; +static const TimeValue kSixteenMinutes = kOneMinute * 16; +static const TimeValue kSeventeenMinutes = kOneMinute * 17; +static const TimeValue kEighteenMinutes = kOneMinute * 18; +static const TimeValue kNineteenMinutes = kOneMinute * 19; +static const TimeValue kTwentyMinutes = kOneMinute * 20; +static const TimeValue kThirtyMinutes = kOneMinute * 30; +static const TimeValue kFortyMinutes = kOneMinute * 40; +static const TimeValue kFiftyMinutes = kOneMinute * 50; +static const TimeValue kOneHour = kOneMinute * 60; +static const TimeValue kTwoHours = kOneHour * 2; + +// Common times. + +static const TimeValue kHalfSecondPerTwoTicks = kTwoTicksPerSecond / 2; +static const TimeValue kHalfSecondPerThirtyTicks = kThirtyTicksPerSecond / 2; +static const TimeValue kHalfSecondPerSixtyTicks = kSixtyTicksPerSecond / 2; + +static const TimeValue kOneSecondPerTwoTicks = kTwoTicksPerSecond; +static const TimeValue kOneSecondPerThirtyTicks = kThirtyTicksPerSecond; +static const TimeValue kOneSecondPerSixtyTicks = kSixtyTicksPerSecond; + +static const TimeValue kOneMinutePerFifteenTicks = kOneMinute * kFifteenTicksPerSecond; +static const TimeValue kFiveMinutesPerFifteenTicks = kFiveMinutes * kFifteenTicksPerSecond; +static const TimeValue kTenMinutesPerFifteenTicks = kTenMinutes * kFifteenTicksPerSecond; + +static const TimeValue kOneMinutePerThirtyTicks = kOneMinute * kThirtyTicksPerSecond; +static const TimeValue kFiveMinutesPerThirtyTicks = kFiveMinutes * kThirtyTicksPerSecond; +static const TimeValue kTenMinutesPerThirtyTicks = kTenMinutes * kThirtyTicksPerSecond; + +static const TimeValue kOneMinutePerSixtyTicks = kOneMinute * kSixtyTicksPerSecond; +static const TimeValue kFiveMinutesPerSixtyTicks = kFiveMinutes * kSixtyTicksPerSecond; +static const TimeValue kTenMinutesPerSixtyTicks = kTenMinutes * kSixtyTicksPerSecond; + +// Time in seconds you can hang around Caldoria without going to work... +static const TimeValue kLateWarning2TimeLimit = kFiveMinutes; +static const TimeValue kLateWarning3TimeLimit = kTenMinutes; + +static const TimeValue kSinclairShootsTimeLimit = kThreeMinutes; +static const TimeValue kCardBombCountDownTime = kTwelveSeconds; + +static const TimeValue kOxyMaskFullTime = kThirtyMinutes; + +static const TimeValue kTSAUncreatedTimeLimit = kFiveMinutes; +static const TimeValue kRipTimeLimit = kTenMinutesPerFifteenTicks; +static const TimeScale kRipTimeScale = kFifteenTicksPerSecond; + +static const TimeValue kIntroTimeOut = kThirtySeconds; + +static const TimeValue kMarsRobotPatienceLimit = kFifteenSeconds; +static const TimeValue kLockFreezeTimeLmit = kFifteenSeconds; +static const TimeValue kSpaceChaseTimeLimit = kTenMinutes; +static const TimeValue kVacuumSurvivalTimeLimit = kThirtySeconds; +static const TimeValue kColorMatchingTimeLimit = kFourMinutes; +static const TimeScale kJunkTimeScale = kFifteenTicksPerSecond; +static const TimeValue kJunkDropBaseTime = kFiveSeconds; +static const TimeValue kJunkDropSlopTime = kThreeSeconds; +static const TimeValue kJunkTravelTime = kTenSeconds * kJunkTimeScale; +static const TimeValue kCollisionReboundTime = kOneSecond * kJunkTimeScale; +static const TimeValue kWeaponReboundTime = kTwoSeconds * kJunkTimeScale; + +static const TimeValue kGawkAtRobotTime = kTenSeconds; +static const TimeValue kGawkAtRobotTime2 = kThirteenSeconds; +static const TimeValue kPlasmaImpactTime = kTwoSeconds; + +static const TimeValue kNoradAirMaskTimeLimit = kOneMinute + kFifteenSeconds; + +static const NotificationID kNeighborhoodNotificationID = 1; +static const NotificationID kLastNeighborhoodNotificationID = kNeighborhoodNotificationID; + +static const NotificationFlags kNeighborhoodMovieCompletedFlag = 1; +static const NotificationFlags kMoveForwardCompletedFlag = kNeighborhoodMovieCompletedFlag << 1; +static const NotificationFlags kStrideCompletedFlag = kMoveForwardCompletedFlag << 1; +static const NotificationFlags kTurnCompletedFlag = kStrideCompletedFlag << 1; +static const NotificationFlags kSpotCompletedFlag = kTurnCompletedFlag << 1; +static const NotificationFlags kDoorOpenCompletedFlag = kSpotCompletedFlag << 1; +static const NotificationFlags kExtraCompletedFlag = kDoorOpenCompletedFlag << 1; +static const NotificationFlags kSpotSoundCompletedFlag = kExtraCompletedFlag << 1; +static const NotificationFlags kDelayCompletedFlag = kSpotSoundCompletedFlag << 1; +static const NotificationFlags kActionRequestCompletedFlag = kDelayCompletedFlag << 1; +static const NotificationFlags kDeathExtraCompletedFlag = kActionRequestCompletedFlag << 1; +static const NotificationFlags kLastNeighborhoodNotificationFlag = kDeathExtraCompletedFlag; + +static const NotificationFlags kNeighborhoodFlags = kNeighborhoodMovieCompletedFlag | + kMoveForwardCompletedFlag | + kStrideCompletedFlag | + kTurnCompletedFlag | + kSpotCompletedFlag | + kDoorOpenCompletedFlag | + kExtraCompletedFlag | + kSpotSoundCompletedFlag | + kDelayCompletedFlag | + kActionRequestCompletedFlag | + kDeathExtraCompletedFlag; + +static const uint32 kPegasusPrimeCreator = MKTAG('J', 'P', 'P', 'P'); +static const uint32 kPegasusPrimeContinueType = MKTAG('P', 'P', 'C', 'T'); + +static const uint32 kPegasusPrimeDisk1GameType = MKTAG('P', 'P', 'G', '1'); +static const uint32 kPegasusPrimeDisk2GameType = MKTAG('P', 'P', 'G', '2'); +static const uint32 kPegasusPrimeDisk3GameType = MKTAG('P', 'P', 'G', '3'); +static const uint32 kPegasusPrimeDisk4GameType = MKTAG('P', 'P', 'G', '4'); + +// We only support one of the save versions; the rest are from betas +// and we are not supporting them. +static const uint32 kPegasusPrimeVersion = 0x00009019; + +static const char kNormalSave = 0; +static const char kContinueSave = 1; + +// Display IDs. + +static const DisplayElementID kNavMovieID = 1; +static const DisplayElementID kTurnPushID = 2; + +static const DisplayElementID kMaxGameShellDisplayID = kTurnPushID; + +// Display ordering. + +static const DisplayOrder kNavLayer = 10000; +static const DisplayOrder kNavMovieOrder = kNavLayer; +static const DisplayOrder kTurnPushOrder = kNavMovieOrder + 1; + +///////////////////////////////////////////// +// +// Display IDs. + +static const DisplayElementID kScreenDimmerID = kMaxGameShellDisplayID + 1; +static const DisplayElementID kInterface1ID = kScreenDimmerID + 1; +static const DisplayElementID kInterface2ID = kInterface1ID + 1; +static const DisplayElementID kInterface3ID = kInterface2ID + 1; +static const DisplayElementID kInterface4ID = kInterface3ID + 1; +static const DisplayElementID kDateID = kInterface4ID + 1; +static const DisplayElementID kCompassID = kDateID + 1; +static const DisplayElementID kInventoryPushID = kCompassID + 1; +static const DisplayElementID kInventoryLidID = kInventoryPushID + 1; +static const DisplayElementID kBiochipPushID = kInventoryLidID + 1; +static const DisplayElementID kBiochipLidID = kBiochipPushID + 1; +static const DisplayElementID kEnergyBarID = kBiochipLidID + 1; +static const DisplayElementID kWarningLightID = kEnergyBarID + 1; +static const DisplayElementID kAILeftAreaID = kWarningLightID + 1; +static const DisplayElementID kAIMiddleAreaID = kAILeftAreaID + 1; +static const DisplayElementID kAIRightAreaID = kAIMiddleAreaID + 1; +static const DisplayElementID kAIMovieID = kAIRightAreaID + 1; +static const DisplayElementID kInventoryDropHighlightID = kAIMovieID + 1; +static const DisplayElementID kBiochipDropHighlightID = kInventoryDropHighlightID + 1; + +static const DisplayElementID kDraggingSpriteID = 1000; + +static const DisplayElementID kCroppedMovieID = 2000; + +static const DisplayElementID kNeighborhoodDisplayID = 3000; + +static const DisplayElementID kItemPictureBaseID = 5000; + +static const CoordType kNavAreaLeft = 64; +static const CoordType kNavAreaTop = 64; + +static const CoordType kBackground1Left = 0; +static const CoordType kBackground1Top = 64; + +static const CoordType kBackground2Left = 0; +static const CoordType kBackground2Top = 0; + +static const CoordType kBackground3Left = 576; +static const CoordType kBackground3Top = 64; + +static const CoordType kBackground4Left = 0; +static const CoordType kBackground4Top = 320; + +static const CoordType kOverviewControllerLeft = 540; +static const CoordType kOverviewControllerTop = 348; + +static const CoordType kSwapLeft = 194; +static const CoordType kSwapTop = 116; + +static const CoordType kSwapHiliteLeft = 200; +static const CoordType kSwapHiliteTop = 206; + +static const CoordType kDateLeft = 136; +static const CoordType kDateTop = 44; + +static const CoordType kCompassLeft = 222; +static const CoordType kCompassTop = 42; +static const CoordType kCompassWidth = 92; + +static const CoordType kInventoryPushLeft = 74; +static const CoordType kInventoryPushTop = 92; + +static const CoordType kInventoryLidLeft = 74; +static const CoordType kInventoryLidTop = 316; + +static const CoordType kBiochipPushLeft = 362; +static const CoordType kBiochipPushTop = 192; + +static const CoordType kBiochipLidLeft = 362; +static const CoordType kBiochipLidTop = 316; + +static const CoordType kInventoryDropLeft = 0; +static const CoordType kInventoryDropTop = 320; +static const CoordType kInventoryDropRight = 232; +static const CoordType kInventoryDropBottom = 480; + +static const CoordType kBiochipDropLeft = 302; +static const CoordType kBiochipDropTop = 320; +static const CoordType kBiochipDropRight = 640; +static const CoordType kBiochipDropBottom = 480; + +static const CoordType kFinalMessageLeft = kInventoryPushLeft + 1; +static const CoordType kFinalMessageTop = kInventoryPushTop + 24; + +///////////////////////////////////////////// +// +// Notifications. + +static const NotificationID kJMPDCShellNotificationID = kLastNeighborhoodNotificationID + 1; +static const NotificationID kInterfaceNotificationID = kJMPDCShellNotificationID + 1; +static const NotificationID kAINotificationID = kInterfaceNotificationID + 1; +static const NotificationID kNoradNotificationID = kAINotificationID + 1; +static const NotificationID kNoradECRNotificationID = kNoradNotificationID + 1; +static const NotificationID kNoradFillingStationNotificationID = kNoradECRNotificationID + 1; +static const NotificationID kNoradPressureNotificationID = kNoradFillingStationNotificationID + 1; +static const NotificationID kNoradUtilityNotificationID = kNoradPressureNotificationID + 1; +static const NotificationID kNoradElevatorNotificationID = kNoradUtilityNotificationID + 1; +static const NotificationID kNoradSubPlatformNotificationID = kNoradElevatorNotificationID + 1; +static const NotificationID kSubControlNotificationID = kNoradSubPlatformNotificationID + 1; +static const NotificationID kNoradGreenBallNotificationID = kSubControlNotificationID + 1; +static const NotificationID kNoradGlobeNotificationID = kNoradGreenBallNotificationID + 1; +static const NotificationID kCaldoriaVidPhoneNotificationID = kNoradGlobeNotificationID + 1; +static const NotificationID kCaldoriaMessagesNotificationID = kCaldoriaVidPhoneNotificationID + 1; +static const NotificationID kCaldoriaBombTimerNotificationID = kCaldoriaMessagesNotificationID + 1; + +// Sent to the shell by fShellNotification. +static const NotificationFlags kGameStartingFlag = 1; +static const NotificationFlags kNeedNewJumpFlag = kGameStartingFlag << 1; +static const NotificationFlags kPlayerDiedFlag = kNeedNewJumpFlag << 1; + +static const NotificationFlags kJMPShellNotificationFlags = kGameStartingFlag | + kNeedNewJumpFlag | + kPlayerDiedFlag; + +// Sent to the interface. +static const NotificationFlags kInventoryLidOpenFlag = 1; +static const NotificationFlags kInventoryLidClosedFlag = kInventoryLidOpenFlag << 1; +static const NotificationFlags kInventoryDrawerUpFlag = kInventoryLidClosedFlag << 1; +static const NotificationFlags kInventoryDrawerDownFlag = kInventoryDrawerUpFlag << 1; +static const NotificationFlags kBiochipLidOpenFlag = kInventoryDrawerDownFlag << 1; +static const NotificationFlags kBiochipLidClosedFlag = kBiochipLidOpenFlag << 1; +static const NotificationFlags kBiochipDrawerUpFlag = kBiochipLidClosedFlag << 1; +static const NotificationFlags kBiochipDrawerDownFlag = kBiochipDrawerUpFlag << 1; + +static const NotificationFlags kInterfaceNotificationFlags = kInventoryLidOpenFlag | + kInventoryLidClosedFlag | + kInventoryDrawerUpFlag | + kInventoryDrawerDownFlag | + kBiochipLidOpenFlag | + kBiochipLidClosedFlag | + kBiochipDrawerUpFlag | + kBiochipDrawerDownFlag; + +// Hot spots. + +// Neighborhood hot spots. + +static const HotSpotID kFirstNeighborhoodSpotID = 5000; + +// kShellSpotFlag is a flag which marks all hot spots which belong to the shell, like +// the current item and current biochip spots. +static const HotSpotFlags kShellSpotFlag = 1; +// kNeighborhoodSpotFlag is a flag which marks all hot spots which belong to a +// neighborhood, like buttons on walls and so on. +static const HotSpotFlags kNeighborhoodSpotFlag = kShellSpotFlag << 1; +// kZoomInSpotFlag is a flag which marks all hot spots which indicate a zoom. +static const HotSpotFlags kZoomInSpotFlag = kNeighborhoodSpotFlag << 1; +// kZoomOutSpotFlag is a flag which marks all hot spots which indicate a zoom. +static const HotSpotFlags kZoomOutSpotFlag = kZoomInSpotFlag << 1; + +static const HotSpotFlags kClickSpotFlag = kZoomOutSpotFlag << 1; +static const HotSpotFlags kPlayExtraSpotFlag = kClickSpotFlag << 1; +static const HotSpotFlags kPickUpItemSpotFlag = kPlayExtraSpotFlag << 1; +static const HotSpotFlags kDropItemSpotFlag = kPickUpItemSpotFlag << 1; +static const HotSpotFlags kOpenDoorSpotFlag = kDropItemSpotFlag << 1; + +static const HotSpotFlags kZoomSpotFlags = kZoomInSpotFlag | kZoomOutSpotFlag; + +static const HotSpotFlags kHighestGameShellSpotFlag = kOpenDoorSpotFlag; + +///////////////////////////////////////////// +// +// Hot spots. + +// Shell hot spots. +// The shell reserves all hot spot IDs from 0 to 999 + +static const HotSpotID kCurrentItemSpotID = 0; +static const HotSpotID kCurrentBiochipSpotID = kCurrentItemSpotID + 1; + +static const HotSpotID kInventoryDropSpotID = kCurrentBiochipSpotID + 1; +static const HotSpotID kBiochipDropSpotID = kInventoryDropSpotID + 1; + +static const HotSpotID kInfoReturnSpotID = kBiochipDropSpotID + 1; + +static const HotSpotID kAIHint1SpotID = kInfoReturnSpotID + 1; +static const HotSpotID kAIHint2SpotID = kAIHint1SpotID + 1; +static const HotSpotID kAIHint3SpotID = kAIHint2SpotID + 1; +static const HotSpotID kAISolveSpotID = kAIHint3SpotID + 1; +static const HotSpotID kAIBriefingSpotID = kAISolveSpotID + 1; +static const HotSpotID kAIScanSpotID = kAIBriefingSpotID + 1; + +static const HotSpotID kPegasusRecallSpotID = kAIScanSpotID + 1; + +static const HotSpotID kAriesSpotID = kPegasusRecallSpotID + 1; +static const HotSpotID kMercurySpotID = kAriesSpotID + 1; +static const HotSpotID kPoseidonSpotID = kMercurySpotID + 1; + +static const HotSpotID kAirMaskToggleSpotID = kPoseidonSpotID + 1; + +static const HotSpotID kShuttleEnergySpotID = kAirMaskToggleSpotID + 1; +static const HotSpotID kShuttleGravitonSpotID = kShuttleEnergySpotID + 1; +static const HotSpotID kShuttleTractorSpotID = kShuttleGravitonSpotID + 1; +static const HotSpotID kShuttleViewSpotID = kShuttleTractorSpotID + 1; +static const HotSpotID kShuttleTransportSpotID = kShuttleViewSpotID + 1; + +// Most of these are obsolete: + +// kInventoryDropSpotFlag is a flag which marks hot spots which are valid drop spots +// for inventory items. +// static const HotSpotFlags kInventoryDropSpotFlag = kHighestGameShellSpotFlag << 1; + +// kBiochipDropSpotFlag is a flag which marks hot spots which are valid drop spots +// for biochips. +// static const HotSpotFlags kBiochipDropSpotFlag = kInventoryDropSpotFlag << 1; + +// kInventorySpotFlag is a flag which marks hot spots which indicate inventory items +// in the environment. +// static const HotSpotFlags kInventorySpotFlag = kBiochipDropSpotFlag << 1; + +// kBiochipSpotFlag is a flag which marks hot spots which indicate biochips +// in the environment. +static const HotSpotFlags kPickUpBiochipSpotFlag = kHighestGameShellSpotFlag << 1; +static const HotSpotFlags kDropBiochipSpotFlag = kPickUpBiochipSpotFlag << 1; + +static const HotSpotFlags kInfoReturnSpotFlag = kDropBiochipSpotFlag << 1; + +// Biochip and inventory hot spot flags... + +static const HotSpotFlags kAIBiochipSpotFlag = kInfoReturnSpotFlag << 1; +static const HotSpotFlags kPegasusBiochipSpotFlag = kAIBiochipSpotFlag << 1; +static const HotSpotFlags kOpticalBiochipSpotFlag = kPegasusBiochipSpotFlag << 1; +static const HotSpotFlags kAirMaskSpotFlag = kOpticalBiochipSpotFlag << 1; + +static const HotSpotFlags kJMPClickingSpotFlags = kClickSpotFlag | + kPlayExtraSpotFlag | + kOpenDoorSpotFlag | + kInfoReturnSpotFlag | + kAIBiochipSpotFlag | + kPegasusBiochipSpotFlag | + kOpticalBiochipSpotFlag | + kAirMaskSpotFlag; + +static const int32 kMainMenuID = 1; +static const int32 kPauseMenuID = 2; +static const int32 kCreditsMenuID = 3; +static const int32 kDeathMenuID = 4; + +///////////////////////////////////////////// +// +// Menu commands. + +static const GameMenuCommand kMenuCmdOverview = kMenuCmdNoCommand + 1; +static const GameMenuCommand kMenuCmdStartAdventure = kMenuCmdOverview + 1; +static const GameMenuCommand kMenuCmdStartWalkthrough = kMenuCmdStartAdventure + 1; +static const GameMenuCommand kMenuCmdRestore = kMenuCmdStartWalkthrough + 1; +static const GameMenuCommand kMenuCmdCredits = kMenuCmdRestore + 1; +static const GameMenuCommand kMenuCmdQuit = kMenuCmdCredits + 1; + +static const GameMenuCommand kMenuCmdDeathContinue = kMenuCmdQuit + 1; + +static const GameMenuCommand kMenuCmdDeathQuitDemo = kMenuCmdDeathContinue + 1; +static const GameMenuCommand kMenuCmdDeathMainMenuDemo = kMenuCmdDeathQuitDemo + 1; + +static const GameMenuCommand kMenuCmdDeathRestore = kMenuCmdDeathMainMenuDemo + 1; +static const GameMenuCommand kMenuCmdDeathMainMenu = kMenuCmdDeathRestore + 1; + +static const GameMenuCommand kMenuCmdPauseSave = kMenuCmdDeathMainMenu + 1; +static const GameMenuCommand kMenuCmdPauseContinue = kMenuCmdPauseSave + 1; +static const GameMenuCommand kMenuCmdPauseRestore = kMenuCmdPauseContinue + 1; +static const GameMenuCommand kMenuCmdPauseQuit = kMenuCmdPauseRestore + 1; + +static const GameMenuCommand kMenuCmdCreditsMainMenu = kMenuCmdPauseQuit + 1; + +static const GameMenuCommand kMenuCmdCancelRestart = kMenuCmdCreditsMainMenu + 1; +static const GameMenuCommand kMenuCmdEjectRestart = kMenuCmdCancelRestart + 1; + +static const TimeValue kMenuButtonHiliteTime = 20; +static const TimeScale kMenuButtonHiliteScale = kSixtyTicksPerSecond; + +// PICT resources: + +// Warning light PICTs: + +static const ResIDType kLightOffID = 128; +static const ResIDType kLightYellowID = 129; +static const ResIDType kLightOrangeID = 130; +static const ResIDType kLightRedID = 131; + +// Date PICTs: + +static const ResIDType kDatePrehistoricID = 138; +static const ResIDType kDate2112ID = 139; +static const ResIDType kDate2185ID = 140; +static const ResIDType kDate2310ID = 141; +static const ResIDType kDate2318ID = 142; + +///////////////////////////////////////////// +// +// Display Order + +static const DisplayOrder kCroppedMovieLayer = 11000; + +static const DisplayOrder kMonitorLayer = 12000; + +static const DisplayOrder kDragSpriteLayer = 15000; +static const DisplayOrder kDragSpriteOrder = kDragSpriteLayer; + +static const DisplayOrder kInterfaceLayer = 20000; +static const DisplayOrder kBackground1Order = kInterfaceLayer; +static const DisplayOrder kBackground2Order = kBackground1Order + 1; +static const DisplayOrder kBackground3Order = kBackground2Order + 1; +static const DisplayOrder kBackground4Order = kBackground3Order + 1; +static const DisplayOrder kDateOrder = kBackground4Order + 1; +static const DisplayOrder kCompassOrder = kDateOrder + 1; +static const DisplayOrder kEnergyBarOrder = kCompassOrder + 1; +static const DisplayOrder kEnergyLightOrder = kEnergyBarOrder + 1; + +static const DisplayOrder kAILayer = 22000; +static const DisplayOrder kAILeftAreaOrder = kAILayer; +static const DisplayOrder kAIMiddleAreaOrder = kAILeftAreaOrder + 1; +static const DisplayOrder kAIRightAreaOrder = kAIMiddleAreaOrder + 1; +static const DisplayOrder kAIMovieOrder = kAIRightAreaOrder + 1; + +static const DisplayOrder kHilitesLayer = 23000; +static const DisplayOrder kInventoryHiliteOrder = kHilitesLayer; +static const DisplayOrder kBiochipHiliteOrder = kInventoryHiliteOrder + 1; + +static const DisplayOrder kPanelsLayer = 25000; +static const DisplayOrder kInventoryPushOrder = kPanelsLayer; +static const DisplayOrder kInventoryLidOrder = kInventoryPushOrder + 1; +static const DisplayOrder kBiochipPushOrder = kInventoryLidOrder + 1; +static const DisplayOrder kBiochipLidOrder = kBiochipPushOrder + 1; +static const DisplayOrder kFinalMessageOrder = kBiochipLidOrder + 1; + +static const DisplayOrder kInfoLayer = 26000; +static const DisplayOrder kInfoBackgroundOrder = kInfoLayer; +static const DisplayOrder kInfoSpinOrder = kInfoBackgroundOrder + 1; + +static const DisplayOrder kScreenDimmerOrder = 30000; + +static const DisplayOrder kPauseScreenLayer = 31000; +static const DisplayOrder kPauseMenuOrder = kPauseScreenLayer; +static const DisplayOrder kSaveGameOrder = kPauseMenuOrder + 1; +static const DisplayOrder kContinueOrder = kSaveGameOrder + 1; +static const DisplayOrder kRestoreOrder = kContinueOrder + 1; +static const DisplayOrder kSoundFXOrder = kRestoreOrder + 1; +static const DisplayOrder kAmbienceOrder = kSoundFXOrder + 1; +static const DisplayOrder kWalkthruOrder = kAmbienceOrder + 1; +static const DisplayOrder kQuitToMainMenuOrder = kWalkthruOrder + 1; +static const DisplayOrder kPauseLargeHiliteOrder = kQuitToMainMenuOrder + 1; +static const DisplayOrder kPauseSmallHiliteOrder = kPauseLargeHiliteOrder + 1; + +///////////////////////////////////////////// +// +// Death reasons. +enum { + // Caldoria + kDeathUncreatedInCaldoria = 1, + kDeathCardBomb, + kDeathShotBySinclair, + kDeathSinclairShotDelegate, + kDeathNuclearExplosion, + + // TSA + kDeathUncreatedInTSA, + kDeathShotByTSARobots, + + // Prehistoric + kDeathFallOffCliff, + kDeathEatenByDinosaur, + kDeathStranded, + + // Norad + kDeathGassedInNorad, + kDeathArrestedInNorad, + kDeathWokeUpNorad, + kDeathSubDestroyed, // Unused + kDeathRobotThroughNoradDoor, + kDeathRobotSubControlRoom, + + // Mars + kDeathWrongShuttleLock, + kDeathArrestedInMars, + kDeathRunOverByPod, + kDeathDidntGetOutOfWay, + kDeathReactorBurn, + kDeathDidntFindMarsBomb, + kDeathDidntDisarmMarsBomb, + kDeathNoMaskInMaze, + kDeathNoAirInMaze, + kDeathGroundByMazebot, + kDeathMissedOreBucket, + kDeathDidntLeaveBucket, + kDeathRanIntoCanyonWall, // Unused + kDeathRanIntoSpaceJunk, + + // WSC + kDeathDidntStopPoison, + kDeathArrestedInWSC, + kDeathHitByPlasma, + kDeathShotOnCatwalk, + + // Winning + kPlayerWonGame +}; + +static const CoordType kAILeftAreaLeft = 76; +static const CoordType kAILeftAreaTop = 334; + +static const CoordType kAILeftAreaWidth = 96; +static const CoordType kAILeftAreaHeight = 96; + +static const CoordType kAIMiddleAreaLeft = 172; +static const CoordType kAIMiddleAreaTop = 334; + +static const CoordType kAIMiddleAreaWidth = 192; +static const CoordType kAIMiddleAreaHeight = 96; + +static const CoordType kAIRightAreaLeft = 364; +static const CoordType kAIRightAreaTop = 334; + +static const CoordType kAIRightAreaWidth = 96; +static const CoordType kAIRightAreaHeight = 96; + +enum { + kTSAPlayerNotArrived, // initial state, must be zero + kTSAPlayerForcedReview, // Player must watch TBP before rip occurs. + kTSAPlayerDetectedRip, // Player finished TBP, rip alarm just went off. + kTSAPlayerNeedsHistoricalLog, // Player is instructed to get historical log + kTSAPlayerGotHistoricalLog, + kTSAPlayerInstalledHistoricalLog, + kTSABossSawHistoricalLog, + kRobotsAtCommandCenter, + kRobotsAtFrontDoor, + kRobotsAtReadyRoom, + kPlayerLockedInPegasus, + kPlayerOnWayToPrehistoric, + kPlayerWentToPrehistoric, + kPlayerOnWayToNorad, + kPlayerOnWayToMars, + kPlayerOnWayToWSC, + kPlayerFinishedWithTSA +}; + +///////////////////////////////////////////// +// +// Mode static constants. + +static const GameMode kModeInventoryPick = kLastGameShellMode + 1; +static const GameMode kModeBiochipPick = kModeInventoryPick + 1; +static const GameMode kModeInfoScreen = kModeBiochipPick + 1; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/cursor.cpp b/engines/pegasus/cursor.cpp new file mode 100644 index 0000000000..205336a00b --- /dev/null +++ b/engines/pegasus/cursor.cpp @@ -0,0 +1,213 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/events.h" +#include "common/stream.h" +#include "common/system.h" +#include "graphics/cursorman.h" +#include "graphics/surface.h" +#include "graphics/decoders/pict.h" + +#include "pegasus/cursor.h" +#include "pegasus/graphics.h" +#include "pegasus/pegasus.h" + +namespace Pegasus { + +Cursor::Cursor() { + _cursorObscured = false; + _index = -1; + startIdling(); +} + +Cursor::~Cursor() { + for (uint32 i = 0; i < _info.size(); i++) { + if (_info[i].surface) { + _info[i].surface->free(); + delete _info[i].surface; + } + delete[] _info[i].palette; + } + + stopIdling(); +} + +void Cursor::addCursorFrames(uint16 id) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + Common::SeekableReadStream *cursStream = vm->_resFork->getResource(MKTAG('C', 'u', 'r', 's'), id); + if (!cursStream) + error("Could not load cursor frames set %d", id); + + uint16 frameCount = cursStream->readUint16BE(); + for (uint16 i = 0; i < frameCount; i++) { + CursorInfo info; + info.tag = cursStream->readUint16BE(); + info.hotspot.x = cursStream->readUint16BE(); + info.hotspot.y = cursStream->readUint16BE(); + info.surface = 0; + info.palette = 0; + info.colorCount = 0; + _info.push_back(info); + } + + delete cursStream; + + setCurrentFrameIndex(0); +} + +void Cursor::setCurrentFrameIndex(int32 index) { + if (_index != index) { + _index = index; + if (index != -1) { + loadCursorImage(_info[index]); + CursorMan.replaceCursorPalette(_info[index].palette, 0, _info[index].colorCount); + CursorMan.replaceCursor((byte *)_info[index].surface->pixels, _info[index].surface->w, _info[index].surface->h, _info[index].hotspot.x, _info[index].hotspot.y, 0); + ((PegasusEngine *)g_engine)->_gfx->markCursorAsDirty(); + } + } +} + +int32 Cursor::getCurrentFrameIndex() const { + return _index; +} + +void Cursor::show() { + if (!isVisible()) + CursorMan.showMouse(true); + + _cursorObscured = false; + ((PegasusEngine *)g_engine)->_gfx->markCursorAsDirty(); +} + +void Cursor::hide() { + CursorMan.showMouse(false); + setCurrentFrameIndex(0); + ((PegasusEngine *)g_engine)->_gfx->markCursorAsDirty(); +} + +void Cursor::hideUntilMoved() { + if (!_cursorObscured) { + hide(); + _cursorObscured = true; + } +} + +void Cursor::useIdleTime() { + if (g_system->getEventManager()->getMousePos() != _cursorLocation) { + _cursorLocation = g_system->getEventManager()->getMousePos(); + if (_index != -1 && _cursorObscured) + show(); + ((PegasusEngine *)g_engine)->_gfx->markCursorAsDirty(); + } +} + +void Cursor::getCursorLocation(Common::Point &pt) const { + pt = _cursorLocation; +} + +bool Cursor::isVisible() { + return CursorMan.isVisible(); +} + +void Cursor::loadCursorImage(CursorInfo &cursorInfo) { + if (cursorInfo.surface) + return; + + cursorInfo.surface = new Graphics::Surface(); + + PegasusEngine *vm = (PegasusEngine *)g_engine; + Common::SeekableReadStream *cicnStream = vm->_resFork->getResource(MKTAG('c', 'i', 'c', 'n'), cursorInfo.tag); + + if (!cicnStream) + error("Failed to find color icon %d", cursorInfo.tag); + + // PixMap section + Graphics::PICTDecoder::PixMap pixMap = Graphics::PICTDecoder::readPixMap(*cicnStream); + + // Mask section + cicnStream->readUint32BE(); // mask baseAddr + uint16 maskRowBytes = cicnStream->readUint16BE(); // mask rowBytes + cicnStream->skip(3 * 2); // mask rect + /* uint16 maskHeight = */ cicnStream->readUint16BE(); + + // Bitmap section + cicnStream->readUint32BE(); // baseAddr + uint16 rowBytes = cicnStream->readUint16BE(); + cicnStream->readUint16BE(); // top + cicnStream->readUint16BE(); // left + uint16 height = cicnStream->readUint16BE(); // bottom + cicnStream->readUint16BE(); // right + + // Data section + cicnStream->readUint32BE(); // icon handle + cicnStream->skip(maskRowBytes * height); // FIXME: maskHeight doesn't work here, though the specs say it should + cicnStream->skip(rowBytes * height); + + // Palette section + cicnStream->readUint32BE(); // always 0 + cicnStream->readUint16BE(); // always 0 + cursorInfo.colorCount = cicnStream->readUint16BE() + 1; + + cursorInfo.palette = new byte[cursorInfo.colorCount * 3]; + for (uint16 i = 0; i < cursorInfo.colorCount; i++) { + cicnStream->readUint16BE(); + cursorInfo.palette[i * 3] = cicnStream->readUint16BE() >> 8; + cursorInfo.palette[i * 3 + 1] = cicnStream->readUint16BE() >> 8; + cursorInfo.palette[i * 3 + 2] = cicnStream->readUint16BE() >> 8; + } + + // PixMap data + if (pixMap.pixelSize == 8) { + cursorInfo.surface->create(pixMap.rowBytes, pixMap.bounds.height(), Graphics::PixelFormat::createFormatCLUT8()); + cicnStream->read(cursorInfo.surface->pixels, pixMap.rowBytes * pixMap.bounds.height()); + + // While this looks sensible, it actually doesn't work for some cursors + // (ie. the 'can grab' hand) + //cursorInfo.surface->w = pixMap.bounds.width(); + } else if (pixMap.pixelSize == 1) { + cursorInfo.surface->create(pixMap.bounds.width(), pixMap.bounds.height(), Graphics::PixelFormat::createFormatCLUT8()); + + for (int y = 0; y < pixMap.bounds.height(); y++) { + byte *line = (byte *)cursorInfo.surface->getBasePtr(0, y); + + for (int x = 0; x < pixMap.bounds.width();) { + byte b = cicnStream->readByte(); + + for (int i = 0; i < 8; i++) { + *line++ = ((b & (1 << (7 - i))) != 0) ? 1 : 0; + + if (++x == pixMap.bounds.width()) + break; + } + } + } + } else { + error("Unhandled %dbpp cicn images", pixMap.pixelSize); + } + + delete cicnStream; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/cursor.h b/engines/pegasus/cursor.h new file mode 100644 index 0000000000..ada82e3967 --- /dev/null +++ b/engines/pegasus/cursor.h @@ -0,0 +1,84 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_CURSOR_H +#define PEGASUS_CURSOR_H + +#include "common/array.h" +#include "common/rect.h" + +#include "pegasus/timers.h" + +namespace Graphics { + struct Surface; +} + +namespace Pegasus { + +// The original cursor code was in the graphics code directly, +// unlike ScummVM where we have the cursor code separate. We're +// going to go with CursorManager here and therefore not inherit +// from the Sprite class. + +class Cursor : private Idler { +public: + Cursor(); + virtual ~Cursor(); + + void addCursorFrames(uint16 id); + + void setCurrentFrameIndex(int32 index); + int32 getCurrentFrameIndex() const; + + void show(); + void hide(); + void hideUntilMoved(); + bool isVisible(); + + void getCursorLocation(Common::Point &) const; + +protected: + virtual void useIdleTime(); + +private: + struct CursorInfo { + uint16 tag; + Common::Point hotspot; + Graphics::Surface *surface; + byte *palette; + uint16 colorCount; + }; + + Common::Point _cursorLocation; + Common::Array<CursorInfo> _info; + bool _cursorObscured; + int _index; + + void loadCursorImage(CursorInfo &cursorInfo); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/detection.cpp b/engines/pegasus/detection.cpp new file mode 100644 index 0000000000..d9afacbf3e --- /dev/null +++ b/engines/pegasus/detection.cpp @@ -0,0 +1,157 @@ +/* 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 "base/plugins.h" + +#include "engines/advancedDetector.h" +#include "common/config-manager.h" +#include "common/file.h" +#include "common/savefile.h" + +#include "pegasus/pegasus.h" + +namespace Pegasus { + +struct PegasusGameDescription { + ADGameDescription desc; +}; + +bool PegasusEngine::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL) + || (f == kSupportsLoadingDuringRuntime) + || (f == kSupportsSavingDuringRuntime); +} + +bool PegasusEngine::isDemo() const { + return (_gameDescription->desc.flags & ADGF_DEMO) != 0; +} + +} // End of namespace Pegasus + +static const PlainGameDescriptor pegasusGames[] = { + {"pegasus", "The Journeyman Project: Pegasus Prime"}, + {0, 0} +}; + + +namespace Pegasus { + +static const PegasusGameDescription gameDescriptions[] = { + { + { + "pegasus", + "", + AD_ENTRY1s("JMP PP Resources", "d13a602d2498010d720a6534f097f88b", 2009943), + Common::EN_ANY, + Common::kPlatformMacintosh, + ADGF_MACRESFORK, + GUIO0() + }, + }, + + { + { + "pegasus", + "Demo", + AD_ENTRY1s("JMP PP Resources", "d13a602d2498010d720a6534f097f88b", 360129), + Common::EN_ANY, + Common::kPlatformMacintosh, + ADGF_MACRESFORK|ADGF_DEMO, + GUIO1(GUIO_NOLAUNCHLOAD) + }, + }, + + { AD_TABLE_END_MARKER } +}; + +} // End of namespace Pegasus + + +class PegasusMetaEngine : public AdvancedMetaEngine { +public: + PegasusMetaEngine() : AdvancedMetaEngine(Pegasus::gameDescriptions, sizeof(Pegasus::PegasusGameDescription), pegasusGames) { + _singleid = "pegasus"; + } + + virtual const char *getName() const { + return "The Journeyman Project: Pegasus Prime"; + } + + virtual const char *getOriginalCopyright() const { + return "The Journeyman Project: Pegasus Prime (C) Presto Studios"; + } + + virtual bool hasFeature(MetaEngineFeature f) const; + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + virtual SaveStateList listSaves(const char *target) const; + virtual int getMaximumSaveSlot() const { return 999; } + virtual void removeSaveState(const char *target, int slot) const; +}; + +bool PegasusMetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsListSaves) + || (f == kSupportsLoadingDuringStartup) + || (f == kSupportsDeleteSave); +} + +SaveStateList PegasusMetaEngine::listSaves(const char *target) const { + // The original had no pattern, so the user must rename theirs + // Note that we ignore the target because saves are compatible between + // all versions + Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles("pegasus-*.sav"); + + SaveStateList saveList; + for (uint32 i = 0; i < filenames.size(); i++) { + // Isolate the description from the file name + Common::String desc = filenames[i].c_str() + 8; + for (int j = 0; j < 4; j++) + desc.deleteLastChar(); + + saveList.push_back(SaveStateDescriptor(i, desc)); + } + + return saveList; +} + +void PegasusMetaEngine::removeSaveState(const char *target, int slot) const { + // See listSaves() for info on the pattern + Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles("pegasus-*.sav"); + g_system->getSavefileManager()->removeSavefile(filenames[slot].c_str()); +} + +bool PegasusMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + const Pegasus::PegasusGameDescription *gd = (const Pegasus::PegasusGameDescription *)desc; + + if (gd) + *engine = new Pegasus::PegasusEngine(syst, gd); + + return (gd != 0); +} + +#if PLUGIN_ENABLED_DYNAMIC(PEGASUS) + REGISTER_PLUGIN_DYNAMIC(PEGASUS, PLUGIN_TYPE_ENGINE, PegasusMetaEngine); +#else + REGISTER_PLUGIN_STATIC(PEGASUS, PLUGIN_TYPE_ENGINE, PegasusMetaEngine); +#endif + diff --git a/engines/pegasus/elements.cpp b/engines/pegasus/elements.cpp new file mode 100644 index 0000000000..0310d26292 --- /dev/null +++ b/engines/pegasus/elements.cpp @@ -0,0 +1,568 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/macresman.h" +#include "common/stream.h" + +#include "pegasus/elements.h" +#include "pegasus/graphics.h" +#include "pegasus/surface.h" + +namespace Pegasus { + +DisplayElement::DisplayElement(const DisplayElementID id) : IDObject(id) { + _elementIsDisplaying = false; + _elementIsVisible = false; + _elementOrder = 0; + _triggeredElement = this; + _nextElement = 0; +} + +DisplayElement::~DisplayElement() { + if (isDisplaying()) + ((PegasusEngine *)g_engine)->_gfx->removeDisplayElement(this); +} + +void DisplayElement::setDisplayOrder(const DisplayOrder order) { + if (_elementOrder != order) { + _elementOrder = order; + if (isDisplaying()) { + ((PegasusEngine *)g_engine)->_gfx->removeDisplayElement(this); + ((PegasusEngine *)g_engine)->_gfx->addDisplayElement(this); + triggerRedraw(); + } + } +} + +void DisplayElement::startDisplaying() { + if (!isDisplaying()) { + ((PegasusEngine *)g_engine)->_gfx->addDisplayElement(this); + triggerRedraw(); + } +} + +void DisplayElement::stopDisplaying() { + if (isDisplaying()) { + triggerRedraw(); + ((PegasusEngine *)g_engine)->_gfx->removeDisplayElement(this); + } +} + +void DisplayElement::setBounds(const CoordType left, const CoordType top, const CoordType right, const CoordType bottom) { + setBounds(Common::Rect(left, top, right, bottom)); +} + +void DisplayElement::getBounds(Common::Rect &r) const { + r = _bounds; +} + +void DisplayElement::sizeElement(const CoordType h, const CoordType v) { + Common::Rect newBounds = _bounds; + newBounds.right = _bounds.left + h; + newBounds.bottom = _bounds.top + v; + setBounds(newBounds); +} + +void DisplayElement::moveElementTo(const CoordType h, const CoordType v) { + Common::Rect newBounds = _bounds; + newBounds.moveTo(h, v); + setBounds(newBounds); +} + +void DisplayElement::moveElement(const CoordType dh, const CoordType dv) { + Common::Rect newBounds = _bounds; + newBounds.translate(dh, dv); + setBounds(newBounds); +} + +void DisplayElement::getLocation(CoordType &h, CoordType &v) const { + h = _bounds.left; + v = _bounds.top; +} + +void DisplayElement::centerElementAt(const CoordType h, const CoordType v) { + Common::Rect newBounds = _bounds; + newBounds.moveTo(h - (_bounds.width() / 2), v - (_bounds.height() / 2)); + setBounds(newBounds); +} + +void DisplayElement::getCenter(CoordType &h, CoordType &v) const { + h = (_bounds.left + _bounds.right) / 2; + v = (_bounds.top + _bounds.bottom) / 2; +} + +void DisplayElement::setBounds(const Common::Rect &r) { + if (r != _bounds) { + triggerRedraw(); + _bounds = r; + triggerRedraw(); + } +} + +void DisplayElement::hide() { + if (_elementIsVisible) { + triggerRedraw(); + _elementIsVisible = false; + } +} + +void DisplayElement::show() { + if (!_elementIsVisible) { + _elementIsVisible = true; + triggerRedraw(); + } +} + +// Only invalidates this element's bounding rectangle if all these conditions are true: +// -- The triggered element is this element. +// -- The element is displaying on the display list. +// -- The element is visible. +// -- The element is part of the active layer OR is one of the reserved items. +void DisplayElement::triggerRedraw() { + GraphicsManager *gfx = ((PegasusEngine *)g_engine)->_gfx; + + if (_triggeredElement == this) { + if (validToDraw(gfx->getBackOfActiveLayer(), gfx->getFrontOfActiveLayer())) + gfx->invalRect(_bounds); + } else { + _triggeredElement->triggerRedraw(); + } +} + +void DisplayElement::setTriggeredElement(DisplayElement *element) { + if (element) + _triggeredElement = element; + else + _triggeredElement = this; +} + +bool DisplayElement::validToDraw(DisplayOrder backLayer, DisplayOrder frontLayer) { + return isDisplaying() && _elementIsVisible && + (getObjectID() <= kHighestReservedElementID || + (getDisplayOrder() >= backLayer && + getDisplayOrder() <= frontLayer)); +} + +DropHighlight::DropHighlight(const DisplayElementID id) : DisplayElement(id) { + _highlightColor = 0; + _thickness = 2; + _cornerDiameter = 0; +} + +void DropHighlight::draw(const Common::Rect &) { + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getWorkArea(); + + // Since this is only used in two different ways, I'm only + // going to implement it in those two ways. Deal with it. + + Common::Rect rect = _bounds; + rect.grow(-_thickness); + screen->frameRect(rect, _highlightColor); + rect.grow(1); + screen->frameRect(rect, _highlightColor); + + if (_cornerDiameter == 8 && _thickness == 4) { + rect.grow(1); + screen->frameRect(rect, _highlightColor); + screen->hLine(rect.left + 1, rect.top - 1, rect.right - 2, _highlightColor); + screen->hLine(rect.left + 1, rect.bottom, rect.right - 2, _highlightColor); + screen->vLine(rect.left - 1, rect.top + 1, rect.bottom - 2, _highlightColor); + screen->vLine(rect.right, rect.top + 1, rect.bottom - 2, _highlightColor); + } +} + +IdlerAnimation::IdlerAnimation(const DisplayElementID id) : Animation(id) { + _lastTime = 0xffffffff; +} + +void IdlerAnimation::startDisplaying() { + if (!isDisplaying()) { + Animation::startDisplaying(); + startIdling(); + } +} + +void IdlerAnimation::stopDisplaying() { + if (isDisplaying()) { + Animation::stopDisplaying(); + stopIdling(); + } +} + +void IdlerAnimation::useIdleTime() { + uint32 currentTime = getTime(); + + if (currentTime != _lastTime) { + _lastTime = currentTime; + timeChanged(_lastTime); + } +} + +void IdlerAnimation::timeChanged(const TimeValue) { + triggerRedraw(); +} + +FrameSequence::FrameSequence(const DisplayElementID id) : IdlerAnimation(id) { + _duration = 0; + _currentFrameNum = 0; + _resFork = new Common::MacResManager(); + _numFrames = 0; +} + +FrameSequence::~FrameSequence() { + delete _resFork; +} + +void FrameSequence::useFileName(const Common::String &fileName) { + _resFork->open(fileName); +} + +void FrameSequence::openFrameSequence() { + if (!_resFork->hasResFork()) + return; + + Common::SeekableReadStream *res = _resFork->getResource(MKTAG('P', 'F', 'r', 'm'), 0x80); + + if (!res) + return; + + uint32 scale = res->readUint32BE(); + _bounds.top = res->readUint16BE(); + _bounds.left = res->readUint16BE(); + _bounds.bottom = res->readUint16BE(); + _bounds.right = res->readUint16BE(); + _numFrames = res->readUint16BE(); + _duration = 0; + + _frameTimes.clear(); + for (uint32 i = 0; i < _numFrames; i++) { + TimeValue time = res->readUint32BE(); + _duration += time; + _frameTimes.push_back(_duration); + } + + setScale(scale); + setSegment(0, _duration); + setTime(0); + _currentFrameNum = 0; + newFrame(_currentFrameNum); + triggerRedraw(); + + delete res; +} + +void FrameSequence::closeFrameSequence() { + stop(); + _resFork->close(); + _duration = 0; + _numFrames = 0; + _frameTimes.clear(); +} + +void FrameSequence::timeChanged(const TimeValue time) { + int16 frameNum = 0; + for (int16 i = _numFrames - 1; i >= 0; i--) { + if (_frameTimes[i] < time) { + frameNum = i; + break; + } + } + + if (frameNum != _currentFrameNum) { + _currentFrameNum = frameNum; + newFrame(_currentFrameNum); + triggerRedraw(); + } +} + +void FrameSequence::setFrameNum(const int16 frameNum) { + int16 f = CLIP<int>(frameNum, 0, _numFrames); + + if (_currentFrameNum != f) { + _currentFrameNum = f; + setTime(_frameTimes[f]); + newFrame(f); + triggerRedraw(); + } +} + +bool FrameSequence::isSequenceOpen() const { + return _numFrames != 0; +} + +Sprite::Sprite(const DisplayElementID id) : DisplayElement(id) { + _numFrames = 0; + _currentFrameNum = 0xffffffff; + _currentFrame = 0; +} + +Sprite::~Sprite() { + discardFrames(); +} + +void Sprite::discardFrames() { + if (!_frameArray.empty()) { + for (uint32 i = 0; i < _numFrames; i++) { + SpriteFrame *frame = _frameArray[i].frame; + frame->_referenceCount--; + if (frame->_referenceCount == 0) + delete frame; + } + + _frameArray.clear(); + _numFrames = 0; + _currentFrame = 0; + _currentFrameNum = 0xffffffff; + setBounds(0, 0, 0, 0); + } +} + +void Sprite::addPICTResourceFrame(const ResIDType pictID, bool transparent, const CoordType left, const CoordType top) { + SpriteFrame *frame = new SpriteFrame(); + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, pictID, transparent); + addFrame(frame, left, top); +} + +uint32 Sprite::addFrame(SpriteFrame *frame, const CoordType left, const CoordType top) { + SpriteFrameRec frameRecord; + frameRecord.frame = frame; + frameRecord.frameLeft = left; + frameRecord.frameTop = top; + _frameArray.push_back(frameRecord); + _numFrames++; + frame->_referenceCount++; + + Common::Rect frameBounds; + frame->getSurfaceBounds(frameBounds); + + // 9/3/96 + // BB Should this be + left or - left? + frameBounds.moveTo(_bounds.left + left, _bounds.top + top); + + frameBounds.extend(_bounds); + + if (_bounds != frameBounds) + setBounds(frameBounds); + + return _numFrames - 1; +} + +void Sprite::removeFrame(const uint32 frameNum) { + _frameArray[frameNum].frame->_referenceCount--; + if (_frameArray[frameNum].frame->_referenceCount == 0) + delete _frameArray[frameNum].frame; + + // Calculate the new bounds + Common::Rect frameBounds; + for (uint32 i = 0; i < _numFrames; i++) { + if (i == frameNum) + continue; + + Common::Rect r; + _frameArray[i].frame->getSurfaceBounds(r); + r.translate(_frameArray[i].frameLeft, _frameArray[i].frameTop); + frameBounds.extend(r); + } + + _frameArray.remove_at(frameNum); + + frameBounds.moveTo(_bounds.left, _bounds.top); + setBounds(frameBounds); + + if (_currentFrameNum == frameNum) + triggerRedraw(); + else if (_currentFrameNum != 0xffffffff && _currentFrameNum > frameNum) + --_currentFrameNum; +} + +void Sprite::setCurrentFrameIndex(const int32 frameNum) { + if (frameNum < 0) { + if (_currentFrameNum != 0xffffffff) { + _currentFrameNum = 0xffffffff; + _currentFrame = 0; + triggerRedraw(); + } + } else if (_numFrames > 0) { + uint32 f = frameNum % _numFrames; + if (f != _currentFrameNum) { + _currentFrameNum = f; + _currentFrame = &_frameArray[f]; + triggerRedraw(); + } + } +} + +SpriteFrame *Sprite::getFrame(const int32 index) { + if (index < 0 || (uint32)index >= _numFrames) + return 0; + + return _frameArray[index].frame; +} + +void Sprite::draw(const Common::Rect &r) { + if (_currentFrame) { + Common::Rect frameBounds; + _currentFrame->frame->getSurfaceBounds(frameBounds); + + frameBounds.translate(_bounds.left + _currentFrame->frameLeft, _bounds.top + _currentFrame->frameTop); + Common::Rect r1 = frameBounds.findIntersectingRect(r); + + Common::Rect r2 = frameBounds; + r2.translate(-_bounds.left - _currentFrame->frameLeft, -_bounds.top - _currentFrame->frameTop); + + _currentFrame->frame->drawImage(r2, r1); + } +} + +SpriteSequence::SpriteSequence(const DisplayElementID id, const DisplayElementID spriteID) : + FrameSequence(id), _sprite(spriteID), _transparent(false) { +} + +void SpriteSequence::openFrameSequence() { + if (!isSequenceOpen()) { + FrameSequence::openFrameSequence(); + + if (isSequenceOpen()) { + uint32 numFrames = getNumFrames(); + + for (uint32 i = 0; i < numFrames; ++i) { + SpriteFrame *frame = new SpriteFrame(); + frame->initFromPICTResource(_resFork, i + 0x80, _transparent); + _sprite.addFrame(frame, 0, 0); + } + + _sprite.setBounds(_bounds); + } + } +} + +void SpriteSequence::closeFrameSequence() { + if (isSequenceOpen()) { + FrameSequence::closeFrameSequence(); + _sprite.discardFrames(); + } +} + +void SpriteSequence::setBounds(const Common::Rect &bounds) { + FrameSequence::setBounds(bounds); + _sprite.setBounds(_bounds); +} + +void SpriteSequence::draw(const Common::Rect &r) { + _sprite.draw(r); +} + +void SpriteSequence::newFrame(const uint16 frame) { + _sprite.setCurrentFrameIndex(frame); +} + +#define DRAW_PIXEL() \ + if (bytesPerPixel == 2) \ + *((uint16 *)dst) = black; \ + else \ + *((uint32 *)dst) = black; \ + dst += bytesPerPixel + +#define SKIP_PIXEL() \ + dst += bytesPerPixel + +void ScreenDimmer::draw(const Common::Rect &r) { + // We're going to emulate QuickDraw's srcOr+gray mode here + // In this mode, every other y column is all black (odd-columns). + // Basically, every row does three black and then one transparent + // repeatedly. + + // The output is identical to the original + + uint32 black = g_system->getScreenFormat().RGBToColor(0, 0, 0); + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getWorkArea(); + byte bytesPerPixel = g_system->getScreenFormat().bytesPerPixel; + + // We're currently doing it to the whole screen to simplify the code + + for (int y = 0; y < 480; y++) { + byte *dst = (byte *)screen->getBasePtr(0, y); + + for (int x = 0; x < 640; x += 4) { + if (y & 1) { + DRAW_PIXEL(); + DRAW_PIXEL(); + SKIP_PIXEL(); + DRAW_PIXEL(); + } else { + SKIP_PIXEL(); + DRAW_PIXEL(); + DRAW_PIXEL(); + DRAW_PIXEL(); + } + } + } +} + +#undef DRAW_PIXEL +#undef SKIP_PIXEL + +SoundLevel::SoundLevel(const DisplayElementID id) : DisplayElement(id) { + _soundLevel = 0; +} + +void SoundLevel::incrementLevel() { + if (_soundLevel < 12) { + _soundLevel++; + triggerRedraw(); + } +} + +void SoundLevel::decrementLevel() { + if (_soundLevel > 0) { + _soundLevel--; + triggerRedraw(); + } +} + +uint16 SoundLevel::getSoundLevel() { + return CLIP<int>(_soundLevel * 22, 0, 256); +} + +void SoundLevel::setSoundLevel(uint16 level) { + uint16 newLevel = (level + 21) / 22; + + if (newLevel != _soundLevel) { + _soundLevel = newLevel; + triggerRedraw(); + } +} + +void SoundLevel::draw(const Common::Rect &r) { + Common::Rect levelRect(_bounds.right + (8 * (_soundLevel - 12)), _bounds.top, _bounds.right, _bounds.bottom); + levelRect = r.findIntersectingRect(levelRect); + + if (!levelRect.isEmpty()) { + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getWorkArea(); + screen->fillRect(levelRect, g_system->getScreenFormat().RGBToColor(0, 0, 0)); + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/elements.h b/engines/pegasus/elements.h new file mode 100644 index 0000000000..d96d2173b1 --- /dev/null +++ b/engines/pegasus/elements.h @@ -0,0 +1,254 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ELEMENTS_H +#define PEGASUS_ELEMENTS_H + +#include "common/array.h" +#include "common/rect.h" +#include "common/str.h" +#include "common/system.h" +#include "graphics/surface.h" + +#include "pegasus/timers.h" +#include "pegasus/util.h" + +namespace Common { + class MacResManager; +} + +namespace Pegasus { + +class DisplayElement : public IDObject { +friend class GraphicsManager; +public: + DisplayElement(const DisplayElementID); + virtual ~DisplayElement(); + + void setDisplayOrder(const DisplayOrder); + DisplayOrder getDisplayOrder() const { return _elementOrder; } + + bool validToDraw(DisplayOrder, DisplayOrder); + + virtual void draw(const Common::Rect&) {} + bool isDisplaying() { return _elementIsDisplaying; } + virtual void startDisplaying(); + virtual void stopDisplaying(); + + virtual void show(); + virtual void hide(); + bool isVisible() { return _elementIsVisible; } + + // triggerRedraw only triggers a draw if the element is displaying and visible. + void triggerRedraw(); + void setTriggeredElement(DisplayElement *); + + virtual void setBounds(const CoordType, const CoordType, const CoordType, const CoordType); + virtual void setBounds(const Common::Rect &); + virtual void getBounds(Common::Rect &) const; + virtual void sizeElement(const CoordType, const CoordType); + virtual void moveElementTo(const CoordType, const CoordType); + virtual void moveElement(const CoordType, const CoordType); + virtual void getLocation(CoordType &, CoordType &) const; + virtual void getCenter(CoordType &, CoordType &) const; + virtual void centerElementAt(const CoordType, const CoordType); + +protected: + Common::Rect _bounds; + bool _elementIsVisible; + DisplayElement *_triggeredElement; + + // Used only by PegasusEngine + bool _elementIsDisplaying; + DisplayOrder _elementOrder; + DisplayElement *_nextElement; +}; + +// I'm using the proper "highlight" instead of the evil +// QuickDraw "hilite" :P (deal with it!) +class DropHighlight : public DisplayElement { +public: + DropHighlight(const DisplayElementID); + virtual ~DropHighlight() {} + + void setHighlightColor(const uint32 &highlight) { _highlightColor = highlight; } + void getHighlightColor(uint32 &highlight) const { highlight = _highlightColor; } + + void setHighlightThickness(const uint16 thickness) { _thickness = thickness; } + uint16 getHighlightThickness() const { return _thickness; } + + void setHighlightCornerDiameter(const uint16 diameter) { _cornerDiameter = diameter; } + uint16 getHighlightCornerDiameter() const { return _cornerDiameter; } + + virtual void draw(const Common::Rect&); + +protected: + uint32 _highlightColor; + uint16 _thickness; + uint16 _cornerDiameter; +}; + +class Animation : public DisplayElement, public DynamicElement { +public: + Animation(const DisplayElementID id) : DisplayElement(id) {} +}; + +class IdlerAnimation : public Animation, public Idler { +public: + IdlerAnimation(const DisplayElementID); + + virtual void startDisplaying(); + virtual void stopDisplaying(); + + TimeValue getLastTime() const { return _lastTime; } + +protected: + virtual void useIdleTime(); + virtual void timeChanged(const TimeValue); + + TimeValue _lastTime; +}; + +// This class reads PICT resources and plays them like a movie. +// Assumes there is a resource of type 'PFrm' describing the time values for each +// PICT frame, as well as the total time in the movie. +// Assumes that PICT frames begin at PICT 128 + +class FrameSequence : public IdlerAnimation { +public: + FrameSequence(const DisplayElementID); + virtual ~FrameSequence(); + + void useFileName(const Common::String &fileName); + + virtual void openFrameSequence(); + virtual void closeFrameSequence(); + bool isSequenceOpen() const; + + uint16 getNumFrames() const { return _numFrames; } + virtual uint16 getFrameNum() const { return _currentFrameNum; } + virtual void setFrameNum(const int16); + +protected: + virtual void timeChanged(const TimeValue); + virtual void newFrame(const uint16) {} + + Common::MacResManager *_resFork; + TimeValue _duration; + + uint16 _numFrames; + Common::Array<TimeValue> _frameTimes; + + uint16 _currentFrameNum; +}; + +class SpriteFrame; + +class Sprite : public DisplayElement { +friend class SpriteFrame; +public: + Sprite(const DisplayElementID); + virtual ~Sprite(); + + virtual void addPICTResourceFrame(const ResIDType, const bool, const CoordType, const CoordType); + virtual uint32 addFrame(SpriteFrame *, const CoordType, const CoordType); + virtual void removeFrame(const uint32); + virtual void discardFrames(); + + // Setting the current frame. + // If the index is negative, sets the current frame to NULL and hides the sprite. + // If the index is larger than the number of frames in the sprite, the number + // is treated modulo the number of frames. + virtual void setCurrentFrameIndex(const int32); + virtual uint32 getCurrentFrameIndex() const { return _currentFrameNum; } + + virtual SpriteFrame *getFrame(const int32); + + virtual void draw(const Common::Rect &); + + uint32 getNumFrames() const { return _numFrames; } + +protected: + struct SpriteFrameRec { + SpriteFrame *frame; + CoordType frameLeft; + CoordType frameTop; + }; + + uint32 _numFrames; + uint32 _currentFrameNum; + SpriteFrameRec *_currentFrame; + Common::Array<SpriteFrameRec> _frameArray; +}; + +class SpriteSequence : public FrameSequence { +public: + SpriteSequence(const DisplayElementID id, const DisplayElementID spriteID); + virtual ~SpriteSequence() {} + + void useTransparent(bool transparent) { _transparent = transparent; } + + virtual void openFrameSequence(); + virtual void closeFrameSequence(); + + virtual void draw(const Common::Rect &); + + virtual void setBounds(const Common::Rect &); + +protected: + virtual void newFrame(const uint16); + + bool _transparent; + Sprite _sprite; +}; + +class ScreenDimmer : public DisplayElement { +public: + ScreenDimmer() : DisplayElement(kScreenDimmerID) {} + virtual ~ScreenDimmer() {} + + virtual void draw(const Common::Rect &); +}; + +class SoundLevel : public DisplayElement { +public: + SoundLevel(const DisplayElementID); + virtual ~SoundLevel() {} + + void incrementLevel(); + void decrementLevel(); + + uint16 getSoundLevel(); + void setSoundLevel(uint16); + + void draw(const Common::Rect &); + +protected: + uint16 _soundLevel; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/energymonitor.cpp b/engines/pegasus/energymonitor.cpp new file mode 100644 index 0000000000..7a9ca7878a --- /dev/null +++ b/engines/pegasus/energymonitor.cpp @@ -0,0 +1,296 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/energymonitor.h" +#include "pegasus/pegasus.h" +#include "pegasus/surface.h" + +namespace Pegasus { + +Blinker::Blinker() { + _sprite = 0; + _frame1 = -1; + _frame2 = -1; + _blinkDuration = 0; +} + +void Blinker::startBlinking(Sprite *sprite, int32 frame1, int32 frame2, uint32 numBlinks, TimeValue blinkDuration, TimeScale blinkScale) { + stopBlinking(); + _sprite = sprite; + _frame1 = frame1; + _frame2 = frame2; + _blinkDuration = blinkDuration; + setScale(blinkScale); + setSegment(0, blinkDuration * numBlinks * 2, blinkScale); + setTime(0); + start(); +} + +void Blinker::stopBlinking() { + if (_sprite) { + _sprite->setCurrentFrameIndex(_frame2); + _sprite = 0; + stop(); + } +} + +void Blinker::timeChanged(const TimeValue time) { + if (_sprite && _blinkDuration != 0) { + if (((time / _blinkDuration) & 1) != 0 || time == getDuration()) { + _sprite->setCurrentFrameIndex(_frame2); + if (!isRunning()) + stopBlinking(); + } else { + _sprite->setCurrentFrameIndex(_frame1); + } + } +} + +static const NotificationFlags kEnergyExpiredFlag = 1; + +EnergyMonitor *g_energyMonitor = 0; + +EnergyMonitor::EnergyMonitor() : IdlerAnimation(kEnergyBarID), _energyLight(kWarningLightID) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + _stage = kStageNoStage; + + _calibrating = false; + _dontFlash = false; + + setBounds(338, 48, 434, 54); + + setDisplayOrder(kEnergyBarOrder); + startDisplaying(); + + SpriteFrame *frame = new SpriteFrame(); + frame->initFromPICTResource(vm->_resFork, kLightOffID); + _energyLight.addFrame(frame, 0, 0); + + frame = new SpriteFrame(); + frame->initFromPICTResource(vm->_resFork, kLightYellowID); + _energyLight.addFrame(frame, 0, 0); + + frame = new SpriteFrame(); + frame->initFromPICTResource(vm->_resFork, kLightOrangeID); + _energyLight.addFrame(frame, 0, 0); + + frame = new SpriteFrame(); + frame->initFromPICTResource(vm->_resFork, kLightRedID); + _energyLight.addFrame(frame, 0, 0); + + _energyLight.setBounds(540, 35, 600, 59); + _energyLight.setDisplayOrder(kEnergyLightOrder); + _energyLight.startDisplaying(); + + setScale(1); + setSegment(0, kMaxJMPEnergy); + + setEnergyValue(kCasualEnergy); + + g_energyMonitor = this; +} + +EnergyMonitor::~EnergyMonitor() { + g_energyMonitor = 0; +} + +void EnergyMonitor::setEnergyValue(const uint32 value) { + if (isRunning()) { + stop(); + setTime(getStop() - value); + start(); + } else { + setTime(getStop() - value); + } +} + +void EnergyMonitor::startEnergyDraining() { + if (!isRunning()) { + _energyLight.show(); + start(); + show(); + } +} + +void EnergyMonitor::setEnergyDrainRate(Common::Rational rate) { + setRate(rate); +} + +Common::Rational EnergyMonitor::getEnergyDrainRate() { + return getRate(); +} + +void EnergyMonitor::stopEnergyDraining() { + if (isRunning()) { + stop(); + _energyLight.hide(); + hide(); + } +} + +void EnergyMonitor::drainEnergy(const int32 delta) { + setTime(getTime() + delta); +} + +int32 EnergyMonitor::getCurrentEnergy() { + return kMaxJMPEnergy - getTime(); +} + +void EnergyMonitor::timeChanged(const TimeValue currentTime) { + if (currentTime == getStop()) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + if (vm->getEnergyDeathReason() != -1) + vm->die(vm->getEnergyDeathReason()); + } else { + uint32 currentEnergy = kMaxJMPEnergy - currentTime; + + EnergyStage newStage; + if (currentEnergy > kWorriedEnergy) + newStage = kStageCasual; + else if (currentEnergy > kNervousEnergy) + newStage = kStageWorried; + else if (currentEnergy > kPanicStrickenEnergy) + newStage = kStageNervous; + else + newStage = kStagePanicStricken; + + if (_stage != newStage) { + uint32 newFrame; + + switch (newStage) { + case kStageCasual: + _barColor = g_system->getScreenFormat().RGBToColor(0x48, 0xB0, 0xD8); + newFrame = kFrameLightOff; + break; + case kStageWorried: + _barColor = g_system->getScreenFormat().RGBToColor(0xD8, 0xC0, 0x30); + newFrame = kFrameLightYellow; + break; + case kStageNervous: + _barColor = g_system->getScreenFormat().RGBToColor(0xD8, 0x78, 0x38); + newFrame = kFrameLightOrange; + break; + case kStagePanicStricken: + _barColor = g_system->getScreenFormat().RGBToColor(0xD8, 0x40, 0x38); + newFrame = kFrameLightRed; + break; + default: + error("no stage in energy monitor?"); + break; + } + + _stage = newStage; + uint32 oldFrame = _energyLight.getCurrentFrameIndex(); + + if (!_calibrating) { + if (oldFrame > newFrame || oldFrame == 0xffffffff || _dontFlash) { + _energyLight.setCurrentFrameIndex(newFrame); + _dontFlash = false; + } else { + _lightBlinker.startBlinking(&_energyLight, oldFrame, newFrame, 4, 1, 3); + triggerRedraw(); + } + } + } + + Common::Rect r; + calcLevelRect(r); + if (r != _levelRect) { + _levelRect = r; + triggerRedraw(); + } + } +} + +void EnergyMonitor::calcLevelRect(Common::Rect &r) { + if (getStop() == 0) { + r = Common::Rect(); + } else { + getBounds(r); + r.left = r.right - r.width() * (kMaxJMPEnergy - getTime()) / getStop(); + } +} + +void EnergyMonitor::draw(const Common::Rect &r) { + Common::Rect r2 = r.findIntersectingRect(_levelRect); + + if (!r2.isEmpty()) { + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getWorkArea(); + screen->fillRect(r2, _barColor); + } +} + +void EnergyMonitor::calibrateEnergyBar() { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + _calibrating = true; + + vm->setEnergyDeathReason(-1); + + uint32 numFrames = _energyLight.getNumFrames(); + for (uint32 i = 1; i < numFrames; i++) { + _energyLight.setCurrentFrameIndex(i); + _energyLight.show(); + vm->delayShell(1, 3); + _energyLight.hide(); + vm->delayShell(1, 3); + } + + _energyLight.setCurrentFrameIndex(0); + _energyLight.hide(); + + show(); + setEnergyValue(0); + setEnergyDrainRate(-(int32)kMaxJMPEnergy / 2); + + // Make sure warning light is hidden... + _energyLight.hide(); + while (getCurrentEnergy() != (int32)kMaxJMPEnergy) { + vm->checkCallBacks(); + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + vm->refreshDisplay(); + setEnergyDrainRate(0); + hide(); + + _calibrating = false; +} + +void EnergyMonitor::restoreLastEnergyValue() { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + _dontFlash = true; + setEnergyValue(vm->getSavedEnergyValue()); + vm->resetEnergyDeathReason(); +} + +void EnergyMonitor::saveCurrentEnergyValue() { + ((PegasusEngine *)g_engine)->setLastEnergyValue(getCurrentEnergy()); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/energymonitor.h b/engines/pegasus/energymonitor.h new file mode 100644 index 0000000000..02377d515a --- /dev/null +++ b/engines/pegasus/energymonitor.h @@ -0,0 +1,111 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ENERGYMONITOR_H +#define PEGASUS_ENERGYMONITOR_H + +#include "pegasus/elements.h" + +namespace Pegasus { + +class Sprite; + +class Blinker : private IdlerTimeBase { +public: + Blinker(); + virtual ~Blinker() {} + + void startBlinking(Sprite *sprite, int32 frame1, int32 frame2, uint32 numBlinks, TimeValue blinkDuration, TimeScale blinkScale); + void stopBlinking(); + +protected: + virtual void timeChanged(const TimeValue); + + Sprite *_sprite; + int32 _frame1; + int32 _frame2; + TimeValue _blinkDuration; +}; + +// Energy monitor constants. + +// These are in seconds. +// Max is two hours +static const uint32 kMaxJMPEnergy = 7200; + +static const uint32 kCasualEnergy = kMaxJMPEnergy * 100 / 100; // 100% +static const uint32 kWorriedEnergy = kMaxJMPEnergy * 50 / 100; // 50% +static const uint32 kNervousEnergy = kMaxJMPEnergy * 25 / 100; // 25% +static const uint32 kPanicStrickenEnergy = kMaxJMPEnergy * 5 / 100; // 5% + +static const uint32 kFullEnergy = kCasualEnergy; + +static const uint32 kFrameLightOff = 0; +static const uint32 kFrameLightYellow = 1; +static const uint32 kFrameLightOrange = 2; +static const uint32 kFrameLightRed = 3; + +static const int kEnergyDrainNormal = 1; +static const int kMarsReactorEnergyDrainNoShield = 6; +static const int kMarsReactorEnergyDrainWithShield = 3; +static const int kWSCPoisonEnergyDrainWithDart = 20; +static const int kWSCPoisonEnergyDrainNoDart = 10; + +class EnergyMonitor : private IdlerAnimation { +public: + EnergyMonitor(); + virtual ~EnergyMonitor(); + + void setEnergyValue(const uint32); + void startEnergyDraining(); + void setEnergyDrainRate(Common::Rational); + Common::Rational getEnergyDrainRate(); + void stopEnergyDraining(); + void drainEnergy(const int32); + int32 getCurrentEnergy(); + + void restoreLastEnergyValue(); + void saveCurrentEnergyValue(); + + void calibrateEnergyBar(); + +protected: + void timeChanged(const TimeValue); + void calcLevelRect(Common::Rect &); + void draw(const Common::Rect &); + + uint32 _barColor; + Common::Rect _levelRect; + EnergyStage _stage; + Sprite _energyLight; + Blinker _lightBlinker; + bool _calibrating, _dontFlash; +}; + +extern EnergyMonitor *g_energyMonitor; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/fader.cpp b/engines/pegasus/fader.cpp new file mode 100644 index 0000000000..77ad2cbe4e --- /dev/null +++ b/engines/pegasus/fader.cpp @@ -0,0 +1,218 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/fader.h" +#include "pegasus/pegasus.h" +#include "pegasus/sound.h" +#include "pegasus/util.h" + +namespace Pegasus { + +Fader::Fader() { + _currentValue = 0; + _currentFaderMove._numKnots = 0; +} + +void Fader::setFaderValue(const int32 newValue) { + _currentValue = newValue; +} + +bool Fader::initFaderMove(const FaderMoveSpec &spec) { + bool faderMoves = false; + int32 value = 0; + + if (spec._numKnots > 0) { + stopFader(); + value = spec._knots[0].knotValue; + TimeValue startTime = spec._knots[0].knotTime; + + if (startTime != 0xffffffff) { + if (spec._numKnots > 1) { + TimeValue stopTime = spec._knots[spec._numKnots - 1].knotTime; + + if (spec._faderScale > 0) { + if (stopTime > startTime) { + for (uint32 i = 1; i < spec._numKnots; ++i) { + if (spec._knots[i - 1].knotValue != spec._knots[i].knotValue) { + faderMoves = true; + break; + } + } + + if (faderMoves) + _currentFaderMove = spec; + } else if (spec._knots[spec._numKnots - 1].knotValue != value) { + value = spec._knots[spec._numKnots - 1].knotValue; + } + } + } + } + } + + setFaderValue(value); + return faderMoves; +} + +void Fader::startFader(const FaderMoveSpec &spec) { + if (initFaderMove(spec)) { + setFlags(0); + setScale(spec._faderScale); + setSegment(spec._knots[0].knotTime, spec._knots[spec._numKnots - 1].knotTime); + setTime(spec._knots[0].knotTime); + start(); + } +} + +void Fader::startFaderSync(const FaderMoveSpec &spec) { + if (initFaderMove(spec)) { + setFlags(0); + setScale(spec._faderScale); + setSegment(spec._knots[0].knotTime, spec._knots[spec._numKnots - 1].knotTime); + setTime(spec._knots[0].knotTime); + start(); + + while (isFading()) { + ((PegasusEngine *)g_engine)->checkCallBacks(); + useIdleTime(); + } + + // Once more, for good measure, to make sure that there are no boundary + // condition problems. + useIdleTime(); + stopFader(); + } +} + +void Fader::loopFader(const FaderMoveSpec &spec) { + if (initFaderMove(spec)) { + setFlags(kLoopTimeBase); + setScale(spec._faderScale); + setSegment(spec._knots[0].knotTime, spec._knots[spec._numKnots - 1].knotTime); + setTime(spec._knots[0].knotTime); + start(); + } +} + +void Fader::stopFader() { + stop(); +} + +void Fader::pauseFader() { + stopFader(); +} + +void Fader::continueFader() { + if (getTime() < getStop()) + start(); +} + +void Fader::timeChanged(const TimeValue newTime) { + if (_currentFaderMove._numKnots != 0) { + uint32 i; + for (i = 0; i < _currentFaderMove._numKnots; i++) + if (_currentFaderMove._knots[i].knotTime > newTime) + break; + + int32 newValue; + if (i == 0) + newValue = _currentFaderMove._knots[0].knotValue; + else if (i == _currentFaderMove._numKnots) + newValue = _currentFaderMove._knots[i - 1].knotValue; + else + newValue = linearInterp(_currentFaderMove._knots[i - 1].knotTime, _currentFaderMove._knots[i].knotTime, newTime, _currentFaderMove._knots[i - 1].knotValue, _currentFaderMove._knots[i].knotValue); + + if (newValue != _currentValue) + setFaderValue(newValue); + } +} + +void FaderMoveSpec::makeOneKnotFaderSpec(const int32 knotValue) { + _numKnots = 1; + _knots[0].knotTime = 0; + _knots[0].knotValue = knotValue; +} + +void FaderMoveSpec::makeTwoKnotFaderSpec(const TimeScale faderScale, const TimeValue time1, const int32 value1, const TimeValue time2, const int32 value2) { + _numKnots = 2; + _faderScale = faderScale; + _knots[0].knotTime = time1; + _knots[0].knotValue = value1; + _knots[1].knotTime = time2; + _knots[1].knotValue = value2; +} + +void FaderMoveSpec::insertFaderKnot(const TimeValue knotTime, const int32 knotValue) { + if (_numKnots != kMaxFaderKnots) { + uint32 index; + for (index = 0; index < _numKnots; index++) { + if (knotTime == _knots[index].knotTime) { + _knots[index].knotValue = knotValue; + return; + } else if (knotTime < _knots[index].knotTime) { + break; + } + } + + for (uint32 i = _numKnots; i > index; i--) + _knots[i] = _knots[i - 1]; + + _knots[index].knotTime = knotTime; + _knots[index].knotValue = knotValue; + _numKnots++; + } +} + +void FaderAnimation::setFaderValue(const int32 newValue) { + if (getFaderValue() != newValue) { + Fader::setFaderValue(newValue); + triggerRedraw(); + } +} + +SoundFader::SoundFader() { + _sound = 0; + _masterVolume = 0xff; +} + +void SoundFader::attachSound(Sound *sound) { + if (!sound && isFading()) + stopFader(); + + _sound = sound; +} + +void SoundFader::setFaderValue(const int32 newVolume) { + if (_sound) + _sound->setVolume((newVolume * _masterVolume) >> 8); + + _currentValue = newVolume; +} + +void SoundFader::setMasterVolume(const uint16 masterVolume) { + _masterVolume = masterVolume; + setFaderValue(getFaderValue()); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/fader.h b/engines/pegasus/fader.h new file mode 100644 index 0000000000..24ecae021b --- /dev/null +++ b/engines/pegasus/fader.h @@ -0,0 +1,130 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_FADER_H +#define PEGASUS_FADER_H + +#include "pegasus/elements.h" +#include "pegasus/timers.h" + +namespace Pegasus { + +class Fader; + +class FaderMoveSpec { +friend class Fader; +public: + FaderMoveSpec() { + _faderScale = kDefaultTimeScale; + _numKnots = 0; + } + + FaderMoveSpec(const TimeScale scale) { + _faderScale = scale; + _numKnots = 0; + } + + void setFaderScale(const TimeScale scale) { _faderScale = scale; } + TimeScale getFaderScale() const { return _faderScale; } + + void makeOneKnotFaderSpec(const int32); + void makeTwoKnotFaderSpec(const TimeScale, const TimeValue, const int32, const TimeValue, const int32); + + void insertFaderKnot(const TimeValue, const int32); + + uint32 getNumKnots() const { return _numKnots; } + TimeValue getNthKnotTime(const uint32 index) const { return _knots[index].knotTime; } + int32 getNthKnotValue(const uint32 index) const { return _knots[index].knotValue; } + +protected: + struct FaderKnot { + TimeValue knotTime; + int32 knotValue; + }; + + TimeScale _faderScale; + uint32 _numKnots; + + static const uint32 kMaxFaderKnots = 20; + FaderKnot _knots[kMaxFaderKnots]; +}; + +class Fader : public IdlerTimeBase { +public: + Fader(); + virtual ~Fader() {} + + virtual void setFaderValue(const int32); + int32 getFaderValue() const { return _currentValue; } + virtual void startFader(const FaderMoveSpec &); + virtual void startFaderSync(const FaderMoveSpec &); + virtual void loopFader(const FaderMoveSpec &); + virtual void stopFader(); + virtual bool isFading() { return isRunning(); } + + void pauseFader(); + void continueFader(); + + void getCurrentFaderMove(FaderMoveSpec &spec) { spec = _currentFaderMove; } + +protected: + bool initFaderMove(const FaderMoveSpec &); + virtual void timeChanged(const TimeValue); + + int32 _currentValue; + FaderMoveSpec _currentFaderMove; +}; + +class FaderAnimation : public DisplayElement, public Fader { +public: + FaderAnimation(const DisplayElementID id) : DisplayElement(id) {} + virtual ~FaderAnimation() {} + + void setFaderValue(const int32); +}; + +class Sound; + +class SoundFader : public Fader { +friend class Sound; +public: + SoundFader(); + virtual ~SoundFader() {} + + void setFaderValue(const int32); + + void setMasterVolume(const uint16); + uint16 getMasterVolume() const { return _masterVolume; } + +protected: + void attachSound(Sound *); + + Sound *_sound; + uint16 _masterVolume; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/gamestate.cpp b/engines/pegasus/gamestate.cpp new file mode 100644 index 0000000000..601960e5de --- /dev/null +++ b/engines/pegasus/gamestate.cpp @@ -0,0 +1,2359 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/error.h" +#include "common/stream.h" + +#include "pegasus/constants.h" +#include "pegasus/gamestate.h" +#include "pegasus/scoring.h" + +namespace Common { +DECLARE_SINGLETON(Pegasus::GameStateManager); +} + +namespace Pegasus { + +Common::Error GameStateManager::writeGameState(Common::WriteStream *stream) { + stream->writeUint16BE(_currentNeighborhood); + stream->writeUint16BE(_currentRoom); + stream->writeByte(_currentDirection); + stream->writeUint16BE(_nexNeighborhoodID); + stream->writeUint16BE(_nextRoomID); + stream->writeByte(_nextDirection); + stream->writeUint16BE(_lastNeighborhood); + stream->writeUint16BE(_lastRoom); + stream->writeByte(_lastDirection); + stream->writeUint16BE(_openDoorRoom); + stream->writeByte(_openDoorDirection); + + _globalFlags.writeToStream(stream); + _scoringFlags.writeToStream(stream); + _itemTakenFlags.writeToStream(stream); + + writeCaldoriaState(stream); + writeTSAState(stream); + writePrehistoricState(stream); + writeNoradState(stream); + writeMarsState(stream); + writeWSCState(stream); + + if (stream->err()) + return Common::kWritingFailed; + + return Common::kNoError; +} + +Common::Error GameStateManager::readGameState(Common::ReadStream *stream) { + _currentNeighborhood = stream->readUint16BE(); + _currentRoom = stream->readUint16BE(); + _currentDirection = stream->readByte(); + _nexNeighborhoodID = stream->readUint16BE(); + _nextRoomID = stream->readUint16BE(); + _nextDirection = stream->readByte(); + _lastNeighborhood = stream->readUint16BE(); + _lastRoom = stream->readUint16BE(); + _lastDirection = stream->readByte(); + _openDoorRoom = stream->readUint16BE(); + _openDoorDirection = stream->readByte(); + + _globalFlags.readFromStream(stream); + _scoringFlags.readFromStream(stream); + _itemTakenFlags.readFromStream(stream); + + readCaldoriaState(stream); + readTSAState(stream); + readPrehistoricState(stream); + readNoradState(stream); + readMarsState(stream); + readWSCState(stream); + + if (stream->err()) + return Common::kReadingFailed; + + return Common::kNoError; +} + +void GameStateManager::resetGameState() { + _currentNeighborhood = kNoNeighborhoodID; + _currentRoom = kNoRoomID; + _currentDirection = kNoDirection; + _nexNeighborhoodID = kNoNeighborhoodID; + _nextRoomID = kNoRoomID; + _nextDirection = kNoDirection; + _lastNeighborhood = kNoNeighborhoodID; + _lastRoom = kNoRoomID; + _lastDirection = kNoDirection; + _openDoorRoom = kNoRoomID; + _openDoorDirection = kNoDirection; + + _globalFlags.clearAllFlags(); + _scoringFlags.clearAllFlags(); + _itemTakenFlags.clearAllFlags(); + + resetCaldoriaState(); + resetTSAState(); + resetPrehistoricState(); + resetNoradState(); + resetMarsState(); + resetWSCState(); +} + +void GameStateManager::getCurrentLocation(NeighborhoodID &neighborhood, RoomID &room, DirectionConstant &direction) { + neighborhood = _currentNeighborhood; + room = _currentRoom; + direction = _currentDirection; +} + +void GameStateManager::setCurrentLocation(const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) { + _lastNeighborhood = _currentNeighborhood; + _lastRoom = _currentRoom; + _lastDirection = _currentDirection; + _currentNeighborhood = neighborhood; + _currentRoom = room; + _currentDirection = direction; +} + +NeighborhoodID GameStateManager::getCurrentNeighborhood() { + return _currentNeighborhood; +} + +void GameStateManager::setCurrentNeighborhood(const NeighborhoodID neighborhood) { + _lastNeighborhood = _currentNeighborhood; + _currentNeighborhood = neighborhood; +} + +RoomID GameStateManager::getCurrentRoom() { + return _currentRoom; +} + +void GameStateManager::setCurrentRoom(const RoomID room) { + _lastRoom = _currentRoom; + _currentRoom = room; +} + +DirectionConstant GameStateManager::getCurrentDirection() { + return _currentDirection; +} + +void GameStateManager::setCurrentDirection(const DirectionConstant direction) { + _lastDirection = _currentDirection; + _currentDirection = direction; +} + +RoomViewID GameStateManager::getCurrentRoomAndView() { + return MakeRoomView(_currentRoom, _currentDirection); +} + +void GameStateManager::getNextLocation(NeighborhoodID &neighborhood, RoomID &room, DirectionConstant &direction) { + neighborhood = _nexNeighborhoodID; + room = _nextRoomID; + direction = _nextDirection; +} + +void GameStateManager::setNextLocation(const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) { + _nexNeighborhoodID = neighborhood; + _nextRoomID = room; + _nextDirection = direction; +} + +NeighborhoodID GameStateManager::getNextNeighborhood() { + return _nexNeighborhoodID; +} + +void GameStateManager::setNextNeighborhood(const NeighborhoodID neighborhood) { + _nexNeighborhoodID = neighborhood; +} + +RoomID GameStateManager::getNextRoom() { + return _nextRoomID; +} + +void GameStateManager::setNextRoom(const RoomID room) { + _nextRoomID = room; +} + +DirectionConstant GameStateManager::getNextDirection() { + return _nextDirection; +} + +void GameStateManager::setNextDirection(const DirectionConstant direction) { + _nextDirection = direction; +} + +void GameStateManager::getLastLocation(NeighborhoodID &neighborhood, RoomID &room, DirectionConstant &direction) { + neighborhood = _currentNeighborhood; + room = _currentRoom; + direction = _currentDirection; +} + +void GameStateManager::setLastLocation(const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) { + _currentNeighborhood = neighborhood; + _currentRoom = room; + _currentDirection = direction; +} + +NeighborhoodID GameStateManager::getLastNeighborhood() { + return _lastNeighborhood; +} + +void GameStateManager::setLastNeighborhood(const NeighborhoodID neighborhood) { + _lastNeighborhood = neighborhood; +} + +RoomID GameStateManager::getLastRoom() { + return _lastRoom; +} + +void GameStateManager::setLastRoom(const RoomID room) { + _lastRoom = room; +} + +DirectionConstant GameStateManager::getLastDirection() { + return _lastDirection; +} + +void GameStateManager::setLastDirection(const DirectionConstant direction) { + _lastDirection = direction; +} + +RoomViewID GameStateManager::getLastRoomAndView() { + return MakeRoomView(_lastRoom, _lastDirection); +} + +void GameStateManager::getOpenDoorLocation(RoomID &room, DirectionConstant &direction) { + room = _openDoorRoom; + direction = _openDoorDirection; +} + +void GameStateManager::setOpenDoorLocation(const RoomID room, const DirectionConstant direction) { + _openDoorRoom = room; + _openDoorDirection = direction; +} + +RoomID GameStateManager::getOpenDoorRoom() { + return _openDoorRoom; +} + +void GameStateManager::setOpenDoorRoom(const RoomID room) { + _openDoorRoom = room; +} + +DirectionConstant GameStateManager::getOpenDoorDirection() { + return _openDoorDirection; +} + +void GameStateManager::setOpenDoorDirection(const DirectionConstant direction) { + _openDoorDirection = direction; +} + +RoomViewID GameStateManager::getDoorOpenRoomAndView() { + return MakeRoomView(_openDoorRoom, _openDoorDirection); +} + +bool GameStateManager::isCurrentDoorOpen() { + return _openDoorRoom == _currentRoom && _openDoorDirection == _currentDirection; +} + +GameScoreType GameStateManager::getCaldoriaTSAScore() { + GameScoreType result = 0; + + if (_scoringFlags.getFlag(kScoringSawINNFlag)) + result += kSawINNScore; + if (_scoringFlags.getFlag(kScoringTookShowerFlag)) + result += kTookShowerScore; + if (_scoringFlags.getFlag(kScoringFixedHairFlag)) + result += kFixedHairScore; + if (_scoringFlags.getFlag(kScoringGotKeyCardFlag)) + result += kGotKeyCardScore; + if (_scoringFlags.getFlag(kScoringReadPaperFlag)) + result += kReadPaperScore; + if (_scoringFlags.getFlag(kScoringLookThroughTelescopeFlag)) + result += kLookThroughTelescopeScore; + if (_scoringFlags.getFlag(kScoringSawCaldoriaKioskFlag)) + result += kSawCaldoriaKioskScore; + if (_scoringFlags.getFlag(kScoringGoToTSAFlag)) + result += kGoToTSAScore; + if (_scoringFlags.getFlag(kScoringEnterTSAFlag)) + result += kEnterTSAScore; + if (_scoringFlags.getFlag(kScoringSawBust1Flag)) + result += kSawBust1Score; + if (_scoringFlags.getFlag(kScoringSawBust2Flag)) + result += kSawBust2Score; + if (_scoringFlags.getFlag(kScoringSawBust3Flag)) + result += kSawBust3Score; + if (_scoringFlags.getFlag(kScoringSawBust4Flag)) + result += kSawBust4Score; + if (_scoringFlags.getFlag(kScoringSawBust5Flag)) + result += kSawBust5Score; + if (_scoringFlags.getFlag(kScoringSawBust6Flag)) + result += kSawBust6Score; + if (_scoringFlags.getFlag(kScoringSawTheoryFlag)) + result += kSawTheoryScore; + if (_scoringFlags.getFlag(kScoringSawBackgroundFlag)) + result += kSawBackgroundScore; + if (_scoringFlags.getFlag(kScoringSawProcedureFlag)) + result += kSawProcedureScore; + if (_scoringFlags.getFlag(kScoringGotJourneymanKeyFlag)) + result += kGotJourneymanKeyScore; + if (_scoringFlags.getFlag(kScoringGotPegasusBiochipFlag)) + result += kGotPegasusBiochipScore; + if (_scoringFlags.getFlag(kScoringGotBiosuitFlag)) + result += kGotBiosuitScore; + if (_scoringFlags.getFlag(kScoringGoToPrehistoricFlag)) + result += kGoToPrehistoricScore; + if (_scoringFlags.getFlag(kScoringPutLogInReaderFlag)) + result += kPutLogInReaderScore; + if (_scoringFlags.getFlag(kScoringSawCaldoriaNormalFlag)) + result += kSawCaldoriaNormalScore; + if (_scoringFlags.getFlag(kScoringSawCaldoriaAlteredFlag)) + result += kSawCaldoriaAlteredScore; + if (_scoringFlags.getFlag(kScoringSawNoradNormalFlag)) + result += kSawNoradNormalScore; + if (_scoringFlags.getFlag(kScoringSawNoradAlteredFlag)) + result += kSawNoradAlteredScore; + if (_scoringFlags.getFlag(kScoringSawMarsNormalFlag)) + result += kSawMarsNormalScore; + if (_scoringFlags.getFlag(kScoringSawMarsAlteredFlag)) + result += kSawMarsAlteredScore; + if (_scoringFlags.getFlag(kScoringSawWSCNormalFlag)) + result += kSawWSCNormalScore; + if (_scoringFlags.getFlag(kScoringSawWSCAlteredFlag)) + result += kSawWSCAlteredScore; + if (_scoringFlags.getFlag(kScoringWentToReadyRoom2Flag)) + result += kWentToReadyRoom2Score; + if (_scoringFlags.getFlag(kScoringWentAfterSinclairFlag)) + result += kWentAfterSinclairScore; + if (_scoringFlags.getFlag(kScoringUsedCardBombFlag)) + result += kUsedCardBombScore; + if (_scoringFlags.getFlag(kScoringShieldedCardBombFlag)) + result += kShieldedCardBombScore; + if (_scoringFlags.getFlag(kScoringStunnedSinclairFlag)) + result += kStunnedSinclairScore; + if (_scoringFlags.getFlag(kScoringDisarmedNukeFlag)) + result += kDisarmedNukeScore; + + return result; +} + +GameScoreType GameStateManager::getPrehistoricScore() { + GameScoreType result = 0; + + if (_scoringFlags.getFlag(kScoringThrewBreakerFlag)) + result += kThrewBreakerScore; + if (_scoringFlags.getFlag(kScoringExtendedBridgeFlag)) + result += kExtendedBridgeScore; + if (_scoringFlags.getFlag(kScoringGotHistoricalLogFlag)) + result += kGotHistoricalLogScore; + if (_scoringFlags.getFlag(kScoringFinishedPrehistoricFlag)) + result += kFinishedPrehistoricScore; + + return result; +} + +GameScoreType GameStateManager::getMarsScore() { + GameScoreType result = 0; + + if (_scoringFlags.getFlag(kScoringThrownByRobotFlag)) + result += kThrownByRobotScore; + if (_scoringFlags.getFlag(kScoringGotMarsCardFlag)) + result += kGotMarsCardScore; + if (_scoringFlags.getFlag(kScoringSawMarsKioskFlag)) + result += kSawMarsKioskScore; + if (_scoringFlags.getFlag(kScoringSawTransportMapFlag)) + result += kSawTransportMapScore; + if (_scoringFlags.getFlag(kScoringGotCrowBarFlag)) + result += kGotCrowBarScore; + if (_scoringFlags.getFlag(kScoringTurnedOnTransportFlag)) + result += kTurnedOnTransportScore; + if (_scoringFlags.getFlag(kScoringGotOxygenMaskFlag)) + result += kGotOxygenMaskScore; + if (_scoringFlags.getFlag(kScoringAvoidedRobotFlag)) + result += kAvoidedRobotScore; + if (_scoringFlags.getFlag(kScoringActivatedPlatformFlag)) + result += kActivatedPlatformScore; + if (_scoringFlags.getFlag(kScoringUsedLiquidNitrogenFlag)) + result += kUsedLiquidNitrogenScore; + if (_scoringFlags.getFlag(kScoringUsedCrowBarFlag)) + result += kUsedCrowBarScore; + if (_scoringFlags.getFlag(kScoringFoundCardBombFlag)) + result += kFoundCardBombScore; + if (_scoringFlags.getFlag(kScoringDisarmedCardBombFlag)) + result += kDisarmedCardBombScore; + if (_scoringFlags.getFlag(kScoringGotCardBombFlag)) + result += kGotCardBombScore; + if (_scoringFlags.getFlag(kScoringThreadedMazeFlag)) + result += kThreadedMazeScore; + if (_scoringFlags.getFlag(kScoringThreadedGearRoomFlag)) + result += kThreadedGearRoomScore; + if (_scoringFlags.getFlag(kScoringEnteredShuttleFlag)) + result += kEnteredShuttleScore; + if (_scoringFlags.getFlag(kScoringEnteredLaunchTubeFlag)) + result += kEnteredLaunchTubeScore; + if (_scoringFlags.getFlag(kScoringStoppedRobotsShuttleFlag)) + result += kStoppedRobotsShuttleScore; + if (_scoringFlags.getFlag(kScoringGotMarsOpMemChipFlag)) + result += kGotMarsOpMemChipScore; + if (_scoringFlags.getFlag(kScoringFinishedMarsFlag)) + result += kFinishedMarsScore; + + return result; +} + +GameScoreType GameStateManager::getNoradScore() { + GameScoreType result = 0; + + if (_scoringFlags.getFlag(kScoringSawSecurityMonitorFlag)) + result += kSawSecurityMonitorScore; + if (_scoringFlags.getFlag(kScoringFilledOxygenCanisterFlag)) + result += kFilledOxygenCanisterScore; + if (_scoringFlags.getFlag(kScoringFilledArgonCanisterFlag)) + result += kFilledArgonCanisterScore; + if (_scoringFlags.getFlag(kScoringSawUnconsciousOperatorFlag)) + result += kSawUnconsciousOperatorScore; + if (_scoringFlags.getFlag(kScoringWentThroughPressureDoorFlag)) + result += kWentThroughPressureDoorScore; + if (_scoringFlags.getFlag(kScoringPreppedSubFlag)) + result += kPreppedSubScore; + if (_scoringFlags.getFlag(kScoringEnteredSubFlag)) + result += kEnteredSubScore; + if (_scoringFlags.getFlag(kScoringExitedSubFlag)) + result += kExitedSubScore; + if (_scoringFlags.getFlag(kScoringSawRobotAt54NorthFlag)) + result += kSawRobotAt54NorthScore; + if (_scoringFlags.getFlag(kScoringPlayedWithClawFlag)) + result += kPlayedWithClawScore; + if (_scoringFlags.getFlag(kScoringUsedRetinalChipFlag)) + result += kUsedRetinalChipScore; + if (_scoringFlags.getFlag(kScoringFinishedGlobeGameFlag)) + result += kFinishedGlobeGameScore; + if (_scoringFlags.getFlag(kScoringStoppedNoradRobotFlag)) + result += kStoppedNoradRobotScore; + if (_scoringFlags.getFlag(kScoringGotNoradOpMemChipFlag)) + result += kGotNoradOpMemChipScore; + if (_scoringFlags.getFlag(kScoringFinishedNoradFlag)) + result += kFinishedNoradScore; + + return result; +} + +GameScoreType GameStateManager::getWSCScore() { + GameScoreType result = 0; + + if (_scoringFlags.getFlag(kScoringRemovedDartFlag)) + result += kRemovedDartScore; + if (_scoringFlags.getFlag(kScoringAnalyzedDartFlag)) + result += kAnalyzedDartScore; + if (_scoringFlags.getFlag(kScoringBuiltAntidoteFlag)) + result += kBuiltAntidoteScore; + if (_scoringFlags.getFlag(kScoringGotSinclairKeyFlag)) + result += kGotSinclairKeyScore; + if (_scoringFlags.getFlag(kScoringGotArgonCanisterFlag)) + result += kGotArgonCanisterScore; + if (_scoringFlags.getFlag(kScoringGotNitrogenCanisterFlag)) + result += kGotNitrogenCanisterScore; + if (_scoringFlags.getFlag(kScoringPlayedWithMessagesFlag)) + result += kPlayedWithMessagesScore; + if (_scoringFlags.getFlag(kScoringSawMorphExperimentFlag)) + result += kSawMorphExperimentScore; + if (_scoringFlags.getFlag(kScoringEnteredSinclairOfficeFlag)) + result += kEnteredSinclairOfficeScore; + if (_scoringFlags.getFlag(kScoringSawBrochureFlag)) + result += kSawBrochureScore; + if (_scoringFlags.getFlag(kScoringSawSinclairEntry1Flag)) + result += kSawSinclairEntry1Score; + if (_scoringFlags.getFlag(kScoringSawSinclairEntry2Flag)) + result += kSawSinclairEntry2Score; + if (_scoringFlags.getFlag(kScoringSawSinclairEntry3Flag)) + result += kSawSinclairEntry3Score; + if (_scoringFlags.getFlag(kScoringSawWSCDirectoryFlag)) + result += kSawWSCDirectoryScore; + if (_scoringFlags.getFlag(kScoringUsedCrowBarInWSCFlag)) + result += kUsedCrowBarInWSCScore; + if (_scoringFlags.getFlag(kScoringFinishedPlasmaDodgeFlag)) + result += kFinishedPlasmaDodgeScore; + if (_scoringFlags.getFlag(kScoringOpenedCatwalkFlag)) + result += kOpenedCatwalkScore; + if (_scoringFlags.getFlag(kScoringStoppedWSCRobotFlag)) + result += kStoppedWSCRobotScore; + if (_scoringFlags.getFlag(kScoringGotWSCOpMemChipFlag)) + result += kGotWSCOpMemChipScore; + if (_scoringFlags.getFlag(kScoringFinishedWSCFlag)) + result += kFinishedWSCScore; + + return result; +} + +GameScoreType GameStateManager::getGandhiScore() { + GameScoreType result = 0; + + if (_scoringFlags.getFlag(kScoringMarsGandhiFlag)) + result += kMarsGandhiScore; + if (_scoringFlags.getFlag(kScoringNoradGandhiFlag)) + result += kNoradGandhiScore; + if (_scoringFlags.getFlag(kScoringWSCGandhiFlag)) + result += kWSCGandhiScore; + + return result; +} + +GameScoreType GameStateManager::getTotalScore() { + return getCaldoriaTSAScore() + + getPrehistoricScore() + + getMarsScore() + + getNoradScore() + + getWSCScore() + + getGandhiScore(); +} + +///////////////////////////////////////////// +// +// Caldoria data + +void GameStateManager::writeCaldoriaState(Common::WriteStream *stream) { + _caldoriaFlags.writeToStream(stream); + stream->writeUint32BE(_caldoriaFuseTimeLimit); +} + +void GameStateManager::readCaldoriaState(Common::ReadStream *stream) { + _caldoriaFlags.readFromStream(stream); + _caldoriaFuseTimeLimit = stream->readUint32BE(); +} + +void GameStateManager::resetCaldoriaState() { + _caldoriaFlags.clearAllFlags(); + _caldoriaFuseTimeLimit = 0; +} + +///////////////////////////////////////////// +// +// TSA data + +void GameStateManager::writeTSAState(Common::WriteStream *stream) { + _TSAFlags.writeToStream(stream); + stream->writeUint32BE(_TSARipTimerTime); + stream->writeUint32BE(_TSAFuseTimeLimit); + stream->writeByte(_TSAState); + stream->writeByte(_T0BMonitorMode); + stream->writeUint32BE(_T0BMonitorStart); +} + +void GameStateManager::readTSAState(Common::ReadStream *stream) { + _TSAFlags.readFromStream(stream); + _TSARipTimerTime = stream->readUint32BE(); + _TSAFuseTimeLimit = stream->readUint32BE(); + _TSAState = stream->readByte(); + _T0BMonitorMode = stream->readByte(); + _T0BMonitorStart = stream->readUint32BE(); +} + +void GameStateManager::resetTSAState() { + _TSAFlags.clearAllFlags(); + _TSAState = 0; + _T0BMonitorMode = 0; + _T0BMonitorStart = 0; + _TSARipTimerTime = 0; + _TSAFuseTimeLimit = kTSAUncreatedTimeLimit; +} + +///////////////////////////////////////////// +// +// Prehistoric data + +void GameStateManager::writePrehistoricState(Common::WriteStream *stream) { + _prehistoricFlags.writeToStream(stream); +} + +void GameStateManager::readPrehistoricState(Common::ReadStream *stream) { + _prehistoricFlags.readFromStream(stream); +} + +void GameStateManager::resetPrehistoricState() { + _prehistoricFlags.clearAllFlags(); +} + +///////////////////////////////////////////// +// +// Norad data + +void GameStateManager::writeNoradState(Common::WriteStream *stream) { + _noradFlags.writeToStream(stream); + stream->writeUint16BE(_noradSubRoomPressure); + stream->writeByte(_noradSubPrepState); +} + +void GameStateManager::readNoradState(Common::ReadStream *stream) { + _noradFlags.readFromStream(stream); + _noradSubRoomPressure = stream->readUint16BE(); + _noradSubPrepState = (NoradSubPrepState)stream->readByte(); +} + +void GameStateManager::resetNoradState() { + _noradFlags.clearAllFlags(); + _noradSubRoomPressure = 9; + _noradSubPrepState = kSubNotPrepped; +} + +///////////////////////////////////////////// +// +// Mars data + +void GameStateManager::writeMarsState(Common::WriteStream *stream) { + _marsFlags.writeToStream(stream); +} + +void GameStateManager::readMarsState(Common::ReadStream *stream) { + _marsFlags.readFromStream(stream); +} + +void GameStateManager::resetMarsState() { + _marsFlags.clearAllFlags(); +} + +///////////////////////////////////////////// +// +// WSC data + +void GameStateManager::writeWSCState(Common::WriteStream *stream) { + _WSCFlags.writeToStream(stream); +} + +void GameStateManager::readWSCState(Common::ReadStream *stream) { + _WSCFlags.readFromStream(stream); +} + +void GameStateManager::resetWSCState() { + _WSCFlags.clearAllFlags(); +} + +void GameStateManager::setScoringSawINN(const bool flag) { + _scoringFlags.setFlag(kScoringSawINNFlag, flag); +} + +void GameStateManager::setScoringTookShower(const bool flag) { + _scoringFlags.setFlag(kScoringTookShowerFlag, flag); +} + +void GameStateManager::setScoringFixedHair(const bool flag) { + _scoringFlags.setFlag(kScoringFixedHairFlag, flag); +} + +void GameStateManager::setScoringGotKeyCard(const bool flag) { + _scoringFlags.setFlag(kScoringGotKeyCardFlag, flag); +} + +void GameStateManager::setScoringReadPaper(const bool flag) { + _scoringFlags.setFlag(kScoringReadPaperFlag, flag); +} + +void GameStateManager::setScoringLookThroughTelescope(const bool flag) { + _scoringFlags.setFlag(kScoringLookThroughTelescopeFlag, flag); +} + +void GameStateManager::setScoringSawCaldoriaKiosk(const bool flag) { + _scoringFlags.setFlag(kScoringSawCaldoriaKioskFlag, flag); +} + +void GameStateManager::setScoringGoToTSA(const bool flag) { + _scoringFlags.setFlag(kScoringGoToTSAFlag, flag); +} + +void GameStateManager::setScoringEnterTSA(const bool flag) { + _scoringFlags.setFlag(kScoringEnterTSAFlag, flag); +} + +void GameStateManager::setScoringSawBust1(const bool flag) { + _scoringFlags.setFlag(kScoringSawBust1Flag, flag); +} + +void GameStateManager::setScoringSawBust2(const bool flag) { + _scoringFlags.setFlag(kScoringSawBust2Flag, flag); +} + +void GameStateManager::setScoringSawBust3(const bool flag) { + _scoringFlags.setFlag(kScoringSawBust3Flag, flag); +} + +void GameStateManager::setScoringSawBust4(const bool flag) { + _scoringFlags.setFlag(kScoringSawBust4Flag, flag); +} + +void GameStateManager::setScoringSawBust5(const bool flag) { + _scoringFlags.setFlag(kScoringSawBust5Flag, flag); +} + +void GameStateManager::setScoringSawBust6(const bool flag) { + _scoringFlags.setFlag(kScoringSawBust6Flag, flag); +} + +void GameStateManager::setScoringSawTheory(const bool flag) { + _scoringFlags.setFlag(kScoringSawTheoryFlag, flag); +} + +void GameStateManager::setScoringSawBackground(const bool flag) { + _scoringFlags.setFlag(kScoringSawBackgroundFlag, flag); +} + +void GameStateManager::setScoringSawProcedure(const bool flag) { + _scoringFlags.setFlag(kScoringSawProcedureFlag, flag); +} + +void GameStateManager::setScoringGotJourneymanKey(const bool flag) { + _scoringFlags.setFlag(kScoringGotJourneymanKeyFlag, flag); +} + +void GameStateManager::setScoringGotPegasusBiochip(const bool flag) { + _scoringFlags.setFlag(kScoringGotPegasusBiochipFlag, flag); +} + +void GameStateManager::setScoringGotBiosuit(const bool flag) { + _scoringFlags.setFlag(kScoringGotBiosuitFlag, flag); +} + +void GameStateManager::setScoringGoToPrehistoric(const bool flag) { + _scoringFlags.setFlag(kScoringGoToPrehistoricFlag, flag); +} + +void GameStateManager::setScoringPutLogInReader(const bool flag) { + _scoringFlags.setFlag(kScoringPutLogInReaderFlag, flag); +} + +void GameStateManager::setScoringSawCaldoriaNormal(const bool flag) { + _scoringFlags.setFlag(kScoringSawCaldoriaNormalFlag, flag); +} + +void GameStateManager::setScoringSawCaldoriaAltered(const bool flag) { + _scoringFlags.setFlag(kScoringSawCaldoriaAlteredFlag, flag); +} + +void GameStateManager::setScoringSawNoradNormal(const bool flag) { + _scoringFlags.setFlag(kScoringSawNoradNormalFlag, flag); +} + +void GameStateManager::setScoringSawNoradAltered(const bool flag) { + _scoringFlags.setFlag(kScoringSawNoradAlteredFlag, flag); +} + +void GameStateManager::setScoringSawMarsNormal(const bool flag) { + _scoringFlags.setFlag(kScoringSawMarsNormalFlag, flag); +} + +void GameStateManager::setScoringSawMarsAltered(const bool flag) { + _scoringFlags.setFlag(kScoringSawMarsAlteredFlag, flag); +} + +void GameStateManager::setScoringSawWSCNormal(const bool flag) { + _scoringFlags.setFlag(kScoringSawWSCNormalFlag, flag); +} + +void GameStateManager::setScoringSawWSCAltered(const bool flag) { + _scoringFlags.setFlag(kScoringSawWSCAlteredFlag, flag); +} + +void GameStateManager::setScoringWentToReadyRoom2(const bool flag) { + _scoringFlags.setFlag(kScoringWentToReadyRoom2Flag, flag); +} + +void GameStateManager::setScoringWentAfterSinclair(const bool flag) { + _scoringFlags.setFlag(kScoringWentAfterSinclairFlag, flag); +} + +void GameStateManager::setScoringUsedCardBomb(const bool flag) { + _scoringFlags.setFlag(kScoringUsedCardBombFlag, flag); +} + +void GameStateManager::setScoringShieldedCardBomb(const bool flag) { + _scoringFlags.setFlag(kScoringShieldedCardBombFlag, flag); +} + +void GameStateManager::setScoringStunnedSinclair(const bool flag) { + _scoringFlags.setFlag(kScoringStunnedSinclairFlag, flag); +} + +void GameStateManager::setScoringDisarmedNuke(const bool flag) { + _scoringFlags.setFlag(kScoringDisarmedNukeFlag, flag); +} + +void GameStateManager::setScoringThrewBreaker(const bool flag) { + _scoringFlags.setFlag(kScoringThrewBreakerFlag, flag); +} + +void GameStateManager::setScoringExtendedBridge(const bool flag) { + _scoringFlags.setFlag(kScoringExtendedBridgeFlag, flag); +} + +void GameStateManager::setScoringGotHistoricalLog(const bool flag) { + _scoringFlags.setFlag(kScoringGotHistoricalLogFlag, flag); +} + +void GameStateManager::setScoringFinishedPrehistoric(const bool flag) { + _scoringFlags.setFlag(kScoringFinishedPrehistoricFlag, flag); +} + +void GameStateManager::setScoringThrownByRobot(const bool flag) { + _scoringFlags.setFlag(kScoringThrownByRobotFlag, flag); +} + +void GameStateManager::setScoringGotMarsCard(const bool flag) { + _scoringFlags.setFlag(kScoringGotMarsCardFlag, flag); +} + +void GameStateManager::setScoringSawMarsKiosk(const bool flag) { + _scoringFlags.setFlag(kScoringSawMarsKioskFlag, flag); +} + +void GameStateManager::setScoringSawTransportMap(const bool flag) { + _scoringFlags.setFlag(kScoringSawTransportMapFlag, flag); +} + +void GameStateManager::setScoringGotCrowBar(const bool flag) { + _scoringFlags.setFlag(kScoringGotCrowBarFlag, flag); +} + +void GameStateManager::setScoringTurnedOnTransport(const bool flag) { + _scoringFlags.setFlag(kScoringTurnedOnTransportFlag, flag); +} + +void GameStateManager::setScoringGotOxygenMask(const bool flag) { + _scoringFlags.setFlag(kScoringGotOxygenMaskFlag, flag); +} + +void GameStateManager::setScoringAvoidedRobot(const bool flag) { + _scoringFlags.setFlag(kScoringAvoidedRobotFlag, flag); +} + +void GameStateManager::setScoringActivatedPlatform(const bool flag) { + _scoringFlags.setFlag(kScoringActivatedPlatformFlag, flag); +} + +void GameStateManager::setScoringUsedLiquidNitrogen(const bool flag) { + _scoringFlags.setFlag(kScoringUsedLiquidNitrogenFlag, flag); +} + +void GameStateManager::setScoringUsedCrowBar(const bool flag) { + _scoringFlags.setFlag(kScoringUsedCrowBarFlag, flag); +} + +void GameStateManager::setScoringFoundCardBomb(const bool flag) { + _scoringFlags.setFlag(kScoringFoundCardBombFlag, flag); +} + +void GameStateManager::setScoringDisarmedCardBomb(const bool flag) { + _scoringFlags.setFlag(kScoringDisarmedCardBombFlag, flag); +} + +void GameStateManager::setScoringGotCardBomb(const bool flag) { + _scoringFlags.setFlag(kScoringGotCardBombFlag, flag); +} + +void GameStateManager::setScoringThreadedMaze(const bool flag) { + _scoringFlags.setFlag(kScoringThreadedMazeFlag, flag); +} + +void GameStateManager::setScoringThreadedGearRoom(const bool flag) { + _scoringFlags.setFlag(kScoringThreadedGearRoomFlag, flag); +} + +void GameStateManager::setScoringEnteredShuttle(const bool flag) { + _scoringFlags.setFlag(kScoringEnteredShuttleFlag, flag); +} + +void GameStateManager::setScoringEnteredLaunchTube(const bool flag) { + _scoringFlags.setFlag(kScoringEnteredLaunchTubeFlag, flag); +} + +void GameStateManager::setScoringStoppedRobotsShuttle(const bool flag) { + _scoringFlags.setFlag(kScoringStoppedRobotsShuttleFlag, flag); +} + +void GameStateManager::setScoringGotMarsOpMemChip(const bool flag) { + _scoringFlags.setFlag(kScoringGotMarsOpMemChipFlag, flag); +} + +void GameStateManager::setScoringFinishedMars(const bool flag) { + _scoringFlags.setFlag(kScoringFinishedMarsFlag, flag); +} + +void GameStateManager::setScoringSawSecurityMonitor(const bool flag) { + _scoringFlags.setFlag(kScoringSawSecurityMonitorFlag, flag); +} + +void GameStateManager::setScoringFilledOxygenCanister(const bool flag) { + _scoringFlags.setFlag(kScoringFilledOxygenCanisterFlag, flag); +} + +void GameStateManager::setScoringFilledArgonCanister(const bool flag) { + _scoringFlags.setFlag(kScoringFilledArgonCanisterFlag, flag); +} + +void GameStateManager::setScoringSawUnconsciousOperator(const bool flag) { + _scoringFlags.setFlag(kScoringSawUnconsciousOperatorFlag, flag); +} + +void GameStateManager::setScoringWentThroughPressureDoor(const bool flag) { + _scoringFlags.setFlag(kScoringWentThroughPressureDoorFlag, flag); +} + +void GameStateManager::setScoringPreppedSub(const bool flag) { + _scoringFlags.setFlag(kScoringPreppedSubFlag, flag); +} + +void GameStateManager::setScoringEnteredSub(const bool flag) { + _scoringFlags.setFlag(kScoringEnteredSubFlag, flag); +} + +void GameStateManager::setScoringExitedSub(const bool flag) { + _scoringFlags.setFlag(kScoringExitedSubFlag, flag); +} + +void GameStateManager::setScoringSawRobotAt54North(const bool flag) { + _scoringFlags.setFlag(kScoringSawRobotAt54NorthFlag, flag); +} + +void GameStateManager::setScoringPlayedWithClaw(const bool flag) { + _scoringFlags.setFlag(kScoringPlayedWithClawFlag, flag); +} + +void GameStateManager::setScoringUsedRetinalChip(const bool flag) { + _scoringFlags.setFlag(kScoringUsedRetinalChipFlag, flag); +} + +void GameStateManager::setScoringFinishedGlobeGame(const bool flag) { + _scoringFlags.setFlag(kScoringFinishedGlobeGameFlag, flag); +} + +void GameStateManager::setScoringStoppedNoradRobot(const bool flag) { + _scoringFlags.setFlag(kScoringStoppedNoradRobotFlag, flag); +} + +void GameStateManager::setScoringGotNoradOpMemChip(const bool flag) { + _scoringFlags.setFlag(kScoringGotNoradOpMemChipFlag, flag); +} + +void GameStateManager::setScoringFinishedNorad(const bool flag) { + _scoringFlags.setFlag(kScoringFinishedNoradFlag, flag); +} + +void GameStateManager::setScoringRemovedDart(const bool flag) { + _scoringFlags.setFlag(kScoringRemovedDartFlag, flag); +} + +void GameStateManager::setScoringAnalyzedDart(const bool flag) { + _scoringFlags.setFlag(kScoringAnalyzedDartFlag, flag); +} + +void GameStateManager::setScoringBuiltAntidote(const bool flag) { + _scoringFlags.setFlag(kScoringBuiltAntidoteFlag, flag); +} + +void GameStateManager::setScoringGotSinclairKey(const bool flag) { + _scoringFlags.setFlag(kScoringGotSinclairKeyFlag, flag); +} + +void GameStateManager::setScoringGotArgonCanister(const bool flag) { + _scoringFlags.setFlag(kScoringGotArgonCanisterFlag, flag); +} + +void GameStateManager::setScoringGotNitrogenCanister(const bool flag) { + _scoringFlags.setFlag(kScoringGotNitrogenCanisterFlag, flag); +} + +void GameStateManager::setScoringPlayedWithMessages(const bool flag) { + _scoringFlags.setFlag(kScoringPlayedWithMessagesFlag, flag); +} + +void GameStateManager::setScoringSawMorphExperiment(const bool flag) { + _scoringFlags.setFlag(kScoringSawMorphExperimentFlag, flag); +} + +void GameStateManager::setScoringEnteredSinclairOffice(const bool flag) { + _scoringFlags.setFlag(kScoringEnteredSinclairOfficeFlag, flag); +} + +void GameStateManager::setScoringSawBrochure(const bool flag) { + _scoringFlags.setFlag(kScoringSawBrochureFlag, flag); +} + +void GameStateManager::setScoringSawSinclairEntry1(const bool flag) { + _scoringFlags.setFlag(kScoringSawSinclairEntry1Flag, flag); +} + +void GameStateManager::setScoringSawSinclairEntry2(const bool flag) { + _scoringFlags.setFlag(kScoringSawSinclairEntry2Flag, flag); +} + +void GameStateManager::setScoringSawSinclairEntry3(const bool flag) { + _scoringFlags.setFlag(kScoringSawSinclairEntry3Flag, flag); +} + +void GameStateManager::setScoringSawWSCDirectory(const bool flag) { + _scoringFlags.setFlag(kScoringSawWSCDirectoryFlag, flag); +} + +void GameStateManager::setScoringUsedCrowBarInWSC(const bool flag) { + _scoringFlags.setFlag(kScoringUsedCrowBarInWSCFlag, flag); +} + +void GameStateManager::setScoringFinishedPlasmaDodge(const bool flag) { + _scoringFlags.setFlag(kScoringFinishedPlasmaDodgeFlag, flag); +} + +void GameStateManager::setScoringOpenedCatwalk(const bool flag) { + _scoringFlags.setFlag(kScoringOpenedCatwalkFlag, flag); +} + +void GameStateManager::setScoringStoppedWSCRobot(const bool flag) { + _scoringFlags.setFlag(kScoringStoppedWSCRobotFlag, flag); +} + +void GameStateManager::setScoringGotWSCOpMemChip(const bool flag) { + _scoringFlags.setFlag(kScoringGotWSCOpMemChipFlag, flag); +} + +void GameStateManager::setScoringFinishedWSC(const bool flag) { + _scoringFlags.setFlag(kScoringFinishedWSCFlag, flag); +} + +void GameStateManager::setScoringMarsGandhi(const bool flag) { + _scoringFlags.setFlag(kScoringMarsGandhiFlag, flag); +} + +void GameStateManager::setScoringNoradGandhi(const bool flag) { + _scoringFlags.setFlag(kScoringNoradGandhiFlag, flag); +} + +void GameStateManager::setScoringWSCGandhi(const bool flag) { + _scoringFlags.setFlag(kScoringWSCGandhiFlag, flag); +} + +bool GameStateManager::getScoringSawINN() { + return _scoringFlags.getFlag(kScoringSawINNFlag); +} + +bool GameStateManager::getScoringTookShower() { + return _scoringFlags.getFlag(kScoringTookShowerFlag); +} + +bool GameStateManager::getScoringFixedHair() { + return _scoringFlags.getFlag(kScoringFixedHairFlag); +} + +bool GameStateManager::getScoringGotKeyCard() { + return _scoringFlags.getFlag(kScoringGotKeyCardFlag); +} + +bool GameStateManager::getScoringReadPaper() { + return _scoringFlags.getFlag(kScoringReadPaperFlag); +} + +bool GameStateManager::getScoringLookThroughTelescope() { + return _scoringFlags.getFlag(kScoringLookThroughTelescopeFlag); +} + +bool GameStateManager::getScoringSawCaldoriaKiosk() { + return _scoringFlags.getFlag(kScoringSawCaldoriaKioskFlag); +} + +bool GameStateManager::getScoringGoToTSA() { + return _scoringFlags.getFlag(kScoringGoToTSAFlag); +} + +bool GameStateManager::getScoringEnterTSA() { + return _scoringFlags.getFlag(kScoringEnterTSAFlag); +} + +bool GameStateManager::getScoringSawBust1() { + return _scoringFlags.getFlag(kScoringSawBust1Flag); +} + +bool GameStateManager::getScoringSawBust2() { + return _scoringFlags.getFlag(kScoringSawBust2Flag); +} + +bool GameStateManager::getScoringSawBust3() { + return _scoringFlags.getFlag(kScoringSawBust3Flag); +} + +bool GameStateManager::getScoringSawBust4() { + return _scoringFlags.getFlag(kScoringSawBust4Flag); +} + +bool GameStateManager::getScoringSawBust5() { + return _scoringFlags.getFlag(kScoringSawBust5Flag); +} + +bool GameStateManager::getScoringSawBust6() { + return _scoringFlags.getFlag(kScoringSawBust6Flag); +} + +bool GameStateManager::getScoringSawTheory() { + return _scoringFlags.getFlag(kScoringSawTheoryFlag); +} + +bool GameStateManager::getScoringSawBackground() { + return _scoringFlags.getFlag(kScoringSawBackgroundFlag); +} + +bool GameStateManager::getScoringSawProcedure() { + return _scoringFlags.getFlag(kScoringSawProcedureFlag); +} + +bool GameStateManager::getScoringGotJourneymanKey() { + return _scoringFlags.getFlag(kScoringGotJourneymanKeyFlag); +} + +bool GameStateManager::getScoringGotPegasusBiochip() { + return _scoringFlags.getFlag(kScoringGotPegasusBiochipFlag); +} + +bool GameStateManager::getScoringGotBiosuit() { + return _scoringFlags.getFlag(kScoringGotBiosuitFlag); +} + +bool GameStateManager::getScoringGoToPrehistoric() { + return _scoringFlags.getFlag(kScoringGoToPrehistoricFlag); +} + +bool GameStateManager::getScoringPutLogInReader() { + return _scoringFlags.getFlag(kScoringPutLogInReaderFlag); +} + +bool GameStateManager::getScoringSawCaldoriaNormal() { + return _scoringFlags.getFlag(kScoringSawCaldoriaNormalFlag); +} + +bool GameStateManager::getScoringSawCaldoriaAltered() { + return _scoringFlags.getFlag(kScoringSawCaldoriaAlteredFlag); +} + +bool GameStateManager::getScoringSawNoradNormal() { + return _scoringFlags.getFlag(kScoringSawNoradNormalFlag); +} + +bool GameStateManager::getScoringSawNoradAltered() { + return _scoringFlags.getFlag(kScoringSawNoradAlteredFlag); +} + +bool GameStateManager::getScoringSawMarsNormal() { + return _scoringFlags.getFlag(kScoringSawMarsNormalFlag); +} + +bool GameStateManager::getScoringSawMarsAltered() { + return _scoringFlags.getFlag(kScoringSawMarsAlteredFlag); +} + +bool GameStateManager::getScoringSawWSCNormal() { + return _scoringFlags.getFlag(kScoringSawWSCNormalFlag); +} + +bool GameStateManager::getScoringSawWSCAltered() { + return _scoringFlags.getFlag(kScoringSawWSCAlteredFlag); +} + +bool GameStateManager::getScoringWentToReadyRoom2() { + return _scoringFlags.getFlag(kScoringWentToReadyRoom2Flag); +} + +bool GameStateManager::getScoringWentAfterSinclair() { + return _scoringFlags.getFlag(kScoringWentAfterSinclairFlag); +} + +bool GameStateManager::getScoringUsedCardBomb() { + return _scoringFlags.getFlag(kScoringUsedCardBombFlag); +} + +bool GameStateManager::getScoringShieldedCardBomb() { + return _scoringFlags.getFlag(kScoringShieldedCardBombFlag); +} + +bool GameStateManager::getScoringStunnedSinclair() { + return _scoringFlags.getFlag(kScoringStunnedSinclairFlag); +} + +bool GameStateManager::getScoringDisarmedNuke() { + return _scoringFlags.getFlag(kScoringDisarmedNukeFlag); +} + +bool GameStateManager::getScoringThrewBreaker() { + return _scoringFlags.getFlag(kScoringThrewBreakerFlag); +} + +bool GameStateManager::getScoringExtendedBridge() { + return _scoringFlags.getFlag(kScoringExtendedBridgeFlag); +} + +bool GameStateManager::getScoringGotHistoricalLog() { + return _scoringFlags.getFlag(kScoringGotHistoricalLogFlag); +} + +bool GameStateManager::getScoringFinishedPrehistoric() { + return _scoringFlags.getFlag(kScoringFinishedPrehistoricFlag); +} + +bool GameStateManager::getScoringThrownByRobot() { + return _scoringFlags.getFlag(kScoringThrownByRobotFlag); +} + +bool GameStateManager::getScoringGotMarsCard() { + return _scoringFlags.getFlag(kScoringGotMarsCardFlag); +} + +bool GameStateManager::getScoringSawMarsKiosk() { + return _scoringFlags.getFlag(kScoringSawMarsKioskFlag); +} + +bool GameStateManager::getScoringSawTransportMap() { + return _scoringFlags.getFlag(kScoringSawTransportMapFlag); +} + +bool GameStateManager::getScoringGotCrowBar() { + return _scoringFlags.getFlag(kScoringGotCrowBarFlag); +} + +bool GameStateManager::getScoringTurnedOnTransport() { + return _scoringFlags.getFlag(kScoringTurnedOnTransportFlag); +} + +bool GameStateManager::getScoringGotOxygenMask() { + return _scoringFlags.getFlag(kScoringGotOxygenMaskFlag); +} + +bool GameStateManager::getScoringAvoidedRobot() { + return _scoringFlags.getFlag(kScoringAvoidedRobotFlag); +} + +bool GameStateManager::getScoringActivatedPlatform() { + return _scoringFlags.getFlag(kScoringActivatedPlatformFlag); +} + +bool GameStateManager::getScoringUsedLiquidNitrogen() { + return _scoringFlags.getFlag(kScoringUsedLiquidNitrogenFlag); +} + +bool GameStateManager::getScoringUsedCrowBar() { + return _scoringFlags.getFlag(kScoringUsedCrowBarFlag); +} + +bool GameStateManager::getScoringFoundCardBomb() { + return _scoringFlags.getFlag(kScoringFoundCardBombFlag); +} + +bool GameStateManager::getScoringDisarmedCardBomb() { + return _scoringFlags.getFlag(kScoringDisarmedCardBombFlag); +} + +bool GameStateManager::getScoringGotCardBomb() { + return _scoringFlags.getFlag(kScoringGotCardBombFlag); +} + +bool GameStateManager::getScoringThreadedMaze() { + return _scoringFlags.getFlag(kScoringThreadedMazeFlag); +} + +bool GameStateManager::getScoringThreadedGearRoom() { + return _scoringFlags.getFlag(kScoringThreadedGearRoomFlag); +} + +bool GameStateManager::getScoringEnteredShuttle() { + return _scoringFlags.getFlag(kScoringEnteredShuttleFlag); +} + +bool GameStateManager::getScoringEnteredLaunchTube() { + return _scoringFlags.getFlag(kScoringEnteredLaunchTubeFlag); +} + +bool GameStateManager::getScoringStoppedRobotsShuttle() { + return _scoringFlags.getFlag(kScoringStoppedRobotsShuttleFlag); +} + +bool GameStateManager::getScoringGotMarsOpMemChip() { + return _scoringFlags.getFlag(kScoringGotMarsOpMemChipFlag); +} + +bool GameStateManager::getScoringFinishedMars() { + return _scoringFlags.getFlag(kScoringFinishedMarsFlag); +} + +bool GameStateManager::getScoringSawSecurityMonitor() { + return _scoringFlags.getFlag(kScoringSawSecurityMonitorFlag); +} + +bool GameStateManager::getScoringFilledOxygenCanister() { + return _scoringFlags.getFlag(kScoringFilledOxygenCanisterFlag); +} + +bool GameStateManager::getScoringFilledArgonCanister() { + return _scoringFlags.getFlag(kScoringFilledArgonCanisterFlag); +} + +bool GameStateManager::getScoringSawUnconsciousOperator() { + return _scoringFlags.getFlag(kScoringSawUnconsciousOperatorFlag); +} + +bool GameStateManager::getScoringWentThroughPressureDoor() { + return _scoringFlags.getFlag(kScoringWentThroughPressureDoorFlag); +} + +bool GameStateManager::getScoringPreppedSub() { + return _scoringFlags.getFlag(kScoringPreppedSubFlag); +} + +bool GameStateManager::getScoringEnteredSub() { + return _scoringFlags.getFlag(kScoringEnteredSubFlag); +} + +bool GameStateManager::getScoringExitedSub() { + return _scoringFlags.getFlag(kScoringExitedSubFlag); +} + +bool GameStateManager::getScoringSawRobotAt54North() { + return _scoringFlags.getFlag(kScoringSawRobotAt54NorthFlag); +} + +bool GameStateManager::getScoringPlayedWithClaw() { + return _scoringFlags.getFlag(kScoringPlayedWithClawFlag); +} + +bool GameStateManager::getScoringUsedRetinalChip() { + return _scoringFlags.getFlag(kScoringUsedRetinalChipFlag); +} + +bool GameStateManager::getScoringFinishedGlobeGame() { + return _scoringFlags.getFlag(kScoringFinishedGlobeGameFlag); +} + +bool GameStateManager::getScoringStoppedNoradRobot() { + return _scoringFlags.getFlag(kScoringStoppedNoradRobotFlag); +} + +bool GameStateManager::getScoringGotNoradOpMemChip() { + return _scoringFlags.getFlag(kScoringGotNoradOpMemChipFlag); +} + +bool GameStateManager::getScoringFinishedNorad() { + return _scoringFlags.getFlag(kScoringFinishedNoradFlag); +} + +bool GameStateManager::getScoringRemovedDart() { + return _scoringFlags.getFlag(kScoringRemovedDartFlag); +} + +bool GameStateManager::getScoringAnalyzedDart() { + return _scoringFlags.getFlag(kScoringAnalyzedDartFlag); +} + +bool GameStateManager::getScoringBuiltAntidote() { + return _scoringFlags.getFlag(kScoringBuiltAntidoteFlag); +} + +bool GameStateManager::getScoringGotSinclairKey() { + return _scoringFlags.getFlag(kScoringGotSinclairKeyFlag); +} + +bool GameStateManager::getScoringGotArgonCanister() { + return _scoringFlags.getFlag(kScoringGotArgonCanisterFlag); +} + +bool GameStateManager::getScoringGotNitrogenCanister() { + return _scoringFlags.getFlag(kScoringGotNitrogenCanisterFlag); +} + +bool GameStateManager::getScoringPlayedWithMessages() { + return _scoringFlags.getFlag(kScoringPlayedWithMessagesFlag); +} + +bool GameStateManager::getScoringSawMorphExperiment() { + return _scoringFlags.getFlag(kScoringSawMorphExperimentFlag); +} + +bool GameStateManager::getScoringEnteredSinclairOffice() { + return _scoringFlags.getFlag(kScoringEnteredSinclairOfficeFlag); +} + +bool GameStateManager::getScoringSawBrochure() { + return _scoringFlags.getFlag(kScoringSawBrochureFlag); +} + +bool GameStateManager::getScoringSawSinclairEntry1() { + return _scoringFlags.getFlag(kScoringSawSinclairEntry1Flag); +} + +bool GameStateManager::getScoringSawSinclairEntry2() { + return _scoringFlags.getFlag(kScoringSawSinclairEntry2Flag); +} + +bool GameStateManager::getScoringSawSinclairEntry3() { + return _scoringFlags.getFlag(kScoringSawSinclairEntry3Flag); +} + +bool GameStateManager::getScoringSawWSCDirectory() { + return _scoringFlags.getFlag(kScoringSawWSCDirectoryFlag); +} + +bool GameStateManager::getScoringUsedCrowBarInWSC() { + return _scoringFlags.getFlag(kScoringUsedCrowBarInWSCFlag); +} + +bool GameStateManager::getScoringFinishedPlasmaDodge() { + return _scoringFlags.getFlag(kScoringFinishedPlasmaDodgeFlag); +} + +bool GameStateManager::getScoringOpenedCatwalk() { + return _scoringFlags.getFlag(kScoringOpenedCatwalkFlag); +} + +bool GameStateManager::getScoringStoppedWSCRobot() { + return _scoringFlags.getFlag(kScoringStoppedWSCRobotFlag); +} + +bool GameStateManager::getScoringGotWSCOpMemChip() { + return _scoringFlags.getFlag(kScoringGotWSCOpMemChipFlag); +} + +bool GameStateManager::getScoringFinishedWSC() { + return _scoringFlags.getFlag(kScoringFinishedWSCFlag); +} + +bool GameStateManager::getScoringMarsGandhi() { + return _scoringFlags.getFlag(kScoringMarsGandhiFlag); +} + +bool GameStateManager::getScoringNoradGandhi() { + return _scoringFlags.getFlag(kScoringNoradGandhiFlag); +} + +bool GameStateManager::getScoringWSCGandhi() { + return _scoringFlags.getFlag(kScoringWSCGandhiFlag); +} + +void GameStateManager::setWalkthroughMode(bool value) { + _globalFlags.setFlag(kGlobalWalkthroughFlag, value); +} + +bool GameStateManager::getWalkthroughMode() { + return _globalFlags.getFlag(kGlobalWalkthroughFlag); +} + +void GameStateManager::setShieldOn(bool value) { + _globalFlags.setFlag(kGlobalShieldOnFlag, value); +} + +bool GameStateManager::getShieldOn() { + return _globalFlags.getFlag(kGlobalShieldOnFlag); +} + +void GameStateManager::setEasterEgg(bool value) { + _globalFlags.setFlag(kGlobalEasterEggFlag, value); +} + +bool GameStateManager::getEasterEgg() { + return _globalFlags.getFlag(kGlobalEasterEggFlag); +} + +void GameStateManager::setBeenToWSC(bool value) { + _globalFlags.setFlag(kGlobalBeenToWSCFlag, value); +} + +bool GameStateManager::getBeenToWSC() { + return _globalFlags.getFlag(kGlobalBeenToWSCFlag); +} + +void GameStateManager::setBeenToMars(bool value) { + _globalFlags.setFlag(kGlobalBeenToMarsFlag, value); +} + +bool GameStateManager::getBeenToMars() { + return _globalFlags.getFlag(kGlobalBeenToMarsFlag); +} + +void GameStateManager::setBeenToNorad(bool value) { + _globalFlags.setFlag(kGlobalBeenToNoradFlag, value); +} + +bool GameStateManager::getBeenToNorad() { + return _globalFlags.getFlag(kGlobalBeenToNoradFlag); +} + +void GameStateManager::setWSCFinished(bool value) { + _globalFlags.setFlag(kGlobalWSCFinishedFlag, value); +} + +bool GameStateManager::getWSCFinished() { + return _globalFlags.getFlag(kGlobalWSCFinishedFlag); +} + +void GameStateManager::setMarsFinished(bool value) { + _globalFlags.setFlag(kGlobalMarsFinishedFlag, value); +} + +bool GameStateManager::getMarsFinished() { + return _globalFlags.getFlag(kGlobalMarsFinishedFlag); +} + +void GameStateManager::setNoradFinished(bool value) { + _globalFlags.setFlag(kGlobalNoradFinishedFlag, value); +} + +bool GameStateManager::getNoradFinished() { + return _globalFlags.getFlag(kGlobalNoradFinishedFlag); +} + +bool GameStateManager::allTimeZonesFinished() { + return getWSCFinished() && getMarsFinished() && getNoradFinished(); +} + +void GameStateManager::setTakenItemID(ItemID id, bool value) { + _itemTakenFlags.setFlag(id, value); +} + +bool GameStateManager::isTakenItemID(ItemID id) { + return _itemTakenFlags.getFlag(id); +} + +void GameStateManager::setTakenItem(Item *item, bool value) { + setTakenItemID(item->getObjectID(), value); +} + +bool GameStateManager::isTakenItem(Item *item) { + return isTakenItemID(item->getObjectID()); +} + +void GameStateManager::setCaldoriaFuseTimeLimit(const TimeValue timeLimit) { + _caldoriaFuseTimeLimit = timeLimit; +} + +TimeValue GameStateManager::getCaldoriaFuseTimeLimit() { + return _caldoriaFuseTimeLimit; +} + +void GameStateManager::setCaldoriaSeenPullback(bool value) { + _caldoriaFlags.setFlag(kCaldoriaSeenPullbackFlag, value); +} + +bool GameStateManager::getCaldoriaSeenPullback() { + return _caldoriaFlags.getFlag(kCaldoriaSeenPullbackFlag); +} + +void GameStateManager::setCaldoriaMadeOJ(bool value) { + _caldoriaFlags.setFlag(kCaldoriaMadeOJFlag, value); +} + +bool GameStateManager::getCaldoriaMadeOJ() { + return _caldoriaFlags.getFlag(kCaldoriaMadeOJFlag); +} + +void GameStateManager::setCaldoriaWokenUp(bool value) { + _caldoriaFlags.setFlag(kCaldoriaWokenUpFlag, value); +} + +bool GameStateManager::getCaldoriaWokenUp() { + return _caldoriaFlags.getFlag(kCaldoriaWokenUpFlag); +} + +void GameStateManager::setCaldoriaDidRecalibration(bool value) { + _caldoriaFlags.setFlag(kCaldoriaDidRecalibrationFlag, value); +} + +bool GameStateManager::getCaldoriaDidRecalibration() { + return _caldoriaFlags.getFlag(kCaldoriaDidRecalibrationFlag); +} + +void GameStateManager::setCaldoriaSeenSinclairInElevator(bool value) { + _caldoriaFlags.setFlag(kCaldoriaSeenSinclairInElevatorFlag, value); +} + +bool GameStateManager::getCaldoriaSeenSinclairInElevator() { + return _caldoriaFlags.getFlag(kCaldoriaSeenSinclairInElevatorFlag); +} + +void GameStateManager::setCaldoriaINNAnnouncing(bool value) { + _caldoriaFlags.setFlag(kCaldoriaINNAnnouncingFlag, value); +} + +bool GameStateManager::getCaldoriaINNAnnouncing() { + return _caldoriaFlags.getFlag(kCaldoriaINNAnnouncingFlag); +} + +void GameStateManager::setCaldoriaSeenINN(bool value) { + _caldoriaFlags.setFlag(kCaldoriaSeenINNFlag, value); +} + +bool GameStateManager::getCaldoriaSeenINN() { + return _caldoriaFlags.getFlag(kCaldoriaSeenINNFlag); +} + +void GameStateManager::setCaldoriaSeenMessages(bool value) { + _caldoriaFlags.setFlag(kCaldoriaSeenMessagesFlag, value); +} + +bool GameStateManager::getCaldoriaSeenMessages() { + return _caldoriaFlags.getFlag(kCaldoriaSeenMessagesFlag); +} + +void GameStateManager::setCaldoriaSinclairShot(bool value) { + _caldoriaFlags.setFlag(kCaldoriaSinclairShotFlag, value); +} + +bool GameStateManager::getCaldoriaSinclairShot() { + return _caldoriaFlags.getFlag(kCaldoriaSinclairShotFlag); +} + +void GameStateManager::setCaldoriaBombDisarmed(bool value) { + _caldoriaFlags.setFlag(kCaldoriaBombDisarmedFlag, value); +} + +bool GameStateManager::getCaldoriaBombDisarmed() { + return _caldoriaFlags.getFlag(kCaldoriaBombDisarmedFlag); +} + +void GameStateManager::setCaldoriaRoofDoorOpen(bool value) { + _caldoriaFlags.setFlag(kCaldoriaRoofDoorOpenFlag, value); +} + +bool GameStateManager::getCaldoriaRoofDoorOpen() { + return _caldoriaFlags.getFlag(kCaldoriaRoofDoorOpenFlag); +} + +void GameStateManager::setCaldoriaDoneHygiene(bool value) { + _caldoriaFlags.setFlag(kCaldoriaDoneHygieneFlag, value); +} + +bool GameStateManager::getCaldoriaDoneHygiene() { + return _caldoriaFlags.getFlag(kCaldoriaDoneHygieneFlag); +} + +void GameStateManager::setCaldoriaSawVoiceAnalysis(bool value) { + _caldoriaFlags.setFlag(kCaldoriaSawVoiceAnalysisFlag, value); +} + +bool GameStateManager::getCaldoriaSawVoiceAnalysis() { + return _caldoriaFlags.getFlag(kCaldoriaSawVoiceAnalysisFlag); +} + +void GameStateManager::setCaldoriaDoorBombed(bool value) { + _caldoriaFlags.setFlag(kCaldoriaDoorBombedFlag, value); +} + +bool GameStateManager::getCaldoriaDoorBombed() { + return _caldoriaFlags.getFlag(kCaldoriaDoorBombedFlag); +} + +void GameStateManager::setCaldoriaGunAimed(bool value) { + _caldoriaFlags.setFlag(kCaldoriaGunAimedFlag, value); +} + +bool GameStateManager::getCaldoriaGunAimed() { + return _caldoriaFlags.getFlag(kCaldoriaGunAimedFlag); +} + +void GameStateManager::setRipTimerTime(TimeValue limit) { + _TSARipTimerTime = limit; +} + +TimeValue GameStateManager::getRipTimerTime() { + return _TSARipTimerTime; +} + +void GameStateManager::setTSAFuseTimeLimit(TimeValue limit) { + _TSAFuseTimeLimit = limit; +} + +TimeValue GameStateManager::getTSAFuseTimeLimit() { + return _TSAFuseTimeLimit; +} + +void GameStateManager::setTSAState(byte state) { + _TSAState = state; +} + +byte GameStateManager::getTSAState() { + return _TSAState; +} + +void GameStateManager::setT0BMonitorMode(byte mode) { + _T0BMonitorMode = mode; +} + +byte GameStateManager::getT0BMonitorMode() { + return _T0BMonitorMode; +} + +void GameStateManager::setT0BMonitorStart(TimeValue start) { + _T0BMonitorStart = start; +} + +TimeValue GameStateManager::getT0BMonitorStart() { + return _T0BMonitorStart; +} + +void GameStateManager::setTSAIDedAtDoor(bool value) { + _TSAFlags.setFlag(kTSAIDedAtDoorFlag, value); +} + +bool GameStateManager::getTSAIDedAtDoor() { + return _TSAFlags.getFlag(kTSAIDedAtDoorFlag); +} + +void GameStateManager::setTSA0BZoomedIn(bool value) { + _TSAFlags.setFlag(kTSA0BZoomedInFlag, value); +} + +bool GameStateManager::getTSA0BZoomedIn() { + return _TSAFlags.getFlag(kTSA0BZoomedInFlag); +} + +void GameStateManager::setTSAFrontDoorUnlockedOutside(bool value) { + _TSAFlags.setFlag(kTSAFrontDoorUnlockedOutsideFlag, value); +} + +bool GameStateManager::getTSAFrontDoorUnlockedOutside() { + return _TSAFlags.getFlag(kTSAFrontDoorUnlockedOutsideFlag); +} + +void GameStateManager::setTSAFrontDoorUnlockedInside(bool value) { + _TSAFlags.setFlag(kTSAFrontDoorUnlockedInsideFlag, value); +} + +bool GameStateManager::getTSAFrontDoorUnlockedInside() { + return _TSAFlags.getFlag(kTSAFrontDoorUnlockedInsideFlag); +} + +void GameStateManager::setTSASeenRobotGreeting(bool value) { + _TSAFlags.setFlag(kTSASeenRobotGreetingFlag, value); +} + +bool GameStateManager::getTSASeenRobotGreeting() { + return _TSAFlags.getFlag(kTSASeenRobotGreetingFlag); +} + +void GameStateManager::setTSASeenTheory(bool value) { + _TSAFlags.setFlag(kTSASeenTheoryFlag, value); +} + +bool GameStateManager::getTSASeenTheory() { + return _TSAFlags.getFlag(kTSASeenTheoryFlag); +} + +void GameStateManager::setTSASeenBackground(bool value) { + _TSAFlags.setFlag(kTSASeenBackgroundFlag, value); +} + +bool GameStateManager::getTSASeenBackground() { + return _TSAFlags.getFlag(kTSASeenBackgroundFlag); +} + +void GameStateManager::setTSASeenProcedure(bool value) { + _TSAFlags.setFlag(kTSASeenProcedureFlag, value); +} + +bool GameStateManager::getTSASeenProcedure() { + return _TSAFlags.getFlag(kTSASeenProcedureFlag); +} + +void GameStateManager::setTSASeenAgent3AtDoor(bool value) { + _TSAFlags.setFlag(kTSASeenAgent3AtDoorFlag, value); +} + +bool GameStateManager::getTSASeenAgent3AtDoor() { + return _TSAFlags.getFlag(kTSASeenAgent3AtDoorFlag); +} + +void GameStateManager::setTSACommandCenterLocked(bool value) { + _TSAFlags.setFlag(kTSACommandCenterLockedFlag, value); +} + +bool GameStateManager::getTSACommandCenterLocked() { + return _TSAFlags.getFlag(kTSACommandCenterLockedFlag); +} + +void GameStateManager::setTSASeenCaldoriaNormal(bool value) { + _TSAFlags.setFlag(kTSASeenCaldoriaNormalFlag, value); +} + +bool GameStateManager::getTSASeenCaldoriaNormal() { + return _TSAFlags.getFlag(kTSASeenCaldoriaNormalFlag); +} + +void GameStateManager::setTSASeenCaldoriaAltered(bool value) { + _TSAFlags.setFlag(kTSASeenCaldoriaAlteredFlag, value); +} + +bool GameStateManager::getTSASeenCaldoriaAltered() { + return _TSAFlags.getFlag(kTSASeenCaldoriaAlteredFlag); +} + +void GameStateManager::setTSASeenNoradNormal(bool value) { + _TSAFlags.setFlag(kTSASeenNoradNormalFlag, value); +} + +bool GameStateManager::getTSASeenNoradNormal() { + return _TSAFlags.getFlag(kTSASeenNoradNormalFlag); +} + +void GameStateManager::setTSASeenNoradAltered(bool value) { + _TSAFlags.setFlag(kTSASeenNoradAlteredFlag, value); +} + +bool GameStateManager::getTSASeenNoradAltered() { + return _TSAFlags.getFlag(kTSASeenNoradAlteredFlag); +} + +void GameStateManager::setTSASeenMarsNormal(bool value) { + _TSAFlags.setFlag(kTSASeenMarsNormalFlag, value); +} + +bool GameStateManager::getTSASeenMarsNormal() { + return _TSAFlags.getFlag(kTSASeenMarsNormalFlag); +} + +void GameStateManager::setTSASeenMarsAltered(bool value) { + _TSAFlags.setFlag(kTSASeenMarsAlteredFlag, value); +} + +bool GameStateManager::getTSASeenMarsAltered() { + return _TSAFlags.getFlag(kTSASeenMarsAlteredFlag); +} + +void GameStateManager::setTSASeenWSCNormal(bool value) { + _TSAFlags.setFlag(kTSASeenWSCNormalFlag, value); +} + +bool GameStateManager::getTSASeenWSCNormal() { + return _TSAFlags.getFlag(kTSASeenWSCNormalFlag); +} + +void GameStateManager::setTSASeenWSCAltered(bool value) { + _TSAFlags.setFlag(kTSASeenWSCAlteredFlag, value); +} + +bool GameStateManager::getTSASeenWSCAltered() { + return _TSAFlags.getFlag(kTSASeenWSCAlteredFlag); +} + +void GameStateManager::setTSABiosuitOn(bool value) { + _TSAFlags.setFlag(kTSABiosuitOnFlag, value); +} + +bool GameStateManager::getTSABiosuitOn() { + return _TSAFlags.getFlag(kTSABiosuitOnFlag); +} + +void GameStateManager::setPrehistoricTriedToExtendBridge(bool value) { + _prehistoricFlags.setFlag(kPrehistoricTriedToExtendBridgeFlag, value); +} + +bool GameStateManager::getPrehistoricTriedToExtendBridge() { + return _prehistoricFlags.getFlag(kPrehistoricTriedToExtendBridgeFlag); +} + +void GameStateManager::setPrehistoricSeenTimeStream(bool value) { + _prehistoricFlags.setFlag(kPrehistoricSeenTimeStreamFlag, value); +} + +bool GameStateManager::getPrehistoricSeenTimeStream() { + return _prehistoricFlags.getFlag(kPrehistoricSeenTimeStreamFlag); +} + +void GameStateManager::setPrehistoricSeenFlyer1(bool value) { + _prehistoricFlags.setFlag(kPrehistoricSeenFlyer1Flag, value); +} + +bool GameStateManager::getPrehistoricSeenFlyer1() { + return _prehistoricFlags.getFlag(kPrehistoricSeenFlyer1Flag); +} + +void GameStateManager::setPrehistoricSeenFlyer2(bool value) { + _prehistoricFlags.setFlag(kPrehistoricSeenFlyer2Flag, value); +} + +bool GameStateManager::getPrehistoricSeenFlyer2() { + return _prehistoricFlags.getFlag(kPrehistoricSeenFlyer2Flag); +} + +void GameStateManager::setPrehistoricSeenBridgeZoom(bool value) { + _prehistoricFlags.setFlag(kPrehistoricSeenBridgeZoomFlag, value); +} + +bool GameStateManager::getPrehistoricSeenBridgeZoom() { + return _prehistoricFlags.getFlag(kPrehistoricSeenBridgeZoomFlag); +} + +void GameStateManager::setPrehistoricBreakerThrown(bool value) { + _prehistoricFlags.setFlag(kPrehistoricBreakerThrownFlag, value); +} + +bool GameStateManager::getPrehistoricBreakerThrown() { + return _prehistoricFlags.getFlag(kPrehistoricBreakerThrownFlag); +} + +void GameStateManager::setNoradSeenTimeStream(bool value) { + _noradFlags.setFlag(kNoradSeenTimeStreamFlag, value); +} + +bool GameStateManager::getNoradSeenTimeStream() { + return _noradFlags.getFlag(kNoradSeenTimeStreamFlag); +} + +void GameStateManager::setNoradGassed(bool value) { + _noradFlags.setFlag(kNoradGassedFlag, value); +} + +bool GameStateManager::getNoradGassed() { + return _noradFlags.getFlag(kNoradGassedFlag); +} + +void GameStateManager::setNoradFillingStationOn(bool value) { + _noradFlags.setFlag(kNoradFillingStationOnFlag, value); +} + +bool GameStateManager::getNoradFillingStationOn() { + return _noradFlags.getFlag(kNoradFillingStationOnFlag); +} + +void GameStateManager::setNoradN22MessagePlayed(bool value) { + _noradFlags.setFlag(kNoradN22MessagePlayedFlag, value); +} + +bool GameStateManager::getNoradN22MessagePlayed() { + return _noradFlags.getFlag(kNoradN22MessagePlayedFlag); +} + +void GameStateManager::setNoradPlayedGlobeGame(bool value) { + _noradFlags.setFlag(kNoradPlayedGlobeGameFlag, value); +} + +bool GameStateManager::getNoradPlayedGlobeGame() { + return _noradFlags.getFlag(kNoradPlayedGlobeGameFlag); +} + +void GameStateManager::setNoradBeatRobotWithClaw(bool value) { + _noradFlags.setFlag(kNoradBeatRobotWithClawFlag, value); +} + +bool GameStateManager::getNoradBeatRobotWithClaw() { + return _noradFlags.getFlag(kNoradBeatRobotWithClawFlag); +} + +void GameStateManager::setNoradBeatRobotWithDoor(bool value) { + _noradFlags.setFlag(kNoradBeatRobotWithDoorFlag, value); +} + +bool GameStateManager::getNoradBeatRobotWithDoor() { + return _noradFlags.getFlag(kNoradBeatRobotWithDoorFlag); +} + +void GameStateManager::setNoradRetScanGood(bool value) { + _noradFlags.setFlag(kNoradRetScanGoodFlag, value); +} + +bool GameStateManager::getNoradRetScanGood() { + return _noradFlags.getFlag(kNoradRetScanGoodFlag); +} + +void GameStateManager::setNoradWaitingForLaser(bool value) { + _noradFlags.setFlag(kNoradWaitingForLaserFlag, value); +} + +bool GameStateManager::getNoradWaitingForLaser() { + return _noradFlags.getFlag(kNoradWaitingForLaserFlag); +} + +void GameStateManager::setNoradSubRoomPressure(uint16 pressure) { + _noradSubRoomPressure = pressure; +} + +uint16 GameStateManager::getNoradSubRoomPressure() { + return _noradSubRoomPressure; +} + +void GameStateManager::setNoradSubPrepState(NoradSubPrepState state) { + _noradSubPrepState = state; +} + +NoradSubPrepState GameStateManager::getNoradSubPrepState() { + return _noradSubPrepState; +} + +void GameStateManager::setNoradArrivedFromSub(bool value) { + _noradFlags.setFlag(kNoradArrivedFromSubFlag, value); +} + +bool GameStateManager::getNoradArrivedFromSub() { + return _noradFlags.getFlag(kNoradArrivedFromSubFlag); +} + +void GameStateManager::setMarsSeenTimeStream(bool value) { + _marsFlags.setFlag(kMarsSeenTimeStreamFlag, value); +} + +bool GameStateManager::getMarsSeenTimeStream() { + return _marsFlags.getFlag(kMarsSeenTimeStreamFlag); +} + +void GameStateManager::setMarsHeardUpperPodMessage(bool value) { + _marsFlags.setFlag(kMarsHeardUpperPodMessageFlag, value); +} + +bool GameStateManager::getMarsHeardUpperPodMessage() { + return _marsFlags.getFlag(kMarsHeardUpperPodMessageFlag); +} + +void GameStateManager::setMarsRobotThrownPlayer(bool value) { + _marsFlags.setFlag(kMarsRobotThrownPlayerFlag, value); +} + +bool GameStateManager::getMarsRobotThrownPlayer() { + return _marsFlags.getFlag(kMarsRobotThrownPlayerFlag); +} + +void GameStateManager::setMarsHeardCheckInMessage(bool value) { + _marsFlags.setFlag(kMarsHeardCheckInMessageFlag, value); +} + +bool GameStateManager::getMarsHeardCheckInMessage() { + return _marsFlags.getFlag(kMarsHeardCheckInMessageFlag); +} + +void GameStateManager::setMarsPodAtUpperPlatform(bool value) { + _marsFlags.setFlag(kMarsPodAtUpperPlatformFlag, value); +} + +bool GameStateManager::getMarsPodAtUpperPlatform() { + return _marsFlags.getFlag(kMarsPodAtUpperPlatformFlag); +} + +void GameStateManager::setMarsSeenThermalScan(bool value) { + _marsFlags.setFlag(kMarsSeenThermalScanFlag, value); +} + +bool GameStateManager::getMarsSeenThermalScan() { + return _marsFlags.getFlag(kMarsSeenThermalScanFlag); +} + +void GameStateManager::setMarsArrivedBelow(bool value) { + _marsFlags.setFlag(kMarsArrivedBelowFlag, value); +} + +bool GameStateManager::getMarsArrivedBelow() { + return _marsFlags.getFlag(kMarsArrivedBelowFlag); +} + +void GameStateManager::setMarsSeenRobotAtReactor(bool value) { + _marsFlags.setFlag(kMarsSeenRobotAtReactorFlag, value); +} + +bool GameStateManager::getMarsSeenRobotAtReactor() { + return _marsFlags.getFlag(kMarsSeenRobotAtReactorFlag); +} + +void GameStateManager::setMarsAvoidedReactorRobot(bool value) { + _marsFlags.setFlag(kMarsAvoidedReactorRobotFlag, value); +} + +bool GameStateManager::getMarsAvoidedReactorRobot() { + return _marsFlags.getFlag(kMarsAvoidedReactorRobotFlag); +} + +void GameStateManager::setMarsSecurityDown(bool value) { + _marsFlags.setFlag(kMarsSecurityDownFlag, value); +} + +bool GameStateManager::getMarsSecurityDown() { + return _marsFlags.getFlag(kMarsSecurityDownFlag); +} + +void GameStateManager::setMarsInAirlock(bool value) { + _marsFlags.setFlag(kMarsInAirlockFlag, value); +} + +bool GameStateManager::getMarsInAirlock() { + return _marsFlags.getFlag(kMarsInAirlockFlag); +} + +void GameStateManager::setMarsAirlockOpen(bool value) { + _marsFlags.setFlag(kMarsAirlockOpenFlag, value); +} + +bool GameStateManager::getMarsAirlockOpen() { + return _marsFlags.getFlag(kMarsAirlockOpenFlag); +} + +void GameStateManager::setMarsMaskOnFiller(bool value) { + _marsFlags.setFlag(kMarsMaskOnFillerFlag, value); +} + +bool GameStateManager::getMarsMaskOnFiller() { + return _marsFlags.getFlag(kMarsMaskOnFillerFlag); +} + +void GameStateManager::setMarsLockFrozen(bool value) { + _marsFlags.setFlag(kMarsLockFrozenFlag, value); +} + +bool GameStateManager::getMarsLockFrozen() { + return _marsFlags.getFlag(kMarsLockFrozenFlag); +} + +void GameStateManager::setMarsLockBroken(bool value) { + _marsFlags.setFlag(kMarsLockBrokenFlag, value); +} + +bool GameStateManager::getMarsLockBroken() { + return _marsFlags.getFlag(kMarsLockBrokenFlag); +} + +void GameStateManager::setMarsMazeDoorPair1(bool value) { + _marsFlags.setFlag(kMarsMazeDoorPair1Flag, value); +} + +bool GameStateManager::getMarsMazeDoorPair1() { + return _marsFlags.getFlag(kMarsMazeDoorPair1Flag); +} + +void GameStateManager::setMarsMazeDoorPair2(bool value) { + _marsFlags.setFlag(kMarsMazeDoorPair2Flag, value); +} + +bool GameStateManager::getMarsMazeDoorPair2() { + return _marsFlags.getFlag(kMarsMazeDoorPair2Flag); +} + +void GameStateManager::setMarsMazeDoorPair3(bool value) { + _marsFlags.setFlag(kMarsMazeDoorPair3Flag, value); +} + +bool GameStateManager::getMarsMazeDoorPair3() { + return _marsFlags.getFlag(kMarsMazeDoorPair3Flag); +} + +void GameStateManager::setMarsSawRobotLeave(bool value) { + _marsFlags.setFlag(kMarsSawRobotLeaveFlag, value); +} + +bool GameStateManager::getMarsSawRobotLeave() { + return _marsFlags.getFlag(kMarsSawRobotLeaveFlag); +} + +void GameStateManager::setMarsHitRobotWithCannon(bool flag) { + _marsFlags.setFlag(kMarsHitRobotWithCannonFlag, flag); +} + +bool GameStateManager::getMarsHitRobotWithCannon() { + return _marsFlags.getFlag(kMarsHitRobotWithCannonFlag); +} + +void GameStateManager::setMarsReadyForShuttleTransport(bool value) { + _marsFlags.setFlag(kMarsReadyForShuttleTransportFlag, value); +} + +bool GameStateManager::getMarsReadyForShuttleTransport() { + return _marsFlags.getFlag(kMarsReadyForShuttleTransportFlag); +} + +void GameStateManager::setMarsFinishedCanyonChase(bool flag) { + _marsFlags.setFlag(kMarsFinishedCanyonChaseFlag, flag); +} + +bool GameStateManager::getMarsFinishedCanyonChase() { + return _marsFlags.getFlag(kMarsFinishedCanyonChaseFlag); +} + +void GameStateManager::setMarsThreadedMaze(bool flag) { + _marsFlags.setFlag(kMarsThreadedMazeFlag, flag); +} + +bool GameStateManager::getMarsThreadedMaze() { + return _marsFlags.getFlag(kMarsThreadedMazeFlag); +} + +void GameStateManager::setWSCSeenTimeStream(bool value) { + _WSCFlags.setFlag(kWSCSeenTimeStreamFlag, value); +} + +bool GameStateManager::getWSCSeenTimeStream() { + return _WSCFlags.getFlag(kWSCSeenTimeStreamFlag); +} + +void GameStateManager::setWSCPoisoned(bool value) { + _WSCFlags.setFlag(kWSCPoisonedFlag, value); +} + +bool GameStateManager::getWSCPoisoned() { + return _WSCFlags.getFlag(kWSCPoisonedFlag); +} + +void GameStateManager::setWSCAnsweredAboutDart(bool value) { + _WSCFlags.setFlag(kWSCAnsweredAboutDartFlag, value); +} + +bool GameStateManager::getWSCAnsweredAboutDart() { + return _WSCFlags.getFlag(kWSCAnsweredAboutDartFlag); +} + +void GameStateManager::setWSCRemovedDart(bool value) { + _WSCFlags.setFlag(kWSCRemovedDartFlag, value); +} + +bool GameStateManager::getWSCRemovedDart() { + return _WSCFlags.getFlag(kWSCRemovedDartFlag); +} + +void GameStateManager::setWSCAnalyzerOn(bool value) { + _WSCFlags.setFlag(kWSCAnalyzerOnFlag, value); +} + +bool GameStateManager::getWSCAnalyzerOn() { + return _WSCFlags.getFlag(kWSCAnalyzerOnFlag); +} + +void GameStateManager::setWSCDartInAnalyzer(bool value) { + _WSCFlags.setFlag(kWSCDartInAnalyzerFlag, value); +} + +bool GameStateManager::getWSCDartInAnalyzer() { + return _WSCFlags.getFlag(kWSCDartInAnalyzerFlag); +} + +void GameStateManager::setWSCAnalyzedDart(bool value) { + _WSCFlags.setFlag(kWSCAnalyzedDartFlag, value); +} + +bool GameStateManager::getWSCAnalyzedDart() { + return _WSCFlags.getFlag(kWSCAnalyzedDartFlag); +} + +void GameStateManager::setWSCSawMorph(bool value) { + _WSCFlags.setFlag(kWSCSawMorphFlag, value); +} + +bool GameStateManager::getWSCSawMorph() { + return _WSCFlags.getFlag(kWSCSawMorphFlag); +} + +void GameStateManager::setWSCDesignedAntidote(bool value) { + _WSCFlags.setFlag(kWSCDesignedAntidoteFlag, value); +} + +bool GameStateManager::getWSCDesignedAntidote() { + return _WSCFlags.getFlag(kWSCDesignedAntidoteFlag); +} + +void GameStateManager::setWSCPickedUpAntidote(bool value) { + _WSCFlags.setFlag(kWSCPickedUpAntidoteFlag, value); +} + +bool GameStateManager::getWSCPickedUpAntidote() { + return _WSCFlags.getFlag(kWSCPickedUpAntidoteFlag); +} + +void GameStateManager::setWSCOfficeMessagesOpen(bool value) { + _WSCFlags.setFlag(kWSCOfficeMessagesOpenFlag, value); +} + +bool GameStateManager::getWSCOfficeMessagesOpen() { + return _WSCFlags.getFlag(kWSCOfficeMessagesOpenFlag); +} + +void GameStateManager::setWSCSeenNerd(bool value) { + _WSCFlags.setFlag(kWSCSeenNerdFlag, value); +} + +bool GameStateManager::getWSCSeenNerd() { + return _WSCFlags.getFlag(kWSCSeenNerdFlag); +} + +void GameStateManager::setWSCHeardPage1(bool value) { + _WSCFlags.setFlag(kWSCHeardPage1Flag, value); +} + +bool GameStateManager::getWSCHeardPage1() { + return _WSCFlags.getFlag(kWSCHeardPage1Flag); +} + +void GameStateManager::setWSCHeardPage2(bool value) { + _WSCFlags.setFlag(kWSCHeardPage2Flag, value); +} + +bool GameStateManager::getWSCHeardPage2() { + return _WSCFlags.getFlag(kWSCHeardPage2Flag); +} + +void GameStateManager::setWSCHeardCheckIn(bool value) { + _WSCFlags.setFlag(kWSCHeardCheckInFlag, value); +} + +bool GameStateManager::getWSCHeardCheckIn() { + return _WSCFlags.getFlag(kWSCHeardCheckInFlag); +} + +void GameStateManager::setWSCDidPlasmaDodge(bool value) { + _WSCFlags.setFlag(kWSCDidPlasmaDodgeFlag, value); +} + +bool GameStateManager::getWSCDidPlasmaDodge() { + return _WSCFlags.getFlag(kWSCDidPlasmaDodgeFlag); +} + +void GameStateManager::setWSCSeenSinclairLecture(bool value) { + _WSCFlags.setFlag(kWSCSeenSinclairLectureFlag, value); +} + +bool GameStateManager::getWSCSeenSinclairLecture() { + return _WSCFlags.getFlag(kWSCSeenSinclairLectureFlag); +} + +void GameStateManager::setWSCBeenAtWSC93(bool value) { + _WSCFlags.setFlag(kWSCBeenAtWSC93Flag, value); +} + +bool GameStateManager::getWSCBeenAtWSC93() { + return _WSCFlags.getFlag(kWSCBeenAtWSC93Flag); +} + +void GameStateManager::setWSCCatwalkDark(bool value) { + _WSCFlags.setFlag(kWSCCatwalkDarkFlag, value); +} + +bool GameStateManager::getWSCCatwalkDark() { + return _WSCFlags.getFlag(kWSCCatwalkDarkFlag); +} + +void GameStateManager::setWSCRobotDead(bool value) { + _WSCFlags.setFlag(kWSCRobotDeadFlag, value); +} + +bool GameStateManager::getWSCRobotDead() { + return _WSCFlags.getFlag(kWSCRobotDeadFlag); +} + +void GameStateManager::setWSCRobotGone(bool value) { + _WSCFlags.setFlag(kWSCRobotGoneFlag, value); +} + +bool GameStateManager::getWSCRobotGone() { + return _WSCFlags.getFlag(kWSCRobotGoneFlag); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/gamestate.h b/engines/pegasus/gamestate.h new file mode 100644 index 0000000000..7812003e15 --- /dev/null +++ b/engines/pegasus/gamestate.h @@ -0,0 +1,886 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_GAMESTATE_H +#define PEGASUS_GAMESTATE_H + +#include "common/singleton.h" +#include "common/util.h" + +#include "pegasus/types.h" +#include "pegasus/util.h" +#include "pegasus/items/item.h" + +namespace Common { + class Error; + class ReadStream; + class WriteStream; +} + +namespace Pegasus { + +// The only things saved in here are things which get written out to a saved game file... + +enum { + kGlobalWalkthroughFlag, + kGlobalShieldOnFlag, + kGlobalEasterEggFlag, + kGlobalBeenToWSCFlag, + kGlobalBeenToMarsFlag, + kGlobalBeenToNoradFlag, + kGlobalWSCFinishedFlag, + kGlobalMarsFinishedFlag, + kGlobalNoradFinishedFlag, + kNumGlobalFlags +}; + +enum { + kScoringSawINNFlag, + kScoringTookShowerFlag, + kScoringFixedHairFlag, + kScoringGotKeyCardFlag, + kScoringReadPaperFlag, + kScoringLookThroughTelescopeFlag, + kScoringSawCaldoriaKioskFlag, + kScoringGoToTSAFlag, + kScoringEnterTSAFlag, + kScoringSawBust1Flag, + kScoringSawBust2Flag, + kScoringSawBust3Flag, + kScoringSawBust4Flag, + kScoringSawBust5Flag, + kScoringSawBust6Flag, + kScoringSawTheoryFlag, + kScoringSawBackgroundFlag, + kScoringSawProcedureFlag, + kScoringGotJourneymanKeyFlag, + kScoringGotPegasusBiochipFlag, + kScoringGotBiosuitFlag, + kScoringGoToPrehistoricFlag, + kScoringPutLogInReaderFlag, + kScoringSawCaldoriaNormalFlag, + kScoringSawCaldoriaAlteredFlag, + kScoringSawNoradNormalFlag, + kScoringSawNoradAlteredFlag, + kScoringSawMarsNormalFlag, + kScoringSawMarsAlteredFlag, + kScoringSawWSCNormalFlag, + kScoringSawWSCAlteredFlag, + kScoringWentToReadyRoom2Flag, + kScoringWentAfterSinclairFlag, + kScoringUsedCardBombFlag, + kScoringShieldedCardBombFlag, + kScoringStunnedSinclairFlag, + kScoringDisarmedNukeFlag, + + kScoringThrewBreakerFlag, + kScoringExtendedBridgeFlag, + kScoringGotHistoricalLogFlag, + kScoringFinishedPrehistoricFlag, + + kScoringThrownByRobotFlag, + kScoringGotMarsCardFlag, + kScoringSawMarsKioskFlag, + kScoringSawTransportMapFlag, + kScoringGotCrowBarFlag, + kScoringTurnedOnTransportFlag, + kScoringGotOxygenMaskFlag, + kScoringAvoidedRobotFlag, + kScoringActivatedPlatformFlag, + kScoringUsedLiquidNitrogenFlag, + kScoringUsedCrowBarFlag, + kScoringFoundCardBombFlag, + kScoringDisarmedCardBombFlag, + kScoringGotCardBombFlag, + kScoringThreadedMazeFlag, + kScoringThreadedGearRoomFlag, + kScoringEnteredShuttleFlag, + kScoringEnteredLaunchTubeFlag, + kScoringStoppedRobotsShuttleFlag, + kScoringGotMarsOpMemChipFlag, + kScoringFinishedMarsFlag, + + kScoringSawSecurityMonitorFlag, + kScoringFilledOxygenCanisterFlag, + kScoringFilledArgonCanisterFlag, + kScoringSawUnconsciousOperatorFlag, + kScoringWentThroughPressureDoorFlag, + kScoringPreppedSubFlag, + kScoringEnteredSubFlag, + kScoringExitedSubFlag, + kScoringSawRobotAt54NorthFlag, + kScoringPlayedWithClawFlag, + kScoringUsedRetinalChipFlag, + kScoringFinishedGlobeGameFlag, + kScoringStoppedNoradRobotFlag, + kScoringGotNoradOpMemChipFlag, + kScoringFinishedNoradFlag, + + kScoringRemovedDartFlag, + kScoringAnalyzedDartFlag, + kScoringBuiltAntidoteFlag, + kScoringGotSinclairKeyFlag, + kScoringGotArgonCanisterFlag, + kScoringGotNitrogenCanisterFlag, + kScoringPlayedWithMessagesFlag, + kScoringSawMorphExperimentFlag, + kScoringEnteredSinclairOfficeFlag, + kScoringSawBrochureFlag, + kScoringSawSinclairEntry1Flag, + kScoringSawSinclairEntry2Flag, + kScoringSawSinclairEntry3Flag, + kScoringSawWSCDirectoryFlag, + kScoringUsedCrowBarInWSCFlag, + kScoringFinishedPlasmaDodgeFlag, + kScoringOpenedCatwalkFlag, + kScoringStoppedWSCRobotFlag, + kScoringGotWSCOpMemChipFlag, + kScoringFinishedWSCFlag, + + kScoringMarsGandhiFlag, + kScoringNoradGandhiFlag, + kScoringWSCGandhiFlag, + + kNumScoringFlags +}; + +enum { + kCaldoriaSeenPullbackFlag, + kCaldoriaMadeOJFlag, + kCaldoriaWokenUpFlag, + kCaldoriaDidRecalibrationFlag, + kCaldoriaSeenSinclairInElevatorFlag, + kCaldoriaINNAnnouncingFlag, + kCaldoriaSeenINNFlag, + kCaldoriaSeenMessagesFlag, + kCaldoriaSinclairShotFlag, + kCaldoriaBombDisarmedFlag, + kCaldoriaRoofDoorOpenFlag, + kCaldoriaDoneHygieneFlag, + kCaldoriaSawVoiceAnalysisFlag, + kCaldoriaDoorBombedFlag, + kCaldoriaGunAimedFlag, + kNumCaldoriaFlags +}; + +enum { + kCaldoriaNoFuseRunning, + kCaldoriaDoorBombFuseRunning, + kCaldoriaSinclairFuseRunning +}; + +enum { + kTSAIDedAtDoorFlag, + kTSA0BZoomedInFlag, + kTSAFrontDoorUnlockedOutsideFlag, + kTSAFrontDoorUnlockedInsideFlag, + kTSASeenRobotGreetingFlag, + kTSASeenTheoryFlag, + kTSASeenBackgroundFlag, + kTSASeenProcedureFlag, + kTSASeenAgent3AtDoorFlag, + kTSACommandCenterLockedFlag, + kTSASeenCaldoriaNormalFlag, + kTSASeenCaldoriaAlteredFlag, + kTSASeenNoradNormalFlag, + kTSASeenNoradAlteredFlag, + kTSASeenMarsNormalFlag, + kTSASeenMarsAlteredFlag, + kTSASeenWSCNormalFlag, + kTSASeenWSCAlteredFlag, + kTSABiosuitOnFlag, + kNumTSAFlags +}; + +enum { + kPrehistoricTriedToExtendBridgeFlag, + kPrehistoricSeenTimeStreamFlag, + kPrehistoricSeenFlyer1Flag, + kPrehistoricSeenFlyer2Flag, + kPrehistoricSeenBridgeZoomFlag, + kPrehistoricBreakerThrownFlag, + kNumPrehistoricFlags +}; + +enum { + kNoradSeenTimeStreamFlag, + kNoradGassedFlag, + kNoradFillingStationOnFlag, + kNoradN22MessagePlayedFlag, + kNoradArrivedFromSubFlag, + kNoradWaitingForLaserFlag, + kNoradRetScanGoodFlag, + kNoradPlayedGlobeGameFlag, + kNoradBeatRobotWithClawFlag, + kNoradBeatRobotWithDoorFlag, + kNumNoradFlags +}; + +enum { + kMarsSeenTimeStreamFlag, + kMarsHeardUpperPodMessageFlag, + kMarsRobotThrownPlayerFlag, + kMarsHeardCheckInMessageFlag, + kMarsPodAtUpperPlatformFlag, + kMarsSeenThermalScanFlag, + kMarsArrivedBelowFlag, + kMarsSeenRobotAtReactorFlag, + kMarsAvoidedReactorRobotFlag, + kMarsInAirlockFlag, + kMarsAirlockOpenFlag, + kMarsMaskOnFillerFlag, + kMarsLockFrozenFlag, + kMarsLockBrokenFlag, + kMarsMazeDoorPair1Flag, + kMarsMazeDoorPair2Flag, + kMarsMazeDoorPair3Flag, + kMarsSawRobotLeaveFlag, + kMarsSecurityDownFlag, + kMarsHitRobotWithCannonFlag, + kMarsReadyForShuttleTransportFlag, + kMarsFinishedCanyonChaseFlag, + kMarsThreadedMazeFlag, + kNumMarsFlags +}; + +enum { + kWSCSeenTimeStreamFlag, + kWSCPoisonedFlag, + kWSCAnsweredAboutDartFlag, + kWSCRemovedDartFlag, + kWSCAnalyzerOnFlag, + kWSCDartInAnalyzerFlag, + kWSCAnalyzedDartFlag, + kWSCSawMorphFlag, + kWSCDesignedAntidoteFlag, + kWSCPickedUpAntidoteFlag, + kWSCOfficeMessagesOpenFlag, + kWSCSeenNerdFlag, + kWSCHeardPage1Flag, + kWSCHeardPage2Flag, + kWSCHeardCheckInFlag, + kWSCDidPlasmaDodgeFlag, + kWSCSeenSinclairLectureFlag, + kWSCBeenAtWSC93Flag, + kWSCCatwalkDarkFlag, + kWSCRobotDeadFlag, + kWSCRobotGoneFlag, + kNumWSCFlags +}; + +class GameStateManager : public Common::Singleton<GameStateManager> { +public: + GameStateManager() { resetGameState(); } + + // Base game state + Common::Error writeGameState(Common::WriteStream *stream); + Common::Error readGameState(Common::ReadStream *stream); + + void resetGameState(); + + void getCurrentLocation(NeighborhoodID &neighborhood, RoomID &room, DirectionConstant &direction); + void setCurrentLocation(const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction); + + NeighborhoodID getCurrentNeighborhood(); + void setCurrentNeighborhood(const NeighborhoodID neighborhood); + RoomID getCurrentRoom(); + void setCurrentRoom(const RoomID room); + DirectionConstant getCurrentDirection(); + void setCurrentDirection(const DirectionConstant direction); + + RoomViewID getCurrentRoomAndView(); + + void getNextLocation(NeighborhoodID &neighborhood, RoomID &room, DirectionConstant &direction); + void setNextLocation(const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction); + + NeighborhoodID getNextNeighborhood(); + void setNextNeighborhood(const NeighborhoodID neighborhood); + RoomID getNextRoom(); + void setNextRoom(const RoomID room); + DirectionConstant getNextDirection(); + void setNextDirection(const DirectionConstant direction); + + void getLastLocation(NeighborhoodID &neighborhood, RoomID &room, DirectionConstant &direction); + void setLastLocation(const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction); + + NeighborhoodID getLastNeighborhood(); + void setLastNeighborhood(const NeighborhoodID neighborhood); + RoomID getLastRoom(); + void setLastRoom(const RoomID room); + DirectionConstant getLastDirection(); + void setLastDirection(const DirectionConstant direction); + + RoomViewID getLastRoomAndView(); + + void getOpenDoorLocation(RoomID &room, DirectionConstant &direction); + void setOpenDoorLocation(const RoomID room, const DirectionConstant direction); + RoomID getOpenDoorRoom(); + void setOpenDoorRoom(const RoomID room); + DirectionConstant getOpenDoorDirection(); + void setOpenDoorDirection(const DirectionConstant direction); + + RoomViewID getDoorOpenRoomAndView(); + + bool isCurrentDoorOpen(); + + // Pegasus Prime + + // Scoring... + // Scoring "Set" functions. + // Caldoria/TSA scoring + void setScoringSawINN(const bool = true); + void setScoringTookShower(const bool = true); + void setScoringFixedHair(const bool = true); + void setScoringGotKeyCard(const bool = true); + void setScoringReadPaper(const bool = true); + void setScoringLookThroughTelescope(const bool = true); + void setScoringSawCaldoriaKiosk(const bool = true); + void setScoringGoToTSA(const bool = true); + void setScoringEnterTSA(const bool = true); + void setScoringSawBust1(const bool = true); + void setScoringSawBust2(const bool = true); + void setScoringSawBust3(const bool = true); + void setScoringSawBust4(const bool = true); + void setScoringSawBust5(const bool = true); + void setScoringSawBust6(const bool = true); + void setScoringSawTheory(const bool = true); + void setScoringSawBackground(const bool = true); + void setScoringSawProcedure(const bool = true); + void setScoringGotJourneymanKey(const bool = true); + void setScoringGotPegasusBiochip(const bool = true); + void setScoringGotBiosuit(const bool = true); + void setScoringGoToPrehistoric(const bool = true); + void setScoringPutLogInReader(const bool = true); + void setScoringSawCaldoriaNormal(const bool = true); + void setScoringSawCaldoriaAltered(const bool = true); + void setScoringSawNoradNormal(const bool = true); + void setScoringSawNoradAltered(const bool = true); + void setScoringSawMarsNormal(const bool = true); + void setScoringSawMarsAltered(const bool = true); + void setScoringSawWSCNormal(const bool = true); + void setScoringSawWSCAltered(const bool = true); + void setScoringWentToReadyRoom2(const bool = true); + void setScoringWentAfterSinclair(const bool = true); + void setScoringUsedCardBomb(const bool = true); + void setScoringShieldedCardBomb(const bool = true); + void setScoringStunnedSinclair(const bool = true); + void setScoringDisarmedNuke(const bool = true); + + // Prehistoric scoring + void setScoringThrewBreaker(const bool = true); + void setScoringExtendedBridge(const bool = true); + void setScoringGotHistoricalLog(const bool = true); + void setScoringFinishedPrehistoric(const bool = true); + + // Mars scoring + void setScoringThrownByRobot(const bool = true); + void setScoringGotMarsCard(const bool = true); + void setScoringSawMarsKiosk(const bool = true); + void setScoringSawTransportMap(const bool = true); + void setScoringGotCrowBar(const bool = true); + void setScoringTurnedOnTransport(const bool = true); + void setScoringGotOxygenMask(const bool = true); + void setScoringAvoidedRobot(const bool = true); + void setScoringActivatedPlatform(const bool = true); + void setScoringUsedLiquidNitrogen(const bool = true); + void setScoringUsedCrowBar(const bool = true); + void setScoringFoundCardBomb(const bool = true); + void setScoringDisarmedCardBomb(const bool = true); + void setScoringGotCardBomb(const bool = true); + void setScoringThreadedMaze(const bool = true); + void setScoringThreadedGearRoom(const bool = true); + void setScoringEnteredShuttle(const bool = true); + void setScoringEnteredLaunchTube(const bool = true); + void setScoringStoppedRobotsShuttle(const bool = true); + void setScoringGotMarsOpMemChip(const bool = true); + void setScoringFinishedMars(const bool = true); + + // Norad scoring + void setScoringSawSecurityMonitor(const bool = true); + void setScoringFilledOxygenCanister(const bool = true); + void setScoringFilledArgonCanister(const bool = true); + void setScoringSawUnconsciousOperator(const bool = true); + void setScoringWentThroughPressureDoor(const bool = true); + void setScoringPreppedSub(const bool = true); + void setScoringEnteredSub(const bool = true); + void setScoringExitedSub(const bool = true); + void setScoringSawRobotAt54North(const bool = true); + void setScoringPlayedWithClaw(const bool = true); + void setScoringUsedRetinalChip(const bool = true); + void setScoringFinishedGlobeGame(const bool = true); + void setScoringStoppedNoradRobot(const bool = true); + void setScoringGotNoradOpMemChip(const bool = true); + void setScoringFinishedNorad(const bool = true); + + // WSC scoring + void setScoringRemovedDart(const bool = true); + void setScoringAnalyzedDart(const bool = true); + void setScoringBuiltAntidote(const bool = true); + void setScoringGotSinclairKey(const bool = true); + void setScoringGotArgonCanister(const bool = true); + void setScoringGotNitrogenCanister(const bool = true); + void setScoringPlayedWithMessages(const bool = true); + void setScoringSawMorphExperiment(const bool = true); + void setScoringEnteredSinclairOffice(const bool = true); + void setScoringSawBrochure(const bool = true); + void setScoringSawSinclairEntry1(const bool = true); + void setScoringSawSinclairEntry2(const bool = true); + void setScoringSawSinclairEntry3(const bool = true); + void setScoringSawWSCDirectory(const bool = true); + void setScoringUsedCrowBarInWSC(const bool = true); + void setScoringFinishedPlasmaDodge(const bool = true); + void setScoringOpenedCatwalk(const bool = true); + void setScoringStoppedWSCRobot(const bool = true); + void setScoringGotWSCOpMemChip(const bool = true); + void setScoringFinishedWSC(const bool = true); + + // Gandhi scoring + void setScoringMarsGandhi(const bool = true); + void setScoringNoradGandhi(const bool = true); + void setScoringWSCGandhi(const bool = true); + + // Scoring "Get" functions. + bool getScoringSawINN(); + bool getScoringTookShower(); + bool getScoringFixedHair(); + bool getScoringGotKeyCard(); + bool getScoringReadPaper(); + bool getScoringLookThroughTelescope(); + bool getScoringSawCaldoriaKiosk(); + bool getScoringGoToTSA(); + bool getScoringEnterTSA(); + bool getScoringSawBust1(); + bool getScoringSawBust2(); + bool getScoringSawBust3(); + bool getScoringSawBust4(); + bool getScoringSawBust5(); + bool getScoringSawBust6(); + bool getScoringSawTheory(); + bool getScoringSawBackground(); + bool getScoringSawProcedure(); + bool getScoringGotJourneymanKey(); + bool getScoringGotPegasusBiochip(); + bool getScoringGotBiosuit(); + bool getScoringGoToPrehistoric(); + bool getScoringPutLogInReader(); + bool getScoringSawCaldoriaNormal(); + bool getScoringSawCaldoriaAltered(); + bool getScoringSawNoradNormal(); + bool getScoringSawNoradAltered(); + bool getScoringSawMarsNormal(); + bool getScoringSawMarsAltered(); + bool getScoringSawWSCNormal(); + bool getScoringSawWSCAltered(); + bool getScoringWentToReadyRoom2(); + bool getScoringWentAfterSinclair(); + bool getScoringUsedCardBomb(); + bool getScoringShieldedCardBomb(); + bool getScoringStunnedSinclair(); + bool getScoringDisarmedNuke(); + bool getScoringThrewBreaker(); + bool getScoringExtendedBridge(); + bool getScoringGotHistoricalLog(); + bool getScoringFinishedPrehistoric(); + bool getScoringThrownByRobot(); + bool getScoringGotMarsCard(); + bool getScoringSawMarsKiosk(); + bool getScoringSawTransportMap(); + bool getScoringGotCrowBar(); + bool getScoringTurnedOnTransport(); + bool getScoringGotOxygenMask(); + bool getScoringAvoidedRobot(); + bool getScoringActivatedPlatform(); + bool getScoringUsedLiquidNitrogen(); + bool getScoringUsedCrowBar(); + bool getScoringFoundCardBomb(); + bool getScoringDisarmedCardBomb(); + bool getScoringGotCardBomb(); + bool getScoringThreadedMaze(); + bool getScoringThreadedGearRoom(); + bool getScoringEnteredShuttle(); + bool getScoringEnteredLaunchTube(); + bool getScoringStoppedRobotsShuttle(); + bool getScoringGotMarsOpMemChip(); + bool getScoringFinishedMars(); + bool getScoringSawSecurityMonitor(); + bool getScoringFilledOxygenCanister(); + bool getScoringFilledArgonCanister(); + bool getScoringSawUnconsciousOperator(); + bool getScoringWentThroughPressureDoor(); + bool getScoringPreppedSub(); + bool getScoringEnteredSub(); + bool getScoringExitedSub(); + bool getScoringSawRobotAt54North(); + bool getScoringPlayedWithClaw(); + bool getScoringUsedRetinalChip(); + bool getScoringFinishedGlobeGame(); + bool getScoringStoppedNoradRobot(); + bool getScoringGotNoradOpMemChip(); + bool getScoringFinishedNorad(); + bool getScoringRemovedDart(); + bool getScoringAnalyzedDart(); + bool getScoringBuiltAntidote(); + bool getScoringGotSinclairKey(); + bool getScoringGotArgonCanister(); + bool getScoringGotNitrogenCanister(); + bool getScoringPlayedWithMessages(); + bool getScoringSawMorphExperiment(); + bool getScoringEnteredSinclairOffice(); + bool getScoringSawBrochure(); + bool getScoringSawSinclairEntry1(); + bool getScoringSawSinclairEntry2(); + bool getScoringSawSinclairEntry3(); + bool getScoringSawWSCDirectory(); + bool getScoringUsedCrowBarInWSC(); + bool getScoringFinishedPlasmaDodge(); + bool getScoringOpenedCatwalk(); + bool getScoringStoppedWSCRobot(); + bool getScoringGotWSCOpMemChip(); + bool getScoringFinishedWSC(); + bool getScoringMarsGandhi(); + bool getScoringNoradGandhi(); + bool getScoringWSCGandhi(); + + GameScoreType getCaldoriaTSAScore(); + GameScoreType getPrehistoricScore(); + GameScoreType getMarsScore(); + GameScoreType getNoradScore(); + GameScoreType getWSCScore(); + GameScoreType getGandhiScore(); + GameScoreType getTotalScore(); + + void writeCaldoriaState(Common::WriteStream *stream); + void readCaldoriaState(Common::ReadStream *stream); + void resetCaldoriaState(); + + void writeTSAState(Common::WriteStream *stream); + void readTSAState(Common::ReadStream *stream); + void resetTSAState(); + + void writePrehistoricState(Common::WriteStream *stream); + void readPrehistoricState(Common::ReadStream *stream); + void resetPrehistoricState(); + + void writeNoradState(Common::WriteStream *stream); + void readNoradState(Common::ReadStream *stream); + void resetNoradState(); + + void writeMarsState(Common::WriteStream *stream); + void readMarsState(Common::ReadStream *stream); + void resetMarsState(); + + void writeWSCState(Common::WriteStream *stream); + void readWSCState(Common::ReadStream *stream); + void resetWSCState(); + + // Globals. + void setWalkthroughMode(bool); + bool getWalkthroughMode(); + void setShieldOn(bool); + bool getShieldOn(); + void setEasterEgg(bool); + bool getEasterEgg(); + void setBeenToWSC(bool value); + bool getBeenToWSC(); + void setBeenToMars(bool value); + bool getBeenToMars(); + void setBeenToNorad(bool value); + bool getBeenToNorad(); + void setWSCFinished(bool); + bool getWSCFinished(); + void setMarsFinished(bool); + bool getMarsFinished(); + void setNoradFinished(bool); + bool getNoradFinished(); + bool allTimeZonesFinished(); + void setTakenItemID(ItemID, bool); + bool isTakenItemID(ItemID); + void setTakenItem(Item *, bool); + bool isTakenItem(Item *); + + // Caldoria + void setCaldoriaFuseTimeLimit(const TimeValue); + TimeValue getCaldoriaFuseTimeLimit(); + void setCaldoriaSeenPullback(bool); + bool getCaldoriaSeenPullback(); + void setCaldoriaMadeOJ(bool); + bool getCaldoriaMadeOJ(); + void setCaldoriaWokenUp(bool); + bool getCaldoriaWokenUp(); + void setCaldoriaDidRecalibration(bool); + bool getCaldoriaDidRecalibration(); + void setCaldoriaSeenSinclairInElevator(bool); + bool getCaldoriaSeenSinclairInElevator(); + void setCaldoriaINNAnnouncing(bool); + bool getCaldoriaINNAnnouncing(); + void setCaldoriaSeenINN(bool); + bool getCaldoriaSeenINN(); + void setCaldoriaSeenMessages(bool); + bool getCaldoriaSeenMessages(); + void setCaldoriaSinclairShot(bool); + bool getCaldoriaSinclairShot(); + void setCaldoriaBombDisarmed(bool); + bool getCaldoriaBombDisarmed(); + void setCaldoriaRoofDoorOpen(bool); + bool getCaldoriaRoofDoorOpen(); + void setCaldoriaDoneHygiene(bool); + bool getCaldoriaDoneHygiene(); + void setCaldoriaSawVoiceAnalysis(bool); + bool getCaldoriaSawVoiceAnalysis(); + void setCaldoriaDoorBombed(bool); + bool getCaldoriaDoorBombed(); + void setCaldoriaGunAimed(bool); + bool getCaldoriaGunAimed(); + + // TSA + void setRipTimerTime(TimeValue); + TimeValue getRipTimerTime(); + void setTSAFuseTimeLimit(TimeValue); + TimeValue getTSAFuseTimeLimit(); + void setT0BMonitorMode(byte); + byte getT0BMonitorMode(); + void setTSAState(byte); + byte getTSAState(); + void setT0BMonitorStart(TimeValue); + TimeValue getT0BMonitorStart(); + void setTSAIDedAtDoor(bool); + bool getTSAIDedAtDoor(); + void setTSA0BZoomedIn(bool); + bool getTSA0BZoomedIn(); + void setTSAFrontDoorUnlockedOutside(bool); + bool getTSAFrontDoorUnlockedOutside(); + void setTSAFrontDoorUnlockedInside(bool); + bool getTSAFrontDoorUnlockedInside(); + void setTSASeenRobotGreeting(bool); + bool getTSASeenRobotGreeting(); + void setTSASeenTheory(bool); + bool getTSASeenTheory(); + void setTSASeenBackground(bool); + bool getTSASeenBackground(); + void setTSASeenProcedure(bool); + bool getTSASeenProcedure(); + void setTSASeenAgent3AtDoor(bool); + bool getTSASeenAgent3AtDoor(); + void setTSACommandCenterLocked(bool); + bool getTSACommandCenterLocked(); + void setTSASeenCaldoriaNormal(bool); + bool getTSASeenCaldoriaNormal(); + void setTSASeenCaldoriaAltered(bool); + bool getTSASeenCaldoriaAltered(); + void setTSASeenNoradNormal(bool); + bool getTSASeenNoradNormal(); + void setTSASeenNoradAltered(bool); + bool getTSASeenNoradAltered(); + void setTSASeenMarsNormal(bool); + bool getTSASeenMarsNormal(); + void setTSASeenMarsAltered(bool); + bool getTSASeenMarsAltered(); + void setTSASeenWSCNormal(bool); + bool getTSASeenWSCNormal(); + void setTSASeenWSCAltered(bool); + bool getTSASeenWSCAltered(); + void setTSABiosuitOn(bool); + bool getTSABiosuitOn(); + + // Prehistoric + void setPrehistoricTriedToExtendBridge(bool); + bool getPrehistoricTriedToExtendBridge(); + void setPrehistoricSeenTimeStream(bool); + bool getPrehistoricSeenTimeStream(); + void setPrehistoricSeenFlyer1(bool); + bool getPrehistoricSeenFlyer1(); + void setPrehistoricSeenFlyer2(bool); + bool getPrehistoricSeenFlyer2(); + void setPrehistoricSeenBridgeZoom(bool); + bool getPrehistoricSeenBridgeZoom(); + void setPrehistoricBreakerThrown(bool); + bool getPrehistoricBreakerThrown(); + + // Norad + void setNoradSeenTimeStream(bool); + bool getNoradSeenTimeStream(); + void setNoradGassed(bool); + bool getNoradGassed(); + void setNoradFillingStationOn(bool); + bool getNoradFillingStationOn(); + void setNoradN22MessagePlayed(bool); + bool getNoradN22MessagePlayed(); + void setNoradPlayedGlobeGame(bool); + bool getNoradPlayedGlobeGame(); + void setNoradBeatRobotWithClaw(bool); + bool getNoradBeatRobotWithClaw(); + void setNoradBeatRobotWithDoor(bool); + bool getNoradBeatRobotWithDoor(); + void setNoradRetScanGood(bool); + bool getNoradRetScanGood(); + void setNoradWaitingForLaser(bool); + bool getNoradWaitingForLaser(); + void setNoradSubRoomPressure(uint16); + uint16 getNoradSubRoomPressure(); + void setNoradSubPrepState(NoradSubPrepState); + NoradSubPrepState getNoradSubPrepState(); + void setNoradArrivedFromSub(bool); + bool getNoradArrivedFromSub(); + + // Mars + void setMarsSeenTimeStream(bool); + bool getMarsSeenTimeStream(); + void setMarsHeardUpperPodMessage(bool); + bool getMarsHeardUpperPodMessage(); + void setMarsRobotThrownPlayer(bool); + bool getMarsRobotThrownPlayer(); + void setMarsHeardCheckInMessage(bool); + bool getMarsHeardCheckInMessage(); + void setMarsPodAtUpperPlatform(bool); + bool getMarsPodAtUpperPlatform(); + void setMarsSeenThermalScan(bool); + bool getMarsSeenThermalScan(); + void setMarsArrivedBelow(bool); + bool getMarsArrivedBelow(); + void setMarsSeenRobotAtReactor(bool); + bool getMarsSeenRobotAtReactor(); + void setMarsAvoidedReactorRobot(bool); + bool getMarsAvoidedReactorRobot(); + void setMarsInAirlock(bool); + bool getMarsInAirlock(); + void setMarsAirlockOpen(bool); + bool getMarsAirlockOpen(); + void setMarsMaskOnFiller(bool); + bool getMarsMaskOnFiller(); + void setMarsLockFrozen(bool); + bool getMarsLockFrozen(); + void setMarsLockBroken(bool); + bool getMarsLockBroken(); + void setMarsMazeDoorPair1(bool); + bool getMarsMazeDoorPair1(); + void setMarsMazeDoorPair2(bool); + bool getMarsMazeDoorPair2(); + void setMarsMazeDoorPair3(bool); + bool getMarsMazeDoorPair3(); + void setMarsSawRobotLeave(bool); + bool getMarsSawRobotLeave(); + void setMarsSecurityDown(bool); + bool getMarsSecurityDown(); + void setMarsFinishedCanyonChase(bool); + bool getMarsFinishedCanyonChase(); + void setMarsThreadedMaze(bool); + bool getMarsThreadedMaze(); + void setMarsHitRobotWithCannon(bool); + bool getMarsHitRobotWithCannon(); + void setMarsReadyForShuttleTransport(bool); + bool getMarsReadyForShuttleTransport(); + + // WSC + void setWSCSeenTimeStream(bool); + bool getWSCSeenTimeStream(); + void setWSCPoisoned(bool); + bool getWSCPoisoned(); + void setWSCAnsweredAboutDart(bool); + bool getWSCAnsweredAboutDart(); + void setWSCRemovedDart(bool); + bool getWSCRemovedDart(); + void setWSCAnalyzerOn(bool); + bool getWSCAnalyzerOn(); + void setWSCDartInAnalyzer(bool); + bool getWSCDartInAnalyzer(); + void setWSCAnalyzedDart(bool); + bool getWSCAnalyzedDart(); + void setWSCSawMorph(bool); + bool getWSCSawMorph(); + void setWSCDesignedAntidote(bool); + bool getWSCDesignedAntidote(); + void setWSCPickedUpAntidote(bool); + bool getWSCPickedUpAntidote(); + void setWSCOfficeMessagesOpen(bool); + bool getWSCOfficeMessagesOpen(); + void setWSCSeenNerd(bool); + bool getWSCSeenNerd(); + void setWSCHeardPage1(bool); + bool getWSCHeardPage1(); + void setWSCHeardPage2(bool); + bool getWSCHeardPage2(); + void setWSCHeardCheckIn(bool); + bool getWSCHeardCheckIn(); + void setWSCDidPlasmaDodge(bool); + bool getWSCDidPlasmaDodge(); + void setWSCSeenSinclairLecture(bool); + bool getWSCSeenSinclairLecture(); + void setWSCBeenAtWSC93(bool); + bool getWSCBeenAtWSC93(); + void setWSCCatwalkDark(bool); + bool getWSCCatwalkDark(); + void setWSCRobotDead(bool); + bool getWSCRobotDead(); + void setWSCRobotGone(bool); + bool getWSCRobotGone(); + +protected: + friend class Common::Singleton<SingletonBaseType>; + +private: + // Base + NeighborhoodID _currentNeighborhood; + RoomID _currentRoom; + DirectionConstant _currentDirection; + NeighborhoodID _nexNeighborhoodID; + RoomID _nextRoomID; + DirectionConstant _nextDirection; + NeighborhoodID _lastNeighborhood; + RoomID _lastRoom; + DirectionConstant _lastDirection; + RoomID _openDoorRoom; + DirectionConstant _openDoorDirection; + + // Pegasus Prime + FlagsArray<byte, kNumGlobalFlags> _globalFlags; + FlagsArray<byte, kNumScoringFlags> _scoringFlags; + FlagsArray<uint32, kNumItems> _itemTakenFlags; + + FlagsArray<byte, kNumCaldoriaFlags> _caldoriaFlags; + TimeValue _caldoriaFuseTimeLimit; + + TimeValue _TSARipTimerTime; + TimeValue _TSAFuseTimeLimit; + byte _TSAState; + byte _T0BMonitorMode; + TimeValue _T0BMonitorStart; + FlagsArray<byte, kNumTSAFlags> _TSAFlags; + + FlagsArray<byte, kNumPrehistoricFlags> _prehistoricFlags; + + FlagsArray<byte, kNumNoradFlags> _noradFlags; + uint16 _noradSubRoomPressure; + NoradSubPrepState _noradSubPrepState; + + FlagsArray<byte, kNumMarsFlags> _marsFlags; + + FlagsArray<byte, kNumWSCFlags> _WSCFlags; +}; + +} // End of namespace Pegasus + +#define GameState (::Pegasus::GameStateManager::instance()) + +#endif diff --git a/engines/pegasus/graphics.cpp b/engines/pegasus/graphics.cpp new file mode 100644 index 0000000000..175f15e91d --- /dev/null +++ b/engines/pegasus/graphics.cpp @@ -0,0 +1,337 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/events.h" +#include "common/file.h" +#include "common/textconsole.h" +#include "engines/util.h" + +#include "pegasus/elements.h" +#include "pegasus/graphics.h" + +namespace Pegasus { + +GraphicsManager::GraphicsManager(PegasusEngine *vm) : _vm(vm) { + initGraphics(640, 480, true, NULL); + + if (_vm->_system->getScreenFormat().bytesPerPixel == 1) + error("No true color mode available"); + + _backLayer = kMinAvailableOrder; + _frontLayer = kMaxAvailableOrder; + _firstDisplayElement = _lastDisplayElement = 0; + _workArea.create(640, 480, _vm->_system->getScreenFormat()); + _modifiedScreen = false; + _curSurface = &_workArea; + _erase = false; +} + +GraphicsManager::~GraphicsManager() { + _workArea.free(); +} + +void GraphicsManager::invalRect(const Common::Rect &rect) { + // We're using a simpler algorithm for dirty rect handling than the original + // The original was way too overcomplicated for what we need here now. + + if (_dirtyRect.width() == 0 || _dirtyRect.height() == 0) { + // We have no dirty rect, so this is now our dirty rect + _dirtyRect = rect; + } else { + // Expand our dirty rect to include rect + _dirtyRect.extend(rect); + } + + // Sanity check: clip our rect to the screen + _dirtyRect.right = MIN<int>(640, _dirtyRect.right); + _dirtyRect.bottom = MIN<int>(480, _dirtyRect.bottom); +} + +void GraphicsManager::addDisplayElement(DisplayElement *newElement) { + newElement->_elementOrder = CLIP<int>(newElement->_elementOrder, kMinAvailableOrder, kMaxAvailableOrder); + + if (_firstDisplayElement) { + DisplayElement *runner = _firstDisplayElement; + DisplayElement *lastRunner = 0; + + // Search for first element whose display order is greater than + // the new element's and add the new element just before it. + while (runner) { + if (newElement->_elementOrder < runner->_elementOrder) { + if (lastRunner) { + lastRunner->_nextElement = newElement; + newElement->_nextElement = runner; + } else { + newElement->_nextElement = _firstDisplayElement; + _firstDisplayElement = newElement; + } + break; + } + lastRunner = runner; + runner = runner->_nextElement; + } + + // If got here and runner == NULL, we ran through the whole list without + // inserting, so add at the end. + if (!runner) { + _lastDisplayElement->_nextElement = newElement; + _lastDisplayElement = newElement; + } + } else { + _firstDisplayElement = newElement; + _lastDisplayElement = newElement; + } + + newElement->_elementIsDisplaying = true; +} + +void GraphicsManager::removeDisplayElement(DisplayElement *oldElement) { + if (!_firstDisplayElement) + return; + + if (oldElement == _firstDisplayElement) { + if (oldElement == _lastDisplayElement) { + _firstDisplayElement = 0; + _lastDisplayElement = 0; + } else { + _firstDisplayElement = oldElement->_nextElement; + } + + invalRect(oldElement->_bounds); + } else { + // Scan list for element. + // If we get here, we know that the list has at least one item, and it + // is not the first item, so we can skip it. + DisplayElement *runner = _firstDisplayElement->_nextElement; + DisplayElement *lastRunner = _firstDisplayElement; + + while (runner) { + if (runner == oldElement) { + lastRunner->_nextElement = runner->_nextElement; + + if (oldElement == _lastDisplayElement) + _lastDisplayElement = lastRunner; + + invalRect(oldElement->_bounds); + break; + } + + lastRunner = runner; + runner = runner->_nextElement; + } + } + + oldElement->_nextElement = 0; + oldElement->_elementIsDisplaying = false; +} + +void GraphicsManager::updateDisplay() { + bool screenDirty = false; + + if (!_dirtyRect.isEmpty()) { + // Fill the dirty area with black if erase mode is enabled + if (_erase) + _workArea.fillRect(_dirtyRect, _workArea.format.RGBToColor(0, 0, 0)); + + for (DisplayElement *runner = _firstDisplayElement; runner != 0; runner = runner->_nextElement) { + Common::Rect bounds; + runner->getBounds(bounds); + + // TODO: Better logic; it does a bit more work than it probably needs to + // but it should work fine for now. + if (bounds.intersects(_dirtyRect) && runner->validToDraw(_backLayer, _frontLayer)) { + runner->draw(bounds); + screenDirty = true; + } + } + + // Copy only the dirty rect to the screen + if (screenDirty) + g_system->copyRectToScreen((byte *)_workArea.getBasePtr(_dirtyRect.left, _dirtyRect.top), _workArea.pitch, _dirtyRect.left, _dirtyRect.top, _dirtyRect.width(), _dirtyRect.height()); + + // Clear the dirty rect + _dirtyRect = Common::Rect(); + } + + if (screenDirty || _modifiedScreen) + g_system->updateScreen(); + + _modifiedScreen = false; +} + +void GraphicsManager::clearScreen() { + Graphics::Surface *screen = g_system->lockScreen(); + screen->fillRect(Common::Rect(0, 0, 640, 480), g_system->getScreenFormat().RGBToColor(0, 0, 0)); + g_system->unlockScreen(); + _modifiedScreen = true; +} + +DisplayElement *GraphicsManager::findDisplayElement(const DisplayElementID id) { + DisplayElement *runner = _firstDisplayElement; + + while (runner) { + if (runner->getObjectID() == id) + return runner; + runner = runner->_nextElement; + } + + return 0; +} + +void GraphicsManager::doFadeOutSync(const TimeValue, const TimeValue, uint32 color) { + if (color == 0) + color = g_system->getScreenFormat().RGBToColor(0, 0, 0); + + // HACK: Until fading out is done, white-out the screen here + Graphics::Surface *screen = g_system->lockScreen(); + screen->fillRect(Common::Rect(0, 0, 640, 480), color); + g_system->unlockScreen(); + g_system->updateScreen(); +} + +void GraphicsManager::doFadeInSync(const TimeValue, const TimeValue, uint32) { + // TODO +} + +void GraphicsManager::markCursorAsDirty() { + _modifiedScreen = true; +} + +void GraphicsManager::newShakePoint(int32 index1, int32 index2, int32 maxRadius) { + int32 index3 = (index1 + index2) >> 1; + + if (maxRadius == 0) { + _shakeOffsets[index3].x = ((_shakeOffsets[index1].x + _shakeOffsets[index2].x) >> 1); + _shakeOffsets[index3].y = ((_shakeOffsets[index1].y + _shakeOffsets[index2].y) >> 1); + } else { + double angle = (int32)(_vm->getRandomNumber(360 - 1) * 3.1415926535 / 180); + int32 radius = maxRadius; + _shakeOffsets[index3].x = (int32)(((_shakeOffsets[index1].x + _shakeOffsets[index2].x) >> 1) + + cos(angle) / 2 * radius); + _shakeOffsets[index3].y = (int32)(((_shakeOffsets[index1].y + _shakeOffsets[index2].y) >> 1) + + sin(angle) * radius); + } + + if (index1 < index3 - 1) + newShakePoint(index1, index3, maxRadius * 2 / 3); + + if (index3 < index2 - 1) + newShakePoint(index3, index2, maxRadius * 2 / 3); +} + +void GraphicsManager::shakeTheWorld(TimeValue duration, TimeScale scale) { + if (duration == 0 || scale == 0) + return; + + _shakeOffsets[0].x = 0; + _shakeOffsets[0].y = 0; + _shakeOffsets[(kMaxShakeOffsets - 1) / 4].x = 0; + _shakeOffsets[(kMaxShakeOffsets - 1) / 4].y = 0; + _shakeOffsets[(kMaxShakeOffsets - 1) / 2].x = 0; + _shakeOffsets[(kMaxShakeOffsets - 1) / 2].y = 0; + _shakeOffsets[(kMaxShakeOffsets - 1) * 3 / 4].x = 0; + _shakeOffsets[(kMaxShakeOffsets - 1) * 3 / 4].y = 0; + _shakeOffsets[kMaxShakeOffsets - 1].x = 0; + _shakeOffsets[kMaxShakeOffsets - 1].y = 0; + + newShakePoint(0, (kMaxShakeOffsets - 1) / 4, 8); + newShakePoint((kMaxShakeOffsets - 1) / 4, (kMaxShakeOffsets - 1) / 2, 6); + newShakePoint((kMaxShakeOffsets - 1) / 2, (kMaxShakeOffsets - 1) * 3 / 4, 4); + newShakePoint((kMaxShakeOffsets - 1) * 3 / 4, kMaxShakeOffsets - 1, 3); + + Common::Point lastOffset(0, 0); + + // Store the current screen for later use + Graphics::Surface oldScreen; + Graphics::Surface *curScreen = g_system->lockScreen(); + oldScreen.copyFrom(*curScreen); + g_system->unlockScreen(); + + // Convert to millis + duration = duration * 1000 / scale; + + uint32 startTime = g_system->getMillis(); + + while (g_system->getMillis() < startTime + duration) { + Common::Point thisOffset = _shakeOffsets[(g_system->getMillis() - startTime) * (kMaxShakeOffsets - 1) / duration]; + if (thisOffset != lastOffset) { + // Fill the screen with black + Graphics::Surface *screen = g_system->lockScreen(); + screen->fillRect(Common::Rect(0, 0, 640, 480), g_system->getScreenFormat().RGBToColor(0, 0, 0)); + g_system->unlockScreen(); + + // Calculate the src/dst offsets and the width/height + int32 srcOffsetX, dstOffsetX, width; + + if (thisOffset.x > 0) { + srcOffsetX = 0; + dstOffsetX = thisOffset.x; + width = 640 - dstOffsetX; + } else { + srcOffsetX = -thisOffset.x; + dstOffsetX = 0; + width = 640 - srcOffsetX; + } + + int32 srcOffsetY, dstOffsetY, height; + + if (thisOffset.y > 0) { + srcOffsetY = 0; + dstOffsetY = thisOffset.y; + height = 480 - dstOffsetY; + } else { + srcOffsetY = -thisOffset.y; + dstOffsetY = 0; + height = 480 - srcOffsetY; + } + + // Now copy to the screen + g_system->copyRectToScreen((byte *)oldScreen.getBasePtr(srcOffsetX, srcOffsetY), oldScreen.pitch, + dstOffsetX, dstOffsetY, width, height); + g_system->updateScreen(); + + lastOffset = thisOffset; + } + + g_system->delayMillis(10); + } + + if (lastOffset.x != 0 || lastOffset.y != 0) { + g_system->copyRectToScreen((byte *)oldScreen.pixels, oldScreen.pitch, 0, 0, 640, 480); + g_system->updateScreen(); + } + + oldScreen.free(); +} + +void GraphicsManager::enableErase() { + _erase = true; +} + +void GraphicsManager::disableErase() { + _erase = false; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/graphics.h b/engines/pegasus/graphics.h new file mode 100644 index 0000000000..502304409a --- /dev/null +++ b/engines/pegasus/graphics.h @@ -0,0 +1,89 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_GRAPHICS_H +#define PEGASUS_GRAPHICS_H + +#include "common/rect.h" +#include "common/str.h" +#include "common/system.h" +#include "graphics/surface.h" + +#include "pegasus/constants.h" +#include "pegasus/pegasus.h" +#include "pegasus/util.h" + +namespace Pegasus { + +class Cursor; +class DisplayElement; +class PegasusEngine; + +class GraphicsManager { +friend class Cursor; +public: + GraphicsManager(PegasusEngine *vm); + ~GraphicsManager(); + + void addDisplayElement(DisplayElement *element); + void removeDisplayElement(DisplayElement *element); + void invalRect(const Common::Rect &rect); + DisplayOrder getBackOfActiveLayer() const { return _backLayer; } + DisplayOrder getFrontOfActiveLayer() const { return _frontLayer; } + void updateDisplay(); + Graphics::Surface *getCurSurface() { return _curSurface; } + void setCurSurface(Graphics::Surface *surface) { _curSurface = surface; } + Graphics::Surface *getWorkArea() { return &_workArea; } + void clearScreen(); + DisplayElement *findDisplayElement(const DisplayElementID id); + void shakeTheWorld(TimeValue time, TimeScale scale); + void enableErase(); + void disableErase(); + + // These default to black + void doFadeOutSync(const TimeValue = kOneSecondPerThirtyTicks, const TimeScale = kThirtyTicksPerSecond, uint32 color = 0); + void doFadeInSync(const TimeValue = kOneSecondPerThirtyTicks, const TimeScale = kThirtyTicksPerSecond, uint32 color = 0); + +protected: + void markCursorAsDirty(); + +private: + PegasusEngine *_vm; + + bool _modifiedScreen, _erase; + Common::Rect _dirtyRect; + DisplayOrder _backLayer, _frontLayer; + DisplayElement *_firstDisplayElement, *_lastDisplayElement; + Graphics::Surface _workArea, *_curSurface; + + // Shake Shake Shake! + static const int kMaxShakeOffsets = 17; + Common::Point _shakeOffsets[kMaxShakeOffsets]; + void newShakePoint(int32 index1, int32 index2, int32 maxRadius); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/hotspot.cpp b/engines/pegasus/hotspot.cpp new file mode 100644 index 0000000000..84731b7a06 --- /dev/null +++ b/engines/pegasus/hotspot.cpp @@ -0,0 +1,327 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/stream.h" + +#include "pegasus/hotspot.h" + +namespace Pegasus { + +HotspotList g_allHotspots; + +Region::Region(Common::ReadStream *stream) { + uint16 length = stream->readUint16BE(); + + assert(length >= 10); + + _bounds.top = stream->readUint16BE(); + _bounds.left = stream->readUint16BE(); + _bounds.bottom = stream->readUint16BE(); + _bounds.right = stream->readUint16BE(); + + _bounds.debugPrint(0, "Bounds:"); + + if (length == 10) + return; + + length -= 10; + + while (length > 0) { + Vector v; + v.y = stream->readUint16BE(); + length -= 2; + + if (v.y == 0x7fff) + break; + + debug(0, "y: %d", v.y); + + // Normalize y to _bounds + v.y -= _bounds.top; + + while (length > 0) { + Run run; + run.start = stream->readUint16BE(); + length -= 2; + + if (run.start == 0x7fff) + break; + + run.end = stream->readUint16BE(); + length -= 2; + + debug(0, "\t[%d, %d)", run.start, run.end); + + // Normalize to _bounds + run.start -= _bounds.left; + run.end -= _bounds.left; + + v.push_back(run); + } + + _vectors.push_back(v); + } +} + +Region::Region(const Common::Rect &rect) { + _bounds = rect; +} + +bool Region::pointInRegion(const Common::Point &point) const { + if (!_bounds.contains(point)) + return false; + + bool pixelActive = false; + + // Normalize the points to _bounds + uint16 x = point.x - _bounds.left; + uint16 y = point.y - _bounds.top; + + for (Common::List<Vector>::const_iterator v = _vectors.begin(); v != _vectors.end(); v++) { + if (v->y > y) + return pixelActive; + + for (Vector::const_iterator run = v->begin(); run != v->end(); run++) { + if (x >= run->start && x < run->end) { + pixelActive = !pixelActive; + break; + } + } + } + + // the case if the region is just a rect + return true; +} + +void Region::moveTo(CoordType h, CoordType v) { + _bounds.moveTo(h, v); +} + +void Region::moveTo(const Common::Point &point) { + _bounds.moveTo(point); +} + +void Region::translate(CoordType h, CoordType v) { + _bounds.translate(h, v); +} + +void Region::translate(const Common::Point &point) { + _bounds.translate(point.x, point.y); +} + +void Region::getCenter(CoordType &h, CoordType &v) const { + h = (_bounds.left + _bounds.right) / 2; + v = (_bounds.top + _bounds.bottom) / 2; +} + +void Region::getCenter(Common::Point &point) const { + getCenter(point.x, point.y); +} + +Hotspot::Hotspot(const HotSpotID id) : IDObject(id) { + _spotFlags = kNoHotSpotFlags; + _spotActive = false; +} + +Hotspot::~Hotspot() { +} + +void Hotspot::setArea(const Common::Rect &area) { + _spotArea = Region(area); +} + +void Hotspot::setArea(const CoordType left, const CoordType top, const CoordType right, const CoordType bottom) { + _spotArea = Region(Common::Rect(left, top, right, bottom)); +} + +void Hotspot::getBoundingBox(Common::Rect &r) const { + r = _spotArea.getBoundingBox(); +} + +void Hotspot::getCenter(Common::Point &pt) const { + _spotArea.getCenter(pt); +} + +void Hotspot::getCenter(CoordType &h, CoordType &v) const { + _spotArea.getCenter(h, v); +} + +void Hotspot::setActive() { + _spotActive = true; +} + +void Hotspot::setInactive() { + _spotActive = false; +} + +void Hotspot::setHotspotFlags(const HotSpotFlags flags) { + _spotFlags = flags; +} + +void Hotspot::setMaskedHotspotFlags(const HotSpotFlags flags, const HotSpotFlags mask) { + _spotFlags = (_spotFlags & ~mask) | flags; +} + +bool Hotspot::isSpotActive() const { + return _spotActive; +} + +void Hotspot::moveSpotTo(const CoordType h, const CoordType v) { + _spotArea.moveTo(h, v); +} + +void Hotspot::moveSpotTo(const Common::Point pt) { + _spotArea.moveTo(pt); +} + +void Hotspot::moveSpot(const CoordType h, const CoordType v) { + _spotArea.translate(h, v); +} + +void Hotspot::moveSpot(const Common::Point pt) { + _spotArea.translate(pt.x, pt.y); +} + +bool Hotspot::pointInSpot(const Common::Point where) const { + return _spotActive && _spotArea.pointInRegion(where); +} + +HotSpotFlags Hotspot::getHotspotFlags() const { + return _spotFlags; +} + +HotspotList::HotspotList() { +} + +HotspotList::~HotspotList() { + // TODO: Should this call deleteHotspots()? +} + +void HotspotList::deleteHotspots() { + for (HotspotIterator it = begin(); it != end(); it++) + delete *it; + + clear(); +} + +Hotspot *HotspotList::findHotspot(const Common::Point where) { + for (HotspotIterator it = begin(); it != end(); it++) + if ((*it)->pointInSpot(where)) + return *it; + + return 0; +} + +HotSpotID HotspotList::findHotspotID(const Common::Point where) { + Hotspot *hotspot = findHotspot(where); + return hotspot ? hotspot->getObjectID() : kNoHotSpotID; +} + +Hotspot *HotspotList::findHotspotByID(const HotSpotID id) { + for (HotspotIterator it = begin(); it != end(); it++) + if ((*it)->getObjectID() == id) + return *it; + + return 0; +} + +Hotspot *HotspotList::findHotspotByMask(const HotSpotFlags flags) { + for (HotspotIterator it = begin(); it != end(); it++) + if (((*it)->getHotspotFlags() & flags) == flags) + return *it; + + return 0; +} + +void HotspotList::activateMaskedHotspots(const HotSpotFlags flags) { + for (HotspotIterator it = begin(); it != end(); it++) + if (flags == kNoHotSpotFlags || ((*it)->getHotspotFlags() & flags) != 0) + (*it)->setActive(); +} + +void HotspotList::deactivateAllHotspots() { + for (HotspotIterator it = begin(); it != end(); it++) + (*it)->setInactive(); +} + +void HotspotList::deactivateMaskedHotspots(const HotSpotFlags flags) { + for (HotspotIterator it = begin(); it != end(); it++) + if (((*it)->getHotspotFlags() & flags) != 0) + (*it)->setInactive(); +} + +void HotspotList::activateOneHotspot(const HotSpotID id) { + for (HotspotIterator it = begin(); it != end(); it++) { + if ((*it)->getObjectID() == id) { + (*it)->setActive(); + return; + } + } +} + +void HotspotList::deactivateOneHotspot(const HotSpotID id) { + for (HotspotIterator it = begin(); it != end(); it++) { + if ((*it)->getObjectID() == id) { + (*it)->setInactive(); + return; + } + } +} + +void HotspotList::removeOneHotspot(const HotSpotID id) { + for (HotspotIterator it = begin(); it != end(); it++) { + if ((*it)->getObjectID() == id) { + erase(it); + return; + } + } +} + +void HotspotList::removeMaskedHotspots(const HotSpotFlags flags) { + if (flags != kNoHotSpotFlags) { + for (HotspotIterator it = begin(); it != end(); ) { + if (((*it)->getHotspotFlags() & flags) != 0) + it = erase(it); + else + it++; + } + } else { + clear(); + } +} + +void HotspotList::setHotspotRect(const HotSpotID id, const Common::Rect &r) { + Hotspot *hotspot = findHotspotByID(id); + if (hotspot) + hotspot->setArea(r); +} + +void HotspotList::getHotspotRect(const HotSpotID id, Common::Rect &r) { + Hotspot *hotspot = findHotspotByID(id); + if (hotspot) + hotspot->getBoundingBox(r); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/hotspot.h b/engines/pegasus/hotspot.h new file mode 100644 index 0000000000..bc1d054f0d --- /dev/null +++ b/engines/pegasus/hotspot.h @@ -0,0 +1,153 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_HOTSPOT_H +#define PEGASUS_HOTSPOT_H + +#include "common/list.h" +#include "common/rect.h" + +#include "pegasus/constants.h" +#include "pegasus/types.h" +#include "pegasus/util.h" + +/* + + Hot spots combine a pixel area, an ID value and an active flag. + + A point is considered in a hot spot if the point is in the hot spot's pixel area and + the active flag is set. + + In addition, hot spots have a 32 bit word of bit flags for filtering use. + +*/ + +namespace Common { + class ReadStream; +} + +namespace Pegasus { + +// Our implementation of QuickDraw regions +class Region { +public: + Region() {} + Region(Common::ReadStream *stream); + Region(const Common::Rect &rect); + + Common::Rect getBoundingBox() const { return _bounds; } + + bool pointInRegion(const Common::Point &point) const; + + void moveTo(CoordType h, CoordType v); + void moveTo(const Common::Point &point); + void translate(CoordType h, CoordType v); + void translate(const Common::Point &point); + void getCenter(CoordType &h, CoordType &v) const; + void getCenter(Common::Point &point) const; + +private: + Common::Rect _bounds; + + struct Run { + uint16 start, end; + }; + + class Vector : public Common::List<Run> { + public: + uint16 y; + }; + + Common::List<Vector> _vectors; +}; + +class Hotspot : public IDObject { +public: + Hotspot(const HotSpotID); + virtual ~Hotspot(); + + void setArea(const Region ®ion) { _spotArea = region; } + void setArea(const Common::Rect &); + void setArea(const CoordType, const CoordType, const CoordType, const CoordType); + void getBoundingBox(Common::Rect &) const; + void getArea(Region &) const; + void getCenter(Common::Point&) const; + void getCenter(CoordType&, CoordType&) const; + + void moveSpotTo(const CoordType, const CoordType); + void moveSpotTo(const Common::Point); + void moveSpot(const CoordType, const CoordType); + void moveSpot(const Common::Point); + + bool pointInSpot(const Common::Point) const; + + void setActive(); + void setInactive(); + bool isSpotActive() const; + + HotSpotFlags getHotspotFlags() const; + void setHotspotFlags(const HotSpotFlags); + void setMaskedHotspotFlags(const HotSpotFlags flags, const HotSpotFlags mask); + +protected: + Region _spotArea; + HotSpotFlags _spotFlags; + bool _spotActive; +}; + +class HotspotList : public Common::List<Hotspot *> { +public: + HotspotList(); + virtual ~HotspotList(); + + void deleteHotspots(); + + Hotspot *findHotspot(const Common::Point); + HotSpotID findHotspotID(const Common::Point); + Hotspot *findHotspotByID(const HotSpotID); + Hotspot *findHotspotByMask(const HotSpotFlags); + + void activateMaskedHotspots(const HotSpotFlags = kNoHotSpotFlags); + void deactivateAllHotspots(); + void deactivateMaskedHotspots(const HotSpotFlags); + + void activateOneHotspot(const HotSpotID); + void deactivateOneHotspot(const HotSpotID); + + void removeOneHotspot(const HotSpotID); + void removeMaskedHotspots(const HotSpotFlags = kNoHotSpotFlags); + + void setHotspotRect(const HotSpotID, const Common::Rect&); + void getHotspotRect(const HotSpotID, Common::Rect&); +}; + +typedef HotspotList::iterator HotspotIterator; + +// FIXME: Remove global construction +extern HotspotList g_allHotspots; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/input.cpp b/engines/pegasus/input.cpp new file mode 100644 index 0000000000..e11be2c8e7 --- /dev/null +++ b/engines/pegasus/input.cpp @@ -0,0 +1,373 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/events.h" +#include "common/system.h" + +#include "pegasus/cursor.h" +#include "pegasus/input.h" +#include "pegasus/pegasus.h" + +namespace Common { +DECLARE_SINGLETON(Pegasus::InputDeviceManager); +} + +namespace Pegasus { + +InputDeviceManager::InputDeviceManager() { + // Set all keys to "not down" + _keyMap[Common::KEYCODE_UP] = false; + _keyMap[Common::KEYCODE_KP8] = false; + _keyMap[Common::KEYCODE_LEFT] = false; + _keyMap[Common::KEYCODE_KP4] = false; + _keyMap[Common::KEYCODE_DOWN] = false; + _keyMap[Common::KEYCODE_KP5] = false; + _keyMap[Common::KEYCODE_RIGHT] = false; + _keyMap[Common::KEYCODE_KP6] = false; + _keyMap[Common::KEYCODE_RETURN] = false; + _keyMap[Common::KEYCODE_SPACE] = false; + _keyMap[Common::KEYCODE_t] = false; + _keyMap[Common::KEYCODE_KP_EQUALS] = false; + _keyMap[Common::KEYCODE_i] = false; + _keyMap[Common::KEYCODE_KP_DIVIDE] = false; + _keyMap[Common::KEYCODE_q] = false; + _keyMap[Common::KEYCODE_ESCAPE] = false; + _keyMap[Common::KEYCODE_p] = false; + _keyMap[Common::KEYCODE_TILDE] = false; + _keyMap[Common::KEYCODE_BACKQUOTE] = false; + _keyMap[Common::KEYCODE_NUMLOCK] = false; + _keyMap[Common::KEYCODE_BACKSPACE] = false; + _keyMap[Common::KEYCODE_KP_MULTIPLY] = false; + _keyMap[Common::KEYCODE_LALT] = false; + _keyMap[Common::KEYCODE_RALT] = false; + _keyMap[Common::KEYCODE_e] = false; + _keyMap[Common::KEYCODE_KP_ENTER] = false; + + g_system->getEventManager()->getEventDispatcher()->registerObserver(this, 2, false); + _lastRawBits = kAllUpBits; + _consoleRequested = false; +} + +InputDeviceManager::~InputDeviceManager() { + g_system->getEventManager()->getEventDispatcher()->unregisterObserver(this); +} + +void InputDeviceManager::getInput(Input &input, const InputBits filter) { + // Poll for events, but ignore them! + // We'll pick them up in notifyEvent() + // We do that so that any pollEvent() call can update the variables + // (ie. if one uses enter to access the restore menu, we never receive + // the key up event, which leads to bad things) + // This is to closely emulate what the GetKeys() function did on Mac OS + Common::Event event; + while (g_system->getEventManager()->pollEvent(event)) + ; + + // Now create the bitfield + InputBits currentBits = 0; + + if (_keyMap[Common::KEYCODE_UP] || _keyMap[Common::KEYCODE_KP8]) + currentBits |= (kRawButtonDown << kUpButtonShift); + + if (_keyMap[Common::KEYCODE_DOWN] || _keyMap[Common::KEYCODE_KP5]) + currentBits |= (kRawButtonDown << kDownButtonShift); + + if (_keyMap[Common::KEYCODE_LEFT] || _keyMap[Common::KEYCODE_KP4]) + currentBits |= (kRawButtonDown << kLeftButtonShift); + + if (_keyMap[Common::KEYCODE_RIGHT] || _keyMap[Common::KEYCODE_KP6]) + currentBits |= (kRawButtonDown << kRightButtonShift); + + if (_keyMap[Common::KEYCODE_SPACE] || _keyMap[Common::KEYCODE_RETURN] || _keyMap[Common::KEYCODE_KP_ENTER]) + currentBits |= (kRawButtonDown << kTwoButtonShift); + + if (_keyMap[Common::KEYCODE_t] || _keyMap[Common::KEYCODE_KP_EQUALS]) + currentBits |= (kRawButtonDown << kThreeButtonShift); + + if (_keyMap[Common::KEYCODE_i] || _keyMap[Common::KEYCODE_KP_DIVIDE]) + currentBits |= (kRawButtonDown << kFourButtonShift); + + if (_keyMap[Common::KEYCODE_q]) + currentBits |= (kRawButtonDown << kMod1ButtonShift); + + if (_keyMap[Common::KEYCODE_ESCAPE] || _keyMap[Common::KEYCODE_p]) + currentBits |= (kRawButtonDown << kMod3ButtonShift); + + if (_keyMap[Common::KEYCODE_TILDE] || _keyMap[Common::KEYCODE_BACKQUOTE] || _keyMap[Common::KEYCODE_NUMLOCK]) + currentBits |= (kRawButtonDown << kLeftFireButtonShift); + + if (_keyMap[Common::KEYCODE_BACKSPACE] || _keyMap[Common::KEYCODE_KP_MULTIPLY]) + currentBits |= (kRawButtonDown << kRightFireButtonShift); + + // Update mouse button state + // Note that we don't use EVENT_LBUTTONUP/EVENT_LBUTTONDOWN because + // they do not show if the button is being held down. We're treating + // both mouse buttons as the same for ease of use. + if (g_system->getEventManager()->getButtonState() != 0) + currentBits |= (kRawButtonDown << kTwoButtonShift); + + // Update the mouse position too + input.setInputLocation(g_system->getEventManager()->getMousePos()); + + // Set the outgoing bits + InputBits filteredBits = currentBits & filter; + input.setInputBits((filteredBits & kAllButtonDownBits) | (filteredBits & _lastRawBits & kAllAutoBits)); + + // Update the last bits + _lastRawBits = currentBits; + + // Set the console to be requested or not + input.setConsoleRequested(_consoleRequested); + + // WORKAROUND: The original had this in currentBits, but then + // pressing alt would count as an event (and mess up someone + // trying to do alt+enter or something). Since it's only used + // as an easter egg, I'm just going to handle it as a separate + // bool value. + // WORKAROUND x2: I'm also accepting 'e' here since an + // alt+click is often intercepted by the OS. 'e' is used as the + // easter egg key in Buried in Time and Legacy of Time. + input.setAltDown(_keyMap[Common::KEYCODE_LALT] || _keyMap[Common::KEYCODE_RALT] || _keyMap[Common::KEYCODE_e]); +} + +// Wait until the input device stops returning input allowed by filter... +void InputDeviceManager::waitInput(const InputBits filter) { + if (filter != 0) { + for (;;) { + Input input; + getInput(input, filter); + if (!input.anyInput()) + break; + } + } +} + +bool InputDeviceManager::notifyEvent(const Common::Event &event) { + // We're mapping from ScummVM events to pegasus events, which + // are based on pippin events. + _consoleRequested = false; + + switch (event.type) { + case Common::EVENT_KEYDOWN: + switch (event.kbd.keycode) { + case Common::KEYCODE_d: + if (event.kbd.flags & Common::KBD_CTRL) // Console! + _consoleRequested = true; + break; + case Common::KEYCODE_s: + // We support meta where available and control elsewhere + if (event.kbd.flags & (Common::KBD_CTRL|Common::KBD_META)) + ((PegasusEngine *)g_engine)->requestSave(); + break; + case Common::KEYCODE_o: // o for open (original) + case Common::KEYCODE_l: // l for load (ScummVM terminology) + // We support meta where available and control elsewhere + if (event.kbd.flags & (Common::KBD_CTRL|Common::KBD_META)) + ((PegasusEngine *)g_engine)->requestLoad(); + break; + default: + // Otherwise, set the key to down if we have it + if (_keyMap.contains(event.kbd.keycode)) + _keyMap[event.kbd.keycode] = true; + break; + } + break; + case Common::EVENT_KEYUP: + // Set the key to up if we have it + if (_keyMap.contains(event.kbd.keycode)) + _keyMap[event.kbd.keycode] = false; + break; + default: + break; + } + + return false; +} + +int operator==(const Input &arg1, const Input &arg2) { + return arg1._inputState == arg2._inputState; +} + +int operator!=(const Input &arg1, const Input &arg2) { + return !operator==(arg1, arg2); +} + +InputHandler *InputHandler::_inputHandler = 0; +bool InputHandler::_invalHotspots = false; +InputBits InputHandler::_lastFilter = kFilterNoInput; + +InputHandler *InputHandler::setInputHandler(InputHandler *currentHandler) { + InputHandler *result = 0; + + if (_inputHandler != currentHandler && (!_inputHandler || _inputHandler->releaseInputFocus())) { + result = _inputHandler; + _inputHandler = currentHandler; + if (_inputHandler) + _inputHandler->grabInputFocus(); + } + + return result; +} + +void InputHandler::pollForInput() { + if (_inputHandler) { + Input input; + Hotspot *cursorSpot = 0; + + InputHandler::getInput(input, cursorSpot); + if (_inputHandler->isClickInput(input, cursorSpot)) + _inputHandler->clickInHotspot(input, cursorSpot); + else + _inputHandler->handleInput(input, cursorSpot); + } +} + +void InputHandler::getInput(Input &input, Hotspot *&cursorSpot) { + Cursor *cursor = ((PegasusEngine *)g_engine)->_cursor; + + if (_inputHandler) + _lastFilter = _inputHandler->getInputFilter(); + else + _lastFilter = kFilterAllInput; + + InputDevice.getInput(input, _lastFilter); + + if (_inputHandler && _inputHandler->wantsCursor() && (_lastFilter & _inputHandler->getClickFilter()) != 0) { + if (cursor->isVisible()) { + g_allHotspots.deactivateAllHotspots(); + _inputHandler->activateHotspots(); + + Common::Point cursorLocation; + cursor->getCursorLocation(cursorLocation); + cursorSpot = g_allHotspots.findHotspot(cursorLocation); + + if (_inputHandler) + _inputHandler->updateCursor(cursorLocation, cursorSpot); + } else { + cursor->hideUntilMoved(); + } + } else { + cursor->hide(); + } +} + +void InputHandler::readInputDevice(Input &input) { + InputDevice.getInput(input, kFilterAllInput); +} + +InputHandler::InputHandler(InputHandler *nextHandler) { + _nextHandler = nextHandler; + allowInput(true); +} + +InputHandler::~InputHandler() { + if (_inputHandler == this) + setInputHandler(_nextHandler); +} + +void InputHandler::handleInput(const Input &input, const Hotspot *cursorSpot) { + if (_nextHandler) + _nextHandler->handleInput(input, cursorSpot); +} + +void InputHandler::clickInHotspot(const Input &input, const Hotspot *cursorSpot) { + if (_nextHandler) + _nextHandler->clickInHotspot(input, cursorSpot); +} + +bool InputHandler::isClickInput(const Input &input, const Hotspot *cursorSpot) { + if (_nextHandler) + return _nextHandler->isClickInput(input, cursorSpot); + + return false; +} + +void InputHandler::activateHotspots() { + if (_nextHandler) + _nextHandler->activateHotspots(); +} + +InputBits InputHandler::getInputFilter() { + if (_allowInput) { + if (_nextHandler) + return _nextHandler->getInputFilter(); + else + return kFilterAllInput; + } + + return kFilterNoInput; +} + +InputBits InputHandler::getClickFilter() { + if (_allowInput && _nextHandler) + return _nextHandler->getClickFilter(); + + return kFilterNoInput; +} + +void InputHandler::updateCursor(const Common::Point cursorLocation, const Hotspot *cursorSpot) { + if (_nextHandler) + _nextHandler->updateCursor(cursorLocation, cursorSpot); +} + +bool InputHandler::wantsCursor() { + if (_allowInput) { + if (_nextHandler) + return _nextHandler->wantsCursor(); + else + return true; + } + + return false; +} + +Tracker *Tracker::_currentTracker = 0; + +void Tracker::handleInput(const Input &input, const Hotspot *) { + if (stopTrackingInput(input)) + stopTracking(input); + else if (isTracking()) + continueTracking(input); +} + +void Tracker::startTracking(const Input &) { + if (!isTracking()) { + _savedHandler = InputHandler::setInputHandler(this); + _currentTracker = this; + } +} + +void Tracker::stopTracking(const Input &) { + if (isTracking()) { + _currentTracker = NULL; + InputHandler::setInputHandler(_savedHandler); + } +} + +bool Tracker::isClickInput(const Input &input, const Hotspot *hotspot) { + return !isTracking() && InputHandler::isClickInput(input, hotspot); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/input.h b/engines/pegasus/input.h new file mode 100644 index 0000000000..f6c29e7780 --- /dev/null +++ b/engines/pegasus/input.h @@ -0,0 +1,500 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_INPUT_H +#define PEGASUS_INPUT_H + +#include "common/events.h" +#include "common/hashmap.h" +#include "common/rect.h" +#include "common/singleton.h" + +#include "pegasus/constants.h" +#include "pegasus/types.h" + +namespace Pegasus { + +class Hotspot; +class Input; + +class InputDeviceManager : public Common::Singleton<InputDeviceManager>, public Common::EventObserver { +public: + InputDeviceManager(); + ~InputDeviceManager(); + + bool notifyEvent(const Common::Event &event); + + void getInput(Input &, const InputBits); + + void waitInput(const InputBits); + +protected: + friend class Common::Singleton<SingletonBaseType>; + + // Keep track of which keys are down (= true) or not + Common::HashMap<uint, bool> _keyMap; + InputBits _lastRawBits; + bool _consoleRequested; +}; + +enum { + kButtonDownBit = 0, + kAutoButtonBit = 1, + kBitsPerButton = 2, + + kButtonDownMask = 1 << kButtonDownBit, + kAutoButtonMask = 1 << kAutoButtonBit, + + kButtonStateBits = kButtonDownMask | kAutoButtonMask, + + kRawButtonUp = 0, + kRawButtonDown = kButtonDownMask | kAutoButtonMask, + + kButtonUp = 0, + kButtonDown = kButtonDownMask, + kButtonAutoUp = kAutoButtonMask, + kButtonAutoDown = kButtonDownMask | kAutoButtonMask +}; + +enum { + kUpButtonNum = 0, + kLeftButtonNum = 1, + kDownButtonNum = 2, + kRightButtonNum = 3, + kLeftFireButtonNum = 4, + kRightFireButtonNum = 5, + kOneButtonNum = 6, + kTwoButtonNum = 7, + kThreeButtonNum = 8, + kFourButtonNum = 9, + kMod1ButtonNum = 10, + kMod2ButtonNum = 11, + kMod3ButtonNum = 12 +}; + +enum { + kUpButtonShift = kUpButtonNum * kBitsPerButton, + kLeftButtonShift = kLeftButtonNum * kBitsPerButton, + kDownButtonShift = kDownButtonNum * kBitsPerButton, + kRightButtonShift = kRightButtonNum * kBitsPerButton, + kLeftFireButtonShift = kLeftFireButtonNum * kBitsPerButton, + kRightFireButtonShift = kRightFireButtonNum * kBitsPerButton, + kOneButtonShift = kOneButtonNum * kBitsPerButton, + kTwoButtonShift = kTwoButtonNum * kBitsPerButton, + kThreeButtonShift = kThreeButtonNum * kBitsPerButton, + kFourButtonShift = kFourButtonNum * kBitsPerButton, + kMod1ButtonShift = kMod1ButtonNum * kBitsPerButton, + kMod2ButtonShift = kMod2ButtonNum * kBitsPerButton, + kMod3ButtonShift = kMod3ButtonNum * kBitsPerButton +}; + +enum { + kAllUpBits = (kButtonUp << kUpButtonShift) | + (kButtonUp << kLeftButtonShift) | + (kButtonUp << kDownButtonShift) | + (kButtonUp << kRightButtonShift) | + (kButtonUp << kLeftFireButtonShift) | + (kButtonUp << kRightFireButtonShift) | + (kButtonUp << kOneButtonShift) | + (kButtonUp << kTwoButtonShift) | + (kButtonUp << kThreeButtonShift) | + (kButtonUp << kFourButtonShift) | + (kButtonUp << kMod1ButtonShift) | + (kButtonUp << kMod2ButtonShift) | + (kButtonUp << kMod3ButtonShift), + kDirectionBits = (kButtonDownMask << kUpButtonShift) | + (kButtonDownMask << kLeftButtonShift) | + (kButtonDownMask << kDownButtonShift) | + (kButtonDownMask << kRightButtonShift), + kButtonBits = (kButtonDownMask << kLeftFireButtonShift) | + (kButtonDownMask << kRightFireButtonShift) | + (kButtonDownMask << kOneButtonShift) | + (kButtonDownMask << kTwoButtonShift) | + (kButtonDownMask << kThreeButtonShift) | + (kButtonDownMask << kFourButtonShift) | + (kButtonDownMask << kMod1ButtonShift) | + (kButtonDownMask << kMod2ButtonShift) | + (kButtonDownMask << kMod3ButtonShift), + kAllButtonDownBits = kDirectionBits | kButtonBits, + kAllAutoBits = (kAutoButtonMask << kUpButtonShift) | + (kAutoButtonMask << kLeftButtonShift) | + (kAutoButtonMask << kDownButtonShift) | + (kAutoButtonMask << kRightButtonShift) | + (kAutoButtonMask << kLeftFireButtonShift) | + (kAutoButtonMask << kRightFireButtonShift) | + (kAutoButtonMask << kOneButtonShift) | + (kAutoButtonMask << kTwoButtonShift) | + (kAutoButtonMask << kThreeButtonShift) | + (kAutoButtonMask << kFourButtonShift) | + (kAutoButtonMask << kMod1ButtonShift) | + (kAutoButtonMask << kMod2ButtonShift) | + (kAutoButtonMask << kMod3ButtonShift), + + kFilterUpButton = kButtonDownMask << kUpButtonShift, + kFilterUpAuto = kAutoButtonMask << kUpButtonShift, + kFilterUpButtonAny = kFilterUpButton | kFilterUpAuto, + kFilterLeftButton = kButtonDownMask << kLeftButtonShift, + kFilterLeftAuto = kAutoButtonMask << kLeftButtonShift, + kFilterLeftButtonAny = kFilterLeftButton | kFilterLeftAuto, + kFilterDownButton = kButtonDownMask << kDownButtonShift, + kFilterDownAuto = kAutoButtonMask << kDownButtonShift, + kFilterDownButtonAny = kFilterDownButton | kFilterDownAuto, + kFilterRightButton = kButtonDownMask << kRightButtonShift, + kFilterRightAuto = kAutoButtonMask << kRightButtonShift, + kFilterRightButtonAny = kFilterRightButton | kFilterRightAuto, + kFilterLeftFireButton = kButtonDownMask << kLeftFireButtonShift, + kFilterLeftFireAuto = kAutoButtonMask << kLeftFireButtonShift, + kFilterLeftFireButtonAny = kFilterLeftFireButton | kFilterLeftFireAuto, + kFilterRightFireButton = kButtonDownMask << kRightFireButtonShift, + kFilterRightFireAuto = kAutoButtonMask << kRightFireButtonShift, + kFilterRightFireButtonAny = kFilterRightFireButton | kFilterRightFireAuto, + kFilterOneButton = kButtonDownMask << kOneButtonShift, + kFilterOneAuto = kAutoButtonMask << kOneButtonShift, + kFilterOneButtonAny = kFilterOneButton | kFilterOneAuto, + kFilterTwoButton = kButtonDownMask << kTwoButtonShift, + kFilterTwoAuto = kAutoButtonMask << kTwoButtonShift, + kFilterTwoButtonAny = kFilterTwoButton | kFilterTwoAuto, + kFilterThreeButton = kButtonDownMask << kThreeButtonShift, + kFilterThreeAuto = kAutoButtonMask << kThreeButtonShift, + kFilterThreeButtonAny = kFilterThreeButton | kFilterThreeAuto, + kFilterFourButton = kButtonDownMask << kFourButtonShift, + kFilterFourAuto = kAutoButtonMask << kFourButtonShift, + kFilterFourButtonAny = kFilterFourButton | kFilterFourAuto, + kFilterMod1Button = kButtonDownMask << kMod1ButtonShift, + kFilterMod1Auto = kAutoButtonMask << kMod1ButtonShift, + kFilterMod1ButtonAny = kFilterMod1Button | kFilterMod1Auto, + kFilterMod2Button = kButtonDownMask << kMod2ButtonShift, + kFilterMod2Auto = kAutoButtonMask << kMod2ButtonShift, + kFilterMod2ButtonAny = kFilterMod2Button | kFilterMod2Auto, + kFilterMod3Button = kButtonDownMask << kMod3ButtonShift, + kFilterMod3Auto = kAutoButtonMask << kMod3ButtonShift, + kFilterMod3ButtonAny = kFilterMod3Button | kFilterMod3Auto, + + kFilterNoInput = 0, + kFilterAllInput = kFilterUpButton | + kFilterUpAuto | + kFilterLeftButton | + kFilterLeftAuto | + kFilterDownButton | + kFilterDownAuto | + kFilterRightButton | + kFilterRightAuto | + kFilterLeftFireButton | + kFilterLeftFireAuto | + kFilterRightFireButton | + kFilterRightFireAuto | + kFilterOneButton | + kFilterOneAuto | + kFilterTwoButton | + kFilterTwoAuto | + kFilterThreeButton | + kFilterThreeAuto | + kFilterFourButton | + kFilterFourAuto | + kFilterMod1Button | + kFilterMod1Auto | + kFilterMod2Button | + kFilterMod2Auto | + kFilterMod3Button | + kFilterMod3Auto, + + kFilterAllDirections = kFilterUpButton | + kFilterUpAuto | + kFilterLeftButton | + kFilterLeftAuto | + kFilterDownButton | + kFilterDownAuto | + kFilterRightButton | + kFilterRightAuto, + + kFilterButtons = kFilterOneButton | + kFilterOneAuto | + kFilterTwoButton | + kFilterTwoAuto | + kFilterThreeButton | + kFilterThreeAuto | + kFilterFourButton | + kFilterFourAuto, + + kFilterFireButtons = kFilterLeftFireButton | + kFilterLeftFireAuto | + kFilterRightFireButton | + kFilterRightFireAuto, + + kFilterAllButtons = kFilterLeftFireButton | + kFilterLeftFireAuto | + kFilterRightFireButton | + kFilterRightFireAuto | + kFilterOneButton | + kFilterOneAuto | + kFilterTwoButton | + kFilterTwoAuto | + kFilterThreeButton | + kFilterThreeAuto | + kFilterFourButton | + kFilterFourAuto | + kFilterMod1Button | + kFilterMod1Auto | + kFilterMod2Button | + kFilterMod2Auto | + kFilterMod3Button | + kFilterMod3Auto, + + kFilterAllInputNoAuto = kFilterUpButton | + kFilterLeftButton | + kFilterDownButton | + kFilterRightButton | + kFilterLeftFireButton | + kFilterRightFireButton | + kFilterOneButton | + kFilterTwoButton | + kFilterThreeButton | + kFilterFourButton | + kFilterMod1Button | + kFilterMod2Button | + kFilterMod3Button +}; + +static const InputBits kHintInterruption = kFilterAllInputNoAuto; +static const InputBits kWarningInterruption = kFilterNoInput; +static const InputBits kOpticalInterruption = kFilterAllInputNoAuto; + +/* + + Buttons are defined as: + up, left, down, right direction buttons. + fireLeft, fireRight: fire buttons. + mod1, mod2, mod3: modifier buttons, similar to shift, control, etc. + a, b, c, d: general purpose buttons. + + button state is held as bits in a long word, two bits per button. + + Filter bits: + for each button, two bits are assigned for filtering. If bit 0 is set, the + corresponding button is available for "button down" input. If bit 1 is set, + the corresponding button is available for "auto down" input. Note that bit + 1 is meaningful only if bit 0 is set. + +*/ + +class Input { +friend int operator==(const Input &, const Input &); +friend int operator!=(const Input &, const Input &); +friend class InputDeviceManager; + +public: + Input() { clearInput(); } + + bool upButtonDown() const { return (_inputState & (kButtonStateBits << kUpButtonShift)) == (kButtonDown << kUpButtonShift); } + bool upButtonAutoDown() const { return (_inputState & (kButtonStateBits << kUpButtonShift)) == (kButtonAutoDown << kUpButtonShift); } + bool upButtonAnyDown() const { return (_inputState & (kButtonAutoDown << kUpButtonShift)) != 0; } + + bool leftButtonDown() const { return (_inputState & (kButtonStateBits << kLeftButtonShift)) == (kButtonDown << kLeftButtonShift); } + bool leftButtonAutoDown() const { return (_inputState & (kButtonStateBits << kLeftButtonShift)) == (kButtonAutoDown << kLeftButtonShift); } + bool leftButtonAnyDown() const { return (_inputState & (kButtonAutoDown << kLeftButtonShift)) != 0; } + + bool downButtonDown() const { return (_inputState & (kButtonStateBits << kDownButtonShift)) == (kButtonDown << kDownButtonShift); } + bool downButtonAutoDown() const { return (_inputState & (kButtonStateBits << kDownButtonShift)) == (kButtonAutoDown << kDownButtonShift); } + bool downButtonAnyDown() const { return (_inputState & (kButtonAutoDown << kDownButtonShift)) != 0; } + + bool rightButtonDown() const { return (_inputState & (kButtonStateBits << kRightButtonShift)) == (kButtonDown << kRightButtonShift); } + bool rightButtonAutoDown() const { return (_inputState & (kButtonStateBits << kRightButtonShift)) == (kButtonAutoDown << kRightButtonShift); } + bool rightButtonAnyDown() const { return (_inputState & (kButtonAutoDown << kRightButtonShift)) != 0; } + + bool leftFireButtonDown() const { return (_inputState & (kButtonStateBits << kLeftFireButtonShift)) == (kButtonDown << kLeftFireButtonShift); } + bool leftFireButtonAutoDown() const { return (_inputState & (kButtonStateBits << kLeftFireButtonShift)) == (kButtonAutoDown << kLeftFireButtonShift); } + bool leftFireButtonAnyDown() const { return (_inputState & (kButtonAutoDown << kLeftFireButtonShift)) != 0; } + + bool rightFireButtonDown() const { return (_inputState & (kButtonStateBits << kRightFireButtonShift)) == (kButtonDown << kRightFireButtonShift); } + bool rightFireButtonAutoDown() const { return (_inputState & (kButtonStateBits << kRightFireButtonShift)) == (kButtonAutoDown << kRightFireButtonShift); } + bool rightFireButtonAnyDown() const { return (_inputState & (kButtonAutoDown << kRightFireButtonShift)) != 0; } + + bool oneButtonDown() const { return (_inputState & (kButtonStateBits << kOneButtonShift)) == (kButtonDown << kOneButtonShift); } + bool oneButtonAutoDown() const { return (_inputState & (kButtonStateBits << kOneButtonShift)) == (kButtonAutoDown << kOneButtonShift); } + bool oneButtonAnyDown() const { return (_inputState & (kButtonAutoDown << kOneButtonShift)) != 0; } + + bool twoButtonDown() const { return (_inputState & (kButtonStateBits << kTwoButtonShift)) == (kButtonDown << kTwoButtonShift); } + bool twoButtonAutoDown() const { return (_inputState & (kButtonStateBits << kTwoButtonShift)) == (kButtonAutoDown << kTwoButtonShift); } + bool twoButtonAnyDown() const { return (_inputState & (kButtonAutoDown << kTwoButtonShift)) != 0; } + + bool threeButtonDown() const { return (_inputState & (kButtonStateBits << kThreeButtonShift)) == (kButtonDown << kThreeButtonShift); } + bool threeButtonAutoDown() const { return (_inputState & (kButtonStateBits << kThreeButtonShift)) == (kButtonAutoDown << kThreeButtonShift); } + bool threeButtonAnyDown() const { return (_inputState & (kButtonAutoDown << kThreeButtonShift)) != 0; } + + bool fourButtonDown() const { return (_inputState & (kButtonStateBits << kFourButtonShift)) == (kButtonDown << kFourButtonShift); } + bool fourButtonAutoDown() const { return (_inputState & (kButtonStateBits << kFourButtonShift)) == (kButtonAutoDown << kFourButtonShift); } + bool fourButtonAnyDown() const { return (_inputState & (kButtonAutoDown << kFourButtonShift)) != 0; } + + bool mod1ButtonDown() const { return (_inputState & (kButtonStateBits << kMod1ButtonShift)) == (kButtonDown << kMod1ButtonShift); } + bool mod1ButtonAutoDown() const { return (_inputState & (kButtonStateBits << kMod1ButtonShift)) == (kButtonAutoDown << kMod1ButtonShift); } + bool mod1ButtonAnyDown() const { return (_inputState & (kButtonAutoDown << kMod1ButtonShift)) != 0; } + + bool mod2ButtonDown() const { return (_inputState & (kButtonStateBits << kMod2ButtonShift)) == (kButtonDown << kMod2ButtonShift); } + bool mod2ButtonAutoDown() const { return (_inputState & (kButtonStateBits << kMod2ButtonShift)) == (kButtonAutoDown << kMod2ButtonShift); } + bool mod2ButtonAnyDown() const { return (_inputState & (kButtonAutoDown << kMod2ButtonShift)) != 0; } + + bool mod3ButtonDown() const { return (_inputState & (kButtonStateBits << kMod3ButtonShift)) == (kButtonDown << kMod3ButtonShift); } + bool mod3ButtonAutoDown() const { return (_inputState & (kButtonStateBits << kMod3ButtonShift)) == (kButtonAutoDown << kMod3ButtonShift); } + bool mod3ButtonAnyDown() const { return (_inputState & (kButtonAutoDown << kMod3ButtonShift)) != 0; } + + bool allAutoInput() const { return (_inputState & kAllAutoBits) != 0; } + bool anyDirectionInput() const { return (_inputState & kDirectionBits) != 0; } + bool anyButtonInput() const { return (_inputState & kButtonBits) != 0; } + bool anyInput() const { return _inputState != 0; } + + void getInputLocation(Common::Point &where) const { where = _inputLocation; } + + bool anyInputBitSet(const InputBits bits) const { return (_inputState & bits) != 0; } + + bool isAltDown() const { return _altDown; } + bool isConsoleRequested() const { return _consoleRequested; } + + void clearInput() { + _inputState = kAllUpBits; + _inputLocation.x = 0; + _inputLocation.y = 0; + _consoleRequested = false; + _altDown = false; + } + +protected: + void setInputBits(const InputBits state) { _inputState = state; } + void setInputLocation(const Common::Point &where) { _inputLocation = where; } + void setConsoleRequested(bool consoleRequested) { _consoleRequested = consoleRequested; } + void setAltDown(bool altDown) { _altDown = altDown; } + + InputBits _inputState; + Common::Point _inputLocation; + bool _consoleRequested; + bool _altDown; +}; + +class InputHandler { +public: + static InputHandler *setInputHandler(InputHandler*); + static InputHandler *getCurrentHandler() { return _inputHandler; } + static void pollForInput(); + static void getInput(Input&, Hotspot*&); + static void readInputDevice(Input&); + static void invalHotspots() { _invalHotspots = true; } + static InputBits getCurrentFilter() { return _lastFilter; } + + InputHandler(InputHandler*); + virtual ~InputHandler(); + + virtual void setNextHandler(InputHandler *nextHandler) { _nextHandler = nextHandler; } + virtual InputHandler *getNextHandler() { return _nextHandler; } + + virtual void handleInput(const Input &, const Hotspot *); + virtual void clickInHotspot(const Input &, const Hotspot *); + + virtual void activateHotspots(); + virtual void updateCursor(const Common::Point, const Hotspot *); + virtual bool isClickInput(const Input &, const Hotspot *); + virtual bool wantsCursor(); + + virtual bool releaseInputFocus() { return true; } + virtual void grabInputFocus() {} + + // This returns bits set for what kinds of input to accept. + virtual InputBits getInputFilter(); + + // This returns bits defining what input constitutes a "click." + virtual InputBits getClickFilter(); + + virtual void allowInput(const bool allow) { _allowInput = allow; } + +protected: + static InputHandler *_inputHandler; + static bool _invalHotspots; + static InputBits _lastFilter; + + InputHandler *_nextHandler; + bool _allowInput; +}; + + +/* + + Tracker implements "dragging". A Tracker can receive a startTracking message, + which causes it to be the current tracker, as well as setting it up as the current + input handler. In addition, only one tracker can be tracking at a time, and no + other handler can be set up as the current handler until the track finishes. By + default, there is no next input handler for a Tracker, but this behavior can be + overridden if desired. + +*/ + +class Tracker : public InputHandler { +public: + Tracker() : InputHandler(0) {} + virtual ~Tracker() {} + + virtual void handleInput(const Input &, const Hotspot *); + virtual bool stopTrackingInput(const Input &) { return false; } + + virtual void startTracking(const Input &); + virtual void stopTracking(const Input &); + virtual void continueTracking(const Input &) {} + + bool isTracking() { return this == _currentTracker; } + bool isClickInput(const Input &, const Hotspot *); + + bool releaseInputFocus() { return !isTracking(); } + +protected: + static Tracker *_currentTracker; + + InputHandler *_savedHandler; +}; + +class JMPPPInput { +public: + static bool isMenuButtonPressInput(const Input &input) { return input.twoButtonDown(); } + + static InputBits getClickInputFilter() { return kFilterTwoButton; } + static bool isClickInput(const Input &input) { return input.twoButtonDown(); } + static bool isDraggingInput(const Input &input) { return input.twoButtonAnyDown(); } + static bool isPressingInput(const Input &input) { return input.twoButtonAnyDown(); } + + static bool isRaiseInventoryInput(const Input &input) { return input.leftFireButtonDown(); } + static bool isRaiseBiochipsInput(const Input &input) { return input.rightFireButtonDown(); } + static InputBits getItemPanelsInputFilter() { return kFilterLeftFireButton | kFilterRightFireButton; } + + static bool isToggleAIMiddleInput(const Input &input) { return input.threeButtonDown(); } + + static bool isToggleInfoInput(const Input &input) { return input.fourButtonDown(); } + + // Hmmmmm.... + static bool isEasterEggModifierInput(const Input &input) { return input.isAltDown(); } + + static bool isTogglePauseInput(const Input &input) { return input.mod3ButtonDown(); } +}; + +} // End of namespace Pegasus + +#define InputDevice (::Pegasus::InputDeviceManager::instance()) + +#endif diff --git a/engines/pegasus/interaction.h b/engines/pegasus/interaction.h new file mode 100644 index 0000000000..b1318563ac --- /dev/null +++ b/engines/pegasus/interaction.h @@ -0,0 +1,110 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_INTERACTION_H +#define PEGASUS_INTERACTION_H + +#include "pegasus/input.h" +#include "pegasus/util.h" + +namespace Pegasus { + +static const InteractionID kNoInteractionID = -1; + +class Neighborhood; + +class GameInteraction : public IDObject, public InputHandler { +public: + GameInteraction(const InteractionID id, Neighborhood *nextHandler) : IDObject(id), InputHandler((InputHandler *)nextHandler) { + _isInteracting = false; + _savedHandler = 0; + _owner = nextHandler; + } + + // If the interaction is open (_isInteracting == true), it's too late to do anything + // about it here. + virtual ~GameInteraction() {} + + // startInteraction and stopInteraction are called by the outside world to + // start and stop the interaction sequence. + // isInteracting returns a bool indicating whether or not the interaction + // is going. + void startInteraction() { + if (!isInteracting()) { + openInteraction(); + initInteraction(); + _isInteracting = true; + _savedHandler = InputHandler::setInputHandler(this); + } + } + void stopInteraction() { + if (isInteracting()) { + closeInteraction(); + _isInteracting = false; + if (InputHandler::_inputHandler == this) + InputHandler::setInputHandler(_savedHandler); + } + } + void startOverInteraction() { + if (isInteracting()) + resetInteraction(); + } + bool isInteracting() const { return _isInteracting; } + Neighborhood *getOwner() const { return _owner; } + + virtual Common::String getBriefingMovie() { return ""; } + virtual Common::String getEnvScanMovie() { return ""; } + virtual long getNumHints() { return 0; } + virtual Common::String getHintMovie(uint) { return ""; } + virtual bool canSolve() { return false; } + + virtual void setSoundFXLevel(const uint16) {} + virtual void setAmbienceLevel(const uint16) {} + + virtual void doSolve() {} + +protected: + // Subclasses override openInteraction and closeInteraction to perform + // specific initialization and cleanup. Override resetInteraction to + // "start the interaction over." resetInteraction is called only when + // the interaction is already open. + // These functions are only called in pairs, never two opens or closes + // in a row. + virtual void openInteraction() {} + virtual void initInteraction() {} + virtual void closeInteraction() {} + virtual void resetInteraction() {} + + InputHandler *_savedHandler; + Neighborhood *_owner; + +private: + // Private so that only StartInteraction and StopInteraction can touch it. + bool _isInteracting; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/interface.cpp b/engines/pegasus/interface.cpp new file mode 100644 index 0000000000..5e5188886e --- /dev/null +++ b/engines/pegasus/interface.cpp @@ -0,0 +1,667 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/compass.h" +#include "pegasus/energymonitor.h" +#include "pegasus/interface.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/biochipitem.h" +#include "pegasus/items/inventory/inventoryitem.h" + +namespace Pegasus { + +Interface *g_interface = 0; + +Interface::Interface() : InputHandler(0), _interfaceNotification(kInterfaceNotificationID, (NotificationManager *)((PegasusEngine *)g_engine)), + _currentItemSpot(kCurrentItemSpotID), _currentBiochipSpot(kCurrentBiochipSpotID), + _background1(kInterface1ID), _background2(kInterface2ID), _background3(kInterface3ID), + _background4(kInterface4ID), _datePicture(kDateID), _inventoryPush(kInventoryPushID), + _inventoryLid(kInventoryLidID, kNoDisplayElement), + _inventoryPanel(kNoDisplayElement, (InputHandler *)((PegasusEngine *)g_engine), ((PegasusEngine *)g_engine)->getItemsInventory()), + _biochipPush(kBiochipPushID), _biochipLid(kBiochipLidID, kNoDisplayElement), + _biochipPanel(kNoDisplayElement, (InputHandler *)((PegasusEngine *)g_engine), ((PegasusEngine *)g_engine)->getBiochipsInventory()) { + g_energyMonitor = 0; + _previousHandler = 0; + _inventoryRaised = false; + _biochipRaised = false; + _playingEndMessage = false; + g_interface = this; +} + +Interface::~Interface() { + throwAwayInterface(); + g_interface = 0; +} + +void Interface::throwAwayInterface() { + g_allHotspots.removeOneHotspot(kCurrentItemSpotID); + g_allHotspots.removeOneHotspot(kCurrentBiochipSpotID); + + throwAwayBackground(); + throwAwayDateMonitor(); + throwAwayEnergyMonitor(); + throwAwayAIArea(); + throwAwayCompass(); + throwAwayNotifications(); + throwAwayInventoryPanel(); + throwAwayBiochipPanel(); +} + +void Interface::validateBackground() { + if (!_background1.isSurfaceValid()) { + _background1.initFromPICTFile("Images/Interface/3DInterface Left"); + _background2.initFromPICTFile("Images/Interface/3DInterface Top"); + _background3.initFromPICTFile("Images/Interface/3DInterface Right"); + _background4.initFromPICTFile("Images/Interface/3DInterface Bottom"); + + _background1.setDisplayOrder(kBackground1Order); + _background1.startDisplaying(); + _background1.moveElementTo(kBackground1Left, kBackground1Top); + + _background2.setDisplayOrder(kBackground2Order); + _background2.startDisplaying(); + _background2.moveElementTo(kBackground2Left, kBackground2Top); + + _background3.setDisplayOrder(kBackground2Order); + _background3.startDisplaying(); + _background3.moveElementTo(kBackground3Left, kBackground3Top); + + _background4.setDisplayOrder(kBackground4Order); + _background4.startDisplaying(); + _background4.moveElementTo(kBackground4Left, kBackground4Top); + + _background1.show(); + _background2.show(); + _background3.show(); + _background4.show(); + } +} + +void Interface::throwAwayBackground() { + _background1.stopDisplaying(); + _background1.deallocateSurface(); + _background2.stopDisplaying(); + _background2.deallocateSurface(); + _background3.stopDisplaying(); + _background3.deallocateSurface(); + _background4.stopDisplaying(); + _background4.deallocateSurface(); +} + +void Interface::validateDateMonitor() { + if (!_datePicture.isSurfaceValid()) { + _datePicture.setDisplayOrder(kDateOrder); + _datePicture.startDisplaying(); + _datePicture.moveElementTo(kDateLeft, kDateTop); + _datePicture.show(); + } +} + +void Interface::throwAwayDateMonitor() { + _datePicture.stopDisplaying(); + _datePicture.deallocateSurface(); +} + +void Interface::setDate(const uint16 dateResID) { + validateDateMonitor(); + _datePicture.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, dateResID); + _datePicture.triggerRedraw(); +} + +void Interface::validateCompass() { + if (!g_compass) { + new Compass(); + g_compass->initCompass(); + g_compass->setDisplayOrder(kCompassOrder); + g_compass->startDisplaying(); + g_compass->moveElementTo(kCompassLeft, kCompassTop); + g_compass->show(); + } +} + +void Interface::throwAwayCompass() { + delete g_compass; +} + +void Interface::validateNotifications() { + _interfaceNotification.notifyMe(this, kInterfaceNotificationFlags, kInterfaceNotificationFlags); + _inventoryLidCallBack.setNotification(&_interfaceNotification); + _inventoryPushCallBack.setNotification(&_interfaceNotification); + _biochipLidCallBack.setNotification(&_interfaceNotification); + _biochipPushCallBack.setNotification(&_interfaceNotification); +} + +void Interface::throwAwayNotifications() { + _interfaceNotification.cancelNotification(this); +} + +void Interface::validateAIArea() { + if (!g_AIArea) { + new AIArea((InputHandler *)((PegasusEngine *)g_engine)); + if (g_AIArea) + g_AIArea->initAIArea(); + } +} + +void Interface::throwAwayAIArea() { + delete g_AIArea; +} + +void Interface::validateInventoryPanel() { + if (!_inventoryPanel.isSurfaceValid()) { + _inventoryPanel.initInventoryImage(&_inventoryPush); + _inventoryPanel.moveElementTo(kInventoryPushLeft, kInventoryPushTop); + _inventoryPush.setSlideDirection(kSlideUpMask); + _inventoryPush.setInAndOutElements(&_inventoryPanel, 0); + _inventoryPush.setDisplayOrder(kInventoryPushOrder); + _inventoryPush.startDisplaying(); + + _inventoryLid.useFileName("Images/Lids/Inventory Lid Sequence"); + _inventoryLid.useTransparent(true); + _inventoryLid.openFrameSequence(); + _inventoryLid.moveElementTo(kInventoryLidLeft, kInventoryLidTop); + _inventoryLid.setDisplayOrder(kInventoryLidOrder); + _inventoryLid.startDisplaying(); + + _inventoryPushCallBack.initCallBack(&_inventoryPush, kCallBackAtExtremes); + _inventoryLidCallBack.initCallBack(&_inventoryLid, kCallBackAtExtremes); + + _inventoryUp = false; + _inventoryRaised = false; + + Item *item = getCurrentInventoryItem(); + if (item) + item->select(); + } +} + +void Interface::throwAwayInventoryPanel() { + _inventoryPanel.stopDisplaying(); + _inventoryPanel.throwAwayInventoryImage(); + _inventoryPush.stopDisplaying(); + _inventoryLid.stopDisplaying(); + _inventoryLid.closeFrameSequence(); + _inventoryPushCallBack.releaseCallBack(); + _inventoryLidCallBack.releaseCallBack(); + + Item *item = getCurrentInventoryItem(); + if (item) + item->deselect(); + + _inventoryUp = false; + _inventoryRaised = false; +} + +void Interface::validateBiochipPanel() { + if (!_biochipPanel.isSurfaceValid()) { + _biochipPanel.initInventoryImage(&_biochipPush); + _biochipPanel.moveElementTo(kBiochipPushLeft, kBiochipPushTop); + _biochipPush.setSlideDirection(kSlideUpMask); + _biochipPush.setInAndOutElements(&_biochipPanel, 0); + _biochipPush.setDisplayOrder(kBiochipPushOrder); + _biochipPush.startDisplaying(); + + _biochipLid.useFileName("Images/Lids/Biochip Lid Sequence"); + _biochipLid.useTransparent(true); + _biochipLid.openFrameSequence(); + _biochipLid.moveElementTo(kBiochipLidLeft, kBiochipLidTop); + _biochipLid.setDisplayOrder(kBiochipLidOrder); + _biochipLid.startDisplaying(); + + _biochipPushCallBack.initCallBack(&_biochipPush, kCallBackAtExtremes); + _biochipLidCallBack.initCallBack(&_biochipLid, kCallBackAtExtremes); + + _biochipUp = false; + _biochipRaised = false; + + Item *item = getCurrentBiochip(); + if (item) + item->select(); + } +} + +void Interface::throwAwayBiochipPanel() { + _biochipPanel.stopDisplaying(); + _biochipPanel.throwAwayInventoryImage(); + _biochipPush.stopDisplaying(); + _biochipLid.stopDisplaying(); + _biochipLid.closeFrameSequence(); + _biochipPushCallBack.releaseCallBack(); + _biochipLidCallBack.releaseCallBack(); + + Item *item = getCurrentBiochip(); + if (item) + item->deselect(); + + _biochipUp = false; + _biochipRaised = false; +} + +void Interface::validateEnergyMonitor() { + if (!g_energyMonitor) + new EnergyMonitor(); +} + +void Interface::throwAwayEnergyMonitor() { + delete g_energyMonitor; +} + +void Interface::createInterface() { + validateBackground(); + validateDateMonitor(); + validateCompass(); + validateNotifications(); + validateAIArea(); + validateBiochipPanel(); + validateInventoryPanel(); + validateEnergyMonitor(); + + if (!g_allHotspots.findHotspotByID(kCurrentItemSpotID)) { + _currentItemSpot.setArea(Common::Rect(76, 334, 172, 430)); + _currentItemSpot.setHotspotFlags(kShellSpotFlag); + _currentItemSpot.setActive(); + g_allHotspots.push_back(&_currentItemSpot); + } + + if (!g_allHotspots.findHotspotByID(kCurrentBiochipSpotID)) { + _currentBiochipSpot.setArea(Common::Rect(364, 334, 460, 430)); + _currentBiochipSpot.setHotspotFlags(kShellSpotFlag); + _currentBiochipSpot.setActive(); + g_allHotspots.push_back(&_currentBiochipSpot); + } +} + +InventoryResult Interface::addInventoryItem(InventoryItem *item) { + return _inventoryPanel.addInventoryItem(item); +} + +InventoryResult Interface::removeInventoryItem(InventoryItem *item) { + return _inventoryPanel.removeInventoryItem(item); +} + +void Interface::removeAllItemsFromInventory() { + _inventoryPanel.removeAllItems(); +} + +InventoryItem *Interface::getCurrentInventoryItem() { + return (InventoryItem *)_inventoryPanel.getCurrentItem(); +} + +void Interface::setCurrentInventoryItem(InventoryItem *item) { + setCurrentInventoryItemID(item->getObjectID()); +} + +void Interface::setCurrentInventoryItemID(ItemID id) { + _inventoryPanel.setCurrentItemID(id); +} + +InventoryResult Interface::addBiochip(BiochipItem *item) { + return _biochipPanel.addInventoryItem(item); +} + +void Interface::removeAllItemsFromBiochips() { + _biochipPanel.removeAllItems(); +} + +BiochipItem *Interface::getCurrentBiochip() { + return (BiochipItem *)_biochipPanel.getCurrentItem(); +} + +void Interface::setCurrentBiochip(BiochipItem *item) { + setCurrentBiochipID(item->getObjectID()); +} + +void Interface::setCurrentBiochipID(ItemID id) { + _biochipPanel.setCurrentItemID(id); +} + +void Interface::receiveNotification(Notification *notification, const NotificationFlags flags) { + if (notification == &_interfaceNotification) { + switch (flags) { + case kInventoryLidOpenFlag: + inventoryLidOpen(true); + break; + case kInventoryLidClosedFlag: + inventoryLidClosed(); + break; + case kInventoryDrawerUpFlag: + inventoryDrawerUp(); + break; + case kInventoryDrawerDownFlag: + inventoryDrawerDown(true); + break; + case kBiochipLidOpenFlag: + biochipLidOpen(true); + break; + case kBiochipLidClosedFlag: + biochipLidClosed(); + break; + case kBiochipDrawerUpFlag: + biochipDrawerUp(); + break; + case kBiochipDrawerDownFlag: + biochipDrawerDown(true); + break; + } + } +} + +void Interface::raiseInventoryDrawer(const bool doCallBacks) { + if (!_biochipUp) + _previousHandler = InputHandler::getCurrentHandler(); + + InputHandler::setInputHandler(&_inventoryPanel); + _inventoryUp = true; + _inventoryPanel.activateInventoryPicture(); + + if (doCallBacks) { + _inventoryLidCallBack.setCallBackFlag(kInventoryLidOpenFlag); + _inventoryLidCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + } + + _inventoryLid.show(); + _inventoryPush.show(); + _inventoryLid.start(); +} + +void Interface::playEndMessage() { + raiseInventoryDrawerForMessage(); + _playingEndMessage = true; + _inventoryPanel.playEndMessage(&_inventoryPush); + lowerInventoryDrawerForMessage(); + _playingEndMessage = false; +} + +void Interface::raiseInventoryDrawerForMessage() { + _inventoryPanel.disableLooping(); + raiseInventoryDrawerSync(); +} + +void Interface::lowerInventoryDrawerForMessage() { + lowerInventoryDrawerSync(); +} + +void Interface::inventoryLidOpen(const bool doCallBacks) { + _inventoryLid.stop(); + + if (doCallBacks) { + _inventoryPushCallBack.setCallBackFlag(kInventoryDrawerUpFlag); + _inventoryPushCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + } + + FaderMoveSpec moveSpec; + moveSpec.makeTwoKnotFaderSpec(60, 0, 0, 15, 1000); + _inventoryPush.startFader(moveSpec); +} + +void Interface::inventoryDrawerUp() { + _inventoryPush.stopFader(); + _inventoryPanel.panelUp(); + _inventoryRaised = true; +} + +bool Interface::isInventoryUp() { + return _inventoryRaised; +} + +bool Interface::isInventoryDown() { + return !_inventoryUp; +} + +void Interface::lowerInventoryDrawer(const bool doCallBacks) { + if (_inventoryRaised) { + _inventoryRaised = false; + + if (!_playingEndMessage) + _inventoryPanel.deactivateInventoryPicture(); + + if (doCallBacks) { + _inventoryPushCallBack.setCallBackFlag(kInventoryDrawerDownFlag); + _inventoryPushCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + } + + FaderMoveSpec moveSpec; + moveSpec.makeTwoKnotFaderSpec(60, 0, 1000, 15, 0); + _inventoryPush.startFader(moveSpec); + } +} + +void Interface::inventoryDrawerDown(const bool doCallBacks) { + _inventoryPush.stopFader(); + + if (doCallBacks) { + _inventoryLidCallBack.setCallBackFlag(kInventoryLidClosedFlag); + _inventoryLidCallBack.scheduleCallBack(kTriggerAtStart, 0, 0); + } + + _inventoryLid.setRate(-1); +} + +void Interface::inventoryLidClosed() { + _inventoryLid.stop(); + + if (!_biochipUp) + InputHandler::setInputHandler(_previousHandler); + + _inventoryLid.hide(); + _inventoryPush.hide(); + _inventoryUp = false; +} + +void Interface::raiseBiochipDrawer(const bool doCallBacks) { + if (!_inventoryUp) + _previousHandler = InputHandler::getCurrentHandler(); + + InputHandler::setInputHandler(&_biochipPanel); + _biochipUp = true; + _biochipPanel.activateInventoryPicture(); + + if (doCallBacks) { + _biochipLidCallBack.setCallBackFlag(kBiochipLidOpenFlag); + _biochipLidCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + } + + _biochipLid.show(); + _biochipPush.show(); + _biochipLid.start(); +} + +void Interface::biochipLidOpen(const bool doCallBacks) { + _biochipLid.stop(); + + if (doCallBacks) { + _biochipPushCallBack.setCallBackFlag(kBiochipDrawerUpFlag); + _biochipPushCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + } + + FaderMoveSpec moveSpec; + moveSpec.makeTwoKnotFaderSpec(60, 0, 0, 9, 1000); + _biochipPush.startFader(moveSpec); +} + +void Interface::biochipDrawerUp() { + _biochipPush.stopFader(); + _biochipPanel.panelUp(); + _biochipRaised = true; +} + +void Interface::lowerBiochipDrawer(const bool doCallBacks) { + if (_biochipRaised) { + _biochipRaised = false; + _biochipPanel.deactivateInventoryPicture(); + + if (doCallBacks) { + _biochipPushCallBack.setCallBackFlag(kBiochipDrawerDownFlag); + _biochipPushCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + } + + FaderMoveSpec moveSpec; + moveSpec.makeTwoKnotFaderSpec(60, 0, 1000, 9, 0); + _biochipPush.startFader(moveSpec); + } +} + +void Interface::biochipDrawerDown(const bool doCallBacks) { + _biochipPush.stopFader(); + + if (doCallBacks) { + _biochipLidCallBack.setCallBackFlag(kBiochipLidClosedFlag); + _biochipLidCallBack.scheduleCallBack(kTriggerAtStart, 0, 0); + } + + _biochipLid.setRate(-1); +} + +void Interface::biochipLidClosed() { + _biochipLid.stop(); + + if (!_inventoryUp) + InputHandler::setInputHandler(_previousHandler); + + _biochipLid.hide(); + _biochipPush.hide(); + _biochipUp = false; +} + +void Interface::calibrateCompass() { + uint32 currentValue = g_compass->getFaderValue(); + FaderMoveSpec compassMove; + compassMove.makeTwoKnotFaderSpec(15, 0, currentValue, 30, currentValue + 360); + + g_compass->startFader(compassMove); + + PegasusEngine *vm = (PegasusEngine *)g_engine; + + while (g_compass->isFading()) { + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + vm->refreshDisplay(); + g_compass->setFaderValue(currentValue); +} + +void Interface::calibrateEnergyBar() { + g_energyMonitor->calibrateEnergyBar(); +} + +void Interface::raiseInventoryDrawerSync() { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + raiseInventoryDrawer(false); + + while (_inventoryLid.isRunning()) { + vm->checkCallBacks(); + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + vm->refreshDisplay(); + inventoryLidOpen(false); + + while (_inventoryPush.isFading()) { + vm->checkCallBacks(); + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + vm->refreshDisplay(); + inventoryDrawerUp(); +} + +void Interface::lowerInventoryDrawerSync() { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + lowerInventoryDrawer(false); + + while (_inventoryPush.isFading()) { + vm->checkCallBacks(); + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + vm->refreshDisplay(); + inventoryDrawerDown(false); + + while (_inventoryLid.isRunning()) { + vm->checkCallBacks(); + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + vm->refreshDisplay(); + inventoryLidClosed(); +} + +void Interface::raiseBiochipDrawerSync() { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + raiseBiochipDrawer(false); + + while (_biochipLid.isRunning()) { + vm->checkCallBacks(); + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + vm->refreshDisplay(); + biochipLidOpen(false); + + while (_biochipPush.isFading()) { + vm->checkCallBacks(); + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + vm->refreshDisplay(); + biochipDrawerUp(); +} + +void Interface::lowerBiochipDrawerSync() { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + lowerBiochipDrawer(false); + + while (_biochipPush.isFading()) { + vm->checkCallBacks(); + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + vm->refreshDisplay(); + biochipDrawerDown(false); + + while (_biochipLid.isRunning()) { + vm->checkCallBacks(); + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + vm->refreshDisplay(); + biochipLidClosed(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/interface.h b/engines/pegasus/interface.h new file mode 100644 index 0000000000..a65d9a595a --- /dev/null +++ b/engines/pegasus/interface.h @@ -0,0 +1,148 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_INTERFACE_H +#define PEGASUS_INTERFACE_H + +#include "pegasus/hotspot.h" +#include "pegasus/input.h" +#include "pegasus/notification.h" +#include "pegasus/surface.h" +#include "pegasus/transition.h" +#include "pegasus/items/inventorypicture.h" + +namespace Pegasus { + +class BiochipItem; +class InventoryItem; + +class Interface : public InputHandler, public NotificationReceiver { +public: + Interface(); + virtual ~Interface(); + + void createInterface(); + + // Recalibration functions... + void calibrateCompass(); + void calibrateEnergyBar(); + void raiseInventoryDrawerSync(); + void lowerInventoryDrawerSync(); + void raiseBiochipDrawerSync(); + void lowerBiochipDrawerSync(); + + void raiseInventoryDrawer(const bool doCallBacks = true); + void raiseBiochipDrawer(const bool doCallBacks = true); + void lowerInventoryDrawer(const bool doCallBacks = true); + void lowerBiochipDrawer(const bool doCallBacks = true); + + void raiseInventoryDrawerForMessage(); + void lowerInventoryDrawerForMessage(); + bool isInventoryUp(); + bool isInventoryDown(); + + InventoryResult addInventoryItem(InventoryItem *); + InventoryResult removeInventoryItem(InventoryItem *); + void removeAllItemsFromInventory(); + InventoryItem *getCurrentInventoryItem(); + void setCurrentInventoryItem(InventoryItem *); + void setCurrentInventoryItemID(ItemID); + InventoryResult addBiochip(BiochipItem *); + void removeAllItemsFromBiochips(); + BiochipItem *getCurrentBiochip(); + void setCurrentBiochip(BiochipItem *); + void setCurrentBiochipID(ItemID); + + void setDate(const uint16); + + void playEndMessage(); + + void throwAwayInterface(); + +protected: + void validateBackground(); + void validateDateMonitor(); + void validateCompass(); + void validateNotifications(); + void validateAIArea(); + void validateInventoryPanel(); + void validateBiochipPanel(); + void validateEnergyMonitor(); + + void throwAwayBackground(); + void throwAwayDateMonitor(); + void throwAwayCompass(); + void throwAwayNotifications(); + void throwAwayAIArea(); + void throwAwayInventoryPanel(); + void throwAwayBiochipPanel(); + void throwAwayEnergyMonitor(); + + void receiveNotification(Notification *, const NotificationFlags); + void inventoryLidOpen(const bool doCallBacks); + void inventoryLidClosed(); + void inventoryDrawerUp(); + void inventoryDrawerDown(const bool doCallBacks); + void biochipLidOpen(const bool doCallBacks); + void biochipLidClosed(); + void biochipDrawerUp(); + void biochipDrawerDown(const bool doCallBacks); + + Picture _background1; + Picture _background2; + Picture _background3; + Picture _background4; + + Picture _datePicture; + + InputHandler *_previousHandler; + + Push _inventoryPush; + SpriteSequence _inventoryLid; + NotificationCallBack _inventoryPushCallBack; + NotificationCallBack _inventoryLidCallBack; + InventoryItemsPicture _inventoryPanel; + bool _inventoryUp, _inventoryRaised; + + Push _biochipPush; + SpriteSequence _biochipLid; + NotificationCallBack _biochipPushCallBack; + NotificationCallBack _biochipLidCallBack; + BiochipPicture _biochipPanel; + bool _biochipUp, _biochipRaised; + + Hotspot _currentItemSpot; + Hotspot _currentBiochipSpot; + + Notification _interfaceNotification; + + bool _playingEndMessage; +}; + +extern Interface *g_interface; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/autodragger.cpp b/engines/pegasus/items/autodragger.cpp new file mode 100644 index 0000000000..de03f8118f --- /dev/null +++ b/engines/pegasus/items/autodragger.cpp @@ -0,0 +1,91 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/elements.h" +#include "pegasus/items/autodragger.h" + +namespace Pegasus { + +AutoDragger::AutoDragger() { + _draggingElement = NULL; + _lastTime = 0; + initCallBack(this, kCallBackAtExtremes); +} + +void AutoDragger::autoDrag(DisplayElement *dragElement, const Common::Point &startPoint, const Common::Point &stopPoint, + TimeValue dragTime, TimeScale dragScale) { + _draggingElement = dragElement; + + if (_draggingElement) { + _startLocation = startPoint; + _stopLocation = stopPoint; + _lastTime = 0; + _done = false; + _draggingElement->moveElementTo(_startLocation.x, _startLocation.y); + setScale(dragScale); + setSegment(0, dragTime); + setTime(0); + scheduleCallBack(kTriggerAtStop, 0, 0); + startIdling(); + start(); + } else { + stopDragging(); + } +} + +void AutoDragger::stopDragging() { + cancelCallBack(); + stopIdling(); + _draggingElement = 0; + _startLocation = Common::Point(); + _stopLocation = Common::Point(); + _lastTime = 0; + _done = true; +} + +bool AutoDragger::isDragging() { + return isIdling(); +} + +void AutoDragger::useIdleTime() { + TimeValue thisTime = getTime(); + + if (thisTime != _lastTime) { + int32 offsetX = (_stopLocation.x - _startLocation.x) * (int32)thisTime / (int32)getDuration(); + int32 offsetY = (_stopLocation.y - _startLocation.y) * (int32)thisTime / (int32)getDuration(); + _draggingElement->moveElementTo(_startLocation.x + offsetX, _startLocation.y + offsetY); + _lastTime = thisTime; + } + + if (_done) + stopDragging(); +} + +void AutoDragger::callBack() { + if (isIdling()) + _done = true; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/autodragger.h b/engines/pegasus/items/autodragger.h new file mode 100644 index 0000000000..6783fdf9a3 --- /dev/null +++ b/engines/pegasus/items/autodragger.h @@ -0,0 +1,57 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_AUTODRAGGER_H +#define PEGASUS_ITEMS_AUTODRAGGER_H + +#include "pegasus/timers.h" + +namespace Pegasus { + +class DisplayElement; + +class AutoDragger : private Idler, private TimeBase, private TimeBaseCallBack { +public: + AutoDragger(); + virtual ~AutoDragger() {} + + void autoDrag(DisplayElement *, const Common::Point &, const Common::Point &, TimeValue, TimeScale); + bool isDragging(); + void stopDragging(); + +protected: + void useIdleTime(); + void callBack(); + + DisplayElement *_draggingElement; + Common::Point _startLocation, _stopLocation; + TimeValue _lastTime; + bool _done; +}; + +} // End of namespace Pegasus + +#endif + diff --git a/engines/pegasus/items/biochips/aichip.cpp b/engines/pegasus/items/biochips/aichip.cpp new file mode 100644 index 0000000000..5e9fc9f8c3 --- /dev/null +++ b/engines/pegasus/items/biochips/aichip.cpp @@ -0,0 +1,279 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/aichip.h" +#include "pegasus/neighborhood/neighborhood.h" + +namespace Pegasus { + +// indexed by [number of hints][number of solves (0, 1, or 2)][which button to highlight] +static const ItemState s_highlightState[4][3][7] = { + { + {kAI000, -1, -1, -1, -1, kAI005, kAI006}, + {kAI010, -1, -1, -1, -1, kAI015, kAI016}, + {kAI020, -1, -1, -1, kAI024, -1, -1} + }, + { + {kAI100, kAI101, -1, -1, -1, kAI105, kAI106}, + {kAI110, kAI111, -1, -1, -1, kAI115, kAI116}, + {kAI120, kAI121, -1, -1, kAI124, kAI125, kAI126} + }, + { + {kAI200, kAI201, kAI202, -1, -1, kAI205, kAI206}, + {kAI210, kAI211, kAI212, -1, -1, kAI215, kAI216}, + {kAI220, kAI221, kAI222, -1, kAI224, kAI225, kAI226} + }, + { + {kAI300, kAI301, kAI302, kAI303, -1, kAI305, kAI306}, + {kAI310, kAI311, kAI312, kAI313, -1, kAI315, kAI316}, + {kAI320, kAI321, kAI322, kAI323, kAI324, kAI325, kAI326} + } +}; + +AIChip *g_AIChip = 0; + +AIChip::AIChip(const ItemID id, const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) : + BiochipItem(id, neighborhood, room, direction), _briefingSpot(kAIBriefingSpotID), _scanSpot(kAIScanSpotID), + _hint1Spot(kAIHint1SpotID), _hint2Spot(kAIHint2SpotID), _hint3Spot(kAIHint3SpotID), _solveSpot(kAISolveSpotID) { + _briefingSpot.setArea(Common::Rect(kAIMiddleAreaLeft + 10, kAIMiddleAreaTop + 27, kAIMiddleAreaLeft + 10 + 81, kAIMiddleAreaTop + 27 + 31)); + _briefingSpot.setHotspotFlags(kAIBiochipSpotFlag); + g_allHotspots.push_back(&_briefingSpot); + + _scanSpot.setArea(Common::Rect(kAIMiddleAreaLeft + 100, kAIMiddleAreaTop + 27, kAIMiddleAreaLeft + 100 + 81, kAIMiddleAreaTop + 27 + 31)); + _scanSpot.setHotspotFlags(kAIBiochipSpotFlag); + g_allHotspots.push_back(&_scanSpot); + + _hint1Spot.setArea(Common::Rect(kAIMiddleAreaLeft + 70, kAIMiddleAreaTop + 67, kAIMiddleAreaLeft + 70 + 21, kAIMiddleAreaTop + 67 + 21)); + _hint1Spot.setHotspotFlags(kAIBiochipSpotFlag); + g_allHotspots.push_back(&_hint1Spot); + + _hint2Spot.setArea(Common::Rect(kAIMiddleAreaLeft + 91, kAIMiddleAreaTop + 67, kAIMiddleAreaLeft + 91 + 20, kAIMiddleAreaTop + 67 + 21)); + _hint2Spot.setHotspotFlags(kAIBiochipSpotFlag); + g_allHotspots.push_back(&_hint2Spot); + + _hint3Spot.setArea(Common::Rect(kAIMiddleAreaLeft + 111, kAIMiddleAreaTop + 67, kAIMiddleAreaLeft + 111 + 20, kAIMiddleAreaTop + 67 + 21)); + _hint3Spot.setHotspotFlags(kAIBiochipSpotFlag); + g_allHotspots.push_back(&_hint3Spot); + + _solveSpot.setArea(Common::Rect(kAIMiddleAreaLeft + 131, kAIMiddleAreaTop + 67, kAIMiddleAreaLeft + 131 + 50, kAIMiddleAreaTop + 67 + 21)); + _solveSpot.setHotspotFlags(kAIBiochipSpotFlag); + g_allHotspots.push_back(&_solveSpot); + + _playingMovie = false; + setItemState(kAI000); + + g_AIChip = this; +} + +AIChip::~AIChip() { + g_AIChip = NULL; + + g_allHotspots.removeOneHotspot(kAIBriefingSpotID); + g_allHotspots.removeOneHotspot(kAIScanSpotID); + g_allHotspots.removeOneHotspot(kAIHint1SpotID); + g_allHotspots.removeOneHotspot(kAIHint2SpotID); + g_allHotspots.removeOneHotspot(kAIHint3SpotID); + g_allHotspots.removeOneHotspot(kAISolveSpotID); +} + +void AIChip::select() { + BiochipItem::select(); + setUpAIChip(); +} + +void AIChip::takeSharedArea() { + setUpAIChip(); +} + +void AIChip::setUpAIChip() { + if (!_playingMovie) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + uint numSolves; + if (GameState.getWalkthroughMode()) { + if (vm->canSolve()) + numSolves = 2; + else + numSolves = 1; + } else { + numSolves = 0; + } + + setItemState(s_highlightState[vm->getNumHints()][numSolves][0]); + } +} + +// Only does something when there are hints or solves available. +void AIChip::setUpAIChipRude() { + if (!_playingMovie) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + uint numSolves; + if (GameState.getWalkthroughMode()) { + if (vm->canSolve()) + numSolves = 2; + else + numSolves = 1; + } else { + numSolves = 0; + } + + uint numHints = vm->getNumHints(); + if (numSolves == 2 || numHints != 0) + setItemState(s_highlightState[numHints][numSolves][0]); + } +} + +void AIChip::activateAIHotspots() { + PegasusEngine *vm = (PegasusEngine *)g_engine; + _briefingSpot.setActive(); + _scanSpot.setActive(); + + switch (vm->getNumHints()) { + case 3: + _hint3Spot.setActive(); + // fall through + case 2: + _hint2Spot.setActive(); + // fall through + case 1: + _hint1Spot.setActive(); + break; + } + + if (GameState.getWalkthroughMode() && vm->canSolve()) + _solveSpot.setActive(); +} + +void AIChip::showBriefingClicked() { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + _playingMovie = true; + + uint numSolves; + if (GameState.getWalkthroughMode()) { + if (vm->canSolve()) + numSolves = 2; + else + numSolves = 1; + } else { + numSolves = 0; + } + + ItemState newState = s_highlightState[vm->getNumHints()][numSolves][kAIBriefingSpotID - kAIHint1SpotID + 1]; + if (newState != -1) + setItemState(newState); +} + +void AIChip::showEnvScanClicked() { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + _playingMovie = true; + + uint numSolves; + if (GameState.getWalkthroughMode()) { + if (vm->canSolve()) + numSolves = 2; + else + numSolves = 1; + } else { + numSolves = 0; + } + + ItemState newState = s_highlightState[vm->getNumHints()][numSolves][kAIScanSpotID - kAIHint1SpotID + 1]; + + if (newState != -1) + setItemState(newState); +} + +void AIChip::clearClicked() { + _playingMovie = false; + setUpAIChip(); +} + +void AIChip::clickInAIHotspot(HotSpotID id) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + Common::String movieName; + + switch (id) { + case kAIBriefingSpotID: + movieName = vm->getBriefingMovie(); + break; + case kAIScanSpotID: + movieName = vm->getEnvScanMovie(); + break; + case kAIHint1SpotID: + movieName = vm->getHintMovie(1); + break; + case kAIHint2SpotID: + movieName = vm->getHintMovie(2); + break; + case kAIHint3SpotID: + movieName = vm->getHintMovie(3); + break; + case kAISolveSpotID: + g_neighborhood->doSolve(); + break; + } + + ItemState state = getItemState(); + + if (!movieName.empty()) { + _playingMovie = true; + + uint numSolves; + if (GameState.getWalkthroughMode()) { + if (vm->canSolve()) + numSolves = 2; + else + numSolves = 1; + } else { + numSolves = 0; + } + + ItemState newState = s_highlightState[vm->getNumHints()][numSolves][id - kAIHint1SpotID + 1]; + + if (newState != -1) + setItemState(newState); + + if (g_AIArea) { + vm->prepareForAIHint(movieName); + g_AIArea->playAIMovie(kRightAreaSignature, movieName, false, kHintInterruption); + vm->cleanUpAfterAIHint(movieName); + } + + if (newState != -1) + setItemState(state); + + _playingMovie = false; + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/biochips/aichip.h b/engines/pegasus/items/biochips/aichip.h new file mode 100644 index 0000000000..7a33953612 --- /dev/null +++ b/engines/pegasus/items/biochips/aichip.h @@ -0,0 +1,69 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_BIOCHIPS_AICHIP_H +#define PEGASUS_ITEMS_BIOCHIPS_AICHIP_H + +#include "pegasus/hotspot.h" +#include "pegasus/items/biochips/biochipitem.h" + +namespace Pegasus { + +class AIChip : public BiochipItem { +public: + AIChip(const ItemID, const NeighborhoodID, const RoomID, const DirectionConstant); + virtual ~AIChip(); + + void select(); + + void setUpAIChip(); + + // Called to set up the AI chip when the AI chip is the current chip but does not + // own the center area. + void setUpAIChipRude(); + void activateAIHotspots(); + void clickInAIHotspot(HotSpotID); + + void takeSharedArea(); + + void showBriefingClicked(); + void showEnvScanClicked(); + void clearClicked(); + +protected: + Hotspot _briefingSpot; + Hotspot _scanSpot; + Hotspot _hint1Spot; + Hotspot _hint2Spot; + Hotspot _hint3Spot; + Hotspot _solveSpot; + bool _playingMovie; +}; + +extern AIChip *g_AIChip; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/biochips/biochipitem.cpp b/engines/pegasus/items/biochips/biochipitem.cpp new file mode 100644 index 0000000000..5686948937 --- /dev/null +++ b/engines/pegasus/items/biochips/biochipitem.cpp @@ -0,0 +1,95 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/stream.h" + +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/biochipitem.h" + +namespace Pegasus { + +BiochipItem::BiochipItem(const ItemID id, const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) : + Item(id, neighborhood, room, direction) { + + PegasusEngine *vm = (PegasusEngine *)g_engine; + + Common::SeekableReadStream *biochipInfo = vm->_resFork->getResource(MKTAG('B', 'i', 'o', 'I'), kItemBaseResID + id); + if (biochipInfo) { + _biochipInfoPanelTime = biochipInfo->readUint32BE(); + delete biochipInfo; + } else { + _biochipInfoPanelTime = 0; + } + + Common::SeekableReadStream *rightInfo = vm->_resFork->getResource(MKTAG('R', 'g', 'h', 't'), kItemBaseResID + id); + if (!rightInfo) + error("Could not find right info for biochip %d", id); + + _rightAreaInfo = readItemState(rightInfo); + delete rightInfo; + + setItemState(kNormalItem); +} + +BiochipItem::~BiochipItem() { + delete[] _rightAreaInfo.entries; +} + +ItemType BiochipItem::getItemType() { + return kBiochipItemType; +} + +TimeValue BiochipItem::getRightAreaTime() const { + if (!_rightAreaInfo.entries) + return 0xffffffff; + + TimeValue time; + ItemState state; + + findItemStateEntryByState(_rightAreaInfo, _itemState, time); + if (time == 0xffffffff) + getItemStateEntry(_rightAreaInfo, 0, state, time); + + return time; +} + +// Must affect images in right area. +void BiochipItem::select() { + Item::select(); + + if (g_AIArea) + g_AIArea->setAIAreaToTime(kBiochipSignature, kRightAreaSignature, getRightAreaTime()); +} + +void BiochipItem::deselect() { + Item::deselect(); + + if (g_AIArea) + g_AIArea->setAIAreaToTime(kBiochipSignature, kRightAreaSignature, 0xffffffff); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/biochips/biochipitem.h b/engines/pegasus/items/biochips/biochipitem.h new file mode 100644 index 0000000000..24b9ae699a --- /dev/null +++ b/engines/pegasus/items/biochips/biochipitem.h @@ -0,0 +1,54 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_BIOCHIPS_BIOCHIPITEM_H +#define PEGASUS_ITEMS_BIOCHIPS_BIOCHIPITEM_H + +#include "pegasus/items/item.h" + +namespace Pegasus { + +class BiochipItem : public Item { +public: + BiochipItem(const ItemID, const NeighborhoodID, const RoomID, const DirectionConstant); + virtual ~BiochipItem(); + + virtual ItemType getItemType(); + + TimeValue getPanelTime() const { return _biochipInfoPanelTime; } + TimeValue getRightAreaTime() const; + + // Must affect images in right area. + virtual void select(); + virtual void deselect(); + +protected: + TimeValue _biochipInfoPanelTime; + ItemStateInfo _rightAreaInfo; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/biochips/mapchip.cpp b/engines/pegasus/items/biochips/mapchip.cpp new file mode 100644 index 0000000000..69050d5193 --- /dev/null +++ b/engines/pegasus/items/biochips/mapchip.cpp @@ -0,0 +1,106 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/gamestate.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/mapchip.h" +#include "pegasus/neighborhood/neighborhood.h" + +namespace Pegasus { + +MapChip *g_map = 0; + +MapChip::MapChip(const ItemID id, const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) : + BiochipItem(id, neighborhood, room, direction) { + g_map = this; + setItemState(kMapUnavailable); +} + +MapChip::~MapChip() { + g_map = 0; +} + +void MapChip::writeToStream(Common::WriteStream *stream) { + return _image.writeToStream(stream); +} + +void MapChip::readFromStream(Common::ReadStream *stream) { + return _image.readFromStream(stream); +} + +void MapChip::select() { + BiochipItem::select(); + moveToMapLocation(GameState.getCurrentNeighborhood(), GameState.getCurrentRoom(), GameState.getCurrentDirection()); + _image.show(); +} + +void MapChip::takeSharedArea() { + _image.show(); +} + +void MapChip::giveUpSharedArea() { + _image.hide(); +} + +void MapChip::deselect() { + BiochipItem::deselect(); + _image.unloadImage(); +} + +void MapChip::moveToMapLocation(const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant dir) { + AirQuality airQuality; + + if (g_neighborhood) + airQuality = g_neighborhood->getAirQuality(room); + else + airQuality = kAirQualityGood; + + switch (neighborhood) { + case kMarsID: + if (airQuality == kAirQualityVacuum) { + if (room >= kMars35 && room <= kMars39) { + setItemState(kMapEngaged); + if (isSelected() && g_AIArea && g_AIArea->getMiddleAreaOwner() == kBiochipSignature) + _image.loadGearRoomIfNecessary(); + } else { + setItemState(kMapEngaged); + if (isSelected() && g_AIArea && g_AIArea->getMiddleAreaOwner() == kBiochipSignature) + _image.loadMazeIfNecessary(); + } + + _image.moveToMapLocation(neighborhood, room, dir); + } else { + _image.unloadImage(); + setItemState(kMapUnavailable); + } + break; + default: + _image.unloadImage(); + setItemState(kMapUnavailable); + break; + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/biochips/mapchip.h b/engines/pegasus/items/biochips/mapchip.h new file mode 100644 index 0000000000..6690090aa4 --- /dev/null +++ b/engines/pegasus/items/biochips/mapchip.h @@ -0,0 +1,64 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_BIOCHIPS_MAPCHIP_H +#define PEGASUS_ITEMS_BIOCHIPS_MAPCHIP_H + +#include "pegasus/items/biochips/biochipitem.h" +#include "pegasus/items/biochips/mapimage.h" + +namespace Common { + class ReadStream; + class WriteStream; +} + +namespace Pegasus { + +class MapChip : public BiochipItem { +public: + MapChip(const ItemID, const NeighborhoodID, const RoomID, const DirectionConstant); + virtual ~MapChip(); + + void select(); + void deselect(); + void takeSharedArea(); + void giveUpSharedArea(); + + void moveToMapLocation(const NeighborhoodID, const RoomID, const DirectionConstant); + + void writeToStream(Common::WriteStream *); + void readFromStream(Common::ReadStream *); + + bool beenToMaze() { return _image.anyFlagSet(); } + +protected: + MapImage _image; +}; + +extern MapChip *g_map; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/biochips/mapimage.cpp b/engines/pegasus/items/biochips/mapimage.cpp new file mode 100644 index 0000000000..bd7b8ff910 --- /dev/null +++ b/engines/pegasus/items/biochips/mapimage.cpp @@ -0,0 +1,443 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/items/biochips/mapimage.h" + +namespace Pegasus { + +#define FLAG_TO_INDEX(flag) ((flag) >> 2) +#define INDEX_TO_FLAG(index) ((index) << 2) + +#define ROOM_TO_INDEX(room) \ + (((room) >= kMars35 && (room) <= kMars39) ? ((room) - kMars35) : \ + (((room) == kMars60) ? (kMars39 - kMars35 + 1) : \ + ((room) - kMarsMaze004 + kMars39 - kMars35 + 2))) + +#define INDEX_TO_ROOM(index) \ + (((index) <= ROOM_TO_INDEX(kMars39)) ? \ + (((index) - ROOM_TO_INDEX(kMars35)) + kMars35) : \ + ((index) <= ROOM_TO_INDEX(kMars60,)) ? kMars60 : \ + ((((index) - ROOM_TO_INDEX(kMarsMaze004))) + kMarsMaze004)) + +#define ROOM_TO_FLAG(room, dir) (INDEX_TO_FLAG(ROOM_TO_INDEX(room)) | (dir)) + +#define FLAG_TO_ROOM(flag) (INDEX_TO_ROOM(FLAG_TO_INDEX(flag))) + +#define FLAG_TO_DIRECTION(flag) ((flag) & 3) + +static const int kGearRoomFlagLow = ROOM_TO_FLAG(kMars35, kNorth); +static const int kGearRoomFlagHigh = ROOM_TO_FLAG(kMars39, kWest); + +static const int kMazeFlagLow = ROOM_TO_FLAG(kMars60, kNorth); +static const int kMazeFlagHigh = ROOM_TO_FLAG(kMarsMaze200, kWest); + +static const CoordType kGearRoomScreenOffsetX = 49; +static const CoordType kGearRoomScreenOffsetY = 47; + +static const CoordType kGearRoomGridOriginX = 1; +static const CoordType kGearRoomGridOriginY = 4; + +static const CoordType kMazeScreenOffsetX = 16; +static const CoordType kMazeScreenOffsetY = 20; + +static const CoordType kMazeGridOriginX = 6; +static const CoordType kMazeGridOriginY = 1; + +static const CoordType kGridWidth = 4; +static const CoordType kGridHeight = 4; + +static const uint16 kMapOfMazePICTID = 906; +static const uint16 kMapOfGearRoomPICTID = 907; + +static const int s_mapCoords[MapImage::kNumMappingRooms][2] = { + /* kMars35 */ { 0, 0 }, + /* kMars36 */ { 1, 0 }, + /* kMars37 */ { 2, 0 }, + /* kMars38 */ { 3, 0 }, + /* kMars39 */ { 4, 0 }, + /* kMars60 */ { 19, 9 }, + /* kMarsMaze004 */ { 18, 9 }, + /* kMarsMaze005 */ { 18, 10 }, + /* kMarsMaze006 */ { 17, 10 }, + /* kMarsMaze007 */ { 16, 10 }, + /* kMarsMaze008 */ { 15, 10 }, + /* kMarsMaze009 */ { 14, 10 }, + /* kMarsMaze010 */ { 14, 9 }, + /* kMarsMaze011 */ { 14, 8 }, + /* kMarsMaze012 */ { 14, 7 }, + /* kMarsMaze015 */ { 16, 7 }, + /* kMarsMaze016 */ { 14, 11 }, + /* kMarsMaze017 */ { 14, 12 }, + /* kMarsMaze018 */ { 15, 12 }, + /* kMarsMaze019 */ { 16, 12 }, + /* kMarsMaze020 */ { 16, 13 }, + /* kMarsMaze021 */ { 16, 14 }, + /* kMarsMaze022 */ { 16, 15 }, + /* kMarsMaze023 */ { 17, 15 }, + /* kMarsMaze024 */ { 18, 15 }, + /* kMarsMaze025 */ { 18, 14 }, + /* kMarsMaze026 */ { 18, 13 }, + /* kMarsMaze027 */ { 18, 12 }, + /* kMarsMaze028 */ { 18, 11 }, + /* kMarsMaze031 */ { 19, 14 }, + /* kMarsMaze032 */ { 20, 14 }, + /* kMarsMaze033 */ { 20, 13 }, + /* kMarsMaze034 */ { 20, 12 }, + /* kMarsMaze035 */ { 20, 11 }, + /* kMarsMaze036 */ { 21, 11 }, + /* kMarsMaze037 */ { 15, 15 }, + /* kMarsMaze038 */ { 14, 15 }, + /* kMarsMaze039 */ { 13, 15 }, + /* kMarsMaze042 */ { 10, 15 }, + /* kMarsMaze043 */ { 9, 15 }, + /* kMarsMaze044 */ { 8, 15 }, + /* kMarsMaze045 */ { 7, 15 }, + /* kMarsMaze046 */ { 6, 15 }, + /* kMarsMaze047 */ { 5, 15 }, + /* kMarsMaze049 */ { 13, 14 }, + /* kMarsMaze050 */ { 12, 14 }, + /* kMarsMaze051 */ { 11, 14 }, + /* kMarsMaze052 */ { 10, 14 }, + /* kMarsMaze053 */ { 10, 13 }, + /* kMarsMaze054 */ { 9, 13 }, + /* kMarsMaze055 */ { 8, 13 }, + /* kMarsMaze056 */ { 8, 12 }, + /* kMarsMaze057 */ { 7, 12 }, + /* kMarsMaze058 */ { 12, 13 }, + /* kMarsMaze059 */ { 12, 12 }, + /* kMarsMaze060 */ { 12, 11 }, + /* kMarsMaze061 */ { 12, 10 }, + /* kMarsMaze063 */ { 12, 9 }, + /* kMarsMaze064 */ { 12, 8 }, + /* kMarsMaze065 */ { 12, 7 }, + /* kMarsMaze066 */ { 13, 7 }, + /* kMarsMaze067 */ { 15, 7 }, + /* kMarsMaze068 */ { 17, 7 }, + /* kMarsMaze069 */ { 18, 7 }, + /* kMarsMaze070 */ { 19, 7 }, + /* kMarsMaze071 */ { 20, 7 }, + /* kMarsMaze072 */ { 20, 6 }, + /* kMarsMaze074 */ { 20, 5 }, + /* kMarsMaze076 */ { 20, 4 }, + /* kMarsMaze078 */ { 20, 3 }, + /* kMarsMaze079 */ { 20, 2 }, + /* kMarsMaze081 */ { 20, 2 }, + /* kMarsMaze083 */ { 20, 0 }, + /* kMarsMaze084 */ { 19, 0 }, + /* kMarsMaze085 */ { 18, 0 }, + /* kMarsMaze086 */ { 17, 0 }, + /* kMarsMaze087 */ { 16, 0 }, + /* kMarsMaze088 */ { 15, 0 }, + /* kMarsMaze089 */ { 14, 0 }, + /* kMarsMaze090 */ { 13, 0 }, + /* kMarsMaze091 */ { 12, 0 }, + /* kMarsMaze092 */ { 11, 0 }, + /* kMarsMaze093 */ { 10, 0 }, + /* kMarsMaze098 */ { 10, 1 }, + /* kMarsMaze099 */ { 8, 2 }, + /* kMarsMaze100 */ { 9, 2 }, + /* kMarsMaze101 */ { 10, 2 }, + /* kMarsMaze104 */ { 13, 2 }, + /* kMarsMaze105 */ { 13, 3 }, + /* kMarsMaze106 */ { 13, 4 }, + /* kMarsMaze107 */ { 13, 5 }, + /* kMarsMaze108 */ { 14, 5 }, + /* kMarsMaze111 */ { 15, 5 }, + /* kMarsMaze113 */ { 16, 5 }, + /* kMarsMaze114 */ { 17, 5 }, + /* kMarsMaze115 */ { 18, 5 }, + /* kMarsMaze116 */ { 18, 4 }, + /* kMarsMaze117 */ { 18, 3 }, + /* kMarsMaze118 */ { 19, 3 }, + /* kMarsMaze119 */ { 18, 2 }, + /* kMarsMaze120 */ { 17, 2 }, + /* kMarsMaze121 */ { 16, 2 }, + /* kMarsMaze122 */ { 15, 2 }, + /* kMarsMaze123 */ { 15, 1 }, + /* kMarsMaze124 */ { 12, 4 }, + /* kMarsMaze125 */ { 11, 4 }, + /* kMarsMaze126 */ { 10, 4 }, + /* kMarsMaze127 */ { 10, 5 }, + /* kMarsMaze128 */ { 10, 6 }, + /* kMarsMaze129 */ { 9, 6 }, + /* kMarsMaze130 */ { 8, 6 }, + /* kMarsMaze131 */ { 7, 6 }, + /* kMarsMaze132 */ { 7, 7 }, + /* kMarsMaze133 */ { 7, 8 }, + /* kMarsMaze136 */ { 7, 11 }, + /* kMarsMaze137 */ { 6, 11 }, + /* kMarsMaze138 */ { 5, 11 }, + /* kMarsMaze139 */ { 5, 12 }, + /* kMarsMaze140 */ { 4, 12 }, + /* kMarsMaze141 */ { 5, 13 }, + /* kMarsMaze142 */ { 5, 14 }, + /* kMarsMaze143 */ { 4, 14 }, + /* kMarsMaze144 */ { 3, 14 }, + /* kMarsMaze145 */ { 3, 13 }, + /* kMarsMaze146 */ { 2, 13 }, + /* kMarsMaze147 */ { 1, 13 }, + /* kMarsMaze148 */ { 1, 14 }, + /* kMarsMaze149 */ { 1, 15 }, + /* kMarsMaze152 */ { 1, 12 }, + /* kMarsMaze153 */ { 1, 11 }, + /* kMarsMaze154 */ { 1, 10 }, + /* kMarsMaze155 */ { 1, 9 }, + /* kMarsMaze156 */ { 1, 8 }, + /* kMarsMaze157 */ { 2, 10 }, + /* kMarsMaze159 */ { 2, 8 }, + /* kMarsMaze160 */ { 2, 7 }, + /* kMarsMaze161 */ { 2, 6 }, + /* kMarsMaze162 */ { 3, 10 }, + /* kMarsMaze163 */ { 3, 9 }, + /* kMarsMaze164 */ { 3, 8 }, + /* kMarsMaze165 */ { 4, 8 }, + /* kMarsMaze166 */ { 5, 8 }, + /* kMarsMaze167 */ { 6, 8 }, + /* kMarsMaze168 */ { 3, 6 }, + /* kMarsMaze169 */ { 4, 6 }, + /* kMarsMaze170 */ { 5, 6 }, + /* kMarsMaze171 */ { 5, 5 }, + /* kMarsMaze172 */ { 5, 4 }, + /* kMarsMaze173 */ { 4, 4 }, + /* kMarsMaze174 */ { 3, 4 }, + /* kMarsMaze175 */ { 3, 5 }, + /* kMarsMaze177 */ { 8, 4 }, + /* kMarsMaze178 */ { 8, 3 }, + /* kMarsMaze179 */ { 7, 4 }, + /* kMarsMaze180 */ { 6, 4 }, + /* kMarsMaze181 */ { 6, 3 }, + /* kMarsMaze182 */ { 6, 2 }, + /* kMarsMaze183 */ { 6, 1 }, + /* kMarsMaze184 */ { 6, 0 }, + /* kMarsMaze187 */ { 3, 0 }, + /* kMarsMaze188 */ { 2, 0 }, + /* kMarsMaze189 */ { 1, 0 }, + /* kMarsMaze190 */ { 1, 1 }, + /* kMarsMaze191 */ { 1, 2 }, + /* kMarsMaze192 */ { 5, 2 }, + /* kMarsMaze193 */ { 4, 2 }, + /* kMarsMaze194 */ { 3, 2 }, + /* kMarsMaze195 */ { 3, 1 }, + /* kMarsMaze198 */ { 1, 3 }, + /* kMarsMaze199 */ { 1, 4 }, + /* kMarsMaze200 */ { 0, 4 } +}; + +MapImage::MapImage() : DisplayElement(kNoDisplayElement) { + _whichArea = kMapNoArea; + setBounds(kAIMiddleAreaLeft, kAIMiddleAreaTop, kAIMiddleAreaLeft + kAIMiddleAreaWidth, kAIMiddleAreaTop + kAIMiddleAreaHeight); + setDisplayOrder(kAIMiddleAreaOrder + 10); + startDisplaying(); + + _darkGreen = g_system->getScreenFormat().RGBToColor(64, 150, 10); + _lightGreen = g_system->getScreenFormat().RGBToColor(102, 239, 0); +} + +void MapImage::writeToStream(Common::WriteStream *stream) { + _mappedRooms.writeToStream(stream); +} + +void MapImage::readFromStream(Common::ReadStream *stream) { + _mappedRooms.readFromStream(stream); +} + +void MapImage::loadGearRoomIfNecessary() { + if (_whichArea != kMapGearRoom) { + _mapImage.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kMapOfGearRoomPICTID); + + Common::Rect bounds; + _mapImage.getSurfaceBounds(bounds); + _mapMask.allocateSurface(bounds); + _whichArea = kMapGearRoom; + + GraphicsManager *gfx = ((PegasusEngine *)g_engine)->_gfx; + gfx->setCurSurface(_mapMask.getSurface()); + + gfx->getCurSurface()->fillRect(bounds, g_system->getScreenFormat().RGBToColor(0xff, 0xff, 0xff)); + + for (int i = kGearRoomFlagLow; i <= kGearRoomFlagHigh; i++) + if (_mappedRooms.getFlag(i)) + addFlagToMask(i); + + gfx->setCurSurface(gfx->getWorkArea()); + show(); + } +} + +void MapImage::loadMazeIfNecessary() { + if (_whichArea != kMapMaze) { + _mapImage.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kMapOfMazePICTID); + + Common::Rect bounds; + _mapImage.getSurfaceBounds(bounds); + _mapMask.allocateSurface(bounds); + _whichArea = kMapMaze; + + GraphicsManager *gfx = ((PegasusEngine *)g_engine)->_gfx; + gfx->setCurSurface(_mapMask.getSurface()); + + gfx->getCurSurface()->fillRect(bounds, g_system->getScreenFormat().RGBToColor(0xff, 0xff, 0xff)); + + for (int i = kMazeFlagLow; i <= kMazeFlagHigh; i++) + if (_mappedRooms.getFlag(i)) + addFlagToMask(i); + + gfx->setCurSurface(gfx->getWorkArea()); + show(); + } +} + +void MapImage::unloadImage() { + _mapImage.deallocateSurface(); + _mapMask.deallocateSurface(); + hide(); + _whichArea = kMapNoArea; +} + +void MapImage::moveToMapLocation(const NeighborhoodID, const RoomID room, const DirectionConstant dir) { + GraphicsManager *gfx = ((PegasusEngine *)g_engine)->_gfx; + + int flag = ROOM_TO_FLAG(room, dir); + + if (!_mappedRooms.getFlag(flag)) { + _mappedRooms.setFlag(flag, true); + + if (_mapMask.isSurfaceValid()) { + gfx->setCurSurface(_mapMask.getSurface()); + addFlagToMask(flag); + gfx->setCurSurface(gfx->getWorkArea()); + } + } + + if (isDisplaying()) + triggerRedraw(); +} + +void MapImage::addFlagToMask(const int flag) { + Common::Rect r1; + getRevealedRects(flag, r1); + ((PegasusEngine *)g_engine)->_gfx->getCurSurface()->fillRect(r1, g_system->getScreenFormat().RGBToColor(0, 0, 0)); +} + +// This function can even be sensitive to open doors. +// clone2727 notices that it's not, though +void MapImage::getRevealedRects(const uint32 flag, Common::Rect &r1) { + CoordType gridX, gridY; + + switch (_whichArea) { + case kMapMaze: + gridX = kMazeGridOriginX; + gridY = kMazeGridOriginY; + break; + case kMapGearRoom: + gridX = kGearRoomGridOriginX; + gridY = kGearRoomGridOriginY; + break; + default: + return; + } + + int index = FLAG_TO_INDEX(flag); + gridX += s_mapCoords[index][0] * kGridWidth; + gridY += s_mapCoords[index][1] * kGridHeight; + + r1 = Common::Rect(gridX - 1, gridY - 1, gridX + kGridWidth + 1, gridY + kGridHeight + 1); +} + +void MapImage::drawPlayer() { + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getCurSurface(); + + CoordType gridX, gridY; + + switch (_whichArea) { + case kMapMaze: + gridX = _bounds.left + kMazeScreenOffsetX + kMazeGridOriginX; + gridY = _bounds.top + kMazeScreenOffsetY + kMazeGridOriginY; + break; + case kMapGearRoom: + gridX = _bounds.left + kGearRoomScreenOffsetX + kGearRoomGridOriginX; + gridY = _bounds.top + kGearRoomScreenOffsetY + kGearRoomGridOriginY; + break; + default: + return; + } + + int index = ROOM_TO_INDEX(GameState.getCurrentRoom()); + gridX += s_mapCoords[index][0] * kGridWidth; + gridY += s_mapCoords[index][1] * kGridHeight; + + // This was intended to make little arrows + switch (GameState.getCurrentDirection()) { + case kNorth: + screen->drawLine(gridX + 1, gridY, gridX + 2, gridY, _darkGreen); + screen->drawLine(gridX, gridY + 1, gridX + 3, gridY + 1, _darkGreen); + screen->drawLine(gridX + 1, gridY + 1, gridX + 2, gridY + 1, _lightGreen); + screen->drawLine(gridX, gridY + 2, gridX + 3, gridY + 2, _lightGreen); + break; + case kSouth: + screen->drawLine(gridX + 1, gridY + 3, gridX + 2, gridY + 3, _darkGreen); + screen->drawLine(gridX, gridY + 2, gridX + 3, gridY + 2, _darkGreen); + screen->drawLine(gridX + 1, gridY + 2, gridX + 2, gridY + 2, _lightGreen); + screen->drawLine(gridX, gridY + 1, gridX + 3, gridY + 1, _lightGreen); + break; + case kEast: + screen->drawLine(gridX + 3, gridY + 1, gridX + 3, gridY + 2, _darkGreen); + screen->drawLine(gridX + 2, gridY, gridX + 2, gridY + 3, _darkGreen); + screen->drawLine(gridX + 2, gridY + 1, gridX + 2, gridY + 2, _lightGreen); + screen->drawLine(gridX + 1, gridY, gridX + 1, gridY + 3, _lightGreen); + break; + case kWest: + screen->drawLine(gridX, gridY + 1, gridX, gridY + 2, _darkGreen); + screen->drawLine(gridX + 1, gridY, gridX + 1, gridY + 3, _darkGreen); + screen->drawLine(gridX + 1, gridY + 1, gridX + 1, gridY + 2, _lightGreen); + screen->drawLine(gridX + 2, gridY, gridX + 2, gridY + 3, _lightGreen); + break; + } +} + +void MapImage::draw(const Common::Rect &) { + Common::Rect r1; + _mapImage.getSurfaceBounds(r1); + + Common::Rect r2 = r1; + switch (_whichArea) { + case kMapMaze: + r2.moveTo(_bounds.left + kMazeScreenOffsetX, _bounds.top + kMazeScreenOffsetY); + break; + case kMapGearRoom: + r2.moveTo(_bounds.left + kGearRoomScreenOffsetX, _bounds.top + kGearRoomScreenOffsetY); + break; + default: + return; + } + + _mapImage.copyToCurrentPortMasked(r1, r2, &_mapMask); + + drawPlayer(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/biochips/mapimage.h b/engines/pegasus/items/biochips/mapimage.h new file mode 100644 index 0000000000..d596b585c4 --- /dev/null +++ b/engines/pegasus/items/biochips/mapimage.h @@ -0,0 +1,84 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_BIOCHIPS_MAPIMAGE_H +#define PEGASUS_ITEMS_BIOCHIPS_MAPIMAGE_H + +#include "pegasus/elements.h" +#include "pegasus/surface.h" +#include "pegasus/util.h" +#include "pegasus/neighborhood/mars/constants.h" + +namespace Common { + class ReadStream; + class WriteStream; +} + +namespace Pegasus { + +class MapImage : public DisplayElement { +public: + MapImage(); + virtual ~MapImage() {} + + void writeToStream(Common::WriteStream *); + void readFromStream(Common::ReadStream *); + + void loadGearRoomIfNecessary(); + void loadMazeIfNecessary(); + void unloadImage(); + void moveToMapLocation(const NeighborhoodID, const RoomID, const DirectionConstant); + + void draw(const Common::Rect &); + + bool anyFlagSet() { return _mappedRooms.anyFlagSet(); } + + static const uint32 kNumMappingRooms = (kMars39 - kMars35 + 1) + (kMars60 - kMars60 + 1) + + (kMarsMaze200 - kMarsMaze004 + 1); + static const uint32 kNumMappingFlags = kNumMappingRooms * 4; + +protected: + enum MapArea { + kMapNoArea, + kMapMaze, + kMapGearRoom + }; + + void addFlagToMask(const int flag); + void getRevealedRects(const uint32, Common::Rect &); + void drawPlayer(); + + MapArea _whichArea; + + FlagsArray<byte, kNumMappingFlags> _mappedRooms; + + uint32 _darkGreen, _lightGreen; + + Surface _mapImage, _mapMask; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/biochips/opticalchip.cpp b/engines/pegasus/items/biochips/opticalchip.cpp new file mode 100644 index 0000000000..0f60981955 --- /dev/null +++ b/engines/pegasus/items/biochips/opticalchip.cpp @@ -0,0 +1,189 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/opticalchip.h" + +namespace Pegasus { + +OpticalChip *g_opticalChip = 0; + +OpticalChip::OpticalChip(const ItemID id, const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) : + BiochipItem(id, neighborhood, room, direction), _ariesHotspot(kAriesSpotID), _mercuryHotspot(kMercurySpotID), + _poseidonHotspot(kPoseidonSpotID) { + _ariesHotspot.setArea(Common::Rect(kAIMiddleAreaLeft + 60, kAIMiddleAreaTop + 27, kAIMiddleAreaLeft + 60 + 121, kAIMiddleAreaTop + 27 + 20)); + _ariesHotspot.setHotspotFlags(kOpticalBiochipSpotFlag); + g_allHotspots.push_back(&_ariesHotspot); + + _mercuryHotspot.setArea(Common::Rect(kAIMiddleAreaLeft + 60, kAIMiddleAreaTop + 47, kAIMiddleAreaLeft + 60 + 121, kAIMiddleAreaTop + 47 + 20)); + _mercuryHotspot.setHotspotFlags(kOpticalBiochipSpotFlag); + g_allHotspots.push_back(&_mercuryHotspot); + + _poseidonHotspot.setArea(Common::Rect(kAIMiddleAreaLeft + 60, kAIMiddleAreaTop + 67, kAIMiddleAreaLeft + 60 + 121, kAIMiddleAreaTop + 67 + 20)); + _poseidonHotspot.setHotspotFlags(kOpticalBiochipSpotFlag); + g_allHotspots.push_back(&_poseidonHotspot); + + setItemState(kOptical000); + + g_opticalChip = this; +} + +OpticalChip::~OpticalChip() { + g_allHotspots.removeOneHotspot(kAriesSpotID); + g_allHotspots.removeOneHotspot(kMercurySpotID); + g_allHotspots.removeOneHotspot(kPoseidonSpotID); +} + +void OpticalChip::writeToStream(Common::WriteStream *stream) { + BiochipItem::writeToStream(stream); + _opticalFlags.writeToStream(stream); +} + +void OpticalChip::readFromStream(Common::ReadStream *stream) { + BiochipItem::readFromStream(stream); + _opticalFlags.readFromStream(stream); +} + +void OpticalChip::addAries() { + _opticalFlags.setFlag(kOpticalAriesExposed, true); + setUpOpticalChip(); +} + +void OpticalChip::addMercury() { + _opticalFlags.setFlag(kOpticalMercuryExposed, true); + setUpOpticalChip(); +} + +void OpticalChip::addPoseidon() { + _opticalFlags.setFlag(kOpticalPoseidonExposed, true); + setUpOpticalChip(); +} + +void OpticalChip::setUpOpticalChip() { + if (_opticalFlags.getFlag(kOpticalAriesExposed)) { + if (_opticalFlags.getFlag(kOpticalMercuryExposed)) { + if (_opticalFlags.getFlag(kOpticalPoseidonExposed)) + setItemState(kOptical111); + else + setItemState(kOptical011); + } else { + if (_opticalFlags.getFlag(kOpticalPoseidonExposed)) + setItemState(kOptical101); + else + setItemState(kOptical001); + } + } else { + if (_opticalFlags.getFlag(kOpticalMercuryExposed)) { + if (_opticalFlags.getFlag(kOpticalPoseidonExposed)) + setItemState(kOptical110); + else + setItemState(kOptical010); + } else { + if (_opticalFlags.getFlag(kOpticalPoseidonExposed)) + setItemState(kOptical100); + else + setItemState(kOptical000); + } + } +} + +void OpticalChip::activateOpticalHotspots() { + if (_opticalFlags.getFlag(kOpticalAriesExposed)) + _ariesHotspot.setActive(); + if (_opticalFlags.getFlag(kOpticalMercuryExposed)) + _mercuryHotspot.setActive(); + if (_opticalFlags.getFlag(kOpticalPoseidonExposed)) + _poseidonHotspot.setActive(); +} + +void OpticalChip::clickInOpticalHotspot(HotSpotID id) { + playOpMemMovie(id); +} + +void OpticalChip::playOpMemMovie(HotSpotID id) { + Common::String movieName; + switch (id) { + case kAriesSpotID: + movieName = "Images/AI/Globals/OMAI"; + break; + case kMercurySpotID: + movieName = "Images/AI/Globals/OMMI"; + break; + case kPoseidonSpotID: + movieName = "Images/AI/Globals/OMPI"; + break; + } + + ItemState state = getItemState(), newState; + switch (state) { + case kOptical001: + newState = kOptical002; + break; + case kOptical010: + newState = kOptical020; + break; + case kOptical011: + if (id == kAriesSpotID) + newState = kOptical012; + else + newState = kOptical021; + break; + case kOptical100: + newState = kOptical200; + break; + case kOptical101: + if (id == kAriesSpotID) + newState = kOptical102; + else + newState = kOptical201; + break; + case kOptical110: + if (id == kMercurySpotID) + newState = kOptical120; + else + newState = kOptical210; + break; + case kOptical111: + if (id == kAriesSpotID) + newState = kOptical112; + else if (id == kMercurySpotID) + newState = kOptical121; + else + newState = kOptical211; + break; + case kOptical000: // Can never happen. + default: + error("Invalid optical chip state"); + } + + setItemState(newState); + + if (g_AIArea) + g_AIArea->playAIMovie(kRightAreaSignature, movieName, false, kOpticalInterruption); + + setItemState(state); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/biochips/opticalchip.h b/engines/pegasus/items/biochips/opticalchip.h new file mode 100644 index 0000000000..b1ea87bffc --- /dev/null +++ b/engines/pegasus/items/biochips/opticalchip.h @@ -0,0 +1,71 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_BIOCHIPS_OPTICALCHIP_H +#define PEGASUS_ITEMS_BIOCHIPS_OPTICALCHIP_H + +#include "pegasus/hotspot.h" +#include "pegasus/util.h" +#include "pegasus/items/biochips/biochipitem.h" + +namespace Pegasus { + +class OpticalChip : public BiochipItem { +public: + OpticalChip(const ItemID, const NeighborhoodID, const RoomID, const DirectionConstant); + virtual ~OpticalChip(); + + virtual void writeToStream(Common::WriteStream *); + virtual void readFromStream(Common::ReadStream *); + + void addAries(); + void addMercury(); + void addPoseidon(); + + void activateOpticalHotspots(); + void clickInOpticalHotspot(HotSpotID); + void playOpMemMovie(HotSpotID); + +protected: + enum { + kOpticalAriesExposed, + kOpticalMercuryExposed, + kOpticalPoseidonExposed, + kNumOpticalChipFlags + }; + + void setUpOpticalChip(); + + FlagsArray<byte, kNumOpticalChipFlags> _opticalFlags; + Hotspot _ariesHotspot; + Hotspot _mercuryHotspot; + Hotspot _poseidonHotspot; +}; + +extern OpticalChip *g_opticalChip; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/biochips/pegasuschip.cpp b/engines/pegasus/items/biochips/pegasuschip.cpp new file mode 100644 index 0000000000..c0c3f6bb9e --- /dev/null +++ b/engines/pegasus/items/biochips/pegasuschip.cpp @@ -0,0 +1,192 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/items/biochips/pegasuschip.h" +#include "pegasus/neighborhood/tsa/fulltsa.h" +#include "pegasus/neighborhood/tsa/tinytsa.h" + +namespace Pegasus { + +PegasusChip::PegasusChip(const ItemID id, const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) : + BiochipItem(id, neighborhood, room, direction), _recallSpot(kPegasusRecallSpotID) { + _recallSpot.setArea(Common::Rect(kAIMiddleAreaLeft + 116, kAIMiddleAreaTop + 63, kAIMiddleAreaLeft + 184, kAIMiddleAreaTop + 91)); + _recallSpot.setHotspotFlags(kPegasusBiochipSpotFlag); + g_allHotspots.push_back(&_recallSpot); + setItemState(kPegasusTSA00); +} + +PegasusChip::~PegasusChip() { + g_allHotspots.removeOneHotspot(kPegasusRecallSpotID); +} + +void PegasusChip::select() { + BiochipItem::select(); + setUpPegasusChip(); +} + +void PegasusChip::setUpPegasusChip() { + switch (GameState.getCurrentNeighborhood()) { + case kCaldoriaID: + setItemState(kPegasusCaldoria); + break; + case kFullTSAID: + case kFinalTSAID: + case kTinyTSAID: + setItemState(kPegasusTSA10); + break; + case kPrehistoricID: + if (((PegasusEngine *)g_engine)->playerHasItemID(kHistoricalLog)) + setItemState(kPegasusPrehistoric00); + else + setItemState(kPegasusPrehistoric10); + break; + case kMarsID: + if (GameState.getMarsFinished()) + setItemState(kPegasusMars00); + else + setItemState(kPegasusMars10); + break; + case kWSCID: + if (GameState.getWSCFinished()) + setItemState(kPegasusWSC00); + else + setItemState(kPegasusWSC10); + break; + case kNoradAlphaID: + case kNoradDeltaID: + if (GameState.getNoradFinished()) + setItemState(kPegasusNorad00); + else + setItemState(kPegasusNorad10); + break; + } +} + +// Only does something if the chip should be announcing that the time zone is finished... +void PegasusChip::setUpPegasusChipRude() { + switch (GameState.getCurrentNeighborhood()) { + case kPrehistoricID: + if (((PegasusEngine *)g_engine)->playerHasItemID(kHistoricalLog)) + setItemState(kPegasusPrehistoric00); + break; + case kMarsID: + if (GameState.getMarsFinished()) + setItemState(kPegasusMars00); + break; + case kWSCID: + if (GameState.getWSCFinished()) + setItemState(kPegasusWSC00); + break; + case kNoradAlphaID: + case kNoradDeltaID: + if (GameState.getNoradFinished()) + setItemState(kPegasusNorad00); + break; + } +} + +void PegasusChip::activatePegasusHotspots() { + switch (GameState.getCurrentNeighborhood()) { + case kPrehistoricID: + case kMarsID: + case kWSCID: + case kNoradAlphaID: + case kNoradDeltaID: + _recallSpot.setActive(); + break; + } +} + +void PegasusChip::clickInPegasusHotspot() { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + ItemState thisState = getItemState(); + ItemState hiliteState; + + switch (thisState) { + case kPegasusPrehistoric00: + hiliteState = kPegasusPrehistoric01; + break; + case kPegasusPrehistoric10: + hiliteState = kPegasusPrehistoric11; + break; + case kPegasusMars00: + hiliteState = kPegasusMars01; + break; + case kPegasusMars10: + hiliteState = kPegasusMars11; + break; + case kPegasusNorad00: + hiliteState = kPegasusNorad01; + break; + case kPegasusNorad10: + hiliteState = kPegasusNorad11; + break; + case kPegasusWSC00: + hiliteState = kPegasusWSC01; + break; + case kPegasusWSC10: + hiliteState = kPegasusWSC11; + break; + default: + error("Invalid pegasus chip state"); + } + + // WORKAROUND: The original called setItemState() here. However, + // since we're overriding select() to call setUpPegasusChip(), + // the highlighted frame is never displayed! So, we're manually + // setting the state and selecting the item. Also of note is that + // setItemState() for this class is effectively useless since it + // always gets overriden in the select() function. The only reason + // that this doesn't end in infinite recursion is because setItemState() + // has a check against the current state to make sure you don't call + // select() again. </rant> + _itemState = hiliteState; + BiochipItem::select(); + + uint32 time = g_system->getMillis(); + while (g_system->getMillis() < time + 500) { + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + setItemState(thisState); + + if (!((Neighborhood *)g_neighborhood)->okayToJump()) + return; + + if (g_energyMonitor) + g_energyMonitor->stopEnergyDraining(); + + if (GameState.getTSAState() == kPlayerWentToPrehistoric || GameState.allTimeZonesFinished()) + vm->jumpToNewEnvironment(kFullTSAID, kTSA37, kNorth); + else + vm->jumpToNewEnvironment(kTinyTSAID, kTinyTSA37, kNorth); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/biochips/pegasuschip.h b/engines/pegasus/items/biochips/pegasuschip.h new file mode 100644 index 0000000000..7597424821 --- /dev/null +++ b/engines/pegasus/items/biochips/pegasuschip.h @@ -0,0 +1,55 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_BIOCHIPS_PEGASUSCHIP_H +#define PEGASUS_ITEMS_BIOCHIPS_PEGASUSCHIP_H + +#include "pegasus/hotspot.h" +#include "pegasus/items/biochips/biochipitem.h" + +namespace Pegasus { + +class PegasusChip : public BiochipItem { +public: + PegasusChip(const ItemID, const NeighborhoodID, const RoomID, const DirectionConstant); + virtual ~PegasusChip(); + + void select(); + + void setUpPegasusChip(); + + // Called to set up the Pegasus chip when the Pegasus chip is the current chip but does not + // own the center area. + void setUpPegasusChipRude(); + void activatePegasusHotspots(); + void clickInPegasusHotspot(); + +protected: + Hotspot _recallSpot; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/biochips/retscanchip.cpp b/engines/pegasus/items/biochips/retscanchip.cpp new file mode 100644 index 0000000000..84b74a63d2 --- /dev/null +++ b/engines/pegasus/items/biochips/retscanchip.cpp @@ -0,0 +1,49 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/retscanchip.h" + +namespace Pegasus { + +RetScanChip::RetScanChip(const ItemID id, const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) : + BiochipItem(id, neighborhood, room, direction) { +} + +void RetScanChip::searchForLaser() { + ItemExtraEntry entry; + findItemExtra(kRetinalScanSearching, entry); + + if (g_AIArea) + g_AIArea->playAIAreaSequence(kBiochipSignature, kMiddleAreaSignature, entry.extraStart, entry.extraStop); + + findItemExtra(kRetinalScanActivated, entry); + if (g_AIArea) + g_AIArea->playAIAreaSequence(kBiochipSignature, kRightAreaSignature, entry.extraStart, entry.extraStop); + + setItemState(kRetinalSimulating); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/biochips/retscanchip.h b/engines/pegasus/items/biochips/retscanchip.h new file mode 100644 index 0000000000..e72eaeddaf --- /dev/null +++ b/engines/pegasus/items/biochips/retscanchip.h @@ -0,0 +1,43 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_BIOCHIPS_RETSCANCHIP_H +#define PEGASUS_ITEMS_BIOCHIPS_RETSCANCHIP_H + +#include "pegasus/items/biochips/biochipitem.h" + +namespace Pegasus { + +class RetScanChip : public BiochipItem { +public: + RetScanChip(const ItemID, const NeighborhoodID, const RoomID, const DirectionConstant); + virtual ~RetScanChip() {} + + void searchForLaser(); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/biochips/shieldchip.cpp b/engines/pegasus/items/biochips/shieldchip.cpp new file mode 100644 index 0000000000..58cbfcc4ec --- /dev/null +++ b/engines/pegasus/items/biochips/shieldchip.cpp @@ -0,0 +1,53 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/gamestate.h" +#include "pegasus/items/biochips/shieldchip.h" +#include "pegasus/neighborhood/neighborhood.h" + +namespace Pegasus { + +ShieldChip *g_shield = 0; + +ShieldChip::ShieldChip(const ItemID id, const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) : + BiochipItem(id, neighborhood, room, direction) { + g_shield = this; +} + +void ShieldChip::select() { + BiochipItem::select(); + GameState.setShieldOn(true); + if (g_neighborhood) + g_neighborhood->shieldOn(); +} + +void ShieldChip::deselect() { + BiochipItem::deselect(); + GameState.setShieldOn(false); + if (g_neighborhood) + g_neighborhood->shieldOff(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/biochips/shieldchip.h b/engines/pegasus/items/biochips/shieldchip.h new file mode 100644 index 0000000000..69c6369236 --- /dev/null +++ b/engines/pegasus/items/biochips/shieldchip.h @@ -0,0 +1,46 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_BIOCHIPS_SHIELDCHIP_H +#define PEGASUS_ITEMS_BIOCHIPS_SHIELDCHIP_H + +#include "pegasus/items/biochips/biochipitem.h" + +namespace Pegasus { + +class ShieldChip : public BiochipItem { +public: + ShieldChip(const ItemID, const NeighborhoodID, const RoomID, const DirectionConstant); + virtual ~ShieldChip() {} + + void select(); + void deselect(); +}; + +extern ShieldChip *g_shield; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/inventory.cpp b/engines/pegasus/items/inventory.cpp new file mode 100644 index 0000000000..16cced3267 --- /dev/null +++ b/engines/pegasus/items/inventory.cpp @@ -0,0 +1,175 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/constants.h" +#include "pegasus/items/item.h" +#include "pegasus/items/inventory.h" + +namespace Pegasus { + +Inventory::Inventory() { + _weightLimit = 100; + _ownerID = kNoActorID; + _referenceCount = 0; +} + +Inventory::~Inventory() { +} + +void Inventory::setWeightLimit(WeightType limit) { + _weightLimit = limit; + // *** What to do if the new weight limit is greater than the current weight? +} + +WeightType Inventory::getWeight() { + WeightType result = 0; + + for (ItemIterator it = _inventoryList.begin(); it != _inventoryList.end(); it++) + result += (*it)->getItemWeight(); + + return result; +} + +// If the item already belongs, just return kInventoryOK. +InventoryResult Inventory::addItem(Item *item) { + if (itemInInventory(item)) + return kInventoryOK; + + if (getWeight() + item->getItemWeight() > _weightLimit) + return kTooMuchWeight; + + _inventoryList.push_back(item); + item->setItemOwner(_ownerID); + + ++_referenceCount; + return kInventoryOK; +} + +InventoryResult Inventory::removeItem(Item *item) { + for (ItemIterator it = _inventoryList.begin(); it != _inventoryList.end(); it++) { + if (*it == item) { + _inventoryList.erase(it); + item->setItemOwner(kNoActorID); + + ++_referenceCount; + return kInventoryOK; + } + } + + return kItemNotInInventory; +} + +InventoryResult Inventory::removeItem(ItemID id) { + Item *item = findItemByID(id); + + if (item) { + _inventoryList.remove(item); + item->setItemOwner(kNoActorID); + + ++_referenceCount; + return kInventoryOK; + } + + return kItemNotInInventory; +} + +void Inventory::removeAllItems() { + _inventoryList.clear(); + ++_referenceCount; +} + +bool Inventory::itemInInventory(Item *item) { + for (ItemIterator it = _inventoryList.begin(); it != _inventoryList.end(); it++) + if (*it == item) + return true; + + return false; +} + +bool Inventory::itemInInventory(ItemID id) { + return findItemByID(id) != NULL; +} + +Item *Inventory::getItemAt(int32 index) { + int32 i = 0; + for (ItemIterator it = _inventoryList.begin(); it != _inventoryList.end(); it++, i++) + if (i == index) + return *it; + + return 0; +} + +ItemID Inventory::getItemIDAt(int32 index) { + Item *item = getItemAt(index); + + if (item) + return item->getObjectID(); + + return kNoItemID; +} + +Item *Inventory::findItemByID(ItemID id) { + return _inventoryList.findItemByID(id); +} + +// Return -1 if not found. + +int32 Inventory::findIndexOf(Item *item) { + uint32 i = 0; + for (ItemIterator it = _inventoryList.begin(); it != _inventoryList.end(); it++, i++) + if (*it == item) + return i; + + return -1; +} + +// Return -1 if not found. + +int32 Inventory::findIndexOf(ItemID id) { + uint32 i = 0; + for (ItemIterator it = _inventoryList.begin(); it != _inventoryList.end(); it++, i++) + if ((*it)->getObjectID() == id) + return i; + + return -1; +} + +WeightType Inventory::getWeightLimit() { + return _weightLimit; +} + +int32 Inventory::getNumItems() { + return _inventoryList.size(); +} + +void Inventory::setOwnerID(const ActorID id) { + _ownerID = id; +} + +ActorID Inventory::getOwnerID() const { + return _ownerID; +} + +} // End of namespae Pegasus diff --git a/engines/pegasus/items/inventory.h b/engines/pegasus/items/inventory.h new file mode 100644 index 0000000000..099ba52ba7 --- /dev/null +++ b/engines/pegasus/items/inventory.h @@ -0,0 +1,80 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_INVENTORY_H +#define PEGASUS_ITEMS_INVENTORY_H + +#include "pegasus/types.h" +#include "pegasus/items/itemlist.h" + +namespace Pegasus { + +class Item; + +// Inventories have a "current item". This item is the default item the player can +// use. In a text adventure system, the current item would be "it", as in +// "Hit the troll with it," where "it" would refer to some weapon which is the current +// item. In a graphic adventure, the current item would be the item the user selects +// to use with the mouse or other pointing device. + +class Inventory { +public: + Inventory(); + virtual ~Inventory(); + + WeightType getWeightLimit(); + void setWeightLimit(WeightType limit); + WeightType getWeight(); + + virtual InventoryResult addItem(Item *item); + virtual InventoryResult removeItem(Item *item); + virtual InventoryResult removeItem(ItemID id); + virtual bool itemInInventory(Item *item); + virtual bool itemInInventory(ItemID id); + virtual Item *getItemAt(int32 index); + virtual ItemID getItemIDAt(int32 index); + virtual Item *findItemByID(ItemID id); + virtual int32 findIndexOf(Item *item); + virtual int32 findIndexOf(ItemID id); + int32 getNumItems(); + virtual void removeAllItems(); + + void setOwnerID(const ActorID id); + ActorID getOwnerID() const; + + uint32 getReferenceCount() { return _referenceCount; } + +protected: + WeightType _weightLimit; + ActorID _ownerID; + ItemList _inventoryList; + +private: + uint32 _referenceCount; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/inventory/airmask.cpp b/engines/pegasus/items/inventory/airmask.cpp new file mode 100644 index 0000000000..56f9d6f62f --- /dev/null +++ b/engines/pegasus/items/inventory/airmask.cpp @@ -0,0 +1,248 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/gamestate.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/inventory/airmask.h" +#include "pegasus/neighborhood/neighborhood.h" + +namespace Pegasus { + +AirMask *g_airMask = 0; + +// Based on full == 100, which is scale used by GetAirLeft(). +static const TimeValue kOxygenLowThreshold = 25; + +void AirMask::airMaskTimerExpired(FunctionPtr *, void *) { + if (g_neighborhood) + g_neighborhood->checkAirMask(); +} + +AirMask::AirMask(const ItemID id, const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) : + InventoryItem(id, neighborhood, room, direction), _toggleSpot(kAirMaskToggleSpotID) { + g_airMask = this; + _toggleSpot.setArea(Common::Rect(kAIMiddleAreaLeft + 10, kAIMiddleAreaTop + 17, kAIMiddleAreaLeft + 110, kAIMiddleAreaTop + 57)); + _toggleSpot.setHotspotFlags(kAirMaskSpotFlag); + g_allHotspots.push_back(&_toggleSpot); + setItemState(kAirMaskEmptyOff); + _oxygenTimer.primeFuse(0); + _oxygenTimer.setFunctionPtr(&airMaskTimerExpired, 0); +} + +AirMask::~AirMask() { + g_allHotspots.removeOneHotspot(kAirMaskToggleSpotID); + g_airMask = 0; +} + +void AirMask::writeToStream(Common::WriteStream *stream) { + InventoryItem::writeToStream(stream); + stream->writeUint32BE(_oxygenTimer.getTimeRemaining()); +} + +void AirMask::readFromStream(Common::ReadStream *stream) { + _oxygenTimer.stopFuse(); + InventoryItem::readFromStream(stream); + _oxygenTimer.primeFuse(stream->readUint32BE()); +} + +void AirMask::putMaskOn() { + AirQuality airQuality; + + if (g_neighborhood) + airQuality = g_neighborhood->getAirQuality(GameState.getCurrentRoom()); + else + airQuality = kAirQualityGood; + + uint airLevel = getAirLeft(); + ItemState newState = getItemState(); + ItemState oldState = newState; + + if (airLevel == 0) { + newState = kAirMaskEmptyFilter; + } else if (airLevel <= kOxygenLowThreshold) { + if (airQuality == kAirQualityVacuum) + newState = kAirMaskLowOn; + else + newState = kAirMaskLowFilter; + } else { + if (airQuality == kAirQualityVacuum) + newState = kAirMaskFullOn; + else + newState = kAirMaskFullFilter; + } + + if (newState != oldState) + setItemState(newState); +} + +void AirMask::takeMaskOff() { + uint airLevel = getAirLeft(); + ItemState newState = getItemState(); + ItemState oldState = newState; + + if (airLevel == 0) + newState = kAirMaskEmptyOff; + else if (airLevel <= kOxygenLowThreshold) + newState = kAirMaskLowOff; + else + newState = kAirMaskFullOff; + + if (newState != oldState) + setItemState(newState); +} + +void AirMask::toggleItemState() { + if (isAirMaskInUse()) + takeMaskOff(); + else + putMaskOn(); +} + +void AirMask::airQualityChanged() { + if (isAirMaskInUse()) + putMaskOn(); + else + takeMaskOff(); +} + +void AirMask::setItemState(const ItemState newState) { + if (newState != getItemState()) { + InventoryItem::setItemState(newState); + + switch (newState) { + case kAirMaskFullOn: + case kAirMaskLowOn: + if (!_oxygenTimer.isFuseLit()) { + _oxygenTimer.lightFuse(); + startIdling(); + } + break; + default: + if (_oxygenTimer.isFuseLit()) { + _oxygenTimer.stopFuse(); + stopIdling(); + } + break; + } + + if (g_neighborhood) + g_neighborhood->checkAirMask(); + + g_AIArea->checkMiddleArea(); + } +} + +void AirMask::useIdleTime() { + if (getAirLeft() == 0) + setItemState(kAirMaskEmptyOff); + else if (getAirLeft() <= kOxygenLowThreshold) + setItemState(kAirMaskLowOn); +} + +void AirMask::refillAirMask() { + switch (getItemState()) { + case kAirMaskEmptyOff: + case kAirMaskLowOff: + setItemState(kAirMaskFullOff); + break; + case kAirMaskEmptyFilter: + case kAirMaskLowFilter: + setItemState(kAirMaskFullFilter); + break; + case kAirMaskLowOn: + setItemState(kAirMaskFullOn); + break; + } + + if (_oxygenTimer.isFuseLit()) { + _oxygenTimer.stopFuse(); + _oxygenTimer.primeFuse(kOxyMaskFullTime); + _oxygenTimer.lightFuse(); + } else { + _oxygenTimer.primeFuse(kOxyMaskFullTime); + } +} + +// Doesn't return 0 until the timer is actually at 0. +uint AirMask::getAirLeft() { + return CLIP<int>(((_oxygenTimer.getTimeRemaining() * 100) + kOxyMaskFullTime - 1) / kOxyMaskFullTime, 0, 100); +} + +bool AirMask::isAirMaskInUse() { + switch (getItemState()) { + case kAirMaskEmptyOff: + case kAirMaskLowOff: + case kAirMaskFullOff: + return false; + break; + default: + return true; + break; + } +} + +bool AirMask::isAirMaskOn() { + switch (getItemState()) { + case kAirMaskLowOn: + case kAirMaskFullOn: + return true; + break; + default: + return false; + break; + } +} + +bool AirMask::isAirFilterOn() { + switch (getItemState()) { + case kAirMaskEmptyFilter: + case kAirMaskLowFilter: + case kAirMaskFullFilter: + return true; + break; + default: + return false; + break; + } +} + +void AirMask::addedToInventory() { + GameState.setMarsMaskOnFiller(false); +} + +void AirMask::removedFromInventory() { + if (isAirMaskInUse()) + toggleItemState(); +} + +void AirMask::activateAirMaskHotspots() { + _toggleSpot.setActive(); +} + +void AirMask::clickInAirMaskHotspot() { + toggleItemState(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/inventory/airmask.h b/engines/pegasus/items/inventory/airmask.h new file mode 100644 index 0000000000..f207f3b82b --- /dev/null +++ b/engines/pegasus/items/inventory/airmask.h @@ -0,0 +1,76 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_INVENTORY_AIRMASK_H +#define PEGASUS_ITEMS_INVENTORY_AIRMASK_H + +#include "pegasus/hotspot.h" +#include "pegasus/timers.h" +#include "pegasus/items/inventory/inventoryitem.h" + +namespace Pegasus { + +class AirMask : public InventoryItem, private Idler { +public: + AirMask(const ItemID, const NeighborhoodID, const RoomID, const DirectionConstant); + virtual ~AirMask(); + + virtual void writeToStream(Common::WriteStream *); + virtual void readFromStream(Common::ReadStream *); + + virtual void setItemState(const ItemState); + void putMaskOn(); + void takeMaskOff(); + void toggleItemState(); + void airQualityChanged(); + + bool isAirMaskInUse(); + bool isAirMaskOn(); + bool isAirFilterOn(); + + void refillAirMask(); + + // Returns a percentage + uint getAirLeft(); + + void activateAirMaskHotspots(); + void clickInAirMaskHotspot(); + +protected: + static void airMaskTimerExpired(FunctionPtr *, void *); + + virtual void removedFromInventory(); + virtual void addedToInventory(); + void useIdleTime(); + + Hotspot _toggleSpot; + FuseFunction _oxygenTimer; +}; + +extern AirMask *g_airMask; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/inventory/gascanister.cpp b/engines/pegasus/items/inventory/gascanister.cpp new file mode 100644 index 0000000000..bf63cc6542 --- /dev/null +++ b/engines/pegasus/items/inventory/gascanister.cpp @@ -0,0 +1,46 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/ai/ai_area.h" +#include "pegasus/items/inventory/gascanister.h" + +namespace Pegasus { + +GasCanister::GasCanister(const ItemID id, const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) : + InventoryItem(id, neighborhood, room, direction) { +} + +void GasCanister::select() { + InventoryItem::select(); + takeSharedArea(); +} + +void GasCanister::takeSharedArea() { + ItemExtraEntry entry; + findItemExtra(kGasCanLoop, entry); + g_AIArea->loopAIAreaSequence(kInventorySignature, kMiddleAreaSignature, entry.extraStart, entry.extraStop); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/inventory/gascanister.h b/engines/pegasus/items/inventory/gascanister.h new file mode 100644 index 0000000000..dae72c12cb --- /dev/null +++ b/engines/pegasus/items/inventory/gascanister.h @@ -0,0 +1,44 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_INVENTORY_GASCANISTER_H +#define PEGASUS_ITEMS_INVENTORY_GASCANISTER_H + +#include "pegasus/items/inventory/inventoryitem.h" + +namespace Pegasus { + +class GasCanister : public InventoryItem { +public: + GasCanister(const ItemID, const NeighborhoodID, const RoomID, const DirectionConstant); + virtual ~GasCanister() {} + + void select(); + void takeSharedArea(); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/inventory/inventoryitem.cpp b/engines/pegasus/items/inventory/inventoryitem.cpp new file mode 100644 index 0000000000..4399708879 --- /dev/null +++ b/engines/pegasus/items/inventory/inventoryitem.cpp @@ -0,0 +1,110 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/stream.h" + +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/inventory/inventoryitem.h" + +namespace Pegasus { + +InventoryItem::InventoryItem(const ItemID id, const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) : + Item(id, neighborhood, room, direction) { + + PegasusEngine *vm = (PegasusEngine *)g_engine; + + Common::SeekableReadStream *leftInfo = vm->_resFork->getResource(MKTAG('L', 'e', 'f', 't'), kItemBaseResID + id); + if (leftInfo) { + _leftAreaInfo = readItemState(leftInfo); + delete leftInfo; + } else { + _leftAreaInfo.numEntries = 0; + _leftAreaInfo.entries = 0; + } + + Common::SeekableReadStream *inventoryInfo = vm->_resFork->getResource(MKTAG('I', 'n', 'v', 'I'), kItemBaseResID + id); + if (inventoryInfo) { + _inventoryInfo.panelStart = inventoryInfo->readUint32BE(); + _inventoryInfo.panelStop = inventoryInfo->readUint32BE(); + delete inventoryInfo; + } else { + _inventoryInfo.panelStart = _inventoryInfo.panelStop = 0; + } + + _itemAnimationTime = 0; +} + +InventoryItem::~InventoryItem() { + delete[] _leftAreaInfo.entries; +} + +ItemType InventoryItem::getItemType() { + return kInventoryItemType; +} + +TimeValue InventoryItem::getLeftAreaTime() const { + if (!_leftAreaInfo.entries) + return 0xffffffff; + + TimeValue time; + ItemState state; + + findItemStateEntryByState(_leftAreaInfo, _itemState, time); + if (time == 0xffffffff) + getItemStateEntry(_leftAreaInfo, 0, state, time); + + return time; +} + +void InventoryItem::setAnimationTime(const TimeValue time) { + _itemAnimationTime = time; +} + +TimeValue InventoryItem::getAnimationTime() const { + return _itemAnimationTime; +} + +// Must affect images in left area. +void InventoryItem::select() { + Item::select(); + + if (g_AIArea) + g_AIArea->setAIAreaToTime(kInventorySignature, kLeftAreaSignature, getLeftAreaTime()); +} + +void InventoryItem::deselect() { + Item::deselect(); + + if (g_AIArea) + g_AIArea->setAIAreaToTime(kInventorySignature, kLeftAreaSignature, 0xffffffff); +} + +void InventoryItem::getPanelTimes(TimeValue &start, TimeValue &stop) const { + start = _inventoryInfo.panelStart; + stop = _inventoryInfo.panelStop; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/inventory/inventoryitem.h b/engines/pegasus/items/inventory/inventoryitem.h new file mode 100644 index 0000000000..b526463b18 --- /dev/null +++ b/engines/pegasus/items/inventory/inventoryitem.h @@ -0,0 +1,67 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_INVENTORY_INVENTORYITEM_H +#define PEGASUS_ITEMS_INVENTORY_INVENTORYITEM_H + +#include "pegasus/items/item.h" + +namespace Pegasus { + +// JMPInventoryInfo contains the resource data used by InventoryItems. + +struct JMPInventoryInfo { + TimeValue panelStart; + TimeValue panelStop; +}; + +class InventoryItem : public Item { +public: + InventoryItem(const ItemID, const NeighborhoodID, const RoomID, const DirectionConstant); + virtual ~InventoryItem(); + + virtual ItemType getItemType(); + + void getPanelTimes(TimeValue &, TimeValue &) const; + TimeValue getLeftAreaTime() const; + + void setAnimationTime(const TimeValue); + TimeValue getAnimationTime() const; + + virtual void toggleItemState() {} + + // Must affect images in left area. + virtual void select(); + virtual void deselect(); + +protected: + JMPInventoryInfo _inventoryInfo; + ItemStateInfo _leftAreaInfo; + TimeValue _itemAnimationTime; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/inventory/keycard.cpp b/engines/pegasus/items/inventory/keycard.cpp new file mode 100644 index 0000000000..c818b6675b --- /dev/null +++ b/engines/pegasus/items/inventory/keycard.cpp @@ -0,0 +1,59 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/pegasus.h" +#include "pegasus/items/inventory/keycard.h" + +namespace Pegasus { + +KeyCard::KeyCard(const ItemID id, const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) : + InventoryItem(id, neighborhood, room, direction) { + setItemState(kFlashlightOff); +} + +void KeyCard::toggleItemState() { + if (getItemState() == kFlashlightOff) + setItemState(kFlashlightOn); + else + setItemState(kFlashlightOff); +} + +void KeyCard::setItemState(const ItemState newState) { + if (newState != getItemState()) { + InventoryItem::setItemState(newState); + ((PegasusEngine *)g_engine)->checkFlashlight(); + } +} + +bool KeyCard::isFlashlightOn() { + return getItemState() == kFlashlightOn; +} + +void KeyCard::removedFromInventory() { + if (isFlashlightOn()) + setItemState(kFlashlightOff); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/inventory/keycard.h b/engines/pegasus/items/inventory/keycard.h new file mode 100644 index 0000000000..c30c789786 --- /dev/null +++ b/engines/pegasus/items/inventory/keycard.h @@ -0,0 +1,48 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_INVENTORY_KEYCARD_H +#define PEGASUS_ITEMS_INVENTORY_KEYCARD_H + +#include "pegasus/items/inventory/inventoryitem.h" + +namespace Pegasus { + +class KeyCard : public InventoryItem { +public: + KeyCard(const ItemID, const NeighborhoodID, const RoomID, const DirectionConstant); + virtual ~KeyCard() {} + + virtual void toggleItemState(); + virtual void setItemState(const ItemState); + bool isFlashlightOn(); + +protected: + virtual void removedFromInventory(); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/inventorypicture.cpp b/engines/pegasus/items/inventorypicture.cpp new file mode 100644 index 0000000000..84999c0b22 --- /dev/null +++ b/engines/pegasus/items/inventorypicture.cpp @@ -0,0 +1,370 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/pegasus.h" +#include "pegasus/transition.h" +#include "pegasus/items/inventorypicture.h" +#include "pegasus/items/biochips/biochipitem.h" +#include "pegasus/items/inventory/inventoryitem.h" + +namespace Pegasus { + +InventoryPicture::InventoryPicture(const DisplayElementID id, InputHandler *nextHandler, Inventory *inventory) : + InputHandler(nextHandler), Picture(id), _panelMovie(kNoDisplayElement){ + _inventory = inventory; + _lastReferenceCount = 0xffffffff; + + if (_inventory->getNumItems() > 0) { + _currentItemIndex = 0; + _currentItem = (Item *)_inventory->getItemAt(0); + } else { + _currentItemIndex = -1; + _currentItem = 0; + } + + _active = false; + _shouldDrawHighlight = true; + _itemsPerRow = 1; + _numberOfRows = 1; + _itemWidth = 0; + _itemHeight = 0; + _itemX = 0; + _itemY = 0; +} + +void InventoryPicture::initInventoryImage(Transition *transition) { + initFromPICTFile(_pictName, true); + _panelMovie.shareSurface(this); + _panelMovie.initFromMovieFile(_movieName); + _panelMovie.getBounds(_highlightBounds); + _panelMovie.setTriggeredElement(transition); + _highlightImage.initFromPICTFile(_highlightName, true); +} + +void InventoryPicture::throwAwayInventoryImage() { + if (isSurfaceValid()) { + _panelMovie.releaseMovie(); + _highlightImage.deallocateSurface(); + deallocateSurface(); + } +} + +void InventoryPicture::getItemXY(uint32 index, CoordType &x, CoordType &y) { + x = (index % _itemsPerRow) * _itemWidth + _itemX; + y = (index / _itemsPerRow) * _itemHeight + _itemY; +} + +void InventoryPicture::drawItemHighlight(const Common::Rect &r) { + if (_highlightImage.isSurfaceValid()) { + Common::Rect r2 = _highlightBounds; + Common::Rect bounds; + getBounds(bounds); + + r2.translate(bounds.left, bounds.top); + r2 = r2.findIntersectingRect(r); + if (!r2.isEmpty()) { + Common::Rect r1 = r2; + r1.translate(-bounds.left - _highlightBounds.left, -bounds.top - _highlightBounds.top); + _highlightImage.drawImage(r1, r2); + } + } +} + +void InventoryPicture::draw(const Common::Rect &r) { + Picture::draw(r); + if (_inventory->getNumItems() != 0 && _shouldDrawHighlight) + drawItemHighlight(r); +} + +// Assumes index >= 0. +void InventoryPicture::setCurrentItemIndex(int32 index) { + if (index >= _inventory->getNumItems()) + index = _inventory->getNumItems() - 1; + + Item *currentItem = 0; + if (index >= 0) + currentItem = (Item *)_inventory->getItemAt(index); + + if (currentItem != _currentItem) { + if (_currentItem) { + if (_currentItem->isSelected()) + _currentItem->deselect(); + + if (_active) + unhighlightCurrentItem(); + } + + _currentItemIndex = index; + _currentItem = currentItem; + if (_currentItem) { + _currentItem->select(); + + if (_active) + highlightCurrentItem(); + } + + if (_active) + triggerRedraw(); + } +} + +void InventoryPicture::setCurrentItemID(ItemID id) { + int32 index = _inventory->findIndexOf(id); + if (index >= 0) + setCurrentItemIndex(index); +} + +InventoryResult InventoryPicture::addInventoryItem(Item *item) { + InventoryResult result = _inventory->addItem(item); + + if (result == kInventoryOK) + setCurrentItemIndex(_inventory->findIndexOf(item)); + + return result; +} + +InventoryResult InventoryPicture::removeInventoryItem(Item *item) { + InventoryResult result = _inventory->removeItem(item); + + if (result == kInventoryOK) + setCurrentItemIndex(getCurrentItemIndex()); + + return result; +} + +void InventoryPicture::removeAllItems() { + _inventory->removeAllItems(); + setCurrentItemIndex(0); +} + +bool InventoryPicture::itemInInventory(Item *item) { + return _inventory->itemInInventory(item); +} + +bool InventoryPicture::itemInInventory(const ItemID id) { + return _inventory->itemInInventory(id); +} + +void InventoryPicture::panelUp() { + allowInput(true); +} + +// Must ensure that the picture matches the _inventory member variable. +void InventoryPicture::activateInventoryPicture() { + if (_active) + return; + + allowInput(false); + + if (_lastReferenceCount != _inventory->getReferenceCount()) { + uint32 numItems = _inventory->getNumItems(); + + CoordType x, y; + getItemXY(0, x, y); + _panelMovie.moveMovieBoxTo(x, y); + _panelMovie.show(); + + for (uint32 i = 0; i < numItems; i++) { + Item *item = (Item *)_inventory->getItemAt(i); + if (item == _currentItem) + item->select(); + + getItemXY(i, x, y); + _panelMovie.moveMovieBoxTo(x, y); + _panelMovie.setTime(getItemPanelTime(item)); + _panelMovie.redrawMovieWorld(); + } + + uint32 numSlots = _itemsPerRow * _numberOfRows; + + for (uint32 i = numItems; i < numSlots; i++) { + getItemXY(i, x, y); + _panelMovie.moveMovieBoxTo(x, y); + _panelMovie.setTime(0); + _panelMovie.redrawMovieWorld(); + } + + _lastReferenceCount = _inventory->getReferenceCount(); + } + + show(); // *** Do we really need this? + if (_currentItem) + highlightCurrentItem(); + + _active = true; +} + +void InventoryPicture::deactivateInventoryPicture() { + if (!_active) + return; + + _active = false; + allowInput(false); + _panelMovie.hide(); + hide(); + + if (_inventory->getNumItems() != 0) + if (!_currentItem->isActive()) + _currentItem->activate(); +} + +void InventoryPicture::handleInput(const Input &input, const Hotspot *cursorSpot) { + if (_active) { + if (input.upButtonDown()) { + if (_currentItemIndex - _itemsPerRow >= 0) + setCurrentItemIndex(_currentItemIndex - _itemsPerRow); + } else if (input.downButtonDown()) { + if (_currentItemIndex + _itemsPerRow < _inventory->getNumItems()) + setCurrentItemIndex(_currentItemIndex + _itemsPerRow); + } else if (input.leftButtonDown()) { + if ((_currentItemIndex % _itemsPerRow) != 0) + setCurrentItemIndex(_currentItemIndex - 1); + } else if (input.rightButtonDown()) { + if (((_currentItemIndex + 1) % _itemsPerRow) != 0 && _currentItemIndex + 1 < _inventory->getNumItems()) + setCurrentItemIndex(_currentItemIndex + 1); + } + } + + InputHandler::handleInput(input, cursorSpot); +} + +void InventoryPicture::highlightCurrentItem() { + CoordType x, y; + getItemXY(_currentItemIndex, x, y); + _highlightBounds.moveTo(x, y); +} + +InventoryItemsPicture::InventoryItemsPicture(const DisplayElementID id, InputHandler *nextHandler, Inventory *inventory) : + InventoryPicture(id, nextHandler, inventory) { + _pictName = "Images/Items/Inventory/Inventory Panel"; + _movieName = "Images/Items/Inventory/Inventory Panel Movie"; + _highlightName = "Images/Items/Inventory/Inventory Hilite"; + + _itemsPerRow = 3; + _numberOfRows = 3; + _itemWidth = 88; + _itemHeight = 64; + _itemX = 8; + _itemY = 26; + _isLooping = true; +} + +void InventoryItemsPicture::loopCurrentItem() { + if (_isLooping) { + CoordType x, y; + getItemXY(_currentItemIndex, x, y); + _panelMovie.moveMovieBoxTo(x, y); + _highlightBounds.moveTo(x, y); + + TimeValue start, stop; + ((InventoryItem *)_currentItem)->getPanelTimes(start, stop); + _panelMovie.stop(); + _panelMovie.setFlags(0); + _panelMovie.setSegment(start, stop); + _panelMovie.setFlags(kLoopTimeBase); + _panelMovie.setTime(((InventoryItem *)_currentItem)->getAnimationTime()); + _panelMovie.start(); + } +} + +void InventoryItemsPicture::highlightCurrentItem() { + InventoryPicture::highlightCurrentItem(); + loopCurrentItem(); +} + +void InventoryItemsPicture::unhighlightCurrentItem() { + InventoryPicture::unhighlightCurrentItem(); + _panelMovie.stop(); + _panelMovie.setFlags(0); + ((InventoryItem *)_currentItem)->setAnimationTime(_panelMovie.getTime()); +} + +TimeValue InventoryItemsPicture::getItemPanelTime(Item *item) { + TimeValue start, stop; + ((InventoryItem *)item)->getPanelTimes(start, stop); + ((InventoryItem *)item)->setAnimationTime(start); + return start; +} + +void InventoryItemsPicture::deactivateInventoryPicture() { + if (_active) { + InventoryPicture::deactivateInventoryPicture(); + _panelMovie.stop(); + _panelMovie.setFlags(0); + _panelMovie.setSegment(0, _panelMovie.getDuration()); + _isLooping = true; + } +} + +void InventoryItemsPicture::playEndMessage(DisplayElement *pushElement) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + Movie endMessage(0); + + _shouldDrawHighlight = false; + endMessage.shareSurface(this); + endMessage.initFromMovieFile("Images/Caldoria/A56 Congrats"); + endMessage.moveMovieBoxTo(kFinalMessageLeft - kInventoryPushLeft, kFinalMessageTop - kInventoryPushTop); + endMessage.setTriggeredElement(pushElement); + endMessage.start(); + + while (endMessage.isRunning()) { + vm->checkCallBacks(); + vm->refreshDisplay(); + g_system->delayMillis(10); + } + + endMessage.stop(); +} + +BiochipPicture::BiochipPicture(const DisplayElementID id, InputHandler *nextHandler, Inventory *inventory) : + InventoryPicture(id, nextHandler, inventory) { + _pictName = "Images/Items/Biochips/Biochip Panel"; + _movieName = "Images/Items/Biochips/Biochip Panel Movie"; + _highlightName = "Images/Items/Biochips/BioChip Hilite"; + + _itemsPerRow = 4; + _numberOfRows = 2; + _itemWidth = 46; + _itemHeight = 46; + _itemX = 4; + _itemY = 24; +} + +void BiochipPicture::unhighlightCurrentItem() { + InventoryPicture::unhighlightCurrentItem(); + CoordType x, y; + getItemXY(_currentItemIndex, x, y); + _panelMovie.show(); + _panelMovie.moveMovieBoxTo(x, y); + _panelMovie.setTime(getItemPanelTime(_currentItem)); + _panelMovie.redrawMovieWorld(); +} + +TimeValue BiochipPicture::getItemPanelTime(Item *item) { + return ((BiochipItem *)item)->getPanelTime(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/inventorypicture.h b/engines/pegasus/items/inventorypicture.h new file mode 100644 index 0000000000..18e85ef988 --- /dev/null +++ b/engines/pegasus/items/inventorypicture.h @@ -0,0 +1,125 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_INVENTORYPICTURE_H +#define PEGASUS_ITEMS_INVENTORYPICTURE_H + +#include "pegasus/input.h" +#include "pegasus/movie.h" +#include "pegasus/surface.h" + +namespace Pegasus { + +class Inventory; +class Item; +class Input; +class Transition; + +class InventoryPicture : public InputHandler, public Picture { +public: + InventoryPicture(const DisplayElementID, InputHandler *, Inventory *); + virtual ~InventoryPicture() {} + + void initInventoryImage(Transition *); + void throwAwayInventoryImage(); + + void panelUp(); + void activateInventoryPicture(); + void deactivateInventoryPicture(); + void handleInput(const Input &, const Hotspot *); + bool wantsCursor() { return false; } + + InventoryResult addInventoryItem(Item *); + InventoryResult removeInventoryItem(Item *); + void removeAllItems(); + Item *getCurrentItem() { return _currentItem; } + void setCurrentItemIndex(int32); + void setCurrentItemID(ItemID); + int32 getCurrentItemIndex() { return _currentItemIndex; } + bool itemInInventory(Item *); + bool itemInInventory(const ItemID); + +protected: + void getItemXY(uint32, CoordType &, CoordType &); + void draw(const Common::Rect &); + void drawItemHighlight(const Common::Rect &); + virtual void highlightCurrentItem(); + virtual void unhighlightCurrentItem() {} + virtual TimeValue getItemPanelTime(Item *) = 0; + + Inventory *_inventory; + uint32 _lastReferenceCount; + Frame _highlightImage; + Movie _panelMovie; + int32 _currentItemIndex; + Item *_currentItem; + Common::Rect _highlightBounds; + bool _active, _shouldDrawHighlight; + + Common::String _pictName; + Common::String _movieName; + Common::String _highlightName; + uint16 _itemsPerRow; + uint16 _numberOfRows; + uint16 _itemWidth; + uint16 _itemHeight; + uint16 _itemX; + uint16 _itemY; +}; + +class InventoryItemsPicture : public InventoryPicture { +public: + InventoryItemsPicture(const DisplayElementID, InputHandler *, Inventory *); + virtual ~InventoryItemsPicture() {} + + void deactivateInventoryPicture(); + + void disableLooping() { _isLooping = false; } + + void playEndMessage(DisplayElement *); + +protected: + virtual void highlightCurrentItem(); + virtual void unhighlightCurrentItem(); + virtual TimeValue getItemPanelTime(Item *); + void loopCurrentItem(); + + InputHandler *_previousHandler; + bool _isLooping; +}; + +class BiochipPicture : public InventoryPicture { +public: + BiochipPicture(const DisplayElementID, InputHandler *, Inventory *); + virtual ~BiochipPicture() {} + +protected: + virtual void unhighlightCurrentItem(); + virtual TimeValue getItemPanelTime(Item *); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/item.cpp b/engines/pegasus/items/item.cpp new file mode 100644 index 0000000000..a49bb4a466 --- /dev/null +++ b/engines/pegasus/items/item.cpp @@ -0,0 +1,314 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/error.h" +#include "common/stream.h" + +#include "pegasus/constants.h" +#include "pegasus/elements.h" +#include "pegasus/pegasus.h" +#include "pegasus/surface.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/item.h" +#include "pegasus/items/itemlist.h" +#include "pegasus/items/biochips/biochipitem.h" +#include "pegasus/items/inventory/inventoryitem.h" + +namespace Pegasus { + +Item::Item(const ItemID id, const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) : IDObject(id) { + _itemNeighborhood = neighborhood; + _itemRoom = room; + _itemDirection = direction; + _itemWeight = 1; + _itemOwnerID = kNoActorID; + _itemState = 0; + + PegasusEngine *vm = (PegasusEngine *)g_engine; + + Common::SeekableReadStream *info = vm->_resFork->getResource(kItemInfoResType, kItemBaseResID + id); + if (info) { + _itemInfo.infoLeftTime = info->readUint32BE(); + _itemInfo.infoRightStart = info->readUint32BE(); + _itemInfo.infoRightStop = info->readUint32BE(); + _itemInfo.dragSpriteNormalID = info->readUint16BE(); + _itemInfo.dragSpriteUsedID = info->readUint16BE(); + + if (vm->isDemo()) { + // Adjust info right times to account for the stuff that was chopped out of the + // info right movies. + // Assumes time scale of 600. + + // Gap times in seconds + static const TimeValue kGap1 = 24; + static const TimeValue kGap2 = 34; + static const TimeValue kGap3 = 4; + static const TimeValue kGap4 = 4; + + static const TimeValue kGapForGroup1 = kGap1; + static const TimeValue kGapForGroup2 = kGapForGroup1 + kGap2; + static const TimeValue kGapForGroup3 = kGapForGroup2 + kGap3; + static const TimeValue kGapForGroup4 = kGapForGroup3 + kGap4; + + switch (id) { + case kHistoricalLog: + case kJourneymanKey: + case kKeyCard: + _itemInfo.infoRightStart -= 600 * kGapForGroup1; + _itemInfo.infoRightStop -= 600 * kGapForGroup1; + break; + case kAIBiochip: + _itemInfo.infoRightStart -= 600 * kGapForGroup2; + _itemInfo.infoRightStop -= 600 * kGapForGroup2; + break; + case kMapBiochip: + _itemInfo.infoRightStart -= 600 * kGapForGroup3; + _itemInfo.infoRightStop -= 600 * kGapForGroup3; + break; + case kPegasusBiochip: + _itemInfo.infoRightStart -= 600 * kGapForGroup4; + _itemInfo.infoRightStop -= 600 * kGapForGroup4; + break; + } + } + + delete info; + } else { + memset(&_itemInfo, 0, sizeof(_itemInfo)); + } + + Common::SeekableReadStream *middleAreaInfo = vm->_resFork->getResource(kMiddleAreaInfoResType, kItemBaseResID + id); + if (middleAreaInfo) { + _sharedAreaInfo = readItemState(middleAreaInfo); + delete middleAreaInfo; + } else { + // Only kArgonPickup does not have this + memset(&_sharedAreaInfo, 0, sizeof(_sharedAreaInfo)); + } + + Common::SeekableReadStream *extraInfo = vm->_resFork->getResource(kItemExtraInfoResType, kItemBaseResID + id); + if (!extraInfo) + error("Extra info not found for item %d", id); + + _itemExtras.numEntries = extraInfo->readUint16BE(); + _itemExtras.entries = new ItemExtraEntry[_itemExtras.numEntries]; + for (uint16 i = 0; i < _itemExtras.numEntries; i++) { + _itemExtras.entries[i].extraID = extraInfo->readUint32BE(); + _itemExtras.entries[i].extraArea = extraInfo->readUint16BE(); + _itemExtras.entries[i].extraStart = extraInfo->readUint32BE(); + _itemExtras.entries[i].extraStop = extraInfo->readUint32BE(); + } + + delete extraInfo; + + g_allItems.push_back(this); +} + +Item::~Item() { + delete[] _sharedAreaInfo.entries; + delete[] _itemExtras.entries; +} + +void Item::writeToStream(Common::WriteStream *stream) { + stream->writeUint16BE(_itemNeighborhood); + stream->writeUint16BE(_itemRoom); + stream->writeByte(_itemDirection); + stream->writeUint16BE(_itemOwnerID); + stream->writeUint16BE(_itemState); +} + +void Item::readFromStream(Common::ReadStream *stream) { + _itemNeighborhood = stream->readUint16BE(); + _itemRoom = stream->readUint16BE(); + _itemDirection = stream->readByte(); + _itemOwnerID = stream->readUint16BE(); + _itemState = stream->readUint16BE(); +} + +ActorID Item::getItemOwner() const { + return _itemOwnerID; +} + +void Item::setItemOwner(const ActorID owner) { + _itemOwnerID = owner; + + if (owner == kNoActorID) { + if (isSelected()) + deselect(); + removedFromInventory(); + } else { + addedToInventory(); + } +} + +WeightType Item::getItemWeight() { + return _itemWeight; +} + +ItemState Item::getItemState() const { + return _itemState; +} + +void Item::setItemState(const ItemState state) { + if (state != _itemState) { + _itemState = state; + + if (getItemType() == kInventoryItemType && ((PegasusEngine *)g_engine)->getCurrentInventoryItem() == (InventoryItem *)this) + select(); + else if (getItemType() == kBiochipItemType && ((PegasusEngine *)g_engine)->getCurrentBiochip() == (BiochipItem *)this) + select(); + } +} + +void Item::getItemRoom(NeighborhoodID &neighborhood, RoomID &room, DirectionConstant &direction) const { + neighborhood = _itemNeighborhood; + room = _itemRoom; + direction = _itemDirection; +} + +void Item::setItemRoom(const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) { + _itemNeighborhood = neighborhood; + _itemRoom = room; + _itemDirection = direction; + + if (neighborhood == kNoNeighborhoodID) + pickedUp(); + else + dropped(); +} + +NeighborhoodID Item::getItemNeighborhood() const { + return _itemNeighborhood; +} + +TimeValue Item::getSharedAreaTime() const { + if (!_sharedAreaInfo.entries) + return 0xffffffff; + + TimeValue time; + ItemState state; + + findItemStateEntryByState(_sharedAreaInfo, _itemState, time); + if (time == 0xffffffff) + getItemStateEntry(_sharedAreaInfo, 0, state, time); + + return time; +} + +// Must affect images in shared area. +void Item::select() { + _isSelected = true; + + if (g_AIArea) { + if (getItemType() == kInventoryItemType) + g_AIArea->setAIAreaToTime(kInventorySignature, kMiddleAreaSignature, getSharedAreaTime()); + else + g_AIArea->setAIAreaToTime(kBiochipSignature, kMiddleAreaSignature, getSharedAreaTime()); + } +} + +void Item::deselect() { + _isActive = false; + _isSelected = false; + + if (g_AIArea) { + if (getItemType() == kInventoryItemType) + g_AIArea->setAIAreaToTime(kInventorySignature, kMiddleAreaSignature, 0xffffffff); + else + g_AIArea->setAIAreaToTime(kBiochipSignature, kMiddleAreaSignature, 0xffffffff); + } +} + +void Item::getItemStateEntry(ItemStateInfo info, uint32 index, ItemState &state, TimeValue &time) { + if (index < info.numEntries) { + state = info.entries[index].itemState; + time = info.entries[index].itemTime; + } else { + state = kNoItemState; + time = 0xffffffff; + } +} + +void Item::findItemStateEntryByState(ItemStateInfo info, ItemState state, TimeValue &time) { + for (uint16 i = 0; i < info.numEntries; i++) { + if (info.entries[i].itemState == state) { + time = info.entries[i].itemTime; + return; + } + } + + time = 0xffffffff; +} + +ItemStateInfo Item::readItemState(Common::SeekableReadStream *stream) { + ItemStateInfo info; + + info.numEntries = stream->readUint16BE(); + info.entries = new ItemStateEntry[info.numEntries]; + for (uint16 i = 0; i < info.numEntries; i++) { + info.entries[i].itemState = stream->readSint16BE(); + info.entries[i].itemTime = stream->readUint32BE(); + } + + return info; +} + +Sprite *Item::getDragSprite(const DisplayElementID id) const { + PegasusEngine *vm = (PegasusEngine *)g_engine; + Sprite *result = new Sprite(id); + SpriteFrame *frame = new SpriteFrame(); + + frame->initFromPICTResource(vm->_resFork, _itemInfo.dragSpriteNormalID, true); + result->addFrame(frame, 0, 0); + + if (_itemInfo.dragSpriteNormalID != _itemInfo.dragSpriteUsedID) { + frame = new SpriteFrame(); + frame->initFromPICTResource(vm->_resFork, _itemInfo.dragSpriteUsedID, true); + } + + result->addFrame(frame, 0, 0); + result->setCurrentFrameIndex(0); + return result; +} + +TimeValue Item::getInfoLeftTime() const { + return _itemInfo.infoLeftTime; +} + +void Item::getInfoRightTimes(TimeValue &start, TimeValue &stop) const { + start = _itemInfo.infoRightStart; + stop = _itemInfo.infoRightStop; +} + +void Item::findItemExtra(const uint32 extraID, ItemExtraEntry &entry) { + for (uint32 i = 0; i < _itemExtras.numEntries; i++) { + if (_itemExtras.entries[i].extraID == extraID) { + entry = _itemExtras.entries[i]; + return; + } + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/item.h b/engines/pegasus/items/item.h new file mode 100644 index 0000000000..efcf24100c --- /dev/null +++ b/engines/pegasus/items/item.h @@ -0,0 +1,363 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_ITEM_H +#define PEGASUS_ITEMS_ITEM_H + +#include "common/endian.h" + +#include "pegasus/types.h" +#include "pegasus/util.h" + +namespace Common { + class Error; + class ReadStream; + class WriteStream; + class SeekableReadStream; +} + +namespace Pegasus { + +// JMPItemInfo contains resource data used by all Items. + +struct JMPItemInfo { + TimeValue infoLeftTime; + TimeValue infoRightStart; + TimeValue infoRightStop; + uint32 dragSpriteNormalID; + uint32 dragSpriteUsedID; +}; + +// ItemStateEntry contains a single state/TimeValue pair. The TimeValue is +// the time value to set the shared area movie that corresponds with the given +// state of an inventory item. + +struct ItemStateEntry { + ItemState itemState; + TimeValue itemTime; +}; + +struct ItemStateInfo { + uint16 numEntries; // For easy ResEdit access + ItemStateEntry *entries; +}; + +// ItemExtraEntry + +static const short kLeftAreaExtra = 0; +static const short kMiddleAreaExtra = 1; +static const short kRightAreaExtra = 2; + +struct ItemExtraEntry { + uint32 extraID; + uint16 extraArea; + TimeValue extraStart; + TimeValue extraStop; +}; + +struct ItemExtraInfo { + uint16 numEntries; // For easy ResEdit access + ItemExtraEntry *entries; +}; + +// Inventory info resource type and ID: +// Individual inventory items are stored in these resource types. +// Resource ID is item ID + kItemBaseResID. + +static const uint32 kItemInfoResType = MKTAG('I', 't', 'e', 'm'); // JMPItemInfoHandle +static const uint32 kLeftAreaInfoResType = MKTAG('L', 'e', 'f', 't'); // ItemStateInfoHandle +static const uint32 kMiddleAreaInfoResType = MKTAG('M', 'i', 'd', 'l'); // ItemStateInfoHandle +static const uint32 kRightAreaInfoResType = MKTAG('R', 'g', 'h', 't'); // ItemStateInfoHandle +static const uint32 kItemExtraInfoResType = MKTAG('I', 'X', 't', 'r'); // ItemExtraInfoHandle + +static const uint16 kItemBaseResID = 128; + +// Item IDs. + +static const ItemID kAirMask = 7; +static const ItemID kAntidote = 8; +static const ItemID kArgonCanister = 9; +static const ItemID kCardBomb = 10; +static const ItemID kCrowbar = 11; +static const ItemID kGasCanister = 12; +static const ItemID kHistoricalLog = 13; +static const ItemID kJourneymanKey = 14; +static const ItemID kKeyCard = 15; +static const ItemID kMachineGun = 16; +static const ItemID kMarsCard = 17; +static const ItemID kNitrogenCanister = 18; +static const ItemID kOrangeJuiceGlassFull = 19; +static const ItemID kOrangeJuiceGlassEmpty = 20; +static const ItemID kPoisonDart = 21; +static const ItemID kSinclairKey = 22; +static const ItemID kStunGun = 23; +static const ItemID kArgonPickup = 24; + +// Biochips. + +static const ItemID kAIBiochip = 0; +static const ItemID kInterfaceBiochip = 1; +static const ItemID kMapBiochip = 2; +static const ItemID kOpticalBiochip = 3; +static const ItemID kPegasusBiochip = 4; +static const ItemID kRetinalScanBiochip = 5; +static const ItemID kShieldBiochip = 6; + +static const ItemID kNumItems = 25; + +// Item States. + +static const ItemState kAI000 = 0; +static const ItemState kAI005 = 1; +static const ItemState kAI006 = 2; +static const ItemState kAI010 = 3; +static const ItemState kAI015 = 4; +static const ItemState kAI016 = 5; +static const ItemState kAI020 = 6; +static const ItemState kAI024 = 7; +static const ItemState kAI100 = 8; +static const ItemState kAI101 = 9; +static const ItemState kAI105 = 10; +static const ItemState kAI106 = 11; +static const ItemState kAI110 = 12; +static const ItemState kAI111 = 13; +static const ItemState kAI115 = 14; +static const ItemState kAI116 = 15; +static const ItemState kAI120 = 16; +static const ItemState kAI121 = 17; +static const ItemState kAI124 = 18; +static const ItemState kAI125 = 19; +static const ItemState kAI126 = 20; +static const ItemState kAI200 = 21; +static const ItemState kAI201 = 22; +static const ItemState kAI202 = 23; +static const ItemState kAI205 = 24; +static const ItemState kAI206 = 25; +static const ItemState kAI210 = 26; +static const ItemState kAI211 = 27; +static const ItemState kAI212 = 28; +static const ItemState kAI215 = 29; +static const ItemState kAI216 = 30; +static const ItemState kAI220 = 31; +static const ItemState kAI221 = 32; +static const ItemState kAI222 = 33; +static const ItemState kAI224 = 34; +static const ItemState kAI225 = 35; +static const ItemState kAI226 = 36; +static const ItemState kAI300 = 37; +static const ItemState kAI301 = 38; +static const ItemState kAI302 = 39; +static const ItemState kAI303 = 40; +static const ItemState kAI305 = 41; +static const ItemState kAI306 = 42; +static const ItemState kAI310 = 43; +static const ItemState kAI311 = 44; +static const ItemState kAI312 = 45; +static const ItemState kAI313 = 46; +static const ItemState kAI315 = 47; +static const ItemState kAI316 = 48; +static const ItemState kAI320 = 49; +static const ItemState kAI321 = 50; +static const ItemState kAI322 = 51; +static const ItemState kAI323 = 52; +static const ItemState kAI324 = 53; +static const ItemState kAI325 = 54; +static const ItemState kAI326 = 55; +static const ItemState kNormalItem = 56; +static const ItemState kMapUnavailable = 57; +static const ItemState kMapEngaged = 58; +static const ItemState kOptical000 = 59; +static const ItemState kOptical001 = 60; +static const ItemState kOptical002 = 61; +static const ItemState kOptical010 = 62; +static const ItemState kOptical011 = 63; +static const ItemState kOptical012 = 64; +static const ItemState kOptical020 = 65; +static const ItemState kOptical021 = 66; +static const ItemState kOptical100 = 67; +static const ItemState kOptical101 = 68; +static const ItemState kOptical102 = 69; +static const ItemState kOptical110 = 70; +static const ItemState kOptical111 = 71; +static const ItemState kOptical112 = 72; +static const ItemState kOptical120 = 73; +static const ItemState kOptical121 = 74; +static const ItemState kOptical200 = 75; +static const ItemState kOptical201 = 76; +static const ItemState kOptical210 = 77; +static const ItemState kOptical211 = 78; +static const ItemState kPegasusTSA00 = 79; +static const ItemState kPegasusTSA10 = 80; +static const ItemState kPegasusPrehistoric00 = 81; +static const ItemState kPegasusPrehistoric01 = 82; +static const ItemState kPegasusPrehistoric10 = 83; +static const ItemState kPegasusPrehistoric11 = 84; +static const ItemState kPegasusMars00 = 85; +static const ItemState kPegasusMars01 = 86; +static const ItemState kPegasusMars10 = 87; +static const ItemState kPegasusMars11 = 88; +static const ItemState kPegasusNorad00 = 89; +static const ItemState kPegasusNorad01 = 90; +static const ItemState kPegasusNorad10 = 91; +static const ItemState kPegasusNorad11 = 92; +static const ItemState kPegasusWSC00 = 93; +static const ItemState kPegasusWSC01 = 94; +static const ItemState kPegasusWSC10 = 95; +static const ItemState kPegasusWSC11 = 96; +static const ItemState kPegasusCaldoria = 97; +static const ItemState kRetinalSimulating = 98; +static const ItemState kShieldNormal = 99; +static const ItemState kShieldRadiation = 100; +static const ItemState kShieldPlasma = 101; +static const ItemState kShieldCardBomb = 102; +static const ItemState kShieldDraining = 103; +static const ItemState kAirMaskEmptyOff = 104; +static const ItemState kAirMaskEmptyFilter = 105; +static const ItemState kAirMaskLowOff = 106; +static const ItemState kAirMaskLowFilter = 107; +static const ItemState kAirMaskLowOn = 108; +static const ItemState kAirMaskFullOff = 109; +static const ItemState kAirMaskFullFilter = 110; +static const ItemState kAirMaskFullOn = 111; +static const ItemState kArgonEmpty = 112; +static const ItemState kArgonFull = 113; +static const ItemState kFlashlightOff = 114; +static const ItemState kFlashlightOn = 115; +static const ItemState kNitrogenEmpty = 116; +static const ItemState kNitrogenFull = 117; +static const ItemState kFullGlass = 118; + +// Extra IDs. + +static const uint32 kRetinalScanSearching = 0; +static const uint32 kRetinalScanActivated = 1; +static const uint32 kShieldIntro = 2; +static const uint32 kRemoveAirMask = 3; +static const uint32 kRemoveArgon = 4; +static const uint32 kRemoveCrowbar = 5; +static const uint32 kGasCanLoop = 6; +static const uint32 kRemoveJourneymanKey = 7; +static const uint32 kRemoveMarsCard = 8; +static const uint32 kRemoveNitrogen = 9; +static const uint32 kRemoveGlass = 10; +static const uint32 kRemoveDart = 11; +static const uint32 kRemoveSinclairKey = 12; + +enum ItemType { + kInventoryItemType, + kBiochipItemType +}; + +class Sprite; + +/* + + Item is an object which can be picked up and carried around. + Items have + a location + an ID. + weight + an owner (kNoActorID if no one is carrying the Item) + +*/ + +class Item : public IDObject { +public: + Item(const ItemID id, const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction); + virtual ~Item(); + + // WriteToStream writes everything EXCEPT the item's ID. + // It is assumed that the calling function will write and read the ID. + virtual void writeToStream(Common::WriteStream *stream); + virtual void readFromStream(Common::ReadStream *stream); + + virtual ActorID getItemOwner() const; + virtual void setItemOwner(const ActorID owner); + + void getItemRoom(NeighborhoodID &neighborhood, RoomID &room, DirectionConstant &direction) const; + void setItemRoom(const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction); + NeighborhoodID getItemNeighborhood() const; + + virtual WeightType getItemWeight(); + + virtual void setItemState(const ItemState state); + virtual ItemState getItemState() const; + + virtual ItemType getItemType() = 0; + + TimeValue getInfoLeftTime() const; + void getInfoRightTimes(TimeValue &, TimeValue &) const; + TimeValue getSharedAreaTime() const; + + Sprite *getDragSprite(const DisplayElementID) const; + + /* + select -- called when this item becomes current. Also called when the inventory + panel holding this item is raised and this is the current item. + deselect -- called when this item is no longer current. + activate -- called on the current item when the panel is closed. + */ + // In an override of these three member functions, you must call the inherited + // member functions. + virtual void select(); + virtual void deselect(); + virtual bool isSelected() { return _isSelected; } + + virtual void activate() { _isActive = true; } + virtual bool isActive() { return _isActive; } + virtual void pickedUp() {} + virtual void addedToInventory() {} + virtual void removedFromInventory() {} + virtual void dropped() {} + + // Called when the shared area is taken by another item, but this item is still + // selected. + virtual void giveUpSharedArea() {} + virtual void takeSharedArea() {} + + void findItemExtra(const uint32 extraID, ItemExtraEntry &entry); + +protected: + NeighborhoodID _itemNeighborhood; + RoomID _itemRoom; + DirectionConstant _itemDirection; + ActorID _itemOwnerID; + WeightType _itemWeight; + ItemState _itemState; + + JMPItemInfo _itemInfo; + ItemStateInfo _sharedAreaInfo; + ItemExtraInfo _itemExtras; + bool _isActive; + bool _isSelected; + + static void getItemStateEntry(ItemStateInfo, uint32, ItemState &, TimeValue &); + static void findItemStateEntryByState(ItemStateInfo, ItemState, TimeValue &); + static ItemStateInfo readItemState(Common::SeekableReadStream *stream); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/itemdragger.cpp b/engines/pegasus/items/itemdragger.cpp new file mode 100644 index 0000000000..9e77ad5745 --- /dev/null +++ b/engines/pegasus/items/itemdragger.cpp @@ -0,0 +1,193 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/elements.h" +#include "pegasus/hotspot.h" +#include "pegasus/pegasus.h" +#include "pegasus/items/itemdragger.h" + +namespace Pegasus { + +SpriteDragger::SpriteDragger() { + _draggingSprite = 0; + _limitRect = Common::Rect(-30000, -30000, 30000, 30000); + _slopRect = Common::Rect(-30000, -30000, 30000, 30000); + _dragOffset.x = 0; + _dragOffset.y = 0; + _lastHotspot = 0; +} + +void SpriteDragger::setDragSprite(Sprite *newSprite) { + if (!isTracking()) + _draggingSprite = newSprite; +} + +void SpriteDragger::setDragConstraints(const Common::Rect &limitRect, const Common::Rect &slopRect) { + if (!isTracking()) { + _rawLimitRect = limitRect; + _slopRect = slopRect; + } +} + +void SpriteDragger::getDragConstraints(Common::Rect &limitRect, Common::Rect &slopRect) const { + limitRect = _rawLimitRect; + slopRect = _slopRect; +} + +void SpriteDragger::startTracking(const Input &input) { + if (_draggingSprite) { + Tracker::startTracking(input); + + if (isTracking()) { + input.getInputLocation(_startPoint); + _lastRawPoint = _startRawPoint = _startPoint; + + Common::Rect r; + _draggingSprite->getBounds(r); + _dragOffset.x = _startPoint.x - r.left; + _dragOffset.y = _startPoint.y - r.top; + + _limitRect = _rawLimitRect; + _limitRect.left += _dragOffset.x; + _limitRect.top += _dragOffset.y; + _limitRect.right -= r.width() - _dragOffset.x; + _limitRect.bottom -= r.height() - _dragOffset.y; + pinPointInRect(_limitRect, _startPoint); + + _lastPoint = _startPoint; + if (_startPoint != _startRawPoint) { + Common::Point pt = _startPoint - _dragOffset; + _draggingSprite->moveElementTo(pt.x, pt.y); + } + + _lastHotspot = g_allHotspots.findHotspot(_lastRawPoint); + if (_lastHotspot) + enterHotspot(_lastHotspot); + } + } +} + +void SpriteDragger::continueTracking(const Input &input) { + if (_draggingSprite) { + Common::Point rawPoint; + input.getInputLocation(rawPoint); + + if (!_slopRect.contains(rawPoint)) + rawPoint = _startRawPoint; + + if (rawPoint != _lastRawPoint) { + Common::Point newPoint = rawPoint; + pinPointInRect(_limitRect, newPoint); + newPoint -= _dragOffset; + + if (newPoint != _lastPoint) { + _draggingSprite->moveElementTo(newPoint.x, newPoint.y); + _lastPoint = newPoint; + } + + Hotspot *newHotspot = g_allHotspots.findHotspot(rawPoint); + if (newHotspot != _lastHotspot) { + if (_lastHotspot) + exitHotspot(_lastHotspot); + if (newHotspot) + enterHotspot(newHotspot); + _lastHotspot = newHotspot; + } + + _lastRawPoint = rawPoint; + } + } +} + +void SpriteDragger::pinPointInRect(const Common::Rect &r, Common::Point &pt) { + pt.x = CLIP<int>(pt.x, r.left, r.right - 1); + pt.y = CLIP<int>(pt.y, r.top, r.bottom - 1); +} + +ItemDragger::ItemDragger(PegasusEngine *owner) : _inventoryDropSpot(kInventoryDropSpotID), _biochipDropSpot(kBiochipDropSpotID), + _inventoryHighlight(kInventoryDropHighlightID), _biochipHighlight(kBiochipDropHighlightID) { + _owner = owner; + + Common::Rect r(kInventoryDropLeft, kInventoryDropTop, kInventoryDropRight, kInventoryDropBottom); + _inventoryDropSpot.setArea(r); + _inventoryDropSpot.setHotspotFlags(kDropItemSpotFlag); + g_allHotspots.push_back(&_inventoryDropSpot); + + r = Common::Rect(kBiochipDropLeft, kBiochipDropTop, kBiochipDropRight, kBiochipDropBottom); + _biochipDropSpot.setArea(r); + _biochipDropSpot.setHotspotFlags(kDropBiochipSpotFlag); + g_allHotspots.push_back(&_biochipDropSpot); +} + +void ItemDragger::startTracking(const Input &input) { + _inventoryHighlight.setDisplayOrder(kInventoryHiliteOrder); + _inventoryHighlight.startDisplaying(); + + _biochipHighlight.setDisplayOrder(kBiochipHiliteOrder); + _biochipHighlight.startDisplaying(); + + SpriteDragger::startTracking(input); +} + +void ItemDragger::stopTracking(const Input &input) { + SpriteDragger::stopTracking(input); + _inventoryHighlight.hide(); + _biochipHighlight.hide(); + _inventoryHighlight.stopDisplaying(); + _biochipHighlight.stopDisplaying(); + _owner->dragTerminated(input); +} + +bool ItemDragger::stopTrackingInput(const Input &input) { + return !JMPPPInput::isDraggingInput(input); +} + +void ItemDragger::enterHotspot(Hotspot *spot) { + if (spot->getObjectID() == kInventoryDropSpotID) + _inventoryHighlight.show(); + else if (spot->getObjectID() == kBiochipDropSpotID) + _biochipHighlight.show(); + else if ((spot->getHotspotFlags() & kDropItemSpotFlag) != 0) + _draggingSprite->setCurrentFrameIndex(1); +} + +void ItemDragger::exitHotspot(Hotspot *spot) { + if (spot->getObjectID() == kInventoryDropSpotID) + _inventoryHighlight.hide(); + else if (spot->getObjectID() == kBiochipDropSpotID) + _biochipHighlight.hide(); + else if ((spot->getHotspotFlags() & kDropItemSpotFlag) != 0) + _draggingSprite->setCurrentFrameIndex(0); +} + +void ItemDragger::setHighlightBounds() { + uint32 color = g_system->getScreenFormat().RGBToColor(0x48, 0x80, 0xD8); + _inventoryHighlight.setBounds(Common::Rect(76, 334, 172, 430)); + _inventoryHighlight.setHighlightColor(color); + _biochipHighlight.setBounds(Common::Rect(364, 334, 460, 430)); + _biochipHighlight.setHighlightColor(color); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/itemdragger.h b/engines/pegasus/items/itemdragger.h new file mode 100644 index 0000000000..69612316f3 --- /dev/null +++ b/engines/pegasus/items/itemdragger.h @@ -0,0 +1,96 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_ITEMDRAGGER_H +#define PEGASUS_ITEMS_ITEMDRAGGER_H + +#include "pegasus/elements.h" +#include "pegasus/input.h" + +namespace Pegasus { + +// TODO: Merge SpriteDragger and ItemDragger + +class Hotspot; +class Sprite; + +class SpriteDragger : public Tracker { +public: + SpriteDragger(); + virtual ~SpriteDragger() {} + + void setDragSprite(Sprite *); + Sprite *getDragSprite() { return _draggingSprite; } + + void setDragConstraints(const Common::Rect &, const Common::Rect &); + void getDragConstraints(Common::Rect &, Common::Rect &) const; + + void startTracking(const Input &); + void continueTracking(const Input&); + + Hotspot *getLastHotspot() const { return _lastHotspot; } + +protected: + virtual void enterHotspot(Hotspot *) {} + virtual void exitHotspot(Hotspot *) {} + + Sprite *_draggingSprite; + Common::Point _startPoint, _lastPoint, _dragOffset; + Common::Point _startRawPoint, _lastRawPoint; + Common::Rect _rawLimitRect; + Common::Rect _limitRect; + Common::Rect _slopRect; + Hotspot *_lastHotspot; + + // This is a replica of QuickDraw's PinPointInRect function + void pinPointInRect(const Common::Rect &, Common::Point &); +}; + +class PegasusEngine; + +class ItemDragger : public SpriteDragger { +public: + ItemDragger(PegasusEngine *); + virtual ~ItemDragger() {} + + void setHighlightBounds(); + void startTracking(const Input &); + void stopTracking(const Input &); + bool stopTrackingInput(const Input &); + +protected: + virtual void enterHotspot(Hotspot *); + virtual void exitHotspot(Hotspot *); + + PegasusEngine *_owner; + DropHighlight _inventoryHighlight; + Hotspot _inventoryDropSpot; + DropHighlight _biochipHighlight; + Hotspot _biochipDropSpot; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/items/itemlist.cpp b/engines/pegasus/items/itemlist.cpp new file mode 100644 index 0000000000..4c30975589 --- /dev/null +++ b/engines/pegasus/items/itemlist.cpp @@ -0,0 +1,69 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/error.h" +#include "common/stream.h" + +#include "engines/pegasus/items/item.h" +#include "engines/pegasus/items/itemlist.h" + +namespace Pegasus { + +// TODO: Don't use global construction! +ItemList g_allItems; + +ItemList::ItemList() { +} + +ItemList::~ItemList() { +} + +void ItemList::writeToStream(Common::WriteStream *stream) { + stream->writeUint32BE(size()); + + for (ItemIterator it = begin(); it != end(); it++) { + stream->writeUint16BE((*it)->getObjectID()); + (*it)->writeToStream(stream); + } +} + +void ItemList::readFromStream(Common::ReadStream *stream) { + uint32 itemCount = stream->readUint32BE(); + + for (uint32 i = 0; i < itemCount; i++) { + ItemID itemID = stream->readUint16BE(); + g_allItems.findItemByID(itemID)->readFromStream(stream); + } +} + +Item *ItemList::findItemByID(const ItemID id) { + for (ItemIterator it = begin(); it != end(); it++) + if ((*it)->getObjectID() == id) + return *it; + + return 0; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/items/itemlist.h b/engines/pegasus/items/itemlist.h new file mode 100644 index 0000000000..b5a1d489be --- /dev/null +++ b/engines/pegasus/items/itemlist.h @@ -0,0 +1,60 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_ITEMS_ITEMLIST_H +#define PEGASUS_ITEMS_ITEMLIST_H + +#include "common/list.h" + +#include "pegasus/types.h" + +namespace Common { + class ReadStream; + class WriteStream; +} + +namespace Pegasus { + +class Item; + +class ItemList : public Common::List<Item *> { +public: + ItemList(); + virtual ~ItemList(); + + virtual void writeToStream(Common::WriteStream *stream); + virtual void readFromStream(Common::ReadStream *stream); + + Item *findItemByID(const ItemID id); +}; + +typedef ItemList::iterator ItemIterator; + +// TODO: Don't use global construction! +extern ItemList g_allItems; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/menu.cpp b/engines/pegasus/menu.cpp new file mode 100644 index 0000000000..5d90f62b14 --- /dev/null +++ b/engines/pegasus/menu.cpp @@ -0,0 +1,1222 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/gamestate.h" +#include "pegasus/menu.h" +#include "pegasus/pegasus.h" +#include "pegasus/scoring.h" + +namespace Pegasus { + +GameMenu::GameMenu(const uint32 id) : IDObject(id), InputHandler((InputHandler *)((PegasusEngine *)g_engine)) { + _previousHandler = 0; + _lastCommand = kMenuCmdNoCommand; +} + +void GameMenu::becomeCurrentHandler() { + _previousHandler = InputHandler::setInputHandler(this); +} + +void GameMenu::restorePreviousHandler() { + InputHandler::setInputHandler(_previousHandler); +} + +void GameMenu::drawScore(GameScoreType score, GameScoreType total, const Common::Rect &scoreBounds, Surface *numbers) { + CoordType x = scoreBounds.right; + drawNumber(total, x, scoreBounds.top, numbers); + + x -= 12; + Common::Rect r1(120, 0, 132, 12); // The slash. + Common::Rect r2 = r1; + r2.moveTo(x, scoreBounds.top); + numbers->copyToCurrentPort(r1, r2); + drawNumber(score, x, scoreBounds.top, numbers); +} + +void GameMenu::drawNumber(GameScoreType number, CoordType &x, CoordType y, Surface *numbers) { + Common::Rect r1(0, 0, 12, 12); // Width, height of one digit + Common::Rect r2 = r1; + r2.moveTo(x - 12, y); + + do { + uint16 digit = number % 10; + number /= 10; + r1.moveTo(digit * 12, 0); + numbers->copyToCurrentPort(r1, r2); + r2.translate(-12, 0); + } while (number != 0); + + x = r2.right; +} + +enum { + kMainMenuStartDemo = 0, + kMainMenuCreditsDemo, + kMainMenuQuitDemo, + kFirstSelectionDemo = kMainMenuStartDemo, + kLastSelectionDemo = kMainMenuQuitDemo, + + kMainMenuOverview = 0, + kMainMenuStart, + kMainMenuRestore, + kMainMenuDifficulty, + kMainMenuCredits, + kMainMenuQuit, + kFirstSelection = kMainMenuOverview, + kLastSelection = kMainMenuQuit +}; + +static const CoordType kStartLeftDemo = 44; +static const CoordType kStartTopDemo = 336; + +static const CoordType kStartSelectLeftDemo = 40; +static const CoordType kStartSelectTopDemo = 331; + +static const CoordType kCreditsLeftDemo = 44; +static const CoordType kCreditsTopDemo = 372; + +static const CoordType kCreditsSelectLeftDemo = 40; +static const CoordType kCreditsSelectTopDemo = 367; + +static const CoordType kMainMenuQuitLeftDemo = 32; +static const CoordType kMainMenuQuitTopDemo = 412; + +static const CoordType kMainMenuQuitSelectLeftDemo = 28; +static const CoordType kMainMenuQuitSelectTopDemo = 408; + +static const CoordType kOverviewLeft = 200; +static const CoordType kOverviewTop = 208; + +static const CoordType kOverviewSelectLeft = 152; +static const CoordType kOverviewSelectTop = 204; + +static const CoordType kStartLeft = 212; +static const CoordType kStartTop = 256; + +static const CoordType kStartSelectLeft = 152; +static const CoordType kStartSelectTop = 252; + +static const CoordType kRestoreLeft = 212; +static const CoordType kRestoreTop = 296; + +static const CoordType kRestoreSelectLeft = 152; +static const CoordType kRestoreSelectTop = 292; + +static const CoordType kDifficultyLeft = 320; +static const CoordType kDifficultyTop = 340; + +static const CoordType kDifficultySelectLeft = 152; +static const CoordType kDifficultySelectTop = 336; + +static const CoordType kCreditsLeft = 212; +static const CoordType kCreditsTop = 388; + +static const CoordType kCreditsSelectLeft = 152; +static const CoordType kCreditsSelectTop = 384; + +static const CoordType kMainMenuQuitLeft = 212; +static const CoordType kMainMenuQuitTop = 428; + +static const CoordType kMainMenuQuitSelectLeft = 152; +static const CoordType kMainMenuQuitSelectTop = 424; + +// Never set the current input handler to the MainMenu. +MainMenu::MainMenu() : GameMenu(kMainMenuID), _menuBackground(0), _overviewButton(0), + _restoreButton(0), _adventureButton(0), _walkthroughButton(0), _startButton(0), + _creditsButton(0), _quitButton(0), _largeSelect(0), _smallSelect(0) { + + bool isDemo = ((PegasusEngine *)g_engine)->isDemo(); + + if (isDemo) + _menuBackground.initFromPICTFile("Images/Demo/DemoMenu.pict"); + else + _menuBackground.initFromPICTFile("Images/Main Menu/MainMenu.mac"); + _menuBackground.setDisplayOrder(0); + _menuBackground.startDisplaying(); + _menuBackground.show(); + + if (!isDemo) { + _overviewButton.initFromPICTFile("Images/Main Menu/pbOvervi.pict"); + _overviewButton.setDisplayOrder(1); + _overviewButton.moveElementTo(kOverviewLeft, kOverviewTop); + _overviewButton.startDisplaying(); + + _restoreButton.initFromPICTFile("Images/Main Menu/pbRestor.pict"); + _restoreButton.setDisplayOrder(1); + _restoreButton.moveElementTo(kRestoreLeft, kRestoreTop); + _restoreButton.startDisplaying(); + + _adventureButton.initFromPICTFile("Images/Main Menu/BtnAdv.pict"); + _adventureButton.setDisplayOrder(1); + _adventureButton.moveElementTo(kDifficultyLeft, kDifficultyTop); + _adventureButton.startDisplaying(); + + _walkthroughButton.initFromPICTFile("Images/Main Menu/BtnWlk.pict"); + _walkthroughButton.setDisplayOrder(1); + _walkthroughButton.moveElementTo(kDifficultyLeft, kDifficultyTop); + _walkthroughButton.startDisplaying(); + } + + if (isDemo) + _startButton.initFromPICTFile("Images/Demo/Start.pict"); + else + _startButton.initFromPICTFile("Images/Main Menu/pbStart.pict"); + _startButton.setDisplayOrder(1); + _startButton.moveElementTo(isDemo ? kStartLeftDemo : kStartLeft, isDemo ? kStartTopDemo : kStartTop); + _startButton.startDisplaying(); + + if (isDemo) + _creditsButton.initFromPICTFile("Images/Demo/Credits.pict"); + else + _creditsButton.initFromPICTFile("Images/Main Menu/pbCredit.pict"); + _creditsButton.setDisplayOrder(1); + _creditsButton.moveElementTo(isDemo ? kCreditsLeftDemo : kCreditsLeft, isDemo ? kCreditsTopDemo : kCreditsTop); + _creditsButton.startDisplaying(); + + if (isDemo) + _quitButton.initFromPICTFile("Images/Demo/Quit.pict"); + else + _quitButton.initFromPICTFile("Images/Main Menu/pbQuit.pict"); + _quitButton.setDisplayOrder(1); + _quitButton.moveElementTo(isDemo ? kMainMenuQuitLeftDemo : kMainMenuQuitLeft, isDemo ? kMainMenuQuitTopDemo : kMainMenuQuitTop); + _quitButton.startDisplaying(); + + if (isDemo) + _largeSelect.initFromPICTFile("Images/Demo/SelectL.pict", true); + else + _largeSelect.initFromPICTFile("Images/Main Menu/SelectL.pict", true); + _largeSelect.setDisplayOrder(1); + _largeSelect.startDisplaying(); + + if (isDemo) + _smallSelect.initFromPICTFile("Images/Demo/SelectS.pict", true); + else + _smallSelect.initFromPICTFile("Images/Main Menu/SelectS.pict", true); + _smallSelect.setDisplayOrder(1); + _smallSelect.startDisplaying(); + + _menuSelection = isDemo ? kFirstSelectionDemo : kFirstSelection; + + _adventureMode = true; + + _menuLoop.attachFader(&_menuFader); + _menuLoop.initFromAIFFFile("Sounds/Main Menu.aiff"); + + updateDisplay(); +} + +MainMenu::~MainMenu() { + if (_menuLoop.isPlaying()) + stopMainMenuLoop(); +} + +void MainMenu::startMainMenuLoop() { + FaderMoveSpec spec; + + _menuLoop.loopSound(); + spec.makeTwoKnotFaderSpec(30, 0, 0, 30, 255); + + // FIXME: Should be sync, but it's a pain to use the main menu right now + // with this one. + _menuFader.startFader(spec); +} + +void MainMenu::stopMainMenuLoop() { + FaderMoveSpec spec; + + spec.makeTwoKnotFaderSpec(30, 0, 255, 30, 0); + _menuFader.startFaderSync(spec); + _menuLoop.stopSound(); +} + +void MainMenu::handleInput(const Input &input, const Hotspot *cursorSpot) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + bool isDemo = vm->isDemo(); + + if (input.upButtonDown()) { + if (_menuSelection > (isDemo ? kFirstSelectionDemo : kFirstSelection)) { + _menuSelection--; + updateDisplay(); + } + } else if (input.downButtonDown()) { + if (_menuSelection < (isDemo ? kLastSelectionDemo : kLastSelection)) { + _menuSelection++; + updateDisplay(); + } + } else if (!isDemo && (input.leftButtonDown() || input.rightButtonDown())) { + if (_menuSelection == kMainMenuDifficulty) { + _adventureMode = !_adventureMode; + updateDisplay(); + } + } else if (JMPPPInput::isMenuButtonPressInput(input)) { + if (isDemo) { + switch (_menuSelection) { + case kMainMenuCreditsDemo: + _creditsButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _creditsButton.hide(); + setLastCommand(kMenuCmdCredits); + break; + case kMainMenuStartDemo: + _startButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _startButton.hide(); + setLastCommand(kMenuCmdStartAdventure); + break; + case kMainMenuQuitDemo: + _quitButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _quitButton.hide(); + setLastCommand(kMenuCmdQuit); + break; + } + } else { + switch (_menuSelection) { + case kMainMenuOverview: + _overviewButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _overviewButton.hide(); + setLastCommand(kMenuCmdOverview); + break; + case kMainMenuRestore: + _restoreButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _restoreButton.hide(); + setLastCommand(kMenuCmdRestore); + break; + case kMainMenuCredits: + _creditsButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _creditsButton.hide(); + setLastCommand(kMenuCmdCredits); + break; + case kMainMenuStart: + _startButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _startButton.hide(); + if (_adventureMode) + setLastCommand(kMenuCmdStartAdventure); + else + setLastCommand(kMenuCmdStartWalkthrough); + break; + case kMainMenuDifficulty: + _adventureMode = !_adventureMode; + updateDisplay(); + break; + case kMainMenuQuit: + _quitButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _quitButton.hide(); + setLastCommand(kMenuCmdQuit); + break; + } + } + } + + InputHandler::handleInput(input, cursorSpot); +} + +void MainMenu::updateDisplay() { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + if (vm->isDemo()) { + switch (_menuSelection) { + case kMainMenuStartDemo: + _smallSelect.moveElementTo(kStartSelectLeftDemo, kStartSelectTopDemo); + _smallSelect.show(); + _largeSelect.hide(); + break; + case kMainMenuCreditsDemo: + _smallSelect.moveElementTo(kCreditsSelectLeftDemo, kCreditsSelectTopDemo); + _smallSelect.show(); + _largeSelect.hide(); + break; + case kMainMenuQuitDemo: + _largeSelect.moveElementTo(kMainMenuQuitSelectLeftDemo, kMainMenuQuitSelectTopDemo); + _largeSelect.show(); + _smallSelect.hide(); + break; + } + } else { + switch (_menuSelection) { + case kMainMenuOverview: + _largeSelect.moveElementTo(kOverviewSelectLeft, kOverviewSelectTop); + _largeSelect.show(); + _smallSelect.hide(); + break; + case kMainMenuRestore: + _smallSelect.moveElementTo(kRestoreSelectLeft, kRestoreSelectTop); + _smallSelect.show(); + _largeSelect.hide(); + break; + case kMainMenuDifficulty: + if (_adventureMode) { + _adventureButton.show(); + _walkthroughButton.hide(); + } else { + _walkthroughButton.show(); + _adventureButton.hide(); + } + + _largeSelect.moveElementTo(kDifficultySelectLeft, kDifficultySelectTop); + _largeSelect.show(); + _smallSelect.hide(); + break; + case kMainMenuStart: + _smallSelect.moveElementTo(kStartSelectLeft, kStartSelectTop); + _smallSelect.show(); + _largeSelect.hide(); + break; + case kMainMenuCredits: + _smallSelect.moveElementTo(kCreditsSelectLeft, kCreditsSelectTop); + _smallSelect.show(); + _largeSelect.hide(); + break; + case kMainMenuQuit: + _smallSelect.moveElementTo(kMainMenuQuitSelectLeft, kMainMenuQuitSelectTop); + _smallSelect.show(); + _largeSelect.hide(); + break; + } + + vm->resetIntroTimer(); + } +} + +enum { + kCreditsMenuCoreTeam, + kCreditsMenuSupportTeam, + kCreditsMenuOriginalTeam, + kCreditsMenuTalent, + kCreditsMenuOtherTitles, + kCreditsMenuMainMenu, + + kCreditsFirstSelection = kCreditsMenuCoreTeam, + kCreditsLastSelection = kCreditsMenuMainMenu +}; + +static const CoordType kCreditsMovieLeft = 288; +static const CoordType kCreditsMovieTop = 0; + +static const CoordType kCoreTeamSelectLeft = 40; +static const CoordType kCoreTeamSelectTop = 223; + +static const CoordType kSupportTeamSelectLeft = 40; +static const CoordType kSupportTeamSelectTop = 259; + +static const CoordType kOriginalTeamSelectLeft = 40; +static const CoordType kOriginalTeamSelectTop = 295; + +static const CoordType kTalentSelectLeft = 40; +static const CoordType kTalentSelectTop = 331; + +static const CoordType kOtherTitlesSelectLeft = 40; +static const CoordType kOtherTitlesSelectTop = 367; + +static const CoordType kCreditsMainMenuLeft = 32; +static const CoordType kCreditsMainMenuTop = 412; + +static const CoordType kCreditsMainMenuSelectLeft = 30; +static const CoordType kCreditsMainMenuSelectTop = 408; + +static const TimeValue kCoreTeamTime = 0; +static const TimeValue kSupportTeamTime = 1920; +static const TimeValue kOriginalTeamTime = 3000; +static const TimeValue kTalentTime = 4440; +static const TimeValue kOtherTitlesTime = 4680; + +static const TimeValue kFrameIncrement = 120; // Three frames... + +// Never set the current input handler to the CreditsMenu. +CreditsMenu::CreditsMenu() : GameMenu(kCreditsMenuID), _menuBackground(0), _creditsMovie(0), + _mainMenuButton(0), _largeSelect(0), _smallSelect(0) { + + _menuBackground.initFromPICTFile("Images/Credits/CredScrn.pict"); + _menuBackground.setDisplayOrder(0); + _menuBackground.startDisplaying(); + _menuBackground.show(); + + _creditsMovie.initFromMovieFile("Images/Credits/Credits.movie"); + _creditsMovie.setDisplayOrder(1); + _creditsMovie.moveElementTo(kCreditsMovieLeft, kCreditsMovieTop); + _creditsMovie.startDisplaying(); + _creditsMovie.show(); + _creditsMovie.redrawMovieWorld(); + + _mainMenuButton.initFromPICTFile("Images/Credits/MainMenu.pict"); + _mainMenuButton.setDisplayOrder(1); + _mainMenuButton.moveElementTo(kCreditsMainMenuLeft, kCreditsMainMenuTop); + _mainMenuButton.startDisplaying(); + + _largeSelect.initFromPICTFile("Images/Credits/SelectL.pict", true); + _largeSelect.setDisplayOrder(2); + _largeSelect.moveElementTo(kCreditsMainMenuSelectLeft, kCreditsMainMenuSelectTop); + _largeSelect.startDisplaying(); + + _smallSelect.initFromPICTFile("Images/Credits/SelectS.pict", true); + _smallSelect.setDisplayOrder(2); + _smallSelect.show(); + _smallSelect.startDisplaying(); + + _menuSelection = -1; + + newMenuSelection(kCreditsMenuCoreTeam); +} + +// Assumes the new selection is never more than one away from the old... +void CreditsMenu::newMenuSelection(const int newSelection) { + if (newSelection != _menuSelection) { + switch (newSelection) { + case kCreditsMenuCoreTeam: + _smallSelect.moveElementTo(kCoreTeamSelectLeft, kCoreTeamSelectTop); + _creditsMovie.setTime(kCoreTeamTime); + _creditsMovie.redrawMovieWorld(); + break; + case kCreditsMenuSupportTeam: + _smallSelect.moveElementTo(kSupportTeamSelectLeft, kSupportTeamSelectTop); + _creditsMovie.setTime(kSupportTeamTime); + _creditsMovie.redrawMovieWorld(); + break; + case kCreditsMenuOriginalTeam: + _smallSelect.moveElementTo(kOriginalTeamSelectLeft, kOriginalTeamSelectTop); + _creditsMovie.setTime(kOriginalTeamTime); + _creditsMovie.redrawMovieWorld(); + break; + case kCreditsMenuTalent: + _smallSelect.moveElementTo(kTalentSelectLeft, kTalentSelectTop); + _creditsMovie.setTime(kTalentTime); + _creditsMovie.redrawMovieWorld(); + break; + case kCreditsMenuOtherTitles: + _smallSelect.moveElementTo(kOtherTitlesSelectLeft, kOtherTitlesSelectTop); + _smallSelect.show(); + _largeSelect.hide(); + _creditsMovie.setTime(kOtherTitlesTime); + _creditsMovie.redrawMovieWorld(); + break; + case kCreditsMenuMainMenu: + _smallSelect.hide(); + _largeSelect.show(); + break; + } + + _menuSelection = newSelection; + } +} + +void CreditsMenu::newMovieTime(const TimeValue newTime) { + if (newTime < kSupportTeamTime) { + _smallSelect.moveElementTo(kCoreTeamSelectLeft, kCoreTeamSelectTop); + _menuSelection = kCreditsMenuCoreTeam; + } else if (newTime < kOriginalTeamTime) { + _smallSelect.moveElementTo(kSupportTeamSelectLeft, kSupportTeamSelectTop); + _menuSelection = kCreditsMenuSupportTeam; + } else if (newTime < kTalentTime) { + _smallSelect.moveElementTo(kOriginalTeamSelectLeft, kOriginalTeamSelectTop); + _menuSelection = kCreditsMenuOriginalTeam; + } else if (newTime < kOtherTitlesTime) { + _smallSelect.moveElementTo(kTalentSelectLeft, kTalentSelectTop); + _smallSelect.show(); + _largeSelect.hide(); + _menuSelection = kCreditsMenuTalent; + } else if ((int)newTime == -120) { + // HACK: Avoid getting sent to the bottom button in the default case + return; + } else { + _smallSelect.moveElementTo(kOtherTitlesSelectLeft, kOtherTitlesSelectTop); + _smallSelect.show(); + _largeSelect.hide(); + _menuSelection = kCreditsMenuOtherTitles; + } + + _creditsMovie.setTime(newTime); + _creditsMovie.redrawMovieWorld(); +} + +void CreditsMenu::handleInput(const Input &input, const Hotspot *cursorSpot) { + if (input.upButtonDown()) { + if (_menuSelection > kCreditsFirstSelection) + newMenuSelection(_menuSelection - 1); + } else if (input.downButtonDown()) { + if (_menuSelection < kCreditsLastSelection) + newMenuSelection(_menuSelection + 1); + } else if (input.leftButtonDown()) { + newMovieTime(_creditsMovie.getTime() - kFrameIncrement); + } else if (input.rightButtonDown()) { + newMovieTime(_creditsMovie.getTime() + kFrameIncrement); + } else if (JMPPPInput::isMenuButtonPressInput(input)) { + if (_menuSelection == kCreditsMenuMainMenu) { + _mainMenuButton.show(); + ((PegasusEngine *)g_engine)->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _mainMenuButton.hide(); + setLastCommand(kMenuCmdCreditsMainMenu); + } + } + + InputHandler::handleInput(input, cursorSpot); +} + +static const CoordType kContinueLeft = 44; +static const CoordType kContinueTop = 336; + +static const CoordType kContinueSelectLeft = 40; +static const CoordType kContinueSelectTopDemo = 331; +static const CoordType kContinueSelectTop = 332; + +static const CoordType kMainMenuLeftDemo = 44; +static const CoordType kMainMenuTopDemo = 372; + +static const CoordType kMainMenuSelectLeftDemo = 40; +static const CoordType kMainMenuSelectTopDemo = 367; + +static const CoordType kQuitLeftDemo = 32; +static const CoordType kQuitTopDemo = 412; + +static const CoordType kQuitSelectLeftDemo = 28; +static const CoordType kQuitSelectTopDemo = 408; + +static const CoordType kRestoreLeftDeath = 44; +static const CoordType kRestoreTopDeath = 372; + +static const CoordType kRestoreSelectLeftDeath = 40; +static const CoordType kRestoreSelectTopDeath = 368; + +static const CoordType kMainMenuLeft = 32; +static const CoordType kMainMenuTop = 412; + +static const CoordType kMainMenuSelectLeft = 28; +static const CoordType kMainMenuSelectTop = 408; + +enum { + kDeathScreenContinueDemo = 0, + kDeathScreenMainMenuDemo, + kDeathScreenQuitDemo, + + kFirstDeathSelectionDemo = kDeathScreenContinueDemo, + kLastDeathSelectionDemo = kDeathScreenQuitDemo, + + kDeathScreenContinue = 0, + kDeathScreenRestore, + kDeathScreenMainMenu, + + kFirstDeathSelection = kDeathScreenContinue, + kLastDeathSelection = kDeathScreenMainMenu +}; + +// Never set the current input handler to the DeathMenu. +DeathMenu::DeathMenu(const DeathReason deathReason) : GameMenu(kDeathMenuID), _deathBackground(0), _continueButton(0), + _mainMenuButton(0), _quitButton(0), _restoreButton(0), _largeSelect(0), _smallSelect(0) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + bool isDemo = vm->isDemo(); + + _playerWon = (deathReason == kPlayerWonGame); + + Common::String prefix = "Images/"; + Common::String imageName; + if (isDemo) { + prefix += "Demo/"; + imageName = prefix; + + switch (deathReason) { + case kDeathFallOffCliff: + imageName += "dPfall"; + break; + case kDeathEatenByDinosaur: + imageName += "dPdino"; + break; + case kDeathStranded: + imageName += "dPstuck"; + break; + default: + imageName += "dPdemowin"; + break; + } + + imageName += ".pict"; + } else { + prefix += "Death Screens/"; + imageName = prefix; + + static const char *fileNames[] = { + "dAunmade", "dAbombed", "dAshot", "dAassass", "dAnuked", + "dTunmade", "dTshot", "dPfall", "dPdino", "dPstuck", + "dNchoke", "dNcaught", "dNcaught", "dNsub", "dNrobot1", + "dNrobot2", "dMfall", "dMcaught", "dMtracks", "dMrobot", + "dMtoast", "dMexplo1", "dMexplo2", "dMchoke1", "dMchoke2", + "dMdroid", "dMfall", "dMgears", "dMshutt1", "dMshutt2", + "dWpoison", "dWcaught", "dWplasma", "dWshot", "dAfinale" + }; + + imageName += fileNames[deathReason - 1]; + imageName += ".pict"; + } + + _deathBackground.initFromPICTFile(imageName); + _deathReason = deathReason; + + if (!isDemo) { + vm->_gfx->setCurSurface(_deathBackground.getSurface()); + drawAllScores(); + vm->_gfx->setCurSurface(vm->_gfx->getWorkArea()); + } + + _deathBackground.setDisplayOrder(0); + _deathBackground.startDisplaying(); + _deathBackground.show(); + + if (isDemo) { + if (_playerWon) // Make credits button... + _continueButton.initFromPICTFile(prefix + "Credits.pict"); + else // Make continue button... + _continueButton.initFromPICTFile(prefix + "Continue.pict"); + + _mainMenuButton.initFromPICTFile(prefix + "MainMenu.pict"); + _mainMenuButton.setDisplayOrder(1); + _mainMenuButton.moveElementTo(kMainMenuLeftDemo, kMainMenuTopDemo); + _mainMenuButton.startDisplaying(); + + _quitButton.initFromPICTFile(prefix + "Quit.pict"); + _quitButton.setDisplayOrder(1); + _quitButton.moveElementTo(kQuitLeftDemo, kQuitTopDemo); + _quitButton.startDisplaying(); + + _menuSelection = kDeathScreenContinueDemo; + } else { + if (!_playerWon) { + _mainMenuButton.initFromPICTFile(prefix + "MainMenu.pict"); + _mainMenuButton.setDisplayOrder(1); + _mainMenuButton.moveElementTo(kMainMenuLeft, kMainMenuTop); + _mainMenuButton.startDisplaying(); + + _restoreButton.initFromPICTFile(prefix + "Restore.pict"); + _restoreButton.setDisplayOrder(1); + _restoreButton.moveElementTo(kRestoreLeftDeath, kRestoreTopDeath); + _restoreButton.startDisplaying(); + } + + _continueButton.initFromPICTFile(prefix + "Continue.pict"); + + _menuSelection = kDeathScreenContinue; + } + + _smallSelect.initFromPICTFile(prefix + "SelectS.pict", true); + _smallSelect.setDisplayOrder(2); + _smallSelect.startDisplaying(); + + _continueButton.setDisplayOrder(1); + _continueButton.moveElementTo(kContinueLeft, kContinueTop); + _continueButton.startDisplaying(); + + if (isDemo || !_playerWon) { + _largeSelect.initFromPICTFile(prefix + "SelectL.pict", true); + _largeSelect.setDisplayOrder(2); + _largeSelect.startDisplaying(); + } else { + _triumphSound.initFromQuickTime("Sounds/Caldoria/Galactic Triumph"); + _triumphSound.playSound(); + } + + updateDisplay(); +} + +void DeathMenu::handleInput(const Input &input, const Hotspot *cursorSpot) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + if (input.upButtonDown()) { + if (_menuSelection > (vm->isDemo() ? kFirstDeathSelectionDemo : kFirstDeathSelection)) { + _menuSelection--; + updateDisplay(); + } + } else if (input.downButtonDown() && (vm->isDemo() || !_playerWon)) { + if (_menuSelection < (vm->isDemo() ? kLastDeathSelectionDemo : kLastDeathSelection)) { + _menuSelection++; + updateDisplay(); + } + } else if (JMPPPInput::isMenuButtonPressInput(input)) { + if (vm->isDemo()) { + switch (_menuSelection) { + case kDeathScreenContinueDemo: + _continueButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _continueButton.hide(); + setLastCommand(kMenuCmdDeathContinue); + break; + case kDeathScreenQuitDemo: + _quitButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _quitButton.hide(); + setLastCommand(kMenuCmdDeathQuitDemo); + break; + case kDeathScreenMainMenuDemo: + _mainMenuButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _mainMenuButton.hide(); + setLastCommand(kMenuCmdDeathMainMenuDemo); + break; + } + } else { + switch (_menuSelection) { + case kDeathScreenContinue: + _continueButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _continueButton.hide(); + setLastCommand(kMenuCmdDeathContinue); + break; + case kDeathScreenRestore: + _restoreButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _restoreButton.hide(); + setLastCommand(kMenuCmdDeathRestore); + break; + case kDeathScreenMainMenu: + _mainMenuButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _mainMenuButton.hide(); + setLastCommand(kMenuCmdDeathMainMenu); + break; + } + } + } + + InputHandler::handleInput(input, cursorSpot); +} + +void DeathMenu::updateDisplay() { + if (((PegasusEngine *)g_engine)->isDemo()) { + switch (_menuSelection) { + case kDeathScreenContinueDemo: + _smallSelect.moveElementTo(kContinueSelectLeft, kContinueSelectTopDemo); + _smallSelect.show(); + _largeSelect.hide(); + break; + case kDeathScreenQuitDemo: + _largeSelect.moveElementTo(kQuitSelectLeftDemo, kQuitSelectTopDemo); + _largeSelect.show(); + _smallSelect.hide(); + break; + case kDeathScreenMainMenuDemo: + _smallSelect.moveElementTo(kMainMenuSelectLeftDemo, kMainMenuSelectTopDemo); + _smallSelect.show(); + _largeSelect.hide(); + break; + } + } else { + switch (_menuSelection) { + case kDeathScreenContinue: + _smallSelect.moveElementTo(kContinueSelectLeft, kContinueSelectTop); + _smallSelect.show(); + _largeSelect.hide(); + break; + case kDeathScreenRestore: + _smallSelect.moveElementTo(kRestoreSelectLeftDeath, kRestoreSelectTopDeath); + _smallSelect.show(); + _largeSelect.hide(); + break; + case kDeathScreenMainMenu: + _largeSelect.moveElementTo(kMainMenuSelectLeft, kMainMenuSelectTop); + _largeSelect.show(); + _smallSelect.hide(); + break; + } + } +} + +void DeathMenu::drawAllScores() { + Surface numbers; + numbers.getImageFromPICTFile("Images/Death Screens/Numbers.pict"); + + Common::Rect scoreBounds; + GameScoreType caldoriaTotal = 0; + + switch (_deathReason) { + case kDeathCardBomb: + case kDeathShotBySinclair: + case kDeathSinclairShotDelegate: + case kDeathNuclearExplosion: + case kDeathGassedInNorad: + case kDeathWokeUpNorad: + case kDeathArrestedInNorad: + case kDeathSubDestroyed: + case kDeathRobotThroughNoradDoor: + case kDeathRobotSubControlRoom: + case kDeathWrongShuttleLock: + case kDeathArrestedInMars: + case kDeathRunOverByPod: + case kDeathDidntGetOutOfWay: + case kDeathReactorBurn: + case kDeathDidntFindMarsBomb: + case kDeathDidntDisarmMarsBomb: + case kDeathNoMaskInMaze: + case kDeathNoAirInMaze: + case kDeathGroundByMazebot: + case kDeathMissedOreBucket: + case kDeathDidntLeaveBucket: + case kDeathRanIntoCanyonWall: + case kDeathRanIntoSpaceJunk: + case kDeathDidntStopPoison: + case kDeathArrestedInWSC: + case kDeathHitByPlasma: + case kDeathShotOnCatwalk: + case kPlayerWonGame: + caldoriaTotal += kMaxCaldoriaTSAScoreAfter; + scoreBounds = Common::Rect(kDeathScreenScoreLeft, kDeathScreenScoreTop - kDeathScreenScoreSkipVert * 5, + kDeathScreenScoreLeft + kDeathScreenScoreWidth, kDeathScreenScoreTop - kDeathScreenScoreSkipVert * 5 + kDeathScreenScoreHeight); + drawScore(GameState.getGandhiScore(), kMaxGandhiScore, scoreBounds, &numbers); + + scoreBounds.translate(0, kDeathScreenScoreSkipVert); + drawScore(GameState.getWSCScore(), kMaxWSCScore, scoreBounds, &numbers); + + scoreBounds.translate(0, kDeathScreenScoreSkipVert); + drawScore(GameState.getNoradScore(), kMaxNoradScore, scoreBounds, &numbers); + + scoreBounds.translate(0, kDeathScreenScoreSkipVert); + drawScore(GameState.getMarsScore(), kMaxMarsScore, scoreBounds, &numbers); + // fall through + case kDeathFallOffCliff: + case kDeathEatenByDinosaur: + case kDeathStranded: + case kDeathShotByTSARobots: + scoreBounds = Common::Rect(kDeathScreenScoreLeft, kDeathScreenScoreTop - kDeathScreenScoreSkipVert, + kDeathScreenScoreLeft + kDeathScreenScoreWidth, kDeathScreenScoreTop - kDeathScreenScoreSkipVert + kDeathScreenScoreHeight); + drawScore(GameState.getPrehistoricScore(), kMaxPrehistoricScore, scoreBounds, &numbers); + // fall through + case kDeathUncreatedInCaldoria: + case kDeathUncreatedInTSA: + scoreBounds = Common::Rect(kDeathScreenScoreLeft, kDeathScreenScoreTop, kDeathScreenScoreLeft + kDeathScreenScoreWidth, + kDeathScreenScoreTop + kDeathScreenScoreHeight); + caldoriaTotal += kMaxCaldoriaTSAScoreBefore; + drawScore(GameState.getCaldoriaTSAScore(), caldoriaTotal, scoreBounds, &numbers); + + scoreBounds = Common::Rect(kDeathScreenScoreLeft, kDeathScreenScoreTop - kDeathScreenScoreSkipVert * 6, + kDeathScreenScoreLeft + kDeathScreenScoreWidth, kDeathScreenScoreTop - kDeathScreenScoreSkipVert * 6 + kDeathScreenScoreHeight); + + drawScore(GameState.getTotalScore(), kMaxTotalScore, scoreBounds, &numbers); + break; + } +} + +enum { + kPauseMenuSave, + kPauseMenuContinue, + kPauseMenuRestore, + kPauseMenuSoundFX, + kPauseMenuAmbience, + kPauseMenuWalkthru, + kPauseMenuQuitToMainMenu, + + kFirstPauseSelection = kPauseMenuSave, + kLastPauseSelection = kPauseMenuQuitToMainMenu +}; + +static const CoordType kPauseLeft = 194; +static const CoordType kPauseTop = 68; + +static const CoordType kSaveGameLeft = kPauseLeft + 6; +static const CoordType kSaveGameTop = kPauseTop + 56; + +static const CoordType kSaveGameSelectLeft = kPauseLeft - 44; +static const CoordType kSaveGameSelectTop = kPauseTop + 52; + +static const CoordType kPauseContinueLeft = kPauseLeft + 18; +static const CoordType kPauseContinueTop = kPauseTop + 100; + +static const CoordType kPauseContinueSelectLeft = kPauseLeft - 44; +static const CoordType kPauseContinueSelectTop = kPauseTop + 95; + +static const CoordType kPauseRestoreLeft = kPauseLeft + 18; +static const CoordType kPauseRestoreTop = kPauseTop + 136; + +static const CoordType kPauseRestoreSelectLeft = kPauseLeft - 44; +static const CoordType kPauseRestoreSelectTop = kPauseTop + 131; + +static const CoordType kSoundFXLeft = kPauseLeft + 128; +static const CoordType kSoundFXTop = kPauseTop + 187; +static const CoordType kSoundFXRight = kSoundFXLeft + 96; +static const CoordType kSoundFXBottom = kSoundFXTop + 14; + +static const CoordType kSoundFXSelectLeft = kPauseLeft - 44; +static const CoordType kSoundFXSelectTop = kPauseTop + 172; + +static const CoordType kAmbienceLeft = kPauseLeft + 128; +static const CoordType kAmbienceTop = kPauseTop + 227; +static const CoordType kAmbienceRight = kAmbienceLeft + 96; +static const CoordType kAmbienceBottom = kAmbienceTop + 14; + +static const CoordType kAmbienceSelectLeft = kPauseLeft - 44; +static const CoordType kAmbienceSelectTop = kPauseTop + 212; + +static const CoordType kWalkthruLeft = kPauseLeft + 128; +static const CoordType kWalkthruTop = kPauseTop + 264; + +static const CoordType kWalkthruSelectLeft = kPauseLeft - 44; +static const CoordType kWalkthruSelectTop = kPauseTop + 255; + +static const CoordType kQuitLeft = kPauseLeft + 18; +static const CoordType kQuitTop = kPauseTop + 302; + +static const CoordType kQuitSelectLeft = kPauseLeft - 44; +static const CoordType kQuitSelectTop = kPauseTop + 297; + +// These are relative to the pause background. +static const CoordType kPauseScoreLeft = 130; +static const CoordType kPauseScoreTop = 34; +static const CoordType kPauseScoreRight = kPauseScoreLeft + 108; +static const CoordType kPauseScoreBottom = kPauseScoreTop + 12; + +// Never set the current input handler to the CPauseMenu. +PauseMenu::PauseMenu() : GameMenu(kPauseMenuID), _pauseBackground(0), _saveButton(0), _restoreButton(0), + _walkthroughButton(0), _continueButton(0), _soundFXLevel(0), _ambienceLevel(0), _quitButton(0), + _largeSelect(0), _smallSelect(0) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + _pauseBackground.initFromPICTFile("Images/Pause Screen/PausScrn.pict", true); + + if (!vm->isDemo()) { + Surface numbers; + numbers.getImageFromPICTFile("Images/Pause Screen/Numbers.pict"); + vm->_gfx->setCurSurface(_pauseBackground.getSurface()); + drawScore(GameState.getTotalScore(), kMaxTotalScore, + Common::Rect(kPauseScoreLeft, kPauseScoreTop, kPauseScoreRight, kPauseScoreBottom), &numbers); + vm->_gfx->setCurSurface(vm->_gfx->getWorkArea()); + } + + _pauseBackground.setDisplayOrder(kPauseMenuOrder); + _pauseBackground.moveElementTo(kPauseLeft, kPauseTop); + _pauseBackground.startDisplaying(); + _pauseBackground.show(); + + if (!vm->isDemo()) { + _saveButton.initFromPICTFile("Images/Pause Screen/SaveGame.pict"); + _saveButton.setDisplayOrder(kSaveGameOrder); + _saveButton.moveElementTo(kSaveGameLeft, kSaveGameTop); + _saveButton.startDisplaying(); + + _restoreButton.initFromPICTFile("Images/Pause Screen/Restore.pict"); + _restoreButton.setDisplayOrder(kRestoreOrder); + _restoreButton.moveElementTo(kPauseRestoreLeft, kPauseRestoreTop); + _restoreButton.startDisplaying(); + + _walkthroughButton.initFromPICTFile("Images/Pause Screen/Walkthru.pict"); + _walkthroughButton.setDisplayOrder(kWalkthruOrder); + _walkthroughButton.moveElementTo(kWalkthruLeft, kWalkthruTop); + _walkthroughButton.startDisplaying(); + + if (GameState.getWalkthroughMode()) + _walkthroughButton.show(); + } + + _continueButton.initFromPICTFile("Images/Pause Screen/Continue.pict"); + _continueButton.setDisplayOrder(kContinueOrder); + _continueButton.moveElementTo(kPauseContinueLeft, kPauseContinueTop); + _continueButton.startDisplaying(); + + _soundFXLevel.setDisplayOrder(kSoundFXOrder); + _soundFXLevel.setBounds(Common::Rect(kSoundFXLeft, kSoundFXTop, kSoundFXRight, kSoundFXBottom)); + _soundFXLevel.startDisplaying(); + _soundFXLevel.show(); + _soundFXLevel.setSoundLevel(vm->getSoundFXLevel()); + + _ambienceLevel.setDisplayOrder(kAmbienceOrder); + _ambienceLevel.setBounds(Common::Rect(kAmbienceLeft, kAmbienceTop, kAmbienceRight, kAmbienceBottom)); + _ambienceLevel.startDisplaying(); + _ambienceLevel.show(); + _ambienceLevel.setSoundLevel(vm->getAmbienceLevel()); + + _quitButton.initFromPICTFile("Images/Pause Screen/Quit2MM.pict"); + _quitButton.setDisplayOrder(kQuitToMainMenuOrder); + _quitButton.moveElementTo(kQuitLeft, kQuitTop); + _quitButton.startDisplaying(); + + _largeSelect.initFromPICTFile("Images/Pause Screen/SelectL.pict", true); + _largeSelect.setDisplayOrder(kPauseLargeHiliteOrder); + _largeSelect.startDisplaying(); + + _smallSelect.initFromPICTFile("Images/Pause Screen/SelectS.pict", true); + _smallSelect.setDisplayOrder(kPauseSmallHiliteOrder); + _smallSelect.startDisplaying(); + + _menuSelection = (vm->isDemo()) ? kPauseMenuContinue : kPauseMenuSave; + + updateDisplay(); +} + +void PauseMenu::handleInput(const Input &input, const Hotspot *cursorSpot) { + PegasusEngine *vm = (PegasusEngine *)g_engine; + + if (input.upButtonDown()) { + if (vm->isDemo()) { + if (_menuSelection > kPauseMenuContinue) { + switch (_menuSelection) { + case kPauseMenuSoundFX: + _menuSelection = kPauseMenuContinue; + break; + case kPauseMenuAmbience: + _menuSelection = kPauseMenuSoundFX; + break; + case kPauseMenuQuitToMainMenu: + _menuSelection = kPauseMenuAmbience; + break; + } + updateDisplay(); + } + } else { + if (_menuSelection > kFirstPauseSelection) { + _menuSelection--; + updateDisplay(); + } + } + } else if (input.downButtonDown()) { + if (vm->isDemo()) { + if (_menuSelection < kPauseMenuQuitToMainMenu) { + switch (_menuSelection) { + case kPauseMenuContinue: + _menuSelection = kPauseMenuSoundFX; + break; + case kPauseMenuSoundFX: + _menuSelection = kPauseMenuAmbience; + break; + case kPauseMenuAmbience: + _menuSelection = kPauseMenuQuitToMainMenu; + break; + } + updateDisplay(); + } + } else { + if (_menuSelection < kLastPauseSelection) { + _menuSelection++; + updateDisplay(); + } + } + } else if (input.leftButtonDown()) { + if (_menuSelection == kPauseMenuSoundFX) { + _soundFXLevel.decrementLevel(); + vm->setSoundFXLevel(_soundFXLevel.getSoundLevel()); + } else if (_menuSelection == kPauseMenuAmbience) { + _ambienceLevel.decrementLevel(); + vm->setAmbienceLevel(_ambienceLevel.getSoundLevel()); + } else if (!vm->isDemo() && _menuSelection == kPauseMenuWalkthru) { + GameState.setWalkthroughMode(!GameState.getWalkthroughMode()); + if (GameState.getWalkthroughMode()) + _walkthroughButton.show(); + else + _walkthroughButton.hide(); + } + } else if (input.rightButtonDown()) { + if (_menuSelection == kPauseMenuSoundFX) { + _soundFXLevel.incrementLevel(); + vm->setSoundFXLevel(_soundFXLevel.getSoundLevel()); + } else if (_menuSelection == kPauseMenuAmbience) { + _ambienceLevel.incrementLevel(); + vm->setAmbienceLevel(_ambienceLevel.getSoundLevel()); + } else if (!vm->isDemo() && _menuSelection == kPauseMenuWalkthru) { + GameState.setWalkthroughMode(!GameState.getWalkthroughMode()); + if (GameState.getWalkthroughMode()) + _walkthroughButton.show(); + else + _walkthroughButton.hide(); + } + } else if (JMPPPInput::isMenuButtonPressInput(input)) { + switch (_menuSelection) { + case kPauseMenuSave: + _saveButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _saveButton.hide(); + setLastCommand(kMenuCmdPauseSave); + break; + case kPauseMenuRestore: + _restoreButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _restoreButton.hide(); + setLastCommand(kMenuCmdPauseRestore); + break; + case kPauseMenuContinue: + _continueButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _continueButton.hide(); + setLastCommand(kMenuCmdPauseContinue); + break; + case kPauseMenuWalkthru: + GameState.setWalkthroughMode(!GameState.getWalkthroughMode()); + if (GameState.getWalkthroughMode()) + _walkthroughButton.show(); + else + _walkthroughButton.hide(); + break; + case kPauseMenuQuitToMainMenu: + _quitButton.show(); + vm->delayShell(kMenuButtonHiliteTime, kMenuButtonHiliteScale); + _quitButton.hide(); + setLastCommand(kMenuCmdPauseQuit); + break; + } + } + + InputHandler::handleInput(input, cursorSpot); +} + +void PauseMenu::updateDisplay() { + switch (_menuSelection) { + case kPauseMenuSave: + _largeSelect.moveElementTo(kSaveGameSelectLeft, kSaveGameSelectTop); + _largeSelect.show(); + _smallSelect.hide(); + break; + case kPauseMenuContinue: + _smallSelect.moveElementTo(kPauseContinueSelectLeft, kPauseContinueSelectTop); + _smallSelect.show(); + _largeSelect.hide(); + break; + case kPauseMenuRestore: + _smallSelect.moveElementTo(kPauseRestoreSelectLeft, kPauseRestoreSelectTop); + _smallSelect.show(); + _largeSelect.hide(); + break; + case kPauseMenuSoundFX: + _largeSelect.moveElementTo(kSoundFXSelectLeft, kSoundFXSelectTop); + _largeSelect.show(); + _smallSelect.hide(); + break; + case kPauseMenuAmbience: + _largeSelect.moveElementTo(kAmbienceSelectLeft, kAmbienceSelectTop); + _largeSelect.show(); + _smallSelect.hide(); + break; + case kPauseMenuWalkthru: + _largeSelect.moveElementTo(kWalkthruSelectLeft, kWalkthruSelectTop); + _largeSelect.show(); + _smallSelect.hide(); + break; + case kPauseMenuQuitToMainMenu: + _smallSelect.moveElementTo(kQuitSelectLeft, kQuitSelectTop); + _smallSelect.show(); + _largeSelect.hide(); + break; + } + + ((PegasusEngine *)g_engine)->resetIntroTimer(); +} + + +} // End of namespace Pegasus diff --git a/engines/pegasus/menu.h b/engines/pegasus/menu.h new file mode 100644 index 0000000000..8b0169adaa --- /dev/null +++ b/engines/pegasus/menu.h @@ -0,0 +1,171 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_MENU_H +#define PEGASUS_MENU_H + +#include "pegasus/constants.h" +#include "pegasus/fader.h" +#include "pegasus/input.h" +#include "pegasus/movie.h" +#include "pegasus/sound.h" +#include "pegasus/surface.h" +#include "pegasus/util.h" + +namespace Pegasus { + +class GameMenu : public IDObject, public InputHandler { +public: + GameMenu(const uint32); + virtual ~GameMenu() {} + + virtual void becomeCurrentHandler(); + virtual void restorePreviousHandler(); + + GameMenuCommand getLastCommand() { return _lastCommand; } + void clearLastCommand() { _lastCommand = kMenuCmdNoCommand; } + +protected: + void setLastCommand(const GameMenuCommand command) { _lastCommand = command; } + + InputHandler *_previousHandler; + GameMenuCommand _lastCommand; + + void drawScore(GameScoreType, GameScoreType, const Common::Rect &, Surface *); + +private: + void drawNumber(GameScoreType, CoordType &, CoordType, Surface *); +}; + +class Hotspot; + +class MainMenu : public GameMenu { +public: + MainMenu(); + virtual ~MainMenu(); + + virtual void handleInput(const Input &input, const Hotspot *); + void startMainMenuLoop(); + void stopMainMenuLoop(); + +protected: + void updateDisplay(); + + uint32 _menuSelection; + + // Full and Demo + Picture _menuBackground; + Picture _startButton; + Picture _creditsButton; + Picture _quitButton; + Picture _largeSelect; + Picture _smallSelect; + + // Full only + bool _adventureMode; + Picture _overviewButton; + Picture _restoreButton; + Picture _adventureButton; + Picture _walkthroughButton; + + Sound _menuLoop; + SoundFader _menuFader; +}; + +class CreditsMenu : public GameMenu { +public: + CreditsMenu(); + virtual ~CreditsMenu() {} + + virtual void handleInput(const Input &input, const Hotspot *); + +protected: + void newMenuSelection(const int); + void newMovieTime(const TimeValue); + + int _menuSelection; + Picture _menuBackground; + Movie _creditsMovie; + Picture _mainMenuButton; + Picture _largeSelect; + Picture _smallSelect; +}; + +class DeathMenu : public GameMenu { +public: + DeathMenu(const DeathReason); + virtual ~DeathMenu() {} + + virtual void handleInput(const Input &input, const Hotspot *); + + bool playerWon() { return _playerWon; } + +protected: + void drawAllScores(); + + void updateDisplay(); + + bool _playerWon; + int _menuSelection; + DeathReason _deathReason; + + Picture _deathBackground; + Picture _continueButton; + Picture _restoreButton; + Picture _mainMenuButton; + Picture _quitButton; + + Picture _largeSelect; + Picture _smallSelect; + + Sound _triumphSound; +}; + +class PauseMenu : public GameMenu { +public: + PauseMenu(); + virtual ~PauseMenu() {} + + virtual void handleInput(const Input &input, const Hotspot *); + +protected: + void updateDisplay(); + + uint32 _menuSelection; + Picture _pauseBackground; + Picture _saveButton; + Picture _restoreButton; + Picture _walkthroughButton; + Picture _continueButton; + SoundLevel _soundFXLevel; + SoundLevel _ambienceLevel; + Picture _quitButton; + Picture _largeSelect; + Picture _smallSelect; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/module.mk b/engines/pegasus/module.mk new file mode 100644 index 0000000000..cb44a04171 --- /dev/null +++ b/engines/pegasus/module.mk @@ -0,0 +1,100 @@ +MODULE := engines/pegasus + +MODULE_OBJS = \ + compass.o \ + console.o \ + cursor.o \ + detection.o \ + elements.o \ + energymonitor.o \ + fader.o \ + gamestate.o \ + graphics.o \ + hotspot.o \ + input.o \ + interface.o \ + menu.o \ + movie.o \ + notification.o \ + pegasus.o \ + sound.o \ + surface.o \ + timers.o \ + transition.o \ + util.o \ + ai/ai_action.o \ + ai/ai_area.o \ + ai/ai_condition.o \ + ai/ai_rule.o \ + items/autodragger.o \ + items/inventory.o \ + items/inventorypicture.o \ + items/item.o \ + items/itemdragger.o \ + items/itemlist.o \ + items/biochips/aichip.o \ + items/biochips/biochipitem.o \ + items/biochips/mapchip.o \ + items/biochips/mapimage.o \ + items/biochips/opticalchip.o \ + items/biochips/pegasuschip.o \ + items/biochips/retscanchip.o \ + items/biochips/shieldchip.o \ + items/inventory/airmask.o \ + items/inventory/gascanister.o \ + items/inventory/inventoryitem.o \ + items/inventory/keycard.o \ + neighborhood/door.o \ + neighborhood/exit.o \ + neighborhood/extra.o \ + neighborhood/hotspotinfo.o \ + neighborhood/neighborhood.o \ + neighborhood/spot.o \ + neighborhood/turn.o \ + neighborhood/view.o \ + neighborhood/zoom.o \ + neighborhood/caldoria/caldoria.o \ + neighborhood/caldoria/caldoria4dsystem.o \ + neighborhood/caldoria/caldoriabomb.o \ + neighborhood/caldoria/caldoriamessages.o \ + neighborhood/caldoria/caldoriamirror.o \ + neighborhood/mars/energybeam.o \ + neighborhood/mars/gravitoncannon.o \ + neighborhood/mars/hermite.o \ + neighborhood/mars/mars.o \ + neighborhood/mars/planetmover.o \ + neighborhood/mars/reactor.o \ + neighborhood/mars/robotship.o \ + neighborhood/mars/shuttleenergymeter.o \ + neighborhood/mars/shuttlehud.o \ + neighborhood/mars/shuttleweapon.o \ + neighborhood/mars/spacechase3d.o \ + neighborhood/mars/spacejunk.o \ + neighborhood/mars/tractorbeam.o \ + neighborhood/norad/norad.o \ + neighborhood/norad/noradelevator.o \ + neighborhood/norad/pressuredoor.o \ + neighborhood/norad/pressuretracker.o \ + neighborhood/norad/subcontrolroom.o \ + neighborhood/norad/subplatform.o \ + neighborhood/norad/alpha/ecrmonitor.o \ + neighborhood/norad/alpha/fillingstation.o \ + neighborhood/norad/alpha/noradalpha.o \ + neighborhood/norad/alpha/panorama.o \ + neighborhood/norad/alpha/panoramascroll.o \ + neighborhood/norad/delta/globegame.o \ + neighborhood/norad/delta/noraddelta.o \ + neighborhood/prehistoric/prehistoric.o \ + neighborhood/tsa/fulltsa.o \ + neighborhood/tsa/tinytsa.o \ + neighborhood/wsc/moleculebin.o \ + neighborhood/wsc/wsc.o + + +# This module can be built as a plugin +ifeq ($(ENABLE_PEGASUS), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/pegasus/movie.cpp b/engines/pegasus/movie.cpp new file mode 100644 index 0000000000..b7c025a89b --- /dev/null +++ b/engines/pegasus/movie.cpp @@ -0,0 +1,251 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/system.h" +#include "graphics/surface.h" +#include "video/qt_decoder.h" +#include "video/video_decoder.h" + +#include "pegasus/movie.h" + +namespace Pegasus { + +Movie::Movie(const DisplayElementID id) : Animation(id) { + _video = 0; + setScale(600); +} + +Movie::~Movie() { + releaseMovie(); +} + +// *** Make sure this will stop displaying the movie. + +void Movie::releaseMovie() { + if (_video) { + delete _video; + _video = 0; + disposeAllCallBacks(); + deallocateSurface(); + } + + setBounds(Common::Rect(0, 0, 0, 0)); +} + +void Movie::initFromMovieFile(const Common::String &fileName, bool transparent) { + _transparent = transparent; + + releaseMovie(); + _video = new Video::QuickTimeDecoder(); + if (!_video->loadFile(fileName)) { + // Replace any colon with an underscore, since only Mac OS X + // supports that. See PegasusEngine::detectOpeningClosingDirectory() + // for more info. + Common::String newName(fileName); + if (newName.contains(':')) + for (uint i = 0; i < newName.size(); i++) + if (newName[i] == ':') + newName.setChar('_', i); + + if (!_video->loadFile(newName)) + error("Could not load video '%s'", fileName.c_str()); + } + + _video->pauseVideo(true); + + Common::Rect bounds(0, 0, _video->getWidth(), _video->getHeight()); + sizeElement(_video->getWidth(), _video->getHeight()); + _movieBox = bounds; + + if (!isSurfaceValid()) + allocateSurface(bounds); + + setStart(0, getScale()); + setStop(_video->getDuration() * getScale() / 1000, getScale()); +} + +void Movie::redrawMovieWorld() { + if (_video && _video->needsUpdate()) { + const Graphics::Surface *frame = _video->decodeNextFrame(); + + if (!frame) + return; + + // Copy to the surface using _movieBox + uint16 width = MIN<int>(frame->w, _movieBox.width()); + uint16 height = MIN<int>(frame->h, _movieBox.height()); + + for (uint16 y = 0; y < height; y++) + memcpy((byte *)_surface->getBasePtr(_movieBox.left, _movieBox.top + y), (const byte *)frame->getBasePtr(0, y), width * frame->format.bytesPerPixel); + + triggerRedraw(); + } +} + +void Movie::draw(const Common::Rect &r) { + Common::Rect worldBounds = _movieBox; + Common::Rect elementBounds; + getBounds(elementBounds); + + worldBounds.moveTo(elementBounds.left, elementBounds.top); + Common::Rect r1 = r.findIntersectingRect(worldBounds); + + Common::Rect r2 = r1; + r2.translate(_movieBox.left - elementBounds.left, _movieBox.top - elementBounds.top); + drawImage(r2, r1); +} + +void Movie::moveMovieBoxTo(const CoordType h, const CoordType v) { + _movieBox.moveTo(h, v); +} + +void Movie::setVolume(uint16 volume) { + if (_video) + _video->setVolume(MIN<uint>(volume, 0xFF)); +} + +void Movie::setTime(const TimeValue time, const TimeScale scale) { + if (_video) { + // Don't go past the ends of the movie + Common::Rational timeFrac = Common::Rational(time, ((scale == 0) ? getScale() : scale)); + + if (timeFrac < Common::Rational(_startTime, _startScale)) + timeFrac = Common::Rational(_startTime, _startScale); + else if (timeFrac >= Common::Rational(_stopTime, _stopScale)) + return; + + _video->seekToTime(Audio::Timestamp(0, timeFrac.getNumerator(), timeFrac.getDenominator())); + _time = timeFrac; + _lastMillis = 0; + } +} + +void Movie::setRate(const Common::Rational rate) { + if (rate != 1 && rate != 0) { + warning("Cannot set movie rate"); + start(); + return; + } + + TimeBase::setRate(rate); +} + +void Movie::start() { + if (_video && _video->isPaused()) + _video->pauseVideo(false); + + TimeBase::start(); +} + +void Movie::stop() { + if (_video && !_video->isPaused()) + _video->pauseVideo(true); + + TimeBase::stop(); +} + +void Movie::resume() { + if (_video) + _video->pauseVideo(false); + + TimeBase::resume(); +} + +void Movie::pause() { + if (_video) + _video->pauseVideo(true); + + TimeBase::pause(); +} + +TimeValue Movie::getDuration(const TimeScale scale) const { + // Unlike TimeBase::getDuration(), this returns the whole duration of the movie + // The original source has a TODO to make this behave like TimeBase::getDuration(), + // but the problem is that too much code requires this function to behave this way... + + if (_video) + return _video->getDuration() * ((scale == 0) ? getScale() : scale) / 1000; + + return 0; +} + +void Movie::updateTime() { + // The reason why we overrode TimeBase's updateTime(): + // Again, avoiding timers and handling it here + if (_video && !_video->isPaused()) { + redrawMovieWorld(); + + uint32 startTime = _startTime * getScale() / _startScale; + uint32 stopTime = _stopTime * getScale() / _stopScale; + uint32 actualTime = CLIP<int>(_video->getTime() * getScale() / 1000, startTime, stopTime); + _time = Common::Rational(actualTime, getScale()); + } +} + +GlowingMovie::GlowingMovie(const DisplayElementID id) : Movie(id) { + _glowing = false; +} + +void GlowingMovie::draw(const Common::Rect &r) { + // Make sure the rectangles are clipped properly, OR guarantee that _bounds will + // never fall off the screen. + if (_glowing) { + Common::Rect bounds; + getBounds(bounds); + + copyToCurrentPortTransparentGlow(_movieBox, bounds); + } else { + Movie::draw(r); + } +} + +void GlowingMovie::setBounds(const Common::Rect &r) { + Common::Rect bounds; + getBounds(bounds); + + if (r != bounds) { + // Avoid Movie::setBounds. + // clone2727 asks why, but goes along with it + Animation::setBounds(r); + } +} + +ScalingMovie::ScalingMovie(const DisplayElementID id) : GlowingMovie(id) { +} + +void ScalingMovie::draw(const Common::Rect &) { + // Make sure the rectangles are clipped properly, OR guarantee that _bounds will + // never fall off the screen. + + Common::Rect bounds; + getBounds(bounds); + + if (_glowing) + scaleTransparentCopyGlow(_movieBox, bounds); + else + scaleTransparentCopy(_movieBox, bounds); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/movie.h b/engines/pegasus/movie.h new file mode 100644 index 0000000000..593442fa44 --- /dev/null +++ b/engines/pegasus/movie.h @@ -0,0 +1,103 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_MOVIE_H +#define PEGASUS_MOVIE_H + +#include "common/str.h" + +#include "pegasus/elements.h" +#include "pegasus/surface.h" + +namespace Video { + class SeekableVideoDecoder; +} + +namespace Pegasus { + +class Movie : public Animation, public PixelImage { +public: + Movie(const DisplayElementID); + virtual ~Movie(); + + virtual void initFromMovieFile(const Common::String &fileName, bool transparent = false); + + bool isMovieValid() { return _video != 0; } + + virtual void releaseMovie(); + + virtual void draw(const Common::Rect &); + virtual void redrawMovieWorld(); + + virtual void setTime(const TimeValue, const TimeScale = 0); + + virtual void setRate(const Common::Rational); + + virtual void start(); + virtual void stop(); + virtual void resume(); + virtual void pause(); + + virtual void moveMovieBoxTo(const CoordType, const CoordType); + + virtual TimeValue getDuration(const TimeScale = 0) const; + + // *** HACK ALERT + Video::SeekableVideoDecoder *getMovie() { return _video; } + void setVolume(uint16); + +protected: + void updateTime(); + + Video::SeekableVideoDecoder *_video; + Common::Rect _movieBox; +}; + +class GlowingMovie : public Movie { +public: + GlowingMovie(DisplayElementID); + virtual ~GlowingMovie() {} + + virtual void draw(const Common::Rect &); + + void setBounds(const Common::Rect &); + + void setGlowing(const bool glowing) { _glowing = glowing; } + +protected: + bool _glowing; +}; + +class ScalingMovie : public GlowingMovie { +public: + ScalingMovie(DisplayElementID); + virtual ~ScalingMovie() {} + + virtual void draw(const Common::Rect &); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/caldoria/caldoria.cpp b/engines/pegasus/neighborhood/caldoria/caldoria.cpp new file mode 100644 index 0000000000..0e5868cb7d --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoria.cpp @@ -0,0 +1,1964 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/system.h" +#include "video/qt_decoder.h" + +#include "pegasus/cursor.h" +#include "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/interface.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/biochipitem.h" +#include "pegasus/neighborhood/caldoria/caldoria.h" +#include "pegasus/neighborhood/caldoria/caldoria4dsystem.h" +#include "pegasus/neighborhood/caldoria/caldoriabomb.h" +#include "pegasus/neighborhood/caldoria/caldoriamessages.h" +#include "pegasus/neighborhood/caldoria/caldoriamirror.h" +#include "pegasus/neighborhood/tsa/fulltsa.h" + +namespace Pegasus { + +static const int16 kVidPhoneAngle = 30; +static const int16 kReplicatorAngle = 50; +static const int16 kDrawersAngle = -30; +static const int16 kCaldoria53Angle = 45; +static const int16 kCaldoria55Angle = -45; + +static const TimeValue kSinclairInterruptionTime1 = 2955; +static const TimeValue kSinclairInterruptionTime2 = 6835; +static const TimeValue kSinclairInterruptionTime3 = 9835; +static const TimeValue kSinclairInterruptionTime4 = 12555; + +static const InputBits kPullbackInterruptFilter = kFilterAllInput; +static const InputBits kRecalibrationInterruptFilter = kFilterAllInput; + +static const TimeValue kCaldoriaReplicatorIntroIn = 4933; +static const TimeValue kCaldoriaReplicatorIntroOut = 6557; + +static const TimeValue kCaldoriaReplicatorWrongChoiceIn = 6557; +static const TimeValue kCaldoriaReplicatorWrongChoiceOut = 8586; + +static const TimeValue kCaldoriaReplicatorOJChoiceIn = 8586; +static const TimeValue kCaldoriaReplicatorOJChoiceOut = 11687; + +static const TimeValue kCaldoriaMessagesIntroIn = 11687; +static const TimeValue kCaldoriaMessagesIntroOut = 13641; + +static const TimeValue kCaldoriaFirstMessageIn = 13641; +static const TimeValue kCaldoriaFirstMessageOut = 14203; + +static const TimeValue kCaldoriaSecondMessageIn = 14203; +static const TimeValue kCaldoriaSecondMessageOut = 14750; + +static const TimeValue kCaldoriaDoorCloseIn = 14750; +static const TimeValue kCaldoriaDoorCloseOut = 15472; + +static const TimeValue kCaldoriaElevatorCloseIn = 15472; +static const TimeValue kCaldoriaElevatorCloseOut = 16336; + +static const TimeValue kCaldoriaShowerCloseIn = 16336; +static const TimeValue kCaldoriaShowerCloseOut = 17101; + +static const TimeValue kCaldoriaGTDoorCloseIn = 17101; +static const TimeValue kCaldoriaGTDoorCloseOut = 18523; + +static const TimeValue kCaldoriaNobodyHomeIn = 18523; +static const TimeValue kCaldoriaNobodyHomeOut = 21469; + +static const TimeValue kCaldoriaNoOtherFloorIn = 21469; +static const TimeValue kCaldoriaNoOtherFloorOut = 28013; + +static const TimeValue kCaldoria4DInstructionsIn = 28013; +static const TimeValue kCaldoria4DInstructionsOut = 29730; + +static const TimeValue kCaldoriaDrinkOJIn = 33910; +static const TimeValue kCaldoriaDrinkOJOut = 35846; + +static const TimeValue kCaldoriaNoOtherDestinationIn = 35846; +static const TimeValue kCaldoriaNoOtherDestinationOut = 37877; + +static const TimeValue kCaldoriaUhghIn = 37877; +static const TimeValue kCaldoriaUhghOut = 38025; + +static const TimeValue kCaldoriaSinclairShootsOSIn = 38025; +static const TimeValue kCaldoriaSinclairShootsOSOut = 40649; + +static const TimeValue kCaldoriaScreamingAfterIn = 40649; +static const TimeValue kCaldoriaScreamingAfterOut = 47661; + +static const TimeValue k4FloorTime = 0; + +static const TimeValue k4To1Start = 40; +static const TimeValue k4To1Stop = 7720; + +static const TimeValue k4To5Start = 7720; +static const TimeValue k4To5Stop = 10280; + +static const TimeValue k4To2Time = 10280; + +static const TimeValue k4To3Time = 10320; + +static const TimeValue k1FloorTime = 10360; + +static const TimeValue k1To4Start = 10400; +static const TimeValue k1To4Stop = 18080; + +static const TimeValue k1To5Start = 18080; +static const TimeValue k1To5Stop = 28320; + +static const TimeValue k1To2Time = 28320; + +static const TimeValue k1To3Time = 28360; + +static const TimeValue k5FloorTime = 28400; + +static const TimeValue k5To1Start = 28440; +static const TimeValue k5To1Stop = 38680; + +static const TimeValue k5To4Start = 38680; +static const TimeValue k5To4Stop = 41240; + +static const TimeValue k5To2Time = 41240; + +static const TimeValue k5To3Time = 41280; + +// FuseFunction functions... + +const NotificationFlags kSinclairLoopDoneFlag = kLastNeighborhoodNotificationFlag << 1; + +void doorBombTimerExpiredFunction(FunctionPtr *, void *caldoria) { + ((Caldoria *)caldoria)->doorBombTimerExpired(); +} + +void sinclairTimerExpiredFunction(FunctionPtr *, void *caldoria) { + ((Caldoria *)caldoria)->sinclairTimerExpired(); +} + +SinclairCallBack::SinclairCallBack(Caldoria *caldoria) { + _caldoria = caldoria; +} + +void SinclairCallBack::callBack() { + _caldoria->checkInterruptSinclair(); +} + +Caldoria::Caldoria(InputHandler* nextHandler, PegasusEngine *owner) + : Neighborhood(nextHandler, owner, "Caldoria", kCaldoriaID), _sinclairInterrupt(this) { + setIsItemTaken(kKeyCard); + setIsItemTaken(kOrangeJuiceGlassEmpty); + GameState.setTakenItemID(kOrangeJuiceGlassFull, GameState.isTakenItemID(kOrangeJuiceGlassEmpty)); + _zoomOutSpot = 0; + _gunSprite = 0; +} + +Caldoria::~Caldoria() { + _sinclairInterrupt.releaseCallBack(); +} + +void Caldoria::init() { + Neighborhood::init(); + + // We need this notification flag as well. + _neighborhoodNotification.notifyMe(this, kSinclairLoopDoneFlag, kSinclairLoopDoneFlag); + + _sinclairInterrupt.initCallBack(&_navMovie, kCallBackAtTime); + + forceStridingStop(kCaldoria55, kSouth, kAltCaldoriaSinclairDown); + forceStridingStop(kCaldoria50, kNorth, kAltCaldoriaSinclairDown); +} + +void Caldoria::start() { + g_energyMonitor->stopEnergyDraining(); + + if (!GameState.getCaldoriaSeenPullback()) { + _vm->_gfx->doFadeOutSync(kOneSecond * kFifteenTicksPerSecond, kFifteenTicksPerSecond); + + g_system->delayMillis(2 * 1000); + + Video::VideoDecoder *pullbackMovie = new Video::QuickTimeDecoder(); + + if (!pullbackMovie->loadFile("Images/Caldoria/Pullback.movie")) + error("Could not load pullback movie"); + + bool skipped = false; + Input input; + + _vm->_gfx->doFadeInSync(kTwoSeconds * kFifteenTicksPerSecond, kFifteenTicksPerSecond); + + bool saveAllowed = _vm->swapSaveAllowed(false); + bool openAllowed = _vm->swapLoadAllowed(false); + + while (!_vm->shouldQuit() && !pullbackMovie->endOfVideo()) { + if (pullbackMovie->needsUpdate()) { + const Graphics::Surface *frame = pullbackMovie->decodeNextFrame(); + + if (frame) { + g_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, 64, 112, frame->w, frame->h); + g_system->updateScreen(); + } + } + + InputDevice.getInput(input, kPullbackInterruptFilter); + if (input.anyInput() || _vm->saveRequested() || _vm->loadRequested()) { + skipped = true; + break; + } + + g_system->delayMillis(10); + } + + delete pullbackMovie; + + _vm->swapSaveAllowed(saveAllowed); + _vm->swapLoadAllowed(openAllowed); + + ExtraTable::Entry entry; + + if (!skipped) { + uint32 white = g_system->getScreenFormat().RGBToColor(0xff, 0xff, 0xff); + + _vm->_gfx->doFadeOutSync(kThreeSeconds * kFifteenTicksPerSecond, kFifteenTicksPerSecond, white); + + g_system->delayMillis(3 * 1000 / 2); + + getExtraEntry(kCaldoria00WakeUp1, entry); + _navMovie.setTime(entry.movieStart); + _navMovie.redrawMovieWorld(); + _navMovie.show(); + _vm->refreshDisplay(); + _vm->_gfx->doFadeInSync(kOneSecond * kFifteenTicksPerSecond, kFifteenTicksPerSecond, white); + } else { + getExtraEntry(kCaldoria00WakeUp1, entry); + _navMovie.setTime(entry.movieStart); + _navMovie.redrawMovieWorld(); + _navMovie.show(); + } + + GameState.setCaldoriaSeenPullback(true); + } + + Neighborhood::start(); +} + +void Caldoria::flushGameState() { + GameState.setCaldoriaFuseTimeLimit(_utilityFuse.getTimeRemaining()); +} + +class AIBombActiveCondition : public AICondition { +public: + AIBombActiveCondition() {} + + bool fireCondition(); +}; + +// Return true if player is on 53 east and Sinclair is shot. +bool AIBombActiveCondition::fireCondition() { + return GameState.getCurrentRoom() == kCaldoria53 && GameState.getCurrentDirection() == kEast && + GameState.getCaldoriaSinclairShot(); +} + +void Caldoria::setUpAIRules() { + Neighborhood::setUpAIRules(); + + if (g_AIArea) { + if (GameState.allTimeZonesFinished()) { + AIPlayMessageAction *messageAction = new AIPlayMessageAction("Images/AI/Caldoria/X49NB1", false); + AILocationCondition *locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kCaldoria49, kNorth)); + AIRule *rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Caldoria/X56EH1", false); + AIBombActiveCondition *activeCondition = new AIBombActiveCondition(); + rule = new AIRule(activeCondition, messageAction); + g_AIArea->addAIRule(rule); + } else { + AIPlayMessageAction *messageAction = new AIPlayMessageAction("Images/AI/Caldoria/XAB2", false); + AITimerCondition *timerCondition = new AITimerCondition(kLateWarning3TimeLimit, 1, true); + AILocationCondition *locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kCaldoria44, kEast)); + AINotCondition *notCondition = new AINotCondition(locCondition); + AIAndCondition *andCondition = new AIAndCondition(timerCondition, notCondition); + AIRule *rule = new AIRule(andCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Caldoria/XAB1", false); + timerCondition = new AITimerCondition(kLateWarning2TimeLimit, 1, true); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kCaldoria44, kEast)); + notCondition = new AINotCondition(locCondition); + andCondition = new AIAndCondition(timerCondition, notCondition); + rule = new AIRule(andCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Caldoria/XA44EB", false); + locCondition = new AILocationCondition(3); + locCondition->addLocation(MakeRoomView(kCaldoria01, kNorth)); + locCondition->addLocation(MakeRoomView(kCaldoria01, kEast)); + locCondition->addLocation(MakeRoomView(kCaldoria01, kSouth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Caldoria/X42WH1", false); + AICondition *condition = makeLocationAndDoesntHaveItemCondition(kCaldoria44, kEast, kKeyCard); + rule = new AIRule(condition, messageAction); + g_AIArea->addAIRule(rule); + + AIActivateRuleAction *ruleAction = new AIActivateRuleAction(rule); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kCaldoria42, kEast)); + rule = new AIRule(locCondition, ruleAction); + g_AIArea->addAIRule(rule); + } + } +} + +uint16 Caldoria::getDateResID() const { + return kDate2318ID; +} + +TimeValue Caldoria::getViewTime(const RoomID room, const DirectionConstant direction) { + ExtraTable::Entry extra; + uint32 extraID = 0xffffffff; + + switch (room) { + case kCaldoria00: + if (direction == kEast && _privateFlags.getFlag(kCaldoriaPrivate4DSystemOpenFlag)) + extraID = k4DEnvironOpenView; + break; + case kCaldoriaDrawers: + if (direction == kNorth && _privateFlags.getFlag(kCaldoriaPrivateRightDrawerOpenFlag)) { + if (GameState.isTakenItemID(kKeyCard)) + extraID = kRightDrawerOpenViewNoKeys; + else + extraID = kRightDrawerOpenViewWithKeys; + } + break; + case kCaldoria16: + if (direction == kSouth && GameState.getCaldoriaSeenSinclairInElevator()) + extraID = kCaldoria16SouthViewWithElevator; + break; + case kCaldoriaReplicator: + if (GameState.getCaldoriaMadeOJ() && !(GameState.isTakenItemID(kOrangeJuiceGlassEmpty) || GameState.isTakenItemID(kOrangeJuiceGlassFull))) + extraID = kReplicatorNorthViewWithOJ; + break; + case kCaldoriaKiosk: + case kCaldoriaBinoculars: + return 0xffffffff; + case kCaldoria48: + if (direction == kNorth && GameState.getCaldoriaRoofDoorOpen()) + extraID = kCa48NorthExplosion; + break; + } + + if (extraID == 0xffffffff) + return Neighborhood::getViewTime(room, direction); + + getExtraEntry(extraID, extra); + return extra.movieEnd - 1; +} + +void Caldoria::startSpotOnceOnly(TimeValue startTime, TimeValue stopTime) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kCaldoria13, kEast): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen13CarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen13CarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria14, kEast): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen14CarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen14CarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria18, kWest): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen18CarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen18CarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria23, kSouth): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen23CarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen23CarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria33, kSouth): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen33CarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen33CarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria36, kNorth): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen36CarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen36CarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria41, kNorth): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen41NorthCarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen41NorthCarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria41, kEast): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen41EastCarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen41EastCarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria41, kWest): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen41WestCarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen41WestCarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + default: + Neighborhood::startSpotOnceOnly(startTime, stopTime); + break; + } +} + +void Caldoria::findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &entry) { + Neighborhood::findSpotEntry(room, direction, flags, entry); + + switch (room) { + case kCaldoria00: + if (direction == kEast && (!GameState.getCaldoriaINNAnnouncing() || GameState.getCaldoriaSeenINN())) + entry.clear(); + break; + case kCaldoriaVidPhone: + if (direction == kNorth && GameState.getCaldoriaSeenMessages()) + entry.clear(); + break; + case kCaldoria44: + if (direction == kEast && GameState.getLastRoom() != kCaldoria42) + entry.clear(); + break; + } +} + +void Caldoria::startExitMovie(const ExitTable::Entry &exitEntry) { + switch (GameState.getCurrentRoom()) { + case kCaldoria05: + case kCaldoria07: + if (GameState.getCurrentDirection() == kWest) + closeCroppedMovie(); + // fall through + case kCaldoria11: + if (GameState.getCurrentDirection() == kEast) + closeCroppedMovie(); + break; + case kCaldoria13: + case kCaldoria14: + if (GameState.getCurrentDirection() == kNorth) + closeCroppedMovie(); + break; + } + + Neighborhood::startExitMovie(exitEntry); +} + +void Caldoria::startZoomMovie(const ZoomTable::Entry &zoomEntry) { + switch (GameState.getCurrentRoom()) { + case kCaldoria12: + if (GameState.getCurrentDirection() == kNorth) + closeCroppedMovie(); + break; + } + + Neighborhood::startZoomMovie(zoomEntry); +} + +void Caldoria::startDoorOpenMovie(const TimeValue startTime, const TimeValue stopTime) { + if (GameState.getCurrentRoom() == kCaldoria27 || GameState.getCurrentRoom() == kCaldoria28 || GameState.getCurrentRoom() == kCaldoria45) + // Must be opening elevator door. + closeCroppedMovie(); + + if (GameState.getCurrentRoom() == kCaldoria44 && GameState.getLastRoom() != kCaldoria42) + startExtraSequence(kArriveAtCaldoriaFromTSA, kDoorOpenCompletedFlag, false); + else + Neighborhood::startDoorOpenMovie(startTime, stopTime); +} + +void Caldoria::startTurnPush(const TurnDirection turnDirection, const TimeValue newViewTime, const DirectionConstant destDirection) { + switch (GameState.getCurrentRoom()) { + case kCaldoria05: + case kCaldoria07: + if (GameState.getCurrentDirection() == kWest) + closeCroppedMovie(); + break; + case kCaldoria11: + if (GameState.getCurrentDirection() == kEast) + closeCroppedMovie(); + break; + case kCaldoria12: + case kCaldoria13: + case kCaldoria14: + case kCaldoria27: + case kCaldoria28: + case kCaldoria45: + if (GameState.getCurrentDirection() == kNorth) + closeCroppedMovie(); + break; + case kCaldoria48: + if (_croppedMovie.isSurfaceValid()) + closeCroppedMovie(); + break; + } + + Neighborhood::startTurnPush(turnDirection, newViewTime, destDirection); +} + +void Caldoria::bumpIntoWall() { + requestSpotSound(kCaldoriaUhghIn, kCaldoriaUhghOut, kFilterNoInput, 0); + Neighborhood::bumpIntoWall(); +} + +void Caldoria::closeDoorOffScreen(const RoomID room, const DirectionConstant direction) { + switch (room) { + case kCaldoria08: + if (direction == kNorth) + playSpotSoundSync(kCaldoriaShowerCloseIn, kCaldoriaShowerCloseOut); + else + playSpotSoundSync(kCaldoriaDoorCloseIn, kCaldoriaDoorCloseOut); + break; + case kCaldoria09: + playSpotSoundSync(kCaldoriaShowerCloseIn, kCaldoriaShowerCloseOut); + break; + case kCaldoria16: + case kCaldoria38: + case kCaldoria46: + case kCaldoria27: + case kCaldoria28: + case kCaldoria45: + playSpotSoundSync(kCaldoriaElevatorCloseIn, kCaldoriaElevatorCloseOut); + break; + case kCaldoria44: + case kCaldoria42: + if (GameState.getCurrentRoom() == kCaldoria42) + playSpotSoundSync(kCaldoriaGTDoorCloseIn, kCaldoriaGTDoorCloseOut); + break; + default: + playSpotSoundSync(kCaldoriaDoorCloseIn, kCaldoriaDoorCloseOut); + break; + } +} + +int16 Caldoria::getStaticCompassAngle(const RoomID room, const DirectionConstant dir) { + int16 result = Neighborhood::getStaticCompassAngle(room, dir); + + switch (room) { + case kCaldoriaVidPhone: + result += kVidPhoneAngle; + break; + case kCaldoriaReplicator: + result += kReplicatorAngle; + break; + case kCaldoriaDrawers: + result += kDrawersAngle; + break; + case kCaldoria53: + result += kCaldoria53Angle; + break; + case kCaldoria55: + result += kCaldoria55Angle; + break; + } + + return result; +} + +void Caldoria::getExitCompassMove(const ExitTable::Entry &exitEntry, FaderMoveSpec &compassMove) { + Neighborhood::getExitCompassMove(exitEntry, compassMove); + + switch (MakeRoomView(exitEntry.room, exitEntry.direction)) { + case MakeRoomView(kCaldoria08, kNorth): + case MakeRoomView(kCaldoria09, kSouth): + compassMove.insertFaderKnot((exitEntry.movieStart + exitEntry.movieEnd) >> 1, compassMove.getNthKnotValue(0) + 30); + break; + case MakeRoomView(kCaldoria10, kEast): + compassMove.insertFaderKnot(exitEntry.movieStart + 4 * kCaldoriaFrameDuration, 90); + compassMove.insertFaderKnot(exitEntry.movieStart + 19 * kCaldoriaFrameDuration, -90); + break; + case MakeRoomView(kCaldoria42, kWest): + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), exitEntry.movieStart, -90, exitEntry.movieEnd, 90); + compassMove.insertFaderKnot(exitEntry.movieStart + 3 * kCaldoriaFrameDuration, -90); + compassMove.insertFaderKnot(exitEntry.movieStart + 33 * kCaldoriaFrameDuration, 90); + break; + case MakeRoomView(kCaldoria54, kEast): + if (getCurrentAlternate() != kAltCaldoriaSinclairDown) { + compassMove.insertFaderKnot(exitEntry.movieStart + 16 * kCaldoriaFrameDuration, 135); + compassMove.insertFaderKnot(exitEntry.movieEnd, 135); + } + break; + case MakeRoomView(kCaldoria55, kNorth): + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), exitEntry.movieStart, 315, exitEntry.movieEnd, 270); + break; + } +} + +void Caldoria::getZoomCompassMove(const ZoomTable::Entry &zoomEntry, FaderMoveSpec &compassMove) { + Neighborhood::getZoomCompassMove(zoomEntry, compassMove); + + switch (zoomEntry.hotspot) { + case kCaBathroomToiletSpotID: + compassMove.insertFaderKnot(zoomEntry.movieStart + 4 * kCaldoriaFrameDuration, 90); + compassMove.insertFaderKnot(zoomEntry.movieStart + 19 * kCaldoriaFrameDuration, -90); + compassMove.insertFaderKnot(zoomEntry.movieEnd, -90); + break; + } +} + +void Caldoria::getExtraCompassMove(const ExtraTable::Entry &entry, FaderMoveSpec &compassMove) { + switch (entry.extra) { + case kCaldoria00WakeUp1: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, 90, entry.movieEnd, 180); + compassMove.insertFaderKnot(entry.movieStart + 1000, 90); + compassMove.insertFaderKnot(entry.movieStart + 1640, 120); + compassMove.insertFaderKnot(entry.movieStart + 2240, 135); + compassMove.insertFaderKnot(entry.movieStart + 2640, 180); + break; + case kCaldoria00WakeUp2: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, 180, entry.movieEnd, 90); + compassMove.insertFaderKnot(entry.movieStart + 560, 90); + break; + case kCaldoria56BombStage1: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, 90, entry.movieEnd, 10); + compassMove.insertFaderKnot(entry.movieStart + 31 * kCaldoriaFrameDuration, 60); + compassMove.insertFaderKnot(entry.movieStart + 49 * kCaldoriaFrameDuration, 60); + compassMove.insertFaderKnot(entry.movieStart + 66 * kCaldoriaFrameDuration, 10); + break; + case kCaldoria56BombStage7: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, 10, entry.movieEnd, 90); + compassMove.insertFaderKnot(entry.movieStart + 131 * kCaldoriaFrameDuration, 10); + compassMove.insertFaderKnot(entry.movieStart + 148 * kCaldoriaFrameDuration, 60); + compassMove.insertFaderKnot(entry.movieStart + 165 * kCaldoriaFrameDuration, 60); + compassMove.insertFaderKnot(entry.movieEnd - 5 * kCaldoriaFrameDuration, 90); + break; + default: + Neighborhood::getExtraCompassMove(entry, compassMove); + break; + } +} + +void Caldoria::loadAmbientLoops() { + RoomID room = GameState.getCurrentRoom(); + + if (room == kCaldoria00 && GameState.getCaldoriaWokenUp()) + loadLoopSound1("Sounds/Caldoria/Apartment Music.AIFF", 0x100 / 4); + else if (room >= kCaldoria01 && room <= kCaldoria14) + loadLoopSound1("Sounds/Caldoria/Apartment Music.AIFF", 0x100 / 4); + else if (room == kCaldoria27 || room == kCaldoria28 || room == kCaldoria45) + loadLoopSound1("Sounds/Caldoria/Elevator Loop.AIFF", 0x100 / 5); + else if (room == kCaldoria44) + loadLoopSound1("Sounds/Caldoria/TSA Hum Loop.AIFF"); + else if (room >= kCaldoria15 && room <= kCaldoria48) + loadLoopSound1("Sounds/Caldoria/Industrial Nuage.aiff", 2 * 0x100 / 3); + else if (room >= kCaldoria49 && room <= kCaldoria56) + loadLoopSound1("Sounds/Caldoria/A50NLB00.22K.AIFF", 0x100 / 4); +} + +void Caldoria::checkContinuePoint(const RoomID room, const DirectionConstant direction) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kCaldoria06, kSouth): + case MakeRoomView(kCaldoria13, kNorth): + case MakeRoomView(kCaldoria16, kSouth): + case MakeRoomView(kCaldoria38, kEast): + case MakeRoomView(kCaldoria38, kWest): + case MakeRoomView(kCaldoria40, kNorth): + case MakeRoomView(kCaldoria44, kEast): + case MakeRoomView(kCaldoria48, kNorth): + case MakeRoomView(kCaldoria49, kNorth): + makeContinuePoint(); + break; + } +} + +void Caldoria::spotCompleted() { + Neighborhood::spotCompleted(); + if (GameState.getCurrentRoom() == kCaldoriaBinoculars) + startExtraSequence(kBinocularsZoomInOnShip, kExtraCompletedFlag, kFilterNoInput); +} + +void Caldoria::arriveAt(const RoomID room, const DirectionConstant direction) { + switch (room) { + case kCaldoria56: + if (!GameState.getCaldoriaGunAimed()) + // Fall through... + case kCaldoria49: + case kCaldoria50: + case kCaldoria51: + case kCaldoria52: + case kCaldoria53: + case kCaldoria54: + case kCaldoria55: + if (GameState.getCaldoriaSinclairShot()) + setCurrentAlternate(kAltCaldoriaSinclairDown); + break; + } + + Neighborhood::arriveAt(room, direction); + Input dummy; + + switch (room) { + case kCaldoria00: + arriveAtCaldoria00(); + break; + case kCaldoria05: + if (direction == kWest && GameState.getCaldoriaINNAnnouncing()) + loopCroppedMovie("Images/Caldoria/A05 Light Loop", kCaldoriaA05LightLoopLeft, kCaldoriaA05LightLoopTop); + break; + case kCaldoria07: + if (direction == kWest && GameState.getCaldoriaINNAnnouncing()) + loopCroppedMovie("Images/Caldoria/A07 Light Loop", kCaldoriaA07LightLoopLeft, kCaldoriaA07LightLoopTop); + break; + case kCaldoria09: + _lastExtra = 0xffffffff; + break; + case kCaldoriaToilet: + GameState.setScoringReadPaper(true); + break; + case kCaldoriaReplicator: + setCurrentActivation(kActivateReplicatorReady); + requestSpotSound(kCaldoriaReplicatorIntroIn, kCaldoriaReplicatorIntroOut, kFilterNoInput, 0); + break; + case kCaldoria11: + setCurrentAlternate(kAltCaldoriaNormal); + if (direction == kEast && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A11 Message Machine Loop", kCaldoria11MessageLoopLeft, kCaldoria11MessageLoopTop); + break; + case kCaldoria12: + if (direction == kNorth && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A12 Message Machine Loop", kCaldoria12MessageLoopLeft, kCaldoria12MessageLoopTop); + break; + case kCaldoriaDrawers: + setCurrentActivation(kActivateDrawersClosed); + break; + case kCaldoria13: + GameState.setCaldoriaINNAnnouncing(true); + if (direction == kNorth && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A13 Message Machine Loop", kCaldoria13MessageLoopLeft, kCaldoria13MessageLoopTop); + break; + case kCaldoria14: + if (direction == kNorth && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A14 Message Machine Loop", kCaldoria14MessageLoopLeft, kCaldoria14MessageLoopTop); + break; + case kCaldoria08: + if (direction == kWest) + setCurrentActivation(kActivateMirrorReady); + // Fall through... + case kCaldoria15: + GameState.setCaldoriaINNAnnouncing(true); + break; + case kCaldoria27: + case kCaldoria28: + case kCaldoria45: + if (GameState.getCurrentDirection() == kNorth) + openDoor(); + break; + case kCaldoriaBinoculars: + GameState.setScoringLookThroughTelescope(true); + break; + case kCaldoriaKiosk: + GameState.setScoringSawCaldoriaKiosk(true); + startExtraSequenceSync(kCaldoriaKioskVideo, kFilterAllInput); + downButton(dummy); + break; + case kCaldoria44: + arriveAtCaldoria44(); + break; + case kCaldoria49: + arriveAtCaldoria49(); + break; + case kCaldoria53: + if (direction == kEast && !GameState.getCaldoriaSinclairShot()) + zoomToSinclair(); + break; + case kCaldoria50: + if (direction == kNorth && !GameState.getCaldoriaSinclairShot()) + setUpSinclairLoops(); + break; + case kCaldoria54: + if (direction == kSouth && !GameState.getCaldoriaSinclairShot()) + setUpSinclairLoops(); + break; + case kCaldoria56: + arriveAtCaldoria56(); + break; + case kCaldoriaDeathRoom: + arriveAtCaldoriaDeath(); + break; + } + + checkSinclairShootsOS(); + setUpRoofTop(); +} + +void Caldoria::doAIRecalibration() { + GameState.setCaldoriaDidRecalibration(true); + + if (!g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Caldoria/XA01EB1", true, kRecalibrationInterruptFilter)) + return; + + g_interface->calibrateEnergyBar(); + if (!g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Caldoria/XA01EB4", true, kRecalibrationInterruptFilter)) + return; + + g_interface->raiseInventoryDrawerSync(); + if (!g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Caldoria/XA01EB6", true, kRecalibrationInterruptFilter)) { + g_interface->lowerInventoryDrawerSync(); + return; + } + + g_interface->lowerInventoryDrawerSync(); + g_interface->raiseBiochipDrawerSync(); + + if (!g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Caldoria/XA01EB5", true, kRecalibrationInterruptFilter)) { + g_interface->lowerBiochipDrawerSync(); + return; + } + + g_interface->lowerBiochipDrawerSync(); + + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Caldoria/XA01EB8", false, kRecalibrationInterruptFilter); +} + +void Caldoria::arriveAtCaldoria00() { + if (GameState.getCurrentDirection() == kEast) { + if (GameState.getCaldoriaWokenUp()) { + if (!GameState.getCaldoriaDidRecalibration()) + doAIRecalibration(); + setCurrentActivation(kActivate4DClosed); + } else { + // Good morning, sleeping beauty + ExtraTable::Entry extra; + getExtraEntry(kCaldoria00WakeUp1, extra); + + if (_navMovie.getTime() != extra.movieStart) { + _navMovie.setTime(extra.movieStart); + _navMovie.redrawMovieWorld(); + } + + startExtraSequenceSync(kCaldoria00WakeUp1, kFilterNoInput); + GameState.setCaldoriaWokenUp(true); + playCroppedMovieOnce("Images/Caldoria/VidPhone.movie", kCaldoriaVidPhoneLeft, kCaldoriaVidPhoneTop, kFilterAllInput); + startExtraSequence(kCaldoria00WakeUp2, kExtraCompletedFlag, kFilterNoInput); + } + } +} + +bool Caldoria::wantsCursor() { + return GameState.getCaldoriaDidRecalibration(); +} + +void Caldoria::arriveAtCaldoria44() { + if (GameState.getLastNeighborhood() != kCaldoriaID) { + openDoor(); + } else { + setCurrentActivation(kActivateReadyForCard); + loopExtraSequence(kCaldoriaTransporterArrowLoop, 0); + } +} + +void Caldoria::arriveAtCaldoria49() { + if (GameState.getLastRoom() == kCaldoria48) + setCurrentAlternate(kAltCaldoriaNormal); + + // Need to force the loop to play. + if (GameState.getCurrentDirection() == kNorth) { + GameState.setCaldoriaFuseTimeLimit(kSinclairShootsTimeLimit); + startExtraSequence(kCa49NorthVoiceAnalysis, kExtraCompletedFlag, kFilterNoInput); + } +} + +void Caldoria::arriveAtCaldoria56() { + if (!GameState.getCaldoriaBombDisarmed()) { + _privateFlags.setFlag(kCaldoriaPrivateZoomingToBombFlag, true); + + if (GameState.getCurrentDirection() == kNorth) { + turnRight(); + } else if (GameState.getCurrentDirection() == kSouth) { + turnLeft(); + } else if (GameState.getCurrentDirection() == kEast) { + _privateFlags.setFlag(kCaldoriaPrivateZoomingToBombFlag, false); + newInteraction(kCaldoriaBombInteractionID); + } + } +} + +void Caldoria::arriveAtCaldoriaDeath() { + if (GameState.getLastRoom() == kCaldoria49) { + if (GameState.getCaldoriaSinclairShot()) { + die(kDeathNuclearExplosion); + } else { + playSpotSoundSync(kCaldoriaSinclairShootsOSIn, kCaldoriaSinclairShootsOSOut); + playSpotSoundSync(kCaldoriaScreamingAfterIn, kCaldoriaScreamingAfterOut); + die(kDeathSinclairShotDelegate); + } + } else { + die(kDeathShotBySinclair); + } +} + +void Caldoria::setUpRoofTop() { + switch (GameState.getCurrentRoom()) { + case kCaldoria48: + if (GameState.getCurrentDirection() == kNorth) { + if (GameState.getCaldoriaRoofDoorOpen()) { + setCurrentAlternate(kAltCaldoriaRoofDoorBlown); + } else if (GameState.getCaldoriaDoorBombed()) { + // Long enough for AI hints...? + _utilityFuse.primeFuse(kCardBombCountDownTime); + _utilityFuse.setFunctionPtr(&doorBombTimerExpiredFunction, (void *)this); + _utilityFuse.lightFuse(); + + loopCroppedMovie("Images/Caldoria/A48 Bomb Loop", kCaldoria48CardBombLoopLeft, kCaldoria48CardBombLoopTop); + } else { + setCurrentActivation(kActivateRoofSlotEmpty); + } + } + break; + case kCaldoria56: + if (GameState.getCurrentDirection() == kEast && GameState.getCaldoriaGunAimed()) + startExtraSequence(kCa53EastShootSinclair, kExtraCompletedFlag, false); + else + // Fall through... + case kCaldoria49: + case kCaldoria50: + case kCaldoria51: + case kCaldoria52: + case kCaldoria53: + case kCaldoria54: + case kCaldoria55: + if (!GameState.getCaldoriaSinclairShot()) { + if (GameState.getCaldoriaSawVoiceAnalysis() && !_utilityFuse.isFuseLit()) { + _utilityFuse.primeFuse(GameState.getCaldoriaFuseTimeLimit()); + _utilityFuse.setFunctionPtr(&sinclairTimerExpiredFunction, (void *)this); + _utilityFuse.lightFuse(); + } + } else { + setCurrentAlternate(kAltCaldoriaSinclairDown); + } + break; + } +} + +void Caldoria::downButton(const Input &input) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kCaldoria01, kEast): + GameState.setCaldoriaWokenUp(true); + startExtraSequence(kCaldoria00SitDown, kExtraCompletedFlag, kFilterNoInput); + break; + default: + Neighborhood::downButton(input); + break; + } +} + +void Caldoria::turnTo(const DirectionConstant direction) { + Neighborhood::turnTo(direction); + + switch (GameState.getCurrentRoom()) { + case kCaldoria00: + if (direction == kEast) + setCurrentActivation(kActivate4DClosed); + break; + case kCaldoria01: + if (direction == kEast) { + GameState.setCaldoriaWokenUp(true); + startExtraSequence(kCaldoria00SitDown, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kCaldoria05: + if (direction == kWest && GameState.getCaldoriaINNAnnouncing()) + loopCroppedMovie("Images/Caldoria/A05 Light Loop", kCaldoriaA05LightLoopLeft, kCaldoriaA05LightLoopTop); + break; + case kCaldoria07: + if (direction == kWest && GameState.getCaldoriaINNAnnouncing()) + loopCroppedMovie("Images/Caldoria/A07 Light Loop", kCaldoriaA07LightLoopLeft, kCaldoriaA07LightLoopTop); + break; + case kCaldoria08: + if (direction == kWest) + setCurrentActivation(kActivateMirrorReady); + break; + case kCaldoria09: + _lastExtra = 0xffffffff; + break; + case kCaldoria11: + if (direction == kEast && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A11 Message Machine Loop", kCaldoria11MessageLoopLeft, kCaldoria11MessageLoopTop); + break; + case kCaldoria12: + if (direction == kNorth && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A12 Message Machine Loop", kCaldoria12MessageLoopLeft, kCaldoria12MessageLoopTop); + break; + case kCaldoria13: + if (direction == kNorth && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A13 Message Machine Loop", kCaldoria13MessageLoopLeft, kCaldoria13MessageLoopTop); + break; + case kCaldoria14: + if (direction == kNorth && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A14 Message Machine Loop", kCaldoria14MessageLoopLeft, kCaldoria14MessageLoopTop); + break; + case kCaldoria27: + case kCaldoria28: + case kCaldoria45: + if (direction == kNorth) + openElevatorMovie(); + else + closeCroppedMovie(); + break; + case kCaldoria48: + if (direction == kNorth && !GameState.getCaldoriaDoorBombed()) + setCurrentActivation(kActivateRoofSlotEmpty); + break; + case kCaldoria53: + if (GameState.getCurrentDirection() == kEast && !GameState.getCaldoriaSinclairShot()) + zoomToSinclair(); + break; + case kCaldoria50: + if (direction == kNorth && !GameState.getCaldoriaSinclairShot()) + setUpSinclairLoops(); + break; + case kCaldoria54: + if (direction == kSouth && !GameState.getCaldoriaSinclairShot()) + setUpSinclairLoops(); + break; + case kCaldoria56: + if (_privateFlags.getFlag(kCaldoriaPrivateZoomingToBombFlag)) { + _privateFlags.setFlag(kCaldoriaPrivateZoomingToBombFlag, false); + newInteraction(kCaldoriaBombInteractionID); + } else if (GameState.getCaldoriaBombDisarmed()) { + _vm->playEndMessage(); + } + break; + } + + checkSinclairShootsOS(); +} + +void Caldoria::zoomTo(const Hotspot *zoomOutSpot) { + // Need to set _zoomOutSpot here because we may come through + // this function another way, say by pressing the down arrow, + // that doesn't involve the ClickInHotSpot function. + _zoomOutSpot = zoomOutSpot; + + if (zoomOutSpot->getObjectID() == kCaldoriaDrawersOutSpotID) { + if (_privateFlags.getFlag(kCaloriaPrivateLeftDrawerOpenFlag)) { + _privateFlags.setFlag(kCaloriaPrivateLeftDrawerOpenFlag, false); + startExtraSequence(kLeftDrawerClose, kExtraCompletedFlag, kFilterNoInput); + } else if (_privateFlags.getFlag(kCaldoriaPrivateRightDrawerOpenFlag)) { + _privateFlags.setFlag(kCaldoriaPrivateRightDrawerOpenFlag, false); + if (GameState.isTakenItemID(kKeyCard)) + startExtraSequence(kRightDrawerCloseNoKeys, kExtraCompletedFlag, false); + else + startExtraSequence(kRightDrawerCloseWithKeys, kExtraCompletedFlag, false); + } else { + Neighborhood::zoomTo(zoomOutSpot); + } + } else { + Neighborhood::zoomTo(zoomOutSpot); + } +} + +void Caldoria::setUpSinclairLoops() { + _navMovie.stop(); + scheduleNavCallBack(kSinclairLoopDoneFlag); + _sinclairLoopCount = 0; + _numSinclairLoops = 2; + _navMovie.start(); +} + +void Caldoria::zoomToSinclair() { + _utilityFuse.stopFuse(); + _privateFlags.setFlag(kCaldoriaPrivateReadyToShootFlag, true); + setCurrentActivation(kActivateZoomedOnSinclair); + + ExtraTable::Entry entry; + getExtraEntry(kCa53EastZoomToSinclair, entry); + _sinclairInterrupt.scheduleCallBack(kTriggerTimeFwd, entry.movieStart + kSinclairInterruptionTime1, _navMovie.getScale()); + startExtraSequence(kCa53EastZoomToSinclair, kExtraCompletedFlag, kFilterAllInput); +} + +void Caldoria::receiveNotification(Notification *notification, const NotificationFlags flags) { + Neighborhood::receiveNotification(notification, flags); + + if ((flags & kExtraCompletedFlag) != 0) { + InventoryItem *item; + _interruptionFilter = kFilterAllInput; + + switch (_lastExtra) { + case kCaldoria00WakeUp2: + makeContinuePoint(); + // Force ArriveAt to do its thing... + GameState.setCurrentRoom(kNoRoomID); + arriveAt(kCaldoria00, kEast); + break; + case k4DEnvironOpenToINN: + GameState.setCaldoriaSeenINN(true); + GameState.setScoringSawINN(true); + // Fall through to k4DEnvironOpen... + case k4DEnvironOpen: + _privateFlags.setFlag(kCaldoriaPrivate4DSystemOpenFlag, true); + setCurrentActivation(kActivate4DOpen); + newInteraction(kCaldoria4DInteractionID); + break; + case kCaldoriaShowerUp: + GameState.setScoringTookShower(true); + GameState.setCaldoriaDoneHygiene(true); + break; + case kLeftDrawerClose: + case kRightDrawerCloseNoKeys: + case kRightDrawerCloseWithKeys: + if (_zoomOutSpot && _zoomOutSpot->getObjectID() == kCaldoriaDrawersOutSpotID) { + Input input; + clickInHotspot(input, _zoomOutSpot); + } + break; + case kCreateOrangeJuice: + setCurrentActivation(kActivateOJOnThePad); + requestSpotSound(kCaldoriaReplicatorOJChoiceIn, kCaldoriaReplicatorOJChoiceOut, kFilterNoInput, 0); + break; + case kCaldoria00SitDown: + arriveAt(kCaldoria00, kEast); + break; + case kCaldoria16ElevatorUp: + startExtraSequence(kCaldoria16ElevatorDown, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoria16ElevatorDown: + GameState.setCaldoriaSeenSinclairInElevator(true); + _privateFlags.setFlag(kCaldoriaPrivateCanOpenElevatorDoorFlag, true); + openDoor(); + break; + case kCaldoriaFourthToGround: + case kCaldoriaRoofToGround: + arriveAt(kCaldoria28, GameState.getCurrentDirection()); + break; + case kCaldoriaFourthToRoof: + case kCaldoriaGroundToRoof: + arriveAt(kCaldoria45, GameState.getCurrentDirection()); + break; + case kCaldoriaGroundToFourth: + case kCaldoriaRoofToFourth: + arriveAt(kCaldoria27, GameState.getCurrentDirection()); + break; + case kCaGTCardSwipe: + item = (InventoryItem *)g_allItems.findItemByID(kKeyCard); + _vm->addItemToInventory(item); + setCurrentActivation(kActivateReadyToTransport); + break; + case kCaGTFryTheFly: + case kCaGTGoToTSA: + _vm->jumpToNewEnvironment(kFullTSAID, kTSA00, kNorth); + break; + case kCaGTGoToTokyo: + playDeathExtra(kCaGTArriveAtTokyo, kDeathUncreatedInCaldoria); + break; + case kCaGTGoToBeach: + playDeathExtra(kCaGTArriveAtBeach, kDeathUncreatedInCaldoria); + break; + case kCa48NorthExplosion: + // Current biochip must be the shield if we got here. + _vm->getCurrentBiochip()->setItemState(kShieldNormal); + break; + case kBinocularsZoomInOnShip: + setCurrentActivation(kActivateFocusedOnShip); + break; + case kCa49NorthVoiceAnalysis: + _utilityFuse.primeFuse(kSinclairShootsTimeLimit); + _utilityFuse.setFunctionPtr(&sinclairTimerExpiredFunction, (void*) this); + _utilityFuse.lightFuse(); + GameState.setCaldoriaSawVoiceAnalysis(true); + break; + case kCa53EastZoomToSinclair: + if (GameState.getCaldoriaSinclairShot()) { + delete _gunSprite; + _gunSprite = 0; + startExtraSequence(kCa53EastShootSinclair, kExtraCompletedFlag, false); + } else { + playDeathExtra(kCa53EastDeath2, kDeathSinclairShotDelegate); + } + break; + case kCa53EastShootSinclair: + _vm->addItemToInventory((InventoryItem *)g_allItems.findItemByID(kStunGun)); + startExtraSequence(kCa53EastZoomOutFromSinclair, kExtraCompletedFlag, false); + GameState.setScoringStunnedSinclair(true); + break; + case kCa53EastZoomOutFromSinclair: + setCurrentAlternate(kAltCaldoriaSinclairDown); + updateViewFrame(); + makeContinuePoint(); + break; + } + } else if ((flags & kSpotSoundCompletedFlag) != 0) { + switch (GameState.getCurrentRoom()) { + case kCaldoria20: + case kCaldoria21: + case kCaldoria26: + case kCaldoria29: + case kCaldoria34: + case kCaldoria35: + updateViewFrame(); + break; + case kCaldoria27: + case kCaldoria28: + case kCaldoria45: + updateElevatorMovie(); + break; + case kCaldoriaReplicator: + emptyOJGlass(); + break; + } + } else if ((flags & kSinclairLoopDoneFlag) != 0) { + if (++_sinclairLoopCount == _numSinclairLoops) { + switch (GameState.getCurrentRoom()) { + case kCaldoria50: + playDeathExtra(kCa50SinclairShoots, kDeathShotBySinclair); + break; + case kCaldoria54: + playDeathExtra(kCa54SouthDeath, kDeathShotBySinclair); + break; + } + } else { + _navMovie.stop(); + scheduleNavCallBack(kSinclairLoopDoneFlag); + _navMovie.start(); + } + } + + g_AIArea->checkMiddleArea(); +} + +InputBits Caldoria::getInputFilter() { + InputBits result = Neighborhood::getInputFilter(); + + switch (GameState.getCurrentRoom()) { + case kCaldoria00: + if (_privateFlags.getFlag(kCaldoriaPrivate4DSystemOpenFlag)) + result &= ~kFilterAllDirections; + break; + case kCaldoriaBinoculars: + if (getCurrentActivation() == kActivateNotFocusedOnShip) + result &= ~(kFilterDownButton | kFilterDownAuto); + break; + case kCaldoria53: + if (_privateFlags.getFlag(kCaldoriaPrivateReadyToShootFlag) && !GameState.getCaldoriaSinclairShot()) + result &= ~kFilterAllDirections; + break; + case kCaldoria48: + if (GameState.getCaldoriaDoorBombed()) + result &= ~kFilterAllDirections; + } + + return result; +} + +void Caldoria::activateHotspots() { + Neighborhood::activateHotspots(); + + switch (GameState.getCurrentRoom()) { + case kCaldoriaDrawers: + if (getCurrentActivation() == kActivateRightOpen) { + if (GameState.isTakenItemID(kKeyCard)) { + g_allHotspots.activateOneHotspot(kCaldoriaRightDrawerNoKeysCloseSpotID); + g_allHotspots.deactivateOneHotspot(kCaldoriaRightDrawerWithKeysCloseSpotID); + } else { + g_allHotspots.activateOneHotspot(kCaldoriaRightDrawerWithKeysCloseSpotID); + g_allHotspots.deactivateOneHotspot(kCaldoriaRightDrawerNoKeysCloseSpotID); + } + } + case kCaldoriaReplicator: + if (GameState.getCaldoriaMadeOJ()) + g_allHotspots.deactivateOneHotspot(kCaldoriaMakeOJSpotID); + break; + case kCaldoria27: + if (GameState.isCurrentDoorOpen()) { + g_allHotspots.deactivateOneHotspot(kCaldoriaFourthFloorElevator1); + g_allHotspots.deactivateOneHotspot(kCaldoriaFourthFloorElevator2); + g_allHotspots.deactivateOneHotspot(kCaldoriaFourthFloorElevator3); + g_allHotspots.deactivateOneHotspot(kCaldoriaFourthFloorElevator4); + g_allHotspots.deactivateOneHotspot(kCaldoriaFourthFloorElevator5); + } + break; + case kCaldoria28: + if (GameState.isCurrentDoorOpen()) { + g_allHotspots.deactivateOneHotspot(kCaldoriaGroundElevator1); + g_allHotspots.deactivateOneHotspot(kCaldoriaGroundElevator2); + g_allHotspots.deactivateOneHotspot(kCaldoriaGroundElevator3); + g_allHotspots.deactivateOneHotspot(kCaldoriaGroundElevator4); + g_allHotspots.deactivateOneHotspot(kCaldoriaGroundElevator5); + } + break; + case kCaldoria45: + if (GameState.isCurrentDoorOpen()) { + g_allHotspots.deactivateOneHotspot(kCaldoriaRoofElevator1); + g_allHotspots.deactivateOneHotspot(kCaldoriaRoofElevator2); + g_allHotspots.deactivateOneHotspot(kCaldoriaRoofElevator3); + g_allHotspots.deactivateOneHotspot(kCaldoriaRoofElevator4); + g_allHotspots.deactivateOneHotspot(kCaldoriaRoofElevator5); + } + break; + } +} + +void Caldoria::clickInHotspot(const Input &input, const Hotspot *spot) { + switch (spot->getObjectID()) { + case kCa4DEnvironOpenSpotID: + if (!GameState.getCaldoriaINNAnnouncing() || GameState.getCaldoriaSeenINN()) { + startExtraSequence(k4DEnvironOpen, kExtraCompletedFlag, kFilterNoInput); + } else { + // This trick depends on the following sequences being in order in the + // world movie: + // k4DEnvironOpenToINN + // k4DINNInterruption + // k4DINNIntro + // k4DINNMarkJohnson + // k4DINNMeganLove + // k4DINNFadeOut + // k4DEnvironOpenFromINN + loadLoopSound1(""); + loadLoopSound2(""); + startExtraLongSequence(k4DEnvironOpenToINN, k4DEnvironOpenFromINN, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kCa4DEnvironCloseSpotID: + ((Caldoria4DSystem *)_currentInteraction)->shutDown4DSystem(); + break; + case kCaBathroomMirrorSpotID: + newInteraction(kCaldoriaMirrorInteractionID); + break; + case kCaShowerSpotID: + requestExtraSequence(kCaldoriaShowerTitle, 0, kFilterNoInput); + requestExtraSequence(kCaldoriaShowerButton, 0, kFilterNoInput); + requestExtraSequence(kCaldoriaShowerDown, 0, kFilterNoInput); + requestExtraSequence(kCaldoriaShowerUp, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaLeftDrawerOpenSpotID: + _privateFlags.setFlag(kCaloriaPrivateLeftDrawerOpenFlag, true); + setCurrentActivation(kActivateLeftOpen); + startExtraSequence(kLeftDrawerOpen, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaLeftDrawerCloseSpotID: + _privateFlags.setFlag(kCaloriaPrivateLeftDrawerOpenFlag, false); + setCurrentActivation(kActivateDrawersClosed); + startExtraSequence(kLeftDrawerClose, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaRightDrawerOpenSpotID: + _privateFlags.setFlag(kCaldoriaPrivateRightDrawerOpenFlag, true); + setCurrentActivation(kActivateRightOpen); + if (GameState.isTakenItemID(kKeyCard)) + startExtraSequence(kRightDrawerOpenNoKeys, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kRightDrawerOpenWithKeys, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaRightDrawerWithKeysCloseSpotID: + _privateFlags.setFlag(kCaldoriaPrivateRightDrawerOpenFlag, false); + setCurrentActivation(kActivateDrawersClosed); + startExtraSequence(kRightDrawerCloseWithKeys, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaRightDrawerNoKeysCloseSpotID: + _privateFlags.setFlag(kCaldoriaPrivateRightDrawerOpenFlag, false); + setCurrentActivation(kActivateDrawersClosed); + startExtraSequence(kRightDrawerCloseNoKeys, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaMakeStickyBunsSpotID: + requestSpotSound(kCaldoriaReplicatorWrongChoiceIn, kCaldoriaReplicatorWrongChoiceOut, kFilterNoInput, 0); + break; + case kCaldoriaMakeOJSpotID: + GameState.setCaldoriaMadeOJ(true); + startExtraSequence(kCreateOrangeJuice, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaBedroomVidPhoneActivationSpotID: + newInteraction(kCaldoriaMessagesInteractionID); + break; + case kCaldoriaFourthFloorElevatorSpotID: + if (!GameState.getCaldoriaSeenSinclairInElevator()) { + startExtraSequence(kCaldoria16ElevatorUp, kExtraCompletedFlag, kFilterNoInput); + } else { + _privateFlags.setFlag(kCaldoriaPrivateCanOpenElevatorDoorFlag, true); + openDoor(); + } + break; + case kCaldoriaGroundElevatorSpotID: + _privateFlags.setFlag(kCaldoriaPrivateCanOpenElevatorDoorFlag, true); + openDoor(); + break; + case kCaldoriaRoofElevatorSpotID: + _privateFlags.setFlag(kCaldoriaPrivateCanOpenElevatorDoorFlag, true); + openDoor(); + break; + case kCaldoriaFourthFloorElevator1: + case kCaldoriaFourthFloorElevator2: + case kCaldoriaFourthFloorElevator3: + case kCaldoriaFourthFloorElevator4: + case kCaldoriaFourthFloorElevator5: + // Assumes that elevator hot spots are consecutive. + takeElevator(4, spot->getObjectID() - kCaldoriaFourthFloorElevator1 + 1); + break; + case kCaldoriaGroundElevator1: + case kCaldoriaGroundElevator2: + case kCaldoriaGroundElevator3: + case kCaldoriaGroundElevator4: + case kCaldoriaGroundElevator5: + // Assumes that elevator hot spots are consecutive. + takeElevator(1, spot->getObjectID() - kCaldoriaGroundElevator1 + 1); + break; + case kCaldoriaRoofElevator1: + case kCaldoriaRoofElevator2: + case kCaldoriaRoofElevator3: + case kCaldoriaRoofElevator4: + case kCaldoriaRoofElevator5: + // Assumes that elevator hot spots are consecutive. + takeElevator(5, spot->getObjectID() - kCaldoriaRoofElevator1 + 1); + break; + case kCaldoriaGTTokyoSpotID: + startExtraSequence(kCaGTGoToTokyo, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaGTTSASpotID: + GameState.setScoringGoToTSA(true); + startExtraLongSequence(kCaGTFryTheFly, kCaGTGoToTSA, kExtraCompletedFlag, false); + break; + case kCaldoriaGTBeachSpotID: + startExtraSequence(kCaGTGoToBeach, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaGTOtherSpotID: + showExtraView(kCaGTOtherChoice); + playSpotSoundSync(kCaldoriaNoOtherDestinationIn, kCaldoriaNoOtherDestinationOut); + showExtraView(kCaGTCardSwipe); + break; + case kCaldoriaZoomInOnShipSpotID: + startExtraSequence(kBinocularsZoomInOnShip, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaRoofDoorSpotID: + startExtraSequence(kCa48NorthRooftopClosed, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoria20DoorbellSpotID: + case kCaldoria21DoorbellSpotID: + case kCaldoria26DoorbellSpotID: + case kCaldoria29DoorbellSpotID: + case kCaldoria34DoorbellSpotID: + case kCaldoria35DoorbellSpotID: + clickOnDoorbell(spot->getObjectID()); + break; + default: + Neighborhood::clickInHotspot(input, spot); + break; + } +} + +void Caldoria::clickOnDoorbell(const HotSpotID doorBellSpotID) { + uint32 extra; + ExtraTable::Entry entry; + + switch (doorBellSpotID) { + case kCaldoria20DoorbellSpotID: + extra = kCaldoria20Doorbell; + break; + case kCaldoria21DoorbellSpotID: + extra = kCaldoria21Doorbell; + break; + case kCaldoria26DoorbellSpotID: + extra = kCaldoria26Doorbell; + break; + case kCaldoria29DoorbellSpotID: + extra = kCaldoria29Doorbell; + break; + case kCaldoria34DoorbellSpotID: + extra = kCaldoria34Doorbell; + break; + case kCaldoria35DoorbellSpotID: + extra = kCaldoria35Doorbell; + break; + default: + error("Invalid doorbell hotspot"); + } + + getExtraEntry(extra, entry); + showViewFrame(entry.movieStart); + requestSpotSound(kCaldoriaNobodyHomeIn, kCaldoriaNobodyHomeOut, kFilterNoInput, kSpotSoundCompletedFlag); +} + +CanOpenDoorReason Caldoria::canOpenDoor(DoorTable::Entry &entry) { + switch (GameState.getCurrentRoom()) { + case kCaldoria16: + case kCaldoria38: + case kCaldoria46: + if (GameState.getCurrentDirection() == kSouth && !_privateFlags.getFlag(kCaldoriaPrivateCanOpenElevatorDoorFlag)) + return kCantOpenLocked; + break; + } + + return Neighborhood::canOpenDoor(entry); +} + +void Caldoria::doorOpened() { + Neighborhood::doorOpened(); + _privateFlags.setFlag(kCaldoriaPrivateCanOpenElevatorDoorFlag, false); +} + +GameInteraction *Caldoria::makeInteraction(const InteractionID interactionID) { + switch (interactionID) { + case kCaldoria4DInteractionID: + return new Caldoria4DSystem(this); + case kCaldoriaBombInteractionID: + return new CaldoriaBomb(this, _vm); + case kCaldoriaMessagesInteractionID: + return new CaldoriaMessages(this, kCaldoriaMessagesNotificationID, _vm); + case kCaldoriaMirrorInteractionID: + return new CaldoriaMirror(this); + } + + return 0; +} + +void Caldoria::newInteraction(const InteractionID interactionID) { + Neighborhood::newInteraction(interactionID); + + if (!_currentInteraction) { + if (_privateFlags.getFlag(kCaldoriaPrivate4DSystemOpenFlag)) { + _privateFlags.setFlag(kCaldoriaPrivate4DSystemOpenFlag, false); + setCurrentActivation(kActivate4DClosed); + startExtraSequence(k4DEnvironClose, kExtraCompletedFlag, kFilterNoInput); + } else if (GameState.getCaldoriaBombDisarmed()) { + turnLeft(); + } + } +} + +// Only called when trying to pick up an item and the player can't (because +// the inventory is too full or because the player lets go of the item before +// dropping it into the inventory). +Hotspot *Caldoria::getItemScreenSpot(Item *item, DisplayElement *element) { + HotSpotID destSpotID = kNoHotSpotID; + + switch (item->getObjectID()) { + case kKeyCard: + destSpotID = kCaldoriaKeyCardSpotID; + break; + case kOrangeJuiceGlassEmpty: + case kOrangeJuiceGlassFull: + destSpotID = kCaldoriaOrangeJuiceSpotID; + break; + } + + if (destSpotID == kNoHotSpotID) + return Neighborhood::getItemScreenSpot(item, element); + + return g_allHotspots.findHotspotByID(destSpotID); +} + +void Caldoria::pickedUpItem(Item *item) { + switch (item->getObjectID()) { + case kKeyCard: + GameState.setScoringGotKeyCard(true); + break; + case kOrangeJuiceGlassFull: + setCurrentActivation(kActivateReplicatorReady); + requestSpotSound(kCaldoriaDrinkOJIn, kCaldoriaDrinkOJOut, kFilterNoInput, kSpotSoundCompletedFlag); + break; + case kStunGun: + GameState.setCaldoriaGunAimed(false); + break; + } +} + +void Caldoria::dropItemIntoRoom(Item *item, Hotspot *dropSpot) { + switch (item->getObjectID()) { + case kKeyCard: + Neighborhood::dropItemIntoRoom(item, dropSpot); + if (dropSpot->getObjectID() == kCaldoriaGTCardDropSpotID) + startExtraSequence(kCaGTCardSwipe, kExtraCompletedFlag, kFilterNoInput); + break; + case kOrangeJuiceGlassEmpty: + Neighborhood::dropItemIntoRoom(item, dropSpot); + if (dropSpot->getObjectID() == kCaldoriaOrangeJuiceDropSpotID) { + GameState.setCaldoriaMadeOJ(false); + startExtraSequence(kDisposeOrangeJuice, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kCardBomb: + GameState.setCaldoriaDoorBombed(true); + setCurrentActivation(kActivateHotSpotAlways); + Neighborhood::dropItemIntoRoom(item, dropSpot); + // Long enough for AI hints...? + _utilityFuse.primeFuse(kCardBombCountDownTime); + _utilityFuse.setFunctionPtr(&doorBombTimerExpiredFunction, (void *)this); + _utilityFuse.lightFuse(); + GameState.setCaldoriaFuseTimeLimit(kCardBombCountDownTime); + loopCroppedMovie("Images/Caldoria/A48 Bomb Loop", kCaldoria48CardBombLoopLeft, kCaldoria48CardBombLoopTop); + GameState.setScoringUsedCardBomb(true); + break; + case kStunGun: + GameState.setCaldoriaGunAimed(true); + GameState.setCaldoriaSinclairShot(true); + _gunSprite = item->getDragSprite(0); + _gunSprite->setCurrentFrameIndex(1); + _gunSprite->setDisplayOrder(kDragSpriteOrder); + _gunSprite->moveElementTo(kCaldoriaGunSpriteLeft, kCaldoriaGunSpriteTop); + _gunSprite->startDisplaying(); + _gunSprite->show(); + break; + default: + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + } +} + +void Caldoria::takeElevator(uint startFloor, uint endFloor) { + _croppedMovie.stop(); + _croppedMovie.setSegment(0, _croppedMovie.getDuration()); + + switch (startFloor) { + case 1: + switch (endFloor) { + case 1: + // Do nothing. + break; + case 2: + _croppedMovie.setTime(k1To2Time); + requestSpotSound(kCaldoriaNoOtherDestinationIn, kCaldoriaNoOtherDestinationOut, kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 3: + _croppedMovie.setTime(k1To3Time); + requestSpotSound(kCaldoriaNoOtherDestinationIn, kCaldoriaNoOtherDestinationOut, kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 4: + _croppedMovie.setSegment(k1To4Start, k1To4Stop); + _croppedMovie.setTime(k1To4Start); + startExtraSequence(kCaldoriaGroundToFourth, kExtraCompletedFlag, false); + _croppedMovie.start(); + break; + case 5: + _croppedMovie.setSegment(k1To5Start, k1To5Stop); + _croppedMovie.setTime(k1To5Start); + startExtraSequence(kCaldoriaGroundToRoof, kExtraCompletedFlag, false); + _croppedMovie.start(); + break; + } + break; + case 4: + switch (endFloor) { + case 1: + _croppedMovie.setSegment(k4To1Start, k4To1Stop); + _croppedMovie.setTime(k4To1Start); + startExtraSequence(kCaldoriaFourthToGround, kExtraCompletedFlag, false); + _croppedMovie.start(); + break; + case 2: + _croppedMovie.setTime(k4To2Time); + requestSpotSound(kCaldoriaNoOtherDestinationIn, kCaldoriaNoOtherDestinationOut, kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 3: + _croppedMovie.setTime(k4To3Time); + requestSpotSound(kCaldoriaNoOtherDestinationIn, kCaldoriaNoOtherDestinationOut, kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 4: + // Do nothing. + break; + case 5: + _croppedMovie.setSegment(k4To5Start, k4To5Stop); + _croppedMovie.setTime(k4To5Start); + startExtraSequence(kCaldoriaFourthToRoof, kExtraCompletedFlag, false); + _croppedMovie.start(); + break; + } + break; + case 5: + switch (endFloor) { + case 1: + _croppedMovie.setSegment(k5To1Start, k5To1Stop); + _croppedMovie.setTime(k5To1Start); + startExtraSequence(kCaldoriaRoofToGround, kExtraCompletedFlag, false); + _croppedMovie.start(); + break; + case 2: + _croppedMovie.setTime(k5To2Time); + requestSpotSound(kCaldoriaNoOtherDestinationIn, kCaldoriaNoOtherDestinationOut, kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 3: + _croppedMovie.setTime(k5To3Time); + requestSpotSound(kCaldoriaNoOtherDestinationIn, kCaldoriaNoOtherDestinationOut, kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 4: + _croppedMovie.setSegment(k5To4Start, k5To4Stop); + _croppedMovie.setTime(k5To4Start); + startExtraSequence(kCaldoriaRoofToFourth, kExtraCompletedFlag, false); + _croppedMovie.start(); + break; + case 5: + // Do nothing. + break; + } + break; + }; +} + +void Caldoria::updateElevatorMovie() { + TimeValue time = 0xffffffff; + + if (GameState.getCurrentDirection() == kNorth) { + switch (GameState.getCurrentRoom()) { + case kCaldoria27: + time = k4FloorTime; + break; + case kCaldoria28: + time = k1FloorTime; + break; + case kCaldoria45: + time = k5FloorTime; + break; + } + } + + _croppedMovie.stop(); + + if (time == 0xffffffff) { + _croppedMovie.hide(); + } else { + _croppedMovie.stop(); + _croppedMovie.setSegment(0, _croppedMovie.getDuration()); + _croppedMovie.setTime(time); + _croppedMovie.redrawMovieWorld(); + _croppedMovie.show(); + + // *** Why do I need this? + // clone2727: "don't ask me!" + _navMovie.redrawMovieWorld(); + } +} + +void Caldoria::openElevatorMovie() { + if (!_croppedMovie.isSurfaceValid()) + openCroppedMovie("Images/Caldoria/Caldoria Elevator.movie", kCaldoriaElevatorLeft, kCaldoriaElevatorTop); + + updateElevatorMovie(); +} + +void Caldoria::emptyOJGlass() { + GameState.setTakenItemID(kOrangeJuiceGlassFull, false); + GameState.setTakenItemID(kOrangeJuiceGlassEmpty, true); + _vm->removeItemFromInventory((InventoryItem *)g_allItems.findItemByID(kOrangeJuiceGlassFull)); + _vm->addItemToInventory((InventoryItem *)g_allItems.findItemByID(kOrangeJuiceGlassEmpty)); +} + +void Caldoria::doorBombTimerExpired() { + closeCroppedMovie(); + + if (GameState.getShieldOn()) { + _vm->getCurrentBiochip()->setItemState(kShieldCardBomb); + setCurrentAlternate(kAltCaldoriaRoofDoorBlown); + startExtraSequence(kCa48NorthExplosion, kExtraCompletedFlag, kFilterNoInput); + GameState.setScoringShieldedCardBomb(true); + GameState.setCaldoriaDoorBombed(false); + GameState.setCaldoriaRoofDoorOpen(true); + } else { + playDeathExtra(kCa48NorthExplosionDeath, kDeathCardBomb); + } +} + +void Caldoria::sinclairTimerExpired() { + _privateFlags.setFlag(kCaldoriaPrivateSinclairTimerExpiredFlag, true); + checkSinclairShootsOS(); +} + +void Caldoria::checkSinclairShootsOS() { + if (_privateFlags.getFlag(kCaldoriaPrivateSinclairTimerExpiredFlag)) + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kCaldoria49, kNorth): + case MakeRoomView(kCaldoria49, kSouth): + case MakeRoomView(kCaldoria49, kEast): + case MakeRoomView(kCaldoria49, kWest): + case MakeRoomView(kCaldoria50, kSouth): + case MakeRoomView(kCaldoria50, kEast): + case MakeRoomView(kCaldoria50, kWest): + case MakeRoomView(kCaldoria51, kNorth): + case MakeRoomView(kCaldoria51, kSouth): + case MakeRoomView(kCaldoria51, kWest): + case MakeRoomView(kCaldoria52, kNorth): + case MakeRoomView(kCaldoria52, kSouth): + case MakeRoomView(kCaldoria52, kWest): + case MakeRoomView(kCaldoria53, kNorth): + case MakeRoomView(kCaldoria53, kSouth): + case MakeRoomView(kCaldoria53, kWest): + case MakeRoomView(kCaldoria54, kNorth): + case MakeRoomView(kCaldoria54, kEast): + case MakeRoomView(kCaldoria54, kWest): + playSpotSoundSync(kCaldoriaSinclairShootsOSIn, kCaldoriaSinclairShootsOSOut); + playSpotSoundSync(kCaldoriaScreamingAfterIn, kCaldoriaScreamingAfterOut); + die(kDeathSinclairShotDelegate); + break; + } +} + +void Caldoria::checkInterruptSinclair() { + if (GameState.getCaldoriaSinclairShot()) { + _navMovie.stop(); + _neighborhoodNotification.setNotificationFlags(kExtraCompletedFlag, kExtraCompletedFlag); + g_AIArea->unlockAI(); + } else { + uint32 currentTime = _navMovie.getTime(); + + ExtraTable::Entry entry; + getExtraEntry(kCa53EastZoomToSinclair, entry); + + if (currentTime < entry.movieStart + kSinclairInterruptionTime2) + _sinclairInterrupt.scheduleCallBack(kTriggerTimeFwd, entry.movieStart + kSinclairInterruptionTime2, + _navMovie.getScale()); + else if (currentTime < entry.movieStart + kSinclairInterruptionTime3) + _sinclairInterrupt.scheduleCallBack(kTriggerTimeFwd, entry.movieStart + kSinclairInterruptionTime3, + _navMovie.getScale()); + else if (currentTime < entry.movieStart + kSinclairInterruptionTime4) + _sinclairInterrupt.scheduleCallBack(kTriggerTimeFwd, entry.movieStart + kSinclairInterruptionTime4, + _navMovie.getScale()); + } +} + +Common::String Caldoria::getBriefingMovie() { + Common::String movieName = Neighborhood::getBriefingMovie(); + + if (movieName.empty()) { + if (GameState.allTimeZonesFinished()) + return "Images/AI/Caldoria/XA02"; + + return "Images/AI/Caldoria/XA01"; + } + + return movieName; +} + +Common::String Caldoria::getEnvScanMovie() { + Common::String movieName = Neighborhood::getEnvScanMovie(); + + if (movieName.empty()) { + RoomID room = GameState.getCurrentRoom(); + + if (room >= kCaldoria00 && room <= kCaldoria14) { + // Inside apartment. + if (GameState.getCaldoriaDoneHygiene()) + return "Images/AI/Caldoria/XAE2"; + + return "Images/AI/Caldoria/XAE1"; + } else if (room >= kCaldoria15 && room <= kCaldoria48) { + // Wandering the halls... + return "Images/AI/Caldoria/XAE3"; + } else { + // Must be the roof. + return "Images/AI/Caldoria/XAEH2"; + } + } + + return movieName; +} + +uint Caldoria::getNumHints() { + uint numHints = Neighborhood::getNumHints(); + + if (numHints == 0) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kCaldoria44, kEast): + if (!GameState.isTakenItemID(kKeyCard) && GameState.getOpenDoorRoom() == kNoRoomID) + numHints = 1; + break; + case MakeRoomView(kCaldoria48, kNorth): + if (!GameState.getCaldoriaRoofDoorOpen()) { + if (_croppedMovie.isRunning()) // Bomb must be looping. + numHints = 3; + else if (GameState.isTakenItemID(kCardBomb)) + numHints = 1; + } + break; + case MakeRoomView(kCaldoria49, kEast): + case MakeRoomView(kCaldoria54, kEast): + numHints = 1; + break; + case MakeRoomView(kCaldoria49, kNorth): + numHints = 1; + break; + } + } + + return numHints; +} + +Common::String Caldoria::getHintMovie(uint hintNum) { + Common::String movieName = Neighborhood::getHintMovie(hintNum); + + if (movieName.empty()) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kCaldoria44, kEast): + return "Images/AI/Caldoria/X42WH2"; + case MakeRoomView(kCaldoria48, kNorth): + if (_croppedMovie.isRunning()) { // Bomb must be looping. + if (hintNum == 1) + return "Images/AI/Caldoria/X48ND1"; + else if (hintNum == 2) + return "Images/AI/Caldoria/X48ND2"; + else if (GameState.isTakenItemID(kShieldBiochip)) + return "Images/AI/Caldoria/X48ND3"; + + // *** Doesn't work yet, need global movies. + break; + } + + return "Images/AI/Globals/XGLOB1A"; + case MakeRoomView(kCaldoria49, kEast): + case MakeRoomView(kCaldoria54, kEast): + return "Images/AI/Caldoria/X49E"; + case MakeRoomView(kCaldoria49, kNorth): + return "Images/AI/Caldoria/X49NB2"; + } + } + + return movieName; +} + +void Caldoria::updateCursor(const Common::Point where, const Hotspot *cursorSpot) { + if (cursorSpot) { + switch (cursorSpot->getObjectID()) { + case kCa4DEnvironCloseSpotID: + _vm->_cursor->setCurrentFrameIndex(2); + return; + case kCaldoriaKioskSpotID: + _vm->_cursor->setCurrentFrameIndex(3); + return; + } + } + + Neighborhood::updateCursor(where, cursorSpot); +} + +Common::String Caldoria::getNavMovieName() { + return "Images/Caldoria/Caldoria.movie"; +} + +Common::String Caldoria::getSoundSpotsName() { + return "Sounds/Caldoria/Caldoria Spots"; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/caldoria/caldoria.h b/engines/pegasus/neighborhood/caldoria/caldoria.h new file mode 100644 index 0000000000..f02101ec3b --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoria.h @@ -0,0 +1,525 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIA_H +#define PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIA_H + +#include "pegasus/neighborhood/neighborhood.h" + +namespace Pegasus { + +static const TimeScale kCaldoriaMovieScale = 600; +static const TimeScale kCaldoriaFramesPerSecond = 15; +static const TimeScale kCaldoriaFrameDuration = 40; + +// Alternate IDs. + +static const AlternateID kAltCaldoriaNormal = 0; +static const AlternateID kAltCaldoriaRoofDoorBlown = 2; +static const AlternateID kAltCaldoriaSinclairDown = 3; + +// Room IDs. + +static const RoomID kCaldoria00 = 1; +static const RoomID kCaldoria01 = 2; +static const RoomID kCaldoria02 = 3; +static const RoomID kCaldoria03 = 4; +static const RoomID kCaldoria04 = 5; +static const RoomID kCaldoria05 = 6; +static const RoomID kCaldoria06 = 7; +static const RoomID kCaldoria07 = 8; +static const RoomID kCaldoria08 = 9; +static const RoomID kCaldoria09 = 10; +static const RoomID kCaldoria10 = 11; +static const RoomID kCaldoriaToilet = 12; +static const RoomID kCaldoria11 = 13; +static const RoomID kCaldoria12 = 14; +static const RoomID kCaldoriaVidPhone = 15; +static const RoomID kCaldoriaReplicator = 16; +static const RoomID kCaldoriaDrawers = 17; +static const RoomID kCaldoria13 = 18; +static const RoomID kCaldoria14 = 19; +static const RoomID kCaldoria15 = 20; +static const RoomID kCaldoria16 = 21; +static const RoomID kCaldoria17 = 22; +static const RoomID kCaldoria18 = 23; +static const RoomID kCaldoria19 = 24; +static const RoomID kCaldoria20 = 25; +static const RoomID kCaldoria21 = 26; +static const RoomID kCaldoria22 = 27; +static const RoomID kCaldoria23 = 28; +static const RoomID kCaldoria24 = 29; +static const RoomID kCaldoria25 = 30; +static const RoomID kCaldoria26 = 31; +static const RoomID kCaldoria27 = 32; +static const RoomID kCaldoria28 = 33; +static const RoomID kCaldoria29 = 34; +static const RoomID kCaldoria30 = 35; +static const RoomID kCaldoria31 = 36; +static const RoomID kCaldoria32 = 37; +static const RoomID kCaldoria33 = 38; +static const RoomID kCaldoria34 = 39; +static const RoomID kCaldoria35 = 40; +static const RoomID kCaldoria36 = 41; +static const RoomID kCaldoria37 = 42; +static const RoomID kCaldoria38 = 43; +static const RoomID kCaldoria39 = 44; +static const RoomID kCaldoria40 = 45; +static const RoomID kCaldoria41 = 46; +static const RoomID kCaldoriaBinoculars = 47; +static const RoomID kCaldoria42 = 48; +static const RoomID kCaldoriaKiosk = 49; +static const RoomID kCaldoria44 = 50; +static const RoomID kCaldoria45 = 51; +static const RoomID kCaldoria46 = 52; +static const RoomID kCaldoria47 = 53; +static const RoomID kCaldoria48 = 54; +static const RoomID kCaldoria49 = 55; +static const RoomID kCaldoria50 = 56; +static const RoomID kCaldoria51 = 57; +static const RoomID kCaldoria52 = 58; +static const RoomID kCaldoria53 = 59; +static const RoomID kCaldoria54 = 60; +static const RoomID kCaldoria55 = 61; +static const RoomID kCaldoria56 = 62; +static const RoomID kCaldoriaDeathRoom = 0; + +// Hot Spot Activation IDs. + +static const HotSpotActivationID kActivate4DClosed = 1; +static const HotSpotActivationID kActivate4DOpen = 2; +static const HotSpotActivationID kActivateMirrorReady = 3; +static const HotSpotActivationID kActivateStylistReady = 4; +static const HotSpotActivationID kActivateReplicatorReady = 5; +static const HotSpotActivationID kActivateOJOnThePad = 6; +static const HotSpotActivationID kActivateDrawersClosed = 7; +static const HotSpotActivationID kActivateRightOpen = 8; +static const HotSpotActivationID kActivateLeftOpen = 9; +static const HotSpotActivationID kActivateFocusedOnShip = 10; +static const HotSpotActivationID kActivateNotFocusedOnShip = 11; +static const HotSpotActivationID kActivateReadyForCard = 12; +static const HotSpotActivationID kActivateReadyToTransport = 13; +static const HotSpotActivationID kActivateRoofSlotEmpty = 14; +static const HotSpotActivationID kActivateZoomedOnSinclair = 15; + +// Hot Spot IDs. + +static const HotSpotID kCa4DEnvironOpenSpotID = 5000; +static const HotSpotID kCa4DEnvironCloseSpotID = 5001; +static const HotSpotID kCa4DVisualSpotID = 5002; +static const HotSpotID kCa4DAudioSpotID = 5003; +static const HotSpotID kCa4DChoice1SpotID = 5004; +static const HotSpotID kCa4DChoice2SpotID = 5005; +static const HotSpotID kCa4DChoice3SpotID = 5006; +static const HotSpotID kCa4DChoice4SpotID = 5007; +static const HotSpotID kCaBathroomMirrorSpotID = 5008; +static const HotSpotID kCaHairStyle1SpotID = 5009; +static const HotSpotID kCaHairStyle2SpotID = 5010; +static const HotSpotID kCaHairStyle3SpotID = 5011; +static const HotSpotID kCaShowerSpotID = 5012; +static const HotSpotID kCaBathroomToiletSpotID = 5013; +static const HotSpotID kCaldoriaVidPhoneSpotID = 5014; +static const HotSpotID kCaldoriaReplicatorSpotID = 5015; +static const HotSpotID kCaldoriaDrawersSpotID = 5016; +static const HotSpotID kCaldoriaVidPhoneOutSpotID = 5017; +static const HotSpotID kCaBedroomVidPhoneActivationSpotID = 5018; +static const HotSpotID kCaldoriaReplicatorOutSpotID = 5019; +static const HotSpotID kCaldoriaMakeOJSpotID = 5020; +static const HotSpotID kCaldoriaMakeStickyBunsSpotID = 5021; +static const HotSpotID kCaldoriaOrangeJuiceSpotID = 5022; +static const HotSpotID kCaldoriaOrangeJuiceDropSpotID = 5023; +static const HotSpotID kCaldoriaDrawersOutSpotID = 5024; +static const HotSpotID kCaldoriaLeftDrawerOpenSpotID = 5025; +static const HotSpotID kCaldoriaRightDrawerOpenSpotID = 5026; +static const HotSpotID kCaldoriaKeyCardSpotID = 5027; +static const HotSpotID kCaldoriaLeftDrawerCloseSpotID = 5028; +static const HotSpotID kCaldoriaRightDrawerWithKeysCloseSpotID = 5029; +static const HotSpotID kCaldoriaRightDrawerNoKeysCloseSpotID = 5030; +static const HotSpotID kCaldoriaFourthFloorElevatorSpotID = 5031; +static const HotSpotID kCaldoria20DoorbellSpotID = 5032; +static const HotSpotID kCaldoria21DoorbellSpotID = 5033; +static const HotSpotID kCaldoria26DoorbellSpotID = 5034; +static const HotSpotID kCaldoriaFourthFloorElevator1 = 5035; +static const HotSpotID kCaldoriaFourthFloorElevator2 = 5036; +static const HotSpotID kCaldoriaFourthFloorElevator3 = 5037; +static const HotSpotID kCaldoriaFourthFloorElevator4 = 5038; +static const HotSpotID kCaldoriaFourthFloorElevator5 = 5039; +static const HotSpotID kCaldoriaGroundElevator1 = 5040; +static const HotSpotID kCaldoriaGroundElevator2 = 5041; +static const HotSpotID kCaldoriaGroundElevator3 = 5042; +static const HotSpotID kCaldoriaGroundElevator4 = 5043; +static const HotSpotID kCaldoriaGroundElevator5 = 5044; +static const HotSpotID kCaldoria29DoorbellSpotID = 5045; +static const HotSpotID kCaldoria34DoorbellSpotID = 5046; +static const HotSpotID kCaldoria35DoorbellSpotID = 5047; +static const HotSpotID kCaldoriaGroundElevatorSpotID = 5048; +static const HotSpotID kCaldoriaBinocularZoomInSpotID = 5049; +static const HotSpotID kCaldoriaBinocularsOutSpotID = 5050; +static const HotSpotID kCaldoriaZoomInOnShipSpotID = 5051; +static const HotSpotID kCaldoriaKioskSpotID = 5052; +static const HotSpotID kCaldoriaKioskOutSpotID = 5053; +static const HotSpotID kCaldoriaKioskInfoSpotID = 5054; +static const HotSpotID kCaldoriaGTCardDropSpotID = 5055; +static const HotSpotID kCaldoriaGTTokyoSpotID = 5056; +static const HotSpotID kCaldoriaGTTSASpotID = 5057; +static const HotSpotID kCaldoriaGTBeachSpotID = 5058; +static const HotSpotID kCaldoriaGTOtherSpotID = 5059; +static const HotSpotID kCaldoriaRoofElevator1 = 5060; +static const HotSpotID kCaldoriaRoofElevator2 = 5061; +static const HotSpotID kCaldoriaRoofElevator3 = 5062; +static const HotSpotID kCaldoriaRoofElevator4 = 5063; +static const HotSpotID kCaldoriaRoofElevator5 = 5064; +static const HotSpotID kCaldoriaRoofElevatorSpotID = 5065; +static const HotSpotID kCaldoriaRoofDoorSpotID = 5066; +static const HotSpotID kCaldoriaRoofCardDropSpotID = 5067; +static const HotSpotID kCaldoria53EastSinclairTargetSpotID = 5068; + +// Extra sequence IDs. + +static const ExtraID kCaldoriaWakeUpView1 = 0; +static const ExtraID kCaldoria00WakeUp1 = 1; +static const ExtraID kCaldoria00WakeUp2 = 2; +static const ExtraID kCaldoria00SitDown = 3; +static const ExtraID k4DEnvironOpenToINN = 4; +static const ExtraID k4DINNInterruption = 5; +static const ExtraID k4DINNIntro = 6; +static const ExtraID k4DINNMarkJohnson = 7; +static const ExtraID k4DINNMeganLove = 8; +static const ExtraID k4DINNFadeOut = 9; +static const ExtraID k4DEnvironOpenFromINN = 10; +static const ExtraID k4DEnvironOpen = 11; +static const ExtraID k4DEnvironOpenView = 12; +static const ExtraID k4DEnvironClose = 13; +static const ExtraID k4DIslandLoop = 14; +static const ExtraID k4DDesertLoop = 15; +static const ExtraID k4DMountainLoop = 16; +static const ExtraID k4DIsland1ToIsland0 = 17; +static const ExtraID k4DIsland2ToIsland0 = 18; +static const ExtraID k4DIsland0ToDesert0 = 19; +static const ExtraID k4DIsland1ToDesert0 = 20; +static const ExtraID k4DIsland2ToDesert0 = 21; +static const ExtraID k4DIsland0ToMountain0 = 22; +static const ExtraID k4DIsland1ToMountain0 = 23; +static const ExtraID k4DIsland2ToMountain0 = 24; +static const ExtraID k4DDesert0ToIsland0 = 25; +static const ExtraID k4DDesert1ToIsland0 = 26; +static const ExtraID k4DDesert2ToIsland0 = 27; +static const ExtraID k4DDesert0ToMountain0 = 28; +static const ExtraID k4DDesert1ToMountain0 = 29; +static const ExtraID k4DDesert2ToMountain0 = 30; +static const ExtraID k4DMountain0ToIsland0 = 31; +static const ExtraID k4DMountain1ToIsland0 = 32; +static const ExtraID k4DMountain2ToIsland0 = 33; +static const ExtraID k4DMountain0ToDesert0 = 34; +static const ExtraID k4DMountain1ToDesert0 = 35; +static const ExtraID k4DMountain2ToDesert0 = 36; +static const ExtraID kCaBathroomGreeting = 37; +static const ExtraID kCaBathroomBodyFat = 38; +static const ExtraID kCaBathroomStylistIntro = 39; +static const ExtraID kCaBathroomRetrothrash = 40; +static const ExtraID kCaBathroomRetrothrashReturn = 41; +static const ExtraID kCaBathroomGeoWave = 42; +static const ExtraID kCaBathroomGeoWaveReturn = 43; +static const ExtraID kCaBathroomAgencyStandard = 44; +static const ExtraID kCaldoriaShowerTitle = 45; +static const ExtraID kCaldoriaShowerButton = 46; +static const ExtraID kCaldoriaShowerDown = 47; +static const ExtraID kCaldoriaShowerUp = 48; +static const ExtraID kCaBedroomVidPhone = 49; +static const ExtraID kCaBedroomMessage1 = 50; +static const ExtraID kCaBedroomMessage2 = 51; +static const ExtraID kCreateOrangeJuice = 52; +static const ExtraID kDisposeOrangeJuice = 53; +static const ExtraID kReplicatorNorthViewWithOJ = 54; +static const ExtraID kLeftDrawerOpen = 55; +static const ExtraID kLeftDrawerClose = 56; +static const ExtraID kRightDrawerOpenWithKeys = 57; +static const ExtraID kRightDrawerCloseWithKeys = 58; +static const ExtraID kRightDrawerOpenNoKeys = 59; +static const ExtraID kRightDrawerCloseNoKeys = 60; +static const ExtraID kRightDrawerOpenViewWithKeys = 61; +static const ExtraID kRightDrawerOpenViewNoKeys = 62; +static const ExtraID kCaldoria16ElevatorUp = 63; +static const ExtraID kCaldoria16ElevatorDown = 64; +static const ExtraID kCaldoria16SouthViewWithElevator = 65; +static const ExtraID kCaldoria20Doorbell = 66; +static const ExtraID kCaldoria21Doorbell = 67; +static const ExtraID kCaldoria26Doorbell = 68; +static const ExtraID kCaldoriaFourthToGround = 69; +static const ExtraID kCaldoriaRoofToFourth = 70; +static const ExtraID kCaldoriaRoofToGround = 71; +static const ExtraID kCaldoriaGroundToFourth = 72; +static const ExtraID kCaldoriaGroundToRoof = 73; +static const ExtraID kCaldoriaFourthToRoof = 74; +static const ExtraID kCaldoria29Doorbell = 75; +static const ExtraID kCaldoria34Doorbell = 76; +static const ExtraID kCaldoria35Doorbell = 77; +static const ExtraID kBinocularsZoomInOnShip = 78; +static const ExtraID kCaldoriaKioskVideo = 79; +static const ExtraID kCaldoriaTransporterArrowLoop = 80; +static const ExtraID kArriveAtCaldoriaFromTSA = 81; +static const ExtraID kCaGTOtherChoice = 82; +static const ExtraID kCaGTCardSwipe = 83; +static const ExtraID kCaGTSelectTSA = 84; +static const ExtraID kCaGTFryTheFly = 85; +static const ExtraID kCaGTGoToTSA = 86; +static const ExtraID kCaGTSelectBeach = 87; +static const ExtraID kCaGTGoToBeach = 88; +static const ExtraID kCaGTArriveAtBeach = 89; +static const ExtraID kCaGTSelectTokyo = 90; +static const ExtraID kCaGTGoToTokyo = 91; +static const ExtraID kCaGTArriveAtTokyo = 92; +static const ExtraID kCa48NorthRooftopClosed = 93; +static const ExtraID kCa48NorthExplosion = 94; +static const ExtraID kCa48NorthExplosionDeath = 95; +static const ExtraID kCa49NorthVoiceAnalysis = 96; +static const ExtraID kCa50SinclairShoots = 97; +static const ExtraID kCa53EastZoomToSinclair = 98; +static const ExtraID kCa53EastDeath2 = 99; +static const ExtraID kCa53EastShootSinclair = 100; +static const ExtraID kCa53EastZoomOutFromSinclair = 101; +static const ExtraID kCa54SouthDeath = 102; +static const ExtraID kCaldoria56BombStage1 = 103; +static const ExtraID kCaldoria56BombStage2 = 104; +static const ExtraID kCaldoria56BombStage3 = 105; +static const ExtraID kCaldoria56BombStage4 = 106; +static const ExtraID kCaldoria56BombStage5 = 107; +static const ExtraID kCaldoria56BombStage6 = 108; +static const ExtraID kCaldoria56BombStage7 = 109; +static const ExtraID kCaldoria56BombExplodes = 110; + +// Caldoria interactions. + +static const InteractionID kCaldoria4DInteractionID = 0; +static const InteractionID kCaldoriaBombInteractionID = 1; +static const InteractionID kCaldoriaMessagesInteractionID = 2; +static const InteractionID kCaldoriaMirrorInteractionID = 3; + +// Caldoria: + +static const DisplayOrder kVidPhoneOrder = kMonitorLayer; +static const DisplayOrder k4DSpritesOrder = kMonitorLayer; +static const DisplayOrder kCaldoriaMessagesOrder = kMonitorLayer; +static const DisplayOrder kCaldoriaElevatorOrder = kMonitorLayer; +static const DisplayOrder kCaldoriaA05LightLoopOrder = kMonitorLayer; +static const DisplayOrder kCaldoriaA07LightLoopOrder = kMonitorLayer; +static const DisplayOrder kCaldoriaBombGridOrder = kMonitorLayer; +static const DisplayOrder kCaldoriaBombTimerOrder = kCaldoriaBombGridOrder + 1; + +///////////////////////////////////////////// +// +// Caldoria + +static const CoordType kCaldoriaVidPhoneLeft = kNavAreaLeft + 105; +static const CoordType kCaldoriaVidPhoneTop = kNavAreaTop + 28; + +static const CoordType kCaldoria4DSpritesLeft = kNavAreaLeft + 10; +static const CoordType kCaldoria4DSpritesTop = kNavAreaTop + 142; + +static const CoordType kCaldoriaMessageLeft = kNavAreaLeft + 202; +static const CoordType kCaldoriaMessageTop = kNavAreaTop + 26; + +static const CoordType kCaldoriaElevatorLeft = kNavAreaLeft + 407; +static const CoordType kCaldoriaElevatorTop = kNavAreaTop + 138; + +static const CoordType kCaldoriaA05LightLoopLeft = kNavAreaLeft + 213; +static const CoordType kCaldoriaA05LightLoopTop = kNavAreaTop + 215; + +static const CoordType kCaldoriaA07LightLoopLeft = kNavAreaLeft + 414; +static const CoordType kCaldoriaA07LightLoopTop = kNavAreaTop + 215; + +static const CoordType kCaldoriaGunSpriteLeft = kNavAreaLeft + 276; +static const CoordType kCaldoriaGunSpriteTop = kNavAreaTop + 115; + +static const CoordType kCaldoria11MessageLoopLeft = kNavAreaLeft + 135; +static const CoordType kCaldoria11MessageLoopTop = kNavAreaTop + 214; + +static const CoordType kCaldoria12MessageLoopLeft = kNavAreaLeft + 209; +static const CoordType kCaldoria12MessageLoopTop = kNavAreaTop + 170; + +static const CoordType kCaldoria13MessageLoopLeft = kNavAreaLeft + 480; +static const CoordType kCaldoria13MessageLoopTop = kNavAreaTop + 191; + +static const CoordType kCaldoria14MessageLoopLeft = kNavAreaLeft + 248; +static const CoordType kCaldoria14MessageLoopTop = kNavAreaTop + 191; + +static const CoordType kCaldoria48CardBombLoopLeft = kNavAreaLeft + 337; +static const CoordType kCaldoria48CardBombLoopTop = kNavAreaTop + 205; + +static const CoordType kCaldoriaBombGridLeft = kNavAreaLeft + 290; +static const CoordType kCaldoriaBombGridTop = kNavAreaTop + 58; + +static const CoordType kCaldoriaBombTimerLeft = kNavAreaLeft + 58; +static const CoordType kCaldoriaBombTimerTop = kNavAreaTop + 204; + +// Caldoria display IDs. + +static const DisplayElementID kCaldoriaVidPhoneID = kNeighborhoodDisplayID; +static const DisplayElementID kCaldoria4DSpritesID = kCaldoriaVidPhoneID + 1; +static const DisplayElementID kCaldoriaMessagesID = kCaldoria4DSpritesID + 1; +static const DisplayElementID kCaldoriaUtilityID = kCaldoriaMessagesID + 1; +static const DisplayElementID kCaldoriaBombGridID = kCaldoriaUtilityID + 1; +static const DisplayElementID kCaldoriaBombTimerID = kCaldoriaBombGridID + 1; + +static const TimeValue kCaldoria4DBlankChoiceIn = 29730; +static const TimeValue kCaldoria4DBlankChoiceOut = 33910; + +class Caldoria; + +class SinclairCallBack : public TimeBaseCallBack { +public: + SinclairCallBack(Caldoria *); + ~SinclairCallBack() {} + +protected: + virtual void callBack(); + + Caldoria *_caldoria; +}; + +class Caldoria : public Neighborhood { +friend class SinclairCallBack; +friend void doorBombTimerExpiredFunction(FunctionPtr *, void *); +friend void sinclairTimerExpiredFunction(FunctionPtr *, void *); + +public: + Caldoria(InputHandler *, PegasusEngine *); + virtual ~Caldoria(); + + virtual uint16 getDateResID() const; + + void pickedUpItem(Item *); + + virtual GameInteraction *makeInteraction(const InteractionID); + + virtual Common::String getBriefingMovie(); + virtual Common::String getEnvScanMovie(); + virtual uint getNumHints(); + virtual Common::String getHintMovie(uint); + void loadAmbientLoops(); + bool wantsCursor(); + void flushGameState(); + + void checkContinuePoint(const RoomID, const DirectionConstant); + +protected: + enum { + kCaldoriaPrivate4DSystemOpenFlag, + kCaloriaPrivateLeftDrawerOpenFlag, + kCaldoriaPrivateRightDrawerOpenFlag, + kCaldoriaPrivateReadyToShootFlag, + kCaldoriaPrivateZoomingToBombFlag, + kCaldoriaPrivateCanOpenElevatorDoorFlag, + kCaldoriaPrivateSinclairTimerExpiredFlag, + kCaldoriaPrivateSeen13CarFlag, + kCaldoriaPrivateSeen14CarFlag, + kCaldoriaPrivateSeen18CarFlag, + kCaldoriaPrivateSeen23CarFlag, + kCaldoriaPrivateSeen33CarFlag, + kCaldoriaPrivateSeen36CarFlag, + kCaldoriaPrivateSeen41NorthCarFlag, + kCaldoriaPrivateSeen41EastCarFlag, + kCaldoriaPrivateSeen41WestCarFlag, + kNumCaldoriaPrivateFlags + }; + + void init(); + void start(); + + void setUpRoofTop(); + + void setUpAIRules(); + void doAIRecalibration(); + TimeValue getViewTime(const RoomID, const DirectionConstant); + void findSpotEntry(const RoomID, const DirectionConstant, SpotFlags, SpotTable::Entry &); + void startSpotOnceOnly(TimeValue, TimeValue); + void startExitMovie(const ExitTable::Entry &); + void startZoomMovie(const ZoomTable::Entry &); + void startDoorOpenMovie(const TimeValue, const TimeValue); + void startTurnPush(const TurnDirection, const TimeValue, const DirectionConstant); + void bumpIntoWall(); + int16 getStaticCompassAngle(const RoomID, const DirectionConstant); + void getExitCompassMove(const ExitTable::Entry &, FaderMoveSpec &); + void getZoomCompassMove(const ZoomTable::Entry &, FaderMoveSpec &); + void getExtraCompassMove(const ExtraTable::Entry &, FaderMoveSpec &); + void spotCompleted(); + void arriveAt(const RoomID, const DirectionConstant); + void arriveAtCaldoria00(); + void arriveAtCaldoriaToilet(); + void arriveAtCaldoria44(); + void arriveAtCaldoria49(); + void arriveAtCaldoria56(); + void arriveAtCaldoriaDeath(); + void turnTo(const DirectionConstant); + void zoomTo(const Hotspot *); + void downButton(const Input &); + void receiveNotification(Notification *, const NotificationFlags); + InputBits getInputFilter(); + void activateHotspots(); + void clickInHotspot(const Input &, const Hotspot *); + void newInteraction(const InteractionID); + + void clickOnDoorbell(const HotSpotID); + + Hotspot *getItemScreenSpot(Item *, DisplayElement *); + void dropItemIntoRoom(Item *, Hotspot *); + void takeElevator(uint, uint); + void updateElevatorMovie(); + void openElevatorMovie(); + void emptyOJGlass(); + void closeDoorOffScreen(const RoomID, const DirectionConstant); + void doorBombTimerExpired(); + void sinclairTimerExpired(); + void checkSinclairShootsOS(); + void setUpSinclairLoops(); + void zoomToSinclair(); + void playEndMessage(); + void checkInterruptSinclair(); + + CanOpenDoorReason canOpenDoor(DoorTable::Entry &); + void doorOpened(); + + void updateCursor(const Common::Point, const Hotspot *); + + FlagsArray<uint16, kNumCaldoriaPrivateFlags> _privateFlags; + + const Hotspot *_zoomOutSpot; + + FuseFunction _utilityFuse; + + long _sinclairLoopCount; + long _numSinclairLoops; + + Sprite *_gunSprite; + + SinclairCallBack _sinclairInterrupt; + + Common::String getSoundSpotsName(); + Common::String getNavMovieName(); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.cpp b/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.cpp new file mode 100644 index 0000000000..d8ac3b08cb --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.cpp @@ -0,0 +1,369 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/ai/ai_area.h" +#include "pegasus/neighborhood/caldoria/caldoria.h" +#include "pegasus/neighborhood/caldoria/caldoria4dsystem.h" + +namespace Pegasus { + +static const TimeValue kSwitchableSlop = 3 * kCaldoriaFrameDuration; +// Two seconds - some slop +static const TimeValue kSwitchableDuration = kCaldoriaMovieScale * 2 - kSwitchableSlop; +// Twelve frames + some slop +static const TimeValue kNonswitchableDuration = kCaldoriaFrameDuration * 12 + kSwitchableSlop; + +static const TimeValue kSwitchable1Start = 0; +static const TimeValue kSwitchable1Stop = kSwitchable1Start + kSwitchableDuration; + +static const TimeValue kSwitchable2Start = kSwitchable1Stop + kNonswitchableDuration; +static const TimeValue kSwitchable2Stop = kSwitchable2Start + kSwitchableDuration; + +static const TimeValue kSwitchable3Start = kSwitchable2Stop + kNonswitchableDuration; +static const TimeValue kSwitchable3Stop = kSwitchable3Start + kSwitchableDuration; + +static const NotificationFlags kVidPhoneDoneFlag = 1; + +static const TimeValue kRockMusicLoopIn = 0; +static const TimeValue kRockMusicLoopOut = 2088; + +static const TimeValue kOrchestralMusicLoopIn = 2088; +static const TimeValue kOrchestralMusicLoopOut = 4985; + +static const TimeValue kRhythmsMusicLoopIn = 4985; +static const TimeValue kRhythmsMusicLoopOut = 6824; + +static const TimeValue kAcousticMusicLoopIn = 6824; +static const TimeValue kAcousticMusicLoopOut = 9387; + +enum { + k4DVideoMenu, + k4DAudioMenu, + k4DShuttingDown, + + // These constants are the exact frame numbers of the sprite movie. + k4DRockChoice = 0, + k4DOrchestralChoice, + k4DRhythmsChoice, + k4DAcousticChoice, + k4DIslandChoice, + k4DDesertChoice, + k4DMountainChoice, + + k4DFirstVideoChoice = k4DIslandChoice +}; + +static const ExtraID s_transitionExtras0[3][3] = { + { 0xffffffff, k4DIsland0ToDesert0, k4DIsland0ToMountain0 }, + { k4DDesert0ToIsland0, 0xffffffff, k4DDesert0ToMountain0 }, + { k4DMountain0ToIsland0, k4DMountain0ToDesert0, 0xffffffff } +}; + +static const ExtraID s_transitionExtras1[3][3] = { + { 0xffffffff, k4DIsland1ToDesert0, k4DIsland1ToMountain0 }, + { k4DDesert1ToIsland0, 0xffffffff, k4DDesert1ToMountain0 }, + { k4DMountain1ToIsland0, k4DMountain1ToDesert0, 0xffffffff } +}; + +static const ExtraID s_transitionExtras2[3][3] = { + { 0xffffffff, k4DIsland2ToDesert0, k4DIsland2ToMountain0 }, + { k4DDesert2ToIsland0, 0xffffffff, k4DDesert2ToMountain0 }, + { k4DMountain2ToIsland0, k4DMountain2ToDesert0, 0xffffffff } +}; + +static const ExtraID s_shutDownExtras[3][3] = { + { 0xffffffff, k4DIsland1ToIsland0, k4DIsland2ToIsland0 }, + { k4DDesert0ToIsland0, k4DDesert1ToIsland0, k4DDesert2ToIsland0 }, + { k4DMountain0ToIsland0, k4DMountain1ToIsland0, k4DMountain2ToIsland0 } +}; + +Caldoria4DSystem::Caldoria4DSystem(Neighborhood *owner) : GameInteraction(kCaldoria4DInteractionID, owner), + _4DSpritesMovie(kCaldoria4DSpritesID) { + g_AIArea->lockAIOut(); +} + +Caldoria4DSystem::~Caldoria4DSystem() { + g_AIArea->unlockAI(); +} + +void Caldoria4DSystem::openInteraction() { + _whichMenu = k4DVideoMenu; + _videoChoice = k4DIslandChoice; + _audioChoice = k4DRockChoice; + _clickedHotspotID = kNoHotSpotID; + + _4DSpritesMovie.initFromMovieFile("Images/Caldoria/4D Sprites", true); + _4DSpritesMovie.moveElementTo(kCaldoria4DSpritesLeft, kCaldoria4DSpritesTop); + _4DSpritesMovie.setDisplayOrder(k4DSpritesOrder); + _4DSpritesMovie.startDisplaying(); + _4DSpritesMovie.show(); + + _4DSpritesScale = _4DSpritesMovie.getScale(); + + _neighborhoodNotification = _owner->getNeighborhoodNotification(); + _neighborhoodNotification->notifyMe(this, kExtraCompletedFlag, kExtraCompletedFlag); + + startIdling(); +} + +void Caldoria4DSystem::loopExtra(const ExtraID extraID) { + ExtraTable::Entry extraEntry; + + _owner->getExtraEntry(extraID, extraEntry); + _loopStart = extraEntry.movieStart; + _owner->loopExtraSequence(extraID); +} + +void Caldoria4DSystem::useIdleTime() { + if (_whichMenu == k4DShuttingDown) { + TimeValue movieTime = _owner->getNavMovie()->getTime() - _loopStart; + ExtraID extraID; + + if (movieTime < kSwitchable1Stop) + extraID = s_shutDownExtras[_videoChoice - k4DFirstVideoChoice][0]; + else if (movieTime >= kSwitchable2Start && movieTime < kSwitchable2Stop) + extraID = s_shutDownExtras[_videoChoice - k4DFirstVideoChoice][1]; + else if (movieTime >= kSwitchable3Start && movieTime < kSwitchable3Stop) + extraID = s_shutDownExtras[_videoChoice - k4DFirstVideoChoice][2]; + else + extraID = 0xffffffff; + + if (extraID != 0xffffffff) { + setSpritesMovie(); + _loopStart = 0; + _owner->startExtraSequence(extraID, kExtraCompletedFlag, kFilterNoInput); + } + } else if (_clickedHotspotID != kNoHotSpotID) { + TimeValue movieTime = _owner->getNavMovie()->getTime() - _loopStart; + ExtraID extraID; + + if (movieTime < kSwitchable1Stop) { + extraID = s_transitionExtras0[_videoChoice - k4DFirstVideoChoice][_clickedHotspotID - kCa4DChoice1SpotID]; + _clickedHotspotID = kNoHotSpotID; + } else if (movieTime >= kSwitchable2Start && movieTime < kSwitchable2Stop) { + extraID = s_transitionExtras1[_videoChoice - k4DFirstVideoChoice][_clickedHotspotID - kCa4DChoice1SpotID]; + _clickedHotspotID = kNoHotSpotID; + } else if (movieTime >= kSwitchable3Start && movieTime < kSwitchable3Stop) { + extraID = s_transitionExtras2[_videoChoice - k4DFirstVideoChoice][_clickedHotspotID - kCa4DChoice1SpotID]; + _clickedHotspotID = kNoHotSpotID; + } else + extraID = 0xffffffff; + + if (extraID != 0xffffffff) { + switch (extraID) { + case k4DDesert0ToIsland0: + case k4DMountain0ToIsland0: + case k4DDesert1ToIsland0: + case k4DMountain1ToIsland0: + case k4DDesert2ToIsland0: + case k4DMountain2ToIsland0: + _videoChoice = k4DIslandChoice; + break; + case k4DIsland0ToDesert0: + case k4DMountain0ToDesert0: + case k4DIsland1ToDesert0: + case k4DMountain1ToDesert0: + case k4DIsland2ToDesert0: + case k4DMountain2ToDesert0: + _videoChoice = k4DDesertChoice; + break; + case k4DDesert0ToMountain0: + case k4DIsland0ToMountain0: + case k4DIsland1ToMountain0: + case k4DDesert1ToMountain0: + case k4DIsland2ToMountain0: + case k4DDesert2ToMountain0: + _videoChoice = k4DMountainChoice; + break; + } + + setSpritesMovie(); + _loopStart = 0; + _owner->startExtraSequence(extraID, kExtraCompletedFlag, kFilterNoInput); + } + } +} + +void Caldoria4DSystem::initInteraction() { + setSpritesMovie(); + + _owner->loadLoopSound1("Sounds/Caldoria/Rock.aiff"); + loopExtra(k4DIslandLoop); +} + +void Caldoria4DSystem::closeInteraction() { + stopIdling(); + _neighborhoodNotification->cancelNotification(this); + _4DSpritesMovie.releaseMovie(); + _owner->loadAmbientLoops(); +} + +void Caldoria4DSystem::setSpritesMovie() { + if (_whichMenu == k4DShuttingDown) + _4DSpritesMovie.setTime(_4DSpritesScale * k4DIslandChoice); + else if (_whichMenu == k4DVideoMenu) + _4DSpritesMovie.setTime(_4DSpritesScale * _videoChoice); + else if (_whichMenu == k4DAudioMenu) + _4DSpritesMovie.setTime(_4DSpritesScale * _audioChoice); + + _4DSpritesMovie.redrawMovieWorld(); +} + +void Caldoria4DSystem::handleInput(const Input &input, const Hotspot *cursorSpot) { + if (input.downButtonAnyDown()) + return; + if (input.anyDirectionInput()) + shutDown4DSystem(); + else + GameInteraction::handleInput(input, cursorSpot); +} + +void Caldoria4DSystem::activateHotspots() { + GameInteraction::activateHotspots(); + if (_whichMenu == k4DAudioMenu) + g_allHotspots.activateOneHotspot(kCa4DChoice4SpotID); +} + +void Caldoria4DSystem::clickInHotspot(const Input &input, const Hotspot *spot) { + switch (spot->getObjectID()) { + case kCa4DVisualSpotID: + if (_whichMenu == k4DAudioMenu) { + _whichMenu = k4DVideoMenu; + setSpritesMovie(); + } + break; + case kCa4DAudioSpotID: + if (_whichMenu == k4DVideoMenu) { + _whichMenu = k4DAudioMenu; + setSpritesMovie(); + } + break; + case kCa4DChoice1SpotID: + if (_whichMenu == k4DVideoMenu) + makeIslandChoice(); + else if (_whichMenu == k4DAudioMenu) + makeRockChoice(); + break; + case kCa4DChoice2SpotID: + if (_whichMenu == k4DVideoMenu) + makeDesertChoice(); + else if (_whichMenu == k4DAudioMenu) + makeOrchestralChoice(); + break; + case kCa4DChoice3SpotID: + if (_whichMenu == k4DVideoMenu) + makeMountainChoice(); + else if (_whichMenu == k4DAudioMenu) + makeRhythmsChoice(); + break; + case kCa4DChoice4SpotID: + if (_whichMenu == k4DAudioMenu) + makeAcousticChoice(); + else + _owner->playSpotSoundSync(kCaldoria4DBlankChoiceIn, kCaldoria4DBlankChoiceOut); + break; + default: + GameInteraction::clickInHotspot(input, spot); + } +} + +void Caldoria4DSystem::receiveNotification(Notification *, const NotificationFlags) { + if (_whichMenu == k4DShuttingDown) { + _owner->requestDeleteCurrentInteraction(); + } else { + uint32 extraID; + + switch (_videoChoice) { + case k4DIslandChoice: + extraID = k4DIslandLoop; + break; + case k4DDesertChoice: + extraID = k4DDesertLoop; + break; + case k4DMountainChoice: + extraID = k4DMountainLoop; + break; + default: + extraID = 0xffffffff; + break; + } + + if (extraID != 0xffffffff) + loopExtra(extraID); + } +} + +void Caldoria4DSystem::makeIslandChoice() { + if (_videoChoice != k4DIslandChoice && _clickedHotspotID == kNoHotSpotID) + _clickedHotspotID = kCa4DChoice1SpotID; +} + +void Caldoria4DSystem::makeDesertChoice() { + if (_videoChoice != k4DDesertChoice && _clickedHotspotID == kNoHotSpotID) + _clickedHotspotID = kCa4DChoice2SpotID; +} + +void Caldoria4DSystem::makeMountainChoice() { + if (_videoChoice != k4DMountainChoice && _clickedHotspotID == kNoHotSpotID) + _clickedHotspotID = kCa4DChoice3SpotID; +} + +void Caldoria4DSystem::makeRockChoice() { + if (_audioChoice != k4DRockChoice) { + _audioChoice = k4DRockChoice; + setSpritesMovie(); + _owner->loadLoopSound1("Sounds/Caldoria/Rock.aiff"); + } +} + +void Caldoria4DSystem::makeOrchestralChoice() { + if (_audioChoice != k4DOrchestralChoice) { + _audioChoice = k4DOrchestralChoice; + setSpritesMovie(); + _owner->loadLoopSound1("Sounds/Caldoria/Orchestral.aiff"); + } +} + +void Caldoria4DSystem::makeRhythmsChoice() { + if (_audioChoice != k4DRhythmsChoice) { + _audioChoice = k4DRhythmsChoice; + setSpritesMovie(); + _owner->loadLoopSound1("Sounds/Caldoria/Rhythms.aiff"); + } +} + +void Caldoria4DSystem::makeAcousticChoice() { + if (_audioChoice != k4DAcousticChoice) { + _audioChoice = k4DAcousticChoice; + setSpritesMovie(); + _owner->loadLoopSound1("Sounds/Caldoria/Acoustic.aiff"); + } +} + +void Caldoria4DSystem::shutDown4DSystem() { + _whichMenu = k4DShuttingDown; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.h b/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.h new file mode 100644 index 0000000000..63de7e1c4e --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.h @@ -0,0 +1,78 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIA4DSYSTEM_H +#define PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIA4DSYSTEM_H + +#include "pegasus/interaction.h" +#include "pegasus/movie.h" +#include "pegasus/notification.h" +#include "pegasus/timers.h" + +namespace Pegasus { + +class Neighborhood; + +class Caldoria4DSystem : public GameInteraction, private Idler, public NotificationReceiver { +public: + Caldoria4DSystem(Neighborhood *); + virtual ~Caldoria4DSystem(); + + void shutDown4DSystem(); + +protected: + void openInteraction(); + void initInteraction(); + void closeInteraction(); + + void handleInput(const Input &, const Hotspot *); + void activateHotspots(); + void clickInHotspot(const Input &, const Hotspot *); + void receiveNotification(Notification *, const NotificationFlags); + void setSpritesMovie(); + void makeIslandChoice(); + void makeRockChoice(); + void makeMountainChoice(); + void makeOrchestralChoice(); + void makeDesertChoice(); + void makeRhythmsChoice(); + void makeAcousticChoice(); + + void useIdleTime(); + void loopExtra(const ExtraID); + + Movie _4DSpritesMovie; + TimeScale _4DSpritesScale; + uint _whichMenu; + uint _videoChoice; + uint _audioChoice; + Notification *_neighborhoodNotification; + TimeValue _loopStart; + HotSpotID _clickedHotspotID; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/caldoria/caldoriabomb.cpp b/engines/pegasus/neighborhood/caldoria/caldoriabomb.cpp new file mode 100644 index 0000000000..c9ee68aefb --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoriabomb.cpp @@ -0,0 +1,1442 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/caldoria/caldoria.h" +#include "pegasus/neighborhood/caldoria/caldoriabomb.h" + +namespace Pegasus { + +// Bomb game PICTs: + +static const uint16 kYellowBombPICTBaseID = 700; +static const uint16 kRedBombPICTBaseID = 709; +static const uint16 kTimerLeftPICTID = 718; +static const uint16 kTimerRightPICTID = 719; + +static const uint32 kFlashOnTime = 20; +static const uint32 kFlashOffTime = 10; + +static const uint32 kOnTime1 = kFlashOnTime; +static const uint32 kOffTime1 = kOnTime1 + kFlashOffTime; +static const uint32 kOnTime2 = kOffTime1 + kFlashOnTime; +static const uint32 kOffTime2 = kOnTime2 + kFlashOffTime; +static const uint32 kOnTime3 = kOffTime2 + kFlashOnTime; +static const uint32 kOffTime3 = kOnTime3 + kFlashOffTime; +static const uint32 kOnTime4 = kOffTime3 + kFlashOnTime; + +static const HotSpotID kVertextHotSpotBaseID = 10000; + +static const CoordType kVertextHotSpotWidth = 24; +static const CoordType kVertextHotSpotHeight = 24; + +static const NotificationFlags kBombTimerExpiredFlag = 1; + +static const VertexType kBombLevelOne[] = { + 0, 1, 0, 1, 0, // hot vertices first. + 1, 1, 0, 1, 1, + 1, 1, 0, 1, 0, + 1, 1, 0, 1, 1, + 0, 1, 0, 1, 0, + + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + + 9, // 9 edges in this level + + kEdgeOneFourth, + 3, + 1, 2, 3, + 0, 0, + + kEdgeOneFourth, + 5, + 5, 6, 7, 8, 9, + 0, 0, 0, 0, + + kEdgeOneFourth, + 4, + 10, 11, 12, 13, + 0, 0, 0, + + kEdgeOneFourth, + 5, + 15, 16, 17, 18, 19, + 0, 0, 0, 0, + + kEdgeOneFourth, + 3, + 21, 22, 23, + 0, 0, + + kEdgeOneHalf, + 3, + 5, 10, 15, + 0, 0, + + kEdgeOneHalf, + 5, + 1, 6, 11, 16, 21, + 0, 0, 0, 0, + + kEdgeOneHalf, + 5, + 3, 8, 13, 18, 23, + 0, 0, 0, 0, + + kEdgeOneHalf, + 3, + 9, 14, 19, + 0, 0 +}; + +static const VertexType kBombLevelTwo[] = { + 0, 1, 0, 1, 0, + 1, 1, 1, 0, 1, + 0, 0, 0, 1, 0, + 1, 1, 1, 0, 1, + 0, 1, 0, 1, 0, + + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + + 15, + + kEdgeOneEighth, + 2, + 5, 1, + 0, + + kEdgeOneEighth, + 3, + 17, 13, 9, + 0, 0, + + kEdgeOneEighth, + 2, + 23, 19, + 0, + + kEdgeThreeEighths, + 2, + 3, 9, + 0, + + kEdgeThreeEighths, + 3, + 7, 13, 19, + 0, 0, + + kEdgeThreeEighths, + 2, + 15, 21, + 0, + + kEdgeOneFourth, + 3, + 1, 2, 3, + 0, 0, + + kEdgeOneFourth, + 4, + 6, 7, 8, 9, + 0, 0, 0, + + kEdgeOneFourth, + 4, + 16, 17, 18, 19, + 0, 0, 0, + + kEdgeOneFourth, + 3, + 21, 22, 23, + 0, 0, + + kEdgeOneHalf, + 3, + 5, 10, 15, + 0, 0, + + kEdgeOneHalf, + 2, + 1, 6, + 0, + + kEdgeOneHalf, + 3, + 7, 12, 17, + 0, 0, + + kEdgeOneHalf, + 3, + 9, 14, 19, + 0, 0, + + kEdgeOneHalf, + 2, + 16, 21, + 0 +}; + +static const VertexType kBombLevelThree[] = { + 0, 1, 0, 1, 0, + 1, 1, 1, 1, 1, + 0, 1, 1, 0, 0, + 1, 1, 1, 1, 1, + 0, 1, 0, 1, 0, + + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + + 22, + + kEdgeThreeSixteenths, + 3, + 15, 12, 9, + 0, 0, + + kEdgeFiveSixteenths, + 3, + 5, 12, 19, + 0, 0, + + kEdgeOneEighth, + 2, + 5, 1, + 0, + + kEdgeOneEighth, + 2, + 7, 3, + 0, + + kEdgeOneEighth, + 2, + 15, 11, + 0, + + kEdgeOneEighth, + 2, + 21, 17, + 0, + + kEdgeOneEighth, + 2, + 23, 19, + 0, + + kEdgeThreeEighths, + 2, + 1, 7, + 0, + + kEdgeThreeEighths, + 2, + 3, 9, + 0, + + kEdgeThreeEighths, + 2, + 5, 11, + 0, + + kEdgeThreeEighths, + 2, + 15, 21, + 0, + + kEdgeThreeEighths, + 2, + 17, 23, + 0, + + kEdgeOneFourth, + 3, + 1, 2, 3, + 0, 0, + + kEdgeOneFourth, + 2, + 5, 6, + 0, + + kEdgeOneFourth, + 2, + 8, 9, + 0, + + kEdgeOneFourth, + 2, + 15, 16, + 0, + + kEdgeOneFourth, + 2, + 18, 19, + 0, + + kEdgeOneFourth, + 3, + 21, 22, 23, + 0, 0, + + kEdgeOneHalf, + 2, + 1, 6, + 0, + + kEdgeOneHalf, + 2, + 3, 8, + 0, + + kEdgeOneHalf, + 2, + 16, 21, + 0, + + kEdgeOneHalf, + 2, + 18, 23, + 0 +}; + +static const VertexType kBombLevelFour[] = { + 1, 1, 1, 1, 0, + 1, 1, 0, 1, 1, + 1, 0, 1, 0, 1, + 1, 1, 0, 1, 1, + 0, 1, 1, 1, 1, + + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + + 19, + + kEdgeOneEighth, + 2, + 5, 1, + 0, + + kEdgeOneEighth, + 3, + 10, 6, 2, + 0, 0, + + kEdgeOneEighth, + 3, + 16, 12, 8, + 0, 0, + + kEdgeOneEighth, + 3, + 22, 18, 14, + 0, 0, + + kEdgeOneEighth, + 2, + 23, 19, + 0, + + kEdgeThreeEighths, + 3, + 2, 8, 14, + 0, 0, + + kEdgeThreeEighths, + 3, + 10, 16, 22, + 0, 0, + + kEdgeOneFourth, + 4, + 0, 1, 2, 3, + 0, 0, 0, + + kEdgeOneFourth, + 2, + 5, 6, + 0, + + kEdgeOneFourth, + 2, + 8, 9, + 0, + + kEdgeOneFourth, + 2, + 15, 16, + 0, + + kEdgeOneFourth, + 2, + 18, 19, + 0, + + kEdgeOneFourth, + 4, + 21, 22, 23, 24, + 0, 0, 0, + + kEdgeOneHalf, + 4, + 0, 5, 10, 15, + 0, 0, 0, + + kEdgeOneHalf, + 2, + 1, 6, + 0, + + kEdgeOneHalf, + 2, + 3, 8, + 0, + + kEdgeOneHalf, + 4, + 9, 14, 19, 24, + 0, 0, 0, + + kEdgeOneHalf, + 2, + 16, 21, + 0, + + kEdgeOneHalf, + 2, + 18, 23, + 0 +}; + +static const VertexType kBombLevelFive[] = { + 0, 1, 0, 1, 0, + 1, 1, 1, 1, 1, + 0, 1, 1, 1, 0, + 1, 1, 1, 1, 1, + 0, 1, 0, 1, 0, + + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + + 19, + + kEdgeOneEighth, + 2, + 5, 1, + 0, + + kEdgeOneEighth, + 2, + 7, 3, + 0, + + kEdgeOneEighth, + 2, + 13, 9, + 0, + + kEdgeOneEighth, + 2, + 15, 11, + 0, + + kEdgeOneEighth, + 2, + 21, 17, + 0, + + kEdgeOneEighth, + 2, + 23, 19, + 0, + + kEdgeThreeEighths, + 2, + 1, 7, + 0, + + kEdgeThreeEighths, + 4, + 5, 11, 17, 23, + 0, 0, 0, + + kEdgeThreeEighths, + 3, + 6, 12, 18, + 0, 0, + + kEdgeThreeEighths, + 2, + 13, 19, + 0, + + kEdgeThreeEighths, + 2, + 15, 21, + 0, + + kEdgeOneFourth, + 5, + 5, 6, 7, 8, 9, + 0, 0, 0, 0, + + kEdgeOneFourth, + 3, + 15, 16, 17, + 0, 0, + + kEdgeOneFourth, + 2, + 18, 19, + 0, + + kEdgeOneFourth, + 3, + 21, 22, 23, + 0, 0, + + kEdgeOneHalf, + 3, + 5, 10, 15, + 0, 0, + + kEdgeOneHalf, + 2, + 1, 6, + 0, + + kEdgeOneHalf, + 3, + 11, 16, 21, + 0, 0, + + kEdgeOneHalf, + 5, + 3, 8, 13, 18, 23, + 0, 0, 0, 0 +}; + +static const VertexType kBombLevelSix[] = { + 0, 1, 1, 1, 0, + 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, + 1, 1, 1, 1, 1, + 0, 1, 1, 1, 0, + + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + + 25, + + kEdgeOneSixteenth, + 2, + 10, 1, + 0, + + kEdgeOneSixteenth, + 2, + 23, 14, + 0, + + kEdgeSevenSixteenths, + 2, + 3, 14, + 0, + + kEdgeSevenSixteenths, + 2, + 10, 21, + 0, + + kEdgeOneEighth, + 2, + 5, 1, + 0, + + kEdgeOneEighth, + 3, + 10, 6, 2, + 0, 0, + + kEdgeOneEighth, + 2, + 7, 3, + 0, + + kEdgeOneEighth, + 2, + 21, 17, + 0, + + kEdgeOneEighth, + 3, + 22, 18, 14, + 0, 0, + + kEdgeOneEighth, + 2, + 23, 19, + 0, + + kEdgeThreeEighths, + 2, + 1, 7, + 0, + + kEdgeThreeEighths, + 3, + 2, 8, 14, + 0, 0, + + kEdgeThreeEighths, + 2, + 3, 9, + 0, + + kEdgeThreeEighths, + 3, + 10, 16, 22, + 0, 0, + + kEdgeThreeEighths, + 2, + 15, 21, + 0, + + kEdgeThreeEighths, + 2, + 17, 23, + 0, + + kEdgeOneFourth, + 3, + 1, 2, 3, + 0, 0, + + kEdgeOneFourth, + 3, + 6, 7, 8, + 0, 0, + + kEdgeOneFourth, + 3, + 16, 17, 18, + 0, 0, + + kEdgeOneFourth, + 3, + 21, 22, 23, + 0, 0, + + kEdgeOneHalf, + 3, + 5, 10, 15, + 0, 0, + + kEdgeOneHalf, + 3, + 6, 11, 16, + 0, 0, + + kEdgeOneHalf, + 5, + 2, 7, 12, 17, 22, + 0, 0, 0, 0, + + kEdgeOneHalf, + 3, + 8, 13, 18, + 0, 0, + + kEdgeOneHalf, + 3, + 9, 14, 19, + 0, 0 +}; + +static const CoordType kBombGridWidth = 140; +static const CoordType kBombGridHeight = 140; + +static const CoordType kDotOriginX = 0; +static const CoordType kDotOriginY = 0; + +static const CoordType kVertOriginX = 2; +static const CoordType kVertOriginY = 6; + +static const CoordType kHorizOriginX = 6; +static const CoordType kHorizOriginY = 2; + +static const CoordType kDiagOriginX = 6; +static const CoordType kDiagOriginY = 6; + +static const int g_originsX[] = { + kDiagOriginX, + kDiagOriginX, + kDiagOriginX, + kHorizOriginX, + kDiagOriginX, + kDiagOriginX, + kDiagOriginX, + kVertOriginX +}; + +static const int g_originsY[] = { + kDiagOriginY - 64, + kDiagOriginY - 32, + kDiagOriginY - 32, + kHorizOriginY, + kDiagOriginY, + kDiagOriginY, + kDiagOriginY, + kVertOriginY +}; + +struct HotVerticesList { + int numHotVerts; + VertexType hotVerts[25]; +}; + +CoordType vertToX(VertexType vertex) { + return (vertex % 5) * 32; +} + +CoordType vertToY(VertexType vertex) { + return (vertex / 5) * 32; +} + +// This function returns the number of edges in the bomb edge list. +VertexType getNumEdges(BombEdgeList edges) { + return edges[50]; +} + +// These four functions return pointers into the given edge list. + +// getFirstEdge and getNextEdge can be used to iterate across all edges +// in an edge list. These functions can be used to walk all the edges +// in a bomb edge list for drawing. +VertexType *getFirstEdge(BombEdgeList edges) { + return &edges[51]; +} + +VertexType *getNextEdge(VertexType *anEdge) { + return anEdge + *(anEdge + 1) * 2 + 1; +} + +// getVertices returns a pointer to all of the vertices that should are +// hot. These vertices indicate all the vertices that should be drawn in +// the game. +VertexType *getVertices(BombEdgeList edges) { + return &edges[0]; +} + +// getUsedVertices returns a pointer to the "used" vertices area: the +// area that keeps track of which vertices have been set by the +// setVertexUsed used function. +VertexType *getUsedVertices(BombEdgeList edges) { + return &edges[25]; +} + +// Useful for saving. Saving the state of the bomb game is as simple as writing +// out the edge list. +int getEdgeListSize(BombEdgeList edges) { + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + + while (numEdges--) + anEdge = getNextEdge(anEdge); + + return anEdge - edges + 4; +} + +// Returns true if the given vertex lies on the given edge. +bool vertexOnEdge(VertexType *anEdge, VertexType whichVertex) { + VertexType numVerts = *++anEdge; + + while (numVerts--) + if (*++anEdge == whichVertex) + return true; + + return false; +} + +// Given an edge list and a from vertex, this function constructs a list +// of all vertices that may be clicked on. +// if fromVertex == -1, all vertices are eligible. +// otherwise, only vertices on a line from fromVertex are eligible. +void makeHotVertexList(BombEdgeList edges, VertexType fromVertex, HotVerticesList &hotVertices) { + hotVertices.numHotVerts = 0; + + if (fromVertex == -1) { + for (VertexType i = 0; i < 25; i++) + if (edges[i]) + hotVertices.hotVerts[hotVertices.numHotVerts++] = i; + } else { + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + hotVertices.hotVerts[hotVertices.numHotVerts++] = fromVertex; + + while (numEdges--) { + if (vertexOnEdge(anEdge, fromVertex)) { + VertexType *p = anEdge + 1; + VertexType numVerts = *p; + + while (numVerts--) + if (*++p != fromVertex) + hotVertices.hotVerts[hotVertices.numHotVerts++] = *p; + } + + anEdge = getNextEdge(anEdge); + } + } +} + +// Set all edges in the edge list to the value passed in "edgeVal". +// For drawing purposes, 0 can mean don't draw, and 1 and higher can +// represent different colors. +void setAllEdgesUsed(BombEdgeList edges, VertexType used) { + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + + while (numEdges--) { + VertexType *p1 = anEdge + 1; + VertexType numVerts = *p1; + p1 += numVerts + 1; + + while (--numVerts) + *p1++ = used; + + anEdge = getNextEdge(anEdge); + } + + VertexType *p1 = edges; + VertexType *p2 = getUsedVertices(edges); + + for (VertexType i = 0; i < 25; i++, p1++, p2++) + if (*p1) + *p2 = used; +} + +// Same as setAllEdgesUsed, but only affects edges that are already set +// to a non-zero value. +void setAllUsedEdgesUsed(BombEdgeList edges, VertexType used) { + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + + while (numEdges--) { + VertexType *p = anEdge + 1; + VertexType numVerts = *p; + p += numVerts + 1; + + while (--numVerts) { + if (*p) + *p = used; + ++p; + } + + anEdge = getNextEdge(anEdge); + } + + VertexType *p = getUsedVertices(edges); + for (VertexType i = 0; i < 25; i++, p++) + if (*p) + *p = used; +} + +// Replace all edges with value "value" with the new value "used". +void replaceUsedEdges(BombEdgeList edges, VertexType value, VertexType used) { + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + + while (numEdges--) { + VertexType *p = anEdge + 1; + VertexType numVerts = *p; + p += numVerts + 1; + + while (--numVerts) { + if (*p == value) + *p = used; + + p++; + } + + anEdge = getNextEdge(anEdge); + } + + VertexType *p = getUsedVertices(edges); + for (VertexType i = 0; i < 25; i++, p++) + if (*p == value) + *p = used; +} + +// Set a vertex's value to "used". +void setVertexUsed(BombEdgeList edges, VertexType whichVertex, VertexType value) { + *(getUsedVertices(edges) + whichVertex) = value; +} + +// Mark an edge in the given list between the two vertices as "used". This marks +// all inbetween vertices as well, even if the vertex is not marked as a "hot" +// vertex in the hot vertex section. Returns true if doing this operation +// crosses an already marked edge. +bool setEdgeUsed(BombEdgeList edges, VertexType fromVertex, VertexType toVertex) { + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + bool crossed = false; + + while (numEdges--) { + VertexType *p = anEdge; + VertexType numVerts = *++p; + VertexType *fromPtr = 0; + VertexType *toPtr = 0; + VertexType i = numVerts; + p++; + + while (i--) { + if (*p == fromVertex) + fromPtr = p; + else if (*p == toVertex) + toPtr = p; + + if (fromPtr && toPtr) { + // Found the edge... + if (fromPtr > toPtr) { + p = fromPtr; + fromPtr = toPtr; + toPtr = p; + } + + p = fromPtr + numVerts; + + for (i = toPtr - fromPtr; i > 0; i--, p++) { + ++(*p); + + if (*p == 2) + crossed = true; + } + + VertexType *verts = getVertices(edges); + VertexType *usedVerts = getUsedVertices(edges); + *(usedVerts + *fromPtr) = 1; + + for (p = fromPtr + 1; p != toPtr; p++) + if (*(verts + *p)) + *(usedVerts + *p) = 1; + + *(usedVerts + *toPtr) = 1; + return crossed; + } + + p++; + } + + anEdge = getNextEdge(anEdge); + } + + return false; +} + +// Return true if all edges are used. Can be used to determine when the bomb +// game is over. +bool allEdgesUsed(BombEdgeList edges) { + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + + while (numEdges--) { + VertexType *p = anEdge + 1; + VertexType numVerts = *p; + p += numVerts + 1; + + while (--numVerts) { + if (!*p) + return false; + + ++p; + } + + anEdge = getNextEdge(anEdge); + } + + return true; +} + +BombGrid::BombGrid(const DisplayElementID id) : Picture(id) { + Common::Rect bounds(0, 0, kBombGridWidth, kBombGridHeight); + + allocateSurface(bounds); + setBounds(bounds); + _surface->fillRect(bounds, g_system->getScreenFormat().RGBToColor(0xff, 0xff, 0xff)); + + _transparent = true; + + _yellowDot.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID, true); + _yellowOneSixteenth.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 1, true); + _yellowOneEighth.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 2, true); + _yellowThreeSixteenths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 3, true); + _yellowOneFourth.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 4, true); + _yellowFiveSixteenths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 5, true); + _yellowThreeEighths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 6, true); + _yellowSevenSixteenths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 7, true); + _yellowOneHalf.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 8, true); + + _redDot.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID, true); + _redOneSixteenth.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 1, true); + _redOneEighth.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 2, true); + _redThreeSixteenths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 3, true); + _redOneFourth.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 4, true); + _redFiveSixteenths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 5, true); + _redThreeEighths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 6, true); + _redSevenSixteenths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 7, true); + _redOneHalf.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 8, true); +} + +void BombGrid::drawEdges(BombEdgeList edges) { + GraphicsManager *gfx = ((PegasusEngine *)g_engine)->_gfx; + gfx->setCurSurface(_surface); + + _surface->fillRect(Common::Rect(0, 0, kBombGridWidth, kBombGridHeight), g_system->getScreenFormat().RGBToColor(0xff, 0xff, 0xff)); + + Frame *yellowStuff = &_yellowDot; + Frame *redStuff = &_redDot; + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + VertexType i, *p; + + Common::Rect bounds; + getSurfaceBounds(bounds); + + while (numEdges--) { + p = anEdge; + VertexType edgeDirection = *p++; + VertexType numVerts = *p++; + VertexType numSegs = numVerts - 1; + + for (i = 0; i < numSegs; i++, p++) { + if (*(p + numVerts) > 0 && *(p + numVerts) < 4) { + Frame *drawStuff; + + if (*(p + numVerts) == 2) + drawStuff = redStuff; + else + drawStuff = yellowStuff; + + int x = vertToX(*p) + g_originsX[edgeDirection]; + int y = vertToY(*p) + g_originsY[edgeDirection]; + + Common::Rect r1; + drawStuff[edgeDirection + 1].getSurfaceBounds(r1); + Common::Rect r2 = r1; + r2.moveTo(x, y); + drawStuff[edgeDirection + 1].drawImage(r1, r2); + } + } + + anEdge = getNextEdge(anEdge); + } + + for (i = 0, p = getUsedVertices(edges); i < 25; i++, p++) { + if (*p > 0 && *p < 4) { + Frame *drawStuff; + + if (*p == 2) + drawStuff = redStuff; + else + drawStuff = yellowStuff; + + int x = vertToX(i) + kDotOriginX; + int y = vertToY(i) + kDotOriginY; + + Common::Rect r1; + drawStuff->getSurfaceBounds(r1); + Common::Rect r2 = r1; + r2.moveTo(x, y); + drawStuff->drawImage(r1, r2); + } + } + + triggerRedraw(); + gfx->setCurSurface(gfx->getWorkArea()); +} + +BombTimer::BombTimer(const DisplayElementID id) : IdlerAnimation(id) { + _middle = -1; + _leftImage.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kTimerLeftPICTID); + _rightImage.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kTimerRightPICTID); + + Common::Rect r; + _leftImage.getSurfaceBounds(r); + setBounds(r); +} + +void BombTimer::draw(const Common::Rect &updateRect) { + Common::Rect bounds; + getBounds(bounds); + + Common::Rect r1 = bounds; + r1.right = _middle; + r1 = r1.findIntersectingRect(updateRect); + + if (!r1.isEmpty()) { + Common::Rect r2 = r1; + r2.moveTo(r1.left - bounds.left, r1.top - bounds.top); + _leftImage.copyToCurrentPort(r2, r1); + } + + r1 = bounds; + r1.left = _middle; + r1 = r1.findIntersectingRect(updateRect); + + if (!r1.isEmpty()) { + Common::Rect r2 = r1; + r2.moveTo(r1.left - bounds.left, r1.top - bounds.top); + _rightImage.copyToCurrentPort(r2, r1); + } +} + +void BombTimer::timeChanged(const TimeValue newTime) { + Common::Rect bounds; + getBounds(bounds); + + int newMiddle = bounds.right - bounds.width() * newTime / getDuration(); + if (newMiddle != _middle) { + _middle = newMiddle; + triggerRedraw(); + } +} + +#define CREATE_BOMB_LEVEL(num, data) \ + _bombLevel[num] = new VertexType[sizeof(data)]; \ + memcpy(_bombLevel[num], data, sizeof(data)) + +CaldoriaBomb::CaldoriaBomb(Neighborhood *owner, NotificationManager *manager) : + GameInteraction(kCaldoriaBombInteractionID, owner), _grid(kCaldoriaBombGridID), + _timer(kCaldoriaBombTimerID), _timerNotification(kCaldoriaBombTimerNotificationID, manager) { + CREATE_BOMB_LEVEL(0, kBombLevelOne); + CREATE_BOMB_LEVEL(1, kBombLevelTwo); + CREATE_BOMB_LEVEL(2, kBombLevelThree); + CREATE_BOMB_LEVEL(3, kBombLevelFour); + CREATE_BOMB_LEVEL(4, kBombLevelFive); + CREATE_BOMB_LEVEL(5, kBombLevelSix); + _currentLevel = 0; +} + +#undef CREATE_BOMB_LEVEL + +CaldoriaBomb::~CaldoriaBomb() { + for (int i = 0; i < 6; i++) + delete[] _bombLevel[i]; +} + +void CaldoriaBomb::openInteraction() { + _grid.moveElementTo(kCaldoriaBombGridLeft, kCaldoriaBombGridTop); + _grid.setDisplayOrder(kCaldoriaBombGridOrder); + _grid.startDisplaying(); + + _timer.moveElementTo(kCaldoriaBombTimerLeft, kCaldoriaBombTimerTop); + _timer.setDisplayOrder(kCaldoriaBombTimerOrder); + _timer.startDisplaying(); + _timer.setSegment(0, kTenMinutesPerFifteenTicks, kFifteenTicksPerSecond); + _timer.setTime(0); + + _timerNotification.notifyMe(this, kBombTimerExpiredFlag, kBombTimerExpiredFlag); + _timerCallBack.setNotification(&_timerNotification); + _timerCallBack.initCallBack(&_timer, kCallBackAtExtremes); + _timerCallBack.setCallBackFlag(kBombTimerExpiredFlag); + + Common::Rect r(0, 0, kVertextHotSpotWidth, kVertextHotSpotHeight); + + for (VertexType i = 0; i < 25; i++) { + _vertexHotspot[i] = new Hotspot(i + kVertextHotSpotBaseID); + r.moveTo(vertToX(i) + kCaldoriaBombGridLeft - kVertextHotSpotWidth / 2 + 6, + vertToY(i) + kCaldoriaBombGridTop - kVertextHotSpotHeight / 2 + 6); + _vertexHotspot[i]->setArea(r); + _vertexHotspot[i]->setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + g_allHotspots.push_back(_vertexHotspot[i]); + } + + _neighborhoodNotification = _owner->getNeighborhoodNotification(); + _neighborhoodNotification->notifyMe(this, kExtraCompletedFlag, kExtraCompletedFlag); +} + +void CaldoriaBomb::initInteraction() { + _owner->loadLoopSound1(""); + _owner->startExtraSequence(kCaldoria56BombStage1, kExtraCompletedFlag, kFilterNoInput); +} + +void CaldoriaBomb::closeInteraction() { + _timer.stop(); + _timer.hide(); + _timer.stopDisplaying(); + _grid.hide(); + _grid.stopDisplaying(); + + // The original did not do this, but we need it here + // Not sure why the original worked without this; probably + // related to the way the List code worked in CodeWarrior. + // If this is not here, the notifications will later attempt + // to remove itself from this receiver causing a very nasty + // crash. + _timerNotification.cancelNotification(this); + _neighborhoodNotification->cancelNotification(this); +} + +void CaldoriaBomb::startBombAmbient(Common::String ambient) { + _owner->loadLoopSound1(ambient); +} + +void CaldoriaBomb::receiveNotification(Notification *notification, const NotificationFlags) { + if (notification == _neighborhoodNotification) { + switch (_owner->getLastExtra()) { + case kCaldoria56BombStage1: + _grid.show(); + _timer.show(); + _timerCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _timer.start(); + _currentLevel = 0; + _lastVertex = -1; + startBombAmbient("Sounds/Caldoria/BmbLoop1.22K.AIFF"); + break; + case kCaldoria56BombStage2: + case kCaldoria56BombStage3: + case kCaldoria56BombStage4: + case kCaldoria56BombStage5: + case kCaldoria56BombStage6: + _grid.show(); + _currentLevel++; + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -1; + startBombAmbient(Common::String::format("Sounds/Caldoria/BmbLoop%d.22K.AIFF", _owner->getLastExtra() - kCaldoria56BombStage1 + 1)); + break; + case kCaldoria56BombStage7: + _owner->requestDeleteCurrentInteraction(); + GameState.setCaldoriaBombDisarmed(true); + GameState.setScoringDisarmedNuke(true); + _owner->loadAmbientLoops(); + break; + } + } else if (notification == &_timerNotification) { + _grid.hide(); + _timer.stop(); + _timer.hide(); + _owner->loadLoopSound1(""); + _owner->playDeathExtra(kCaldoria56BombExplodes, kDeathNuclearExplosion); + } +} + +void CaldoriaBomb::activateHotspots() { + GameInteraction::activateHotspots(); + + if (_currentLevel != -1 && _lastVertex >= -1) { + HotVerticesList hotVertices; + makeHotVertexList(_bombLevel[_currentLevel], _lastVertex, hotVertices); + + for (VertexType i = 0; i < hotVertices.numHotVerts; i++) + g_allHotspots.activateOneHotspot(hotVertices.hotVerts[i] + kVertextHotSpotBaseID); + } +} + +void CaldoriaBomb::clickInHotspot(const Input &input, const Hotspot *hotspot) { + int clickedVertex = (int)hotspot->getObjectID() - (int)kVertextHotSpotBaseID; + + if (clickedVertex >= 0 && clickedVertex < 25) { + if (_lastVertex != -1 && setEdgeUsed(_bombLevel[_currentLevel], _lastVertex, clickedVertex)) { + clickedVertex = -2; + _flashTime = tickCount(); + } else if (allEdgesUsed(_bombLevel[_currentLevel])) { + setVertexUsed(_bombLevel[_currentLevel], clickedVertex, 1); + clickedVertex = -20; + _flashTime = tickCount(); + } else { + setVertexUsed(_bombLevel[_currentLevel], clickedVertex, 2); + } + + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = clickedVertex; + } else { + GameInteraction::clickInHotspot(input, hotspot); + } +} + +InputBits CaldoriaBomb::getInputFilter() { + // Disallow arrow buttons. + return GameInteraction::getInputFilter() & kFilterAllButtons; +} + +void CaldoriaBomb::handleInput(const Input &input, const Hotspot *hotspot) { + GameInteraction::handleInput(input, hotspot); + + switch (_lastVertex) { + case -2: // Flash back to yellow. + if (tickCount() > _flashTime + kOnTime1) { + replaceUsedEdges(_bombLevel[_currentLevel], 2, 3); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -3; + } + break; + case -3: // Flash back to red. + if (tickCount() > _flashTime + kOffTime1) { + replaceUsedEdges(_bombLevel[_currentLevel], 3, 2); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -4; + } + break; + case -4: // Flash all to yellow. + if (tickCount() > _flashTime + kOnTime2) { + setAllUsedEdgesUsed(_bombLevel[_currentLevel], 1); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -5; + } + break; + case -5: // Flash all to red. + if (tickCount() > _flashTime + kOffTime2) { + setAllUsedEdgesUsed(_bombLevel[_currentLevel], 2); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -6; + } + break; + case -6: // Flash all to yellow. + if (tickCount() > _flashTime + kOnTime3) { + setAllUsedEdgesUsed(_bombLevel[_currentLevel], 1); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -7; + } + break; + case -7: // Flash all to red. + if (tickCount() > _flashTime + kOffTime3) { + setAllUsedEdgesUsed(_bombLevel[_currentLevel], 2); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -8; + } + break; + case -8: // Restore to normal. + if (tickCount() > _flashTime + kOnTime4) { + setAllEdgesUsed(_bombLevel[_currentLevel], 0); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -1; + } + break; + + // Flash grid after success. + case -20: // Flash off. + if (tickCount() > _flashTime + kOnTime1) { + setAllEdgesUsed(_bombLevel[_currentLevel], 4); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -21; + } + break; + case -21: // Flash on. + if (tickCount() > _flashTime + kOffTime1) { + setAllEdgesUsed(_bombLevel[_currentLevel], 1); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -22; + } + break; + case -22: // Flash off. + if (tickCount() > _flashTime + kOnTime2) { + setAllEdgesUsed(_bombLevel[_currentLevel], 4); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -23; + } + break; + case -23: // Flash on. + if (tickCount() > _flashTime + kOffTime2) { + setAllEdgesUsed(_bombLevel[_currentLevel], 1); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -24; + } + break; + case -24: + if (tickCount() > _flashTime + kOnTime3) { + _grid.hide(); + _lastVertex = -1; + _owner->loadLoopSound1(""); + + switch (_currentLevel) { + case 0: + _owner->startExtraSequence(kCaldoria56BombStage2, kExtraCompletedFlag, kFilterNoInput); + break; + case 1: + _owner->startExtraSequence(kCaldoria56BombStage3, kExtraCompletedFlag, kFilterNoInput); + break; + case 2: + _owner->startExtraSequence(kCaldoria56BombStage4, kExtraCompletedFlag, kFilterNoInput); + break; + case 3: + _owner->startExtraSequence(kCaldoria56BombStage5, kExtraCompletedFlag, kFilterNoInput); + break; + case 4: + _owner->startExtraSequence(kCaldoria56BombStage6, kExtraCompletedFlag, kFilterNoInput); + break; + case 5: + _timer.stop(); + _grid.hide(); + _timer.hide(); + _owner->startExtraSequence(kCaldoria56BombStage7, kExtraCompletedFlag, kFilterNoInput); + break; + } + } + break; + } +} + +long CaldoriaBomb::getNumHints() { + return 2; +} + +Common::String CaldoriaBomb::getHintMovie(uint hintNum) { + return (hintNum == 1) ? "Images/AI/Caldoria/X56EH2" : "Images/AI/Caldoria/X56EH3"; +} + +bool CaldoriaBomb::canSolve() { + return true; +} + +void CaldoriaBomb::doSolve() { + _timer.stop(); + _grid.hide(); + _timer.hide(); + _owner->loadLoopSound1(""); + _owner->startExtraSequence(kCaldoria56BombStage7, kExtraCompletedFlag, kFilterNoInput); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/caldoria/caldoriabomb.h b/engines/pegasus/neighborhood/caldoria/caldoriabomb.h new file mode 100644 index 0000000000..55d0409dec --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoriabomb.h @@ -0,0 +1,156 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIABOMB_H +#define PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIABOMB_H + +#include "pegasus/interaction.h" +#include "pegasus/notification.h" +#include "pegasus/surface.h" + +namespace Pegasus { + +/* + Edge list is arranged as follows: + + all values in the edge list are bytes. + + all vertices are numbers between 0 and 24. x coordinate of vertex is vertex % 5, + and y coordinate is vertex / 5. + + an edge is + a direction code + a number of vertices in the edge + an array of vertices -- all vertices along the edge, whether or not they're + clickable. + an array of bools (bytes) indicating that a portion of the edge is + traversed (and should be drawn). the number of bools is one less than + the number of vertices. + + an edge list is + an array of 25 bools indicating which vertex is clickable. + an array of 25 bools indicating which vertex is used (drawn). + a number of edges + an array of edges. + + a hot vertex list is + a number of vertices + an array of 25 vertices + +*/ + +typedef int8 VertexType; +typedef VertexType *BombEdgeList; + +static const VertexType kEdgeOneSixteenth = 0; +static const VertexType kEdgeOneEighth = 1; +static const VertexType kEdgeThreeSixteenths = 2; +static const VertexType kEdgeOneFourth = 3; +static const VertexType kEdgeFiveSixteenths = 4; +static const VertexType kEdgeThreeEighths = 5; +static const VertexType kEdgeSevenSixteenths = 6; +static const VertexType kEdgeOneHalf = 7; + +class BombTimer : public IdlerAnimation { +public: + BombTimer(const DisplayElementID); + virtual ~BombTimer() {} + + void draw(const Common::Rect &); + +protected: + void timeChanged(const TimeValue); + + int _middle; + Surface _leftImage, _rightImage; +}; + +class BombGrid : public Picture { +public: + BombGrid(const DisplayElementID); + virtual ~BombGrid() {} + + void drawEdges(BombEdgeList); + +protected: + Frame _yellowDot; + Frame _yellowOneSixteenth; + Frame _yellowOneEighth; + Frame _yellowThreeSixteenths; + Frame _yellowOneFourth; + Frame _yellowFiveSixteenths; + Frame _yellowThreeEighths; + Frame _yellowSevenSixteenths; + Frame _yellowOneHalf; + Frame _redDot; + Frame _redOneSixteenth; + Frame _redOneEighth; + Frame _redThreeSixteenths; + Frame _redOneFourth; + Frame _redFiveSixteenths; + Frame _redThreeEighths; + Frame _redSevenSixteenths; + Frame _redOneHalf; +}; + +class Hotspot; + +class CaldoriaBomb : public GameInteraction, public NotificationReceiver { +public: + CaldoriaBomb(Neighborhood *, NotificationManager *); + virtual ~CaldoriaBomb(); + + long getNumHints(); + Common::String getHintMovie(uint); + void doSolve(); + bool canSolve(); + +protected: + void openInteraction(); + void initInteraction(); + void closeInteraction(); + void receiveNotification(Notification *, const NotificationFlags); + void activateHotspots(); + void clickInHotspot(const Input &, const Hotspot *); + void handleInput(const Input &, const Hotspot *); + InputBits getInputFilter(); + void startBombAmbient(Common::String); + + Notification *_neighborhoodNotification; + BombGrid _grid; + BombTimer _timer; + BombEdgeList _bombLevel[6]; + int _currentLevel, _flashTime; + Hotspot *_vertexHotspot[25]; + VertexType _lastVertex; + Notification _timerNotification; + NotificationCallBack _timerCallBack; + + TimeValue _readTime; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/caldoria/caldoriamessages.cpp b/engines/pegasus/neighborhood/caldoria/caldoriamessages.cpp new file mode 100644 index 0000000000..5b9a823f7c --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoriamessages.cpp @@ -0,0 +1,115 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/gamestate.h" +#include "pegasus/neighborhood/neighborhood.h" +#include "pegasus/neighborhood/caldoria/caldoria.h" +#include "pegasus/neighborhood/caldoria/caldoriamessages.h" + +namespace Pegasus { + +static const NotificationFlags kMessageDoneFlag = 1; + +CaldoriaMessages::CaldoriaMessages(Neighborhood *owner, const NotificationID id, NotificationManager *manager) : + GameInteraction(kCaldoriaMessagesInteractionID, owner), Notification(id, manager), _messageMovie(kCaldoriaMessagesID) { +} + +void CaldoriaMessages::openInteraction() { + _neighborhoodNotification = GameInteraction::_owner->getNeighborhoodNotification(); + _neighborhoodNotification->notifyMe(this, kExtraCompletedFlag, kExtraCompletedFlag); + _messageCallBack.setNotification(this); + notifyMe(this, kMessageDoneFlag, kMessageDoneFlag); + _messageCallBack.setCallBackFlag(kMessageDoneFlag); + _messageNumber = 1; +} + +void CaldoriaMessages::initInteraction() { + GameInteraction::_owner->startExtraSequence(kCaBedroomVidPhone, kExtraCompletedFlag, kFilterNoInput); +} + +void CaldoriaMessages::closeInteraction() { + cancelNotification(this); + _neighborhoodNotification->cancelNotification(this); +} + +void CaldoriaMessages::receiveNotification(Notification *notification, const NotificationFlags) { + if (notification == _neighborhoodNotification) { + switch (GameInteraction::_owner->getLastExtra()) { + case kCaBedroomVidPhone: + GameInteraction::_owner->showExtraView(kCaBedroomMessage1); + break; + case kCaBedroomMessage1: + play1Message(1); + break; + case kCaBedroomMessage2: + play1Message(2); + break; + } + } else { + _messageCallBack.releaseCallBack(); + _messageMovie.releaseMovie(); + + uint32 extraID = (_messageNumber == 1) ? kCaBedroomMessage1 : kCaBedroomMessage2; + GameInteraction::_owner->showExtraView(extraID); + allowInput(true); + } +} + +void CaldoriaMessages::clickInHotspot(const Input &input, const Hotspot *spot) { + uint32 extraID; + + switch (spot->getObjectID()) { + case kCaBedroomVidPhoneActivationSpotID: + extraID = (_messageNumber == 1) ? kCaBedroomMessage1 : kCaBedroomMessage2; + GameInteraction::_owner->startExtraSequence(extraID, kExtraCompletedFlag, kFilterNoInput); + break; + default: + GameInteraction::clickInHotspot(input, spot); + break; + } +} + +void CaldoriaMessages::play1Message(uint messageNumber) { + if (messageNumber == 1) { + _messageMovie.initFromMovieFile("Images/Caldoria/A12NVA.movie"); + _messageNumber = 2; + } else { + _messageMovie.initFromMovieFile("Images/Caldoria/A12NVB.movie"); + _messageNumber = 1; + GameState.setCaldoriaSeenMessages(true); + } + + _messageMovie.moveElementTo(kCaldoriaMessageLeft, kCaldoriaMessageTop); + _messageMovie.setDisplayOrder(kCaldoriaMessagesOrder); + _messageMovie.startDisplaying(); + _messageCallBack.initCallBack(&_messageMovie, kCallBackAtExtremes); + _messageCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + allowInput(false); + _messageMovie.show(); + _messageMovie.redrawMovieWorld(); + _messageMovie.start(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/caldoria/caldoriamessages.h b/engines/pegasus/neighborhood/caldoria/caldoriamessages.h new file mode 100644 index 0000000000..04079b52be --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoriamessages.h @@ -0,0 +1,60 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIAMESSAGES_H +#define PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIAMESSAGES_H + +#include "pegasus/input.h" +#include "pegasus/interaction.h" +#include "pegasus/movie.h" +#include "pegasus/notification.h" +#include "pegasus/timers.h" + +namespace Pegasus { + +class Neighborhood; + +class CaldoriaMessages : public GameInteraction, public Notification, public NotificationReceiver { +public: + CaldoriaMessages(Neighborhood *, const NotificationID, NotificationManager *); + virtual ~CaldoriaMessages() {} + +protected: + void openInteraction(); + void initInteraction(); + void closeInteraction(); + void receiveNotification(Notification *, const NotificationFlags); + void clickInHotspot(const Input &, const Hotspot *); + void play1Message(uint); + + Movie _messageMovie; + NotificationCallBack _messageCallBack; + Notification *_neighborhoodNotification; + uint _messageNumber; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/caldoria/caldoriamirror.cpp b/engines/pegasus/neighborhood/caldoria/caldoriamirror.cpp new file mode 100644 index 0000000000..2b70ead442 --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoriamirror.cpp @@ -0,0 +1,134 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/gamestate.h" +#include "pegasus/neighborhood/neighborhood.h" +#include "pegasus/neighborhood/caldoria/caldoria.h" +#include "pegasus/neighborhood/caldoria/caldoriamirror.h" + +namespace Pegasus { + +CaldoriaMirror::CaldoriaMirror(Neighborhood *owner) : GameInteraction(kCaldoriaMirrorInteractionID, owner) { +} + +void CaldoriaMirror::openInteraction() { + _neighborhoodNotification = _owner->getNeighborhoodNotification(); + _neighborhoodNotification->notifyMe(this, kExtraCompletedFlag, kExtraCompletedFlag); +} + +void CaldoriaMirror::initInteraction() { + _owner->setCurrentActivation(kActivateMirrorReady); + _owner->startExtraSequence(kCaBathroomGreeting, kExtraCompletedFlag, kFilterNoInput); +} + +void CaldoriaMirror::closeInteraction() { + _neighborhoodNotification->cancelNotification(this); +} + +void CaldoriaMirror::handleInput(const Input &input, const Hotspot *cursorSpot) { + if (_owner->getLastExtra() == (uint32)kCaBathroomAgencyStandard || !input.anyDirectionInput()) + GameInteraction::handleInput(input, cursorSpot); +} + +void CaldoriaMirror::activateHotspots() { + GameInteraction::activateHotspots(); + + switch (_owner->getLastExtra()) { + case kCaBathroomGreeting: + case kCaBathroomBodyFat: + case kCaBathroomRetrothrash: + case kCaBathroomGeoWave: + g_allHotspots.activateOneHotspot(kCaBathroomMirrorSpotID); + g_allHotspots.deactivateOneHotspot(kCaHairStyle1SpotID); + g_allHotspots.deactivateOneHotspot(kCaHairStyle2SpotID); + g_allHotspots.deactivateOneHotspot(kCaHairStyle3SpotID); + break; + case kCaBathroomStylistIntro: + case kCaBathroomRetrothrashReturn: + case kCaBathroomGeoWaveReturn: + g_allHotspots.activateOneHotspot(kCaHairStyle1SpotID); + g_allHotspots.activateOneHotspot(kCaHairStyle2SpotID); + g_allHotspots.activateOneHotspot(kCaHairStyle3SpotID); + g_allHotspots.deactivateOneHotspot(kCaBathroomMirrorSpotID); + break; + } +} + +void CaldoriaMirror::clickInHotspot(const Input &input, const Hotspot *spot) { + switch (spot->getObjectID()) { + case kCaBathroomMirrorSpotID: + switch (_owner->getLastExtra()) { + case kCaBathroomGreeting: + _owner->startExtraSequence(kCaBathroomBodyFat, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaBathroomBodyFat: + _owner->startExtraSequence(kCaBathroomStylistIntro, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaBathroomRetrothrash: + _owner->startExtraSequence(kCaBathroomRetrothrashReturn, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaBathroomGeoWave: + _owner->startExtraSequence(kCaBathroomGeoWaveReturn, kExtraCompletedFlag, kFilterNoInput); + break; + } + break; + case kCaHairStyle1SpotID: + _owner->startExtraSequence(kCaBathroomRetrothrash, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaHairStyle2SpotID: + _owner->startExtraSequence(kCaBathroomAgencyStandard, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaHairStyle3SpotID: + _owner->startExtraSequence(kCaBathroomGeoWave, kExtraCompletedFlag, kFilterNoInput); + break; + default: + GameInteraction::clickInHotspot(input, spot); + break; + } +} + +void CaldoriaMirror::receiveNotification(Notification *, const NotificationFlags) { + switch (_owner->getLastExtra()) { + case kCaBathroomRetrothrash: + case kCaBathroomGeoWave: + _owner->setCurrentActivation(kActivateMirrorReady); + break; + case kCaBathroomStylistIntro: + case kCaBathroomRetrothrashReturn: + case kCaBathroomGeoWaveReturn: + _owner->setCurrentActivation(kActivateStylistReady); + break; + case kCaBathroomAgencyStandard: + _owner->setCurrentActivation(kActivateHotSpotAlways); + _owner->requestDeleteCurrentInteraction(); + GameState.setScoringFixedHair(true); + GameState.setCaldoriaDoneHygiene(true); + break; + } + + allowInput(true); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/caldoria/caldoriamirror.h b/engines/pegasus/neighborhood/caldoria/caldoriamirror.h new file mode 100644 index 0000000000..1ca47ec774 --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoriamirror.h @@ -0,0 +1,54 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIAMIRROR_H +#define PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIAMIRROR_H + +#include "pegasus/interaction.h" +#include "pegasus/notification.h" + +namespace Pegasus { + +class CaldoriaMirror : public GameInteraction, public NotificationReceiver { +public: + CaldoriaMirror(Neighborhood *); + virtual ~CaldoriaMirror() {} + +protected: + void openInteraction(); + void initInteraction(); + void closeInteraction(); + + void handleInput(const Input &, const Hotspot *); + void activateHotspots(); + void clickInHotspot(const Input &, const Hotspot *); + void receiveNotification(Notification *, const NotificationFlags); + + Notification *_neighborhoodNotification; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/door.cpp b/engines/pegasus/neighborhood/door.cpp new file mode 100644 index 0000000000..f7ec7559fc --- /dev/null +++ b/engines/pegasus/neighborhood/door.cpp @@ -0,0 +1,64 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/door.h" + +namespace Pegasus { + +void DoorTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].room = stream->readUint16BE(); + _entries[i].direction = stream->readByte(); + _entries[i].altCode = stream->readByte(); + _entries[i].movieStart = stream->readUint32BE(); + _entries[i].movieEnd = stream->readUint32BE(); + _entries[i].flags = stream->readByte(); + stream->readByte(); // alignment + debug(0, "Door[%d]: %d %d %d %d %d %d", i, _entries[i].room, _entries[i].direction, + _entries[i].altCode, _entries[i].movieStart, _entries[i].movieEnd, + _entries[i].flags); + } +} + +void DoorTable::clear() { + _entries.clear(); +} + +DoorTable::Entry DoorTable::findEntry(RoomID room, DirectionConstant direction, AlternateID altCode) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].room == room && _entries[i].direction == direction && _entries[i].altCode == altCode) + return _entries[i]; + + return Entry(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/door.h b/engines/pegasus/neighborhood/door.h new file mode 100644 index 0000000000..8ea757559a --- /dev/null +++ b/engines/pegasus/neighborhood/door.h @@ -0,0 +1,90 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_DOOR_H +#define PEGASUS_NEIGHBORHOOD_DOOR_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +typedef byte DoorFlags; + +enum { + kDoorPresentBit, // Bit set if there is a door here. + kDoorLockedBit // Bit set if door is locked, clear if unlocked. +}; + +static const DoorFlags kNoDoorFlags = 0; +static const DoorFlags kDoorPresentMask = 1 << kDoorPresentBit; +static const DoorFlags kDoorLockedMask = 1 << kDoorLockedBit; + +class DoorTable { +public: + DoorTable() {} + ~DoorTable() {} + + static uint32 getResTag() { return MKTAG('D', 'o', 'o', 'r'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry() { clear(); } + bool isEmpty() { return movieStart == 0xffffffff; } + void clear() { + room = kNoRoomID; + direction = kNoDirection; + altCode = kNoAlternateID; + movieStart = 0xffffffff; + movieEnd = 0xffffffff; + flags = kNoDoorFlags; + } + + RoomID room; + DirectionConstant direction; + AlternateID altCode; + TimeValue movieStart; + TimeValue movieEnd; + DoorFlags flags; + }; + + Entry findEntry(RoomID room, DirectionConstant direction, AlternateID altCode); + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif + diff --git a/engines/pegasus/neighborhood/exit.cpp b/engines/pegasus/neighborhood/exit.cpp new file mode 100644 index 0000000000..f0dfff12d3 --- /dev/null +++ b/engines/pegasus/neighborhood/exit.cpp @@ -0,0 +1,70 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/exit.h" + +namespace Pegasus { + +void ExitTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].room = stream->readUint16BE(); + _entries[i].direction = stream->readByte(); + _entries[i].altCode = stream->readByte(); + _entries[i].movieStart = stream->readUint32BE(); + _entries[i].movieEnd = stream->readUint32BE(); + _entries[i].exitEnd = stream->readUint32BE(); + _entries[i].exitLoop = stream->readUint32BE(); + _entries[i].exitRoom = stream->readUint16BE(); + _entries[i].exitDirection = stream->readByte(); + stream->readByte(); // alignment + + _entries[i].originalEnd = _entries[i].exitEnd; + + debug(0, "Exit[%d]: %d %d %d %d %d %d %d %d %d", i, _entries[i].room, _entries[i].direction, + _entries[i].altCode, _entries[i].movieStart, _entries[i].movieEnd, _entries[i].exitEnd, + _entries[i].exitLoop, _entries[i].exitRoom, _entries[i].exitDirection); + } +} + +void ExitTable::clear() { + _entries.clear(); +} + +ExitTable::Entry ExitTable::findEntry(RoomID room, DirectionConstant direction, AlternateID altCode) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].room == room && _entries[i].direction == direction && _entries[i].altCode == altCode) + return _entries[i]; + + return Entry(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/exit.h b/engines/pegasus/neighborhood/exit.h new file mode 100644 index 0000000000..17150892f9 --- /dev/null +++ b/engines/pegasus/neighborhood/exit.h @@ -0,0 +1,93 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_EXIT_H +#define PEGASUS_NEIGHBORHOOD_EXIT_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +class ExitTable { +public: + ExitTable() {} + ~ExitTable() {} + + static uint32 getResTag() { return MKTAG('E', 'x', 'i', 't'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry() { clear(); } + bool isEmpty() { return movieStart == 0xffffffff; } + void clear() { + room = kNoRoomID; + direction = kNoDirection; + altCode = kNoAlternateID; + movieStart = 0xffffffff; + movieEnd = 0xffffffff; + exitEnd = 0xffffffff; + originalEnd = 0xffffffff; + exitLoop = 0xffffffff; + exitRoom = kNoRoomID; + exitDirection = kNoDirection; + } + + RoomID room; + DirectionConstant direction; + AlternateID altCode; + TimeValue movieStart; + TimeValue movieEnd; + // exitEnd is the end of the optimized run of walks. + TimeValue exitEnd; + TimeValue originalEnd; + // exitLoop is the loop start time of the optimized run of walks if the run + // loops back on itself (so far, only in TSA). + TimeValue exitLoop; + RoomID exitRoom; + DirectionConstant exitDirection; + }; + + Entry findEntry(RoomID room, DirectionConstant direction, AlternateID altCode); + + typedef Common::Array<Entry>::iterator iterator; + iterator begin() { return _entries.begin(); } + iterator end() { return _entries.end(); } + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/extra.cpp b/engines/pegasus/neighborhood/extra.cpp new file mode 100644 index 0000000000..b8c4e5b510 --- /dev/null +++ b/engines/pegasus/neighborhood/extra.cpp @@ -0,0 +1,58 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/extra.h" + +namespace Pegasus { + +void ExtraTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].extra = stream->readUint32BE(); + _entries[i].movieStart = stream->readUint32BE(); + _entries[i].movieEnd = stream->readUint32BE(); + debug(0, "Extra[%d]: %d %d %d", i, _entries[i].extra, _entries[i].movieStart, _entries[i].movieEnd); + } +} + +void ExtraTable::clear() { + _entries.clear(); +} + +ExtraTable::Entry ExtraTable::findEntry(ExtraID extra) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].extra == extra) + return _entries[i]; + + return Entry(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/extra.h b/engines/pegasus/neighborhood/extra.h new file mode 100644 index 0000000000..14fcff1009 --- /dev/null +++ b/engines/pegasus/neighborhood/extra.h @@ -0,0 +1,67 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_EXTRA_H +#define PEGASUS_NEIGHBORHOOD_EXTRA_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +class ExtraTable { +public: + ExtraTable() {} + ~ExtraTable() {} + + static uint32 getResTag() { return MKTAG('X', 't', 'r', 'a'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry() { movieStart = 0xffffffff; } + bool isEmpty() { return movieStart == 0xffffffff; } + + ExtraID extra; + TimeValue movieStart; + TimeValue movieEnd; + }; + + Entry findEntry(ExtraID extra); + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/hotspotinfo.cpp b/engines/pegasus/neighborhood/hotspotinfo.cpp new file mode 100644 index 0000000000..c7524f3a0f --- /dev/null +++ b/engines/pegasus/neighborhood/hotspotinfo.cpp @@ -0,0 +1,65 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/hotspotinfo.h" + +namespace Pegasus { + +void HotspotInfoTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].hotspot = stream->readUint16BE(); + _entries[i].hotspotActivation = stream->readSByte(); + stream->readByte(); // alignment + _entries[i].hotspotRoom = stream->readUint16BE(); + _entries[i].hotspotDirection = stream->readByte(); + stream->readByte(); // alignment + _entries[i].hotspotExtra = stream->readUint32BE(); + _entries[i].hotspotItem = stream->readUint16BE(); + debug(0, "Hotspot[%d]: %d %d %d %d %d %d", i, _entries[i].hotspot, _entries[i].hotspotActivation, + _entries[i].hotspotRoom, _entries[i].hotspotDirection, _entries[i].hotspotExtra, + _entries[i].hotspotItem); + } +} + +void HotspotInfoTable::clear() { + _entries.clear(); +} + +HotspotInfoTable::Entry HotspotInfoTable::findEntry(HotSpotID hotspot) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].hotspot == hotspot) + return _entries[i]; + + return Entry(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/hotspotinfo.h b/engines/pegasus/neighborhood/hotspotinfo.h new file mode 100644 index 0000000000..965f445ba8 --- /dev/null +++ b/engines/pegasus/neighborhood/hotspotinfo.h @@ -0,0 +1,77 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_HOTSPOTINFO_H +#define PEGASUS_NEIGHBORHOOD_HOTSPOTINFO_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +class HotspotInfoTable { +public: + HotspotInfoTable() {} + ~HotspotInfoTable() {} + + static uint32 getResTag() { return MKTAG('H', 'S', 'I', 'n'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry() { hotspotRoom = kNoRoomID; } + bool isEmpty() { return hotspotRoom == kNoRoomID; } + + HotSpotID hotspot; + HotSpotActivationID hotspotActivation; + // Location hot spot lives in: + RoomID hotspotRoom; + DirectionConstant hotspotDirection; + // Extra to play if this is a "play extra" hot spot. + ExtraID hotspotExtra; + // Item corresponding to this hot spot if it is an item-related hot spot. + ItemID hotspotItem; + }; + + Entry findEntry(HotSpotID hotspot); + + typedef Common::Array<Entry>::iterator iterator; + iterator begin() { return _entries.begin(); } + iterator end() { return _entries.end(); } + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/constants.h b/engines/pegasus/neighborhood/mars/constants.h new file mode 100644 index 0000000000..82a7f03b68 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/constants.h @@ -0,0 +1,941 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_CONSTANTS_H +#define PEGASUS_NEIGHBORHOOD_MARS_CONSTANTS_H + +#include "pegasus/constants.h" + +namespace Pegasus { + +// Element Coordinates + +static const CoordType kUndoHiliteLeft = kNavAreaLeft + 140; +static const CoordType kUndoHiliteTop = kNavAreaTop + 36; + +static const CoordType kCurrentGuessLeft = kNavAreaLeft + 146; +static const CoordType kCurrentGuessTop = kNavAreaTop + 90; + +static const CoordType kReactorChoiceHiliteLeft = kNavAreaLeft + 116; +static const CoordType kReactorChoiceHiliteTop = kNavAreaTop + 158; + +static const CoordType kReactorHistoryLeft = kNavAreaLeft + 302; +static const CoordType kReactorHistoryTop = kNavAreaTop + 39; + +static const CoordType kAnswerLeft = kNavAreaLeft + 304; +static const CoordType kAnswerTop = kNavAreaTop + 180; + +static const CoordType kShuttle1Left = 0; +static const CoordType kShuttle1Top = 0; + +static const CoordType kShuttle2Left = 0; +static const CoordType kShuttle2Top = 96; + +static const CoordType kShuttle3Left = 500; +static const CoordType kShuttle3Top = 96; + +static const CoordType kShuttle4Left = 0; +static const CoordType kShuttle4Top = 320; + +static const CoordType kShuttleWindowLeft = 140; +static const CoordType kShuttleWindowTop = 96; +static const CoordType kShuttleWindowWidth = 360; +static const CoordType kShuttleWindowHeight = 224; + +static const CoordType kShuttleWindowMidH = (kShuttleWindowLeft * 2 + kShuttleWindowWidth) / 2; +static const CoordType kShuttleWindowMidV = (kShuttleWindowTop * 2 + kShuttleWindowHeight) / 2; + +static const CoordType kShuttleLeftLeft = 0; +static const CoordType kShuttleLeftTop = 128; + +static const CoordType kShuttleRightLeft = 506; +static const CoordType kShuttleRightTop = 128; + +static const CoordType kShuttleLowerLeftLeft = 74; +static const CoordType kShuttleLowerLeftTop = 358; + +static const CoordType kShuttleLowerRightLeft = 486; +static const CoordType kShuttleLowerRightTop = 354; + +static const CoordType kShuttleCenterLeft = 260; +static const CoordType kShuttleCenterTop = 336; + +static const CoordType kShuttleUpperLeftLeft = 30; +static const CoordType kShuttleUpperLeftTop = 32; + +static const CoordType kShuttleUpperRightLeft = 506; +static const CoordType kShuttleUpperRightTop = 52; + +static const CoordType kShuttleLeftEnergyLeft = 110; +static const CoordType kShuttleLeftEnergyTop = 186; + +static const CoordType kShuttleRightEnergyLeft = 510; +static const CoordType kShuttleRightEnergyTop = 186; + +static const CoordType kShuttleEnergyLeft = 186; +static const CoordType kShuttleEnergyTop = 60; +static const CoordType kShuttleEnergyWidth = 252; +static const CoordType kShuttleEnergyHeight = 22; + +static const CoordType kPlanetStartLeft = kShuttleWindowLeft; +static const CoordType kPlanetStartTop = kShuttleWindowTop + kShuttleWindowHeight; + +static const CoordType kPlanetStopLeft = kShuttleWindowLeft; +static const CoordType kPlanetStopTop = kShuttleWindowTop + kShuttleWindowHeight - 100; + +static const CoordType kShuttleTractorLeft = kShuttleWindowLeft + 6; +static const CoordType kShuttleTractorTop = kShuttleWindowTop + 56; +static const CoordType kShuttleTractorWidth = 348; +static const CoordType kShuttleTractorHeight = 112; + +static const CoordType kShuttleJunkLeft = kShuttleWindowLeft + 6; +static const CoordType kShuttleJunkTop = kShuttleWindowTop + 6; + +static const DisplayOrder kShuttlePlanetOrder = kInterfaceLayer; +static const DisplayOrder kShuttleAlienShipOrder = kShuttlePlanetOrder + 1; +static const DisplayOrder kShuttleRobotShipOrder = kShuttleAlienShipOrder + 1; +static const DisplayOrder kShuttleTractorBeamMovieOrder = kShuttleRobotShipOrder + 1; +static const DisplayOrder kShuttleWeaponBackOrder = kShuttleTractorBeamMovieOrder + 1; +static const DisplayOrder kShuttleJunkOrder = kShuttleWeaponBackOrder + 1; +static const DisplayOrder kShuttleWeaponFrontOrder = kShuttleJunkOrder + 1; +static const DisplayOrder kShuttleTractorBeamOrder = kShuttleWeaponFrontOrder + 1; +static const DisplayOrder kShuttleHUDOrder = kShuttleTractorBeamOrder + 1; +static const DisplayOrder kShuttleBackgroundOrder = kShuttleHUDOrder + 1; +static const DisplayOrder kShuttleMonitorOrder = kShuttleBackgroundOrder + 1; +static const DisplayOrder kShuttleStatusOrder = kShuttleMonitorOrder + 1; + +static const TimeValue kShuttleSwingStart = 0; +static const TimeValue kShuttleSwingStop = 5 * 600; + +static const TimeValue kCanyonChaseStart = kShuttleSwingStop; +static const TimeValue kCanyonChaseStop = 60 * 600 + 43 * 600 + 14 * 40; + +static const TimeValue kLaunchTubeReachedTime = 60 * 600 + 38 * 600 - kCanyonChaseStart; +static const TimeValue kCanyonChaseFinishedTime = kCanyonChaseStop - kCanyonChaseStart - + kLaunchTubeReachedTime; + +// Left shuttle. + +static const TimeValue kShuttleLeftIntroStart = 0; +static const TimeValue kShuttleLeftIntroStop = 400; + +static const TimeValue kShuttleLeftBlankTime = 400; + +static const TimeValue kShuttleLeftNormalTime = 440; + +static const TimeValue kShuttleLeftAutoTestTime = 480; + +static const TimeValue kShuttleLeftDamagedTime = 520; + +static const TimeValue kShuttleLeftDampingTime = 560; + +static const TimeValue kShuttleLeftGravitonTime = 600; + +static const TimeValue kShuttleLeftTractorTime = 640; + +// Right shuttle. + +static const TimeValue kShuttleRightIntroStart = 0; +static const TimeValue kShuttleRightIntroStop = 400; + +static const TimeValue kShuttleRightDestroyedStart = 400; +static const TimeValue kShuttleRightDestroyedStop = 840; + +static const TimeValue kShuttleRightBlankTime = 840; + +static const TimeValue kShuttleRightNormalTime = 880; + +static const TimeValue kShuttleRightDamagedTime = 920; + +static const TimeValue kShuttleRightTargetLockTime = 960; + +static const TimeValue kShuttleRightGravitonTime = 1000; + +static const TimeValue kShuttleRightOverloadTime = 1040; + +// Lower Left shuttle. + +static const TimeValue kShuttleLowerLeftCollisionTime = 0; + +static const TimeValue kShuttleLowerLeftTubeTime = 40; + +static const TimeValue kShuttleLowerLeftAutopilotTime = 80; + +// Lower Right shuttle. + +static const TimeValue kShuttleLowerRightOffTime = 0; + +static const TimeValue kShuttleLowerRightTrackingTime = 40; + +static const TimeValue kShuttleLowerRightTransportTime = 80; + +static const TimeValue kShuttleLowerRightTransportHiliteTime = 120; + +// Center shuttle. + +static const TimeValue kShuttleCenterBoardingTime = 0; + +static const TimeValue kShuttleCenterCheckTime = 40; + +static const TimeValue kShuttleCenterNavCompTime = 80; + +static const TimeValue kShuttleCenterCommTime = 120; + +static const TimeValue kShuttleCenterWeaponsTime = 160; + +static const TimeValue kShuttleCenterAllSystemsTime = 200; + +static const TimeValue kShuttleCenterSecureLooseTime = 240; + +static const TimeValue kShuttleCenterAutoTestTime = 280; + +static const TimeValue kShuttleCenterLaunchTime = 320; + +static const TimeValue kShuttleCenterEnterTubeTime = 360; + +static const TimeValue kShuttleCenterTargetSightedTime = 400; + +static const TimeValue kShuttleCenterVerifyingTime = 440; + +static const TimeValue kShuttleCenterScanningTime = 480; + +static const TimeValue kShuttleCenterSafeTime = 520; + +// Upper Left shuttle. + +static const TimeValue kShuttleUpperLeftDimTime = 0; + +static const TimeValue kShuttleUpperLeftDampingTime = 40; + +static const TimeValue kShuttleUpperLeftGravitonTime = 80; + +static const TimeValue kShuttleUpperLeftTractorTime = 120; + +// Upper Right shuttle. + +static const TimeValue kShuttleUpperRightLockedTime = 0; + +static const TimeValue kShuttleUpperRightArmedTime = 40; + +static const TimeValue kShuttleUpperRightAlienDestroyedTime = 80; + +static const TimeValue kShuttleUpperRightOverloadTime = 120; + +static const TimeValue kShuttleUpperRightTargetDestroyedTime = 160; + +// Shuttle distance + +static const int kShuttleDistance = 500; + +static const int kJunkMaxDistance = kShuttleDistance; +static const int kJunkMinDistance = 40; + +static const int kEnergyBeamMaxDistance = kShuttleDistance; +static const int kEnergyBeamMinDistance = 40; + +static const int kGravitonMaxDistance = kShuttleDistance; +static const int kGravitonMinDistance = 40; + +static const TimeValue kMarsOxyMaskOnIn = 0; +static const TimeValue kMarsOxyMaskOnOut = 1560; + +static const TimeValue kMarsAirlockButtonBeepIn = 1560; +static const TimeValue kMarsAirlockButtonBeepOut = 1620; + +static const TimeValue kMarsColorMatchingButtonBeepIn = 1620; +static const TimeValue kMarsColorMatchingButtonBeepOut = 1680; + +static const TimeValue kMarsKioskBeepIn = 1680; +static const TimeValue kMarsKioskBeepOut = 1740; + +static const TimeValue kMarsBumpIntoWallIn = 1740; +static const TimeValue kMarsBumpIntoWallOut = 1888; + +static const TimeValue kMarsGantryDoorCloseIn = 1888; +static const TimeValue kMarsGantryDoorCloseOut = 2866; + +static const TimeValue kMarsTransportDoorCloseIn = 2866; +static const TimeValue kMarsTransportDoorCloseOut = 3593; + +static const TimeValue kMarsAirlockPressurizeIn = 3593; +static const TimeValue kMarsAirlockPressurizeOut = 4766; + +static const TimeValue kMarsBigAirlockDoorCloseIn = 4766; +static const TimeValue kMarsBigAirlockDoorCloseOut = 7872; + +static const TimeValue kMarsSmallAirlockDoorCloseIn = 7872; +static const TimeValue kMarsSmallAirlockDoorCloseOut = 10000; + +static const TimeValue kMarsMazeDoorCloseIn = 10000; +static const TimeValue kMarsMazeDoorCloseOut = 10969; + +static const TimeValue kMarsRobotTakesTransportIn = 10969; +static const TimeValue kMarsRobotTakesTransportOut = 12802; + +static const TimeValue kMarsPodDepartedUpperPlatformIn = 12802; +static const TimeValue kMarsPodDepartedUpperPlatformOut = 15783; + +static const TimeValue kMarsPodDepartedLowerPlatformIn = 15783; +static const TimeValue kMarsPodDepartedLowerPlatformOut = 18736; + +static const TimeValue kMarsPodArrivedUpperPlatformIn = 18736; +static const TimeValue kMarsPodArrivedUpperPlatformOut = 21605; + +static const TimeValue kMarsCheckInRequiredIn = 21605; +static const TimeValue kMarsCheckInRequiredOut = 27463; + +static const TimeValue kMarsCantOpenShuttleIn = 27463; +static const TimeValue kMarsCantOpenShuttleOut = 29214; + +static const TimeValue kMarsShuttleLockOverrideIn = 29214; +static const TimeValue kMarsShuttleLockOverrideOut = 30330; + +static const TimeValue kMarsNoShuttleIn = 30330; +static const TimeValue kMarsNoShuttleOut = 31502; + +static const TimeValue kMustBeUnlockedIn = 31502; +static const TimeValue kMustBeUnlockedOut = 33960; + +static const TimeValue kColorMatchBlueIn = 33960; +static const TimeValue kColorMatchBlueOut = 34240; + +static const TimeValue kColorMatchRedIn = 34240; +static const TimeValue kColorMatchRedOut = 34538; + +static const TimeValue kColorMatchGreenIn = 34538; +static const TimeValue kColorMatchGreenOut = 34827; + +static const TimeValue kColorMatchYellowIn = 34827; +static const TimeValue kColorMatchYellowOut = 35162; + +static const TimeValue kColorMatchPurpleIn = 35162; +static const TimeValue kColorMatchPurpleOut = 35426; + +static const TimeValue kColorMatchZeroNodesIn = 35426; +static const TimeValue kColorMatchZeroNodesOut = 36376; + +static const TimeValue kColorMatchOneNodeIn = 36376; +static const TimeValue kColorMatchOneNodeOut = 37209; + +static const TimeValue kColorMatchTwoNodesIn = 37209; +static const TimeValue kColorMatchTwoNodesOut = 37983; + +static const TimeValue kColorMatchThreeNodesIn = 37983; +static const TimeValue kColorMatchThreeNodesOut = 38784; + +static const TimeValue kMarsShuttle1DepartedIn = 38784; +static const TimeValue kMarsShuttle1DepartedOut = 40323; + +static const TimeValue kMarsShuttle2DepartedIn = 40323; +static const TimeValue kMarsShuttle2DepartedOut = 41824; + +static const TimeValue kShuttleCockpitIn = 41824; +static const TimeValue kShuttleCockpitOut = 43126; + +static const TimeValue kShuttleOnboardIn = 43126; +static const TimeValue kShuttleOnboardOut = 44284; + +static const TimeValue kShuttleNavigationIn = 44284; +static const TimeValue kShuttleNavigationOut = 46049; + +static const TimeValue kShuttleCommunicationIn = 46049; +static const TimeValue kShuttleCommunicationOut = 47288; + +static const TimeValue kShuttleAutoTestingIn = 47288; +static const TimeValue kShuttleAutoTestingOut = 48179; + +static const TimeValue kMarsThrusterAutoTestIn = 48179; +static const TimeValue kMarsThrusterAutoTestOut = 49979; + +static const TimeValue kShuttleAllSystemsIn = 49979; +static const TimeValue kShuttleAllSystemsOut = 51065; + +static const TimeValue kShuttleSecureLooseIn = 51065; +static const TimeValue kShuttleSecureLooseOut = 52346; + +static const TimeValue kShuttlePrepareForDropIn = 52346; +static const TimeValue kShuttlePrepareForDropOut = 53216; + +static const TimeValue kShuttleAllClearIn = 53216; +static const TimeValue kShuttleAllClearOut = 54031; + +static const TimeValue kShuttleConfiguringIn = 54031; +static const TimeValue kShuttleConfiguringOut = 54994; + +static const TimeValue kShuttleGeneratingIn = 54994; +static const TimeValue kShuttleGeneratingOut = 56033; + +static const TimeValue kShuttleBreakawayIn = 56033; +static const TimeValue kShuttleBreakawayOut = 57346; + +static const TimeValue kMarsAtmosphericBreakawayIn = 57346; +static const TimeValue kMarsAtmosphericBreakawayOut = 59237; + +static const TimeValue kMarsCockpitChatterIn = 59237; +static const TimeValue kMarsCockpitChatterOut = 70344; + +static const TimeValue kShuttleDamperDescIn = 70344; +static const TimeValue kShuttleDamperDescOut = 73262; + +static const TimeValue kShuttleGravitonDescIn = 73262; +static const TimeValue kShuttleGravitonDescOut = 75296; + +static const TimeValue kShuttleTractorDescIn = 75296; +static const TimeValue kShuttleTractorDescOut = 78381; + +static const TimeValue kShuttleTargetSightedIn = 78381; +static const TimeValue kShuttleTargetSightedOut = 79074; + +static const TimeValue kShuttleAutopilotEngagedIn = 79074; +static const TimeValue kShuttleAutopilotEngagedOut = 80414; + +static const TimeValue kMarsEDBBlastIn = 80414; +static const TimeValue kMarsEDBBlastOut = 80705; + +static const TimeValue kMarsGravitonBlastIn = 80705; +static const TimeValue kMarsGravitonBlastOut = 81199; + +static const TimeValue kMarsJunkCollisionIn = 81199; +static const TimeValue kMarsJunkCollisionOut = 81961; + +static const TimeValue kShuttleGravitonIn = 81961; +static const TimeValue kShuttleGravitonOut = 82587; + +static const TimeValue kShuttleDampingBeamIn = 82587; +static const TimeValue kShuttleDampingBeamOut = 83331; + +static const TimeValue kShuttleTractorBeamIn = 83331; +static const TimeValue kShuttleTractorBeamOut = 83802; + +static const TimeValue kShuttleHullBreachIn = 83802; +static const TimeValue kShuttleHullBreachOut = 84721; + +static const TimeValue kShuttleWingDamageIn = 84721; +static const TimeValue kShuttleWingDamageOut = 85640; + +static const TimeValue kShuttleHullDamageIn = 85640; +static const TimeValue kShuttleHullDamageOut = 86513; + +static const TimeValue kShuttleEnergyTooLowIn = 86513; +static const TimeValue kShuttleEnergyTooLowOut = 87578; + +static const TimeValue kShuttleTractorLimitedIn = 87578; +static const TimeValue kShuttleTractorLimitedOut = 89164; + +static const TimeValue kShuttleCantHoldIn = 89164; +static const TimeValue kShuttleCantHoldOut = 90945; + +static const TimeValue kShuttleBrokeFreeIn = 90945; +static const TimeValue kShuttleBrokeFreeOut = 92322; + +static const TimeValue kShuttleDestroyedIn = 92322; +static const TimeValue kShuttleDestroyedOut = 93189; + +static const TimeValue kShuttleCoordinatesIn = 93189; +static const TimeValue kShuttleCoordinatesOut = 94018; + +static const TimeValue kShuttleScanningIn = 94018; +static const TimeValue kShuttleScanningOut = 94975; + +static const TimeValue kShuttleSafeIn = 94975; +static const TimeValue kShuttleSafeOut = 96176; + +static const TimeValue kShuttleOverloadedIn = 96176; +static const TimeValue kShuttleOverloadedOut = 101308; + +static const TimeScale kMarsMovieScale = 600; +static const TimeScale kMarsFramesPerSecond = 15; +static const TimeScale kMarsFrameDuration = 40; + +// Alternate IDs. + +static const AlternateID kAltMarsNormal = 0; +static const AlternateID kAltMarsPodAtMars34 = 1; +static const AlternateID kAltMarsTookCard = 2; +static const AlternateID kAltMars35AirlockEast = 3; +static const AlternateID kAltMars35AirlockWest = 4; +static const AlternateID kAltMarsPodAtMars45 = 5; +static const AlternateID kAltMarsTookMask = 6; +static const AlternateID kAltMarsMaskOnFiller = 7; +static const AlternateID kAltMars60AirlockEast = 8; +static const AlternateID kAltMars60AirlockWest = 9; + +// Room IDs. + +static const RoomID kMars0A = 0; +static const RoomID kMars00 = 1; +static const RoomID kMars01 = 2; +static const RoomID kMars02 = 3; +static const RoomID kMars03 = 4; +static const RoomID kMars04 = 5; +static const RoomID kMars05 = 6; +static const RoomID kMars06 = 7; +static const RoomID kMars07 = 8; +static const RoomID kMars08 = 9; +static const RoomID kMars09 = 10; +static const RoomID kMars10 = 11; +static const RoomID kMars11 = 12; +static const RoomID kMars12 = 13; +static const RoomID kMars13 = 14; +static const RoomID kMars14 = 15; +static const RoomID kMars15 = 16; +static const RoomID kMars16 = 17; +static const RoomID kMars17 = 18; +static const RoomID kMars18 = 19; +static const RoomID kMars19 = 20; +static const RoomID kMars20 = 21; +static const RoomID kMars21 = 22; +static const RoomID kMars22 = 23; +static const RoomID kMars23 = 24; +static const RoomID kMars24 = 25; +static const RoomID kMars25 = 26; +static const RoomID kMars26 = 27; +static const RoomID kMars27 = 28; +static const RoomID kMars28 = 29; +static const RoomID kMars29 = 30; +static const RoomID kMars30 = 31; +static const RoomID kMars31 = 32; +static const RoomID kMars31South = 33; +static const RoomID kMars32 = 34; +static const RoomID kMars33 = 35; +static const RoomID kMars33North = 36; +static const RoomID kMars34 = 37; +static const RoomID kMars35 = 38; +static const RoomID kMars36 = 39; +static const RoomID kMars37 = 40; +static const RoomID kMars38 = 41; +static const RoomID kMars39 = 42; +static const RoomID kMars41 = 43; +static const RoomID kMars42 = 44; +static const RoomID kMars43 = 45; +static const RoomID kMars44 = 46; +static const RoomID kMars45 = 47; +static const RoomID kMars46 = 48; +static const RoomID kMars47 = 49; +static const RoomID kMars48 = 50; +static const RoomID kMars49 = 51; +static const RoomID kMars50 = 52; +static const RoomID kMars51 = 53; +static const RoomID kMars52 = 54; +static const RoomID kMars54 = 55; +static const RoomID kMars56 = 56; +static const RoomID kMars58 = 57; +static const RoomID kMars60 = 58; +static const RoomID kMarsRobotShuttle = 59; +static const RoomID kMarsMaze004 = 60; +static const RoomID kMarsMaze005 = 61; +static const RoomID kMarsMaze006 = 62; +static const RoomID kMarsMaze007 = 63; +static const RoomID kMarsMaze008 = 64; +static const RoomID kMarsMaze009 = 65; +static const RoomID kMarsMaze010 = 66; +static const RoomID kMarsMaze011 = 67; +static const RoomID kMarsMaze012 = 68; +static const RoomID kMarsMaze015 = 69; +static const RoomID kMarsMaze016 = 70; +static const RoomID kMarsMaze017 = 71; +static const RoomID kMarsMaze018 = 72; +static const RoomID kMarsMaze019 = 73; +static const RoomID kMarsMaze020 = 74; +static const RoomID kMarsMaze021 = 75; +static const RoomID kMarsMaze022 = 76; +static const RoomID kMarsMaze023 = 77; +static const RoomID kMarsMaze024 = 78; +static const RoomID kMarsMaze025 = 79; +static const RoomID kMarsMaze026 = 80; +static const RoomID kMarsMaze027 = 81; +static const RoomID kMarsMaze028 = 82; +static const RoomID kMarsMaze031 = 83; +static const RoomID kMarsMaze032 = 84; +static const RoomID kMarsMaze033 = 85; +static const RoomID kMarsMaze034 = 86; +static const RoomID kMarsMaze035 = 87; +static const RoomID kMarsMaze036 = 88; +static const RoomID kMarsMaze037 = 89; +static const RoomID kMarsMaze038 = 90; +static const RoomID kMarsMaze039 = 91; +static const RoomID kMarsMaze042 = 92; +static const RoomID kMarsMaze043 = 93; +static const RoomID kMarsMaze044 = 94; +static const RoomID kMarsMaze045 = 95; +static const RoomID kMarsMaze046 = 96; +static const RoomID kMarsMaze047 = 97; +static const RoomID kMarsMaze049 = 98; +static const RoomID kMarsMaze050 = 99; +static const RoomID kMarsMaze051 = 100; +static const RoomID kMarsMaze052 = 101; +static const RoomID kMarsMaze053 = 102; +static const RoomID kMarsMaze054 = 103; +static const RoomID kMarsMaze055 = 104; +static const RoomID kMarsMaze056 = 105; +static const RoomID kMarsMaze057 = 106; +static const RoomID kMarsMaze058 = 107; +static const RoomID kMarsMaze059 = 108; +static const RoomID kMarsMaze060 = 109; +static const RoomID kMarsMaze061 = 110; +static const RoomID kMarsMaze063 = 111; +static const RoomID kMarsMaze064 = 112; +static const RoomID kMarsMaze065 = 113; +static const RoomID kMarsMaze066 = 114; +static const RoomID kMarsMaze067 = 115; +static const RoomID kMarsMaze068 = 116; +static const RoomID kMarsMaze069 = 117; +static const RoomID kMarsMaze070 = 118; +static const RoomID kMarsMaze071 = 119; +static const RoomID kMarsMaze072 = 120; +static const RoomID kMarsMaze074 = 121; +static const RoomID kMarsMaze076 = 122; +static const RoomID kMarsMaze078 = 123; +static const RoomID kMarsMaze079 = 124; +static const RoomID kMarsMaze081 = 125; +static const RoomID kMarsMaze083 = 126; +static const RoomID kMarsMaze084 = 127; +static const RoomID kMarsMaze085 = 128; +static const RoomID kMarsMaze086 = 129; +static const RoomID kMarsMaze087 = 130; +static const RoomID kMarsMaze088 = 131; +static const RoomID kMarsMaze089 = 132; +static const RoomID kMarsMaze090 = 133; +static const RoomID kMarsMaze091 = 134; +static const RoomID kMarsMaze092 = 135; +static const RoomID kMarsMaze093 = 136; +static const RoomID kMarsMaze098 = 137; +static const RoomID kMarsMaze099 = 138; +static const RoomID kMarsMaze100 = 139; +static const RoomID kMarsMaze101 = 140; +static const RoomID kMarsMaze104 = 141; +static const RoomID kMarsMaze105 = 142; +static const RoomID kMarsMaze106 = 143; +static const RoomID kMarsMaze107 = 144; +static const RoomID kMarsMaze108 = 145; +static const RoomID kMarsMaze111 = 146; +static const RoomID kMarsMaze113 = 147; +static const RoomID kMarsMaze114 = 148; +static const RoomID kMarsMaze115 = 149; +static const RoomID kMarsMaze116 = 150; +static const RoomID kMarsMaze117 = 151; +static const RoomID kMarsMaze118 = 152; +static const RoomID kMarsMaze119 = 153; +static const RoomID kMarsMaze120 = 154; +static const RoomID kMarsMaze121 = 155; +static const RoomID kMarsMaze122 = 156; +static const RoomID kMarsMaze123 = 157; +static const RoomID kMarsMaze124 = 158; +static const RoomID kMarsMaze125 = 159; +static const RoomID kMarsMaze126 = 160; +static const RoomID kMarsMaze127 = 161; +static const RoomID kMarsMaze128 = 162; +static const RoomID kMarsMaze129 = 163; +static const RoomID kMarsMaze130 = 164; +static const RoomID kMarsMaze131 = 165; +static const RoomID kMarsMaze132 = 166; +static const RoomID kMarsMaze133 = 167; +static const RoomID kMarsMaze136 = 168; +static const RoomID kMarsMaze137 = 169; +static const RoomID kMarsMaze138 = 170; +static const RoomID kMarsMaze139 = 171; +static const RoomID kMarsMaze140 = 172; +static const RoomID kMarsMaze141 = 173; +static const RoomID kMarsMaze142 = 174; +static const RoomID kMarsMaze143 = 175; +static const RoomID kMarsMaze144 = 176; +static const RoomID kMarsMaze145 = 177; +static const RoomID kMarsMaze146 = 178; +static const RoomID kMarsMaze147 = 179; +static const RoomID kMarsMaze148 = 180; +static const RoomID kMarsMaze149 = 181; +static const RoomID kMarsMaze152 = 182; +static const RoomID kMarsMaze153 = 183; +static const RoomID kMarsMaze154 = 184; +static const RoomID kMarsMaze155 = 185; +static const RoomID kMarsMaze156 = 186; +static const RoomID kMarsMaze157 = 187; +static const RoomID kMarsMaze159 = 188; +static const RoomID kMarsMaze160 = 189; +static const RoomID kMarsMaze161 = 190; +static const RoomID kMarsMaze162 = 191; +static const RoomID kMarsMaze163 = 192; +static const RoomID kMarsMaze164 = 193; +static const RoomID kMarsMaze165 = 194; +static const RoomID kMarsMaze166 = 195; +static const RoomID kMarsMaze167 = 196; +static const RoomID kMarsMaze168 = 197; +static const RoomID kMarsMaze169 = 198; +static const RoomID kMarsMaze170 = 199; +static const RoomID kMarsMaze171 = 200; +static const RoomID kMarsMaze172 = 201; +static const RoomID kMarsMaze173 = 202; +static const RoomID kMarsMaze174 = 203; +static const RoomID kMarsMaze175 = 204; +static const RoomID kMarsMaze177 = 205; +static const RoomID kMarsMaze178 = 206; +static const RoomID kMarsMaze179 = 207; +static const RoomID kMarsMaze180 = 208; +static const RoomID kMarsMaze181 = 209; +static const RoomID kMarsMaze182 = 210; +static const RoomID kMarsMaze183 = 211; +static const RoomID kMarsMaze184 = 212; +static const RoomID kMarsMaze187 = 213; +static const RoomID kMarsMaze188 = 214; +static const RoomID kMarsMaze189 = 215; +static const RoomID kMarsMaze190 = 216; +static const RoomID kMarsMaze191 = 217; +static const RoomID kMarsMaze192 = 218; +static const RoomID kMarsMaze193 = 219; +static const RoomID kMarsMaze194 = 220; +static const RoomID kMarsMaze195 = 221; +static const RoomID kMarsMaze198 = 222; +static const RoomID kMarsMaze199 = 223; +static const RoomID kMarsMaze200 = 224; +static const RoomID kMarsDeathRoom = 225; + +// Hot Spot Activation IDs. + +static const HotSpotActivationID kActivationReadyForKiosk = 1; +static const HotSpotActivationID kActivationKioskChoice = 2; +static const HotSpotActivationID kActivationTunnelMapReady = 3; +static const HotSpotActivationID kActivateMarsPodClosed = 4; +static const HotSpotActivationID kActivateMarsPodOpen = 5; +static const HotSpotActivationID kActivateReadyToPressurizeAirlock = 6; +static const HotSpotActivationID kActivateAirlockPressurized = 7; +static const HotSpotActivationID kActivateMaskOnHolder = 8; +static const HotSpotActivationID kActivateMaskOnFiller = 9; +static const HotSpotActivationID kActivateReactorPlatformOut = 10; +static const HotSpotActivationID kActivateReactorPlatformIn = 11; +static const HotSpotActivationID kActivateReactorAskLowerScreen = 12; +static const HotSpotActivationID kActivateReactorReadyForNitrogen = 13; +static const HotSpotActivationID kActivateReactorReadyForCrowBar = 14; +static const HotSpotActivationID kActivateReactorAskOperation = 15; +static const HotSpotActivationID kActivateReactorRanEvaluation = 16; +static const HotSpotActivationID kActivateReactorRanDiagnostics = 17; +static const HotSpotActivationID kActivateReactorAnalyzed = 18; +static const HotSpotActivationID kActivateReactorInstructions = 19; +static const HotSpotActivationID kActivateReactorInGame = 20; +static const HotSpotActivationID kActivateReactorBombSafe = 21; +static const HotSpotActivationID kActivateReactorBombExposed = 22; +static const HotSpotActivationID kActivationRobotHeadClosed = 23; +static const HotSpotActivationID kActivationRobotHeadOpen = 24; + +// Hot Spot IDs. + +static const HotSpotID kMars11NorthKioskSpotID = 5000; +static const HotSpotID kMars11NorthKioskSightsSpotID = 5001; +static const HotSpotID kMars11NorthKioskColonySpotID = 5002; +static const HotSpotID kMars12NorthKioskSpotID = 5003; +static const HotSpotID kMars12NorthKioskSightsSpotID = 5004; +static const HotSpotID kMars12NorthKioskColonySpotID = 5005; +static const HotSpotID kMars31SouthSpotID = 5006; +static const HotSpotID kMars31SouthOutSpotID = 5007; +static const HotSpotID kMars31SouthCardSpotID = 5008; +static const HotSpotID kMars33NorthSpotID = 5009; +static const HotSpotID kMars33NorthOutSpotID = 5010; +static const HotSpotID kMars33NorthMonitorSpotID = 5011; +static const HotSpotID kMars34NorthCardDropSpotID = 5012; +static const HotSpotID kMars34SouthOpenStorageSpotID = 5013; +static const HotSpotID kMars34SouthCloseStorageSpotID = 5014; +static const HotSpotID kMars34SouthCrowbarSpotID = 5015; +static const HotSpotID kMars35EastPressurizeSpotID = 5016; +static const HotSpotID kMars35EastSpinSpotID = 5017; +static const HotSpotID kMars35WestPressurizeSpotID = 5018; +static const HotSpotID kMars35WestSpinSpotID = 5019; +static const HotSpotID kMars45NorthOpenStorageSpotID = 5020; +static const HotSpotID kMars45NorthCloseStorageSpotID = 5021; +static const HotSpotID kMars45NorthCrowbarSpotID = 5022; +static const HotSpotID kAttackRobotHotSpotID = 5023; +static const HotSpotID kMars49AirMaskSpotID = 5024; +static const HotSpotID kMars49AirMaskFilledSpotID = 5025; +static const HotSpotID kMars49AirFillingDropSpotID = 5026; +static const HotSpotID kMars52MoveLeftSpotID = 5027; +static const HotSpotID kMars52MoveRightSpotID = 5028; +static const HotSpotID kMars52ExtractSpotID = 5029; +static const HotSpotID kMars53RetractSpotID = 5030; +static const HotSpotID kMars54MoveLeftSpotID = 5031; +static const HotSpotID kMars54MoveRightSpotID = 5032; +static const HotSpotID kMars54ExtractSpotID = 5033; +static const HotSpotID kMars55RetractSpotID = 5034; +static const HotSpotID kMars56MoveLeftSpotID = 5035; +static const HotSpotID kMars56MoveRightSpotID = 5036; +static const HotSpotID kMars56ExtractSpotID = 5037; +static const HotSpotID kMars57RetractSpotID = 5038; +static const HotSpotID kMars57LowerScreenSpotID = 5039; +static const HotSpotID kMars57Retract2SpotID = 5040; +static const HotSpotID kMars57DropNitrogenSpotID = 5041; +static const HotSpotID kMars57DropCrowBarSpotID = 5042; +static const HotSpotID kMars57CantOpenPanelSpotID = 5043; +static const HotSpotID kMars57ShieldEvaluationSpotID = 5044; +static const HotSpotID kMars57MeasureOutputSpotID = 5045; +static const HotSpotID kMars57RunDiagnosticsSpotID = 5046; +static const HotSpotID kMars57BackToOperationMenuSpotID = 5047; +static const HotSpotID kMars57AnalyzeObjectSpotID = 5048; +static const HotSpotID kMars57RemoveObjectMenuSpotID = 5049; +static const HotSpotID kMars57CircuitLinkSpotID = 5050; +static const HotSpotID kMars57CancelCircuitLinkSpotID = 5051; +static const HotSpotID kMars57GameInstructionsSpotID = 5052; +static const HotSpotID kMars57UndoMoveSpotID = 5053; +static const HotSpotID kMars57RedMoveSpotID = 5054; +static const HotSpotID kMars57YellowMoveSpotID = 5055; +static const HotSpotID kMars57GreenMoveSpotID = 5056; +static const HotSpotID kMars57BlueMoveSpotID = 5057; +static const HotSpotID kMars57PurpleMoveSpotID = 5058; +static const HotSpotID kMars57LowerScreenSafelySpotID = 5059; +static const HotSpotID kMars57GrabBombSpotID = 5060; +static const HotSpotID kMars58MoveLeftSpotID = 5061; +static const HotSpotID kMars58MoveRightSpotID = 5062; +static const HotSpotID kMars58ExtractSpotID = 5063; +static const HotSpotID kMars59RetractSpotID = 5064; +static const HotSpotID kMars60EastPressurizeSpotID = 5065; +static const HotSpotID kMars60EastSpinSpotID = 5066; +static const HotSpotID kMars60WestPressurizeSpotID = 5067; +static const HotSpotID kMars60WestSpinSpotID = 5068; +static const HotSpotID kRobotShuttleOpenHeadSpotID = 5069; +static const HotSpotID kRobotShuttleMapChipSpotID = 5070; +static const HotSpotID kRobotShuttleOpticalChipSpotID = 5071; +static const HotSpotID kRobotShuttleShieldChipSpotID = 5072; + +// Extra sequence IDs. + +static const ExtraID kMarsArrivalFromTSA = 0; +static const ExtraID kMars0AWatchShuttleDepart = 1; +static const ExtraID kRobotThrowsPlayer = 2; +static const ExtraID kMarsInfoKioskIntro = 3; +static const ExtraID kMarsColonyInfo = 4; +static const ExtraID kMarsSightsInfo = 5; +static const ExtraID kRobotOnWayToShuttle = 6; +static const ExtraID kMars31SouthZoomInNoCard = 7; +static const ExtraID kMars31SouthViewNoCard = 8; +static const ExtraID kMars31SouthZoomOutNoCard = 9; +static const ExtraID kMars31SouthZoomViewNoCard = 10; +static const ExtraID kMars33SlideShow1 = 11; +static const ExtraID kMars33SlideShow2 = 12; +static const ExtraID kMars33SlideShow3 = 13; +static const ExtraID kMars33SlideShow4 = 14; +static const ExtraID kMars34SpotOpenWithBar = 15; +static const ExtraID kMars34SpotCloseWithBar = 16; +static const ExtraID kMars34SpotOpenNoBar = 17; +static const ExtraID kMars34SpotCloseNoBar = 18; +static const ExtraID kMars34ViewOpenWithBar = 19; +static const ExtraID kMars34ViewOpenNoBar = 20; +static const ExtraID kMars34NorthPodGreeting = 21; +static const ExtraID kMarsTurnOnPod = 22; +static const ExtraID kMarsTakePodToMars45 = 23; +static const ExtraID kMars35WestSpinAirlockToEast = 24; +static const ExtraID kMars35EastSpinAirlockToWest = 25; +static const ExtraID kMars45SpotOpenWithBar = 26; +static const ExtraID kMars45SpotCloseWithBar = 27; +static const ExtraID kMars45SpotOpenNoBar = 28; +static const ExtraID kMars45SpotCloseNoBar = 29; +static const ExtraID kMars45ViewOpenWithBar = 30; +static const ExtraID kMars45ViewOpenNoBar = 31; +static const ExtraID kMars48RobotApproaches = 32; +static const ExtraID kMars48RobotKillsPlayer = 33; +static const ExtraID kMars48RobotLoops = 34; +static const ExtraID kMars48RobotView = 35; +static const ExtraID kMars48RobotDefends = 36; +static const ExtraID kMars49SouthViewMaskFilling = 37; +static const ExtraID kMars52SpinLeft = 38; +static const ExtraID kMars52SpinRight = 39; +static const ExtraID kMars52Extend = 40; +static const ExtraID kMars53Retract = 41; +static const ExtraID kMars54SpinLeft = 42; +static const ExtraID kMars54SpinRight = 43; +static const ExtraID kMars54Extend = 44; +static const ExtraID kMars55Retract = 45; +static const ExtraID kMars56SpinLeft = 46; +static const ExtraID kMars56SpinRight = 47; +static const ExtraID kMars56ExtendWithBomb = 48; +static const ExtraID kMars56ExtendNoBomb = 49; +static const ExtraID kMars57RetractWithBomb = 50; +static const ExtraID kMars57RetractNoBomb = 51; +static const ExtraID kMars57LowerScreenClosed = 52; +static const ExtraID kMars57CantOpenPanel = 53; +static const ExtraID kMars57FreezeLock = 54; +static const ExtraID kMars57BreakLock = 55; +static const ExtraID kMars57LockFrozenView = 56; +static const ExtraID kMars57ThawLock = 57; +static const ExtraID kMars57OpenPanel = 58; +static const ExtraID kMars57OpenPanelChoices = 59; +static const ExtraID kMars57ShieldEvaluation = 60; +static const ExtraID kMars57MeasureOutput = 61; +static const ExtraID kMars57ShieldOkayLoop = 62; +static const ExtraID kMars57RunDiagnostics = 63; +static const ExtraID kMars57BombExplodes = 64; +static const ExtraID kMars57BombAnalysis = 65; +static const ExtraID kMars57DontLink = 66; +static const ExtraID kMars57CircuitLink = 67; +static const ExtraID kMars57GameLevel1 = 68; +static const ExtraID kMars57GameLevel2 = 69; +static const ExtraID kMars57GameLevel3 = 70; +static const ExtraID kMars57BombExplodesInGame = 71; +static const ExtraID kMars57GameSolved = 72; +static const ExtraID kMars57ExposeBomb = 73; +static const ExtraID kMars57BackToNormal = 74; +static const ExtraID kMars57ViewOpenNoBomb = 75; +static const ExtraID kMars58SpinLeft = 76; +static const ExtraID kMars58SpinRight = 77; +static const ExtraID kMars58Extend = 78; +static const ExtraID kMars59Retract = 79; +static const ExtraID kMars60WestSpinAirlockToEast = 80; +static const ExtraID kMars60EastSpinAirlockToWest = 81; +static const ExtraID kMarsRobotHeadOpen = 82; +static const ExtraID kMarsRobotHeadClose = 83; +static const ExtraID kMarsRobotHead000 = 84; +static const ExtraID kMarsRobotHead001 = 85; +static const ExtraID kMarsRobotHead010 = 86; +static const ExtraID kMarsRobotHead011 = 87; +static const ExtraID kMarsRobotHead100 = 88; +static const ExtraID kMarsRobotHead101 = 89; +static const ExtraID kMarsRobotHead110 = 90; +static const ExtraID kMarsRobotHead111 = 91; +static const ExtraID kMarsMaze007RobotApproach = 92; +static const ExtraID kMarsMaze007RobotLoop = 93; +static const ExtraID kMarsMaze007RobotDeath = 94; +static const ExtraID kMarsMaze015SouthRobotApproach = 95; +static const ExtraID kMarsMaze015SouthRobotLoop = 96; +static const ExtraID kMarsMaze015SouthRobotDeath = 97; +static const ExtraID kMarsMaze101EastRobotApproach = 98; +static const ExtraID kMarsMaze101EastRobotLoop = 99; +static const ExtraID kMarsMaze101EastRobotDeath = 100; +static const ExtraID kMarsMaze104WestLoop = 101; +static const ExtraID kMarsMaze104WestDeath = 102; +static const ExtraID kMarsMaze133SouthApproach = 103; +static const ExtraID kMarsMaze133SouthLoop = 104; +static const ExtraID kMarsMaze133SouthDeath = 105; +static const ExtraID kMarsMaze136NorthApproach = 106; +static const ExtraID kMarsMaze136NorthLoop = 107; +static const ExtraID kMarsMaze136NorthDeath = 108; +static const ExtraID kMarsMaze184WestLoop = 109; +static const ExtraID kMarsMaze184WestDeath = 110; +static const ExtraID kMars200DeathInBucket = 111; + +static const ResIDType kReactorUndoHilitePICTID = 900; + +static const int16 kMars52Compass = 90; +static const int16 kMars54Compass = 180; +static const int16 kMars56Compass = 270; +static const int16 kMars58Compass = 0; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/energybeam.cpp b/engines/pegasus/neighborhood/mars/energybeam.cpp new file mode 100644 index 0000000000..964c8ba381 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/energybeam.cpp @@ -0,0 +1,70 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/pegasus.h" +#include "pegasus/neighborhood/mars/energybeam.h" + +namespace Pegasus { + +static const TimeValue kEnergyBeamTime = kOneSecond * kShuttleWeaponScale / 2; + +static const CoordType kEnergyBeamOriginH = kShuttleWindowMidH; +static const CoordType kEnergyBeamOriginV = kShuttleWindowTop + kShuttleWindowHeight; + +static const float kBeamXOrigin = convertScreenHToSpaceX(kEnergyBeamOriginH, kEnergyBeamMinDistance); +static const float kBeamYOrigin = convertScreenVToSpaceY(kEnergyBeamOriginV, kEnergyBeamMinDistance); +static const float kBeamZOrigin = kEnergyBeamMinDistance; + +EnergyBeam::EnergyBeam() { + _weaponDuration = kEnergyBeamTime; + setSegment(0, kEnergyBeamTime); + _weaponOrigin = Point3D(kBeamXOrigin, kBeamYOrigin, kBeamZOrigin); +} + +void EnergyBeam::draw(const Common::Rect &) { + static const int kBeamColorRed1 = 224; + static const int kBeamColorRed2 = 64; + + Graphics::Surface *surface = ((PegasusEngine *)g_engine)->_gfx->getWorkArea(); + + byte red = linearInterp(0, kEnergyBeamTime, _lastTime, kBeamColorRed1, kBeamColorRed2); + uint32 color = surface->format.RGBToColor(red, 0, 0); + + Point3D startPoint; + if (_weaponTime < 0.1) + startPoint = _weaponOrigin; + else + linearInterp(_weaponOrigin, _weaponTarget, _weaponTime - 0.1, startPoint); + + Common::Point lineStart; + project3DTo2D(startPoint, lineStart); + + Common::Point lineEnd; + project3DTo2D(_weaponLocation, lineEnd); + + surface->drawThickLine(lineStart.x, lineStart.y, lineEnd.x, lineEnd.y, 2, 1, color); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/energybeam.h b/engines/pegasus/neighborhood/mars/energybeam.h new file mode 100644 index 0000000000..715ed4b01d --- /dev/null +++ b/engines/pegasus/neighborhood/mars/energybeam.h @@ -0,0 +1,43 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_ENERGYBEAM_H +#define PEGASUS_NEIGHBORHOOD_MARS_ENERGYBEAM_H + +#include "pegasus/neighborhood/mars/shuttleweapon.h" + +namespace Pegasus { + +class EnergyBeam : public ShuttleWeapon { +public: + EnergyBeam(); + virtual ~EnergyBeam() {} + + void draw(const Common::Rect &); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/gravitoncannon.cpp b/engines/pegasus/neighborhood/mars/gravitoncannon.cpp new file mode 100644 index 0000000000..13dc6dfc77 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/gravitoncannon.cpp @@ -0,0 +1,134 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/neighborhood/mars/gravitoncannon.h" +#include "pegasus/neighborhood/mars/robotship.h" +#include "pegasus/neighborhood/mars/spacejunk.h" + +namespace Pegasus { + +static const TimeValue kGravitonTime = kOneSecond * kShuttleWeaponScale; + +static const CoordType kGravitonOriginH = kShuttleWindowLeft - 1; +static const CoordType kGravitonOriginV = kShuttleWindowMidV; + +static const float kGravitonXOrigin = convertScreenHToSpaceX(kGravitonOriginH, kGravitonMinDistance); +static const float kGravitonYOrigin = convertScreenVToSpaceY(kGravitonOriginV, kGravitonMinDistance); +static const float kGravitonZOrigin = kGravitonMinDistance; + +// Width of graviton sprite... +static const CoordType kGravitonMaxScreenWidth = 78; +static const CoordType kGravitonMaxScreenHeight = 46; + +static const float kGravitonWidth = convertScreenHToSpaceX(kShuttleWindowMidH + kGravitonMaxScreenWidth / 2, kGravitonMinDistance) + - convertScreenHToSpaceX(kShuttleWindowMidH - kGravitonMaxScreenWidth / 2, kGravitonMinDistance); +static const float kGravitonHeight = convertScreenVToSpaceY(kShuttleWindowMidV - kGravitonMaxScreenHeight / 2, kGravitonMinDistance) + - convertScreenVToSpaceY(kShuttleWindowMidV + kGravitonMaxScreenHeight / 2, kGravitonMinDistance); + +GravitonCannon::GravitonCannon() { + _weaponDuration = kGravitonTime; + setSegment(0, kGravitonTime); + _weaponOrigin = Point3D(kGravitonXOrigin, kGravitonYOrigin, kGravitonZOrigin); + _rightOrigin = Point3D(-kGravitonXOrigin, kGravitonYOrigin, kGravitonZOrigin); +} + +void GravitonCannon::initShuttleWeapon() { + ShuttleWeapon::initShuttleWeapon(); + _gravitonImage.getImageFromPICTFile("Images/Mars/Graviton Cannon"); + _gravitonImage.getSurfaceBounds(_gravitonBounds); +} + +void GravitonCannon::cleanUpShuttleWeapon() { + _gravitonImage.deallocateSurface(); + ShuttleWeapon::cleanUpShuttleWeapon(); +} + +void GravitonCannon::draw(const Common::Rect &) { + // Left graviton... + Point3D pt3D = _weaponLocation; + pt3D.translate(-kGravitonWidth / 2, kGravitonHeight / 2, 0); + Common::Point pt2D; + project3DTo2D(pt3D, pt2D); + Common::Rect gravitonRect; + gravitonRect.left = pt2D.x; + gravitonRect.top = pt2D.y; + + pt3D.translate(kGravitonWidth, -kGravitonHeight, 0); + project3DTo2D(pt3D, pt2D); + gravitonRect.right = pt2D.x; + gravitonRect.bottom = pt2D.y; + + _gravitonImage.scaleTransparentCopy(_gravitonBounds, gravitonRect); + + // Right graviton... + pt3D = _rightLocation; + pt3D.translate(-kGravitonWidth / 2, kGravitonHeight / 2, 0); + project3DTo2D(pt3D, pt2D); + gravitonRect.left = pt2D.x; + gravitonRect.top = pt2D.y; + + pt3D.translate(kGravitonWidth, -kGravitonHeight, 0); + project3DTo2D(pt3D, pt2D); + gravitonRect.right = pt2D.x; + gravitonRect.bottom = pt2D.y; + + _gravitonImage.scaleTransparentCopy(_gravitonBounds, gravitonRect); +} + +void GravitonCannon::updateWeaponPosition() { + ShuttleWeapon::updateWeaponPosition(); + if (_weaponTime != 1.0) + linearInterp(_rightOrigin, _weaponTarget, _weaponTime, _rightLocation); +} + +bool GravitonCannon::collisionWithJunk(Common::Point &impactPoint) { + if (getDisplayOrder() == kShuttleWeaponFrontOrder) { + Point3D junkPosition; + g_spaceJunk->getJunkPosition(junkPosition); + + if (junkPosition.z < _weaponLocation.z) { + setDisplayOrder(kShuttleWeaponBackOrder); + project3DTo2D(_weaponLocation, impactPoint); + + if (g_spaceJunk->pointInJunk(impactPoint)) + return true; + + project3DTo2D(_rightLocation, impactPoint); + return g_spaceJunk->pointInJunk(impactPoint); + } + } + + return false; +} + +void GravitonCannon::hitJunk(Common::Point impactPoint) { + g_spaceJunk->hitByGravitonCannon(impactPoint); +} + +void GravitonCannon::hitShuttle(Common::Point impactPoint) { + g_robotShip->hitByGravitonCannon(impactPoint); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/gravitoncannon.h b/engines/pegasus/neighborhood/mars/gravitoncannon.h new file mode 100644 index 0000000000..b94fd55e5b --- /dev/null +++ b/engines/pegasus/neighborhood/mars/gravitoncannon.h @@ -0,0 +1,57 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_GRAVITONCANNON_H +#define PEGASUS_NEIGHBORHOOD_MARS_GRAVITONCANNON_H + +#include "pegasus/surface.h" +#include "pegasus/neighborhood/mars/shuttleweapon.h" + +namespace Pegasus { + +class GravitonCannon : public ShuttleWeapon { +public: + GravitonCannon(); + virtual ~GravitonCannon() {} + + void initShuttleWeapon(); + void cleanUpShuttleWeapon(); + + void draw(const Common::Rect &); + +protected: + virtual void updateWeaponPosition(); + virtual bool collisionWithJunk(Common::Point &impactPoint); + virtual void hitJunk(Common::Point impactPoint); + virtual void hitShuttle(Common::Point impactPoint); + + Surface _gravitonImage; + Common::Rect _gravitonBounds; + Point3D _rightOrigin, _rightLocation; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/hermite.cpp b/engines/pegasus/neighborhood/mars/hermite.cpp new file mode 100644 index 0000000000..dc4a2c5fc2 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/hermite.cpp @@ -0,0 +1,76 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/neighborhood/mars/hermite.h" + +namespace Pegasus { + +CoordType hermite(CoordType p1, CoordType p4, CoordType r1, CoordType r4, int32 time, int32 duration) { + float t = (float)time / duration; + float tsq = t * t; + float tcu = t * tsq; + float tcu2 = tcu + tcu; + float tsq2 = tsq + tsq; + float tsq3 = tsq2 + tsq; + return (CoordType)((tcu2 - tsq3 + 1) * p1 + (tsq3 - tcu2) * p4 + (tcu - tsq2 + t) * r1 + (tcu - tsq) * r4); +} + +CoordType dHermite(CoordType p1, CoordType p4, CoordType r1, CoordType r4, int32 time, int32 duration) { + float t = (float)time / duration; + float t2 = t + t; + float t4 = t2 + t2; + float t6 = t4 + t2; + float tsq = t * t; + float tsq3 = tsq + tsq + tsq; + float tsq6 = tsq3 + tsq3; + return (CoordType)((tsq6 - t6) * p1 + (t6 - tsq6) * p4 + (tsq3 - t4 + 1) * r1 + (tsq3 - t2) * r4); +} + +void hermite(Common::Point p1, Common::Point p4, Common::Point r1, Common::Point r4, int32 time, int32 duration, Common::Point &result) { + float t = (float)time / duration; + float tsq = t * t; + float tcu = t * tsq; + float tcu2 = tcu + tcu; + float tsq2 = tsq + tsq; + float tsq3 = tsq2 + tsq; + + result.x = (int16)((tcu2 - tsq3 + 1) * p1.x + (tsq3 - tcu2) * p4.x + (tcu - tsq2 + t) * r1.x + (tcu - tsq) * r4.x); + result.y = (int16)((tcu2 - tsq3 + 1) * p1.y + (tsq3 - tcu2) * p4.y + (tcu - tsq2 + t) * r1.y + (tcu - tsq) * r4.y); +} + +void dHermite(Common::Point p1, Common::Point p4, Common::Point r1, Common::Point r4, int32 time, int32 duration, Common::Point &result) { + float t = (float)time / duration; + float t2 = t + t; + float t4 = t2 + t2; + float t6 = t4 + t2; + float tsq = t * t; + float tsq3 = tsq + tsq + tsq; + float tsq6 = tsq3 + tsq3; + + result.x = (int16)((tsq6 - t6) * p1.x + (t6 - tsq6) * p4.x + (tsq3 - t4 + 1) * r1.x + (tsq3 - t2) * r4.x); + result.y = (int16)((tsq6 - t6) * p1.y + (t6 - tsq6) * p4.y + (tsq3 - t4 + 1) * r1.y + (tsq3 - t2) * r4.y); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/hermite.h b/engines/pegasus/neighborhood/mars/hermite.h new file mode 100644 index 0000000000..44cb3a5a11 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/hermite.h @@ -0,0 +1,41 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_HERMITE_H +#define PEGASUS_NEIGHBORHOOD_MARS_HERMITE_H + +#include "common/rect.h" +#include "pegasus/types.h" + +namespace Pegasus { + +CoordType hermite(CoordType p1, CoordType p4, CoordType r1, CoordType r4, int32 t, int32 duration); +CoordType dHermite(CoordType p1, CoordType p4, CoordType r1, CoordType r4, int32 t, int32 duration); +void hermite(Common::Point p1, Common::Point p4, Common::Point r1, Common::Point r4, int32 t, int32 duration, Common::Point &result); +void dHermite(Common::Point p1, Common::Point p4, Common::Point r1, Common::Point r4, int32 t, int32 duration, Common::Point &result); + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/mars.cpp b/engines/pegasus/neighborhood/mars/mars.cpp new file mode 100644 index 0000000000..c950a85897 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/mars.cpp @@ -0,0 +1,3749 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/events.h" +#include "video/qt_decoder.h" + +#include "pegasus/cursor.h" +#include "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/opticalchip.h" +#include "pegasus/items/biochips/shieldchip.h" +#include "pegasus/items/inventory/airmask.h" +#include "pegasus/neighborhood/mars/mars.h" + +namespace Pegasus { + +// This should really be 22.5. +// Probably no one will know the difference. +static const int16 kMarsShieldPanelOffsetAngle = 22; + +static const CanMoveForwardReason kCantMoveRobotBlocking = kCantMoveLastReason + 1; + +static const NotificationFlags kTimeForCanyonChaseFlag = kLastNeighborhoodNotificationFlag << 1; +static const NotificationFlags kExplosionFinishedFlag = kTimeForCanyonChaseFlag << 1; +static const NotificationFlags kTimeToTransportFlag = kExplosionFinishedFlag << 1; + +static const NotificationFlags kMarsNotificationFlags = kTimeForCanyonChaseFlag | + kExplosionFinishedFlag | + kTimeToTransportFlag; + +static const TimeValue kLittleExplosionStart = 0 * 40; +static const TimeValue kLittleExplosionStop = 24 * 40; + +static const TimeValue kBigExplosionStart = 24 * 40; +static const TimeValue kBigExplosionStop = 62 * 40; + +enum { + kMaze007RobotLoopingEvent, + kMaze015RobotLoopingEvent, + kMaze101RobotLoopingEvent, + kMaze104RobotLoopingEvent, + kMaze133RobotLoopingEvent, + kMaze136RobotLoopingEvent, + kMaze184RobotLoopingEvent +}; + +enum { + kMaze007RobotLoopingTime = (64 + 96) * kMarsFrameDuration, + kMaze015RobotLoopingTime = (64 + 93) * kMarsFrameDuration, + kMaze101RobotLoopingTime = (64 + 45) * kMarsFrameDuration, + kMaze104RobotLoopingTime = 96 * kMarsFrameDuration, + kMaze133RobotLoopingTime = (64 + 96) * kMarsFrameDuration, + kMaze136RobotLoopingTime = (64 + 96) * kMarsFrameDuration, + kMaze184RobotLoopingTime = 96 * kMarsFrameDuration +}; + +// I've made a couple macros for these rects so we don't +// have to globally construct them or whatnot +#define kShuttleEnergyBeamBounds Common::Rect(24, 27, 24 + 112, 27 + 46) +#define kShuttleGravitonBounds Common::Rect(24, 73, 24 + 112, 73 + 30) +#define kShuttleTractorBounds Common::Rect(24, 103, 24 + 112, 103 + 30) +#define kShuttleTransportBounds Common::Rect(484, 353, 89 + 484, 79 + 353) + +void robotTimerExpiredFunction(FunctionPtr *, void *mars) { + ((Mars *)mars)->robotTiredOfWaiting(); +} + +void lockThawTimerExpiredFunction(FunctionPtr *, void *mars) { + ((Mars *)mars)->lockThawed(); +} + +void bombTimerExpiredFunction(FunctionPtr *, void *mars) { + ((Mars *)mars)->didntFindBomb(); +} + +void bombTimerExpiredInGameFunction(FunctionPtr *, void *mars) { + ((Mars *)mars)->bombExplodesInGame(); +} + +void airStageExpiredFunction(FunctionPtr *, void *mars) { + ((Mars *)mars)->airStageExpired(); +} + +void marsTimerFunction(FunctionPtr *, void *event) { + ((MarsTimerEvent *)event)->mars->marsTimerExpired(*(MarsTimerEvent *)event); +} + +Mars::Mars(InputHandler *nextHandler, PegasusEngine *owner) : Neighborhood(nextHandler, owner, "Mars", kMarsID), + _guessObject(kNoDisplayElement), _undoPict(kNoDisplayElement), _guessHistory(kNoDisplayElement), + _choiceHighlight(kNoDisplayElement), _shuttleInterface1(kNoDisplayElement), _shuttleInterface2(kNoDisplayElement), + _shuttleInterface3(kNoDisplayElement), _shuttleInterface4(kNoDisplayElement), _canyonChaseMovie(kNoDisplayElement), + _leftShuttleMovie(kNoDisplayElement), _rightShuttleMovie(kNoDisplayElement), _lowerLeftShuttleMovie(kNoDisplayElement), + _lowerRightShuttleMovie(kNoDisplayElement), _centerShuttleMovie(kNoDisplayElement), + _upperLeftShuttleMovie(kNoDisplayElement), _upperRightShuttleMovie(kNoDisplayElement), + _leftDamageShuttleMovie(kNoDisplayElement), _rightDamageShuttleMovie(kNoDisplayElement), _explosions(kNoDisplayElement), + _planetMovie(kNoDisplayElement), _junk(kNoDisplayElement), _energyChoiceSpot(kShuttleEnergySpotID), + _gravitonChoiceSpot(kShuttleGravitonSpotID), _tractorChoiceSpot(kShuttleTractorSpotID), + _shuttleViewSpot(kShuttleViewSpotID), _shuttleTransportSpot(kShuttleTransportSpotID) { + _noAirFuse.setFunctionPtr(&airStageExpiredFunction, this); + setIsItemTaken(kMarsCard); + setIsItemTaken(kAirMask); + setIsItemTaken(kCrowbar); + setIsItemTaken(kCardBomb); +} + +Mars::~Mars() { + g_allHotspots.remove(&_energyChoiceSpot); + g_allHotspots.remove(&_gravitonChoiceSpot); + g_allHotspots.remove(&_tractorChoiceSpot); + g_allHotspots.remove(&_shuttleViewSpot); + g_allHotspots.remove(&_shuttleTransportSpot); +} + +void Mars::init() { + Neighborhood::init(); + + Hotspot *attackSpot = g_allHotspots.findHotspotByID(kAttackRobotHotSpotID); + attackSpot->setMaskedHotspotFlags(kDropItemSpotFlag, kDropItemSpotFlag); + _attackingItem = NULL; + + forceStridingStop(kMars08, kNorth, kAltMarsNormal); + + _neighborhoodNotification.notifyMe(this, kMarsNotificationFlags, kMarsNotificationFlags); + + _explosionCallBack.setNotification(&_neighborhoodNotification); + _explosionCallBack.setCallBackFlag(kExplosionFinishedFlag); +} + +void Mars::flushGameState() { + g_energyMonitor->saveCurrentEnergyValue(); +} + +void Mars::start() { + g_energyMonitor->stopEnergyDraining(); + g_energyMonitor->restoreLastEnergyValue(); + _vm->resetEnergyDeathReason(); + g_energyMonitor->startEnergyDraining(); + Neighborhood::start(); +} + +class AirMaskCondition : public AICondition { +public: + AirMaskCondition(const uint32); + + virtual bool fireCondition(); + +protected: + uint32 _airThreshold; + uint32 _lastAirLevel; +}; + +AirMaskCondition::AirMaskCondition(const uint32 airThreshold) { + _airThreshold = airThreshold; + _lastAirLevel = g_airMask->getAirLeft(); +} + +bool AirMaskCondition::fireCondition() { + bool result = g_airMask && g_airMask->isAirMaskOn() && + g_airMask->getAirLeft() <= _airThreshold && _lastAirLevel > _airThreshold; + + _lastAirLevel = g_airMask->getAirLeft(); + return result; +} + +void Mars::setUpAIRules() { + Neighborhood::setUpAIRules(); + + // Don't add these rules if we're going to the robot's shuttle... + if (g_AIArea && !GameState.getMarsReadyForShuttleTransport()) { + AIPlayMessageAction *messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB1E", false); + AILocationCondition *locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kMars47, kSouth)); + AIRule *rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Mars/XM27NB", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kMars27, kNorth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Mars/XM27NB", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kMars28, kNorth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Mars/XM41ED", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kMars19, kEast)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + AIDeactivateRuleAction *deactivate = new AIDeactivateRuleAction(rule); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kMars35, kWest)); + rule = new AIRule(locCondition, deactivate); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Mars/XM41ED", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kMars48, kWest)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + AirMaskCondition *airMask50Condition = new AirMaskCondition(50); + messageAction = new AIPlayMessageAction("Images/AI/Mars/XMMAZB1", false); + AIRule *rule50 = new AIRule(airMask50Condition, messageAction); + + AirMaskCondition *airMask25Condition = new AirMaskCondition(25); + AICompoundAction *compound = new AICompoundAction(); + messageAction = new AIPlayMessageAction("Images/AI/Mars/XMMAZB2", false); + compound->addAction(messageAction); + deactivate = new AIDeactivateRuleAction(rule50); + compound->addAction(deactivate); + AIRule *rule25 = new AIRule(airMask25Condition, compound); + + AirMaskCondition *airMask5Condition = new AirMaskCondition(5); + compound = new AICompoundAction; + messageAction = new AIPlayMessageAction("Images/AI/Mars/XMMAZB3", false); + compound->addAction(messageAction); + deactivate = new AIDeactivateRuleAction(rule50); + compound->addAction(deactivate); + deactivate = new AIDeactivateRuleAction(rule25); + compound->addAction(deactivate); + AIRule *rule5 = new AIRule(airMask5Condition, compound); + + g_AIArea->addAIRule(rule5); + g_AIArea->addAIRule(rule25); + g_AIArea->addAIRule(rule50); + + messageAction = new AIPlayMessageAction("Images/AI/Mars/XM51ND", false); + AIDoorOpenedCondition *doorOpen = new AIDoorOpenedCondition(MakeRoomView(kMars51, kEast)); + rule = new AIRule(doorOpen, messageAction); + g_AIArea->addAIRule(rule); + } +} + +uint16 Mars::getDateResID() const { + return kDate2185ID; +} + +TimeValue Mars::getViewTime(const RoomID room, const DirectionConstant direction) { + ExtraTable::Entry extra; + SpotTable::Entry spotEntry; + uint32 extraID = 0xffffffff; + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kMars0A, kNorth): + if (!GameState.getMarsSeenTimeStream()) { + getExtraEntry(kMarsArrivalFromTSA, extra); + return extra.movieStart; + } + break; + case MakeRoomView(kMars31South, kSouth): + if (GameState.isTakenItemID(kMarsCard)) + extraID = kMars31SouthZoomViewNoCard; + break; + case MakeRoomView(kMars31, kSouth): + if (GameState.isTakenItemID(kMarsCard)) + extraID = kMars31SouthViewNoCard; + break; + case MakeRoomView(kMars34, kSouth): + if (_privateFlags.getFlag(kMarsPrivatePodStorageOpenFlag)) { + if (GameState.isTakenItemID(kCrowbar)) + extraID = kMars34ViewOpenNoBar; + else + extraID = kMars34ViewOpenWithBar; + } + break; + case MakeRoomView(kMars36, kSouth): + case MakeRoomView(kMars37, kSouth): + case MakeRoomView(kMars38, kSouth): + findSpotEntry(room, direction, kSpotOnTurnMask | kSpotLoopsMask, spotEntry); + return spotEntry.movieStart; + case MakeRoomView(kMars45, kNorth): + if (_privateFlags.getFlag(kMarsPrivatePodStorageOpenFlag)) { + if (GameState.isTakenItemID(kCrowbar)) + extraID = kMars45ViewOpenNoBar; + else + extraID = kMars45ViewOpenWithBar; + } + break; + case MakeRoomView(kMars48, kEast): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) + extraID = kMars48RobotView; + break; + case MakeRoomView(kMars56, kEast): + if (_privateFlags.getFlag(kMarsPrivateBombExposedFlag)) { + if (_privateFlags.getFlag(kMarsPrivateDraggingBombFlag)) + extraID = kMars57ViewOpenNoBomb; + else + extraID = kMars57ExposeBomb; + } else if (GameState.getMarsLockBroken()) { + extraID = kMars57OpenPanelChoices; + } else if (GameState.getMarsLockFrozen()) { + extraID = kMars57LockFrozenView; + } + break; + case MakeRoomView(kMarsRobotShuttle, kEast): + if (getCurrentActivation() == kActivationRobotHeadOpen) { + extraID = kMarsRobotHead111; + + if (_privateFlags.getFlag(kMarsPrivateGotMapChipFlag)) + extraID -= 1; + if (_privateFlags.getFlag(kMarsPrivateGotOpticalChipFlag)) + extraID -= 2; + if (_privateFlags.getFlag(kMarsPrivateGotShieldChipFlag)) + extraID -= 4; + } + break; + } + + if (extraID == 0xffffffff) + return Neighborhood::getViewTime(room, direction); + + getExtraEntry(extraID, extra); + return extra.movieEnd - 1; +} + +void Mars::getZoomEntry(const HotSpotID spotID, ZoomTable::Entry &entry) { + Neighborhood::getZoomEntry(spotID, entry); + + uint32 extraID = 0xffffffff; + + switch (spotID) { + case kMars31SouthSpotID: + if (GameState.getCurrentDirection() == kSouth && GameState.isTakenItemID(kMarsCard)) + extraID = kMars31SouthZoomInNoCard; + break; + case kMars31SouthOutSpotID: + if (GameState.getCurrentDirection() == kSouth && GameState.isTakenItemID(kMarsCard)) + extraID = kMars31SouthZoomOutNoCard; + break; + } + + if (extraID != 0xffffffff) { + ExtraTable::Entry extra; + getExtraEntry(extraID, extra); + entry.movieStart = extra.movieStart; + entry.movieEnd = extra.movieEnd; + } +} + +void Mars::findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &entry) { + Neighborhood::findSpotEntry(room, direction, flags, entry); + + if ((flags & (kSpotOnArrivalMask | kSpotOnTurnMask)) != 0) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars27, kNorth): + if (GameState.getMarsSeenThermalScan()) + entry.clear(); + else + GameState.setMarsSeenThermalScan(true); + break; + case MakeRoomView(kMars28, kNorth): + if (GameState.getMarsSeenThermalScan()) + entry.clear(); + else + GameState.setMarsSeenThermalScan(true); + break; + } + } +} + +CanMoveForwardReason Mars::canMoveForward(ExitTable::Entry &entry) { + CanMoveForwardReason reason = Neighborhood::canMoveForward(entry); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars48, kEast): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) + reason = kCantMoveRobotBlocking; + break; + case MakeRoomView(kMars48, kSouth): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) + _utilityFuse.stopFuse(); + break; + } + + return reason; +} + +void Mars::cantMoveThatWay(CanMoveForwardReason reason) { + if (reason == kCantMoveRobotBlocking) { + startExtraSequence(kMars48RobotKillsPlayer, kExtraCompletedFlag, kFilterNoInput); + loadLoopSound2(""); + } else { + Neighborhood::cantMoveThatWay(reason); + } +} + +void Mars::moveForward() { + if (GameState.getCurrentRoom() == kMars02 || (GameState.getCurrentRoom() >= kMars05 && GameState.getCurrentRoom() <= kMars08)) + loadLoopSound2(""); + + Neighborhood::moveForward(); +} + +void Mars::bumpIntoWall() { + requestSpotSound(kMarsBumpIntoWallIn, kMarsBumpIntoWallOut, kFilterNoInput, 0); + Neighborhood::bumpIntoWall(); +} + +CanOpenDoorReason Mars::canOpenDoor(DoorTable::Entry &entry) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars05, kEast): + case MakeRoomView(kMars06, kEast): + case MakeRoomView(kMars07, kEast): + if (!GameState.getMarsSecurityDown()) + return kCantOpenLocked; + break; + case MakeRoomView(kMarsMaze037, kWest): + case MakeRoomView(kMarsMaze038, kEast): + if (GameState.getMarsMazeDoorPair1()) + return kCantOpenLocked; + break; + case MakeRoomView(kMarsMaze050, kNorth): + case MakeRoomView(kMarsMaze058, kSouth): + if (!GameState.getMarsMazeDoorPair1()) + return kCantOpenLocked; + break; + case MakeRoomView(kMarsMaze047, kNorth): + case MakeRoomView(kMarsMaze142, kSouth): + if (GameState.getMarsMazeDoorPair2()) + return kCantOpenLocked; + break; + case MakeRoomView(kMarsMaze057, kNorth): + case MakeRoomView(kMarsMaze136, kSouth): + if (!GameState.getMarsMazeDoorPair2()) + return kCantOpenLocked; + break; + case MakeRoomView(kMarsMaze120, kWest): + case MakeRoomView(kMarsMaze121, kEast): + if (GameState.getMarsMazeDoorPair3()) + return kCantOpenLocked; + break; + case MakeRoomView(kMarsMaze081, kNorth): + case MakeRoomView(kMarsMaze083, kSouth): + if (!GameState.getMarsMazeDoorPair3()) + return kCantOpenLocked; + break; + } + + return Neighborhood::canOpenDoor(entry); +} + +void Mars::cantOpenDoor(CanOpenDoorReason reason) { + switch (GameState.getCurrentRoom()) { + case kMars05: + case kMars06: + case kMars07: + playSpotSoundSync(kMarsCantOpenShuttleIn, kMarsCantOpenShuttleOut); + break; + default: + Neighborhood::cantOpenDoor(reason); + break; + } +} + +void Mars::openDoor() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars06, kEast): + case MakeRoomView(kMars07, kEast): + if (GameState.getMarsSecurityDown()) + playSpotSoundSync(kMarsNoShuttleIn, kMarsNoShuttleOut); + break; + case MakeRoomView(kMars47, kSouth): + if (GameState.isTakenItemID(kAirMask)) + setCurrentAlternate(kAltMarsTookMask); + else + setCurrentAlternate(kAltMarsNormal); + break; + case MakeRoomView(kMars48, kNorth): + if (GameState.getMarsPodAtUpperPlatform()) + setCurrentAlternate(kAltMarsNormal); + else + setCurrentAlternate(kAltMarsPodAtMars45); + break; + case MakeRoomView(kMars48, kEast): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) { + die(kDeathDidntGetOutOfWay); + return; + } + break; + } + + Neighborhood::openDoor(); +} + +void Mars::doorOpened() { + switch (GameState.getCurrentRoom()) { + case kMars27: + case kMars28: + if (GameState.getCurrentDirection() == kNorth) + _vm->die(kDeathArrestedInMars); + else + Neighborhood::doorOpened(); + break; + case kMars41: + case kMars42: + if (GameState.getCurrentDirection() == kEast) + _vm->die(kDeathWrongShuttleLock); + else + Neighborhood::doorOpened(); + break; + case kMars51: + Neighborhood::doorOpened(); + setUpReactorEnergyDrain(); + + if (g_AIArea) + g_AIArea->checkRules(); + break; + case kMars19: + if (GameState.getCurrentDirection() == kEast) + GameState.setMarsAirlockOpen(true); + + Neighborhood::doorOpened(); + break; + case kMars48: + if (GameState.getCurrentDirection() == kWest) + GameState.setMarsAirlockOpen(true); + + Neighborhood::doorOpened(); + break; + default: + Neighborhood::doorOpened(); + break; + } +} + +void Mars::setUpReactorEnergyDrain() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars51, kEast): + if (GameState.isCurrentDoorOpen()) { + if (g_energyMonitor->getEnergyDrainRate() == kEnergyDrainNormal) { + if (GameState.getShieldOn()) { + g_shield->setItemState(kShieldRadiation); + g_energyMonitor->setEnergyDrainRate(kMarsReactorEnergyDrainWithShield); + } else { + g_energyMonitor->setEnergyDrainRate(kMarsReactorEnergyDrainNoShield); + } + _vm->setEnergyDeathReason(kDeathReactorBurn); + } + } else { + if (g_energyMonitor->getEnergyDrainRate() != kEnergyDrainNormal) { + if (GameState.getShieldOn()) + g_shield->setItemState(kShieldNormal); + g_energyMonitor->setEnergyDrainRate(kEnergyDrainNormal); + _vm->resetEnergyDeathReason(); + } + } + break; + case MakeRoomView(kMars52, kNorth): + case MakeRoomView(kMars52, kSouth): + case MakeRoomView(kMars52, kEast): + case MakeRoomView(kMars52, kWest): + case MakeRoomView(kMars54, kNorth): + case MakeRoomView(kMars54, kSouth): + case MakeRoomView(kMars54, kEast): + case MakeRoomView(kMars54, kWest): + case MakeRoomView(kMars56, kNorth): + case MakeRoomView(kMars56, kSouth): + case MakeRoomView(kMars56, kEast): + case MakeRoomView(kMars56, kWest): + case MakeRoomView(kMars58, kNorth): + case MakeRoomView(kMars58, kSouth): + case MakeRoomView(kMars58, kEast): + case MakeRoomView(kMars58, kWest): + if (g_energyMonitor->getEnergyDrainRate() == kEnergyDrainNormal) { + if (GameState.getShieldOn()) { + g_shield->setItemState(kShieldRadiation); + g_energyMonitor->setEnergyDrainRate(kMarsReactorEnergyDrainWithShield); + } else { + g_energyMonitor->setEnergyDrainRate(kMarsReactorEnergyDrainNoShield); + } + _vm->setEnergyDeathReason(kDeathReactorBurn); + } + break; + default: + if (g_energyMonitor->getEnergyDrainRate() != kEnergyDrainNormal) { + if (GameState.getShieldOn()) + g_shield->setItemState(kShieldNormal); + g_energyMonitor->setEnergyDrainRate(kEnergyDrainNormal); + _vm->resetEnergyDeathReason(); + } + break; + } +} + +void Mars::closeDoorOffScreen(const RoomID room, const DirectionConstant direction) { + switch (room) { + case kMars51: + playSpotSoundSync(kMarsGantryDoorCloseIn, kMarsGantryDoorCloseOut); + if (GameState.getShieldOn()) + g_shield->setItemState(kShieldNormal); + g_energyMonitor->setEnergyDrainRate(kEnergyDrainNormal); + _vm->resetEnergyDeathReason(); + break; + case kMars05: + case kMars06: + case kMars07: + case kMars13: + case kMars22: + case kMars47: + case kMars52: + playSpotSoundSync(kMarsGantryDoorCloseIn, kMarsGantryDoorCloseOut); + break; + case kMars18: + case kMars32: + playSpotSoundSync(kMarsTransportDoorCloseIn, kMarsTransportDoorCloseOut); + break; + case kMars19: + if (GameState.getCurrentRoom() != kMars35) { + playSpotSoundSync(kMarsBigAirlockDoorCloseIn, kMarsBigAirlockDoorCloseOut); + GameState.setMarsAirlockOpen(false); + } + break; + case kMars36: + if (GameState.getCurrentRoom() != kMars35) + playSpotSoundSync(kMarsSmallAirlockDoorCloseIn, kMarsSmallAirlockDoorCloseOut); + break; + case kMars48: + if (direction == kWest) { + if (GameState.getCurrentRoom() != kMars60) { + playSpotSoundSync(kMarsSmallAirlockDoorCloseIn, kMarsSmallAirlockDoorCloseOut); + GameState.setMarsAirlockOpen(false); + } + } else { + playSpotSoundSync(kMarsGantryDoorCloseIn, kMarsGantryDoorCloseOut); + } + break; + case kMars41: + case kMars42: + case kMars43: + if (direction == kWest) + playSpotSoundSync(kMarsGantryDoorCloseIn, kMarsGantryDoorCloseOut); + break; + case kMarsMaze037: + case kMarsMaze038: + case kMarsMaze012: + case kMarsMaze066: + case kMarsMaze050: + case kMarsMaze058: + case kMarsMaze057: + case kMarsMaze136: + case kMarsMaze047: + case kMarsMaze142: + case kMarsMaze133: + case kMarsMaze132: + case kMarsMaze113: + case kMarsMaze114: + case kMarsMaze120: + case kMarsMaze121: + case kMarsMaze081: + case kMarsMaze083: + case kMarsMaze088: + case kMarsMaze089: + case kMarsMaze179: + case kMarsMaze180: + playSpotSoundSync(kMarsMazeDoorCloseIn, kMarsMazeDoorCloseOut); + break; + } +} + +void Mars::checkAirlockDoors() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars19, kWest): + case MakeRoomView(kMars18, kWest): + case MakeRoomView(kMars17, kWest): + case MakeRoomView(kMars16, kWest): + case MakeRoomView(kMars15, kWest): + case MakeRoomView(kMars14, kWest): + case MakeRoomView(kMars12, kWest): + case MakeRoomView(kMars11, kWest): + case MakeRoomView(kMars10, kWest): + if (GameState.getMarsInAirlock()) { + playSpotSoundSync(kMarsBigAirlockDoorCloseIn, kMarsBigAirlockDoorCloseOut); + GameState.setMarsInAirlock(false); + } + break; + case MakeRoomView(kMars36, kEast): + case MakeRoomView(kMars37, kEast): + case MakeRoomView(kMars38, kEast): + case MakeRoomView(kMars39, kEast): + case MakeRoomView(kMars48, kEast): + case MakeRoomView(kMars50, kEast): + case MakeRoomView(kMars51, kEast): + case MakeRoomView(kMars52, kEast): + if (GameState.getMarsInAirlock()) { + playSpotSoundSync(kMarsSmallAirlockDoorCloseIn, kMarsSmallAirlockDoorCloseOut); + GameState.setMarsInAirlock(false); + } + break; + case MakeRoomView(kMars35, kWest): + case MakeRoomView(kMars35, kEast): + case MakeRoomView(kMars60, kWest): + case MakeRoomView(kMars60, kEast): + GameState.setMarsInAirlock(true); + break; + default: + GameState.setMarsInAirlock(false); + break; + } +} + +int16 Mars::getStaticCompassAngle(const RoomID room, const DirectionConstant dir) { + int16 angle = Neighborhood::getStaticCompassAngle(room, dir); + + switch (MakeRoomView(room, dir)) { + case MakeRoomView(kMars0A, kNorth): + angle -= 20; + break; + case MakeRoomView(kMars23, kNorth): + case MakeRoomView(kMars23, kSouth): + case MakeRoomView(kMars23, kEast): + case MakeRoomView(kMars23, kWest): + case MakeRoomView(kMars26, kNorth): + case MakeRoomView(kMars26, kSouth): + case MakeRoomView(kMars26, kEast): + case MakeRoomView(kMars26, kWest): + angle += 30; + break; + case MakeRoomView(kMars24, kNorth): + case MakeRoomView(kMars24, kSouth): + case MakeRoomView(kMars24, kEast): + case MakeRoomView(kMars24, kWest): + case MakeRoomView(kMars25, kNorth): + case MakeRoomView(kMars25, kSouth): + case MakeRoomView(kMars25, kEast): + case MakeRoomView(kMars25, kWest): + angle -= 30; + break; + case MakeRoomView(kMars54, kNorth): + case MakeRoomView(kMars54, kSouth): + case MakeRoomView(kMars54, kEast): + case MakeRoomView(kMars54, kWest): + angle += 90; + break; + case MakeRoomView(kMars56, kNorth): + case MakeRoomView(kMars56, kSouth): + case MakeRoomView(kMars56, kEast): + case MakeRoomView(kMars56, kWest): + angle += 180; + break; + case MakeRoomView(kMars58, kNorth): + case MakeRoomView(kMars58, kSouth): + case MakeRoomView(kMars58, kEast): + case MakeRoomView(kMars58, kWest): + angle -= 90; + break; + } + + return angle; +} + +void Mars::getExitCompassMove(const ExitTable::Entry &exitEntry, FaderMoveSpec &compassMove) { + Neighborhood::getExitCompassMove(exitEntry, compassMove); + + if (exitEntry.room == kMars43 && exitEntry.direction == kEast) { + compassMove.insertFaderKnot(exitEntry.movieStart + 16 * kMarsFrameDuration, 90); + compassMove.insertFaderKnot(exitEntry.movieStart + 32 * kMarsFrameDuration, 270); + } else if (exitEntry.room == kMars46 && exitEntry.direction == kWest && exitEntry.altCode != kAltMarsPodAtMars45) { + compassMove.makeTwoKnotFaderSpec(kMarsMovieScale, exitEntry.movieStart, 270, exitEntry.movieEnd, 360); + compassMove.insertFaderKnot(exitEntry.movieStart + 43 * kMarsFrameDuration, 270); + compassMove.insertFaderKnot(exitEntry.movieStart + 58 * kMarsFrameDuration, 360); + } +} + +void Mars::getExtraCompassMove(const ExtraTable::Entry &entry, FaderMoveSpec &compassMove) { + switch (entry.extra) { + case kMarsTakePodToMars45: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, 0, entry.movieEnd, 180); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 3), 30); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 11), 10); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 14), 40); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 16), 30); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 23), 100); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 31), 70); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 34), 100); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 37), 85); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 42), 135); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 44), 125); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 46), 145); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 49), 160); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 51), 180); + break; + case kMars35WestSpinAirlockToEast: + case kMars60WestSpinAirlockToEast: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, 90, entry.movieEnd, 270); + compassMove.insertFaderKnot(entry.movieStart + kMarsMovieScale, 90); + compassMove.insertFaderKnot(entry.movieStart + kMarsMovieScale * 3, 270); + break; + case kMars35EastSpinAirlockToWest: + case kMars60EastSpinAirlockToWest: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, 270, entry.movieEnd, 90); + compassMove.insertFaderKnot(entry.movieStart + kMarsMovieScale, 270); + compassMove.insertFaderKnot(entry.movieStart + kMarsMovieScale * 3, 90); + break; + case kMars52SpinLeft: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars52Compass, entry.movieEnd, kMars54Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars52Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars54Compass); + break; + case kMars52SpinRight: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars52Compass, entry.movieEnd, kMars58Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars52Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars58Compass); + break; + case kMars52Extend: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars52Compass, + entry.movieEnd, kMars52Compass + kMarsShieldPanelOffsetAngle); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars52Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 60, kMars52Compass + kMarsShieldPanelOffsetAngle); + break; + case kMars53Retract: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, + kMars52Compass + kMarsShieldPanelOffsetAngle, entry.movieEnd, kMars52Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars52Compass + kMarsShieldPanelOffsetAngle); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 60, kMars52Compass); + break; + case kMars56ExtendWithBomb: + case kMars56ExtendNoBomb: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars56Compass, + entry.movieEnd, kMars56Compass - kMarsShieldPanelOffsetAngle); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars56Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 60, kMars56Compass - kMarsShieldPanelOffsetAngle); + break; + case kMars57RetractWithBomb: + case kMars57RetractNoBomb: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, + kMars56Compass - kMarsShieldPanelOffsetAngle, entry.movieEnd, kMars56Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars56Compass - kMarsShieldPanelOffsetAngle); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 60, kMars56Compass); + break; + case kMars54SpinLeft: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars54Compass, entry.movieEnd, kMars56Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars54Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars56Compass); + break; + case kMars54SpinRight: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars54Compass, entry.movieEnd, kMars52Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars54Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars52Compass); + break; + case kMars56SpinLeft: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars56Compass, + entry.movieEnd, kMars58Compass + 360); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars56Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars58Compass + 360); + break; + case kMars56SpinRight: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars56Compass, entry.movieEnd, kMars54Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars56Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars54Compass); + break; + case kMars58SpinLeft: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars58Compass, + entry.movieEnd, kMars52Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars58Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars52Compass); + break; + case kMars58SpinRight: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, + kMars58Compass + 360, entry.movieEnd, kMars56Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars58Compass + 360); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars56Compass); + break; + default: + Neighborhood::getExtraCompassMove(entry, compassMove); + } +} + +void Mars::loadAmbientLoops() { + RoomID room = GameState.getCurrentRoom(); + + if ((room >= kMars0A && room <= kMars21) || (room >= kMars41 && room <= kMars43)) { + if (GameState.getMarsSeenTimeStream()) + loadLoopSound1("Sounds/Mars/Gantry Ambient.22K.8.AIFF"); + } else if (room >= kMars22 && room <= kMars31South) { + loadLoopSound1("Sounds/Mars/Reception.02.22K.8.AIFF", 0x100 / 4); + } else if (room >= kMars32 && room <= kMars34) { + loadLoopSound1("Sounds/Mars/Pod Room Ambient.22K.8.AIFF"); + } else if (room == kMars35) { + if (getAirQuality(room) == kAirQualityVacuum) + loadLoopSound1("Sounds/Mars/Gear Room Ambient.22K.8.AIFF"); + else + loadLoopSound1("Sounds/Mars/Gantry Ambient.22K.8.AIFF", 0x100 / 2); + } else if (room >= kMars36 && room <= kMars39) { + loadLoopSound1("Sounds/Mars/Gear Room Ambient.22K.8.AIFF"); + } else if (room >= kMars45 && room <= kMars51) { + loadLoopSound1("Sounds/Mars/Lower Mars Ambient.22K.8.AIFF"); + } else if (room >= kMars52 && room <= kMars58) { + loadLoopSound1("Sounds/Mars/ReactorLoop.22K.8.AIFF"); + } else if (room == kMars60) { + if (getAirQuality(room) == kAirQualityVacuum) + loadLoopSound1("Sounds/Mars/Mars Maze Ambient.22K.8.AIFF"); + else + loadLoopSound1("Sounds/Mars/Lower Mars Ambient.22K.8.AIFF", 0x100 / 2); + } else if (room >= kMarsMaze004 && room <= kMarsMaze200) { + loadLoopSound1("Sounds/Mars/Mars Maze Ambient.22K.8.AIFF"); + } else if (room == kMarsRobotShuttle) { + loadLoopSound1("Sounds/Mars/Robot Shuttle.22K.8.AIFF"); + } + + if (!_noAirFuse.isFuseLit()) { + switch (room) { + case kMars02: + case kMars05: + case kMars06: + case kMars07: + case kMars08: + loadLoopSound2("Sounds/Mars/Gantry Loop.aiff", 0x100, 0, 0); + break; + // Robot at maze 48 + case kMarsMaze037: + if (GameState.isCurrentDoorOpen()) + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 2); + else + loadLoopSound2(""); + break; + case kMarsMaze038: + case kMarsMaze039: + case kMarsMaze049: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100); + break; + case kMarsMaze050: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 * 3 / 4); + break; + case kMarsMaze051: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 2); + break; + case kMarsMaze052: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 4); + break; + case kMarsMaze042: + case kMarsMaze053: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 8); + break; + case kMarsMaze058: + if (GameState.isCurrentDoorOpen()) + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 4); + else + loadLoopSound2(""); + break; + // Robot at 151 + case kMarsMaze148: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100); + break; + case kMarsMaze147: + case kMarsMaze149: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 * 3 / 4); + break; + case kMarsMaze146: + case kMarsMaze152: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 2); + break; + case kMarsMaze145: + case kMarsMaze153: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 4); + break; + // Robots at 80 and 82. + case kMarsMaze079: + case kMarsMaze081: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100); + break; + case kMarsMaze078: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 * 3 / 4); + break; + case kMarsMaze083: + if (GameState.isCurrentDoorOpen()) + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 * 3 / 4); + else + loadLoopSound2(""); + break; + case kMarsMaze118: + case kMarsMaze076: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 2); + break; + case kMarsMaze074: + case kMarsMaze117: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 4); + break; + // Robot at 94 + case kMarsMaze093: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100); + break; + case kMarsMaze091: + case kMarsMaze092: + case kMarsMaze098: + case kMarsMaze101: + case kMarsMaze100: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 * 3 / 4); + break; + case kMarsMaze090: + case kMarsMaze099: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 2); + break; + case kMarsMaze089: + if (GameState.isCurrentDoorOpen()) + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 2); + break; + case kMarsMaze178: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 4); + break; + // Robot at 197 + case kMarsMaze191: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100); + break; + case kMarsMaze190: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 * 3 / 4); + break; + case kMarsMaze198: + case kMarsMaze189: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 2); + break; + default: + loadLoopSound2(""); + break; + } + } +} + +void Mars::checkContinuePoint(const RoomID room, const DirectionConstant direction) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kMars02, kSouth): + case MakeRoomView(kMars19, kEast): + case MakeRoomView(kMars22, kNorth): + case MakeRoomView(kMars43, kEast): + case MakeRoomView(kMars51, kEast): + case MakeRoomView(kMars56, kEast): + case MakeRoomView(kMars60, kWest): + case MakeRoomView(kMarsMaze004, kWest): + case MakeRoomView(kMarsMaze009, kWest): + case MakeRoomView(kMarsMaze012, kWest): + case MakeRoomView(kMarsMaze037, kWest): + case MakeRoomView(kMarsMaze047, kNorth): + case MakeRoomView(kMarsMaze052, kWest): + case MakeRoomView(kMarsMaze057, kNorth): + case MakeRoomView(kMarsMaze071, kWest): + case MakeRoomView(kMarsMaze081, kNorth): + case MakeRoomView(kMarsMaze088, kWest): + case MakeRoomView(kMarsMaze093, kWest): + case MakeRoomView(kMarsMaze115, kNorth): + case MakeRoomView(kMarsMaze120, kWest): + case MakeRoomView(kMarsMaze126, kEast): + case MakeRoomView(kMarsMaze133, kNorth): + case MakeRoomView(kMarsMaze144, kNorth): + case MakeRoomView(kMarsMaze156, kEast): + case MakeRoomView(kMarsMaze162, kNorth): + case MakeRoomView(kMarsMaze177, kWest): + case MakeRoomView(kMarsMaze180, kNorth): + case MakeRoomView(kMarsMaze187, kWest): + case MakeRoomView(kMarsMaze199, kWest): + makeContinuePoint(); + break; + case MakeRoomView(kMars05, kEast): + case MakeRoomView(kMars06, kEast): + case MakeRoomView(kMars07, kEast): + if (GameState.getMarsSecurityDown()) + makeContinuePoint(); + break; + case MakeRoomView(kMars46, kSouth): + if (!GameState.getMarsSeenRobotAtReactor()) + makeContinuePoint(); + break; + case MakeRoomView(kMars46, kWest): + if (GameState.getMarsAvoidedReactorRobot()) + makeContinuePoint(); + break; + } +} + +void Mars::launchMaze007Robot() { + startExtraLongSequence(kMarsMaze007RobotApproach, kMarsMaze007RobotDeath, kExtraCompletedFlag, kFilterAllInput); + scheduleEvent(kMaze007RobotLoopingTime, kMarsMovieScale, kMaze007RobotLoopingEvent); +} + +void Mars::launchMaze015Robot() { + startExtraLongSequence(kMarsMaze015SouthRobotApproach, kMarsMaze015SouthRobotDeath, kExtraCompletedFlag, kFilterAllInput); + scheduleEvent(kMaze015RobotLoopingTime, kMarsMovieScale, kMaze015RobotLoopingEvent); +} + +void Mars::launchMaze101Robot() { + startExtraLongSequence(kMarsMaze101EastRobotApproach, kMarsMaze101EastRobotDeath, kExtraCompletedFlag, kFilterAllInput); + scheduleEvent(kMaze101RobotLoopingTime, kMarsMovieScale, kMaze101RobotLoopingEvent); +} + +void Mars::launchMaze104Robot() { + startExtraLongSequence(kMarsMaze104WestLoop, kMarsMaze104WestDeath, kExtraCompletedFlag, kFilterAllInput); + scheduleEvent(kMaze104RobotLoopingTime, kMarsMovieScale, kMaze104RobotLoopingEvent); +} + +void Mars::launchMaze133Robot() { + startExtraLongSequence(kMarsMaze133SouthApproach, kMarsMaze133SouthDeath, kExtraCompletedFlag, kFilterAllInput); + scheduleEvent(kMaze133RobotLoopingTime, kMarsMovieScale, kMaze133RobotLoopingEvent); +} + +void Mars::launchMaze136Robot() { + startExtraLongSequence(kMarsMaze136NorthApproach, kMarsMaze136NorthDeath, kExtraCompletedFlag, kFilterAllInput); + scheduleEvent(kMaze136RobotLoopingTime, kMarsMovieScale, kMaze136RobotLoopingEvent); +} + +void Mars::launchMaze184Robot() { + startExtraLongSequence(kMarsMaze184WestLoop, kMarsMaze184WestDeath, kExtraCompletedFlag, kFilterAllInput); + scheduleEvent(kMaze184RobotLoopingTime, kMarsMovieScale, kMaze184RobotLoopingEvent); +} + +void Mars::timerExpired(const uint32 eventType) { + switch (eventType) { + case kMaze007RobotLoopingEvent: + case kMaze015RobotLoopingEvent: + case kMaze101RobotLoopingEvent: + case kMaze104RobotLoopingEvent: + case kMaze133RobotLoopingEvent: + case kMaze136RobotLoopingEvent: + case kMaze184RobotLoopingEvent: + _interruptionFilter = kFilterNoInput; + break; + } +} + +void Mars::arriveAt(const RoomID room, const DirectionConstant direction) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kMars18, kNorth): + if (GameState.getMarsPodAtUpperPlatform()) + setCurrentAlternate(kAltMarsPodAtMars34); + break; + case MakeRoomView(kMars27, kEast): + case MakeRoomView(kMars29, kEast): + if (GameState.isTakenItemID(kMarsCard)) + setCurrentAlternate(kAltMarsTookCard); + else + setCurrentAlternate(kAltMarsNormal); + break; + case MakeRoomView(kMars35, kEast): + case MakeRoomView(kMars35, kWest): + if (GameState.getMarsAirlockOpen()) + setCurrentAlternate(kAltMars35AirlockWest); + else + setCurrentAlternate(kAltMars35AirlockEast); + break; + case MakeRoomView(kMars60, kEast): + case MakeRoomView(kMars60, kWest): + if (GameState.getMarsAirlockOpen()) + setCurrentAlternate(kAltMars60AirlockEast); + else + setCurrentAlternate(kAltMars60AirlockWest); + break; + case MakeRoomView(kMars45, kNorth): + case MakeRoomView(kMars45, kSouth): + case MakeRoomView(kMars45, kEast): + case MakeRoomView(kMars45, kWest): + GameState.setMarsPodAtUpperPlatform(false); + setCurrentAlternate(kAltMarsPodAtMars45); + break; + case MakeRoomView(kMars46, kNorth): + case MakeRoomView(kMars46, kSouth): + case MakeRoomView(kMars46, kEast): + case MakeRoomView(kMars46, kWest): + case MakeRoomView(kMars47, kNorth): + case MakeRoomView(kMars47, kSouth): + case MakeRoomView(kMars47, kEast): + case MakeRoomView(kMars47, kWest): + if (GameState.getMarsPodAtUpperPlatform()) + setCurrentAlternate(kAltMarsNormal); + else + setCurrentAlternate(kAltMarsPodAtMars45); + break; + case MakeRoomView(kMars48, kNorth): + case MakeRoomView(kMars48, kSouth): + case MakeRoomView(kMars48, kEast): + case MakeRoomView(kMars48, kWest): + case MakeRoomView(kMars49, kNorth): + case MakeRoomView(kMars49, kEast): + case MakeRoomView(kMars49, kWest): + if (GameState.isTakenItemID(kAirMask)) + setCurrentAlternate(kAltMarsTookMask); + else + setCurrentAlternate(kAltMarsNormal); + break; + case MakeRoomView(kMars49, kSouth): + if (GameState.getMarsMaskOnFiller()) + setCurrentAlternate(kAltMarsMaskOnFiller); + else if (GameState.isTakenItemID(kAirMask)) + setCurrentAlternate(kAltMarsTookMask); + else + setCurrentAlternate(kAltMarsNormal); + break; + } + + Neighborhood::arriveAt(room, direction); + checkAirlockDoors(); + setUpReactorEnergyDrain(); + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kMars0A, kNorth): + if (!GameState.getMarsSeenTimeStream()) + startExtraLongSequence(kMarsArrivalFromTSA, kMars0AWatchShuttleDepart, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kMars07, kSouth): + case MakeRoomView(kMars13, kNorth): + if (!GameState.getMarsHeardCheckInMessage()) { + playSpotSoundSync(kMarsCheckInRequiredIn, kMarsCheckInRequiredOut); + GameState.setMarsHeardCheckInMessage(true); + } + break; + case MakeRoomView(kMars44, kWest): + if (GameState.getMarsReadyForShuttleTransport()) + startUpFromFinishedSpaceChase(); + else if (GameState.getMarsFinishedCanyonChase()) + startUpFromSpaceChase(); + else + _neighborhoodNotification.setNotificationFlags(kTimeForCanyonChaseFlag, kTimeForCanyonChaseFlag); + break; + case MakeRoomView(kMars10, kNorth): + if (!GameState.getMarsRobotThrownPlayer()) + startExtraSequence(kRobotThrowsPlayer, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kMars11, kSouth): + case MakeRoomView(kMars12, kSouth): + setCurrentActivation(kActivationReadyForKiosk); + break; + case MakeRoomView(kMars15, kWest): + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSecurityDown()) { + playSpotSoundSync(kMarsShuttle2DepartedIn, kMarsShuttle2DepartedOut); + restoreStriding(kMars17, kWest, kAltMarsNormal); + GameState.setMarsSecurityDown(true); + } + break; + case MakeRoomView(kMars17, kNorth): + case MakeRoomView(kMars17, kSouth): + case MakeRoomView(kMars17, kEast): + case MakeRoomView(kMars17, kWest): + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSecurityDown()) + forceStridingStop(kMars17, kWest, kAltMarsNormal); + + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSawRobotLeave()) { + startExtraSequence(kRobotOnWayToShuttle, kExtraCompletedFlag, kFilterNoInput); + restoreStriding(kMars19, kWest, kAltMarsNormal); + GameState.setMarsSawRobotLeave(true); + } + break; + case MakeRoomView(kMars19, kNorth): + case MakeRoomView(kMars19, kSouth): + case MakeRoomView(kMars19, kWest): + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSawRobotLeave()) + forceStridingStop(kMars19, kWest, kAltMarsNormal); + + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSecurityDown()) + forceStridingStop(kMars17, kWest, kAltMarsNormal); + break; + case MakeRoomView(kMars19, kEast): + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSawRobotLeave()) + forceStridingStop(kMars19, kWest, kAltMarsNormal); + + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSecurityDown()) + forceStridingStop(kMars17, kWest, kAltMarsNormal); + break; + case MakeRoomView(kMars32, kNorth): + if (!GameState.getMarsPodAtUpperPlatform()) { + playSpotSoundSync(kMarsPodArrivedUpperPlatformIn, kMarsPodArrivedUpperPlatformOut); + GameState.setMarsPodAtUpperPlatform(true); + } + break; + case MakeRoomView(kMars33North, kNorth): + setCurrentActivation(kActivationTunnelMapReady); + // Fall through... + case MakeRoomView(kMars33, kSouth): + case MakeRoomView(kMars33, kEast): + case MakeRoomView(kMars33, kWest): + case MakeRoomView(kMars32, kSouth): + case MakeRoomView(kMars32, kEast): + case MakeRoomView(kMars32, kWest): + if (!GameState.getMarsPodAtUpperPlatform()) + GameState.setMarsPodAtUpperPlatform(true); + break; + case MakeRoomView(kMars34, kNorth): + startExtraSequence(kMars34NorthPodGreeting, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kMars34, kSouth): + case MakeRoomView(kMars45, kNorth): + setCurrentActivation(kActivateMarsPodClosed); + break; + case MakeRoomView(kMars35, kWest): + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSecurityDown()) + forceStridingStop(kMars19, kWest, kAltMarsNormal); + // Fall through... + case MakeRoomView(kMars60, kEast): + if (!GameState.getMarsAirlockOpen()) + setCurrentActivation(kActivateReadyToPressurizeAirlock); + break; + case MakeRoomView(kMars35, kEast): + case MakeRoomView(kMars60, kWest): + if (GameState.getMarsAirlockOpen()) + setCurrentActivation(kActivateReadyToPressurizeAirlock); + break; + case MakeRoomView(kMars39, kWest): + if (GameState.getLastRoom() == kMarsMaze200) + GameState.setMarsPodAtUpperPlatform(false); + break; + case MakeRoomView(kMars45, kSouth): + // Set up maze doors here. + // Doing it here makes sure that it will be the same if the player comes + // back out of the maze and goes back in, but will vary if + // the player comes back down to the maze a second time. + GameState.setMarsMazeDoorPair1(_vm->getRandomBit()); + GameState.setMarsMazeDoorPair2(_vm->getRandomBit()); + GameState.setMarsMazeDoorPair3(_vm->getRandomBit()); + GameState.setMarsArrivedBelow(true); + break; + case MakeRoomView(kMars48, kEast): + if (!GameState.getMarsSeenRobotAtReactor()) { + // Preload the looping sound... + loadLoopSound2("Sounds/Mars/Robot Loop.aiff", 0, 0, 0); + startExtraSequence(kMars48RobotApproaches, kExtraCompletedFlag, kFilterNoInput); + } else if (!GameState.getMarsAvoidedReactorRobot()) { + loadLoopSound2("Sounds/Mars/Robot Loop.aiff", 0x100, 0, 0); + loopExtraSequence(kMars48RobotLoops); + _utilityFuse.primeFuse(kMarsRobotPatienceLimit); + _utilityFuse.setFunctionPtr(&robotTimerExpiredFunction, (void *)this); + _utilityFuse.lightFuse(); + } + break; + case MakeRoomView(kMars48, kSouth): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) { + loadLoopSound2("Sounds/Mars/Robot Loop.aiff", 0x100, 0, 0); + _utilityFuse.primeFuse(kMarsRobotPatienceLimit); + _utilityFuse.setFunctionPtr(&robotTimerExpiredFunction, (void *)this); + _utilityFuse.lightFuse(); + } + break; + case MakeRoomView(kMars49, kSouth): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) { + playSpotSoundSync(kMarsRobotTakesTransportIn, kMarsRobotTakesTransportOut); + playSpotSoundSync(kMarsPodDepartedLowerPlatformIn, kMarsPodDepartedLowerPlatformOut); + GameState.setMarsAvoidedReactorRobot(true); + GameState.setMarsPodAtUpperPlatform(true); + GameState.getScoringAvoidedRobot(); + } + + if (GameState.isTakenItemID(kAirMask)) + setCurrentActivation(kActivateHotSpotAlways); + else if (GameState.getMarsMaskOnFiller()) + setCurrentActivation(kActivateMaskOnFiller); + else + setCurrentActivation(kActivateMaskOnHolder); + break; + case MakeRoomView(kMars51, kWest): + case MakeRoomView(kMars50, kWest): + case MakeRoomView(kMars48, kWest): + if (GameState.getShieldOn()) + g_shield->setItemState(kShieldNormal); + g_energyMonitor->setEnergyDrainRate(kEnergyDrainNormal); + _vm->resetEnergyDeathReason(); + break; + case MakeRoomView(kMars52, kNorth): + case MakeRoomView(kMars52, kSouth): + case MakeRoomView(kMars52, kEast): + case MakeRoomView(kMars52, kWest): + case MakeRoomView(kMars54, kNorth): + case MakeRoomView(kMars54, kSouth): + case MakeRoomView(kMars54, kEast): + case MakeRoomView(kMars54, kWest): + case MakeRoomView(kMars56, kNorth): + case MakeRoomView(kMars56, kSouth): + case MakeRoomView(kMars56, kWest): + case MakeRoomView(kMars58, kNorth): + case MakeRoomView(kMars58, kSouth): + case MakeRoomView(kMars58, kEast): + case MakeRoomView(kMars58, kWest): + setCurrentActivation(kActivateReactorPlatformOut); + break; + case MakeRoomView(kMars56, kEast): + if (GameState.getMarsLockBroken()) { + setCurrentActivation(kActivateReactorAskOperation); + _privateFlags.setFlag(kMarsPrivatePlatformZoomedInFlag, true); + } else if (GameState.getMarsLockFrozen()) { + setCurrentActivation(kActivateReactorReadyForCrowBar); + _privateFlags.setFlag(kMarsPrivatePlatformZoomedInFlag, true); + _utilityFuse.primeFuse(kLockFreezeTimeLmit); + _utilityFuse.setFunctionPtr(&lockThawTimerExpiredFunction, (void *)this); + _utilityFuse.lightFuse(); + } else { + setCurrentActivation(kActivateReactorPlatformOut); + } + break; + case MakeRoomView(kMarsRobotShuttle, kEast): + setCurrentActivation(kActivationRobotHeadClosed); + break; + case MakeRoomView(kMarsMaze007, kNorth): + launchMaze007Robot(); + break; + case MakeRoomView(kMarsMaze015, kSouth): + launchMaze015Robot(); + break; + case MakeRoomView(kMarsMaze101, kEast): + launchMaze101Robot(); + break; + case MakeRoomView(kMarsMaze104, kWest): + launchMaze104Robot(); + break; + case MakeRoomView(kMarsMaze133, kSouth): + launchMaze133Robot(); + break; + case MakeRoomView(kMarsMaze136, kNorth): + launchMaze136Robot(); + break; + case MakeRoomView(kMarsMaze184, kWest): + launchMaze184Robot(); + break; + case MakeRoomView(kMarsMaze199, kSouth): + GameState.setScoringThreadedMaze(); + GameState.setMarsThreadedMaze(true); + break; + case MakeRoomView(kMarsDeathRoom, kNorth): + case MakeRoomView(kMarsDeathRoom, kSouth): + case MakeRoomView(kMarsDeathRoom, kEast): + case MakeRoomView(kMarsDeathRoom, kWest): + switch (GameState.getLastRoom()) { + case kMars39: + die(kDeathDidntLeaveBucket); + break; + case kMars46: + die(kDeathRunOverByPod); + break; + } + break; + } + + checkAirMask(); +} + +void Mars::shieldOn() { + setUpReactorEnergyDrain(); +} + +void Mars::shieldOff() { + setUpReactorEnergyDrain(); +} + +void Mars::turnTo(const DirectionConstant direction) { + switch (MakeRoomView(GameState.getCurrentRoom(), direction)) { + case MakeRoomView(kMars27, kNorth): + case MakeRoomView(kMars27, kSouth): + case MakeRoomView(kMars27, kEast): + case MakeRoomView(kMars29, kNorth): + case MakeRoomView(kMars29, kSouth): + case MakeRoomView(kMars29, kEast): + if (GameState.isTakenItemID(kMarsCard)) + setCurrentAlternate(kAltMarsTookCard); + break; + case MakeRoomView(kMars35, kNorth): + case MakeRoomView(kMars35, kSouth): + case MakeRoomView(kMars60, kNorth): + case MakeRoomView(kMars60, kSouth): + if (getCurrentActivation() == kActivateAirlockPressurized) + playSpotSoundSync(kMarsAirlockPressurizeIn, kMarsAirlockPressurizeOut); + break; + } + + Neighborhood::turnTo(direction); + + switch (MakeRoomView(GameState.getCurrentRoom(), direction)) { + case MakeRoomView(kMars11, kSouth): + case MakeRoomView(kMars12, kSouth): + setCurrentActivation(kActivationReadyForKiosk); + break; + case MakeRoomView(kMars18, kNorth): + if (GameState.getMarsPodAtUpperPlatform()) + setCurrentAlternate(kAltMarsPodAtMars34); + break; + case MakeRoomView(kMars22, kSouth): + if (!GameState.getMarsHeardCheckInMessage()) { + playSpotSoundSync(kMarsCheckInRequiredIn, kMarsCheckInRequiredOut); + GameState.setMarsHeardCheckInMessage(true); + } + break; + case MakeRoomView(kMars34, kSouth): + case MakeRoomView(kMars45, kNorth): + setCurrentActivation(kActivateMarsPodClosed); + break; + case MakeRoomView(kMars34, kNorth): + startExtraSequence(kMars34NorthPodGreeting, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kMars35, kEast): + case MakeRoomView(kMars60, kWest): + if (GameState.getMarsAirlockOpen()) + setCurrentActivation(kActivateReadyToPressurizeAirlock); + break; + case MakeRoomView(kMars60, kEast): + if (!GameState.getMarsAirlockOpen()) + setCurrentActivation(kActivateReadyToPressurizeAirlock); + break; + case MakeRoomView(kMars35, kWest): + if (!GameState.getMarsAirlockOpen()) + setCurrentActivation(kActivateReadyToPressurizeAirlock); + + // Do this here because this will be called after spinning the airlock after + // going through the gear room. + if (GameState.getMarsThreadedMaze()) + GameState.setScoringThreadedGearRoom(); + break; + case MakeRoomView(kMars48, kNorth): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) + die(kDeathDidntGetOutOfWay); + break; + case MakeRoomView(kMars48, kEast): + if (!GameState.getMarsSeenRobotAtReactor()) { + // Preload the looping sound... + loadLoopSound2("Sounds/Mars/Robot Loop.aiff", 0, 0, 0); + startExtraSequence(kMars48RobotApproaches, kExtraCompletedFlag, kFilterNoInput); + } else if (!GameState.getMarsAvoidedReactorRobot()) { + loopExtraSequence(kMars48RobotLoops); + } else if (GameState.isTakenItemID(kAirMask)) { + setCurrentAlternate(kAltMarsTookMask); + } else { + setCurrentAlternate(kAltMarsNormal); + } + break; + case MakeRoomView(kMars48, kWest): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) + die(kDeathDidntGetOutOfWay); + else if (GameState.isTakenItemID(kAirMask)) + setCurrentAlternate(kAltMarsTookMask); + else + setCurrentAlternate(kAltMarsNormal); + break; + case MakeRoomView(kMars49, kSouth): + if (GameState.isTakenItemID(kAirMask)) + setCurrentActivation(kActivateHotSpotAlways); + else + setCurrentActivation(kActivateMaskOnHolder); + break; + case MakeRoomView(kMars52, kNorth): + case MakeRoomView(kMars52, kSouth): + case MakeRoomView(kMars52, kEast): + case MakeRoomView(kMars52, kWest): + case MakeRoomView(kMars54, kNorth): + case MakeRoomView(kMars54, kSouth): + case MakeRoomView(kMars54, kEast): + case MakeRoomView(kMars54, kWest): + case MakeRoomView(kMars56, kNorth): + case MakeRoomView(kMars56, kSouth): + case MakeRoomView(kMars56, kEast): + case MakeRoomView(kMars56, kWest): + case MakeRoomView(kMars58, kNorth): + case MakeRoomView(kMars58, kSouth): + case MakeRoomView(kMars58, kEast): + case MakeRoomView(kMars58, kWest): + setCurrentActivation(kActivateReactorPlatformOut); + break; + case MakeRoomView(kMarsMaze007, kNorth): + launchMaze007Robot(); + break; + case MakeRoomView(kMarsMaze015, kSouth): + launchMaze015Robot(); + break; + case MakeRoomView(kMarsMaze101, kEast): + launchMaze101Robot(); + break; + case MakeRoomView(kMarsMaze104, kWest): + launchMaze104Robot(); + break; + case MakeRoomView(kMarsMaze133, kSouth): + launchMaze133Robot(); + break; + case MakeRoomView(kMarsMaze136, kNorth): + launchMaze136Robot(); + break; + case MakeRoomView(kMarsMaze184, kWest): + launchMaze184Robot(); + break; + } +} + +void Mars::activateOneHotspot(HotspotInfoTable::Entry &entry, Hotspot *hotspot) { + switch (hotspot->getObjectID()) { + case kMars57RedMoveSpotID: + case kMars57YellowMoveSpotID: + case kMars57GreenMoveSpotID: + if (!_choiceHighlight.choiceHighlighted(hotspot->getObjectID() - kMars57RedMoveSpotID)) + hotspot->setActive(); + break; + case kMars57BlueMoveSpotID: + if (_reactorStage >= 2 && !_choiceHighlight.choiceHighlighted(3)) + hotspot->setActive(); + break; + case kMars57PurpleMoveSpotID: + if (_reactorStage == 3 && !_choiceHighlight.choiceHighlighted(4)) + hotspot->setActive(); + break; + default: + Neighborhood::activateOneHotspot(entry, hotspot); + break; + } +} + +void Mars::activateHotspots() { + InventoryItem *item; + + Neighborhood::activateHotspots(); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars48, kEast): + if ((_navMovie.getFlags() & kLoopTimeBase) != 0 && _vm->getDragType() == kDragInventoryUse) + g_allHotspots.activateOneHotspot(kAttackRobotHotSpotID); + break; + case MakeRoomView(kMars56, kEast): + switch (getCurrentActivation()) { + case kActivateReactorReadyForNitrogen: + item = (InventoryItem *)g_allItems.findItemByID(kNitrogenCanister); + if (item->getItemState() != kNitrogenFull) + g_allHotspots.deactivateOneHotspot(kMars57DropNitrogenSpotID); + // Fall through... + case kActivateReactorReadyForCrowBar: + g_allHotspots.activateOneHotspot(kMars57CantOpenPanelSpotID); + break; + } + break; + case MakeRoomView(kMarsRobotShuttle, kEast): + if (_privateFlags.getFlag(kMarsPrivateGotMapChipFlag)) + g_allHotspots.deactivateOneHotspot(kRobotShuttleMapChipSpotID); + else + g_allHotspots.activateOneHotspot(kRobotShuttleMapChipSpotID); + + if (_privateFlags.getFlag(kMarsPrivateGotOpticalChipFlag)) + g_allHotspots.deactivateOneHotspot(kRobotShuttleOpticalChipSpotID); + else + g_allHotspots.activateOneHotspot(kRobotShuttleOpticalChipSpotID); + + if (_privateFlags.getFlag(kMarsPrivateGotShieldChipFlag)) + g_allHotspots.deactivateOneHotspot(kRobotShuttleShieldChipSpotID); + else + g_allHotspots.activateOneHotspot(kRobotShuttleShieldChipSpotID); + break; + default: + if (_privateFlags.getFlag(kMarsPrivateInSpaceChaseFlag)) { + if (GameState.getMarsReadyForShuttleTransport()) { + _shuttleTransportSpot.setActive(); + } else { + _energyChoiceSpot.setActive(); + _gravitonChoiceSpot.setActive(); + _tractorChoiceSpot.setActive(); + if (_weaponSelection != kNoWeapon) + _shuttleViewSpot.setActive(); + } + } + break; + } +} + +void Mars::clickInHotspot(const Input &input, const Hotspot *clickedSpot) { + switch (clickedSpot->getObjectID()) { + case kMars11NorthKioskSpotID: + case kMars12NorthKioskSpotID: + playSpotSoundSync(kMarsKioskBeepIn, kMarsKioskBeepOut); + Neighborhood::clickInHotspot(input, clickedSpot); + break; + case kMars11NorthKioskSightsSpotID: + case kMars12NorthKioskSightsSpotID: + playSpotSoundSync(kMarsKioskBeepIn, kMarsKioskBeepOut); + if (!startExtraSequenceSync(kMarsSightsInfo, kFilterAllInput)) + showExtraView(kMarsInfoKioskIntro); + break; + case kMars11NorthKioskColonySpotID: + case kMars12NorthKioskColonySpotID: + playSpotSoundSync(kMarsKioskBeepIn, kMarsKioskBeepOut); + if (!startExtraSequenceSync(kMarsColonyInfo, kFilterAllInput)) + showExtraView(kMarsInfoKioskIntro); + break; + case kMars33NorthMonitorSpotID: + switch (_lastExtra) { + case kMars33SlideShow1: + startExtraSequence(kMars33SlideShow2, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars33SlideShow2: + startExtraSequence(kMars33SlideShow3, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars33SlideShow3: + startExtraSequence(kMars33SlideShow4, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars33SlideShow4: + // Should never happen... + default: + startExtraSequence(kMars33SlideShow1, kExtraCompletedFlag, kFilterNoInput); + break; + } + break; + case kMars34SouthOpenStorageSpotID: + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars34SpotOpenNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars34SpotOpenWithBar, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars34SouthCloseStorageSpotID: + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars34SpotCloseNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars34SpotCloseWithBar, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars35WestPressurizeSpotID: + case kMars35EastPressurizeSpotID: + case kMars60WestPressurizeSpotID: + case kMars60EastPressurizeSpotID: + playSpotSoundSync(kMarsAirlockButtonBeepIn, kMarsAirlockButtonBeepOut); + playSpotSoundSync(kMarsAirlockPressurizeIn, kMarsAirlockPressurizeOut); + setCurrentActivation(kActivateAirlockPressurized); + break; + case kMars45NorthOpenStorageSpotID: + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars45SpotOpenNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars45SpotOpenWithBar, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars45NorthCloseStorageSpotID: + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars45SpotCloseNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars45SpotCloseWithBar, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars56ExtractSpotID: + if (GameState.isTakenItemID(kCardBomb)) { + startExtraSequence(kMars56ExtendNoBomb, kExtraCompletedFlag, kFilterNoInput); + setCurrentActivation(kActivateReactorPlatformIn); + } else { + startExtraSequence(kMars56ExtendWithBomb, kExtraCompletedFlag, kFilterNoInput); + setCurrentActivation(kActivateReactorAskLowerScreen); + } + break; + case kMars57UndoMoveSpotID: + playSpotSoundSync(kMarsColorMatchingButtonBeepIn, kMarsColorMatchingButtonBeepOut); + doUndoOneGuess(); + break; + case kMars57RedMoveSpotID: + playSpotSoundSync(kMarsColorMatchingButtonBeepIn, kMarsColorMatchingButtonBeepOut); + doReactorGuess(0); + break; + case kMars57YellowMoveSpotID: + playSpotSoundSync(kMarsColorMatchingButtonBeepIn, kMarsColorMatchingButtonBeepOut); + doReactorGuess(1); + break; + case kMars57GreenMoveSpotID: + playSpotSoundSync(kMarsColorMatchingButtonBeepIn, kMarsColorMatchingButtonBeepOut); + doReactorGuess(2); + break; + case kMars57BlueMoveSpotID: + playSpotSoundSync(kMarsColorMatchingButtonBeepIn, kMarsColorMatchingButtonBeepOut); + doReactorGuess(3); + break; + case kMars57PurpleMoveSpotID: + playSpotSoundSync(kMarsColorMatchingButtonBeepIn, kMarsColorMatchingButtonBeepOut); + doReactorGuess(4); + break; + case kShuttleEnergySpotID: + case kShuttleGravitonSpotID: + case kShuttleTractorSpotID: + case kShuttleViewSpotID: + case kShuttleTransportSpotID: + spaceChaseClick(input, clickedSpot->getObjectID()); + break; + default: + Neighborhood::clickInHotspot(input, clickedSpot); + break; + } +} + +InputBits Mars::getInputFilter() { + InputBits result = Neighborhood::getInputFilter(); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars49, kSouth): + if (GameState.getMarsMaskOnFiller()) + // Can't move when mask is on filler. + result &= ~kFilterAllDirections; + break; + case MakeRoomView(kMars52, kNorth): + case MakeRoomView(kMars52, kSouth): + case MakeRoomView(kMars52, kEast): + case MakeRoomView(kMars52, kWest): + case MakeRoomView(kMars54, kNorth): + case MakeRoomView(kMars54, kSouth): + case MakeRoomView(kMars54, kEast): + case MakeRoomView(kMars54, kWest): + case MakeRoomView(kMars56, kNorth): + case MakeRoomView(kMars56, kSouth): + case MakeRoomView(kMars56, kEast): + case MakeRoomView(kMars56, kWest): + case MakeRoomView(kMars58, kNorth): + case MakeRoomView(kMars58, kSouth): + case MakeRoomView(kMars58, kEast): + case MakeRoomView(kMars58, kWest): + if (_privateFlags.getFlag(kMarsPrivatePlatformZoomedInFlag)) + // Can't move when platform is extended. + result &= ~kFilterAllDirections; + break; + case MakeRoomView(kMars44, kWest): + if (_canyonChaseMovie.isMovieValid() && _canyonChaseMovie.isRunning()) + result &= ~kFilterAllDirections; + break; + } + + return result; +} + +// Only called when trying to pick up an item and the player can't (because +// the inventory is too full or because the player lets go of the item before +// dropping it into the inventory). +Hotspot *Mars::getItemScreenSpot(Item *item, DisplayElement *element) { + HotSpotID destSpotID; + + switch (item->getObjectID()) { + case kCardBomb: + destSpotID = kMars57GrabBombSpotID; + break; + case kMarsCard: + destSpotID = kMars31SouthCardSpotID; + break; + case kAirMask: + if (GameState.getMarsMaskOnFiller()) + destSpotID = kMars49AirFillingDropSpotID; + else + destSpotID = kMars49AirMaskSpotID; + break; + case kCrowbar: + if (GameState.getCurrentRoom() == kMars34) + destSpotID = kMars34SouthCrowbarSpotID; + else + destSpotID = kMars45NorthCrowbarSpotID; + break; + case kMapBiochip: + destSpotID = kRobotShuttleMapChipSpotID; + break; + case kOpticalBiochip: + destSpotID = kRobotShuttleOpticalChipSpotID; + break; + case kShieldBiochip: + destSpotID = kRobotShuttleShieldChipSpotID; + break; + default: + destSpotID = kNoHotSpotID; + break; + } + + if (destSpotID == kNoHotSpotID) + return Neighborhood::getItemScreenSpot(item, element); + + return g_allHotspots.findHotspotByID(destSpotID); +} + +void Mars::takeItemFromRoom(Item *item) { + switch (item->getObjectID()) { + case kAirMask: + setCurrentAlternate(kAltMarsTookMask); + break; + case kCardBomb: + _privateFlags.setFlag(kMarsPrivateDraggingBombFlag, true); + break; + case kMapBiochip: + _privateFlags.setFlag(kMarsPrivateGotMapChipFlag, true); + break; + case kShieldBiochip: + _privateFlags.setFlag(kMarsPrivateGotShieldChipFlag, true); + break; + case kOpticalBiochip: + _privateFlags.setFlag(kMarsPrivateGotOpticalChipFlag, true); + break; + } + + Neighborhood::takeItemFromRoom(item); +} + +void Mars::pickedUpItem(Item *item) { + switch (item->getObjectID()) { + case kAirMask: + setCurrentActivation(kActivateHotSpotAlways); + if (!GameState.getScoringGotOxygenMask()) { + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Mars/XM48SB", false, kWarningInterruption); + GameState.setScoringGotOxygenMask(); + } + break; + case kCrowbar: + GameState.setScoringGotCrowBar(); + g_AIArea->checkMiddleArea(); + break; + case kMarsCard: + GameState.setScoringGotMarsCard(); + g_AIArea->checkMiddleArea(); + break; + case kCardBomb: + GameState.setScoringGotCardBomb(); + if (GameState.getMarsLockBroken()) { + startExtraSequence(kMars57BackToNormal, kExtraCompletedFlag, kFilterNoInput); + GameState.setMarsLockBroken(false); + } + + _privateFlags.setFlag(kMarsPrivateDraggingBombFlag, false); + break; + case kMapBiochip: + if (_privateFlags.getFlag(kMarsPrivateGotMapChipFlag) && + _privateFlags.getFlag(kMarsPrivateGotShieldChipFlag) && + _privateFlags.getFlag(kMarsPrivateGotOpticalChipFlag)) { + GameState.setMarsFinished(true); + GameState.setScoringMarsGandhi(); + startExtraSequence(kMarsRobotHeadClose, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kShieldBiochip: + if (_privateFlags.getFlag(kMarsPrivateGotMapChipFlag) && + _privateFlags.getFlag(kMarsPrivateGotShieldChipFlag) && + _privateFlags.getFlag(kMarsPrivateGotOpticalChipFlag)) { + GameState.setMarsFinished(true); + GameState.setScoringMarsGandhi(); + startExtraSequence(kMarsRobotHeadClose, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kOpticalBiochip: + g_opticalChip->addAries(); + GameState.setScoringGotMarsOpMemChip(); + + if (_privateFlags.getFlag(kMarsPrivateGotMapChipFlag) && + _privateFlags.getFlag(kMarsPrivateGotShieldChipFlag) && + _privateFlags.getFlag(kMarsPrivateGotOpticalChipFlag)) { + GameState.setMarsFinished(true); + GameState.setScoringMarsGandhi(); + startExtraSequence(kMarsRobotHeadClose, kExtraCompletedFlag, kFilterNoInput); + } + break; + } +} + +void Mars::dropItemIntoRoom(Item *item, Hotspot *dropSpot) { + if (dropSpot->getObjectID() == kAttackRobotHotSpotID) { + _attackingItem = (InventoryItem *)item; + startExtraSequence(kMars48RobotDefends, kExtraCompletedFlag, kFilterNoInput); + loadLoopSound2(""); + } else { + switch (item->getObjectID()) { + case kMarsCard: + Neighborhood::dropItemIntoRoom(item, dropSpot); + if (dropSpot && dropSpot->getObjectID() == kMars34NorthCardDropSpotID) + startExtraSequence(kMarsTurnOnPod, kExtraCompletedFlag, kFilterNoInput); + break; + case kNitrogenCanister: + Neighborhood::dropItemIntoRoom(item, dropSpot); + if (dropSpot && dropSpot->getObjectID() == kMars57DropNitrogenSpotID) + startExtraSequence(kMars57FreezeLock, kExtraCompletedFlag, kFilterNoInput); + break; + case kCrowbar: + _utilityFuse.stopFuse(); + Neighborhood::dropItemIntoRoom(item, dropSpot); + if (dropSpot && dropSpot->getObjectID() == kMars57DropCrowBarSpotID) + startExtraSequence(kMars57BreakLock, kExtraCompletedFlag, kFilterNoInput); + break; + case kAirMask: + if (dropSpot) { + if (dropSpot->getObjectID() == kMars49AirFillingDropSpotID) { + if (!GameState.getMarsMaskOnFiller()) { + Neighborhood::dropItemIntoRoom(item, dropSpot); + startExtraSequence(kMars49SouthViewMaskFilling, kExtraCompletedFlag, kFilterNoInput); + } else { + setCurrentActivation(kActivateMaskOnFiller); + setCurrentAlternate(kAltMarsMaskOnFiller); + Neighborhood::dropItemIntoRoom(item, dropSpot); + } + } else if (dropSpot->getObjectID() == kMars49AirMaskSpotID) { + setCurrentAlternate(kAltMarsNormal); + setCurrentActivation(kActivateMaskOnHolder); + Neighborhood::dropItemIntoRoom(item, dropSpot); + } + } + break; + case kCardBomb: + _privateFlags.setFlag(kMarsPrivateDraggingBombFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + case kMapBiochip: + _privateFlags.setFlag(kMarsPrivateGotMapChipFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + case kShieldBiochip: + _privateFlags.setFlag(kMarsPrivateGotShieldChipFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + case kOpticalBiochip: + _privateFlags.setFlag(kMarsPrivateGotOpticalChipFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + default: + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + } + } +} + +void Mars::robotTiredOfWaiting() { + if (GameState.getCurrentRoomAndView() == MakeRoomView(kMars48, kEast)) { + if (_attackingItem) { + startExtraSequence(kMars48RobotKillsPlayer, kExtraCompletedFlag, kFilterNoInput); + loadLoopSound2(""); + } else { + _privateFlags.setFlag(kMarsPrivateRobotTiredOfWaitingFlag, true); + } + } else { + die(kDeathDidntGetOutOfWay); + } +} + +void Mars::turnLeft() { + if (isEventTimerRunning()) + cancelEvent(); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars34, kSouth): + if (_privateFlags.getFlag(kMarsPrivatePodStorageOpenFlag)) { + _privateFlags.setFlag(kMarsPrivatePodTurnLeftFlag, true); + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars34SpotCloseNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars34SpotCloseWithBar, kExtraCompletedFlag, kFilterNoInput); + } else { + Neighborhood::turnLeft(); + } + break; + case MakeRoomView(kMars45, kNorth): + if (_privateFlags.getFlag(kMarsPrivatePodStorageOpenFlag)) { + _privateFlags.setFlag(kMarsPrivatePodTurnLeftFlag, true); + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars45SpotCloseNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars45SpotCloseWithBar, kExtraCompletedFlag, kFilterNoInput); + } else { + Neighborhood::turnLeft(); + } + break; + default: + Neighborhood::turnLeft(); + break; + } +} + +void Mars::turnRight() { + if (isEventTimerRunning()) + cancelEvent(); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars34, kSouth): + if (_privateFlags.getFlag(kMarsPrivatePodStorageOpenFlag)) { + _privateFlags.setFlag(kMarsPrivatePodTurnRightFlag, true); + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars34SpotCloseNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars34SpotCloseWithBar, kExtraCompletedFlag, kFilterNoInput); + } else { + Neighborhood::turnRight(); + } + break; + case MakeRoomView(kMars45, kNorth): + if (_privateFlags.getFlag(kMarsPrivatePodStorageOpenFlag)) { + _privateFlags.setFlag(kMarsPrivatePodTurnRightFlag, true); + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars45SpotCloseNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars45SpotCloseWithBar, kExtraCompletedFlag, kFilterNoInput); + } else { + Neighborhood::turnRight(); + } + break; + default: + Neighborhood::turnRight(); + break; + } +} + +void Mars::receiveNotification(Notification *notification, const NotificationFlags flag) { + InventoryItem *item; + + Neighborhood::receiveNotification(notification, flag); + + if ((flag & kExtraCompletedFlag) != 0) { + _interruptionFilter = kFilterAllInput; + + switch (_lastExtra) { + case kMarsArrivalFromTSA: + GameState.setMarsSeenTimeStream(true); + loadAmbientLoops(); + playSpotSoundSync(kMarsShuttle1DepartedIn, kMarsShuttle1DepartedOut); + makeContinuePoint(); + break; + case kRobotThrowsPlayer: + GameState.setMarsRobotThrownPlayer(true); + GameState.setScoringThrownByRobot(); + restoreStriding(kMars08, kNorth, kAltMarsNormal); + arriveAt(kMars08, kNorth); + if (!GameState.getMarsHeardUpperPodMessage()) { + playSpotSoundSync(kMarsPodDepartedUpperPlatformIn, + kMarsPodDepartedUpperPlatformOut); + GameState.setMarsHeardUpperPodMessage(true); + } + break; + case kMarsInfoKioskIntro: + GameState.setScoringSawMarsKiosk(); + setCurrentActivation(kActivationKioskChoice); + break; + case kMars33SlideShow4: + GameState.setScoringSawTransportMap(); + setCurrentActivation(kActivateHotSpotAlways); + break; + case kMars34SpotOpenNoBar: + case kMars34SpotOpenWithBar: + case kMars45SpotOpenNoBar: + case kMars45SpotOpenWithBar: + _privateFlags.setFlag(kMarsPrivatePodStorageOpenFlag, true); + setCurrentActivation(kActivateMarsPodOpen); + break; + case kMars34SpotCloseNoBar: + case kMars34SpotCloseWithBar: + case kMars45SpotCloseNoBar: + case kMars45SpotCloseWithBar: + _privateFlags.setFlag(kMarsPrivatePodStorageOpenFlag, false); + setCurrentActivation(kActivateMarsPodClosed); + if (_privateFlags.getFlag(kMarsPrivatePodTurnLeftFlag)) { + _privateFlags.setFlag(kMarsPrivatePodTurnLeftFlag, false); + turnLeft(); + } else if (_privateFlags.getFlag(kMarsPrivatePodTurnRightFlag)) { + _privateFlags.setFlag(kMarsPrivatePodTurnRightFlag, false); + turnRight(); + } + break; + case kMarsTurnOnPod: + item = (InventoryItem *)g_allItems.findItemByID(kMarsCard); + _vm->addItemToInventory(item); + GameState.setScoringTurnedOnTransport(); + loadLoopSound1(""); + loadLoopSound2(""); + startExtraSequence(kMarsTakePodToMars45, kExtraCompletedFlag, kFilterNoInput); + break; + case kMarsTakePodToMars45: + arriveAt(kMars45, kSouth); + break; + case kMars35WestSpinAirlockToEast: + GameState.setMarsAirlockOpen(false); + setCurrentAlternate(kAltMars35AirlockEast); + turnTo(kWest); + setCurrentActivation(kActivateReadyToPressurizeAirlock); + g_airMask->airQualityChanged(); + checkAirMask(); + loadAmbientLoops(); + break; + case kMars35EastSpinAirlockToWest: + GameState.setMarsAirlockOpen(true); + setCurrentAlternate(kAltMars35AirlockWest); + turnTo(kEast); + setCurrentActivation(kActivateReadyToPressurizeAirlock); + g_airMask->airQualityChanged(); + checkAirMask(); + loadAmbientLoops(); + break; + case kMars48RobotApproaches: + loadLoopSound2("Sounds/Mars/Robot Loop.aiff", 0x100, 0, 0); + GameState.setMarsSeenRobotAtReactor(true); + loopExtraSequence(kMars48RobotLoops); + _utilityFuse.primeFuse(kMarsRobotPatienceLimit); + _utilityFuse.setFunctionPtr(&robotTimerExpiredFunction, (void *)this); + _utilityFuse.lightFuse(); + break; + case kMars48RobotDefends: + _vm->addItemToInventory(_attackingItem); + _attackingItem = 0; + if (_privateFlags.getFlag(kMarsPrivateRobotTiredOfWaitingFlag)) { + startExtraSequence(kMars48RobotKillsPlayer, kExtraCompletedFlag, kFilterNoInput); + loadLoopSound2("", 0x100, 0, 0); + } else { + loadLoopSound2("Sounds/Mars/Robot Loop.aiff", 0x100, 0, 0); + loopExtraSequence(kMars48RobotLoops, kExtraCompletedFlag); + } + break; + case kMars48RobotKillsPlayer: + loadLoopSound2(""); + die(kDeathDidntGetOutOfWay); + break; + case kMars49SouthViewMaskFilling: + setCurrentActivation(kActivateMaskOnFiller); + setCurrentAlternate(kAltMarsMaskOnFiller); + GameState.setMarsMaskOnFiller(true); + break; + case kMars58SpinLeft: + case kMars54SpinRight: + GameState.setScoringActivatedPlatform(); + arriveAt(kMars52, kEast); + break; + case kMars52SpinLeft: + case kMars56SpinRight: + GameState.setScoringActivatedPlatform(); + arriveAt(kMars54, kEast); + break; + case kMars54SpinLeft: + case kMars58SpinRight: + GameState.setScoringActivatedPlatform(); + arriveAt(kMars56, kEast); + break; + case kMars56SpinLeft: + case kMars52SpinRight: + GameState.setScoringActivatedPlatform(); + arriveAt(kMars58, kEast); + break; + case kMars52Extend: + case kMars54Extend: + case kMars56ExtendNoBomb: + case kMars58Extend: + GameState.setScoringActivatedPlatform(); + setCurrentActivation(kActivateReactorPlatformIn); + _privateFlags.setFlag(kMarsPrivatePlatformZoomedInFlag, true); + break; + case kMars53Retract: + case kMars55Retract: + case kMars57RetractWithBomb: + case kMars57RetractNoBomb: + case kMars59Retract: + GameState.setScoringActivatedPlatform(); + setCurrentActivation(kActivateReactorPlatformOut); + _privateFlags.setFlag(kMarsPrivatePlatformZoomedInFlag, false); + break; + case kMars56ExtendWithBomb: + playSpotSoundSync(kMustBeUnlockedIn, kMustBeUnlockedOut); + GameState.setScoringActivatedPlatform(); + _privateFlags.setFlag(kMarsPrivatePlatformZoomedInFlag, true); + break; + case kMars57CantOpenPanel: + GameState.setScoringActivatedPlatform(); + setCurrentActivation(kActivateReactorAskLowerScreen); + break; + case kMars57LowerScreenClosed: + case kMars57ThawLock: + setCurrentActivation(kActivateReactorReadyForNitrogen); + GameState.setMarsLockFrozen(false); + break; + case kMars57FreezeLock: + item = (InventoryItem *)g_allItems.findItemByID(kNitrogenCanister); + item->setItemState(kNitrogenEmpty); + _vm->addItemToInventory(item); + setCurrentActivation(kActivateReactorReadyForCrowBar); + GameState.setScoringUsedLiquidNitrogen(); + GameState.setMarsLockFrozen(true); + showExtraView(kMars57LockFrozenView); + _utilityFuse.primeFuse(kLockFreezeTimeLmit); + _utilityFuse.setFunctionPtr(&lockThawTimerExpiredFunction, (void *)this); + _utilityFuse.lightFuse(); + break; + case kMars57BreakLock: + item = (InventoryItem *)g_allItems.findItemByID(kCrowbar); + _vm->addItemToInventory(item); + GameState.setScoringUsedCrowBar(); + GameState.setMarsLockBroken(true); + GameState.setMarsLockFrozen(false); + startExtraLongSequence(kMars57OpenPanel, kMars57OpenPanelChoices, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars57OpenPanel: + case kMars57OpenPanelChoices: + setCurrentActivation(kActivateReactorAskOperation); + break; + case kMars57ShieldEvaluation: + case kMars57MeasureOutput: + setCurrentActivation(kActivateReactorRanEvaluation); + loopExtraSequence(kMars57ShieldOkayLoop); + break; + case kMars57RunDiagnostics: + setCurrentActivation(kActivateReactorRanDiagnostics); + GameState.setScoringFoundCardBomb(); + break; + case kMars57BombExplodes: + case kMars57BombExplodesInGame: + die(kDeathDidntDisarmMarsBomb); + break; + case kMars57BombAnalysis: + setCurrentActivation(kActivateReactorAnalyzed); + break; + case kMars57DontLink: + startExtraSequence(kMars57OpenPanelChoices, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars57CircuitLink: + setCurrentActivation(kActivateReactorInstructions); + break; + case kMars57GameLevel1: + setUpReactorLevel1(); + break; + case kMars57GameLevel2: + case kMars57GameLevel3: + setUpNextReactorLevel(); + break; + case kMars57GameSolved: + setCurrentActivation(kActivateReactorBombSafe); + break; + case kMars57ExposeBomb: + setCurrentActivation(kActivateReactorBombExposed); + _privateFlags.setFlag(kMarsPrivateBombExposedFlag, true); + break; + case kMars57BackToNormal: + setCurrentActivation(kActivateReactorPlatformIn); + _privateFlags.setFlag(kMarsPrivateBombExposedFlag, false); + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Mars/XM51SW", false, kWarningInterruption); + break; + case kMars60WestSpinAirlockToEast: + GameState.setMarsAirlockOpen(true); + setCurrentAlternate(kAltMars60AirlockEast); + turnTo(kWest); + setCurrentActivation(kActivateReadyToPressurizeAirlock); + g_airMask->airQualityChanged(); + checkAirMask(); + loadAmbientLoops(); + break; + case kMars60EastSpinAirlockToWest: + GameState.setMarsAirlockOpen(false); + setCurrentAlternate(kAltMars60AirlockWest); + turnTo(kEast); + setCurrentActivation(kActivateReadyToPressurizeAirlock); + g_airMask->airQualityChanged(); + checkAirMask(); + loadAmbientLoops(); + break; + case kMarsRobotHeadOpen: + setCurrentActivation(kActivationRobotHeadOpen); + break; + case kMarsRobotHeadClose: + recallToTSASuccess(); + break; + case kMarsMaze007RobotApproach: + case kMarsMaze015SouthRobotApproach: + case kMarsMaze101EastRobotApproach: + case kMarsMaze104WestLoop: + case kMarsMaze133SouthApproach: + case kMarsMaze136NorthApproach: + case kMarsMaze184WestLoop: + die(kDeathGroundByMazebot); + break; + } + } else if ((flag & kTimeForCanyonChaseFlag) != 0) { + doCanyonChase(); + } else if ((flag & kExplosionFinishedFlag) != 0) { + _explosions.stop(); + _explosions.hide(); + if (g_robotShip->isDead()) { + GameState.setMarsFinished(true); + _centerShuttleMovie.hide(); + _upperRightShuttleMovie.show(); + _upperRightShuttleMovie.setTime(kShuttleUpperRightTargetDestroyedTime); + _upperRightShuttleMovie.redrawMovieWorld(); + _rightDamageShuttleMovie.hide(); + playMovieSegment(&_rightShuttleMovie, kShuttleRightDestroyedStart, kShuttleRightDestroyedStop); + playSpotSoundSync(kShuttleDestroyedIn, kShuttleDestroyedOut); + throwAwayMarsShuttle(); + reinstateMonocleInterface(); + recallToTSASuccess(); + } + } else if ((flag & kTimeToTransportFlag) != 0) { + transportToRobotShip(); + } + + if (g_AIArea) + g_AIArea->checkMiddleArea(); +} + +void Mars::spotCompleted() { + Neighborhood::spotCompleted(); + + if (GameState.getCurrentRoom() == kMarsRobotShuttle) + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Mars/XN59WD", false, kWarningInterruption); +} + +void Mars::doCanyonChase() { + GameState.setScoringEnteredShuttle(); + setNextHandler(_vm); + throwAwayInterface(); + + _vm->_cursor->hide(); + + // Open the spot sounds movie again... + _spotSounds.initFromQuickTime(getSoundSpotsName()); + _spotSounds.setVolume(_vm->getSoundFXLevel()); + + Video::VideoDecoder *video = new Video::QuickTimeDecoder(); + if (!video->loadFile("Images/Mars/M44ESA.movie")) + error("Could not load interface->shuttle transition video"); + + while (!_vm->shouldQuit() && !video->endOfVideo()) { + if (video->needsUpdate()) { + const Graphics::Surface *frame = video->decodeNextFrame(); + + if (frame) + _vm->drawScaledFrame(frame, 0, 0); + } + + Common::Event event; + while (g_system->getEventManager()->pollEvent(event)) + ; + + g_system->delayMillis(10); + } + + delete video; + + if (_vm->shouldQuit()) + return; + + initOnePicture(&_shuttleInterface1, "Images/Mars/MCmain1.pict", kShuttleBackgroundOrder, kShuttle1Left, + kShuttle1Top, true); + initOnePicture(&_shuttleInterface2, "Images/Mars/MCmain2.pict", kShuttleBackgroundOrder, kShuttle2Left, + kShuttle2Top, true); + initOnePicture(&_shuttleInterface3, "Images/Mars/MCmain3.pict", kShuttleBackgroundOrder, kShuttle3Left, + kShuttle3Top, true); + initOnePicture(&_shuttleInterface4, "Images/Mars/MCmain4.pict", kShuttleBackgroundOrder, kShuttle4Left, + kShuttle4Top, true); + + initOneMovie(&_canyonChaseMovie, "Images/Mars/Canyon.movie", + kShuttleMonitorOrder, kShuttleWindowLeft, kShuttleWindowTop, true); + _canyonChaseMovie.setVolume(_vm->getSoundFXLevel()); + + loadLoopSound1("Sounds/Mars/Inside Cockpit.22K.8.AIFF"); + + // Swing shuttle around... + playMovieSegment(&_canyonChaseMovie, kShuttleSwingStart, kShuttleSwingStop); + + initOneMovie(&_leftShuttleMovie, "Images/Mars/Left Shuttle.movie", + kShuttleMonitorOrder, kShuttleLeftLeft, kShuttleLeftTop, false); + + initOneMovie(&_rightShuttleMovie, "Images/Mars/Right Shuttle.movie", + kShuttleMonitorOrder, kShuttleRightLeft, kShuttleRightTop, false); + + initOneMovie(&_lowerLeftShuttleMovie, "Images/Mars/Lower Left Shuttle.movie", kShuttleMonitorOrder, + kShuttleLowerLeftLeft, kShuttleLowerLeftTop, false); + + initOneMovie(&_lowerRightShuttleMovie, "Images/Mars/Lower Right Shuttle.movie", kShuttleMonitorOrder, + kShuttleLowerRightLeft, kShuttleLowerRightTop, false); + + initOneMovie(&_centerShuttleMovie, "Images/Mars/Center Shuttle.movie", + kShuttleMonitorOrder, kShuttleCenterLeft, kShuttleCenterTop, false); + + initOneMovie(&_upperLeftShuttleMovie, "Images/Mars/Upper Left Shuttle.movie", kShuttleMonitorOrder, + kShuttleUpperLeftLeft, kShuttleUpperLeftTop, false); + + initOneMovie(&_upperRightShuttleMovie, "Images/Mars/Upper Right Shuttle.movie", kShuttleMonitorOrder, + kShuttleUpperRightLeft, kShuttleUpperRightTop, false); + + initOneMovie(&_leftDamageShuttleMovie, "Images/Mars/Left Damage Shuttle.movie", + kShuttleStatusOrder, kShuttleLeftEnergyLeft, kShuttleLeftEnergyTop, false); + + initOneMovie(&_rightDamageShuttleMovie, "Images/Mars/Right Damage Shuttle.movie", + kShuttleStatusOrder, kShuttleRightEnergyLeft, kShuttleRightEnergyTop, false); + + _centerShuttleMovie.show(); + _centerShuttleMovie.setTime(kShuttleCenterBoardingTime); + playSpotSoundSync(kShuttleCockpitIn, kShuttleCockpitOut); + + _centerShuttleMovie.setTime(kShuttleCenterCheckTime); + playSpotSoundSync(kShuttleOnboardIn, kShuttleOnboardOut); + + _shuttleEnergyMeter.initShuttleEnergyMeter(); + _shuttleEnergyMeter.powerUpMeter(); + while (_shuttleEnergyMeter.isFading()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + g_system->updateScreen(); + } + + _leftShuttleMovie.show(); + playMovieSegment(&_leftShuttleMovie, kShuttleLeftIntroStart, kShuttleLeftIntroStop); + + _leftShuttleMovie.setTime(kShuttleLeftNormalTime); + _leftShuttleMovie.redrawMovieWorld(); + + _leftDamageShuttleMovie.show(); + playMovieSegment(&_leftDamageShuttleMovie); + + // Take it down a tick initially. This sets the time to the time of the last tick, + // so that subsequence drops will drop it down a tick. + _leftDamageShuttleMovie.setTime(_leftDamageShuttleMovie.getTime() - 40); + _leftDamageShuttleMovie.redrawMovieWorld(); + + _lowerRightShuttleMovie.show(); + _lowerRightShuttleMovie.setTime(kShuttleLowerRightOffTime); + _lowerRightShuttleMovie.redrawMovieWorld(); + _centerShuttleMovie.setTime(kShuttleCenterNavCompTime); + _centerShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttleNavigationIn, kShuttleNavigationOut); + + _centerShuttleMovie.setTime(kShuttleCenterCommTime); + _centerShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttleCommunicationIn, kShuttleCommunicationOut); + + _centerShuttleMovie.setTime(kShuttleCenterAllSystemsTime); + _centerShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttleAllSystemsIn, kShuttleAllSystemsOut); + + _centerShuttleMovie.setTime(kShuttleCenterSecureLooseTime); + _centerShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttleSecureLooseIn, kShuttleSecureLooseOut); + + _centerShuttleMovie.setTime(kShuttleCenterAutoTestTime); + _centerShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttleAutoTestingIn, kShuttleAutoTestingOut); + + _leftShuttleMovie.setTime(kShuttleLeftAutoTestTime); + _leftShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kMarsThrusterAutoTestIn, kMarsThrusterAutoTestOut); + _leftShuttleMovie.setTime(kShuttleLeftNormalTime); + _leftShuttleMovie.redrawMovieWorld(); + + _centerShuttleMovie.setTime(kShuttleCenterLaunchTime); + _centerShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttlePrepareForDropIn, kShuttlePrepareForDropOut); + + playSpotSoundSync(kShuttleAllClearIn, kShuttleAllClearOut); + + _centerShuttleMovie.setTime(kShuttleCenterEnterTubeTime); + _centerShuttleMovie.redrawMovieWorld(); + + _lowerLeftShuttleMovie.show(); + _lowerLeftShuttleMovie.setTime(kShuttleLowerLeftCollisionTime); + + loadLoopSound1(""); + + _canyonChaseMovie.setSegment(kCanyonChaseStart, kCanyonChaseStop); + _canyonChaseMovie.start(); + + startMarsTimer(kLaunchTubeReachedTime, kMovieTicksPerSecond, kMarsLaunchTubeReached); +} + +void Mars::startUpFromFinishedSpaceChase() { + setNextHandler(_vm); + throwAwayInterface(); + + initOnePicture(&_shuttleInterface1, "Images/Mars/MCmain1.pict", kShuttleBackgroundOrder, kShuttle1Left, + kShuttle1Top, true); + initOnePicture(&_shuttleInterface2, "Images/Mars/MCmain2.pict", kShuttleBackgroundOrder, kShuttle2Left, + kShuttle2Top, true); + initOnePicture(&_shuttleInterface3, "Images/Mars/MCmain3.pict", kShuttleBackgroundOrder, kShuttle3Left, + kShuttle3Top, true); + initOnePicture(&_shuttleInterface4, "Images/Mars/MCmain4.pict", kShuttleBackgroundOrder, kShuttle4Left, + kShuttle4Top, true); + + initOneMovie(&_leftShuttleMovie, "Images/Mars/Left Shuttle.movie", + kShuttleMonitorOrder, kShuttleLeftLeft, kShuttleLeftTop, false); + + initOneMovie(&_rightShuttleMovie, "Images/Mars/Right Shuttle.movie", + kShuttleMonitorOrder, kShuttleRightLeft, kShuttleRightTop, false); + + initOneMovie(&_lowerLeftShuttleMovie, "Images/Mars/Lower Left Shuttle.movie", kShuttleMonitorOrder, + kShuttleLowerLeftLeft, kShuttleLowerLeftTop, false); + + initOneMovie(&_lowerRightShuttleMovie, "Images/Mars/Lower Right Shuttle.movie", kShuttleMonitorOrder, + kShuttleLowerRightLeft, kShuttleLowerRightTop, false); + + initOneMovie(&_centerShuttleMovie, "Images/Mars/Center Shuttle.movie", + kShuttleMonitorOrder, kShuttleCenterLeft, kShuttleCenterTop, false); + + initOneMovie(&_upperLeftShuttleMovie, "Images/Mars/Upper Left Shuttle.movie", kShuttleMonitorOrder, + kShuttleUpperLeftLeft, kShuttleUpperLeftTop, false); + + initOneMovie(&_upperRightShuttleMovie, "Images/Mars/Upper Right Shuttle.movie", kShuttleMonitorOrder, + kShuttleUpperRightLeft, kShuttleUpperRightTop, false); + + initOneMovie(&_leftDamageShuttleMovie, "Images/Mars/Left Damage Shuttle.movie", + kShuttleStatusOrder, kShuttleLeftEnergyLeft, kShuttleLeftEnergyTop, false); + + initOneMovie(&_rightDamageShuttleMovie, "Images/Mars/Right Damage Shuttle.movie", + kShuttleStatusOrder, kShuttleRightEnergyLeft, kShuttleRightEnergyTop, false); + + _centerShuttleMovie.show(); + + _shuttleEnergyMeter.initShuttleEnergyMeter(); + _shuttleEnergyMeter.setEnergyValue(kFullShuttleEnergy); + + _leftShuttleMovie.show(); + _leftShuttleMovie.setTime(kShuttleLeftNormalTime); + _leftShuttleMovie.redrawMovieWorld(); + + _leftDamageShuttleMovie.show(); + _leftDamageShuttleMovie.setTime(_leftDamageShuttleMovie.getDuration() - 40); + _leftDamageShuttleMovie.redrawMovieWorld(); + + _lowerRightShuttleMovie.show(); + + _lowerLeftShuttleMovie.show(); + + loadLoopSound1("Sounds/Mars/Space Ambient.22K.8.AIFF"); + + initOneMovie(&_junk, "Images/Mars/Junk.movie", kShuttleJunkOrder, kShuttleJunkLeft, + kShuttleJunkTop, false); + + initOneMovie(&_explosions, "Images/Mars/Explosions.movie", kShuttleWeaponFrontOrder, 0, 0, false); + _explosionCallBack.initCallBack(&_explosions, kCallBackAtExtremes); + + _energyBeam.initShuttleWeapon(); + _gravitonCannon.initShuttleWeapon(); + + _upperLeftShuttleMovie.show(); + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftDimTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + + _rightShuttleMovie.show(); + _rightShuttleMovie.setTime(kShuttleRightIntroStop - 1); + _rightShuttleMovie.redrawMovieWorld(); + + _rightDamageShuttleMovie.show(); + _rightDamageShuttleMovie.setTime(40); + _rightDamageShuttleMovie.redrawMovieWorld(); + + _lowerLeftShuttleMovie.setTime(kShuttleLowerLeftAutopilotTime); + _lowerLeftShuttleMovie.redrawMovieWorld(); + + _shuttleTransportSpot.setArea(kShuttleTransportBounds); + _shuttleTransportSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + g_allHotspots.push_back(&_shuttleTransportSpot); + + _privateFlags.setFlag(kMarsPrivateInSpaceChaseFlag, true); + + _upperRightShuttleMovie.show(); + _upperRightShuttleMovie.setTime(kShuttleUpperRightOverloadTime); + _upperRightShuttleMovie.redrawMovieWorld(); + + _centerShuttleMovie.setTime(kShuttleCenterSafeTime); + _centerShuttleMovie.redrawMovieWorld(); + + _lowerRightShuttleMovie.setTime(kShuttleLowerRightTransportTime); + _lowerRightShuttleMovie.redrawMovieWorld(); + + initOneMovie(&_canyonChaseMovie, "Images/Mars/M98EAS.movie", kShuttleTractorBeamMovieOrder, + kShuttleWindowLeft, kShuttleWindowTop, true); + _canyonChaseMovie.setTime(_canyonChaseMovie.getDuration()); + _canyonChaseMovie.redrawMovieWorld(); +} + +void Mars::startUpFromSpaceChase() { + setNextHandler(_vm); + throwAwayInterface(); + + // Open the spot sounds movie again... + _spotSounds.initFromQuickTime(getSoundSpotsName()); + _spotSounds.setVolume(_vm->getSoundFXLevel());; + + initOnePicture(&_shuttleInterface1, "Images/Mars/MCmain1.pict", kShuttleBackgroundOrder, kShuttle1Left, + kShuttle1Top, true); + initOnePicture(&_shuttleInterface2, "Images/Mars/MCmain2.pict", kShuttleBackgroundOrder, kShuttle2Left, + kShuttle2Top, true); + initOnePicture(&_shuttleInterface3, "Images/Mars/MCmain3.pict", kShuttleBackgroundOrder, kShuttle3Left, + kShuttle3Top, true); + initOnePicture(&_shuttleInterface4, "Images/Mars/MCmain4.pict", kShuttleBackgroundOrder, kShuttle4Left, + kShuttle4Top, true); + + initOneMovie(&_leftShuttleMovie, "Images/Mars/Left Shuttle.movie", + kShuttleMonitorOrder, kShuttleLeftLeft, kShuttleLeftTop, false); + + initOneMovie(&_rightShuttleMovie, "Images/Mars/Right Shuttle.movie", + kShuttleMonitorOrder, kShuttleRightLeft, kShuttleRightTop, false); + + initOneMovie(&_lowerLeftShuttleMovie, "Images/Mars/Lower Left Shuttle.movie", kShuttleMonitorOrder, + kShuttleLowerLeftLeft, kShuttleLowerLeftTop, false); + + initOneMovie(&_lowerRightShuttleMovie, "Images/Mars/Lower Right Shuttle.movie", kShuttleMonitorOrder, + kShuttleLowerRightLeft, kShuttleLowerRightTop, false); + + initOneMovie(&_centerShuttleMovie, "Images/Mars/Center Shuttle.movie", + kShuttleMonitorOrder, kShuttleCenterLeft, kShuttleCenterTop, false); + + initOneMovie(&_upperLeftShuttleMovie, "Images/Mars/Upper Left Shuttle.movie", kShuttleMonitorOrder, + kShuttleUpperLeftLeft, kShuttleUpperLeftTop, false); + + initOneMovie(&_upperRightShuttleMovie, "Images/Mars/Upper Right Shuttle.movie", kShuttleMonitorOrder, + kShuttleUpperRightLeft, kShuttleUpperRightTop, false); + + initOneMovie(&_leftDamageShuttleMovie, "Images/Mars/Left Damage Shuttle.movie", + kShuttleStatusOrder, kShuttleLeftEnergyLeft, kShuttleLeftEnergyTop, false); + + initOneMovie(&_rightDamageShuttleMovie, "Images/Mars/Right Damage Shuttle.movie", + kShuttleStatusOrder, kShuttleRightEnergyLeft, kShuttleRightEnergyTop, false); + + _centerShuttleMovie.show(); + + _shuttleEnergyMeter.initShuttleEnergyMeter(); + _shuttleEnergyMeter.setEnergyValue(kFullShuttleEnergy); + + _leftShuttleMovie.show(); + _leftShuttleMovie.setTime(kShuttleLeftNormalTime); + _leftShuttleMovie.redrawMovieWorld(); + + _leftDamageShuttleMovie.show(); + _leftDamageShuttleMovie.setTime(_leftDamageShuttleMovie.getDuration() - 40); + _leftDamageShuttleMovie.redrawMovieWorld(); + + _lowerRightShuttleMovie.show(); + + _lowerLeftShuttleMovie.show(); + + loadLoopSound1("Sounds/Mars/Space Ambient.22K.8.AIFF"); + + initOneMovie(&_planetMovie, "Images/Mars/Planet.movie", kShuttlePlanetOrder, + kPlanetStartLeft, kPlanetStartTop, true); + _planetMovie.setFlags(kLoopTimeBase); + + initOneMovie(&_junk, "Images/Mars/Junk.movie", kShuttleJunkOrder, kShuttleJunkLeft, + kShuttleJunkTop, false); + + initOneMovie(&_explosions, "Images/Mars/Explosions.movie", kShuttleWeaponFrontOrder, 0, 0, false); + _explosionCallBack.initCallBack(&_explosions, kCallBackAtExtremes); + + _energyBeam.initShuttleWeapon(); + _gravitonCannon.initShuttleWeapon(); + + _upperLeftShuttleMovie.show(); + + _robotShip.initRobotShip(); + + _planetMovie.start(); + _planetMover.startMoving(&_planetMovie); + + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftDimTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + + _centerShuttleMovie.setTime(kShuttleCenterTargetSightedTime); + _centerShuttleMovie.redrawMovieWorld(); + + _lowerRightShuttleMovie.setTime(kShuttleLowerRightTrackingTime); + _lowerRightShuttleMovie.redrawMovieWorld(); + + _rightShuttleMovie.show(); + _rightShuttleMovie.setTime(kShuttleRightIntroStop - 1); + _rightShuttleMovie.redrawMovieWorld(); + + _rightDamageShuttleMovie.show(); + _rightDamageShuttleMovie.setTime(_rightDamageShuttleMovie.getDuration() - 40); + _rightDamageShuttleMovie.redrawMovieWorld(); + + _lowerLeftShuttleMovie.setTime(kShuttleLowerLeftAutopilotTime); + _lowerLeftShuttleMovie.redrawMovieWorld(); + + _robotShip.startMoving(); + + _shuttleHUD.initShuttleHUD(); + + _tractorBeam.startDisplaying(); + + _energyChoiceSpot.setArea(kShuttleEnergyBeamBounds); + _energyChoiceSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + g_allHotspots.push_back(&_energyChoiceSpot); + _gravitonChoiceSpot.setArea(kShuttleGravitonBounds); + _gravitonChoiceSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + g_allHotspots.push_back(&_gravitonChoiceSpot); + _tractorChoiceSpot.setArea(kShuttleTractorBounds); + _tractorChoiceSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + g_allHotspots.push_back(&_tractorChoiceSpot); + _shuttleViewSpot.setArea(kShuttleWindowLeft, kShuttleWindowTop, + kShuttleWindowLeft + kShuttleWindowWidth, kShuttleWindowTop + kShuttleWindowHeight); + _shuttleViewSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + g_allHotspots.push_back(&_shuttleViewSpot); + _shuttleTransportSpot.setArea(kShuttleTransportBounds); + _shuttleTransportSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + g_allHotspots.push_back(&_shuttleTransportSpot); + + _privateFlags.setFlag(kMarsPrivateInSpaceChaseFlag, true); + + startMarsTimer(kSpaceChaseTimeLimit, kOneTickPerSecond, kMarsSpaceChaseFinished); +} + +void Mars::setSoundFXLevel(const uint16 level) { + Neighborhood::setSoundFXLevel(level); + + if (_canyonChaseMovie.isMovieValid()) + _canyonChaseMovie.setVolume(level); + + if (_explosions.isMovieValid()) + _explosions.setVolume(level); +} + +void Mars::startMarsTimer(TimeValue time, TimeScale scale, MarsTimerCode code) { + _utilityFuse.primeFuse(time, scale); + _marsEvent.mars = this; + _marsEvent.event = code; + _utilityFuse.setFunctionPtr(&marsTimerFunction, (void *)&_marsEvent); + _utilityFuse.lightFuse(); +} + +void Mars::marsTimerExpired(MarsTimerEvent &event) { + Common::Rect r; + uint16 x, y; + + switch (event.event) { + case kMarsLaunchTubeReached: + _lowerLeftShuttleMovie.setTime(kShuttleLowerLeftTubeTime); + _lowerLeftShuttleMovie.redrawMovieWorld(); + startMarsTimer(kCanyonChaseFinishedTime, kMovieTicksPerSecond, kMarsCanyonChaseFinished); + break; + case kMarsCanyonChaseFinished: + GameState.setScoringEnteredLaunchTube(); + + while (_canyonChaseMovie.isRunning()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + _canyonChaseMovie.stop(); + _canyonChaseMovie.stopDisplaying(); + _canyonChaseMovie.releaseMovie(); + + _vm->_gfx->enableErase(); + + loadLoopSound1("Sounds/Mars/Space Ambient.22K.8.AIFF"); + + playSpotSoundSync(kShuttleConfiguringIn, kShuttleConfiguringOut); + playSpotSoundSync(kShuttleGeneratingIn, kShuttleGeneratingOut); + playSpotSoundSync(kShuttleBreakawayIn, kShuttleBreakawayOut); + playSpotSoundSync(kMarsAtmosphericBreakawayIn, kMarsAtmosphericBreakawayOut); + + initOneMovie(&_planetMovie, "Images/Mars/Planet.movie", kShuttlePlanetOrder, kPlanetStartLeft, kPlanetStartTop, true); + _planetMovie.setFlags(kLoopTimeBase); + + initOneMovie(&_junk, "Images/Mars/Junk.movie", kShuttleJunkOrder, kShuttleJunkLeft, kShuttleJunkTop, false); + + initOneMovie(&_explosions, "Images/Mars/Explosions.movie", kShuttleWeaponFrontOrder, 0, 0, false); + _explosionCallBack.initCallBack(&_explosions, kCallBackAtExtremes); + + _energyBeam.initShuttleWeapon(); + _gravitonCannon.initShuttleWeapon(); + + _centerShuttleMovie.setTime(kShuttleCenterWeaponsTime); + _centerShuttleMovie.redrawMovieWorld(); + + _upperLeftShuttleMovie.show(); + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftDampingTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + + _robotShip.initRobotShip(); + + _planetMovie.start(); + _planetMover.startMoving(&_planetMovie); + + playSpotSoundSync(kShuttleDamperDescIn, kShuttleDamperDescOut); + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftGravitonTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + + playSpotSoundSync(kShuttleGravitonDescIn, kShuttleGravitonDescOut); + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftTractorTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + + playSpotSoundSync(kShuttleTractorDescIn, kShuttleTractorDescOut); + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftDimTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + + _centerShuttleMovie.setTime(kShuttleCenterTargetSightedTime); + _centerShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttleTargetSightedIn, kShuttleTargetSightedOut); + + _lowerRightShuttleMovie.setTime(kShuttleLowerRightTrackingTime); + _lowerRightShuttleMovie.redrawMovieWorld(); + _rightShuttleMovie.show(); + playMovieSegment(&_rightShuttleMovie, kShuttleRightIntroStart, kShuttleRightIntroStop); + + _rightDamageShuttleMovie.show(); + playMovieSegment(&_rightDamageShuttleMovie); + + // Take it down a tick initially. This sets the time to the time of the last tick, + // so that subsequence drops will drop it down a tick. + _rightDamageShuttleMovie.setTime(_rightDamageShuttleMovie.getTime() - 40); + _rightDamageShuttleMovie.redrawMovieWorld(); + + _lowerLeftShuttleMovie.setTime(kShuttleLowerLeftAutopilotTime); + _lowerLeftShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttleAutopilotEngagedIn, kShuttleAutopilotEngagedOut); + + _robotShip.startMoving(); + + _shuttleHUD.initShuttleHUD(); + + _tractorBeam.startDisplaying(); + + _energyChoiceSpot.setArea(kShuttleEnergyBeamBounds); + _energyChoiceSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + g_allHotspots.push_back(&_energyChoiceSpot); + _gravitonChoiceSpot.setArea(kShuttleGravitonBounds); + _gravitonChoiceSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + g_allHotspots.push_back(&_gravitonChoiceSpot); + _tractorChoiceSpot.setArea(kShuttleTractorBounds); + _tractorChoiceSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + g_allHotspots.push_back(&_tractorChoiceSpot); + _shuttleViewSpot.setArea(kShuttleWindowLeft, kShuttleWindowTop, + kShuttleWindowLeft + kShuttleWindowWidth, kShuttleWindowTop + kShuttleWindowHeight); + _shuttleViewSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + g_allHotspots.push_back(&_shuttleViewSpot); + _shuttleTransportSpot.setArea(kShuttleTransportBounds); + _shuttleTransportSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + g_allHotspots.push_back(&_shuttleTransportSpot); + + _privateFlags.setFlag(kMarsPrivateInSpaceChaseFlag, true); + + playSpotSoundSync(kMarsCockpitChatterIn, kMarsCockpitChatterOut); + + GameState.setMarsFinishedCanyonChase(true); + + startMarsTimer(kSpaceChaseTimeLimit, kOneTickPerSecond, kMarsSpaceChaseFinished); + + _vm->_cursor->hideUntilMoved(); + break; + case kMarsSpaceChaseFinished: + // Player failed to stop the robot in time... + _interruptionFilter = kFilterNoInput; + + _rightShuttleMovie.setTime(kShuttleRightTargetLockTime); + _rightShuttleMovie.redrawMovieWorld(); + + _upperRightShuttleMovie.show(); + _upperRightShuttleMovie.setTime(kShuttleUpperRightLockedTime); + _upperRightShuttleMovie.redrawMovieWorld(); + + _rightShuttleMovie.setTime(kShuttleRightGravitonTime); + _rightShuttleMovie.redrawMovieWorld(); + _upperRightShuttleMovie.setTime(kShuttleUpperRightArmedTime); + _upperRightShuttleMovie.redrawMovieWorld(); + + _vm->delayShell(3, 1); + + x = _vm->getRandomNumber(19); + y = _vm->getRandomNumber(19); + + r = Common::Rect(kShuttleWindowMidH - x, kShuttleWindowMidV - y, + kShuttleWindowMidH - x + 20, kShuttleWindowMidV - y + 20); + showBigExplosion(r, kShuttleAlienShipOrder); + + while (_explosions.isRunning()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + g_system->delayMillis(10); + } + + throwAwayMarsShuttle(); + reinstateMonocleInterface(); + recallToTSAFailure(); + break; + default: + break; + } + + _interruptionFilter = kFilterAllInput; +} + +void Mars::throwAwayMarsShuttle() { + _shuttleInterface1.deallocateSurface(); + _shuttleInterface1.stopDisplaying(); + _shuttleInterface2.deallocateSurface(); + _shuttleInterface2.stopDisplaying(); + _shuttleInterface3.deallocateSurface(); + _shuttleInterface3.stopDisplaying(); + _shuttleInterface4.deallocateSurface(); + _shuttleInterface4.stopDisplaying(); + + _spotSounds.disposeSound(); + + _canyonChaseMovie.releaseMovie(); + _canyonChaseMovie.stopDisplaying(); + _leftShuttleMovie.releaseMovie(); + _leftShuttleMovie.stopDisplaying(); + _rightShuttleMovie.releaseMovie(); + _rightShuttleMovie.stopDisplaying(); + _lowerLeftShuttleMovie.releaseMovie(); + _lowerLeftShuttleMovie.stopDisplaying(); + _lowerRightShuttleMovie.releaseMovie(); + _lowerRightShuttleMovie.stopDisplaying(); + _centerShuttleMovie.releaseMovie(); + _centerShuttleMovie.stopDisplaying(); + _upperLeftShuttleMovie.releaseMovie(); + _upperLeftShuttleMovie.stopDisplaying(); + _upperRightShuttleMovie.releaseMovie(); + _upperRightShuttleMovie.stopDisplaying(); + _leftDamageShuttleMovie.releaseMovie(); + _leftDamageShuttleMovie.stopDisplaying(); + _rightDamageShuttleMovie.releaseMovie(); + _rightDamageShuttleMovie.stopDisplaying(); + + _shuttleEnergyMeter.disposeShuttleEnergyMeter(); + _robotShip.cleanUpRobotShip(); + _shuttleHUD.cleanUpShuttleHUD(); + _tractorBeam.stopDisplaying(); + _junk.releaseMovie(); + _junk.stopDisplaying(); + _energyBeam.cleanUpShuttleWeapon(); + _gravitonCannon.cleanUpShuttleWeapon(); + g_allHotspots.remove(&_energyChoiceSpot); + g_allHotspots.remove(&_gravitonChoiceSpot); + g_allHotspots.remove(&_tractorChoiceSpot); + g_allHotspots.remove(&_shuttleViewSpot); + g_allHotspots.remove(&_shuttleTransportSpot); + _explosions.releaseMovie(); + _explosions.stopDisplaying(); + + loadLoopSound1(""); +} + +void Mars::transportToRobotShip() { + throwAwayMarsShuttle(); + + Video::VideoDecoder *video = new Video::QuickTimeDecoder(); + if (!video->loadFile("Images/Mars/M98EAE.movie")) + error("Could not load shuttle->interface transition video"); + + while (!_vm->shouldQuit() && !video->endOfVideo()) { + if (video->needsUpdate()) { + const Graphics::Surface *frame = video->decodeNextFrame(); + + if (frame) + _vm->drawScaledFrame(frame, 0, 0); + } + + Common::Event event; + while (g_system->getEventManager()->pollEvent(event)) + ; + + g_system->delayMillis(10); + } + + delete video; + + if (_vm->shouldQuit()) + return; + + reinstateMonocleInterface(); + + g_energyMonitor->stopEnergyDraining(); + g_energyMonitor->restoreLastEnergyValue(); + _vm->resetEnergyDeathReason(); + g_energyMonitor->startEnergyDraining(); + + arriveAt(kMarsRobotShuttle, kEast); + + _navMovie.stop(); + _navMovie.setTime(_navMovie.getStart()); + _navMovie.start(); +} + +const int kRobotTooStrong = 1; +const int kTractorTooWeak = 2; +const int kCapturedRobotShip = 3; + +void Mars::spaceChaseClick(const Input &input, const HotSpotID id) { + Common::Point pt; + + switch (id) { + case kShuttleEnergySpotID: + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftDampingTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + _leftShuttleMovie.setTime(kShuttleLeftDampingTime); + _leftShuttleMovie.redrawMovieWorld(); + _shuttleHUD.hide(); + _weaponSelection = kEnergyBeam; + playSpotSoundSync(kShuttleDampingBeamIn, kShuttleDampingBeamOut); + break; + case kShuttleGravitonSpotID: + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftGravitonTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + _leftShuttleMovie.setTime(kShuttleLeftGravitonTime); + _leftShuttleMovie.redrawMovieWorld(); + _shuttleHUD.hide(); + _weaponSelection = kGravitonCannon; + playSpotSoundSync(kShuttleGravitonIn, kShuttleGravitonOut); + break; + case kShuttleTractorSpotID: + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftTractorTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + _leftShuttleMovie.setTime(kShuttleLeftTractorTime); + _leftShuttleMovie.redrawMovieWorld(); + _shuttleHUD.show(); + _weaponSelection = kTractorBeam; + playSpotSoundSync(kShuttleTractorBeamIn, kShuttleTractorBeamOut); + break; + case kShuttleViewSpotID: + switch (_weaponSelection) { + case kEnergyBeam: + if (_shuttleEnergyMeter.getEnergyValue() < kMinDampingEnergy) { + playSpotSoundSync(kShuttleEnergyTooLowIn, kShuttleEnergyTooLowOut); + } else { + if (_energyBeam.canFireWeapon()) { + _shuttleEnergyMeter.dropEnergyValue(kMinDampingEnergy); + input.getInputLocation(pt); + _energyBeam.fireWeapon(pt.x, pt.y); + playSpotSoundSync(kMarsEDBBlastIn, kMarsEDBBlastOut); + } + } + break; + case kGravitonCannon: + if (_shuttleEnergyMeter.getEnergyValue() < kMinGravitonEnergy) { + playSpotSoundSync(kShuttleEnergyTooLowIn, kShuttleEnergyTooLowOut); + } else { + if (_gravitonCannon.canFireWeapon()) { + _shuttleEnergyMeter.dropEnergyValue(kMinGravitonEnergy); + input.getInputLocation(pt); + _gravitonCannon.fireWeapon(pt.x, pt.y); + playSpotSoundSync(kMarsGravitonBlastIn, kMarsGravitonBlastOut); + } + } + break; + case kTractorBeam: + if (_shuttleHUD.isTargetLocked()) { + // play tractor beam sound? + _utilityFuse.stopFuse(); + + _tractorBeam.show(); + + int capture; + if (_rightDamageShuttleMovie.getTime() > 40) { + capture = kRobotTooStrong; + } else if (!_shuttleEnergyMeter.enoughEnergyForTractorBeam()) { + capture = kTractorTooWeak; + } else { + _robotShip.snareByTractorBeam(); + capture = kCapturedRobotShip; + _planetMover.dropPlanetOutOfSight(); + } + + _shuttleEnergyMeter.drainForTractorBeam(); + + while (_shuttleEnergyMeter.isFading()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + _shuttleEnergyMeter.setEnergyValue(_shuttleEnergyMeter.getEnergyValue()); + + switch (capture) { + case kRobotTooStrong: + _tractorBeam.hide(); + playSpotSoundSync(kShuttleBrokeFreeIn, kShuttleBrokeFreeOut); + _utilityFuse.lightFuse(); + break; + case kTractorTooWeak: + playSpotSoundSync(kShuttleCantHoldIn, kShuttleCantHoldOut); + _tractorBeam.hide(); + _utilityFuse.lightFuse(); + break; + case kCapturedRobotShip: + _tractorBeam.hide(); + _shuttleHUD.hide(); + _robotShip.cleanUpRobotShip(); + _planetMovie.stop(); + _planetMovie.stopDisplaying(); + _planetMovie.releaseMovie(); + + // Shameless reuse of a variable :P + initOneMovie(&_canyonChaseMovie, "Images/Mars/M98EAS.movie", kShuttleTractorBeamMovieOrder, + kShuttleWindowLeft, kShuttleWindowTop, true); + _canyonChaseMovie.redrawMovieWorld(); + playMovieSegment(&_canyonChaseMovie, 0, _canyonChaseMovie.getDuration()); + + // wait here until any junk clears... + while (_junk.junkFlying()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + _upperRightShuttleMovie.show(); + _upperRightShuttleMovie.setTime(kShuttleUpperRightOverloadTime); + _upperRightShuttleMovie.redrawMovieWorld(); + + playSpotSoundSync(kShuttleOverloadedIn, kShuttleOverloadedOut); + _centerShuttleMovie.setTime(kShuttleCenterVerifyingTime); + _centerShuttleMovie.redrawMovieWorld(); + + playSpotSoundSync(kShuttleCoordinatesIn, kShuttleCoordinatesOut); + _centerShuttleMovie.setTime(kShuttleCenterScanningTime); + _centerShuttleMovie.redrawMovieWorld(); + + playSpotSoundSync(kShuttleScanningIn, kShuttleScanningOut); + _centerShuttleMovie.setTime(kShuttleCenterSafeTime); + _centerShuttleMovie.redrawMovieWorld(); + + playSpotSoundSync(kShuttleSafeIn, kShuttleSafeOut); + _lowerRightShuttleMovie.setTime(kShuttleLowerRightTransportTime); + _lowerRightShuttleMovie.redrawMovieWorld(); + GameState.setMarsReadyForShuttleTransport(true); + break; + } + } else { + playSpotSoundSync(kShuttleTractorLimitedIn, kShuttleTractorLimitedOut); + } + break; + default: + break; + } + break; + case kShuttleTransportSpotID: + _lowerRightShuttleMovie.setTime(kShuttleLowerRightTransportHiliteTime); + _lowerRightShuttleMovie.redrawMovieWorld(); + _neighborhoodNotification.setNotificationFlags(kTimeToTransportFlag, kTimeToTransportFlag); + break; + } +} + +void Mars::showBigExplosion(const Common::Rect &r, const DisplayOrder order) { + if (_explosions.isMovieValid()) { + _explosions.setDisplayOrder(order); + + Common::Rect r2 = r; + int dx = r.width() / 2; + int dy = r.height() / 2; + r2.left -= dx; + r2.right += dx; + r2.top -= dy; + r2.bottom += dy; + + _explosions.setBounds(r2); + _explosions.show(); + _explosions.stop(); + _explosions.setSegment(kBigExplosionStart, kBigExplosionStop); + _explosions.setTime(kBigExplosionStart); + _explosionCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _explosions.start(); + } +} + +void Mars::showLittleExplosion(const Common::Rect &r, const DisplayOrder order) { + if (_explosions.isMovieValid()) { + _explosions.setDisplayOrder(order); + + Common::Rect r2 = r; + int dx = r.width() / 2; + int dy = r.height() / 2; + r2.left -= dx; + r2.right += dx; + r2.top -= dy; + r2.bottom += dy; + _explosions.setBounds(r2); + + _explosions.show(); + _explosions.stop(); + _explosions.setSegment(kLittleExplosionStart, kLittleExplosionStop); + _explosions.setTime(kLittleExplosionStart); + _explosionCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _explosions.start(); + } +} + +void Mars::hitByJunk() { + _leftDamageShuttleMovie.setTime(_leftDamageShuttleMovie.getTime() - 40); + _leftDamageShuttleMovie.redrawMovieWorld(); + + playSpotSoundSync(kMarsJunkCollisionIn, kMarsJunkCollisionOut); + + if (_leftDamageShuttleMovie.getTime() == 0) { + die(kDeathRanIntoSpaceJunk); + } else { + TimeValue t = _leftDamageShuttleMovie.getTime() / 40; + + if (t == 1) + playSpotSoundSync(kShuttleHullBreachIn, kShuttleHullBreachOut); + + t = _leftShuttleMovie.getTime(); + _leftShuttleMovie.setTime(kShuttleLeftDamagedTime); + _leftShuttleMovie.redrawMovieWorld(); + _vm->delayShell(1, 3); + _leftShuttleMovie.setTime(t); + _leftShuttleMovie.redrawMovieWorld(); + } +} + +void Mars::setUpNextDropTime() { + _robotShip.setUpNextDropTime(); +} + +void Mars::decreaseRobotShuttleEnergy(const int delta, Common::Point impactPoint) { + _rightDamageShuttleMovie.setTime(_rightDamageShuttleMovie.getTime() - 40 * delta); + _rightDamageShuttleMovie.redrawMovieWorld(); + + if (_rightDamageShuttleMovie.getTime() == 0) { + Common::Rect r; + _robotShip.getShuttleBounds(r); + int size = MAX(r.width(), r.height()); + r = Common::Rect::center(impactPoint.x, impactPoint.y, size, size); + _robotShip.killRobotShip(); + showBigExplosion(r, kShuttleRobotShipOrder); + } else if (delta > 1) { + Common::Rect r; + _robotShip.getShuttleBounds(r); + int size = MIN(r.width(), r.height()); + r = Common::Rect::center(impactPoint.x, impactPoint.y, size, size); + showLittleExplosion(r, kShuttleWeaponBackOrder); + TimeValue t = _rightShuttleMovie.getTime(); + _rightShuttleMovie.setTime(kShuttleRightDamagedTime); + _rightShuttleMovie.redrawMovieWorld(); + _vm->delayShell(1, 3); + _rightShuttleMovie.setTime(t); + _rightShuttleMovie.redrawMovieWorld(); + } + + if (_rightDamageShuttleMovie.getTime() <= 40) { + GameState.setScoringStoppedRobotsShuttle(); + if (!GameState.getMarsHitRobotWithCannon()) + GameState.setScoringMarsGandhi(); + } +} + +void Mars::updateCursor(const Common::Point cursorLocation, const Hotspot *cursorSpot) { + if (cursorSpot && cursorSpot->getObjectID() == kShuttleViewSpotID) { + if (_weaponSelection != kNoWeapon) + _vm->_cursor->setCurrentFrameIndex(6); + else + _vm->_cursor->setCurrentFrameIndex(0); + } else { + Neighborhood::updateCursor(cursorLocation, cursorSpot); + } +} + +AirQuality Mars::getAirQuality(const RoomID room) { + if ((room >= kMars36 && room <= kMars39) || (room >= kMarsMaze004 && room <= kMarsMaze200)) + return kAirQualityVacuum; + if (room == kMars35 && !GameState.getMarsAirlockOpen()) + return kAirQualityVacuum; + if (room == kMars60 && !GameState.getMarsAirlockOpen()) + return kAirQualityVacuum; + + return Neighborhood::getAirQuality(room); +} + +// Start up panting sound if necessary. + +void Mars::checkAirMask() { + Neighborhood::checkAirMask(); + + if (getAirQuality(GameState.getCurrentRoom()) == kAirQualityVacuum) { + if (g_airMask->isAirMaskOn()) { + if (_noAirFuse.isFuseLit()) { + _noAirFuse.stopFuse(); + loadLoopSound2(""); + loadAmbientLoops(); + playSpotSoundSync(kMarsOxyMaskOnIn, kMarsOxyMaskOnOut); + } + } else { + if (!_noAirFuse.isFuseLit()) { + loadLoopSound2("Sounds/Mars/SukWind1.22K.AIFF"); + _noAirFuse.primeFuse(kVacuumSurvivalTimeLimit); + _noAirFuse.lightFuse(); + } + } + } else { + if (_noAirFuse.isFuseLit()) { + _noAirFuse.stopFuse(); + loadLoopSound2(""); + loadAmbientLoops(); + } + } +} + +void Mars::airStageExpired() { + if (((PegasusEngine *)g_engine)->playerHasItemID(kAirMask)) + die(kDeathNoAirInMaze); + else + die(kDeathNoMaskInMaze); +} + +void Mars::lockThawed() { + startExtraSequence(kMars57ThawLock, kExtraCompletedFlag, kFilterNoInput); +} + +void Mars::setUpReactorLevel1() { + _reactorStage = 1; + makeColorSequence(); + _guessObject.initReactorGuess(); + _undoPict.initFromPICTResource(_vm->_resFork, kReactorUndoHilitePICTID); + _undoPict.setDisplayOrder(kMonitorLayer); + _undoPict.moveElementTo(kUndoHiliteLeft, kUndoHiliteTop); + _undoPict.startDisplaying(); + _guessHistory.initReactorHistory(); + _choiceHighlight.initReactorChoiceHighlight(); + setCurrentActivation(kActivateReactorInGame); + _bombFuse.primeFuse(kColorMatchingTimeLimit); + _bombFuse.setFunctionPtr(&bombTimerExpiredInGameFunction, (void *)this); + _bombFuse.lightFuse(); +} + +void Mars::setUpNextReactorLevel() { + _guessObject.show(); + _guessHistory.show(); + _guessHistory.clearHistory(); + _choiceHighlight.show(); + _reactorStage++; + makeColorSequence(); +} + +void Mars::makeColorSequence() { + int32 code[5]; + int32 highest = _reactorStage + 2; + + for (int32 i = 0; i < highest; i++) + code[i] = i; + + _vm->shuffleArray(code, highest); + _currentGuess[0] = -1; + _currentGuess[1] = -1; + _currentGuess[2] = -1; + _nextGuess = 0; + _guessObject.setGuess(-1, -1, -1); + _guessHistory.setAnswer(code[0], code[1], code[2]); +} + +void Mars::doUndoOneGuess() { + if (_nextGuess > 0) { + _undoPict.show(); + _vm->delayShell(1, 2); + _undoPict.hide(); + _nextGuess--; + _currentGuess[_nextGuess] = -1; + _guessObject.setGuess(_currentGuess[0], _currentGuess[1], _currentGuess[2]); + _choiceHighlight.resetHighlight(); + + if (_currentGuess[0] != -1) { + _choiceHighlight.highlightChoice(_currentGuess[0]); + + if (_currentGuess[1] != -1) { + _choiceHighlight.highlightChoice(_currentGuess[1]); + + if (_currentGuess[2] != -1) + _choiceHighlight.highlightChoice(_currentGuess[2]); + } + } + } +} + +void Mars::doReactorGuess(int32 guess) { + _choiceHighlight.highlightChoice(guess); + _currentGuess[_nextGuess] = guess; + _guessObject.setGuess(_currentGuess[0], _currentGuess[1], _currentGuess[2]); + + switch (guess) { + case 0: + playSpotSoundSync(kColorMatchRedIn, kColorMatchRedOut); + break; + case 1: + playSpotSoundSync(kColorMatchYellowIn, kColorMatchYellowOut); + break; + case 2: + playSpotSoundSync(kColorMatchGreenIn, kColorMatchGreenOut); + break; + case 3: + playSpotSoundSync(kColorMatchBlueIn, kColorMatchBlueOut); + break; + case 4: + playSpotSoundSync(kColorMatchPurpleIn, kColorMatchPurpleOut); + break; + } + + _nextGuess++; + + if (_nextGuess == 3) { + _vm->delayShell(1, 2); + _nextGuess = 0; + _guessHistory.addGuess(_currentGuess[0], _currentGuess[1], _currentGuess[2]); + + switch (_guessHistory.getCurrentNumCorrect()) { + case 0: + playSpotSoundSync(kColorMatchZeroNodesIn, kColorMatchZeroNodesOut); + break; + case 1: + playSpotSoundSync(kColorMatchOneNodeIn, kColorMatchOneNodeOut); + break; + case 2: + playSpotSoundSync(kColorMatchTwoNodesIn, kColorMatchTwoNodesOut); + break; + case 3: + playSpotSoundSync(kColorMatchThreeNodesIn, kColorMatchThreeNodesOut); + break; + } + + _currentGuess[0] = -1; + _currentGuess[1] = -1; + _currentGuess[2] = -1; + _guessObject.setGuess(-1, -1, -1); + _choiceHighlight.resetHighlight(); + + if (_guessHistory.isSolved()) { + _guessHistory.showAnswer(); + _vm->delayShell(1, 2); + _guessObject.hide(); + _guessHistory.hide(); + _choiceHighlight.hide(); + + switch (_reactorStage) { + case 1: + startExtraSequence(kMars57GameLevel2, kExtraCompletedFlag, kFilterNoInput); + break; + case 2: + startExtraSequence(kMars57GameLevel3, kExtraCompletedFlag, kFilterNoInput); + break; + case 3: + _bombFuse.stopFuse(); + _guessObject.disposeReactorGuess(); + _undoPict.deallocateSurface(); + _guessHistory.disposeReactorHistory(); + _choiceHighlight.disposeReactorChoiceHighlight(); + GameState.setScoringDisarmedCardBomb(); + startExtraSequence(kMars57GameSolved, kExtraCompletedFlag, kFilterNoInput); + break; + } + } else if (_guessHistory.getNumGuesses() >= 5) { + _vm->delayShell(2, 1); + bombExplodesInGame(); + } + } +} + +void Mars::bombExplodesInGame() { + _guessObject.disposeReactorGuess(); + _undoPict.deallocateSurface(); + _guessHistory.disposeReactorHistory(); + _choiceHighlight.disposeReactorChoiceHighlight(); + startExtraSequence(kMars57BombExplodesInGame, kExtraCompletedFlag, kFilterNoInput); +} + +void Mars::didntFindBomb() { + die(kDeathDidntFindMarsBomb); +} + +Common::String Mars::getBriefingMovie() { + Common::String movieName = Neighborhood::getBriefingMovie(); + + if (!movieName.empty()) + return movieName; + + return "Images/AI/Mars/XM01"; +} + +Common::String Mars::getEnvScanMovie() { + Common::String movieName = Neighborhood::getEnvScanMovie(); + + if (movieName.empty()) { + RoomID room = GameState.getCurrentRoom(); + + if (room >= kMars0A && room <= kMars21) + return "Images/AI/Mars/XME1"; + else if (room >= kMars22 && room <= kMars31South) + return "Images/AI/Mars/XME2"; + else if (room >= kMars52 && room <= kMars58) + return "Images/AI/Mars/XMREACE"; + + return "Images/AI/Mars/XME3"; + } + + return movieName; +} + +uint Mars::getNumHints() { + uint numHints = Neighborhood::getNumHints(); + + if (numHints == 0) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars27, kNorth): + case MakeRoomView(kMars28, kNorth): + case MakeRoomView(kMars49, kSouth): + numHints = 1; + break; + case MakeRoomView(kMars31, kSouth): + case MakeRoomView(kMars31South, kSouth): + if (!GameState.isTakenItemID(kMarsCard)) + numHints = 1; + break; + case MakeRoomView(kMars34, kNorth): + if (!GameState.isTakenItemID(kMarsCard)) + numHints = 2; + break; + case MakeRoomView(kMars34, kSouth): + case MakeRoomView(kMars45, kNorth): + if (!GameState.isTakenItemID(kCrowbar)) + numHints = 1; + break; + case MakeRoomView(kMars51, kEast): + if (GameState.isCurrentDoorOpen() && !GameState.getShieldOn()) { + if (GameState.isTakenItemID(kShieldBiochip)) + numHints = 1; + else + numHints = 2; + } + break; + case MakeRoomView(kMars52, kNorth): + case MakeRoomView(kMars52, kSouth): + case MakeRoomView(kMars52, kEast): + case MakeRoomView(kMars52, kWest): + case MakeRoomView(kMars54, kNorth): + case MakeRoomView(kMars54, kSouth): + case MakeRoomView(kMars54, kEast): + case MakeRoomView(kMars54, kWest): + case MakeRoomView(kMars56, kNorth): + case MakeRoomView(kMars56, kSouth): + case MakeRoomView(kMars56, kWest): + case MakeRoomView(kMars58, kNorth): + case MakeRoomView(kMars58, kSouth): + case MakeRoomView(kMars58, kEast): + case MakeRoomView(kMars58, kWest): + if (!GameState.getShieldOn()) { + if (GameState.isTakenItemID(kShieldBiochip)) + numHints = 1; + else + numHints = 2; + } + break; + case MakeRoomView(kMars56, kEast): + if (getCurrentActivation() == kActivateReactorReadyForNitrogen) { + if ((ExtraID)_lastExtra == kMars57LowerScreenClosed) + numHints = 3; + } else if (getCurrentActivation() == kActivateReactorPlatformOut) { + if (!GameState.getShieldOn()) { + if (GameState.isTakenItemID(kShieldBiochip)) + numHints = 1; + else + numHints = 2; + } + } + break; + } + } + + return numHints; +} + +Common::String Mars::getHintMovie(uint hintNum) { + Common::String movieName = Neighborhood::getHintMovie(hintNum); + + if (movieName.empty()) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars27, kNorth): + case MakeRoomView(kMars28, kNorth): + return "Images/AI/Globals/XGLOB5C"; + case MakeRoomView(kMars31, kSouth): + case MakeRoomView(kMars31South, kSouth): + case MakeRoomView(kMars34, kSouth): + case MakeRoomView(kMars45, kNorth): + return "Images/AI/Globals/XGLOB1C"; + case MakeRoomView(kMars34, kNorth): + if (hintNum == 1) + return "Images/AI/Globals/XGLOB2C"; + + return "Images/AI/Globals/XGLOB3G"; + case MakeRoomView(kMars49, kSouth): + if (GameState.isTakenItemID(kAirMask)) + return "Images/AI/Globals/XGLOB3E"; + + return "Images/AI/Globals/XGLOB1C"; + case MakeRoomView(kMars51, kEast): + if (GameState.isTakenItemID(kShieldBiochip)) + return "Images/AI/Mars/XM52NW"; + + if (hintNum == 1) + return "Images/AI/Globals/XGLOB2D"; + + return "Images/AI/Globals/XGLOB3F"; + case MakeRoomView(kMars52, kNorth): + case MakeRoomView(kMars52, kSouth): + case MakeRoomView(kMars52, kEast): + case MakeRoomView(kMars52, kWest): + case MakeRoomView(kMars54, kNorth): + case MakeRoomView(kMars54, kSouth): + case MakeRoomView(kMars54, kEast): + case MakeRoomView(kMars54, kWest): + case MakeRoomView(kMars56, kNorth): + case MakeRoomView(kMars56, kSouth): + case MakeRoomView(kMars56, kWest): + case MakeRoomView(kMars58, kNorth): + case MakeRoomView(kMars58, kSouth): + case MakeRoomView(kMars58, kEast): + case MakeRoomView(kMars58, kWest): + if (hintNum == 1) { + if (GameState.isTakenItemID(kShieldBiochip)) + return "Images/AI/Mars/XM52NW"; + + return "Images/AI/Globals/XGLOB2D"; + } + + return "Images/AI/Globals/XGLOB3F"; + case MakeRoomView(kMars56, kEast): + if (getCurrentActivation() == kActivateReactorReadyForNitrogen) + return Common::String::format("Images/AI/Mars/XM57SD%d", hintNum); + + if (hintNum == 1) { + if (GameState.isTakenItemID(kShieldBiochip)) + return "Images/AI/Mars/XM52NW"; + + return "Images/AI/Globals/XGLOB2D"; + } + + return "Images/AI/Globals/XGLOB3F"; + } + } + + return movieName; +} + +bool Mars::inColorMatchingGame() { + return _guessObject.isDisplaying(); +} + +bool Mars::canSolve() { + return GameState.getCurrentRoomAndView() == MakeRoomView(kMars56, kEast) && (getCurrentActivation() == kActivateReactorReadyForNitrogen || + getCurrentActivation() == kActivateReactorReadyForCrowBar || inColorMatchingGame()); +} + +void Mars::doSolve() { + if (getCurrentActivation() == kActivateReactorReadyForNitrogen || getCurrentActivation() == kActivateReactorReadyForCrowBar) { + _utilityFuse.stopFuse(); + GameState.setMarsLockBroken(true); + GameState.setMarsLockFrozen(false); + startExtraLongSequence(kMars57OpenPanel, kMars57OpenPanelChoices, kExtraCompletedFlag, kFilterNoInput); + } else if (inColorMatchingGame()) { + _bombFuse.stopFuse(); + _guessObject.disposeReactorGuess(); + _undoPict.deallocateSurface(); + _guessHistory.disposeReactorHistory(); + _choiceHighlight.disposeReactorChoiceHighlight(); + startExtraSequence(kMars57GameSolved, kExtraCompletedFlag, kFilterNoInput); + } +} + +Common::String Mars::getSoundSpotsName() { + return "Sounds/Mars/Mars Spots"; +} + +Common::String Mars::getNavMovieName() { + return "Images/Mars/Mars.movie"; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/mars.h b/engines/pegasus/neighborhood/mars/mars.h new file mode 100644 index 0000000000..9aca10a703 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/mars.h @@ -0,0 +1,242 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_MARS_H +#define PEGASUS_NEIGHBORHOOD_MARS_MARS_H + +#include "pegasus/neighborhood/neighborhood.h" +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/mars/energybeam.h" +#include "pegasus/neighborhood/mars/gravitoncannon.h" +#include "pegasus/neighborhood/mars/planetmover.h" +#include "pegasus/neighborhood/mars/reactor.h" +#include "pegasus/neighborhood/mars/robotship.h" +#include "pegasus/neighborhood/mars/shuttleenergymeter.h" +#include "pegasus/neighborhood/mars/shuttlehud.h" +#include "pegasus/neighborhood/mars/spacejunk.h" +#include "pegasus/neighborhood/mars/tractorbeam.h" + +namespace Pegasus { + +class InventoryItem; +class Mars; + +enum MarsTimerCode { + kMarsLaunchTubeReached, + kMarsCanyonChaseFinished, + kMarsSpaceChaseFinished // Player ran out of time... +}; + +struct MarsTimerEvent { + Mars *mars; + MarsTimerCode event; +}; + +enum ShuttleWeaponSelection { + kNoWeapon, + kEnergyBeam, + kGravitonCannon, + kTractorBeam +}; + +class Mars : public Neighborhood { +friend void robotTimerExpiredFunction(FunctionPtr *, void *); +friend void lockThawTimerExpiredFunction(FunctionPtr *, void *); +friend void bombTimerExpiredFunction(FunctionPtr *, void *); +friend void bombTimerExpiredInGameFunction(FunctionPtr *, void *); +friend void airStageExpiredFunction(FunctionPtr *, void *); +friend void marsTimerFunction(FunctionPtr *, void *); + +public: + Mars(InputHandler *, PegasusEngine *); + virtual ~Mars(); + + void flushGameState(); + + virtual uint16 getDateResID() const; + + virtual AirQuality getAirQuality(const RoomID); + + void checkAirMask(); + + void showBigExplosion(const Common::Rect &, const DisplayOrder); + void showLittleExplosion(const Common::Rect &, const DisplayOrder); + void hitByJunk(); + void decreaseRobotShuttleEnergy(const int, Common::Point impactPoint); + void setUpNextDropTime(); + + Common::String getBriefingMovie(); + Common::String getEnvScanMovie(); + uint getNumHints(); + Common::String getHintMovie(uint); + + virtual void shieldOn(); + virtual void shieldOff(); + + void checkContinuePoint(const RoomID, const DirectionConstant); + + void setSoundFXLevel(const uint16); + + bool canSolve(); + void doSolve(); + + bool inColorMatchingGame(); + +protected: + enum { + kMarsPrivatePodStorageOpenFlag, + kMarsPrivatePodTurnLeftFlag, + kMarsPrivatePodTurnRightFlag, + kMarsPrivateRobotTiredOfWaitingFlag, + kMarsPrivatePlatformZoomedInFlag, + kMarsPrivateBombExposedFlag, + kMarsPrivateDraggingBombFlag, + kMarsPrivateInSpaceChaseFlag, + kMarsPrivateGotMapChipFlag, + kMarsPrivateGotOpticalChipFlag, + kMarsPrivateGotShieldChipFlag, + kNumMarsPrivateFlags + }; + + void init(); + void start(); + void setUpAIRules(); + void arriveAt(const RoomID, const DirectionConstant); + void takeItemFromRoom(Item *); + void dropItemIntoRoom(Item *, Hotspot *); + void activateHotspots(); + void activateOneHotspot(HotspotInfoTable::Entry &, Hotspot *); + void clickInHotspot(const Input &, const Hotspot *); + InputBits getInputFilter(); + + TimeValue getViewTime(const RoomID, const DirectionConstant); + void getZoomEntry(const HotSpotID, ZoomTable::Entry &); + void findSpotEntry(const RoomID, const DirectionConstant, SpotFlags, SpotTable::Entry &); + CanOpenDoorReason canOpenDoor(DoorTable::Entry &); + void openDoor(); + void closeDoorOffScreen(const RoomID, const DirectionConstant); + int16 getStaticCompassAngle(const RoomID, const DirectionConstant); + void getExitCompassMove(const ExitTable::Entry &, FaderMoveSpec &); + void getExtraCompassMove(const ExtraTable::Entry &, FaderMoveSpec &); + void turnTo(const DirectionConstant); + void receiveNotification(Notification *, const NotificationFlags); + void doorOpened(); + void setUpReactorEnergyDrain(); + Hotspot *getItemScreenSpot(Item *, DisplayElement *); + void lockThawed(); + void robotTiredOfWaiting(); + + void setUpReactorLevel1(); + void setUpNextReactorLevel(); + void makeColorSequence(); + void doUndoOneGuess(); + void doReactorGuess(int32 guess); + void bombExplodesInGame(); + void didntFindBomb(); + CanMoveForwardReason canMoveForward(ExitTable::Entry &); + void cantMoveThatWay(CanMoveForwardReason); + void moveForward(); + void bumpIntoWall(); + void turnLeft(); + void turnRight(); + void airStageExpired(); + void loadAmbientLoops(); + void checkAirlockDoors(); + void pickedUpItem(Item *item); + void cantOpenDoor(CanOpenDoorReason); + void launchMaze007Robot(); + void launchMaze015Robot(); + void launchMaze101Robot(); + void launchMaze104Robot(); + void launchMaze133Robot(); + void launchMaze136Robot(); + void launchMaze184Robot(); + void timerExpired(const uint32); + void spotCompleted(); + + void doCanyonChase(void); + void startMarsTimer(TimeValue, TimeScale, MarsTimerCode); + void marsTimerExpired(MarsTimerEvent &); + void throwAwayMarsShuttle(); + void startUpFromFinishedSpaceChase(); + void startUpFromSpaceChase(); + void transportToRobotShip(); + void spaceChaseClick(const Input &, const HotSpotID); + void updateCursor(const Common::Point, const Hotspot *); + + Common::String getSoundSpotsName(); + Common::String getNavMovieName(); + + InventoryItem *_attackingItem; + FuseFunction _bombFuse; + FuseFunction _noAirFuse; + FuseFunction _utilityFuse; + FlagsArray<byte, kNumMarsPrivateFlags> _privateFlags; + uint _reactorStage, _nextGuess; + int32 _currentGuess[3]; + ReactorGuess _guessObject; + Picture _undoPict; + ReactorHistory _guessHistory; + ReactorChoiceHighlight _choiceHighlight; + + Picture _shuttleInterface1; + Picture _shuttleInterface2; + Picture _shuttleInterface3; + Picture _shuttleInterface4; + Movie _canyonChaseMovie; + + MarsTimerEvent _marsEvent; + + Movie _leftShuttleMovie; + Movie _rightShuttleMovie; + Movie _lowerLeftShuttleMovie; + Movie _lowerRightShuttleMovie; + Movie _centerShuttleMovie; + Movie _upperLeftShuttleMovie; + Movie _upperRightShuttleMovie; + Movie _leftDamageShuttleMovie; + Movie _rightDamageShuttleMovie; + ShuttleEnergyMeter _shuttleEnergyMeter; + Movie _planetMovie; + PlanetMover _planetMover; + RobotShip _robotShip; + ShuttleHUD _shuttleHUD; + TractorBeam _tractorBeam; + SpaceJunk _junk; + EnergyBeam _energyBeam; + GravitonCannon _gravitonCannon; + Hotspot _energyChoiceSpot; + Hotspot _gravitonChoiceSpot; + Hotspot _tractorChoiceSpot; + Hotspot _shuttleViewSpot; + Hotspot _shuttleTransportSpot; + ShuttleWeaponSelection _weaponSelection; + ScalingMovie _explosions; + NotificationCallBack _explosionCallBack; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/planetmover.cpp b/engines/pegasus/neighborhood/mars/planetmover.cpp new file mode 100644 index 0000000000..ef26a7b573 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/planetmover.cpp @@ -0,0 +1,104 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/movie.h" +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/mars/hermite.h" +#include "pegasus/neighborhood/mars/planetmover.h" +#include "pegasus/neighborhood/mars/shuttleenergymeter.h" + +namespace Pegasus { + +static const TimeScale kRovingScale = kTractorBeamScale; +static const TimeValue kRovingTime = kTenSeconds * kRovingScale; +static const TimeValue kRovingSlop = kTwoSeconds * kRovingScale; + +static const CoordType kMaxVelocity = 20; + +PlanetMover::PlanetMover() { + setScale(kRovingScale); + _dropping = false; + _planetMovie = 0; +} + +void PlanetMover::startMoving(Movie *planetMovie) { + _planetMovie = planetMovie; + _p4 = kPlanetStartTop; + _r4 = ((PegasusEngine *)g_engine)->getRandomNumber(kMaxVelocity - 1); + if (_r4 + _p4 < kPlanetStopTop) + _r4 = kPlanetStopTop - _p4; + newDestination(); +} + +void PlanetMover::stopMoving() { + stop(); +} + +void PlanetMover::dropPlanetOutOfSight() { + stop(); + CoordType currentLoc = hermite(_p1, _p4, _r1, _r4, _lastTime, _duration); + CoordType currentV = dHermite(_p1, _p4, _r1, _r4, _lastTime, _duration); + _p1 = currentLoc; + _r1 = currentV; + _p4 = kPlanetStartTop; + _r4 = 0; + _duration = kTractorBeamTime - kTractorBeamScale; + _dropping = true; + setSegment(0, _duration); + setTime(0); + start(); +} + +void PlanetMover::newDestination() { + _p1 = _p4; + _r1 = _r4; + + _p4 = kPlanetStopTop + ((PegasusEngine *)g_engine)->getRandomNumber(kPlanetStartTop - kPlanetStopTop - 1); + _r4 = ((PegasusEngine *)g_engine)->getRandomNumber(kMaxVelocity - 1); + + if (_r4 + _p4 < kPlanetStopTop) + _r4 = kPlanetStopTop - _p4; + + stop(); + _duration = kRovingTime + ((PegasusEngine *)g_engine)->getRandomNumber(kRovingSlop - 1); + setSegment(0, _duration); + setTime(0); + start(); +} + +void PlanetMover::timeChanged(const TimeValue) { + if (_planetMovie) { + _planetMovie->moveElementTo(kPlanetStartLeft, hermite(_p1, _p4, _r1, _r4, _lastTime, _duration)); + if (_lastTime == _duration) { + if (_dropping) + stop(); + else + newDestination(); + } + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/planetmover.h b/engines/pegasus/neighborhood/mars/planetmover.h new file mode 100644 index 0000000000..cc2c412548 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/planetmover.h @@ -0,0 +1,57 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_PLANETMOVER_H +#define PEGASUS_NEIGHBORHOOD_MARS_PLANETMOVER_H + +#include "pegasus/timers.h" + +namespace Pegasus { + +class Movie; + +class PlanetMover : IdlerTimeBase { +public: + PlanetMover(); + virtual ~PlanetMover() {} + + void startMoving(Movie *); + void stopMoving(); + + void dropPlanetOutOfSight(); + +protected: + void newDestination(); + virtual void timeChanged(const TimeValue); + + Movie *_planetMovie; + CoordType _p1, _p4, _r1, _r4; + TimeValue _duration; + bool _dropping; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/reactor.cpp b/engines/pegasus/neighborhood/mars/reactor.cpp new file mode 100644 index 0000000000..478a01c155 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/reactor.cpp @@ -0,0 +1,297 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 + * aint32 with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/mars/reactor.h" + +namespace Pegasus { + +static const CoordType kCurrentGuessWidth = 121; +static const CoordType kCurrentGuessHeight = 23; + +static const CoordType kOneGuessWidth = 25; +static const CoordType kOneGuessHeight = 23; + +static const ResIDType kReactorChoicesPICTID = 905; + +static const CoordType kCurrentGuessLeft = kNavAreaLeft + 146; +static const CoordType kCurrentGuessTop = kNavAreaTop + 90; + +ReactorGuess::ReactorGuess(const DisplayElementID id) : DisplayElement(id) { + setBounds(kCurrentGuessLeft, kCurrentGuessTop, kCurrentGuessLeft + kCurrentGuessWidth, + kCurrentGuessTop + kCurrentGuessHeight); + setDisplayOrder(kMonitorLayer); + _currentGuess[0] = -1; + _currentGuess[1] = -1; + _currentGuess[2] = -1; +} + +void ReactorGuess::initReactorGuess() { + _colors.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kReactorChoicesPICTID); + startDisplaying(); + show(); +} + +void ReactorGuess::disposeReactorGuess() { + stopDisplaying(); + _colors.deallocateSurface(); +} + +void ReactorGuess::setGuess(int32 a, int32 b, int32 c) { + _currentGuess[0] = a; + _currentGuess[1] = b; + _currentGuess[2] = c; + triggerRedraw(); +} + +void ReactorGuess::draw(const Common::Rect &) { + if (_colors.isSurfaceValid()) { + Common::Rect r1(0, 0, kOneGuessWidth, kOneGuessHeight); + Common::Rect r2 = r1; + + for (int i = 0; i < 3; i++) { + if (_currentGuess[i] >= 0) { + r1.moveTo(kOneGuessWidth * _currentGuess[i], 0); + r2.moveTo(kCurrentGuessLeft + 48 * i, kCurrentGuessTop); + _colors.copyToCurrentPortTransparent(r1, r2); + } + } + } +} + +static const CoordType kReactorChoiceHiliteWidth = 166; +static const CoordType kReactorChoiceHiliteHeight = 26; + +static const CoordType kChoiceHiliteLefts[6] = { + 0, + 34, + 34 + 34, + 34 + 34 + 32, + 34 + 34 + 32 + 34, + 34 + 34 + 32 + 34 + 32 +}; + +static const ResIDType kReactorChoiceHilitePICTID = 901; + +static const CoordType kReactorChoiceHiliteLeft = kNavAreaLeft + 116; +static const CoordType kReactorChoiceHiliteTop = kNavAreaTop + 158; + +ReactorChoiceHighlight::ReactorChoiceHighlight(const DisplayElementID id) : DisplayElement(id) { + setBounds(kReactorChoiceHiliteLeft, kReactorChoiceHiliteTop, kReactorChoiceHiliteLeft + kReactorChoiceHiliteWidth, + kReactorChoiceHiliteTop + kReactorChoiceHiliteHeight); + setDisplayOrder(kMonitorLayer); +} + +void ReactorChoiceHighlight::initReactorChoiceHighlight() { + _colors.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kReactorChoiceHilitePICTID); + startDisplaying(); + show(); +} + +void ReactorChoiceHighlight::disposeReactorChoiceHighlight() { + stopDisplaying(); + _colors.deallocateSurface(); +} + +void ReactorChoiceHighlight::draw(const Common::Rect &) { + if (_colors.isSurfaceValid()) { + for (int i = 0; i < 5; ++i) { + if (_choices.getFlag(i)) { + Common::Rect r1(0, 0, kChoiceHiliteLefts[i + 1] - kChoiceHiliteLefts[i], kReactorChoiceHiliteHeight); + Common::Rect r2 = r1; + r1.moveTo(kChoiceHiliteLefts[i], 0); + r2.moveTo(kReactorChoiceHiliteLeft + kChoiceHiliteLefts[i], kReactorChoiceHiliteTop); + _colors.copyToCurrentPort(r1, r2); + } + } + } +} + +static const CoordType kReactorHistoryWidth = 128; +static const CoordType kReactorHistoryHeight = 168; + +static const CoordType kColorWidths[5] = { 24, 25, 25, 26, 27 }; +static const CoordType kColorHeights[5] = { 14, 15, 17, 17, 19}; + +static const CoordType kHistoryLefts[5][3] = { + { 302 + kNavAreaLeft, 329 + kNavAreaLeft, 357 + kNavAreaLeft }, + { 302 + kNavAreaLeft, 331 + kNavAreaLeft, 360 + kNavAreaLeft }, + { 303 + kNavAreaLeft, 333 + kNavAreaLeft, 363 + kNavAreaLeft }, + { 304 + kNavAreaLeft, 335 + kNavAreaLeft, 366 + kNavAreaLeft }, + { 305 + kNavAreaLeft, 337 + kNavAreaLeft, 369 + kNavAreaLeft } +}; + +static const CoordType kHistoryTops[5] = { + 39 + kNavAreaTop, + 61 + kNavAreaTop, + 84 + kNavAreaTop, + 110 + kNavAreaTop, + 137 + kNavAreaTop +}; + +static const CoordType kOneAnswerWidth = 35; +static const CoordType kOneAnswerHeight = 27; + +static const CoordType kDigitWidth = 16; +static const CoordType kDigitHeight = 12; + +static const CoordType kCorrectCountLefts[5] = { + 388 + kNavAreaLeft, + 392 + kNavAreaLeft, + 398 + kNavAreaLeft, + 402 + kNavAreaLeft, + 406 + kNavAreaLeft +}; + +static const CoordType kCorrectCountTops[5] = { + 40 + kNavAreaTop, + 62 + kNavAreaTop, + 86 + kNavAreaTop, + 112 + kNavAreaTop, + 140 + kNavAreaTop +}; + +static const ResIDType kReactorDigitsPICTID = 902; +static const ResIDType kReactorHistoryPICTID = 903; +static const ResIDType kReactorAnswerPICTID = 904; + +static const CoordType kReactorHistoryLeft = kNavAreaLeft + 302; +static const CoordType kReactorHistoryTop = kNavAreaTop + 39; + +static const CoordType kAnswerLeft = kNavAreaLeft + 304; +static const CoordType kAnswerTop = kNavAreaTop + 180; + +ReactorHistory::ReactorHistory(const DisplayElementID id) : DisplayElement(id) { + setBounds(kReactorHistoryLeft, kReactorHistoryTop, kReactorHistoryLeft + kReactorHistoryWidth, + kReactorHistoryTop + kReactorHistoryHeight); + setDisplayOrder(kMonitorLayer); + _numGuesses = 0; + _answer[0] = -1; + _answer[1] = -1; + _answer[2] = -1; + _showAnswer = false; +} + +void ReactorHistory::initReactorHistory() { + _colors.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kReactorHistoryPICTID); + _digits.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kReactorDigitsPICTID); + _answerColors.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kReactorAnswerPICTID); + startDisplaying(); + show(); +} + +void ReactorHistory::disposeReactorHistory() { + stopDisplaying(); + _colors.deallocateSurface(); +} + +void ReactorHistory::addGuess(int32 a, int32 b, int32 c) { + _history[_numGuesses][0] = a; + _history[_numGuesses][1] = b; + _history[_numGuesses][2] = c; + _numGuesses++; + triggerRedraw(); +} + +void ReactorHistory::clearHistory() { + _numGuesses = 0; + _showAnswer = false; + triggerRedraw(); +} + +void ReactorHistory::setAnswer(int32 a, int32 b, int32 c) { + _answer[0] = a; + _answer[1] = b; + _answer[2] = c; +} + +void ReactorHistory::showAnswer() { + _showAnswer = true; + triggerRedraw(); +} + +bool ReactorHistory::isSolved() { + for (int i = 0; i < _numGuesses; i++) + if (_history[i][0] == _answer[0] && _history[i][1] == _answer[1] && _history[i][2] == _answer[2]) + return true; + + return false; +} + +void ReactorHistory::draw(const Common::Rect &) { + static const CoordType kColorTops[5] = { + 0, + kColorHeights[0], + kColorHeights[0] + kColorHeights[1], + kColorHeights[0] + kColorHeights[1] + kColorHeights[2], + kColorHeights[0] + kColorHeights[1] + kColorHeights[2] + kColorHeights[3], + }; + + if (_colors.isSurfaceValid() && _digits.isSurfaceValid()) { + for (int i = 0; i < _numGuesses; ++i) { + Common::Rect r1(0, 0, kColorWidths[i], kColorHeights[i]); + Common::Rect r2 = r1; + Common::Rect r3(0, 0, kDigitWidth, kDigitHeight); + Common::Rect r4 = r3; + int correct = 0; + + for (int j = 0; j < 3; ++j) { + r1.moveTo(kColorWidths[i] * _history[i][j], kColorTops[i]); + r2.moveTo(kHistoryLefts[i][j], kHistoryTops[i]); + _colors.copyToCurrentPortTransparent(r1, r2); + + if (_history[i][j] == _answer[j]) + correct++; + } + + r3.moveTo(kDigitWidth * correct, 0); + r4.moveTo(kCorrectCountLefts[i], kCorrectCountTops[i]); + _digits.copyToCurrentPort(r3, r4); + } + + if (_showAnswer && _answerColors.isSurfaceValid()) { + Common::Rect r1(0, 0, kOneAnswerWidth, kOneAnswerHeight); + Common::Rect r2 = r1; + + for (int i = 0; i < 3; i++) { + r1.moveTo(kOneAnswerWidth * _answer[i], 0); + r2.moveTo(kAnswerLeft + 34 * i, kAnswerTop); + _answerColors.copyToCurrentPortTransparent(r1, r2); + } + } + } +} + +int32 ReactorHistory::getCurrentNumCorrect() { + int correct = 0; + + for (int i = 0; i < 3; i++) + if (_history[_numGuesses - 1][i] == _answer[i]) + correct++; + + return correct; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/reactor.h b/engines/pegasus/neighborhood/mars/reactor.h new file mode 100644 index 0000000000..86338f8266 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/reactor.h @@ -0,0 +1,108 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_REACTOR_H +#define PEGASUS_NEIGHBORHOOD_MARS_REACTOR_H + +#include "pegasus/elements.h" +#include "pegasus/surface.h" +#include "pegasus/util.h" + +namespace Pegasus { + +class ReactorGuess : public DisplayElement { +public: + ReactorGuess(const DisplayElementID); + virtual ~ReactorGuess() {} + + void initReactorGuess(); + void disposeReactorGuess(); + + void setGuess(int32, int32, int32); + + void draw(const Common::Rect &); + +protected: + int32 _currentGuess[3]; + + Surface _colors; +}; + +class ReactorChoiceHighlight : public DisplayElement { +public: + ReactorChoiceHighlight(const DisplayElementID); + virtual ~ReactorChoiceHighlight() {} + + void initReactorChoiceHighlight(); + void disposeReactorChoiceHighlight(); + + void resetHighlight() { + _choices.clearAllFlags(); + triggerRedraw(); + } + + bool choiceHighlighted(uint32 whichChoice) { return _choices.getFlag(whichChoice); } + + void draw(const Common::Rect &); + + void highlightChoice(uint32 whichChoice) { + _choices.setFlag(whichChoice); + triggerRedraw(); + } + +protected: + Surface _colors; + FlagsArray<byte, 5> _choices; +}; + +class ReactorHistory : public DisplayElement { +public: + ReactorHistory(const DisplayElementID); + virtual ~ReactorHistory() {} + + void initReactorHistory(); + void disposeReactorHistory(); + + void draw(const Common::Rect &); + + void addGuess(int32, int32, int32); + int32 getNumGuesses() { return _numGuesses; } + void clearHistory(); + void setAnswer(int32, int32, int32); + void showAnswer(); + bool isSolved(); + int32 getCurrentNumCorrect(); + +protected: + Surface _colors, _digits, _answerColors; + int32 _answer[3]; + int32 _history[5][3]; + int32 _numGuesses; + bool _showAnswer; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/robotship.cpp b/engines/pegasus/neighborhood/mars/robotship.cpp new file mode 100644 index 0000000000..a0ff749910 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/robotship.cpp @@ -0,0 +1,270 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/mars/hermite.h" +#include "pegasus/neighborhood/mars/mars.h" +#include "pegasus/neighborhood/mars/robotship.h" +#include "pegasus/neighborhood/mars/spacechase3d.h" +#include "pegasus/neighborhood/mars/spacejunk.h" + +namespace Pegasus { + +static const TimeScale kRovingScale = kTractorBeamScale; +static const TimeValue kRovingTime = kSixSeconds * kRovingScale; +static const TimeValue kRovingSlop = kThreeSeconds * kRovingScale; + +static const int kNumSpriteColumns = 15; +static const int kNumSpriteRows = 16; + +static const CoordType kInitialLocationLeft = kShuttleWindowLeft - 50; +static const CoordType kInitialLocationTop = kShuttleWindowTop - 50; +static const CoordType kInitialLocationWidth = kShuttleWindowWidth + 100; +static const CoordType kInitialLocationHeight = kShuttleWindowHeight + 100; + +static const CoordType kVelocityVectorLength = 100; +static const CoordType kVelocityVectorSlop = 50; + +static const CoordType kRovingLeft = kShuttleWindowLeft + 20; +static const CoordType kRovingTop = kShuttleWindowTop + 20; +static const CoordType kRovingWidth = kShuttleWindowMidH - kRovingLeft; +static const CoordType kRovingHeight = kShuttleWindowMidV - kRovingTop; + +RobotShip* g_robotShip = 0; + +void timeToDropJunkFunction(FunctionPtr *, void *robotShip) { + ((RobotShip *)robotShip)->timeToDropJunk(); +} + +RobotShip::RobotShip() : _spritesMovie(kNoDisplayElement) { + g_robotShip = this; + _shipRange = Common::Rect(kShuttleWindowLeft, kShuttleWindowTop, kShuttleWindowLeft + kShuttleWindowWidth, + kShuttleWindowTop + kShuttleWindowHeight); + setScale(kRovingScale); + _currentLocation.x = 0; + _currentLocation.y = 0; + _snaring = false; + _dropJunkFuse.setFunctionPtr(&timeToDropJunkFunction, (void *)this); +} + +RobotShip::~RobotShip() { + g_robotShip = 0; +} + +void RobotShip::initRobotShip() { + _spritesMovie.initFromMovieFile("Images/Mars/Ship.movie", true); + _spritesMovie.setDisplayOrder(kShuttleRobotShipOrder); + _spritesMovie.moveElementTo(kPlanetStartLeft, kPlanetStartTop); + _spritesMovie.startDisplaying(); + _spritesMovie.show(); + + Common::Rect r; + _spritesMovie.getBounds(r); + _shipWidth = r.width(); + _shipHeight = r.height(); + _dead = false; +} + +void RobotShip::cleanUpRobotShip() { + _dropJunkFuse.stopFuse(); + _spritesMovie.stopDisplaying(); + _spritesMovie.releaseMovie(); +} + +void RobotShip::startMoving() { + if (((PegasusEngine *)g_engine)->getRandomBit()) { + _p4.x = kInitialLocationLeft + ((PegasusEngine *)g_engine)->getRandomNumber(kInitialLocationWidth - 1); + if (((PegasusEngine *)g_engine)->getRandomBit()) + _p4.y = kInitialLocationTop; + else + _p4.y = kInitialLocationTop + kInitialLocationHeight; + } else { + _p4.y = kInitialLocationTop + ((PegasusEngine *)g_engine)->getRandomNumber(kInitialLocationHeight - 1); + if (((PegasusEngine *)g_engine)->getRandomBit()) + _p4.x = kInitialLocationLeft; + else + _p4.x = kInitialLocationLeft + kInitialLocationWidth; + } + + makeVelocityVector(_p4.x, _p4.y, kShuttleWindowLeft + kShuttleWindowWidth / 2, + kShuttleWindowTop + kShuttleWindowHeight / 2, _r4); + newDestination(); + setUpNextDropTime(); +} + +void RobotShip::killRobotShip() { + cleanUpRobotShip(); + _dead = true; +} + +void RobotShip::setUpNextDropTime() { + if (!isSnared()) { + _dropJunkFuse.primeFuse(kJunkDropBaseTime + ((PegasusEngine *)g_engine)->getRandomNumber(kJunkDropSlopTime)); + _dropJunkFuse.lightFuse(); + } +} + +void RobotShip::timeToDropJunk() { + if (g_spaceJunk) { + CoordType x, y; + _spritesMovie.getCenter(x, y); + g_spaceJunk->launchJunk(((PegasusEngine *)g_engine)->getRandomNumber(24), x, y); + } +} + +void RobotShip::newDestination() { + _p1 = _p4; + _r1 = _r4; + + _p4.x = kRovingLeft + ((PegasusEngine *)g_engine)->getRandomNumber(kRovingWidth - 1); + _p4.y = kRovingTop + ((PegasusEngine *)g_engine)->getRandomNumber(kRovingHeight - 1); + + if (((PegasusEngine *)g_engine)->getRandomNumber(7) < 6) { + if (!sameSign(_p4.x - kShuttleWindowMidH, kShuttleWindowMidH - _p1.x)) { + if (sign(_p4.x - kShuttleWindowMidH) > 0) + _p4.x -= kRovingWidth; + else + _p4.x += kRovingWidth; + } + } + + if (((PegasusEngine *)g_engine)->getRandomNumber(7) < 6) { + if (!sameSign(_p4.y - kShuttleWindowMidV, kShuttleWindowMidV - _p1.y)) { + if (sign(_p4.y - kShuttleWindowMidV) > 0) + _p4.y -= kRovingHeight; + else + _p4.y += kRovingHeight; + } + } + + makeVelocityVector(_p4.x, _p4.y, kShuttleWindowLeft + kShuttleWindowWidth / 2, + kShuttleWindowTop + kShuttleWindowHeight / 2, _r4); + stop(); + _duration = kRovingTime + ((PegasusEngine *)g_engine)->getRandomNumber(kRovingSlop - 1); + setSegment(0, _duration); + setTime(0); + start(); +} + +void RobotShip::moveRobotTo(CoordType x, CoordType y) { + _currentLocation.x = x; + _currentLocation.y = y; + + if (_spritesMovie.isMovieValid()) { + _spritesMovie.moveElementTo(x - (_shipWidth >> 1), y - (_shipHeight >> 1)); + + if (x < _shipRange.left) + x = 0; + else if (x > _shipRange.right - 1) + x = _shipRange.width() - 1; + else + x -= _shipRange.left; + + if (y < _shipRange.top) + y = 0; + else if (y > _shipRange.bottom - 1) + y = _shipRange.height() - 1; + else + y -= _shipRange.top; + + x = kNumSpriteColumns * x / _shipRange.width(); + y = kNumSpriteRows * y / _shipRange.height(); + + _spritesMovie.setTime(40 * (x + y * kNumSpriteColumns)); + _spritesMovie.redrawMovieWorld(); + } +} + +bool RobotShip::pointInShuttle(Common::Point &pt) { + Common::Rect r; + _spritesMovie.getBounds(r); + + int dx = r.width() / 4; + int dy = r.height() / 6; + + r.left += dx; + r.right -= dx; + r.top += dy; + r.bottom -= dy; + + return r.contains(pt); +} + +void RobotShip::hitByEnergyBeam(Common::Point impactPoint) { + ((Mars *)g_neighborhood)->decreaseRobotShuttleEnergy(1, impactPoint); + setGlowing(true); + ((PegasusEngine *)g_engine)->delayShell(1, 3); + setGlowing(false); +} + +void RobotShip::hitByGravitonCannon(Common::Point impactPoint) { + GameState.setMarsHitRobotWithCannon(true); + ((Mars *)g_neighborhood)->decreaseRobotShuttleEnergy(6, impactPoint); +} + +void RobotShip::snareByTractorBeam() { + _dropJunkFuse.stopFuse(); + stop(); + + Common::Point currentV; + dHermite(_p1, _p4, _r1, _r4, _lastTime, _duration, currentV); + + _p1 = _currentLocation; + _r1 = currentV; + _p4.x = kShuttleWindowMidH; + _p4.y = kShuttleWindowMidV; + _r4.x = 0; + _r4.y = 0; + _duration = kTractorBeamTime; + _snaring = true; + setSegment(0, _duration); + setTime(0); + start(); +} + +void RobotShip::timeChanged(const TimeValue) { + Common::Point newLocation; + hermite(_p1, _p4, _r1, _r4, _lastTime, _duration, newLocation); + moveRobotTo(newLocation.x, newLocation.y); + + if (_lastTime == _duration) { + if (_snaring) + stop(); + else + newDestination(); + } +} + +void RobotShip::makeVelocityVector(CoordType x1, CoordType y1, CoordType x2, CoordType y2, Common::Point &vector) { + CoordType length = ((PegasusEngine *)g_engine)->getRandomNumber(kVelocityVectorSlop - 1) + kVelocityVectorLength; + vector.x = x2 - x1; + vector.y = y2 - y1; + float oldLength = sqrt((float)(vector.x * vector.x + vector.y * vector.y)); + vector.x = (int)(vector.x * length / oldLength); + vector.y = (int)(vector.y * length / oldLength); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/robotship.h b/engines/pegasus/neighborhood/mars/robotship.h new file mode 100644 index 0000000000..b668e8f154 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/robotship.h @@ -0,0 +1,86 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_ROBOTSHIP_H +#define PEGASUS_NEIGHBORHOOD_MARS_ROBOTSHIP_H + +#include "pegasus/movie.h" + +namespace Pegasus { + +static const CoordType kShuttleMovieWidth = 114; +static const CoordType kShuttleMovieHeight = 42; + +class RobotShip : IdlerTimeBase { +friend void timeToDropJunkFunction(FunctionPtr *, void *); + +public: + RobotShip(); + virtual ~RobotShip(); + + void initRobotShip(); + void cleanUpRobotShip(); + + void startMoving(); + + void killRobotShip(); + + bool pointInShuttle(Common::Point&); + + void hitByEnergyBeam(Common::Point impactPoint); + void hitByGravitonCannon(Common::Point impactPoint); + + void getShuttleBounds(Common::Rect &r) { _spritesMovie.getBounds(r); } + + void setGlowing(const bool glowing) { _spritesMovie.setGlowing(glowing); } + + void snareByTractorBeam(); + bool isSnared() { return _snaring && getTime() == _duration; } + + bool isDead() { return _dead; } + + void setUpNextDropTime(); + +protected: + void newDestination(); + void moveRobotTo(CoordType, CoordType); + void timeToDropJunk(); + virtual void timeChanged(const TimeValue); + void makeVelocityVector(CoordType, CoordType, CoordType, CoordType, Common::Point &); + + GlowingMovie _spritesMovie; + Common::Rect _shipRange; + int _shipWidth, _shipHeight; + Common::Point _p1, _p4, _r1, _r4, _currentLocation; + FuseFunction _dropJunkFuse; + TimeValue _duration; + bool _snaring, _dead; +}; + +extern RobotShip *g_robotShip; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/shuttleenergymeter.cpp b/engines/pegasus/neighborhood/mars/shuttleenergymeter.cpp new file mode 100644 index 0000000000..21bb1fb700 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/shuttleenergymeter.cpp @@ -0,0 +1,116 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/mars/shuttleenergymeter.h" + +namespace Pegasus { + +ShuttleEnergyMeter::ShuttleEnergyMeter() : FaderAnimation(kNoDisplayElement) { + setBounds(kShuttleEnergyLeft, kShuttleEnergyTop, kShuttleEnergyLeft + kShuttleEnergyWidth, + kShuttleEnergyTop + kShuttleEnergyHeight); + setDisplayOrder(kShuttleStatusOrder); + setFaderValue(0); +} + +void ShuttleEnergyMeter::initShuttleEnergyMeter() { + _meterImage.getImageFromPICTFile("Images/Mars/Shuttle Energy.pict"); + _lowWarning.getImageFromPICTFile("Images/Mars/Shuttle Low Energy.pict"); + startDisplaying(); + show(); +} + +void ShuttleEnergyMeter::disposeShuttleEnergyMeter() { + stopFader(); + hide(); + stopDisplaying(); + _meterImage.deallocateSurface(); + _lowWarning.deallocateSurface(); +} + +void ShuttleEnergyMeter::draw(const Common::Rect &) { + int32 currentValue = getFaderValue(); + + Common::Rect r1, r2, bounds; + getBounds(bounds); + + if (currentValue < kLowShuttleEnergy) { + _lowWarning.getSurfaceBounds(r1); + r2 = r1; + r2.moveTo(bounds.left, bounds.top); + _lowWarning.copyToCurrentPort(r1, r2); + } + + _meterImage.getSurfaceBounds(r1); + r1.right = r1.left + r1.width() * currentValue / kFullShuttleEnergy; + r2 = r1; + r2.moveTo(bounds.left + 102, bounds.top + 6); + _meterImage.copyToCurrentPort(r1, r2); +} + +void ShuttleEnergyMeter::powerUpMeter() { + FaderMoveSpec moveSpec; + moveSpec.makeTwoKnotFaderSpec(kThirtyTicksPerSecond, 0, 0, 45, kFullShuttleEnergy); + startFader(moveSpec); +} + +void ShuttleEnergyMeter::setEnergyValue(const int32 value) { + stopFader(); + FaderMoveSpec moveSpec; + moveSpec.makeTwoKnotFaderSpec(kFifteenTicksPerSecond, value * 3, value, kFullShuttleEnergy * 3, kFullShuttleEnergy); + startFader(moveSpec); +} + +void ShuttleEnergyMeter::drainForTractorBeam() { + stopFader(); + TimeValue startTime = 0, stopTime; + int32 startValue = getFaderValue(), stopValue; + + if (startValue < kTractorBeamEnergy) { + stopTime = startValue * kTractorBeamTime / kTractorBeamEnergy; + stopValue = 0; + } else { + stopTime = kTractorBeamTime; + stopValue = startValue - kTractorBeamEnergy; + } + + FaderMoveSpec moveSpec; + moveSpec.makeTwoKnotFaderSpec(kTractorBeamScale, startTime, startValue, stopTime, stopValue); + startFader(moveSpec); +} + +int32 ShuttleEnergyMeter::getEnergyValue() const { + return getFaderValue(); +} + +void ShuttleEnergyMeter::dropEnergyValue(const int32 delta) { + setEnergyValue(getFaderValue() - delta); +} + +bool ShuttleEnergyMeter::enoughEnergyForTractorBeam() const { + return getEnergyValue() >= kTractorBeamEnergy; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/shuttleenergymeter.h b/engines/pegasus/neighborhood/mars/shuttleenergymeter.h new file mode 100644 index 0000000000..51161e094e --- /dev/null +++ b/engines/pegasus/neighborhood/mars/shuttleenergymeter.h @@ -0,0 +1,73 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_SHUTTLEENERGYMETER_H +#define PEGASUS_NEIGHBORHOOD_MARS_SHUTTLEENERGYMETER_H + +#include "pegasus/fader.h" +#include "pegasus/surface.h" + +namespace Pegasus { + +static const int32 kFullShuttleEnergy = 100; +// Low is 20 percent +static const int32 kLowShuttleEnergy = kFullShuttleEnergy * 20 / 100; + +static const int32 kMinDampingEnergy = 15; +static const int32 kMinGravitonEnergy = 63; + +static const TimeScale kTractorBeamScale = kFifteenTicksPerSecond; +static const TimeValue kTractorBeamTime = kFiveSeconds * kTractorBeamScale; +static const int32 kTractorBeamEnergy = kLowShuttleEnergy; + +class ShuttleEnergyMeter : public FaderAnimation { +public: + ShuttleEnergyMeter(); + ~ShuttleEnergyMeter() {} + + void initShuttleEnergyMeter(); + void disposeShuttleEnergyMeter(); + + void powerUpMeter(); + + void setEnergyValue(const int32); + int32 getEnergyValue() const; + + void dropEnergyValue(const int32); + + void drainForTractorBeam(); + + bool enoughEnergyForTractorBeam() const; + + void draw(const Common::Rect &); + +protected: + Surface _meterImage; + Surface _lowWarning; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/shuttlehud.cpp b/engines/pegasus/neighborhood/mars/shuttlehud.cpp new file mode 100644 index 0000000000..14f5b88319 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/shuttlehud.cpp @@ -0,0 +1,246 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/pegasus.h" +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/mars/robotship.h" +#include "pegasus/neighborhood/mars/shuttlehud.h" + +namespace Pegasus { + +static const CoordType kHUDTargetGridLeft = kShuttleWindowLeft + 16; +static const CoordType kHUDTargetGridTop = kShuttleWindowTop + 8; +static const CoordType kHUDTargetGridWidth = 328; +static const CoordType kHUDTargetGridHeight = 206; + +static const CoordType kHUDRS232Left = kHUDTargetGridLeft + 264; +static const CoordType kHUDRS232Top = kHUDTargetGridTop + 2; + +static const CoordType kHUDLockLeft = kShuttleWindowLeft + 101; +static const CoordType kHUDLockTop = kShuttleWindowTop + 49; +static const CoordType kHUDLockWidth = 145; +static const CoordType kHUDLockHeight = 124; + +static const CoordType kTractorLockWidth = 50; +static const CoordType kTractorLockHeight = 30; + +static const CoordType kTractorLockLeft = kShuttleWindowMidH - kTractorLockWidth / 2; +static const CoordType kTractorLockTop = kShuttleWindowMidV - kTractorLockHeight / 2; +static const CoordType kTractorLockRight = kTractorLockLeft + kTractorLockWidth; +static const CoordType kTractorLockBottom = kTractorLockTop + kTractorLockHeight; + +static const uint16 s_RS232Data[] = { + 0xF0E1, 0xCE70, + 0xF9E1, 0xEF78, + 0x4900, 0x2108, + 0x79C0, 0xE738, + 0x70E1, 0xC770, + 0x5821, 0x0140, + 0x4DE1, 0xEF78, + 0x45C1, 0xEE78 +}; + +static const uint16 s_lockData[] = { + 0xE007, 0xFE1F, 0xF8E0, 0x7000, + 0xE00F, 0xFF3F, 0xFCE0, 0xE000, + 0xE00E, 0x0738, 0x1CE1, 0xC000, + 0xE00E, 0x0738, 0x00FF, 0x8000, + 0xE00E, 0x0738, 0x00FF, 0x0000, + 0xE00E, 0x0738, 0x00E3, 0x8000, + 0xE00E, 0x0738, 0x1CE1, 0xC000, + 0xFFCF, 0xFF3F, 0xFCE0, 0xE000, + 0xFFC7, 0xFE1F, 0xF8E0, 0x7000 +}; + +#define drawHUDLockLine(x1, y1, x2, y2, penX, penY, color) \ + screen->drawThickLine((x1) + kHUDLockLeft, (y1) + kHUDLockTop, \ + (x2) + kHUDLockLeft, (y2) + kHUDLockTop, penX, penY, color) + +#define drawHUDLockArrows(offset, color) \ + drawHUDLockLine(63, 0 + (offset), 68, 5 + (offset), 1, 3, color); \ + drawHUDLockLine(71, 8 + (offset), 77, 14 + (offset), 1, 3, color); \ + drawHUDLockLine(78, 14 + (offset), 84, 8 + (offset), 1, 3, color); \ + drawHUDLockLine(87, 5 + (offset), 92, 0 + (offset), 1, 3, color); \ + drawHUDLockLine(63, 121 - (offset), 68, 116 - (offset), 1, 3, color); \ + drawHUDLockLine(71, 113 - (offset), 77, 107 - (offset), 1, 3, color); \ + drawHUDLockLine(78, 107 - (offset), 84, 113 - (offset), 1, 3, color); \ + drawHUDLockLine(87, 116 - (offset), 92, 121 - (offset), 1, 3, color); \ +\ + drawHUDLockLine(13 + (offset), 47, 18 + (offset), 52, 3, 1, color); \ + drawHUDLockLine(21 + (offset), 55, 27 + (offset), 61, 3, 1, color); \ + drawHUDLockLine(27 + (offset), 62, 21 + (offset), 68, 3, 1, color); \ + drawHUDLockLine(18 + (offset), 71, 13 + (offset), 76, 3, 1, color); \ + drawHUDLockLine(142 - (offset), 47, 137 - (offset), 52, 3, 1, color); \ + drawHUDLockLine(134 - (offset), 55, 128 - (offset), 61, 3, 1, color); \ + drawHUDLockLine(128 - (offset), 62, 134 - (offset), 68, 3, 1, color); \ + drawHUDLockLine(137 - (offset), 71, 142 - (offset), 76, 3, 1, color) + +ShuttleHUD::ShuttleHUD() : DisplayElement(kNoDisplayElement) { + _lightGreen = g_system->getScreenFormat().RGBToColor(0, 204, 0); + _gridDarkGreen = g_system->getScreenFormat().RGBToColor(0, 85, 0); + _lockDarkGreen1 = g_system->getScreenFormat().RGBToColor(0, 68, 0); + _lockDarkGreen2 = g_system->getScreenFormat().RGBToColor(0, 65, 0); + + _targetLocked = false; + setBounds(kShuttleWindowLeft, kShuttleWindowTop, kShuttleWindowLeft + kShuttleWindowWidth, + kShuttleWindowTop + kShuttleWindowHeight); + setDisplayOrder(kShuttleHUDOrder); +} + +void ShuttleHUD::initShuttleHUD() { + startDisplaying(); + startIdling(); +} + +void ShuttleHUD::cleanUpShuttleHUD() { + stopIdling(); + stopDisplaying(); +} + +void ShuttleHUD::showTargetGrid() { + show(); +} + +void ShuttleHUD::hideTargetGrid() { + hide(); + unlockOnTarget(); +} + +void ShuttleHUD::useIdleTime() { + if (isVisible()) { + Common::Rect r; + g_robotShip->getShuttleBounds(r); + if (r.left < kTractorLockRight && r.right > kTractorLockLeft && r.top < kTractorLockBottom && r.bottom > kTractorLockTop) + lockOnTarget(); + else + unlockOnTarget(); + } +} + +void ShuttleHUD::lockOnTarget() { + if (!_targetLocked) { + _targetLocked = true; + triggerRedraw(); + } +} + +void ShuttleHUD::unlockOnTarget() { + if (_targetLocked) { + _targetLocked = false; + triggerRedraw(); + } +} + +void ShuttleHUD::draw(const Common::Rect &) { + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getWorkArea(); + + for (int y = 0; y < 35; y++) { + Common::Rect r; + + if (y & 1) { + if (y == 17) { + r = Common::Rect(0, 0, 4, 2); + r.moveTo(kHUDTargetGridLeft + 8, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 12, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + + r = Common::Rect(0, 0, 6, 2); + r.moveTo(kHUDTargetGridLeft + 2, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _lightGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 8, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _lightGreen); + + r = Common::Rect(0, 0, 23, 2); + r.moveTo(kHUDTargetGridLeft + 12, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _lightGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 35, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _lightGreen); + } else if (y == 1 || y == 15 || y == 19 || y == 33) { + r = Common::Rect(0, 0, 4, 2); + r.moveTo(kHUDTargetGridLeft + 2, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 6, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + + r = Common::Rect(0, 0, 15, 2); + r.moveTo(kHUDTargetGridLeft + 8, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 23, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + } else { + r = Common::Rect(0, 0, 4, 2); + r.moveTo(kHUDTargetGridLeft + 2, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 6, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + + r = Common::Rect(0, 0, 10, 2); + r.moveTo(kHUDTargetGridLeft + 8, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 18, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + } + } else { + r = Common::Rect(0, 0, 2, 2); + r.moveTo(kHUDTargetGridLeft, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 2, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + + r = Common::Rect(0, 0, 4, 2); + r.moveTo(kHUDTargetGridLeft + 8, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 12, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + } + } + + drawOneBitImageOr(screen, s_RS232Data, 2, Common::Rect(kHUDRS232Left, kHUDRS232Top, + kHUDRS232Left + 29, kHUDRS232Top + 8), _gridDarkGreen); + + if (_targetLocked) { + drawHUDLockArrows(0, _lockDarkGreen2); + drawHUDLockArrows(12, _lockDarkGreen1); + drawHUDLockArrows(24, _lightGreen); + drawOneBitImageOr(screen, s_lockData, 4, Common::Rect(kHUDLockLeft, kHUDLockTop + 115, + kHUDLockLeft + 52, kHUDLockTop + 115 + 9), _lightGreen); + } +} + +void ShuttleHUD::drawOneBitImageOr(Graphics::Surface *screen, const uint16 *data, int pitch, const Common::Rect &bounds, uint32 color) { + for (int y = 0; y < bounds.height(); y++) { + for (int x = 0; x < bounds.width(); x++) { + if ((data[y * pitch + x / 16] & (1 << (15 - (x % 16)))) != 0) { + if (screen->format.bytesPerPixel == 2) + WRITE_UINT16((byte *)screen->getBasePtr(x + bounds.left, y + bounds.top), color); + else + WRITE_UINT32((byte *)screen->getBasePtr(x + bounds.left, y + bounds.top), color); + } + } + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/shuttlehud.h b/engines/pegasus/neighborhood/mars/shuttlehud.h new file mode 100644 index 0000000000..dc1c7598b5 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/shuttlehud.h @@ -0,0 +1,61 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_SHUTTLEHUD_H +#define PEGASUS_NEIGHBORHOOD_MARS_SHUTTLEHUD_H + +#include "pegasus/elements.h" +#include "pegasus/timers.h" + +namespace Pegasus { + +class ShuttleHUD : public DisplayElement, public Idler { +public: + ShuttleHUD(); + virtual ~ShuttleHUD() {} + + void showTargetGrid(); + void hideTargetGrid(); + + void initShuttleHUD(); + void cleanUpShuttleHUD(); + + bool isTargetLocked() { return _targetLocked; } + + void draw(const Common::Rect &); + +protected: + void useIdleTime(); + void lockOnTarget(); + void unlockOnTarget(); + void drawOneBitImageOr(Graphics::Surface *, const uint16 *, int, const Common::Rect &, uint32); + + bool _targetLocked; + uint32 _lightGreen, _gridDarkGreen, _lockDarkGreen1, _lockDarkGreen2; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/shuttleweapon.cpp b/engines/pegasus/neighborhood/mars/shuttleweapon.cpp new file mode 100644 index 0000000000..0151a26f29 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/shuttleweapon.cpp @@ -0,0 +1,129 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/mars/robotship.h" +#include "pegasus/neighborhood/mars/shuttleweapon.h" +#include "pegasus/neighborhood/mars/spacejunk.h" + +namespace Pegasus { + +ShuttleWeapon::ShuttleWeapon() : IdlerAnimation(kNoDisplayElement) { + setScale(kShuttleWeaponScale); + _weaponDuration = kShuttleWeaponScale * 2; + setSegment(0, _weaponDuration); + setBounds(kShuttleWindowLeft, kShuttleWindowTop, kShuttleWindowLeft + kShuttleWindowWidth, + kShuttleWindowTop + kShuttleWindowHeight); + setDisplayOrder(kShuttleWeaponFrontOrder); +} + +void ShuttleWeapon::initShuttleWeapon() { + startDisplaying(); +} + +void ShuttleWeapon::cleanUpShuttleWeapon() { + stop(); + hide(); + stopDisplaying(); +} + +bool ShuttleWeapon::canFireWeapon() { + return !isRunning(); +} + +void ShuttleWeapon::fireWeapon(const CoordType hStop, const CoordType vStop) { + if (!isRunning()) { + stop(); + setTime(0); + show(); + + Common::Point pt2D(hStop, vStop); + project2DTo3D(pt2D, kShuttleDistance, _weaponTarget); + _weaponTime = 0; + setDisplayOrder(kShuttleWeaponFrontOrder); + start(); + } +} + +void ShuttleWeapon::updateWeaponPosition() { + _weaponTime = (float)_lastTime / _weaponDuration; + linearInterp(_weaponOrigin, _weaponTarget, _weaponTime, _weaponLocation); + + if (_weaponTime == 1.0) { + stop(); + hide(); + } else { + triggerRedraw(); + } +} + +void ShuttleWeapon::timeChanged(const TimeValue) { + updateWeaponPosition(); + + bool hit = false; + Common::Point impactPoint; + + if (g_spaceJunk->isJunkFlying()) { + hit = collisionWithJunk(impactPoint); + if (hit) { + stop(); + hide(); + hitJunk(impactPoint); + } + } + + if (!hit && _weaponTime == 1.0 && collisionWithShuttle(impactPoint)) + hitShuttle(impactPoint); +} + +bool ShuttleWeapon::collisionWithJunk(Common::Point &impactPoint) { + if (getDisplayOrder() == kShuttleWeaponFrontOrder) { + Point3D junkPosition; + g_spaceJunk->getJunkPosition(junkPosition); + + if (junkPosition.z < _weaponLocation.z) { + setDisplayOrder(kShuttleWeaponBackOrder); + project3DTo2D(_weaponLocation, impactPoint); + return g_spaceJunk->pointInJunk(impactPoint); + } + } + + return false; +} + +bool ShuttleWeapon::collisionWithShuttle(Common::Point &impactPoint) { + project3DTo2D(_weaponLocation, impactPoint); + return g_robotShip->pointInShuttle(impactPoint); +} + +void ShuttleWeapon::hitJunk(Common::Point impactPoint) { + g_spaceJunk->hitByEnergyBeam(impactPoint); +} + +void ShuttleWeapon::hitShuttle(Common::Point impactPoint) { + g_robotShip->hitByEnergyBeam(impactPoint); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/shuttleweapon.h b/engines/pegasus/neighborhood/mars/shuttleweapon.h new file mode 100644 index 0000000000..38529c8919 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/shuttleweapon.h @@ -0,0 +1,68 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_SHUTTLEWEAPON_H +#define PEGASUS_NEIGHBORHOOD_MARS_SHUTTLEWEAPON_H + +#include "pegasus/elements.h" +#include "pegasus/neighborhood/mars/spacechase3d.h" + +namespace Pegasus { + +// Can fire multiple times? +// For now, no... +// clone2727 adds: And now forever + +static const TimeScale kShuttleWeaponScale = kFifteenTicksPerSecond; + +class ShuttleWeapon : public IdlerAnimation { +public: + ShuttleWeapon(); + virtual ~ShuttleWeapon() {} + + virtual void initShuttleWeapon(); + virtual void cleanUpShuttleWeapon(); + + virtual void fireWeapon(const CoordType, const CoordType); + + bool canFireWeapon(); + +protected: + void timeChanged(const TimeValue); + virtual void updateWeaponPosition(); + virtual bool collisionWithJunk(Common::Point &impactPoint); + bool collisionWithShuttle(Common::Point &impactPoint); + virtual void hitJunk(Common::Point impactPoint); + virtual void hitShuttle(Common::Point impactPoint); + + Point3D _weaponOrigin, _weaponTarget; + Point3D _weaponLocation; + float _weaponTime; + TimeValue _weaponDuration; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/spacechase3d.cpp b/engines/pegasus/neighborhood/mars/spacechase3d.cpp new file mode 100644 index 0000000000..05f8233763 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/spacechase3d.cpp @@ -0,0 +1,106 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/neighborhood/mars/spacechase3d.h" + +namespace Pegasus { + +void project3DTo2D(const Point3D &pt3D, Common::Point &pt2D) { + pt2D.x = (int)convertSpaceXToScreenH(pt3D.x, pt3D.z); + pt2D.y = (int)convertSpaceYToScreenV(pt3D.y, pt3D.z); +} + +void project2DTo3D(const Common::Point &pt2D, const float screenDistance, Point3D &pt3D) { + pt3D.x = convertScreenHToSpaceX(pt2D.x, screenDistance); + pt3D.y = convertScreenVToSpaceY(pt2D.y, screenDistance); + pt3D.z = screenDistance; +} + +void linearInterp(const Point3D &pt1, const Point3D &pt2, const float t, Point3D &pt3) { + pt3.x = pt1.x + (pt2.x - pt1.x) * t; + pt3.y = pt1.y + (pt2.y - pt1.y) * t; + pt3.z = pt1.z + (pt2.z - pt1.z) * t; +} + +void linearInterp(const Point3D &pt1, const float x2, const float y2, const float z2, const float t, Point3D &pt3) { + pt3.x = pt1.x + (x2 - pt1.x) * t; + pt3.y = pt1.y + (y2 - pt1.y) * t; + pt3.z = pt1.z + (z2 - pt1.z) * t; +} + +void linearInterp(const float x1, const float y1, const float z1, const Point3D &pt2, const float t, Point3D &pt3) { + pt3.x = x1 + (pt2.x - x1) * t; + pt3.y = y1 + (pt2.y - y1) * t; + pt3.z = z1 + (pt2.z - z1) * t; +} + +void linearInterp(const float x1, const float y1, const float z1, const float x2, const float y2, const float z2, + const float t, Point3D &pt3) { + pt3.x = x1 + (x2 - x1) * t; + pt3.y = y1 + (y2 - y1) * t; + pt3.z = z1 + (z2 - z1) * t; +} + +void linearInterp(const Common::Point &pt1, const Common::Point &pt2, const float t, Common::Point &pt3) { + pt3.x = (int)(pt1.x + (pt2.x - pt1.x) * t); + pt3.y = (int)(pt1.y + (pt2.y - pt1.y) * t); +} + +void linearInterp(const Common::Point &pt1, const float h2, const float v2, const float t, Common::Point &pt3) { + pt3.x = (int)(pt1.x + (h2 - pt1.x) * t); + pt3.y = (int)(pt1.y + (v2 - pt1.y) * t); +} + +void linearInterp(const float h1, const float v1, const Common::Point &pt2, const float t, Common::Point &pt3) { + pt3.x = (int)(h1 + (pt2.x - h1) * t); + pt3.y = (int)(v1 + (pt2.y - v1) * t); +} + +void linearInterp(const float h1, const float v1, const float h2, const float v2, const float t, Common::Point &pt3) { + pt3.x = (int)(h1 + (h2 - h1) * t); + pt3.y = (int)(v1 + (v2 - v1) * t); +} + +float linearInterp(const float arg1, const float arg2, const float t) { + return arg1 + (arg2 - arg1) * t; +} + +bool isNegative(int a) { + return a < 0; +} + +bool isPositive(int a) { + return a > 0; +} + +int sign(int a) { + return isNegative(a) ? -1 : isPositive(a) ? 1 : 0; +} + +bool sameSign(int a, int b) { + return sign(a) == sign(b); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/spacechase3d.h b/engines/pegasus/neighborhood/mars/spacechase3d.h new file mode 100644 index 0000000000..f6815e69bd --- /dev/null +++ b/engines/pegasus/neighborhood/mars/spacechase3d.h @@ -0,0 +1,91 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_SPACECHASE3D_H +#define PEGASUS_NEIGHBORHOOD_MARS_SPACECHASE3D_H + +#include "pegasus/neighborhood/mars/constants.h" + +namespace Pegasus { + +// This is approximately right for a field of view of 72 degrees +// (Should be set to the tangent of FOV). +//static const float kTangentFOV = 0.76254; +static const float kTangentFOV = 1.0; + +// Define these as macros and they can be used to define constants... +#define convertSpaceXToScreenH(x, z) \ + ((x) / (z) * (kScreenWidth / (2 * kTangentFOV)) + kShuttleWindowMidH) + +#define convertSpaceYToScreenV(y, z) \ + (kShuttleWindowMidV - (y) / (z) * (kScreenWidth / (2 * kTangentFOV))) + +#define convertScreenHToSpaceX(x, d) \ + (((2.0 * kTangentFOV) / kScreenWidth) * ((float)(x) - kShuttleWindowMidH) * (d)) + +#define convertScreenVToSpaceY(y, d) \ + (((2.0 * kTangentFOV) / kScreenWidth) * ((float)kShuttleWindowMidV - (y)) * (d)) + +struct Point3D { + float x, y, z; + + Point3D() : x(0), y(0), z(0) {} + Point3D(float x1, float y1, float z1) : x(x1), y(y1), z(z1) {} + bool operator==(const Point3D &p) const { return x == p.x && y == p.y && z == p.z; } + bool operator!=(const Point3D &p) const { return x != p.x || y != p.y || z != p.z; } + + void translate(float dx, float dy, float dz) { + x += dx; + y += dy; + z += dz; + } +}; + +static const int kScreenWidth = kShuttleWindowWidth; + +bool isNegative(int a); +bool isPositive(int a); +int sign(int a); +bool sameSign(int a, int b); + +void project3DTo2D(const Point3D &pt3D, Common::Point &pt2D); +void project2DTo3D(const Common::Point &pt2D, const float screenDistance, Point3D &pt3D); + +void linearInterp(const Point3D &pt1, const Point3D &pt2, const float t, Point3D &pt3); +void linearInterp(const Point3D &pt1, const float x2, const float y2, const float z2, const float t, Point3D &pt3); +void linearInterp(const float x1, const float y1, const float z1, const Point3D &pt2, const float t, Point3D &pt3); +void linearInterp(const float x1, const float y1, const float z1, const float x2, + const float y2, const float z2, const float t, Point3D &pt3); + +void linearInterp(const Common::Point &pt1, const Common::Point &pt2, const float t, Common::Point &pt3); +void linearInterp(const Common::Point &pt1, const float h2, const float v2, const float t, Common::Point &pt3); +void linearInterp(const float h1, const float v1, const Common::Point &pt2, const float t, Common::Point &pt3); +void linearInterp(const float h1, const float v1, const float h2, const float v2, const float t, Common::Point &pt3); + +float linearInterp(const float arg1, const float arg2, const float t); + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/spacejunk.cpp b/engines/pegasus/neighborhood/mars/spacejunk.cpp new file mode 100644 index 0000000000..ac8b1a23cc --- /dev/null +++ b/engines/pegasus/neighborhood/mars/spacejunk.cpp @@ -0,0 +1,212 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/pegasus.h" +#include "pegasus/neighborhood/mars/mars.h" +#include "pegasus/neighborhood/mars/spacejunk.h" + +namespace Pegasus { + +static const CoordType kMaxBounceSize = 90; +static const CoordType kBounceTargetHRange = 640 - kMaxBounceSize - 2; +static const CoordType kBounceTargetVRange = 480 - kMaxBounceSize - 2; + +static const float kJunkXTarget = 0; +static const float kJunkYTarget = 0; +static const float kJunkZTarget = kJunkMinDistance; + +SpaceJunk *g_spaceJunk = 0; + +SpaceJunk::SpaceJunk(const DisplayElementID id) : ScalingMovie(id) { + _timer.setScale(kJunkTimeScale); + _bouncing = false; + g_spaceJunk = this; +} + +SpaceJunk::~SpaceJunk() { + g_spaceJunk = 0; +} + +void SpaceJunk::launchJunk(int16 whichJunk, CoordType xOrigin, CoordType yOrigin) { + _bouncing = false; + TimeValue startTime = whichJunk * 16 * 40; + TimeValue stopTime = startTime + 16 * 40; + + _launchPoint = Point3D(convertScreenHToSpaceX(xOrigin, kJunkMaxDistance), + convertScreenVToSpaceY(yOrigin, kJunkMaxDistance), kJunkMaxDistance); + startIdling(); + stop(); + setFlags(0); + setSegment(startTime, stopTime); + setFlags(kLoopTimeBase); + setTime(startTime); + start(); + show(); + _timer.stop(); + _timer.setSegment(0, kJunkTravelTime); + _timer.setTime(0); + + // Force it to set up correctly from the get-go + useIdleTime(); + + _timer.start(); +} + +void SpaceJunk::setCenter(const CoordType centerX, const CoordType centerY) { + _center.x = centerX; + _center.y = centerY; + + Common::Rect r; + getBounds(r); + r.moveTo(CLIP<int>(centerX - (r.width() >> 1), 0, 640 - r.width()), CLIP<int>(centerY - (r.height() >> 1), 0, 480 - r.height())); + setBounds(r); +} + +void SpaceJunk::setScaleSize(const CoordType size) { + Common::Rect r; + r.left = _center.x - (size >> 1); + r.top = _center.y - (size >> 1); + r.right = r.left + size; + r.bottom = r.top + size; + setBounds(r); +} + +void SpaceJunk::useIdleTime() { + if (_bouncing) { + TimeValue time = _timer.getTime(); + Common::Point pt; + pt.x = linearInterp(0, _bounceTime, time, _bounceStart.x, _bounceStop.x); + pt.y = linearInterp(0, _bounceTime, time, _bounceStart.y, _bounceStop.y); + CoordType size = linearInterp(0, _bounceTime, time, _bounceSizeStart, _bounceSizeStop); + setCenter(pt.x, pt.y); + setScaleSize(size); + + if (time == _bounceTime) { + stop(); + stopIdling(); + hide(); + ((Mars *)g_neighborhood)->setUpNextDropTime(); + } + } else { + float t = (float)_timer.getTime() / kJunkTravelTime; + linearInterp(_launchPoint, kJunkXTarget, kJunkYTarget, kJunkZTarget, t, _junkPosition); + + Common::Point pt2D; + project3DTo2D(_junkPosition, pt2D); + setCenter(pt2D.x, pt2D.y); + setScaleSize((int)(convertSpaceYToScreenV(_junkPosition.y - kJunkSize / 2, _junkPosition.z) - + convertSpaceYToScreenV(_junkPosition.y + kJunkSize / 2, _junkPosition.z))); + + if (t == 1.0) { + rebound(kCollisionReboundTime); + ((Mars *)g_neighborhood)->hitByJunk(); + } + } +} + +bool SpaceJunk::pointInJunk(const Common::Point &pt) { + Common::Rect r; + getBounds(r); + + int dx = r.width() / 4; + int dy = r.height() / 4; + + r.left += dx; + r.right -= dx; + r.top += dy; + r.top -= dy; + + return r.contains(pt); +} + +void SpaceJunk::rebound(const TimeValue reboundTime) { + Common::Rect bounds; + getBounds(bounds); + + _bounceStart.x = (bounds.left + bounds.right) >> 1; + _bounceStart.y = (bounds.top + bounds.bottom) >> 1; + + PegasusEngine *vm = (PegasusEngine *)g_engine; + + switch (vm->getRandomNumber(3)) { + case 0: + _bounceStop.x = kMaxBounceSize / 2 + 1 + vm->getRandomNumber(kBounceTargetHRange - 1); + _bounceStop.y = kMaxBounceSize / 2 + 1; + break; + case 1: + _bounceStop.x = kMaxBounceSize / 2 + 1 + vm->getRandomNumber(kBounceTargetHRange - 1); + _bounceStop.y = 480 - kMaxBounceSize / 2 + 1; + break; + case 2: + _bounceStop.x = kMaxBounceSize / 2 + 1; + _bounceStop.y = kMaxBounceSize / 2 + 1 + vm->getRandomNumber(kBounceTargetVRange - 1); + break; + case 3: + _bounceStop.x = 640 - kMaxBounceSize / 2 + 1; + _bounceStop.y = kMaxBounceSize / 2 + 1 + vm->getRandomNumber(kBounceTargetVRange - 1); + break; + } + + _bounceSizeStart = bounds.width(); + _bounceSizeStop = MIN(_bounceSizeStart, kMaxBounceSize); + + _timer.stop(); + _timer.setSegment(0, reboundTime); + _bounceTime = reboundTime; + _timer.setTime(0); + _timer.start(); + + _bouncing = true; +} + +void SpaceJunk::hitByEnergyBeam(Common::Point) { + rebound(kWeaponReboundTime); + setGlowing(true); + ((PegasusEngine *)g_engine)->delayShell(1, 3); + setGlowing(false); +} + +void SpaceJunk::hitByGravitonCannon(Common::Point impactPoint) { + stop(); + stopIdling(); + hide(); + + Common::Rect r; + getBounds(r); + r = Common::Rect::center(impactPoint.x, impactPoint.y, r.width(), r.height()); + + ((Mars *)g_neighborhood)->showBigExplosion(r, kShuttleJunkOrder); + ((Mars *)g_neighborhood)->setUpNextDropTime(); +} + +void SpaceJunk::getJunkPosition(Point3D &position) { + position = _junkPosition; +} + +bool SpaceJunk::isJunkFlying() { + return isIdling(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/spacejunk.h b/engines/pegasus/neighborhood/mars/spacejunk.h new file mode 100644 index 0000000000..f2dbf9a838 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/spacejunk.h @@ -0,0 +1,78 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_SPACEJUNK_H +#define PEGASUS_NEIGHBORHOOD_MARS_SPACEJUNK_H + +#include "pegasus/movie.h" +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/mars/spacechase3d.h" + +namespace Pegasus { + +static const CoordType kJunkMaxScreenSize = 250; + +static const float kJunkSize = convertScreenVToSpaceY(kShuttleWindowMidV - kJunkMaxScreenSize / 2, kJunkMinDistance) - + convertScreenVToSpaceY(kShuttleWindowMidV + kJunkMaxScreenSize / 2, kJunkMinDistance); + +class SpaceJunk : public ScalingMovie, public Idler { +public: + SpaceJunk(const DisplayElementID); + virtual ~SpaceJunk(); + + void setCenter(const CoordType, const CoordType); + void setScaleSize(const CoordType); + + void useIdleTime(); + + void launchJunk(int16, CoordType, CoordType); + + void getJunkPosition(Point3D &); + bool isJunkFlying(); + + bool pointInJunk(const Common::Point &); + + void hitByEnergyBeam(Common::Point impactPoint); + void hitByGravitonCannon(Common::Point impactPoint); + + bool junkFlying() { return _timer.isRunning(); } + +protected: + void rebound(const TimeValue); + + TimeBase _timer; + Point3D _launchPoint, _junkPosition; + Common::Point _center; + bool _bouncing; + Common::Point _bounceStart, _bounceStop; + CoordType _bounceSizeStart, _bounceSizeStop; + TimeValue _bounceTime; +}; + +extern SpaceJunk *g_spaceJunk; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/tractorbeam.cpp b/engines/pegasus/neighborhood/mars/tractorbeam.cpp new file mode 100644 index 0000000000..81c96299cf --- /dev/null +++ b/engines/pegasus/neighborhood/mars/tractorbeam.cpp @@ -0,0 +1,139 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/pegasus.h" +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/mars/tractorbeam.h" + +namespace Pegasus { + +TractorBeam::TractorBeam() : DisplayElement(kNoDisplayElement) { + setBounds(kShuttleTractorLeft, kShuttleTractorTop, kShuttleTractorLeft + kShuttleTractorWidth, + kShuttleTractorTop + kShuttleTractorHeight); + setDisplayOrder(kShuttleTractorBeamOrder); + +} + +static const int kHalfWidth = kShuttleTractorWidth >> 1; +static const int kHalfHeight = kShuttleTractorHeight >> 1; + +static const int kW3Vert = kHalfHeight * kHalfHeight * kHalfHeight; +static const int kW3Div2Vert = kW3Vert >> 1; + +static const int kW3Horiz = kHalfWidth * kHalfWidth * kHalfWidth; +static const int kW3Div2Horiz = kW3Horiz >> 1; + +static const int kMaxLevel = 50; + +static const int kAVert = -2 * kMaxLevel; +static const int kBVert = 3 * kMaxLevel * kHalfHeight; + +#define READ_PIXEL(ptr) \ + if (screen->format.bytesPerPixel == 2) \ + color = READ_UINT16(ptr); \ + else \ + color = READ_UINT32(ptr); \ + screen->format.colorToRGB(color, r, g, b) + +#define WRITE_PIXEL(ptr) \ + color = screen->format.RGBToColor(r, g, b); \ + if (screen->format.bytesPerPixel == 2) \ + WRITE_UINT16(ptr, color); \ + else \ + WRITE_UINT32(ptr, color) + +#define DO_BLEND(ptr) \ + READ_PIXEL(ptr); \ + g += (((0xff - g) * blendHoriz) >> 8); \ + b += (((0xff - b) * blendHoriz) >> 8); \ + WRITE_PIXEL(ptr) + +void TractorBeam::draw(const Common::Rect &) { + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getWorkArea(); + + // Set up vertical DDA. + int blendVert = 0; + int dVert = 0; + int d1Vert = kAVert + kBVert; + int d2Vert = 6 * kAVert + 2 * kBVert; + int d3Vert = 6 * kAVert; + + byte *rowPtrTop = (byte *)screen->getBasePtr(_bounds.left, _bounds.top); + byte *rowPtrBottom = (byte *)screen->getBasePtr(_bounds.left, _bounds.top + ((kHalfHeight << 1) - 1)); + + for (int y = kHalfHeight; y > 0; y--) { + // Set up horizontal DDA + int A = -2 * blendVert; + int B = 3 * blendVert * kHalfWidth; + int blendHoriz = 0; + int dHoriz = 0; + int d1Horiz = A + B; + int d2Horiz = 6 * A + 2 * B; + int d3Horiz = 6 * A; + + byte *pTopLeft = rowPtrTop; + byte *pTopRight = rowPtrTop + (kHalfWidth * 2 - 1) * screen->format.bytesPerPixel; + byte *pBottomLeft = rowPtrBottom; + byte *pBottomRight = rowPtrBottom + (kHalfWidth * 2 - 1) * screen->format.bytesPerPixel; + + for (int x = kHalfWidth; x > 0; x--) { + byte r, g, b; + uint32 color; + + DO_BLEND(pTopLeft); + DO_BLEND(pTopRight); + DO_BLEND(pBottomLeft); + DO_BLEND(pBottomRight); + + pTopLeft += screen->format.bytesPerPixel; + pBottomLeft += screen->format.bytesPerPixel; + pTopRight -= screen->format.bytesPerPixel; + pBottomRight -= screen->format.bytesPerPixel; + + while (dHoriz > kW3Div2Horiz) { + blendHoriz++; + dHoriz -= kW3Horiz; + } + + dHoriz += d1Horiz; + d1Horiz += d2Horiz; + d2Horiz += d3Horiz; + } + + rowPtrTop += screen->pitch; + rowPtrBottom -= screen->pitch; + + while (dVert > kW3Div2Vert) { + blendVert++; + dVert -= kW3Vert; + } + + dVert += d1Vert; + d1Vert += d2Vert; + d2Vert += d3Vert; + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/tractorbeam.h b/engines/pegasus/neighborhood/mars/tractorbeam.h new file mode 100644 index 0000000000..cd87992d11 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/tractorbeam.h @@ -0,0 +1,43 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_TRACTORBEAM_H +#define PEGASUS_NEIGHBORHOOD_MARS_TRACTORBEAM_H + +#include "pegasus/elements.h" + +namespace Pegasus { + +class TractorBeam : public DisplayElement { +public: + TractorBeam(); + virtual ~TractorBeam() {} + + void draw(const Common::Rect &); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/neighborhood.cpp b/engines/pegasus/neighborhood/neighborhood.cpp new file mode 100644 index 0000000000..3378459a5d --- /dev/null +++ b/engines/pegasus/neighborhood/neighborhood.cpp @@ -0,0 +1,1774 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/debug.h" +#include "common/stream.h" + +#include "pegasus/compass.h" +#include "pegasus/cursor.h" +#include "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/graphics.h" +#include "pegasus/input.h" +#include "pegasus/interaction.h" +#include "pegasus/interface.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/mapchip.h" +#include "pegasus/neighborhood/neighborhood.h" +#include "pegasus/neighborhood/tsa/fulltsa.h" +#include "pegasus/neighborhood/tsa/tinytsa.h" + +namespace Pegasus { + +StriderCallBack::StriderCallBack(Neighborhood *neighborhood) { + _neighborhood = neighborhood; +} + +void StriderCallBack::callBack() { + _neighborhood->checkStriding(); +} + +static const TimeValue kStridingSlop = 39; + +Neighborhood *g_neighborhood = 0; + +Neighborhood::Neighborhood(InputHandler *nextHandler, PegasusEngine *vm, const Common::String &resName, NeighborhoodID id) + : InputHandler(nextHandler), IDObject(id), _vm(vm), _resName(resName), _navMovie(kNavMovieID), _stridingCallBack(this), + _neighborhoodNotification(kNeighborhoodNotificationID, (NotificationManager *)vm), _pushIn(kNoDisplayElement), + _turnPush(kTurnPushID), _croppedMovie(kCroppedMovieID) { + GameState.setOpenDoorLocation(kNoRoomID, kNoDirection); + _currentAlternate = 0; + _currentActivation = kActivateHotSpotAlways; + _interruptionFilter = kFilterAllInput; + allowInput(true); + resetLastExtra(); + g_neighborhood = this; + _currentInteraction = 0; + _doneWithInteraction = false; + _croppedMovie.setDisplayOrder(kCroppedMovieLayer); +} + +Neighborhood::~Neighborhood() { + for (HotspotIterator it = _neighborhoodHotspots.begin(); it != _neighborhoodHotspots.end(); it++) + g_allHotspots.remove(*it); + + _neighborhoodHotspots.deleteHotspots(); + g_neighborhood = 0; + + loadLoopSound1(""); + loadLoopSound2(""); + newInteraction(kNoInteractionID); + + if (g_AIArea) + g_AIArea->removeAllRules(); +} + +void Neighborhood::init() { + _neighborhoodNotification.notifyMe(this, kNeighborhoodFlags, kNeighborhoodFlags); + _navMovieCallBack.setNotification(&_neighborhoodNotification); + _turnPushCallBack.setNotification(&_neighborhoodNotification); + _delayCallBack.setNotification(&_neighborhoodNotification); + _spotSoundCallBack.setNotification(&_neighborhoodNotification); + + debug(0, "Loading '%s' neighborhood resources", _resName.c_str()); + + Common::SeekableReadStream *stream = _vm->_resFork->getResource(_doorTable.getResTag(), _resName); + if (!stream) + error("Failed to load doors"); + _doorTable.loadFromStream(stream); + delete stream; + + stream = _vm->_resFork->getResource(_exitTable.getResTag(), _resName); + if (!stream) + error("Failed to load exits"); + _exitTable.loadFromStream(stream); + delete stream; + + stream = _vm->_resFork->getResource(_extraTable.getResTag(), _resName); + if (!stream) + error("Failed to load extras"); + _extraTable.loadFromStream(stream); + delete stream; + + stream = _vm->_resFork->getResource(_hotspotInfoTable.getResTag(), _resName); + if (!stream) + error("Failed to load hotspot info"); + _hotspotInfoTable.loadFromStream(stream); + delete stream; + + stream = _vm->_resFork->getResource(_spotTable.getResTag(), _resName); + if (!stream) + error("Failed to load spots"); + _spotTable.loadFromStream(stream); + delete stream; + + stream = _vm->_resFork->getResource(_turnTable.getResTag(), _resName); + if (!stream) + error("Failed to load turns"); + _turnTable.loadFromStream(stream); + delete stream; + + stream = _vm->_resFork->getResource(_viewTable.getResTag(), _resName); + if (!stream) + error("Failed to load views"); + _viewTable.loadFromStream(stream); + delete stream; + + stream = _vm->_resFork->getResource(_zoomTable.getResTag(), _resName); + if (!stream) + error("Failed to load zooms"); + _zoomTable.loadFromStream(stream); + delete stream; + + createNeighborhoodSpots(); + + _navMovie.initFromMovieFile(getNavMovieName()); + _navMovie.setVolume(_vm->getSoundFXLevel()); + + Common::String soundSpotsName = getSoundSpotsName(); + if (soundSpotsName.empty()) { + _spotSounds.disposeSound(); + } else { + _spotSounds.initFromQuickTime(getSoundSpotsName()); + _spotSounds.setVolume(_vm->getSoundFXLevel()); + } + + _navMovie.setDisplayOrder(kNavMovieOrder); + _navMovie.startDisplaying(); + + Common::Rect bounds; + _navMovie.getBounds(bounds); + _pushIn.allocateSurface(bounds); + + _turnPush.setInAndOutElements(&_pushIn, &_navMovie); + _turnPush.setDisplayOrder(kTurnPushOrder); + _turnPush.startDisplaying(); + _navMovieCallBack.initCallBack(&_navMovie, kCallBackAtExtremes); + _stridingCallBack.initCallBack(&_navMovie, kCallBackAtTime); + _turnPushCallBack.initCallBack(&_turnPush, kCallBackAtExtremes); + _delayCallBack.initCallBack(&_delayTimer, kCallBackAtExtremes); + _spotSoundCallBack.initCallBack(&_spotSounds, kCallBackAtExtremes); + + setUpAIRules(); + + if (g_compass) + g_compass->setFaderValue(getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection())); + + _soundLoop1.attachFader(&_loop1Fader); + _soundLoop2.attachFader(&_loop2Fader); + startIdling(); +} + +void Neighborhood::start() { + GameState.setCurrentRoom(GameState.getLastRoom()); + GameState.setCurrentDirection(GameState.getLastDirection()); + arriveAt(GameState.getNextRoom(), GameState.getNextDirection()); +} + +void Neighborhood::receiveNotification(Notification *, const NotificationFlags flags) { + if ((flags & (kNeighborhoodMovieCompletedFlag | kTurnCompletedFlag)) != 0 && g_AIArea) + g_AIArea->unlockAI(); + if (flags & kMoveForwardCompletedFlag) + arriveAt(GameState.getNextRoom(), GameState.getNextDirection()); + if (flags & kTurnCompletedFlag) + turnTo(GameState.getNextDirection()); + if (flags & kSpotCompletedFlag) + spotCompleted(); + if (flags & kDoorOpenCompletedFlag) + doorOpened(); + if (flags & kActionRequestCompletedFlag) + popActionQueue(); + if (flags & kDeathExtraCompletedFlag) + die(_extraDeathReason); +} + +void Neighborhood::arriveAt(const RoomID room, const DirectionConstant direction) { + if (g_map) + g_map->moveToMapLocation(GameState.getCurrentNeighborhood(), room, direction); + + GameState.setCurrentNeighborhood(getObjectID()); + + _currentActivation = kActivateHotSpotAlways; + _interruptionFilter = kFilterAllInput; + + if (room != GameState.getCurrentRoom() || direction != GameState.getCurrentDirection()) { + GameState.setCurrentRoom(room); + GameState.setCurrentDirection(direction); + loadAmbientLoops(); + activateCurrentView(room, direction, kSpotOnArrivalMask); + } else { + loadAmbientLoops(); + showViewFrame(getViewTime(GameState.getCurrentRoom(), GameState.getCurrentDirection())); + } + + if (GameState.getOpenDoorRoom() != kNoRoomID) { + // Arriving always closes a door. + loadAmbientLoops(); + closeDoorOffScreen(GameState.getOpenDoorRoom(), GameState.getOpenDoorDirection()); + GameState.setOpenDoorLocation(kNoRoomID, kNoDirection); + } + + if (g_compass) + g_compass->setFaderValue(getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection())); + + if (g_AIArea) + g_AIArea->checkMiddleArea(); + + checkContinuePoint(room, direction); +} + +// These functions can be overridden to tweak the exact frames used. + +void Neighborhood::getExitEntry(const RoomID room, const DirectionConstant direction, ExitTable::Entry &entry) { + entry = _exitTable.findEntry(room, direction, _currentAlternate); + + if (entry.isEmpty()) + entry = _exitTable.findEntry(room, direction, kNoAlternateID); +} + +TimeValue Neighborhood::getViewTime(const RoomID room, const DirectionConstant direction) { + if (GameState.getOpenDoorRoom() == room && GameState.getOpenDoorDirection() == direction) { + // If we get here, the door entry for this location must exist. + DoorTable::Entry doorEntry = _doorTable.findEntry(room, direction, _currentAlternate); + + if (doorEntry.isEmpty()) + doorEntry = _doorTable.findEntry(room, direction, kNoAlternateID); + + return doorEntry.movieEnd - 1; + } + + ViewTable::Entry viewEntry = _viewTable.findEntry(room, direction, _currentAlternate); + + if (viewEntry.isEmpty()) + viewEntry = _viewTable.findEntry(room, direction, kNoAlternateID); + + return viewEntry.time; +} + +void Neighborhood::getDoorEntry(const RoomID room, const DirectionConstant direction, DoorTable::Entry &doorEntry) { + doorEntry = _doorTable.findEntry(room, direction, _currentAlternate); + + if (doorEntry.isEmpty()) + doorEntry = _doorTable.findEntry(room, direction, kNoAlternateID); +} + +DirectionConstant Neighborhood::getTurnEntry(const RoomID room, const DirectionConstant direction, const TurnDirection turnDirection) { + TurnTable::Entry turnEntry = _turnTable.findEntry(room, direction, turnDirection, _currentAlternate); + + if (turnEntry.isEmpty()) + turnEntry = _turnTable.findEntry(room, direction, turnDirection, kNoAlternateID); + + return turnEntry.endDirection; +} + +void Neighborhood::findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &spotEntry) { + spotEntry = _spotTable.findEntry(room, direction, flags, _currentAlternate); + + if (spotEntry.isEmpty()) + spotEntry = _spotTable.findEntry(room, direction, flags, kNoAlternateID); +} + +void Neighborhood::getZoomEntry(const HotSpotID id, ZoomTable::Entry &zoomEntry) { + zoomEntry = _zoomTable.findEntry(id); +} + +void Neighborhood::getHotspotEntry(const HotSpotID id, HotspotInfoTable::Entry &hotspotEntry) { + hotspotEntry = _hotspotInfoTable.findEntry(id); +} + +void Neighborhood::getExtraEntry(const uint32 id, ExtraTable::Entry &extraEntry) { + extraEntry = _extraTable.findEntry(id); +} + +///////////////////////////////////////////// +// +// "Can" functions: Called to see whether or not a user is allowed to do something + +CanMoveForwardReason Neighborhood::canMoveForward(ExitTable::Entry &entry) { + DoorTable::Entry door; + + getExitEntry(GameState.getCurrentRoom(), GameState.getCurrentDirection(), entry); + getDoorEntry(GameState.getCurrentRoom(), GameState.getCurrentDirection(), door); + + // Fixed this so that doors that don't lead anywhere can be opened, but not walked + // through. + if (door.flags & kDoorPresentMask) { + if (GameState.isCurrentDoorOpen()) { + if (entry.exitRoom == kNoRoomID) + return kCantMoveBlocked; + else + return kCanMoveForward; + } else if (door.flags & kDoorLockedMask) { + return kCantMoveDoorLocked; + } else { + return kCantMoveDoorClosed; + } + } else if (entry.exitRoom == kNoRoomID) { + return kCantMoveBlocked; + } + + return kCanMoveForward; +} + +CanTurnReason Neighborhood::canTurn(TurnDirection turnDirection, DirectionConstant &nextDir) { + nextDir = getTurnEntry(GameState.getCurrentRoom(), GameState.getCurrentDirection(), turnDirection); + + if (nextDir == kNoDirection) + return kCantTurnNoTurn; + + return kCanTurn; +} + +CanOpenDoorReason Neighborhood::canOpenDoor(DoorTable::Entry &entry) { + getDoorEntry(GameState.getCurrentRoom(), GameState.getCurrentDirection(), entry); + + if (entry.flags & kDoorPresentMask) { + if (GameState.isCurrentDoorOpen()) + return kCantOpenAlreadyOpen; + + if (entry.flags & kDoorLockedMask) + return kCantOpenLocked; + + return kCanOpenDoor; + } + + return kCantOpenNoDoor; +} + +void Neighborhood::createNeighborhoodSpots() { + Common::SeekableReadStream *hotspotList = _vm->_resFork->getResource(MKTAG('H', 'S', 'L', 's'), _resName); + if (!hotspotList) + error("Could not load neighborhood hotspots"); + + uint32 hotspotCount = hotspotList->readUint32BE(); + + while (hotspotCount--) { + uint16 id = hotspotList->readUint16BE(); + uint32 flags = hotspotList->readUint32BE(); + uint32 rgnSize = hotspotList->readUint32BE(); + + int32 startPos = hotspotList->pos(); + + debug(0, "Hotspot %d:", id); + Region region(hotspotList); + + hotspotList->seek(startPos + rgnSize); + + Hotspot *hotspot = new Hotspot(id); + hotspot->setHotspotFlags(flags); + hotspot->setArea(region); + + g_allHotspots.push_back(hotspot); + _neighborhoodHotspots.push_back(hotspot); + } + + delete hotspotList; +} + +void Neighborhood::popActionQueue() { + if (!_actionQueue.empty()) { + QueueRequest topRequest = _actionQueue.pop(); + + switch (topRequest.requestType) { + case kNavExtraRequest: + _navMovie.stop(); + break; + case kSpotSoundRequest: + _spotSounds.stopSound(); + break; + case kDelayRequest: + _delayTimer.stop(); + break; + } + + serviceActionQueue(); + } +} + +void Neighborhood::serviceActionQueue() { + if (!_actionQueue.empty()) { + QueueRequest &topRequest = _actionQueue.front(); + + if (!topRequest.playing) { + topRequest.playing = true; + switch (topRequest.requestType) { + case kNavExtraRequest: + startExtraSequence(topRequest.extra, topRequest.flags, topRequest.interruptionFilter); + break; + case kSpotSoundRequest: + _spotSounds.stopSound(); + _spotSounds.playSoundSegment(topRequest.start, topRequest.stop); + _interruptionFilter = topRequest.interruptionFilter; + _spotSoundCallBack.setCallBackFlag(topRequest.flags); + _spotSoundCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + break; + case kDelayRequest: + _delayTimer.stop(); + _delayCallBack.setCallBackFlag(topRequest.flags); + _delayTimer.setSegment(0, topRequest.start, topRequest.stop); + _delayTimer.setTime(0); + _delayCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _interruptionFilter = topRequest.interruptionFilter; + _delayTimer.start(); + break; + } + } + } else { + _interruptionFilter = kFilterAllInput; + } +} + +void Neighborhood::requestAction(const QueueRequestType requestType, const ExtraID extra, const TimeValue in, const TimeValue out, + const InputBits interruptionFilter, const NotificationFlags flags) { + + QueueRequest request; + + request.requestType = requestType; + request.extra = extra; + request.start = in; + request.stop = out; + request.interruptionFilter = interruptionFilter; + request.playing = false; + request.flags = flags | kActionRequestCompletedFlag; + request.notification = &_neighborhoodNotification; + _actionQueue.push(request); + if (_actionQueue.size() == 1) + serviceActionQueue(); +} + +void Neighborhood::requestExtraSequence(const ExtraID whichExtra, const NotificationFlags flags, const InputBits interruptionFilter) { + requestAction(kNavExtraRequest, whichExtra, 0, 0, interruptionFilter, flags); +} + +void Neighborhood::requestSpotSound(const TimeValue in, const TimeValue out, const InputBits interruptionFilter, const NotificationFlags flags) { + requestAction(kSpotSoundRequest, 0xFFFFFFFF, in, out, interruptionFilter, flags); +} + +void Neighborhood::playSpotSoundSync(const TimeValue in, const TimeValue out) { + // Let the action queue play out first... + while (!actionQueueEmpty()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->checkNotifications(); + _vm->_system->delayMillis(10); + } + + _spotSounds.stopSound(); + _spotSounds.playSoundSegment(in, out); + + while (_spotSounds.isPlaying()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } +} + +void Neighborhood::requestDelay(const TimeValue delayDuration, const TimeScale delayScale, const InputBits interruptionFilter, const NotificationFlags flags) { + requestAction(kDelayRequest, 0xFFFFFFFF, delayDuration, delayScale, interruptionFilter, flags); +} + +bool operator==(const QueueRequest &arg1, const QueueRequest &arg2) { + return arg1.requestType == arg2.requestType && arg1.extra == arg2.extra && + arg1.start == arg2.start && arg1.stop == arg2.stop; +} + +bool operator!=(const QueueRequest &arg1, const QueueRequest &arg2) { + return !operator==(arg1, arg2); +} + +Common::String Neighborhood::getBriefingMovie() { + if (_currentInteraction) + return _currentInteraction->getBriefingMovie(); + + return Common::String(); +} + +Common::String Neighborhood::getEnvScanMovie() { + if (_currentInteraction) + return _currentInteraction->getEnvScanMovie(); + + return Common::String(); +} + +uint Neighborhood::getNumHints() { + if (_currentInteraction) + return _currentInteraction->getNumHints(); + + return 0; +} + +Common::String Neighborhood::getHintMovie(uint hintNum) { + if (_currentInteraction) + return _currentInteraction->getHintMovie(hintNum); + + return Common::String(); +} + +bool Neighborhood::canSolve() { + if (_currentInteraction) + return _currentInteraction->canSolve(); + + return false; +} + +void Neighborhood::doSolve() { + if (_currentInteraction) + _currentInteraction->doSolve(); +} + +bool Neighborhood::okayToJump() { + return !_vm->playerHasItemID(kGasCanister) && !_vm->playerHasItemID(kMachineGun); +} + +AirQuality Neighborhood::getAirQuality(const RoomID) { + return kAirQualityGood; +} + +void Neighborhood::checkStriding() { + if (stillMoveForward()) { + ExitTable::Entry nextExit; + getExitEntry(GameState.getNextRoom(), GameState.getNextDirection(), nextExit); + keepStriding(nextExit); + } else { + stopStriding(); + } +} + +bool Neighborhood::stillMoveForward() { + Input input; + + InputHandler::readInputDevice(input); + return input.upButtonAnyDown(); +} + +void Neighborhood::keepStriding(ExitTable::Entry &nextExitEntry) { + FaderMoveSpec compassMove; + + if (g_map) + g_map->moveToMapLocation(GameState.getCurrentNeighborhood(), GameState.getNextRoom(), GameState.getNextDirection()); + + if (g_compass) + getExitCompassMove(nextExitEntry, compassMove); + + GameState.setCurrentRoom(GameState.getNextRoom()); + GameState.setCurrentDirection(GameState.getNextDirection()); + GameState.setNextRoom(nextExitEntry.exitRoom); + GameState.setNextDirection(nextExitEntry.exitDirection); + + if (nextExitEntry.movieEnd == nextExitEntry.exitEnd) + scheduleNavCallBack(kNeighborhoodMovieCompletedFlag | kMoveForwardCompletedFlag); + else + scheduleStridingCallBack(nextExitEntry.movieEnd - kStridingSlop, kStrideCompletedFlag); + + if (g_compass) + g_compass->startFader(compassMove); +} + +void Neighborhood::stopStriding() { + _navMovie.stop(); + _neighborhoodNotification.setNotificationFlags(kNeighborhoodMovieCompletedFlag | + kMoveForwardCompletedFlag, kNeighborhoodMovieCompletedFlag | kMoveForwardCompletedFlag); +} + +// Compass support +int16 Neighborhood::getStaticCompassAngle(const RoomID, const DirectionConstant dir) { + // North, south, east, west + static const int16 compassAngles[] = { 0, 180, 90, 270 }; + return compassAngles[dir]; +} + +void Neighborhood::getExitCompassMove(const ExitTable::Entry &exitEntry, FaderMoveSpec &compassMove) { + int32 startAngle = getStaticCompassAngle(exitEntry.room, exitEntry.direction); + int32 stopAngle = getStaticCompassAngle(exitEntry.exitRoom, exitEntry.exitDirection); + + if (startAngle > stopAngle) { + if (stopAngle + 180 < startAngle) + stopAngle += 360; + } else { + if (startAngle + 180 < stopAngle) + startAngle += 360; + } + + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), exitEntry.movieStart, startAngle, exitEntry.movieEnd, stopAngle); +} + +void Neighborhood::scheduleNavCallBack(NotificationFlags flags) { + _navMovieCallBack.cancelCallBack(); + + if (flags != 0) { + _navMovieCallBack.setCallBackFlag(flags); + _navMovieCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + } +} + +void Neighborhood::scheduleStridingCallBack(const TimeValue strideStop, NotificationFlags flags) { + _stridingCallBack.cancelCallBack(); + + if (flags != 0) + _stridingCallBack.scheduleCallBack(kTriggerTimeFwd, strideStop, _navMovie.getScale()); +} + +void Neighborhood::moveNavTo(const CoordType h, const CoordType v) { + CoordType oldH, oldV; + _navMovie.getLocation(oldH, oldV); + + CoordType offH = h - oldH; + CoordType offV = v - oldV; + + _navMovie.moveElementTo(h, v); + _turnPush.moveElementTo(h, v); + + if (offH != 0 || offV != 0) + for (HotspotList::iterator it = _neighborhoodHotspots.begin(); it != _neighborhoodHotspots.end(); it++) + if ((*it)->getHotspotFlags() & kNeighborhoodSpotFlag) + (*it)->moveSpot(offH, offV); +} + +void Neighborhood::activateHotspots() { + InputHandler::activateHotspots(); + + for (HotspotInfoTable::iterator it = _hotspotInfoTable.begin(); it != _hotspotInfoTable.end(); it++) { + HotspotInfoTable::Entry entry = *it; + + if (entry.hotspotRoom == GameState.getCurrentRoom() && entry.hotspotDirection == GameState.getCurrentDirection() + && (entry.hotspotActivation == _currentActivation || entry.hotspotActivation == kActivateHotSpotAlways)) { + Hotspot *hotspot = g_allHotspots.findHotspotByID(entry.hotspot); + if (hotspot) + activateOneHotspot(entry, hotspot); + } + } +} + +void Neighborhood::clickInHotspot(const Input &input, const Hotspot *clickedSpot) { + HotSpotFlags flags = clickedSpot->getHotspotFlags(); + + if ((flags & (kPickUpItemSpotFlag | kPickUpBiochipSpotFlag)) != 0) { + ItemID itemID = kNoItemID; + + for (HotspotInfoTable::iterator it = _hotspotInfoTable.begin(); it != _hotspotInfoTable.end(); it++) { + if (it->hotspot == clickedSpot->getObjectID()) { + itemID = it->hotspotItem; + break; + } + } + + if (itemID != kNoItemID) { + Item *draggingItem = g_allItems.findItemByID(itemID); + + if (draggingItem) { + takeItemFromRoom(draggingItem); + + if ((flags & kPickUpItemSpotFlag) != 0) + _vm->dragItem(input, draggingItem, kDragInventoryPickup); + else + _vm->dragItem(input, draggingItem, kDragBiochipPickup); + } + } + } else { + // Check other flags here? + if ((flags & kZoomSpotFlags) != 0) { + zoomTo(clickedSpot); + } else if ((flags & kPlayExtraSpotFlag) != 0) { + HotspotInfoTable::Entry hotspotEntry; + getHotspotEntry(clickedSpot->getObjectID(), hotspotEntry); + startExtraSequence(hotspotEntry.hotspotExtra, kExtraCompletedFlag, kFilterNoInput); + } else if ((flags & kOpenDoorSpotFlag) != 0) { + openDoor(); + } else { + InputHandler::clickInHotspot(input, clickedSpot); + } + } +} + +void Neighborhood::cantMoveThatWay(CanMoveForwardReason reason) { + switch (reason) { + case kCantMoveDoorClosed: + case kCantMoveDoorLocked: + openDoor(); + break; + case kCantMoveBlocked: + zoomUpOrBump(); + break; + default: + bumpIntoWall(); + break; + } +} + +void Neighborhood::cantOpenDoor(CanOpenDoorReason) { + bumpIntoWall(); +} + +void Neighborhood::turnTo(const DirectionConstant direction) { + if (g_map) + g_map->moveToMapLocation(GameState.getCurrentNeighborhood(), GameState.getCurrentRoom(), direction); + + // clone2727 says: Is this necessary? + _vm->_gfx->setCurSurface(_navMovie.getSurface()); + _pushIn.copyToCurrentPort(); + _vm->_gfx->setCurSurface(_vm->_gfx->getWorkArea()); + + // Added 2/10/97. Shouldn't this be here? Shouldn't we set the current activation to + // always when turning to a new view? + _currentActivation = kActivateHotSpotAlways; + + _interruptionFilter = kFilterAllInput; + + if (direction != GameState.getCurrentDirection()) { + GameState.setCurrentDirection(direction); + activateCurrentView(GameState.getCurrentRoom(), direction, kSpotOnTurnMask); + } else { + showViewFrame(getViewTime(GameState.getCurrentRoom(), GameState.getCurrentDirection())); + } + + if (GameState.getOpenDoorRoom() != kNoRoomID) { + // Turning always closes a door. + loadAmbientLoops(); + closeDoorOffScreen(GameState.getOpenDoorRoom(), GameState.getOpenDoorDirection()); + GameState.setOpenDoorLocation(kNoRoomID, kNoDirection); + } + + if (g_AIArea) + g_AIArea->checkMiddleArea(); + + checkContinuePoint(GameState.getCurrentRoom(), direction); + + _vm->_cursor->hideUntilMoved(); +} + +void Neighborhood::spotCompleted() { + _interruptionFilter = kFilterAllInput; + showViewFrame(getViewTime(GameState.getCurrentRoom(), GameState.getCurrentDirection())); +} + +void Neighborhood::doorOpened() { + _interruptionFilter = kFilterAllInput; + + // 2/23/97 + // Fixes funny bug with doors that are opened by dropping things on them... + setCurrentActivation(kActivateHotSpotAlways); + + GameState.setOpenDoorLocation(GameState.getCurrentRoom(), GameState.getCurrentDirection()); + + SpotTable::Entry entry; + findSpotEntry(GameState.getCurrentRoom(), GameState.getCurrentDirection(), kSpotOnDoorOpenMask, entry); + + if (entry.dstFlags & kSpotOnDoorOpenMask) { + startSpotOnceOnly(entry.movieStart, entry.movieEnd); + } else { + findSpotEntry(GameState.getCurrentRoom(), GameState.getCurrentDirection(), kSpotOnDoorOpenMask | kSpotLoopsMask, entry); + + if (entry.dstFlags & kSpotOnDoorOpenMask) + startSpotLoop(entry.movieStart, entry.movieEnd); + } + + loadAmbientLoops(); + + if (g_map) + g_map->moveToMapLocation(GameState.getCurrentNeighborhood(), GameState.getNextRoom(), GameState.getNextDirection()); + + if (g_AIArea) + g_AIArea->checkMiddleArea(); +} + +void Neighborhood::moveForward() { + ExitTable::Entry exitEntry; + CanMoveForwardReason moveReason = canMoveForward(exitEntry); + + if (moveReason == kCanMoveForward) + startExitMovie(exitEntry); + else + cantMoveThatWay(moveReason); +} + +void Neighborhood::turn(const TurnDirection turnDirection) { + DirectionConstant nextDir; + CanTurnReason turnReason = canTurn(turnDirection, nextDir); + + if (turnReason == kCanTurn) + startTurnPush(turnDirection, getViewTime(GameState.getCurrentRoom(), nextDir), nextDir); + else + cantTurnThatWay(turnReason); +} + +void Neighborhood::turnLeft() { + turn(kTurnLeft); +} + +void Neighborhood::turnRight() { + turn(kTurnRight); +} + +void Neighborhood::turnUp() { + turn(kTurnUp); +} + +void Neighborhood::turnDown() { + turn(kTurnDown); +} + +void Neighborhood::openDoor() { + DoorTable::Entry door; + CanOpenDoorReason doorReason = canOpenDoor(door); + + if (doorReason == kCanOpenDoor) + startDoorOpenMovie(door.movieStart, door.movieEnd); + else + cantOpenDoor(doorReason); +} + +void Neighborhood::zoomTo(const Hotspot *hotspot) { + ZoomTable::Entry zoomEntry; + getZoomEntry(hotspot->getObjectID(), zoomEntry); + if (!zoomEntry.isEmpty()) + startZoomMovie(zoomEntry); +} + +void Neighborhood::updateViewFrame() { + showViewFrame(getViewTime(GameState.getCurrentRoom(), GameState.getCurrentDirection())); +} + +void Neighborhood::startSpotLoop(TimeValue startTime, TimeValue stopTime, NotificationFlags flags) { + _turnPush.hide(); + startMovieSequence(startTime, stopTime, flags, true, kFilterAllInput); +} + +void Neighborhood::showViewFrame(TimeValue viewTime) { + if ((int32)viewTime >= 0) { + _turnPush.hide(); + _navMovie.stop(); + _navMovie.setFlags(0); + _navMovie.setSegment(0, _navMovie.getDuration()); + _navMovie.setTime(viewTime); + + Common::Rect pushBounds; + _turnPush.getBounds(pushBounds); + + _navMovie.moveElementTo(pushBounds.left, pushBounds.top); + _navMovie.show(); + _navMovie.redrawMovieWorld(); + } +} + +void Neighborhood::startExtraSequence(const ExtraID extraID, const NotificationFlags flags, const InputBits interruptionFilter) { + ExtraTable::Entry entry; + getExtraEntry(extraID, entry); + + if (entry.movieStart != 0xffffffff) + playExtraMovie(entry, flags, interruptionFilter); +} + +bool Neighborhood::startExtraSequenceSync(const ExtraID extraID, const InputBits interruptionFilter) { + InputDevice.waitInput(interruptionFilter); + return prepareExtraSync(extraID) && waitMovieFinish(&_navMovie, interruptionFilter); +} + +void Neighborhood::loopExtraSequence(const uint32 extraID, NotificationFlags flags) { + ExtraTable::Entry entry; + getExtraEntry(extraID, entry); + + if (entry.movieStart != 0xffffffff) { + _lastExtra = extraID; + startSpotLoop(entry.movieStart, entry.movieEnd, flags); + } +} + +bool Neighborhood::navMoviePlaying() { + return _navMovie.isRunning(); +} + +void Neighborhood::playDeathExtra(ExtraID extra, DeathReason deathReason) { + _extraDeathReason = deathReason; + startExtraSequence(extra, kDeathExtraCompletedFlag, kFilterNoInput); +} + +void Neighborhood::die(const DeathReason deathReason) { + loadLoopSound1(""); + loadLoopSound2(""); + _vm->die(deathReason); +} + +void Neighborhood::setSoundFXLevel(const uint16 fxLevel) { + if (_navMovie.isSurfaceValid()) + _navMovie.setVolume(fxLevel); + if (_spotSounds.isSoundLoaded()) + _spotSounds.setVolume(fxLevel); + if (_currentInteraction) + _currentInteraction->setSoundFXLevel(fxLevel); +} + +void Neighborhood::setAmbienceLevel(const uint16 ambientLevel) { + if (_soundLoop1.isSoundLoaded()) + _loop1Fader.setMasterVolume(_vm->getAmbienceLevel()); + if (_soundLoop2.isSoundLoaded()) + _loop2Fader.setMasterVolume(_vm->getAmbienceLevel()); + if (_currentInteraction) + _currentInteraction->setAmbienceLevel(ambientLevel); +} + +// Force the exit taken from (room, direction, alternate) to come to a stop. +void Neighborhood::forceStridingStop(const RoomID room, const DirectionConstant direction, const AlternateID alternate) { + ExitTable::Entry entry = _exitTable.findEntry(room, direction, alternate); + + if (entry.movieStart != 0xffffffff) { + TimeValue strideStop = entry.exitEnd; + TimeValue exitStop = entry.movieEnd; + + if (strideStop != exitStop) { + for (ExitTable::iterator it = _exitTable.begin(); it != _exitTable.end(); it++) { + entry = *it; + + if (entry.exitEnd == strideStop && entry.movieEnd <= exitStop) { + entry.exitEnd = exitStop; + *it = entry; + } + } + } + } +} + +// Restore the exit taken from (room, direction, alternate) to stride. +void Neighborhood::restoreStriding(const RoomID room, const DirectionConstant direction, const AlternateID alternate) { + ExitTable::Entry entry = _exitTable.findEntry(room, direction, alternate); + + if (entry.movieStart != 0xffffffff) { + TimeValue strideStop = entry.exitEnd; + TimeValue exitStop = entry.movieEnd; + + if (strideStop != entry.originalEnd) { + for (ExitTable::iterator it = _exitTable.begin(); it != _exitTable.end(); it++) { + entry = *it; + + if (entry.exitEnd == strideStop && entry.movieEnd <= exitStop) { + entry.exitEnd = entry.originalEnd; + *it = entry; + } + } + } + } +} + +HotspotInfoTable::Entry *Neighborhood::findHotspotEntry(const HotSpotID id) { + for (HotspotInfoTable::iterator it = _hotspotInfoTable.begin(); it != _hotspotInfoTable.end(); it++) + if (it->hotspot == id) + return &(*it); + + return 0; +} + +void Neighborhood::hideNav() { + _isRunning = _navMovie.isRunning(); + _navMovie.stop(); + _navMovie.hide(); + _turnPush.stopFader(); + _turnPush.hide(); +} + +void Neighborhood::showNav() { + _navMovie.show(); + _turnPush.hide(); + if (_isRunning) + _navMovie.start(); +} + +void Neighborhood::startExitMovie(const ExitTable::Entry &exitEntry) { + FaderMoveSpec compassMove; + + if (g_compass) + getExitCompassMove(exitEntry, compassMove); + + GameState.setNextRoom(exitEntry.exitRoom); + GameState.setNextDirection(exitEntry.exitDirection); + + if (exitEntry.movieEnd == exitEntry.exitEnd) // Just a walk. + startMovieSequence(exitEntry.movieStart, exitEntry.movieEnd, kMoveForwardCompletedFlag, kFilterNoInput, false); + else // We're stridin'! + startMovieSequence(exitEntry.movieStart, exitEntry.exitEnd, kStrideCompletedFlag, kFilterNoInput, false, exitEntry.movieEnd); + + if (g_compass) + g_compass->startFader(compassMove); +} + +void Neighborhood::startZoomMovie(const ZoomTable::Entry &zoomEntry) { + FaderMoveSpec compassMove; + + if (g_compass) + getZoomCompassMove(zoomEntry, compassMove); + + GameState.setNextRoom(zoomEntry.room); + GameState.setNextDirection(zoomEntry.direction); + + startMovieSequence(zoomEntry.movieStart, zoomEntry.movieEnd, kMoveForwardCompletedFlag, kFilterNoInput, false); + + if (g_compass) + g_compass->startFader(compassMove); +} + +void Neighborhood::startDoorOpenMovie(const TimeValue startTime, const TimeValue stopTime) { + startMovieSequence(startTime, stopTime, kDoorOpenCompletedFlag, kFilterNoInput, false); +} + +void Neighborhood::startTurnPush(const TurnDirection turnDirection, const TimeValue newView, const DirectionConstant nextDir) { + if (g_AIArea) + g_AIArea->lockAIOut(); + + _vm->_cursor->hide(); + + GameState.setNextDirection(nextDir); + + _interruptionFilter = kFilterNoInput; + _turnPush.stopFader(); + + // Set up callback. + _turnPushCallBack.setCallBackFlag(kTurnCompletedFlag); + _turnPushCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + + // Stop nav movie. + _navMovie.stop(); + _navMovie.setFlags(0); + + // Set segment of nav movie to whole movie, so that subsequent initFromMovieFrame + // will work. + _navMovie.setSegment(0, _navMovie.getDuration()); + + _pushIn.initFromMovieFrame(_navMovie.getMovie(), newView); + + _navMovie.hide(); + + switch (turnDirection) { + case kTurnLeft: + _turnPush.setSlideDirection(kSlideRightMask); + break; + case kTurnRight: + _turnPush.setSlideDirection(kSlideLeftMask); + break; + case kTurnUp: + _turnPush.setSlideDirection(kSlideDownMask); + break; + case kTurnDown: + _turnPush.setSlideDirection(kSlideUpMask); + break; + } + + _turnPush.show(); + + FaderMoveSpec moveSpec; + moveSpec.makeTwoKnotFaderSpec(60, 0, 0, 15, 1000); + _turnPush.startFader(moveSpec); + + if (g_compass) { + _turnPush.pauseFader(); + + int32 startAngle = getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection()); + int32 stopAngle = getStaticCompassAngle(GameState.getCurrentRoom(), nextDir); + + if (turnDirection == kTurnLeft) { + if (startAngle < stopAngle) + startAngle += 360; + } else { + if (stopAngle < startAngle) + stopAngle += 360; + } + + FaderMoveSpec turnSpec; + _turnPush.getCurrentFaderMove(turnSpec); + + FaderMoveSpec compassMove; + compassMove.makeTwoKnotFaderSpec(turnSpec.getFaderScale(), turnSpec.getNthKnotTime(0), startAngle, turnSpec.getNthKnotTime(1), stopAngle); + g_compass->startFader(compassMove); + } + + _turnPushCallBack.cancelCallBack(); + _turnPush.continueFader(); + + do { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } while (_turnPush.isFading()); + + _turnPush.stopFader(); + _neighborhoodNotification.setNotificationFlags(kTurnCompletedFlag, kTurnCompletedFlag); +} + +void Neighborhood::playExtraMovie(const ExtraTable::Entry &extraEntry, const NotificationFlags flags, const InputBits interruptionInput) { + FaderMoveSpec compassMove; + + if (g_compass) + getExtraCompassMove(extraEntry, compassMove); + + _lastExtra = extraEntry.extra; + _turnPush.hide(); + startMovieSequence(extraEntry.movieStart, extraEntry.movieEnd, flags, false, interruptionInput); + + if (g_compass) + g_compass->startFader(compassMove); +} + +void Neighborhood::activateCurrentView(const RoomID room, const DirectionConstant direction, SpotFlags flag) { + SpotTable::Entry entry; + findSpotEntry(room, direction, flag, entry); + + if (entry.dstFlags & flag) { + startSpotOnceOnly(entry.movieStart, entry.movieEnd); + } else { + findSpotEntry(room, direction, flag | kSpotLoopsMask, entry); + + if (entry.dstFlags & flag) + startSpotLoop(entry.movieStart, entry.movieEnd); + else + showViewFrame(getViewTime(room, direction)); + } +} + +void Neighborhood::activateOneHotspot(HotspotInfoTable::Entry &entry, Hotspot *hotspot) { + switch (_vm->getDragType()) { + case kDragInventoryUse: + if ((hotspot->getHotspotFlags() & kDropItemSpotFlag) != 0 && + _vm->getDraggingItem()->getObjectID() == entry.hotspotItem) + hotspot->setActive(); + break; + case kDragInventoryPickup: + case kDragBiochipPickup: + // Do nothing -- neighborhoods activate no hot spots in this case... + break; + default: + if ((hotspot->getHotspotFlags() & kPickUpBiochipSpotFlag) != 0) { + Item *item = g_allItems.findItemByID(entry.hotspotItem); + if (item && item->getItemNeighborhood() == getObjectID()) + hotspot->setActive(); + } else { + HotSpotFlags flags = hotspot->getHotspotFlags(); + + if ((flags & kNeighborhoodSpotFlag) != 0) { + if (flags & kOpenDoorSpotFlag) { + if (!GameState.isCurrentDoorOpen()) + hotspot->setActive(); + } else if ((flags & (kZoomSpotFlags | kClickSpotFlag | kPlayExtraSpotFlag)) != 0) { + hotspot->setActive(); + } else if ((flags & kPickUpItemSpotFlag) != 0) { + // Changed this 2/19/96 + // Should only light up this hot spot if the item's taken flag is not + // set. It's not based on neighborhood ID since that can be reset by the + // destroying process. + + if (!GameState.isTakenItemID(entry.hotspotItem)) + hotspot->setActive(); + } + } + } + break; + } +} + +void Neighborhood::startSpotOnceOnly(TimeValue startTime, TimeValue stopTime) { + _turnPush.hide(); + startMovieSequence(startTime, stopTime, kSpotCompletedFlag, kFilterNoInput, false); +} + +void Neighborhood::startMovieSequence(const TimeValue startTime, const TimeValue stopTime, NotificationFlags flags, bool loopSequence, + const InputBits interruptionInput, const TimeValue strideStop) { + if (!loopSequence && g_AIArea) + g_AIArea->lockAIOut(); + + _interruptionFilter = interruptionInput; + + // Stop the movie before doing anything else + _navMovie.stop(); + + Common::Rect pushBounds; + _turnPush.getBounds(pushBounds); + + _navMovie.moveElementTo(pushBounds.left, pushBounds.top); + _navMovie.show(); + _navMovie.setFlags(0); + _navMovie.setSegment(startTime, stopTime); + _navMovie.setTime(startTime); + + if (loopSequence) + _navMovie.setFlags(kLoopTimeBase); + else + flags |= kNeighborhoodMovieCompletedFlag; + + if (strideStop != 0xffffffff) + // Subtract a little slop from the striding stop time to keep from "pumping" at the + // end of a walk. + // 40 is one frame (scale == 600, 15 fps). + scheduleStridingCallBack(strideStop - kStridingSlop, flags); + else + scheduleNavCallBack(flags); + + _navMovie.start(); +} + +void Neighborhood::throwAwayInterface() { + _doorTable.clear(); + _exitTable.clear(); + _extraTable.clear(); + _hotspotInfoTable.clear(); + _spotTable.clear(); + _turnTable.clear(); + _viewTable.clear(); + _zoomTable.clear(); + + _navMovie.stopDisplaying(); + _navMovie.releaseMovie(); + _pushIn.deallocateSurface(); + _turnPush.stopDisplaying(); + _turnPush.setInAndOutElements(0, 0); + _turnPush.disposeAllCallBacks(); + + for (HotspotList::iterator it = _neighborhoodHotspots.begin(); it != _neighborhoodHotspots.end(); it++) + g_allHotspots.remove(*it); + + _neighborhoodHotspots.deleteHotspots(); + _spotSounds.disposeSound(); + _delayTimer.disposeAllCallBacks(); + + if (g_AIArea) { + g_AIArea->saveAIState(); + g_AIArea->removeAllRules(); + } + + if (_currentInteraction) + newInteraction(kNoInteractionID); + + _croppedMovie.releaseMovie(); + + loadLoopSound1(""); + loadLoopSound2(""); + + if (g_energyMonitor) { + g_energyMonitor->stopEnergyDraining(); + g_energyMonitor->saveCurrentEnergyValue(); + } + + delete g_interface; +} + +bool Neighborhood::prepareExtraSync(const ExtraID extraID) { + ExtraTable::Entry extraEntry; + FaderMoveSpec compassMove; + + if (g_compass) { + getExtraEntry(extraID, extraEntry); + getExtraCompassMove(extraEntry, compassMove); + } + + ExtraTable::Entry entry; + getExtraEntry(extraID, entry); + bool result; + + if (entry.movieStart != 0xffffffff) { + _turnPush.hide(); + + // Stop the movie before doing anything else + _navMovie.stop(); + + Common::Rect pushBounds; + _turnPush.getBounds(pushBounds); + _navMovie.moveElementTo(pushBounds.left, pushBounds.top); + + _navMovie.show(); + _navMovie.setFlags(0); + _navMovie.setSegment(entry.movieStart, entry.movieEnd); + _navMovie.setTime(entry.movieStart); + _navMovie.start(); + result = true; + } else { + result = false; + } + + if (result && g_compass) + g_compass->startFader(compassMove); + + return result; +} + +bool Neighborhood::waitMovieFinish(Movie *movie, const InputBits interruptionFilter) { + Input input; + bool result = true; + bool saveAllowed = _vm->swapSaveAllowed(false); + bool openAllowed = _vm->swapLoadAllowed(false); + + while (movie->isRunning()) { + InputDevice.getInput(input, interruptionFilter); + + if (input.anyInput() || _vm->shouldQuit()) { + result = false; + break; + } + + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + movie->stop(); + _vm->swapSaveAllowed(saveAllowed); + _vm->swapLoadAllowed(openAllowed); + + return result; +} + +InputBits Neighborhood::getInputFilter() { + return _interruptionFilter & InputHandler::getInputFilter(); +} + +void Neighborhood::getZoomCompassMove(const ZoomTable::Entry &zoomEntry, FaderMoveSpec &compassMove) { + int32 startAngle = getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection()); + int32 stopAngle = getStaticCompassAngle(zoomEntry.room, zoomEntry.direction); + + if (startAngle > stopAngle) { + if (stopAngle + 180 < startAngle) + stopAngle += 360; + } else { + if (startAngle + 180 < stopAngle) + startAngle += 360; + } + + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), zoomEntry.movieStart, startAngle, zoomEntry.movieEnd, stopAngle); +} + +void Neighborhood::getExtraCompassMove(const ExtraTable::Entry &, FaderMoveSpec &compassMove) { + compassMove.makeOneKnotFaderSpec(g_compass->getFaderValue()); +} + +void Neighborhood::setUpAIRules() { + // Set up default rules here: + // -- Energy warning rules. + + if (g_AIArea) { + g_AIArea->forceAIUnlocked(); + + if (!_vm->isDemo() && (getObjectID() == kPrehistoricID || getObjectID() == kNoradAlphaID || + getObjectID() == kNoradDeltaID || getObjectID() == kMarsID || getObjectID() == kWSCID)) { + + AIEnergyMonitorCondition *condition50 = new AIEnergyMonitorCondition(kWorriedEnergy); + AIPlayMessageAction *message = new AIPlayMessageAction("Images/AI/Globals/XGLOB4A", false); + AIRule *rule50 = new AIRule(condition50, message); + + AIEnergyMonitorCondition *condition25 = new AIEnergyMonitorCondition(kNervousEnergy); + AICompoundAction *compound = new AICompoundAction(); + message = new AIPlayMessageAction("Images/AI/Globals/XGLOB4B", false); + compound->addAction(message); + AIDeactivateRuleAction *deactivate = new AIDeactivateRuleAction(rule50); + compound->addAction(deactivate); + AIRule *rule25 = new AIRule(condition25, compound); + + AIEnergyMonitorCondition *condition5 = new AIEnergyMonitorCondition(kPanicStrickenEnergy); + compound = new AICompoundAction(); + message = new AIPlayMessageAction("Images/AI/Globals/XGLOB4C", false); + compound->addAction(message); + deactivate = new AIDeactivateRuleAction(rule50); + compound->addAction(deactivate); + deactivate = new AIDeactivateRuleAction(rule25); + compound->addAction(deactivate); + AIRule *rule5 = new AIRule(condition5, compound); + + g_AIArea->addAIRule(rule5); + g_AIArea->addAIRule(rule25); + g_AIArea->addAIRule(rule50); + } + } +} + +GameInteraction *Neighborhood::makeInteraction(const InteractionID interactionID) { + if (interactionID == kNoInteractionID) + return 0; + + return new GameInteraction(interactionID, this); +} + +void Neighborhood::newInteraction(const InteractionID interactionID) { + GameInteraction *interaction = makeInteraction(interactionID); + _doneWithInteraction = false; + + if (_currentInteraction) { + _currentInteraction->stopInteraction(); + delete _currentInteraction; + } + + _currentInteraction = interaction; + + if (_currentInteraction) + _currentInteraction->startInteraction(); + + if (g_AIArea) + g_AIArea->checkMiddleArea(); +} + +void Neighborhood::bumpIntoWall() { + _vm->_gfx->shakeTheWorld(15, 30); +} + +void Neighborhood::zoomUpOrBump() { + Hotspot *zoomSpot = 0; + + for (HotspotList::iterator it = g_allHotspots.begin(); it != g_allHotspots.end(); it++) { + Hotspot *hotspot = *it; + + if ((hotspot->getHotspotFlags() & (kNeighborhoodSpotFlag | kZoomInSpotFlag)) == (kNeighborhoodSpotFlag | kZoomInSpotFlag)) { + HotspotInfoTable::Entry *entry = findHotspotEntry(hotspot->getObjectID()); + + if (entry && entry->hotspotRoom == GameState.getCurrentRoom() && entry->hotspotDirection == GameState.getCurrentDirection()) { + if (zoomSpot) { + zoomSpot = 0; + break; + } else { + zoomSpot = hotspot; + } + } + } + } + + if (zoomSpot) + zoomTo(zoomSpot); + else + bumpIntoWall(); +} + +void Neighborhood::loadLoopSound1(const Common::String &soundName, uint16 volume, TimeValue fadeOut, TimeValue fadeIn, TimeScale fadeScale) { + FaderMoveSpec faderMove; + + if (!loop1Loaded(soundName)) { + _loop1SoundString = soundName; + + if (_soundLoop1.isSoundLoaded()) { + faderMove.makeTwoKnotFaderSpec(fadeScale, 0, _loop1Fader.getFaderValue(), fadeOut, 0); + _loop1Fader.startFaderSync(faderMove); + } + + if (!_loop1SoundString.empty()) { + _soundLoop1.initFromAIFFFile(_loop1SoundString); + _soundLoop1.loopSound(); + _loop1Fader.setMasterVolume(_vm->getAmbienceLevel()); + _loop1Fader.setFaderValue(0); + faderMove.makeTwoKnotFaderSpec(fadeScale, 0, 0, fadeIn, volume); + _loop1Fader.startFaderSync(faderMove); + } else { + _soundLoop1.disposeSound(); + } + } else if (_loop1Fader.getFaderValue() != volume) { + faderMove.makeTwoKnotFaderSpec(fadeScale, 0, _loop1Fader.getFaderValue(), fadeIn, volume); + _loop1Fader.startFaderSync(faderMove); + } +} + +void Neighborhood::loadLoopSound2(const Common::String &soundName, uint16 volume, TimeValue fadeOut, TimeValue fadeIn, TimeScale fadeScale) { + FaderMoveSpec faderMove; + + if (!loop2Loaded(soundName)) { + _loop2SoundString = soundName; + + if (_soundLoop2.isSoundLoaded()) { + faderMove.makeTwoKnotFaderSpec(fadeScale, 0, _loop2Fader.getFaderValue(), fadeOut, 0); + _loop2Fader.startFaderSync(faderMove); + } + + if (!_loop2SoundString.empty()) { + _soundLoop2.initFromAIFFFile(_loop2SoundString); + _soundLoop2.loopSound(); + _loop2Fader.setMasterVolume(_vm->getAmbienceLevel()); + _loop2Fader.setFaderValue(0); + faderMove.makeTwoKnotFaderSpec(fadeScale, 0, 0, fadeIn, volume); + _loop2Fader.startFaderSync(faderMove); + } else { + _soundLoop2.disposeSound(); + } + } else if (_loop2Fader.getFaderValue() != volume) { + faderMove.makeTwoKnotFaderSpec(fadeScale, 0, _loop2Fader.getFaderValue(), fadeIn, volume); + _loop2Fader.startFaderSync(faderMove); + } +} + +void Neighborhood::takeItemFromRoom(Item *item) { + item->setItemRoom(kNoNeighborhoodID, kNoRoomID, kNoDirection); + // Also set the taken item flag. Do this before updating the view frame. + GameState.setTakenItem(item, true); + updateViewFrame(); +} + +void Neighborhood::dropItemIntoRoom(Item *item, Hotspot *) { + item->setItemRoom(getObjectID(), GameState.getCurrentRoom(), GameState.getCurrentDirection()); + // Also set the taken item flag. Do this before updating the view frame. + GameState.setTakenItem(item, false); + updateViewFrame(); +} + +void Neighborhood::makeContinuePoint() { + _vm->makeContinuePoint(); +} + +void Neighborhood::startLoop1Fader(const FaderMoveSpec &faderMove) { + _loop1Fader.startFader(faderMove); +} + +void Neighborhood::startLoop2Fader(const FaderMoveSpec &faderMove) { + _loop2Fader.startFader(faderMove); +} + +// *** Revised 6/13/96 to use the last frame of the extra sequence. +// Necessary for Cinepak buildup. +void Neighborhood::showExtraView(uint32 extraID) { + ExtraTable::Entry entry; + getExtraEntry(extraID, entry); + + if (entry.movieEnd != 0xffffffff) + showViewFrame(entry.movieEnd - 1); +} + +void Neighborhood::startExtraLongSequence(const uint32 firstExtra, const uint32 lastExtra, NotificationFlags flags, + const InputBits interruptionFilter) { + ExtraTable::Entry firstEntry, lastEntry; + getExtraEntry(firstExtra, firstEntry); + + if (firstEntry.movieStart != 0xffffffff) { + getExtraEntry(lastExtra, lastEntry); + _lastExtra = firstExtra; + _turnPush.hide(); + startMovieSequence(firstEntry.movieStart, lastEntry.movieEnd, flags, kFilterNoInput, interruptionFilter); + } +} + +void Neighborhood::openCroppedMovie(const Common::String &movieName, CoordType left, CoordType top) { + if (_croppedMovie.isMovieValid()) + closeCroppedMovie(); + + _croppedMovie.initFromMovieFile(movieName); + _croppedMovie.moveElementTo(left, top); + _croppedMovie.startDisplaying(); + _croppedMovie.show(); +} + +void Neighborhood::loopCroppedMovie(const Common::String &movieName, CoordType left, CoordType top) { + openCroppedMovie(movieName, left, top); + _croppedMovie.redrawMovieWorld(); + _croppedMovie.setFlags(kLoopTimeBase); + _croppedMovie.start(); +} + +void Neighborhood::closeCroppedMovie() { + _croppedMovie.releaseMovie(); +} + +void Neighborhood::playCroppedMovieOnce(const Common::String &movieName, CoordType left, CoordType top, const InputBits interruptionFilter) { + openCroppedMovie(movieName, left, top); + _croppedMovie.redrawMovieWorld(); + _croppedMovie.start(); + + InputBits oldInterruptionFilter = _interruptionFilter; + if (oldInterruptionFilter != kFilterNoInput) + _interruptionFilter = kFilterNoInput; + + bool saveAllowed = _vm->swapSaveAllowed(false); + bool openAllowed = _vm->swapLoadAllowed(false); + + Input input; + while (_croppedMovie.isRunning() && !_vm->shouldQuit()) { + _vm->processShell(); + InputDevice.getInput(input, interruptionFilter); + if (input.anyInput() || _vm->saveRequested() || _vm->loadRequested() || _vm->shouldQuit()) + break; + _vm->_system->delayMillis(10); + } + + if (oldInterruptionFilter != kFilterNoInput) + _interruptionFilter = oldInterruptionFilter; + + closeCroppedMovie(); + _vm->swapSaveAllowed(saveAllowed); + _vm->swapLoadAllowed(openAllowed); +} + +void Neighborhood::playMovieSegment(Movie *movie, TimeValue startTime, TimeValue stopTime) { + TimeValue oldStart, oldStop; + movie->getSegment(oldStart, oldStop); + + if (stopTime == 0xffffffff) + stopTime = movie->getDuration(); + + movie->setSegment(startTime, stopTime); + movie->setTime(startTime); + movie->start(); + + while (movie->isRunning()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + movie->stop(); + movie->setSegment(oldStart, oldStop); +} + +void Neighborhood::recallToTSASuccess() { + if (GameState.allTimeZonesFinished()) + _vm->jumpToNewEnvironment(kFullTSAID, kTSA37, kNorth); + else + _vm->jumpToNewEnvironment(kTinyTSAID, kTinyTSA37, kNorth); +} + +void Neighborhood::recallToTSAFailure() { + _vm->jumpToNewEnvironment(kTinyTSAID, kTinyTSA37, kNorth); +} + +void Neighborhood::handleInput(const Input &input, const Hotspot *cursorSpot) { + if (_vm->getGameMode() == kModeNavigation) { + if (input.upButtonAnyDown()) + upButton(input); + else if (input.downButtonAnyDown()) + downButton(input); + else if (input.leftButtonAnyDown()) + leftButton(input); + else if (input.rightButtonAnyDown()) + rightButton(input); + } + + InputHandler::handleInput(input, cursorSpot); +} + +void Neighborhood::setHotspotFlags(const HotSpotID id, const HotSpotFlags flags) { + Hotspot *hotspot = g_allHotspots.findHotspotByID(id); + hotspot->setMaskedHotspotFlags(flags, flags); +} + +void Neighborhood::setIsItemTaken(const ItemID id) { + GameState.setTakenItemID(id, _vm->playerHasItemID(id)); +} + +void Neighborhood::upButton(const Input &) { + moveForward(); +} + +void Neighborhood::leftButton(const Input &) { + turnLeft(); +} + +void Neighborhood::rightButton(const Input &) { + turnRight(); +} + +void Neighborhood::downButton(const Input &) { + if (_inputHandler->wantsCursor()) { + g_allHotspots.deactivateAllHotspots(); + _inputHandler->activateHotspots(); + + for (HotspotList::iterator it = g_allHotspots.begin(); it != g_allHotspots.end(); it++) { + Hotspot *hotspot = *it; + + if (hotspot->isSpotActive() && (hotspot->getHotspotFlags() & (kNeighborhoodSpotFlag | kZoomOutSpotFlag)) == (kNeighborhoodSpotFlag | kZoomOutSpotFlag)) { + HotspotInfoTable::Entry *entry = findHotspotEntry(hotspot->getObjectID()); + + if (entry && entry->hotspotRoom == GameState.getCurrentRoom() && entry->hotspotDirection == GameState.getCurrentDirection()) { + Input scratch; + _inputHandler->clickInHotspot(scratch, hotspot); + return; + } + } + } + } +} + +void Neighborhood::initOnePicture(Picture *picture, const Common::String &pictureName, DisplayOrder order, CoordType left, CoordType top, bool show) { + picture->initFromPICTFile(pictureName); + picture->setDisplayOrder(order); + picture->moveElementTo(left, top); + picture->startDisplaying(); + if (show) + picture->show(); +} + +void Neighborhood::initOneMovie(Movie *movie, const Common::String &movieName, DisplayOrder order, CoordType left, CoordType top, bool show) { + movie->initFromMovieFile(movieName); + movie->setDisplayOrder(order); + movie->moveElementTo(left, top); + movie->startDisplaying(); + + if (show) + movie->show(); + + movie->redrawMovieWorld(); +} + +void Neighborhood::reinstateMonocleInterface() { + _vm->_gfx->disableErase(); + + _vm->createInterface(); + + if (g_AIArea) + setNextHandler(g_AIArea); + + init(); + + moveNavTo(kNavAreaLeft, kNavAreaTop); + + if (g_interface) + g_interface->setDate(getDateResID()); + + if (g_AIArea) + g_AIArea->restoreAIState(); +} + +void Neighborhood::useIdleTime() { + if (_doneWithInteraction) { + newInteraction(kNoInteractionID); + loadAmbientLoops(); + } +} + +void timerFunction(FunctionPtr *, void *neighborhood) { + ((Neighborhood *)neighborhood)->timerExpired(((Neighborhood *)neighborhood)->getTimerEvent()); +} + +void Neighborhood::scheduleEvent(const TimeValue time, const TimeScale scale, const uint32 eventType) { + _eventTimer.stopFuse(); + _eventTimer.primeFuse(time, scale); + _timerEvent = eventType; + _eventTimer.setFunctionPtr(&timerFunction, this); + _eventTimer.lightFuse(); +} + +void Neighborhood::cancelEvent() { + _eventTimer.stopFuse(); +} + +void Neighborhood::pauseTimer() { + _eventTimer.pauseFuse(); +} + +void Neighborhood::resumeTimer() { + // NOTE: Yes, this function calls pauseFuse! + // Looks like an original game bug, will need + // to investigate how this affects gameplay. + _eventTimer.pauseFuse(); +} + +bool Neighborhood::timerPaused() { + return _eventTimer.isFusePaused(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/neighborhood.h b/engines/pegasus/neighborhood/neighborhood.h new file mode 100644 index 0000000000..8a38eb3389 --- /dev/null +++ b/engines/pegasus/neighborhood/neighborhood.h @@ -0,0 +1,408 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_H +#define PEGASUS_NEIGHBORHOOD_H + +#include "common/queue.h" +#include "common/str.h" + +#include "pegasus/fader.h" +#include "pegasus/hotspot.h" +#include "pegasus/input.h" +#include "pegasus/movie.h" +#include "pegasus/notification.h" +#include "pegasus/sound.h" +#include "pegasus/timers.h" +#include "pegasus/transition.h" +#include "pegasus/util.h" +#include "pegasus/neighborhood/door.h" +#include "pegasus/neighborhood/exit.h" +#include "pegasus/neighborhood/extra.h" +#include "pegasus/neighborhood/hotspotinfo.h" +#include "pegasus/neighborhood/spot.h" +#include "pegasus/neighborhood/turn.h" +#include "pegasus/neighborhood/view.h" +#include "pegasus/neighborhood/zoom.h" + +namespace Pegasus { + +class PegasusEngine; + +// Pegasus Prime neighborhood id's +static const NeighborhoodID kCaldoriaID = 0; +static const NeighborhoodID kFullTSAID = 1; +static const NeighborhoodID kFinalTSAID = 2; +static const NeighborhoodID kTinyTSAID = 3; +static const NeighborhoodID kPrehistoricID = 4; +static const NeighborhoodID kMarsID = 5; +static const NeighborhoodID kWSCID = 6; +static const NeighborhoodID kNoradAlphaID = 7; +static const NeighborhoodID kNoradDeltaID = 8; +// The sub chase is not really a neighborhood, but we define a constant that is used +// to allow an easy transition out of Norad Alpha. +static const NeighborhoodID kNoradSubChaseID = 1000; + +static const TimeScale kDefaultLoopFadeScale = kThirtyTicksPerSecond; +static const TimeValue kDefaultLoopFadeOut = kHalfSecondPerThirtyTicks; +static const TimeValue kDefaultLoopFadeIn = kHalfSecondPerThirtyTicks; + +enum QueueRequestType { + kNavExtraRequest, + kSpotSoundRequest, + kDelayRequest +}; + +// For delay requests, start is interpreted as the total delay and stop is interpreted +// as the scale the delay is in. +// For extra requests, start and stop are not used. +struct QueueRequest { + QueueRequestType requestType; + ExtraID extra; + TimeValue start, stop; + InputBits interruptionFilter; + bool playing; + NotificationFlags flags; + Notification *notification; +}; + +bool operator==(const QueueRequest &arg1, const QueueRequest &arg2); +bool operator!=(const QueueRequest &arg1, const QueueRequest &arg2); + +class GameInteraction; +class Item; +class Neighborhood; + +class StriderCallBack : public TimeBaseCallBack { +public: + StriderCallBack(Neighborhood *); + virtual ~StriderCallBack() {} + +protected: + virtual void callBack(); + + Neighborhood *_neighborhood; +}; + +typedef Common::Queue<QueueRequest> NeighborhoodActionQueue; + +class Neighborhood : public IDObject, public NotificationReceiver, public InputHandler, public Idler { +friend class StriderCallBack; +friend void timerFunction(FunctionPtr *, void *); + +public: + Neighborhood(InputHandler *nextHandler, PegasusEngine *vm, const Common::String &resName, NeighborhoodID id); + virtual ~Neighborhood(); + + virtual void init(); + virtual void start(); + virtual void moveNavTo(const CoordType, const CoordType); + virtual void checkContinuePoint(const RoomID, const DirectionConstant) = 0; + void makeContinuePoint(); + + virtual void activateHotspots(); + virtual void clickInHotspot(const Input &, const Hotspot *); + + virtual CanMoveForwardReason canMoveForward(ExitTable::Entry &entry); + virtual CanTurnReason canTurn(TurnDirection turn, DirectionConstant &nextDir); + virtual CanOpenDoorReason canOpenDoor(DoorTable::Entry &entry); + + virtual void cantMoveThatWay(CanMoveForwardReason); + virtual void cantTurnThatWay(CanTurnReason) {} + virtual void cantOpenDoor(CanOpenDoorReason); + virtual void arriveAt(const RoomID room, const DirectionConstant direction); + virtual void turnTo(const DirectionConstant); + virtual void spotCompleted(); + virtual void doorOpened(); + virtual void closeDoorOffScreen(const RoomID, const DirectionConstant) {} + + virtual void moveForward(); + virtual void turn(const TurnDirection); + virtual void turnLeft(); + virtual void turnRight(); + virtual void turnUp(); + virtual void turnDown(); + virtual void openDoor(); + virtual void zoomTo(const Hotspot *); + + virtual void updateViewFrame(); + + void requestExtraSequence(const ExtraID, const NotificationFlags, const InputBits interruptionFilter); + void requestSpotSound(const TimeValue, const TimeValue, const InputBits interruptionFilter, const NotificationFlags); + void playSpotSoundSync(const TimeValue in, const TimeValue out); + void requestDelay(const TimeValue, const TimeScale, const InputBits interruptionFilter, const NotificationFlags); + + Notification *getNeighborhoodNotification() { return &_neighborhoodNotification; } + + virtual void getExtraEntry(const uint32 id, ExtraTable::Entry &extraEntry); + virtual void startSpotLoop(TimeValue, TimeValue, NotificationFlags = 0); + virtual bool actionQueueEmpty() { return _actionQueue.empty(); } + virtual void showViewFrame(TimeValue); + virtual void findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &spotEntry); + virtual void startExtraSequence(const ExtraID, const NotificationFlags, const InputBits interruptionFilter); + bool startExtraSequenceSync(const ExtraID, const InputBits); + virtual void loopExtraSequence(const uint32, NotificationFlags = 0); + int32 getLastExtra() const { return _lastExtra; } + virtual void scheduleNavCallBack(NotificationFlags); + + Movie *getNavMovie() { return &_navMovie; } + bool navMoviePlaying(); + + void setCurrentAlternate(const AlternateID alt) { _currentAlternate = alt; } + AlternateID getCurrentAlternate() const { return _currentAlternate; } + + void setCurrentActivation(const HotSpotActivationID a) { _currentActivation = a; } + HotSpotActivationID getCurrentActivation() { return _currentActivation; } + + virtual void playDeathExtra(ExtraID, DeathReason); + virtual void die(const DeathReason); + + virtual void setSoundFXLevel(const uint16); + virtual void setAmbienceLevel(const uint16); + + void forceStridingStop(const RoomID, const DirectionConstant, const AlternateID); + void restoreStriding(const RoomID, const DirectionConstant, const AlternateID); + + HotspotInfoTable::Entry *findHotspotEntry(const HotSpotID); + + Push *getTurnPush() { return &_turnPush; } + Picture *getTurnPushPicture() { return &_pushIn; } + + void hideNav(); + void showNav(); + + virtual void loadAmbientLoops() {} + + virtual void flushGameState() {} + + virtual Common::String getBriefingMovie(); + virtual Common::String getEnvScanMovie(); + virtual uint getNumHints(); + virtual Common::String getHintMovie(uint); + virtual bool canSolve(); + virtual void prepareForAIHint(const Common::String &) {} + virtual void cleanUpAfterAIHint(const Common::String &) {} + virtual void doSolve(); + + virtual bool okayToJump(); + + virtual AirQuality getAirQuality(const RoomID); + virtual void checkAirMask() {} + virtual void checkFlashlight() {} + virtual void shieldOn() {} + virtual void shieldOff() {} + + virtual void loadLoopSound1(const Common::String &, const uint16 volume = 0x100, + const TimeValue fadeOut = kDefaultLoopFadeOut, const TimeValue fadeIn = kDefaultLoopFadeIn, + const TimeScale fadeScale = kDefaultLoopFadeScale); + virtual void loadLoopSound2(const Common::String &, const uint16 volume = 0x100, + const TimeValue fadeOut = kDefaultLoopFadeOut, const TimeValue fadeIn = kDefaultLoopFadeIn, + const TimeScale fadeScale = kDefaultLoopFadeScale); + bool loop1Loaded(const Common::String &soundName) { return _loop1SoundString == soundName; } + bool loop2Loaded(const Common::String &soundName) { return _loop2SoundString == soundName; } + void startLoop1Fader(const FaderMoveSpec &); + void startLoop2Fader(const FaderMoveSpec &); + + virtual void takeItemFromRoom(Item *); + virtual void dropItemIntoRoom(Item *, Hotspot *); + virtual Hotspot *getItemScreenSpot(Item *, DisplayElement *) { return 0; } + + virtual GameInteraction *makeInteraction(const InteractionID); + virtual void requestDeleteCurrentInteraction() { _doneWithInteraction = true; } + + virtual uint16 getDateResID() const = 0; + + virtual void showExtraView(uint32); + virtual void startExtraLongSequence(const uint32, const uint32, NotificationFlags, const InputBits interruptionFilter); + + void openCroppedMovie(const Common::String &, CoordType, CoordType); + void loopCroppedMovie(const Common::String &, CoordType, CoordType); + void closeCroppedMovie(); + void playCroppedMovieOnce(const Common::String &, CoordType, CoordType, const InputBits interruptionFilter = kFilterNoInput); + + void playMovieSegment(Movie *, TimeValue = 0, TimeValue = 0xffffffff); + + virtual void recallToTSASuccess(); + virtual void recallToTSAFailure(); + + virtual void pickedUpItem(Item *) {} + + virtual void handleInput(const Input &, const Hotspot *); +protected: + PegasusEngine *_vm; + Common::String _resName; + + virtual Common::String getSoundSpotsName() = 0; + virtual Common::String getNavMovieName() = 0; + + // Notification function. + virtual void receiveNotification(Notification *, const NotificationFlags); + + // Map info functions. + virtual void getExitEntry(const RoomID room, const DirectionConstant direction, ExitTable::Entry &entry); + virtual TimeValue getViewTime(const RoomID room, const DirectionConstant direction); + virtual void getDoorEntry(const RoomID room, const DirectionConstant direction, DoorTable::Entry &doorEntry); + virtual DirectionConstant getTurnEntry(const RoomID room, const DirectionConstant direction, const TurnDirection turn); + virtual void getZoomEntry(const HotSpotID id, ZoomTable::Entry &zoomEntry); + virtual void getHotspotEntry(const HotSpotID id, HotspotInfoTable::Entry &hotspotEntry); + + // Nav movie sequences. + virtual void startExitMovie(const ExitTable::Entry &); + virtual void keepStriding(ExitTable::Entry &); + virtual void stopStriding(); + virtual void checkStriding(); + virtual bool stillMoveForward(); + virtual void scheduleStridingCallBack(const TimeValue, NotificationFlags flags); + virtual void startZoomMovie(const ZoomTable::Entry &); + virtual void startDoorOpenMovie(const TimeValue, const TimeValue); + virtual void startTurnPush(const TurnDirection, const TimeValue, const DirectionConstant); + virtual void playExtraMovie(const ExtraTable::Entry &, const NotificationFlags, const InputBits interruptionFilter); + + virtual void activateCurrentView(const RoomID, const DirectionConstant, SpotFlags); + + virtual void activateOneHotspot(HotspotInfoTable::Entry &, Hotspot *); + + virtual void startSpotOnceOnly(TimeValue, TimeValue); + + virtual void startMovieSequence(const TimeValue, const TimeValue, NotificationFlags, + bool loopSequence, const InputBits interruptionFilter, const TimeValue strideStop = 0xffffffff); + + virtual void createNeighborhoodSpots(); + + void resetLastExtra() { _lastExtra = -1; } + + virtual void throwAwayInterface(); + + // Action queue stuff + void popActionQueue(); + void serviceActionQueue(); + void requestAction(const QueueRequestType, const ExtraID, const TimeValue, const TimeValue, const InputBits, const NotificationFlags); + + virtual bool prepareExtraSync(const ExtraID); + virtual bool waitMovieFinish(Movie *, const InputBits); + + virtual InputBits getInputFilter(); + + // Misc. + virtual int16 getStaticCompassAngle(const RoomID, const DirectionConstant dir); + virtual void getExitCompassMove(const ExitTable::Entry &, FaderMoveSpec &); + virtual void getZoomCompassMove(const ZoomTable::Entry &, FaderMoveSpec&); + virtual void getExtraCompassMove(const ExtraTable::Entry &, FaderMoveSpec&); + + virtual void setUpAIRules(); + virtual void setHotspotFlags(const HotSpotID, const HotSpotFlags); + virtual void setIsItemTaken(const ItemID); + + virtual void upButton(const Input &); + virtual void leftButton(const Input &); + virtual void rightButton(const Input &); + virtual void downButton(const Input &); + + void initOnePicture(Picture *, const Common::String &, DisplayOrder, CoordType, CoordType, bool); + void initOneMovie(Movie *, const Common::String &, DisplayOrder, CoordType, CoordType, bool); + + void reinstateMonocleInterface(); + + virtual void newInteraction(const InteractionID); + virtual void useIdleTime(); + virtual void bumpIntoWall(); + virtual void zoomUpOrBump(); + + void scheduleEvent(const TimeValue, const TimeScale, const uint32); + void cancelEvent(); + virtual void timerExpired(const uint32) {} + bool isEventTimerRunning() { return _eventTimer.isFuseLit(); } + uint32 getTimerEvent() { return _timerEvent; } + + void pauseTimer(); + void resumeTimer(); + bool timerPaused(); + + // Navigation Data + DoorTable _doorTable; + ExitTable _exitTable; + ExtraTable _extraTable; + HotspotInfoTable _hotspotInfoTable; + SpotTable _spotTable; + TurnTable _turnTable; + ViewTable _viewTable; + ZoomTable _zoomTable; + AlternateID _currentAlternate; + HotSpotActivationID _currentActivation; + + int32 _lastExtra; + DeathReason _extraDeathReason; + + // Graphics + Movie _navMovie; + Picture _pushIn; + Push _turnPush; + + // Callbacks + Notification _neighborhoodNotification; + NotificationCallBack _navMovieCallBack; + StriderCallBack _stridingCallBack; + NotificationCallBack _turnPushCallBack; + NotificationCallBack _spotSoundCallBack; + NotificationCallBack _delayCallBack; + + // Hotspots + HotspotList _neighborhoodHotspots; + + // Sounds + SoundTimeBase _spotSounds; + + // Action queue + NeighborhoodActionQueue _actionQueue; + TimeBase _delayTimer; + + // Interruptibility... + InputBits _interruptionFilter; + + // Nav hiding (for info support...) + bool _isRunning; + + GameInteraction *_currentInteraction; + bool _doneWithInteraction; + Movie _croppedMovie; + + Sound _soundLoop1; + Common::String _loop1SoundString; + SoundFader _loop1Fader; + + Sound _soundLoop2; + Common::String _loop2SoundString; + SoundFader _loop2Fader; + + // The event timer... + FuseFunction _eventTimer; + uint32 _timerEvent; +}; + +extern Neighborhood *g_neighborhood; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/alpha/ecrmonitor.cpp b/engines/pegasus/neighborhood/norad/alpha/ecrmonitor.cpp new file mode 100644 index 0000000000..ff1f078b15 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/alpha/ecrmonitor.cpp @@ -0,0 +1,219 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/pegasus.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/norad/norad.h" +#include "pegasus/neighborhood/norad/alpha/ecrmonitor.h" + +namespace Pegasus { + +static const NotificationFlags kECRSection1FinishedFlag = 1; +static const NotificationFlags kECRPanFinishedFlag = kECRSection1FinishedFlag << 1; +static const NotificationFlags kECRSection2FinishedFlag = kECRPanFinishedFlag << 1; +static const NotificationFlags kECRNotificationFlags = kECRSection1FinishedFlag | + kECRPanFinishedFlag | + kECRSection2FinishedFlag; + +static const TimeValue kSection1Start = 0; +static const TimeValue kSection1Stop = 25; +static const TimeValue kPanStart = 0; +static const TimeValue kPanStop = 20; +static const TimeValue kSection2Start = 26; +static const TimeValue kSection2Stop = 1000; + +// Seems to be a good value for a 20 second pan. +static const CoordType kPanPixelsPerFrame = 8; + +// Interesting times are in seconds. +static const TimeValue s_ECRInterestingTimes[] = { + 0, 1, 2, 10, 25, 26, 56, 64, 72, 80, 88, 94, 102, 108, 116, 999 +}; + +// Index into s_ECRInterestingTimes of interesting time before security pan. +static const int kBeforePanTime = 3; + +// Index into s_ECRInterestingTimes of interesting time after security pan. +static const int kAfterPanTime = 5; + +NoradAlphaECRMonitor::NoradAlphaECRMonitor(Neighborhood *nextHandler) : GameInteraction(kNoradECRMonitorInteractionID, nextHandler), + _ecrSlideShowNotification(kNoradECRNotificationID, (PegasusEngine *)g_engine), _ecrMovie(kECRSlideShowMovieID), + _ecrPan(kECRPanID) { +} + +void NoradAlphaECRMonitor::receiveNotification(Notification *, const NotificationFlags flags) { + if (flags & kECRSection1FinishedFlag) + ecrSection1Finished(); + else if (flags & kECRPanFinishedFlag) + ecrPanFinished(); + else if (flags & kECRSection2FinishedFlag) + ecrSection2Finished(); +} + +int NoradAlphaECRMonitor::findCurrentInterestingTime() { + TimeValue time = _ecrMovie.getTime(); + TimeScale scale = _ecrMovie.getScale(); + + for (int i = ARRAYSIZE(s_ECRInterestingTimes) - 1; i >= 0; i--) + if (time >= s_ECRInterestingTimes[i] * scale) + return i; + + return 0; +} + +void NoradAlphaECRMonitor::skipToNextInterestingTime() { + if (_ecrMovie.isRunning()) { + int interestingTime = findCurrentInterestingTime(); + _ecrMovie.setTime(s_ECRInterestingTimes[interestingTime + 1] * _ecrMovie.getScale()); + _ecrMovie.redrawMovieWorld(); + } else if (_ecrPan.isRunning()) { + _ecrPanCallBack.cancelCallBack(); + ecrPanFinished(); + } +} + +void NoradAlphaECRMonitor::skipToPreviousInterestingTime() { + if (_ecrPan.isRunning()) { + _ecrPan.stop(); + _ecrPan.stopDisplaying(); + _ecrPanCallBack.cancelCallBack(); + + _ecrMovieCallBack.setCallBackFlag(kECRSection1FinishedFlag); + _ecrMovieCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + + TimeScale scale = _ecrMovie.getScale(); + _ecrMovie.setSegment(kSection1Start * scale, kSection1Stop * scale + 1); + _ecrMovie.setTime(s_ECRInterestingTimes[kBeforePanTime] * scale); + _ecrMovie.start(); + } else { + int interestingTime = findCurrentInterestingTime(); + + if (interestingTime == kAfterPanTime) { + _ecrMovieCallBack.cancelCallBack(); + TimeScale scale = _ecrMovie.getScale(); + _ecrMovie.setSegment(kSection1Start * scale, kSection1Stop * scale + 1); + _ecrMovie.setTime(kSection1Stop * scale); + ecrSection1Finished(); + } else if (interestingTime == 0) { + _ecrMovie.setTime(kSection1Start * _ecrMovie.getScale()); + _ecrMovie.redrawMovieWorld(); + } else { + _ecrMovie.setTime(s_ECRInterestingTimes[interestingTime - 1] * _ecrMovie.getScale()); + _ecrMovie.redrawMovieWorld(); + } + } +} + +void NoradAlphaECRMonitor::handleInput(const Input &input, const Hotspot *cursorSpot) { + if (isInteracting()) { + if (input.rightButtonDown()) + skipToNextInterestingTime(); + else if (input.leftButtonDown()) + skipToPreviousInterestingTime(); + else + InputHandler::handleInput(input, cursorSpot); + } else { + InputHandler::handleInput(input, cursorSpot); + } +} + +void NoradAlphaECRMonitor::ecrSection1Finished() { + _ecrMovie.stop(); + _ecrPanCallBack.setNotification(&_ecrSlideShowNotification); + _ecrPanCallBack.initCallBack(&_ecrPan, kCallBackAtExtremes); + _ecrPanCallBack.setCallBackFlag(kECRPanFinishedFlag); + _ecrSlideShowNotification.notifyMe(this, kECRNotificationFlags, kECRNotificationFlags); + _ecrPanCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _ecrPan.startDisplaying(); + _ecrPan.show(); + + TimeScale scale = _ecrPan.getScale(); + _ecrPan.setSegment(kPanStart * scale, kPanStop * scale); + _ecrPan.setTime(0); + _ecrPan.start(); +} + +void NoradAlphaECRMonitor::ecrPanFinished() { + _ecrPan.stop(); + _ecrPan.stopDisplaying(); + _ecrMovieCallBack.setCallBackFlag(kECRSection2FinishedFlag); + _ecrMovieCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + + TimeScale scale = _ecrMovie.getScale(); + _ecrMovie.setSegment(kSection2Start * scale, kSection2Stop * scale); + _ecrMovie.start(); +} + +void NoradAlphaECRMonitor::ecrSection2Finished() { + _ecrMovie.stop(); + _ecrMovie.stopDisplaying(); +} + +void NoradAlphaECRMonitor::openInteraction() { + // Initialize the security pan. + _ecrPan.initFromMovieFile("Images/Norad Alpha/Security Pan.pano"); + _ecrPan.initMaskFromPICTFile("Images/Norad Alpha/Security Pan Mask"); + _ecrPan.setBounds(Common::Rect(kECRPanLeft, kECRPanTop, kECRPanRight, kECRPanBottom)); + _ecrPan.setDisplayOrder(kECRPanOrder); + _ecrPan.setScale(15); // 15 fps. + + // Begin the lame ECR slide show. + // clone2727: I didn't say it :P + _ecrMovie.initFromMovieFile("Images/Norad Alpha/ECR Monitor Movie"); + + _ecrMovieCallBack.setNotification(&_ecrSlideShowNotification); + _ecrMovieCallBack.initCallBack(&_ecrMovie, kCallBackAtExtremes); + _ecrMovieCallBack.setCallBackFlag(kECRSection1FinishedFlag); + + _ecrSlideShowNotification.notifyMe(this, kECRNotificationFlags, kECRNotificationFlags); + _ecrMovieCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + + _ecrMovie.moveElementTo(kECRSlideShowLeft, kECRSlideShowTop); + _ecrMovie.setDisplayOrder(kECRMonitorOrder); + _ecrMovie.startDisplaying(); + _ecrMovie.show(); + _ecrMovie.redrawMovieWorld(); + + TimeScale scale = _ecrMovie.getScale(); + _ecrMovie.setSegment(kSection1Start * scale, kSection1Stop * scale + 1); + + _ecrMovie.start(); +} + +void NoradAlphaECRMonitor::closeInteraction() { + _ecrMovieCallBack.releaseCallBack(); + _ecrMovie.stop(); + _ecrMovie.stopDisplaying(); + _ecrMovie.releaseMovie(); + _ecrMovieCallBack.releaseCallBack(); + + _ecrPanCallBack.releaseCallBack(); + _ecrPan.stop(); + _ecrPan.stopDisplaying(); + _ecrPan.releasePanorama(); + _ecrPanCallBack.releaseCallBack(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/norad/alpha/ecrmonitor.h b/engines/pegasus/neighborhood/norad/alpha/ecrmonitor.h new file mode 100644 index 0000000000..9e286ed337 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/alpha/ecrmonitor.h @@ -0,0 +1,65 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_NORAD_ALPHA_ecrMONITOR_H +#define PEGASUS_NEIGHBORHOOD_NORAD_ALPHA_ecrMONITOR_H + +#include "pegasus/interaction.h" +#include "pegasus/notification.h" +#include "pegasus/neighborhood/norad/alpha/panoramascroll.h" + +namespace Pegasus { + +class NoradAlphaECRMonitor : public GameInteraction, public NotificationReceiver { +public: + NoradAlphaECRMonitor(Neighborhood *); + virtual ~NoradAlphaECRMonitor() {} + + virtual void handleInput(const Input &, const Hotspot *); + +protected: + virtual void openInteraction(); + virtual void closeInteraction(); + + virtual void receiveNotification(Notification *, const NotificationFlags); + + void ecrSection1Finished(); + void ecrPanFinished(); + void ecrSection2Finished(); + + int findCurrentInterestingTime(); + void skipToNextInterestingTime(); + void skipToPreviousInterestingTime(); + + Notification _ecrSlideShowNotification; + Movie _ecrMovie; + NotificationCallBack _ecrMovieCallBack; + PanoramaScroll _ecrPan; + NotificationCallBack _ecrPanCallBack; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/alpha/fillingstation.cpp b/engines/pegasus/neighborhood/norad/alpha/fillingstation.cpp new file mode 100644 index 0000000000..61d77bf353 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/alpha/fillingstation.cpp @@ -0,0 +1,444 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/items/inventory/airmask.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/norad/alpha/fillingstation.h" +#include "pegasus/neighborhood/norad/alpha/noradalpha.h" + +namespace Pegasus { + +static const NotificationFlags kFSPowerUpFinishedFlag = 1; +static const NotificationFlags kFSSplashFinishedFlag = kFSPowerUpFinishedFlag << 1; +static const NotificationFlags kFSIntakeWarningFinishedFlag = kFSSplashFinishedFlag << 1; +static const NotificationFlags kFSIntakeHiliteFinishedFlag = kFSIntakeWarningFinishedFlag << 1; +static const NotificationFlags kFSDispenseHiliteFinishedFlag = kFSIntakeHiliteFinishedFlag << 1; +static const NotificationFlags kFSArHiliteFinishedFlag = kFSDispenseHiliteFinishedFlag << 1; +static const NotificationFlags kFSCO2HiliteFinishedFlag = kFSArHiliteFinishedFlag << 1; +static const NotificationFlags kFSHeHiliteFinishedFlag = kFSCO2HiliteFinishedFlag << 1; +static const NotificationFlags kFSOHiliteFinishedFlag = kFSHeHiliteFinishedFlag << 1; +static const NotificationFlags kFSNHiliteFinishedFlag = kFSOHiliteFinishedFlag << 1; + +static const NotificationFlags kFSNotificationFlags = kFSPowerUpFinishedFlag | + kFSSplashFinishedFlag | + kFSIntakeWarningFinishedFlag | + kFSIntakeHiliteFinishedFlag | + kFSDispenseHiliteFinishedFlag | + kFSArHiliteFinishedFlag | + kFSCO2HiliteFinishedFlag | + kFSHeHiliteFinishedFlag | + kFSOHiliteFinishedFlag | + kFSNHiliteFinishedFlag; + +static const int16 kNoState = 0; +static const int16 kMainMenu = 1; +static const int16 kWaitingForAttach = 2; +static const int16 kDispenseMenu = 3; +static const int16 kWaitingForDispense = 4; + +// Dummy itemIDs +static const ItemID kCO2Item = 10000; +static const ItemID kHeItem = 10001; + +// Interactive points. +static const TimeValue kFSPowerUpStartStart = 0; +static const TimeValue kFSPowerUpStartStop = 600; +static const TimeValue kFSSplashStart = 600; +static const TimeValue kFSSplashStop = 7800; +static const TimeValue kFSSplashIntakeStart = 7800; +static const TimeValue kFSSplashIntakeStop = 18600; + +static const TimeValue kFSMainMenu = 18600; +static const TimeValue kFSIntakeHiliteStart = 19200; +static const TimeValue kFSIntakeHiliteStop = 19800; +static const TimeValue kFSDispenseHiliteStart = 19800; +static const TimeValue kFSDispenseHiliteStop = 20400; + +static const TimeValue kFSDispenseMenu = 20400; + +static const TimeValue kFSArHiliteStart = 21000; +static const TimeValue kFSArHiliteStop = 21600; +static const TimeValue kFSArAttach = 21600; +static const TimeValue kFSArFilledStart = 22200; +static const TimeValue kFSArFilledStop = 25200; +static const TimeValue kFSArIncompatibleStart = 25200; +static const TimeValue kFSArIncompatibleStop = 30000; + +static const TimeValue kFSCO2HiliteStart = 30000; +static const TimeValue kFSCO2HiliteStop = 30600; +static const TimeValue kFSCO2Attach = 30600; +static const TimeValue kFSCO2FilledStart = 31200; +static const TimeValue kFSCO2FilledStop = 34200; +static const TimeValue kFSCO2IncompatibleStart = 34200; +static const TimeValue kFSCO2IncompatibleStop = 39000; + +static const TimeValue kFSHeHiliteStart = 39000; +static const TimeValue kFSHeHiliteStop = 39600; +static const TimeValue kFSHeAttach = 39600; +static const TimeValue kFSHeFilledStart = 40200; +static const TimeValue kFSHeFilledStop = 43200; +static const TimeValue kFSHeIncompatibleStart = 43200; +static const TimeValue kFSHeIncompatibleStop = 48000; + +static const TimeValue kFSOHiliteStart = 48000; +static const TimeValue kFSOHiliteStop = 48600; +static const TimeValue kFSOAttach = 48600; +static const TimeValue kFSOFilledStart = 49200; +static const TimeValue kFSOFilledStop = 52200; +static const TimeValue kFSOIncompatibleStart = 52200; +static const TimeValue kFSOIncompatibleStop = 57000; + +static const TimeValue kFSNHiliteStart = 57000; +static const TimeValue kFSNHiliteStop = 57600; +static const TimeValue kFSNAttach = 57600; +static const TimeValue kFSNFilledStart = 58200; +static const TimeValue kFSNFilledStop = 61200; +static const TimeValue kFSNIncompatibleStart = 61200; +static const TimeValue kFSNIncompatibleStop = 66000; + +static const TimeValue kFSIntakeMenu = 66000; +static const TimeValue kFSIntakeInProgressStart = 66600; +static const TimeValue kFSIntakeInProgressStop = 69600; + +NoradAlphaFillingStation::NoradAlphaFillingStation(Neighborhood *owner) : GameInteraction(kNoradFillingStationInteractionID, owner), + _rightSideMovie(kN01RightSideID), _rightSideNotification(kNoradFillingStationNotificationID, ((PegasusEngine *)g_engine)) { + _state = kNoState; +} + +void NoradAlphaFillingStation::openInteraction() { + _rightSideMovie.initFromMovieFile("Images/Norad Alpha/N01W Right Side"); + _rightSideMovie.moveElementTo(kNoradAlpha01RightSideLeft, kNoradAlpha01RightSideTop); + _rightSideMovie.setDisplayOrder(kN01RightSideOrder); + _rightSideMovie.startDisplaying(); + _rightSideCallBack.setNotification(&_rightSideNotification); + _rightSideCallBack.initCallBack(&_rightSideMovie, kCallBackAtExtremes); + _rightSideCallBack.setCallBackFlag(kFSPowerUpFinishedFlag); + _rightSideNotification.notifyMe(this, kFSNotificationFlags, kFSNotificationFlags); + _rightSideCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _rightSideMovie.show(); + _rightSideMovie.redrawMovieWorld(); + _rightSideMovie.setSegment(kFSPowerUpStartStart, kFSPowerUpStartStop); +} + +void NoradAlphaFillingStation::initInteraction() { + allowInput(false); + + _rightSideMovie.setRate(2); +} + +void NoradAlphaFillingStation::closeInteraction() { + _rightSideMovie.stop(); + _rightSideMovie.stopDisplaying(); + _rightSideMovie.releaseMovie(); + _rightSideCallBack.releaseCallBack(); + ((NoradAlpha *)getOwner())->turnOffFillingStation(); +} + +void NoradAlphaFillingStation::setStaticState(TimeValue time, int16 state) { + _rightSideMovie.stop(); + _rightSideMovie.setSegment(0, _rightSideMovie.getDuration()); + _rightSideMovie.setTime(time); + _state = state; + allowInput(true); +} + +void NoradAlphaFillingStation::setSegmentState(TimeValue start, TimeValue stop, NotificationFlags flag, int16 state) { + _rightSideMovie.stop(); + _rightSideMovie.setSegment(start, stop); + _rightSideMovie.setTime(start); + _rightSideCallBack.setCallBackFlag(flag); + _rightSideCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _state = state; + allowInput(false); + _rightSideMovie.setRate(2); +} + +void NoradAlphaFillingStation::powerUpFinished() { + ((NoradAlpha *)getOwner())->turnOnFillingStation(); + setSegmentState(kFSSplashStart, kFSSplashStop, kFSSplashFinishedFlag, kNoState); +} + +void NoradAlphaFillingStation::splashFinished() { + if (GameState.getNoradGassed()) + setSegmentState(kFSSplashIntakeStart, kFSSplashIntakeStop, kFSIntakeWarningFinishedFlag, kNoState); + else + intakeWarningFinished(); +} + +void NoradAlphaFillingStation::intakeWarningFinished() { + setStaticState(kFSMainMenu, kMainMenu); +} + +void NoradAlphaFillingStation::showIntakeInProgress(uint16 numSeconds) { + if (numSeconds == 0) { + setSegmentState(kFSIntakeInProgressStart, kFSIntakeInProgressStop, kFSIntakeWarningFinishedFlag, kNoState); + Item *item = ((NoradAlpha *)getOwner())->getFillingItem(); + + if (item->getObjectID() == kGasCanister) { + GameState.setNoradGassed(true); + ((NoradAlpha *)getOwner())->loadAmbientLoops(); + getOwner()->restoreStriding(kNorad03, kEast, kAltNoradAlphaNormal); + } + } else { + setSegmentState(kFSIntakeInProgressStart, kFSIntakeInProgressStart + _rightSideMovie.getScale() * numSeconds, + kFSIntakeWarningFinishedFlag, kNoState); + } +} + +void NoradAlphaFillingStation::intakeHighlightFinished() { + _rightSideMovie.stop(); + + if (GameState.getNoradGassed()) { + showIntakeInProgress(2); + } else { + Item *item = ((NoradAlpha *)getOwner())->getFillingItem(); + if (item) + showIntakeInProgress(0); + else + setStaticState(kFSIntakeMenu, kWaitingForAttach); + } +} + +void NoradAlphaFillingStation::dispenseHighlightFinished() { + setStaticState(kFSDispenseMenu, kDispenseMenu); +} + +void NoradAlphaFillingStation::dispenseGas() { + Item *item = ((NoradAlpha *)getOwner())->getFillingItem(); + + if (item) { + if (item->getObjectID() != _dispenseItemID) + switch (_dispenseItemID) { + case kArgonCanister: + setSegmentState(kFSArIncompatibleStart, kFSArIncompatibleStop, + kFSIntakeWarningFinishedFlag, kNoState); + break; + case kCO2Item: + setSegmentState(kFSCO2IncompatibleStart, kFSCO2IncompatibleStop, + kFSIntakeWarningFinishedFlag, kNoState); + break; + case kHeItem: + setSegmentState(kFSHeIncompatibleStart, kFSHeIncompatibleStop, + kFSIntakeWarningFinishedFlag, kNoState); + break; + case kAirMask: + setSegmentState(kFSOIncompatibleStart, kFSOIncompatibleStop, + kFSIntakeWarningFinishedFlag, kNoState); + break; + case kNitrogenCanister: + setSegmentState(kFSNIncompatibleStart, kFSNIncompatibleStop, + kFSIntakeWarningFinishedFlag, kNoState); + break; + } + else { + if (_dispenseItemID == kArgonCanister) { + setSegmentState(kFSArFilledStart, kFSArFilledStop, kFSIntakeWarningFinishedFlag, kNoState); + item->setItemState(kArgonFull); + GameState.setScoringFilledArgonCanister(true); + } else if (_dispenseItemID == kAirMask) { + setSegmentState(kFSOFilledStart, kFSOFilledStop, kFSIntakeWarningFinishedFlag, kNoState); + ((AirMask *)item)->refillAirMask(); + GameState.setScoringFilledOxygenCanister(true); + } else if (_dispenseItemID == kNitrogenCanister) { + setSegmentState(kFSNFilledStart, kFSNFilledStop, kFSIntakeWarningFinishedFlag, kNoState); + item->setItemState(kNitrogenFull); + } + } + } else { + switch (_dispenseItemID) { + case kArgonCanister: + setStaticState(kFSArAttach, kWaitingForDispense); + break; + case kCO2Item: + setStaticState(kFSCO2Attach, kWaitingForDispense); + break; + case kHeItem: + setStaticState(kFSHeAttach, kWaitingForDispense); + break; + case kAirMask: + setStaticState(kFSOAttach, kWaitingForDispense); + break; + case kNitrogenCanister: + setStaticState(kFSNAttach, kWaitingForDispense); + break; + } + } +} + +void NoradAlphaFillingStation::ArHighlightFinished() { + _dispenseItemID = kArgonCanister; + dispenseGas(); +} + +void NoradAlphaFillingStation::CO2HighlightFinished() { + _dispenseItemID = kCO2Item; + dispenseGas(); +} + +void NoradAlphaFillingStation::HeHighlightFinished() { + _dispenseItemID = kHeItem; + dispenseGas(); +} + +void NoradAlphaFillingStation::OHighlightFinished() { + _dispenseItemID = kAirMask; + dispenseGas(); +} + +void NoradAlphaFillingStation::NHighlightFinished() { + _dispenseItemID = kNitrogenCanister; + dispenseGas(); +} + +void NoradAlphaFillingStation::receiveNotification(Notification *, const NotificationFlags flags) { + switch (flags) { + case kFSPowerUpFinishedFlag: + powerUpFinished(); + break; + case kFSSplashFinishedFlag: + splashFinished(); + break; + case kFSIntakeWarningFinishedFlag: + intakeWarningFinished(); + break; + case kFSIntakeHiliteFinishedFlag: + intakeHighlightFinished(); + break; + case kFSDispenseHiliteFinishedFlag: + dispenseHighlightFinished(); + break; + case kFSArHiliteFinishedFlag: + ArHighlightFinished(); + break; + case kFSCO2HiliteFinishedFlag: + CO2HighlightFinished(); + break; + case kFSHeHiliteFinishedFlag: + HeHighlightFinished(); + break; + case kFSOHiliteFinishedFlag: + OHighlightFinished(); + break; + case kFSNHiliteFinishedFlag: + NHighlightFinished(); + break; + } +} + +void NoradAlphaFillingStation::handleInput(const Input &input, const Hotspot *cursorSpot) { + InputHandler::handleInput(input, cursorSpot); +} + +void NoradAlphaFillingStation::clickInIntake() { + setSegmentState(kFSIntakeHiliteStart, kFSIntakeHiliteStop, kFSIntakeHiliteFinishedFlag, kNoState); +} + +void NoradAlphaFillingStation::clickInDispense() { + setSegmentState(kFSDispenseHiliteStart, kFSDispenseHiliteStop, kFSDispenseHiliteFinishedFlag, kNoState); +} + +void NoradAlphaFillingStation::clickInAr() { + setSegmentState(kFSArHiliteStart, kFSArHiliteStop, kFSArHiliteFinishedFlag, kNoState); +} + +void NoradAlphaFillingStation::clickInCO2() { + setSegmentState(kFSCO2HiliteStart, kFSCO2HiliteStop, kFSCO2HiliteFinishedFlag, kNoState); +} + +void NoradAlphaFillingStation::clickInHe() { + setSegmentState(kFSHeHiliteStart, kFSHeHiliteStop, kFSHeHiliteFinishedFlag, kNoState); +} + +void NoradAlphaFillingStation::clickInO() { + setSegmentState(kFSOHiliteStart, kFSOHiliteStop, kFSOHiliteFinishedFlag, kNoState); +} + +void NoradAlphaFillingStation::clickInN() { + setSegmentState(kFSNHiliteStart, kFSNHiliteStop, kFSNHiliteFinishedFlag, kNoState); +} + +void NoradAlphaFillingStation::clickInHotspot(const Input &input, const Hotspot *spot) { + GameInteraction::clickInHotspot(input, spot); + + switch (spot->getObjectID()) { + case kNorad01IntakeSpotID: + clickInIntake(); + break; + case kNorad01DispenseSpotID: + clickInDispense(); + break; + case kNorad01ArSpotID: + clickInAr(); + break; + case kNorad01CO2SpotID: + clickInCO2(); + break; + case kNorad01HeSpotID: + clickInHe(); + break; + case kNorad01OSpotID: + clickInO(); + break; + case kNorad01NSpotID: + clickInN(); + break; + } +} + +void NoradAlphaFillingStation::activateHotspots() { + GameInteraction::activateHotspots(); + + switch (_state) { + case kMainMenu: + g_allHotspots.activateOneHotspot(kNorad01IntakeSpotID); + g_allHotspots.activateOneHotspot(kNorad01DispenseSpotID); + break; + case kDispenseMenu: + g_allHotspots.activateOneHotspot(kNorad01ArSpotID); + g_allHotspots.activateOneHotspot(kNorad01CO2SpotID); + g_allHotspots.activateOneHotspot(kNorad01HeSpotID); + g_allHotspots.activateOneHotspot(kNorad01OSpotID); + g_allHotspots.activateOneHotspot(kNorad01NSpotID); + break; + } +} + +void NoradAlphaFillingStation::newFillingItem(Item *item) { + switch (_state) { + case kWaitingForAttach: + if (item) + showIntakeInProgress(0); + break; + case kWaitingForDispense: + dispenseGas(); + break; + default: + break; + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/norad/alpha/fillingstation.h b/engines/pegasus/neighborhood/norad/alpha/fillingstation.h new file mode 100644 index 0000000000..eb2088e373 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/alpha/fillingstation.h @@ -0,0 +1,91 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_NORAD_ALPHA_FILLINGSTATION_H +#define PEGASUS_NEIGHBORHOOD_NORAD_ALPHA_FILLINGSTATION_H + +#include "pegasus/interaction.h" +#include "pegasus/movie.h" +#include "pegasus/notification.h" + +namespace Pegasus { + +class Item; + +class NoradAlphaFillingStation : public GameInteraction, public NotificationReceiver { +public: + NoradAlphaFillingStation(Neighborhood *); + virtual ~NoradAlphaFillingStation() {} + + virtual void handleInput(const Input &, const Hotspot *); + + virtual void clickInHotspot(const Input &, const Hotspot *); + virtual void activateHotspots(); + + void newFillingItem(Item *); + +protected: + void receiveNotification(Notification *, const NotificationFlags); + + virtual void openInteraction(); + virtual void initInteraction(); + virtual void closeInteraction(); + + void powerUpFinished(); + void splashFinished(); + void intakeWarningFinished(); + void intakeHighlightFinished(); + void dispenseHighlightFinished(); + void ArHighlightFinished(); + void CO2HighlightFinished(); + void HeHighlightFinished(); + void OHighlightFinished(); + void NHighlightFinished(); + + void showIntakeInProgress(uint16); + + void clickInIntake(); + void clickInDispense(); + void clickInAr(); + void clickInCO2(); + void clickInHe(); + void clickInO(); + void clickInN(); + + void dispenseGas(); + + void setStaticState(TimeValue, int16); + void setSegmentState(TimeValue, TimeValue, NotificationFlags, int16); + + Movie _rightSideMovie; + Notification _rightSideNotification; + NotificationCallBack _rightSideCallBack; + int16 _state; + ItemID _dispenseItemID; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/alpha/noradalpha.cpp b/engines/pegasus/neighborhood/norad/alpha/noradalpha.cpp new file mode 100644 index 0000000000..8391a3ff10 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/alpha/noradalpha.cpp @@ -0,0 +1,763 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/inventory/airmask.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/norad/subcontrolroom.h" +#include "pegasus/neighborhood/norad/alpha/ecrmonitor.h" +#include "pegasus/neighborhood/norad/alpha/fillingstation.h" +#include "pegasus/neighborhood/norad/alpha/noradalpha.h" + +namespace Pegasus { + +const uint32 NoradAlpha::_noradAlphaClawExtras[22] = { + kN22ClawFromAToB, + kN22ClawALoop, + kN22ClawAPinch, + kN22ClawACounterclockwise, + kN22ClawAClockwise, + kN22ClawFromBToA, + kN22ClawFromBToC, + kN22ClawFromBToD, + kN22ClawBLoop, + kN22ClawBPinch, + kN22ClawBCounterclockwise, + kN22ClawBClockwise, + kN22ClawFromCToB, + kN22ClawCLoop, + kN22ClawCPinch, + kN22ClawCCounterclockwise, + kN22ClawCClockwise, + kN22ClawFromDToB, + kN22ClawDLoop, + kN22ClawDPinch, + kN22ClawDCounterclockwise, + kN22ClawDClockwise +}; + +NoradAlpha::NoradAlpha(InputHandler *nextHandler, PegasusEngine *owner) : Norad(nextHandler, owner, "Norad Alpha", kNoradAlphaID) { + _elevatorUpRoomID = kNorad11South; + _elevatorDownRoomID = kNorad12South; + _elevatorUpSpotID = kNorad12ElevatorUpSpotID; + _elevatorDownSpotID = kNorad11ElevatorDownSpotID; + + _subRoomEntryRoom1 = kNorad10; + _subRoomEntryDir1 = kEast; + _subRoomEntryRoom2 = kNorad21; + _subRoomEntryDir2 = kWest; + _upperPressureDoorRoom = kNorad10East; + _lowerPressureDoorRoom = kNorad21West; + + _upperPressureDoorUpSpotID = kAlphaUpperPressureDoorUpSpotID; + _upperPressureDoorDownSpotID = kAlphaUpperPressureDoorDownSpotID; + _upperPressureDoorAbortSpotID = kNorad10EastOutSpotID; + + _lowerPressureDoorUpSpotID = kAlphaLowerPressureDoorUpSpotID; + _lowerPressureDoorDownSpotID = kAlphaLowerPressureDoorDownSpotID; + _lowerPressureDoorAbortSpotID = kNorad21WestOutSpotID; + + _pressureSoundIn = kPressureDoorIntro1In; + _pressureSoundOut = kPressureDoorIntro1Out; + _equalizeSoundIn = kPressureDoorIntro2In; + _equalizeSoundOut = kPressureDoorIntro2Out; + _accessDeniedIn = kAlphaAccessDeniedIn; + _accessDeniedOut = kAlphaAccessDeniedOut; + + _platformRoom = kNorad19West; + _subControlRoom = kNorad22West; + + _subPrepFailed = false; + + setIsItemTaken(kGasCanister); +} + +void NoradAlpha::init() { + Norad::init(); + + Hotspot *hotspot = g_allHotspots.findHotspotByID(kN01GasCanisterSpotID); + hotspot->setMaskedHotspotFlags(kPickUpItemSpotFlag, kPickUpItemSpotFlag); + HotspotInfoTable::Entry *hotspotEntry = findHotspotEntry(kN01GasCanisterSpotID); + hotspotEntry->hotspotItem = kGasCanister; + + hotspot = g_allHotspots.findHotspotByID(kN01ArgonCanisterSpotID); + hotspot->setMaskedHotspotFlags(kPickUpItemSpotFlag, kPickUpItemSpotFlag); + hotspotEntry = findHotspotEntry(kN01ArgonCanisterSpotID); + hotspotEntry->hotspotItem = kArgonCanister; + + hotspot = g_allHotspots.findHotspotByID(kN01NitrogenCanisterSpotID); + hotspot->setMaskedHotspotFlags(kPickUpItemSpotFlag, kPickUpItemSpotFlag); + hotspotEntry = findHotspotEntry(kN01NitrogenCanisterSpotID); + hotspotEntry->hotspotItem = kNitrogenCanister; + + hotspot = g_allHotspots.findHotspotByID(kN01AirMaskSpotID); + hotspot->setMaskedHotspotFlags(kPickUpItemSpotFlag, kPickUpItemSpotFlag); + hotspotEntry = findHotspotEntry(kN01AirMaskSpotID); + hotspotEntry->hotspotItem = kAirMask; + + hotspot = g_allHotspots.findHotspotByID(kN01GasOutletSpotID); + hotspot->setMaskedHotspotFlags(kDropItemSpotFlag, kDropItemSpotFlag); +} + +void NoradAlpha::start() { + if (g_energyMonitor) { + g_energyMonitor->stopEnergyDraining(); + g_energyMonitor->restoreLastEnergyValue(); + _vm->resetEnergyDeathReason(); + g_energyMonitor->startEnergyDraining(); + } + + NeighborhoodID itemNeighborhood; + RoomID itemRoom; + DirectionConstant itemDirection; + + Item *item = (Item *)g_allItems.findItemByID(kGasCanister); + item->getItemRoom(itemNeighborhood, itemRoom, itemDirection); + + if (itemNeighborhood == getObjectID()) { + _fillingStationItem = item; + } else { + item = (Item *)g_allItems.findItemByID(kAirMask); + item->getItemRoom(itemNeighborhood, itemRoom, itemDirection); + + if (itemNeighborhood == getObjectID()) { + _fillingStationItem = item; + } else { + item = (Item *)g_allItems.findItemByID(kNitrogenCanister); + item->getItemRoom(itemNeighborhood, itemRoom, itemDirection); + + if (itemNeighborhood == getObjectID()) { + _fillingStationItem = item; + } else { + item = (Item *)g_allItems.findItemByID(kArgonCanister); + item->getItemRoom(itemNeighborhood, itemRoom, itemDirection); + if (itemNeighborhood == getObjectID()) + _fillingStationItem = item; + else + _fillingStationItem = 0; + } + } + } + + if (!GameState.getNoradGassed()) + forceStridingStop(kNorad03, kEast, kAltNoradAlphaNormal); + + GameState.setNoradArrivedFromSub(false); + Norad::start(); +} + +void NoradAlpha::setUpAIRules() { + Neighborhood::setUpAIRules(); + + if (g_AIArea) { + AIPlayMessageAction *messageAction = new AIPlayMessageAction("Images/AI/Norad/XN01WD1", false); + AIHasItemCondition *hasGasCanisterCondition = new AIHasItemCondition(kGasCanister); + AIRule *rule = new AIRule(hasGasCanisterCondition, messageAction); + g_AIArea->addAIRule(rule); + } +} + +bool NoradAlpha::okayToJump() { + bool result = Neighborhood::okayToJump(); + + if (!result) + playSpotSoundSync(kAlphaCantTransportIn, kAlphaCantTransportOut); + + return result; +} + +void NoradAlpha::getExtraCompassMove(const ExtraTable::Entry &entry, FaderMoveSpec &compassMove) { + if (entry.extra == kNorad19ExitToSub) { + compassMove.makeTwoKnotFaderSpec(kNoradAlphaMovieScale, entry.movieStart, 270 + kSubPlatformCompassAngle, + entry.movieEnd, 90 + 20 + 360); + compassMove.insertFaderKnot(entry.movieStart + 10 * kNoradAlphaFrameDuration, 270 + kSubPlatformCompassAngle); + compassMove.insertFaderKnot(entry.movieStart + 29 * kNoradAlphaFrameDuration, 270 + kSubPlatformCompassAngle + 20); + compassMove.insertFaderKnot(entry.movieStart + 52 * kNoradAlphaFrameDuration, 270 + kSubPlatformCompassAngle + 20); + compassMove.insertFaderKnot(entry.movieStart + 84 * kNoradAlphaFrameDuration, 360 + 90); + compassMove.insertFaderKnot(entry.movieStart + 198 * kNoradAlphaFrameDuration, 360 + 90); + compassMove.insertFaderKnot(entry.movieStart + 270 * kNoradAlphaFrameDuration, 360 + 90 + 15); + compassMove.insertFaderKnot(entry.movieStart + 280 * kNoradAlphaFrameDuration, 360 + 90 + 20); + } else { + Norad::getExtraCompassMove(entry, compassMove); + } +} + +void NoradAlpha::playClawMonitorIntro() { + playSpotSoundSync(kLoadClawIntroIn, kLoadClawIntroOut); +} + +GameInteraction *NoradAlpha::makeInteraction(const InteractionID interactionID) { + switch (interactionID) { + case kNoradECRMonitorInteractionID: + return new NoradAlphaECRMonitor(this); + case kNoradFillingStationInteractionID: + return new NoradAlphaFillingStation(this); + } + + return Norad::makeInteraction(interactionID); +} + +void NoradAlpha::loadAmbientLoops() { + // clone2727 would like to point out that the following comment does not quite + // match the code logic below + +/* + Logic: + + loop sound 1: + if gassed, + play warning loop of some sort + else + play nothing + loop sound 2: + if gassed and not wearing air mask + if in ECR + play breathing water loop + else + play breathing + else + if in ECR + play water loop + if at N07 north + play unmanned loop +*/ + + if (!GameState.getNoradSeenTimeStream()) + return; + + RoomID room = GameState.getCurrentRoom(); + if (GameState.getNoradGassed()) { + if (room >= kNorad11 && room <= kNorad19West) + loadLoopSound1("Sounds/Norad/NEW SUB AMB.22K.AIFF", kNoradWarningVolume * 3); + else if (room >= kNorad21 && room <= kNorad22West) + loadLoopSound1("Sounds/Norad/SUB CONTRL LOOP.22K.AIFF", kNoradWarningVolume * 3); + else + loadLoopSound1("Sounds/Norad/WARNING LOOP.22K.AIFF", kNoradWarningVolume); + } else { + loadLoopSound1(""); + } + + if (GameState.getNoradGassed() && !g_airMask->isAirFilterOn()) { + if (room >= kNorad01 && room <= kNorad01West) { + loadLoopSound2("Sounds/Norad/Breathing Water.22K.AIFF", kNoradSuckWindVolume); + } else if (room == kNorad02) { + if (GameState.isCurrentDoorOpen()) + loadLoopSound2("Sounds/Norad/Breathing Water.22K.AIFF", kNoradSuckWindVolume); + else + loadLoopSound2("Sounds/Norad/SUCKING WIND.22K.AIFF", kNoradSuckWindVolume, 0, 0); + } else { + loadLoopSound2("Sounds/Norad/SUCKING WIND.22K.AIFF", kNoradSuckWindVolume, 0, 0); + } + } else { + if (room >= kNorad01 && room <= kNorad01West) { + loadLoopSound2("Sounds/Norad/WATER FLOWING.AIFF", 0x100 / 2); + } else if (room == kNorad02) { + if (GameState.isCurrentDoorOpen()) + loadLoopSound2("Sounds/Norad/WATER FLOWING.AIFF", 0x100 / 2); + else + loadLoopSound2(""); + } else { + loadLoopSound2(""); + } + } + +} + +void NoradAlpha::checkContinuePoint(const RoomID room, const DirectionConstant direction) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kNorad02, kEast): + case MakeRoomView(kNorad06, kEast): + case MakeRoomView(kNorad11, kEast): + case MakeRoomView(kNorad15, kEast): + case MakeRoomView(kNorad19, kWest): + case MakeRoomView(kNorad21, kSouth): + makeContinuePoint(); + break; + } +} + +void NoradAlpha::arriveAt(const RoomID room, const DirectionConstant direction) { + Norad::arriveAt(room, direction); + + switch (GameState.getCurrentRoom()) { + case kNorad01: + arriveAtNorad01(); + break; + case kNorad01East: + arriveAtNorad01East(); + break; + case kNorad01West: + arriveAtNorad01West(); + break; + case kNorad04: + arriveAtNorad04(); + break; + case kNorad07North: + GameState.setScoringSawUnconsciousOperator(true); + break; + case kNorad11: + GameState.setScoringWentThroughPressureDoor(true); + break; + case kNorad22: + arriveAtNorad22(); + break; + } +} + +void NoradAlpha::arriveAtNorad01() { + if (!GameState.getNoradSeenTimeStream() && GameState.getCurrentDirection() == kSouth) { + GameState.setNoradN22MessagePlayed(false); + requestExtraSequence(kNoradArriveFromTSA, kExtraCompletedFlag, kFilterNoInput); + // You are no match for me, human. + requestExtraSequence(kNorad01RobotTaunt, kExtraCompletedFlag, kFilterNoInput); + } +} + +void NoradAlpha::arriveAtNorad01East() { + GameState.setScoringSawSecurityMonitor(true); + newInteraction(kNoradECRMonitorInteractionID); +} + +void NoradAlpha::arriveAtNorad01West() { + newInteraction(kNoradFillingStationInteractionID); +} + +void NoradAlpha::arriveAtNorad04() { + if (GameState.getCurrentDirection() == kEast && !GameState.getNoradGassed()) + playDeathExtra(kNorad04EastDeath, kDeathWokeUpNorad); +} + +void NoradAlpha::arriveAtNorad22() { + if (!GameState.getNoradN22MessagePlayed() && GameState.getCurrentDirection() == kSouth) { + startExtraSequence(kNorad22SouthIntro, kExtraCompletedFlag, kFilterNoInput); + GameState.setNoradN22MessagePlayed(true); + } +} + +void NoradAlpha::bumpIntoWall() { + requestSpotSound(kAlphaBumpIntoWallIn, kAlphaBumpIntoWallOut, kFilterNoInput, 0); + Neighborhood::bumpIntoWall(); +} + +void NoradAlpha::receiveNotification(Notification *notification, const NotificationFlags flags) { + if ((flags & kExtraCompletedFlag) != 0) { + switch (_lastExtra) { + case kNoradArriveFromTSA: + GameState.setNoradSeenTimeStream(true); + loadAmbientLoops(); + break; + case kNorad01RobotTaunt: + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Norad/XN01SB", false, kWarningInterruption); + _interruptionFilter = kFilterAllInput; + makeContinuePoint(); + break; + } + } + + Norad::receiveNotification(notification, flags); + + if ((flags & kExtraCompletedFlag) != 0) { + switch (_lastExtra) { + case kNorad22SouthIntro: + loopExtraSequence(kNorad22SouthReply); + playSpotSoundSync(kN22ReplyIn, kN22ReplyOut); + startExtraSequence(kNorad22SouthFinish, kExtraCompletedFlag, kFilterNoInput); + break; + case kNorad22SouthFinish: + _interruptionFilter = kFilterAllInput; + // Force ArriveAt to do its thing... + GameState.setCurrentRoom(kNorad21); + arriveAt(kNorad22, kSouth); + break; + } + } + + g_AIArea->checkMiddleArea(); +} + +void NoradAlpha::getZoomEntry(const HotSpotID spotID, ZoomTable::Entry &entry) { + Norad::getZoomEntry(spotID, entry); + + ExtraTable::Entry extra; + + if (spotID == kNorad01GasSpotID) { + if (_fillingStationItem) { + if (_fillingStationItem->getObjectID() == kGasCanister) { + getExtraEntry(kNorad01ZoomInWithGasCanister, extra); + entry.movieStart = extra.movieStart; + entry.movieEnd = extra.movieEnd; + } else { + entry.clear(); + } + } + } else if (spotID == kNorad01GasOutSpotID) { + if (_fillingStationItem) { + if (_fillingStationItem->getObjectID() == kGasCanister) { + getExtraEntry(kNorad01ZoomOutWithGasCanister, extra); + entry.movieStart = extra.movieStart; + entry.movieEnd = extra.movieEnd; + } else { + entry.clear(); + } + } + } +} + +TimeValue NoradAlpha::getViewTime(const RoomID room, const DirectionConstant direction) { + ExtraTable::Entry entry; + + if (room == kNorad01 && direction == kSouth && !GameState.getNoradSeenTimeStream()) { + getExtraEntry(kNoradArriveFromTSA, entry); + return entry.movieStart; + } + + if (room == kNorad01 && direction == kWest) { + if (!_fillingStationItem) { + return Norad::getViewTime(room, direction); + } else { + getExtraEntry(kN01WGasCanister, entry); + return entry.movieStart; + } + } else if (room == kNorad01West && direction == kWest) { + uint32 extraID = 0xffffffff; + if (_fillingStationItem) { + switch (_fillingStationItem->getObjectID()) { + case kArgonCanister: + if (GameState.getNoradFillingStationOn()) + extraID = kN01WZArgonCanisterLit; + else + extraID = kN01WZArgonCanisterDim; + break; + case kGasCanister: + if (GameState.getNoradFillingStationOn()) + extraID = kN01WZGasCanisterLit; + else + extraID = kN01WZGasCanisterDim; + break; + case kAirMask: + if (GameState.getNoradFillingStationOn()) + extraID = kN01WZAirMaskLit; + else + extraID = kN01WZAirMaskDim; + break; + case kNitrogenCanister: + if (GameState.getNoradFillingStationOn()) + extraID = kN01WZNitrogenCanisterLit; + else + extraID = kN01WZNitrogenCanisterDim; + break; + default: + // Should never happen. + break; + } + } else if (GameState.getNoradFillingStationOn()) { + extraID = kN01WZEmptyLit; + } + + if (extraID == 0xffffffff) { + return Norad::getViewTime(room, direction); + } else { + getExtraEntry(extraID, entry); + return entry.movieStart; + } + } + + return Norad::getViewTime(room, direction); +} + +void NoradAlpha::turnOnFillingStation() { + if (GameState.getCurrentRoom() == kNorad01West && !GameState.getNoradFillingStationOn()) { + GameState.setNoradFillingStationOn(true); + updateViewFrame(); + } +} + +void NoradAlpha::turnOffFillingStation() { + if (GameState.getCurrentRoom() == kNorad01West && GameState.getNoradFillingStationOn()) { + GameState.setNoradFillingStationOn(false); + updateViewFrame(); + } +} + +void NoradAlpha::activateHotspots() { + Norad::activateHotspots(); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kNorad01West, kWest): + if (_vm->getDragType() == kDragInventoryUse) { + if (!_fillingStationItem) { + ItemID itemID = _vm->getDraggingItem()->getObjectID(); + if (itemID == kArgonCanister || itemID == kGasCanister || itemID == kAirMask || + itemID == kNitrogenCanister) + g_allHotspots.activateOneHotspot(kN01GasOutletSpotID); + } + } else { + HotSpotID spotID; + + if (_fillingStationItem) { + switch (_fillingStationItem->getObjectID()) { + case kArgonCanister: + spotID = kN01ArgonCanisterSpotID; + g_allHotspots.deactivateOneHotspot(kNorad01GasOutSpotID); + break; + case kGasCanister: + spotID = kN01GasCanisterSpotID; + break; + case kAirMask: + spotID = kN01AirMaskSpotID; + g_allHotspots.deactivateOneHotspot(kNorad01GasOutSpotID); + break; + case kNitrogenCanister: + spotID = kN01NitrogenCanisterSpotID; + g_allHotspots.deactivateOneHotspot(kNorad01GasOutSpotID); + break; + default: + // Should never happen. + spotID = kNoHotSpotID; + break; + } + g_allHotspots.activateOneHotspot(spotID); + } + } + break; + case MakeRoomView(kNorad10, kEast): + if (GameState.isCurrentDoorOpen()) + g_allHotspots.deactivateOneHotspot(kNorad10DoorSpotID); + break; + case MakeRoomView(kNorad21, kWest): + if (GameState.isCurrentDoorOpen()) + g_allHotspots.deactivateOneHotspot(kNorad21WestSpotID); + break; + } +} + +void NoradAlpha::clickInHotspot(const Input &input, const Hotspot *cursorSpot) { + Norad::clickInHotspot(input, cursorSpot); + + if (_vm->getDragType() == kDragInventoryUse) { + if (GameState.getCurrentRoomAndView() == MakeRoomView(kNorad01West, kWest)) { + Item *item = _vm->getDraggingItem(); + if (item->getObjectID() == kAirMask || item->getObjectID() == kArgonCanister || + item->getObjectID() == kNitrogenCanister || item->getObjectID() == kGasCanister) { + HotspotInfoTable::Entry *hotspotEntry = findHotspotEntry(kN01GasOutletSpotID); + hotspotEntry->hotspotItem = item->getObjectID(); + } + } + } +} + +void NoradAlpha::takeItemFromRoom(Item *item) { + if (GameState.getCurrentRoom() == kNorad01West) { + if (_fillingStationItem == item) { + _fillingStationItem = 0; + GameState.setNoradGassed(false); + loadAmbientLoops(); + ((NoradAlphaFillingStation *)_currentInteraction)->newFillingItem(0); + forceStridingStop(kNorad03, kEast, kAltNoradAlphaNormal); + } + } + + Norad::takeItemFromRoom(item); +} + +void NoradAlpha::dropItemIntoRoom(Item *item, Hotspot *droppedSpot) { + if (GameState.getCurrentRoom() == kNorad01West) { + if (!_fillingStationItem) { + _fillingStationItem = item; + ((NoradAlphaFillingStation *)_currentInteraction)->newFillingItem(item); + } + } + + Norad::dropItemIntoRoom(item, droppedSpot); +} + +void NoradAlpha::getClawInfo(HotSpotID &outSpotID, HotSpotID &prepSpotID, HotSpotID &clawControlSpotID, HotSpotID &pinchClawSpotID, + HotSpotID &moveClawDownSpotID, HotSpotID &moveClawRightSpotID, HotSpotID &moveClawLeftSpotID, HotSpotID &moveClawUpSpotID, + HotSpotID &clawCCWSpotID, HotSpotID &clawCWSpotID, uint32 &clawPosition, const uint32 *&clawExtraIDs) { + outSpotID = kNorad22MonitorOutSpotID; + prepSpotID = kNorad22LaunchPrepSpotID; + clawControlSpotID = kNorad22ClawControlSpotID; + pinchClawSpotID = kNorad22ClawPinchSpotID; + moveClawDownSpotID = kNorad22ClawDownSpotID; + moveClawRightSpotID = kNorad22ClawRightSpotID; + moveClawLeftSpotID = kNorad22ClawLeftSpotID; + moveClawUpSpotID = kNorad22ClawUpSpotID; + clawCCWSpotID = kNorad22ClawCCWSpotID; + clawCWSpotID = kNorad22ClawCWSpotID; + clawPosition = kClawAtD; + clawExtraIDs = _noradAlphaClawExtras; +} + +Hotspot *NoradAlpha::getItemScreenSpot(Item *item, DisplayElement *element) { + switch (item->getObjectID()) { + case kGasCanister: + return g_allHotspots.findHotspotByID(kN01GasCanisterSpotID); + case kAirMask: + return g_allHotspots.findHotspotByID(kN01AirMaskSpotID); + case kArgonCanister: + return g_allHotspots.findHotspotByID(kN01ArgonCanisterSpotID); + case kNitrogenCanister: + return g_allHotspots.findHotspotByID(kN01NitrogenCanisterSpotID); + } + + return Norad::getItemScreenSpot(item, element); +} + +Common::String NoradAlpha::getEnvScanMovie() { + Common::String movieName = Neighborhood::getEnvScanMovie(); + + if (movieName.empty()) { + RoomID room = GameState.getCurrentRoom(); + if (room >= kNorad01 && room <= kNorad01West) + return "Images/AI/Norad/XNE1"; + else if ((room >= kNorad02 && room <= kNorad19West)) + return "Images/AI/Norad/XNE2"; + + return "Images/AI/Norad/XNE3"; + } + + return movieName; +} + +uint NoradAlpha::getNumHints() { + uint numHints = Neighborhood::getNumHints(); + + if (numHints == 0) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kNorad01, kNorth): + case MakeRoomView(kNorad01, kSouth): + case MakeRoomView(kNorad01, kEast): + case MakeRoomView(kNorad01, kWest): + case MakeRoomView(kNorad01East, kEast): + case MakeRoomView(kNorad01West, kWest): + if (GameState.getNoradGassed()) { + if (g_airMask->isAirFilterOn()) + numHints = 0; + else + numHints = 3; + } else { + numHints = 2; + } + break; + case MakeRoomView(kNorad19West, kWest): + if (getSubPrepFailed() && GameState.getNoradSubPrepState() != kSubPrepped) + numHints = 1; + break; + case MakeRoomView(kNorad22, kWest): + numHints = 1; + break; + } + } + + return numHints; +} + +Common::String NoradAlpha::getHintMovie(uint hintNum) { + Common::String movieName = Neighborhood::getHintMovie(hintNum); + + if (movieName.empty()) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kNorad01, kNorth): + case MakeRoomView(kNorad01, kSouth): + case MakeRoomView(kNorad01, kEast): + case MakeRoomView(kNorad01, kWest): + case MakeRoomView(kNorad01East, kEast): + case MakeRoomView(kNorad01West, kWest): + switch (hintNum) { + case 1: + if (GameState.getNoradGassed()) + return "Images/AI/Norad/XN01SW"; + + return "Images/AI/Norad/XN01WD2"; + case 2: + if (GameState.getNoradGassed()) { + if (_vm->playerHasItemID(kAirMask)) + // Mask must not be on if we get here... + return "Images/AI/Globals/XGLOB1A"; + + return "Images/AI/Globals/XGLOB3D"; + } + + return "Images/AI/Globals/XGLOB5C"; + case 3: + return "Images/AI/Norad/XN01SH"; + } + break; + case MakeRoomView(kNorad19West, kWest): + return "Images/AI/Norad/XN19NH"; + case MakeRoomView(kNorad22, kWest): + return "Images/AI/Globals/XGLOB1C"; + } + } + + return movieName; +} + +void NoradAlpha::closeDoorOffScreen(const RoomID room, const DirectionConstant) { + switch (room) { + case kNorad12: + case kNorad13: + case kNorad18: + case kNorad19: + playSpotSoundSync(kAlphaElevatorDoorCloseIn, kAlphaElevatorDoorCloseOut); + break; + default: + playSpotSoundSync(kAlphaRegDoorCloseIn, kAlphaRegDoorCloseOut); + break; + } +} + +void NoradAlpha::findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &spotEntry) { + if (room == kNorad01 && direction == kSouth) + spotEntry.clear(); + else + Norad::findSpotEntry(room, direction, flags, spotEntry); +} + +bool NoradAlpha::canSolve() { + return Norad::canSolve() || getHintMovie(1) == "Images/AI/Norad/XN01SW"; +} + +void NoradAlpha::doSolve() { + Norad::doSolve(); + + if (getHintMovie(1) == "Images/AI/Norad/XN01SW") { + _vm->addItemToInventory(g_airMask); + g_airMask->putMaskOn(); + } +} + +Common::String NoradAlpha::getNavMovieName() { + return "Images/Norad Alpha/Norad Alpha.movie"; +} + +Common::String NoradAlpha::getSoundSpotsName() { + return "Sounds/Norad/Norad Alpha Spots"; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/norad/alpha/noradalpha.h b/engines/pegasus/neighborhood/norad/alpha/noradalpha.h new file mode 100644 index 0000000000..b88a6eb802 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/alpha/noradalpha.h @@ -0,0 +1,115 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_NORAD_ALPHA_NORADALPHA_H +#define PEGASUS_NEIGHBORHOOD_NORAD_ALPHA_NORADALPHA_H + +#include "pegasus/neighborhood/norad/norad.h" + +namespace Pegasus { + +class Item; + +class NoradAlpha : public Norad { +public: + NoradAlpha(InputHandler *, PegasusEngine *); + virtual ~NoradAlpha() {} + + virtual void init(); + void start(); + + virtual bool okayToJump(); + + void playClawMonitorIntro(); + + void getExtraCompassMove(const ExtraTable::Entry &, FaderMoveSpec &); + + void turnOnFillingStation(); + void turnOffFillingStation(); + Item *getFillingItem() { return _fillingStationItem; } + bool gasCanisterIntake(); + + virtual void takeItemFromRoom(Item *); + virtual void dropItemIntoRoom(Item *, Hotspot *); + + virtual GameInteraction *makeInteraction(const InteractionID); + + virtual void getClawInfo(HotSpotID &outSpotID, HotSpotID &prepSpotID, HotSpotID &clawControlSpotID, + HotSpotID &pinchClawSpotID, HotSpotID &moveClawDownSpotID, HotSpotID &moveClawRightSpotID, + HotSpotID &moveClawLeftSpotID, HotSpotID &moveClawUpSpotID, HotSpotID &clawCCWSpotID, + HotSpotID &clawCWSpotID, uint32 &, const uint32 *&); + + void loadAmbientLoops(); + + Common::String getEnvScanMovie(); + uint getNumHints(); + Common::String getHintMovie(uint); + void setUpAIRules(); + + void setSubPrepFailed(bool value) { _subPrepFailed = value; } + bool getSubPrepFailed() { return _subPrepFailed; } + + void closeDoorOffScreen(const RoomID, const DirectionConstant); + void findSpotEntry(const RoomID, const DirectionConstant, SpotFlags, SpotTable::Entry &); + void clickInHotspot(const Input &, const Hotspot *); + + void checkContinuePoint(const RoomID, const DirectionConstant); + + bool canSolve(); + void doSolve(); + +protected: + static const uint32 _noradAlphaClawExtras[22]; + + virtual void arriveAtNorad01(); + virtual void arriveAtNorad01East(); + virtual void arriveAtNorad01West(); + virtual void arriveAtNorad04(); + virtual void arriveAtNorad22(); + + virtual void arriveAt(const RoomID, const DirectionConstant); + + virtual void getZoomEntry(const HotSpotID, ZoomTable::Entry &); + virtual TimeValue getViewTime(const RoomID, const DirectionConstant); + + virtual void receiveNotification(Notification *, const NotificationFlags); + + virtual void activateHotspots(); + + Hotspot *getItemScreenSpot(Item *, DisplayElement *); + + void bumpIntoWall(); + + Item *_fillingStationItem; + + bool _subPrepFailed; + + Common::String getSoundSpotsName(); + Common::String getNavMovieName(); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/alpha/panorama.cpp b/engines/pegasus/neighborhood/norad/alpha/panorama.cpp new file mode 100644 index 0000000000..ecd428239b --- /dev/null +++ b/engines/pegasus/neighborhood/norad/alpha/panorama.cpp @@ -0,0 +1,239 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/macresman.h" +#include "common/stream.h" +#include "pegasus/neighborhood/norad/alpha/panorama.h" + +namespace Pegasus { + +Panorama::Panorama() : _panoramaMovie(kNoDisplayElement) { + blankFields(); +} + +Panorama::~Panorama() { + releasePanorama(); +} + +void Panorama::blankFields() { + _viewBounds = Common::Rect(); + _drawBounds = Common::Rect(); + _mask = 0; + _panoramaWidth = 0; + _panoramaHeight = 0; + _stripWidth = 0; + _stripLeft = -1; + _stripRight = -1; +} + +void Panorama::releasePanorama() { + if (_panoramaMovie.isMovieValid()) { + _panoramaMovie.releaseMovie(); + _panoramaWorld.deallocateSurface(); + blankFields(); + } +} + +static const uint32 kPanoramaResType = MKTAG('P', 'a', 'n', 'I'); // Panorama Information. +static const uint16 kPanoramaResID = 128; + +void Panorama::initFromMovieFile(const Common::String &fileName) { + // First, we need the resource fork for other reasons -- PanI resource + Common::MacResManager *resFork = new Common::MacResManager(); + if (!resFork->open(fileName) || !resFork->hasResFork()) + error("Could not open the resource fork of '%s'", fileName.c_str()); + + Common::SeekableReadStream *resource = resFork->getResource(kPanoramaResType, kPanoramaResID); + if (!resource) + error("No panorama information in the resource fork of '%s'", fileName.c_str()); + + _panoramaWidth = resource->readUint16BE(); + _panoramaHeight = resource->readUint16BE(); + _stripWidth = resource->readUint16BE(); + + delete resource; + delete resFork; + + // Now we open the movie like normal + _panoramaMovie.initFromMovieFile(fileName); +} + +void Panorama::setMask(Surface *mask) { + _mask = mask; +} + +// If the panorama is not open, do nothing and return. +// Otherwise, set up the view bounds. +void Panorama::setViewBounds(const Common::Rect &newView) { + if (!isPanoramaOpen()) + return; + + if (newView.isEmpty()) + return; + + Common::Rect r = newView; + + if (r.width() > _panoramaWidth) { + r.left = 0; + r.right = _panoramaWidth; + } else { + if (r.right > _panoramaWidth) + r.translate(_panoramaWidth - r.right, 0); + + if (r.left < 0) + r.translate(-r.left, 0); + } + + if (r.height() > _panoramaHeight) { + r.top = 0; + r.bottom = _panoramaHeight; + } else { + if (r.bottom > _panoramaHeight) + r.translate(0, _panoramaHeight - r.bottom); + + if (r.top < 0) + r.translate(0, -r.top); + } + + if (_viewBounds != r) { + CoordType stripLeft = 0; + + if (r.width() != _viewBounds.width() || !_panoramaWorld.isSurfaceValid()) { + _panoramaWorld.deallocateSurface(); + makeNewSurface(r); + } else { + CoordType stripRight; + calcStripRange(r, stripLeft, stripRight); + loadStrips(stripLeft, stripRight); + } + + _viewBounds = r; + _drawBounds = r; + _drawBounds.translate(-stripLeft * _stripWidth, 0); + } +} + +void Panorama::getViewBounds(Common::Rect &r) const { + r = _viewBounds; +} + +void Panorama::getPanoramaBounds(Common::Rect &r) const { + r = Common::Rect(0, 0, _panoramaWidth, _panoramaHeight); +} + +void Panorama::drawPanorama(const Common::Rect &destRect) { + if (_panoramaWorld.isSurfaceValid()) { + if (_mask) + _panoramaWorld.copyToCurrentPortMasked(_drawBounds, destRect, _mask); + else + _panoramaWorld.copyToCurrentPortTransparent(_drawBounds, destRect); + } +} + +// Make a new Surface big enough to show r, which is assumed to be a valid view bounds. +// Assumptions: +// r is a valid view bounds. +// _panoramaWorld is not allocated. +// _panoramaHeight, _stripWidth is correct. +// _panoramaMovie is allocated. +void Panorama::makeNewSurface(const Common::Rect& view) { + CoordType stripLeft, stripRight; + calcStripRange(view, stripLeft, stripRight); + + Common::Rect r(0, 0, (stripRight - stripLeft + 1) * _stripWidth, _panoramaHeight); + _panoramaWorld.allocateSurface(r); + _panoramaMovie.shareSurface(&_panoramaWorld); + loadStrips(stripLeft, stripRight); +} + +// Assumes view is not empty. +void Panorama::calcStripRange(const Common::Rect &view, CoordType &stripLeft, CoordType &stripRight) { + stripLeft = view.left / _stripWidth; + stripRight = (view.left - view.left % _stripWidth + _stripWidth - 1 + view.width()) / _stripWidth; +} + +// Load in all needed strips to put range (stripLeft, stripRight) into the +// panorama's Surface. Try to optimize by saving any pixels already in the Surface. +// Assumptions: +// Surface is allocated and is big enough for maximum range of +// stripLeft and stripRight +void Panorama::loadStrips(CoordType stripLeft, CoordType stripRight) { + if (_stripLeft == -1) { + // Surface has just been allocated. + // Load in all strips. + for (CoordType i = stripLeft; i <= stripRight; i++) + loadOneStrip(i, stripLeft); + + _stripLeft = stripLeft; + _stripRight = stripRight; + } else if (stripLeft != _stripLeft) { + CoordType overlapLeft = MAX(stripLeft, _stripLeft); + CoordType overlapRight = MIN(stripRight, _stripRight); + + if (overlapLeft <= overlapRight) { + Common::Rect r1((overlapLeft - _stripLeft) * _stripWidth, 0, + (overlapRight - _stripLeft + 1) * _stripWidth, _panoramaHeight); + + if (stripLeft < _stripLeft) { + Common::Rect bounds; + _panoramaWorld.getSurfaceBounds(bounds); + _panoramaWorld.getSurface()->move(bounds.right - r1.right, 0, _panoramaHeight); + + for (CoordType i = stripLeft; i < _stripLeft; i++) + loadOneStrip(i, stripLeft); + } else { + _panoramaWorld.getSurface()->move(-r1.left, 0, _panoramaHeight); + + for (CoordType i = _stripRight + 1; i <= stripRight; i++) + loadOneStrip(i, stripLeft); + } + } else { + // No overlap. + // Load everything. + for (CoordType i = stripLeft; i <= stripRight; i++) + loadOneStrip(i, stripLeft); + } + + _stripLeft = stripLeft; + _stripRight = stripRight; + } else if (stripRight > _stripRight) { + // Need to add one or more strips. + for (CoordType i = _stripRight + 1; i <= stripRight; i++) + loadOneStrip(i, _stripLeft); + + _stripRight = stripRight; + } else if (stripRight < _stripRight) { + // Need to chop off one strip. + _stripRight = stripRight; + } +} + +void Panorama::loadOneStrip(CoordType stripToLoad, CoordType leftStrip) { + _panoramaMovie.moveMovieBoxTo((stripToLoad - leftStrip) * _stripWidth, 0); + _panoramaMovie.setTime(stripToLoad, 1); + _panoramaMovie.redrawMovieWorld(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/norad/alpha/panorama.h b/engines/pegasus/neighborhood/norad/alpha/panorama.h new file mode 100644 index 0000000000..3ca2c1e305 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/alpha/panorama.h @@ -0,0 +1,98 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_NORAD_ALPHA_PANORAMA_H +#define PEGASUS_NEIGHBORHOOD_NORAD_ALPHA_PANORAMA_H + +#include "pegasus/movie.h" + +namespace Pegasus { + +/* + + Panorama implements a wide image using a specially constructed movie file. + The movie holds the image as a series of vertical strips, say 16 or 32 pixels wide. + + The panorama bounds defines the entire panorama. The view bounds represents the + area on the panorama that is kept in memory. + + The panorama bounds is also stored in the movie file; it cannot be changed. The + view bounds must always be a subset of the panorama bounds. + + In actuality, the area kept in memory is at least as wide as the view bounds (but + may be wider to coincide with the width of the movies slices), and is as tall as + the panorama bounds. The view bounds is used by the drawPanorama function to draw + a piece of the panorama to the current screen. + + The panorama movie is built at a time scale of 1, with each strip lasting for one + second, so that strip number corresponds exactly with the time value at which the + strip is stored. + + TO USE: + + Call one initFromMovieFile to open the movie. Then set up a view rect by + calling setViewBounds. Once these two functions have been called, drawPanorama + will draw the panorama. + +*/ + +class Panorama { +public: + Panorama(); + virtual ~Panorama(); + + void initFromMovieFile(const Common::String &); + void releasePanorama(); + bool isPanoramaOpen() { return _panoramaMovie.isMovieValid(); } + + void setViewBounds(const Common::Rect &); + void getViewBounds(Common::Rect &) const; + + void setMask(Surface *); + + void getPanoramaBounds(Common::Rect &) const; + + void drawPanorama(const Common::Rect &); + +protected: + void blankFields(); + void makeNewSurface(const Common::Rect &); + void calcStripRange(const Common::Rect &, CoordType &, CoordType &); + void loadStrips(CoordType, CoordType); + void loadOneStrip(CoordType, CoordType); + + Movie _panoramaMovie; + Surface _panoramaWorld, *_mask; + Common::Rect _viewBounds; + Common::Rect _drawBounds; + CoordType _panoramaWidth, _panoramaHeight; + CoordType _stripWidth; // Pixels per strip. + CoordType _numStrips; + CoordType _stripLeft, _stripRight; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/alpha/panoramascroll.cpp b/engines/pegasus/neighborhood/norad/alpha/panoramascroll.cpp new file mode 100644 index 0000000000..fcb49c52ee --- /dev/null +++ b/engines/pegasus/neighborhood/norad/alpha/panoramascroll.cpp @@ -0,0 +1,91 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/neighborhood/norad/alpha/panoramascroll.h" + +namespace Pegasus { + +PanoramaScroll::PanoramaScroll(const DisplayElementID id) : IdlerAnimation(id) { + _boundsWidth = 0; + _totalWidth = 0; +} + +void PanoramaScroll::initFromMovieFile(const Common::String &fileName) { + _panorama.initFromMovieFile(fileName); + + Common::Rect r; + _panorama.getPanoramaBounds(r); + _totalWidth = r.width(); +} + +void PanoramaScroll::initMaskFromPICTFile(const Common::String &fileName) { + if (!_panorama.isPanoramaOpen()) + return; + + _mask.getImageFromPICTFile(fileName); + _panorama.setMask(&_mask); +} + +void PanoramaScroll::releasePanorama() { + if (_panorama.isPanoramaOpen()) + _panorama.releasePanorama(); + + _mask.deallocateSurface(); +} + +void PanoramaScroll::setBounds(const Common::Rect &r) { + Animation::setBounds(r); + + _boundsWidth = r.width(); + + Common::Rect r2; + _panorama.getViewBounds(r2); + r2.right = r2.left + _boundsWidth; + r2.bottom = r2.top + r.height(); + _panorama.setViewBounds(r2); +} + +void PanoramaScroll::draw(const Common::Rect &) { + _panorama.drawPanorama(_bounds); +} + +void PanoramaScroll::timeChanged(const TimeValue newTime) { + CoordType leftPixel = (_totalWidth - _boundsWidth) * newTime / getDuration(); + + Common::Rect r; + _panorama.getViewBounds(r); + if (leftPixel != r.left) { + _panorama.getViewBounds(r); + r.moveTo(leftPixel, 0); + _panorama.setViewBounds(r); + triggerRedraw(); + } +} + +void PanoramaScroll::getPanoramaBounds(Common::Rect &r) const { + _panorama.getPanoramaBounds(r); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/norad/alpha/panoramascroll.h b/engines/pegasus/neighborhood/norad/alpha/panoramascroll.h new file mode 100644 index 0000000000..6a3e1515e2 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/alpha/panoramascroll.h @@ -0,0 +1,60 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_NORAD_ALPHA_PANORAMASCROLL_H +#define PEGASUS_NEIGHBORHOOD_NORAD_ALPHA_PANORAMASCROLL_H + +#include "pegasus/neighborhood/norad/alpha/panorama.h" + +namespace Pegasus { + +class PanoramaScroll : public IdlerAnimation { +public: + PanoramaScroll(const DisplayElementID); + virtual ~PanoramaScroll() {} + + void initFromMovieFile(const Common::String &); + void initMaskFromPICTFile(const Common::String &); + + void releasePanorama(); + + bool isPanoramaOpen() { return _panorama.isPanoramaOpen(); } + void getPanoramaBounds(Common::Rect &) const; + + void setBounds(const Common::Rect&); + + void draw(const Common::Rect &); + +protected: + void timeChanged(const TimeValue); + + Panorama _panorama; + Surface _mask; + CoordType _totalWidth, _boundsWidth; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/constants.h b/engines/pegasus/neighborhood/norad/constants.h new file mode 100644 index 0000000000..37c1769309 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/constants.h @@ -0,0 +1,755 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_NORAD_CONSTANTS_H +#define PEGASUS_NEIGHBORHOOD_NORAD_CONSTANTS_H + +#include "pegasus/constants.h" + +namespace Pegasus { + +// Norad Alpha spot constants + +static const TimeValue kAlphaBumpIntoWallIn = 0; +static const TimeValue kAlphaBumpIntoWallOut = 303; + +static const TimeValue kAlphaAccessDeniedIn = 303; +static const TimeValue kAlphaAccessDeniedOut = 3045; + +static const TimeValue kAlphaRegDoorCloseIn = 3045; +static const TimeValue kAlphaRegDoorCloseOut = 4476; + +static const TimeValue kAlphaElevatorDoorCloseIn = 4476; +static const TimeValue kAlphaElevatorDoorCloseOut = 5071; + +static const TimeValue kAlphaCantTransportIn = 5071; +static const TimeValue kAlphaCantTransportOut = 9348; + +static const TimeValue kAlphaPressureDoorIntro1In = 9348; +static const TimeValue kAlphaPressureDoorIntro1Out = 11061; + +static const TimeValue kAlphaPressureDoorIntro2In = 11061; +static const TimeValue kAlphaPressureDoorIntro2Out = 14098; + +static const TimeValue kN22ReplyIn = 14098; +static const TimeValue kN22ReplyOut = 18442; + +static const TimeValue kAlphaLoadClawIntroIn = 18442; +static const TimeValue kAlphaLoadClawIntroOut = 20698; + +// Norad Delta spot constants + +static const TimeValue kDeltaBumpIntoWallIn = 0; +static const TimeValue kDeltaBumpIntoWallOut = 303; + +static const TimeValue kDeltaAccessDeniedIn = 303; +static const TimeValue kDeltaAccessDeniedOut = 3045; + +static const TimeValue kDeltaRegDoorCloseIn = 3045; +static const TimeValue kDeltaRegDoorCloseOut = 4476; + +static const TimeValue kDeltaElevatorDoorCloseIn = 4476; +static const TimeValue kDeltaElevatorDoorCloseOut = 5071; + +static const TimeValue kPressureDoorIntro1In = 5071; +static const TimeValue kPressureDoorIntro1Out = 6784; + +static const TimeValue kPressureDoorIntro2In = 6784; +static const TimeValue kPressureDoorIntro2Out = 9821; + +static const TimeValue kLoadClawIntroIn = 9821; +static const TimeValue kLoadClawIntroOut = 12077; + +static const TimeValue kHoldForRetinalIn = 12077; +static const TimeValue kHoldForRetinalOut = 14104; + +static const TimeValue kRetinalScanFailedIn = 14104; +static const TimeValue kRetinalScanFailedOut = 17538; + +static const TimeValue kAddisAbabaIn = 17538; +static const TimeValue kAddisAbabaOut = 19263; + +static const TimeValue kBangkokIn = 19263; +static const TimeValue kBangkokOut = 20201; + +static const TimeValue kBonnIn = 20201; +static const TimeValue kBonnOut = 20915; + +static const TimeValue kDublinIn = 20915; +static const TimeValue kDublinOut = 21660; + +static const TimeValue kHonoluluIn = 21660; +static const TimeValue kHonoluluOut = 22498; + +static const TimeValue kMadridIn = 22498; +static const TimeValue kMadridOut = 23474; + +static const TimeValue kReykjavikIn = 23474; +static const TimeValue kReykjavikOut = 24488; + +static const TimeValue kSanAntonioIn = 24488; +static const TimeValue kSanAntonioOut = 25561; + +static const TimeValue kSeoulIn = 25561; +static const TimeValue kSeoulOut = 26461; + +static const TimeValue kSvortalskIn = 26461; +static const TimeValue kSvortalskOut = 27582; + +static const TimeValue kSiloBeepIn = 27582; +static const TimeValue kSiloBeepOut = 27721; + +static const TimeValue kAllSilosDeactivatedIn = 27721; +static const TimeValue kAllSilosDeactivatedOut = 28928; + +static const TimeValue kGlobalLaunchOverrideIn = 28928; +static const TimeValue kGlobalLaunchOverrideOut = 30736; + +static const TimeValue kLaunchSiloSelectedIn = 30736; +static const TimeValue kLaunchSiloSelectedOut = 31660; + +static const TimeValue kLaunchToProceedIn = 31660; +static const TimeValue kLaunchToProceedOut = 32536; + +static const TimeValue kMaximumDeactivationIn = 32536; +static const TimeValue kMaximumDeactivationOut = 34337; + +static const TimeValue kMissileLaunchedIn = 34337; +static const TimeValue kMissileLaunchedOut = 35082; + +static const TimeValue kNewLaunchSiloIn = 35082; +static const TimeValue kNewLaunchSiloOut = 36320; + +static const TimeValue kStrikeAuthorizedIn = 36320; +static const TimeValue kStrikeAuthorizedOut = 37393; + +static const TimeValue kPrimaryTargetIn = 37393; +static const TimeValue kPrimaryTargetOut = 38628; + +static const TimeValue kSiloDeactivatedIn = 38628; +static const TimeValue kSiloDeactivatedOut = 39566; + +static const TimeValue kStrikeCodeRejectedIn = 39566; +static const TimeValue kStrikeCodeRejectedOut = 41056; + +static const TimeValue kToDeactivateIn = 41056; +static const TimeValue kToDeactivateOut = 46494; + +static const TimeValue kTwoMinutesIn = 46494; +static const TimeValue kTwoMinutesOut = 47166; + +static const TimeValue kOneMinuteIn = 47166; +static const TimeValue kOneMinuteOut = 47856; + +static const TimeValue kFiftySecondsIn = 47856; +static const TimeValue kFiftySecondsOut = 48691; + +static const TimeValue kFortySecondsIn = 48691; +static const TimeValue kFortySecondsOut = 49500; + +static const TimeValue kThirtySecondsIn = 49500; +static const TimeValue kThirtySecondsOut = 50362; + +static const TimeValue kTwentySecondsIn = 50362; +static const TimeValue kTwentySecondsOut = 51245; + +static const TimeValue kTenSecondsIn = 51245; +static const TimeValue kTenSecondsOut = 52069; + +static const TimeValue kGiveUpHumanIn = 52069; +static const TimeValue kGiveUpHumanOut = 55023; + +static const TimeValue kIJustBrokeIn = 55023; +static const TimeValue kIJustBrokeOut = 59191; + +static const TimeValue kTheOnlyGoodHumanIn = 59191; +static const TimeValue kTheOnlyGoodHumanOut = 62379; + +static const TimeValue kYouAreRunningIn = 62379; +static const TimeValue kYouAreRunningOut = 64201; + +static const TimeValue kYouCannotPossiblyIn = 64201; +static const TimeValue kYouCannotPossiblyOut = 65740; + +static const TimeValue kYouWillFailIn = 65740; +static const TimeValue kYouWillFailOut = 67217; + +static const CanOpenDoorReason kCantOpenBadPressure = kCantOpenLastReason + 1; + +static const NotificationFlags kAirTimerExpiredFlag = kLastNeighborhoodNotificationFlag << 1; + +static const uint16 kNoradWarningVolume = 0x100 / 3; +static const uint16 kNoradSuckWindVolume = 0x100 / 2; + +static const int16 kElevatorCompassAngle = -40; +static const int16 kSubPlatformCompassAngle = 45; +static const int16 kSubControlCompassAngle = -10; + +// Norad interactions. + +static const InteractionID kNoradGlobeGameInteractionID = 0; +static const InteractionID kNoradECRMonitorInteractionID = 1; +static const InteractionID kNoradFillingStationInteractionID = 2; +static const InteractionID kNoradElevatorInteractionID = 3; +static const InteractionID kNoradPressureDoorInteractionID = 4; +static const InteractionID kNoradSubControlRoomInteractionID = 5; +static const InteractionID kNoradSubPlatformInteractionID = 6; + +///////////////////////////////////////////// +// +// Norad Alpha + +static const CoordType kECRSlideShowLeft = kNavAreaLeft + 78; +static const CoordType kECRSlideShowTop = kNavAreaTop + 1; + +static const CoordType kECRPanLeft = kNavAreaLeft + 78 + 5; +static const CoordType kECRPanTop = kNavAreaTop + 1 + 4; +static const CoordType kECRPanRight = kECRPanLeft + 213; +static const CoordType kECRPanBottom = kECRPanTop + 241; + +static const CoordType kNoradAlphaElevatorControlsLeft = kNavAreaLeft + 332; +static const CoordType kNoradAlphaElevatorControlsTop = kNavAreaTop + 127; + +static const CoordType kNoradAlpha01LeftSideLeft = kNavAreaLeft + 0; +static const CoordType kNoradAlpha01LeftSideTop = kNavAreaTop + 0; + +static const CoordType kNoradAlpha01RightSideLeft = kNavAreaLeft + 240; +static const CoordType kNoradAlpha01RightSideTop = kNavAreaTop + 12; + +static const CoordType kNoradUpperLevelsLeft = kNavAreaLeft + 98; +static const CoordType kNoradUpperLevelsTop = kNavAreaTop + 31; + +static const CoordType kNoradUpperTypeLeft = kNoradUpperLevelsLeft + 114; +static const CoordType kNoradUpperTypeTop = kNoradUpperLevelsTop + 8; + +static const CoordType kNoradUpperUpLeft = kNavAreaLeft + 361; +static const CoordType kNoradUpperUpTop = kNavAreaTop + 32; + +static const CoordType kNoradUpperDownLeft = kNavAreaLeft + 367; +static const CoordType kNoradUpperDownTop = kNavAreaTop + 66; + +static const CoordType kNoradLowerLevelsLeft = kNavAreaLeft + 74; +static const CoordType kNoradLowerLevelsTop = kNavAreaTop + 157; + +static const CoordType kNoradLowerTypeLeft = kNoradLowerLevelsLeft + 144; +static const CoordType kNoradLowerTypeTop = kNoradLowerLevelsTop + 9; + +static const CoordType kNoradLowerUpLeft = kNavAreaLeft + 380; +static const CoordType kNoradLowerUpTop = kNavAreaTop + 164; + +static const CoordType kNoradLowerDownLeft = kNavAreaLeft + 388; +static const CoordType kNoradLowerDownTop = kNavAreaTop + 212; + +static const CoordType kNoradPlatformLeft = kNavAreaLeft + 36; +static const CoordType kNoradPlatformTop = kNavAreaTop + 87; + +static const CoordType kNoradSubControlLeft = kNavAreaLeft + 0; +static const CoordType kNoradSubControlTop = kNavAreaTop + 84; + +static const CoordType kNoradSubControlPinchLeft = kNoradSubControlLeft + 106; +static const CoordType kNoradSubControlPinchTop = kNoradSubControlTop + 86; + +static const CoordType kNoradSubControlDownLeft = kNoradSubControlLeft + 66; +static const CoordType kNoradSubControlDownTop = kNoradSubControlTop + 106; + +static const CoordType kNoradSubControlRightLeft = kNoradSubControlLeft + 83; +static const CoordType kNoradSubControlRightTop = kNoradSubControlTop + 90; + +static const CoordType kNoradSubControlLeftLeft = kNoradSubControlLeft + 56; +static const CoordType kNoradSubControlLeftTop = kNoradSubControlTop + 91; + +static const CoordType kNoradSubControlUpLeft = kNoradSubControlLeft + 66; +static const CoordType kNoradSubControlUpTop = kNoradSubControlTop + 81; + +static const CoordType kNoradSubControlCCWLeft = kNoradSubControlLeft + 29; +static const CoordType kNoradSubControlCCWTop = kNoradSubControlTop + 88; + +static const CoordType kNoradSubControlCWLeft = kNoradSubControlLeft + 0; +static const CoordType kNoradSubControlCWTop = kNoradSubControlTop + 89; + +static const CoordType kNoradClawMonitorLeft = kNavAreaLeft + 288; +static const CoordType kNoradClawMonitorTop = kNavAreaTop + 97; + +static const CoordType kNoradGreenBallAtALeft = kNoradClawMonitorLeft + 179; +static const CoordType kNoradGreenBallAtATop = kNoradClawMonitorTop + 82; + +static const CoordType kNoradGreenBallAtBLeft = kNoradClawMonitorLeft + 130; +static const CoordType kNoradGreenBallAtBTop = kNoradClawMonitorTop + 73; + +static const CoordType kNoradGreenBallAtCLeft = kNoradClawMonitorLeft + 110; +static const CoordType kNoradGreenBallAtCTop = kNoradClawMonitorTop + 26; + +static const CoordType kNoradGreenBallAtDLeft = kNoradClawMonitorLeft + 21; +static const CoordType kNoradGreenBallAtDTop = kNoradClawMonitorTop + 49; + +///////////////////////////////////////////// +// +// Norad Delta + +static const CoordType kGlobeMonitorLeft = kNavAreaLeft + 360; +static const CoordType kGlobeMonitorTop = kNavAreaTop + 144; + +static const CoordType kGlobeLeft = kNavAreaLeft + 172; +static const CoordType kGlobeTop = kNavAreaTop; + +static const CoordType kGlobeCircleLeftLeft = kNavAreaLeft + 186; +static const CoordType kGlobeCircleLeftTop = kNavAreaTop + 41; + +static const CoordType kGlobeCircleRightLeft = kNavAreaLeft + 321; +static const CoordType kGlobeCircleRightTop = kNavAreaTop + 41; + +static const CoordType kGlobeCircleUpLeft = kNavAreaLeft + 220; +static const CoordType kGlobeCircleUpTop = kNavAreaTop + 7; + +static const CoordType kGlobeCircleDownLeft = kNavAreaLeft + 220; +static const CoordType kGlobeCircleDownTop = kNavAreaTop + 142; + +static const CoordType kGlobeUpperLeftHiliteLeft = kNavAreaLeft + 207; +static const CoordType kGlobeUpperLeftHiliteTop = kNavAreaTop + 28; + +static const CoordType kGlobeUpperRightHiliteLeft = kNavAreaLeft + 307; +static const CoordType kGlobeUpperRightHiliteTop = kNavAreaTop + 28; + +static const CoordType kGlobeLowerLeftHiliteLeft = kNavAreaLeft + 207; +static const CoordType kGlobeLowerLeftHiliteTop = kNavAreaTop + 128; + +static const CoordType kGlobeLowerRightHiliteLeft = kNavAreaLeft + 307; +static const CoordType kGlobeLowerRightHiliteTop = kNavAreaTop + 128; + +static const CoordType kGlobeLeftMotionHiliteLeft = kNavAreaLeft + 182; +static const CoordType kGlobeLeftMotionHiliteTop = kNavAreaTop + 60; + +static const CoordType kGlobeRightMotionHiliteLeft = kNavAreaLeft + 331; +static const CoordType kGlobeRightMotionHiliteTop = kNavAreaTop + 60; + +static const CoordType kGlobeUpMotionHiliteLeft = kNavAreaLeft + 239; +static const CoordType kGlobeUpMotionHiliteTop = kNavAreaTop + 3; + +static const CoordType kGlobeDownMotionHiliteLeft = kNavAreaLeft + 239; +static const CoordType kGlobeDownMotionHiliteTop = kNavAreaTop + 152; + +static const CoordType kGlobeUpperNamesLeft = kNavAreaLeft + 368; +static const CoordType kGlobeUpperNamesTop = kNavAreaTop + 188; + +static const CoordType kGlobeLowerNamesLeft = kNavAreaLeft + 368; +static const CoordType kGlobeLowerNamesTop = kNavAreaTop + 212; + +static const CoordType kGlobeCountdownLeft = kNavAreaLeft + 478; +static const CoordType kGlobeCountdownTop = kNavAreaTop + 164; + +// Norad Alpha display IDs. + +static const DisplayElementID kECRSlideShowMovieID = kNeighborhoodDisplayID; +static const DisplayElementID kECRPanID = kECRSlideShowMovieID + 1; +static const DisplayElementID kNoradAlphaDeathMovieID = kECRPanID + 1; +static const DisplayElementID kNoradElevatorControlsID = kNoradAlphaDeathMovieID + 1; +static const DisplayElementID kN01LeftSideID = kNoradElevatorControlsID + 1; +static const DisplayElementID kN01RightSideID = kN01LeftSideID + 1; +static const DisplayElementID kPressureDoorLevelsID = kN01RightSideID + 1; +static const DisplayElementID kPressureDoorTypeID = kPressureDoorLevelsID + 1; +static const DisplayElementID kPressureDoorUpButtonID = kPressureDoorTypeID + 1; +static const DisplayElementID kPressureDoorDownButtonID = kPressureDoorUpButtonID + 1; +static const DisplayElementID kPlatformMonitorID = kPressureDoorDownButtonID + 1; +static const DisplayElementID kSubControlMonitorID = kPlatformMonitorID + 1; +static const DisplayElementID kClawMonitorID = kSubControlMonitorID + 1; +static const DisplayElementID kSubControlPinchID = kClawMonitorID + 1; +static const DisplayElementID kSubControlDownID = kSubControlPinchID + 1; +static const DisplayElementID kSubControlRightID = kSubControlDownID + 1; +static const DisplayElementID kSubControlLeftID = kSubControlRightID + 1; +static const DisplayElementID kSubControlUpID = kSubControlLeftID + 1; +static const DisplayElementID kSubControlCCWID = kSubControlUpID + 1; +static const DisplayElementID kSubControlCWID = kSubControlCCWID + 1; +static const DisplayElementID kClawMonitorGreenBallID = kSubControlCWID + 1; + +// Norad Delta display IDs. + +static const DisplayElementID kGlobeMonitorID = kNeighborhoodDisplayID; +static const DisplayElementID kGlobeMovieID = kGlobeMonitorID + 14; +static const DisplayElementID kGlobeCircleLeftID = kGlobeMovieID + 1; +static const DisplayElementID kGlobeCircleRightID = kGlobeCircleLeftID + 1; +static const DisplayElementID kGlobeCircleUpID = kGlobeCircleRightID + 1; +static const DisplayElementID kGlobeCircleDownID = kGlobeCircleUpID + 1; +static const DisplayElementID kMotionHiliteLeftID = kGlobeCircleDownID + 1; +static const DisplayElementID kMotionHiliteRightID = kMotionHiliteLeftID + 1; +static const DisplayElementID kMotionHiliteUpID = kMotionHiliteRightID + 1; +static const DisplayElementID kMotionHiliteDownID = kMotionHiliteUpID + 1; +static const DisplayElementID kTargetHiliteUpperLeftID = kMotionHiliteDownID + 1; +static const DisplayElementID kTargetHiliteUpperRightID = kTargetHiliteUpperLeftID + 1; +static const DisplayElementID kTargetHiliteLowerLeftID = kTargetHiliteUpperRightID + 1; +static const DisplayElementID kTargetHiliteLowerRightID = kTargetHiliteLowerLeftID + 1; +static const DisplayElementID kGlobeUpperNamesID = kTargetHiliteLowerRightID + 1; +static const DisplayElementID kGlobeLowerNamesID = kGlobeUpperNamesID + 1; +static const DisplayElementID kGlobeCountdownID = kGlobeLowerNamesID + 1; + +// Norad Alpha: + +static const DisplayOrder kECRMonitorOrder = kMonitorLayer; +static const DisplayOrder kECRPanOrder = kECRMonitorOrder + 1; + +static const DisplayOrder kN01LeftSideOrder = kMonitorLayer; +static const DisplayOrder kN01RightSideOrder = kN01LeftSideOrder + 1; + +static const DisplayOrder kElevatorControlsOrder = kMonitorLayer; + +static const DisplayOrder kPressureLevelsOrder = kMonitorLayer; +static const DisplayOrder kPressureTypeOrder = kPressureLevelsOrder + 1; +static const DisplayOrder kPressureUpOrder = kPressureTypeOrder + 1; +static const DisplayOrder kPressureDownOrder = kPressureUpOrder + 1; + +static const DisplayOrder kPlatformOrder = kMonitorLayer; + +static const DisplayOrder kSubControlOrder = kMonitorLayer; +static const DisplayOrder kClawMonitorOrder = kSubControlOrder + 1; +static const DisplayOrder kSubControlPinchOrder = kClawMonitorOrder + 1; +static const DisplayOrder kSubControlDownOrder = kSubControlPinchOrder + 1; +static const DisplayOrder kSubControlRightOrder = kSubControlDownOrder + 1; +static const DisplayOrder kSubControlLeftOrder = kSubControlRightOrder + 1; +static const DisplayOrder kSubControlUpOrder = kSubControlLeftOrder + 1; +static const DisplayOrder kSubControlCCWOrder = kSubControlUpOrder + 1; +static const DisplayOrder kSubControlCWOrder = kSubControlCCWOrder + 1; +static const DisplayOrder kClawMonitorGreenBallOrder = kSubControlCWOrder + 1; + +// Norad Delta: + +static const DisplayOrder kGlobeMonitorLayer = kMonitorLayer; +static const DisplayOrder kGlobeMovieLayer = kGlobeMonitorLayer + 1; +static const DisplayOrder kGlobeCircleLayer = kGlobeMovieLayer + 1; +static const DisplayOrder kGlobeHilitesLayer = kGlobeCircleLayer + 1; +static const DisplayOrder kGlobeUpperNamesLayer = kGlobeHilitesLayer + 1; +static const DisplayOrder kGlobeLowerNamesLayer = kGlobeUpperNamesLayer + 1; +static const DisplayOrder kGlobeCountdownLayer = kGlobeLowerNamesLayer + 1; + +// Norad Alpha Tables + +static const TimeScale kNoradAlphaMovieScale = 600; +static const TimeScale kNoradAlphaFramesPerSecond = 15; +static const TimeScale kNoradAlphaFrameDuration = 40; + +// Alternate IDs. + +static const AlternateID kAltNoradAlphaNormal = 0; + +// Room IDs. + +static const RoomID kNorad01 = 0; +static const RoomID kNorad01East = 1; +static const RoomID kNorad01West = 2; +static const RoomID kNorad02 = 3; +static const RoomID kNorad03 = 4; +static const RoomID kNorad04 = 5; +static const RoomID kNorad05 = 6; +static const RoomID kNorad06 = 7; +static const RoomID kNorad07 = 8; +static const RoomID kNorad07North = 9; +static const RoomID kNorad08 = 10; +static const RoomID kNorad09 = 11; +static const RoomID kNorad10 = 12; +static const RoomID kNorad10East = 13; +static const RoomID kNorad11 = 14; +static const RoomID kNorad11South = 15; +static const RoomID kNorad12 = 16; +static const RoomID kNorad12South = 17; +static const RoomID kNorad13 = 18; +static const RoomID kNorad14 = 19; +static const RoomID kNorad15 = 20; +static const RoomID kNorad16 = 21; +static const RoomID kNorad17 = 22; +static const RoomID kNorad18 = 23; +static const RoomID kNorad19 = 24; +static const RoomID kNorad19West = 25; +static const RoomID kNorad21 = 26; +static const RoomID kNorad21West = 27; +static const RoomID kNorad22 = 28; +static const RoomID kNorad22West = 29; + +// Hot Spot Activation IDs. + + +// Hot Spot IDs. + +static const HotSpotID kNorad01ECRSpotID = 5000; +static const HotSpotID kNorad01GasSpotID = 5001; +static const HotSpotID kNorad01ECROutSpotID = 5002; +static const HotSpotID kNorad01GasOutSpotID = 5003; +static const HotSpotID kNorad01MonitorSpotID = 5004; +static const HotSpotID kNorad01IntakeSpotID = 5005; +static const HotSpotID kNorad01DispenseSpotID = 5006; +static const HotSpotID kNorad01ArSpotID = 5007; +static const HotSpotID kNorad01CO2SpotID = 5008; +static const HotSpotID kNorad01HeSpotID = 5009; +static const HotSpotID kNorad01OSpotID = 5010; +static const HotSpotID kNorad01NSpotID = 5011; +static const HotSpotID kN01GasCanisterSpotID = 5012; +static const HotSpotID kN01ArgonCanisterSpotID = 5013; +static const HotSpotID kN01AirMaskSpotID = 5014; +static const HotSpotID kN01NitrogenCanisterSpotID = 5015; +static const HotSpotID kN01GasOutletSpotID = 5016; +static const HotSpotID kNorad07DoorSpotID = 5017; +static const HotSpotID kNorad07DoorOutSpotID = 5018; +static const HotSpotID kNorad10DoorSpotID = 5019; +static const HotSpotID kNorad10EastOutSpotID = 5020; +static const HotSpotID kAlphaUpperPressureDoorUpSpotID = 5021; +static const HotSpotID kAlphaUpperPressureDoorDownSpotID = 5022; +static const HotSpotID kNorad11ElevatorSpotID = 5023; +static const HotSpotID kNorad11ElevatorOutSpotID = 5024; +static const HotSpotID kNorad11ElevatorDownSpotID = 5025; +static const HotSpotID kNorad12ElevatorSpotID = 5026; +static const HotSpotID kNorad12ElevatorOutSpotID = 5027; +static const HotSpotID kNorad12ElevatorUpSpotID = 5028; +static const HotSpotID kNorad19MonitorSpotID = 5029; +static const HotSpotID kNorad19MonitorOutSpotID = 5030; +static const HotSpotID kNorad19ActivateMonitorSpotID = 5031; +static const HotSpotID kNorad21WestSpotID = 5032; +static const HotSpotID kNorad21WestOutSpotID = 5033; +static const HotSpotID kAlphaLowerPressureDoorUpSpotID = 5034; +static const HotSpotID kAlphaLowerPressureDoorDownSpotID = 5035; +static const HotSpotID kNorad22MonitorSpotID = 5036; +static const HotSpotID kNorad22MonitorOutSpotID = 5037; +static const HotSpotID kNorad22LaunchPrepSpotID = 5038; +static const HotSpotID kNorad22ClawControlSpotID = 5039; +static const HotSpotID kNorad22ClawPinchSpotID = 5040; +static const HotSpotID kNorad22ClawDownSpotID = 5041; +static const HotSpotID kNorad22ClawRightSpotID = 5042; +static const HotSpotID kNorad22ClawLeftSpotID = 5043; +static const HotSpotID kNorad22ClawUpSpotID = 5044; +static const HotSpotID kNorad22ClawCCWSpotID = 5045; +static const HotSpotID kNorad22ClawCWSpotID = 5046; + +// Extra sequence IDs. + +static const ExtraID kNoradArriveFromTSA = 0; +static const ExtraID kNorad01RobotTaunt = 1; +static const ExtraID kNorad01ZoomInWithGasCanister = 2; +static const ExtraID kN01WGasCanister = 3; +static const ExtraID kNorad01ZoomOutWithGasCanister = 4; +static const ExtraID kN01WZEmptyLit = 5; +static const ExtraID kN01WZGasCanisterDim = 6; +static const ExtraID kN01WZGasCanisterLit = 7; +static const ExtraID kN01WZArgonCanisterDim = 8; +static const ExtraID kN01WZArgonCanisterLit = 9; +static const ExtraID kN01WZAirMaskDim = 10; +static const ExtraID kN01WZAirMaskLit = 11; +static const ExtraID kN01WZNitrogenCanisterDim = 12; +static const ExtraID kN01WZNitrogenCanisterLit = 13; +static const ExtraID kNorad04EastDeath = 14; +static const ExtraID kNorad19PrepSub = 15; +static const ExtraID kNorad19ExitToSub = 16; +static const ExtraID kNorad22SouthIntro = 17; +static const ExtraID kNorad22SouthReply = 18; +static const ExtraID kNorad22SouthFinish = 19; +static const ExtraID kN22ClawFromAToB = 20; +static const ExtraID kN22ClawALoop = 21; +static const ExtraID kN22ClawAPinch = 22; +static const ExtraID kN22ClawACounterclockwise = 23; +static const ExtraID kN22ClawAClockwise = 24; +static const ExtraID kN22ClawFromBToA = 25; +static const ExtraID kN22ClawFromBToC = 26; +static const ExtraID kN22ClawFromBToD = 27; +static const ExtraID kN22ClawBLoop = 28; +static const ExtraID kN22ClawBPinch = 29; +static const ExtraID kN22ClawBCounterclockwise = 30; +static const ExtraID kN22ClawBClockwise = 31; +static const ExtraID kN22ClawFromCToB = 32; +static const ExtraID kN22ClawCLoop = 33; +static const ExtraID kN22ClawCPinch = 34; +static const ExtraID kN22ClawCCounterclockwise = 35; +static const ExtraID kN22ClawCClockwise = 36; +static const ExtraID kN22ClawFromDToB = 37; +static const ExtraID kN22ClawDLoop = 38; +static const ExtraID kN22ClawDPinch = 39; +static const ExtraID kN22ClawDCounterclockwise = 40; +static const ExtraID kN22ClawDClockwise = 41; + + +// Norad Delta Extra sequence IDs. + +static const ExtraID kArriveFromSubChase = 0; +static const ExtraID kN59ZoomWithRobot = 1; +static const ExtraID kN59RobotApproaches = 2; +static const ExtraID kN59RobotPunchLoop = 3; +static const ExtraID kN59PlayerWins1 = 4; +static const ExtraID kN59PlayerWins2 = 5; +static const ExtraID kN59RobotWins = 6; +static const ExtraID kN59RobotHeadOpens = 7; +static const ExtraID kN59Biochips111 = 8; +static const ExtraID kN59Biochips011 = 9; +static const ExtraID kN59Biochips101 = 10; +static const ExtraID kN59Biochips001 = 11; +static const ExtraID kN59Biochips110 = 12; +static const ExtraID kN59Biochips010 = 13; +static const ExtraID kN59Biochips100 = 14; +static const ExtraID kN59Biochips000 = 15; +static const ExtraID kN59RobotDisappears = 16; +static const ExtraID kN60ClawFromAToB = 17; +static const ExtraID kN60ClawALoop = 18; +static const ExtraID kN60ClawAPinch = 19; +static const ExtraID kN60ClawACounterclockwise = 20; +static const ExtraID kN60ClawAClockwise = 21; +static const ExtraID kN60ClawFromBToA = 22; +static const ExtraID kN60ClawFromBToC = 23; +static const ExtraID kN60ClawFromBToD = 24; +static const ExtraID kN60ClawBLoop = 25; +static const ExtraID kN60ClawBPinch = 26; +static const ExtraID kN60ClawBCounterclockwise = 27; +static const ExtraID kN60ClawBClockwise = 28; +static const ExtraID kN60ClawFromCToB = 29; +static const ExtraID kN60ClawCLoop = 30; +static const ExtraID kN60ClawCPinch = 31; +static const ExtraID kN60ClawCCounterclockwise = 32; +static const ExtraID kN60ClawCClockwise = 33; +static const ExtraID kN60ClawFromDToB = 34; +static const ExtraID kN60ClawDLoop = 35; +static const ExtraID kN60ClawDPinch = 36; +static const ExtraID kN60ClawDCounterclockwise = 37; +static const ExtraID kN60ClawDClockwise = 38; +static const ExtraID kN60RobotApproaches = 39; +static const ExtraID kN60FirstMistake = 40; +static const ExtraID kN60ArmActivated = 41; +static const ExtraID kN60SecondMistake = 42; +static const ExtraID kN60ArmToPositionB = 43; +static const ExtraID kN60ThirdMistake = 44; +static const ExtraID kN60ArmGrabsRobot = 45; +static const ExtraID kN60FourthMistake = 46; +static const ExtraID kN60ArmCarriesRobotToPositionA = 47; +static const ExtraID kN60PlayerFollowsRobotToDoor = 48; +static const ExtraID kN60RobotHeadOpens = 49; +static const ExtraID kN60Biochips111 = 50; +static const ExtraID kN60Biochips011 = 51; +static const ExtraID kN60Biochips101 = 52; +static const ExtraID kN60Biochips001 = 53; +static const ExtraID kN60Biochips110 = 54; +static const ExtraID kN60Biochips010 = 55; +static const ExtraID kN60Biochips100 = 56; +static const ExtraID kN60Biochips000 = 57; +static const ExtraID kN60RobotDisappears = 58; +static const ExtraID kNoradDeltaRetinalScanBad = 59; +static const ExtraID kNoradDeltaRetinalScanGood = 60; +static const ExtraID kN79BrightView = 61; + +// Norad Delta Tables + +static const TimeScale kNoradDeltaMovieScale = 600; +static const TimeScale kNoradDeltaFramesPerSecond = 15; +static const TimeScale kNoradDeltaFrameDuration = 40; + +// Alternate IDs. + +static const AlternateID kAltNoradDeltaNormal = 0; + +// Room IDs. + +static const RoomID kNorad41 = 0; +static const RoomID kNorad42 = 1; +static const RoomID kNorad43 = 2; +static const RoomID kNorad44 = 3; +static const RoomID kNorad45 = 4; +static const RoomID kNorad46 = 5; +static const RoomID kNorad47 = 6; +static const RoomID kNorad48 = 7; +static const RoomID kNorad48South = 8; +static const RoomID kNorad49 = 9; +static const RoomID kNorad49South = 10; +static const RoomID kNorad50 = 11; +static const RoomID kNorad50East = 12; +static const RoomID kNorad51 = 13; +static const RoomID kNorad52 = 14; +static const RoomID kNorad53 = 15; +static const RoomID kNorad54 = 16; +static const RoomID kNorad54North = 17; +static const RoomID kNorad55 = 18; +static const RoomID kNorad56 = 19; +static const RoomID kNorad57 = 20; +static const RoomID kNorad58 = 21; +static const RoomID kNorad59 = 22; +static const RoomID kNorad59West = 23; +static const RoomID kNorad60 = 24; +static const RoomID kNorad60West = 25; +static const RoomID kNorad61 = 26; +static const RoomID kNorad62 = 27; +static const RoomID kNorad63 = 28; +static const RoomID kNorad64 = 29; +static const RoomID kNorad65 = 30; +static const RoomID kNorad66 = 31; +static const RoomID kNorad67 = 32; +static const RoomID kNorad68 = 33; +static const RoomID kNorad68West = 34; +static const RoomID kNorad69 = 35; +static const RoomID kNorad78 = 36; +static const RoomID kNorad79 = 37; +static const RoomID kNorad79West = 38; + +// Hot Spot Activation IDs. + + +// Hot Spot IDs. + +static const HotSpotID kNorad48ElevatorSpotID = 5000; +static const HotSpotID kNorad48ElevatorOutSpotID = 5001; +static const HotSpotID kNorad48ElevatorUpSpotID = 5002; +static const HotSpotID kNorad49ElevatorSpotID = 5003; +static const HotSpotID kNorad49ElevatorOutSpotID = 5004; +static const HotSpotID kNorad49ElevatorDownSpotID = 5005; +static const HotSpotID kNorad50DoorSpotID = 5006; +static const HotSpotID kNorad50DoorOutSpotID = 5007; +static const HotSpotID kDeltaUpperPressureDoorUpSpotID = 5008; +static const HotSpotID kDeltaUpperPressureDoorDownSpotID = 5009; +static const HotSpotID kNorad54DoorSpotID = 5010; +static const HotSpotID kNorad54DoorOutSpotID = 5011; +static const HotSpotID kNorad59WestSpotID = 5012; +static const HotSpotID kNorad59WestOutSpotID = 5013; +static const HotSpotID kDeltaLowerPressureDoorUpSpotID = 5014; +static const HotSpotID kDeltaLowerPressureDoorDownSpotID = 5015; +static const HotSpotID kDelta59RobotHeadSpotID = 5016; +static const HotSpotID kDelta59RobotShieldBiochipSpotID = 5017; +static const HotSpotID kDelta59RobotOpMemBiochipSpotID = 5018; +static const HotSpotID kDelta59RobotRetinalBiochipSpotID = 5019; +static const HotSpotID kNorad60MonitorSpotID = 5020; +static const HotSpotID kNorad60MonitorOutSpotID = 5021; +static const HotSpotID kNorad60LaunchPrepSpotID = 5022; +static const HotSpotID kNorad60ClawControlSpotID = 5023; +static const HotSpotID kNorad60ClawPinchSpotID = 5024; +static const HotSpotID kNorad60ClawDownSpotID = 5025; +static const HotSpotID kNorad60ClawRightSpotID = 5026; +static const HotSpotID kNorad60ClawLeftSpotID = 5027; +static const HotSpotID kNorad60ClawUpSpotID = 5028; +static const HotSpotID kNorad60ClawCCWSpotID = 5029; +static const HotSpotID kNorad60ClawCWSpotID = 5030; +static const HotSpotID kDelta60RobotHeadSpotID = 5031; +static const HotSpotID kDelta60RobotShieldBiochipSpotID = 5032; +static const HotSpotID kDelta60RobotOpMemBiochipSpotID = 5033; +static const HotSpotID kDelta60RobotRetinalBiochipSpotID = 5034; +static const HotSpotID kNorad68WestSpotID = 5035; +static const HotSpotID kNorad68WestOutSpotID = 5036; +static const HotSpotID kNorad79WestSpotID = 5037; +static const HotSpotID kNorad79WestOutSpotID = 5038; +static const HotSpotID kNorad79SpinLeftSpotID = 5039; +static const HotSpotID kNorad79SpinRightSpotID = 5040; +static const HotSpotID kNorad79SpinUpSpotID = 5041; +static const HotSpotID kNorad79SpinDownSpotID = 5042; +static const HotSpotID kNorad79SiloAreaSpotID = 5043; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/delta/globegame.cpp b/engines/pegasus/neighborhood/norad/delta/globegame.cpp new file mode 100644 index 0000000000..7d4c1c5f8f --- /dev/null +++ b/engines/pegasus/neighborhood/norad/delta/globegame.cpp @@ -0,0 +1,1062 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/cursor.h" +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/norad/delta/globegame.h" +#include "pegasus/neighborhood/norad/delta/noraddelta.h" + +namespace Pegasus { + +static const TimeValue kDurationPerFrame = 600 / 15; +static const TimeValue kDurationPerRow = kNumLongSlices * kDurationPerFrame; +static const short kVerticalDuration = 16; + +GlobeTracker::GlobeTracker(Movie *globeMovie, Picture *leftHighlight, Picture *rightHighlight, + Picture *upHighlight, Picture *downHighlight) { + _globeMovie = globeMovie; + _leftHighlight = leftHighlight; + _rightHighlight = rightHighlight; + _upHighlight = upHighlight; + _downHighlight = downHighlight; +} + +void GlobeTracker::setTrackParameters(const Hotspot *trackSpot, GlobeTrackDirection direction) { + _trackSpot = trackSpot; + _trackDirection = direction; + + TimeValue time, newTime, start; + + switch (_trackDirection) { + case kTrackLeft: + time = _globeMovie->getTime(); + + if (((time / kDurationPerRow) & 1) == 0) { + start = (time / kDurationPerRow + 1) * kDurationPerRow; + newTime = start + kDurationPerRow - time % kDurationPerRow; + } else { + start = (time / kDurationPerRow) * kDurationPerRow; + newTime = time; + } + + _globeMovie->setSegment(start, start + kDurationPerRow); + + if (newTime != time) { + _globeMovie->setTime(newTime); + _globeMovie->redrawMovieWorld(); + } + + _globeMovie->setFlags(kLoopTimeBase); + break; + case kTrackRight: + time = _globeMovie->getTime(); + + if (((time / kDurationPerRow) & 1) == 0) { + start = (time / kDurationPerRow) * kDurationPerRow; + newTime = time; + } else { + start = (time / kDurationPerRow - 1) * kDurationPerRow; + newTime = start + kDurationPerRow - time % kDurationPerRow; + } + + _globeMovie->setSegment(start, start + kDurationPerRow); + + if (newTime != time) { + _globeMovie->setTime(newTime); + _globeMovie->redrawMovieWorld(); + } + + _globeMovie->setFlags(kLoopTimeBase); + break; + case kTrackUp: + case kTrackDown: + _globeMovie->setSegment(0, _globeMovie->getDuration()); + _globeMovie->setFlags(0); + break; + } +} + +void GlobeTracker::activateHotspots() { + Tracker::activateHotspots(); + + if (_trackSpot) + g_allHotspots.activateOneHotspot(_trackSpot->getObjectID()); +} + +bool GlobeTracker::stopTrackingInput(const Input &input) { + return !JMPPPInput::isPressingInput(input); +} + +void GlobeTracker::continueTracking(const Input &input) { + Common::Point where; + input.getInputLocation(where); + + if (g_allHotspots.findHotspot(where) == _trackSpot) + trackGlobeMovie(); + else + stopGlobeMovie(); +} + +void GlobeTracker::startTracking(const Input &input) { + Tracker::startTracking(input); + trackGlobeMovie(); +} + +void GlobeTracker::stopTracking(const Input &input) { + Tracker::stopTracking(input); + stopGlobeMovie(); +} + +void GlobeTracker::trackGlobeMovie() { + TimeValue time; + + switch (_trackDirection) { + case kTrackLeft: + if (!_globeMovie->isRunning()) + _globeMovie->start(); + + _leftHighlight->show(); + break; + case kTrackRight: + if (!_globeMovie->isRunning()) + _globeMovie->start(); + + _rightHighlight->show(); + break; + case kTrackUp: + time = _globeMovie->getTime(); + + if (_trackTime == 0) { + _trackTime = tickCount(); + } else if ((int)time - (int)kDurationPerRow * 2 >= 0 && (int)tickCount() >= _trackTime + kVerticalDuration) { + _trackTime = tickCount(); + _globeMovie->setTime(time - kDurationPerRow * 2); + _globeMovie->redrawMovieWorld(); + } + + _upHighlight->show(); + break; + case kTrackDown: + time = _globeMovie->getTime(); + + if (_trackTime == 0) { + _trackTime = tickCount(); + } else if (time + kDurationPerRow * 2 < _globeMovie->getDuration() && (int)tickCount() >= _trackTime + kVerticalDuration) { + _trackTime = tickCount(); + _globeMovie->setTime(time + kDurationPerRow * 2); + _globeMovie->redrawMovieWorld(); + } + + _downHighlight->show(); + break; + } +} + +void GlobeTracker::stopGlobeMovie() { + switch (_trackDirection) { + case kTrackLeft: + _leftHighlight->hide(); + _globeMovie->stop(); + break; + case kTrackRight: + _rightHighlight->hide(); + _globeMovie->stop(); + break; + case kTrackUp: + _upHighlight->hide(); + _trackTime = tickCount() - kVerticalDuration; + break; + case kTrackDown: + _downHighlight->hide(); + _trackTime = tickCount() - kVerticalDuration; + break; + } +} + +// Globe game PICTs: +static const ResIDType kGlobeCircleLeftPICTID = 300; +static const ResIDType kGlobeCircleRightPICTID = 301; +static const ResIDType kGlobeCircleUpPICTID = 302; +static const ResIDType kGlobeCircleDownPICTID = 303; +static const ResIDType kTargetUpperLeftPICTID = 304; +static const ResIDType kTargetUpperRightPICTID = 305; +static const ResIDType kTargetLowerLeftPICTID = 306; +static const ResIDType kTargetLowerRightPICTID = 307; +static const ResIDType kMotionHiliteLeftPICTID = 308; +static const ResIDType kMotionHiliteRightPICTID = 309; +static const ResIDType kMotionHiliteUpPICTID = 310; +static const ResIDType kMotionHiliteDownPICTID = 311; + +static const ResIDType kGlobeCountdownDigitsID = 350; + +static const int kGlobeCountdownWidth = 28; +static const int kGlobeCountdownHeight = 12; +static const int kGlobeCountdownOffset1 = 12; +static const int kGlobeCountdownOffset2 = 20; + +GlobeCountdown::GlobeCountdown(const DisplayElementID id) : IdlerAnimation(id) { + _digits.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kGlobeCountdownDigitsID); + + Common::Rect r; + _digits.getSurfaceBounds(r); + _digitOffset = r.width() / 10; + setScale(1); + sizeElement(kGlobeCountdownWidth, kGlobeCountdownHeight); +} + +void GlobeCountdown::setDisplayOrder(const DisplayOrder order) { + IdlerAnimation::setDisplayOrder(order); +} + +void GlobeCountdown::show() { + IdlerAnimation::show(); +} + +void GlobeCountdown::hide() { + IdlerAnimation::hide(); +} + +void GlobeCountdown::moveElementTo(const CoordType x, const CoordType y) { + IdlerAnimation::moveElementTo(x, y); +} + +void GlobeCountdown::setCountdownTime(const int numSeconds) { + stop(); + setSegment(0, numSeconds); + setTime(numSeconds); +} + +void GlobeCountdown::startCountdown() { + setRate(-1); +} + +void GlobeCountdown::stopCountdown() { + stop(); +} + +void GlobeCountdown::draw(const Common::Rect &) { + Common::Rect r1; + _digits.getSurfaceBounds(r1); + r1.right = r1.left + _digitOffset; + Common::Rect r2 = r1; + TimeValue time = getTime(); + + Common::Rect bounds; + getBounds(bounds); + + if (time > 60 * 9 + 59) { + r2.moveTo(bounds.left, bounds.top); + r1.moveTo(9 * _digitOffset, 0); + _digits.copyToCurrentPort(r1, r2); + + r2.moveTo(bounds.left + kGlobeCountdownOffset1, bounds.top); + r1.moveTo(5 * _digitOffset, 0); + _digits.copyToCurrentPort(r1, r2); + + r2.moveTo(bounds.left + kGlobeCountdownOffset2, bounds.top); + r1.moveTo(9 * _digitOffset, 0); + _digits.copyToCurrentPort(r1, r2); + } else { + r2.moveTo(bounds.left, bounds.top); + r1.moveTo((time / 60) * _digitOffset, 0); + _digits.copyToCurrentPort(r1, r2); + + time %= 60; + r2.moveTo(bounds.left + kGlobeCountdownOffset1, bounds.top); + r1.moveTo((time / 10) * _digitOffset, 0); + _digits.copyToCurrentPort(r1, r2); + + r2.moveTo(bounds.left + kGlobeCountdownOffset2, bounds.top); + r1.moveTo((time % 10) * _digitOffset, 0); + _digits.copyToCurrentPort(r1, r2); + } +} + +const int16 GlobeGame::_siloCoords[kNumAllSilos][2] = { + { 60, -151 }, // Anchorage, Alaska + { 6, 39 }, // Addis Ababa, Ethiopia + { -22, 44 }, // Antaro, Madagascar + { 30, -83 }, // Atlanta, Georgia + { -41, 173 }, // Auckland, New Zealand + { 39, -78 }, // Baltimore, Maryland + { 11, 101 }, // Bangkok, Thailand + { 2, -75 }, // Bogota, Colombia + { 46, 4 }, // Bonn, Germany + { 51, -7 }, // Dublin, Ireland + { 28, -1 }, // El Menia, Algeria + { 67, -111 }, // Ellesmere, Canada + { 43, -107 }, // Glasgow, Montana + { 61, -48 }, // Godthab, Greenland + { 19, -157 }, // Honolulu, Hawaii + { 6, 5 }, // Ibadan, Nigeria + { -29, 26 }, // Johannesburg, South Africa + { 46, 92 }, // Kobdo, Mongolia + { -15, -63 }, // La Paz, Bolivia + { -35, -61 }, // La Plata, Argentina + { -9, -76 }, // Lima, Peru + { 38, -4 }, // Madrid, Spain + { -8, -51 }, // Manaus, Brazil + { 13, 120 }, // Manila, Phillipines + { -35, 143 }, // Melbourne, Australia + { 60, -161 }, // Nome, Alaska + { -7, 142 }, // Papua, New Guinea + { -32, 117 }, // Perth, West Australia + { 34, -114 }, // Phoenix, Arizona + { 18, -71 }, // Port-Au-Prince, Haiti + { 42, -121 }, // Portland, Oregon + { 61, -20 }, // Reykjavik, Iceland + { -22, -46 }, // Rio de Janeiro + { 27, -101 }, // San Antonio, Texas + { 34, 126 }, // Seoul, Korea + { 37, -87 }, // Saint Louis, Missouri + { 60, 30 }, // Saint Petersberg, Russia + { 56, 12 }, // Stockholm, Sweden + { 51, 105 }, // Svortalsk, Siberia + { 36, -96 } // Tulsa, Oklahoma +}; + +const int16 GlobeGame::_targetSilo[kNumTargetSilos] = { + 14, 9, 1, 33, 6, 8, 34, 31, 38, 21 +}; + +const short GlobeGame::_timeLimit[kNumTargetSilos] = { + 120, 110, 100, 90, 80, 70, 60, 50, 40, 30 +}; + +const TimeValue GlobeGame::_siloName[kNumTargetSilos][2] = { + { kHonoluluIn, kHonoluluOut }, + { kDublinIn, kDublinOut }, + { kAddisAbabaIn, kAddisAbabaOut }, + { kSanAntonioIn, kSanAntonioOut }, + { kBangkokIn, kBangkokOut }, + { kBonnIn, kBonnOut }, + { kSeoulIn, kSeoulOut }, + { kReykjavikIn, kReykjavikOut }, + { kSvortalskIn, kSvortalskOut }, + { kMadridIn, kMadridOut } +}; + +// From globe room models + +static const GlobeGame::Point3D kCameraLocation = { 0.53f, 4.4f, -0.86f }; +static const GlobeGame::Point3D kGlobeCenter = { -31.5f, 8.0f, 0.0f }; +static const float kGlobeRadius = 8.25f; +static const int16 kDegreesPerLongSlice = 360 / kNumLongSlices; +static const int16 kDegreesPerLatSlice = 25; +static const int16 kLongOrigin = -95; + +// Other constants. + +static const float kTanFieldOfView = 0.7082373180482f; +static const float kPicturePlaneDistance = 10.0f; // Completely arbitrary. +static const int16 kLatError = 2; +static const int16 kLongError = 2; +static const TimeValue kGlobeMovieStartTime = 2 * 2 * kNumLongSlices * 600 / 15; + +static const TimeValue kTimePerGlobeFrame = 40; + +static const NotificationFlags kGlobeSplash1Finished = 1; +static const NotificationFlags kGlobeTimerExpired = kGlobeSplash1Finished << 1; +static const NotificationFlags kMaxDeactivatedFinished = kGlobeTimerExpired << 1; + +static const NotificationFlags kGlobeNotificationFlags = kGlobeSplash1Finished | + kGlobeTimerExpired | + kMaxDeactivatedFinished; + +static const int16 kSplash1End = 4; +static const int16 kSplash2End = 5; +static const int16 kSplash3Start = 8; +static const int16 kSplash3Stop = 9; +static const int16 kSplash4Start = 9; +static const int16 kSplash4Stop = 10; +static const int16 kNewLaunchSiloTime = 10; +static const int16 kSiloDeactivatedTime = 11; +static const int16 kMissileLaunchedTime = 12; +static const int16 kMaxDeactivatedStart = 13; +static const int16 kMaxDeactivatedStop = 23; + +static const int16 kGamePlaying = 1; +static const int16 kGameOver = 2; + +enum { + kGameIntro, + kPlayingRobotIntro, + kPlayingStrikeAuthorized, + kPlayingPrimaryTarget, + kPlayingNewSilo1, + kPlayingNewSilo2, + kPlayingNewSilo3, + kPlayingTime, + kPlayingInstructions, + kWaitingForPlayer, + kSiloDeactivated, + kRobotTaunting, + kDelayingPlayer, + kPlayerWon1, + kPlayerWon2, + kPlayerLost1 +}; + +// TODO: Use ScummVM equivalent +static const float kPI = 3.1415926535f; + +float degreesToRadians(float angle) { + return (angle * kPI) / 180; +} + +float radiansToDegrees(float angle) { + return (angle * 180) / kPI; +} + +GlobeGame::GlobeGame(Neighborhood *handler) : GameInteraction(kNoradGlobeGameInteractionID, handler), + _monitorMovie(kGlobeMonitorID), _globeMovie(kGlobeMovieID), _upperNamesMovie(kGlobeUpperNamesID), + _lowerNamesMovie(kGlobeLowerNamesID), _globeNotification(kNoradGlobeNotificationID, (PegasusEngine *)g_engine), + _globeCircleLeft(kGlobeCircleLeftID), _globeCircleRight(kGlobeCircleRightID), + _globeCircleUp(kGlobeCircleUpID), _globeCircleDown(kGlobeCircleDownID), + _motionHighlightLeft(kMotionHiliteLeftID), _motionHighlightRight(kMotionHiliteRightID), + _motionHighlightUp(kMotionHiliteUpID), _motionHighlightDown(kMotionHiliteDownID), + _targetHighlightUpperLeft(kTargetHiliteUpperLeftID), _targetHighlightUpperRight(kTargetHiliteUpperRightID), + _targetHighlightLowerLeft(kTargetHiliteLowerLeftID), _targetHighlightLowerRight(kTargetHiliteLowerRightID), + _globeTracker(&_globeMovie, &_motionHighlightLeft, &_motionHighlightRight, &_motionHighlightUp, + &_motionHighlightDown), _countdown(kGlobeCountdownID) { + _neighborhoodNotification = handler->getNeighborhoodNotification(); +} + +void GlobeGame::openInteraction() { + _monitorMovie.initFromMovieFile("Images/Norad Delta/N79 Left Monitor"); + _monitorMovie.moveElementTo(kGlobeMonitorLeft, kGlobeMonitorTop); + _monitorMovie.setDisplayOrder(kGlobeMonitorLayer); + _monitorMovie.startDisplaying(); + _monitorMovie.setSegment(0, kSplash1End * _monitorMovie.getScale()); + _monitorMovie.show(); + + _monitorCallBack.setNotification(&_globeNotification); + _monitorCallBack.initCallBack(&_monitorMovie, kCallBackAtExtremes); + _monitorCallBack.setCallBackFlag(kGlobeSplash1Finished); + _monitorCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + + _upperNamesMovie.initFromMovieFile("Images/Norad Delta/Upper Names"); + _upperNamesMovie.moveElementTo(kGlobeUpperNamesLeft, kGlobeUpperNamesTop); + _upperNamesMovie.setDisplayOrder(kGlobeUpperNamesLayer); + _upperNamesMovie.startDisplaying(); + + _lowerNamesMovie.initFromMovieFile("Images/Norad Delta/Lower Names"); + _lowerNamesMovie.moveElementTo(kGlobeLowerNamesLeft, kGlobeLowerNamesTop); + _lowerNamesMovie.setDisplayOrder(kGlobeLowerNamesLayer); + _lowerNamesMovie.startDisplaying(); + + _globeMovie.initFromMovieFile("Images/Norad Delta/Spinning Globe"); + _globeMovie.moveElementTo(kGlobeLeft, kGlobeTop); + _globeMovie.setDisplayOrder(kGlobeMovieLayer); + _globeMovie.startDisplaying(); + _globeMovie.setTime(kGlobeMovieStartTime); + _globeMovie.redrawMovieWorld(); + + _globeCircleLeft.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kGlobeCircleLeftPICTID, true); + _globeCircleLeft.moveElementTo(kGlobeCircleLeftLeft, kGlobeCircleLeftTop); + _globeCircleLeft.setDisplayOrder(kGlobeCircleLayer); + _globeCircleLeft.startDisplaying(); + + _globeCircleRight.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kGlobeCircleRightPICTID, true); + _globeCircleRight.moveElementTo(kGlobeCircleRightLeft, kGlobeCircleRightTop); + _globeCircleRight.setDisplayOrder(kGlobeCircleLayer); + _globeCircleRight.startDisplaying(); + + _globeCircleUp.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kGlobeCircleUpPICTID, true); + _globeCircleUp.moveElementTo(kGlobeCircleUpLeft, kGlobeCircleUpTop); + _globeCircleUp.setDisplayOrder(kGlobeCircleLayer); + _globeCircleUp.startDisplaying(); + + _globeCircleDown.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kGlobeCircleDownPICTID, true); + _globeCircleDown.moveElementTo(kGlobeCircleDownLeft, kGlobeCircleDownTop); + _globeCircleDown.setDisplayOrder(kGlobeCircleLayer); + _globeCircleDown.startDisplaying(); + + _motionHighlightLeft.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kMotionHiliteLeftPICTID, true); + _motionHighlightLeft.moveElementTo(kGlobeLeftMotionHiliteLeft, kGlobeLeftMotionHiliteTop); + _motionHighlightLeft.setDisplayOrder(kGlobeHilitesLayer); + _motionHighlightLeft.startDisplaying(); + + _motionHighlightRight.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kMotionHiliteRightPICTID, true); + _motionHighlightRight.moveElementTo(kGlobeRightMotionHiliteLeft, kGlobeRightMotionHiliteTop); + _motionHighlightRight.setDisplayOrder(kGlobeCircleLayer); + _motionHighlightRight.startDisplaying(); + + _motionHighlightUp.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kMotionHiliteUpPICTID, true); + _motionHighlightUp.moveElementTo(kGlobeUpMotionHiliteLeft, kGlobeUpMotionHiliteTop); + _motionHighlightUp.setDisplayOrder(kGlobeHilitesLayer); + _motionHighlightUp.startDisplaying(); + + _motionHighlightDown.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kMotionHiliteDownPICTID, true); + _motionHighlightDown.moveElementTo(kGlobeDownMotionHiliteLeft, kGlobeDownMotionHiliteTop); + _motionHighlightDown.setDisplayOrder(kGlobeHilitesLayer); + _motionHighlightDown.startDisplaying(); + + _targetHighlightUpperLeft.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kTargetUpperLeftPICTID, true); + _targetHighlightUpperLeft.moveElementTo(kGlobeUpperLeftHiliteLeft, kGlobeUpperLeftHiliteTop); + _targetHighlightUpperLeft.setDisplayOrder(kGlobeHilitesLayer); + _targetHighlightUpperLeft.startDisplaying(); + + _targetHighlightUpperRight.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kTargetUpperRightPICTID, true); + _targetHighlightUpperRight.moveElementTo(kGlobeUpperRightHiliteLeft, kGlobeUpperRightHiliteTop); + _targetHighlightUpperRight.setDisplayOrder(kGlobeHilitesLayer); + _targetHighlightUpperRight.startDisplaying(); + + _targetHighlightLowerLeft.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kTargetLowerLeftPICTID, true); + _targetHighlightLowerLeft.moveElementTo(kGlobeLowerLeftHiliteLeft, kGlobeLowerLeftHiliteTop); + _targetHighlightLowerLeft.setDisplayOrder(kGlobeHilitesLayer); + _targetHighlightLowerLeft.startDisplaying(); + + _targetHighlightLowerRight.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kTargetLowerRightPICTID, true); + _targetHighlightLowerRight.moveElementTo(kGlobeLowerRightHiliteLeft, kGlobeLowerRightHiliteTop); + _targetHighlightLowerRight.setDisplayOrder(kGlobeHilitesLayer); + _targetHighlightLowerRight.startDisplaying(); + + _countdown.setDisplayOrder(kGlobeCountdownLayer); + _countdown.moveElementTo(kGlobeCountdownLeft, kGlobeCountdownTop); + _countdown.startDisplaying(); + _countdown.setCountdownTime(_timeLimit[0]); + + _countdownCallBack.setNotification(&_globeNotification); + _countdownCallBack.initCallBack(&_countdown, kCallBackAtExtremes); + _countdownCallBack.setCallBackFlag(kGlobeTimerExpired); + _countdownCallBack.scheduleCallBack(kTriggerAtStart, 0, 0); + + _globeNotification.notifyMe(this, kGlobeNotificationFlags, kGlobeNotificationFlags); + + _gameState = kGameIntro; + _currentSiloIndex = 0; + _playedInstructions = false; + + _neighborhoodNotification->notifyMe(this, kDelayCompletedFlag | kSpotSoundCompletedFlag, kDelayCompletedFlag | kSpotSoundCompletedFlag); +} + +void GlobeGame::initInteraction() { + _monitorMovie.start(); + _monitorMovie.redrawMovieWorld(); +} + +void GlobeGame::closeInteraction() { + _monitorMovie.stop(); + _monitorMovie.stopDisplaying(); + _monitorMovie.releaseMovie(); + _monitorCallBack.releaseCallBack(); + + _globeMovie.stop(); + _globeMovie.stopDisplaying(); + _globeMovie.releaseMovie(); + _globeNotification.cancelNotification(this); + + _upperNamesMovie.stop(); + _upperNamesMovie.stopDisplaying(); + _upperNamesMovie.releaseMovie(); + + _lowerNamesMovie.stop(); + _lowerNamesMovie.stopDisplaying(); + _lowerNamesMovie.releaseMovie(); + + _countdown.hide(); + _countdown.stopDisplaying(); + _countdownCallBack.releaseCallBack(); + + _globeCircleLeft.stopDisplaying(); + _globeCircleLeft.deallocateSurface(); + _globeCircleRight.stopDisplaying(); + _globeCircleRight.deallocateSurface(); + _globeCircleUp.stopDisplaying(); + _globeCircleUp.deallocateSurface(); + _globeCircleDown.stopDisplaying(); + _globeCircleDown.deallocateSurface(); + + _motionHighlightLeft.stopDisplaying(); + _motionHighlightLeft.deallocateSurface(); + _motionHighlightRight.stopDisplaying(); + _motionHighlightRight.deallocateSurface(); + _motionHighlightUp.stopDisplaying(); + _motionHighlightUp.deallocateSurface(); + _motionHighlightDown.stopDisplaying(); + _motionHighlightDown.deallocateSurface(); + + _targetHighlightUpperLeft.stopDisplaying(); + _targetHighlightUpperLeft.deallocateSurface(); + _targetHighlightUpperRight.stopDisplaying(); + _targetHighlightUpperRight.deallocateSurface(); + _targetHighlightLowerLeft.stopDisplaying(); + _targetHighlightLowerLeft.deallocateSurface(); + _targetHighlightLowerRight.stopDisplaying(); + _targetHighlightLowerRight.deallocateSurface(); + + _neighborhoodNotification->cancelNotification(this); +} + +void GlobeGame::receiveNotification(Notification *notification, const NotificationFlags flags) { + TimeScale scale = _monitorMovie.getScale(); + + if (notification == _neighborhoodNotification) { + switch (_gameState) { + case kPlayingRobotIntro: + _monitorMovie.stop(); + _monitorMovie.setSegment(0, _monitorMovie.getDuration()); + _monitorMovie.setTime(kSplash2End * scale - 1); + _monitorMovie.setFlags(0); + + _owner->requestDelay(1, 2, kFilterNoInput, 0); + _owner->requestSpotSound(kStrikeAuthorizedIn, kStrikeAuthorizedOut, + kFilterNoInput, kSpotSoundCompletedFlag); + _gameState = kPlayingStrikeAuthorized; + break; + case kPlayingStrikeAuthorized: + _monitorMovie.setSegment(kSplash3Start * scale, kSplash3Stop * scale); + _monitorMovie.setTime(kSplash3Start * scale); + _monitorMovie.redrawMovieWorld(); + + _owner->requestDelay(1, 3, kFilterNoInput, 0); + _owner->requestSpotSound(kPrimaryTargetIn, kPrimaryTargetOut, kFilterNoInput, 0); + _owner->requestDelay(1, 5, kFilterNoInput, kDelayCompletedFlag); + _monitorMovie.start(); + _gameState = kPlayingPrimaryTarget; + break; + case kPlayingPrimaryTarget: + _monitorMovie.stop(); + _monitorMovie.setSegment(0, _monitorMovie.getDuration()); + _monitorMovie.setTime(kNewLaunchSiloTime * scale); + _owner->requestSpotSound(kNewLaunchSiloIn, kNewLaunchSiloOut, kFilterNoInput, + kSpotSoundCompletedFlag); + _gameState = kPlayingNewSilo1; + break; + case kPlayingNewSilo1: + _monitorMovie.stop(); + _monitorMovie.setSegment(0, _monitorMovie.getDuration()); + _owner->requestDelay(1, 3, kFilterNoInput, kDelayCompletedFlag); + _gameState = kPlayingNewSilo2; + break; + case kPlayingNewSilo2: + _upperNamesMovie.show(); + _upperNamesMovie.setTime(_currentSiloIndex * _upperNamesMovie.getScale()); + _upperNamesMovie.redrawMovieWorld(); + _monitorMovie.setTime(kSplash4Stop * scale - 1); + _monitorMovie.redrawMovieWorld(); + _owner->requestSpotSound(_siloName[_currentSiloIndex][0], _siloName[_currentSiloIndex][1], kFilterNoInput, 0); + _owner->requestDelay(1, 3, kFilterNoInput, 0); + _owner->requestSpotSound(kLaunchToProceedIn, kLaunchToProceedOut, kFilterNoInput, 0); + _owner->requestDelay(1, 5, kFilterNoInput, kDelayCompletedFlag); + _gameState = kPlayingNewSilo3; + break; + case kPlayingNewSilo3: + _countdown.stopCountdown(); + _countdown.setCountdownTime(_timeLimit[_currentSiloIndex]); + _countdown.show(); + _gameState = kPlayingTime; + + if (_timeLimit[_currentSiloIndex] >= 120) + _owner->requestSpotSound(kTwoMinutesIn, kTwoMinutesOut, kFilterNoInput, 0); + else if (_timeLimit[_currentSiloIndex] >= 60) + _owner->requestSpotSound(kOneMinuteIn, kOneMinuteOut, kFilterNoInput, 0); + + switch (_timeLimit[_currentSiloIndex] % 60) { + case 0: + _owner->requestDelay(1, 5, kFilterNoInput, kDelayCompletedFlag); + break; + case 10: + _owner->requestDelay(1, 5, kFilterNoInput, 0); + _owner->requestSpotSound(kTenSecondsIn, kTenSecondsOut, kFilterNoInput, + kSpotSoundCompletedFlag); + break; + case 20: + _owner->requestDelay(1, 5, kFilterNoInput, 0); + _owner->requestSpotSound(kTwentySecondsIn, kTwentySecondsOut, + kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 30: + _owner->requestDelay(1, 5, kFilterNoInput, 0); + _owner->requestSpotSound(kThirtySecondsIn, kThirtySecondsOut, + kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 40: + _owner->requestDelay(1, 5, kFilterNoInput, 0); + _owner->requestSpotSound(kFortySecondsIn, kFortySecondsOut, + kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 50: + _owner->requestDelay(1, 5, kFilterNoInput, 0); + _owner->requestSpotSound(kFiftySecondsIn, kFiftySecondsOut, + kFilterNoInput, kSpotSoundCompletedFlag); + break; + } + case kPlayingTime: + _gameState = kPlayingInstructions; + _globeMovie.show(); + _globeCircleLeft.show(); + _globeCircleRight.show(); + _globeCircleUp.show(); + _globeCircleDown.show(); + + if (_playedInstructions) { + receiveNotification(notification, flags); + } else { + _owner->requestSpotSound(kToDeactivateIn, kToDeactivateOut, kFilterNoInput, + kSpotSoundCompletedFlag); + _playedInstructions = true; + } + break; + case kPlayingInstructions: + _gameState = kWaitingForPlayer; + _countdown.startCountdown(); + break; + case kSiloDeactivated: + _gameState = kRobotTaunting; + + switch (_currentSiloIndex) { + case 3: + _owner->requestSpotSound(kYouCannotPossiblyIn, kYouCannotPossiblyOut, + kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 5: + _owner->requestSpotSound(kYouWillFailIn, kYouWillFailOut, kFilterNoInput, + kSpotSoundCompletedFlag); + break; + case 7: + _owner->requestSpotSound(kGiveUpHumanIn, kGiveUpHumanOut, kFilterNoInput, + kSpotSoundCompletedFlag); + break; + case 9: + _owner->requestSpotSound(kYouAreRunningIn, kYouAreRunningOut, + kFilterNoInput, kSpotSoundCompletedFlag); + break; + default: + _owner->requestSpotSound(kNewLaunchSiloIn, kNewLaunchSiloOut, + kFilterNoInput, kSpotSoundCompletedFlag); + _monitorMovie.setTime(kNewLaunchSiloTime * scale); + _monitorMovie.redrawMovieWorld(); + _gameState = kPlayingNewSilo1; + break; + } + break; + case kRobotTaunting: + _owner->requestDelay(1, 1, kFilterNoInput, 0); + _owner->requestSpotSound(kNewLaunchSiloIn, kNewLaunchSiloOut, kFilterNoInput, kSpotSoundCompletedFlag); + _monitorMovie.setTime(kNewLaunchSiloTime * scale); + _monitorMovie.redrawMovieWorld(); + _gameState = kPlayingNewSilo1; + break; + case kDelayingPlayer: + _gameState = kWaitingForPlayer; + break; + case kPlayerLost1: + _owner->recallToTSAFailure(); + break; + case kPlayerWon2: + ((NoradDelta *)_owner)->finishedGlobeGame(); + _owner->requestDeleteCurrentInteraction(); + break; + default: + break; + } + } else if (notification == &_globeNotification) { + ExtraTable::Entry entry; + + switch (flags) { + case kGlobeSplash1Finished: + _owner->getExtraEntry(kN79BrightView, entry); + _monitorMovie.stop(); + _monitorMovie.setSegment(kSplash1End * scale, kSplash2End * scale); + _monitorMovie.setFlags(kLoopTimeBase); + _monitorMovie.start(); + _owner->showViewFrame(entry.movieStart); + _owner->requestSpotSound(kIJustBrokeIn, kIJustBrokeOut, kFilterNoInput, 0); + _owner->requestDelay(1, 2, kFilterNoInput, kDelayCompletedFlag); + _gameState = kPlayingRobotIntro; + break; + case kGlobeTimerExpired: + // Missile launched, player loses. + _owner->requestSpotSound(kMissileLaunchedIn, kMissileLaunchedOut, kFilterNoInput, kSpotSoundCompletedFlag); + _gameState = kPlayerLost1; + break; + case kMaxDeactivatedFinished: + _monitorMovie.stop(); + _monitorMovie.setSegment(0, _monitorMovie.getDuration()); + _owner->requestDelay(1, 2, kFilterNoInput, 0); + _owner->requestSpotSound(kTheOnlyGoodHumanIn, kTheOnlyGoodHumanOut, kFilterNoInput, 0); + _owner->requestDelay(1, 2, kFilterNoInput, kDelayCompletedFlag); + _gameState = kPlayerWon2; + break; + default: + break; + } + } +} + +// Prevent the player from getting up until the game is over. + +void GlobeGame::handleInput(const Input &input, const Hotspot *cursorSpot) { + Common::Point where; + input.getInputLocation(where); + Hotspot *spot = g_allHotspots.findHotspot(where); + + if (((PegasusEngine *)g_engine)->_cursor->isVisible() && spot != 0 && + spot->getObjectID() == kNorad79SiloAreaSpotID && findClickedSilo(input) != -1) { + _targetHighlightUpperLeft.show(); + _targetHighlightUpperRight.show(); + _targetHighlightLowerLeft.show(); + _targetHighlightLowerRight.show(); + } else { + _targetHighlightUpperLeft.hide(); + _targetHighlightUpperRight.hide(); + _targetHighlightLowerLeft.hide(); + _targetHighlightLowerRight.hide(); + } + + // Interrupt certain inputs to prevent player from switching modes. + InputHandler::handleInput(input, cursorSpot); +} + +int16 GlobeGame::findClickedSilo(const Input &input) { + Common::Point screenPoint; + input.getInputLocation(screenPoint); + screenPoint.x -= kNavAreaLeft; + screenPoint.y -= kNavAreaTop; + + Line3D ray; + screenPointTo3DPoint(screenPoint.x, screenPoint.y, ray.pt2); + ray.pt1 = kCameraLocation; + + Point3D globePoint; + if (lineHitsGlobe(ray, globePoint)) { + int16 latOrigin, longOrigin, latitude, longitude; + globeMovieFrameToOrigin(_globeMovie.getTime() / kTimePerGlobeFrame, latOrigin, longOrigin); + globePointToLatLong(globePoint, latOrigin, longOrigin, latitude, longitude); + + for (int16 i = 0; i < kNumAllSilos; i++) + if (_siloCoords[i][0] >= latitude - kLatError && _siloCoords[i][0] <= latitude + kLatError && + _siloCoords[i][1] >= longitude - kLongError && _siloCoords[i][1] <= longitude + kLongError) + return i; + } + + return -1; +} + +void GlobeGame::spinGlobe(const Input &input, const Hotspot *spot, GlobeTrackDirection trackDirection) { + _globeTracker.setTrackParameters(spot, trackDirection); + _globeTracker.startTracking(input); +} + +void GlobeGame::clickGlobe(const Input &input) { + int16 newSilo = findClickedSilo(input); + + if (newSilo != -1) { + _targetHighlightUpperLeft.hide(); + _targetHighlightUpperRight.hide(); + _targetHighlightLowerLeft.hide(); + _targetHighlightLowerRight.hide(); + _lowerNamesMovie.show(); + _lowerNamesMovie.setTime(newSilo * _lowerNamesMovie.getScale()); + _lowerNamesMovie.redrawMovieWorld(); + _owner->requestSpotSound(kSiloBeepIn, kSiloBeepOut, kFilterNoInput, 0); + + if (newSilo == _targetSilo[_currentSiloIndex]) { + _currentSiloIndex++; + _countdown.stopCountdown(); + _owner->requestSpotSound(kSiloDeactivatedIn, kSiloDeactivatedOut, kFilterNoInput, 0); + + if (_currentSiloIndex == kNumTargetSilos) { + // Player won. + _owner->requestDelay(1, 2, kFilterNoInput, 0); + _upperNamesMovie.hide(); + _lowerNamesMovie.hide(); + _countdown.hide(); + _monitorMovie.setSegment(kMaxDeactivatedStart * _monitorMovie.getScale(), + kMaxDeactivatedStop * _monitorMovie.getScale()); + _monitorMovie.setTime(kMaxDeactivatedStart * _monitorMovie.getScale()); + _monitorCallBack.setCallBackFlag(kMaxDeactivatedFinished); + _monitorCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _monitorMovie.start(); + _owner->requestSpotSound(kMaximumDeactivationIn, kMaximumDeactivationOut, + kFilterNoInput, kSpotSoundCompletedFlag); + _gameState = kPlayerWon1; + } else { + _owner->requestDelay(2, 1, kFilterNoInput, kDelayCompletedFlag); + _upperNamesMovie.hide(); + _lowerNamesMovie.hide(); + _countdown.hide(); + _monitorMovie.setTime(kSiloDeactivatedTime * _monitorMovie.getScale()); + _monitorMovie.redrawMovieWorld(); + _gameState = kSiloDeactivated; + } + } else { + _owner->requestDelay(5, 1, kFilterNoInput, kDelayCompletedFlag); + _gameState = kDelayingPlayer; + // Play "incorrect" sound? + } + } +} + +void GlobeGame::clickInHotspot(const Input &input, const Hotspot *spot) { + switch (spot->getObjectID()) { + case kNorad79SpinLeftSpotID: + spinGlobe(input, spot, kTrackLeft); + break; + case kNorad79SpinRightSpotID: + spinGlobe(input, spot, kTrackRight); + break; + case kNorad79SpinUpSpotID: + spinGlobe(input, spot, kTrackUp); + break; + case kNorad79SpinDownSpotID: + spinGlobe(input, spot, kTrackDown); + break; + case kNorad79SiloAreaSpotID: + clickGlobe(input); + break; + default: + GameInteraction::clickInHotspot(input, spot); + break; + } +} + +void GlobeGame::activateHotspots() { + GameInteraction::activateHotspots(); + + switch (_gameState) { + case kWaitingForPlayer: + g_allHotspots.deactivateOneHotspot(kNorad79WestOutSpotID); + g_allHotspots.activateOneHotspot(kNorad79SpinLeftSpotID); + g_allHotspots.activateOneHotspot(kNorad79SpinRightSpotID); + g_allHotspots.activateOneHotspot(kNorad79SpinUpSpotID); + g_allHotspots.activateOneHotspot(kNorad79SpinDownSpotID); + g_allHotspots.activateOneHotspot(kNorad79SiloAreaSpotID); + break; + default: + g_allHotspots.deactivateOneHotspot(kNorad79WestOutSpotID); + break; + } +} + +void GlobeGame::globeMovieFrameToOrigin(int16 frameNum, int16 &latOrigin, int16 &longOrigin) { + latOrigin = kDegreesPerLatSlice * 2 - (frameNum / (kNumLongSlices * 2)) * kDegreesPerLatSlice; + frameNum %= kNumLongSlices * 2; + + if (frameNum >= kNumLongSlices) + longOrigin = kLongOrigin + (kNumLongSlices * 2 - 1 - frameNum) * kDegreesPerLongSlice; + else + longOrigin = kLongOrigin + frameNum * kDegreesPerLongSlice; + + if (longOrigin > 180) + longOrigin -= 360; +} + +void GlobeGame::globePointToLatLong(const GlobeGame::Point3D &pt, int16 latOrigin, int16 longOrigin, + int16 &latitude, int16 &longitude) { + Point3D scratch = pt; + + // Translate globe center to origin. + scratch.x -= kGlobeCenter.x; + scratch.y -= kGlobeCenter.y; + scratch.z -= kGlobeCenter.z; + + // Rotate around z axis latOrigin degrees to bring equator parallel with XZ plane + float theta = degreesToRadians(latOrigin); + float s = sin(theta); + float c = cos(theta); + float x = scratch.x * c - scratch.y * s; + float y = scratch.y * c + scratch.x * s; + scratch.x = x; + scratch.y = y; + + // Calculate latitude + latitude = (int16)radiansToDegrees(asin(scratch.y / kGlobeRadius)); + + // Rotate around y axis longOrigin degrees to bring longitude 0 to positive X axis + theta = degreesToRadians(longOrigin); + s = sin(theta); + c = cos(theta); + x = scratch.x * c - scratch.z * s; + float z = scratch.z * c + scratch.x * s; + scratch.x = x; + scratch.z = z; + + // Calculate longitude + longitude = (int16)radiansToDegrees(acos(scratch.x / sqrt(scratch.x * scratch.x + scratch.z * scratch.z))); + + if (scratch.z < 0) + longitude = -longitude; +} + +// h, v in [0, 511][0, 255] +// Looking down negative x axis. +void GlobeGame::screenPointTo3DPoint(int16 h, int16 v, GlobeGame::Point3D &pt) { + pt.x = kCameraLocation.x - kPicturePlaneDistance; + pt.y = kCameraLocation.y + (128 - v) * kPicturePlaneDistance * kTanFieldOfView / 256; + pt.z = kCameraLocation.z + (h - 256) * kPicturePlaneDistance * kTanFieldOfView / 256; +} + +// Fundamentals of Three-Dimensional Graphics, by Alan Watt +// pp. 163-164 +bool GlobeGame::lineHitsGlobe(const GlobeGame::Line3D &line, GlobeGame::Point3D &pt) { + float i = line.pt2.x - line.pt1.x; + float j = line.pt2.y - line.pt1.y; + float k = line.pt2.z - line.pt1.z; + float a = i * i + j * j + k * k; + float b = 2 * i * (line.pt1.x - kGlobeCenter.x) + 2 * j * (line.pt1.y - kGlobeCenter.y) + + 2 * k * (line.pt1.z - kGlobeCenter.z); + float c = kGlobeCenter.x * kGlobeCenter.x + kGlobeCenter.y * kGlobeCenter.y + + kGlobeCenter.z * kGlobeCenter.z + line.pt1.x * line.pt1.x + line.pt1.y * line.pt1.y + + line.pt1.z * line.pt1.z + -2 * (kGlobeCenter.x * line.pt1.x + kGlobeCenter.y * line.pt1.y + + kGlobeCenter.z * line.pt1.z) - kGlobeRadius * kGlobeRadius; + + // Solve quadratic equation of a, b, c. + float t = b * b - 4 * a * c; + + if (t >= 0.0f) { + // Return smaller root, which corresponds to closest intersection point. + t = (-b - sqrt(t)) / (2 * a); + pt.x = i * t + line.pt1.x; + pt.y = j * t + line.pt1.y; + pt.z = k * t + line.pt1.z; + return true; + } + + return false; +} + +bool GlobeGame::canSolve() { + return _gameState != kPlayerWon1 && _gameState != kPlayerWon2 && _gameState != kPlayerLost1; +} + +void GlobeGame::doSolve() { + _owner->requestDelay(1, 2, kFilterNoInput, 0); + _upperNamesMovie.hide(); + _lowerNamesMovie.hide(); + _countdown.hide(); + _monitorMovie.setSegment(kMaxDeactivatedStart * _monitorMovie.getScale(), kMaxDeactivatedStop * _monitorMovie.getScale()); + _monitorMovie.setTime(kMaxDeactivatedStart * _monitorMovie.getScale()); + _monitorCallBack.setCallBackFlag(kMaxDeactivatedFinished); + _monitorCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _monitorMovie.start(); + _owner->requestSpotSound(kMaximumDeactivationIn, kMaximumDeactivationOut, kFilterNoInput, kSpotSoundCompletedFlag); + _gameState = kPlayerWon1; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/norad/delta/globegame.h b/engines/pegasus/neighborhood/norad/delta/globegame.h new file mode 100644 index 0000000000..9c31a931fc --- /dev/null +++ b/engines/pegasus/neighborhood/norad/delta/globegame.h @@ -0,0 +1,169 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_NORAD_DELTA_GLOBEGAME_H +#define PEGASUS_NEIGHBORHOOD_NORAD_DELTA_GLOBEGAME_H + +#include "pegasus/interaction.h" +#include "pegasus/movie.h" +#include "pegasus/notification.h" + +namespace Pegasus { + +enum GlobeTrackDirection { + kTrackLeft, + kTrackRight, + kTrackUp, + kTrackDown +}; + +// This class assumes that the globe movie is built at 15 frames per second with a +// time scale of 600, yielding 40 time unit per frame. + +class GlobeTracker : public Tracker { +public: + GlobeTracker(Movie *, Picture *, Picture *, Picture *, Picture *); + virtual ~GlobeTracker() {} + + void setTrackParameters(const Hotspot *, GlobeTrackDirection); + void continueTracking(const Input &); + void startTracking(const Input &); + void stopTracking(const Input &); + void activateHotspots(); + bool stopTrackingInput(const Input &); + +protected: + void trackGlobeMovie(); + void stopGlobeMovie(); + + Movie *_globeMovie; + Picture *_leftHighlight; + Picture *_rightHighlight; + Picture *_upHighlight; + Picture *_downHighlight; + const Hotspot *_trackSpot; + int _trackTime; + GlobeTrackDirection _trackDirection; +}; + +class GlobeCountdown : public IdlerAnimation { +public: + GlobeCountdown(const DisplayElementID); + virtual ~GlobeCountdown() {} + + void setCountdownTime(const int); + void startCountdown(); + void stopCountdown(); + + void setDisplayOrder(const DisplayOrder); + void show(); + void hide(); + void moveElementTo(const CoordType, const CoordType); + + void draw(const Common::Rect &); + +protected: + Surface _digits; + int16 _digitOffset; +}; + +static const int16 kNumAllSilos = 40; +static const int16 kNumTargetSilos = 10; +static const int16 kNumLongSlices = 72; + +class GlobeGame : public GameInteraction, public NotificationReceiver { +public: + GlobeGame(Neighborhood *); + virtual ~GlobeGame() {} + + void handleInput(const Input &, const Hotspot *); + void clickInHotspot(const Input &, const Hotspot *); + void activateHotspots(); + + bool canSolve(); + void doSolve(); + + struct Point3D { + float x, y, z; + }; + + struct Line3D { + Point3D pt1, pt2; + }; + +protected: + // Latitude (-90 - 90) and longitude (-180 - 180) + static const int16 _siloCoords[kNumAllSilos][2]; + + static const int16 _targetSilo[kNumTargetSilos]; + static const int16 _timeLimit[kNumTargetSilos]; + static const TimeValue _siloName[kNumTargetSilos][2]; + + void openInteraction(); + void initInteraction(); + void closeInteraction(); + + void receiveNotification(Notification *, const NotificationFlags); + + void spinGlobe(const Input &, const Hotspot *, GlobeTrackDirection); + void clickGlobe(const Input &); + + int16 findClickedSilo(const Input &); + + void globeMovieFrameToOrigin(int16, int16 &, int16 &); + void globePointToLatLong(const Point3D &, int16, int16, int16 &, int16 &); + void screenPointTo3DPoint(int16, int16, Point3D &); + bool lineHitsGlobe(const Line3D &, Point3D &); + + Movie _monitorMovie; + Movie _globeMovie; + Movie _upperNamesMovie; + Movie _lowerNamesMovie; + Notification _globeNotification; + NotificationCallBack _monitorCallBack; + GlobeTracker _globeTracker; + Picture _globeCircleLeft; + Picture _globeCircleRight; + Picture _globeCircleUp; + Picture _globeCircleDown; + Picture _motionHighlightLeft; + Picture _motionHighlightRight; + Picture _motionHighlightUp; + Picture _motionHighlightDown; + Picture _targetHighlightUpperLeft; + Picture _targetHighlightUpperRight; + Picture _targetHighlightLowerLeft; + Picture _targetHighlightLowerRight; + GlobeCountdown _countdown; + NotificationCallBack _countdownCallBack; + int16 _gameState; + int16 _currentSiloIndex; + Notification *_neighborhoodNotification; + bool _playedInstructions; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/delta/noraddelta.cpp b/engines/pegasus/neighborhood/norad/delta/noraddelta.cpp new file mode 100644 index 0000000000..2b208aab71 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/delta/noraddelta.cpp @@ -0,0 +1,869 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/interface.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/opticalchip.h" +#include "pegasus/items/biochips/retscanchip.h" +#include "pegasus/items/inventory/airmask.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/norad/subcontrolroom.h" +#include "pegasus/neighborhood/norad/delta/globegame.h" +#include "pegasus/neighborhood/norad/delta/noraddelta.h" + +namespace Pegasus { + +const uint32 NoradDelta::_noradDeltaClawExtras[22] = { + kN60ClawFromAToB, + kN60ClawALoop, + kN60ClawAPinch, + kN60ClawACounterclockwise, + kN60ClawAClockwise, + kN60ClawFromBToA, + kN60ClawFromBToC, + kN60ClawFromBToD, + kN60ClawBLoop, + kN60ClawBPinch, + kN60ClawBCounterclockwise, + kN60ClawBClockwise, + kN60ClawFromCToB, + kN60ClawCLoop, + kN60ClawCPinch, + kN60ClawCCounterclockwise, + kN60ClawCClockwise, + kN60ClawFromDToB, + kN60ClawDLoop, + kN60ClawDPinch, + kN60ClawDCounterclockwise, + kN60ClawDClockwise +}; + +NoradDelta::NoradDelta(InputHandler *nextHandler, PegasusEngine *owner) : Norad(nextHandler, owner, "Norad Delta", kNoradDeltaID) { + _elevatorUpRoomID = kNorad49South; + _elevatorDownRoomID = kNorad48South; + _elevatorUpSpotID = kNorad48ElevatorUpSpotID; + _elevatorDownSpotID = kNorad49ElevatorDownSpotID; + + // Pressure door stuff. + + _subRoomEntryRoom1 = kNorad50; + _subRoomEntryDir1 = kEast; + _subRoomEntryRoom2 = kNorad59; + _subRoomEntryDir2 = kWest; + _upperPressureDoorRoom = kNorad50East; + _lowerPressureDoorRoom = kNorad59West; + + _upperPressureDoorUpSpotID = kDeltaUpperPressureDoorUpSpotID; + _upperPressureDoorDownSpotID = kDeltaUpperPressureDoorDownSpotID; + _upperPressureDoorAbortSpotID = kNorad50DoorOutSpotID; + + _lowerPressureDoorUpSpotID = kDeltaLowerPressureDoorUpSpotID; + _lowerPressureDoorDownSpotID = kDeltaLowerPressureDoorDownSpotID; + _lowerPressureDoorAbortSpotID = kNorad59WestOutSpotID; + + _pressureSoundIn = kPressureDoorIntro1In; + _pressureSoundOut = kPressureDoorIntro1Out; + _equalizeSoundIn = kPressureDoorIntro2In; + _equalizeSoundOut = kPressureDoorIntro2Out; + _accessDeniedIn = kDeltaAccessDeniedIn; + _accessDeniedOut = kDeltaAccessDeniedOut; + + GameState.setNoradSubPrepState(kSubDamaged); + + _subControlRoom = kNorad60West; +} + +void NoradDelta::init() { + Norad::init(); + + // Little fix for the retinal scan zoom in spot... + Hotspot *hotspot = g_allHotspots.findHotspotByID(kNorad68WestSpotID); + hotspot->setMaskedHotspotFlags(kZoomInSpotFlag, kZoomInSpotFlag | kZoomOutSpotFlag); + + hotspot = g_allHotspots.findHotspotByID(kNorad79WestSpotID); + hotspot->setMaskedHotspotFlags(kZoomInSpotFlag, kZoomInSpotFlag | kZoomOutSpotFlag); + + hotspot = g_allHotspots.findHotspotByID(kDelta59RobotShieldBiochipSpotID); + hotspot->setMaskedHotspotFlags(kPickUpBiochipSpotFlag, kPickUpBiochipSpotFlag); + HotspotInfoTable::Entry *hotspotEntry = findHotspotEntry(kDelta59RobotShieldBiochipSpotID); + hotspotEntry->hotspotItem = kShieldBiochip; + + hotspot = g_allHotspots.findHotspotByID(kDelta59RobotOpMemBiochipSpotID); + hotspot->setMaskedHotspotFlags(kPickUpBiochipSpotFlag, kPickUpBiochipSpotFlag); + hotspotEntry = findHotspotEntry(kDelta59RobotOpMemBiochipSpotID); + hotspotEntry->hotspotItem = kOpticalBiochip; + + hotspot = g_allHotspots.findHotspotByID(kDelta59RobotRetinalBiochipSpotID); + hotspot->setMaskedHotspotFlags(kPickUpBiochipSpotFlag, kPickUpBiochipSpotFlag); + hotspotEntry = findHotspotEntry(kDelta59RobotRetinalBiochipSpotID); + hotspotEntry->hotspotItem = kRetinalScanBiochip; + + hotspot = g_allHotspots.findHotspotByID(kDelta60RobotShieldBiochipSpotID); + hotspot->setMaskedHotspotFlags(kPickUpBiochipSpotFlag, kPickUpBiochipSpotFlag); + hotspotEntry = findHotspotEntry(kDelta60RobotShieldBiochipSpotID); + hotspotEntry->hotspotItem = kShieldBiochip; + + hotspot = g_allHotspots.findHotspotByID(kDelta60RobotOpMemBiochipSpotID); + hotspot->setMaskedHotspotFlags(kPickUpBiochipSpotFlag, kPickUpBiochipSpotFlag); + hotspotEntry = findHotspotEntry(kDelta60RobotOpMemBiochipSpotID); + hotspotEntry->hotspotItem = kOpticalBiochip; + + hotspot = g_allHotspots.findHotspotByID(kDelta60RobotRetinalBiochipSpotID); + hotspot->setMaskedHotspotFlags(kPickUpBiochipSpotFlag, kPickUpBiochipSpotFlag); + hotspotEntry = findHotspotEntry(kDelta60RobotRetinalBiochipSpotID); + hotspotEntry->hotspotItem = kRetinalScanBiochip; +} + +void NoradDelta::start() { + if (g_energyMonitor) { + g_energyMonitor->stopEnergyDraining(); + g_energyMonitor->restoreLastEnergyValue(); + _vm->resetEnergyDeathReason(); + g_energyMonitor->startEnergyDraining(); + } + + Norad::start(); +} + +void NoradDelta::setUpAIRules() { + Neighborhood::setUpAIRules(); + + if (g_AIArea) { + AIPlayMessageAction *messageAction = new AIPlayMessageAction("Images/AI/Norad/XN07NE", false); + AILocationCondition *locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kNorad68, kWest)); + AIRule *rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + } +} + +void NoradDelta::getExtraCompassMove(const ExtraTable::Entry &entry, FaderMoveSpec &compassMove) { + switch (entry.extra) { + case kArriveFromSubChase: + compassMove.makeTwoKnotFaderSpec(kNoradDeltaMovieScale, entry.movieStart, 20, entry.movieEnd, 90); + compassMove.insertFaderKnot(entry.movieStart + 25 * kNoradDeltaFrameDuration, 20); + compassMove.insertFaderKnot(entry.movieStart + 94 * kNoradDeltaFrameDuration, 45); + compassMove.insertFaderKnot(entry.movieStart + 101 * kNoradDeltaFrameDuration, 45); + compassMove.insertFaderKnot(entry.movieStart + 146 * kNoradDeltaFrameDuration, 90 + 15); + compassMove.insertFaderKnot(entry.movieStart + 189 * kNoradDeltaFrameDuration, 90 + 15); + compassMove.insertFaderKnot(entry.movieStart + 204 * kNoradDeltaFrameDuration, 90 + 30); + compassMove.insertFaderKnot(entry.movieStart + 214 * kNoradDeltaFrameDuration, 90 + 20); + compassMove.insertFaderKnot(entry.movieStart + 222 * kNoradDeltaFrameDuration, 90 + 20); + compassMove.insertFaderKnot(entry.movieStart + 228 * kNoradDeltaFrameDuration, 90 + 10); + compassMove.insertFaderKnot(entry.movieStart + 245 * kNoradDeltaFrameDuration, 90 + 85); + compassMove.insertFaderKnot(entry.movieStart + 262 * kNoradDeltaFrameDuration, 90 + 70); + compassMove.insertFaderKnot(entry.movieStart + 273 * kNoradDeltaFrameDuration, 90 + 80); + compassMove.insertFaderKnot(entry.movieStart + 287 * kNoradDeltaFrameDuration, 90); + break; + case kN60PlayerFollowsRobotToDoor: + compassMove.makeTwoKnotFaderSpec(kNoradDeltaMovieScale, entry.movieStart, 270 + kSubControlCompassAngle, + entry.movieEnd, 270 - 15); + compassMove.insertFaderKnot(entry.movieStart + 280, 270 + kSubControlCompassAngle); + compassMove.insertFaderKnot(entry.movieStart + 920, 360); + compassMove.insertFaderKnot(entry.movieStart + 1840, 360); + compassMove.insertFaderKnot(entry.movieStart + 2520, 270); + compassMove.insertFaderKnot(entry.movieStart + 3760, 270); + compassMove.insertFaderKnot(entry.movieStart + 4640, 270 + kSubControlCompassAngle); + break; + case kN59PlayerWins2: + compassMove.makeTwoKnotFaderSpec(kNoradDeltaMovieScale, entry.movieStart, 270, entry.movieEnd, 280); + compassMove.insertFaderKnot(entry.movieEnd - 1000, 270); + default: + Norad::getExtraCompassMove(entry, compassMove); + break; + } +} + +GameInteraction *NoradDelta::makeInteraction(const InteractionID interactionID) { + if (interactionID == kNoradGlobeGameInteractionID) + return new GlobeGame(this); + + return Norad::makeInteraction(interactionID); +} + +void NoradDelta::playClawMonitorIntro() { + playSpotSoundSync(kLoadClawIntroIn, kLoadClawIntroOut); +} + +void NoradDelta::getExitEntry(const RoomID room, const DirectionConstant direction, ExitTable::Entry &entry) { + Norad::getExitEntry(room, direction, entry); + + if (room == kNorad61 && direction == kSouth) + entry.movieStart += kNoradDeltaFrameDuration; +} + +void NoradDelta::getZoomEntry(const HotSpotID id, ZoomTable::Entry &zoomEntry) { + Norad::getZoomEntry(id, zoomEntry); + + if (id == kNorad59WestSpotID && GameState.getNoradPlayedGlobeGame()) { + ExtraTable::Entry extraEntry; + getExtraEntry(kN59ZoomWithRobot, extraEntry); + zoomEntry.movieStart = extraEntry.movieStart; + zoomEntry.movieEnd = extraEntry.movieEnd; + } +} + +void NoradDelta::loadAmbientLoops() { +/* + Logic: + + loop sound 1: + if room == kNorad79West + if player globe game + play kNoradGlobeLoop2SoundNum + else + play kNoradRedAlertLoopSoundNum + else if room >= kNorad78 && room <= kNorad79 + play kNoradGlobeLoop2SoundNum + else if gassed, + if room >= kNorad41 && room <= kNorad49South + play kNoradNewSubLoopSoundNum, kNoradWarningVolume + else if room >= kNorad59 && room <= kNorad60West + play kNoradSubControlLoopSoundNum, kNoradWarningVolume + else + play kNoradWarningLoopSoundNum, kNoradWarningVolume + else + play nothing + loop sound 2: + if gassed and not wearing air mask + if room == kNorad54North + play breathing unmanned loop + else + play breathing + else + if room == kNorad54North + play unmanned loop + else + play nothing +*/ + + if (GameState.getNoradArrivedFromSub()) { + RoomID room = GameState.getCurrentRoom(); + + if (room == kNorad79West) { + if (_privateFlags.getFlag(kNoradPrivateFinishedGlobeGameFlag)) + loadLoopSound1("Sounds/Norad/GlobAmb2.22K.AIFF"); + else + loadLoopSound1("Sounds/Norad/RedAlert.22K.AIFF"); + } else if (room >= kNorad78 && room <= kNorad79) { + // clone2727 says: This looks like it should be loadLoopSound1... + loadLoopSound2("Sounds/Norad/RedAlert.22K.AIFF"); + } else if (GameState.getNoradGassed()) { + if (room >= kNorad41 && room <= kNorad49South) + loadLoopSound1("Sounds/Norad/NEW SUB AMB.22K.AIFF", kNoradWarningVolume * 3); + else if (room >= kNorad59 && room <= kNorad60West) + loadLoopSound1("Sounds/Norad/SUB CONTRL LOOP.22K.AIFF", kNoradWarningVolume * 3); + else + loadLoopSound1("Sounds/Norad/WARNING LOOP.22K.AIFF", kNoradWarningVolume); + } else { + loadLoopSound1(""); + } + + if (GameState.getNoradGassed() && !g_airMask->isAirFilterOn()) { + if (room == kNorad54North) + loadLoopSound2("Sounds/Norad/Breathing Typing.22K.AIFF", 0x100 / 2); + else + loadLoopSound2("Sounds/Norad/SUCKING WIND.22K.AIFF", kNoradSuckWindVolume, 0, 0); + } else { + if (room == kNorad54North) + loadLoopSound2("Sounds/Norad/N54NAS.22K.AIFF", 0x100 / 2); + else + loadLoopSound2(""); + } + } else { + // Start them off at zero... + if (GameState.getNoradGassed()) + loadLoopSound1("Sounds/Norad/NEW SUB AMB.22K.AIFF", 0, 0, 0); + if (!g_airMask->isAirFilterOn()) + loadLoopSound2("Sounds/Norad/SUCKING WIND.22K.AIFF", 0, 0, 0); + } +} + +void NoradDelta::checkContinuePoint(const RoomID room, const DirectionConstant direction) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kNorad41, kEast): + case MakeRoomView(kNorad49, kEast): + case MakeRoomView(kNorad49, kWest): + case MakeRoomView(kNorad61, kSouth): + case MakeRoomView(kNorad68, kEast): + case MakeRoomView(kNorad79, kWest): + makeContinuePoint(); + break; + } +} + +void NoradDelta::arriveAt(const RoomID room, const DirectionConstant direction) { + if (room != kNorad68) + GameState.setNoradRetScanGood(false); + + Norad::arriveAt(room, direction); + + FaderMoveSpec loop1Spec, loop2Spec; + ExtraTable::Entry entry; + + switch (room) { + case kNorad41: + if (direction == kEast && !GameState.getNoradArrivedFromSub()) { + GameState.setNoradPlayedGlobeGame(false); + + GameState.setNoradBeatRobotWithClaw(false); + GameState.setNoradBeatRobotWithDoor(false); + GameState.setNoradRetScanGood(false); + + GameState.setScoringExitedSub(true); + + getExtraEntry(kArriveFromSubChase, entry); + + loop1Spec.makeTwoKnotFaderSpec(kNoradDeltaMovieScale, 0, 0, entry.movieEnd - + entry.movieStart, kNoradWarningVolume); + loop1Spec.insertFaderKnot(7320, 0); + loop1Spec.insertFaderKnot(7880, kNoradWarningVolume); + + loop2Spec.makeTwoKnotFaderSpec(kNoradDeltaMovieScale, 0, 0, entry.movieEnd - + entry.movieStart, kNoradSuckWindVolume); + loop1Spec.insertFaderKnot(7320, 0); + loop1Spec.insertFaderKnot(7880, kNoradSuckWindVolume); + + startExtraSequence(kArriveFromSubChase, kExtraCompletedFlag, kFilterNoInput); + + startLoop1Fader(loop1Spec); + startLoop2Fader(loop2Spec); + } + break; + case kNorad54North: + GameState.setScoringSawRobotAt54North(true); + break; + case kNorad68: + if (GameState.getNoradRetScanGood()) + openDoor(); + break; + case kNorad68West: + arriveAtNorad68West(); + break; + case kNorad79West: + arriveAtNorad79West(); + break; + default: + break; + } +} + +void NoradDelta::doorOpened() { + Norad::doorOpened(); + GameState.setNoradRetScanGood(false); +} + +void NoradDelta::arriveAtNorad68West() { + playSpotSoundSync(kHoldForRetinalIn, kHoldForRetinalOut); + + BiochipItem *retScan = _vm->getCurrentBiochip(); + + if (retScan != 0 && retScan->getObjectID() == kRetinalScanBiochip) { + ((RetScanChip *)retScan)->searchForLaser(); + succeedRetinalScan(); + } else { + failRetinalScan(); + } +} + +void NoradDelta::arriveAtNorad79West() { + if (!GameState.getNoradPlayedGlobeGame()) + newInteraction(kNoradGlobeGameInteractionID); +} + +void NoradDelta::bumpIntoWall() { + requestSpotSound(kDeltaBumpIntoWallIn, kDeltaBumpIntoWallOut, kFilterNoInput, 0); + Neighborhood::bumpIntoWall(); +} + +void NoradDelta::failRetinalScan() { + startExtraSequence(kNoradDeltaRetinalScanBad, kExtraCompletedFlag, kFilterNoInput); +} + +void NoradDelta::succeedRetinalScan() { + startExtraSequence(kNoradDeltaRetinalScanGood, kExtraCompletedFlag, kFilterNoInput); + GameState.setNoradRetScanGood(true); + GameState.setScoringUsedRetinalChip(true); +} + +void NoradDelta::getDoorEntry(const RoomID room, const DirectionConstant direction, DoorTable::Entry &entry) { + Norad::getDoorEntry(room, direction, entry); + + if (room == kNorad68 && direction == kWest && !GameState.getNoradRetScanGood()) + entry.flags = kDoorPresentMask | kDoorLockedMask; +} + +void NoradDelta::finishedGlobeGame() { + GameState.setNoradPlayedGlobeGame(true); + _privateFlags.setFlag(kNoradPrivateFinishedGlobeGameFlag, true); + GameState.setScoringFinishedGlobeGame(true); + loadAmbientLoops(); + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Norad/XN60WD1", false, kWarningInterruption); +} + +bool NoradDelta::playingAgainstRobot() { + return GameState.getNoradPlayedGlobeGame(); +} + +void NoradDelta::getClawInfo(HotSpotID &outSpotID, HotSpotID &prepSpotID, HotSpotID &clawControlSpotID, HotSpotID &pinchClawSpotID, + HotSpotID &moveClawDownSpotID, HotSpotID &moveClawRightSpotID, HotSpotID &moveClawLeftSpotID, HotSpotID &moveClawUpSpotID, + HotSpotID &clawCCWSpotID, HotSpotID &clawCWSpotID, uint32 &clawPosition, const uint32 *&clawExtraIDs) { + outSpotID = kNorad60MonitorOutSpotID; + prepSpotID = kNorad60LaunchPrepSpotID; + clawControlSpotID = kNorad60ClawControlSpotID; + pinchClawSpotID = kNorad60ClawPinchSpotID; + moveClawDownSpotID = kNorad60ClawDownSpotID; + moveClawRightSpotID = kNorad60ClawRightSpotID; + moveClawLeftSpotID = kNorad60ClawLeftSpotID; + moveClawUpSpotID = kNorad60ClawUpSpotID; + clawCCWSpotID = kNorad60ClawCCWSpotID; + clawCWSpotID = kNorad60ClawCWSpotID; + clawPosition = kClawAtC; + clawExtraIDs = _noradDeltaClawExtras; +} + +void NoradDelta::playerBeatRobotWithDoor() { + GameState.setNoradBeatRobotWithDoor(true); + updateViewFrame(); + GameState.setScoringStoppedNoradRobot(true); + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Norad/XN59WD", false, kWarningInterruption); +} + +void NoradDelta::playerBeatRobotWithClaw() { + GameState.setNoradBeatRobotWithClaw(true); + updateViewFrame(); + GameState.setScoringStoppedNoradRobot(true); + GameState.setScoringNoradGandhi(true); + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Norad/XN59WD", false, kWarningInterruption); +} + +TimeValue NoradDelta::getViewTime(const RoomID room, const DirectionConstant direction) { + ExtraTable::Entry entry; + + if (room == kNorad41 && direction == kSouth && !GameState.getNoradArrivedFromSub()) { + getExtraEntry(kArriveFromSubChase, entry); + return entry.movieStart; + } + + if (GameState.getNoradBeatRobotWithDoor()) { + if (_privateFlags.getFlag(kNoradPrivateRobotHeadOpenFlag)) { + uint32 extraID = kN59Biochips111; + if (_privateFlags.getFlag(kNoradPrivateGotShieldChipFlag)) + extraID += 1; + if (_privateFlags.getFlag(kNoradPrivateGotOpticalChipFlag)) + extraID += 2; + if (_privateFlags.getFlag(kNoradPrivateGotRetScanChipFlag)) + extraID += 4; + getExtraEntry(extraID, entry); + return entry.movieStart; + } + + getExtraEntry(kN59RobotHeadOpens, entry); + return entry.movieStart; + } else if (GameState.getNoradBeatRobotWithClaw()) { + if (_privateFlags.getFlag(kNoradPrivateRobotHeadOpenFlag)) { + uint32 extraID = kN60Biochips111; + if (_privateFlags.getFlag(kNoradPrivateGotShieldChipFlag)) + extraID += 1; + if (_privateFlags.getFlag(kNoradPrivateGotOpticalChipFlag)) + extraID += 2; + if (_privateFlags.getFlag(kNoradPrivateGotRetScanChipFlag)) + extraID += 4; + getExtraEntry(extraID, entry); + return entry.movieStart; + } + + getExtraEntry(kN60RobotHeadOpens, entry); + return entry.movieStart; + } + + return Norad::getViewTime(room, direction); +} + +void NoradDelta::openDoor() { + if (GameState.getCurrentRoom() == kNorad59 && GameState.getCurrentDirection() == kWest && GameState.getNoradPlayedGlobeGame()) { + Input scratch; + InputHandler::_inputHandler->clickInHotspot(scratch, g_allHotspots.findHotspotByID(kNorad59WestSpotID)); + } else { + Norad::openDoor(); + } +} + +void NoradDelta::activateHotspots() { + Norad::activateHotspots(); + + if (GameState.getCurrentRoom() == kNorad59West && GameState.getCurrentDirection() == kWest && GameState.getNoradBeatRobotWithDoor()) { + g_allHotspots.deactivateOneHotspot(kNorad59WestOutSpotID); + + if (_privateFlags.getFlag(kNoradPrivateRobotHeadOpenFlag)) { + if (!_privateFlags.getFlag(kNoradPrivateGotShieldChipFlag)) + g_allHotspots.activateOneHotspot(kDelta59RobotShieldBiochipSpotID); + else + g_allHotspots.deactivateOneHotspot(kDelta59RobotShieldBiochipSpotID); + + if (!_privateFlags.getFlag(kNoradPrivateGotOpticalChipFlag)) + g_allHotspots.activateOneHotspot(kDelta59RobotOpMemBiochipSpotID); + else + g_allHotspots.deactivateOneHotspot(kDelta59RobotOpMemBiochipSpotID); + + if (!_privateFlags.getFlag(kNoradPrivateGotRetScanChipFlag)) + g_allHotspots.activateOneHotspot(kDelta59RobotRetinalBiochipSpotID); + else + g_allHotspots.deactivateOneHotspot(kDelta59RobotRetinalBiochipSpotID); + } else + g_allHotspots.activateOneHotspot(kDelta59RobotHeadSpotID); + } else if (GameState.getCurrentRoom() == kNorad60West && GameState.getCurrentDirection() == kWest && + GameState.getNoradBeatRobotWithClaw()) { + g_allHotspots.deactivateOneHotspot(kNorad60MonitorOutSpotID); + + if (_privateFlags.getFlag(kNoradPrivateRobotHeadOpenFlag)) { + if (!_privateFlags.getFlag(kNoradPrivateGotShieldChipFlag)) + g_allHotspots.activateOneHotspot(kDelta60RobotShieldBiochipSpotID); + else + g_allHotspots.deactivateOneHotspot(kDelta60RobotShieldBiochipSpotID); + + if (!_privateFlags.getFlag(kNoradPrivateGotOpticalChipFlag)) + g_allHotspots.activateOneHotspot(kDelta60RobotOpMemBiochipSpotID); + else + g_allHotspots.deactivateOneHotspot(kDelta60RobotOpMemBiochipSpotID); + + if (!_privateFlags.getFlag(kNoradPrivateGotRetScanChipFlag)) + g_allHotspots.activateOneHotspot(kDelta60RobotRetinalBiochipSpotID); + else + g_allHotspots.deactivateOneHotspot(kDelta60RobotRetinalBiochipSpotID); + } else { + g_allHotspots.activateOneHotspot(kDelta60RobotHeadSpotID); + } + } else if (GameState.getCurrentRoomAndView() == MakeRoomView(kNorad50, kEast)) { + if (GameState.isCurrentDoorOpen()) + g_allHotspots.deactivateOneHotspot(kNorad50DoorSpotID); + } else if (GameState.getCurrentRoomAndView() == MakeRoomView(kNorad59, kWest)) { + if (GameState.isCurrentDoorOpen()) + g_allHotspots.deactivateOneHotspot(kNorad59WestSpotID); + } +} + +void NoradDelta::clickInHotspot(const Input &input, const Hotspot *clickedSpot) { + switch (clickedSpot->getObjectID()) { + case kDelta59RobotHeadSpotID: + startExtraSequence(kN59RobotHeadOpens, kExtraCompletedFlag, kFilterNoInput); + break; + case kDelta60RobotHeadSpotID: + startExtraSequence(kN60RobotHeadOpens, kExtraCompletedFlag, kFilterNoInput); + break; + default: + Norad::clickInHotspot(input, clickedSpot); + break; + } +} + +void NoradDelta::receiveNotification(Notification *notification, const NotificationFlags flags) { + Norad::receiveNotification(notification, flags); + + if ((flags & kExtraCompletedFlag) != 0) { + RetScanChip *retScan; + Input dummy; + + switch (_lastExtra) { + case kArriveFromSubChase: + GameState.setNoradArrivedFromSub(true); + GameState.setCurrentRoom(kNoRoomID); + GameState.setCurrentDirection(kNoDirection); + arriveAt(kNorad41, kEast); + break; + case kN59RobotHeadOpens: + case kN60RobotHeadOpens: + _privateFlags.setFlag(kNoradPrivateRobotHeadOpenFlag, true); + break; + case kNoradDeltaRetinalScanBad: + retScan = (RetScanChip *)_vm->getCurrentBiochip(); + retScan->setItemState(kNormalItem); + playSpotSoundSync(kRetinalScanFailedIn, kRetinalScanFailedOut); + downButton(dummy); + break; + case kNoradDeltaRetinalScanGood: + retScan = (RetScanChip *)_vm->getCurrentBiochip(); + retScan->setItemState(kNormalItem); + downButton(dummy); + break; + case kN59RobotDisappears: + case kN60RobotDisappears: + recallToTSASuccess(); + break; + } + + _interruptionFilter = kFilterAllInput; + } + + g_AIArea->checkMiddleArea(); +} + +void NoradDelta::pickedUpItem(Item *item) { + switch (item->getObjectID()) { + case kShieldBiochip: + if (_privateFlags.getFlag(kNoradPrivateGotShieldChipFlag) && + _privateFlags.getFlag(kNoradPrivateGotRetScanChipFlag) && + _privateFlags.getFlag(kNoradPrivateGotOpticalChipFlag)) { + GameState.setNoradFinished(true); + + if (GameState.getCurrentRoom() == kNorad59West) + startExtraSequence(kN59RobotDisappears, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kN60RobotDisappears, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kRetinalScanBiochip: + if (_privateFlags.getFlag(kNoradPrivateGotShieldChipFlag) && + _privateFlags.getFlag(kNoradPrivateGotRetScanChipFlag) && + _privateFlags.getFlag(kNoradPrivateGotOpticalChipFlag)) { + GameState.setNoradFinished(true); + + if (GameState.getCurrentRoom() == kNorad59West) + startExtraSequence(kN59RobotDisappears, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kN60RobotDisappears, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kOpticalBiochip: + g_opticalChip->addPoseidon(); + GameState.setScoringGotNoradOpMemChip(); + + if (_privateFlags.getFlag(kNoradPrivateGotShieldChipFlag) && + _privateFlags.getFlag(kNoradPrivateGotRetScanChipFlag) && + _privateFlags.getFlag(kNoradPrivateGotOpticalChipFlag)) { + GameState.setNoradFinished(true); + + if (GameState.getCurrentRoom() == kNorad59West) + startExtraSequence(kN59RobotDisappears, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kN60RobotDisappears, kExtraCompletedFlag, kFilterNoInput); + } + break; + } + + Norad::pickedUpItem(item); +} + +void NoradDelta::takeItemFromRoom(Item *item) { + switch (item->getObjectID()) { + case kShieldBiochip: + _privateFlags.setFlag(kNoradPrivateGotShieldChipFlag, true); + break; + case kRetinalScanBiochip: + _privateFlags.setFlag(kNoradPrivateGotRetScanChipFlag, true); + break; + case kOpticalBiochip: + _privateFlags.setFlag(kNoradPrivateGotOpticalChipFlag, true); + break; + } + + Norad::takeItemFromRoom(item); +} + +void NoradDelta::dropItemIntoRoom(Item *item, Hotspot *hotspot) { + switch (item->getObjectID()) { + case kShieldBiochip: + _privateFlags.setFlag(kNoradPrivateGotShieldChipFlag, false); + break; + case kOpticalBiochip: + _privateFlags.setFlag(kNoradPrivateGotOpticalChipFlag, false); + break; + case kRetinalScanBiochip: + _privateFlags.setFlag(kNoradPrivateGotRetScanChipFlag, false); + break; + } + + Norad::dropItemIntoRoom(item, hotspot); +} + +Hotspot *NoradDelta::getItemScreenSpot(Item *item, DisplayElement *element) { + HotSpotID id = kNoHotSpotID; + + switch (item->getObjectID()) { + case kShieldBiochip: + if (GameState.getNoradBeatRobotWithDoor()) + id = kDelta59RobotShieldBiochipSpotID; + else + id = kDelta60RobotShieldBiochipSpotID; + break; + case kOpticalBiochip: + if (GameState.getNoradBeatRobotWithDoor()) + id = kDelta59RobotOpMemBiochipSpotID; + else + id = kDelta60RobotOpMemBiochipSpotID; + break; + case kRetinalScanBiochip: + if (GameState.getNoradBeatRobotWithDoor()) + id = kDelta59RobotRetinalBiochipSpotID; + else + id = kDelta60RobotRetinalBiochipSpotID; + break; + } + + if (id != kNoHotSpotID) + return g_allHotspots.findHotspotByID(id); + + return Norad::getItemScreenSpot(item, element); +} + +Common::String NoradDelta::getEnvScanMovie() { + return "Images/AI/Norad/XNE2"; +} + +uint NoradDelta::getNumHints() { + uint numHints = Neighborhood::getNumHints(); + + if (numHints == 0) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kNorad60, kWest): + if (GameState.getNoradPlayedGlobeGame()) + numHints = 2; + else + numHints = 1; + break; + case MakeRoomView(kNorad59, kNorth): + case MakeRoomView(kNorad59, kSouth): + case MakeRoomView(kNorad59, kEast): + case MakeRoomView(kNorad59, kWest): + case MakeRoomView(kNorad60, kNorth): + case MakeRoomView(kNorad60, kSouth): + case MakeRoomView(kNorad60, kEast): + if (GameState.getNoradPlayedGlobeGame()) + numHints = 2; + break; + case MakeRoomView(kNorad68, kWest): + if (_vm->playerHasItemID(kRetinalScanBiochip)) { + BiochipItem *retScan = _vm->getCurrentBiochip(); + if (retScan == 0 || retScan->getObjectID() != kRetinalScanBiochip) + numHints = 2; + } else if (!GameState.isCurrentDoorOpen()) { + numHints = 2; + } + break; + } + } + + return numHints; +} + +Common::String NoradDelta::getHintMovie(uint hintNum) { + Common::String movieName = Neighborhood::getHintMovie(hintNum); + + if (movieName.empty()) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kNorad60, kWest): + if (GameState.getNoradPlayedGlobeGame()) { + if (hintNum == 1) + return "Images/AI/Norad/XN60WD2"; + + return "Images/AI/Norad/XN60WD3"; + } + + return "Images/AI/Globals/XGLOB1C"; + case MakeRoomView(kNorad59, kNorth): + case MakeRoomView(kNorad59, kSouth): + case MakeRoomView(kNorad59, kEast): + case MakeRoomView(kNorad59, kWest): + case MakeRoomView(kNorad60, kNorth): + case MakeRoomView(kNorad60, kSouth): + case MakeRoomView(kNorad60, kEast): + if (hintNum == 1) + return "Images/AI/Norad/XN60WD2"; + + return "Images/AI/Norad/XN60WD3"; + case MakeRoomView(kNorad68, kWest): + if (_vm->playerHasItemID(kRetinalScanBiochip)) { + if (hintNum == 1) + return "Images/AI/Globals/XGLOB1A"; + + return "Images/AI/Globals/XGLOB1C"; + } + + if (hintNum == 1) + return "Images/AI/Globals/XGLOB1B"; + + return "Images/AI/Globals/XGLOB3B"; + } + } + + return movieName; +} + +void NoradDelta::closeDoorOffScreen(const RoomID room, const DirectionConstant) { + switch (room) { + case kNorad47: + case kNorad48: + case kNorad41: + case kNorad42: + playSpotSoundSync(kDeltaElevatorDoorCloseIn, kDeltaElevatorDoorCloseOut); + break; + default: + playSpotSoundSync(kDeltaRegDoorCloseIn, kDeltaRegDoorCloseOut); + break; + } +} + +bool NoradDelta::canSolve() { + if (Norad::canSolve()) + return true; + + if (GameState.getCurrentRoomAndView() == MakeRoomView(kNorad68, kWest)) { + BiochipItem *biochip = _vm->getCurrentBiochip(); + if (biochip != 0 && biochip->getObjectID() != kRetinalScanBiochip) + return true; + } + + return false; +} + +void NoradDelta::doSolve() { + Norad::doSolve(); + + if (GameState.getCurrentRoomAndView() == MakeRoomView(kNorad68, kWest)) { + if (!_vm->playerHasItemID(kRetinalScanBiochip)) + _vm->addItemToBiochips((BiochipItem *)g_allItems.findItemByID(kRetinalScanBiochip)); + + BiochipItem *biochip = _vm->getCurrentBiochip(); + if (biochip != 0 && biochip->getObjectID() != kRetinalScanBiochip && g_interface) + g_interface->setCurrentBiochipID(kRetinalScanBiochip); + + Hotspot *spot = g_allHotspots.findHotspotByID(kNorad68WestSpotID); + Input scratch; + InputHandler::_inputHandler->clickInHotspot(scratch, spot); + } +} + +Common::String NoradDelta::getSoundSpotsName() { + return "Sounds/Norad/Norad Delta Spots"; +} + +Common::String NoradDelta::getNavMovieName() { + return "Images/Norad Delta/Norad Delta.movie"; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/norad/delta/noraddelta.h b/engines/pegasus/neighborhood/norad/delta/noraddelta.h new file mode 100644 index 0000000000..11065f2c9d --- /dev/null +++ b/engines/pegasus/neighborhood/norad/delta/noraddelta.h @@ -0,0 +1,117 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_NORAD_DELTA_NORADDELTA_H +#define PEGASUS_NEIGHBORHOOD_NORAD_DELTA_NORADDELTA_H + +#include "pegasus/neighborhood/norad/norad.h" + +namespace Pegasus { + +class NoradDelta : public Norad { +public: + NoradDelta(InputHandler *, PegasusEngine *); + virtual ~NoradDelta() {} + + void init(); + + void start(); + + void getExtraCompassMove(const ExtraTable::Entry &, FaderMoveSpec &); + + void finishedGlobeGame(); + + virtual GameInteraction *makeInteraction(const InteractionID); + + void playClawMonitorIntro(); + + virtual void getClawInfo(HotSpotID &outSpotID, HotSpotID &prepSpotID, HotSpotID &clawControlSpotID, + HotSpotID &pinchClawSpotID, HotSpotID &moveClawDownSpotID, HotSpotID &moveClawRightSpotID, + HotSpotID &moveClawLeftSpotID, HotSpotID &moveClawUpSpotID, HotSpotID &clawCCWSpotID, + HotSpotID &clawCWSpotID, uint32 &, const uint32 *&); + + void playerBeatRobotWithClaw(); + void playerBeatRobotWithDoor(); + + void loadAmbientLoops(); + + void setUpAIRules(); + Common::String getEnvScanMovie(); + uint getNumHints(); + Common::String getHintMovie(uint); + void closeDoorOffScreen(const RoomID, const DirectionConstant); + + void checkContinuePoint(const RoomID, const DirectionConstant); + + bool canSolve(); + void doSolve(); + + void doorOpened(); + +protected: + enum { + kNoradPrivateArrivedFromSubFlag, + kNoradPrivateFinishedGlobeGameFlag, + kNoradPrivateRobotHeadOpenFlag, + kNoradPrivateGotShieldChipFlag, + kNoradPrivateGotOpticalChipFlag, + kNoradPrivateGotRetScanChipFlag, + kNumNoradPrivateFlags + }; + + static const uint32 _noradDeltaClawExtras[22]; + + void getExitEntry(const RoomID, const DirectionConstant, ExitTable::Entry &); + void getZoomEntry(const HotSpotID, ZoomTable::Entry &); + virtual void arriveAt(const RoomID, const DirectionConstant); + void arriveAtNorad68West(); + void arriveAtNorad79West(); + TimeValue getViewTime(const RoomID, const DirectionConstant); + void openDoor(); + void activateHotspots(); + void clickInHotspot(const Input &, const Hotspot *); + void receiveNotification(Notification *, const NotificationFlags); + void pickedUpItem(Item *item); + void takeItemFromRoom(Item *item); + void dropItemIntoRoom(Item *item, Hotspot *); + Hotspot *getItemScreenSpot(Item *, DisplayElement *); + + virtual bool playingAgainstRobot(); + + void failRetinalScan(); + void succeedRetinalScan(); + void getDoorEntry(const RoomID, const DirectionConstant, DoorTable::Entry &); + + void bumpIntoWall(); + + FlagsArray<byte, kNumNoradPrivateFlags> _privateFlags; + + Common::String getSoundSpotsName(); + Common::String getNavMovieName(); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/norad.cpp b/engines/pegasus/neighborhood/norad/norad.cpp new file mode 100644 index 0000000000..9ee8205ec1 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/norad.cpp @@ -0,0 +1,285 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/inventory/airmask.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/norad/norad.h" +#include "pegasus/neighborhood/norad/noradelevator.h" +#include "pegasus/neighborhood/norad/pressuredoor.h" +#include "pegasus/neighborhood/norad/subcontrolroom.h" +#include "pegasus/neighborhood/norad/subplatform.h" + +namespace Pegasus { + +const NotificationFlags kDoneWithPressureDoorNotification = 1; + +const NotificationFlags kNoradNotificationFlags = kDoneWithPressureDoorNotification; + +// This class handles everything that Norad Alpha and Delta have in common, such as +// oxygen mask usage, the elevator and the pressure doors. + +Norad::Norad(InputHandler *nextHandler, PegasusEngine *vm, const Common::String &resName, NeighborhoodID id) : + Neighborhood(nextHandler, vm, resName, id), _noradNotification(kNoradNotificationID, vm) { + _elevatorUpSpotID = kNoHotSpotID; + _elevatorDownSpotID = kNoHotSpotID; + _elevatorUpRoomID = kNoHotSpotID; + _elevatorDownRoomID = kNoHotSpotID; + + _subRoomEntryRoom1 = kNoRoomID; + _subRoomEntryDir1 = kNoDirection; + _subRoomEntryRoom2 = kNoRoomID; + _subRoomEntryDir2 = kNoDirection; + _upperPressureDoorRoom = kNoRoomID; + _lowerPressureDoorRoom = kNoRoomID; + + _upperPressureDoorUpSpotID = kNoHotSpotID; + _upperPressureDoorDownSpotID = kNoHotSpotID; + _upperPressureDoorAbortSpotID = kNoHotSpotID; + + _lowerPressureDoorUpSpotID = kNoHotSpotID; + _lowerPressureDoorDownSpotID = kNoHotSpotID; + _lowerPressureDoorAbortSpotID = kNoHotSpotID; + + _pressureSoundIn = 0xffffffff; + _pressureSoundOut = 0xffffffff; + _equalizeSoundIn = 0xffffffff; + _equalizeSoundOut = 0xffffffff; + _accessDeniedIn = 0xffffffff; + _accessDeniedOut = 0xffffffff; + + _platformRoom = kNoRoomID; + _subControlRoom = kNoRoomID; + + _doneWithPressureDoor = false; + + _noradNotification.notifyMe(this, kNoradNotificationFlags, kNoradNotificationFlags); +} + +GameInteraction *Norad::makeInteraction(const InteractionID interactionID) { + PressureDoor *pressureDoor; + SubControlRoom *subControl; + + switch (interactionID) { + case kNoradElevatorInteractionID: + return new NoradElevator(this, _elevatorUpRoomID, _elevatorDownRoomID, _elevatorUpSpotID, _elevatorDownSpotID); + case kNoradPressureDoorInteractionID: + if (GameState.getCurrentRoom() == _upperPressureDoorRoom) + pressureDoor = new PressureDoor(this, true, _upperPressureDoorUpSpotID, _upperPressureDoorDownSpotID, + _upperPressureDoorAbortSpotID, _pressureSoundIn, _pressureSoundOut, _equalizeSoundIn, _equalizeSoundOut); + else + pressureDoor = new PressureDoor(this, false, _lowerPressureDoorUpSpotID, _lowerPressureDoorDownSpotID, + _lowerPressureDoorAbortSpotID, _pressureSoundIn, _pressureSoundOut, _equalizeSoundIn, _equalizeSoundOut); + + if (GameState.getCurrentRoom() == kNorad59West && playingAgainstRobot()) + pressureDoor->playAgainstRobot(); + + return pressureDoor; + case kNoradSubControlRoomInteractionID: + subControl = new SubControlRoom(this); + + if (GameState.getCurrentRoom() == kNorad60West && playingAgainstRobot()) + subControl->playAgainstRobot(); + + return subControl; + case kNoradSubPlatformInteractionID: + return new SubPlatform(this); + default: + return 0; + } +} + +void Norad::flushGameState() { + g_energyMonitor->saveCurrentEnergyValue(); +} + +void Norad::start() { + setUpAirMask(); + Neighborhood::start(); +} + +void Norad::activateHotspots() { + Neighborhood::activateHotspots(); + + RoomID room = GameState.getCurrentRoom(); + if (room == _elevatorUpRoomID) + _neighborhoodHotspots.activateOneHotspot(_elevatorDownSpotID); + else if (room == _elevatorDownRoomID) + _neighborhoodHotspots.activateOneHotspot(_elevatorUpSpotID); +} + +void Norad::arriveAt(const RoomID room, const DirectionConstant direction) { + Neighborhood::arriveAt(room, direction); + + if (GameState.getCurrentRoom() == _elevatorUpRoomID || GameState.getCurrentRoom() == _elevatorDownRoomID) + arriveAtNoradElevator(); + else if (GameState.getCurrentRoom() == _upperPressureDoorRoom) + arriveAtUpperPressureDoorRoom(); + else if (GameState.getCurrentRoom() == _lowerPressureDoorRoom) + arriveAtLowerPressureDoorRoom(); + else if (GameState.getCurrentRoom() == _platformRoom) + arriveAtSubPlatformRoom(); + else if (GameState.getCurrentRoom() == _subControlRoom) + arriveAtSubControlRoom(); + + if (_doneWithPressureDoor) { + _doneWithPressureDoor = false; + openDoor(); + } +} + +void Norad::arriveAtNoradElevator() { + if (_currentInteraction) + _currentInteraction->startOverInteraction(); + else + newInteraction(kNoradElevatorInteractionID); +} + +void Norad::arriveAtUpperPressureDoorRoom() { + newInteraction(kNoradPressureDoorInteractionID); +} + +void Norad::arriveAtLowerPressureDoorRoom() { + newInteraction(kNoradPressureDoorInteractionID); +} + +void Norad::arriveAtSubPlatformRoom() { + newInteraction(kNoradSubPlatformInteractionID); +} + +void Norad::arriveAtSubControlRoom() { + newInteraction(kNoradSubControlRoomInteractionID); +} + +int16 Norad::getStaticCompassAngle(const RoomID room, const DirectionConstant dir) { + int16 result = Neighborhood::getStaticCompassAngle(room, dir); + + if (room == _elevatorUpRoomID || room == _elevatorDownRoomID) + result += kElevatorCompassAngle; + else if (room == _platformRoom) + result += kSubPlatformCompassAngle; + else if (room == _subControlRoom) + result += kSubControlCompassAngle; + + return result; +} + +CanOpenDoorReason Norad::canOpenDoor(DoorTable::Entry &entry) { + if (((GameState.getCurrentRoom() == _subRoomEntryRoom1 && GameState.getCurrentDirection() == _subRoomEntryDir1) || + (GameState.getCurrentRoom() == _subRoomEntryRoom2 && GameState.getCurrentDirection() == _subRoomEntryDir2)) && + GameState.getNoradSubRoomPressure() != kNormalSubRoomPressure) + return kCantOpenBadPressure; + + return Neighborhood::canOpenDoor(entry); +} + +void Norad::cantOpenDoor(CanOpenDoorReason reason) { + if (reason == kCantOpenBadPressure) + playSpotSoundSync(_pressureSoundIn, _pressureSoundOut); + else + playSpotSoundSync(_accessDeniedIn, _accessDeniedOut); +} + +void Norad::startExitMovie(const ExitTable::Entry &exitEntry) { + if (GameState.getCurrentRoom() == _elevatorUpRoomID) { + if (exitEntry.exitRoom != _elevatorDownRoomID) + newInteraction(kNoInteractionID); + } else if (GameState.getCurrentRoom() == _elevatorDownRoomID) { + if (exitEntry.exitRoom != _elevatorUpRoomID) + newInteraction(kNoInteractionID); + } else { + newInteraction(kNoInteractionID); + } + + Neighborhood::startExitMovie(exitEntry); +} + +void Norad::startZoomMovie(const ZoomTable::Entry &zoomEntry) { + newInteraction(kNoInteractionID); + Neighborhood::startZoomMovie(zoomEntry); +} + +void Norad::upButton(const Input &input) { + if (GameState.getCurrentRoom() != _elevatorUpRoomID && GameState.getCurrentRoom() != _elevatorDownRoomID) + Neighborhood::upButton(input); +} + +void Norad::setUpAirMask() { + _airMaskCallBack.setNotification(&_neighborhoodNotification); + _airMaskCallBack.initCallBack(&_airMaskTimer, kCallBackAtExtremes); + _airMaskCallBack.setCallBackFlag(kAirTimerExpiredFlag); + _neighborhoodNotification.notifyMe(this, kAirTimerExpiredFlag, kAirTimerExpiredFlag); + _airMaskCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _airMaskTimer.setScale(1); + _airMaskTimer.setSegment(0, kNoradAirMaskTimeLimit); + checkAirMask(); +} + +void Norad::checkAirMask() { + if (g_airMask && g_airMask->isAirFilterOn()) { + _airMaskTimer.stop(); + } else if (GameState.getNoradGassed() && !_airMaskTimer.isRunning()) { + _airMaskTimer.setTime(0); + _airMaskTimer.start(); + } + + loadAmbientLoops(); +} + +void Norad::receiveNotification(Notification *notification, const NotificationFlags flags) { + if (notification == &_neighborhoodNotification && (flags & kAirTimerExpiredFlag) != 0) + ((PegasusEngine *)g_engine)->die(kDeathGassedInNorad); + + Neighborhood::receiveNotification(notification, flags); + + if (notification == &_noradNotification) { + // Must be kDoneWithPressureDoorNotification... + Input scratch; + _doneWithPressureDoor = true; + downButton(scratch); + } +} + +uint16 Norad::getDateResID() const { + return kDate2112ID; +} + +Common::String Norad::getBriefingMovie() { + return "Images/AI/Norad/XNO"; +} + +void Norad::pickedUpItem(Item *item) { + Neighborhood::pickedUpItem(item); + g_AIArea->checkMiddleArea(); +} + +void Norad::doneWithPressureDoor() { + _noradNotification.setNotificationFlags(kDoneWithPressureDoorNotification, kDoneWithPressureDoorNotification); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/norad/norad.h b/engines/pegasus/neighborhood/norad/norad.h new file mode 100644 index 0000000000..4723410b4c --- /dev/null +++ b/engines/pegasus/neighborhood/norad/norad.h @@ -0,0 +1,121 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_NORAD_NORAD_H +#define PEGASUS_NEIGHBORHOOD_NORAD_NORAD_H + +#include "pegasus/neighborhood/neighborhood.h" + +namespace Pegasus { + +// This is the code common to both Norad Alpha and Norad Delta + +class Norad : public Neighborhood { +public: + Norad(InputHandler *, PegasusEngine *owner, const Common::String &resName, const NeighborhoodID); + virtual ~Norad() {} + + void flushGameState(); + + virtual void start(); + + virtual void getClawInfo(HotSpotID &outSpotID, HotSpotID &prepSpotID, + HotSpotID &clawControlSpotID, HotSpotID &pinchClawSpotID, + HotSpotID &moveClawDownSpotID, HotSpotID &moveClawRightSpotID, + HotSpotID &moveClawLeftSpotID,HotSpotID &moveClawUpSpotID, + HotSpotID &clawCCWSpotID, HotSpotID &clawCWSpotID, uint32 &, const uint32 *&) = 0; + void checkAirMask(); + + virtual uint16 getDateResID() const; + + virtual GameInteraction *makeInteraction(const InteractionID); + + Common::String getBriefingMovie(); + + void pickedUpItem(Item *); + + virtual void playClawMonitorIntro() {} + + void doneWithPressureDoor(); + +protected: + CanOpenDoorReason canOpenDoor(DoorTable::Entry &); + void cantOpenDoor(CanOpenDoorReason); + int16 getStaticCompassAngle(const RoomID, const DirectionConstant); + virtual void startExitMovie(const ExitTable::Entry &); + void startZoomMovie(const ZoomTable::Entry &); + virtual void upButton(const Input &); + virtual void activateHotspots(); + + virtual void arriveAt(const RoomID, const DirectionConstant); + virtual void arriveAtNoradElevator(); + virtual void arriveAtUpperPressureDoorRoom(); + virtual void arriveAtLowerPressureDoorRoom(); + virtual void arriveAtSubPlatformRoom(); + virtual void arriveAtSubControlRoom(); + void setUpAirMask(); + virtual void receiveNotification(Notification *, const NotificationFlags); + virtual bool playingAgainstRobot() { return false; } + + Notification _noradNotification; + bool _doneWithPressureDoor; + + RoomID _elevatorUpRoomID; + RoomID _elevatorDownRoomID; + HotSpotID _elevatorUpSpotID; + HotSpotID _elevatorDownSpotID; + + TimeBase _airMaskTimer; + NotificationCallBack _airMaskCallBack; + + RoomID _subRoomEntryRoom1; + DirectionConstant _subRoomEntryDir1; + RoomID _subRoomEntryRoom2; + DirectionConstant _subRoomEntryDir2; + RoomID _upperPressureDoorRoom; + RoomID _lowerPressureDoorRoom; + + HotSpotID _upperPressureDoorUpSpotID; + HotSpotID _upperPressureDoorDownSpotID; + HotSpotID _upperPressureDoorAbortSpotID; + + HotSpotID _lowerPressureDoorUpSpotID; + HotSpotID _lowerPressureDoorDownSpotID; + HotSpotID _lowerPressureDoorAbortSpotID; + + TimeValue _pressureSoundIn; + TimeValue _pressureSoundOut; + TimeValue _equalizeSoundIn; + TimeValue _equalizeSoundOut; + TimeValue _accessDeniedIn; + TimeValue _accessDeniedOut; + + RoomID _platformRoom; + RoomID _subControlRoom; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/noradelevator.cpp b/engines/pegasus/neighborhood/norad/noradelevator.cpp new file mode 100644 index 0000000000..4279e236ae --- /dev/null +++ b/engines/pegasus/neighborhood/norad/noradelevator.cpp @@ -0,0 +1,130 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/norad/norad.h" +#include "pegasus/neighborhood/norad/noradelevator.h" + +namespace Pegasus { + +// Norad elevator PICTs: +static const ResIDType kElevatorLabelID = 200; +static const ResIDType kElevatorButtonsID = 201; +static const ResIDType kElevatorDownOnID = 202; +static const ResIDType kElevatorUpOnID = 203; + +NoradElevator::NoradElevator(Neighborhood *handler, const RoomID upRoom, const RoomID downRoom, + const HotSpotID upHotspot, const HotSpotID downHotspot) : GameInteraction(kNoradElevatorInteractionID, handler), + _elevatorControls(kNoradElevatorControlsID), _elevatorNotification(kNoradElevatorNotificationID, ((PegasusEngine *)g_engine)) { + _timerExpired = false; + _upRoom = upRoom; + _downRoom = downRoom; + _upHotspot = upHotspot; + _downHotspot = downHotspot; +} + +void NoradElevator::openInteraction() { + SpriteFrame *frame = new SpriteFrame(); + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kElevatorLabelID, true); + _elevatorControls.addFrame(frame, 0, 0); + + frame = new SpriteFrame(); + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kElevatorButtonsID, true); + _elevatorControls.addFrame(frame, 0, 0); + + frame = new SpriteFrame(); + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kElevatorDownOnID, true); + _elevatorControls.addFrame(frame, 0, 0); + + frame = new SpriteFrame(); + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kElevatorUpOnID, true); + _elevatorControls.addFrame(frame, 0, 0); + + _elevatorControls.setCurrentFrameIndex(0); + _elevatorControls.setDisplayOrder(kElevatorControlsOrder); + + Common::Rect r; + frame->getSurfaceBounds(r); + r.moveTo(kNoradAlphaElevatorControlsLeft, kNoradAlphaElevatorControlsTop); + + _elevatorControls.setBounds(r); + _elevatorControls.startDisplaying(); + _elevatorControls.show(); +} + +void NoradElevator::initInteraction() { + _elevatorTimer.setScale(2); + _elevatorTimer.setSegment(0, 1); + _elevatorCallBack.initCallBack(&_elevatorTimer, kCallBackAtExtremes); + _elevatorCallBack.setCallBackFlag(1); + _elevatorCallBack.setNotification(&_elevatorNotification); + _elevatorNotification.notifyMe(this, 1, 1); + _elevatorCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _elevatorTimer.start(); +} + +void NoradElevator::closeInteraction() { + _elevatorControls.stopDisplaying(); + _elevatorControls.discardFrames(); + _elevatorCallBack.releaseCallBack(); +} + +void NoradElevator::resetInteraction() { + _elevatorControls.setCurrentFrameIndex(1); +} + +void NoradElevator::activateHotspots() { + GameInteraction::activateHotspots(); + + if (_timerExpired) { + if (GameState.getCurrentRoom() == _upRoom) + g_allHotspots.activateOneHotspot(_downHotspot); + else if (GameState.getCurrentRoom() == _downRoom) + g_allHotspots.activateOneHotspot(_upHotspot); + } +} + +void NoradElevator::clickInHotspot(const Input &input, const Hotspot *spot) { + HotSpotID id = spot->getObjectID(); + + if (id == _upHotspot || id == _downHotspot) { + g_neighborhood->moveForward(); + if (id == _downHotspot) + _elevatorControls.setCurrentFrameIndex(2); + else + _elevatorControls.setCurrentFrameIndex(3); + } else { + GameInteraction::clickInHotspot(input, spot); + } +} + +void NoradElevator::receiveNotification(Notification *, const NotificationFlags) { + _elevatorControls.setCurrentFrameIndex(1); + _timerExpired = true; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/norad/noradelevator.h b/engines/pegasus/neighborhood/norad/noradelevator.h new file mode 100644 index 0000000000..a34c77b2e0 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/noradelevator.h @@ -0,0 +1,67 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_NORAD_NORADELEVATOR_H +#define PEGASUS_NEIGHBORHOOD_NORAD_NORADELEVATOR_H + +#include "pegasus/interaction.h" +#include "pegasus/notification.h" +#include "pegasus/surface.h" +#include "pegasus/timers.h" + +namespace Pegasus { + +class Neighborhood; + +class NoradElevator : public GameInteraction, private NotificationReceiver { +public: + NoradElevator(Neighborhood *, const RoomID, const RoomID, const HotSpotID, const HotSpotID); + virtual ~NoradElevator() {} + +protected: + virtual void openInteraction(); + virtual void initInteraction(); + virtual void closeInteraction(); + virtual void resetInteraction(); + + virtual void activateHotspots(); + virtual void clickInHotspot(const Input &, const Hotspot *); + + virtual void receiveNotification(Notification *, const NotificationFlags); + + RoomID _upRoom; + RoomID _downRoom; + HotSpotID _upHotspot; + HotSpotID _downHotspot; + Sprite _elevatorControls; + TimeBase _elevatorTimer; + NotificationCallBack _elevatorCallBack; + Notification _elevatorNotification; + bool _timerExpired; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/pressuredoor.cpp b/engines/pegasus/neighborhood/norad/pressuredoor.cpp new file mode 100644 index 0000000000..520d568b5d --- /dev/null +++ b/engines/pegasus/neighborhood/norad/pressuredoor.cpp @@ -0,0 +1,554 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/norad/norad.h" +#include "pegasus/neighborhood/norad/pressuredoor.h" +#include "pegasus/neighborhood/norad/delta/noraddelta.h" + +namespace Pegasus { + +static const TimeValue kLevelsSplashStart = 0; +static const TimeValue kLevelsSplashStop = 1; +static const TimeValue kPressureBase = 1; + +static const TimeValue kDoorSealedTime = 0; +static const TimeValue kEqualizeTime = 1; +static const TimeValue kMaxPressureLoopStart = 2; +static const TimeValue kMaxPressureLoopStop = 3; +static const TimeValue kOpeningDoorLoopStart = 3; +static const TimeValue kOpeningDoorLoopStop = 4; +static const TimeValue kIncreasingPressureTime = 4; +static const TimeValue kDecreasingPressureTime = 5; +static const TimeValue kCautionLoopStart = 6; +static const TimeValue kCautionLoopStop = 7; + +static const NotificationFlags kSplashFinished = 1; +static const NotificationFlags kPressureDroppingFlag = kSplashFinished << 1; + +static const NotificationFlags kPressureNotificationFlags = kSplashFinished | + kPressureDroppingFlag; + +static const NotificationFlags kDoorJumpsUpFlag = 1; +static const NotificationFlags kDoorJumpsBackFlag = kDoorJumpsUpFlag << 1; +static const NotificationFlags kDoorCrushedFlag = kDoorJumpsBackFlag << 1; + +static const NotificationFlags kUtilityNotificationFlags = kDoorJumpsUpFlag | + kDoorJumpsBackFlag | + kDoorCrushedFlag; + +enum { + kPlayingRobotApproaching, + kRobotPunching, + kRobotComingThrough, + kRobotDying, + kRobotDead +}; + +const short kMaxPunches = 5; + +enum { + kPlayingSplash, + kPlayingPressureMessage, + kPlayingEqualizeMessage, + kWaitingForPlayer, + kPlayingDoneMessage, + kGameOver +}; + +// Pressure values range from 0 to 11. +static const short kMinPressure = 0; +static const short kMaxPressure = 11; + +static const TimeScale kNavTimeScale = 600; +static const TimeValue kNavFrameRate = 15; +static const TimeValue kNavTimePerFrame = kNavTimeScale / kNavFrameRate; + +static const TimeValue kApproachPunchInTime = 122 * kNavTimePerFrame; +static const TimeValue kLoopPunchInTime = 38 * kNavTimePerFrame; +static const TimeValue kPunchThroughTime = 38 * kNavTimePerFrame; + +// Pressure door PICTs: +static const ResIDType kUpperPressureUpOffPICTID = 400; +static const ResIDType kUpperPressureUpOnPICTID = 401; +static const ResIDType kUpperPressureDownOffPICTID = 402; +static const ResIDType kUpperPressureDownOnPICTID = 403; + +static const ResIDType kLowerPressureUpOffPICTID = 404; +static const ResIDType kLowerPressureUpOnPICTID = 405; +static const ResIDType kLowerPressureDownOffPICTID = 406; +static const ResIDType kLowerPressureDownOnPICTID = 407; + +PressureDoor::PressureDoor(Neighborhood *handler, bool isUpperDoor, const HotSpotID upSpotID, const HotSpotID downSpotID, + const HotSpotID outSpotID, TimeValue pressureSoundIn, TimeValue pressureSoundOut, TimeValue equalizeSoundIn, + TimeValue equalizeSoundOut) : GameInteraction(kNoradPressureDoorInteractionID, handler), + _levelsMovie(kPressureDoorLevelsID), _typeMovie(kPressureDoorTypeID), _upButton(kPressureDoorUpButtonID), + _downButton(kPressureDoorDownButtonID), _pressureNotification(kNoradPressureNotificationID, ((PegasusEngine *)g_engine)), + _doorTracker(this), _utilityNotification(kNoradUtilityNotificationID, ((PegasusEngine *)g_engine)) { + _neighborhoodNotification = handler->getNeighborhoodNotification(); + _upHotspotID = upSpotID; + _downHotspotID = downSpotID; + _outHotspotID = outSpotID; + _pressureSoundIn = pressureSoundIn; + _pressureSoundOut = pressureSoundOut; + _equalizeSoundIn = equalizeSoundIn; + _equalizeSoundOut = equalizeSoundOut; + _playingAgainstRobot = false; + _isUpperDoor = isUpperDoor; +} + +void PressureDoor::openInteraction() { + if (_isUpperDoor) { + _levelsMovie.initFromMovieFile("Images/Norad Alpha/Upper Levels Movie"); + _levelsMovie.moveElementTo(kNoradUpperLevelsLeft, kNoradUpperLevelsTop); + } else { + _levelsMovie.initFromMovieFile("Images/Norad Alpha/Lower Levels Movie"); + _levelsMovie.moveElementTo(kNoradLowerLevelsLeft, kNoradLowerLevelsTop); + } + + _levelsScale = _levelsMovie.getScale(); + _levelsMovie.setDisplayOrder(kPressureLevelsOrder); + _levelsMovie.startDisplaying(); + _levelsMovie.setSegment(kLevelsSplashStart * _levelsScale, kLevelsSplashStop * _levelsScale); + _levelsMovie.setTime(kLevelsSplashStart * _levelsScale); + _levelsMovie.redrawMovieWorld(); + _levelsMovie.show(); + + _pressureCallBack.setNotification(&_pressureNotification); + _pressureCallBack.initCallBack(&_levelsMovie, kCallBackAtExtremes); + _pressureCallBack.setCallBackFlag(kSplashFinished); + _pressureCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + + _pressureNotification.notifyMe(this, kPressureNotificationFlags, kPressureNotificationFlags); + + if (_isUpperDoor) { + _typeMovie.initFromMovieFile("Images/Norad Alpha/Upper Type Movie"); + _typeMovie.moveElementTo(kNoradUpperTypeLeft, kNoradUpperTypeTop); + } else { + _typeMovie.initFromMovieFile("Images/Norad Alpha/Lower Type Movie"); + _typeMovie.moveElementTo(kNoradLowerTypeLeft, kNoradLowerTypeTop); + } + + _typeScale = _typeMovie.getScale(); + _typeMovie.setDisplayOrder(kPressureTypeOrder); + _typeMovie.startDisplaying(); + _typeMovie.setTime(kDoorSealedTime * _typeScale); + _typeMovie.redrawMovieWorld(); + + SpriteFrame *frame = new SpriteFrame(); + if (_isUpperDoor) + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kLowerPressureUpOffPICTID); + else + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kUpperPressureUpOffPICTID); + _upButton.addFrame(frame, 0, 0); + + frame = new SpriteFrame(); + if (_isUpperDoor) + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kLowerPressureUpOnPICTID); + else + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kUpperPressureUpOnPICTID); + _upButton.addFrame(frame, 0, 0); + + _upButton.setCurrentFrameIndex(0); + _upButton.setDisplayOrder(kPressureUpOrder); + + Common::Rect r; + frame->getSurfaceBounds(r); + if (_isUpperDoor) + r.moveTo(kNoradUpperUpLeft, kNoradUpperUpTop); + else + r.moveTo(kNoradLowerUpLeft, kNoradLowerUpTop); + + _upButton.setBounds(r); + _upButton.startDisplaying(); + _upButton.show(); + + frame = new SpriteFrame(); + if (_isUpperDoor) + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kLowerPressureDownOffPICTID); + else + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kUpperPressureDownOffPICTID); + _downButton.addFrame(frame, 0, 0); + + frame = new SpriteFrame(); + if (_isUpperDoor) + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kLowerPressureDownOnPICTID); + else + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kUpperPressureDownOnPICTID); + _downButton.addFrame(frame, 0, 0); + + _downButton.setCurrentFrameIndex(0); + _downButton.setDisplayOrder(kPressureDownOrder); + + frame->getSurfaceBounds(r); + if (_isUpperDoor) + r.moveTo(kNoradUpperDownLeft, kNoradUpperDownTop); + else + r.moveTo(kNoradLowerDownLeft, kNoradLowerDownTop); + + _downButton.setBounds(r); + _downButton.startDisplaying(); + _downButton.show(); + + _utilityCallBack.setNotification(&_utilityNotification); + _utilityCallBack.initCallBack(&_utilityTimer, kCallBackAtTime); + _utilityNotification.notifyMe(this, kUtilityNotificationFlags, kUtilityNotificationFlags); + _utilityTimer.setMasterTimeBase(getOwner()->getNavMovie()); + + if (_playingAgainstRobot) + _neighborhoodNotification->notifyMe(this, kExtraCompletedFlag | kDelayCompletedFlag | + kSpotSoundCompletedFlag, kExtraCompletedFlag | kDelayCompletedFlag | kSpotSoundCompletedFlag); + else + _neighborhoodNotification->notifyMe(this, kDelayCompletedFlag | kSpotSoundCompletedFlag, + kDelayCompletedFlag | kSpotSoundCompletedFlag); + + _gameState = kPlayingSplash; +} + +void PressureDoor::initInteraction() { + _levelsMovie.start(); + + if (_playingAgainstRobot) { + ExtraTable::Entry entry; + _owner->getExtraEntry(kN59RobotApproaches, entry); + _utilityTimer.setSegment(entry.movieStart, entry.movieEnd); + _utilityCallBack.setCallBackFlag(kDoorJumpsUpFlag); + _punchInTime = kApproachPunchInTime + entry.movieStart; + _utilityCallBack.scheduleCallBack(kTriggerTimeFwd, _punchInTime, kNavTimeScale); + _utilityTimer.setTime(entry.movieStart); + _owner->startExtraSequence(kN59RobotApproaches, kExtraCompletedFlag, kFilterAllInput); + _utilityTimer.start(); + _robotState = kPlayingRobotApproaching; + } + + _levelsMovie.redrawMovieWorld(); +} + +void PressureDoor::closeInteraction() { + _pressureNotification.cancelNotification(this); + _pressureCallBack.releaseCallBack(); + _utilityNotification.cancelNotification(this); + _utilityCallBack.releaseCallBack(); + _neighborhoodNotification->cancelNotification(this); +} + +void PressureDoor::playAgainstRobot() { + _playingAgainstRobot = true; +} + +void PressureDoor::receiveNotification(Notification *notification, const NotificationFlags flags) { + Neighborhood *owner = getOwner(); + + if (notification == _neighborhoodNotification) { + if (_playingAgainstRobot && (flags & kExtraCompletedFlag) != 0) { + ExtraTable::Entry entry; + + switch (_robotState) { + case kPlayingRobotApproaching: + _utilityTimer.stop(); + if (GameState.getNoradSubRoomPressure() == kMaxPressure) { + owner->getExtraEntry(kN59PlayerWins1, entry); + _utilityTimer.setSegment(entry.movieStart, entry.movieEnd); + _utilityTimer.setTime(entry.movieStart); + _utilityCallBack.setCallBackFlag(kDoorJumpsUpFlag); + _punchInTime = kLoopPunchInTime + entry.movieStart; + _utilityCallBack.scheduleCallBack(kTriggerTimeFwd, _punchInTime, kNavTimeScale); + owner->startExtraSequence(kN59PlayerWins1, kExtraCompletedFlag, kFilterNoInput); + _utilityTimer.start(); + _robotState = kRobotDying; + } else { + owner->getExtraEntry(kN59RobotPunchLoop, entry); + _utilityTimer.setSegment(entry.movieStart, entry.movieEnd); + _utilityTimer.setTime(entry.movieStart); + _utilityCallBack.setCallBackFlag(kDoorJumpsUpFlag); + _punchInTime = kLoopPunchInTime + entry.movieStart; + _utilityCallBack.scheduleCallBack(kTriggerTimeFwd, _punchInTime, kNavTimeScale); + owner->startSpotLoop(entry.movieStart, entry.movieEnd, kExtraCompletedFlag); + _utilityTimer.start(); + _robotState = kRobotPunching; + _punchCount = 1; + } + break; + case kRobotPunching: + if (GameState.getNoradSubRoomPressure() == kMaxPressure) { + owner->startExtraSequence(kN59PlayerWins1, kExtraCompletedFlag, kFilterNoInput); + _robotState = kRobotDying; + } else if (++_punchCount >= kMaxPunches) { + _robotState = kRobotComingThrough; + owner->getExtraEntry(kN59RobotWins, entry); + _utilityTimer.stop(); + _utilityTimer.setSegment(entry.movieStart, entry.movieEnd); + _utilityTimer.setTime(entry.movieStart); + _utilityCallBack.cancelCallBack(); + _utilityCallBack.setCallBackFlag(kDoorCrushedFlag); + _utilityCallBack.scheduleCallBack(kTriggerTimeFwd, kPunchThroughTime + entry.movieStart, kNavTimeScale); + owner->startExtraSequence(kN59RobotWins, kExtraCompletedFlag, kFilterNoInput); + _utilityTimer.start(); + } else { + _utilityCallBack.setCallBackFlag(kDoorJumpsUpFlag); + _utilityCallBack.scheduleCallBack(kTriggerTimeFwd, _punchInTime, kNavTimeScale); + owner->scheduleNavCallBack(kExtraCompletedFlag); + } + break; + case kRobotComingThrough: + g_system->delayMillis(2 * 1000); + ((PegasusEngine *)g_engine)->die(kDeathRobotThroughNoradDoor); + break; + case kRobotDying: + _robotState = kRobotDead; + _levelsMovie.stop(); + _levelsMovie.setSegment((kNormalSubRoomPressure + kPressureBase) * _levelsScale, + (GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale); + _pressureCallBack.setCallBackFlag(kPressureDroppingFlag); + _pressureCallBack.scheduleCallBack(kTriggerAtStart, 0, 0); + _typeMovie.stop(); + _typeMovie.setSegment(0, _typeMovie.getDuration()); + _typeMovie.setTime(kDecreasingPressureTime * _typeScale); + _typeMovie.redrawMovieWorld(); + _typeMovie.show(); + _downButton.show(); + _downButton.setCurrentFrameIndex(1); + _gameState = kGameOver; + allowInput(false); + _levelsMovie.setRate(Common::Rational(0x5555, 0x10000) - 1); // Should match door tracker. + break; + case kRobotDead: + allowInput(true); + ((NoradDelta *)owner)->playerBeatRobotWithDoor(); + owner->requestDeleteCurrentInteraction(); + break; + } + } + + if ((flags & (kDelayCompletedFlag | kSpotSoundCompletedFlag)) != 0) { + switch (_gameState) { + case kPlayingPressureMessage: + _typeMovie.setTime(kEqualizeTime * _typeScale); + _typeMovie.redrawMovieWorld(); + owner->requestDelay(1, 5, kFilterNoInput, 0); + owner->requestSpotSound(_equalizeSoundIn, _equalizeSoundOut, kFilterNoInput, 0); + owner->requestDelay(1, 5, kFilterNoInput, kDelayCompletedFlag); + _gameState = kPlayingEqualizeMessage; + break; + case kPlayingEqualizeMessage: + _gameState = kWaitingForPlayer; + stopChangingPressure(); + break; + case kPlayingDoneMessage: + _gameState = kWaitingForPlayer; + _typeMovie.stop(); + _typeMovie.setFlags(0); + _typeMovie.hide(); + if (!_playingAgainstRobot) + ((Norad *)_owner)->doneWithPressureDoor(); + break; + } + } + } else if (notification == &_pressureNotification) { + switch (flags) { + case kSplashFinished: + _levelsMovie.stop(); + _levelsMovie.setSegment(0, _levelsMovie.getDuration()); + _levelsMovie.setTime((GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale); + _levelsMovie.redrawMovieWorld(); + + if (GameState.getNoradSubRoomPressure() != kNormalSubRoomPressure) { + _typeMovie.show(); + owner->requestDelay(1, 5, kFilterNoInput, 0); + owner->requestSpotSound(_pressureSoundIn, _pressureSoundOut, kFilterNoInput, 0); + owner->requestDelay(1, 5, kFilterNoInput, kDelayCompletedFlag); + _gameState = kPlayingPressureMessage; + } else { + _gameState = kWaitingForPlayer; + } + break; + case kPressureDroppingFlag: + _levelsMovie.stop(); + _levelsMovie.hide(); + _typeMovie.stop(); + _typeMovie.hide(); + _upButton.hide(); + _downButton.hide(); + owner->startExtraSequence(kN59PlayerWins2, kExtraCompletedFlag, kFilterNoInput); + break; + } + } else if (notification == &_utilityNotification) { + switch (flags) { + case kDoorJumpsUpFlag: + _utilityCallBack.setCallBackFlag(kDoorJumpsBackFlag); + _utilityCallBack.scheduleCallBack(kTriggerTimeFwd, _punchInTime + kNavTimePerFrame, kNavTimeScale); + _levelsMovie.hide(); + _typePunched = _typeMovie.isVisible(); + if (_typePunched == true) + _typeMovie.hide(); + _upButton.hide(); + _downButton.hide(); + break; + case kDoorJumpsBackFlag: + _levelsMovie.show(); + _upButton.show(); + _downButton.show(); + if (_typePunched) + _typeMovie.show(); + break; + case kDoorCrushedFlag: + _levelsMovie.hide(); + _typeMovie.hide(); + _upButton.hide(); + _downButton.hide(); + break; + } + } +} + +void PressureDoor::activateHotspots() { + GameInteraction::activateHotspots(); + + switch (_gameState) { + case kWaitingForPlayer: + g_allHotspots.activateOneHotspot(_upHotspotID); + g_allHotspots.activateOneHotspot(_downHotspotID); + if (!_playingAgainstRobot) + g_allHotspots.activateOneHotspot(_outHotspotID); + break; + default: + break; + } +} + +void PressureDoor::clickInHotspot(const Input &input, const Hotspot *spot) { + HotSpotID id = spot->getObjectID(); + + if (id == _upHotspotID || id == _downHotspotID) { + if (id == _upHotspotID) + _doorTracker.setTrackParameters(spot, &_upButton); + else + _doorTracker.setTrackParameters(spot, &_downButton); + + _doorTracker.startTracking(input); + } else { + GameInteraction::clickInHotspot(input, spot); + } +} + +void PressureDoor::incrementPressure(const HotSpotID id) { + _typeMovie.stop(); + _typeMovie.setSegment(0, _typeMovie.getDuration()); + _typeMovie.setFlags(0); + + if (id == _upHotspotID) { + if (GameState.getNoradSubRoomPressure() < kMaxPressure) { + GameState.setNoradSubRoomPressure(GameState.getNoradSubRoomPressure() + 1); + _levelsMovie.setTime((GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale); + _levelsMovie.redrawMovieWorld(); + _typeMovie.setTime(kIncreasingPressureTime * _typeScale); + _typeMovie.redrawMovieWorld(); + _typeMovie.show(); + g_AIArea->checkMiddleArea(); + } else { + _typeMovie.hide(); + } + } else if (id == _downHotspotID) { + if (GameState.getNoradSubRoomPressure() > kMinPressure) { + GameState.setNoradSubRoomPressure(GameState.getNoradSubRoomPressure() - 1); + _levelsMovie.setTime((GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale); + _levelsMovie.redrawMovieWorld(); + _typeMovie.setTime(kDecreasingPressureTime * _typeScale); + _typeMovie.redrawMovieWorld(); + _typeMovie.show(); + g_AIArea->checkMiddleArea(); + } else { + _typeMovie.hide(); + } + } +} + +void PressureDoor::stopChangingPressure() { + Neighborhood *owner; + + switch (GameState.getNoradSubRoomPressure()) { + case 11: + _typeMovie.setSegment(kMaxPressureLoopStart * _typeScale, kMaxPressureLoopStop * _typeScale); + _typeMovie.setFlags(kLoopTimeBase); + _typeMovie.show(); + _typeMovie.start(); + break; + case 10: + _typeMovie.setSegment(kCautionLoopStart * _typeScale, kCautionLoopStop * _typeScale); + _typeMovie.setFlags(kLoopTimeBase); + _typeMovie.show(); + _typeMovie.start(); + break; + case kNormalSubRoomPressure: + owner = getOwner(); + _typeMovie.setSegment(kOpeningDoorLoopStart * _typeScale, kOpeningDoorLoopStop * _typeScale); + _typeMovie.setFlags(kLoopTimeBase); + _typeMovie.show(); + _gameState = kPlayingDoneMessage; + owner->requestDelay(2, 1, kFilterNoInput, kDelayCompletedFlag); + _typeMovie.start(); + break; + default: + _typeMovie.hide(); + break; + } +} + +bool PressureDoor::canSolve() { + if (_playingAgainstRobot) + return GameState.getNoradSubRoomPressure() < 11; + + return GameState.getNoradSubRoomPressure() != kNormalSubRoomPressure; +} + +void PressureDoor::doSolve() { + if (_playingAgainstRobot) { + GameState.setNoradSubRoomPressure(11); + _levelsMovie.setTime((11 + kPressureBase) * _levelsScale); + _levelsMovie.redrawMovieWorld(); + _typeMovie.setSegment(kMaxPressureLoopStart * _typeScale, kMaxPressureLoopStop * _typeScale); + _typeMovie.setFlags(kLoopTimeBase); + _typeMovie.show(); + _typeMovie.start(); + g_AIArea->checkMiddleArea(); + } else { + GameState.setNoradSubRoomPressure(kNormalSubRoomPressure); + _levelsMovie.setTime((kNormalSubRoomPressure + kPressureBase) * _levelsScale); + _levelsMovie.redrawMovieWorld(); + _typeMovie.setSegment(kOpeningDoorLoopStart * _typeScale, kOpeningDoorLoopStop * _typeScale); + _typeMovie.setFlags(kLoopTimeBase); + _typeMovie.show(); + Neighborhood *owner = getOwner(); + owner->requestDelay(2, 1, kFilterNoInput, kDelayCompletedFlag); + _gameState = kPlayingDoneMessage; + _typeMovie.start(); + g_AIArea->checkMiddleArea(); + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/norad/pressuredoor.h b/engines/pegasus/neighborhood/norad/pressuredoor.h new file mode 100644 index 0000000000..7ef1518c9f --- /dev/null +++ b/engines/pegasus/neighborhood/norad/pressuredoor.h @@ -0,0 +1,93 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_NORAD_PRESSUREDOOR_H +#define PEGASUS_NEIGHBORHOOD_NORAD_PRESSUREDOOR_H + +#include "pegasus/interaction.h" +#include "pegasus/movie.h" +#include "pegasus/notification.h" +#include "pegasus/neighborhood/norad/pressuretracker.h" + +namespace Pegasus { + +static const short kNormalSubRoomPressure = 2; + +class PressureDoor : public GameInteraction, public NotificationReceiver { +public: + PressureDoor(Neighborhood *, bool isUpperDoor, const HotSpotID, const HotSpotID, + const HotSpotID, TimeValue pressureSoundIn, TimeValue pressureSoundOut, + TimeValue equalizeSoundIn, TimeValue equalizeSoundOut); + virtual ~PressureDoor() {} + + void incrementPressure(const HotSpotID); + void stopChangingPressure(); + + void playAgainstRobot(); + + bool canSolve(); + void doSolve(); + +protected: + virtual void openInteraction(); + virtual void initInteraction(); + virtual void closeInteraction(); + + virtual void activateHotspots(); + virtual void clickInHotspot(const Input &, const Hotspot *); + + virtual void receiveNotification(Notification *, const NotificationFlags); + + Movie _levelsMovie; + TimeScale _levelsScale; + Movie _typeMovie; + TimeScale _typeScale; + Sprite _upButton; + Sprite _downButton; + Notification _pressureNotification; + NotificationCallBack _pressureCallBack; + Notification *_neighborhoodNotification; + int _gameState; + HotSpotID _upHotspotID; + HotSpotID _downHotspotID; + HotSpotID _outHotspotID; + PressureTracker _doorTracker; + TimeValue _pressureSoundIn; + TimeValue _pressureSoundOut; + TimeValue _equalizeSoundIn; + TimeValue _equalizeSoundOut; + bool _isUpperDoor; + + bool _playingAgainstRobot, _typePunched; + int _robotState, _punchCount; + TimeBase _utilityTimer; + Notification _utilityNotification; + NotificationCallBack _utilityCallBack; + TimeValue _punchInTime; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/pressuretracker.cpp b/engines/pegasus/neighborhood/norad/pressuretracker.cpp new file mode 100644 index 0000000000..60521a4a98 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/pressuretracker.cpp @@ -0,0 +1,86 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/hotspot.h" +#include "pegasus/neighborhood/norad/pressuredoor.h" +#include "pegasus/neighborhood/norad/pressuretracker.h" + +namespace Pegasus { + +PressureTracker::PressureTracker(PressureDoor *pressureDoor) { + _pressureDoor = pressureDoor; + _trackSpot = 0; + _trackTime = 0; +} + +void PressureTracker::setTrackParameters(const Hotspot *trackSpot, Sprite *trackButton) { + _trackSpot = trackSpot; + _trackButton = trackButton; + _trackTime = 0; +} + +void PressureTracker::activateHotspots() { + Tracker::activateHotspots(); + + if (_trackSpot) + g_allHotspots.activateOneHotspot(_trackSpot->getObjectID()); +} + +// For click-hold dragging. +bool PressureTracker::stopTrackingInput(const Input &input) { + return !JMPPPInput::isPressingInput(input); +} + +void PressureTracker::continueTracking(const Input &input) { + Common::Point where; + input.getInputLocation(where); + + if (g_allHotspots.findHotspot(where) == _trackSpot) { + trackPressure(); + _trackButton->setCurrentFrameIndex(1); + } else { + _trackButton->setCurrentFrameIndex(0); + } +} + +void PressureTracker::startTracking(const Input &input) { + Tracker::startTracking(input); + trackPressure(); +} + +void PressureTracker::stopTracking(const Input &input) { + _trackButton->setCurrentFrameIndex(0); + _pressureDoor->stopChangingPressure(); + Tracker::stopTracking(input); +} + +void PressureTracker::trackPressure() { + if (g_system->getMillis() - _trackTime > kPressureDoorTrackInterval * 1000 / 60) { + _pressureDoor->incrementPressure(_trackSpot->getObjectID()); + _trackTime = g_system->getMillis(); + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/norad/pressuretracker.h b/engines/pegasus/neighborhood/norad/pressuretracker.h new file mode 100644 index 0000000000..7d572593d0 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/pressuretracker.h @@ -0,0 +1,69 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_NORAD_PRESSURETRACKER_H +#define PEGASUS_NEIGHBORHOOD_NORAD_PRESSURETRACKER_H + +#include "pegasus/input.h" + +namespace Pegasus { + +// This class assumes that the globe movie is built at 15 frames per second with a +// time scale of 600, yielding 40 time unit per frame. + +enum PressureTrackDirection { + kTrackPressureUp, + kTrackPressureDown +}; + +static const int kPressureDoorTrackInterval = 45; + +class PressureDoor; +class Sprite; + +class PressureTracker : public Tracker { +public: + PressureTracker(PressureDoor *); + virtual ~PressureTracker() {} + + void setTrackParameters(const Hotspot *, Sprite *); + void continueTracking(const Input &); + void startTracking(const Input &); + void stopTracking(const Input &); + void activateHotspots(); + bool stopTrackingInput(const Input &); + +protected: + void trackPressure(); + + PressureDoor *_pressureDoor; + const Hotspot *_trackSpot; + Sprite *_trackButton; + long _trackTime; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/subcontrolroom.cpp b/engines/pegasus/neighborhood/norad/subcontrolroom.cpp new file mode 100644 index 0000000000..2b15ad4b7d --- /dev/null +++ b/engines/pegasus/neighborhood/norad/subcontrolroom.cpp @@ -0,0 +1,1178 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/norad/norad.h" +#include "pegasus/neighborhood/norad/subcontrolroom.h" +#include "pegasus/neighborhood/norad/delta/noraddelta.h" + +namespace Pegasus { + +// Right Monitor times + +static const TimeValue kAlphaClawSplashStart = 0; +static const TimeValue kAlphaClawSplashStop = 4000; + +static const TimeValue kDeltaClawSplashStart = 4000; +static const TimeValue kDeltaClawSplashStop = 8000; + +static const TimeValue kClawAtATime = 8000; +static const TimeValue kClawAtAPinchedTime = 8600; +static const TimeValue kClawAtATurnedTime = 9200; +static const TimeValue kClawAtAWithRobotPinchedTime = 9800; + +static const TimeValue kClawAtBTime = 10400; +static const TimeValue kClawAtBPinchedTime = 11000; +static const TimeValue kClawAtBTurnedTime = 11600; +static const TimeValue kClawAtBWithRobotTime = 12200; +static const TimeValue kClawAtBWithRobotPinchedTime = 12800; + +static const TimeValue kClawAtCTime = 13400; +static const TimeValue kClawAtCPinchedTime = 14000; +static const TimeValue kClawAtCTurnedTime = 14600; + +static const TimeValue kClawAtDTime = 15200; +static const TimeValue kClawAtDPinchedTime = 15800; +static const TimeValue kClawAtDTurnedTime = 16400; + +static const TimeValue kAToBStart = 17000; +static const TimeValue kAToBStop = 18680; +static const TimeValue kAPinchStart = 18680; +static const TimeValue kAPinchStop = 20200; +static const TimeValue kACCWStart = 20200; +static const TimeValue kACCWStop = 21600; +static const TimeValue kACWStart = 21600; +static const TimeValue kACWStop = 23000; + +static const TimeValue kBToAStart = 23000; +static const TimeValue kBToAStop = 24680; +static const TimeValue kBToCStart = 24680; +static const TimeValue kBToCStop = 26520; +static const TimeValue kBToDStart = 26520; +static const TimeValue kBToDStop = 28320; +static const TimeValue kBPinchStart = 28320; +static const TimeValue kBPinchStop = 29680; +static const TimeValue kBCCWStart = 29680; +static const TimeValue kBCCWStop = 31200; +static const TimeValue kBCWStart = 31200; +static const TimeValue kBCWStop = 32720; + +static const TimeValue kCToBStart = 32720; +static const TimeValue kCToBStop = 34560; +static const TimeValue kCPinchStart = 34560; +static const TimeValue kCPinchStop = 36400; +static const TimeValue kCCCWStart = 36400; +static const TimeValue kCCCWStop = 37840; +static const TimeValue kCCWStart = 37840; +static const TimeValue kCCWStop = 39280; + +static const TimeValue kDToBStart = 39280; +static const TimeValue kDToBStop = 41080; +static const TimeValue kDPinchStart = 41080; +static const TimeValue kDPinchStop = 42600; +static const TimeValue kDCCWStart = 42600; +static const TimeValue kDCCWStop = 44000; +static const TimeValue kDCWStart = 44000; +static const TimeValue kDCWStop = 45400; + +static const TimeValue kRobotApproachStart = 45400; +static const TimeValue kRobotApproachStop = 56800; + +static const TimeValue kCToBWithRobotStart = 56800; +static const TimeValue kCToBWithRobotStop = 58600; + +static const TimeValue kBPinchWithRobotStart = 58600; +static const TimeValue kBPinchWithRobotStop = 60400; +static const TimeValue kBToAWithRobotStart = 60400; +static const TimeValue kBToAWithRobotStop = 62240; + +// As usual, times here are in seconds. + +// Left monitor times. + +static const TimeValue kAlphaSplashStart = 0; +static const TimeValue kAlphaSplashStop = 2; + +static const TimeValue kMainMenuTime = 2; +static const TimeValue kLaunchPrepRolloverTime = 3; +static const TimeValue kLaunchPrepHighlightStart = 4; +static const TimeValue kLaunchPrepHighlightStop = 5; +static const TimeValue kClawControlRolloverTime = 5; +static const TimeValue kClawControlHighlightStart = 6; +static const TimeValue kClawControlHighlightStop = 7; + +static const TimeValue kAlphaLaunchPrepStart = 7; +static const TimeValue kAlphaLaunchPrepStop = 17; + +static const TimeValue kClawMenuStart = 17; +static const TimeValue kClawMenuStop = 18; + +static const TimeValue kClawMenuTime = 18; + +static const TimeValue kDeltaSplashStart = 19; +static const TimeValue kDeltaSplashStop = 21; + +static const TimeValue kDeltaLaunchPrepStart = 21; +static const TimeValue kDeltaLaunchPrepStop = 30; + +// Right monitor times. + +static const NotificationFlags kAlphaSplashFinished = 1; +static const NotificationFlags kAlphaPrepFinished = kAlphaSplashFinished << 1; +static const NotificationFlags kPrepHighlightFinished = kAlphaPrepFinished << 1; +static const NotificationFlags kClawHighlightFinished = kPrepHighlightFinished << 1; +static const NotificationFlags kClawMenuFinished = kClawHighlightFinished << 1; +static const NotificationFlags kDeltaSplashFinished = kClawMenuFinished << 1; +static const NotificationFlags kDeltaPrepFinished = kDeltaSplashFinished << 1; + +static const NotificationFlags kSubControlNotificationFlags = kAlphaSplashFinished | + kAlphaPrepFinished | + kPrepHighlightFinished | + kClawHighlightFinished | + kClawMenuFinished | + kDeltaSplashFinished | + kDeltaPrepFinished; + +static const NotificationFlags kOneSecondOfMoveFinished = 1; + +static const NotificationFlags kGreenBallNotificationFlags = kOneSecondOfMoveFinished; + +enum { + kButtonDimFrame, + kButtonActiveFrame, + kButtonHighlightedFrame +}; + +enum { + kAlphaSplash, + kAlphaMainMenu, + kDeltaSplash, + kDeltaMainMenu, + kClawMenu, + kPlayingHighlight, + kPuttingClawAway +}; + +// The owning neighborhood must provide an array of longs which hold the various +// extra IDs for moving the claw around. In addition, the owner must tell the sub +// control room interaction what position the claw starts out in (which is also the +// position the claw must be in before leaving). + +// Standard array indices: +enum { + kClawFromAToBIndex, + kClawALoopIndex, + kClawAPinchIndex, + kClawACounterclockwiseIndex, + kClawAClockwiseIndex, + kClawFromBToAIndex, + kClawFromBToCIndex, + kClawFromBToDIndex, + kClawBLoopIndex, + kClawBPinchIndex, + kClawBCounterclockwiseIndex, + kClawBClockwiseIndex, + kClawFromCToBIndex, + kClawCLoopIndex, + kClawCPinchIndex, + kClawCCounterclockwiseIndex, + kClawCClockwiseIndex, + kClawFromDToBIndex, + kClawDLoopIndex, + kClawDPinchIndex, + kClawDCounterclockwiseIndex, + kClawDClockwiseIndex +}; + +// Action indices for s_clawStateTable: +// Can also be used as indices into _buttons (except for kNoActionIndex and kLoopActionIndex). +enum { + kNoActionIndex = -1, + kPinchActionIndex = 0, + kMoveDownActionIndex, + kMoveRightActionIndex, + kMoveLeftActionIndex, + kMoveUpActionIndex, + kCCWActionIndex, + kCWActionIndex, + kLoopActionIndex +}; + +/* + _currentAction and _nextAction: + + At any time, _currentAction contains an action index (defined above). The current + action index is what the claw is doing right now. If the player presses a button + before the current action completes, _nextAction saves the new action and input + is disabled. This has the effect of implementing a queue of commands for the claw + that can save at most one extra command. + + The general strategy for using _currentAction and _nextAction are: + -- If the player presses a claw button and _currentAction is kNoActionIndex, + do the command immediately and set _currentAction accordingly. + -- If the player presses a claw button and _currentAction is not kNoActionIndex, + save the appropriate action index in _nextAction. + -- When a command animation completes, set _nextAction to kNoActionIndex, then + check if _nextAction has a command waiting in it. If so, play the appriate + animation, copy _nextAction into _currentAction and set _nextAction to + kNoActionIndex. + -- If the player tries to get up, disable input (including all claw buttons) until + the player rises. Then, if the claw is in its original position, play the + animation of the player rising. + -- If the claw needs to be put back, play the first move required to put the + claw back by setting _currentAction and playing the appropriate animation. + Leave _nextAction alone. When the animation, completes, check to see if the + claw is in its original position or not. If so, complete the player rising + sequence by playing the rising animation. If not, repeat this whole step. + + Using this general strategy allows the use of a single function, + DispatchClawAction, which can both cause the claw to perform a command and saving + the next command in _nextAction. +*/ + +// Array indexed by [claw position] [action] +// array yields an index into the neighborhood's extra id table for claw animation or -1. +static const int s_clawStateTable[4][8] = { + { + kClawAPinchIndex, + kNoActionIndex, + kNoActionIndex, + kClawFromAToBIndex, + kNoActionIndex, + kClawACounterclockwiseIndex, + kClawAClockwiseIndex, + kClawALoopIndex + }, + { + kClawBPinchIndex, + kNoActionIndex, + kClawFromBToAIndex, + kClawFromBToDIndex, + kClawFromBToCIndex, + kClawBCounterclockwiseIndex, + kClawBClockwiseIndex, + kClawBLoopIndex + }, + { + kClawCPinchIndex, + kClawFromCToBIndex, + kNoActionIndex, + kNoActionIndex, + kNoActionIndex, + kClawCCounterclockwiseIndex, + kClawCClockwiseIndex, + kClawCLoopIndex + }, + { + kClawDPinchIndex, + kNoActionIndex, + kClawFromDToBIndex, + kNoActionIndex, + kNoActionIndex, + kClawDCounterclockwiseIndex, + kClawDClockwiseIndex, + kClawDLoopIndex + } +}; + +// Move directions for s_clawMovieTable: +enum { + kMoveClawDown, + kMoveClawRight, + kMoveClawLeft, + kMoveClawUp +}; + +static const int kClawNowhere = -1; + +// Array indexed by [claw position] [move direction] +// array yields new claw position or -1. +static const int s_clawMovieTable[4][4] = { + { + kClawNowhere, + kClawNowhere, + kClawAtB, + kClawNowhere + }, + { + kClawNowhere, + kClawAtA, + kClawAtD, + kClawAtC + }, + { + kClawAtB, + kClawNowhere, + kClawNowhere, + kClawNowhere + }, + { + kClawNowhere, + kClawAtB, + kClawNowhere, + kClawNowhere + } +}; + +// Indexed by claw action index, claw position, plus 0 for start, 1 for stop. +// (Never indexed with kLoopActionIndex.) +static const TimeValue s_clawMonitorTable[7][4][2] = { + { + { kAPinchStart, kAPinchStop }, + { kBPinchStart, kBPinchStop }, + { kCPinchStart, kCPinchStop }, + { kDPinchStart, kDPinchStop } + }, + { + { 0xffffffff, 0xffffffff }, + { 0xffffffff, 0xffffffff }, + { kCToBStart, kCToBStop }, + { 0xffffffff, 0xffffffff } + }, + { + { 0xffffffff, 0xffffffff }, + { kBToAStart, kBToAStop }, + { 0xffffffff, 0xffffffff }, + { kDToBStart, kDToBStop } + }, + { + { kAToBStart, kAToBStop }, + { kBToDStart, kBToDStop }, + { 0xffffffff, 0xffffffff }, + { 0xffffffff, 0xffffffff } + }, + { + { 0xffffffff, 0xffffffff }, + { kBToCStart, kBToCStop }, + { 0xffffffff, 0xffffffff }, + { 0xffffffff, 0xffffffff } + }, + { + { kACCWStart, kACCWStop }, + { kBCCWStart, kBCCWStop }, + { kCCCWStart, kCCCWStop }, + { kDCCWStart, kDCCWStop } + }, + { + { kACWStart, kACWStop }, + { kBCWStart, kBCWStop }, + { kCCWStart, kCCWStop }, + { kDCWStart, kDCWStop } + } +}; + +// Frame indices for the green ball sprite. +enum { + kGreenBallAtA, + kGreenBallAtAWithClaw, + kGreenBallAtAWithClawAndRobot, + kGreenBallAtB, + kGreenBallAtBWithClaw, + kGreenBallAtBWithClawAndRobot, + kGreenBallAtCArmAtA, + kGreenBallAtCArmAtB, + kGreenBallAtCArmAtD, + kGreenBallAtCWithClaw, + kGreenBallAtD, + kGreenBallAtDWithClaw, + kNumClawGreenBalls +}; + +// State constants for _robotState. +enum { + kNoRobot, + kRobotApproaching, + kPunchingOnce, + kPunchingTwice, + kPunchingThrice, + kCarriedToDoor, + kPlayerWon, + kRobotWon +}; + +// Sub Control Room button PICTs: +static const ResIDType kSubControlButtonBaseID = 500; +static const ResIDType kClawMonitorGreenBallBaseID = 600; + +// Constructor +SubControlRoom::SubControlRoom(Neighborhood *handler) : GameInteraction(kNoradSubControlRoomInteractionID, handler), + _subControlMovie(kSubControlMonitorID), _subControlNotification(kSubControlNotificationID, (PegasusEngine *)g_engine), + _clawMonitorMovie(kClawMonitorID), _pinchButton(kSubControlPinchID), _downButton(kSubControlDownID), + _rightButton(kSubControlRightID), _leftButton(kSubControlLeftID), _upButton(kSubControlUpID), + _ccwButton(kSubControlCCWID), _cwButton(kSubControlCWID), _greenBall(kClawMonitorGreenBallID), + _greenBallNotification(kNoradGreenBallNotificationID, (PegasusEngine *)g_engine) { + _neighborhoodNotification = handler->getNeighborhoodNotification(); + _playingAgainstRobot = false; + _robotState = kNoRobot; +} + +void SubControlRoom::playAgainstRobot() { + _playingAgainstRobot = true; +} + +void SubControlRoom::openInteraction() { + _currentAction = kNoActionIndex; + _nextAction = kNoActionIndex; + + Norad *owner = (Norad *)getOwner(); + owner->getClawInfo(_outSpotID, _prepSpotID, _clawControlSpotID, _clawButtonSpotIDs[0], + _clawButtonSpotIDs[1], _clawButtonSpotIDs[2], _clawButtonSpotIDs[3], + _clawButtonSpotIDs[4], _clawButtonSpotIDs[5], _clawButtonSpotIDs[6], + _clawStartPosition, _clawExtraIDs); + + _clawPosition = _clawStartPosition; + _clawNextPosition = _clawPosition; + _subControlMovie.initFromMovieFile("Images/Norad Alpha/N22 Left Monitor Movie"); + _subControlMovie.setVolume(((PegasusEngine *)g_engine)->getSoundFXLevel()); + _subControlMovie.moveElementTo(kNoradSubControlLeft, kNoradSubControlTop); + _subControlScale = _subControlMovie.getScale(); + _subControlMovie.setDisplayOrder(kSubControlOrder); + _subControlMovie.startDisplaying(); + _subControlCallBack.setNotification(&_subControlNotification); + _subControlCallBack.initCallBack(&_subControlMovie, kCallBackAtExtremes); + + _clawMonitorMovie.initFromMovieFile("Images/Norad Alpha/N22:N60 Right Monitor"); + _clawMonitorMovie.moveElementTo(kNoradClawMonitorLeft, kNoradClawMonitorTop); + _clawMonitorMovie.setDisplayOrder(kClawMonitorOrder); + _clawMonitorMovie.startDisplaying(); + _clawMonitorCallBack.setNotification(&_subControlNotification); + _clawMonitorCallBack.initCallBack(&_clawMonitorMovie, kCallBackAtExtremes); + + _subControlNotification.notifyMe(this, kSubControlNotificationFlags, kSubControlNotificationFlags); + + _neighborhoodNotification->notifyMe(this, kExtraCompletedFlag, kExtraCompletedFlag); + + _buttons[0] = &_pinchButton; + _buttons[1] = &_downButton; + _buttons[2] = &_rightButton; + _buttons[3] = &_leftButton; + _buttons[4] = &_upButton; + _buttons[5] = &_ccwButton; + _buttons[6] = &_cwButton; + + _pinchButton.setDisplayOrder(kSubControlPinchOrder); + _downButton.setDisplayOrder(kSubControlDownOrder); + _rightButton.setDisplayOrder(kSubControlRightOrder); + _leftButton.setDisplayOrder(kSubControlLeftOrder); + _upButton.setDisplayOrder(kSubControlUpOrder); + _ccwButton.setDisplayOrder(kSubControlCCWOrder); + _cwButton.setDisplayOrder(kSubControlCWOrder); + + for (int i = 0; i < kNumClawButtons; i++) { + SpriteFrame *frame = new SpriteFrame(); + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kSubControlButtonBaseID + i * 3, true); + _buttons[i]->addFrame(frame, 0, 0); + + frame = new SpriteFrame(); + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kSubControlButtonBaseID + i * 3 + 1, true); + _buttons[i]->addFrame(frame, 0, 0); + + frame = new SpriteFrame(); + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kSubControlButtonBaseID + i * 3 + 2, true); + _buttons[i]->addFrame(frame, 0, 0); + + _buttons[i]->setCurrentFrameIndex(0); + _buttons[i]->startDisplaying(); + } + + _pinchButton.moveElementTo(kNoradSubControlPinchLeft, kNoradSubControlPinchTop); + _downButton.moveElementTo(kNoradSubControlDownLeft, kNoradSubControlDownTop); + _rightButton.moveElementTo(kNoradSubControlRightLeft, kNoradSubControlRightTop); + _leftButton.moveElementTo(kNoradSubControlLeftLeft, kNoradSubControlLeftTop); + _upButton.moveElementTo(kNoradSubControlUpLeft, kNoradSubControlUpTop); + _ccwButton.moveElementTo(kNoradSubControlCCWLeft, kNoradSubControlCCWTop); + _cwButton.moveElementTo(kNoradSubControlCWLeft, kNoradSubControlCWTop); + + _greenBall.setDisplayOrder(kClawMonitorGreenBallOrder); + + for (int i = 0; i < kNumClawGreenBalls; i++) { + SpriteFrame *frame = new SpriteFrame(); + frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kClawMonitorGreenBallBaseID + i); + _greenBall.addFrame(frame, 0, 0); + } + + _greenBall.setCurrentFrameIndex(0); + _greenBall.startDisplaying(); + + _greenBallTimer.setScale(owner->getNavMovie()->getScale()); + _greenBallCallBack.setNotification(&_greenBallNotification); + _greenBallCallBack.initCallBack(&_greenBallTimer, kCallBackAtExtremes); + _greenBallCallBack.setCallBackFlag(kOneSecondOfMoveFinished); + _greenBallNotification.notifyMe(this, kGreenBallNotificationFlags, kGreenBallNotificationFlags); + + _subControlMovie.show(); + _clawMonitorMovie.show(); +} + +void SubControlRoom::initInteraction() { + if (GameState.getNoradSubPrepState() == kSubDamaged) { + playControlMonitorSection(kDeltaSplashStart * _subControlScale, kDeltaSplashStop * _subControlScale, + 0, kDeltaSplash, false); + playClawMonitorSection(kDeltaClawSplashStart, kDeltaClawSplashStop, kDeltaSplashFinished, _gameState, false); + } else { + playControlMonitorSection(kAlphaSplashStart * _subControlScale, kAlphaSplashStop * _subControlScale, + 0, kAlphaSplash, false); + playClawMonitorSection(kAlphaClawSplashStart, kAlphaClawSplashStop, kAlphaSplashFinished, _gameState, false); + } + + _subControlMovie.redrawMovieWorld(); + _clawMonitorMovie.redrawMovieWorld(); + + GameState.setScoringPlayedWithClaw(true); +} + +void SubControlRoom::closeInteraction() { + _subControlNotification.cancelNotification(this); + _subControlCallBack.releaseCallBack(); + _greenBallNotification.cancelNotification(this); + _greenBallCallBack.releaseCallBack(); + _neighborhoodNotification->cancelNotification(this); +} + +void SubControlRoom::setSoundFXLevel(const uint16 fxLevel) { + _subControlMovie.setVolume(fxLevel); +} + +void SubControlRoom::receiveNotification(Notification *notification, const NotificationFlags flags) { + Norad *owner = (Norad *)getOwner(); + + if (notification == &_subControlNotification) { + switch (flags) { + case kAlphaSplashFinished: + setControlMonitorToTime(kMainMenuTime * _subControlScale, kAlphaMainMenu, true); + break; + case kPrepHighlightFinished: + if (GameState.getNoradSubPrepState() == kSubDamaged) + playControlMonitorSection(kDeltaLaunchPrepStart * _subControlScale, + kDeltaLaunchPrepStop * _subControlScale, kDeltaPrepFinished, _gameState, false); + else + playControlMonitorSection(kAlphaLaunchPrepStart * _subControlScale, + kAlphaLaunchPrepStop * _subControlScale, kAlphaPrepFinished, _gameState, false); + break; + case kAlphaPrepFinished: + GameState.setNoradSubPrepState(kSubPrepped); + GameState.setScoringPreppedSub(true); + setControlMonitorToTime(kMainMenuTime * _subControlScale, kAlphaMainMenu, true); + break; + case kClawHighlightFinished: + playControlMonitorSection(kClawMenuStart * _subControlScale, kClawMenuStop * _subControlScale, + kClawMenuFinished, _gameState, false); + break; + case kClawMenuFinished: + owner->playClawMonitorIntro(); + showButtons(); + setControlMonitorToTime(kClawMenuTime * _subControlScale, kClawMenu, true); + + if (!_playingAgainstRobot) { + updateClawMonitor(); + owner->loopExtraSequence(_clawExtraIDs[s_clawStateTable[_clawPosition][kLoopActionIndex]]); + } + break; + case kDeltaSplashFinished: + setControlMonitorToTime(kMainMenuTime * _subControlScale, kDeltaMainMenu, true); + + if (_playingAgainstRobot) { + _robotState = kRobotApproaching; + playClawMonitorSection(kRobotApproachStart, kRobotApproachStop, 0, _gameState, true); + owner->startExtraSequence(kN60RobotApproaches, kExtraCompletedFlag, kFilterAllInput); + } + break; + case kDeltaPrepFinished: + setControlMonitorToTime(kMainMenuTime * _subControlScale, kDeltaMainMenu, true); + break; + } + } else if (notification == &_greenBallNotification) { + if (_robotState == kRobotWon) { + // We are using the green ball notification to hide stuff when the robot comes through + // the glass. + hideEverything(); + } else { + // We are now midway through a move, time to update the claw's position and the green + // ball. + _clawPosition = _clawNextPosition; + updateClawMonitor(); + updateGreenBall(); + } + } else if (notification == _neighborhoodNotification) { + _currentAction = kNoActionIndex; + if (_playingAgainstRobot) { + switch (_robotState) { + case kRobotApproaching: + if (_gameState == kClawMenu) { + _robotState = kPunchingOnce; + dispatchClawAction(kNoActionIndex); + } else { + robotKillsPlayer(kN60FirstMistake, owner); + } + break; + case kPunchingOnce: + if (_nextAction == kMoveDownActionIndex) { + _robotState = kPunchingTwice; + performActionImmediately(_nextAction, _clawExtraIDs[s_clawStateTable[_clawPosition][_nextAction]], owner); + } else { + robotKillsPlayer(kN60SecondMistake, owner); + } + break; + case kPunchingTwice: + if (_nextAction == kPinchActionIndex) { + _robotState = kPunchingThrice; + performActionImmediately(_nextAction, _clawExtraIDs[s_clawStateTable[_clawPosition][_nextAction]], owner); + } else { + robotKillsPlayer(kN60ThirdMistake, owner); + } + break; + case kPunchingThrice: + if (_nextAction == kMoveRightActionIndex) { + _robotState = kCarriedToDoor; + performActionImmediately(_nextAction, _clawExtraIDs[s_clawStateTable[_clawPosition][_nextAction]], owner); + } else { + robotKillsPlayer(kN60FourthMistake, owner); + } + break; + case kCarriedToDoor: + hideEverything(); + _robotState = kPlayerWon; + owner->startExtraSequence(kN60PlayerFollowsRobotToDoor, kExtraCompletedFlag, kFilterAllInput); + break; + case kPlayerWon: + ((NoradDelta *)owner)->playerBeatRobotWithClaw(); + owner->requestDeleteCurrentInteraction(); + break; + case kRobotWon: + g_system->delayMillis(2 * 1000); // 120 ticks + ((PegasusEngine *)g_engine)->die(kDeathRobotSubControlRoom); + break; + } + } else { + if (_gameState == kPuttingClawAway && _nextAction == kNoActionIndex) { + if (_clawPosition == _clawStartPosition) { + Input scratch; + GameInteraction::clickInHotspot(scratch, g_allHotspots.findHotspotByID(_outSpotID)); + } else { + switch (_clawPosition) { + case kClawAtA: + dispatchClawAction(kMoveLeftActionIndex); + break; + case kClawAtB: + if (_clawStartPosition == kClawAtD) // Norad Alpha + dispatchClawAction(kMoveLeftActionIndex); + else if (_clawStartPosition == kClawAtC) // Norad Delta + dispatchClawAction(kMoveUpActionIndex); + break; + case kClawAtC: + dispatchClawAction(kMoveDownActionIndex); + break; + case kClawAtD: + dispatchClawAction(kMoveRightActionIndex); + break; + } + } + } else { + dispatchClawAction(_nextAction); + } + } + } +} + +void SubControlRoom::hideEverything() { + hideButtons(); + _subControlMovie.hide(); + _clawMonitorMovie.hide(); + _greenBall.hide(); +} + +void SubControlRoom::robotKillsPlayer(const uint32 extraID, Neighborhood *owner) { + _robotState = kRobotWon; + owner->startExtraSequence(extraID, kExtraCompletedFlag, kFilterAllInput); + _greenBallTimer.stop(); + _greenBallTimer.setSegment(0, 32 * _greenBallTimer.getScale() / 15); + _greenBallTimer.setTime(0); + _greenBallCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _greenBallTimer.start(); +} + +void SubControlRoom::activateHotspots() { + if (_robotState == kRobotWon || _robotState == kPlayerWon) + return; + + GameInteraction::activateHotspots(); + + switch (_gameState) { + case kAlphaMainMenu: + case kDeltaMainMenu: + g_allHotspots.activateOneHotspot(_prepSpotID); + g_allHotspots.activateOneHotspot(_clawControlSpotID); + break; + case kClawMenu: + // This could be called during a move, so use _clawNextPosition. + if (_playingAgainstRobot) { + g_allHotspots.deactivateOneHotspot(_outSpotID); + if (_robotState != kRobotApproaching && _nextAction == kNoActionIndex) + for (int i = 0; i < kNumClawButtons; i++) + if (s_clawStateTable[_clawNextPosition][i] != kNoActionIndex) + g_allHotspots.activateOneHotspot(_clawButtonSpotIDs[i]); + } else if (_nextAction == kNoActionIndex) { + for (int i = 0; i < kNumClawButtons; i++) + if (s_clawStateTable[_clawNextPosition][i] != kNoActionIndex) + g_allHotspots.activateOneHotspot(_clawButtonSpotIDs[i]); + } + break; + default: + break; + } +} + +void SubControlRoom::showButtons() { + if (_playingAgainstRobot && _robotState == kRobotApproaching) { + for (int i = 0; i < kNumClawButtons; i++) { + _buttons[i]->show(); + _buttons[i]->setCurrentFrameIndex(kButtonDimFrame); + } + } else if (_nextAction != kNoActionIndex) { + for (int i = 0; i < kNumClawButtons; i++) { + _buttons[i]->show(); + if (i == _currentAction || i == _nextAction) + _buttons[i]->setCurrentFrameIndex(kButtonHighlightedFrame); + else + _buttons[i]->setCurrentFrameIndex(kButtonDimFrame); + } + } else { + for (int i = 0; i < kNumClawButtons; i++) { + _buttons[i]->show(); + if (i == _currentAction) + _buttons[i]->setCurrentFrameIndex(kButtonHighlightedFrame); + else if (s_clawStateTable[_clawNextPosition][i] != kNoActionIndex && + _gameState != kPuttingClawAway) // this could be called during a move, so check _clawNextPosition + _buttons[i]->setCurrentFrameIndex(kButtonActiveFrame); + else + _buttons[i]->setCurrentFrameIndex(kButtonDimFrame); + } + } +} + +void SubControlRoom::hideButtons() { + for (int i = 0; i < kNumClawButtons; i++) + _buttons[i]->hide(); +} + +int SubControlRoom::findActionIndex(HotSpotID id) { + for (int i = 0; i < kNumClawButtons; i++) + if (id == _clawButtonSpotIDs[i]) + return i; + + return kNoActionIndex; +} + +void SubControlRoom::clickInHotspot(const Input &input, const Hotspot *spot) { + HotSpotID clickedID = spot->getObjectID(); + int actionIndex = findActionIndex(clickedID); + + if (actionIndex != kNoActionIndex) { + dispatchClawAction(actionIndex); + } else if (clickedID == _prepSpotID) { + playControlMonitorSection(kLaunchPrepHighlightStart * _subControlScale, + kLaunchPrepHighlightStop * _subControlScale, + kPrepHighlightFinished, kPlayingHighlight, false); + } else if (clickedID == _clawControlSpotID) { + playControlMonitorSection(kClawControlHighlightStart * _subControlScale, + kClawControlHighlightStop * _subControlScale, + kClawHighlightFinished, kPlayingHighlight, false); + } else if (clickedID == _outSpotID) { + _gameState = kPuttingClawAway; + + if (_currentAction == kNoActionIndex) { + if (_clawPosition == _clawStartPosition) { + GameInteraction::clickInHotspot(input, spot); + } else { + switch (_clawPosition) { + case kClawAtA: + dispatchClawAction(kMoveLeftActionIndex); + break; + case kClawAtB: + if (_clawStartPosition == kClawAtD) // Norad Alpha + dispatchClawAction(kMoveLeftActionIndex); + else if (_clawStartPosition == kClawAtC) // Norad Delta + dispatchClawAction(kMoveUpActionIndex); + break; + case kClawAtC: + dispatchClawAction(kMoveDownActionIndex); + break; + case kClawAtD: + dispatchClawAction(kMoveRightActionIndex); + break; + } + } + } + } else { + GameInteraction::clickInHotspot(input, spot); + } +} + +void SubControlRoom::dispatchClawAction(const int newAction) { + Neighborhood *owner = getOwner(); + + if (newAction == kNoActionIndex) { + _currentAction = kNoActionIndex; + _nextAction = kNoActionIndex; + showButtons(); + updateGreenBall(); + + if (_playingAgainstRobot) + owner->startExtraSequence(kN60ArmActivated, kExtraCompletedFlag, kFilterAllInput); + else + owner->loopExtraSequence(_clawExtraIDs[s_clawStateTable[_clawPosition][kLoopActionIndex]]); + } else { + if (_currentAction == kNoActionIndex) { + if (_playingAgainstRobot) { + _nextAction = newAction; + showButtons(); + updateGreenBall(); + } else { + performActionImmediately(newAction, _clawExtraIDs[s_clawStateTable[_clawPosition][newAction]], owner); + } + } else if (_nextAction == kNoActionIndex) { + _nextAction = newAction; + showButtons(); + updateGreenBall(); + } + } +} + +void SubControlRoom::performActionImmediately(const int action, const uint32 extraID, Neighborhood *owner) { + _currentAction = action; + _nextAction = kNoActionIndex; + ExtraTable::Entry entry; + + switch (action) { + case kMoveDownActionIndex: + case kMoveRightActionIndex: + case kMoveLeftActionIndex: + case kMoveUpActionIndex: + // Set up green ball callback. + owner->getExtraEntry(extraID, entry); + _greenBallTimer.stop(); + _greenBallTimer.setSegment(entry.movieStart, entry.movieStart + owner->getNavMovie()->getScale()); + _greenBallTimer.setTime(entry.movieStart); + _greenBallCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + // Start move. + _greenBallTimer.start(); + break; + } + + if (_playingAgainstRobot) { + switch (_robotState) { + case kPunchingTwice: + owner->startExtraSequence(kN60ArmToPositionB, kExtraCompletedFlag, kFilterAllInput); + break; + case kPunchingThrice: + owner->startExtraSequence(kN60ArmGrabsRobot, kExtraCompletedFlag, kFilterAllInput); + break; + case kCarriedToDoor: + owner->startExtraSequence(kN60ArmCarriesRobotToPositionA, kExtraCompletedFlag, kFilterAllInput); + break; + } + } else { + owner->startExtraSequence(extraID, kExtraCompletedFlag, kFilterAllInput); + } + + switch (action) { + case kMoveDownActionIndex: + _clawNextPosition = s_clawMovieTable[_clawPosition][kMoveClawDown]; + break; + case kMoveRightActionIndex: + _clawNextPosition = s_clawMovieTable[_clawPosition][kMoveClawRight]; + break; + case kMoveLeftActionIndex: + _clawNextPosition = s_clawMovieTable[_clawPosition][kMoveClawLeft]; + break; + case kMoveUpActionIndex: + _clawNextPosition = s_clawMovieTable[_clawPosition][kMoveClawUp]; + break; + case kLoopActionIndex: + // Do nothing. + break; + default: + playClawMonitorSection(s_clawMonitorTable[action][_clawPosition][0], + s_clawMonitorTable[action][_clawPosition][1], 0, _gameState, true); + break; + } + + showButtons(); + updateGreenBall(); +} + +void SubControlRoom::setControlMonitorToTime(const TimeValue newTime, const int newState, const bool shouldAllowInput) { + _subControlMovie.stop(); + _subControlMovie.setSegment(0, _subControlMovie.getDuration()); + _subControlMovie.setTime(newTime); + _subControlMovie.redrawMovieWorld(); + _gameState = newState; + allowInput(shouldAllowInput); +} + +void SubControlRoom::playControlMonitorSection(const TimeValue in, const TimeValue out, const NotificationFlags flags, + const int newState, const bool shouldAllowInput) { + _subControlMovie.stop(); + _subControlMovie.setSegment(in, out); + _subControlMovie.setTime(in); + + if (flags != 0) { + _subControlCallBack.setCallBackFlag(flags); + _subControlCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + } + + _gameState = newState; + allowInput(shouldAllowInput); + _subControlMovie.start(); +} + +void SubControlRoom::updateClawMonitor() { + switch (_clawPosition) { + case kClawAtA: + setClawMonitorToTime(kClawAtATime); + break; + case kClawAtB: + setClawMonitorToTime(kClawAtBTime); + break; + case kClawAtC: + setClawMonitorToTime(kClawAtCTime); + break; + case kClawAtD: + setClawMonitorToTime(kClawAtDTime); + break; + } +} + +void SubControlRoom::setClawMonitorToTime(const TimeValue newTime) { + _clawMonitorMovie.stop(); + _clawMonitorMovie.setSegment(0, _clawMonitorMovie.getDuration()); + _clawMonitorMovie.setTime(newTime); + _clawMonitorMovie.redrawMovieWorld(); +} + +void SubControlRoom::playClawMonitorSection(const TimeValue in, const TimeValue out, const NotificationFlags flags, + const int newState, const bool shouldAllowInput) { + _clawMonitorMovie.stop(); + _clawMonitorMovie.setSegment(in, out); + _clawMonitorMovie.setTime(in); + + if (flags != 0) { + _clawMonitorCallBack.setCallBackFlag(flags); + _clawMonitorCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + } + + _gameState = newState; + allowInput(shouldAllowInput); + _clawMonitorMovie.start(); +} + +void SubControlRoom::updateGreenBall() { + switch (_currentAction) { + case kMoveDownActionIndex: + switch (_nextAction) { + case kMoveRightActionIndex: + moveGreenBallToA(); + break; + case kMoveLeftActionIndex: + moveGreenBallToD(); + break; + case kMoveUpActionIndex: + moveGreenBallToC(); + break; + default: + moveGreenBallToB(); + break; + } + break; + case kMoveRightActionIndex: + if (_clawNextPosition == kClawAtA) { + switch (_nextAction) { + case kMoveLeftActionIndex: + moveGreenBallToB(); + break; + default: + moveGreenBallToA(); + break; + } + } else { + switch (_nextAction) { + case kMoveRightActionIndex: + moveGreenBallToA(); + break; + case kMoveLeftActionIndex: + moveGreenBallToD(); + break; + case kMoveUpActionIndex: + moveGreenBallToC(); + break; + default: + moveGreenBallToB(); + break; + } + } + break; + case kMoveLeftActionIndex: + if (_clawNextPosition == kClawAtB) { + switch (_nextAction) { + case kMoveRightActionIndex: + moveGreenBallToA(); + break; + case kMoveLeftActionIndex: + moveGreenBallToD(); + break; + case kMoveUpActionIndex: + moveGreenBallToC(); + break; + default: + moveGreenBallToB(); + break; + } + } else { + switch (_nextAction) { + case kMoveRightActionIndex: + moveGreenBallToB(); + break; + default: + moveGreenBallToD(); + break; + } + } + break; + case kMoveUpActionIndex: + switch (_nextAction) { + case kMoveDownActionIndex: + moveGreenBallToB(); + break; + default: + moveGreenBallToC(); + break; + } + break; + default: + switch (_nextAction) { + case kMoveDownActionIndex: + moveGreenBallToB(); + break; + case kMoveRightActionIndex: + if (_clawPosition == kClawAtB) + moveGreenBallToA(); + else + moveGreenBallToB(); + break; + case kMoveLeftActionIndex: + if (_clawPosition == kClawAtB) + moveGreenBallToD(); + else + moveGreenBallToB(); + break; + case kMoveUpActionIndex: + moveGreenBallToC(); + break; + default: + _greenBall.hide(); + break; + } + break; + } +} + +void SubControlRoom::moveGreenBallToA() { + if (_clawPosition == kClawAtA) { + if (_playingAgainstRobot) + _greenBall.setCurrentFrameIndex(kGreenBallAtAWithClawAndRobot); + else + _greenBall.setCurrentFrameIndex(kGreenBallAtAWithClaw); + } else { + _greenBall.setCurrentFrameIndex(kGreenBallAtA); + } + + _greenBall.moveElementTo(kNoradGreenBallAtALeft, kNoradGreenBallAtATop); + _greenBall.show(); +} + +void SubControlRoom::moveGreenBallToB() { + if (_clawPosition == kClawAtB) { + if (_playingAgainstRobot) + _greenBall.setCurrentFrameIndex(kGreenBallAtBWithClawAndRobot); + else + _greenBall.setCurrentFrameIndex(kGreenBallAtBWithClaw); + } else { + _greenBall.setCurrentFrameIndex(kGreenBallAtB); + } + + _greenBall.moveElementTo(kNoradGreenBallAtBLeft, kNoradGreenBallAtBTop); + _greenBall.show(); +} + +void SubControlRoom::moveGreenBallToC() { + switch (_clawPosition) { + case kClawAtA: + _greenBall.setCurrentFrameIndex(kGreenBallAtCArmAtA); + break; + case kClawAtB: + _greenBall.setCurrentFrameIndex(kGreenBallAtCArmAtB); + break; + case kClawAtC: + _greenBall.setCurrentFrameIndex(kGreenBallAtCWithClaw); + break; + case kClawAtD: + _greenBall.setCurrentFrameIndex(kGreenBallAtCArmAtD); + break; + } + + _greenBall.moveElementTo(kNoradGreenBallAtCLeft, kNoradGreenBallAtCTop); + _greenBall.show(); +} + +void SubControlRoom::moveGreenBallToD() { + if (_clawPosition == kClawAtD) + _greenBall.setCurrentFrameIndex(kGreenBallAtDWithClaw); + else + _greenBall.setCurrentFrameIndex(kGreenBallAtD); + + _greenBall.moveElementTo(kNoradGreenBallAtDLeft, kNoradGreenBallAtDTop); + _greenBall.show(); +} + +bool SubControlRoom::canSolve() { + return _playingAgainstRobot && _robotState < kCarriedToDoor; +} + +void SubControlRoom::doSolve() { + _robotState = kCarriedToDoor; + hideEverything(); + getOwner()->startExtraSequence(kN60ArmGrabsRobot, kExtraCompletedFlag, kFilterAllInput); +} + +InputBits SubControlRoom::getInputFilter() { + if (_playingAgainstRobot) + return GameInteraction::getInputFilter() & ~kFilterDownButtonAny; + + return GameInteraction::getInputFilter(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/norad/subcontrolroom.h b/engines/pegasus/neighborhood/norad/subcontrolroom.h new file mode 100644 index 0000000000..3ee729b302 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/subcontrolroom.h @@ -0,0 +1,133 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_NORAD_SUBCONTROLROOM_H +#define PEGASUS_NEIGHBORHOOD_NORAD_SUBCONTROLROOM_H + +#include "pegasus/interaction.h" +#include "pegasus/notification.h" + +namespace Pegasus { + +static const uint32 kClawAtA = 0; +static const uint32 kClawAtB = 1; +static const uint32 kClawAtC = 2; +static const uint32 kClawAtD = 3; + +static const int kNumClawButtons = 7; + +class Norad; + +class SubControlRoom : public GameInteraction, public NotificationReceiver { +public: + SubControlRoom(Neighborhood *); + virtual ~SubControlRoom() {} + + void playAgainstRobot(); + + virtual void setSoundFXLevel(const uint16); + + bool canSolve(); + void doSolve(); + +protected: + virtual void openInteraction(); + virtual void initInteraction(); + virtual void closeInteraction(); + + virtual void activateHotspots(); + virtual void clickInHotspot(const Input &, const Hotspot *); + + virtual void receiveNotification(Notification *, const NotificationFlags); + + void robotKillsPlayer(const uint32, Neighborhood *); + InputBits getInputFilter(); + + int findActionIndex(HotSpotID); + void dispatchClawAction(const int); + void performActionImmediately(const int, const uint32, Neighborhood *); + + void hideEverything(); + void showButtons(); + void hideButtons(); + + void updateGreenBall(); + void moveGreenBallToA(); + void moveGreenBallToB(); + void moveGreenBallToC(); + void moveGreenBallToD(); + + void setControlMonitorToTime(const TimeValue, const int, const bool); + void playControlMonitorSection(const TimeValue, const TimeValue, const NotificationFlags, + const int, const bool); + + void updateClawMonitor(); + void setClawMonitorToTime(const TimeValue); + void playClawMonitorSection(const TimeValue, const TimeValue, const NotificationFlags, + const int, const bool); + + Movie _subControlMovie; + TimeScale _subControlScale; + Notification _subControlNotification; + NotificationCallBack _subControlCallBack; + Movie _clawMonitorMovie; + NotificationCallBack _clawMonitorCallBack; + int _gameState; + uint32 _clawStartPosition; + uint32 _clawPosition; + uint32 _clawNextPosition; + const uint32 *_clawExtraIDs; + + int _currentAction; + int _nextAction; + + Sprite *_buttons[kNumClawButtons]; + Sprite _pinchButton; + Sprite _downButton; + Sprite _rightButton; + Sprite _leftButton; + Sprite _upButton; + Sprite _ccwButton; + Sprite _cwButton; + + Sprite _greenBall; + TimeBase _greenBallTimer; + Notification _greenBallNotification; + NotificationCallBack _greenBallCallBack; + + HotSpotID _outSpotID; + HotSpotID _prepSpotID; + HotSpotID _clawControlSpotID; + HotSpotID _clawButtonSpotIDs[kNumClawButtons]; + + Notification *_neighborhoodNotification; + + bool _playingAgainstRobot; + int _robotState; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/subplatform.cpp b/engines/pegasus/neighborhood/norad/subplatform.cpp new file mode 100644 index 0000000000..bfe93ea22c --- /dev/null +++ b/engines/pegasus/neighborhood/norad/subplatform.cpp @@ -0,0 +1,205 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/norad/norad.h" +#include "pegasus/neighborhood/norad/subplatform.h" +#include "pegasus/neighborhood/norad/alpha/noradalpha.h" + +namespace Pegasus { + +// As usual, times here are in seconds. + +static const TimeValue kNormalSplashStart = 0; +static const TimeValue kNormalSplashStop = 5; + +static const TimeValue kPrepSubStart = 5; +static const TimeValue kPrepSubStop = 15; + +static const TimeValue kPrepIncompleteStart = 15; +static const TimeValue kPrepIncompleteStop = 19; + +static const TimeValue kDamagedStart = 19; +static const TimeValue kDamagedStop = 28; + +static const NotificationFlags kNormalSplashFinished = 1; +static const NotificationFlags kPrepSubFinished = kNormalSplashFinished << 1; +static const NotificationFlags kPrepIncompleteFinished = kPrepSubFinished << 1; +static const NotificationFlags kDamagedFinished = kPrepIncompleteFinished << 1; + +static const NotificationFlags kPlatformNotificationFlags = kNormalSplashFinished | + kPrepSubFinished | + kPrepIncompleteFinished | + kDamagedFinished; + +static const uint16 kSubPreppedBit = (1 << 0); +static const uint16 kWaitingForPlayerBit = (1 << 1); + +SubPlatform::SubPlatform(Neighborhood *handler) : GameInteraction(kNoradSubPlatformInteractionID, handler), + _platformMovie(kPlatformMonitorID), _platformNotification(kNoradSubPlatformNotificationID, (PegasusEngine *)g_engine) { + _neighborhoodNotification = handler->getNeighborhoodNotification(); +} + +void SubPlatform::openInteraction() { + _stateBits = 0; + + // TODO: These next two lines seem unused? + if (GameState.getNoradSubPrepState() == kSubPrepped) + _stateBits |= kSubPreppedBit; + + _stateBits |= kWaitingForPlayerBit; + _platformMovie.initFromMovieFile("Images/Norad Alpha/Platform Monitor Movie"); + _platformMovie.setVolume(((PegasusEngine *)g_engine)->getSoundFXLevel()); + _platformMovie.moveElementTo(kNoradPlatformLeft, kNoradPlatformTop); + _platformScale = _platformMovie.getScale(); + _platformMovie.setDisplayOrder(kPlatformOrder); + _platformMovie.startDisplaying(); + _platformCallBack.setNotification(&_platformNotification); + _platformCallBack.initCallBack(&_platformMovie, kCallBackAtExtremes); + + _platformNotification.notifyMe(this, kPlatformNotificationFlags, kPlatformNotificationFlags); +} + +void SubPlatform::initInteraction() { + _neighborhoodNotification->notifyMe(this, kExtraCompletedFlag, kExtraCompletedFlag); +} + +void SubPlatform::closeInteraction() { + _platformNotification.cancelNotification(this); + _platformCallBack.releaseCallBack(); + _neighborhoodNotification->cancelNotification(this); +} + +void SubPlatform::setSoundFXLevel(const uint16 fxLevel) { + _platformMovie.setVolume(fxLevel); +} + +void SubPlatform::receiveNotification(Notification *notification, const NotificationFlags flags) { + FaderMoveSpec loop1Spec, loop2Spec; + ExtraTable::Entry entry; + + Norad *owner = (Norad *)getOwner(); + + if (notification == &_platformNotification) { + switch (flags) { + case kNormalSplashFinished: + _platformMovie.stop(); + switch (GameState.getNoradSubPrepState()) { + case kSubNotPrepped: + _platformMovie.setSegment(kPrepIncompleteStart * _platformScale, kPrepIncompleteStop * _platformScale); + _platformMovie.setTime(kPrepIncompleteStart * _platformScale); + _platformCallBack.setCallBackFlag(kPrepIncompleteFinished); + _platformCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _platformMovie.start(); + break; + case kSubPrepped: + _platformMovie.setSegment(kPrepSubStart * _platformScale, kPrepSubStop * _platformScale); + _platformMovie.setTime(kPrepSubStart * _platformScale); + _platformCallBack.setCallBackFlag(kPrepSubFinished); + _platformCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + owner->startExtraSequence(kNorad19PrepSub, 0, kFilterNoInput); + _platformMovie.start(); + break; + case kSubDamaged: + // Shouldn't happen. + break; + } + break; + case kPrepSubFinished: + _platformMovie.stop(); + _platformMovie.stopDisplaying(); + + owner->getExtraEntry(kNorad19ExitToSub, entry); + + loop1Spec.makeTwoKnotFaderSpec(kNoradAlphaMovieScale, 0, kNoradWarningVolume, + entry.movieEnd - entry.movieStart, 0); + loop1Spec.insertFaderKnot(4560, kNoradWarningVolume); + loop1Spec.insertFaderKnot(5080, 0); + + loop2Spec.makeTwoKnotFaderSpec(kNoradAlphaMovieScale, 0, kNoradSuckWindVolume, + entry.movieEnd - entry.movieStart, 0); + loop1Spec.insertFaderKnot(4560, kNoradSuckWindVolume); + loop1Spec.insertFaderKnot(5080, 0); + + owner->startExtraSequence(kNorad19ExitToSub, kExtraCompletedFlag, kFilterNoInput); + + owner->startLoop1Fader(loop1Spec); + owner->startLoop2Fader(loop2Spec); + break; + case kPrepIncompleteFinished: + ((NoradAlpha *)owner)->setSubPrepFailed(true); + g_AIArea->checkMiddleArea(); + // Fall through... + case kDamagedFinished: + _platformMovie.stop(); + _platformMovie.hide(); + _stateBits |= kWaitingForPlayerBit; + allowInput(true); + break; + } + } else if (notification == _neighborhoodNotification) { + allowInput(true); + ((PegasusEngine *)g_engine)->jumpToNewEnvironment(kNoradSubChaseID, kNoRoomID, kNoDirection); + GameState.setScoringEnteredSub(true); + } +} + +void SubPlatform::activateHotspots() { + if (_stateBits & kWaitingForPlayerBit) + g_allHotspots.activateOneHotspot(kNorad19ActivateMonitorSpotID); + + GameInteraction::activateHotspots(); +} + +void SubPlatform::clickInHotspot(const Input &input, const Hotspot *spot) { + if (spot->getObjectID() == kNorad19ActivateMonitorSpotID) { + if (GameState.getNoradSubPrepState() == kSubDamaged) { + _platformMovie.setSegment(kDamagedStart * _platformScale, kDamagedStop * _platformScale); + _platformMovie.setTime(kDamagedStart * _platformScale); + _platformCallBack.setCallBackFlag(kDamagedFinished); + } else { + _platformMovie.setSegment(kNormalSplashStart * _platformScale, kNormalSplashStop * _platformScale); + _platformMovie.setTime(kNormalSplashStart * _platformScale); + _platformCallBack.setCallBackFlag(kNormalSplashFinished); + } + + _platformCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + + _platformMovie.show(); + _platformMovie.start(); + _platformMovie.redrawMovieWorld(); + + _stateBits &= ~kWaitingForPlayerBit; + + allowInput(false); + } else { + GameInteraction::clickInHotspot(input, spot); + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/norad/subplatform.h b/engines/pegasus/neighborhood/norad/subplatform.h new file mode 100644 index 0000000000..a83487db23 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/subplatform.h @@ -0,0 +1,63 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_NORAD_SUBPLATFORM_H +#define PEGASUS_NEIGHBORHOOD_NORAD_SUBPLATFORM_H + +#include "pegasus/interaction.h" +#include "pegasus/movie.h" +#include "pegasus/notification.h" +#include "pegasus/timers.h" + +namespace Pegasus { + +class SubPlatform : public GameInteraction, public NotificationReceiver { +public: + SubPlatform(Neighborhood *); + virtual ~SubPlatform() {} + + virtual void setSoundFXLevel(const uint16); + +protected: + virtual void openInteraction(); + virtual void initInteraction(); + virtual void closeInteraction(); + + virtual void activateHotspots(); + virtual void clickInHotspot(const Input &, const Hotspot *); + + virtual void receiveNotification(Notification *, const NotificationFlags); + + Movie _platformMovie; + TimeScale _platformScale; + Notification _platformNotification; + NotificationCallBack _platformCallBack; + Notification *_neighborhoodNotification; + uint16 _stateBits; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/prehistoric/prehistoric.cpp b/engines/pegasus/neighborhood/prehistoric/prehistoric.cpp new file mode 100644 index 0000000000..b67d31ad95 --- /dev/null +++ b/engines/pegasus/neighborhood/prehistoric/prehistoric.cpp @@ -0,0 +1,689 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/compass.h" +#include "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_action.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/ai/ai_condition.h" +#include "pegasus/ai/ai_rule.h" +#include "pegasus/neighborhood/prehistoric/prehistoric.h" + +namespace Pegasus { + +static const int16 s_prehistoricCompass[kPrehistoric25 + 1][4] = { + { 0, 170, 90, 270 }, // kPrehistoric01 + { 0, 180, 90, 270 }, // kPrehistoric02 + { 10, 180, 90, 270 }, // kPrehistoric03 + { 10, 190, 90, 270 }, // kPrehistoric04 + { 10, 195, 90, 270 }, // kPrehistoric05 + { 20, 210, 90, 270 }, // kPrehistoric06 + { 20, 200, 130, 276 }, // kPrehistoric07 + { 20, 176, 110, 260 }, // kPrehistoric08 + { 20, 200, 100, 270 }, // kPrehistoric09 + { 14, 186, 100, 280 }, // kPrehistoric10 + { 26, 206, 116, 296 }, // kPrehistoric11 + { 60, 226, 140, 320 }, // kPrehistoric12 + { 0, 180, 80, 270 }, // kPrehistoric13 + { 14, 200, 106, 286 }, // kPrehistoric14 + { -10, 174, 80, 260 }, // kPrehistoric15 + { 54, 236, 140, 210 }, // kPrehistoric16 + { -24, 160, 70, 250 }, // kPrehistoric17 + { 26, 206, 140, 296 }, // kPrehistoric18 + { -16, 160, 70, 250 }, // kPrehistoric19 + { -16, 160, 70, 250 }, // kPrehistoric20 + { -10, 160, 90, 250 }, // kPrehistoric21 + { -20, 160, 70, 244 }, // kPrehistoric22 + { -20, 160, 70, 244 }, // kPrehistoric22North + { 60, 234, 150, 330 }, // kPrehistoric23 + { 50, 230, 140, 320 }, // kPrehistoric24 + { 60, 240, 140, 330 } // kPrehistoric25 +}; + +static const TimeValue kPrehistoricFlashlightClickIn = 0; +static const TimeValue kPrehistoricFlashlightClickOut = 138; + +static const TimeValue kPrehistoricBumpIntoWallIn = 138; +static const TimeValue kPrehistoricBumpIntoWallOut = 291; + +static const TimeValue kBridgeRetractIn = 291; +static const TimeValue kBridgeRetractOut = 1499; + +static const TimeValue kPrehistoricWarningTimeLimit = kTenMinutes; + +Prehistoric::Prehistoric(InputHandler *nextHandler, PegasusEngine *owner) : Neighborhood(nextHandler, owner, "Prehistoric", kPrehistoricID) { + setIsItemTaken(kHistoricalLog); +} + +uint16 Prehistoric::getDateResID() const { + return kDatePrehistoricID; +} + +void Prehistoric::init() { + Neighborhood::init(); + + // Forces a stop so the flashlight can turn off... + forceStridingStop(kPrehistoric12, kSouth, kNoAlternateID); +} + +void Prehistoric::start() { + if (g_energyMonitor) { + g_energyMonitor->stopEnergyDraining(); + g_energyMonitor->restoreLastEnergyValue(); + _vm->resetEnergyDeathReason(); + g_energyMonitor->startEnergyDraining(); + } + + Neighborhood::start(); +} + +class FinishPrehistoricAction : public AIPlayMessageAction { +public: + FinishPrehistoricAction() : AIPlayMessageAction("Images/AI/Prehistoric/XP25W", false) {} + ~FinishPrehistoricAction() {} + + void performAIAction(AIRule *); + +}; + +void FinishPrehistoricAction::performAIAction(AIRule *rule) { + AIPlayMessageAction::performAIAction(rule); + ((PegasusEngine *)g_engine)->die(kPlayerWonGame); +} + +void Prehistoric::setUpAIRules() { + Neighborhood::setUpAIRules(); + + if (g_AIArea) { + if (_vm->isDemo()) { + FinishPrehistoricAction *doneAction = new FinishPrehistoricAction(); + AIHasItemCondition *hasLogCondition = new AIHasItemCondition(kHistoricalLog); + AIRule *rule = new AIRule(hasLogCondition, doneAction); + g_AIArea->addAIRule(rule); + } else { + AIPlayMessageAction *messageAction = new AIPlayMessageAction("Images/AI/Prehistoric/XP1NB", false); + AILocationCondition *locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kPrehistoric16, kNorth)); + AIRule *rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Prehistoric/XP2SB", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kPrehistoric01, kSouth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Prehistoric/XP2SB", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kPrehistoric08, kEast)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Prehistoric/XP2SB", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kPrehistoric25, kWest)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Prehistoric/XP16NB", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kPrehistoric23, kNorth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Prehistoric/XP18NB", false); + AITimerCondition *timerCondition = new AITimerCondition(kPrehistoricWarningTimeLimit, 1, true); + rule = new AIRule(timerCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Prehistoric/XP25W", false); + AIHasItemCondition *hasLogCondition = new AIHasItemCondition(kHistoricalLog); + rule = new AIRule(hasLogCondition, messageAction); + g_AIArea->addAIRule(rule); + } + } +} + +TimeValue Prehistoric::getViewTime(const RoomID room, const DirectionConstant direction) { + ExtraTable::Entry extra; + uint32 extraID = 0xffffffff; + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kPrehistoric02, kSouth): + if (!GameState.getPrehistoricSeenTimeStream()) { + getExtraEntry(kPreArrivalFromTSA, extra); + return extra.movieStart; + } + break; + case MakeRoomView(kPrehistoric25, kEast): + if (_privateFlags.getFlag(kPrehistoricPrivateVaultOpenFlag)) { + if (_vm->itemInLocation(kHistoricalLog, kPrehistoricID, kPrehistoric25, kEast)) + extraID = kPre25EastViewWithLog; + else + extraID = kPre25EastViewNoLog; + } + break; + } + + if (extraID == 0xffffffff) + return Neighborhood::getViewTime(room, direction); + + getExtraEntry(extraID, extra); + return extra.movieEnd - 1; +} + + +void Prehistoric::findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &entry) { + Neighborhood::findSpotEntry(room, direction, flags, entry); + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kPrehistoric01, kSouth): + case MakeRoomView(kPrehistoric25, kSouth): + entry.clear(); + break; + case MakeRoomView(kPrehistoric01, kEast): + if (GameState.getPrehistoricSeenFlyer1()) + entry.clear(); + else + GameState.setPrehistoricSeenFlyer1(true); + break; + case MakeRoomView(kPrehistoric08, kEast): + if (GameState.getPrehistoricSeenFlyer2()) + entry.clear(); + else + GameState.setPrehistoricSeenFlyer2(true); + break; + } +} + +int16 Prehistoric::getStaticCompassAngle(const RoomID room, const DirectionConstant dir) { + if (room == kPrehistoricDeath) + return g_compass->getFaderValue(); + + return s_prehistoricCompass[room][dir]; +} + +void Prehistoric::getExitCompassMove(const ExitTable::Entry &exitEntry, FaderMoveSpec &compassMove) { + uint32 angle; + Neighborhood::getExitCompassMove(exitEntry, compassMove); + + switch (MakeRoomView(exitEntry.room, exitEntry.direction)) { + case MakeRoomView(kPrehistoric01, kNorth): + compassMove.insertFaderKnot(exitEntry.movieStart + (exitEntry.movieEnd - exitEntry.movieStart) / 2, -10); + break; + case MakeRoomView(kPrehistoric06, kEast): + compassMove.insertFaderKnot(exitEntry.movieStart + (exitEntry.movieEnd - exitEntry.movieStart) / 4, 95); + compassMove.insertFaderKnot(exitEntry.movieStart + (exitEntry.movieEnd - exitEntry.movieStart) / 4 * 1, 100); + break; + case MakeRoomView(kPrehistoric18, kEast): + if (getCurrentAlternate() == kAltPrehistoricBridgeSet) { + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 11, 145); + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 26, 145); + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 39, 148); + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 114, 140); + } else { + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 10, 140); + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 16, 145); + compassMove.insertFaderKnot(exitEntry.movieEnd, 145); + } + break; + case MakeRoomView(kPrehistoric23, kWest): + angle = compassMove.getNthKnotValue(0); + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 17, angle); + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 32, angle - 90); + compassMove.insertFaderKnot(exitEntry.movieEnd, angle - 90); + break; + } +} + +void Prehistoric::turnTo(const DirectionConstant newDirection) { + setCurrentAlternate(kAltPrehistoricNormal); + _privateFlags.setFlag(kPrehistoricPrivateVaultOpenFlag, false); + Neighborhood::turnTo(newDirection); + + Item *keyCard; + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kPrehistoric18, kEast): + zoomToVault(); + break; + case MakeRoomView(kPrehistoric18, kNorth): + case MakeRoomView(kPrehistoric18, kSouth): + if (_privateFlags.getFlag(kPrehistoricPrivateExtendedBridgeFlag)) { + playSpotSoundSync(kBridgeRetractIn, kBridgeRetractOut); + _privateFlags.setFlag(kPrehistoricPrivateExtendedBridgeFlag, false); + loadAmbientLoops(); + } + // fall through + case MakeRoomView(kPrehistoric25, kEast): + setCurrentActivation(kActivationVaultClosed); + break; + case MakeRoomView(kPrehistoric16, kNorth): + case MakeRoomView(kPrehistoric21, kWest): + keyCard = g_allItems.findItemByID(kKeyCard); + if (keyCard->getItemState() == kFlashlightOff) { + keyCard->setItemState(kFlashlightOn); + playSpotSoundSync(kPrehistoricFlashlightClickIn, kPrehistoricFlashlightClickOut); + } + break; + case MakeRoomView(kPrehistoric16, kEast): + case MakeRoomView(kPrehistoric16, kWest): + case MakeRoomView(kPrehistoric21, kNorth): + case MakeRoomView(kPrehistoric21, kSouth): + keyCard = g_allItems.findItemByID(kKeyCard); + if (keyCard->getItemState() == kFlashlightOn) { + keyCard->setItemState(kFlashlightOff); + playSpotSoundSync(kPrehistoricFlashlightClickIn, kPrehistoricFlashlightClickOut); + } + break; + } +} + +void Prehistoric::zoomToVault() { + if (!GameState.getPrehistoricSeenBridgeZoom()) + startExtraSequence(kPre18EastZoom, kExtraCompletedFlag, kFilterNoInput); +} + +void Prehistoric::checkContinuePoint(const RoomID room, const DirectionConstant direction) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kPrehistoric08, kEast): + case MakeRoomView(kPrehistoric18, kSouth): + case MakeRoomView(kPrehistoric16, kNorth): + case MakeRoomView(kPrehistoric21, kNorth): + case MakeRoomView(kPrehistoric25, kNorth): + makeContinuePoint(); + break; + } +} + +void Prehistoric::arriveAt(const RoomID room, const DirectionConstant direction) { + Item *keyCard; + + if (MakeRoomView(room, direction) == MakeRoomView(kPrehistoric25, kEast) && + _privateFlags.getFlag(kPrehistoricPrivateExtendedBridgeFlag)) { + _navMovie.stop(); + playSpotSoundSync(kBridgeRetractIn, kBridgeRetractOut); + _privateFlags.setFlag(kPrehistoricPrivateExtendedBridgeFlag, false); + } + + Neighborhood::arriveAt(room, direction); + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kPrehistoricDeath, kNorth): + case MakeRoomView(kPrehistoricDeath, kSouth): + case MakeRoomView(kPrehistoricDeath, kEast): + case MakeRoomView(kPrehistoricDeath, kWest): + if (GameState.getLastRoom() == kPrehistoric23) + die(kDeathEatenByDinosaur); + else + die(kDeathFallOffCliff); + break; + case MakeRoomView(kPrehistoric02, kSouth): + if (!GameState.getPrehistoricSeenTimeStream()) { + GameState.setPrehistoricTriedToExtendBridge(false); + GameState.setPrehistoricSeenFlyer1(false); + GameState.setPrehistoricSeenFlyer2(false); + GameState.setPrehistoricSeenBridgeZoom(false); + GameState.setPrehistoricBreakerThrown(false); + startExtraSequence(kPreArrivalFromTSA, kExtraCompletedFlag, kFilterNoInput); + } + break; + case MakeRoomView(kPrehistoric18, kEast): + zoomToVault(); + break; + case MakeRoomView(kPrehistoric16, kNorth): + keyCard = g_allItems.findItemByID(kKeyCard); + + if (keyCard->getItemState() == kFlashlightOff) { + keyCard->setItemState(kFlashlightOn); + playSpotSoundSync(kPrehistoricFlashlightClickIn, kPrehistoricFlashlightClickOut); + } + + if (g_AIArea) + g_AIArea->checkRules(); + break; + case MakeRoomView(kPrehistoric01, kSouth): + case MakeRoomView(kPrehistoric23, kNorth): + if (g_AIArea) + g_AIArea->checkRules(); + break; + case MakeRoomView(kPrehistoric08, kSouth): + case MakeRoomView(kPrehistoric10, kSouth): + case MakeRoomView(kPrehistoric12, kSouth): + case MakeRoomView(kPrehistoric13, kNorth): + case MakeRoomView(kPrehistoric14, kSouth): + case MakeRoomView(kPrehistoric15, kNorth): + case MakeRoomView(kPrehistoric16, kSouth): + case MakeRoomView(kPrehistoric17, kNorth): + case MakeRoomView(kPrehistoric18, kSouth): + case MakeRoomView(kPrehistoric19, kNorth): + case MakeRoomView(kPrehistoric20, kNorth): + case MakeRoomView(kPrehistoric21, kEast): + keyCard = g_allItems.findItemByID(kKeyCard); + + if (keyCard->getItemState() == kFlashlightOn) { + keyCard->setItemState(kFlashlightOff); + playSpotSoundSync(kPrehistoricFlashlightClickIn, kPrehistoricFlashlightClickOut); + } + break; + case MakeRoomView(kPrehistoric25, kEast): + setCurrentActivation(kActivationVaultClosed); + break; + } +} + +void Prehistoric::loadAmbientLoops() { + RoomID room = GameState.getCurrentRoom(); + + switch (room) { + case kPrehistoric02: + // 1/4 volume. + if (GameState.getPrehistoricSeenTimeStream()) + loadLoopSound1("Sounds/Prehistoric/P02SAL00.22k.AIFF", 64); + break; + case kPrehistoric01: + case kPrehistoric03: + case kPrehistoric04: + case kPrehistoric05: + case kPrehistoric06: + case kPrehistoric07: + case kPrehistoric09: + case kPrehistoric11: + case kPrehistoric13: + case kPrehistoric15: + case kPrehistoric17: + case kPrehistoric19: + case kPrehistoric20: + // 1/4 volume. + loadLoopSound1("Sounds/Prehistoric/P02SAL00.22k.AIFF", 64); + break; + case kPrehistoric08: + case kPrehistoric10: + case kPrehistoric12: + case kPrehistoric14: + case kPrehistoric16: + case kPrehistoric18: + case kPrehistoric21: + // 3/16 volume. + loadLoopSound1("Sounds/Prehistoric/P02SAL00.22k.AIFF", 48); + break; + case kPrehistoric25: + // 1/8 volume. + loadLoopSound1("Sounds/Prehistoric/P02SAL00.22k.AIFF", 32); + break; + case kPrehistoric22: + case kPrehistoric22North: + case kPrehistoric23: + case kPrehistoric24: + case kPrehistoricDeath: + // 0 volume. + loadLoopSound1(""); + break; + } + + switch (room) { + case kPrehistoric02: + case kPrehistoric03: + case kPrehistoric04: + case kPrehistoric05: + case kPrehistoric06: + case kPrehistoric07: + case kPrehistoric08: + case kPrehistoric09: + case kPrehistoric10: + case kPrehistoric11: + case kPrehistoric12: + case kPrehistoric13: + case kPrehistoric14: + case kPrehistoric15: + case kPrehistoric16: + case kPrehistoric17: + case kPrehistoric19: + case kPrehistoric20: + case kPrehistoric21: + case kPrehistoricDeath: + loadLoopSound2(""); + break; + case kPrehistoric01: + case kPrehistoric25: + loadLoopSound2("Sounds/Prehistoric/VolcLoop.22K.AIFF", 64); + break; + case kPrehistoric18: + if (_privateFlags.getFlag(kPrehistoricPrivateExtendedBridgeFlag)) + loadLoopSound2("Sounds/Prehistoric/P18EAL00.22k.AIFF", 0x100, 0, 0); + else + loadLoopSound2(""); + break; + case kPrehistoric23: + case kPrehistoric24: + case kPrehistoric22: + case kPrehistoric22North: + loadLoopSound2("Sounds/Prehistoric/P24NAL00.22k.AIFF", 64); + break; + } +} + +void Prehistoric::activateHotspots() { + Neighborhood::activateHotspots(); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kPrehistoric18, kEast): + if (!_privateFlags.getFlag(kPrehistoricPrivateExtendedBridgeFlag)) + g_allHotspots.activateOneHotspot(kPre18EastSpotID); + break; + case MakeRoomView(kPrehistoric22North, kNorth): + g_allHotspots.activateOneHotspot(kPre22NorthBreakerSpotID); + break; + } +} + +void Prehistoric::clickInHotspot(const Input &input, const Hotspot *spot) { + switch (spot->getObjectID()) { + case kPre18EastSpotID: + if (GameState.getPrehistoricBreakerThrown()) + startExtraSequence(kPre18EastBridgeOn, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kPre18EastBridgeOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kPre22NorthBreakerSpotID: + startExtraSequence(kPre22ThrowBreaker, kExtraCompletedFlag, kFilterNoInput); + break; + default: + Neighborhood::clickInHotspot(input, spot); + break; + } +} + +void Prehistoric::receiveNotification(Notification *notification, const NotificationFlags flags) { + Neighborhood::receiveNotification(notification, flags); + + if ((flags & kExtraCompletedFlag) != 0) { + _interruptionFilter = kFilterAllInput; + + switch (_lastExtra) { + case kPreArrivalFromTSA: + GameState.setPrehistoricSeenTimeStream(true); + loadAmbientLoops(); + makeContinuePoint(); + break; + case kPre18EastZoom: + startExtraSequence(kPre18EastZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kPre18EastZoomOut: + GameState.setPrehistoricSeenBridgeZoom(true); + break; + case kPre18EastBridgeOn: + _privateFlags.setFlag(kPrehistoricPrivateExtendedBridgeFlag, true); + setCurrentAlternate(kAltPrehistoricBridgeSet); + GameState.setPrehistoricTriedToExtendBridge(false); + loadAmbientLoops(); + GameState.setScoringExtendedBridge(true); + break; + case kPre18EastBridgeOut: + GameState.setPrehistoricTriedToExtendBridge(true); + if (g_AIArea) + g_AIArea->checkMiddleArea(); + break; + case kPre22ThrowBreaker: + GameState.setPrehistoricBreakerThrown(true); + GameState.setScoringThrewBreaker(true); + break; + case kPre25EastUnlockingVaultNoLog: + case kPre25EastUnlockingVaultWithLog: + _vm->addItemToInventory((InventoryItem *)g_allItems.findItemByID(kJourneymanKey)); + break; + } + } + + g_AIArea->checkMiddleArea(); +} + +Common::String Prehistoric::getBriefingMovie() { + Common::String movieName = Neighborhood::getBriefingMovie(); + + if (movieName.empty()) + movieName = "Images/AI/Prehistoric/XPE"; + + return movieName; +} + +Common::String Prehistoric::getEnvScanMovie() { + Common::String movieName = Neighborhood::getEnvScanMovie(); + + if (movieName.empty()) { + if (!_vm->isDemo()) { + switch (GameState.getCurrentRoom()) { + case kPrehistoric16: + case kPrehistoric23: + case kPrehistoric24: + return "Images/AI/Prehistoric/XP7WB"; + } + } + + return "Images/AI/Prehistoric/XP17NB"; + } + + return movieName; +} + +uint Prehistoric::getNumHints() { + uint numHints = Neighborhood::getNumHints(); + + if (numHints == 0) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kPrehistoric18, kEast): + if (!GameState.getPrehistoricBreakerThrown() && GameState.getPrehistoricTriedToExtendBridge() && + !_privateFlags.getFlag(kPrehistoricPrivateExtendedBridgeFlag)) + numHints = 1; + break; + case MakeRoomView(kPrehistoric25, kEast): + if (!_privateFlags.getFlag(kPrehistoricPrivateVaultOpenFlag)) + numHints = 1; + break; + } + } + + return numHints; +} + +Common::String Prehistoric::getHintMovie(uint hintNum) { + Common::String movieName = Neighborhood::getHintMovie(hintNum); + + if (movieName.empty()) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kPrehistoric18, kEast): + return "Images/AI/Prehistoric/XP18WD"; + case MakeRoomView(kPrehistoric25, kEast): + return "Images/AI/Globals/XGLOB1A"; + } + } + + return movieName; +} + +bool Prehistoric::canSolve() { + return GameState.getCurrentRoomAndView() == MakeRoomView(kPrehistoric18, kEast) && + !GameState.getPrehistoricBreakerThrown() && + GameState.getPrehistoricTriedToExtendBridge() && + !_privateFlags.getFlag(kPrehistoricPrivateExtendedBridgeFlag); +} + +void Prehistoric::doSolve() { + GameState.setPrehistoricBreakerThrown(true); + startExtraSequence(kPre18EastBridgeOn, kExtraCompletedFlag, kFilterNoInput); +} + +Hotspot *Prehistoric::getItemScreenSpot(Item *item, DisplayElement *element) { + if (item->getObjectID() == kHistoricalLog) + return g_allHotspots.findHotspotByID(kPrehistoricHistoricalLogSpotID); + + return Neighborhood::getItemScreenSpot(item, element); +} + +void Prehistoric::pickedUpItem(Item *item) { + switch (item->getObjectID()) { + case kHistoricalLog: + GameState.setScoringGotHistoricalLog(true); + break; + } + + Neighborhood::pickedUpItem(item); +} + +void Prehistoric::dropItemIntoRoom(Item *item, Hotspot *dropSpot) { + switch (item->getObjectID()) { + case kJourneymanKey: + Neighborhood::dropItemIntoRoom(item, dropSpot); + + if (GameState.isTakenItemID(kHistoricalLog)) + startExtraLongSequence(kPre25EastUnlockingVaultNoLog, kPre25EastVaultOpenNoLog, kExtraCompletedFlag, kFilterNoInput); + else + startExtraLongSequence(kPre25EastUnlockingVaultWithLog, kPre25EastVaultOpenWithLog, kExtraCompletedFlag, kFilterNoInput); + + _privateFlags.setFlag(kPrehistoricPrivateVaultOpenFlag, true); + setCurrentActivation(kActivationVaultOpen); + break; + default: + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + } +} + +void Prehistoric::bumpIntoWall() { + requestSpotSound(kPrehistoricBumpIntoWallIn, kPrehistoricBumpIntoWallOut, kFilterAllInput, 0); + Neighborhood::bumpIntoWall(); +} + +Common::String Prehistoric::getNavMovieName() { + return "Images/Prehistoric/Prehistoric.movie"; +} + +Common::String Prehistoric::getSoundSpotsName() { + return "Sounds/Prehistoric/Prehistoric Spots"; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/prehistoric/prehistoric.h b/engines/pegasus/neighborhood/prehistoric/prehistoric.h new file mode 100644 index 0000000000..2750fc0ee8 --- /dev/null +++ b/engines/pegasus/neighborhood/prehistoric/prehistoric.h @@ -0,0 +1,158 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_PREHISTORIC_H +#define PEGASUS_NEIGHBORHOOD_PREHISTORIC_H + +#include "pegasus/neighborhood/neighborhood.h" + +namespace Pegasus { + +static const TimeScale kPrehistoricMovieScale = 600; +static const TimeScale kPrehistoricFramesPerSecond = 15; +static const TimeScale kPrehistoricFrameDuration = 40; + +// Alternate IDs. + +static const AlternateID kAltPrehistoricNormal = 0; +static const AlternateID kAltPrehistoricBridgeSet = 1; + +// Room IDs. + +static const RoomID kPrehistoric01 = 0; +static const RoomID kPrehistoric02 = 1; +static const RoomID kPrehistoric03 = 2; +static const RoomID kPrehistoric04 = 3; +static const RoomID kPrehistoric05 = 4; +static const RoomID kPrehistoric06 = 5; +static const RoomID kPrehistoric07 = 6; +static const RoomID kPrehistoric08 = 7; +static const RoomID kPrehistoric09 = 8; +static const RoomID kPrehistoric10 = 9; +static const RoomID kPrehistoric11 = 10; +static const RoomID kPrehistoric12 = 11; +static const RoomID kPrehistoric13 = 12; +static const RoomID kPrehistoric14 = 13; +static const RoomID kPrehistoric15 = 14; +static const RoomID kPrehistoric16 = 15; +static const RoomID kPrehistoric17 = 16; +static const RoomID kPrehistoric18 = 17; +static const RoomID kPrehistoric19 = 18; +static const RoomID kPrehistoric20 = 19; +static const RoomID kPrehistoric21 = 20; +static const RoomID kPrehistoric22 = 21; +static const RoomID kPrehistoric22North = 22; +static const RoomID kPrehistoric23 = 23; +static const RoomID kPrehistoric24 = 24; +static const RoomID kPrehistoric25 = 25; +static const RoomID kPrehistoricDeath = 26; + +// Hot Spot Activation IDs. + +static const HotSpotActivationID kActivationVaultClosed = 1; +static const HotSpotActivationID kActivationVaultOpen = 2; + +// Hot Spot IDs. + +static const HotSpotID kPre18EastSpotID = 5000; +static const HotSpotID kPre22NorthSpotID = 5001; +static const HotSpotID kPre22NorthOutSpotID = 5002; +static const HotSpotID kPre22NorthBreakerSpotID = 5003; +static const HotSpotID kPrehistoricKeyDropSpotID = 5004; +static const HotSpotID kPrehistoricHistoricalLogSpotID = 5005; + +// Extra sequence IDs. + +static const ExtraID kPreArrivalFromTSA = 0; +static const ExtraID kPre18EastBridgeOut = 1; +static const ExtraID kPre18EastBridgeOn = 2; +static const ExtraID kPre18EastZoom = 3; +static const ExtraID kPre18EastZoomOut = 4; +static const ExtraID kPre22ThrowBreaker = 5; +static const ExtraID kPre25EastUnlockingVaultWithLog = 6; +static const ExtraID kPre25EastVaultOpenWithLog = 7; +static const ExtraID kPre25EastViewWithLog = 8; +static const ExtraID kPre25EastUnlockingVaultNoLog = 9; +static const ExtraID kPre25EastVaultOpenNoLog = 10; +static const ExtraID kPre25EastViewNoLog = 11; + +class PegasusEngine; + +class Prehistoric : public Neighborhood { +public: + Prehistoric(InputHandler *, PegasusEngine *); + virtual ~Prehistoric() {} + + virtual uint16 getDateResID() const; + virtual void init(); + + virtual void arriveAt(const RoomID, const DirectionConstant); + virtual void activateHotspots(); + virtual void clickInHotspot(const Input &, const Hotspot *); + Common::String getBriefingMovie(); + Common::String getEnvScanMovie(); + uint getNumHints(); + Common::String getHintMovie(uint); + + Hotspot *getItemScreenSpot(Item *, DisplayElement *); + void dropItemIntoRoom(Item *, Hotspot *); + void pickedUpItem(Item *); + + void start(); + + void bumpIntoWall(); + + void checkContinuePoint(const RoomID, const DirectionConstant); + + bool canSolve(); + void doSolve(); + +protected: + enum { + kPrehistoricPrivateVaultOpenFlag, + kPrehistoricPrivateExtendedBridgeFlag, + kNumPrehistoricPrivateFlags + }; + + void setUpAIRules(); + int16 getStaticCompassAngle(const RoomID, const DirectionConstant); + void getExitCompassMove(const ExitTable::Entry &, FaderMoveSpec &); + virtual void receiveNotification(Notification *, const NotificationFlags); + void turnTo(const DirectionConstant); + void zoomToVault(); + TimeValue getViewTime(const RoomID, const DirectionConstant); + void findSpotEntry(const RoomID, const DirectionConstant, SpotFlags, SpotTable::Entry &); + + void loadAmbientLoops(); + + FlagsArray<byte, kNumPrehistoricPrivateFlags> _privateFlags; + + Common::String getNavMovieName(); + Common::String getSoundSpotsName(); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/spot.cpp b/engines/pegasus/neighborhood/spot.cpp new file mode 100644 index 0000000000..f285bf9bc2 --- /dev/null +++ b/engines/pegasus/neighborhood/spot.cpp @@ -0,0 +1,70 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/spot.h" + +namespace Pegasus { + +void SpotTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].room = stream->readUint16BE(); + _entries[i].direction = stream->readByte(); + _entries[i].srcFlags = stream->readByte(); + _entries[i].altCode = stream->readByte(); + stream->readByte(); // alignment + _entries[i].movieStart = stream->readUint32BE(); + _entries[i].movieEnd = stream->readUint32BE(); + _entries[i].dstFlags = stream->readByte(); + stream->readByte(); // alignment + debug(0, "Spot[%d]: %d %d %d %d %d %d %d", i, _entries[i].room, _entries[i].direction, + _entries[i].srcFlags, _entries[i].altCode, _entries[i].movieStart, + _entries[i].movieEnd, _entries[i].dstFlags); + } +} + +void SpotTable::clear() { + _entries.clear(); +} + +// Two SpotTable::Entries are equal if +// In addition to having their rooms, directions and alt codes identical... +// They are both either loops or once only animations AND +// They overlap in at least one of the on arrival, on turn and on door open bits. +SpotTable::Entry SpotTable::findEntry(RoomID room, DirectionConstant direction, SpotFlags srcFlags, AlternateID altCode) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].room == room && _entries[i].direction == direction && _entries[i].altCode == altCode && (_entries[i].srcFlags & kSpotLoopsMask) == (srcFlags & kSpotLoopsMask) && ((_entries[i].srcFlags & srcFlags) & kSpotTriggers) != 0) + return _entries[i]; + + return Entry(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/spot.h b/engines/pegasus/neighborhood/spot.h new file mode 100644 index 0000000000..a985420b7c --- /dev/null +++ b/engines/pegasus/neighborhood/spot.h @@ -0,0 +1,97 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_SPOT_H +#define PEGASUS_NEIGHBORHOOD_SPOT_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +typedef byte SpotFlags; + +enum { + kSpotLoopsBit, // Loop or once only? + kSpotOnArrivalBit, + kSpotOnTurnBit, + kSpotOnDoorOpenBit +}; + +static const SpotFlags kNoSpotFlags = 0; +static const SpotFlags kSpotLoopsMask = 1 << kSpotLoopsBit; +static const SpotFlags kSpotOnArrivalMask = 1 << kSpotOnArrivalBit; +static const SpotFlags kSpotOnTurnMask = 1 << kSpotOnTurnBit; +static const SpotFlags kSpotOnDoorOpenMask = 1 << kSpotOnDoorOpenBit; + +static const SpotFlags kSpotTriggers = kSpotOnArrivalMask | kSpotOnTurnMask | kSpotOnDoorOpenMask; + +class SpotTable { +public: + SpotTable() {} + ~SpotTable() {} + + static uint32 getResTag() { return MKTAG('S', 'p', 'o', 't'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry() { clear(); } + bool isEmpty() { return movieStart == 0xffffffff; } + void clear() { + room = kNoRoomID; + direction = kNoDirection; + srcFlags = kNoSpotFlags; + altCode = kNoAlternateID; + movieStart = 0xffffffff; + movieEnd = 0xffffffff; + dstFlags = kNoSpotFlags; + } + + RoomID room; + DirectionConstant direction; + SpotFlags srcFlags; + AlternateID altCode; + TimeValue movieStart; + TimeValue movieEnd; + SpotFlags dstFlags; + }; + + Entry findEntry(RoomID room, DirectionConstant direction, SpotFlags srcFlags, AlternateID altCode); + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/tsa/fulltsa.cpp b/engines/pegasus/neighborhood/tsa/fulltsa.cpp new file mode 100644 index 0000000000..ccc08348fa --- /dev/null +++ b/engines/pegasus/neighborhood/tsa/fulltsa.cpp @@ -0,0 +1,3023 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/cursor.h" +#include "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/aichip.h" +#include "pegasus/items/biochips/opticalchip.h" +#include "pegasus/neighborhood/caldoria/caldoria.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/prehistoric/prehistoric.h" +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/tsa/fulltsa.h" +#include "pegasus/neighborhood/wsc/wsc.h" + +namespace Pegasus { + +// TSA PICTs: + +static const ResIDType kTBPCloseBoxPICTID = 800; +static const ResIDType kTBPRewindPICTID = 801; +static const ResIDType kUnresolvedPICTID = 802; +static const ResIDType kResolvedPICTID = 803; +static const ResIDType kJumpMenuPICTID = 804; +static const ResIDType kJumpMenuHilitedPICTID = 805; +static const ResIDType kExitPICTID = 806; +static const ResIDType kExitHilitedPICTID = 807; +static const ResIDType kLeftRipPICTID = 808; +static const ResIDType kComparisonCloseBoxPICTID = 809; +static const ResIDType kComparisonLeftRewindPICTID = 810; +static const ResIDType kComparisonRightRewindPICTID = 811; +static const ResIDType kComparisonHiliteNoradPICTID = 812; +static const ResIDType kComparisonHiliteMarsPICTID = 813; +static const ResIDType kComparisonHiliteCaldoriaPICTID = 814; +static const ResIDType kComparisonHiliteWSCPICTID = 815; +static const ResIDType kComparisonChancesNoradPICTID = 816; +static const ResIDType kComparisonChancesMarsPICTID = 817; +static const ResIDType kComparisonChancesCaldoriaPICTID = 818; +static const ResIDType kComparisonChancesWSCPICTID = 819; +static const ResIDType kRedirectionCCRolloverPICTID = 820; +static const ResIDType kRedirectionRRRolloverPICTID = 821; +static const ResIDType kRedirectionFDRolloverPICTID = 822; +static const ResIDType kRedirectionCCDoorPICTID = 823; +static const ResIDType kRedirectionRRDoorPICTID = 824; +static const ResIDType kRedirectionFDDoorPICTID = 825; +static const ResIDType kRedirectionSecuredPICTID = 826; +static const ResIDType kRedirectionNewTargetPICTID = 827; +static const ResIDType kRedirectionClosePICTID = 828; + +static const int16 kCompassShift = 15; + +static const TimeScale kFullTSAMovieScale = 600; +static const TimeScale kFullTSAFramesPerSecond = 15; +static const TimeScale kFullTSAFrameDuration = 40; + +// Alternate IDs. +static const AlternateID kAltTSANormal = 0; +static const AlternateID kAltTSARobotsAtReadyRoom = 1; +static const AlternateID kAltTSARobotsAtFrontDoor = 2; +static const AlternateID kAltTSARedAlert = 3; + +// Room IDs. +static const RoomID kTSA01 = 1; +static const RoomID kTSA02 = 2; +static const RoomID kTSA03 = 3; +static const RoomID kTSA04 = 4; +static const RoomID kTSA05 = 5; +static const RoomID kTSA0A = 6; +static const RoomID kTSA06 = 7; +static const RoomID kTSA07 = 8; +static const RoomID kTSA08 = 9; +static const RoomID kTSA09 = 10; +static const RoomID kTSA10 = 11; +static const RoomID kTSA11 = 12; +static const RoomID kTSA12 = 13; +static const RoomID kTSA13 = 14; +static const RoomID kTSA14 = 15; +static const RoomID kTSA15 = 16; +static const RoomID kTSA16 = 17; +static const RoomID kTSA17 = 18; +static const RoomID kTSA18 = 19; +static const RoomID kTSA19 = 20; +static const RoomID kTSA0B = 21; +static const RoomID kTSA21Cyan = 22; +static const RoomID kTSA22Cyan = 23; +static const RoomID kTSA23Cyan = 24; +static const RoomID kTSA24Cyan = 25; +static const RoomID kTSA25Cyan = 26; +static const RoomID kTSA21Red = 27; +static const RoomID kTSA23Red = 29; +static const RoomID kTSA24Red = 30; +static const RoomID kTSA25Red = 31; +static const RoomID kTSA26 = 32; +static const RoomID kTSA27 = 33; +static const RoomID kTSA28 = 34; +static const RoomID kTSA29 = 35; +static const RoomID kTSA30 = 36; +static const RoomID kTSA31 = 37; +static const RoomID kTSA32 = 38; +static const RoomID kTSA33 = 39; +static const RoomID kTSA34 = 40; +static const RoomID kTSA35 = 41; +static const RoomID kTSADeathRoom = 43; + +// Hot Spot Activation IDs. +static const HotSpotActivationID kActivateTSAReadyForCard = 1; +static const HotSpotActivationID kActivateTSAReadyToTransport = 2; +static const HotSpotActivationID kActivateTSARobotsAwake = 3; +static const HotSpotActivationID kActivateTSA0BZoomedOut = 4; +static const HotSpotActivationID kActivateTSA0BZoomedIn = 5; +static const HotSpotActivationID kActivateTSA0BComparisonVideo = 6; +static const HotSpotActivationID kActivationLogReaderOpen = 7; +static const HotSpotActivationID kActivateTSA0BTBPVideo = 8; +static const HotSpotActivationID kActivationDoesntHaveKey = 9; +static const HotSpotActivationID kActivationKeyVaultOpen = 10; +static const HotSpotActivationID kActivationDoesntHaveChips = 11; +static const HotSpotActivationID kActivationChipVaultOpen = 12; +static const HotSpotActivationID kActivationJumpToPrehistoric = 13; +static const HotSpotActivationID kActivationJumpToNorad = 14; +static const HotSpotActivationID kActivationJumpToMars = 15; +static const HotSpotActivationID kActivationJumpToWSC = 16; +static const HotSpotActivationID kActivationReadyToExit = 17; +static const HotSpotActivationID kActivationReadyForJumpMenu = 18; +static const HotSpotActivationID kActivationMainJumpMenu = 19; + +// Hot Spot IDs. +static const HotSpotID kTSAGTCardDropSpotID = 5000; +static const HotSpotID kTSAGTTokyoSpotID = 5001; +static const HotSpotID kTSAGTCaldoriaSpotID = 5002; +static const HotSpotID kTSAGTBeachSpotID = 5003; +static const HotSpotID kTSAGTOtherSpotID = 5004; +static const HotSpotID kTSA02DoorSpotID = 5005; +static const HotSpotID kTSA03EastJimenezSpotID = 5006; +static const HotSpotID kTSA03WestCrenshawSpotID = 5007; +static const HotSpotID kTSA04EastMatsumotoSpotID = 5008; +static const HotSpotID kTSA04WestCastilleSpotID = 5009; +static const HotSpotID kTSA05EastSinclairSpotID = 5010; +static const HotSpotID kTSA05WestWhiteSpotID = 5011; +static const HotSpotID kTSA0AEastSpotID = 5012; +static const HotSpotID kTSA0AWastSpotID = 5013; +static const HotSpotID kTSA0BEastMonitorSpotID = 5014; +static const HotSpotID kTSA0BEastMonitorOutSpotID = 5015; +static const HotSpotID kTSA0BEastCompareNoradSpotID = 5016; +static const HotSpotID kTSA0BEastCompareMarsSpotID = 5017; +static const HotSpotID kTSA0BEastCompareCaldoriaSpotID = 5018; +static const HotSpotID kTSA0BEastCompareWSCSpotID = 5019; +static const HotSpotID kTSA0BEastLeftRewindSpotID = 5020; +static const HotSpotID kTSA0BEastLeftPlaySpotID = 5021; +static const HotSpotID kTSA0BEastRightRewindSpotID = 5022; +static const HotSpotID kTSA0BEastRightPlaySpotID = 5023; +static const HotSpotID kTSA0BEastCloseVideoSpotID = 5024; +static const HotSpotID kTSA0BNorthMonitorSpotID = 5025; +static const HotSpotID kTSA0BNorthMonitorOutSpotID = 5026; +static const HotSpotID kTSA0BNorthHistLogSpotID = 5027; +static const HotSpotID kTSA0BNorthRobotsToCommandCenterSpotID = 5028; +static const HotSpotID kTSA0BNorthRobotsToReadyRoomSpotID = 5029; +static const HotSpotID kTSA0BNorthRobotsToFrontDoorSpotID = 5030; +static const HotSpotID kTSA0BWestMonitorSpotID = 5031; +static const HotSpotID kTSA0BWestMonitorOutSpotID = 5032; +static const HotSpotID kTSA0BWestTheorySpotID = 5033; +static const HotSpotID kTSA0BWestBackgroundSpotID = 5034; +static const HotSpotID kTSA0BWestProcedureSpotID = 5035; +static const HotSpotID kTSA0BWestCloseVideoSpotID = 5036; +static const HotSpotID kTSA0BWestPlayVideoSpotID = 5037; +static const HotSpotID kTSA0BWestRewindVideoSpotID = 5038; +static const HotSpotID kTSA22EastMonitorSpotID = 5039; +static const HotSpotID kTSA22EastKeySpotID = 5040; +static const HotSpotID kTSA23WestMonitorSpotID = 5041; +static const HotSpotID kTSA23WestChipsSpotID = 5042; +static const HotSpotID kTSA34NorthDoorSpotID = 5043; +static const HotSpotID kTSA37NorthJumpToPrehistoricSpotID = 5044; +static const HotSpotID kTSA37NorthJumpToNoradSpotID = 5045; +static const HotSpotID kTSA37NorthCancelNoradSpotID = 5046; +static const HotSpotID kTSA37NorthJumpToMarsSpotID = 5047; +static const HotSpotID kTSA37NorthCancelMarsSpotID = 5048; +static const HotSpotID kTSA37NorthJumpToWSCSpotID = 5049; +static const HotSpotID kTSA37NorthCancelWSCSpotID = 5050; +static const HotSpotID kTSA37NorthExitSpotID = 5051; +static const HotSpotID kTSA37NorthJumpMenuSpotID = 5052; +static const HotSpotID kTSA37NorthNoradMenuSpotID = 5053; +static const HotSpotID kTSA37NorthMarsMenuSpotID = 5054; +static const HotSpotID kTSA37NorthWSCMenuSpotID = 5055; + +// Extra sequence IDs. +static const ExtraID kTSATransporterArrowLoop = 0; +static const ExtraID kTSAArriveFromCaldoria = 1; +static const ExtraID kTSAGTOtherChoice = 2; +static const ExtraID kTSAGTCardSwipe = 3; +static const ExtraID kTSAGTSelectCaldoria = 4; +static const ExtraID kTSAGTGoToCaldoria = 5; +static const ExtraID kTSAGTSelectBeach = 6; +static const ExtraID kTSAGTGoToBeach = 7; +static const ExtraID kTSAGTArriveAtBeach = 8; +static const ExtraID kTSAGTSelectTokyo = 9; +static const ExtraID kTSAGTGoToTokyo = 10; +static const ExtraID kTSAGTArriveAtTokyo = 11; +static const ExtraID kTSA02NorthZoomIn = 12; +static const ExtraID kTSA02NorthTenSecondDoor = 13; +static const ExtraID kTSA02NorthZoomOut = 14; +static const ExtraID kTSA02NorthDoorWithAgent3 = 15; +static const ExtraID kTSA03JimenezZoomIn = 16; +static const ExtraID kTSA03JimenezSpeech = 17; +static const ExtraID kTSA03JimenezZoomOut = 18; +static const ExtraID kTSA03CrenshawZoomIn = 19; +static const ExtraID kTSA03CrenshawSpeech = 20; +static const ExtraID kTSA03CrenshawZoomOut = 21; +static const ExtraID kTSA03SouthRobotDeath = 22; +static const ExtraID kTSA04NorthRobotGreeting = 23; +static const ExtraID kTSA04MatsumotoZoomIn = 24; +static const ExtraID kTSA04MatsumotoSpeech = 25; +static const ExtraID kTSA04MatsumotoZoomOut = 26; +static const ExtraID kTSA04CastilleZoomIn = 27; +static const ExtraID kTSA04CastilleSpeech = 28; +static const ExtraID kTSA04CastilleZoomOut = 29; +static const ExtraID kTSA05SinclairZoomIn = 30; +static const ExtraID kTSA05SinclairSpeech = 31; +static const ExtraID kTSA05SinclairZoomOut = 32; +static const ExtraID kTSA05WhiteZoomIn = 33; +static const ExtraID kTSA05WhiteSpeech = 34; +static const ExtraID kTSA05WhiteZoomOut = 35; +static const ExtraID kTSA0AEastRobot = 36; +static const ExtraID kTSA0AWestRobot = 37; +static const ExtraID kTSA16NorthRobotDeath = 38; +static const ExtraID kTSA0BEastZoomIn = 39; +static const ExtraID kTSA0BEastZoomedView = 40; +static const ExtraID kTSA0BEastZoomOut = 41; +static const ExtraID kTSA0BEastTurnLeft = 42; +static const ExtraID kTSA0BComparisonStartup = 43; +static const ExtraID kTSA0BComparisonView0000 = 44; +static const ExtraID kTSA0BComparisonView0002 = 45; +static const ExtraID kTSA0BComparisonView0020 = 46; +static const ExtraID kTSA0BComparisonView0022 = 47; +static const ExtraID kTSA0BComparisonView0200 = 48; +static const ExtraID kTSA0BComparisonView0202 = 49; +static const ExtraID kTSA0BComparisonView0220 = 50; +static const ExtraID kTSA0BComparisonView0222 = 51; +static const ExtraID kTSA0BComparisonView2000 = 52; +static const ExtraID kTSA0BComparisonView2002 = 53; +static const ExtraID kTSA0BComparisonView2020 = 54; +static const ExtraID kTSA0BComparisonView2022 = 55; +static const ExtraID kTSA0BComparisonView2200 = 56; +static const ExtraID kTSA0BComparisonView2202 = 57; +static const ExtraID kTSA0BComparisonView2220 = 58; +static const ExtraID kTSA0BComparisonView2222 = 59; +static const ExtraID kTSA0BNoradComparisonView = 60; +static const ExtraID kTSA0BNoradUnaltered = 61; +static const ExtraID kTSA0BNoradAltered = 62; +static const ExtraID kTSA0BMarsComparisonView = 63; +static const ExtraID kTSA0BMarsUnaltered = 64; +static const ExtraID kTSA0BMarsAltered = 65; +static const ExtraID kTSA0BWSCComparisonView = 66; +static const ExtraID kTSA0BWSCUnaltered = 67; +static const ExtraID kTSA0BWSCAltered = 68; +static const ExtraID kTSA0BCaldoriaComparisonView = 69; +static const ExtraID kTSA0BCaldoriaUnaltered = 70; +static const ExtraID kTSA0BCaldoriaAltered = 71; +static const ExtraID kTSA0BNorthZoomIn = 72; +static const ExtraID kTSA0BNorthZoomedView = 73; +static const ExtraID kTSA0BNorthZoomOut = 74; +static const ExtraID kTSA0BNorthTurnLeft = 75; +static const ExtraID kTSA0BNorthTurnRight = 76; +static const ExtraID kTSA0BNorthHistLogOpen = 77; +static const ExtraID kTSA0BNorthHistLogClose = 78; +static const ExtraID kTSA0BNorthHistLogCloseWithLog = 79; +static const ExtraID kTSA0BNorthCantChangeHistory = 80; +static const ExtraID kTSA0BNorthYoureBusted = 81; +static const ExtraID kTSA0BNorthFinallyHappened = 82; +static const ExtraID kTSA0BShowRip1 = 83; +static const ExtraID kTSA0BNorthRipView1 = 84; +static const ExtraID kTSA0BShowRip2 = 85; +static const ExtraID kTSA0BShowGuardRobots = 86; +static const ExtraID kTSA0BAIInterruption = 87; +static const ExtraID kTSA0BRobotsToCommandCenter = 88; +static const ExtraID kTSA0BNorthRobotsAtCCView = 89; +static const ExtraID kTSA0BNorthRobotsAtRRView = 90; +static const ExtraID kTSA0BNorthRobotsAtFDView = 91; +static const ExtraID kTSA0BRobotsFromCommandCenterToReadyRoom = 92; +static const ExtraID kTSA0BRobotsFromReadyRoomToCommandCenter = 93; +static const ExtraID kTSA0BRobotsFromCommandCenterToFrontDoor = 94; +static const ExtraID kTSA0BRobotsFromFrontDoorToCommandCenter = 95; +static const ExtraID kTSA0BRobotsFromFrontDoorToReadyRoom = 96; +static const ExtraID kTSA0BRobotsFromReadyRoomToFrontDoor = 97; +static const ExtraID kTSA0BWestZoomIn = 98; +static const ExtraID kTSA0BWestZoomedView = 99; +static const ExtraID kTSA0BWestZoomOut = 100; +static const ExtraID kTSA0BWestTurnRight = 101; +static const ExtraID kTSA0BTBPTheoryHighlight = 102; +static const ExtraID kTSA0BTBPBackgroundHighlight = 103; +static const ExtraID kTSA0BTBPProcedureHighlight = 104; +static const ExtraID kTSA0BTBPTheory = 105; +static const ExtraID kTSA0BTBPBackground = 106; +static const ExtraID kTSA0BTBPProcedure = 107; +static const ExtraID kTSA0BRipAlarmScreen = 108; +static const ExtraID kTSA22RedEastZoomInSequence = 109; +static const ExtraID kTSA22RedEastVaultViewWithKey = 110; +static const ExtraID kTSA22RedEastVaultViewNoKey = 111; +static const ExtraID kTSA23RedWestVaultZoomInSequence = 112; +static const ExtraID kTSA23RedWestVaultViewWithChips = 113; +static const ExtraID kTSA23RedWestVaultViewNoChips = 114; +static const ExtraID kTSA25NorthDeniedNoKey = 115; +static const ExtraID kTSA25NorthDeniedNoChip = 116; +static const ExtraID kTSA25NorthPutOnSuit = 117; +static const ExtraID kTSA25NorthAlreadyHaveSuit = 118; +static const ExtraID kTSA25NorthDescending1 = 119; +static const ExtraID kTSA25NorthDescending2 = 120; +static const ExtraID kTSA37HorseToAI1 = 121; +static const ExtraID kTSA37PegasusAI1 = 122; +static const ExtraID kTSA37AI1ToCommissioner1 = 123; +static const ExtraID kTSA37Commissioner1 = 124; +static const ExtraID kTSA37Commissioner1ToZoom = 125; +static const ExtraID kTSA37ZoomToPrehistoric = 126; +static const ExtraID kTSA37PrehistoricToAI2 = 127; +static const ExtraID kTSA37PegasusAI2 = 128; +static const ExtraID kTSA37AI2ToPrehistoric = 129; +static const ExtraID kTSA37PrehistoricToDepart = 130; +static const ExtraID kTSA37PegasusDepart = 131; +static const ExtraID kTSA37TimeJumpToPegasus = 132; +static const ExtraID kTSA37RecallToDownload = 133; +static const ExtraID kTSA37DownloadToColonel1 = 134; +static const ExtraID kTSA37Colonel1 = 135; +static const ExtraID kTSA37Colonel1ToReviewRequired = 136; +static const ExtraID kTSA37ReviewRequiredToExit = 137; +static const ExtraID kTSA37ExitHilited = 138; +static const ExtraID kTSA37ExitToHorse = 139; +static const ExtraID kTSA37HorseToColonel2 = 140; +static const ExtraID kTSA37Colonel2 = 141; +static const ExtraID kTSA37PegasusAI3 = 142; +static const ExtraID kTSA37AI3ToHorse = 143; +static const ExtraID kTSA37HorseToZoom = 144; +static const ExtraID kTSA37ZoomToMainMenu = 145; +static const ExtraID kTSA37MainMenuToAI4 = 146; +static const ExtraID kTSA37PegasusAI4 = 147; +static const ExtraID kTSA37AI4ToMainMenu = 148; +static const ExtraID kTSA37JumpMenu000 = 149; +static const ExtraID kTSA37JumpMenu001 = 150; +static const ExtraID kTSA37JumpMenu010 = 151; +static const ExtraID kTSA37JumpMenu011 = 152; +static const ExtraID kTSA37JumpMenu100 = 153; +static const ExtraID kTSA37JumpMenu101 = 154; +static const ExtraID kTSA37JumpMenu110 = 155; +static const ExtraID kTSA37JumpMenu111 = 156; +static const ExtraID kTSA37JumpToWSCMenu = 157; +static const ExtraID kTSA37CancelWSC = 158; +static const ExtraID kTSA37JumpToWSC = 159; +static const ExtraID kTSA37WSCToAI5 = 160; +static const ExtraID kTSA37PegasusAI5 = 161; +static const ExtraID kTSA37AI5ToWSC = 162; +static const ExtraID kTSA37WSCToDepart = 163; +static const ExtraID kTSA37JumpToMarsMenu = 164; +static const ExtraID kTSA37CancelMars = 165; +static const ExtraID kTSA37JumpToMars = 166; +static const ExtraID kTSA37MarsToAI6 = 167; +static const ExtraID kTSA37PegasusAI6 = 168; +static const ExtraID kTSA37AI6ToMars = 169; +static const ExtraID kTSA37MarsToDepart = 170; +static const ExtraID kTSA37JumpToNoradMenu = 171; +static const ExtraID kTSA37CancelNorad = 172; +static const ExtraID kTSA37JumpToNorad = 173; +static const ExtraID kTSA37NoradToAI7 = 174; +static const ExtraID kTSA37PegasusAI7 = 175; +static const ExtraID kTSA37AI7ToNorad = 176; +static const ExtraID kTSA37NoradToDepart = 177; +static const ExtraID kTSA37EnvironmentalScan = 178; +static const ExtraID kTSA37DownloadToMainMenu = 179; +static const ExtraID kTSA37DownloadToOpMemReview = 180; +static const ExtraID kTSA37OpMemReviewToMainMenu = 181; +static const ExtraID kTSA37OpMemReviewToAllClear = 182; +static const ExtraID kTSA37AllClearToCongratulations = 183; +static const ExtraID kTSA37Congratulations = 184; +static const ExtraID kTSA37CongratulationsToExit = 185; + +const DisplayOrder kRipTimerOrder = kMonitorLayer; + + +const CoordType kUnresolvedLeft = kNavAreaLeft + 14; +const CoordType kUnresolvedTop = kNavAreaTop + 236; + +const CoordType kResolvedLeft = kNavAreaLeft + 36; +const CoordType kResolvedTop = kNavAreaTop + 236; + +const CoordType kJumpMenuLeft = kNavAreaLeft + 360; +const CoordType kJumpMenuTop = kNavAreaTop + 202; + +const CoordType kJumpMenuHilitedLeft = kNavAreaLeft + 354; +const CoordType kJumpMenuHilitedTop = kNavAreaTop + 196; + +const CoordType kExitLeft = kNavAreaLeft + 360; +const CoordType kExitTop = kNavAreaTop + 216; + +const CoordType kExitHilitedLeft = kNavAreaLeft + 354; +const CoordType kExitHilitedTop = kNavAreaTop + 210; + +const CoordType kRipTimerLeft = kNavAreaLeft + 95; +const CoordType kRipTimerTop = kNavAreaTop + 87; + +const CoordType kTBPCloseLeft = kNavAreaLeft + 30; +const CoordType kTBPCloseTop = kNavAreaTop + 16; + +const CoordType kTBPRewindLeft = kNavAreaLeft + 86; +const CoordType kTBPRewindTop = kNavAreaTop + 218; + +const CoordType kComparisonCloseLeft = kNavAreaLeft + 50; +const CoordType kComparisonCloseTop = kNavAreaTop + 14; + +const CoordType kComparisonLeftRewindLeft = kNavAreaLeft + 96; +const CoordType kComparisonLeftRewindTop = kNavAreaTop + 190; + +const CoordType kComparisonRightRewindLeft = kNavAreaLeft + 282; +const CoordType kComparisonRightRewindTop = kNavAreaTop + 190; + +const CoordType kComparisonHiliteSpriteLeft = kNavAreaLeft + 45; +const CoordType kComparisonHiliteSpriteTop = kNavAreaTop + 65; + +const CoordType kComparisonHiliteNoradLeft = kNavAreaLeft + 45; +const CoordType kComparisonHiliteNoradTop = kNavAreaTop + 65; + +const CoordType kComparisonHiliteMarsLeft = kNavAreaLeft + 45 + 4; +const CoordType kComparisonHiliteMarsTop = kNavAreaTop + 65 + 23; + +const CoordType kComparisonHiliteCaldoriaLeft = kNavAreaLeft + 45 + 7; +const CoordType kComparisonHiliteCaldoriaTop = kNavAreaTop + 65 + 46; + +const CoordType kComparisonHiliteWSCLeft = kNavAreaLeft + 45 + 11; +const CoordType kComparisonHiliteWSCTop = kNavAreaTop + 65 + 68; + +const CoordType kComparisonChancesSpriteLeft = kNavAreaLeft + 148; +const CoordType kComparisonChancesSpriteTop = kNavAreaTop + 162; + +const CoordType kComparisonChancesNoradLeft = kNavAreaLeft + 148; +const CoordType kComparisonChancesNoradTop = kNavAreaTop + 162; + +const CoordType kComparisonChancesMarsLeft = kNavAreaLeft + 148; +const CoordType kComparisonChancesMarsTop = kNavAreaTop + 162; + +const CoordType kComparisonChancesCaldoriaLeft = kNavAreaLeft + 148; +const CoordType kComparisonChancesCaldoriaTop = kNavAreaTop + 162 + 1; + +const CoordType kComparisonChancesWSCLeft = kNavAreaLeft + 148; +const CoordType kComparisonChancesWSCTop = kNavAreaTop + 162; + +const CoordType kRedirectionSprite1Left = kNavAreaLeft + 58; +const CoordType kRedirectionSprite1Top = kNavAreaTop + 16; + +const CoordType kRedirectionSprite2Left = kNavAreaLeft + 36; +const CoordType kRedirectionSprite2Top = kNavAreaTop + 166; + +const CoordType kRedirectionCCRolloverLeft = kNavAreaLeft + 58; +const CoordType kRedirectionCCRolloverTop = kNavAreaTop + 16; + +const CoordType kRedirectionRRRolloverLeft = kNavAreaLeft + 430; +const CoordType kRedirectionRRRolloverTop = kNavAreaTop + 30; + +const CoordType kRedirectionFDRolloverLeft = kNavAreaLeft + 278; +const CoordType kRedirectionFDRolloverTop = kNavAreaTop + 160; + +const CoordType kRedirectionCCDoorLeft = kNavAreaLeft + 174; +const CoordType kRedirectionCCDoorTop = kNavAreaTop + 36; + +const CoordType kRedirectionRRDoorLeft = kNavAreaLeft + 418; +const CoordType kRedirectionRRDoorTop = kNavAreaTop + 32; + +const CoordType kRedirectionFDDoorLeft = kNavAreaLeft + 298; +const CoordType kRedirectionFDDoorTop = kNavAreaTop + 240; + +const CoordType kRedirectionSecuredLeft = kNavAreaLeft + 36; +const CoordType kRedirectionSecuredTop = kNavAreaTop + 166; + +const CoordType kRedirectionNewTargetLeft = kNavAreaLeft + 36; +const CoordType kRedirectionNewTargetTop = kNavAreaTop + 166; + +const CoordType kRedirectionCloseLeft = kNavAreaLeft + 56; +const CoordType kRedirectionCloseTop = kNavAreaTop + 220; + +static const TimeValue kTSABumpIntoWallIn = 0; +static const TimeValue kTSABumpIntoWallOut = 148; + +static const TimeValue kTSAGTDoorCloseIn = 148; +static const TimeValue kTSAGTDoorCloseOut = 1570; + +static const TimeValue kTSANoOtherDestinationIn = 1570; +static const TimeValue kTSANoOtherDestinationOut = 3601; + +static const TimeValue kTSAEntryDoorCloseIn = 3601; +static const TimeValue kTSAEntryDoorCloseOut = 4200; + +static const TimeValue kTSAInsideDoorCloseIn = 4200; +static const TimeValue kTSAInsideDoorCloseOut = 4800; + +static const TimeValue kTSAVaultCloseIn = 4800; +static const TimeValue kTSAVaultCloseOut = 5388; + +static const TimeValue kTSAPegasusDoorCloseIn = 5388; +static const TimeValue kTSAPegasusDoorCloseOut = 6457; + +static const bool kPegasusUnresolved = false; +static const bool kPegasusResolved = true; +static const bool kPegasusCantExit = false; +static const bool kPegasusCanExit = true; + +// Monitor modes +enum { + kMonitorNeutral = 0, + kMonitorTheory = 1, + kMonitorProcedure = 2, + kMonitorBackground = 3, + kMonitorNoradComparison = 4, + kMonitorMarsComparison = 5, + kMonitorCaldoriaComparison = 6, + kMonitorWSCComparison = 7, + + kRawModeMask = 0x0F, + kPlayingTBPMask = 0x10, + kPlayingLeftComparisonMask = 0x20, + kPlayingRightComparisonMask = 0x40, + + kPlayingAnyMask = kPlayingTBPMask | + kPlayingLeftComparisonMask | + kPlayingRightComparisonMask, + + kMonitorPlayingTheory = kMonitorTheory | kPlayingTBPMask, + kMonitorPlayingProcedure = kMonitorProcedure | kPlayingTBPMask, + kMonitorPlayingBackground = kMonitorBackground | kPlayingTBPMask, + + kMonitorPlayingLeftNoradComparison = kMonitorNoradComparison | + kPlayingLeftComparisonMask, + kMonitorPlayingRightNoradComparison = kMonitorNoradComparison | + kPlayingRightComparisonMask, + kMonitorPlayingLeftMarsComparison = kMonitorMarsComparison | + kPlayingLeftComparisonMask, + kMonitorPlayingRightMarsComparison = kMonitorMarsComparison | + kPlayingRightComparisonMask, + kMonitorPlayingLeftCaldoriaComparison = kMonitorCaldoriaComparison | + kPlayingLeftComparisonMask, + kMonitorPlayingRightCaldoriaComparison = kMonitorCaldoriaComparison | + kPlayingRightComparisonMask, + kMonitorPlayingLeftWSCComparison = kMonitorWSCComparison | + kPlayingLeftComparisonMask, + kMonitorPlayingRightWSCComparison = kMonitorWSCComparison | + kPlayingRightComparisonMask +}; + +static const ExtraID s_historicalLogViews[16] = { + kTSA0BComparisonView0000, + kTSA0BComparisonView0002, + kTSA0BComparisonView0020, + kTSA0BComparisonView0022, + kTSA0BComparisonView0200, + kTSA0BComparisonView0202, + kTSA0BComparisonView0220, + kTSA0BComparisonView0222, + kTSA0BComparisonView2000, + kTSA0BComparisonView2002, + kTSA0BComparisonView2020, + kTSA0BComparisonView2022, + kTSA0BComparisonView2200, + kTSA0BComparisonView2202, + kTSA0BComparisonView2220, + kTSA0BComparisonView2222 +}; + +static const int kRedirectionCCRolloverSprite = 0; +static const int kRedirectionRRRolloverSprite = 1; +static const int kRedirectionFDRolloverSprite = 2; +static const int kRedirectionCCDoorSprite = 3; +static const int kRedirectionRRDoorSprite = 4; +static const int kRedirectionFDDoorSprite = 5; +static const int kRedirectionCloseSprite = 6; +static const int kRedirectionSecuredSprite = 0; +static const int kRedirectionNewTargetSprite = 1; + +void RipTimer::initImage() { + _middle = -1; + + _timerImage.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kLeftRipPICTID); + + Common::Rect r; + _timerImage.getSurfaceBounds(r); + setBounds(r); +} + +void RipTimer::releaseImage() { + _timerImage.deallocateSurface(); +} + +void RipTimer::draw(const Common::Rect &updateRect) { + Common::Rect bounds; + getBounds(bounds); + + Common::Rect r1 = bounds; + r1.right = _middle; + r1 = updateRect.findIntersectingRect(r1); + + if (!r1.isEmpty()) { + Common::Rect r2 = r1; + r2.moveTo(r1.left - _bounds.left, r1.top - bounds.top); + _timerImage.copyToCurrentPort(r2, r1); + } +} + +void RipTimer::timeChanged(const TimeValue newTime) { + Common::Rect bounds; + getBounds(bounds); + + CoordType newMiddle = bounds.left + bounds.width() * newTime / getDuration(); + + if (newMiddle != _middle) { + _middle = newMiddle; + triggerRedraw(); + } + + if (newTime == getStop()) + ((PegasusEngine *)g_engine)->die(kDeathUncreatedInTSA); +} + +FullTSA::FullTSA(InputHandler *nextHandler, PegasusEngine *owner) : Neighborhood(nextHandler, owner, "Full TSA", kFullTSAID), + _ripTimer(kNoDisplayElement), _sprite1(kNoDisplayElement), _sprite2(kNoDisplayElement), _sprite3(kNoDisplayElement) { + setIsItemTaken(kJourneymanKey); + setIsItemTaken(kPegasusBiochip); + setIsItemTaken(kMapBiochip); +} + +void FullTSA::init() { + Neighborhood::init(); + _ripTimer.setDisplayOrder(kRipTimerOrder); + _ripTimer.startDisplaying(); + + if (!GameState.getTSASeenRobotGreeting()) + forceStridingStop(kTSA03, kNorth, kNoAlternateID); + + _sprite1.setDisplayOrder(kMonitorLayer); + _sprite1.startDisplaying(); + _sprite2.setDisplayOrder(kMonitorLayer); + _sprite2.startDisplaying(); + _sprite3.setDisplayOrder(kMonitorLayer); + _sprite3.startDisplaying(); + + // Fix a mistake in the world builder tables. + HotspotInfoTable::Entry *entry = findHotspotEntry(kTSA23WestChipsSpotID); + entry->hotspotItem = kPegasusBiochip; +} + +void uncreatedInTSAFunction(FunctionPtr *, void *tsa) { + ((FullTSA *)tsa)->die(kDeathUncreatedInTSA); +} + +void FullTSA::start() { + g_energyMonitor->stopEnergyDraining(); + + if (!GameState.getScoringEnterTSA()) { + _utilityFuse.primeFuse(GameState.getTSAFuseTimeLimit()); + _utilityFuse.setFunctionPtr(&uncreatedInTSAFunction, (void *)this); + _utilityFuse.lightFuse(); + } else if (GameState.getTSAState() == kTSAPlayerDetectedRip || GameState.getTSAState() == kTSAPlayerNeedsHistoricalLog) { + _ripTimer.initImage(); + _ripTimer.moveElementTo(kRipTimerLeft, kRipTimerTop); + _ripTimer.setSegment(0, kRipTimeLimit, kRipTimeScale); + _ripTimer.setTime(GameState.getRipTimerTime()); + _ripTimer.start(); + } + + Neighborhood::start(); +} + +void FullTSA::flushGameState() { + GameState.setRipTimerTime(_ripTimer.getTime()); + GameState.setTSAFuseTimeLimit(_utilityFuse.getTimeRemaining()); +} + +Common::String FullTSA::getBriefingMovie() { + Common::String movieName = Neighborhood::getBriefingMovie(); + + if (movieName.empty()) { + RoomID room = GameState.getCurrentRoom(); + + switch (GameState.getTSAState()) { + case kTSAPlayerNotArrived: + case kTSAPlayerForcedReview: + if (room >= kTSA16 && room <= kTSA0B) + return "Images/AI/TSA/XT01A"; + + return "Images/AI/TSA/XT01"; + case kTSAPlayerDetectedRip: + case kTSAPlayerNeedsHistoricalLog: + return "Images/AI/TSA/XT02"; + case kTSAPlayerGotHistoricalLog: + case kTSAPlayerInstalledHistoricalLog: + return "Images/AI/TSA/XT03"; + default: + switch (getCurrentActivation()) { + case kActivationJumpToPrehistoric: + g_AIChip->showBriefingClicked(); + startExtraSequenceSync(kTSA37PegasusAI2, kHintInterruption); + startExtraSequenceSync(kTSA37AI2ToPrehistoric, kFilterNoInput); + g_AIChip->clearClicked(); + break; + case kActivationJumpToNorad: + g_AIChip->showBriefingClicked(); + startExtraSequenceSync(kTSA37PegasusAI7, kHintInterruption); + startExtraSequenceSync(kTSA37AI7ToNorad, kFilterNoInput); + g_AIChip->clearClicked(); + break; + case kActivationJumpToMars: + g_AIChip->showBriefingClicked(); + startExtraSequenceSync(kTSA37PegasusAI6, kHintInterruption); + startExtraSequenceSync(kTSA37AI6ToMars, kFilterNoInput); + g_AIChip->clearClicked(); + break; + case kActivationJumpToWSC: + g_AIChip->showBriefingClicked(); + startExtraSequenceSync(kTSA37PegasusAI5, kHintInterruption); + startExtraSequenceSync(kTSA37AI5ToWSC, kFilterNoInput); + g_AIChip->clearClicked(); + break; + default: + if (GameState.allTimeZonesFinished()) + return "Images/AI/TSA/XT05"; + + return "Images/AI/TSA/XT04"; + } + break; + } + } + + return movieName; +} + +Common::String FullTSA::getEnvScanMovie() { + Common::String movieName = Neighborhood::getEnvScanMovie(); + + if (movieName.empty()) { + switch (GameState.getTSAState()) { + case kTSAPlayerNotArrived: + case kTSAPlayerForcedReview: + case kTSAPlayerDetectedRip: + case kTSAPlayerNeedsHistoricalLog: + return "Images/AI/TSA/XTE1"; + default: + if (GameState.getCurrentRoom() == kTSA37) { + g_AIChip->showEnvScanClicked(); + startExtraSequenceSync(kTSA37EnvironmentalScan, kHintInterruption); + + switch (getCurrentActivation()) { + case kActivationJumpToPrehistoric: + startExtraSequenceSync(kTSA37AI2ToPrehistoric, kFilterNoInput); + break; + case kActivationJumpToNorad: + startExtraSequenceSync(kTSA37AI7ToNorad, kFilterNoInput); + showExtraView(kTSA37JumpToNoradMenu); + break; + case kActivationJumpToMars: + startExtraSequenceSync(kTSA37AI6ToMars, kFilterNoInput); + showExtraView(kTSA37JumpToMarsMenu); + break; + case kActivationJumpToWSC: + startExtraSequenceSync(kTSA37AI5ToWSC, kFilterNoInput); + showExtraView(kTSA37JumpToWSCMenu); + break; + default: + startExtraSequenceSync(kTSA37AI4ToMainMenu, kFilterNoInput); + break; + } + + g_AIChip->clearClicked(); + } else if (GameState.allTimeZonesFinished()) { + return "Images/AI/TSA/XTE1"; + } else { + return "Images/AI/TSA/XTE2"; + } + break; + } + } + + return movieName; +} + +uint FullTSA::getNumHints() { + uint numHints = Neighborhood::getNumHints(); + + if (numHints == 0) { + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + if (GameState.getCurrentRoom() == kTSA0B && GameState.getTSA0BZoomedIn()) + numHints = 3; + break; + } + } + + return numHints; +} + +Common::String FullTSA::getHintMovie(uint hintNum) { + Common::String movieName = Neighborhood::getHintMovie(hintNum); + + if (movieName.empty()) + movieName = Common::String::format("Images/AI/TSA/XT20NH%d", hintNum); + + return movieName; +} + +void FullTSA::loadAmbientLoops() { + RoomID room = GameState.getCurrentRoom(); + + switch (GameState.getTSAState()) { + case kTSAPlayerDetectedRip: + case kTSAPlayerNeedsHistoricalLog: + if ((room >= kTSA16 && room <= kTSA0B) || (room >= kTSA21Cyan && room <= kTSA24Cyan) || (room >= kTSA21Red && room <= kTSA24Red)) + loadLoopSound1("Sounds/TSA/TSA CLAXON.22K.AIFF", 0x100 / 4, 0, 0); + else if (room == kTSA25Cyan || room == kTSA25Red) + loadLoopSound1("Sounds/TSA/TSA CLAXON.22K.AIFF", 0x100 / 6, 0, 0); + else + loadLoopSound1("Sounds/TSA/TSA EchoClaxon.22K.AIFF", 0x100 / 4, 0, 0); + break; + default: + if (room >= kTSA00 && room <= kTSA02) + loadLoopSound1("Sounds/TSA/T01NAE.NEW.22K.AIFF"); + else if (room >= kTSA03 && room <= kTSA15) + loadLoopSound1("Sounds/TSA/T01NAE.NEW.22K.AIFF"); + else if (room >= kTSA16 && room <= kTSA0B) + loadLoopSound1("Sounds/TSA/T14SAEO1.22K.AIFF"); + else if (room >= kTSA21Cyan && room <= kTSA25Red) + loadLoopSound1("Sounds/TSA/T15SAE01.22K.AIFF"); + else if (room >= kTSA26 && room <= kTSA37) + loadLoopSound1("Sounds/TSA/T01NAE.NEW.22K.AIFF"); + break; + } +} + +short FullTSA::getStaticCompassAngle(const RoomID room, const DirectionConstant dir) { + int16 result = Neighborhood::getStaticCompassAngle(room, dir); + + switch (room) { + case kTSA08: + result += kCompassShift; + break; + case kTSA09: + result -= kCompassShift; + break; + case kTSA10: + result += kCompassShift * 2; + break; + case kTSA11: + case kTSA22Cyan: + case kTSA22Red: + result -= kCompassShift * 2; + break; + case kTSA12: + result += kCompassShift * 3; + break; + case kTSA13: + result -= kCompassShift * 3; + break; + case kTSA14: + case kTSA16: + case kTSA17: + case kTSA18: + case kTSA19: + result += kCompassShift * 4; + break; + case kTSA0B: + result += kCompassShift * 4; + + if (dir == kWest) + result += 30; + else if (dir == kEast) + result -= 30; + break; + case kTSA33: + result += kCompassShift * 4; + break; + case kTSA15: + case kTSA21Cyan: + case kTSA24Cyan: + case kTSA25Cyan: + case kTSA21Red: + case kTSA24Red: + case kTSA25Red: + case kTSA26: + case kTSA27: + case kTSA28: + case kTSA29: + case kTSA30: + result -= kCompassShift * 4; + break; + case kTSA23Cyan: + case kTSA23Red: + result -= kCompassShift * 6; + break; + case kTSA32: + result -= kCompassShift * 8; + break; + case kTSA34: + result -= kCompassShift * 12; + break; + case kTSA35: + result += kCompassShift * 8; + break; + case kTSA37: + result -= kCompassShift * 2; + break; + } + + return result; +} + +void FullTSA::getExitCompassMove(const ExitTable::Entry &exitEntry, FaderMoveSpec &compassMove) { + Neighborhood::getExitCompassMove(exitEntry, compassMove); + + switch (MakeRoomView(exitEntry.room, exitEntry.direction)) { + case MakeRoomView(kTSA01, kSouth): + compassMove.insertFaderKnot(exitEntry.movieStart, -180); + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 3, -180); + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 33, + getStaticCompassAngle(exitEntry.exitRoom, exitEntry.exitDirection)); + break; + case MakeRoomView(kTSA11, kEast): + if (getCurrentAlternate() == kAltTSARobotsAtReadyRoom) { + compassMove.makeTwoKnotFaderSpec(kFullTSAMovieScale, exitEntry.movieStart, + getStaticCompassAngle(kTSA11, kEast), exitEntry.movieEnd, + getStaticCompassAngle(kTSA13, kEast)); + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 13, compassMove.getNthKnotValue(1)); + } + break; + case MakeRoomView(kTSA34, kNorth): + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 48, + getStaticCompassAngle(exitEntry.room, exitEntry.direction)); + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 68, + getStaticCompassAngle(exitEntry.exitRoom, exitEntry.exitDirection)); + break; + case MakeRoomView(kTSA37, kNorth): + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 38, + getStaticCompassAngle(exitEntry.room, exitEntry.direction)); + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 64, + getStaticCompassAngle(exitEntry.room, exitEntry.direction) + kCompassShift * 3 / 2); + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 105, + getStaticCompassAngle(exitEntry.exitRoom, exitEntry.exitDirection)); + break; + } +} + +void FullTSA::getExtraCompassMove(const ExtraTable::Entry &extraEntry, FaderMoveSpec &compassMove) { + int16 angle; + + switch (extraEntry.extra) { + case kTSA0BEastTurnLeft: + case kTSA0BNorthTurnLeft: + angle =getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection()); + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), extraEntry.movieStart, angle, + extraEntry.movieEnd, angle - 60); + break; + case kTSA0BNorthTurnRight: + case kTSA0BWestTurnRight: + angle = getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection()); + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), extraEntry.movieStart, angle, + extraEntry.movieEnd, angle + 60); + break; + case kTSA22RedEastZoomInSequence: + angle = getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection()); + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), extraEntry.movieStart, angle, + extraEntry.movieEnd, angle); + compassMove.insertFaderKnot(extraEntry.movieStart + 1200, angle - kCompassShift * 2); + compassMove.insertFaderKnot(extraEntry.movieStart + 8160, angle - kCompassShift * 2); + compassMove.insertFaderKnot(extraEntry.movieStart + 9840, angle); + break; + case kTSA23RedWestVaultZoomInSequence: + angle = getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection()); + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), extraEntry.movieStart, angle, + extraEntry.movieEnd, angle); + compassMove.insertFaderKnot(extraEntry.movieStart + 1200, angle - kCompassShift * 2); + compassMove.insertFaderKnot(extraEntry.movieStart + 10100, angle - kCompassShift * 2); + compassMove.insertFaderKnot(extraEntry.movieStart + 11880, angle); + break; + default: + Neighborhood::getExtraCompassMove(extraEntry, compassMove); + break; + } +} + +uint16 FullTSA::getDateResID() const { + return kDate2318ID; +} + +TimeValue FullTSA::getViewTime(const RoomID room, const DirectionConstant direction) { + ExtraID extraID = 0xffffffff; + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kTSA0B, kEast): + if (GameState.getTSA0BZoomedIn()) + switch (GameState.getTSAState()) { + case kTSAPlayerInstalledHistoricalLog: + case kTSABossSawHistoricalLog: + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + extraID = s_historicalLogViews[getHistoricalLogIndex()]; + break; + default: + extraID = kTSA0BEastZoomedView; + break; + } + break; + case MakeRoomView(kTSA0B, kNorth): + if (GameState.getTSA0BZoomedIn()) + switch (GameState.getTSAState()) { + case kTSAPlayerNeedsHistoricalLog: + extraID = kTSA0BNorthRipView1; + break; + default: + extraID = kTSA0BNorthZoomedView; + break; + } + break; + case MakeRoomView(kTSA0B, kWest): + if (GameState.getTSA0BZoomedIn()) + extraID = kTSA0BWestZoomedView; + break; + case MakeRoomView(kTSA22Red, kEast): + if (_privateFlags.getFlag(kTSAPrivateKeyVaultOpenFlag)) { + if (_vm->itemInLocation(kJourneymanKey, kFullTSAID, kTSA22Red, kEast)) + extraID = kTSA22RedEastVaultViewWithKey; + else + extraID = kTSA22RedEastVaultViewNoKey; + } + break; + case MakeRoomView(kTSA23Red, kWest): + if (_privateFlags.getFlag(kTSAPrivateChipVaultOpenFlag)) { + if (_vm->itemInLocation(kPegasusBiochip, kFullTSAID, kTSA23Red, kWest)) + extraID = kTSA23RedWestVaultViewWithChips; + else + extraID = kTSA23RedWestVaultViewNoChips; + } + break; + case MakeRoomView(kTSA37, kNorth): + switch (GameState.getTSAState()) { + case kTSAPlayerGotHistoricalLog: + extraID = kTSA37ReviewRequiredToExit; + break; + case kPlayerFinishedWithTSA: + extraID = kTSA37CongratulationsToExit; + break; + default: + extraID = kTSA37AI3ToHorse; + break; + } + break; + } + + if (extraID != 0xffffffff) { + ExtraTable::Entry entry; + getExtraEntry(extraID, entry); + return entry.movieEnd - 1; + } + + return Neighborhood::getViewTime(room, direction); +} + +void FullTSA::findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &entry) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kTSA0B, kNorth): + case MakeRoomView(kTSA0B, kEast): + case MakeRoomView(kTSA0B, kWest): + if (!GameState.getTSA0BZoomedIn()) + Neighborhood::findSpotEntry(room, direction, flags, entry); + break; + default: + Neighborhood::findSpotEntry(room, direction, flags, entry); + break; + } +} + +void FullTSA::getExtraEntry(const uint32 id, ExtraTable::Entry &extraEntry) { + Neighborhood::getExtraEntry(id, extraEntry); + + if (id == kTSA0BShowGuardRobots) + extraEntry.movieStart += kFullTSAFrameDuration * 3; +} + +void FullTSA::pickedUpItem(Item *item) { + BiochipItem *biochip; + + switch (item->getObjectID()) { + case kJourneymanKey: + GameState.setScoringGotJourneymanKey(true); + break; + case kPegasusBiochip: + biochip = (BiochipItem *)g_allItems.findItemByID(kMapBiochip); + _vm->addItemToBiochips(biochip); + GameState.setScoringGotPegasusBiochip(true); + break; + } +} + +void FullTSA::playExtraMovie(const ExtraTable::Entry &extraEntry, const NotificationFlags flags, const InputBits interruptionInput) { + switch (extraEntry.extra) { + case kTSA0BNorthZoomIn: + if (_privateFlags.getFlag(kTSAPrivateLogReaderOpenFlag)) { + _privateFlags.setFlag(kTSAPrivateLogReaderOpenFlag, false); + requestExtraSequence(kTSA0BNorthHistLogClose, 0, kFilterNoInput); + requestExtraSequence(kTSA0BNorthZoomIn, kExtraCompletedFlag, kFilterNoInput); + } else { + Neighborhood::playExtraMovie(extraEntry, flags, interruptionInput); + } + break; + case kTSA0BNorthZoomOut: + if (_ripTimer.isVisible()) + _ripTimer.hide(); + + shutDownRobotMonitor(); + Neighborhood::playExtraMovie(extraEntry, flags, interruptionInput); + break; + case kTSA0BEastZoomOut: + shutDownComparisonMonitor(); + Neighborhood::playExtraMovie(extraEntry, flags, interruptionInput); + break; + default: + Neighborhood::playExtraMovie(extraEntry, flags, interruptionInput); + break; + } +} + +void FullTSA::startDoorOpenMovie(const TimeValue startTime, const TimeValue stopTime) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kTSA00, kNorth): + if (GameState.getLastNeighborhood() != kFullTSAID) { + startExtraSequence(kTSAArriveFromCaldoria, kDoorOpenCompletedFlag, kFilterNoInput); + return; + } + break; + case MakeRoomView(kTSA02, kNorth): + if (!GameState.getTSAIDedAtDoor()) { + GameState.setTSAIDedAtDoor(true); + requestExtraSequence(kTSA02NorthZoomIn, 0, kFilterNoInput); + requestExtraSequence(kTSA02NorthTenSecondDoor, 0, kFilterNoInput); + + if (GameState.getTSASeenAgent3AtDoor()) { + requestExtraSequence(kTSA02NorthZoomOut, kExtraCompletedFlag, kFilterNoInput); + } else { + GameState.setTSASeenAgent3AtDoor(true); + requestExtraSequence(kTSA02NorthZoomOut, 0, kFilterNoInput); + requestExtraSequence(kTSA02NorthDoorWithAgent3, kDoorOpenCompletedFlag, kFilterNoInput); + } + return; + } + break; + case MakeRoomView(kTSA03, kSouth): + if (GameState.getTSAState() == kRobotsAtFrontDoor) { + playDeathExtra(kTSA03SouthRobotDeath, kDeathShotByTSARobots); + return; + } + break; + case MakeRoomView(kTSA16, kNorth): + if (GameState.getTSAState() == kRobotsAtCommandCenter) { + playDeathExtra(kTSA16NorthRobotDeath, kDeathShotByTSARobots); + return; + } + break; + } + + Neighborhood::startDoorOpenMovie(startTime, stopTime); +} + +InputBits FullTSA::getInputFilter() { + InputBits result = Neighborhood::getInputFilter(); + + switch (GameState.getCurrentRoom()) { + case kTSA0B: + if (GameState.getT0BMonitorMode() != kMonitorNeutral) + // Only allow a click. + result &= JMPPPInput::getClickInputFilter(); + break; + case kTSA37: + // Can't move forward in Pegasus. Only press the exit button. + result &= ~(kFilterUpButton | kFilterUpAuto); + break; + } + + return result; +} + +void FullTSA::turnLeft() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kTSA15, kNorth): + if (GameState.getTSAState() == kTSAPlayerNeedsHistoricalLog) + setCurrentAlternate(kAltTSANormal); + break; + case MakeRoomView(kTSA0B, kNorth): + if (_ripTimer.isVisible()) + _ripTimer.hide(); + releaseSprites(); + break; + case MakeRoomView(kTSA0B, kEast): + shutDownComparisonMonitor(); + break; + } + + Neighborhood::turnLeft(); +} + +void FullTSA::turnRight() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kTSA15, kSouth): + if (GameState.getTSAState() == kTSAPlayerNeedsHistoricalLog) + setCurrentAlternate(kAltTSANormal); + break; + case MakeRoomView(kTSA0B, kNorth): + if (_ripTimer.isVisible()) + _ripTimer.hide(); + releaseSprites(); + break; + case MakeRoomView(kTSA0B, kEast): + shutDownComparisonMonitor(); + break; + } + + Neighborhood::turnRight(); +} + +void FullTSA::openDoor() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kTSA15, kSouth): + if (GameState.getTSAState() == kTSAPlayerNeedsHistoricalLog || GameState.getTSAState() == kRobotsAtFrontDoor) + setCurrentAlternate(kAltTSARedAlert); + break; + } + + Neighborhood::openDoor(); +} + +CanMoveForwardReason FullTSA::canMoveForward(ExitTable::Entry &entry) { + if (GameState.getCurrentRoomAndView() == MakeRoomView(kTSA25Red, kNorth)) + return kCantMoveBlocked; + + return Neighborhood::canMoveForward(entry); +} + +CanOpenDoorReason FullTSA::canOpenDoor(DoorTable::Entry &entry) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kTSA02, kNorth): + if (!GameState.getTSAFrontDoorUnlockedOutside()) + return kCantOpenLocked; + break; + case MakeRoomView(kTSA03, kSouth): + if (!GameState.getTSAFrontDoorUnlockedInside()) + return kCantOpenLocked; + break; + case MakeRoomView(kTSA16, kNorth): + if (GameState.getTSACommandCenterLocked()) + return kCantOpenLocked; + break; + } + + return Neighborhood::canOpenDoor(entry); +} + +void FullTSA::bumpIntoWall() { + requestSpotSound(kTSABumpIntoWallIn, kTSABumpIntoWallOut, kFilterAllInput, 0); + Neighborhood::bumpIntoWall(); +} + +void FullTSA::downButton(const Input &input) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kTSA0B, kEast): + if (GameState.getTSA0BZoomedIn()) + startExtraSequence(kTSA0BEastZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kTSA0B, kNorth): + if (GameState.getTSA0BZoomedIn()) + startExtraSequence(kTSA0BNorthZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kTSA0B, kWest): + if (GameState.getTSA0BZoomedIn() && GameState.getT0BMonitorMode() == kMonitorNeutral) + startExtraSequence(kTSA0BWestZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + default: + Neighborhood::downButton(input); + } +} + +void FullTSA::activateOneHotspot(HotspotInfoTable::Entry &entry, Hotspot *spot) { + switch (spot->getObjectID()) { + case kTSA0BEastLeftRewindSpotID: + case kTSA0BEastLeftPlaySpotID: + if (_privateFlags.getFlag(kTSAPrivatePlayingRightComparisonFlag)) + spot->setInactive(); + else + Neighborhood::activateOneHotspot(entry, spot); + break; + case kTSA0BEastRightRewindSpotID: + case kTSA0BEastRightPlaySpotID: + if (_privateFlags.getFlag(kTSAPrivatePlayingLeftComparisonFlag)) + spot->setInactive(); + else + Neighborhood::activateOneHotspot(entry, spot); + break; + default: + Neighborhood::activateOneHotspot(entry, spot); + break; + } +} + +void FullTSA::activateHotspots() { + Neighborhood::activateHotspots(); + + switch (MakeRoomView(GameState.getCurrentRoom(), GameState.getCurrentDirection())) { + case MakeRoomView(kTSA02, kNorth): + if (!GameState.getTSAFrontDoorUnlockedOutside()) + g_allHotspots.activateOneHotspot(kTSA02DoorSpotID); + break; + case MakeRoomView(kTSA0B, kEast): + if (GameState.getTSA0BZoomedIn()) + switch (GameState.getTSAState()) { + case kTSAPlayerInstalledHistoricalLog: + case kTSABossSawHistoricalLog: + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + if (getCurrentActivation() != kActivateTSA0BComparisonVideo) { + g_allHotspots.activateOneHotspot(kTSA0BEastCompareNoradSpotID); + g_allHotspots.activateOneHotspot(kTSA0BEastCompareMarsSpotID); + g_allHotspots.activateOneHotspot(kTSA0BEastCompareCaldoriaSpotID); + g_allHotspots.activateOneHotspot(kTSA0BEastCompareWSCSpotID); + } + break; + } + break; + case MakeRoomView(kTSA0B, kNorth): + if (GameState.getTSA0BZoomedIn()) + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + g_allHotspots.activateOneHotspot(kTSA0BNorthRobotsToCommandCenterSpotID); + g_allHotspots.activateOneHotspot(kTSA0BNorthRobotsToReadyRoomSpotID); + g_allHotspots.activateOneHotspot(kTSA0BNorthRobotsToFrontDoorSpotID); + break; + } + break; + } +} + +void FullTSA::clickInHotspot(const Input &input, const Hotspot *clickedSpot) { + switch (clickedSpot->getObjectID()) { + case kTSAGTOtherSpotID: + showExtraView(kTSAGTOtherChoice); + playSpotSoundSync(kTSANoOtherDestinationIn, kTSANoOtherDestinationOut); + showExtraView(kTSAGTCardSwipe); + break; + case kTSA02DoorSpotID: + GameState.setTSAFrontDoorUnlockedOutside(true); + Neighborhood::clickInHotspot(input, clickedSpot); + break; + case kTSA03EastJimenezSpotID: + startExtraLongSequence(kTSA03JimenezZoomIn, kTSA03JimenezZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA03WestCrenshawSpotID: + startExtraLongSequence(kTSA03CrenshawZoomIn, kTSA03CrenshawZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA04EastMatsumotoSpotID: + startExtraLongSequence(kTSA04MatsumotoZoomIn, kTSA04MatsumotoZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA04WestCastilleSpotID: + startExtraLongSequence(kTSA04CastilleZoomIn, kTSA04CastilleZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA05EastSinclairSpotID: + startExtraLongSequence(kTSA05SinclairZoomIn, kTSA05SinclairZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA05WestWhiteSpotID: + startExtraLongSequence(kTSA05WhiteZoomIn, kTSA05WhiteZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA0BEastCompareNoradSpotID: + initializeComparisonMonitor(kMonitorNoradComparison, kTSA0BNoradComparisonView); + break; + case kTSA0BEastCompareMarsSpotID: + initializeComparisonMonitor(kMonitorMarsComparison, kTSA0BMarsComparisonView); + break; + case kTSA0BEastCompareCaldoriaSpotID: + initializeComparisonMonitor(kMonitorCaldoriaComparison, kTSA0BCaldoriaComparisonView); + break; + case kTSA0BEastCompareWSCSpotID: + initializeComparisonMonitor(kMonitorWSCComparison, kTSA0BWSCComparisonView); + break; + case kTSA0BEastCloseVideoSpotID: + _navMovie.stop(); + _sprite3.show(); + _vm->delayShell(1, 2); + _sprite3.hide(); + initializeComparisonMonitor(kMonitorNeutral, 0); + break; + case kTSA0BEastLeftPlaySpotID: + playLeftComparison(); + break; + case kTSA0BEastRightPlaySpotID: + playRightComparison(); + break; + + // Command center + case kTSA0BWestTheorySpotID: + initializeTBPMonitor(kMonitorTheory, kTSA0BTBPTheoryHighlight); + break; + case kTSA0BWestBackgroundSpotID: + initializeTBPMonitor(kMonitorBackground, kTSA0BTBPBackgroundHighlight); + break; + case kTSA0BWestProcedureSpotID: + initializeTBPMonitor(kMonitorProcedure, kTSA0BTBPProcedureHighlight); + break; + case kTSA0BWestCloseVideoSpotID: + _navMovie.stop(); + _sprite2.show(); + _vm->delayShell(1, 2); + _sprite2.hide(); + initializeTBPMonitor(kMonitorNeutral, 0); + break; + case kTSA0BWestPlayVideoSpotID: + playTBPMonitor(); + break; + case kTSA0BEastLeftRewindSpotID: + case kTSA0BEastRightRewindSpotID: + case kTSA0BWestRewindVideoSpotID: + if ((GameState.getT0BMonitorMode() & kPlayingAnyMask) != 0) { + bool playing = _navMovie.isRunning(); + if (playing) + _navMovie.stop(); + + if (clickedSpot->getObjectID() == kTSA0BEastRightRewindSpotID) + _sprite2.show(); + else + _sprite1.show(); + + _vm->delayShell(1, 2); + + if (clickedSpot->getObjectID() == kTSA0BEastRightRewindSpotID) + _sprite2.hide(); + else + _sprite1.hide(); + + _navMovie.setTime(GameState.getT0BMonitorStart()); + + if (playing) { + _navMovie.start(); + } else { + _privateFlags.setFlag(kTSAPrivatePlayingLeftComparisonFlag, false); + _privateFlags.setFlag(kTSAPrivatePlayingRightComparisonFlag, false); + } + } + break; + case kTSA22EastMonitorSpotID: + requestExtraSequence(kTSA22RedEastZoomInSequence, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA23WestMonitorSpotID: + requestExtraSequence(kTSA23RedWestVaultZoomInSequence, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA0BNorthRobotsToCommandCenterSpotID: + _sprite1.setCurrentFrameIndex(kRedirectionCCDoorSprite); + _sprite1.show(); + _vm->delayShell(1, 2); + _sprite1.hide(); + + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + // Nothing + break; + case kRobotsAtFrontDoor: + GameState.setTSAState(kRobotsAtCommandCenter); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromFrontDoorToCommandCenter, kExtraCompletedFlag, kFilterNoInput); + break; + case kRobotsAtReadyRoom: + GameState.setTSAState(kRobotsAtCommandCenter); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromReadyRoomToCommandCenter, kExtraCompletedFlag, kFilterNoInput); + break; + } + break; + case kTSA0BNorthRobotsToReadyRoomSpotID: + _sprite1.setCurrentFrameIndex(kRedirectionRRDoorSprite); + _sprite1.show(); + _vm->delayShell(1, 2); + _sprite1.hide(); + + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + GameState.setTSAState(kRobotsAtReadyRoom); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromCommandCenterToReadyRoom, kExtraCompletedFlag, kFilterNoInput); + break; + case kRobotsAtFrontDoor: + GameState.setTSAState(kRobotsAtReadyRoom); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromFrontDoorToReadyRoom, kExtraCompletedFlag, kFilterNoInput); + break; + case kRobotsAtReadyRoom: + // Nothing + break; + } + break; + case kTSA0BNorthRobotsToFrontDoorSpotID: + _sprite1.setCurrentFrameIndex(kRedirectionFDDoorSprite); + _sprite1.show(); + _vm->delayShell(1, 2); + _sprite1.hide(); + + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + GameState.setTSAState(kRobotsAtFrontDoor); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromCommandCenterToFrontDoor, kExtraCompletedFlag, kFilterNoInput); + break; + case kRobotsAtFrontDoor: + // Nothing + break; + case kRobotsAtReadyRoom: + GameState.setTSAState(kRobotsAtFrontDoor); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromReadyRoomToFrontDoor, kExtraCompletedFlag, kFilterNoInput); + break; + } + break; + + // Pegasus + case kTSA37NorthJumpToPrehistoricSpotID: + startExtraSequence(kTSA37PegasusDepart, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA37NorthExitSpotID: + _sprite2.setCurrentFrameIndex(1); + _vm->delayShell(1, 2); + releaseSprites(); + moveForward(); + break; + case kTSA37NorthJumpMenuSpotID: + _sprite2.setCurrentFrameIndex(1); + _vm->delayShell(1, 2); + releaseSprites(); + break; + case kTSA37NorthJumpToNoradSpotID: + GameState.setTSAState(kPlayerOnWayToNorad); + requestExtraSequence(kTSA37JumpToNorad, 0, kFilterNoInput); + + if (!GameState.getBeenToNorad()) { + requestExtraSequence(kTSA37NoradToAI7, 0, kFilterNoInput); + requestExtraSequence(kTSA37PegasusAI7, 0, kFilterNoInput); + requestExtraSequence(kTSA37AI7ToNorad, 0, kFilterNoInput); + GameState.setBeenToNorad(true); + } + + requestExtraSequence(kTSA37NoradToDepart, 0, kFilterNoInput); + requestExtraSequence(kTSA37PegasusDepart, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA37NorthJumpToMarsSpotID: + GameState.setTSAState(kPlayerOnWayToMars); + requestExtraSequence(kTSA37JumpToMars, 0, kFilterNoInput); + + if (!GameState.getBeenToMars()) { + requestExtraSequence(kTSA37MarsToAI6, 0, kFilterNoInput); + requestExtraSequence(kTSA37PegasusAI6, 0, kFilterNoInput); + requestExtraSequence(kTSA37AI6ToMars, 0, kFilterNoInput); + GameState.setBeenToMars(true); + } + + requestExtraSequence(kTSA37MarsToDepart, 0, kFilterNoInput); + requestExtraSequence(kTSA37PegasusDepart, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA37NorthJumpToWSCSpotID: + GameState.setTSAState(kPlayerOnWayToWSC); + requestExtraSequence(kTSA37JumpToWSC, 0, kFilterNoInput); + + if (!GameState.getBeenToWSC()) { + requestExtraSequence(kTSA37WSCToAI5, 0, kFilterNoInput); + requestExtraSequence(kTSA37PegasusAI5, 0, kFilterNoInput); + requestExtraSequence(kTSA37AI5ToWSC, 0, kFilterNoInput); + GameState.setBeenToWSC(true); + } + + requestExtraSequence(kTSA37WSCToDepart, 0, kFilterNoInput); + requestExtraSequence(kTSA37PegasusDepart, kExtraCompletedFlag, kFilterNoInput); + break; + default: + Neighborhood::clickInHotspot(input, clickedSpot); + break; + } +} + +void FullTSA::showMainJumpMenu() { + ExtraID jumpMenuView = kTSA37JumpMenu000; + + if (GameState.getNoradFinished()) + jumpMenuView += 4; + if (GameState.getMarsFinished()) + jumpMenuView += 2; + if (GameState.getWSCFinished()) + jumpMenuView += 1; + + showExtraView(jumpMenuView); + setCurrentActivation(kActivationMainJumpMenu); +} + +void FullTSA::playTBPMonitor() { + InputDevice.waitInput(kFilterAllButtons); + + if ((GameState.getT0BMonitorMode() & kPlayingTBPMask) == 0) { + ExtraID extra; + + switch (GameState.getT0BMonitorMode() & kRawModeMask) { + case kMonitorTheory: + GameState.setTSASeenTheory(true); + extra = kTSA0BTBPTheory; + GameState.setScoringSawTheory(true); + break; + case kMonitorBackground: + GameState.setTSASeenBackground(true); + extra = kTSA0BTBPBackground; + GameState.setScoringSawBackground(true); + break; + case kMonitorProcedure: + GameState.setTSASeenProcedure(true); + extra = kTSA0BTBPProcedure; + GameState.setScoringSawProcedure(true); + break; + default: + error("Invalid monitor mode"); + } + + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() | kPlayingTBPMask); + + ExtraTable::Entry entry; + getExtraEntry(extra, entry); + _lastExtra = extra; + + GameState.setT0BMonitorStart(entry.movieStart + kFullTSAFrameDuration * 5); + startMovieSequence(GameState.getT0BMonitorStart(), entry.movieEnd, kExtraCompletedFlag, false, kFilterAllInput); + } else if (_navMovie.isRunning()) { + _navMovie.stop(); + } else { + _navMovie.start(); + } +} + +void FullTSA::initializeTBPMonitor(const int newMode, const ExtraID highlightExtra) { + GameState.setT0BMonitorMode(newMode); + + if (newMode != kMonitorNeutral) { + showExtraView(highlightExtra); + _vm->delayShell(1, 2); + setCurrentActivation(kActivateTSA0BTBPVideo); + _sprite1.addPICTResourceFrame(kTBPRewindPICTID, false, 0, 0); + _sprite1.moveElementTo(kTBPRewindLeft, kTBPRewindTop); + _sprite1.setCurrentFrameIndex(0); + _sprite2.addPICTResourceFrame(kTBPCloseBoxPICTID, false, 0, 0); + _sprite2.moveElementTo(kTBPCloseLeft, kTBPCloseTop); + _sprite2.setCurrentFrameIndex(0); + playTBPMonitor(); + } else { + if (GameState.getTSAState() == kTSAPlayerForcedReview && GameState.getTSASeenTheory() && + GameState.getTSASeenBackground() && GameState.getTSASeenProcedure()) { + setOffRipAlarm(); + } else { + setCurrentActivation(kActivateTSA0BZoomedIn); + updateViewFrame(); + } + + releaseSprites(); + } + + _interruptionFilter = kFilterAllInput; +} + +void FullTSA::startUpComparisonMonitor() { + releaseSprites(); + + _sprite1.addPICTResourceFrame(kComparisonHiliteNoradPICTID, false, + kComparisonHiliteNoradLeft - kComparisonHiliteSpriteLeft, + kComparisonHiliteNoradTop - kComparisonHiliteSpriteTop); + _sprite1.addPICTResourceFrame(kComparisonHiliteMarsPICTID, false, + kComparisonHiliteMarsLeft - kComparisonHiliteSpriteLeft, + kComparisonHiliteMarsTop - kComparisonHiliteSpriteTop); + _sprite1.addPICTResourceFrame(kComparisonHiliteCaldoriaPICTID, false, + kComparisonHiliteCaldoriaLeft - kComparisonHiliteSpriteLeft, + kComparisonHiliteCaldoriaTop - kComparisonHiliteSpriteTop); + _sprite1.addPICTResourceFrame(kComparisonHiliteWSCPICTID, false, + kComparisonHiliteWSCLeft - kComparisonHiliteSpriteLeft, + kComparisonHiliteWSCTop - kComparisonHiliteSpriteTop); + + _sprite1.setCurrentFrameIndex(0); + _sprite1.moveElementTo(kComparisonHiliteSpriteLeft, kComparisonHiliteSpriteTop); + + _sprite2.addPICTResourceFrame(kComparisonChancesNoradPICTID, false, + kComparisonChancesNoradLeft - kComparisonChancesSpriteLeft, + kComparisonChancesNoradTop - kComparisonChancesSpriteTop); + _sprite2.addPICTResourceFrame(kComparisonChancesMarsPICTID, false, + kComparisonChancesMarsLeft - kComparisonChancesSpriteLeft, + kComparisonChancesMarsTop - kComparisonChancesSpriteTop); + _sprite2.addPICTResourceFrame(kComparisonChancesCaldoriaPICTID, false, + kComparisonChancesCaldoriaLeft - kComparisonChancesSpriteLeft, + kComparisonChancesCaldoriaTop - kComparisonChancesSpriteTop); + _sprite2.addPICTResourceFrame(kComparisonChancesWSCPICTID, false, + kComparisonChancesWSCLeft - kComparisonChancesSpriteLeft, + kComparisonChancesWSCTop - kComparisonChancesSpriteTop); + + _sprite2.setCurrentFrameIndex(0); + _sprite2.moveElementTo(kComparisonChancesSpriteLeft, kComparisonChancesSpriteTop); + updateViewFrame(); +} + +void FullTSA::shutDownComparisonMonitor() { + releaseSprites(); +} + +void FullTSA::initializeComparisonMonitor(const int newMode, const ExtraID comparisonView) { + GameState.setT0BMonitorMode(newMode); + _privateFlags.setFlag(kTSAPrivatePlayingLeftComparisonFlag, false); + _privateFlags.setFlag(kTSAPrivatePlayingRightComparisonFlag, false); + + if (newMode != kMonitorNeutral) { + shutDownComparisonMonitor(); + setCurrentActivation(kActivateTSA0BComparisonVideo); + _sprite1.addPICTResourceFrame(kComparisonLeftRewindPICTID, false, 0, 0); + _sprite1.moveElementTo(kComparisonLeftRewindLeft, kComparisonLeftRewindTop); + _sprite1.setCurrentFrameIndex(0); + _sprite2.addPICTResourceFrame(kComparisonRightRewindPICTID, false, 0, 0); + _sprite2.moveElementTo(kComparisonRightRewindLeft, kComparisonRightRewindTop); + _sprite2.setCurrentFrameIndex(0); + _sprite3.addPICTResourceFrame(kComparisonCloseBoxPICTID, false, 0, 0); + _sprite3.moveElementTo(kComparisonCloseLeft, kComparisonCloseTop); + _sprite3.setCurrentFrameIndex(0); + showExtraView(comparisonView); + } else { + if (GameState.getTSAState() == kTSAPlayerInstalledHistoricalLog && + GameState.getTSASeenNoradNormal() && + GameState.getTSASeenNoradAltered() && + GameState.getTSASeenMarsNormal() && + GameState.getTSASeenMarsAltered() && + GameState.getTSASeenCaldoriaNormal() && + GameState.getTSASeenCaldoriaAltered() && + GameState.getTSASeenWSCNormal() && + GameState.getTSASeenWSCAltered()) { + GameState.setTSAState(kTSABossSawHistoricalLog); + requestExtraSequence(kTSA0BEastZoomOut, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kTSA0BEastTurnLeft, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kTSA0BNorthZoomIn, kExtraCompletedFlag, kFilterNoInput); + } else { + setCurrentActivation(kActivateTSA0BZoomedIn); + releaseSprites(); + startUpComparisonMonitor(); + } + } + + _interruptionFilter = kFilterAllInput; +} + +void FullTSA::playLeftComparison() { + InputDevice.waitInput(kFilterAllButtons); + + if ((GameState.getT0BMonitorMode() & kPlayingLeftComparisonMask) == 0) { + ExtraID extra; + + switch (GameState.getT0BMonitorMode() & kRawModeMask) { + case kMonitorNoradComparison: + GameState.setTSASeenNoradAltered(true); + extra = kTSA0BNoradAltered; + GameState.setScoringSawNoradAltered(true); + break; + case kMonitorMarsComparison: + GameState.setTSASeenMarsAltered(true); + extra = kTSA0BMarsAltered; + GameState.setScoringSawMarsAltered(true); + break; + case kMonitorCaldoriaComparison: + GameState.setTSASeenCaldoriaAltered(true); + extra = kTSA0BCaldoriaAltered; + GameState.setScoringSawCaldoriaAltered(true); + break; + case kMonitorWSCComparison: + GameState.setTSASeenWSCAltered(true); + extra = kTSA0BWSCAltered; + GameState.setScoringSawWSCAltered(true); + break; + default: + error("Invalid monitor mode"); + } + + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() | kPlayingLeftComparisonMask); + + ExtraTable::Entry entry; + getExtraEntry(extra, entry); + _lastExtra = extra; + + // skip first five frames of movie + // (this is a dissolve that doesn't belong...) + GameState.setT0BMonitorStart(entry.movieStart + kFullTSAFrameDuration * 5); + _privateFlags.setFlag(kTSAPrivatePlayingLeftComparisonFlag); + + // Allow clicking... + startMovieSequence(GameState.getT0BMonitorStart(), entry.movieEnd, + kExtraCompletedFlag, false, JMPPPInput::getClickInputFilter()); + } else if (_navMovie.isRunning()) { + _navMovie.stop(); + } else { + _navMovie.start(); + } +} + +void FullTSA::playRightComparison() { + InputDevice.waitInput(kFilterAllButtons); + + if ((GameState.getT0BMonitorMode() & kPlayingRightComparisonMask) == 0) { + ExtraID extra; + + switch (GameState.getT0BMonitorMode() & kRawModeMask) { + case kMonitorNoradComparison: + GameState.setTSASeenNoradNormal(true); + extra = kTSA0BNoradUnaltered; + GameState.setScoringSawNoradNormal(true); + break; + case kMonitorMarsComparison: + GameState.setTSASeenMarsNormal(true); + extra = kTSA0BMarsUnaltered; + GameState.setScoringSawMarsNormal(true); + break; + case kMonitorCaldoriaComparison: + GameState.setTSASeenCaldoriaNormal(true); + extra = kTSA0BCaldoriaUnaltered; + GameState.setScoringSawCaldoriaNormal(true); + break; + case kMonitorWSCComparison: + GameState.setTSASeenWSCNormal(true); + extra = kTSA0BWSCUnaltered; + GameState.setScoringSawWSCNormal(true); + break; + default: + error("Invalid monitor mode"); + } + + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() | kPlayingRightComparisonMask); + + ExtraTable::Entry entry; + getExtraEntry(extra, entry); + _lastExtra = extra; + + // skip first five frames of movie + // (this is a dissolve that doesn't belong...) + GameState.setT0BMonitorStart(entry.movieStart + kFullTSAFrameDuration * 5); + _privateFlags.setFlag(kTSAPrivatePlayingRightComparisonFlag); + + // Allow clicking... + startMovieSequence(GameState.getT0BMonitorStart(), entry.movieEnd, + kExtraCompletedFlag, false, JMPPPInput::getClickInputFilter()); + } else if (_navMovie.isRunning()) { + _navMovie.stop(); + } else { + _navMovie.start(); + } +} + +// When this function is called, the player is zoomed up on the center monitor, and the +// TSA state is kTSABossSawHistoricalLog. +void FullTSA::startRobotGame() { + requestExtraSequence(kTSA0BNorthCantChangeHistory, 0, kFilterNoInput); + requestExtraSequence(kTSA0BAIInterruption, 0, kFilterNoInput); + requestExtraSequence(kTSA0BShowGuardRobots, 0, kFilterNoInput); + requestExtraSequence(kTSA0BRobotsToCommandCenter, kExtraCompletedFlag, kFilterNoInput); +} + +void FullTSA::startUpRobotMonitor() { + releaseSprites(); + + _sprite1.addPICTResourceFrame(kRedirectionCCRolloverPICTID, true, + kRedirectionCCRolloverLeft - kRedirectionSprite1Left, + kRedirectionCCRolloverTop - kRedirectionSprite1Top); + _sprite1.addPICTResourceFrame(kRedirectionRRRolloverPICTID, true, + kRedirectionRRRolloverLeft - kRedirectionSprite1Left, + kRedirectionRRRolloverTop - kRedirectionSprite1Top); + _sprite1.addPICTResourceFrame(kRedirectionFDRolloverPICTID, false, + kRedirectionFDRolloverLeft - kRedirectionSprite1Left, + kRedirectionFDRolloverTop - kRedirectionSprite1Top); + _sprite1.addPICTResourceFrame(kRedirectionCCDoorPICTID, true, + kRedirectionCCDoorLeft - kRedirectionSprite1Left, + kRedirectionCCDoorTop - kRedirectionSprite1Top); + _sprite1.addPICTResourceFrame(kRedirectionRRDoorPICTID, true, + kRedirectionRRDoorLeft - kRedirectionSprite1Left, + kRedirectionRRDoorTop - kRedirectionSprite1Top); + _sprite1.addPICTResourceFrame(kRedirectionFDDoorPICTID, false, + kRedirectionFDDoorLeft - kRedirectionSprite1Left, + kRedirectionFDDoorTop - kRedirectionSprite1Top); + _sprite1.addPICTResourceFrame(kRedirectionClosePICTID, false, + kRedirectionCloseLeft - kRedirectionSprite1Left, + kRedirectionCloseTop - kRedirectionSprite1Top); + _sprite1.moveElementTo(kRedirectionSprite1Left, kRedirectionSprite1Top); + + _sprite2.addPICTResourceFrame(kRedirectionSecuredPICTID, false, + kRedirectionSecuredLeft - kRedirectionSprite2Left, + kRedirectionSecuredTop - kRedirectionSprite2Top); + _sprite2.addPICTResourceFrame(kRedirectionNewTargetPICTID, false, + kRedirectionNewTargetLeft - kRedirectionSprite2Left, + kRedirectionNewTargetTop - kRedirectionSprite2Top); + _sprite2.moveElementTo(kRedirectionSprite2Left, kRedirectionSprite2Top); + + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + showExtraView(kTSA0BNorthRobotsAtCCView); + break; + case kRobotsAtFrontDoor: + showExtraView(kTSA0BNorthRobotsAtFDView); + break; + case kRobotsAtReadyRoom: + showExtraView(kTSA0BNorthRobotsAtRRView); + break; + } +} + +void FullTSA::shutDownRobotMonitor() { + releaseSprites(); +} + +// Assume this is called only when zoomed in at T0B west +void FullTSA::setOffRipAlarm() { + GameState.setTSAState(kTSAPlayerDetectedRip); + _ripTimer.initImage(); + _ripTimer.moveElementTo(kRipTimerLeft, kRipTimerTop); + _ripTimer.setSegment(0, kRipTimeLimit, kRipTimeScale); + _ripTimer.start(); + loadAmbientLoops(); + startExtraSequenceSync(kTSA0BRipAlarmScreen, kFilterNoInput); + _vm->delayShell(2, 1); // Two seconds.. + requestExtraSequence(kTSA0BWestZoomOut, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kTSA0BWestTurnRight, 0, kFilterNoInput); + requestExtraSequence(kTSA0BNorthZoomIn, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kTSA0BNorthFinallyHappened, 0, kFilterNoInput); + requestExtraSequence(kTSA0BShowRip1, kExtraCompletedFlag, kFilterNoInput); +} + +void FullTSA::checkContinuePoint(const RoomID room, const DirectionConstant direction) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kTSA04, kNorth): + case MakeRoomView(kTSA14, kEast): + case MakeRoomView(kTSA15, kWest): + case MakeRoomView(kTSA16, kNorth): + case MakeRoomView(kTSA16, kSouth): + case MakeRoomView(kTSA21Cyan, kSouth): + case MakeRoomView(kTSA21Red, kSouth): + case MakeRoomView(kTSA26, kNorth): + makeContinuePoint(); + break; + } +} + +void FullTSA::arriveAt(const RoomID room, const DirectionConstant direction) { + checkRobotLocations(room, direction); + Neighborhood::arriveAt(room, direction); + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kTSADeathRoom, kNorth): + case MakeRoomView(kTSADeathRoom, kSouth): + case MakeRoomView(kTSADeathRoom, kEast): + case MakeRoomView(kTSADeathRoom, kWest): + die(kDeathShotByTSARobots); + break; + case MakeRoomView(kTSA00, kNorth): + if (GameState.getLastNeighborhood() != kFullTSAID) { + makeContinuePoint(); + openDoor(); + } else { + setCurrentActivation(kActivateTSAReadyForCard); + loopExtraSequence(kTSATransporterArrowLoop, 0); + } + break; + case MakeRoomView(kTSA03, kNorth): + case MakeRoomView(kTSA05, kNorth): + case MakeRoomView(kTSA0A, kNorth): + case MakeRoomView(kTSA06, kNorth): + case MakeRoomView(kTSA07, kNorth): + if (_utilityFuse.isFuseLit()) + _utilityFuse.stopFuse(); + GameState.setScoringEnterTSA(true); + break; + case MakeRoomView(kTSA04, kNorth): + if (_utilityFuse.isFuseLit()) + _utilityFuse.stopFuse(); + if (!GameState.getTSASeenRobotGreeting()) + startExtraSequence(kTSA04NorthRobotGreeting, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kTSA03, kSouth): + GameState.setTSAFrontDoorUnlockedInside(GameState.getTSAState() == kRobotsAtFrontDoor || GameState.allTimeZonesFinished()); + break; + case MakeRoomView(kTSA0A, kEast): + case MakeRoomView(kTSA0A, kWest): + if (GameState.getTSAState() == kTSAPlayerNotArrived) + setCurrentActivation(kActivateTSARobotsAwake); + break; + case MakeRoomView(kTSA0B, kNorth): + if (GameState.getTSA0BZoomedIn()) { + setCurrentActivation(kActivateTSA0BZoomedIn); + + switch (GameState.getTSAState()) { + case kTSAPlayerNeedsHistoricalLog: + _ripTimer.show(); + break; + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + startUpRobotMonitor(); + break; + } + } else { + setCurrentActivation(kActivateTSA0BZoomedOut); + + switch (GameState.getTSAState()) { + case kTSAPlayerNotArrived: + requestExtraSequence(kTSA0BNorthZoomIn, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kTSA0BNorthYoureBusted, 0, kFilterNoInput); + requestExtraSequence(kTSA0BNorthZoomOut, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kTSA0BNorthTurnLeft, 0, kFilterNoInput); + requestExtraSequence(kTSA0BWestZoomIn, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSAPlayerGotHistoricalLog: + startExtraSequence(kTSA0BNorthHistLogOpen, kExtraCompletedFlag, kFilterNoInput); + break; + } + } + break; + case MakeRoomView(kTSA0B, kSouth): + GameState.setTSA0BZoomedIn(false); + setCurrentActivation(kActivateTSA0BZoomedOut); + break; + case MakeRoomView(kTSA0B, kWest): + if (GameState.getTSA0BZoomedIn()) { + setCurrentActivation(kActivateTSA0BZoomedIn); + initializeTBPMonitor(kMonitorNeutral, 0); + } else { + setCurrentActivation(kActivateTSA0BZoomedOut); + } + break; + case MakeRoomView(kTSA0B, kEast): + if (GameState.getTSA0BZoomedIn()) { + setCurrentActivation(kActivateTSA0BZoomedIn); + + switch (GameState.getTSAState()) { + case kTSAPlayerInstalledHistoricalLog: + case kTSABossSawHistoricalLog: + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + initializeComparisonMonitor(kMonitorNeutral, 0); + break; + } + } else { + setCurrentActivation(kActivateTSA0BZoomedOut); + } + break; + case MakeRoomView(kTSA21Red, kSouth): + if (GameState.getTSAState() == kRobotsAtFrontDoor) + GameState.setScoringWentToReadyRoom2(true); + break; + case MakeRoomView(kTSA22Red, kEast): + if (!_vm->playerHasItemID(kJourneymanKey)) + setCurrentActivation(kActivationDoesntHaveKey); + break; + case MakeRoomView(kTSA23Red, kWest): + if (!_vm->playerHasItemID(kPegasusBiochip)) + setCurrentActivation(kActivationDoesntHaveChips); + break; + case MakeRoomView(kTSA25Red, kNorth): + arriveAtTSA25Red(); + break; + case MakeRoomView(kTSA34, kSouth): + if (GameState.getLastRoom() == kTSA37) + closeDoorOffScreen(kTSA37, kNorth); + break; + case MakeRoomView(kTSA37, kNorth): + arriveAtTSA37(); + break; + } +} + +void FullTSA::checkRobotLocations(const RoomID room, const DirectionConstant dir) { + switch (room) { + case kTSA03: + case kTSA04: + case kTSA05: + case kTSA06: + case kTSA0A: + case kTSA07: + case kTSA08: + case kTSA09: + case kTSA10: + case kTSA11: + case kTSA12: + case kTSA13: + case kTSA14: + case kTSA15: + switch (GameState.getTSAState()) { + case kRobotsAtFrontDoor: + setCurrentAlternate(kAltTSARobotsAtFrontDoor); + break; + case kRobotsAtReadyRoom: + setCurrentAlternate(kAltTSARobotsAtReadyRoom); + break; + } + break; + case kTSA16: + if (dir == kNorth) { + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + if (!_privateFlags.getFlag(kTSAPrivateSeenRobotWarningFlag)) { + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/TSA/XT11WB", false, kWarningInterruption); + _privateFlags.setFlag(kTSAPrivateSeenRobotWarningFlag, true); + } + break; + case kRobotsAtFrontDoor: + setCurrentAlternate(kAltTSARobotsAtFrontDoor); + break; + case kRobotsAtReadyRoom: + setCurrentAlternate(kAltTSARobotsAtReadyRoom); + break; + } + } + break; + } +} + +void FullTSA::arriveAtTSA25Red() { + if (!_vm->playerHasItemID(kJourneymanKey)) + startExtraSequence(kTSA25NorthDeniedNoKey, kExtraCompletedFlag, kFilterNoInput); + else if (!_vm->playerHasItemID(kPegasusBiochip)) + startExtraSequence(kTSA25NorthDeniedNoChip, kExtraCompletedFlag, kFilterNoInput); + else if (GameState.getTSABiosuitOn()) + startExtraSequence(kTSA25NorthAlreadyHaveSuit, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kTSA25NorthPutOnSuit, kExtraCompletedFlag, kFilterNoInput); +} + +void FullTSA::arriveAtTSA37() { + _ripTimer.stop(); + _ripTimer.releaseImage(); + + switch (GameState.getTSAState()) { + case kTSAPlayerNeedsHistoricalLog: + startExtraLongSequence(kTSA37HorseToAI1, kTSA37AI2ToPrehistoric, kExtraCompletedFlag, kFilterNoInput); + break; + case kPlayerOnWayToPrehistoric: + setCurrentActivation(kActivationJumpToPrehistoric); + showExtraView(kTSA37AI2ToPrehistoric); + break; + case kTSAPlayerGotHistoricalLog: + initializePegasusButtons(false); + break; + case kPlayerWentToPrehistoric: + case kPlayerOnWayToNorad: + case kPlayerOnWayToMars: + case kPlayerOnWayToWSC: + startExtraSequence(kTSA37TimeJumpToPegasus, kExtraCompletedFlag, kFilterNoInput); + break; + case kRobotsAtFrontDoor: + startExtraLongSequence(kTSA37HorseToColonel2, kTSA37AI4ToMainMenu, kExtraCompletedFlag, kFilterNoInput); + break; + case kPlayerLockedInPegasus: + showMainJumpMenu(); + break; + case kPlayerFinishedWithTSA: + initializePegasusButtons(true); + break; + } +} + +void FullTSA::turnTo(const DirectionConstant newDirection) { + Neighborhood::turnTo(newDirection); + + switch (MakeRoomView(GameState.getCurrentRoom(), newDirection)) { + case MakeRoomView(kTSA03, kSouth): + if (GameState.getTSAState() == kRobotsAtFrontDoor || GameState.allTimeZonesFinished()) + GameState.setTSAFrontDoorUnlockedInside(true); + else + GameState.setTSAFrontDoorUnlockedInside(false); + break; + case MakeRoomView(kTSA0A, kEast): + case MakeRoomView(kTSA0A, kWest): + setCurrentActivation(kActivateTSARobotsAwake); + break; + case MakeRoomView(kTSA0B, kEast): + if (GameState.getTSA0BZoomedIn()) + setCurrentActivation(kActivateTSA0BZoomedIn); + else + setCurrentActivation(kActivateTSA0BZoomedOut); + + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() & ~kPlayingAnyMask); + + if (_privateFlags.getFlag(kTSAPrivateLogReaderOpenFlag)) + _privateFlags.setFlag(kTSAPrivateLogReaderOpenFlag, false); + + switch (GameState.getTSAState()) { + case kTSAPlayerInstalledHistoricalLog: + case kTSABossSawHistoricalLog: + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + if (GameState.getTSA0BZoomedIn()) + startUpComparisonMonitor(); + break; + } + break; + case MakeRoomView(kTSA0B, kNorth): + if (GameState.getTSA0BZoomedIn()) + setCurrentActivation(kActivateTSA0BZoomedIn); + else + setCurrentActivation(kActivateTSA0BZoomedOut); + + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() & ~kPlayingAnyMask); + + switch (GameState.getTSAState()) { + case kTSAPlayerNeedsHistoricalLog: + if (GameState.getTSA0BZoomedIn()) + _ripTimer.show(); + break; + case kTSAPlayerGotHistoricalLog: + if (!GameState.getTSA0BZoomedIn()) + startExtraSequence(kTSA0BNorthHistLogOpen, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSAPlayerInstalledHistoricalLog: + if (GameState.getTSA0BZoomedIn()) { + if ((GameState.getTSASeenNoradNormal() || GameState.getTSASeenNoradAltered()) && + (GameState.getTSASeenMarsNormal() || GameState.getTSASeenMarsAltered()) && + (GameState.getTSASeenCaldoriaNormal() || GameState.getTSASeenCaldoriaAltered()) && + (GameState.getTSASeenWSCNormal() || GameState.getTSASeenWSCAltered())) { + GameState.setTSAState(kTSABossSawHistoricalLog); + startRobotGame(); + } + } + break; + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + if (GameState.getTSA0BZoomedIn()) + startExtraSequence(kTSA0BShowGuardRobots, kExtraCompletedFlag, kFilterNoInput); + break; + } + break; + case MakeRoomView(kTSA0B, kWest): + if (GameState.getTSA0BZoomedIn()) + setCurrentActivation(kActivateTSA0BZoomedIn); + else + setCurrentActivation(kActivateTSA0BZoomedOut); + + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() & ~kPlayingAnyMask); + + if (_privateFlags.getFlag(kTSAPrivateLogReaderOpenFlag)) + _privateFlags.setFlag(kTSAPrivateLogReaderOpenFlag, false); + + if (GameState.getTSA0BZoomedIn()) + initializeTBPMonitor(kMonitorNeutral, 0); + break; + case MakeRoomView(kTSA0B, kSouth): + GameState.setTSA0BZoomedIn(false); + setCurrentActivation(kActivateTSA0BZoomedOut); + break; + case MakeRoomView(kTSA16, kNorth): + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + if (!_privateFlags.getFlag(kTSAPrivateSeenRobotWarningFlag)) { + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/TSA/XT11WB", false, kWarningInterruption); + _privateFlags.setFlag(kTSAPrivateSeenRobotWarningFlag, true); + } + break; + case kRobotsAtFrontDoor: + setCurrentAlternate(kAltTSARobotsAtFrontDoor); + break; + case kRobotsAtReadyRoom: + setCurrentAlternate(kAltTSARobotsAtReadyRoom); + break; + } + break; + case MakeRoomView(kTSA22Red, kEast): + if (!_vm->playerHasItemID(kJourneymanKey)) + setCurrentActivation(kActivationDoesntHaveKey); + break; + case MakeRoomView(kTSA22Red, kNorth): + case MakeRoomView(kTSA22Red, kSouth): + if (_privateFlags.getFlag(kTSAPrivateKeyVaultOpenFlag)) { + playSpotSoundSync(kTSAVaultCloseIn, kTSAVaultCloseOut); + _privateFlags.setFlag(kTSAPrivateKeyVaultOpenFlag, false); + } + + setCurrentActivation(kActivateHotSpotAlways); + break; + case MakeRoomView(kTSA23Red, kWest): + if (!_vm->playerHasItemID(kPegasusBiochip)) + setCurrentActivation(kActivationDoesntHaveChips); + break; + case MakeRoomView(kTSA23Red, kNorth): + case MakeRoomView(kTSA23Red, kSouth): + if (_privateFlags.getFlag(kTSAPrivateChipVaultOpenFlag)) { + playSpotSoundSync(kTSAVaultCloseIn, kTSAVaultCloseOut); + _privateFlags.setFlag(kTSAPrivateChipVaultOpenFlag, false); + } + + setCurrentActivation(kActivateHotSpotAlways); + break; + } + + // Make sure the TBP monitor is forced neutral. + GameState.setT0BMonitorMode(kMonitorNeutral); +} + +void FullTSA::closeDoorOffScreen(const RoomID room, const DirectionConstant) { + switch (room) { + case kTSA00: + case kTSA01: + if (GameState.getCurrentRoom() == kTSA01 || GameState.getCurrentRoom() == kTSA02) + playSpotSoundSync(kTSAGTDoorCloseIn, kTSAGTDoorCloseOut); + break; + case kTSA02: + case kTSA03: + playSpotSoundSync(kTSAEntryDoorCloseIn, kTSAEntryDoorCloseOut); + break; + case kTSA14: + case kTSA15: + case kTSA16: + case kTSA21Cyan: + case kTSA21Red: + playSpotSoundSync(kTSAInsideDoorCloseIn, kTSAInsideDoorCloseOut); + break; + case kTSA34: + case kTSA37: + playSpotSoundSync(kTSAPegasusDoorCloseIn, kTSAPegasusDoorCloseOut); + break; + } +} + +void FullTSA::receiveNotification(Notification *notification, const NotificationFlags flags) { + ExtraID lastExtra = _lastExtra; + + if ((flags & kExtraCompletedFlag) != 0) { + switch (lastExtra) { + case kTSA0BEastTurnLeft: + // Need to check this here because turnTo will call _navMovie.stop, + // so it has to happen before Neighborhood::receiveNotification, + // which may end up starting another sequence... + turnTo(kNorth); + break; + } + } + + Neighborhood::receiveNotification(notification, flags); + + InventoryItem *item; + + if ((flags & kExtraCompletedFlag) != 0) { + // Only allow input if we're not in the middle of series of queue requests. + if (actionQueueEmpty()) + _interruptionFilter = kFilterAllInput; + + switch (lastExtra) { + case kTSAGTCardSwipe: + item = (InventoryItem *)g_allItems.findItemByID(kKeyCard); + _vm->addItemToInventory(item); + setCurrentActivation(kActivateTSAReadyToTransport); + break; + case kTSAGTGoToCaldoria: + _vm->jumpToNewEnvironment(kCaldoriaID, kCaldoria44, kEast); + + if (GameState.allTimeZonesFinished()) + GameState.setScoringWentAfterSinclair(true); + break; + case kTSAGTGoToTokyo: + case kTSAGTGoToBeach: + if (GameState.allTimeZonesFinished()) + die(kDeathSinclairShotDelegate); + else + die(kDeathUncreatedInTSA); + break; + case kTSA02NorthZoomOut: + openDoor(); + break; + + // Hall of suspects. + case kTSA04NorthRobotGreeting: + GameState.setTSASeenRobotGreeting(true); + restoreStriding(kTSA03, kNorth, kNoAlternateID); + break; + case kTSA03JimenezZoomIn: + GameState.setScoringSawBust1(true); + break; + case kTSA03CrenshawZoomIn: + GameState.setScoringSawBust2(true); + break; + case kTSA04MatsumotoZoomIn: + GameState.setScoringSawBust3(true); + break; + case kTSA04CastilleZoomIn: + GameState.setScoringSawBust4(true); + break; + case kTSA05SinclairZoomIn: + GameState.setScoringSawBust5(true); + break; + case kTSA05WhiteZoomIn: + GameState.setScoringSawBust6(true); + break; + + // Command center + // Historical comparison... + case kTSA0BEastZoomIn: + GameState.setTSA0BZoomedIn(true); + setCurrentActivation(kActivateTSA0BZoomedIn); + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() & ~kPlayingAnyMask); + + switch (GameState.getTSAState()) { + case kTSAPlayerInstalledHistoricalLog: + case kTSABossSawHistoricalLog: + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + startUpComparisonMonitor(); + break; + } + break; + case kTSA0BEastZoomOut: + GameState.setTSA0BZoomedIn(false); + setCurrentActivation(kActivateTSA0BZoomedOut); + + switch (GameState.getTSAState()) { + case kTSABossSawHistoricalLog: + // Prevent current view from activating. + break; + default: + activateCurrentView(GameState.getCurrentRoom(), GameState.getCurrentDirection(), + kSpotOnTurnMask); + break; + } + break; + case kTSA0BComparisonStartup: + if ((flags & kActionRequestCompletedFlag) != 0) { + _privateFlags.setFlag(kTSAPrivateLogReaderOpenFlag, false); + GameState.setTSAState(kTSAPlayerInstalledHistoricalLog); + turnTo(kEast); + } + + startUpComparisonMonitor(); + break; + case kTSA0BNoradAltered: + case kTSA0BMarsAltered: + case kTSA0BCaldoriaAltered: + case kTSA0BWSCAltered: + case kTSA0BNoradUnaltered: + case kTSA0BMarsUnaltered: + case kTSA0BCaldoriaUnaltered: + case kTSA0BWSCUnaltered: + initializeComparisonMonitor(kMonitorNeutral, 0); + break; + + // Center monitor. + case kTSA0BNorthZoomIn: + GameState.setTSA0BZoomedIn(true); + setCurrentActivation(kActivateTSA0BZoomedIn); + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() & ~kPlayingAnyMask); + + switch (GameState.getTSAState()) { + case kTSAPlayerNeedsHistoricalLog: + startExtraSequence(kTSA0BShowRip1, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSABossSawHistoricalLog: + case kTSAPlayerInstalledHistoricalLog: + if ((GameState.getTSASeenNoradNormal() || GameState.getTSASeenNoradAltered()) && + (GameState.getTSASeenMarsNormal() || GameState.getTSASeenMarsAltered()) && + (GameState.getTSASeenCaldoriaNormal() || GameState.getTSASeenCaldoriaAltered()) && + (GameState.getTSASeenWSCNormal() || GameState.getTSASeenWSCAltered())) { + GameState.setTSAState(kTSABossSawHistoricalLog); + startRobotGame(); + } + break; + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + startExtraSequence(kTSA0BShowGuardRobots, kExtraCompletedFlag, kFilterNoInput); + break; + } + break; + case kTSA0BNorthZoomOut: + GameState.setTSA0BZoomedIn(false); + setCurrentActivation(kActivateTSA0BZoomedOut); + break; + case kTSA0BShowRip1: + GameState.setTSAState(kTSAPlayerNeedsHistoricalLog); + GameState.setTSACommandCenterLocked(false); + + if ((flags & kActionRequestCompletedFlag) != 0) + turnTo(kNorth); + + _ripTimer.show(); + break; + case kTSA0BNorthHistLogOpen: + setCurrentActivation(kActivationLogReaderOpen); + _privateFlags.setFlag(kTSAPrivateLogReaderOpenFlag, true); + break; + case kTSA0BRobotsToCommandCenter: + GameState.setTSAState(kRobotsAtCommandCenter); + // Fall through + case kTSA0BShowGuardRobots: + startUpRobotMonitor(); + // Fall through + case kTSA0BRobotsFromCommandCenterToReadyRoom: + case kTSA0BRobotsFromReadyRoomToCommandCenter: + case kTSA0BRobotsFromCommandCenterToFrontDoor: + case kTSA0BRobotsFromFrontDoorToCommandCenter: + case kTSA0BRobotsFromFrontDoorToReadyRoom: + case kTSA0BRobotsFromReadyRoomToFrontDoor: + _sprite2.setCurrentFrameIndex(kRedirectionSecuredSprite); + _sprite2.show(); + break; + + // TBP monitor. + case kTSA0BWestZoomIn: + GameState.setTSA0BZoomedIn(true); + setCurrentActivation(kActivateTSA0BZoomedIn); + + if (GameState.getTSAState() == kTSAPlayerNotArrived) { + turnTo(kWest); + GameState.setTSACommandCenterLocked(true); + GameState.setTSAState(kTSAPlayerForcedReview); + } + + initializeTBPMonitor(kMonitorNeutral, 0); + break; + case kTSA0BWestZoomOut: + GameState.setTSA0BZoomedIn(false); + setCurrentActivation(kActivateTSA0BZoomedOut); + GameState.setT0BMonitorMode(kMonitorNeutral); + + switch (GameState.getTSAState()) { + case kTSAPlayerDetectedRip: + // Keep the current view from activating. + break; + default: + activateCurrentView(GameState.getCurrentRoom(), GameState.getCurrentDirection(), + kSpotOnTurnMask); + break; + } + break; + case kTSA0BTBPTheory: + case kTSA0BTBPBackground: + case kTSA0BTBPProcedure: + initializeTBPMonitor(kMonitorNeutral, 0); + break; + + // Ready room + case kTSA22RedEastZoomInSequence: + _privateFlags.setFlag(kTSAPrivateKeyVaultOpenFlag, true); + setCurrentActivation(kActivationKeyVaultOpen); + break; + case kTSA23RedWestVaultZoomInSequence: + _privateFlags.setFlag(kTSAPrivateChipVaultOpenFlag, true); + setCurrentActivation(kActivationChipVaultOpen); + break; + case kTSA25NorthPutOnSuit: + GameState.setTSABiosuitOn(true); + GameState.setScoringGotBiosuit(true); + // Fall through... + case kTSA25NorthAlreadyHaveSuit: + requestExtraSequence(kTSA25NorthDescending1, 0, kFilterNoInput); + requestExtraSequence(kTSA25NorthDescending2, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA25NorthDescending2: + arriveAt(kTSA26, kNorth); + break; + + // Pegasus. + case kTSA37HorseToAI1: + case kTSA37AI2ToPrehistoric: + setCurrentActivation(kActivationJumpToPrehistoric); + GameState.setTSAState(kPlayerOnWayToPrehistoric); + break; + case kTSA37PegasusDepart: + _vm->setLastEnergyValue(kFullEnergy); + + switch (GameState.getTSAState()) { + case kPlayerOnWayToPrehistoric: + _vm->jumpToNewEnvironment(kPrehistoricID, kPrehistoric02, kSouth); + GameState.setPrehistoricSeenTimeStream(false); + GameState.setPrehistoricSeenFlyer1(false); + GameState.setPrehistoricSeenFlyer2(false); + GameState.setPrehistoricSeenBridgeZoom(false); + GameState.setPrehistoricBreakerThrown(false); + GameState.setScoringGoToPrehistoric(true); + GameState.setTSAState(kPlayerWentToPrehistoric); + break; + case kPlayerOnWayToNorad: + _vm->jumpToNewEnvironment(kNoradAlphaID, kNorad01, kSouth); + GameState.setNoradSeenTimeStream(false); + GameState.setNoradGassed(true); + GameState.setNoradFillingStationOn(false); + GameState.setNoradN22MessagePlayed(false); + GameState.setNoradPlayedGlobeGame(false); + GameState.setNoradBeatRobotWithClaw(false); + GameState.setNoradBeatRobotWithDoor(false); + GameState.setNoradRetScanGood(false); + GameState.setNoradWaitingForLaser(false); + GameState.setNoradSubRoomPressure(9); + GameState.setNoradSubPrepState(kSubNotPrepped); + break; + case kPlayerOnWayToMars: + _vm->jumpToNewEnvironment(kMarsID, kMars0A, kNorth); + GameState.setMarsSeenTimeStream(false); + GameState.setMarsHeardUpperPodMessage(false); + GameState.setMarsRobotThrownPlayer(false); + GameState.setMarsHeardCheckInMessage(false); + GameState.setMarsPodAtUpperPlatform(false); + GameState.setMarsSeenThermalScan(false); + GameState.setMarsArrivedBelow(false); + GameState.setMarsSeenRobotAtReactor(false); + GameState.setMarsAvoidedReactorRobot(false); + GameState.setMarsLockFrozen(false); + GameState.setMarsLockBroken(false); + GameState.setMarsSecurityDown(false); + GameState.setMarsAirlockOpen(false); + GameState.setMarsReadyForShuttleTransport(false); + GameState.setMarsFinishedCanyonChase(false); + GameState.setMarsThreadedMaze(false); + break; + case kPlayerOnWayToWSC: + _vm->jumpToNewEnvironment(kWSCID, kWSC01, kWest); + GameState.setWSCSeenTimeStream(false); + GameState.setWSCPoisoned(false); + GameState.setWSCAnsweredAboutDart(false); + GameState.setWSCRemovedDart(false); + GameState.setWSCAnalyzerOn(false); + GameState.setWSCDartInAnalyzer(false); + GameState.setWSCAnalyzedDart(false); + GameState.setWSCPickedUpAntidote(false); + GameState.setWSCSawMorph(false); + GameState.setWSCDesignedAntidote(false); + GameState.setWSCOfficeMessagesOpen(false); + GameState.setWSCSeenNerd(false); + GameState.setWSCHeardPage1(false); + GameState.setWSCHeardPage2(false); + GameState.setWSCHeardCheckIn(false); + GameState.setWSCDidPlasmaDodge(false); + GameState.setWSCSeenSinclairLecture(false); + GameState.setWSCBeenAtWSC93(false); + GameState.setWSCCatwalkDark(false); + GameState.setWSCRobotDead(false); + GameState.setWSCRobotGone(false); + break; + }; + break; + case kTSA37TimeJumpToPegasus: + if (g_energyMonitor) + g_energyMonitor->stopEnergyDraining(); + + switch (GameState.getTSAState()) { + case kPlayerWentToPrehistoric: + arriveFromPrehistoric(); + break; + case kPlayerOnWayToNorad: + arriveFromNorad(); + break; + case kPlayerOnWayToMars: + arriveFromMars(); + break; + case kPlayerOnWayToWSC: + arriveFromWSC(); + break; + default: + break; + } + break; + case kTSA37DownloadToOpMemReview: + switch (GameState.getTSAState()) { + case kPlayerOnWayToNorad: + g_opticalChip->playOpMemMovie(kPoseidonSpotID); + break; + case kPlayerOnWayToMars: + g_opticalChip->playOpMemMovie(kAriesSpotID); + break; + case kPlayerOnWayToWSC: + g_opticalChip->playOpMemMovie(kMercurySpotID); + break; + } + + if (GameState.allTimeZonesFinished()) { + requestExtraSequence(kTSA37OpMemReviewToAllClear, 0, kFilterNoInput); + requestExtraSequence(kTSA37AllClearToCongratulations, 0, kFilterNoInput); + requestExtraSequence(kTSA37Congratulations, 0, kFilterNoInput); + requestExtraSequence(kTSA37CongratulationsToExit, kExtraCompletedFlag, kFilterNoInput); + } else { + requestExtraSequence(kTSA37OpMemReviewToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kTSA37RecallToDownload: + case kTSA37ReviewRequiredToExit: + GameState.setTSAState(kTSAPlayerGotHistoricalLog); + initializePegasusButtons(kPegasusUnresolved); + break; + case kTSA37ZoomToMainMenu: + case kTSA37HorseToColonel2: + case kTSA37DownloadToMainMenu: + case kTSA37OpMemReviewToMainMenu: + case kTSA37AI4ToMainMenu: + GameState.setTSAState(kPlayerLockedInPegasus); + showMainJumpMenu(); + makeContinuePoint(); + break; + case kTSA37JumpToNoradMenu: + setCurrentActivation(kActivationJumpToNorad); + break; + case kTSA37JumpToMarsMenu: + setCurrentActivation(kActivationJumpToMars); + break; + case kTSA37JumpToWSCMenu: + setCurrentActivation(kActivationJumpToWSC); + break; + case kTSA37CancelNorad: + case kTSA37CancelMars: + case kTSA37CancelWSC: + showMainJumpMenu(); + break; + case kTSA37CongratulationsToExit: + GameState.setTSAState(kPlayerFinishedWithTSA); + initializePegasusButtons(true); + break; + } + } + + g_AIArea->checkMiddleArea(); +} + +void FullTSA::arriveFromPrehistoric() { + if (_vm->playerHasItemID(kHistoricalLog)) { + GameState.setScoringFinishedPrehistoric(); + requestExtraSequence(kTSA37RecallToDownload, 0, kFilterNoInput); + requestExtraSequence(kTSA37DownloadToColonel1, 0, kFilterNoInput); + requestExtraSequence(kTSA37Colonel1, 0, kFilterNoInput); + requestExtraSequence(kTSA37Colonel1ToReviewRequired, 0, kFilterNoInput); + requestExtraSequence(kTSA37ReviewRequiredToExit, kExtraCompletedFlag, kFilterNoInput); + } else { + // Make sure rip timer is going... + startExtraSequence(kTSA37DownloadToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } +} + +void FullTSA::arriveFromNorad() { + requestExtraSequence(kTSA37RecallToDownload, 0, kFilterNoInput); + + if (GameState.getNoradFinished() && !GameState.getScoringFinishedNorad()) { + GameState.setScoringFinishedNorad(); + requestExtraSequence(kTSA37DownloadToOpMemReview, kExtraCompletedFlag, kFilterNoInput); + } else { + requestExtraSequence(kTSA37DownloadToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } +} + +void FullTSA::arriveFromMars() { + requestExtraSequence(kTSA37RecallToDownload, 0, kFilterNoInput); + + if (GameState.getMarsFinished() && !GameState.getScoringFinishedMars()) { + GameState.setScoringFinishedMars(); + requestExtraSequence(kTSA37DownloadToOpMemReview, kExtraCompletedFlag, kFilterNoInput); + } else { + requestExtraSequence(kTSA37DownloadToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } +} + +void FullTSA::arriveFromWSC() { + requestExtraSequence(kTSA37RecallToDownload, 0, kFilterNoInput); + + if (GameState.getWSCFinished() && !GameState.getScoringFinishedWSC()) { + GameState.setScoringFinishedWSC(); + requestExtraSequence(kTSA37DownloadToOpMemReview, kExtraCompletedFlag, kFilterNoInput); + } else { + requestExtraSequence(kTSA37DownloadToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } +} + +void FullTSA::initializePegasusButtons(bool resolved) { + if (resolved) { + _sprite1.addPICTResourceFrame(kResolvedPICTID, false, 0, 0); + _sprite1.moveElementTo(kResolvedLeft, kResolvedTop); + } else { + _sprite1.addPICTResourceFrame(kUnresolvedPICTID, false, 0, 0); + _sprite1.moveElementTo(kUnresolvedLeft, kUnresolvedTop); + } + + _sprite1.setCurrentFrameIndex(0); + _sprite1.show(); + + _sprite2.addPICTResourceFrame(kExitPICTID, false, kExitLeft - kExitHilitedLeft, kExitTop - kExitHilitedTop); + _sprite2.addPICTResourceFrame(kExitHilitedPICTID, false, 0, 0); + _sprite2.moveElementTo(kExitHilitedLeft, kExitHilitedTop); + setCurrentActivation(kActivationReadyToExit); + _sprite2.setCurrentFrameIndex(0); + _sprite2.show(); +} + +Hotspot *FullTSA::getItemScreenSpot(Item *item, DisplayElement *element) { + switch (item->getObjectID()) { + case kJourneymanKey: + return g_allHotspots.findHotspotByID(kTSA22EastKeySpotID); + break; + case kPegasusBiochip: + return g_allHotspots.findHotspotByID(kTSA23WestChipsSpotID); + break; + } + + return Neighborhood::getItemScreenSpot(item, element); +} + +void FullTSA::dropItemIntoRoom(Item *item, Hotspot *dropSpot) { + Neighborhood::dropItemIntoRoom(item, dropSpot); + + switch (item->getObjectID()) { + case kKeyCard: + if (dropSpot->getObjectID() == kTSAGTCardDropSpotID) + startExtraSequence(kTSAGTCardSwipe, kExtraCompletedFlag, kFilterNoInput); + break; + case kHistoricalLog: + if (dropSpot->getObjectID() == kTSA0BNorthHistLogSpotID) { + requestExtraSequence(kTSA0BNorthHistLogCloseWithLog, 0, kFilterNoInput); + requestExtraSequence(kTSA0BNorthTurnRight, 0, kFilterNoInput); + requestExtraSequence(kTSA0BEastZoomIn, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kTSA0BComparisonStartup, kExtraCompletedFlag, kFilterNoInput); + GameState.setScoringPutLogInReader(true); + } + break; + } +} + +uint FullTSA::getHistoricalLogIndex() { + uint index; + + if (GameState.getTSASeenNoradNormal() && GameState.getTSASeenNoradAltered()) + index = 8; + else + index = 0; + + if (GameState.getTSASeenMarsNormal() && GameState.getTSASeenMarsAltered()) + index += 4; + + if (GameState.getTSASeenCaldoriaNormal() && GameState.getTSASeenCaldoriaAltered()) + index += 2; + + if (GameState.getTSASeenWSCNormal() && GameState.getTSASeenWSCAltered()) + index += 1; + + return index; +} + +void FullTSA::handleInput(const Input &input, const Hotspot *cursorSpot) { + switch (MakeRoomView(GameState.getCurrentRoom(), GameState.getCurrentDirection())) { + case MakeRoomView(kTSA0B, kEast): + if (GameState.getTSA0BZoomedIn() && !_navMovie.isRunning() && GameState.getT0BMonitorMode() == kMonitorNeutral) { + switch (GameState.getTSAState()) { + case kTSAPlayerInstalledHistoricalLog: + case kTSABossSawHistoricalLog: + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + if (cursorSpot) { + switch (cursorSpot->getObjectID()) { + case kTSA0BEastCompareNoradSpotID: + _sprite1.setCurrentFrameIndex(0); + _sprite2.setCurrentFrameIndex(0); + _sprite1.show(); + _sprite2.show(); + break; + case kTSA0BEastCompareMarsSpotID: + _sprite1.setCurrentFrameIndex(1); + _sprite2.setCurrentFrameIndex(1); + _sprite1.show(); + _sprite2.show(); + break; + case kTSA0BEastCompareCaldoriaSpotID: + _sprite1.setCurrentFrameIndex(2); + _sprite2.setCurrentFrameIndex(2); + _sprite1.show(); + _sprite2.show(); + break; + case kTSA0BEastCompareWSCSpotID: + _sprite1.setCurrentFrameIndex(3); + _sprite2.setCurrentFrameIndex(3); + _sprite1.show(); + _sprite2.show(); + break; + default: + _sprite1.hide(); + _sprite2.hide(); + break; + } + } else { + _sprite1.hide(); + _sprite2.hide(); + } + break; + } + } + break; + case MakeRoomView(kTSA0B, kNorth): + if (GameState.getTSA0BZoomedIn() && !_navMovie.isRunning()) { + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + if (cursorSpot) { + switch (cursorSpot->getObjectID()) { + case kTSA0BNorthRobotsToCommandCenterSpotID: + _sprite1.setCurrentFrameIndex(kRedirectionCCRolloverSprite); + _sprite1.show(); + break; + case kTSA0BNorthRobotsToReadyRoomSpotID: + _sprite1.setCurrentFrameIndex(kRedirectionRRRolloverSprite); + _sprite1.show(); + break; + case kTSA0BNorthRobotsToFrontDoorSpotID: + _sprite1.setCurrentFrameIndex(kRedirectionFDRolloverSprite); + _sprite1.show(); + break; + default: + _sprite1.hide(); + break; + } + } else { + _sprite1.hide(); + } + break; + } + } + break; + } + + Neighborhood::handleInput(input, cursorSpot); +} + +void FullTSA::releaseSprites() { + _sprite1.hide(); + _sprite2.hide(); + _sprite3.hide(); + _sprite1.discardFrames(); + _sprite2.discardFrames(); + _sprite3.discardFrames(); +} + +bool FullTSA::canSolve() { + return GameState.getCurrentRoomAndView() == MakeRoomView(kTSA0B, kNorth) && + GameState.getTSA0BZoomedIn() && + (GameState.getTSAState() == kRobotsAtCommandCenter || + GameState.getTSAState() == kRobotsAtFrontDoor || + GameState.getTSAState() == kRobotsAtReadyRoom); +} + +void FullTSA::doSolve() { + // REROUTING ROBOTS + + _sprite1.setCurrentFrameIndex(kRedirectionFDDoorSprite); + _sprite1.show(); + _vm->delayShell(1, 2); + _sprite1.hide(); + + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + GameState.setTSAState(kRobotsAtFrontDoor); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromCommandCenterToFrontDoor, kExtraCompletedFlag, kFilterNoInput); + break; + case kRobotsAtFrontDoor: + // Nothing + break; + case kRobotsAtReadyRoom: + GameState.setTSAState(kRobotsAtFrontDoor); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromReadyRoomToFrontDoor, kExtraCompletedFlag, kFilterNoInput); + break; + } +} + +void FullTSA::updateCursor(const Common::Point where, const Hotspot *cursorSpot) { + if (cursorSpot) { + switch (cursorSpot->getObjectID()) { + case kTSA0BEastMonitorSpotID: + case kTSA0BNorthMonitorSpotID: + case kTSA0BWestMonitorSpotID: + case kTSA22EastMonitorSpotID: + case kTSA23WestMonitorSpotID: + _vm->_cursor->setCurrentFrameIndex(1); + return; + case kTSA0BEastMonitorOutSpotID: + case kTSA0BNorthMonitorOutSpotID: + case kTSA0BWestMonitorOutSpotID: + _vm->_cursor->setCurrentFrameIndex(2); + return; + } + } + + Neighborhood::updateCursor(where, cursorSpot); +} + +Common::String FullTSA::getNavMovieName() { + return "Images/TSA/Full TSA.movie"; +} + +Common::String FullTSA::getSoundSpotsName() { + return "Sounds/TSA/TSA Spots"; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/tsa/fulltsa.h b/engines/pegasus/neighborhood/tsa/fulltsa.h new file mode 100644 index 0000000000..4260a83a78 --- /dev/null +++ b/engines/pegasus/neighborhood/tsa/fulltsa.h @@ -0,0 +1,159 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_TSA_FULLTSA_H +#define PEGASUS_NEIGHBORHOOD_TSA_FULLTSA_H + +#include "pegasus/neighborhood/neighborhood.h" + +namespace Pegasus { + +class RipTimer : public IdlerAnimation { +public: + RipTimer(const DisplayElementID id) : IdlerAnimation(id) {} + virtual ~RipTimer() {} + + void initImage(); + void releaseImage(); + + void draw(const Common::Rect &); + +protected: + void timeChanged(const TimeValue); + + CoordType _middle; + Surface _timerImage; +}; + +// Room IDs. + +static const RoomID kTSA00 = 0; +static const RoomID kTSA22Red = 28; +static const RoomID kTSA37 = 42; + +class FullTSA : public Neighborhood { +friend void uncreatedInTSAFunction(FunctionPtr *, void *tsa); + +public: + FullTSA(InputHandler *, PegasusEngine *); + virtual ~FullTSA() {} + + virtual void init(); + + void start(); + + virtual uint16 getDateResID() const; + + void flushGameState(); + + void checkContinuePoint(const RoomID, const DirectionConstant); + + bool canSolve(); + void doSolve(); + + void updateCursor(const Common::Point, const Hotspot *); + +protected: + enum { + kTSAPrivateLogReaderOpenFlag, + kTSAPrivateKeyVaultOpenFlag, + kTSAPrivateChipVaultOpenFlag, + kTSAPrivatePlayingLeftComparisonFlag, + kTSAPrivatePlayingRightComparisonFlag, + kTSAPrivateSeenRobotWarningFlag, + kNumTSAPrivateFlags + }; + + Common::String getBriefingMovie(); + Common::String getEnvScanMovie(); + uint getNumHints(); + Common::String getHintMovie(uint); + void loadAmbientLoops(); + virtual void clickInHotspot(const Input &, const Hotspot *); + + virtual int16 getStaticCompassAngle(const RoomID, const DirectionConstant); + void activateOneHotspot(HotspotInfoTable::Entry &, Hotspot *spot); + virtual void activateHotspots(); + void getExitCompassMove(const ExitTable::Entry &, FaderMoveSpec &); + void dropItemIntoRoom(Item *, Hotspot *); + void downButton(const Input &); + void startDoorOpenMovie(const TimeValue, const TimeValue); + TimeValue getViewTime(const RoomID, const DirectionConstant); + void findSpotEntry(const RoomID, const DirectionConstant, SpotFlags, SpotTable::Entry &); + void turnTo(const DirectionConstant); + CanMoveForwardReason canMoveForward(ExitTable::Entry &); + CanOpenDoorReason canOpenDoor(DoorTable::Entry &); + void bumpIntoWall(); + void initializeTBPMonitor(const int, const ExtraID); + void playTBPMonitor(); + void getExtraCompassMove(const ExtraTable::Entry &, FaderMoveSpec &); + Hotspot *getItemScreenSpot(Item *, DisplayElement *); + void openDoor(); + void turnRight(); + void turnLeft(); + void closeDoorOffScreen(const RoomID, const DirectionConstant); + void playExtraMovie(const ExtraTable::Entry &, const NotificationFlags, const InputBits interruptionInput); + void handleInput(const Input &, const Hotspot *); + void arriveAtTSA25Red(); + void startUpComparisonMonitor(); + void shutDownComparisonMonitor(); + void initializeComparisonMonitor(const int, const ExtraID); + void playLeftComparison(); + void playRightComparison(); + void startRobotGame(); + void setOffRipAlarm(); + uint getHistoricalLogIndex(); + void startUpRobotMonitor(); + void shutDownRobotMonitor(); + void pickedUpItem(Item *item); + void arriveFromPrehistoric(); + + void arriveFromNorad(); + void arriveFromMars(); + void arriveFromWSC(); + + InputBits getInputFilter(); + void arriveAt(const RoomID, const DirectionConstant); + void initializePegasusButtons(bool); + void releaseSprites(); + void showMainJumpMenu(); + void arriveAtTSA37(); + void receiveNotification(Notification *, const NotificationFlags); + void checkRobotLocations(const RoomID, const DirectionConstant); + void getExtraEntry(const uint32, ExtraTable::Entry &); + + Sprite _sprite1, _sprite2, _sprite3; + FuseFunction _utilityFuse; + RipTimer _ripTimer; + + FlagsArray<byte, kNumTSAPrivateFlags> _privateFlags; + + Common::String getNavMovieName(); + Common::String getSoundSpotsName(); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/tsa/tinytsa.cpp b/engines/pegasus/neighborhood/tsa/tinytsa.cpp new file mode 100644 index 0000000000..2fa6c5377a --- /dev/null +++ b/engines/pegasus/neighborhood/tsa/tinytsa.cpp @@ -0,0 +1,453 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/aichip.h" +#include "pegasus/items/biochips/opticalchip.h" +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/tsa/tinytsa.h" +#include "pegasus/neighborhood/wsc/wsc.h" + +namespace Pegasus { + +static const int16 kCompassShift = 30; + +static const TimeScale kTinyTSAMovieScale = 600; +static const TimeScale kTinyTSAFramesPerSecond = 15; +static const TimeScale kTinyTSAFrameDuration = 40; + +// Alternate IDs. +static const AlternateID kAltTinyTSANormal = 0; + +// Hot Spot Activation IDs. +static const HotSpotActivationID kActivationTinyTSAJumpToNorad = 1; +static const HotSpotActivationID kActivationTinyTSAJumpToMars = 2; +static const HotSpotActivationID kActivationTinyTSAJumpToWSC = 3; +static const HotSpotActivationID kActivationTinyTSAReadyForJumpMenu = 4; +static const HotSpotActivationID kActivationTinyTSAMainJumpMenu = 5; + +// Hot Spot IDs. +static const HotSpotID kTinyTSA37NorthJumpToNoradSpotID = 5000; +static const HotSpotID kTinyTSA37NorthCancelNoradSpotID = 5001; +static const HotSpotID kTinyTSA37NorthJumpToMarsSpotID = 5002; +static const HotSpotID kTinyTSA37NorthCancelMarsSpotID = 5003; +static const HotSpotID kTinyTSA37NorthJumpToWSCSpotID = 5004; +static const HotSpotID kTinyTSA37NorthCancelWSCSpotID = 5005; +static const HotSpotID kTinyTSA37NorthJumpMenuSpotID = 5006; +static const HotSpotID kTinyTSA37NorthNoradMenuSpotID = 5007; +static const HotSpotID kTinyTSA37NorthMarsMenuSpotID = 5008; +static const HotSpotID kTinyTSA37NorthWSCMenuSpotID = 5009; + +// Extra sequence IDs. +static const ExtraID kTinyTSA37PegasusDepart = 0; +static const ExtraID kTinyTSA37TimeJumpToPegasus = 1; +static const ExtraID kTinyTSA37RecallToDownload = 2; +static const ExtraID kTinyTSA37ExitHilited = 3; +static const ExtraID kTinyTSA37ExitToHorse = 4; +static const ExtraID kTinyTSA37JumpMenu000 = 5; +static const ExtraID kTinyTSA37JumpMenu001 = 6; +static const ExtraID kTinyTSA37JumpMenu010 = 7; +static const ExtraID kTinyTSA37JumpMenu011 = 8; +static const ExtraID kTinyTSA37JumpMenu100 = 9; +static const ExtraID kTinyTSA37JumpMenu101 = 10; +static const ExtraID kTinyTSA37JumpMenu110 = 11; +static const ExtraID kTinyTSA37JumpMenu111 = 12; +static const ExtraID kTinyTSA37JumpToWSCMenu = 13; +static const ExtraID kTinyTSA37CancelWSC = 14; +static const ExtraID kTinyTSA37JumpToWSC = 15; +static const ExtraID kTinyTSA37WSCToAI5 = 16; +static const ExtraID kTinyTSA37PegasusAI5 = 17; +static const ExtraID kTinyTSA37AI5ToWSC = 18; +static const ExtraID kTinyTSA37WSCToDepart = 19; +static const ExtraID kTinyTSA37JumpToMarsMenu = 20; +static const ExtraID kTinyTSA37CancelMars = 21; +static const ExtraID kTinyTSA37JumpToMars = 22; +static const ExtraID kTinyTSA37MarsToAI6 = 23; +static const ExtraID kTinyTSA37PegasusAI6 = 24; +static const ExtraID kTinyTSA37AI6ToMars = 25; +static const ExtraID kTinyTSA37MarsToDepart = 26; +static const ExtraID kTinyTSA37JumpToNoradMenu = 27; +static const ExtraID kTinyTSA37CancelNorad = 28; +static const ExtraID kTinyTSA37JumpToNorad = 29; +static const ExtraID kTinyTSA37NoradToAI7 = 30; +static const ExtraID kTinyTSA37PegasusAI7 = 31; +static const ExtraID kTinyTSA37AI7ToNorad = 32; +static const ExtraID kTinyTSA37NoradToDepart = 33; +static const ExtraID kTinyTSA37EnvironmentalScan = 34; +static const ExtraID kTinyTSA37DownloadToMainMenu = 35; +static const ExtraID kTinyTSA37DownloadToOpMemReview = 36; +static const ExtraID kTinyTSA37OpMemReviewToMainMenu = 37; + +TinyTSA::TinyTSA(InputHandler *nextHandler, PegasusEngine *owner) : Neighborhood(nextHandler, owner, "Tiny TSA", kTinyTSAID) { +} + +void TinyTSA::start() { + g_energyMonitor->stopEnergyDraining(); + Neighborhood::start(); +} + +Common::String TinyTSA::getBriefingMovie() { + Common::String movieName = Neighborhood::getBriefingMovie(); + + if (movieName.empty()) { + switch (getCurrentActivation()) { + case kActivationTinyTSAJumpToNorad: + g_AIChip->showBriefingClicked(); + startExtraSequenceSync(kTinyTSA37PegasusAI7, kHintInterruption); + startExtraSequenceSync(kTinyTSA37AI7ToNorad, kFilterNoInput); + g_AIChip->clearClicked(); + movieName = ""; + break; + case kActivationTinyTSAJumpToMars: + g_AIChip->showBriefingClicked(); + startExtraSequenceSync(kTinyTSA37PegasusAI6, kHintInterruption); + startExtraSequenceSync(kTinyTSA37AI6ToMars, kFilterNoInput); + g_AIChip->clearClicked(); + movieName = ""; + break; + case kActivationTinyTSAJumpToWSC: + g_AIChip->showBriefingClicked(); + startExtraSequenceSync(kTinyTSA37PegasusAI5, kHintInterruption); + startExtraSequenceSync(kTinyTSA37AI5ToWSC, kFilterNoInput); + g_AIChip->clearClicked(); + movieName = ""; + break; + default: + movieName = "Images/AI/TSA/XT04"; + break; + } + } + + return movieName; +} + +Common::String TinyTSA::getEnvScanMovie() { + Common::String movieName = Neighborhood::getEnvScanMovie(); + + if (movieName.empty()) { + g_AIChip->showEnvScanClicked(); + startExtraSequenceSync(kTinyTSA37EnvironmentalScan, kHintInterruption); + + switch (getCurrentActivation()) { + case kActivationTinyTSAJumpToNorad: + startExtraSequenceSync(kTinyTSA37AI7ToNorad, kFilterNoInput); + showExtraView(kTinyTSA37JumpToNoradMenu); + break; + case kActivationTinyTSAJumpToMars: + startExtraSequenceSync(kTinyTSA37AI6ToMars, kFilterNoInput); + showExtraView(kTinyTSA37JumpToMarsMenu); + break; + case kActivationTinyTSAJumpToWSC: + startExtraSequenceSync(kTinyTSA37AI5ToWSC, kFilterNoInput); + showExtraView(kTinyTSA37JumpToWSCMenu); + break; + default: + showMainJumpMenu(); + break; + } + + g_AIChip->clearClicked(); + } + + return movieName; +} + +void TinyTSA::loadAmbientLoops() { + loadLoopSound1("Sounds/TSA/T01NAE.NEW.22K.AIFF"); +} + +int16 TinyTSA::getStaticCompassAngle(const RoomID room, const DirectionConstant dir) { + return Neighborhood::getStaticCompassAngle(room, dir) - kCompassShift; +} + +uint16 TinyTSA::getDateResID() const { + return kDate2318ID; +} + +InputBits TinyTSA::getInputFilter() { + // Can't move forward... + return Neighborhood::getInputFilter() & ~(kFilterUpButton | kFilterUpAuto); +} + +void TinyTSA::clickInHotspot(const Input &input, const Hotspot *clickedSpot) { + if (clickedSpot) { + switch (clickedSpot->getObjectID()) { + case kTinyTSA37NorthJumpMenuSpotID: + // This hotspot isn't accessable from Tiny TSA + warning("jump menu spot"); + return; + case kTinyTSA37NorthJumpToNoradSpotID: + GameState.setTSAState(kPlayerOnWayToNorad); + requestExtraSequence(kTinyTSA37JumpToNorad, 0, kFilterNoInput); + if (!GameState.getBeenToNorad()) { + requestExtraSequence(kTinyTSA37NoradToAI7, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37PegasusAI7, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37AI7ToNorad, 0, kFilterNoInput); + GameState.setBeenToNorad(true); + } + + requestExtraSequence(kTinyTSA37NoradToDepart, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37PegasusDepart, kExtraCompletedFlag, kFilterNoInput); + return; + case kTinyTSA37NorthJumpToMarsSpotID: + GameState.setTSAState(kPlayerOnWayToMars); + requestExtraSequence(kTinyTSA37JumpToMars, 0, kFilterNoInput); + if (!GameState.getBeenToMars()) { + requestExtraSequence(kTinyTSA37MarsToAI6, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37PegasusAI6, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37AI6ToMars, 0, kFilterNoInput); + GameState.setBeenToMars(true); + } + + requestExtraSequence(kTinyTSA37MarsToDepart, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37PegasusDepart, kExtraCompletedFlag, kFilterNoInput); + return; + case kTinyTSA37NorthJumpToWSCSpotID: + GameState.setTSAState(kPlayerOnWayToWSC); + requestExtraSequence(kTinyTSA37JumpToWSC, 0, kFilterNoInput); + if (!GameState.getBeenToWSC()) { + requestExtraSequence(kTinyTSA37WSCToAI5, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37PegasusAI5, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37AI5ToWSC, 0, kFilterNoInput); + GameState.setBeenToWSC(true); + } + + requestExtraSequence(kTinyTSA37WSCToDepart, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37PegasusDepart, kExtraCompletedFlag, kFilterNoInput); + return; + } + } + + Neighborhood::clickInHotspot(input, clickedSpot); +} + +void TinyTSA::showMainJumpMenu() { + ExtraID jumpMenuView = kTinyTSA37JumpMenu000; + + if (GameState.getNoradFinished()) + jumpMenuView += 4; + if (GameState.getMarsFinished()) + jumpMenuView += 2; + if (GameState.getWSCFinished()) + jumpMenuView += 1; + + showExtraView(jumpMenuView); + setCurrentActivation(kActivationTinyTSAMainJumpMenu); +} + +void TinyTSA::checkContinuePoint(const RoomID, const DirectionConstant) { + makeContinuePoint(); +} + +void TinyTSA::arriveAt(const RoomID room, const DirectionConstant direction) { + Neighborhood::arriveAt(room, direction); + + switch (GameState.getTSAState()) { + case kPlayerOnWayToNorad: + case kPlayerOnWayToMars: + case kPlayerOnWayToWSC: + startExtraSequence(kTinyTSA37TimeJumpToPegasus, kExtraCompletedFlag, kFilterNoInput); + break; + case kPlayerLockedInPegasus: + showMainJumpMenu(); + break; + } +} + +void TinyTSA::receiveNotification(Notification *notification, const NotificationFlags flags) { + ExtraID lastExtra = _lastExtra; + + Neighborhood::receiveNotification(notification, flags); + + if ((flags & kExtraCompletedFlag) != 0) { + // Only allow input if we're not in the middle of series of queue requests. + if (actionQueueEmpty()) + _interruptionFilter = kFilterAllInput; + + switch (lastExtra) { + case kTinyTSA37PegasusDepart: + _vm->setLastEnergyValue(kFullEnergy); + + switch (GameState.getTSAState()) { + case kPlayerOnWayToNorad: + _vm->jumpToNewEnvironment(kNoradAlphaID, kNorad01, kSouth); + GameState.setNoradSeenTimeStream(false); + GameState.setNoradGassed(true); + GameState.setNoradFillingStationOn(false); + GameState.setNoradN22MessagePlayed(false); + GameState.setNoradPlayedGlobeGame(false); + GameState.setNoradBeatRobotWithClaw(false); + GameState.setNoradBeatRobotWithDoor(false); + GameState.setNoradRetScanGood(false); + GameState.setNoradWaitingForLaser(false); + GameState.setNoradSubRoomPressure(9); + GameState.setNoradSubPrepState(kSubNotPrepped); + break; + case kPlayerOnWayToMars: + _vm->jumpToNewEnvironment(kMarsID, kMars0A, kNorth); + GameState.setMarsSeenTimeStream(false); + GameState.setMarsHeardUpperPodMessage(false); + GameState.setMarsRobotThrownPlayer(false); + GameState.setMarsHeardCheckInMessage(false); + GameState.setMarsPodAtUpperPlatform(false); + GameState.setMarsSeenThermalScan(false); + GameState.setMarsArrivedBelow(false); + GameState.setMarsSeenRobotAtReactor(false); + GameState.setMarsAvoidedReactorRobot(false); + GameState.setMarsLockFrozen(false); + GameState.setMarsLockBroken(false); + GameState.setMarsSecurityDown(false); + GameState.setMarsAirlockOpen(false); + GameState.setMarsReadyForShuttleTransport(false); + GameState.setMarsFinishedCanyonChase(false); + GameState.setMarsThreadedMaze(false); + break; + case kPlayerOnWayToWSC: + _vm->jumpToNewEnvironment(kWSCID, kWSC01, kWest); + GameState.setWSCSeenTimeStream(false); + GameState.setWSCPoisoned(false); + GameState.setWSCAnsweredAboutDart(false); + GameState.setWSCDartInAnalyzer(false); + GameState.setWSCRemovedDart(false); + GameState.setWSCAnalyzerOn(false); + GameState.setWSCAnalyzedDart(false); + GameState.setWSCPickedUpAntidote(false); + GameState.setWSCSawMorph(false); + GameState.setWSCDesignedAntidote(false); + GameState.setWSCOfficeMessagesOpen(false); + GameState.setWSCSeenNerd(false); + GameState.setWSCHeardPage1(false); + GameState.setWSCHeardPage2(false); + GameState.setWSCHeardCheckIn(false); + GameState.setWSCDidPlasmaDodge(false); + GameState.setWSCSeenSinclairLecture(false); + GameState.setWSCBeenAtWSC93(false); + GameState.setWSCCatwalkDark(false); + GameState.setWSCRobotDead(false); + GameState.setWSCRobotGone(false); + break; + }; + break; + case kTinyTSA37TimeJumpToPegasus: + if (g_energyMonitor) + g_energyMonitor->stopEnergyDraining(); + + switch (GameState.getTSAState()) { + case kPlayerOnWayToNorad: + arriveFromNorad(); + break; + case kPlayerOnWayToMars: + arriveFromMars(); + break; + case kPlayerOnWayToWSC: + arriveFromWSC(); + break; + default: + break; + } + break; + case kTinyTSA37DownloadToOpMemReview: + switch (GameState.getTSAState()) { + case kPlayerOnWayToNorad: + g_opticalChip->playOpMemMovie(kPoseidonSpotID); + break; + case kPlayerOnWayToMars: + g_opticalChip->playOpMemMovie(kAriesSpotID); + break; + case kPlayerOnWayToWSC: + g_opticalChip->playOpMemMovie(kMercurySpotID); + break; + } + + requestExtraSequence(kTinyTSA37OpMemReviewToMainMenu, kExtraCompletedFlag, kFilterNoInput); + break; + case kTinyTSA37DownloadToMainMenu: + case kTinyTSA37OpMemReviewToMainMenu: + GameState.setTSAState(kPlayerLockedInPegasus); + showMainJumpMenu(); + makeContinuePoint(); + break; + case kTinyTSA37JumpToNoradMenu: + setCurrentActivation(kActivationTinyTSAJumpToNorad); + break; + case kTinyTSA37JumpToMarsMenu: + setCurrentActivation(kActivationTinyTSAJumpToMars); + break; + case kTinyTSA37JumpToWSCMenu: + setCurrentActivation(kActivationTinyTSAJumpToWSC); + break; + case kTinyTSA37CancelNorad: + case kTinyTSA37CancelMars: + case kTinyTSA37CancelWSC: + showMainJumpMenu(); + break; + } + } + + g_AIArea->checkMiddleArea(); +} + +void TinyTSA::arriveFromNorad() { + requestExtraSequence(kTinyTSA37RecallToDownload, 0, kFilterNoInput); + + if (GameState.getNoradFinished() && !GameState.getScoringFinishedNorad()) { + GameState.setScoringFinishedNorad(); + requestExtraSequence(kTinyTSA37DownloadToOpMemReview, kExtraCompletedFlag, kFilterNoInput); + } else { + requestExtraSequence(kTinyTSA37DownloadToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } +} + +void TinyTSA::arriveFromMars() { + requestExtraSequence(kTinyTSA37RecallToDownload, 0, kFilterNoInput); + + if (GameState.getMarsFinished() && !GameState.getScoringFinishedMars()) { + GameState.setScoringFinishedMars(); + requestExtraSequence(kTinyTSA37DownloadToOpMemReview, kExtraCompletedFlag, kFilterNoInput); + } else { + requestExtraSequence(kTinyTSA37DownloadToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } +} + +void TinyTSA::arriveFromWSC() { + requestExtraSequence(kTinyTSA37RecallToDownload, 0, kFilterNoInput); + + if (GameState.getWSCFinished() && !GameState.getScoringFinishedWSC()) { + GameState.setScoringFinishedWSC(); + requestExtraSequence(kTinyTSA37DownloadToOpMemReview, kExtraCompletedFlag, kFilterNoInput); + } else { + requestExtraSequence(kTinyTSA37DownloadToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } +} + +Common::String TinyTSA::getNavMovieName() { + return "Images/TSA/Tiny TSA.movie"; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/tsa/tinytsa.h b/engines/pegasus/neighborhood/tsa/tinytsa.h new file mode 100644 index 0000000000..db74206d4f --- /dev/null +++ b/engines/pegasus/neighborhood/tsa/tinytsa.h @@ -0,0 +1,71 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_TSA_TINYTSA_H +#define PEGASUS_NEIGHBORHOOD_TSA_TINYTSA_H + +#include "pegasus/neighborhood/neighborhood.h" + +namespace Pegasus { + +// Room IDs. + +static const RoomID kTinyTSA37 = 0; + +class TinyTSA : public Neighborhood { +public: + TinyTSA(InputHandler *, PegasusEngine *); + virtual ~TinyTSA() {} + + virtual uint16 getDateResID() const; + + void start(); + + void checkContinuePoint(const RoomID, const DirectionConstant); + +protected: + Common::String getBriefingMovie(); + Common::String getEnvScanMovie(); + void loadAmbientLoops(); + virtual void clickInHotspot(const Input &, const Hotspot *); + + virtual int16 getStaticCompassAngle(const RoomID, const DirectionConstant); + + void arriveFromNorad(); + void arriveFromMars(); + void arriveFromWSC(); + + InputBits getInputFilter(); + void arriveAt(const RoomID, const DirectionConstant); + void showMainJumpMenu(); + void receiveNotification(Notification *, const NotificationFlags); + + Common::String getNavMovieName(); + Common::String getSoundSpotsName() { return ""; } +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/turn.cpp b/engines/pegasus/neighborhood/turn.cpp new file mode 100644 index 0000000000..1157796f55 --- /dev/null +++ b/engines/pegasus/neighborhood/turn.cpp @@ -0,0 +1,63 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/turn.h" + +namespace Pegasus { + +void TurnTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].room = stream->readUint16BE(); + _entries[i].direction = stream->readByte(); + _entries[i].turnDirection = stream->readByte(); + _entries[i].altCode = stream->readByte(); + stream->readByte(); // alignment + _entries[i].endDirection = stream->readByte(); + stream->readByte(); // alignment + debug(0, "Turn[%d]: %d %d %d %d %d", i, _entries[i].room, _entries[i].direction, + _entries[i].turnDirection, _entries[i].altCode, _entries[i].endDirection); + } +} + +void TurnTable::clear() { + _entries.clear(); +} + +TurnTable::Entry TurnTable::findEntry(RoomID room, DirectionConstant direction, TurnDirection turnDirection, AlternateID altCode) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].room == room && _entries[i].direction == direction && _entries[i].turnDirection == turnDirection && _entries[i].altCode == altCode) + return _entries[i]; + + return Entry(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/turn.h b/engines/pegasus/neighborhood/turn.h new file mode 100644 index 0000000000..329b03eddb --- /dev/null +++ b/engines/pegasus/neighborhood/turn.h @@ -0,0 +1,69 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_TURN_H +#define PEGASUS_NEIGHBORHOOD_TURN_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +class TurnTable { +public: + TurnTable() {} + ~TurnTable() {} + + static uint32 getResTag() { return MKTAG('T', 'u', 'r', 'n'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry() { endDirection = kNoDirection; } + bool isEmpty() { return endDirection == kNoDirection; } + + RoomID room; + DirectionConstant direction; + TurnDirection turnDirection; + AlternateID altCode; + DirectionConstant endDirection; + }; + + Entry findEntry(RoomID room, DirectionConstant direction, TurnDirection turnDirection, AlternateID altCode); + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/view.cpp b/engines/pegasus/neighborhood/view.cpp new file mode 100644 index 0000000000..4e46f5374e --- /dev/null +++ b/engines/pegasus/neighborhood/view.cpp @@ -0,0 +1,60 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/view.h" + +namespace Pegasus { + +void ViewTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].room = stream->readUint16BE(); + _entries[i].direction = stream->readByte(); + _entries[i].altCode = stream->readByte(); + _entries[i].time = stream->readUint32BE(); + debug(0, "View[%d]: %d %d %d %d", i, _entries[i].room, _entries[i].direction, + _entries[i].altCode, _entries[i].time); + } +} + +void ViewTable::clear() { + _entries.clear(); +} + +ViewTable::Entry ViewTable::findEntry(RoomID room, DirectionConstant direction, AlternateID altCode) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].room == room && _entries[i].direction == direction && _entries[i].altCode == altCode) + return _entries[i]; + + return Entry(); +} + +} // End of namespace pegasus diff --git a/engines/pegasus/neighborhood/view.h b/engines/pegasus/neighborhood/view.h new file mode 100644 index 0000000000..3397508b61 --- /dev/null +++ b/engines/pegasus/neighborhood/view.h @@ -0,0 +1,68 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_VIEW_H +#define PEGASUS_NEIGHBORHOOD_VIEW_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +class ViewTable { +public: + ViewTable() {} + ~ViewTable() {} + + static uint32 getResTag() { return MKTAG('V', 'i', 'e', 'w'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry() { time = 0xffffffff; } + bool isEmpty() { return time == 0xffffffff; } + + RoomID room; + DirectionConstant direction; + AlternateID altCode; + TimeValue time; + }; + + Entry findEntry(RoomID room, DirectionConstant direction, AlternateID altCode); + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/wsc/moleculebin.cpp b/engines/pegasus/neighborhood/wsc/moleculebin.cpp new file mode 100644 index 0000000000..210c0ad313 --- /dev/null +++ b/engines/pegasus/neighborhood/wsc/moleculebin.cpp @@ -0,0 +1,127 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/graphics.h" +#include "pegasus/neighborhood/wsc/moleculebin.h" +#include "pegasus/neighborhood/wsc/wsc.h" + +namespace Pegasus { + +static const CoordType kMoleculeBinWidth = 138; +static const CoordType kMoleculeBinHeight = 128; + +static const CoordType kMoleculeWidth = 66; +static const CoordType kMoleculeHeight = 40; + +static const CoordType kMoleculeBinLeft = kNavAreaLeft + 286; +static const CoordType kMoleculeBinTop = kNavAreaLeft + 96; + +// Layouts: + +MoleculeBin::MoleculeBin() : DisplayElement(kNoDisplayElement) { + _highlightColor = g_system->getScreenFormat().RGBToColor(0xff, 0xff, 102); + _selectedMolecule = -1; +} + +void MoleculeBin::initMoleculeBin() { + if (!isDisplaying()) { + for (int i = 0; i < 6; i++) + _binLayout[i] = i; + + resetBin(); + _binImages.getImageFromPICTFile("Images/World Science Center/Molecules"); + setDisplayOrder(kWSCMoleculeBinOrder); + setBounds(kMoleculeBinLeft, kMoleculeBinTop, kMoleculeBinLeft + kMoleculeBinWidth, + kMoleculeBinTop + kMoleculeBinHeight); + startDisplaying(); + show(); + } +} + +void MoleculeBin::cleanUpMoleculeBin() { + if (isDisplaying()) { + stopDisplaying(); + _binImages.deallocateSurface(); + } +} + +void MoleculeBin::setBinLayout(const uint32 *layout) { + for (int i = 0; i < 6; i++) + _binLayout[i] = layout[i]; +} + +void MoleculeBin::highlightMolecule(const uint32 whichMolecule) { + if (!_moleculeFlags.getFlag(whichMolecule)) { + _moleculeFlags.setFlag(whichMolecule, true); + triggerRedraw(); + } +} + +bool MoleculeBin::isMoleculeHighlighted(uint32 whichMolecule) { + return _moleculeFlags.getFlag(whichMolecule); +} + +void MoleculeBin::selectMolecule(const int whichMolecule) { + if (_selectedMolecule != whichMolecule) { + _selectedMolecule = whichMolecule; + triggerRedraw(); + } +} + +void MoleculeBin::resetBin() { + _moleculeFlags.clearAllFlags(); + _selectedMolecule = -1; + triggerRedraw(); +} + +void MoleculeBin::draw(const Common::Rect &) { + Common::Rect r1(0, 0, kMoleculeWidth, kMoleculeHeight); + Common::Rect r2 = r1; + + for (int i = 0; i < 6; i++) { + r1.moveTo(i * (kMoleculeWidth * 2), 0); + + if (_moleculeFlags.getFlag(_binLayout[i])) + r1.translate(kMoleculeWidth, 0); + + r2.moveTo((_binLayout[i] & 1) * (kMoleculeWidth + 2) + _bounds.left + 2, + (_binLayout[i] >> 1) * (kMoleculeHeight + 2) + _bounds.top + 2); + + _binImages.copyToCurrentPort(r1, r2); + } + + if (_selectedMolecule >= 0) { + r2.moveTo((_selectedMolecule & 1) * (kMoleculeWidth + 2) + _bounds.left + 2, + (_selectedMolecule >> 1) * (kMoleculeHeight + 2) + _bounds.top + 2); + + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getWorkArea(); + + screen->frameRect(r2, _highlightColor); + r2.grow(1); + screen->frameRect(r2, _highlightColor); + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/wsc/moleculebin.h b/engines/pegasus/neighborhood/wsc/moleculebin.h new file mode 100644 index 0000000000..3de4b5ed2a --- /dev/null +++ b/engines/pegasus/neighborhood/wsc/moleculebin.h @@ -0,0 +1,72 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_WSC_MOLECULEBIN_H +#define PEGASUS_NEIGHBORHOOD_WSC_MOLECULEBIN_H + +#include "pegasus/elements.h" +#include "pegasus/surface.h" +#include "pegasus/util.h" + +namespace Pegasus { + +enum { + kMolecule1, + kMolecule2, + kMolecule3, + kMolecule4, + kMolecule5, + kMolecule6 +}; + +class MoleculeBin : public DisplayElement { +public: + MoleculeBin(); + virtual ~MoleculeBin() {} + + void initMoleculeBin(); + void cleanUpMoleculeBin(); + + void setBinLayout(const uint32 *); + + void highlightMolecule(const uint32 whichMolecule); + void selectMolecule(const int whichMolecule); + void resetBin(); + + bool isMoleculeHighlighted(uint32); + +protected: + void draw(const Common::Rect &); + + Surface _binImages; + FlagsArray<byte, kMolecule6 + 1> _moleculeFlags; + int _selectedMolecule; + uint32 _binLayout[6]; + uint32 _highlightColor; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/wsc/wsc.cpp b/engines/pegasus/neighborhood/wsc/wsc.cpp new file mode 100644 index 0000000000..1f00999aa7 --- /dev/null +++ b/engines/pegasus/neighborhood/wsc/wsc.cpp @@ -0,0 +1,2540 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/opticalchip.h" +#include "pegasus/items/biochips/shieldchip.h" +#include "pegasus/neighborhood/wsc/wsc.h" + +namespace Pegasus { + +static const CanMoveForwardReason kCantMoveWatchingDiagnosis = kCantMoveLastReason + 1; + +static const CanTurnReason kCantTurnWatchingDiagnosis = kCantTurnLastReason + 1; +static const CanTurnReason kCantTurnWatchingAnalysis = kCantTurnWatchingDiagnosis + 1; +static const CanTurnReason kCantTurnInMoleculeGame = kCantTurnWatchingAnalysis + 1; + +static const TimeScale kMoleculesMovieScale = 600; +static const TimeValue kMoleculeLoopTime = 4 * kMoleculesMovieScale; +static const TimeValue kMoleculeFailTime = 2 * kMoleculesMovieScale; + +enum { + kMoleculeLoop0Time = 0, + kMoleculeFail0Time = kMoleculeLoop0Time + kMoleculeLoopTime, + kMoleculeLoop1Time = kMoleculeFail0Time + kMoleculeFailTime, + kMoleculeFail1Time = kMoleculeLoop1Time + kMoleculeLoopTime, + kMoleculeLoop2Time = kMoleculeFail1Time + kMoleculeFailTime, + kMoleculeFail2Time = kMoleculeLoop2Time + kMoleculeLoopTime, + kMoleculeLoop3Time = kMoleculeFail2Time + kMoleculeFailTime, + kMoleculeFail3Time = kMoleculeLoop3Time + kMoleculeLoopTime, + kMoleculeLoop4Time = kMoleculeFail3Time + kMoleculeFailTime, + kMoleculeFail4Time = kMoleculeLoop4Time + kMoleculeLoopTime, + kMoleculeLoop5Time = kMoleculeFail4Time + kMoleculeFailTime, + kMoleculeFail5Time = kMoleculeLoop5Time + kMoleculeLoopTime, + kMoleculeLoop6Time = kMoleculeFail5Time + kMoleculeFailTime +}; + +static const TimeValue s_moleculeLoopTimes[] = { + kMoleculeLoop0Time, + kMoleculeLoop1Time, + kMoleculeLoop2Time, + kMoleculeLoop3Time, + kMoleculeLoop4Time, + kMoleculeLoop5Time, + kMoleculeLoop6Time +}; + +static const TimeValue s_moleculeFailTimes[] = { + kMoleculeFail0Time, + kMoleculeFail1Time, + kMoleculeFail2Time, + kMoleculeFail3Time, + kMoleculeFail4Time, + kMoleculeFail5Time +}; + +static const int16 kAuditoriumAngleOffset = 5; + +static const int kPlasmaEnergyWithShield = kMaxJMPEnergy * 10 / 100; +static const int kPlasmaEnergyNoShield = kMaxJMPEnergy * 20 / 100; + +static const int kTimerEventPlasmaHit = 0; +static const int kTimerEventPlayerGawkingAtRobot = 1; +static const int kTimerEventPlayerGawkingAtRobot2 = 2; + +static const TimeValue kWSCMolecule1In = 0; +static const TimeValue kWSCMolecule1Out = 937; + +static const TimeValue kWSCMolecule2In = 937; +static const TimeValue kWSCMolecule2Out = 1864; + +static const TimeValue kWSCMolecule3In = 1864; +static const TimeValue kWSCMolecule3Out = 2790; + +static const TimeValue kWSCClick1In = 2790; +static const TimeValue kWSCClick1Out = 2890; + +static const TimeValue kWSCClick2In = 2890; +static const TimeValue kWSCClick2Out = 3059; + +static const TimeValue kWSCClick3In = 3059; +static const TimeValue kWSCClick3Out = 3156; + +static const TimeValue kWSCFlashlightClickIn = 3156; +static const TimeValue kWSCFlashlightClickOut = 3211; + +static const TimeValue kWSCBumpIntoWallIn = 3211; +static const TimeValue kWSCBumpIntoWallOut = 3514; + +static const TimeValue kWSCCantTransportIn = 3514; +static const TimeValue kWSCCantTransportOut = 7791; + +static const TimeValue kHernandezNotHomeIn = 7791; +static const TimeValue kHernandezNotHomeOut = 10199; + +static const TimeValue kWashingtonNotHomeIn = 10199; +static const TimeValue kWashingtonNotHomeOut = 12649; + +static const TimeValue kSullivanNotHomeIn = 12649; +static const TimeValue kSullivanNotHomeOut = 15031; + +static const TimeValue kNakamuraNotHomeIn = 15031; +static const TimeValue kNakamuraNotHomeOut = 17545; + +static const TimeValue kGrailisNotHomeIn = 17545; +static const TimeValue kGrailisNotHomeOut = 19937; + +static const TimeValue kTheriaultNotHomeIn = 19937; +static const TimeValue kTheriaultNotHomeOut = 22395; + +static const TimeValue kGlennerNotHomeIn = 22395; +static const TimeValue kGlennerNotHomeOut = 24770; + +static const TimeValue kSinclairNotHomeIn = 24770; +static const TimeValue kSinclairNotHomeOut = 27328; + +static const TimeValue kWSCLabClosedIn = 27328; +static const TimeValue kWSCLabClosedOut = 28904; + +static const TimeValue kSlidingDoorCloseIn = 28904; +static const TimeValue kSlidingDoorCloseOut = 29295; + +static const TimeValue kSlimyDoorCloseIn = 29295; +static const TimeValue kSlimyDoorCloseOut = 29788; + +static const TimeValue kPaging1In = 29788; +static const TimeValue kPaging1Out = 32501; + +static const TimeValue kPaging2In = 32501; +static const TimeValue kPaging2Out = 34892; + +static const TimeValue kCheckInIn = 34892; +static const TimeValue kCheckInOut = 37789; + +static const TimeValue kDrinkAntidoteIn = 37789; +static const TimeValue kDrinkAntidoteOut = 39725; + +static const TimeScale kWSCMovieScale = 600; +static const TimeScale kWSCFramesPerSecond = 15; +static const TimeScale kWSCFrameDuration = 40; + +// Alternate IDs. +static const AlternateID kAltWSCNormal = 0; +static const AlternateID kAltWSCTookMachineGun = 1; +static const AlternateID kAltWSCW0ZDoorOpen = 2; +static const AlternateID kAltWSCPeopleAtW19North = 3; + +// Room IDs. +static const RoomID kWSC02 = 1; +static const RoomID kWSC03 = 4; +static const RoomID kWSC04 = 5; +static const RoomID kWSC06 = 6; +static const RoomID kWSC07 = 7; +static const RoomID kWSC08 = 8; +static const RoomID kWSC09 = 9; +static const RoomID kWSC10 = 10; +static const RoomID kWSC11 = 11; +static const RoomID kWSC13 = 12; +static const RoomID kWSC14 = 13; +static const RoomID kWSC15 = 14; +static const RoomID kWSC16 = 15; +static const RoomID kWSC17 = 16; +static const RoomID kWSC18 = 17; +static const RoomID kWSC19 = 18; +static const RoomID kWSC20 = 19; +static const RoomID kWSC21 = 20; +static const RoomID kWSC22 = 21; +static const RoomID kWSC23 = 22; +static const RoomID kWSC24 = 23; +static const RoomID kWSC25 = 24; +static const RoomID kWSC26 = 25; +static const RoomID kWSC27 = 26; +static const RoomID kWSC28 = 27; +static const RoomID kWSC29 = 28; +static const RoomID kWSC31 = 29; +static const RoomID kWSC32 = 30; +static const RoomID kWSC33 = 31; +static const RoomID kWSC34 = 32; +static const RoomID kWSC35 = 33; +static const RoomID kWSC36 = 34; +static const RoomID kWSC37 = 35; +static const RoomID kWSC38 = 36; +static const RoomID kWSC39 = 37; +static const RoomID kWSC40 = 38; +static const RoomID kWSC41 = 39; +static const RoomID kWSC42 = 40; +static const RoomID kWSC43 = 41; +static const RoomID kWSC44 = 42; +static const RoomID kWSC45 = 43; +static const RoomID kWSC46 = 44; +static const RoomID kWSC47 = 45; +static const RoomID kWSC48 = 46; +static const RoomID kWSC49 = 47; +static const RoomID kWSC50 = 48; +static const RoomID kWSC52 = 49; +static const RoomID kWSC53 = 50; +static const RoomID kWSC54 = 51; +static const RoomID kWSC55 = 52; +static const RoomID kWSC56 = 53; +static const RoomID kWSC57 = 54; +static const RoomID kWSC58 = 55; +static const RoomID kWSC60 = 56; +static const RoomID kWSC60East = 57; +static const RoomID kWSC60North = 58; +static const RoomID kWSC61 = 59; +static const RoomID kWSC61South = 60; +static const RoomID kWSC61West = 61; +static const RoomID kWSC63 = 63; +static const RoomID kWSC64 = 64; +static const RoomID kWSC65 = 65; +static const RoomID kWSC65Screen = 66; +static const RoomID kWSC66 = 67; +static const RoomID kWSC67 = 68; +static const RoomID kWSC68 = 69; +static const RoomID kWSC69 = 70; +static const RoomID kWSC70 = 71; +static const RoomID kWSC71 = 72; +static const RoomID kWSC72 = 73; +static const RoomID kWSC73 = 74; +static const RoomID kWSC74 = 75; +static const RoomID kWSC75 = 76; +static const RoomID kWSC0Z = 77; +static const RoomID kWSC76 = 78; +static const RoomID kWSC77 = 79; +static const RoomID kWSC78 = 80; +static const RoomID kWSC79 = 81; +static const RoomID kWSC80 = 82; +static const RoomID kWSC81 = 83; +static const RoomID kWSC82 = 84; +static const RoomID kWSC83 = 85; +static const RoomID kWSC84 = 86; +static const RoomID kWSC85 = 87; +static const RoomID kWSC86 = 88; +static const RoomID kWSC87 = 89; +static const RoomID kWSC88 = 90; +static const RoomID kWSC89 = 91; +static const RoomID kWSC90 = 92; +static const RoomID kWSC91 = 93; +static const RoomID kWSC92 = 94; +static const RoomID kWSC93 = 95; +static const RoomID kWSC94 = 96; +static const RoomID kWSC95 = 97; +static const RoomID kWSC96 = 98; +static const RoomID kWSC97 = 99; +static const RoomID kWSC98 = 100; +static const RoomID kWSCDeathRoom = 101; + +// Hot Spot Activation IDs. +static const HotSpotActivationID kActivationZoomedInToAnalyzer = 1; +static const HotSpotActivationID kActivationShotByRobot = 2; +static const HotSpotActivationID kActivationWarnedAboutPoison = 3; +static const HotSpotActivationID kActivationMorphScreenOff = 4; +static const HotSpotActivationID kActivationReadyForMorph = 5; +static const HotSpotActivationID kActivationMorphLooping = 6; +static const HotSpotActivationID kActivationMorphInterrupted = 7; +static const HotSpotActivationID kActivationW03NorthOff = 8; +static const HotSpotActivationID kActivationW03NorthReadyForInstructions = 9; +static const HotSpotActivationID kActivationW03NorthSawInstructions = 10; +static const HotSpotActivationID kActivationW03NorthInGame = 11; +static const HotSpotActivationID kActivationReadyForSynthesis = 12; +static const HotSpotActivationID kActivationSynthesizerLooping = 13; +static const HotSpotActivationID kActivationReadyForMap = 14; +static const HotSpotActivationID kActivationSinclairOfficeLocked = 15; +static const HotSpotActivationID kActivationW58SouthDoorLocked = 16; +static const HotSpotActivationID kActivationW61SouthOff = 17; +static const HotSpotActivationID kActivationW61SouthOn = 18; +static const HotSpotActivationID kActivationW61MessagesOff = 19; +static const HotSpotActivationID kActivationW61MessagesOn = 20; +static const HotSpotActivationID kActivationWSCRobotHeadOpen = 21; +static const HotSpotActivationID kActivationRobotTurning = 22; +static const HotSpotActivationID kActivationRobotDead = 23; +static const HotSpotActivationID kActivationRobotGone = 24; + +// Hot Spot IDs. +static const HotSpotID kWSCDropDartSpotID = 5000; +static const HotSpotID kWSCTurnOnAnalyzerSpotID = 5001; +static const HotSpotID kWSCAnalyzerScreenSpotID = 5002; +static const HotSpotID kWSCSpinRobotSpotID = 5003; +static const HotSpotID kWSC01YesSpotID = 5004; +static const HotSpotID kWSC01NoSpotID = 5005; +static const HotSpotID kWSC01AcknowledgeWarningSpotID = 5006; +static const HotSpotID kWSC02SouthMorphSpotID = 5007; +static const HotSpotID kWSC02SouthMessagesSpotID = 5008; +static const HotSpotID kWSC02SouthMorphOutSpotID = 5009; +static const HotSpotID kWSC02ActivateMorphScreenSpotID = 5010; +static const HotSpotID kWSC02SouthStartMorphSpotID = 5011; +static const HotSpotID kWSC02SouthInterruptMorphSpotID = 5012; +static const HotSpotID kWSC02SouthMorphFinishedSpotID = 5013; +static const HotSpotID kWSC02SouthTakeArgonSpotID = 5014; +static const HotSpotID kWSC02SouthMessagesOutSpotID = 5015; +static const HotSpotID kWSC02SouthTakeNitrogenSpotID = 5016; +static const HotSpotID kWSC02SouthPlayMessagesSpotID = 5017; +static const HotSpotID kWSC03NorthActivateScreenSpotID = 5018; +static const HotSpotID kWSC03NorthBuildMoleculeSpotID = 5019; +static const HotSpotID kWSC03NorthProceedSpotID = 5020; +static const HotSpotID kWSC03NorthMolecule1SpotID = 5021; +static const HotSpotID kWSC03NorthMolecule2SpotID = 5022; +static const HotSpotID kWSC03NorthMolecule3SpotID = 5023; +static const HotSpotID kWSC03NorthMolecule4SpotID = 5024; +static const HotSpotID kWSC03NorthMolecule5SpotID = 5025; +static const HotSpotID kWSC03NorthMolecule6SpotID = 5026; +static const HotSpotID kWSC03SouthActivateSynthesizerSpotID = 5027; +static const HotSpotID kWSC03SouthPickUpAntidoteSpotID = 5028; +static const HotSpotID kWSC07SouthMapSpotID = 5029; +static const HotSpotID kW42EastUnlockDoorSpotID = 5030; +static const HotSpotID kW56NorthMapSpotID = 5031; +static const HotSpotID kW58SouthPryDoorSpotID = 5032; +static const HotSpotID kWSC60EastSpotID = 5033; +static const HotSpotID kWSC60NorthSpotID = 5034; +static const HotSpotID kWSC60EastOutSpotID = 5035; +static const HotSpotID kWSC60NorthOutSpotID = 5036; +static const HotSpotID kWSC61EastSpotID = 5037; +static const HotSpotID kWSC61SouthSpotID = 5038; +static const HotSpotID kW61SouthMachineGunSpotID = 5039; +static const HotSpotID kW61SouthDropMachineGunSpotID = 5040; +static const HotSpotID kWSC61WestSpotID = 5041; +static const HotSpotID kWSC61SouthOutSpotID = 5042; +static const HotSpotID kW61SouthActivateSpotID = 5043; +static const HotSpotID kW61SmartAlloysSpotID = 5044; +static const HotSpotID kW61MorphingSpotID = 5045; +static const HotSpotID kW61TimeBendingSpotID = 5046; +static const HotSpotID kWSC61WestOutSpotID = 5047; +static const HotSpotID kW61TurnOnMessagesSpotID = 5048; +static const HotSpotID kW61WhiteMessageSpotID = 5049; +static const HotSpotID kW61WalchekMessageSpotID = 5050; +static const HotSpotID kWSC65SouthScreenSpotID = 5051; +static const HotSpotID kWSC65SouthScreenOutSpotID = 5052; +static const HotSpotID kW98RetinalChipSpotID = 5053; +static const HotSpotID kW98MapChipSpotID = 5054; +static const HotSpotID kW98OpticalChipSpotID = 5055; +static const HotSpotID kW98DropArgonSpotID = 5056; +static const HotSpotID kW98GrabCableSpotID = 5057; +static const HotSpotID kW98OpenRobotSpotID = 5058; +static const HotSpotID kW98StunGunSpotID = 5059; + +// Extra sequence IDs. +static const ExtraID kWSCArrivalFromTSA = 0; +static const ExtraID kWSCShotByRobot = 1; +static const ExtraID kWSCDartScan1 = 2; +static const ExtraID kWSCDartScan2 = 3; +static const ExtraID kWSCDartScanNo = 4; +static const ExtraID kWSCDartScan3 = 5; +static const ExtraID kWSCAnalyzerPowerUp = 6; +static const ExtraID kWSCAnalyzerPowerUpWithDart = 7; +static const ExtraID kWSCDropDartIntoAnalyzer = 8; +static const ExtraID kWSCAnalyzeDart = 9; +static const ExtraID kWSCZoomOutFromAnalyzer = 10; +static const ExtraID kWSCSpinRobot = 11; +static const ExtraID kWSC02MorphZoomNoArgon = 12; +static const ExtraID kWSC02MessagesZoomNoNitrogen = 13; +static const ExtraID kWSC02ZoomOutNoArgon = 14; +static const ExtraID kWSC02TurnOnMorphScreen = 15; +static const ExtraID kWSC02DropToMorphExperiment = 16; +static const ExtraID kWSC02MorphLoop = 17; +static const ExtraID kWSC02MorphInterruption = 18; +static const ExtraID kWSC02MorphFinished = 19; +static const ExtraID kWSC02TurnOffMorphScreen = 20; +static const ExtraID kWSC02SouthViewNoArgon = 21; +static const ExtraID kMessagesMovedToOffice = 22; +static const ExtraID kMessagesOff = 23; +static const ExtraID kMessagesZoomOutNoNitrogen = 24; +static const ExtraID kMessagesMovedToOfficeNoNitrogen = 25; +static const ExtraID kMessagesOffNoNitrogen = 26; +static const ExtraID kMessagesViewNoNitrogen = 27; +static const ExtraID kMessagesViewMachineOnNoNitrogen = 28; +static const ExtraID kW03NorthActivate = 29; +static const ExtraID kW03NorthGetData = 30; +static const ExtraID kW03NorthInstructions = 31; +static const ExtraID kW03NorthPrepMolecule1 = 32; +static const ExtraID kW03NorthPrepMolecule2 = 33; +static const ExtraID kW03NorthPrepMolecule3 = 34; +static const ExtraID kW03NorthFinishSynthesis = 35; +static const ExtraID kW03SouthCreateAntidote = 36; +static const ExtraID kW03SouthAntidoteLoop = 37; +static const ExtraID kW03SouthDeactivate = 38; +static const ExtraID kW03SouthViewNoAntidote = 39; +static const ExtraID kWSC07SouthMap = 40; +static const ExtraID kW17WestPeopleCrossing = 41; +static const ExtraID kW17WestPeopleCrossingView = 42; +static const ExtraID kW21SouthPeopleCrossing = 43; +static const ExtraID kW24SouthPeopleCrossing = 44; +static const ExtraID kW34EastPeopleCrossing = 45; +static const ExtraID kW36WestPeopleCrossing = 46; +static const ExtraID kW38NorthPeopleCrossing = 47; +static const ExtraID kW46SouthPeopleCrossing = 48; +static const ExtraID kW49NorthPeopleCrossing = 49; +static const ExtraID kW49NorthPeopleCrossingView = 50; +static const ExtraID kWSC56SouthMap = 51; +static const ExtraID kNerdAtTheDoor1 = 52; +static const ExtraID kNerdAtTheDoor2 = 53; +static const ExtraID kW61SouthZoomInNoGun = 54; +static const ExtraID kW61Brochure = 55; +static const ExtraID kW61SouthScreenOnWithGun = 56; +static const ExtraID kW61SouthScreenOffWithGun = 57; +static const ExtraID kW61SouthSmartAlloysWithGun = 58; +static const ExtraID kW61SouthMorphingWithGun = 59; +static const ExtraID kW61SouthTimeBendingWithGun = 60; +static const ExtraID kW61SouthZoomOutNoGun = 61; +static const ExtraID kW61SouthScreenOnNoGun = 62; +static const ExtraID kW61SouthScreenOffNoGun = 63; +static const ExtraID kW61SouthSmartAlloysNoGun = 64; +static const ExtraID kW61SouthMorphingNoGun = 65; +static const ExtraID kW61SouthTimeBendingNoGun = 66; +static const ExtraID kW61MessagesOn = 67; +static const ExtraID kW61MessagesOff = 68; +static const ExtraID kW61WhiteMessage = 69; +static const ExtraID kW61WalchekMessage = 70; +static const ExtraID kW61WalchekEasterEgg1 = 71; +static const ExtraID kW62SouthPlasmaRobotAppears = 72; +static const ExtraID kW62ZoomToRobot = 73; +static const ExtraID kW62ZoomOutFromRobot = 74; +static const ExtraID kW62PlasmaDodgeSurvive = 75; +static const ExtraID kW62PlasmaDodgeDie = 76; +static const ExtraID kW65SouthSinclairLecture = 77; +static const ExtraID kW73WestPeopleCrossing = 78; +static const ExtraID kW73WestPeopleCrossingView = 79; +static const ExtraID kW0ZSpottedByWomen = 80; +static const ExtraID kW95RobotShoots = 81; +static const ExtraID kW98MorphsToRobot = 82; +static const ExtraID kW98RobotShoots = 83; +static const ExtraID kW98RobotShocked = 84; +static const ExtraID kW98RobotGassed = 85; +static const ExtraID kW98RobotHeadOpensDark = 86; +static const ExtraID kW98RobotHead000Dark = 87; +static const ExtraID kW98RobotHead001Dark = 88; +static const ExtraID kW98RobotHead010Dark = 89; +static const ExtraID kW98RobotHead011Dark = 90; +static const ExtraID kW98RobotHead100Dark = 91; +static const ExtraID kW98RobotHead101Dark = 92; +static const ExtraID kW98RobotHead110Dark = 93; +static const ExtraID kW98RobotHead111Dark = 94; +static const ExtraID kW98RobotHeadClosesDark = 95; +static const ExtraID kW98WestViewWithGunDark = 96; +static const ExtraID kW98WestViewNoGunDark = 97; +static const ExtraID kW98RobotHeadOpensLight = 98; +static const ExtraID kW98RobotHead000Light = 99; +static const ExtraID kW98RobotHead001Light = 100; +static const ExtraID kW98RobotHead010Light = 101; +static const ExtraID kW98RobotHead011Light = 102; +static const ExtraID kW98RobotHead100Light = 103; +static const ExtraID kW98RobotHead101Light = 104; +static const ExtraID kW98RobotHead110Light = 105; +static const ExtraID kW98RobotHead111Light = 106; +static const ExtraID kW98RobotHeadClosesLight = 107; +static const ExtraID kW98WestViewWithGunLight = 108; +static const ExtraID kW98WestViewNoGunLight = 109; + +static const CoordType kMoleculesMovieLeft = kNavAreaLeft + 112; +static const CoordType kMoleculesMovieTop = kNavAreaTop + 40; + +WSC::WSC(InputHandler *nextHandler, PegasusEngine *owner) : Neighborhood(nextHandler, owner, "WSC", kWSCID), + _moleculesMovie(kNoDisplayElement) { + setIsItemTaken(kArgonCanister); + setIsItemTaken(kSinclairKey); + setIsItemTaken(kNitrogenCanister); + setIsItemTaken(kPoisonDart); + setIsItemTaken(kAntidote); + setIsItemTaken(kMachineGun); + setIsItemTaken(kStunGun); + + GameState.setTakenItemID(kArgonPickup, GameState.isTakenItemID(kArgonCanister) && + GameState.isTakenItemID(kSinclairKey)); +} + +uint16 WSC::getDateResID() const { + return kDate2310ID; +} + +void WSC::init() { + Neighborhood::init(); + + _cachedZoomSpot = 0; + _argonSprite = 0; + + // HACK: Fix the drag item for picking up the Sinclair Key Card + HotspotInfoTable::Entry *entry = findHotspotEntry(kWSC02SouthTakeArgonSpotID); + entry->hotspotItem = kArgonPickup; +} + +void WSC::flushGameState() { + g_energyMonitor->saveCurrentEnergyValue(); +} + +void WSC::start() { + if (g_energyMonitor) { + g_energyMonitor->stopEnergyDraining(); + g_energyMonitor->restoreLastEnergyValue(); + _vm->resetEnergyDeathReason(); + g_energyMonitor->startEnergyDraining(); + } + + if (!GameState.getWSCDidPlasmaDodge()) + forceStridingStop(kWSC58, kSouth, kAltWSCNormal); + + Neighborhood::start(); +} + +class PryDoorMessage : public AIPlayMessageAction { +public: + PryDoorMessage() : AIPlayMessageAction("Images/AI/WSC/XW59SD3", false) {} + +protected: + virtual void performAIAction(AIRule *); +}; + +void PryDoorMessage::performAIAction(AIRule *rule) { + if (((PegasusEngine *)g_engine)->playerHasItemID(kShieldBiochip) + && ((PegasusEngine *)g_engine)->getCurrentBiochip()->getObjectID() != kShieldBiochip) + AIPlayMessageAction::performAIAction(rule); +} + +void WSC::setUpAIRules() { + Neighborhood::setUpAIRules(); + + if (g_AIArea) { + AIPlayMessageAction *messageAction = new AIPlayMessageAction("Images/AI/WSC/XW1WB1", false); + AILastExtraCondition *extraCondition = new AILastExtraCondition(kWSCDartScan1); + AIRule *rule = new AIRule(extraCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB5A", false); + AILocationCondition *locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC06, kNorth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB5A", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC10, kWest)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB5A", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC28, kWest)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB5A", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC49, kWest)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB5A", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC65, kSouth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB5A", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC73, kSouth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB5A", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC79, kWest)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/WSC/XW59SD1", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC58, kSouth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + PryDoorMessage *pryDoorMessage = new PryDoorMessage(); + AIDoorOpenedCondition *doorCondition = new AIDoorOpenedCondition(MakeRoomView(kWSC58, kSouth)); + rule = new AIRule(doorCondition, pryDoorMessage); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/WSC/XW61E", false); + AIHasItemCondition *itemCondition = new AIHasItemCondition(kMachineGun); + rule = new AIRule(itemCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB1E", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC95, kWest)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + } +} + +Common::String WSC::getBriefingMovie() { + return "Images/AI/WSC/XWO"; +} + +Common::String WSC::getEnvScanMovie() { + RoomID room = GameState.getCurrentRoom(); + + if (room >= kWSC01 && room <= kWSC04) + return "Images/AI/WSC/XWE1"; + else if (room >= kWSC06 && room <= kWSC58) + return "Images/AI/WSC/XWE2"; + else if (room >= kWSC60 && room <= kWSC61West) + return "Images/AI/WSC/XWE3"; + else if (room >= kWSC64 && room <= kWSC98) + return "Images/AI/WSC/XWE4"; + + return "Images/AI/WSC/XWE5"; +} + +uint WSC::getNumHints() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC10, kWest): + case MakeRoomView(kWSC28, kWest): + case MakeRoomView(kWSC49, kWest): + case MakeRoomView(kWSC65, kSouth): + case MakeRoomView(kWSC75, kSouth): + case MakeRoomView(kWSC79, kWest): + return 2; + case MakeRoomView(kWSC02, kSouth): + if (_vm->getEnergyDeathReason() == kDeathDidntStopPoison && + !_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag) && + !GameState.getWSCDesignedAntidote()) + return 3; + else if (!GameState.getScoringGotNitrogenCanister() || + !GameState.getScoringGotSinclairKey()) + return 1; + break; + case MakeRoomView(kWSC03, kNorth): + if (inSynthesizerGame() || (_vm->getEnergyDeathReason() == kDeathDidntStopPoison && + !_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag) && + !GameState.getWSCDesignedAntidote())) + return 3; + break; + case MakeRoomView(kWSC01, kNorth): + case MakeRoomView(kWSC01, kSouth): + case MakeRoomView(kWSC01, kEast): + case MakeRoomView(kWSC01, kWest): + case MakeRoomView(kWSC02, kNorth): + case MakeRoomView(kWSC02, kEast): + case MakeRoomView(kWSC02, kWest): + case MakeRoomView(kWSC02Morph, kNorth): + case MakeRoomView(kWSC02Morph, kEast): + case MakeRoomView(kWSC02Morph, kWest): + case MakeRoomView(kWSC02Messages, kNorth): + case MakeRoomView(kWSC02Messages, kEast): + case MakeRoomView(kWSC02Messages, kWest): + case MakeRoomView(kWSC03, kSouth): + case MakeRoomView(kWSC03, kEast): + case MakeRoomView(kWSC03, kWest): + case MakeRoomView(kWSC04, kNorth): + case MakeRoomView(kWSC04, kSouth): + case MakeRoomView(kWSC04, kEast): + case MakeRoomView(kWSC04, kWest): + if (_vm->getEnergyDeathReason() == kDeathDidntStopPoison && + !_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag) && + !GameState.getWSCDesignedAntidote()) + return 3; + break; + case MakeRoomView(kWSC02Messages, kSouth): + if (_vm->getEnergyDeathReason() == kDeathDidntStopPoison && + !_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag) && + !GameState.getWSCDesignedAntidote()) + return 3; + else if (!GameState.getScoringGotNitrogenCanister()) + return 1; + break; + case MakeRoomView(kWSC02Morph, kSouth): + if (_vm->getEnergyDeathReason() == kDeathDidntStopPoison && + !_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag) && + !GameState.getWSCDesignedAntidote()) + return 3; + else if (!GameState.getScoringGotSinclairKey()) + return 1; + break; + case MakeRoomView(kWSC42, kEast): + if (!GameState.isCurrentDoorOpen()) + return 1; + break; + case MakeRoomView(kWSC58, kSouth): + if (GameState.isCurrentDoorOpen()) { + if (GameState.getWSCDidPlasmaDodge()) + return 0; + else + return 1; + } else if (_vm->playerHasItemID(kCrowbar)) + return 2; + + return 3; + case MakeRoomView(kWSC61, kEast): + if (!GameState.getScoringSawBrochure()) + return 1; + break; + case MakeRoomView(kWSC61, kSouth): + if (!GameState.getScoringSawSinclairEntry1() || + !GameState.getScoringSawSinclairEntry2() || + !GameState.getScoringSawSinclairEntry3()) + return 1; + break; + case MakeRoomView(kWSC98, kWest): + if (getCurrentActivation() == kActivationRobotTurning) + return 1; + break; + } + + return 0; +} + +Common::String WSC::getHintMovie(uint hintNum) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC10, kWest): + case MakeRoomView(kWSC28, kWest): + case MakeRoomView(kWSC49, kWest): + case MakeRoomView(kWSC65, kSouth): + case MakeRoomView(kWSC75, kSouth): + case MakeRoomView(kWSC79, kWest): + if (hintNum == 1) + return "Images/AI/Globals/XGLOB5B"; + + return "Images/AI/Globals/XGLOB5C"; + case MakeRoomView(kWSC02, kSouth): + if (_vm->getEnergyDeathReason() == kDeathDidntStopPoison && + !_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag) && + !GameState.getWSCDesignedAntidote()) + return Common::String::format("Images/AI/WSC/XWPH%d", hintNum); + + return "Images/AI/Globals/XGLOB1C"; + case MakeRoomView(kWSC61, kEast): + case MakeRoomView(kWSC61, kSouth): + return "Images/AI/Globals/XGLOB1C"; + case MakeRoomView(kWSC42, kEast): + if (_vm->playerHasItemID(kSinclairKey)) + return "Images/AI/Globals/XGLOB1A"; + + return "Images/AI/Globals/XGLOB2C"; + case MakeRoomView(kWSC58, kSouth): + switch (hintNum) { + case 1: + if (GameState.isCurrentDoorOpen()) { + // Only get here if we haven't done the plasma dodge game... + if (_vm->playerHasItemID(kShieldBiochip)) + return "Images/AI/Globals/XGLOB1A"; + else + return "Images/AI/Globals/XGLOB3F"; + } else if (_vm->playerHasItemID(kCrowbar)) { + return "Images/AI/Globals/XGLOB1A"; + } + + return "Images/AI/Globals/XGLOB1B"; + case 2: + // Only get here if the door is still locked... + if (_vm->playerHasItemID(kCrowbar)) + return "Images/AI/WSC/XW59SD2"; + + return "Images/AI/Globals/XGLOB2D"; + case 3: + // Only get here if the door is still locked and we don't have the + // crowbar... + return "Images/AI/WSC/XW59SD2"; + } + break; + case MakeRoomView(kWSC03, kNorth): + if (inSynthesizerGame()) + return Common::String::format("Images/AI/WSC/XW03NH%d", hintNum); + + return Common::String::format("Images/AI/WSC/XWPH%d", hintNum); + case MakeRoomView(kWSC01, kNorth): + case MakeRoomView(kWSC01, kSouth): + case MakeRoomView(kWSC01, kEast): + case MakeRoomView(kWSC01, kWest): + case MakeRoomView(kWSC02, kNorth): + case MakeRoomView(kWSC02, kEast): + case MakeRoomView(kWSC02, kWest): + case MakeRoomView(kWSC02Morph, kNorth): + case MakeRoomView(kWSC02Morph, kEast): + case MakeRoomView(kWSC02Morph, kWest): + case MakeRoomView(kWSC02Messages, kNorth): + case MakeRoomView(kWSC02Messages, kEast): + case MakeRoomView(kWSC02Messages, kWest): + case MakeRoomView(kWSC03, kSouth): + case MakeRoomView(kWSC03, kEast): + case MakeRoomView(kWSC03, kWest): + case MakeRoomView(kWSC04, kNorth): + case MakeRoomView(kWSC04, kSouth): + case MakeRoomView(kWSC04, kEast): + case MakeRoomView(kWSC04, kWest): + // analyzer hint + return Common::String::format("Images/AI/WSC/XWPH%d", hintNum); + case MakeRoomView(kWSC02Messages, kSouth): + case MakeRoomView(kWSC02Morph, kSouth): + if (_vm->getEnergyDeathReason() == kDeathDidntStopPoison && + !_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag) && + !GameState.getWSCDesignedAntidote()) + // analyzer hint + return Common::String::format("Images/AI/WSC/XWPH%d", hintNum); + + return "Images/AI/Globals/XGLOB1C"; + case MakeRoomView(kWSC98, kWest): + return "Images/AI/WSC/XW98WH2"; + } + + return ""; +} + +void WSC::prepareForAIHint(const Common::String &movieName) { + if (movieName == "Images/AI/WSC/XW98WH2" && isEventTimerRunning()) + pauseTimer(); +} + +void WSC::cleanUpAfterAIHint(const Common::String &movieName) { + if (movieName == "Images/AI/WSC/XW98WH2" && isEventTimerRunning()) + resumeTimer(); +} + +bool WSC::okayToJump() { + if (GameState.getWSCPoisoned()) { + die(kDeathDidntStopPoison); + return false; + } + + bool result = Neighborhood::okayToJump(); + if (!result) + playSpotSoundSync(kWSCCantTransportIn, kWSCCantTransportOut); + + return result; +} + +TimeValue WSC::getViewTime(const RoomID room, const DirectionConstant direction) { + ExtraID viewExtra = 0xffffffff; + ExtraTable::Entry extra; + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kWSC01, kWest): + if (!GameState.getWSCSeenTimeStream()) { + getExtraEntry(kWSCArrivalFromTSA, extra); + return extra.movieStart; + } else if (GameState.getWSCPoisoned() && !GameState.getWSCAnsweredAboutDart()) { + viewExtra = kWSCDartScan1; + } + break; + case MakeRoomView(kWSC02Morph, kSouth): + if (GameState.isTakenItemID(kArgonPickup) || GameState.isTakenItemID(kArgonCanister)) + viewExtra = kWSC02SouthViewNoArgon; + break; + case MakeRoomView(kWSC02Messages, kSouth): + if (GameState.isTakenItemID(kNitrogenCanister)) { + if (_privateFlags.getFlag(kWSCPrivateLabMessagesOpenFlag)) + viewExtra = kMessagesViewMachineOnNoNitrogen; + else + viewExtra = kMessagesViewNoNitrogen; + } + break; + case MakeRoomView(kWSC03, kSouth): + if (_privateFlags.getFlag(kWSCDraggingAntidoteFlag)) + viewExtra = kW03SouthViewNoAntidote; + break; + case MakeRoomView(kWSC17, kWest): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt17WestFlag)) + viewExtra = kW17WestPeopleCrossingView; + break; + case MakeRoomView(kWSC49, kNorth): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt49NorthFlag)) + viewExtra = kW49NorthPeopleCrossingView; + break; + case MakeRoomView(kWSC73, kWest): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt73WestFlag)) + viewExtra = kW73WestPeopleCrossingView; + break; + case MakeRoomView(kWSC98, kWest): + if (GameState.getWSCRobotDead()) { + if (GameState.getWSCRobotGone()) { + if (GameState.isTakenItemID(kStunGun)) { + if (GameState.getWSCCatwalkDark()) + viewExtra = kW98WestViewNoGunDark; + else + viewExtra = kW98WestViewNoGunLight; + } else { + if (GameState.getWSCCatwalkDark()) + viewExtra = kW98WestViewWithGunDark; + else + viewExtra = kW98WestViewWithGunLight; + } + } else if (_privateFlags.getFlag(kWSCPrivateRobotHeadOpenFlag)) { + if (GameState.getWSCCatwalkDark()) + viewExtra = kW98RobotHead111Dark; + else + viewExtra = kW98RobotHead111Light; + + if (_privateFlags.getFlag(kWSCPrivateGotRetScanChipFlag)) + viewExtra -= 1; + if (_privateFlags.getFlag(kWSCPrivateGotMapChipFlag)) + viewExtra -= 2; + if (_privateFlags.getFlag(kWSCPrivateGotOpticalChipFlag)) + viewExtra -= 4; + } else if (GameState.getWSCRobotDead()) { + // Should only happen on loading a saved game, so it can take its time. + if (GameState.getWSCCatwalkDark()) + viewExtra = kW98RobotShocked; + else + viewExtra = kW98RobotGassed; + } + } + break; + } + + if (viewExtra != 0xffffffff) { + getExtraEntry(viewExtra, extra); + return extra.movieEnd - 1; + } + + return Neighborhood::getViewTime(room, direction); +} + +void WSC::findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &spotEntry) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kWSC58, kSouth): + case MakeRoomView(kWSC79, kWest): + if ((flags & kSpotOnTurnMask) != 0) { + spotEntry.clear(); + return; + } + break; + } + + Neighborhood::findSpotEntry(room, direction, flags, spotEntry); +} + +void WSC::getZoomEntry(const HotSpotID id, ZoomTable::Entry &zoomEntry) { + Neighborhood::getZoomEntry(id, zoomEntry); + + ExtraTable::Entry extra; + ExtraID zoomExtra = 0xffffffff; + + switch (id) { + case kWSC02SouthMessagesSpotID: + if (GameState.isTakenItemID(kNitrogenCanister)) + zoomExtra = kWSC02MessagesZoomNoNitrogen; + break; + case kWSC02SouthMessagesOutSpotID: + if (GameState.isTakenItemID(kNitrogenCanister)) + zoomExtra = kMessagesZoomOutNoNitrogen; + break; + case kWSC02SouthMorphSpotID: + if (GameState.isTakenItemID(kArgonCanister)) + zoomExtra = kWSC02MorphZoomNoArgon; + break; + case kWSC02SouthMorphOutSpotID: + if (GameState.isTakenItemID(kArgonCanister)) + zoomExtra = kWSC02ZoomOutNoArgon; + break; + case kWSC61SouthSpotID: + if (GameState.isTakenItemID(kMachineGun)) + zoomExtra = kW61SouthZoomInNoGun; + break; + case kWSC61SouthOutSpotID: + if (GameState.isTakenItemID(kMachineGun)) + zoomExtra = kW61SouthZoomOutNoGun; + break; + } + + if (zoomExtra != 0xffffffff) { + getExtraEntry(zoomExtra, extra); + zoomEntry.movieStart = extra.movieStart; + zoomEntry.movieEnd = extra.movieEnd; + } +} + +void WSC::getExtraEntry(const uint32 id, ExtraTable::Entry &extraEntry) { + switch (id) { + case kWSCZoomOutFromAnalyzer: + Neighborhood::getExtraEntry(kWSCZoomOutFromAnalyzer, extraEntry); + extraEntry.movieEnd = extraEntry.movieStart + 14 * kWSCFrameDuration; + break; + case kW61WalchekMessage: + if (GameState.getEasterEgg()) + Neighborhood::getExtraEntry(kW61WalchekEasterEgg1, extraEntry); + else + Neighborhood::getExtraEntry(id, extraEntry); + break; + case kW61SouthScreenOnWithGun: + if (GameState.isTakenItemID(kMachineGun)) + Neighborhood::getExtraEntry(id, extraEntry); + else + Neighborhood::getExtraEntry(kW61SouthScreenOnNoGun, extraEntry); + break; + case kW61SouthSmartAlloysWithGun: + if (GameState.isTakenItemID(kMachineGun)) + Neighborhood::getExtraEntry(id, extraEntry); + else + Neighborhood::getExtraEntry(kW61SouthSmartAlloysNoGun, extraEntry); + break; + case kW61SouthMorphingWithGun: + if (GameState.isTakenItemID(kMachineGun)) + Neighborhood::getExtraEntry(id, extraEntry); + else + Neighborhood::getExtraEntry(kW61SouthMorphingNoGun, extraEntry); + break; + case kW61SouthTimeBendingWithGun: + if (GameState.isTakenItemID(kMachineGun)) + Neighborhood::getExtraEntry(id, extraEntry); + else + Neighborhood::getExtraEntry(kW61SouthTimeBendingNoGun, extraEntry); + break; + case kW98RobotHeadOpensLight: + if (GameState.getWSCCatwalkDark()) + Neighborhood::getExtraEntry(kW98RobotHeadOpensDark, extraEntry); + else + Neighborhood::getExtraEntry(id, extraEntry); + break; + default: + Neighborhood::getExtraEntry(id, extraEntry); + break; + } +} + +CanMoveForwardReason WSC::canMoveForward(ExitTable::Entry &entry) { + if (GameState.getCurrentRoomAndView() == MakeRoomView(kWSC01, kWest) && + getCurrentActivation() != kActivateHotSpotAlways) + return kCantMoveWatchingDiagnosis; + + return Neighborhood::canMoveForward(entry); +} + +// Also add cases here for compound analyzer... +CanTurnReason WSC::canTurn(TurnDirection turnDirection, DirectionConstant &nextDir) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC01, kWest): + if (getCurrentActivation() != kActivateHotSpotAlways) + return kCantTurnWatchingDiagnosis; + break; + case MakeRoomView(kWSC01, kEast): + if (getCurrentActivation() != kActivateHotSpotAlways) + return kCantTurnWatchingAnalysis; + break; + case MakeRoomView(kWSC03, kNorth): + if (_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag)) + return kCantTurnInMoleculeGame; + break; + } + + return Neighborhood::canTurn(turnDirection, nextDir); +} + +CanOpenDoorReason WSC::canOpenDoor(DoorTable::Entry &entry) { + switch (GameState.getCurrentRoom()) { + case kWSC42: + if (!_privateFlags.getFlag(kWSCPrivateSinclairOfficeOpenFlag)) + return kCantOpenLocked; + break; + case kWSC58: + if (!_privateFlags.getFlag(kWSCPrivate58SouthOpenFlag)) + return kCantOpenLocked; + break; + } + + return Neighborhood::canOpenDoor(entry); +} + +void WSC::bumpIntoWall() { + requestSpotSound(kWSCBumpIntoWallIn, kWSCBumpIntoWallOut, kFilterAllInput, 0); + Neighborhood::bumpIntoWall(); +} + +void WSC::closeDoorOffScreen(const RoomID room, const DirectionConstant) { + Item *keyCard; + + switch (room) { + case kWSC58: + case kWSC62: + case kWSC63: + case kWSC64: + case kWSC85: + case kWSC86: + case kWSC88: + case kWSC89: + playSpotSoundSync(kSlidingDoorCloseIn, kSlidingDoorCloseOut); + break; + case kWSC81: + case kWSC82: + case kWSC92: + case kWSC93: + keyCard = g_allItems.findItemByID(kKeyCard); + if (keyCard->getItemState() == kFlashlightOn && (GameState.getCurrentRoom() == kWSC81 || + GameState.getCurrentRoom() == kWSC93)) { + keyCard->setItemState(kFlashlightOff); + playSpotSoundSync(kWSCFlashlightClickIn, kWSCFlashlightClickOut); + } else if (keyCard->getItemState() == kFlashlightOff && (GameState.getCurrentRoom() == kWSC82 || + GameState.getCurrentRoom() == kWSC92)) { + keyCard->setItemState(kFlashlightOn); + playSpotSoundSync(kWSCFlashlightClickIn, kWSCFlashlightClickOut); + } + + playSpotSoundSync(kSlimyDoorCloseIn, kSlimyDoorCloseOut); + break; + default: + playSpotSoundSync(kSlimyDoorCloseIn, kSlimyDoorCloseOut); + break; + } +} + +void WSC::cantMoveThatWay(CanMoveForwardReason reason) { + if (reason != kCantMoveWatchingDiagnosis) + Neighborhood::cantMoveThatWay(reason); +} + +void WSC::cantOpenDoor(CanOpenDoorReason reason) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC22, kWest): + playSpotSoundSync(kNakamuraNotHomeIn, kNakamuraNotHomeOut); + break; + case MakeRoomView(kWSC23, kEast): + playSpotSoundSync(kHernandezNotHomeIn, kHernandezNotHomeOut); + break; + case MakeRoomView(kWSC26, kWest): + playSpotSoundSync(kGrailisNotHomeIn, kGrailisNotHomeOut); + break; + case MakeRoomView(kWSC27, kEast): + playSpotSoundSync(kWashingtonNotHomeIn, kWashingtonNotHomeOut); + break; + case MakeRoomView(kWSC32, kWest): + playSpotSoundSync(kTheriaultNotHomeIn, kTheriaultNotHomeOut); + break; + case MakeRoomView(kWSC33, kEast): + playSpotSoundSync(kSullivanNotHomeIn, kSullivanNotHomeOut); + break; + case MakeRoomView(kWSC41, kWest): + playSpotSoundSync(kGlennerNotHomeIn, kGlennerNotHomeOut); + break; + case MakeRoomView(kWSC42, kEast): + playSpotSoundSync(kSinclairNotHomeIn, kSinclairNotHomeOut); + break; + case MakeRoomView(kWSC15, kWest): + case MakeRoomView(kWSC25, kWest): + case MakeRoomView(kWSC33, kWest): + case MakeRoomView(kWSC41, kEast): + case MakeRoomView(kWSC46, kWest): + playSpotSoundSync(kWSCLabClosedIn, kWSCLabClosedOut); + break; + default: + Neighborhood::cantOpenDoor(reason); + break; + } +} + +void WSC::doorOpened() { + Neighborhood::doorOpened(); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC42, kEast): + _vm->addItemToInventory((InventoryItem *)g_allItems.findItemByID(kSinclairKey)); + break; + case MakeRoomView(kWSC58, kSouth): + GameState.setScoringUsedCrowBarInWSC(); + _vm->addItemToInventory((InventoryItem *)g_allItems.findItemByID(kCrowbar)); + break; + case MakeRoomView(kWSC06, kNorth): + case MakeRoomView(kWSC79, kWest): + die(kDeathArrestedInWSC); + break; + case MakeRoomView(kWSC60, kWest): + if (_vm->itemInInventory(kMachineGun)) + startExtraSequence(kNerdAtTheDoor2, kExtraCompletedFlag, kFilterNoInput); + else if (!GameState.getWSCSeenNerd()) + startExtraSequence(kNerdAtTheDoor1, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC95, kWest): + GameState.setScoringOpenedCatwalk(); + scheduleEvent(kGawkAtRobotTime, 1, kTimerEventPlayerGawkingAtRobot); + break; + } +} + +void WSC::turnLeft() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC17, kNorth): + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt17WestFlag) && _vm->getRandomNumber(2) == 0) + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt17WestFlag, true); + break; + case MakeRoomView(kWSC49, kEast): + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt49NorthFlag) && _vm->getRandomNumber(2) == 0) + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt49NorthFlag, true); + break; + case MakeRoomView(kWSC73, kNorth): + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt73WestFlag) && _vm->getRandomNumber(2) == 0) + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt73WestFlag, true); + break; + case MakeRoomView(kWSC73, kWest): + if (!GameState.getWSCBeenAtWSC93()) + setCurrentAlternate(kAltWSCW0ZDoorOpen); + break; + case MakeRoomView(kWSC95, kWest): + cancelEvent(); + break; + } + + Neighborhood::turnLeft(); +} + +void WSC::turnRight() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC17, kSouth): + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt17WestFlag) && _vm->getRandomNumber(2) == 0) + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt17WestFlag, true); + break; + case MakeRoomView(kWSC49, kWest): + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt49NorthFlag) && _vm->getRandomNumber(2) == 0) + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt49NorthFlag, true); + break; + case MakeRoomView(kWSC73, kSouth): + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt73WestFlag) && _vm->getRandomNumber(2) == 0) + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt73WestFlag, true); + break; + case MakeRoomView(kWSC73, kEast): + if (!GameState.getWSCBeenAtWSC93()) + setCurrentAlternate(kAltWSCW0ZDoorOpen); + break; + case MakeRoomView(kWSC95, kWest): + cancelEvent(); + break; + } + + Neighborhood::turnRight(); +} + +void WSC::moveForward() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC19, kNorth): + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt19NorthFlag)) + setCurrentAlternate(kAltWSCPeopleAtW19North); + break; + case MakeRoomView(kWSC95, kWest): + cancelEvent(); + break; + } + + Neighborhood::moveForward(); +} + +void WSC::zoomTo(const Hotspot *hotspot) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC02Messages, kSouth): + if (_privateFlags.getFlag(kWSCPrivateLabMessagesOpenFlag)) { + _cachedZoomSpot = hotspot; + if (GameState.isTakenItemID(kNitrogenCanister)) + startExtraSequence(kMessagesOffNoNitrogen, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMessagesOff, kExtraCompletedFlag, kFilterNoInput); + return; + } + break; + case MakeRoomView(kWSC61West, kWest): + if (GameState.getWSCOfficeMessagesOpen()) { + _cachedZoomSpot = hotspot; + startExtraSequence(kW61MessagesOff, kExtraCompletedFlag, kFilterNoInput); + return; + } + break; + case MakeRoomView(kWSC61South, kSouth): + if (_privateFlags.getFlag(kWSCPrivateOfficeLogOpenFlag)) { + _cachedZoomSpot = hotspot; + if (GameState.isTakenItemID(kMachineGun)) + startExtraSequence(kW61SouthScreenOffNoGun, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kW61SouthScreenOffWithGun, kExtraCompletedFlag, kFilterNoInput); + return; + } + break; + } + + Neighborhood::zoomTo(hotspot); +} + +void WSC::startExtraSequence(const ExtraID extraID, const NotificationFlags flags, const InputBits interruptionFilter) { + if (extraID == kW61Brochure) + loadLoopSound1(""); + + Neighborhood::startExtraSequence(extraID, flags, interruptionFilter); +} + +int16 WSC::getStaticCompassAngle(const RoomID room, const DirectionConstant dir) { + int16 angle = Neighborhood::getStaticCompassAngle(room, dir); + + switch (room) { + case kWSC02Messages: + angle -= 50; + break; + case kWSC02Morph: + angle += 5; + break; + case kWSC60East: + angle -= 10; + break; + case kWSC66: + angle -= kAuditoriumAngleOffset; + break; + case kWSC67: + angle += kAuditoriumAngleOffset; + break; + case kWSC68: + angle -= kAuditoriumAngleOffset * 2; + break; + case kWSC69: + angle += kAuditoriumAngleOffset * 2; + break; + case kWSC70: + angle -= kAuditoriumAngleOffset * 3; + break; + case kWSC71: + angle += kAuditoriumAngleOffset * 3; + break; + case kWSC72: + if (dir == kEast || dir == kWest) + angle -= kAuditoriumAngleOffset * 4; + break; + case kWSC73: + if (dir == kEast || dir == kWest) + angle += kAuditoriumAngleOffset * 4; + break; + } + + return angle; +} + +void WSC::getExitCompassMove(const ExitTable::Entry &exitEntry, FaderMoveSpec &compassMove) { + Neighborhood::getExitCompassMove(exitEntry, compassMove); + + if (exitEntry.room == kWSC65 && exitEntry.direction == kSouth) { + compassMove.insertFaderKnot(exitEntry.movieStart + 100 * kWSCFrameDuration, 180); + compassMove.insertFaderKnot(exitEntry.movieStart + 108 * kWSCFrameDuration, 150); + compassMove.insertFaderKnot(exitEntry.movieEnd, 150); + } +} + +void WSC::getExtraCompassMove(const ExtraTable::Entry &entry, FaderMoveSpec &compassMove) { + switch (entry.extra) { + case kW61Brochure: + compassMove.insertFaderKnot(entry.movieStart + 15 * kWSCFrameDuration, 85); + compassMove.insertFaderKnot(entry.movieEnd - 15 * kWSCFrameDuration, 85); + compassMove.insertFaderKnot(entry.movieEnd, 90); + break; + default: + Neighborhood::getExtraCompassMove(entry, compassMove); + break; + } +} + +void WSC::loadAmbientLoops() { + RoomID room = GameState.getCurrentRoom(); + + if (room >= kWSC01 && room <= kWSC04) { + if (GameState.getWSCSeenTimeStream()) + loadLoopSound1("Sounds/World Science Center/WLabLoop.22K.AIFF", 0x100 / 2); + } else if ((room >= kWSC06 && room <= kWSC58) || (room >= kWSC62 && room <= kWSC63)) + loadLoopSound1("Sounds/World Science Center/Organic Walls.22K.AIFF", 0x100 / 2); + else if (room >= kWSC82 && room <= kWSC92) + loadLoopSound1("Sounds/World Science Center/Creature Feature.22K.AIFF"); + else if ((room >= kWSC60 && room <= kWSC61West) || (room >= kWSC64 && room <= kWSC81) || + (room >= kWSC93 && room <= kWSC97)) + loadLoopSound1("Sounds/World Science Center/The Other Side.22K.AIFF", 0x100 / 12); + else if (room == kWSC98) + loadLoopSound1("Sounds/World Science Center/WCatLoop.22K.AIFF"); +} + +void WSC::checkContinuePoint(const RoomID room, const DirectionConstant direction) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kWSC07, kNorth): + case MakeRoomView(kWSC11, kSouth): + case MakeRoomView(kWSC13, kSouth): + case MakeRoomView(kWSC13, kWest): + case MakeRoomView(kWSC16, kWest): + case MakeRoomView(kWSC17, kEast): + case MakeRoomView(kWSC19, kWest): + case MakeRoomView(kWSC28, kNorth): + case MakeRoomView(kWSC28, kSouth): + case MakeRoomView(kWSC28, kEast): + case MakeRoomView(kWSC28, kWest): + case MakeRoomView(kWSC29, kNorth): + case MakeRoomView(kWSC29, kSouth): + case MakeRoomView(kWSC29, kEast): + case MakeRoomView(kWSC29, kWest): + case MakeRoomView(kWSC40, kEast): + case MakeRoomView(kWSC42, kEast): + case MakeRoomView(kWSC49, kWest): + case MakeRoomView(kWSC49, kNorth): + case MakeRoomView(kWSC50, kNorth): + case MakeRoomView(kWSC55, kEast): + case MakeRoomView(kWSC65, kSouth): + case MakeRoomView(kWSC65, kEast): + case MakeRoomView(kWSC65, kWest): + case MakeRoomView(kWSC72, kEast): + case MakeRoomView(kWSC72, kSouth): + case MakeRoomView(kWSC73, kWest): + case MakeRoomView(kWSC73, kSouth): + case MakeRoomView(kWSC79, kWest): + case MakeRoomView(kWSC81, kEast): + case MakeRoomView(kWSC93, kNorth): + case MakeRoomView(kWSC95, kWest): + makeContinuePoint(); + break; + case MakeRoomView(kWSC58, kSouth): + if (!GameState.getWSCDidPlasmaDodge()) + makeContinuePoint(); + break; + case MakeRoomView(kWSC60, kWest): + if (_vm->playerHasItemID(kMachineGun)) + makeContinuePoint(); + break; + } +} + +void WSC::arriveAt(const RoomID room, const DirectionConstant dir) { + switch (MakeRoomView(room, dir)) { + case MakeRoomView(kWSC60, kNorth): + case MakeRoomView(kWSC60, kSouth): + case MakeRoomView(kWSC60, kEast): + case MakeRoomView(kWSC60, kWest): + case MakeRoomView(kWSC60East, kNorth): + case MakeRoomView(kWSC60East, kSouth): + case MakeRoomView(kWSC60East, kEast): + case MakeRoomView(kWSC60East, kWest): + case MakeRoomView(kWSC60North, kNorth): + case MakeRoomView(kWSC60North, kSouth): + case MakeRoomView(kWSC60North, kEast): + case MakeRoomView(kWSC60North, kWest): + case MakeRoomView(kWSC61, kNorth): + case MakeRoomView(kWSC61, kSouth): + case MakeRoomView(kWSC61, kEast): + case MakeRoomView(kWSC61, kWest): + case MakeRoomView(kWSC61South, kNorth): + case MakeRoomView(kWSC61South, kSouth): + case MakeRoomView(kWSC61South, kEast): + case MakeRoomView(kWSC61South, kWest): + case MakeRoomView(kWSC61West, kNorth): + case MakeRoomView(kWSC61West, kSouth): + case MakeRoomView(kWSC61West, kEast): + case MakeRoomView(kWSC61West, kWest): + if (GameState.isTakenItemID(kMachineGun)) + setCurrentAlternate(kAltWSCTookMachineGun); + else + setCurrentAlternate(kAltWSCNormal); + break; + case MakeRoomView(kWSC73, kSouth): + case MakeRoomView(kWSC75, kNorth): + case MakeRoomView(kWSC75, kSouth): + case MakeRoomView(kWSC75, kEast): + case MakeRoomView(kWSC75, kWest): + if (!GameState.getWSCBeenAtWSC93()) + setCurrentAlternate(kAltWSCW0ZDoorOpen); + break; + } + + Neighborhood::arriveAt(room, dir); + + switch (MakeRoomView(room, dir)) { + case MakeRoomView(kWSC01, kWest): + if (!GameState.getWSCSeenTimeStream()) { + requestExtraSequence(kWSCArrivalFromTSA, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kWSCShotByRobot, 0, kFilterNoInput); + requestExtraSequence(kWSCDartScan1, kExtraCompletedFlag, kFilterNoInput); + } else if (GameState.getWSCPoisoned() && !GameState.getWSCAnsweredAboutDart()) { + setCurrentActivation(kActivationShotByRobot); + } + break; + case MakeRoomView(kWSC01, kEast): + if (GameState.getWSCDartInAnalyzer()) + requestExtraSequence(kWSCDropDartIntoAnalyzer, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC02Morph, kSouth): + setCurrentActivation(kActivationMorphScreenOff); + break; + case MakeRoomView(kWSC03, kNorth): + setCurrentActivation(kActivationW03NorthOff); + break; + case MakeRoomView(kWSC03, kSouth): + if (GameState.getWSCDesignedAntidote() && !GameState.getWSCPickedUpAntidote()) + setCurrentActivation(kActivationReadyForSynthesis); + break; + case MakeRoomView(kWSC16, kNorth): + if (getCurrentAlternate() == kAltWSCPeopleAtW19North) { + setCurrentAlternate(kAltWSCNormal); + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt19NorthFlag, true); + } + break; + case MakeRoomView(kWSC07, kSouth): + case MakeRoomView(kWSC56, kNorth): + setCurrentActivation(kActivationReadyForMap); + break; + case MakeRoomView(kWSC42, kWest): + setCurrentAlternate(kAltWSCNormal); + break; + case MakeRoomView(kWSC42, kEast): + _privateFlags.setFlag(kWSCPrivateSinclairOfficeOpenFlag, false); + setCurrentActivation(kActivationSinclairOfficeLocked); + break; + case MakeRoomView(kWSC58, kSouth): + setCurrentActivation(kActivationW58SouthDoorLocked); + _privateFlags.setFlag(kWSCPrivate58SouthOpenFlag, false); + break; + case MakeRoomView(kWSC60, kEast): + GameState.setScoringEnteredSinclairOffice(); + break; + case MakeRoomView(kWSC61West, kWest): + setCurrentActivation(kActivationW61MessagesOff); + break; + case MakeRoomView(kWSC61South, kSouth): + setCurrentActivation(kActivationW61SouthOff); + break; + case MakeRoomView(kWSC62, kSouth): + if (!GameState.getWSCDidPlasmaDodge()) { + g_AIArea->lockAIOut(); + loadLoopSound1("Sounds/World Science Center/Plasma Rock.22K.AIFF"); + requestExtraSequence(kW62SouthPlasmaRobotAppears, 0, kFilterNoInput); + requestExtraSequence(kW62ZoomToRobot, 0, kFilterNoInput); + requestExtraSequence(kW62ZoomOutFromRobot, kExtraCompletedFlag, kFilterNoInput); + } + break; + case MakeRoomView(kWSC65Screen, kSouth): + if (!GameState.getWSCSeenSinclairLecture()) { + GameState.setWSCSeenSinclairLecture(true); + startExtraSequence(kW65SouthSinclairLecture, kExtraCompletedFlag, kFilterAllInput); + } + break; + case MakeRoomView(kWSC66, kWest): + case MakeRoomView(kWSC67, kEast): + if (!GameState.getWSCHeardPage2()) { + playSpotSoundSync(kPaging2In, kPaging2Out); + GameState.setWSCHeardPage2(true); + } + case MakeRoomView(kWSC10, kNorth): + case MakeRoomView(kWSC26, kSouth): + case MakeRoomView(kWSC72, kWest): + case MakeRoomView(kWSC83, kWest): + if (!GameState.getWSCHeardCheckIn()) { + playSpotSoundSync(kCheckInIn, kCheckInOut); + GameState.setWSCHeardCheckIn(true); + } + break; + case MakeRoomView(kWSC0Z, kSouth): + if (getCurrentAlternate() == kAltWSCW0ZDoorOpen) + turnLeft(); + break; + case MakeRoomView(kWSC93, kEast): + GameState.setWSCBeenAtWSC93(true); + break; + case MakeRoomView(kWSC98, kWest): + if (!GameState.getWSCRobotDead()) { + scheduleEvent(kGawkAtRobotTime2, 1, kTimerEventPlayerGawkingAtRobot2); + setCurrentActivation(kActivationRobotTurning); + if (g_AIArea) + g_AIArea->checkMiddleArea(); + } else if (!GameState.getWSCRobotGone()) { + setCurrentActivation(kActivationRobotDead); + } else { + if (GameState.getWSCCatwalkDark()) { + // Change the gun hot spot... + g_allHotspots.setHotspotRect(kW98StunGunSpotID, Common::Rect(181 + kNavAreaLeft, + 99 + kNavAreaTop,372 + kNavAreaLeft, 149 + kNavAreaTop)); + } + setCurrentActivation(kActivationRobotGone); + } + break; + case MakeRoomView(kWSCDeathRoom, kNorth): + case MakeRoomView(kWSCDeathRoom, kSouth): + case MakeRoomView(kWSCDeathRoom, kEast): + case MakeRoomView(kWSCDeathRoom, kWest): + die(kDeathArrestedInWSC); + break; + } + + checkPeopleCrossing(); + setUpPoison(); +} + +void WSC::turnTo(const DirectionConstant direction) { + Neighborhood::turnTo(direction); + + switch (MakeRoomView(GameState.getCurrentRoom(), direction)) { + case MakeRoomView(kWSC01, kNorth): + case MakeRoomView(kWSC01, kSouth): + GameState.setWSCAnalyzerOn(false); + break; + case MakeRoomView(kWSC03, kNorth): + setCurrentActivation(kActivationW03NorthOff); + break; + case MakeRoomView(kWSC03, kSouth): + if (GameState.getWSCDesignedAntidote() && !GameState.getWSCPickedUpAntidote()) + setCurrentActivation(kActivationReadyForSynthesis); + break; + case MakeRoomView(kWSC07, kSouth): + case MakeRoomView(kWSC56, kNorth): + setCurrentActivation(kActivationReadyForMap); + break; + case MakeRoomView(kWSC18, kSouth): + case MakeRoomView(kWSC57, kEast): + case MakeRoomView(kWSC75, kEast): + case MakeRoomView(kWSC90, kSouth): + if (!GameState.getWSCHeardCheckIn()) { + playSpotSoundSync(kCheckInIn, kCheckInOut); + GameState.setWSCHeardCheckIn(true); + } + break; + case MakeRoomView(kWSC56, kSouth): + if (!GameState.getWSCHeardPage1()) { + playSpotSoundSync(kPaging1In, kPaging1Out); + GameState.setWSCHeardPage1(true); + } + // clone2727 says: This falls through?!??! WTF? + case MakeRoomView(kWSC42, kEast): + _privateFlags.setFlag(kWSCPrivateSinclairOfficeOpenFlag, false); + setCurrentActivation(kActivationSinclairOfficeLocked); + break; + case MakeRoomView(kWSC58, kSouth): + setCurrentActivation(kActivationW58SouthDoorLocked); + _privateFlags.setFlag(kWSCPrivate58SouthOpenFlag, false); + break; + case MakeRoomView(kWSC73, kWest): + setCurrentAlternate(kAltWSCNormal); + break; + case MakeRoomView(kWSC0Z, kEast): + if (getCurrentAlternate() == kAltWSCW0ZDoorOpen) + startExtraSequence(kW0ZSpottedByWomen, kExtraCompletedFlag, kFilterNoInput); + break; + } + + checkPeopleCrossing(); +} + +void WSC::receiveNotification(Notification *notification, const NotificationFlags flags) { + int32 currentEnergy; + Item *item; + + if (flags & kExtraCompletedFlag) { + _interruptionFilter = kFilterAllInput; + + switch (_lastExtra) { + case kWSCArrivalFromTSA: + GameState.setWSCSeenTimeStream(true); + loadAmbientLoops(); + break; + case kWSCDartScan1: + setCurrentActivation(kActivationShotByRobot); + GameState.setWSCPoisoned(true); + setUpPoison(); + makeContinuePoint(); + break; + case kWSCDartScan2: + _vm->addItemToInventory((InventoryItem *)g_allItems.findItemByID(kPoisonDart)); + GameState.setScoringRemovedDart(); + GameState.setWSCRemovedDart(true); + setUpPoison(); + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/WSC/XW1WB2", false, kHintInterruption); + // Fall through... + case kWSCDartScanNo: + GameState.setWSCAnsweredAboutDart(true); + startExtraSequence(kWSCDartScan3, kExtraCompletedFlag, kFilterNoInput); + break; + case kWSCDartScan3: + setCurrentActivation(kActivateHotSpotAlways); + break; + case kWSCAnalyzerPowerUp: + case kWSCAnalyzerPowerUpWithDart: + GameState.setWSCAnalyzerOn(true); + break; + case kWSCDropDartIntoAnalyzer: + setCurrentActivation(kActivationZoomedInToAnalyzer); + break; + case kWSCAnalyzeDart: + GameState.setWSCAnalyzedDart(true); + GameState.setScoringAnalyzedDart(); + break; + case kWSCZoomOutFromAnalyzer: + setCurrentActivation(kActivateHotSpotAlways); + GameState.setWSCAnalyzerOn(false); + GameState.setWSCDartInAnalyzer(false); + updateViewFrame(); + break; + case kMessagesMovedToOffice: + case kMessagesMovedToOfficeNoNitrogen: + _privateFlags.setFlag(kWSCPrivateLabMessagesOpenFlag, true); + GameState.setScoringPlayedWithMessages(); + break; + case kMessagesOff: + case kMessagesOffNoNitrogen: + _privateFlags.setFlag(kWSCPrivateLabMessagesOpenFlag, false); + if (_cachedZoomSpot) { + zoomTo(_cachedZoomSpot); + _cachedZoomSpot = 0; + } + break; + case kWSC02TurnOnMorphScreen: + setCurrentActivation(kActivationReadyForMorph); + break; + case kWSC02DropToMorphExperiment: + loopExtraSequence(kWSC02MorphLoop, kExtraCompletedFlag); + setCurrentActivation(kActivationMorphLooping); + break; + case kWSC02MorphLoop: + if (_privateFlags.getFlag(kWSCPrivateInterruptedMorphFlag)) + startExtraSequence(kWSC02MorphInterruption, kExtraCompletedFlag, kFilterNoInput); + else + scheduleNavCallBack(kExtraCompletedFlag); + break; + case kWSC02MorphInterruption: + setCurrentActivation(kActivationMorphInterrupted); + GameState.setScoringSawMorphExperiment(); + break; + case kWSC02TurnOffMorphScreen: + setCurrentActivation(kActivationMorphScreenOff); + GameState.setWSCSawMorph(true); + break; + case kW03NorthActivate: + if (GameState.getWSCAnalyzedDart() && !GameState.getWSCDesignedAntidote()) + startExtraSequence(kW03NorthGetData, kExtraCompletedFlag, kFilterNoInput); + else + setCurrentActivation(kActivateHotSpotAlways); + break; + case kW03NorthGetData: + setCurrentActivation(kActivationW03NorthReadyForInstructions); + break; + case kW03NorthInstructions: + setCurrentActivation(kActivationW03NorthSawInstructions); + break; + case kW03NorthPrepMolecule1: + setUpMoleculeGame(); + break; + case kW03NorthPrepMolecule2: + case kW03NorthPrepMolecule3: + nextMoleculeGameLevel(); + break; + case kW03NorthFinishSynthesis: + setCurrentActivation(kActivateHotSpotAlways); + _privateFlags.setFlag(kWSCPrivateInMoleculeGameFlag, false); + GameState.setWSCDesignedAntidote(true); + GameState.setScoringBuiltAntidote(); + break; + case kW03SouthCreateAntidote: + setCurrentActivation(kActivationSynthesizerLooping); + loopExtraSequence(kW03SouthAntidoteLoop); + break; + case kW03SouthDeactivate: + setCurrentActivation(kActivateHotSpotAlways); + break; + case kWSC07SouthMap: + case kWSC56SouthMap: + setCurrentActivation(kActivateHotSpotAlways); + GameState.setScoringSawWSCDirectory(); + break; + case kNerdAtTheDoor1: + GameState.setWSCSeenNerd(true); + break; + case kNerdAtTheDoor2: + die(kDeathArrestedInWSC); + break; + case kW61Brochure: + GameState.setScoringSawBrochure(); + loadAmbientLoops(); + break; + case kW61SouthSmartAlloysWithGun: + case kW61SouthSmartAlloysNoGun: + GameState.setScoringSawSinclairEntry1(); + break; + case kW61SouthMorphingWithGun: + case kW61SouthMorphingNoGun: + GameState.setScoringSawSinclairEntry2(); + break; + case kW61SouthTimeBendingWithGun: + case kW61SouthTimeBendingNoGun: + GameState.setScoringSawSinclairEntry3(); + break; + case kW61MessagesOn: + GameState.setWSCOfficeMessagesOpen(true); + setCurrentActivation(kActivationW61MessagesOn); + break; + case kW61MessagesOff: + GameState.setWSCOfficeMessagesOpen(false); + setCurrentActivation(kActivationW61MessagesOff); + if (_cachedZoomSpot) { + zoomTo(_cachedZoomSpot); + _cachedZoomSpot = 0; + } + break; + case kW61SouthScreenOnWithGun: + case kW61SouthScreenOnNoGun: + _privateFlags.setFlag(kWSCPrivateOfficeLogOpenFlag, true); + setCurrentActivation(kActivationW61SouthOn); + break; + case kW61SouthScreenOffWithGun: + case kW61SouthScreenOffNoGun: + _privateFlags.setFlag(kWSCPrivateOfficeLogOpenFlag, false); + setCurrentActivation(kActivationW61SouthOff); + if (_cachedZoomSpot) { + zoomTo(_cachedZoomSpot); + _cachedZoomSpot = 0; + } + break; + case kW62ZoomOutFromRobot: + // Handle action queue before starting new movie sequences. + Neighborhood::receiveNotification(notification, flags); + _energyDrainRate = g_energyMonitor->getEnergyDrainRate(); + g_energyMonitor->setEnergyDrainRate(0); + currentEnergy = g_energyMonitor->getCurrentEnergy(); + _vm->setEnergyDeathReason(kDeathHitByPlasma); + + if (GameState.getShieldOn()) + currentEnergy -= kPlasmaEnergyWithShield; + else + currentEnergy -= kPlasmaEnergyNoShield; + + if (currentEnergy <= 0) + startExtraSequence(kW62PlasmaDodgeDie, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kW62PlasmaDodgeSurvive, kExtraCompletedFlag, kFilterNoInput); + + scheduleEvent(kPlasmaImpactTime, kOneTickPerSecond, kTimerEventPlasmaHit); + break; + case kW62PlasmaDodgeDie: + g_energyMonitor->setEnergyValue(0); + break; + case kW62PlasmaDodgeSurvive: + if (GameState.getShieldOn()) { + g_shield->setItemState(kShieldNormal); + g_energyMonitor->drainEnergy(kPlasmaEnergyWithShield); + } else { + g_energyMonitor->drainEnergy(kPlasmaEnergyNoShield); + } + + g_energyMonitor->setEnergyDrainRate(_energyDrainRate); + g_AIArea->unlockAI(); + GameState.setScoringFinishedPlasmaDodge(); + GameState.setWSCDidPlasmaDodge(true); + restoreStriding(kWSC58, kSouth, kAltWSCNormal); + loadAmbientLoops(); + break; + case kW0ZSpottedByWomen: + die(kDeathArrestedInWSC); + break; + case kW17WestPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt17WestFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt17WestFlag, false); + break; + case kW21SouthPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt21SouthFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt21SouthFlag, true); + break; + case kW24SouthPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt24SouthFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt24SouthFlag, true); + break; + case kW34EastPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt34EastFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt34EastFlag, true); + break; + case kW36WestPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt36WestFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt36WestFlag, true); + break; + case kW38NorthPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt38NorthFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt38NorthFlag, true); + break; + case kW46SouthPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt46SouthFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt46SouthFlag, true); + break; + case kW49NorthPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt49NorthFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt49NorthFlag, false); + break; + case kW73WestPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt73WestFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt73WestFlag, false); + break; + case kW95RobotShoots: + case kW98RobotShoots: + die(kDeathShotOnCatwalk); + break; + case kW98MorphsToRobot: + if (_argonSprite) { + delete _argonSprite; _argonSprite = 0; + startExtraSequence(kW98RobotGassed, kExtraCompletedFlag, kFilterNoInput); + } else if (_privateFlags.getFlag(kWSCPrivateClickedCatwalkCableFlag)) { + startExtraSequence(kW98RobotShocked, kExtraCompletedFlag, kFilterNoInput); + } else { + startExtraSequence(kW98RobotShoots, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kW98RobotShocked: + GameState.setWSCCatwalkDark(true); + // Change the gun hot spot... + g_allHotspots.setHotspotRect(kW98StunGunSpotID, Common::Rect(181 + kNavAreaLeft, 99 + kNavAreaTop, + 372 + kNavAreaLeft, 149 + kNavAreaTop)); + setCurrentActivation(kActivationRobotDead); + GameState.setWSCRobotDead(true); + + // Video is not present + //g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/WSC/XN59WD", false, kWarningInterruption); + break; + case kW98RobotGassed: + item = (Item *)g_allItems.findItemByID(kArgonCanister); + _vm->addItemToInventory((InventoryItem *)item); + setCurrentActivation(kActivationRobotDead); + GameState.setWSCRobotDead(true); + + // Video is not present + //g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/WSC/XN59WD", false, kWarningInterruption); + break; + case kW98RobotHeadOpensLight: + case kW98RobotHeadOpensDark: + setCurrentActivation(kActivationWSCRobotHeadOpen); + _privateFlags.setFlag(kWSCPrivateRobotHeadOpenFlag, true); + break; + case kW98RobotHeadClosesDark: + case kW98RobotHeadClosesLight: + setCurrentActivation(kActivationRobotGone); + _privateFlags.setFlag(kWSCPrivateRobotHeadOpenFlag, false); + GameState.setWSCRobotGone(true); + break; + } + } + + Neighborhood::receiveNotification(notification, flags); + g_AIArea->checkMiddleArea(); +} + +void WSC::timerExpired(const uint32 event) { + switch (event) { + case kTimerEventPlasmaHit: + if (GameState.getShieldOn()) + g_shield->setItemState(kShieldPlasma); + break; + case kTimerEventPlayerGawkingAtRobot: + startExtraSequence(kW95RobotShoots, kExtraCompletedFlag, kFilterNoInput); + break; + case kTimerEventPlayerGawkingAtRobot2: + startExtraSequence(kW98MorphsToRobot, kExtraCompletedFlag, kFilterAllInput); + break; + } +} + +void WSC::setUpMoleculeGame() { + _privateFlags.setFlag(kWSCPrivateInMoleculeGameFlag, true); + setCurrentActivation(kActivationW03NorthInGame); + initOneMovie(&_moleculesMovie, "Images/World Science Center/Molecules.movie", + kWSCMoleculesMovieOrder, kMoleculesMovieLeft, kMoleculesMovieTop, true); + _moleculesMovie.redrawMovieWorld(); + _moleculeBin.initMoleculeBin(); + _moleculeGameLevel = 0; + nextMoleculeGameLevel(); +} + +void WSC::nextMoleculeGameLevel() { + _moleculeGameLevel++; + + for (byte i = 0; i < 6; ++i) + _levelArray[i] = i; + + _vm->shuffleArray((int32 *)_levelArray, 6); + _moleculeBin.setBinLayout(_levelArray); + startMoleculeGameLevel(); +} + +void WSC::startMoleculeGameLevel() { + _moleculeBin.resetBin(); + _numCorrect = 0; + _moleculesMovie.stop(); + _moleculesMovie.setFlags(0); + _moleculesMovie.setSegment(s_moleculeLoopTimes[0], s_moleculeLoopTimes[0] + kMoleculeLoopTime); + _moleculesMovie.setTime(s_moleculeLoopTimes[0]); + _moleculesMovie.setFlags(kLoopTimeBase); + _moleculesMovie.show(); + + switch (_moleculeGameLevel) { + case 1: + playSpotSoundSync(kWSCMolecule1In, kWSCMolecule1Out); + break; + case 2: + playSpotSoundSync(kWSCMolecule2In, kWSCMolecule2Out); + break; + case 3: + playSpotSoundSync(kWSCMolecule3In, kWSCMolecule3Out); + break; + } + + _moleculesMovie.start(); +} + +void WSC::moleculeGameClick(const HotSpotID id) { + uint32 molecule = id - kWSC03NorthMolecule1SpotID; + + _moleculeBin.highlightMolecule(molecule); + _moleculeBin.selectMolecule(molecule); + + if (molecule == _levelArray[_numCorrect]) { + playSpotSoundSync(kWSCClick2In, kWSCClick2Out); + _numCorrect++; + _moleculesMovie.stop(); + _moleculesMovie.setFlags(0); + + TimeValue time = _moleculesMovie.getTime(); + _moleculesMovie.setSegment(s_moleculeLoopTimes[_numCorrect], s_moleculeLoopTimes[_numCorrect] + kMoleculeLoopTime); + _moleculesMovie.setTime(s_moleculeLoopTimes[_numCorrect] + time - s_moleculeLoopTimes[_numCorrect - 1]); + + if (_numCorrect == 6) { + _moleculesMovie.start(); + + while (_moleculesMovie.isRunning()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + _moleculesMovie.stop(); + _moleculesMovie.hide(); + + switch (_moleculeGameLevel) { + case 1: + startExtraSequence(kW03NorthPrepMolecule2, kExtraCompletedFlag, kFilterNoInput); + break; + case 2: + startExtraSequence(kW03NorthPrepMolecule3, kExtraCompletedFlag, kFilterNoInput); + break; + case 3: + _moleculesMovie.releaseMovie(); + _moleculeBin.cleanUpMoleculeBin(); + requestExtraSequence(kW03NorthFinishSynthesis, kExtraCompletedFlag, kFilterNoInput); + break; + } + } else { + _moleculesMovie.setFlags(kLoopTimeBase); + _moleculesMovie.start(); + } + } else { + // FAIL + playSpotSoundSync(kWSCClick3In, kWSCClick3Out); + + _moleculesMovie.stop(); + _moleculesMovie.setFlags(0); + _moleculesMovie.start(); + + while (_moleculesMovie.isRunning()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + _moleculesMovie.stop(); + _moleculesMovie.setFlags(0); + _moleculesMovie.setSegment(s_moleculeFailTimes[_numCorrect], s_moleculeFailTimes[_numCorrect] + kMoleculeFailTime); + _moleculesMovie.setTime(s_moleculeFailTimes[_numCorrect]); + _moleculesMovie.start(); + + + while (_moleculesMovie.isRunning()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + _moleculesMovie.stop(); + startMoleculeGameLevel(); + } +} + +void WSC::activateOneHotspot(HotspotInfoTable::Entry &entry, Hotspot *hotspot) { + Neighborhood::activateOneHotspot(entry, hotspot); + + Item *argonCanister; + + switch (hotspot->getObjectID()) { + case kWSCTurnOnAnalyzerSpotID: + if (GameState.getWSCAnalyzerOn()) + hotspot->setInactive(); + break; + case kWSC02SouthTakeArgonSpotID: + if (!GameState.getWSCSawMorph() || GameState.isTakenItemID(kArgonCanister)) + hotspot->setInactive(); + break; + case kWSC02ActivateMorphScreenSpotID: + if (GameState.getWSCSawMorph()) + hotspot->setInactive(); + break; + case kWSC03NorthMolecule1SpotID: + case kWSC03NorthMolecule2SpotID: + case kWSC03NorthMolecule3SpotID: + case kWSC03NorthMolecule4SpotID: + case kWSC03NorthMolecule5SpotID: + case kWSC03NorthMolecule6SpotID: + if (_moleculeBin.isMoleculeHighlighted(hotspot->getObjectID() - kWSC03NorthMolecule1SpotID)) + hotspot->setInactive(); + break; + case kWSC03SouthPickUpAntidoteSpotID: + if (getCurrentActivation() == kActivationSynthesizerLooping) + hotspot->setActive(); + break; + case kW98DropArgonSpotID: + argonCanister = g_allItems.findItemByID(kArgonCanister); + if (argonCanister->getItemState() != kArgonFull) + hotspot->setInactive(); + break; + } +} + +void WSC::activateHotspots() { + Neighborhood::activateHotspots(); + + if (GameState.getCurrentRoomAndView() == MakeRoomView(kWSC98, kWest) && _privateFlags.getFlag(kWSCPrivateRobotHeadOpenFlag)) { + if (_privateFlags.getFlag(kWSCPrivateGotRetScanChipFlag)) + g_allHotspots.deactivateOneHotspot(kW98RetinalChipSpotID); + else + g_allHotspots.activateOneHotspot(kW98RetinalChipSpotID); + + if (_privateFlags.getFlag(kWSCPrivateGotMapChipFlag)) + g_allHotspots.deactivateOneHotspot(kW98MapChipSpotID); + else + g_allHotspots.activateOneHotspot(kW98MapChipSpotID); + + if (_privateFlags.getFlag(kWSCPrivateGotOpticalChipFlag)) + g_allHotspots.deactivateOneHotspot(kW98OpticalChipSpotID); + else + g_allHotspots.activateOneHotspot(kW98OpticalChipSpotID); + } +} + +void WSC::clickInHotspot(const Input &input, const Hotspot *clickedSpot) { + if (JMPPPInput::isEasterEggModifierInput(input)) + GameState.setEasterEgg(true); + + if (clickedSpot) { + switch (clickedSpot->getObjectID()) { + case kWSCAnalyzerScreenSpotID: + requestExtraSequence(kWSCAnalyzeDart, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kWSCZoomOutFromAnalyzer, kExtraCompletedFlag, kFilterNoInput); + break; + case kWSC02SouthPlayMessagesSpotID: + if (GameState.isTakenItemID(kNitrogenCanister)) { + if (_lastExtra == (uint32)kMessagesMovedToOfficeNoNitrogen) + startExtraSequence(kMessagesOffNoNitrogen, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMessagesMovedToOfficeNoNitrogen, kExtraCompletedFlag, kFilterNoInput); + } else { + if (_lastExtra == (uint32)kMessagesMovedToOffice) + startExtraSequence(kMessagesOff, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMessagesMovedToOffice, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kWSC02SouthInterruptMorphSpotID: + _privateFlags.setFlag(kWSCPrivateInterruptedMorphFlag, true); + break; + case kWSC02SouthMorphFinishedSpotID: + requestExtraSequence(kWSC02MorphFinished, 0, kFilterNoInput); + requestExtraSequence(kWSC02TurnOffMorphScreen, kExtraCompletedFlag, kFilterNoInput); + break; + case kWSC03NorthMolecule1SpotID: + case kWSC03NorthMolecule2SpotID: + case kWSC03NorthMolecule3SpotID: + case kWSC03NorthMolecule4SpotID: + case kWSC03NorthMolecule5SpotID: + case kWSC03NorthMolecule6SpotID: + moleculeGameClick(clickedSpot->getObjectID()); + break; + case kW98GrabCableSpotID: + if (isEventTimerRunning()) { + cancelEvent(); + startExtraSequence(kW98MorphsToRobot, kExtraCompletedFlag, kFilterAllInput); + } + + _privateFlags.setFlag(kWSCPrivateClickedCatwalkCableFlag, true); + break; + default: + Neighborhood::clickInHotspot(input, clickedSpot); + break; + } + } else { + Neighborhood::clickInHotspot(input, clickedSpot); + } + + GameState.setEasterEgg(false); +} + +void WSC::dropItemIntoRoom(Item *item, Hotspot *dropSpot) { + CoordType h, v; + + switch (item->getObjectID()) { + case kPoisonDart: + Neighborhood::dropItemIntoRoom(item, dropSpot); + GameState.setWSCDartInAnalyzer(true); + if (dropSpot && dropSpot->getObjectID() == kWSCDropDartSpotID) { + if (!GameState.getWSCAnalyzerOn()) + requestExtraSequence(kWSCAnalyzerPowerUpWithDart, kExtraCompletedFlag, kFilterNoInput); + + requestExtraSequence(kWSCDropDartIntoAnalyzer, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kAntidote: + _privateFlags.setFlag(kWSCDraggingAntidoteFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + loopExtraSequence(kW03SouthAntidoteLoop); + break; + case kSinclairKey: + Neighborhood::dropItemIntoRoom(item, dropSpot); + _privateFlags.setFlag(kWSCPrivateSinclairOfficeOpenFlag, true); + openDoor(); + break; + case kCrowbar: + Neighborhood::dropItemIntoRoom(item, dropSpot); + _privateFlags.setFlag(kWSCPrivate58SouthOpenFlag, true); + openDoor(); + break; + case kMachineGun: + setCurrentAlternate(kAltWSCNormal); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + case kArgonCanister: + item->setItemState(kArgonEmpty); + _argonSprite = item->getDragSprite(0); + _argonSprite->setCurrentFrameIndex(1); + _argonSprite->setDisplayOrder(kDragSpriteOrder); + dropSpot->getCenter(h, v); + _argonSprite->centerElementAt(h, v); + _argonSprite->startDisplaying(); + _argonSprite->show(); + + if (isEventTimerRunning()) { + cancelEvent(); + startExtraSequence(kW98MorphsToRobot, kExtraCompletedFlag, kFilterAllInput); + } + break; + case kRetinalScanBiochip: + _privateFlags.setFlag(kWSCPrivateGotRetScanChipFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + case kMapBiochip: + _privateFlags.setFlag(kWSCPrivateGotMapChipFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + case kOpticalBiochip: + _privateFlags.setFlag(kWSCPrivateGotOpticalChipFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + default: + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + } +} + +void WSC::takeItemFromRoom(Item *item) { + switch (item->getObjectID()) { + case kAntidote: + _privateFlags.setFlag(kWSCDraggingAntidoteFlag, true); + Neighborhood::takeItemFromRoom(item); + break; + case kMachineGun: + setCurrentAlternate(kAltWSCTookMachineGun); + Neighborhood::takeItemFromRoom(item); + break; + case kRetinalScanBiochip: + _privateFlags.setFlag(kWSCPrivateGotRetScanChipFlag, true); + Neighborhood::takeItemFromRoom(item); + break; + case kMapBiochip: + _privateFlags.setFlag(kWSCPrivateGotMapChipFlag, true); + Neighborhood::takeItemFromRoom(item); + break; + case kOpticalBiochip: + _privateFlags.setFlag(kWSCPrivateGotOpticalChipFlag, true); + Neighborhood::takeItemFromRoom(item); + break; + default: + Neighborhood::takeItemFromRoom(item); + break; + } +} + +Hotspot *WSC::getItemScreenSpot(Item *item, DisplayElement *element) { + HotSpotID destSpotID; + + switch (item->getObjectID()) { + case kNitrogenCanister: + destSpotID = kWSC02SouthTakeNitrogenSpotID; + break; + case kArgonPickup: + destSpotID = kWSC02SouthTakeArgonSpotID; + break; + case kAntidote: + destSpotID = kWSC03SouthPickUpAntidoteSpotID; + break; + case kMachineGun: + destSpotID = kW61SouthMachineGunSpotID; + break; + case kRetinalScanBiochip: + destSpotID = kW98RetinalChipSpotID; + break; + case kMapBiochip: + destSpotID = kW98MapChipSpotID; + break; + case kOpticalBiochip: + destSpotID = kW98OpticalChipSpotID; + break; + default: + destSpotID = kNoHotSpotID; + break; + } + + if (destSpotID == kNoHotSpotID) + return Neighborhood::getItemScreenSpot(item, element); + + return g_allHotspots.findHotspotByID(destSpotID); +} + +void WSC::pickedUpItem(Item *item) { + switch (item->getObjectID()) { + case kAntidote: + if (!GameState.getWSCPickedUpAntidote()) { + GameState.setWSCPoisoned(false); + GameState.setWSCRemovedDart(false); + GameState.setWSCPickedUpAntidote(true); + _privateFlags.setFlag(kWSCDraggingAntidoteFlag, false); + playSpotSoundSync(kDrinkAntidoteIn, kDrinkAntidoteOut); + setUpPoison(); + startExtraSequence(kW03SouthDeactivate, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kArgonPickup: + _vm->removeItemFromInventory((InventoryItem *)item); + item = (Item *)g_allItems.findItemByID(kArgonCanister); + _vm->addItemToInventory((InventoryItem *)item); + item = (Item *)g_allItems.findItemByID(kSinclairKey); + _vm->addItemToInventory((InventoryItem *)item); + g_allHotspots.setHotspotRect(kWSC02SouthMorphOutSpotID, + Common::Rect(kNavAreaLeft, kNavAreaTop, 512 + kNavAreaLeft, 256 + kNavAreaTop)); + break; + case kArgonCanister: + GameState.setScoringGotArgonCanister(); + break; + case kSinclairKey: + GameState.setScoringGotSinclairKey(); + break; + case kNitrogenCanister: + GameState.setScoringGotNitrogenCanister(); + break; + case kRetinalScanBiochip: + if (_privateFlags.getFlag(kWSCPrivateGotMapChipFlag) && _privateFlags.getFlag(kWSCPrivateGotOpticalChipFlag)) { + if (GameState.getWSCCatwalkDark()) + startExtraSequence(kW98RobotHeadClosesDark, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kW98RobotHeadClosesLight, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kMapBiochip: + if (_privateFlags.getFlag(kWSCPrivateGotRetScanChipFlag) && _privateFlags.getFlag(kWSCPrivateGotOpticalChipFlag)) { + if (GameState.getWSCCatwalkDark()) + startExtraSequence(kW98RobotHeadClosesDark, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kW98RobotHeadClosesLight, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kOpticalBiochip: + g_opticalChip->addMercury(); + GameState.setScoringGotWSCOpMemChip(); + if (_privateFlags.getFlag(kWSCPrivateGotRetScanChipFlag) && _privateFlags.getFlag(kWSCPrivateGotMapChipFlag)) { + if (GameState.getWSCCatwalkDark()) + startExtraSequence(kW98RobotHeadClosesDark, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kW98RobotHeadClosesLight, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kStunGun: + GameState.setWSCFinished(true); + + if (!GameState.getWSCCatwalkDark()) + GameState.setScoringWSCGandhi(); + + recallToTSASuccess(); + break; + } +} + +void WSC::checkPeopleCrossing() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC17, kWest): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt17WestFlag)) + startExtraSequence(kW17WestPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC21, kSouth): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt21SouthFlag)) + startExtraSequence(kW21SouthPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC24, kSouth): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt24SouthFlag)) + startExtraSequence(kW24SouthPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC34, kEast): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt34EastFlag)) + startExtraSequence(kW34EastPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC36, kWest): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt36WestFlag)) + startExtraSequence(kW36WestPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC38, kNorth): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt38NorthFlag)) + startExtraSequence(kW38NorthPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC46, kSouth): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt46SouthFlag)) + startExtraSequence(kW46SouthPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC49, kNorth): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt49NorthFlag)) + startExtraSequence(kW49NorthPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC73, kWest): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt73WestFlag)) + startExtraSequence(kW73WestPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + default: + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt21SouthFlag) && _vm->getRandomNumber(2) == 0) { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt21SouthFlag, true); + forceStridingStop(kWSC18, kSouth, kAltWSCNormal); + } else { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt21SouthFlag, false); + restoreStriding(kWSC18, kSouth, kAltWSCNormal); + } + + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt19NorthFlag) && _vm->getRandomNumber(2) == 0) { + forceStridingStop(kWSC22, kNorth, kAltWSCNormal); + } else { + restoreStriding(kWSC22, kNorth, kAltWSCNormal); + } + + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt24SouthFlag) && _vm->getRandomNumber(2) == 0) { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt24SouthFlag, true); + forceStridingStop(kWSC22, kSouth, kAltWSCNormal); + } else { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt24SouthFlag, false); + restoreStriding(kWSC22, kSouth, kAltWSCNormal); + } + + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt34EastFlag) && _vm->getRandomNumber(2) == 0) { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt34EastFlag, true); + forceStridingStop(kWSC28, kEast, kAltWSCNormal); + } else { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt34EastFlag, false); + restoreStriding(kWSC28, kEast, kAltWSCNormal); + } + + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt36WestFlag) && _vm->getRandomNumber(2) == 0) { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt36WestFlag, true); + forceStridingStop(kWSC40, kWest, kAltWSCNormal); + } else { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt36WestFlag, false); + restoreStriding(kWSC40, kWest, kAltWSCNormal); + } + + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt38NorthFlag) && _vm->getRandomNumber(2) == 0) { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt38NorthFlag, true); + forceStridingStop(kWSC42, kNorth, kAltWSCNormal); + } else { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt38NorthFlag, false); + restoreStriding(kWSC42, kNorth, kAltWSCNormal); + } + + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt46SouthFlag) && _vm->getRandomNumber(2) == 0) { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt46SouthFlag, true); + forceStridingStop(kWSC44, kSouth, kAltWSCNormal); + } else { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt46SouthFlag, false); + restoreStriding(kWSC44, kSouth, kAltWSCNormal); + } + break; + } +} + +void WSC::setUpPoison() { + if (GameState.getWSCPoisoned()) { + if (GameState.getWSCRemovedDart()) { + if (g_energyMonitor->getEnergyDrainRate() != kWSCPoisonEnergyDrainNoDart) { + g_energyMonitor->setEnergyDrainRate(kWSCPoisonEnergyDrainNoDart); + _vm->setEnergyDeathReason(kDeathDidntStopPoison); + } + } else { + if (g_energyMonitor->getEnergyDrainRate() != kWSCPoisonEnergyDrainWithDart) { + g_energyMonitor->setEnergyDrainRate(kWSCPoisonEnergyDrainWithDart); + _vm->setEnergyDeathReason(kDeathDidntStopPoison); + } + } + } else if (g_energyMonitor->getEnergyDrainRate() != kEnergyDrainNormal) { + g_energyMonitor->setEnergyDrainRate(kEnergyDrainNormal); + _vm->resetEnergyDeathReason(); + } +} + +bool WSC::inSynthesizerGame() { + return _moleculesMovie.isMovieValid(); +} + +bool WSC::canSolve() { + return (inSynthesizerGame() || (GameState.getCurrentRoom() == kWSC98 && !GameState.getWSCRobotDead())); +} + +void WSC::doSolve() { + if (inSynthesizerGame()) { + _moleculesMovie.releaseMovie(); + _moleculeBin.cleanUpMoleculeBin(); + requestExtraSequence(kW03NorthFinishSynthesis, kExtraCompletedFlag, kFilterNoInput); + } else if (GameState.getCurrentRoom() == kWSC98 && !GameState.getWSCRobotDead()) { + cancelEvent(); + startExtraSequence(kW98RobotShocked, kExtraCompletedFlag, kFilterNoInput); + } +} + +Common::String WSC::getNavMovieName() { + return "Images/World Science Center/WSC.movie"; +} + +Common::String WSC::getSoundSpotsName() { + return "Sounds/World Science Center/WSC Spots"; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/wsc/wsc.h b/engines/pegasus/neighborhood/wsc/wsc.h new file mode 100644 index 0000000000..e2e931ecdc --- /dev/null +++ b/engines/pegasus/neighborhood/wsc/wsc.h @@ -0,0 +1,166 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_WSC_WSC_H +#define PEGASUS_NEIGHBORHOOD_WSC_WSC_H + +#include "pegasus/neighborhood/neighborhood.h" +#include "pegasus/neighborhood/wsc/moleculebin.h" + +namespace Pegasus { + +static const DisplayOrder kWSCMoleculeBinOrder = kMonitorLayer; +static const DisplayOrder kWSCMoleculesMovieOrder = kWSCMoleculeBinOrder + 1; + +static const RoomID kWSC01 = 0; +static const RoomID kWSC02Morph = 2; +static const RoomID kWSC02Messages = 3; +static const RoomID kWSC62 = 62; + +class WSC : public Neighborhood { +public: + WSC(InputHandler *, PegasusEngine *); + virtual ~WSC() {} + + void flushGameState(); + + virtual uint16 getDateResID() const; + + bool okayToJump(); + + void checkContinuePoint(const RoomID, const DirectionConstant); + + bool inSynthesizerGame(); + + bool canSolve(); + void doSolve(); + + virtual void prepareForAIHint(const Common::String &); + virtual void cleanUpAfterAIHint(const Common::String &); + + void init(); + void start(); + +protected: + enum { + kWSCDraggingAntidoteFlag, + + kWSCPrivateLabMessagesOpenFlag, + kWSCPrivateInterruptedMorphFlag, + kWSCPrivateInMoleculeGameFlag, + kWSCPrivateSinclairOfficeOpenFlag, + kWSCPrivateOfficeLogOpenFlag, + kWSCPrivate58SouthOpenFlag, + kWSCPrivateClickedCatwalkCableFlag, + kWSCPrivateRobotHeadOpenFlag, + + kWSCPrivateSeenPeopleAt17WestFlag, + kWSCPrivateSeenPeopleAt19NorthFlag, + kWSCPrivateSeenPeopleAt21SouthFlag, + kWSCPrivateSeenPeopleAt24SouthFlag, + kWSCPrivateSeenPeopleAt34EastFlag, + kWSCPrivateSeenPeopleAt36WestFlag, + kWSCPrivateSeenPeopleAt38NorthFlag, + kWSCPrivateSeenPeopleAt46SouthFlag, + kWSCPrivateSeenPeopleAt49NorthFlag, + kWSCPrivateSeenPeopleAt73WestFlag, + + kWSCPrivateNeedPeopleAt17WestFlag, + kWSCPrivateNeedPeopleAt21SouthFlag, + kWSCPrivateNeedPeopleAt24SouthFlag, + kWSCPrivateNeedPeopleAt34EastFlag, + kWSCPrivateNeedPeopleAt36WestFlag, + kWSCPrivateNeedPeopleAt38NorthFlag, + kWSCPrivateNeedPeopleAt46SouthFlag, + kWSCPrivateNeedPeopleAt49NorthFlag, + kWSCPrivateNeedPeopleAt73WestFlag, + + kWSCPrivateGotRetScanChipFlag, + kWSCPrivateGotMapChipFlag, + kWSCPrivateGotOpticalChipFlag, + + kNumWSCPrivateFlags + }; + + void arriveAt(const RoomID, const DirectionConstant); + void turnTo(const DirectionConstant); + void receiveNotification(Notification *, const NotificationFlags); + void dropItemIntoRoom(Item *, Hotspot *); + void clickInHotspot(const Input &, const Hotspot *); + TimeValue getViewTime(const RoomID, const DirectionConstant); + void getZoomEntry(const HotSpotID, ZoomTable::Entry &); + CanMoveForwardReason canMoveForward(ExitTable::Entry &entry); + void cantMoveThatWay(CanMoveForwardReason reason); + CanTurnReason canTurn(TurnDirection turn, DirectionConstant &nextDir); + void zoomTo(const Hotspot *hotspot); + void activateOneHotspot(HotspotInfoTable::Entry &, Hotspot *); + void setUpMoleculeGame(); + void nextMoleculeGameLevel(); + void startMoleculeGameLevel(); + void moleculeGameClick(const HotSpotID); + void loadAmbientLoops(); + CanOpenDoorReason canOpenDoor(DoorTable::Entry &); + void cantOpenDoor(CanOpenDoorReason); + void pickedUpItem(Item *); + void doorOpened(); + void startExtraSequence(const ExtraID, const NotificationFlags, const InputBits); + void getExtraEntry(const uint32, ExtraTable::Entry &); + void takeItemFromRoom(Item *item); + void checkPeopleCrossing(); + void turnLeft(); + void turnRight(); + void moveForward(); + Hotspot *getItemScreenSpot(Item *, DisplayElement *); + int16 getStaticCompassAngle(const RoomID, const DirectionConstant); + void getExitCompassMove(const ExitTable::Entry &exitEntry, FaderMoveSpec &compassMove); + void getExtraCompassMove(const ExtraTable::Entry &entry, FaderMoveSpec &compassMove); + void bumpIntoWall(); + void activateHotspots(); + void setUpAIRules(); + Common::String getBriefingMovie(); + Common::String getEnvScanMovie(); + uint getNumHints(); + Common::String getHintMovie(uint); + void closeDoorOffScreen(const RoomID, const DirectionConstant); + void setUpPoison(); + void findSpotEntry(const RoomID, const DirectionConstant, SpotFlags, SpotTable::Entry &); + void timerExpired(const uint32); + + Common::String getSoundSpotsName(); + Common::String getNavMovieName(); + + FlagsArray<byte, kNumWSCPrivateFlags> _privateFlags; + const Hotspot *_cachedZoomSpot; + MoleculeBin _moleculeBin; + int32 _moleculeGameLevel, _numCorrect; + Movie _moleculesMovie; + uint32 _levelArray[6]; + Common::Rational _energyDrainRate; + Sprite *_argonSprite; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/zoom.cpp b/engines/pegasus/neighborhood/zoom.cpp new file mode 100644 index 0000000000..478ec6e493 --- /dev/null +++ b/engines/pegasus/neighborhood/zoom.cpp @@ -0,0 +1,74 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/zoom.h" + +namespace Pegasus { + +void ZoomTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].hotspot = stream->readUint16BE(); + _entries[i].movieStart = stream->readUint32BE(); + _entries[i].movieEnd = stream->readUint32BE(); + _entries[i].room = stream->readUint16BE(); + _entries[i].direction = stream->readByte(); + debug(0, "Zoom[%d]: %d %d %d %d %d", i, _entries[i].hotspot, _entries[i].movieStart, + _entries[i].movieEnd, _entries[i].room, _entries[i].direction); + stream->readByte(); // alignment + } +} + +void ZoomTable::clear() { + _entries.clear(); +} + +ZoomTable::Entry::Entry() { + clear(); +} + +void ZoomTable::Entry::clear() { + hotspot = kNoHotSpotID; + movieStart = 0xffffffff; + movieEnd = 0xffffffff; + room = kNoRoomID; + direction = kNoDirection; +} + +ZoomTable::Entry ZoomTable::findEntry(HotSpotID hotspot) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].hotspot == hotspot) + return _entries[i]; + + return Entry(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/zoom.h b/engines/pegasus/neighborhood/zoom.h new file mode 100644 index 0000000000..8bcf8974f8 --- /dev/null +++ b/engines/pegasus/neighborhood/zoom.h @@ -0,0 +1,70 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_ZOOM_H +#define PEGASUS_NEIGHBORHOOD_ZOOM_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +class ZoomTable { +public: + ZoomTable() {} + ~ZoomTable() {} + + static uint32 getResTag() { return MKTAG('Z', 'o', 'o', 'm'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry(); + void clear(); + bool isEmpty() { return movieStart == 0xffffffff; } + + HotSpotID hotspot; + TimeValue movieStart; + TimeValue movieEnd; + RoomID room; + DirectionConstant direction; + }; + + Entry findEntry(HotSpotID hotspot); + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/notification.cpp b/engines/pegasus/notification.cpp new file mode 100644 index 0000000000..4179afa70d --- /dev/null +++ b/engines/pegasus/notification.cpp @@ -0,0 +1,149 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/constants.h" +#include "pegasus/notification.h" + +namespace Pegasus { + +typedef ReceiverList::iterator ReceiverIterator; + +Notification::Notification(const NotificationID id, NotificationManager *owner) : IDObject(id) { + _owner = owner; + _currentFlags = kNoNotificationFlags; + if (_owner) + _owner->addNotification(this); +} + +Notification::~Notification() { + for (uint i = 0; i < _receivers.size(); i++) + _receivers[i].receiver->newNotification(NULL); + + if (_owner) + _owner->removeNotification(this); +} + +// Selectively set or clear notificiation bits. +// Wherever mask is 0, leave existing bits untouched. +// Wherever mask is 1, set bit equivalent to flags. +void Notification::notifyMe(NotificationReceiver *receiver, NotificationFlags flags, NotificationFlags mask) { + for (uint i = 0; i < _receivers.size(); i++) { + if (_receivers[i].receiver == receiver) { + _receivers[i].mask = (_receivers[i].mask & ~mask) | (flags & mask); + receiver->newNotification(this); + return; + } + } + + ReceiverEntry newEntry; + newEntry.receiver = receiver; + newEntry.mask = flags; + _receivers.push_back(newEntry); + + receiver->newNotification(this); +} + +void Notification::cancelNotification(NotificationReceiver *receiver) { + for (uint i = 0; i < _receivers.size(); i++) { + if (_receivers[i].receiver == receiver) { + _receivers.remove_at(i); + i--; + } + } +} + +void Notification::setNotificationFlags(NotificationFlags flags, NotificationFlags mask) { + _currentFlags = (_currentFlags & ~mask) | flags; +} + +void Notification::checkReceivers() { + NotificationFlags currentFlags = _currentFlags; + _currentFlags = kNoNotificationFlags; + + for (uint i = 0; i < _receivers.size(); i++) + if (_receivers[i].mask & currentFlags) + _receivers[i].receiver->receiveNotification(this, currentFlags); +} + +// Receiver entries are equal if their receivers are equal. + +int operator==(const ReceiverEntry &entry1, const ReceiverEntry &entry2) { + return entry1.receiver == entry2.receiver; +} + +int operator!=(const ReceiverEntry &entry1, const ReceiverEntry &entry2) { + return entry1.receiver != entry2.receiver; +} + +NotificationReceiver::NotificationReceiver() { + _notification = NULL; +} + +NotificationReceiver::~NotificationReceiver() { + if (_notification) + _notification->cancelNotification(this); +} + +void NotificationReceiver::receiveNotification(Notification *, const NotificationFlags) { +} + +void NotificationReceiver::newNotification(Notification *notification) { + _notification = notification; +} + +typedef NotificationList::iterator NotificationIterator; + +NotificationManager::NotificationManager() { +} + +NotificationManager::~NotificationManager() { + detachNotifications(); +} + +void NotificationManager::addNotification(Notification *notification) { + _notifications.push_back(notification); +} + +void NotificationManager::removeNotification(Notification *notification) { + for (NotificationIterator it = _notifications.begin(); it != _notifications.end();) { + if ((*it) == notification) + it = _notifications.erase(it); + else + it++; + } +} + +void NotificationManager::detachNotifications() { + for (NotificationIterator it = _notifications.begin(); it != _notifications.end(); it++) + (*it)->_owner = 0; +} + +void NotificationManager::checkNotifications() { + for (NotificationIterator it = _notifications.begin(); it != _notifications.end(); it++) + if ((*it)->_currentFlags != kNoNotificationFlags) + (*it)->checkReceivers(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/notification.h b/engines/pegasus/notification.h new file mode 100644 index 0000000000..e795a4b375 --- /dev/null +++ b/engines/pegasus/notification.h @@ -0,0 +1,123 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_NOTIFICATION_H +#define PEGASUS_NOTIFICATION_H + +#include "common/array.h" +#include "common/list.h" + +#include "pegasus/types.h" +#include "pegasus/util.h" + +namespace Pegasus { + +class NotificationManager; +class NotificationReceiver; + +struct ReceiverEntry { + NotificationReceiver *receiver; + NotificationFlags mask; +}; + +int operator==(const ReceiverEntry &entry1, const ReceiverEntry &entry2); +int operator!=(const ReceiverEntry &entry1, const ReceiverEntry &entry2); + +typedef Common::Array<ReceiverEntry> ReceiverList; + +/* + A notification can have 32 flags associated with it, which can be user-defined. +*/ + +class Notification : public IDObject { +friend class NotificationManager; + +public: + Notification(const NotificationID id, NotificationManager *owner); + virtual ~Notification(); + + // notifyMe will have this receiver notified when any of the specified notification + // flags are set. + // If there is already a notification set for this receiver, notifyMe does a bitwise + // OR with the receiver's current notification flags. + + // Can selectively set or clear notification bits by using the flags and mask argument. + + void notifyMe(NotificationReceiver*, NotificationFlags flags, NotificationFlags mask); + void cancelNotification(NotificationReceiver *receiver); + + void setNotificationFlags(NotificationFlags flags, NotificationFlags mask); + NotificationFlags getNotificationFlags() { return _currentFlags; } + + void clearNotificationFlags() { setNotificationFlags(0, ~(NotificationFlags)0); } + +protected: + void checkReceivers(); + + NotificationManager *_owner; + ReceiverList _receivers; + NotificationFlags _currentFlags; +}; + +class NotificationReceiver { +friend class Notification; + +public: + NotificationReceiver(); + virtual ~NotificationReceiver(); + +protected: + // receiveNotification is called automatically whenever a notification that this + // receiver depends on has its flags set + + virtual void receiveNotification(Notification *, const NotificationFlags); + virtual void newNotification(Notification *notification); + +private: + Notification *_notification; +}; + +typedef Common::List<Notification *> NotificationList; + +class NotificationManager : public NotificationReceiver { +friend class Notification; + +public: + NotificationManager(); + virtual ~NotificationManager(); + + void checkNotifications(); + +protected: + void addNotification(Notification *notification); + void removeNotification(Notification *notification); + void detachNotifications(); + + NotificationList _notifications; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/pegasus.cpp b/engines/pegasus/pegasus.cpp new file mode 100644 index 0000000000..43ffc4c460 --- /dev/null +++ b/engines/pegasus/pegasus.cpp @@ -0,0 +1,2292 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/config-manager.h" +#include "common/error.h" +#include "common/events.h" +#include "common/fs.h" +#include "common/file.h" +#include "common/memstream.h" +#include "common/savefile.h" +#include "common/textconsole.h" +#include "common/translation.h" +#include "common/random.h" +#include "base/plugins.h" +#include "base/version.h" +#include "gui/saveload.h" +#include "video/qt_decoder.h" + +#include "pegasus/console.h" +#include "pegasus/cursor.h" +#include "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/interface.h" +#include "pegasus/menu.h" +#include "pegasus/movie.h" +#include "pegasus/pegasus.h" +#include "pegasus/timers.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/itemlist.h" +#include "pegasus/items/biochips/aichip.h" +#include "pegasus/items/biochips/biochipitem.h" +#include "pegasus/items/biochips/mapchip.h" +#include "pegasus/items/biochips/opticalchip.h" +#include "pegasus/items/biochips/pegasuschip.h" +#include "pegasus/items/biochips/retscanchip.h" +#include "pegasus/items/biochips/shieldchip.h" +#include "pegasus/items/inventory/airmask.h" +#include "pegasus/items/inventory/gascanister.h" +#include "pegasus/items/inventory/inventoryitem.h" +#include "pegasus/items/inventory/keycard.h" +#include "pegasus/neighborhood/neighborhood.h" +#include "pegasus/neighborhood/caldoria/caldoria.h" +#include "pegasus/neighborhood/mars/mars.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/norad/alpha/noradalpha.h" +#include "pegasus/neighborhood/norad/delta/noraddelta.h" +#include "pegasus/neighborhood/prehistoric/prehistoric.h" +#include "pegasus/neighborhood/tsa/fulltsa.h" +#include "pegasus/neighborhood/tsa/tinytsa.h" +#include "pegasus/neighborhood/wsc/wsc.h" + +namespace Pegasus { + +PegasusEngine::PegasusEngine(OSystem *syst, const PegasusGameDescription *gamedesc) : Engine(syst), InputHandler(0), _gameDescription(gamedesc), + _shellNotification(kJMPDCShellNotificationID, this), _returnHotspot(kInfoReturnSpotID), _itemDragger(this), _bigInfoMovie(kNoDisplayElement), + _smallInfoMovie(kNoDisplayElement) { + _continuePoint = 0; + _saveAllowed = _loadAllowed = true; + _saveRequested = _loadRequested = false; + _gameMenu = 0; + _deathReason = kDeathStranded; + _neighborhood = 0; + _FXLevel = 0x80; + _ambientLevel = 0x80; + _gameMode = kNoMode; + _switchModesSync = false; + _draggingItem = 0; + _dragType = kDragNoDrag; + _idlerHead = 0; + _currentCD = 1; + _introTimer = 0; + _aiSaveStream = 0; +} + +PegasusEngine::~PegasusEngine() { + delete _resFork; + delete _console; + delete _cursor; + delete _continuePoint; + delete _gameMenu; + delete _neighborhood; + delete _rnd; + delete _introTimer; + delete _aiSaveStream; + InputDeviceManager::destroy(); + + // NOTE: This must be deleted last! + delete _gfx; +} + +void introTimerExpiredFunction(FunctionPtr *, void *) { + ((PegasusEngine *)g_engine)->introTimerExpired(); +} + +Common::Error PegasusEngine::run() { + _console = new PegasusConsole(this); + _gfx = new GraphicsManager(this); + _resFork = new Common::MacResManager(); + _cursor = new Cursor(); + _rnd = new Common::RandomSource("Pegasus"); + + if (!_resFork->open("JMP PP Resources") || !_resFork->hasResFork()) + error("Could not load JMP PP Resources"); + + // Initialize items + createItems(); + + // Initialize cursors + _cursor->addCursorFrames(0x80); // Main + _cursor->addCursorFrames(900); // Mars Shuttle + + // Initialize the item dragger bounds + _itemDragger.setHighlightBounds(); + + if (!isDemo() && !detectOpeningClosingDirectory()) { + Common::String message = "Missing intro directory. "; + + // Give Mac OS X a more specific message because we can +#ifdef MACOSX + message += "Make sure \"Opening/Closing\" is present."; +#else + message += "Be sure to rename \"Opening/Closing\" to \"Opening_Closing\"."; +#endif + + GUIErrorMessage(message); + warning("%s", message.c_str()); + return Common::kNoGameDataFoundError; + } + + // Set up input + InputHandler::setInputHandler(this); + allowInput(true); + + // Set up inventories + _items.setWeightLimit(9); + _items.setOwnerID(kPlayerID); + _biochips.setWeightLimit(8); + _biochips.setOwnerID(kPlayerID); + + _returnHotspot.setArea(Common::Rect(kNavAreaLeft, kNavAreaTop, 512 + kNavAreaLeft, 256 + kNavAreaTop)); + _returnHotspot.setHotspotFlags(kInfoReturnSpotFlag); + g_allHotspots.push_back(&_returnHotspot); + + _screenDimmer.setBounds(Common::Rect(0, 0, 640, 480)); + _screenDimmer.setDisplayOrder(kScreenDimmerOrder); + + // Load from the launcher/cli if requested (and don't show the intro in those cases) + bool doIntro = true; + if (ConfMan.hasKey("save_slot")) { + uint32 gameToLoad = ConfMan.getInt("save_slot"); + doIntro = (loadGameState(gameToLoad).getCode() != Common::kNoError); + } + + _shellNotification.notifyMe(this, kJMPShellNotificationFlags, kJMPShellNotificationFlags); + + if (doIntro) + // Start up the first notification + _shellNotification.setNotificationFlags(kGameStartingFlag, kGameStartingFlag); + + if (!isDemo()) { + _introTimer = new FuseFunction(); + _introTimer->setFunctionPtr(&introTimerExpiredFunction, 0); + } + + while (!shouldQuit()) { + processShell(); + _system->delayMillis(10); // Ease off the CPU + } + + return Common::kNoError; +} + +bool PegasusEngine::detectOpeningClosingDirectory() { + // We need to detect what our Opening/Closing directory is listed as + // On the original disc, it was 'Opening/Closing' but only HFS(+) supports the slash + // Mac OS X will display this as 'Opening:Closing' and we can use that directly + // On other systems, users will need to rename to "Opening_Closing" + + Common::FSNode gameDataDir(ConfMan.get("path")); + gameDataDir = gameDataDir.getChild("Images"); + + if (!gameDataDir.exists()) + return false; + + Common::FSList fsList; + if (!gameDataDir.getChildren(fsList, Common::FSNode::kListDirectoriesOnly, true)) + return false; + + for (uint i = 0; i < fsList.size() && _introDirectory.empty(); i++) { + Common::String name = fsList[i].getName(); + + if (name.equalsIgnoreCase("Opening:Closing")) + _introDirectory = name; + else if (name.equalsIgnoreCase("Opening_Closing")) + _introDirectory = name; + } + + if (_introDirectory.empty()) + return false; + + debug(0, "Detected intro location as '%s'", _introDirectory.c_str()); + _introDirectory = Common::String("Images/") + _introDirectory; + return true; +} + +void PegasusEngine::createItems() { + Common::SeekableReadStream *res = _resFork->getResource(MKTAG('N', 'I', 't', 'm'), 0x80); + + uint16 entryCount = res->readUint16BE(); + + for (uint16 i = 0; i < entryCount; i++) { + ItemID itemID = res->readUint16BE(); + NeighborhoodID neighborhoodID = res->readUint16BE(); + RoomID roomID = res->readUint16BE(); + DirectionConstant direction = res->readByte(); + res->readByte(); // alignment + + createItem(itemID, neighborhoodID, roomID, direction); + } + + delete res; +} + +void PegasusEngine::createItem(ItemID itemID, NeighborhoodID neighborhoodID, RoomID roomID, DirectionConstant direction) { + switch (itemID) { + case kInterfaceBiochip: + // Unused in game, but still in the data and we need to create + // it because it's saved/loaded from save files. + new BiochipItem(itemID, neighborhoodID, roomID, direction); + break; + case kAIBiochip: + new AIChip(itemID, neighborhoodID, roomID, direction); + break; + case kPegasusBiochip: + new PegasusChip(itemID, neighborhoodID, roomID, direction); + break; + case kOpticalBiochip: + new OpticalChip(itemID, neighborhoodID, roomID, direction); + break; + case kMapBiochip: + new MapChip(itemID, neighborhoodID, roomID, direction); + break; + case kRetinalScanBiochip: + new RetScanChip(itemID, neighborhoodID, roomID, direction); + break; + case kShieldBiochip: + new ShieldChip(itemID, neighborhoodID, roomID, direction); + break; + case kAirMask: + new AirMask(itemID, neighborhoodID, roomID, direction); + break; + case kKeyCard: + new KeyCard(itemID, neighborhoodID, roomID, direction); + break; + case kGasCanister: + new GasCanister(itemID, neighborhoodID, roomID, direction); + break; + default: + // Everything else is a normal inventory item + new InventoryItem(itemID, neighborhoodID, roomID, direction); + break; + } +} + +void PegasusEngine::runIntro() { + stopIntroTimer(); + + bool skipped = false; + + Video::SeekableVideoDecoder *video = new Video::QuickTimeDecoder(); + if (video->loadFile(_introDirectory + "/BandaiLogo.movie")) { + while (!shouldQuit() && !video->endOfVideo() && !skipped) { + if (video->needsUpdate()) { + const Graphics::Surface *frame = video->decodeNextFrame(); + + if (frame) { + _system->copyRectToScreen((byte *)frame->pixels, frame->pitch, 0, 0, frame->w, frame->h); + _system->updateScreen(); + } + } + + Input input; + InputDevice.getInput(input, kFilterAllInput); + if (input.anyInput()) + skipped = true; + + _system->delayMillis(10); + } + } + + delete video; + + if (shouldQuit() || skipped) + return; + + video = new Video::QuickTimeDecoder(); + + if (!video->loadFile(_introDirectory + "/Big Movie.movie")) + error("Could not load intro movie"); + + video->seekToTime(Audio::Timestamp(0, 10 * 600, 600)); + + playMovieScaled(video, 0, 0); + + delete video; +} + +Common::Error PegasusEngine::showLoadDialog() { + GUI::SaveLoadChooser slc(_("Load game:"), _("Load"), false); + + Common::String gameId = ConfMan.get("gameid"); + + const EnginePlugin *plugin = 0; + EngineMan.findGame(gameId, &plugin); + + int slot = slc.runModalWithPluginAndTarget(plugin, ConfMan.getActiveDomainName()); + + Common::Error result; + + if (slot >= 0) { + if (loadGameState(slot).getCode() == Common::kNoError) + result = Common::kNoError; + else + result = Common::kUnknownError; + } else { + result = Common::kUserCanceled; + } + + slc.close(); + + return result; +} + +Common::Error PegasusEngine::showSaveDialog() { + GUI::SaveLoadChooser slc(_("Save game:"), _("Save"), true); + + Common::String gameId = ConfMan.get("gameid"); + + const EnginePlugin *plugin = 0; + EngineMan.findGame(gameId, &plugin); + + int slot = slc.runModalWithPluginAndTarget(plugin, ConfMan.getActiveDomainName()); + + Common::Error result; + + if (slot >= 0) { + if (saveGameState(slot, slc.getResultString()).getCode() == Common::kNoError) + result = Common::kNoError; + else + result = Common::kUnknownError; + } else { + result = Common::kUserCanceled; + } + + slc.close(); + + return result; +} + +GUI::Debugger *PegasusEngine::getDebugger() { + return _console; +} + +void PegasusEngine::addIdler(Idler *idler) { + idler->_nextIdler = _idlerHead; + if (_idlerHead) + _idlerHead->_prevIdler = idler; + idler->_prevIdler = 0; + _idlerHead = idler; +} + +void PegasusEngine::removeIdler(Idler *idler) { + if (idler->_prevIdler) + idler->_prevIdler->_nextIdler = idler->_nextIdler; + if (idler->_nextIdler) + idler->_nextIdler->_prevIdler = idler->_prevIdler; + if (idler == _idlerHead) + _idlerHead = idler->_nextIdler; + idler->_nextIdler = 0; + idler->_prevIdler = 0; +} + +void PegasusEngine::giveIdleTime() { + for (Idler *idler = _idlerHead; idler != 0; idler = idler->_nextIdler) + idler->useIdleTime(); +} + +void PegasusEngine::addTimeBase(TimeBase *timeBase) { + _timeBases.push_back(timeBase); +} + +void PegasusEngine::removeTimeBase(TimeBase *timeBase) { + _timeBases.remove(timeBase); +} + +bool PegasusEngine::loadFromStream(Common::ReadStream *stream) { + // Dispose currently running stuff + useMenu(0); + useNeighborhood(0); + removeAllItemsFromInventory(); + removeAllItemsFromBiochips(); + _currentItemID = kNoItemID; + _currentBiochipID = kNoItemID; + + if (!g_interface) + createInterface(); + + // Signature + uint32 creator = stream->readUint32BE(); + if (creator != kPegasusPrimeCreator) { + warning("Bad save creator '%s'", tag2str(creator)); + return false; + } + + uint32 gameType = stream->readUint32BE(); + int saveType; + + switch (gameType) { + case kPegasusPrimeDisk1GameType: + case kPegasusPrimeDisk2GameType: + case kPegasusPrimeDisk3GameType: + case kPegasusPrimeDisk4GameType: + _currentCD = gameType - kPegasusPrimeDisk1GameType + 1; + saveType = kNormalSave; + break; + case kPegasusPrimeContinueType: + saveType = kContinueSave; + break; + default: + // There are five other possible game types on the Pippin + // version, but hopefully we don't see any of those here + warning("Unhandled pegasus game type '%s'", tag2str(gameType)); + return false; + } + + uint32 version = stream->readUint32BE(); + if (version != kPegasusPrimeVersion) { + warning("Where did you get this save? It's a beta (v%04x)!", version & 0x7fff); + return false; + } + + // Game State + GameState.readGameState(stream); + + // Energy + setLastEnergyValue(stream->readUint32BE()); + + // Death reason + setEnergyDeathReason(stream->readByte()); + + // Items + g_allItems.readFromStream(stream); + + // Inventory + byte itemCount = stream->readByte(); + + if (itemCount > 0) { + for (byte i = 0; i < itemCount; i++) { + InventoryItem *inv = (InventoryItem *)g_allItems.findItemByID((ItemID)stream->readUint16BE()); + addItemToInventory(inv); + } + + g_interface->setCurrentInventoryItemID((ItemID)stream->readUint16BE()); + } + + // Biochips + byte biochipCount = stream->readByte(); + + if (biochipCount > 0) { + for (byte i = 0; i < biochipCount; i++) { + BiochipItem *biochip = (BiochipItem *)g_allItems.findItemByID((ItemID)stream->readUint16BE()); + addItemToBiochips(biochip); + } + + g_interface->setCurrentBiochipID((ItemID)stream->readUint16BE()); + } + + + // TODO: Disc check + + // Jump to environment + jumpToNewEnvironment(GameState.getCurrentNeighborhood(), GameState.getCurrentRoom(), GameState.getCurrentDirection()); + _shellNotification.setNotificationFlags(0, kNeedNewJumpFlag); + performJump(GameState.getCurrentNeighborhood()); + + // AI rules + if (g_AIArea) + g_AIArea->readAIRules(stream); + + startNeighborhood(); + + // Make a new continue point if this isn't already one + if (saveType == kNormalSave) + makeContinuePoint(); + + return true; +} + +bool PegasusEngine::writeToStream(Common::WriteStream *stream, int saveType) { + if (g_neighborhood) + g_neighborhood->flushGameState(); + + // Signature + stream->writeUint32BE(kPegasusPrimeCreator); + + if (saveType == kNormalSave) + stream->writeUint32BE(kPegasusPrimeDisk1GameType + _currentCD - 1); + else // Continue + stream->writeUint32BE(kPegasusPrimeContinueType); + + stream->writeUint32BE(kPegasusPrimeVersion); + + // Game State + GameState.writeGameState(stream); + + // Energy + stream->writeUint32BE(getSavedEnergyValue()); + + // Death reason + stream->writeByte(getEnergyDeathReason()); + + // Items + g_allItems.writeToStream(stream); + + // Inventory + byte itemCount = _items.getNumItems(); + stream->writeByte(itemCount); + + if (itemCount > 0) { + for (uint32 i = 0; i < itemCount; i++) + stream->writeUint16BE(_items.getItemIDAt(i)); + + stream->writeUint16BE(g_interface->getCurrentInventoryItem()->getObjectID()); + } + + // Biochips + byte biochipCount = _biochips.getNumItems(); + stream->writeByte(biochipCount); + + if (biochipCount > 0) { + for (uint32 i = 0; i < biochipCount; i++) + stream->writeUint16BE(_biochips.getItemIDAt(i)); + + stream->writeUint16BE(g_interface->getCurrentBiochip()->getObjectID()); + } + + // AI rules + if (g_AIArea) + g_AIArea->writeAIRules(stream); + + return true; +} + +void PegasusEngine::makeContinuePoint() { + // WORKAROUND: Do not attempt to make a continue point if the interface is not set + // up. The original did *not* do this and attempting to restore the game using the pause + // menu during the canyon/space chase sequence would segfault the game and crash the + // system. Nice! + if (!g_interface) + return; + + delete _continuePoint; + + Common::MemoryWriteStreamDynamic newPoint(DisposeAfterUse::NO); + writeToStream(&newPoint, kContinueSave); + _continuePoint = new Common::MemoryReadStream(newPoint.getData(), newPoint.size(), DisposeAfterUse::YES); +} + +void PegasusEngine::loadFromContinuePoint() { + // Failure to load a continue point is fatal + + if (!_continuePoint) + error("Attempting to load from non-existant continue point"); + + if (!loadFromStream(_continuePoint)) + error("Failed loading continue point"); +} + +Common::Error PegasusEngine::loadGameState(int slot) { + Common::StringArray filenames = _saveFileMan->listSavefiles("pegasus-*.sav"); + Common::InSaveFile *loadFile = _saveFileMan->openForLoading(filenames[slot]); + if (!loadFile) + return Common::kUnknownError; + + bool valid = loadFromStream(loadFile); + delete loadFile; + + return valid ? Common::kNoError : Common::kUnknownError; +} + +Common::Error PegasusEngine::saveGameState(int slot, const Common::String &desc) { + Common::String output = Common::String::format("pegasus-%s.sav", desc.c_str()); + Common::OutSaveFile *saveFile = _saveFileMan->openForSaving(output); + if (!saveFile) + return Common::kUnknownError; + + bool valid = writeToStream(saveFile, kNormalSave); + delete saveFile; + + return valid ? Common::kNoError : Common::kUnknownError; +} + +void PegasusEngine::receiveNotification(Notification *notification, const NotificationFlags flags) { + if (&_shellNotification == notification) { + switch (flags) { + case kGameStartingFlag: { + useMenu(new MainMenu()); + + if (!isDemo()) { + runIntro(); + resetIntroTimer(); + } else { + showTempScreen("Images/Demo/NGsplashScrn.pict"); + } + + if (shouldQuit()) + return; + + _gfx->invalRect(Common::Rect(0, 0, 640, 480)); + _gfx->updateDisplay(); + ((MainMenu *)_gameMenu)->startMainMenuLoop(); + break; + } + case kPlayerDiedFlag: + doDeath(); + break; + case kNeedNewJumpFlag: + performJump(GameState.getNextNeighborhood()); + startNeighborhood(); + break; + default: + break; + } + } +} + +void PegasusEngine::checkCallBacks() { + for (Common::List<TimeBase *>::iterator it = _timeBases.begin(); it != _timeBases.end(); it++) + (*it)->checkCallBacks(); +} + +void PegasusEngine::resetIntroTimer() { + if (!isDemo() && _gameMenu && _gameMenu->getObjectID() == kMainMenuID) { + _introTimer->stopFuse(); + _introTimer->primeFuse(kIntroTimeOut); + _introTimer->lightFuse(); + } +} + +void PegasusEngine::introTimerExpired() { + if (_gameMenu && _gameMenu->getObjectID() == kMainMenuID) { + ((MainMenu *)_gameMenu)->stopMainMenuLoop(); + + bool skipped = false; + + Video::SeekableVideoDecoder *video = new Video::QuickTimeDecoder(); + if (!video->loadFile(_introDirectory + "/LilMovie.movie")) + error("Failed to load little movie"); + + bool saveAllowed = swapSaveAllowed(false); + bool openAllowed = swapLoadAllowed(false); + + skipped = playMovieScaled(video, 0, 0); + + delete video; + + if (shouldQuit()) + return; + + if (!skipped) { + runIntro(); + + if (shouldQuit()) + return; + } + + resetIntroTimer(); + _gfx->invalRect(Common::Rect(0, 0, 640, 480)); + + swapSaveAllowed(saveAllowed); + swapLoadAllowed(openAllowed); + + _gfx->updateDisplay(); + ((MainMenu *)_gameMenu)->startMainMenuLoop(); + } +} + +void PegasusEngine::stopIntroTimer() { + if (_introTimer) + _introTimer->stopFuse(); +} + +void PegasusEngine::delayShell(TimeValue time, TimeScale scale) { + if (time == 0 || scale == 0) + return; + + uint32 startTime = g_system->getMillis(); + uint32 timeInMillis = time * 1000 / scale; + + while (g_system->getMillis() < startTime + timeInMillis) { + checkCallBacks(); + _gfx->updateDisplay(); + } +} + +void PegasusEngine::useMenu(GameMenu *newMenu) { + if (_gameMenu) { + _gameMenu->restorePreviousHandler(); + delete _gameMenu; + } + + _gameMenu = newMenu; + + if (_gameMenu) + _gameMenu->becomeCurrentHandler(); +} + +bool PegasusEngine::checkGameMenu() { + GameMenuCommand command = kMenuCmdNoCommand; + + if (_gameMenu) { + command = _gameMenu->getLastCommand(); + if (command != kMenuCmdNoCommand) { + _gameMenu->clearLastCommand(); + doGameMenuCommand(command); + } + } + + return command != kMenuCmdNoCommand; +} + +void PegasusEngine::doGameMenuCommand(const GameMenuCommand command) { + Common::Error result; + + switch (command) { + case kMenuCmdStartAdventure: + stopIntroTimer(); + GameState.setWalkthroughMode(false); + startNewGame(); + break; + case kMenuCmdCredits: + if (isDemo()) { + showTempScreen("Images/Demo/DemoCredits.pict"); + _gfx->doFadeOutSync(); + _gfx->updateDisplay(); + _gfx->doFadeInSync(); + } else { + stopIntroTimer(); + _gfx->doFadeOutSync(); + useMenu(new CreditsMenu()); + _gfx->updateDisplay(); + _gfx->doFadeInSync(); + } + break; + case kMenuCmdQuit: + case kMenuCmdDeathQuitDemo: + if (isDemo()) + showTempScreen("Images/Demo/NGquitScrn.pict"); + _system->quit(); + break; + case kMenuCmdOverview: + stopIntroTimer(); + doInterfaceOverview(); + resetIntroTimer(); + break; + case kMenuCmdStartWalkthrough: + stopIntroTimer(); + GameState.setWalkthroughMode(true); + startNewGame(); + break; + case kMenuCmdRestore: + stopIntroTimer(); + // fall through + case kMenuCmdDeathRestore: + result = showLoadDialog(); + if (command == kMenuCmdRestore && result.getCode() != Common::kNoError) + resetIntroTimer(); + break; + case kMenuCmdCreditsMainMenu: + _gfx->doFadeOutSync(); + useMenu(new MainMenu()); + _gfx->updateDisplay(); + ((MainMenu *)_gameMenu)->startMainMenuLoop(); + _gfx->doFadeInSync(); + resetIntroTimer(); + break; + case kMenuCmdDeathContinue: + if (((DeathMenu *)_gameMenu)->playerWon()) { + if (isDemo()) { + showTempScreen("Images/Demo/DemoCredits.pict"); + _gfx->doFadeOutSync(); + _gfx->updateDisplay(); + _gfx->doFadeInSync(); + } else { + _gfx->doFadeOutSync(); + useMenu(0); + _gfx->clearScreen(); + _gfx->updateDisplay(); + + Video::SeekableVideoDecoder *video = new Video::QuickTimeDecoder(); + if (!video->loadFile(_introDirectory + "/Closing.movie")) + error("Could not load closing movie"); + + uint16 x = (640 - video->getWidth() * 2) / 2; + uint16 y = (480 - video->getHeight() * 2) / 2; + + playMovieScaled(video, x, y); + + delete video; + + if (shouldQuit()) + return; + + useMenu(new MainMenu()); + _gfx->updateDisplay(); + ((MainMenu *)_gameMenu)->startMainMenuLoop(); + _gfx->doFadeInSync(); + resetIntroTimer(); + } + } else { + loadFromContinuePoint(); + } + break; + case kMenuCmdDeathMainMenuDemo: + case kMenuCmdDeathMainMenu: + _gfx->doFadeOutSync(); + useMenu(new MainMenu()); + _gfx->updateDisplay(); + ((MainMenu *)_gameMenu)->startMainMenuLoop(); + _gfx->doFadeInSync(); + resetIntroTimer(); + break; + case kMenuCmdPauseSave: + if (showSaveDialog().getCode() != Common::kUserCanceled) + pauseMenu(false); + break; + case kMenuCmdPauseContinue: + pauseMenu(false); + break; + case kMenuCmdPauseRestore: + makeContinuePoint(); + result = showLoadDialog(); + + if (result.getCode() == Common::kNoError) { + // Successfully loaded, unpause the game + pauseMenu(false); + } else if (result.getCode() != Common::kUserCanceled) { + // Try to get us back to a sane state + loadFromContinuePoint(); + } + break; + case kMenuCmdPauseQuit: + _gfx->doFadeOutSync(); + throwAwayEverything(); + pauseMenu(false); + useMenu(new MainMenu()); + _gfx->updateDisplay(); + ((MainMenu *)_gameMenu)->startMainMenuLoop(); + _gfx->doFadeInSync(); + resetIntroTimer(); + break; + case kMenuCmdNoCommand: + break; + default: + error("Unknown menu command %d", command); + } +} + +void PegasusEngine::handleInput(const Input &input, const Hotspot *cursorSpot) { + if (!checkGameMenu()) + shellGameInput(input, cursorSpot); + + // Handle the console here + if (input.isConsoleRequested()) { + _console->attach(); + _console->onFrame(); + } + + // Handle save requests here + if (_saveRequested && _saveAllowed) { + _saveRequested = false; + + // Can only save during a game and not in the demo + if (g_neighborhood && !isDemo()) { + pauseEngine(true); + showSaveDialog(); + pauseEngine(false); + } + } + + // Handle load requests here + if (_loadRequested && _loadAllowed) { + _loadRequested = false; + + // WORKAROUND: Do not entertain load requests when the pause menu is up + // The original did and the game entered a bad state after loading. + // It's theoretically possible to make it so it does work while the + // pause menu is up, but the pause state of the engine is just too weird. + // Just use the pause menu's restore button since it's there for that + // for you to load anyway. + if (!isDemo() && !(_gameMenu && _gameMenu->getObjectID() == kPauseMenuID)) { + pauseEngine(true); + + if (g_neighborhood) { + makeContinuePoint(); + + Common::Error result = showLoadDialog(); + if (result.getCode() != Common::kNoError && result.getCode() != Common::kUserCanceled) + loadFromContinuePoint(); + } else { + if (_introTimer) + _introTimer->stopFuse(); + + Common::Error result = showLoadDialog(); + if (result.getCode() != Common::kNoError) { + if (!_gameMenu) { + useMenu(new MainMenu()); + ((MainMenu *)_gameMenu)->startMainMenuLoop(); + } + + resetIntroTimer(); + } + } + + pauseEngine(false); + } + } +} + +void PegasusEngine::doInterfaceOverview() { + static const short kNumOverviewSpots = 11; + static const Common::Rect overviewSpots[kNumOverviewSpots] = { + Common::Rect(354, 318, 354 + 204, 318 + 12), + Common::Rect(211, 34, 211 + 114, 34 + 28), + Common::Rect(502, 344, 502 + 138, 344 + 120), + Common::Rect(132, 40, 132 + 79, 40 + 22), + Common::Rect(325, 40, 332 + 115, 40 + 22), + Common::Rect(70, 318, 70 + 284, 318 + 12), + Common::Rect(76, 334, 76 + 96, 334 + 96), + Common::Rect(64, 64, 64 + 512, 64 + 256), + Common::Rect(364, 334, 364 + 96, 334 + 96), + Common::Rect(172, 334, 172 + 192, 334 + 96), + Common::Rect(542, 36, 542 + 58, 36 + 20) + }; + + _gfx->doFadeOutSync(); + useMenu(0); + + Picture leftBackground(kNoDisplayElement); + leftBackground.initFromPICTFile("Images/Interface/OVLeft.mac"); + leftBackground.setDisplayOrder(0); + leftBackground.moveElementTo(kBackground1Left, kBackground1Top); + leftBackground.startDisplaying(); + leftBackground.show(); + + Picture topBackground(kNoDisplayElement); + topBackground.initFromPICTFile("Images/Interface/OVTop.mac"); + topBackground.setDisplayOrder(0); + topBackground.moveElementTo(kBackground2Left, kBackground2Top); + topBackground.startDisplaying(); + topBackground.show(); + + Picture rightBackground(kNoDisplayElement); + rightBackground.initFromPICTFile("Images/Interface/OVRight.mac"); + rightBackground.setDisplayOrder(0); + rightBackground.moveElementTo(kBackground3Left, kBackground3Top); + rightBackground.startDisplaying(); + rightBackground.show(); + + Picture bottomBackground(kNoDisplayElement); + bottomBackground.initFromPICTFile("Images/Interface/OVBottom.mac"); + bottomBackground.setDisplayOrder(0); + bottomBackground.moveElementTo(kBackground4Left, kBackground4Top); + bottomBackground.startDisplaying(); + bottomBackground.show(); + + Picture controllerHighlight(kNoDisplayElement); + controllerHighlight.initFromPICTFile("Images/Interface/OVcontrollerHilite.mac"); + controllerHighlight.setDisplayOrder(0); + controllerHighlight.moveElementTo(kOverviewControllerLeft, kOverviewControllerTop); + controllerHighlight.startDisplaying(); + + Movie overviewText(kNoDisplayElement); + overviewText.initFromMovieFile("Images/Interface/Overview Mac.movie"); + overviewText.setDisplayOrder(0); + overviewText.moveElementTo(kNavAreaLeft, kNavAreaTop); + overviewText.startDisplaying(); + overviewText.show(); + overviewText.redrawMovieWorld(); + + DropHighlight highlight(kNoDisplayElement); + highlight.setDisplayOrder(1); + highlight.startDisplaying(); + highlight.setHighlightThickness(4); + highlight.setHighlightColor(g_system->getScreenFormat().RGBToColor(239, 239, 0)); + highlight.setHighlightCornerDiameter(8); + + Input input; + InputDevice.getInput(input, kFilterAllInput); + + Common::Point cursorLoc; + input.getInputLocation(cursorLoc); + + uint16 i; + for (i = 0; i < kNumOverviewSpots; ++i) + if (overviewSpots[i].contains(cursorLoc)) + break; + + TimeValue time; + if (i == kNumOverviewSpots) + time = 5; + else if (i > 4) + time = i + 1; + else + time = i; + + if (time == 2) { + highlight.hide(); + controllerHighlight.show(); + } else if (i != kNumOverviewSpots) { + controllerHighlight.hide(); + Common::Rect r = overviewSpots[i]; + r.grow(5); + highlight.setBounds(r); + highlight.show(); + } else { + highlight.hide(); + controllerHighlight.hide(); + } + + overviewText.setTime(time * 3 + 2, 15); + overviewText.redrawMovieWorld(); + + _cursor->setCurrentFrameIndex(3); + _cursor->show(); + + _gfx->updateDisplay(); + _gfx->doFadeInSync(); + + for (;;) { + InputDevice.getInput(input, kFilterAllInput); + + if (input.anyInput() || shouldQuit() || _loadRequested || _saveRequested) + break; + + input.getInputLocation(cursorLoc); + for (i = 0; i < kNumOverviewSpots; ++i) + if (overviewSpots[i].contains(cursorLoc)) + break; + + if (i == kNumOverviewSpots) + time = 5; + else if (i > 4) + time = i + 1; + else + time = i; + + if (time == 2) { + highlight.hide(); + controllerHighlight.show(); + } else if (i != kNumOverviewSpots) { + controllerHighlight.hide(); + Common::Rect r = overviewSpots[i]; + r.grow(5); + highlight.setBounds(r); + highlight.show(); + } else { + highlight.hide(); + controllerHighlight.hide(); + } + + overviewText.setTime(time * 3 + 2, 15); + overviewText.redrawMovieWorld(); + + refreshDisplay(); + } + + if (shouldQuit()) + return; + + highlight.hide(); + _cursor->hide(); + + _gfx->doFadeOutSync(); + useMenu(new MainMenu()); + _gfx->updateDisplay(); + ((MainMenu *)_gameMenu)->startMainMenuLoop(); + _gfx->doFadeInSync(); + + _saveRequested = false; + _loadRequested = false; +} + +void PegasusEngine::showTempScreen(const Common::String &fileName) { + _gfx->doFadeOutSync(); + + Picture picture(0); + picture.initFromPICTFile(fileName); + picture.setDisplayOrder(kMaxAvailableOrder); + picture.startDisplaying(); + picture.show(); + _gfx->updateDisplay(); + + _gfx->doFadeInSync(); + + // Wait for the next event + bool done = false; + while (!shouldQuit() && !done) { + Common::Event event; + while (_eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONUP: + case Common::EVENT_KEYDOWN: + done = true; + break; + default: + break; + } + } + + _system->delayMillis(10); + } +} + +void PegasusEngine::refreshDisplay() { + giveIdleTime(); + _gfx->updateDisplay(); +} + +void PegasusEngine::resetEnergyDeathReason() { + switch (getCurrentNeighborhoodID()) { + case kMarsID: + _deathReason = kDeathArrestedInMars; + break; + case kNoradAlphaID: + case kNoradDeltaID: + _deathReason = kDeathArrestedInNorad; + break; + case kWSCID: + _deathReason = kDeathArrestedInWSC; + break; + default: + _deathReason = kDeathStranded; + break; + } +} + +bool PegasusEngine::playerHasItem(const Item *item) { + return playerHasItemID(item->getObjectID()); +} + +bool PegasusEngine::playerHasItemID(const ItemID itemID) { + return itemInInventory(itemID) || itemInBiochips(itemID); +} + +InventoryItem *PegasusEngine::getCurrentInventoryItem() { + if (g_interface) + return g_interface->getCurrentInventoryItem(); + + return 0; +} + +bool PegasusEngine::itemInInventory(InventoryItem *item) { + return _items.itemInInventory(item); +} + +bool PegasusEngine::itemInInventory(ItemID id) { + return _items.itemInInventory(id); +} + +BiochipItem *PegasusEngine::getCurrentBiochip() { + if (g_interface) + return g_interface->getCurrentBiochip(); + + return 0; +} + +bool PegasusEngine::itemInBiochips(BiochipItem *item) { + return _biochips.itemInInventory(item); +} + +bool PegasusEngine::itemInBiochips(ItemID id) { + return _biochips.itemInInventory(id); +} + +bool PegasusEngine::playerAlive() { + return (_shellNotification.getNotificationFlags() & kPlayerDiedFlag) == 0; +} + +Common::String PegasusEngine::getBriefingMovie() { + if (_neighborhood) + return _neighborhood->getBriefingMovie(); + + return Common::String(); +} + +Common::String PegasusEngine::getEnvScanMovie() { + if (_neighborhood) + return _neighborhood->getEnvScanMovie(); + + return Common::String(); +} + +uint PegasusEngine::getNumHints() { + if (_neighborhood) + return _neighborhood->getNumHints(); + + return 0; +} + +Common::String PegasusEngine::getHintMovie(uint hintNum) { + if (_neighborhood) + return _neighborhood->getHintMovie(hintNum); + + return Common::String(); +} + +bool PegasusEngine::canSolve() { + if (_neighborhood) + return _neighborhood->canSolve(); + + return false; +} + +void PegasusEngine::prepareForAIHint(const Common::String &movieName) { + if (g_neighborhood) + g_neighborhood->prepareForAIHint(movieName); +} + +void PegasusEngine::cleanUpAfterAIHint(const Common::String &movieName) { + if (g_neighborhood) + g_neighborhood->cleanUpAfterAIHint(movieName); +} + +void PegasusEngine::jumpToNewEnvironment(const NeighborhoodID neighborhoodID, const RoomID roomID, const DirectionConstant direction) { + GameState.setNextLocation(neighborhoodID, roomID, direction); + _shellNotification.setNotificationFlags(kNeedNewJumpFlag, kNeedNewJumpFlag); +} + +void PegasusEngine::checkFlashlight() { + if (_neighborhood) + _neighborhood->checkFlashlight(); +} + +bool PegasusEngine::playMovieScaled(Video::SeekableVideoDecoder *video, uint16 x, uint16 y) { + bool skipped = false; + + while (!shouldQuit() && !video->endOfVideo() && !skipped) { + if (video->needsUpdate()) { + const Graphics::Surface *frame = video->decodeNextFrame(); + + if (frame) + drawScaledFrame(frame, x, y); + } + + Input input; + InputDevice.getInput(input, kFilterAllInput); + if (input.anyInput() || _saveRequested || _loadRequested) + skipped = true; + + _system->delayMillis(10); + } + + return skipped; +} + +void PegasusEngine::die(const DeathReason reason) { + Input dummy; + if (isDragging()) + _itemDragger.stopTracking(dummy); + + _deathReason = reason; + _shellNotification.setNotificationFlags(kPlayerDiedFlag, kPlayerDiedFlag); +} + +void PegasusEngine::doDeath() { + _gfx->doFadeOutSync(); + throwAwayEverything(); + useMenu(new DeathMenu(_deathReason)); + _gfx->updateDisplay(); + _gfx->doFadeInSync(); +} + +void PegasusEngine::throwAwayEverything() { + if (_items.getNumItems() != 0 && g_interface) + _currentItemID = g_interface->getCurrentInventoryItem()->getObjectID(); + else + _currentItemID = kNoItemID; + + if (_biochips.getNumItems() != 0 && g_interface) + _currentItemID = g_interface->getCurrentBiochip()->getObjectID(); + else + _currentItemID = kNoItemID; + + useMenu(0); + useNeighborhood(0); + + delete g_interface; + g_interface = 0; +} + +void PegasusEngine::processShell() { + checkCallBacks(); + checkNotifications(); + InputHandler::pollForInput(); + refreshDisplay(); +} + +void PegasusEngine::createInterface() { + if (!g_interface) + new Interface(); + + g_interface->createInterface(); +} + +void PegasusEngine::setGameMode(const GameMode newMode) { + if (newMode != _gameMode && canSwitchGameMode(newMode, _gameMode)) { + switchGameMode(newMode, _gameMode); + _gameMode = newMode; + } +} + +void PegasusEngine::switchGameMode(const GameMode newMode, const GameMode oldMode) { + // Start raising panels before lowering panels, to give the activating panel time + // to set itself up without cutting into the lowering panel's animation time. + + if (_switchModesSync) { + if (newMode == kModeInventoryPick) + raiseInventoryDrawerSync(); + else if (newMode == kModeBiochipPick) + raiseBiochipDrawerSync(); + else if (newMode == kModeInfoScreen) + showInfoScreen(); + + if (oldMode == kModeInventoryPick) + lowerInventoryDrawerSync(); + else if (oldMode == kModeBiochipPick) + lowerBiochipDrawerSync(); + else if (oldMode == kModeInfoScreen) + hideInfoScreen(); + } else { + if (newMode == kModeInventoryPick) + raiseInventoryDrawer(); + else if (newMode == kModeBiochipPick) + raiseBiochipDrawer(); + else if (newMode == kModeInfoScreen) + showInfoScreen(); + + if (oldMode == kModeInventoryPick) + lowerInventoryDrawer(); + else if (oldMode == kModeBiochipPick) + lowerBiochipDrawer(); + else if (oldMode == kModeInfoScreen) + hideInfoScreen(); + } +} + +bool PegasusEngine::canSwitchGameMode(const GameMode newMode, const GameMode oldMode) { + if (newMode == kModeInventoryPick && oldMode == kModeBiochipPick) + return false; + if (newMode == kModeBiochipPick && oldMode == kModeInventoryPick) + return false; + return true; +} + +bool PegasusEngine::itemInLocation(const ItemID itemID, const NeighborhoodID neighborhood, const RoomID room, const DirectionConstant direction) { + NeighborhoodID itemNeighborhood; + RoomID itemRoom; + DirectionConstant itemDirection; + + Item *item = g_allItems.findItemByID(itemID); + item->getItemRoom(itemNeighborhood, itemRoom, itemDirection); + + return itemNeighborhood == neighborhood && itemRoom == room && itemDirection == direction; +} + +InventoryResult PegasusEngine::addItemToInventory(InventoryItem *item) { + InventoryResult result; + + do { + if (g_interface) + result = g_interface->addInventoryItem(item); + else + result = _items.addItem(item); + + if (result == kTooMuchWeight) + destroyInventoryItem(pickItemToDestroy()); + } while (result != kInventoryOK); + + GameState.setTakenItem(item, true); + if (g_neighborhood) + g_neighborhood->pickedUpItem(item); + + g_AIArea->checkMiddleArea(); + + return result; +} + +void PegasusEngine::useNeighborhood(Neighborhood *neighborhood) { + delete _neighborhood; + _neighborhood = neighborhood; + + if (_neighborhood) { + InputHandler::setInputHandler(_neighborhood); + _neighborhood->init(); + _neighborhood->moveNavTo(kNavAreaLeft, kNavAreaTop); + g_interface->setDate(_neighborhood->getDateResID()); + } else { + InputHandler::setInputHandler(this); + } +} + +void PegasusEngine::performJump(NeighborhoodID neighborhoodID) { + if (_neighborhood) + useNeighborhood(0); + + // Sub chase is special + if (neighborhoodID == kNoradSubChaseID) { + throwAwayEverything(); + doSubChase(); + + if (shouldQuit()) + return; + + neighborhoodID = kNoradDeltaID; + GameState.setNextRoom(kNorad41); + GameState.setNextDirection(kEast); + } + + Neighborhood *neighborhood; + makeNeighborhood(neighborhoodID, neighborhood); + useNeighborhood(neighborhood); + + // Update the CD variable (used just for saves currently) + _currentCD = getNeighborhoodCD(neighborhoodID); +} + +void PegasusEngine::startNeighborhood() { + if (g_interface && _currentItemID != kNoItemID) + g_interface->setCurrentInventoryItemID(_currentItemID); + + if (g_interface && _currentBiochipID != kNoItemID) + g_interface->setCurrentBiochipID(_currentBiochipID); + + setGameMode(kModeNavigation); + + if (_neighborhood) + _neighborhood->start(); +} + +void PegasusEngine::startNewGame() { + // WORKAROUND: The original game ignored the menu difficulty + // setting. We're going to pass it through here so that + // the menu actually makes sense now. + bool isWalkthrough = GameState.getWalkthroughMode(); + GameState.resetGameState(); + GameState.setWalkthroughMode(isWalkthrough); + + // TODO: Enable erase + _gfx->doFadeOutSync(); + useMenu(0); + _gfx->updateDisplay(); + + createInterface(); + + if (isDemo()) { + setLastEnergyValue(kFullEnergy); + jumpToNewEnvironment(kPrehistoricID, kPrehistoric02, kSouth); + GameState.setPrehistoricSeenTimeStream(false); + GameState.setPrehistoricSeenFlyer1(false); + GameState.setPrehistoricSeenFlyer2(false); + GameState.setPrehistoricSeenBridgeZoom(false); + GameState.setPrehistoricBreakerThrown(false); + } else { + jumpToNewEnvironment(kCaldoriaID, kCaldoria00, kEast); + } + + removeAllItemsFromInventory(); + removeAllItemsFromBiochips(); + + BiochipItem *biochip = (BiochipItem *)g_allItems.findItemByID(kAIBiochip); + addItemToBiochips(biochip); + + if (isDemo()) { + biochip = (BiochipItem *)g_allItems.findItemByID(kPegasusBiochip); + addItemToBiochips(biochip); + biochip = (BiochipItem *)g_allItems.findItemByID(kMapBiochip); + addItemToBiochips(biochip); + InventoryItem *item = (InventoryItem *)g_allItems.findItemByID(kKeyCard); + addItemToInventory(item); + item = (InventoryItem *)g_allItems.findItemByID(kJourneymanKey); + addItemToInventory(item); + _currentItemID = kJourneymanKey; + } else { + _currentItemID = kNoItemID; + } + + _currentBiochipID = kAIBiochip; + + // Clear jump notification flags and just perform the jump... + _shellNotification.setNotificationFlags(0, kNeedNewJumpFlag); + + performJump(GameState.getNextNeighborhood()); + + startNeighborhood(); +} + +void PegasusEngine::makeNeighborhood(NeighborhoodID neighborhoodID, Neighborhood *&neighborhood) { + // TODO: CD check + + switch (neighborhoodID) { + case kCaldoriaID: + neighborhood = new Caldoria(g_AIArea, this); + break; + case kMarsID: + neighborhood = new Mars(g_AIArea, this); + break; + case kPrehistoricID: + neighborhood = new Prehistoric(g_AIArea, this); + break; + case kFullTSAID: + neighborhood = new FullTSA(g_AIArea, this); + break; + case kTinyTSAID: + neighborhood = new TinyTSA(g_AIArea, this); + break; + case kWSCID: + neighborhood = new WSC(g_AIArea, this); + break; + case kNoradAlphaID: + neighborhood = new NoradAlpha(g_AIArea, this); + break; + case kNoradDeltaID: + createInterface(); + neighborhood = new NoradDelta(g_AIArea, this); + break; + default: + error("Unknown neighborhood %d", neighborhoodID); + } +} + +bool PegasusEngine::wantsCursor() { + return _gameMenu == 0; +} + +void PegasusEngine::updateCursor(const Common::Point, const Hotspot *cursorSpot) { + if (_itemDragger.isTracking()) { + _cursor->setCurrentFrameIndex(5); + } else { + if (!cursorSpot) { + _cursor->setCurrentFrameIndex(0); + } else { + uint32 id = cursorSpot->getObjectID(); + + switch (id) { + case kCurrentItemSpotID: + if (countInventoryItems() != 0) + _cursor->setCurrentFrameIndex(4); + else + _cursor->setCurrentFrameIndex(0); + break; + default: + HotSpotFlags flags = cursorSpot->getHotspotFlags(); + + if (flags & kZoomInSpotFlag) + _cursor->setCurrentFrameIndex(1); + else if (flags & kZoomOutSpotFlag) + _cursor->setCurrentFrameIndex(2); + else if (flags & (kPickUpItemSpotFlag | kPickUpBiochipSpotFlag)) + _cursor->setCurrentFrameIndex(4); + else if (flags & kJMPClickingSpotFlags) + _cursor->setCurrentFrameIndex(3); + else + _cursor->setCurrentFrameIndex(0); + } + } + } +} + +void PegasusEngine::toggleInventoryDisplay() { + if (_gameMode == kModeInventoryPick) + setGameMode(kModeNavigation); + else + setGameMode(kModeInventoryPick); +} + +void PegasusEngine::toggleBiochipDisplay() { + if (_gameMode == kModeBiochipPick) + setGameMode(kModeNavigation); + else + setGameMode(kModeBiochipPick); +} + +void PegasusEngine::showInfoScreen() { + if (g_neighborhood) { + // Break the input handler chain... + _savedHandler = InputHandler::getCurrentHandler(); + InputHandler::setInputHandler(this); + + Picture *pushPicture = ((Neighborhood *)g_neighborhood)->getTurnPushPicture(); + + _bigInfoMovie.shareSurface(pushPicture); + _smallInfoMovie.shareSurface(pushPicture); + + g_neighborhood->hideNav(); + + _smallInfoMovie.initFromMovieFile("Images/Items/Info Right Movie"); + _smallInfoMovie.setDisplayOrder(kInfoSpinOrder); + _smallInfoMovie.moveElementTo(kNavAreaLeft + 304, kNavAreaTop + 8); + _smallInfoMovie.moveMovieBoxTo(304, 8); + _smallInfoMovie.startDisplaying(); + _smallInfoMovie.show(); + + TimeValue startTime, stopTime; + g_AIArea->getSmallInfoSegment(startTime, stopTime); + _smallInfoMovie.setSegment(startTime, stopTime); + _smallInfoMovie.setTime(startTime); + _smallInfoMovie.setFlags(kLoopTimeBase); + + _bigInfoMovie.initFromMovieFile("Images/Items/Info Left Movie"); + _bigInfoMovie.setDisplayOrder(kInfoBackgroundOrder); + _bigInfoMovie.moveElementTo(kNavAreaLeft, kNavAreaTop); + _bigInfoMovie.startDisplaying(); + _bigInfoMovie.show(); + _bigInfoMovie.setTime(g_AIArea->getBigInfoTime()); + + _bigInfoMovie.redrawMovieWorld(); + _smallInfoMovie.redrawMovieWorld(); + _smallInfoMovie.start(); + } +} + +void PegasusEngine::hideInfoScreen() { + if (g_neighborhood) { + InputHandler::setInputHandler(_savedHandler); + + _bigInfoMovie.hide(); + _bigInfoMovie.stopDisplaying(); + _bigInfoMovie.releaseMovie(); + + _smallInfoMovie.hide(); + _smallInfoMovie.stopDisplaying(); + _smallInfoMovie.stop(); + _smallInfoMovie.releaseMovie(); + + g_neighborhood->showNav(); + } +} + +void PegasusEngine::raiseInventoryDrawer() { + if (g_interface) + g_interface->raiseInventoryDrawer(); +} + +void PegasusEngine::raiseBiochipDrawer() { + if (g_interface) + g_interface->raiseBiochipDrawer(); +} + +void PegasusEngine::lowerInventoryDrawer() { + if (g_interface) + g_interface->lowerInventoryDrawer(); +} + +void PegasusEngine::lowerBiochipDrawer() { + if (g_interface) + g_interface->lowerBiochipDrawer(); +} + +void PegasusEngine::raiseInventoryDrawerSync() { + if (g_interface) + g_interface->raiseInventoryDrawerSync(); +} + +void PegasusEngine::raiseBiochipDrawerSync() { + if (g_interface) + g_interface->raiseBiochipDrawerSync(); +} + +void PegasusEngine::lowerInventoryDrawerSync() { + if (g_interface) + g_interface->lowerInventoryDrawerSync(); +} + +void PegasusEngine::lowerBiochipDrawerSync() { + if (g_interface) + g_interface->lowerBiochipDrawerSync(); +} + +void PegasusEngine::toggleInfo() { + if (_gameMode == kModeInfoScreen) + setGameMode(kModeNavigation); + else if (_gameMode == kModeNavigation) + setGameMode(kModeInfoScreen); +} + +void PegasusEngine::dragTerminated(const Input &) { + Hotspot *finalSpot = _itemDragger.getLastHotspot(); + InventoryResult result; + + if (_dragType == kDragInventoryPickup) { + if (finalSpot && finalSpot->getObjectID() == kInventoryDropSpotID) + result = addItemToInventory((InventoryItem *)_draggingItem); + else + result = kTooMuchWeight; + + if (result != kInventoryOK) + autoDragItemIntoRoom(_draggingItem, _draggingSprite); + else + delete _draggingSprite; + } else if (_dragType == kDragBiochipPickup) { + if (finalSpot && finalSpot->getObjectID() == kBiochipDropSpotID) + result = addItemToBiochips((BiochipItem *)_draggingItem); + else + result = kTooMuchWeight; + + if (result != kInventoryOK) + autoDragItemIntoRoom(_draggingItem, _draggingSprite); + else + delete _draggingSprite; + } else if (_dragType == kDragInventoryUse) { + if (finalSpot && (finalSpot->getHotspotFlags() & kDropItemSpotFlag) != 0) { + // *** Need to decide on a case by case basis what to do here. + // the crowbar should break the cover off the Mars reactor if its frozen, the + // global transport card should slide through the slot, the oxygen mask should + // attach to the filling station, and so on... + _neighborhood->dropItemIntoRoom(_draggingItem, finalSpot); + delete _draggingSprite; + } else { + autoDragItemIntoInventory(_draggingItem, _draggingSprite); + } + } + + _dragType = kDragNoDrag; + + if (g_AIArea) + g_AIArea->unlockAI(); +} + + +void PegasusEngine::dragItem(const Input &input, Item *item, DragType type) { + _draggingItem = item; + _dragType = type; + + // Create the sprite. + _draggingSprite = _draggingItem->getDragSprite(kDraggingSpriteID); + Common::Point where; + input.getInputLocation(where); + Common::Rect r1; + _draggingSprite->getBounds(r1); + r1 = Common::Rect::center(where.x, where.y, r1.width(), r1.height()); + _draggingSprite->setBounds(r1); + + // Set up drag constraints. + DisplayElement *navMovie = _gfx->findDisplayElement(kNavMovieID); + Common::Rect r2; + navMovie->getBounds(r2); + r2.left -= r1.width() / 3; + r2.right += r1.width() / 3; + r2.top -= r1.height() / 3; + r2.bottom += r2.height() / 3; + + r1 = Common::Rect(-30000, -30000, 30000, 30000); + _itemDragger.setDragConstraints(r2, r1); + + // Start dragging. + _draggingSprite->setDisplayOrder(kDragSpriteOrder); + _draggingSprite->startDisplaying(); + _draggingSprite->show(); + _itemDragger.setDragSprite(_draggingSprite); + _itemDragger.setNextHandler(_neighborhood); + _itemDragger.startTracking(input); + + if (g_AIArea) + g_AIArea->lockAIOut(); +} + +void PegasusEngine::shellGameInput(const Input &input, const Hotspot *cursorSpot) { + if (_gameMode == kModeInfoScreen) { + if (JMPPPInput::isToggleAIMiddleInput(input)) { + LowerClientSignature middleOwner = g_AIArea->getMiddleAreaOwner(); + g_AIArea->toggleMiddleAreaOwner(); + + if (middleOwner != g_AIArea->getMiddleAreaOwner()) { + _bigInfoMovie.setTime(g_AIArea->getBigInfoTime()); + _smallInfoMovie.stop(); + _smallInfoMovie.setFlags(0); + + TimeValue startTime, stopTime; + g_AIArea->getSmallInfoSegment(startTime, stopTime); + _smallInfoMovie.setSegment(startTime, stopTime); + _smallInfoMovie.setTime(startTime); + _smallInfoMovie.setFlags(kLoopTimeBase); + + _bigInfoMovie.redrawMovieWorld(); + _smallInfoMovie.redrawMovieWorld(); + _smallInfoMovie.start(); + } + } + } else { + if (JMPPPInput::isRaiseInventoryInput(input)) + toggleInventoryDisplay(); + + if (JMPPPInput::isRaiseBiochipsInput(input)) + toggleBiochipDisplay(); + + if (JMPPPInput::isTogglePauseInput(input) && _neighborhood) + pauseMenu(!isPaused()); + } + + if (JMPPPInput::isToggleInfoInput(input)) + toggleInfo(); +} + +void PegasusEngine::activateHotspots() { + if (_gameMode == kModeInfoScreen) { + g_allHotspots.activateOneHotspot(kInfoReturnSpotID); + } else { + // Set up hot spots. + if (_dragType == kDragInventoryPickup) + g_allHotspots.activateOneHotspot(kInventoryDropSpotID); + else if (_dragType == kDragBiochipPickup) + g_allHotspots.activateOneHotspot(kBiochipDropSpotID); + else if (_dragType == kDragNoDrag) + g_allHotspots.activateMaskedHotspots(kShellSpotFlag); + } +} + +bool PegasusEngine::isClickInput(const Input &input, const Hotspot *cursorSpot) { + if (_cursor->isVisible() && cursorSpot) + return JMPPPInput::isClickInput(input); + else + return false; +} + +InputBits PegasusEngine::getClickFilter() { + return JMPPPInput::getClickInputFilter(); +} + +void PegasusEngine::clickInHotspot(const Input &input, const Hotspot *clickedSpot) { + if (clickedSpot->getObjectID() == kCurrentItemSpotID) { + InventoryItem *currentItem = getCurrentInventoryItem(); + if (currentItem) { + removeItemFromInventory(currentItem); + dragItem(input, currentItem, kDragInventoryUse); + } + } else if (clickedSpot->getObjectID() == kInfoReturnSpotID) { + toggleInfo(); + } +} + +InventoryResult PegasusEngine::removeItemFromInventory(InventoryItem *item) { + InventoryResult result; + + if (g_interface) + result = g_interface->removeInventoryItem(item); + else + result = _items.removeItem(item); + + // This should never happen + assert(result == kInventoryOK); + + return result; +} + +void PegasusEngine::removeAllItemsFromInventory() { + if (g_interface) + g_interface->removeAllItemsFromInventory(); + else + _items.removeAllItems(); +} + +///////////////////////////////////////////// +// +// Biochip handling. + +// Adding biochips to the biochip drawer is a little funny, because of two things: +// -- We get the map biochip and pegasus biochip at the same time by dragging +// one sprite in TSA +// -- We can drag in more than one copy of the various biochips. +// Because of this we need to make sure that no more than one copy of each biochip +// is ever added. + +InventoryResult PegasusEngine::addItemToBiochips(BiochipItem *biochip) { + InventoryResult result; + + if (g_interface) + result = g_interface->addBiochip(biochip); + else + result = _biochips.addItem(biochip); + + // This can never happen + assert(result == kInventoryOK); + + GameState.setTakenItem(biochip, true); + + if (g_neighborhood) + g_neighborhood->pickedUpItem(biochip); + + g_AIArea->checkMiddleArea(); + + return result; +} + +void PegasusEngine::removeAllItemsFromBiochips() { + if (g_interface) + g_interface->removeAllItemsFromBiochips(); + else + _biochips.removeAllItems(); +} + +void PegasusEngine::setSoundFXLevel(uint16 fxLevel) { + _FXLevel = fxLevel; + if (_neighborhood) + _neighborhood->setSoundFXLevel(fxLevel); + if (g_AIArea) + g_AIArea->setAIVolume(fxLevel); +} + +void PegasusEngine::setAmbienceLevel(uint16 ambientLevel) { + _ambientLevel = ambientLevel; + if (_neighborhood) + _neighborhood->setAmbienceLevel(ambientLevel); +} + +void PegasusEngine::pauseMenu(bool menuUp) { + if (menuUp) { + pauseEngine(true); + _screenDimmer.startDisplaying(); + _screenDimmer.show(); + _gfx->updateDisplay(); + useMenu(new PauseMenu()); + } else { + pauseEngine(false); + _screenDimmer.hide(); + _screenDimmer.stopDisplaying(); + useMenu(0); + g_AIArea->checkMiddleArea(); + } +} + +void PegasusEngine::autoDragItemIntoRoom(Item *item, Sprite *draggingSprite) { + if (g_AIArea) + g_AIArea->lockAIOut(); + + Common::Point start, stop; + draggingSprite->getLocation(start.x, start.y); + + Hotspot *dropSpot = _neighborhood->getItemScreenSpot(item, draggingSprite); + + if (dropSpot) { + dropSpot->getCenter(stop.x, stop.y); + } else { + stop.x = kNavAreaLeft + 256; + stop.y = kNavAreaTop + 128; + } + + Common::Rect bounds; + draggingSprite->getBounds(bounds); + stop.x -= bounds.width() >> 1; + stop.y -= bounds.height() >> 1; + + int dx = ABS(stop.x - start.x); + int dy = ABS(stop.y - start.y); + TimeValue time = MAX(dx, dy); + + allowInput(false); + _autoDragger.autoDrag(draggingSprite, start, stop, time, kDefaultTimeScale); + + while (_autoDragger.isDragging()) { + checkCallBacks(); + refreshDisplay(); + _system->delayMillis(10); + } + + _neighborhood->dropItemIntoRoom(_draggingItem, dropSpot); + allowInput(true); + delete _draggingSprite; + + if (g_AIArea) + g_AIArea->unlockAI(); +} + +void PegasusEngine::autoDragItemIntoInventory(Item *, Sprite *draggingSprite) { + if (g_AIArea) + g_AIArea->lockAIOut(); + + Common::Point start; + draggingSprite->getLocation(start.x, start.y); + + Common::Rect r; + draggingSprite->getBounds(r); + + Common::Point stop((76 + 172 - r.width()) / 2, 334 - (2 * r.height() / 3)); + + int dx = ABS(stop.x - start.x); + int dy = ABS(stop.y - start.y); + TimeValue time = MAX(dx, dy); + + allowInput(false); + _autoDragger.autoDrag(draggingSprite, start, stop, time, kDefaultTimeScale); + + while (_autoDragger.isDragging()) { + checkCallBacks(); + refreshDisplay(); + _system->delayMillis(10); + } + + addItemToInventory((InventoryItem *)_draggingItem); + allowInput(true); + delete _draggingSprite; + + if (g_AIArea) + g_AIArea->unlockAI(); +} + +NeighborhoodID PegasusEngine::getCurrentNeighborhoodID() const { + if (_neighborhood) + return _neighborhood->getObjectID(); + + return kNoNeighborhoodID; +} + +void PegasusEngine::pauseEngineIntern(bool pause) { + Engine::pauseEngineIntern(pause); + + if (pause) { + for (Common::List<TimeBase *>::iterator it = _timeBases.begin(); it != _timeBases.end(); it++) + (*it)->pause(); + } else { + for (Common::List<TimeBase *>::iterator it = _timeBases.begin(); it != _timeBases.end(); it++) + (*it)->resume(); + } +} + +uint PegasusEngine::getRandomBit() { + return _rnd->getRandomBit(); +} + +uint PegasusEngine::getRandomNumber(uint max) { + return _rnd->getRandomNumber(max); +} + +void PegasusEngine::shuffleArray(int32 *arr, int32 count) { + if (count > 1) { + for (int32 i = 1; i < count; ++i) { + int32 j = _rnd->getRandomNumber(i); + if (j != i) + SWAP(arr[i], arr[j]); + } + } +} + +void PegasusEngine::playEndMessage() { + if (g_interface) { + allowInput(false); + g_interface->playEndMessage(); + allowInput(true); + } + + die(kPlayerWonGame); +} + +void PegasusEngine::doSubChase() { + static const uint32 endTime = 133200 * 1000 / 600; + + Video::SeekableVideoDecoder *video = new Video::QuickTimeDecoder(); + if (!video->loadFile("Images/Norad Alpha/Sub Chase Movie")) + error("Failed to load sub chase"); + + while (!shouldQuit() && !video->endOfVideo() && video->getTime() < endTime) { + if (video->needsUpdate()) { + const Graphics::Surface *frame = video->decodeNextFrame(); + + if (frame) + drawScaledFrame(frame, 0, 0); + } + + Common::Event event; + while (_eventMan->pollEvent(event)) + ; + + _system->delayMillis(10); + } + + delete video; +} + +void PegasusEngine::drawScaledFrame(const Graphics::Surface *frame, uint16 x, uint16 y) { + // Scale up the frame doing some simple scaling + Graphics::Surface scaledFrame; + scaledFrame.create(frame->w * 2, frame->h * 2, frame->format); + const byte *src = (const byte *)frame->pixels; + byte *dst1 = (byte *)scaledFrame.pixels; + byte *dst2 = (byte *)scaledFrame.pixels + scaledFrame.pitch; + + for (int i = 0; i < frame->h; i++) { + for (int j = 0; j < frame->w; j++) { + memcpy(dst1, src, frame->format.bytesPerPixel); + dst1 += frame->format.bytesPerPixel; + memcpy(dst1, src, frame->format.bytesPerPixel); + dst1 += frame->format.bytesPerPixel; + memcpy(dst2, src, frame->format.bytesPerPixel); + dst2 += frame->format.bytesPerPixel; + memcpy(dst2, src, frame->format.bytesPerPixel); + dst2 += frame->format.bytesPerPixel; + src += frame->format.bytesPerPixel; + } + + src += frame->pitch - frame->format.bytesPerPixel * frame->w; + dst1 += scaledFrame.pitch * 2 - scaledFrame.format.bytesPerPixel * scaledFrame.w; + dst2 += scaledFrame.pitch * 2 - scaledFrame.format.bytesPerPixel * scaledFrame.w; + } + + _system->copyRectToScreen((byte *)scaledFrame.pixels, scaledFrame.pitch, x, y, scaledFrame.w, scaledFrame.h); + _system->updateScreen(); + scaledFrame.free(); +} + +void PegasusEngine::destroyInventoryItem(const ItemID itemID) { + InventoryItem *item = (InventoryItem *)g_allItems.findItemByID(itemID); + + ItemExtraEntry entry; + + switch (itemID) { + case kAirMask: + item->findItemExtra(kRemoveAirMask, entry); + item->setItemRoom(kMarsID, kMars49, kSouth); + break; + case kArgonCanister: + item->findItemExtra(kRemoveArgon, entry); + item->setItemRoom(kWSCID, kWSC02Morph, kSouth); + break; + case kCrowbar: + item->findItemExtra(kRemoveCrowbar, entry); + item->setItemRoom(kMarsID, kMars34, kSouth); + break; + case kJourneymanKey: + item->findItemExtra(kRemoveJourneymanKey, entry); + item->setItemRoom(kFullTSAID, kTSA22Red, kEast); + break; + case kMarsCard: + item->findItemExtra(kRemoveMarsCard, entry); + item->setItemRoom(kMarsID, kMars31South, kSouth); + break; + case kNitrogenCanister: + item->findItemExtra(kRemoveNitrogen, entry); + item->setItemRoom(kWSCID, kWSC02Messages, kSouth); + break; + case kOrangeJuiceGlassEmpty: + item->findItemExtra(kRemoveGlass, entry); + item->setItemRoom(kCaldoriaID, kCaldoriaReplicator, kNorth); + break; + case kPoisonDart: + item->findItemExtra(kRemoveDart, entry); + item->setItemRoom(kWSCID, kWSC01, kWest); + break; + case kSinclairKey: + item->findItemExtra(kRemoveSinclairKey, entry); + item->setItemRoom(kWSCID, kWSC02Morph, kSouth); + break; + default: + return; + } + + g_interface->setCurrentInventoryItemID(itemID); + g_AIArea->playAIAreaSequence(kInventorySignature, kMiddleAreaSignature, entry.extraStart, entry.extraStop); + removeItemFromInventory(item); +} + +ItemID PegasusEngine::pickItemToDestroy() { +/* + Must pick an item to destroy + + Part I: Polite -- try to find an item that's been used + Part II: Desperate -- return the first available item. +*/ + + // Polite: + if (playerHasItemID(kOrangeJuiceGlassEmpty)) + return kOrangeJuiceGlassEmpty; + if (playerHasItemID(kPoisonDart)) { + if (GameState.getCurrentNeighborhood() != kWSCID || + GameState.getWSCAnalyzedDart()) + return kPoisonDart; + } + if (playerHasItemID(kJourneymanKey)) { + if (GameState.getTSAState() >= kTSAPlayerGotHistoricalLog && + GameState.getTSAState() != kPlayerOnWayToPrehistoric && + GameState.getTSAState() != kPlayerWentToPrehistoric) + return kJourneymanKey; + } + if (playerHasItemID(kMarsCard)) { + if (GameState.getCurrentNeighborhood() != kMarsID || GameState.getMarsArrivedBelow()) + return kMarsCard; + } + + // Don't want to deal with deleting the sinclair key and argon canister, since it's + // impossible to pick them up one at a time. + + if (playerHasItemID(kNitrogenCanister)) { + if (GameState.getScoringGotCardBomb() && GameState.getCurrentNeighborhood() != kMarsID) + return kNitrogenCanister; + } + if (playerHasItemID(kCrowbar)) { + if (GameState.getCurrentNeighborhood() == kWSCID) { + if (GameState.getCurrentRoom() >= kWSC62) + return kCrowbar; + } else if (GameState.getCurrentNeighborhood() == kMarsID) { + if (GameState.getScoringGotCardBomb()) + return kCrowbar; + } else + return kCrowbar; + } + if (playerHasItemID(kAirMask)) { + if (GameState.getCurrentNeighborhood() == kMarsID) { + if (g_neighborhood->getAirQuality(GameState.getCurrentRoom()) == kAirQualityGood) + return kAirMask; + } else if (GameState.getCurrentNeighborhood() != kNoradAlphaID && + GameState.getCurrentNeighborhood() != kNoradDeltaID) { + return kAirMask; + } + } + + // Desperate: + if (playerHasItemID(kPoisonDart)) + return kPoisonDart; + if (playerHasItemID(kJourneymanKey)) + return kJourneymanKey; + if (playerHasItemID(kMarsCard)) + return kMarsCard; + if (playerHasItemID(kNitrogenCanister)) + return kNitrogenCanister; + if (playerHasItemID(kCrowbar)) + return kCrowbar; + if (playerHasItemID(kAirMask)) + return kAirMask; + + // Should never get this far... + error("Could not find item to delete"); + + return kNoItemID; +} + +uint PegasusEngine::getNeighborhoodCD(const NeighborhoodID neighborhood) const { + switch (neighborhood) { + case kCaldoriaID: + case kNoradAlphaID: + case kNoradSubChaseID: + return 1; + case kFullTSAID: + case kPrehistoricID: + return 2; + case kMarsID: + return 3; + case kWSCID: + case kNoradDeltaID: + return 4; + case kTinyTSAID: + // Tiny TSA exists on three of the CD's, so just continue + // with the CD we're on + return _currentCD; + } + + // Can't really happen, but it's a good fallback anyway :P + return 1; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/pegasus.h b/engines/pegasus/pegasus.h new file mode 100644 index 0000000000..a1b4cff9ab --- /dev/null +++ b/engines/pegasus/pegasus.h @@ -0,0 +1,322 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_H +#define PEGASUS_H + +#include "common/list.h" +#include "common/macresman.h" +#include "common/rect.h" +#include "common/scummsys.h" +#include "common/system.h" +#include "common/util.h" + +#include "engines/engine.h" + +#include "pegasus/graphics.h" +#include "pegasus/hotspot.h" +#include "pegasus/input.h" +#include "pegasus/notification.h" +#include "pegasus/timers.h" +#include "pegasus/items/autodragger.h" +#include "pegasus/items/inventory.h" +#include "pegasus/items/itemdragger.h" +#include "pegasus/neighborhood/neighborhood.h" + +namespace Common { + class RandomSource; +} + +namespace Video { + class SeekableVideoDecoder; +} + +namespace Pegasus { + +class PegasusConsole; +struct PegasusGameDescription; +class SoundManager; +class GraphicsManager; +class Idler; +class Cursor; +class TimeBase; +class GameMenu; +class InventoryItem; +class BiochipItem; +class Neighborhood; + +class PegasusEngine : public ::Engine, public InputHandler, public NotificationManager { +friend class InputHandler; + +public: + PegasusEngine(OSystem *syst, const PegasusGameDescription *gamedesc); + virtual ~PegasusEngine(); + + // Engine stuff + const PegasusGameDescription *_gameDescription; + bool hasFeature(EngineFeature f) const; + GUI::Debugger *getDebugger(); + bool canLoadGameStateCurrently() { return _loadAllowed && !isDemo(); } + bool canSaveGameStateCurrently() { return _saveAllowed && !isDemo(); } + Common::Error loadGameState(int slot); + Common::Error saveGameState(int slot, const Common::String &desc); + + // Base classes + GraphicsManager *_gfx; + Common::MacResManager *_resFork; + Cursor *_cursor; + + // Menu + void useMenu(GameMenu *menu); + bool checkGameMenu(); + + // Misc. + bool isDemo() const; + void addIdler(Idler *idler); + void removeIdler(Idler *idler); + void addTimeBase(TimeBase *timeBase); + void removeTimeBase(TimeBase *timeBase); + void delayShell(TimeValue time, TimeScale scale); + void resetIntroTimer(); + void introTimerExpired(); + void refreshDisplay(); + bool playerAlive(); + void processShell(); + void checkCallBacks(); + void createInterface(); + void setGameMode(const GameMode); + GameMode getGameMode() const { return _gameMode; } + uint getRandomBit(); + uint getRandomNumber(uint max); + void shuffleArray(int32 *arr, int32 count); + void drawScaledFrame(const Graphics::Surface *frame, uint16 x, uint16 y); + + // Energy + void setLastEnergyValue(const int32 value) { _savedEnergyValue = value; } + int32 getSavedEnergyValue() { return _savedEnergyValue; } + + // Death + void setEnergyDeathReason(const DeathReason reason) { _deathReason = reason; } + DeathReason getEnergyDeathReason() { return _deathReason; } + void resetEnergyDeathReason(); + void die(const DeathReason); + void playEndMessage(); + + // Volume + uint16 getSoundFXLevel() { return _FXLevel; } + void setSoundFXLevel(uint16); + uint16 getAmbienceLevel() { return _ambientLevel; } + void setAmbienceLevel(uint16); + + // Items + bool playerHasItem(const Item *); + bool playerHasItemID(const ItemID); + void checkFlashlight(); + bool itemInLocation(const ItemID, const NeighborhoodID, const RoomID, const DirectionConstant); + + // Inventory Items + InventoryItem *getCurrentInventoryItem(); + bool itemInInventory(InventoryItem *); + bool itemInInventory(ItemID); + Inventory *getItemsInventory() { return &_items; } + InventoryResult addItemToInventory(InventoryItem *); + void removeAllItemsFromInventory(); + InventoryResult removeItemFromInventory(InventoryItem *); + uint32 countInventoryItems() { return _items.getNumItems(); } + + // Biochips + BiochipItem *getCurrentBiochip(); + bool itemInBiochips(BiochipItem *); + bool itemInBiochips(ItemID); + Inventory *getBiochipsInventory() { return &_biochips; } + void removeAllItemsFromBiochips(); + InventoryResult addItemToBiochips(BiochipItem *); + + // AI + Common::String getBriefingMovie(); + Common::String getEnvScanMovie(); + uint getNumHints(); + Common::String getHintMovie(uint); + bool canSolve(); + void prepareForAIHint(const Common::String &); + void cleanUpAfterAIHint(const Common::String &); + Common::SeekableReadStream *_aiSaveStream; + + // Neighborhood + void jumpToNewEnvironment(const NeighborhoodID, const RoomID, const DirectionConstant); + NeighborhoodID getCurrentNeighborhoodID() const; + + // Dragging + void dragItem(const Input &, Item *, DragType); + bool isDragging() const { return _dragType != kDragNoDrag; } + DragType getDragType() const { return _dragType; } + Item *getDraggingItem() const { return _draggingItem; } + void dragTerminated(const Input &); + void autoDragItemIntoRoom(Item *, Sprite *); + void autoDragItemIntoInventory(Item *, Sprite*); + + // Save/Load + void makeContinuePoint(); + bool swapSaveAllowed(bool allow) { + bool old = _saveAllowed; + _saveAllowed = allow; + return old; + } + bool swapLoadAllowed(bool allow) { + bool old = _loadAllowed; + _loadAllowed = allow; + return old; + } + void requestSave() { _saveRequested = true; } + bool saveRequested() const { return _saveRequested; } + void requestLoad() { _loadRequested = true; } + bool loadRequested() const { return _loadRequested; } + +protected: + Common::Error run(); + void pauseEngineIntern(bool pause); + + Notification _shellNotification; + virtual void receiveNotification(Notification *notification, const NotificationFlags flags); + + void handleInput(const Input &input, const Hotspot *cursorSpot); + virtual bool isClickInput(const Input &, const Hotspot *); + virtual InputBits getClickFilter(); + + void clickInHotspot(const Input &, const Hotspot *); + void activateHotspots(void); + + void updateCursor(const Common::Point, const Hotspot *); + bool wantsCursor(); + +private: + // Console + PegasusConsole *_console; + + // Intro + void runIntro(); + void stopIntroTimer(); + bool detectOpeningClosingDirectory(); + Common::String _introDirectory; + FuseFunction *_introTimer; + + // Idlers + Idler *_idlerHead; + void giveIdleTime(); + + // Items + void createItems(); + void createItem(ItemID itemID, NeighborhoodID neighborhoodID, RoomID roomID, DirectionConstant direction); + Inventory _items; + Inventory _biochips; + ItemID _currentItemID; + ItemID _currentBiochipID; + void destroyInventoryItem(const ItemID itemID); + ItemID pickItemToDestroy(); + + // TimeBases + Common::List<TimeBase *> _timeBases; + + // Save/Load + bool loadFromStream(Common::ReadStream *stream); + bool writeToStream(Common::WriteStream *stream, int saveType); + void loadFromContinuePoint(); + Common::ReadStream *_continuePoint; + bool _saveAllowed, _loadAllowed; // It's so nice that this was in the original code already :P + Common::Error showLoadDialog(); + Common::Error showSaveDialog(); + bool _saveRequested, _loadRequested; + + // Misc. + Hotspot _returnHotspot; + InputHandler *_savedHandler; + void showTempScreen(const Common::String &fileName); + bool playMovieScaled(Video::SeekableVideoDecoder *video, uint16 x, uint16 y); + void throwAwayEverything(); + void shellGameInput(const Input &input, const Hotspot *cursorSpot); + Common::RandomSource *_rnd; + void doSubChase(); + uint getNeighborhoodCD(const NeighborhoodID neighborhood) const; + uint _currentCD; + + // Menu + GameMenu *_gameMenu; + void doGameMenuCommand(const GameMenuCommand); + void doInterfaceOverview(); + ScreenDimmer _screenDimmer; + void pauseMenu(bool menuUp); + + // Energy + int32 _savedEnergyValue; + + // Death + DeathReason _deathReason; + void doDeath(); + + // Neighborhood + Neighborhood *_neighborhood; + void useNeighborhood(Neighborhood *neighborhood); + void performJump(NeighborhoodID start); + void startNewGame(); + void startNeighborhood(); + void makeNeighborhood(NeighborhoodID, Neighborhood *&); + + // Sound + uint16 _ambientLevel; + uint16 _FXLevel; + + // Game Mode + GameMode _gameMode; + bool _switchModesSync; + void switchGameMode(const GameMode, const GameMode); + bool canSwitchGameMode(const GameMode, const GameMode); + + // Dragging + ItemDragger _itemDragger; + Item *_draggingItem; + Sprite *_draggingSprite; + DragType _dragType; + AutoDragger _autoDragger; + + // Interface + void toggleInventoryDisplay(); + void toggleBiochipDisplay(); + void raiseInventoryDrawer(); + void raiseBiochipDrawer(); + void lowerInventoryDrawer(); + void lowerBiochipDrawer(); + void raiseInventoryDrawerSync(); + void raiseBiochipDrawerSync(); + void lowerInventoryDrawerSync(); + void lowerBiochipDrawerSync(); + void showInfoScreen(); + void hideInfoScreen(); + void toggleInfo(); + Movie _bigInfoMovie, _smallInfoMovie; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/scoring.h b/engines/pegasus/scoring.h new file mode 100644 index 0000000000..fbf8641ecb --- /dev/null +++ b/engines/pegasus/scoring.h @@ -0,0 +1,281 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_SCORING_H +#define PEGASUS_SCORING_H + +#include "pegasus/types.h" + +namespace Pegasus { + +///////////////////////////////////////////// +// +// Scoring. + +static const CoordType kDeathScreenScoreLeft = 151; +static const CoordType kDeathScreenScoreTop = 212; +static const CoordType kDeathScreenScoreWidth = 124; +static const CoordType kDeathScreenScoreHeight = 12; +static const CoordType kDeathScreenScoreSkipVert = -16; + +// Caldoria & TSA + +static const GameScoreType kSawINNScore = 5; +static const GameScoreType kTookShowerScore = 2; +static const GameScoreType kFixedHairScore = 2; +static const GameScoreType kGotKeyCardScore = 5; +static const GameScoreType kReadPaperScore = 2; +static const GameScoreType kLookThroughTelescopeScore = 2; +static const GameScoreType kSawCaldoriaKioskScore = 2; +static const GameScoreType kGoToTSAScore = 3; + +static const GameScoreType kEnterTSAScore = 2; +static const GameScoreType kSawBust1Score = 2; +static const GameScoreType kSawBust2Score = 2; +static const GameScoreType kSawBust3Score = 2; +static const GameScoreType kSawBust4Score = 2; +static const GameScoreType kSawBust5Score = 2; +static const GameScoreType kSawBust6Score = 2; +static const GameScoreType kSawTheoryScore = 4; +static const GameScoreType kSawBackgroundScore = 4; +static const GameScoreType kSawProcedureScore = 4; +static const GameScoreType kGotJourneymanKeyScore = 5; +static const GameScoreType kGotPegasusBiochipScore = 5; +static const GameScoreType kGotBiosuitScore = 5; +static const GameScoreType kGoToPrehistoricScore = 5; + +static const GameScoreType kPutLogInReaderScore = 5; +static const GameScoreType kSawCaldoriaNormalScore = 2; +static const GameScoreType kSawCaldoriaAlteredScore = 2; +static const GameScoreType kSawNoradNormalScore = 2; +static const GameScoreType kSawNoradAlteredScore = 2; +static const GameScoreType kSawMarsNormalScore = 2; +static const GameScoreType kSawMarsAlteredScore = 2; +static const GameScoreType kSawWSCNormalScore = 2; +static const GameScoreType kSawWSCAlteredScore = 2; +static const GameScoreType kWentToReadyRoom2Score = 5; +static const GameScoreType kWentAfterSinclairScore = 5; +static const GameScoreType kUsedCardBombScore = 10; +static const GameScoreType kShieldedCardBombScore = 5; +static const GameScoreType kStunnedSinclairScore = 10; +static const GameScoreType kDisarmedNukeScore = 10; + +static const GameScoreType kMaxCaldoriaTSAScoreBefore = kSawINNScore + + kTookShowerScore + + kFixedHairScore + + kGotKeyCardScore + + kReadPaperScore + + kLookThroughTelescopeScore + + kSawCaldoriaKioskScore + + kGoToTSAScore + + kEnterTSAScore + + kSawBust1Score + + kSawBust2Score + + kSawBust3Score + + kSawBust4Score + + kSawBust5Score + + kSawBust6Score + + kSawTheoryScore + + kSawBackgroundScore + + kSawProcedureScore + + kGotJourneymanKeyScore + + kGotPegasusBiochipScore + + kGotBiosuitScore + + kGoToPrehistoricScore + + kPutLogInReaderScore + + kSawCaldoriaNormalScore + + kSawCaldoriaAlteredScore + + kSawNoradNormalScore + + kSawNoradAlteredScore + + kSawMarsNormalScore + + kSawMarsAlteredScore + + kSawWSCNormalScore + + kSawWSCAlteredScore + + kWentToReadyRoom2Score; + +static const GameScoreType kMaxCaldoriaTSAScoreAfter = kWentAfterSinclairScore + + kUsedCardBombScore + + kShieldedCardBombScore + + kStunnedSinclairScore + + kDisarmedNukeScore; + +static const GameScoreType kMaxCaldoriaTSAScore = kMaxCaldoriaTSAScoreBefore + + kMaxCaldoriaTSAScoreAfter; + +// Prehistoric + +static const GameScoreType kThrewBreakerScore = 10; +static const GameScoreType kExtendedBridgeScore = 10; +static const GameScoreType kGotHistoricalLogScore = 5; +static const GameScoreType kFinishedPrehistoricScore = 10; + +static const GameScoreType kMaxPrehistoricScore = kThrewBreakerScore + + kExtendedBridgeScore + + kGotHistoricalLogScore + + kFinishedPrehistoricScore; + +// Mars + +static const GameScoreType kThrownByRobotScore = 3; +static const GameScoreType kGotMarsCardScore = 5; +static const GameScoreType kSawMarsKioskScore = 2; +static const GameScoreType kSawTransportMapScore = 2; +static const GameScoreType kGotCrowBarScore = 5; +static const GameScoreType kTurnedOnTransportScore = 5; +static const GameScoreType kGotOxygenMaskScore = 5; +static const GameScoreType kAvoidedRobotScore = 5; +static const GameScoreType kActivatedPlatformScore = 2; +static const GameScoreType kUsedLiquidNitrogenScore = 3; +static const GameScoreType kUsedCrowBarScore = 3; +static const GameScoreType kFoundCardBombScore = 4; +static const GameScoreType kDisarmedCardBombScore = 8; +static const GameScoreType kGotCardBombScore = 5; +static const GameScoreType kThreadedMazeScore = 5; +static const GameScoreType kThreadedGearRoomScore = 2; +static const GameScoreType kEnteredShuttleScore = 2; +static const GameScoreType kEnteredLaunchTubeScore = 4; +static const GameScoreType kStoppedRobotsShuttleScore = 10; +static const GameScoreType kGotMarsOpMemChipScore = 10; +static const GameScoreType kFinishedMarsScore = 10; + +static const GameScoreType kMaxMarsScore = kThrownByRobotScore + + kGotMarsCardScore + + kSawMarsKioskScore + + kSawTransportMapScore + + kGotCrowBarScore + + kTurnedOnTransportScore + + kGotOxygenMaskScore + + kAvoidedRobotScore + + kActivatedPlatformScore + + kUsedLiquidNitrogenScore + + kUsedCrowBarScore + + kFoundCardBombScore + + kDisarmedCardBombScore + + kGotCardBombScore + + kThreadedMazeScore + + kThreadedGearRoomScore + + kEnteredShuttleScore + + kEnteredLaunchTubeScore + + kStoppedRobotsShuttleScore + + kGotMarsOpMemChipScore + + kFinishedMarsScore; + +// Norad + +static const GameScoreType kSawSecurityMonitorScore = 5; +static const GameScoreType kFilledOxygenCanisterScore = 5; +static const GameScoreType kFilledArgonCanisterScore = 5; +static const GameScoreType kSawUnconsciousOperatorScore = 5; +static const GameScoreType kWentThroughPressureDoorScore = 5; +static const GameScoreType kPreppedSubScore = 5; +static const GameScoreType kEnteredSubScore = 5; +static const GameScoreType kExitedSubScore = 10; +static const GameScoreType kSawRobotAt54NorthScore = 5; +static const GameScoreType kPlayedWithClawScore = 5; +static const GameScoreType kUsedRetinalChipScore = 5; +static const GameScoreType kFinishedGlobeGameScore = 10; +static const GameScoreType kStoppedNoradRobotScore = 10; +static const GameScoreType kGotNoradOpMemChipScore = 10; +static const GameScoreType kFinishedNoradScore = 10; + +static const GameScoreType kMaxNoradScore = kSawSecurityMonitorScore + + kFilledOxygenCanisterScore + + kFilledArgonCanisterScore + + kSawUnconsciousOperatorScore + + kWentThroughPressureDoorScore + + kPreppedSubScore + + kEnteredSubScore + + kExitedSubScore + + kSawRobotAt54NorthScore + + kPlayedWithClawScore + + kUsedRetinalChipScore + + kFinishedGlobeGameScore + + kStoppedNoradRobotScore + + kGotNoradOpMemChipScore + + kFinishedNoradScore; + +// WSC + +static const GameScoreType kRemovedDartScore = 5; +static const GameScoreType kAnalyzedDartScore = 5; +static const GameScoreType kBuiltAntidoteScore = 5; +static const GameScoreType kGotSinclairKeyScore = 5; +static const GameScoreType kGotArgonCanisterScore = 5; +static const GameScoreType kGotNitrogenCanisterScore = 5; +static const GameScoreType kPlayedWithMessagesScore = 2; +static const GameScoreType kSawMorphExperimentScore = 3; +static const GameScoreType kEnteredSinclairOfficeScore = 2; +static const GameScoreType kSawBrochureScore = 3; +static const GameScoreType kSawSinclairEntry1Score = 3; +static const GameScoreType kSawSinclairEntry2Score = 3; +static const GameScoreType kSawSinclairEntry3Score = 3; +static const GameScoreType kSawWSCDirectoryScore = 3; +static const GameScoreType kUsedCrowBarInWSCScore = 5; +static const GameScoreType kFinishedPlasmaDodgeScore = 10; +static const GameScoreType kOpenedCatwalkScore = 3; +static const GameScoreType kStoppedWSCRobotScore = 10; +static const GameScoreType kGotWSCOpMemChipScore = 10; +static const GameScoreType kFinishedWSCScore = 10; + +static const GameScoreType kMaxWSCScore = kRemovedDartScore + + kAnalyzedDartScore + + kBuiltAntidoteScore + + kGotSinclairKeyScore + + kGotArgonCanisterScore + + kGotNitrogenCanisterScore + + kPlayedWithMessagesScore + + kSawMorphExperimentScore + + kEnteredSinclairOfficeScore + + kSawBrochureScore + + kSawSinclairEntry1Score + + kSawSinclairEntry2Score + + kSawSinclairEntry3Score + + kSawWSCDirectoryScore + + kUsedCrowBarInWSCScore + + kFinishedPlasmaDodgeScore + + kOpenedCatwalkScore + + kStoppedWSCRobotScore + + kGotWSCOpMemChipScore + + kFinishedWSCScore; + +// Gandhi + +static const GameScoreType kMarsGandhiScore = 10; +static const GameScoreType kNoradGandhiScore = 10; +static const GameScoreType kWSCGandhiScore = 10; + +static const GameScoreType kMaxGandhiScore = kMarsGandhiScore + + kNoradGandhiScore + + kWSCGandhiScore; + +static const GameScoreType kMaxTotalScore = kMaxCaldoriaTSAScore + + kMaxPrehistoricScore + + kMaxMarsScore + + kMaxNoradScore + + kMaxWSCScore + + kMaxGandhiScore; +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/sound.cpp b/engines/pegasus/sound.cpp new file mode 100644 index 0000000000..bf15858e80 --- /dev/null +++ b/engines/pegasus/sound.cpp @@ -0,0 +1,181 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "audio/audiostream.h" +#include "audio/decoders/aiff.h" +#include "audio/decoders/quicktime.h" +#include "common/file.h" +#include "common/system.h" + +#include "pegasus/fader.h" +#include "pegasus/sound.h" + +namespace Pegasus { + +Sound::Sound() { + _stream = 0; + _volume = 0xFF; + _fader = 0; +} + +Sound::~Sound() { + disposeSound(); +} + +void Sound::disposeSound() { + stopSound(); + delete _stream; _stream = 0; +} + +void Sound::initFromAIFFFile(const Common::String &fileName) { + disposeSound(); + + Common::File *file = new Common::File(); + if (!file->open(fileName)) { + warning("Failed to open AIFF file '%s'", fileName.c_str()); + delete file; + return; + } + + _stream = Audio::makeAIFFStream(file, DisposeAfterUse::YES); +} + +void Sound::initFromQuickTime(const Common::String &fileName) { + disposeSound(); + + _stream = Audio::makeQuickTimeStream(fileName); + + if (!_stream) + warning("Failed to open QuickTime file '%s'", fileName.c_str()); +} + +void Sound::attachFader(SoundFader *fader) { + if (_fader) + _fader->attachSound(0); + + _fader = fader; + + if (_fader) + _fader->attachSound(this); +} + +void Sound::playSound() { + if (!isSoundLoaded()) + return; + + stopSound(); + + if (_fader) + setVolume(_fader->getFaderValue()); + + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_handle, _stream, -1, _volume, 0, DisposeAfterUse::NO); +} + +void Sound::loopSound() { + if (!isSoundLoaded()) + return; + + stopSound(); + + // Create a looping stream + Audio::AudioStream *loopStream = new Audio::LoopingAudioStream(_stream, 0, DisposeAfterUse::NO); + + // Assume that if there is a fader, we're going to fade the sound in. + if (_fader) + setVolume(0); + + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_handle, loopStream, -1, _volume, 0, DisposeAfterUse::YES); +} + +void Sound::playSoundSegment(uint32 start, uint32 end) { + if (!isSoundLoaded()) + return; + + stopSound(); + + Audio::AudioStream *subStream = new Audio::SubSeekableAudioStream(_stream, Audio::Timestamp(0, start, 600), Audio::Timestamp(0, end, 600), DisposeAfterUse::NO); + + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_handle, subStream, -1, _volume, 0, DisposeAfterUse::YES); +} + +void Sound::stopSound() { + g_system->getMixer()->stopHandle(_handle); +} + +void Sound::setVolume(const uint16 volume) { + // Clipping the volume to [0x00, 0xFF] instead of Apple's [0, 0x100] + // We store the volume in case SetVolume is called before the sound starts + + _volume = (volume == 0x100) ? 0xFF : volume; + g_system->getMixer()->setChannelVolume(_handle, _volume); +} + +bool Sound::isPlaying() { + return isSoundLoaded() && g_system->getMixer()->isSoundHandleActive(_handle); +} + +bool Sound::isSoundLoaded() const { + return _stream != 0; +} + +SoundTimeBase::SoundTimeBase() { + setScale(600); + _startScale = 600; + _stopScale = 600; + _setToStart = false; +} + +void SoundTimeBase::playSoundSegment(uint32 startTime, uint32 endTime) { + _startTime = startTime; + _stopTime = endTime; + _setToStart = true; + _time = Common::Rational(startTime, getScale()); + setRate(1); + Sound::playSoundSegment(startTime, endTime); +} + +void SoundTimeBase::updateTime() { + if (_setToStart) { + if (isPlaying()) { + // Not at the end, let's get the time + uint numFrames = g_system->getMixer()->getSoundElapsedTime(_handle) * 600 / 1000; + + // WORKAROUND: Our mixer is woefully inaccurate and quite often returns + // times that exceed the actual length of the clip. We'll just fake times + // that are under the final time to ensure any trigger for the end time is + // only sent when the sound has actually stopped. + if (numFrames >= (_stopTime - _startTime)) + numFrames = _stopTime - _startTime - 1; + + _time = Common::Rational(_startTime + numFrames, getScale()); + } else { + // Assume we reached the end + _setToStart = false; + _time = Common::Rational(_stopTime, getScale()); + } + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/sound.h b/engines/pegasus/sound.h new file mode 100644 index 0000000000..57cfd52e41 --- /dev/null +++ b/engines/pegasus/sound.h @@ -0,0 +1,108 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_SOUND_H +#define PEGASUS_SOUND_H + +#include "audio/mixer.h" +#include "common/str.h" +#include "pegasus/timers.h" + +namespace Audio { + class SeekableAudioStream; +} + +namespace Pegasus { + +class SoundFader; + +// Things you might want to do with sound: +// - Start it +// - Stop it +// - Loop it +// - Pause it +// - Set the volume +// - Set the pitch (rate) +// - Pan the sound +// - Change these settings dynamically over time + +class Sound { +public: + Sound(); + ~Sound(); + + // We only have one access point here because we should + // only be opening an AIFF file from a file name. We're + // not using the resource fork string resources. + void initFromAIFFFile(const Common::String &fileName); + + // Unlike the original game, we're going to use a regular + // audio stream for sound spots. The original treated them + // as movies. + void initFromQuickTime(const Common::String &fileName); + + void disposeSound(); + bool isSoundLoaded() const; + void playSound(); + void loopSound(); + void playSoundSegment(uint32 start, uint32 end); + void stopSound(); + void setVolume(const uint16 volume); + bool isPlaying(); + + void attachFader(SoundFader *fader); + +protected: + Audio::SeekableAudioStream *_stream; + Audio::SoundHandle _handle; + byte _volume; + + SoundFader *_fader; +}; + +// TODO: Make this class follow TimeBase better +// Right now it's just a loose wrapper to plug callbacks +// into sounds. Since this is only used for spot sounds, +// I'm not too worried about it right now as its usage +// is very limited. +// At the very least, the regular TimeBase functions for +// setting/getting should be neutered. +class SoundTimeBase : public Sound, public TimeBase { +public: + SoundTimeBase(); + ~SoundTimeBase() {} + + void playSoundSegment(uint32 start, uint32 end); + +protected: + void updateTime(); + +private: + bool _setToStart; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/surface.cpp b/engines/pegasus/surface.cpp new file mode 100644 index 0000000000..897305f2a3 --- /dev/null +++ b/engines/pegasus/surface.cpp @@ -0,0 +1,392 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/file.h" +#include "common/macresman.h" +#include "common/stream.h" +#include "common/system.h" +#include "graphics/surface.h" +#include "graphics/decoders/pict.h" +#include "video/video_decoder.h" + +#include "pegasus/pegasus.h" +#include "pegasus/surface.h" + +namespace Pegasus { + +Surface::Surface() { + _ownsSurface = false; + _surface = 0; +} + +Surface::~Surface() { + deallocateSurface(); +} + +void Surface::deallocateSurface() { + if (_surface) { + if (_ownsSurface) { + _surface->free(); + delete _surface; + } + + _surface = 0; + _bounds = Common::Rect(); + _ownsSurface = false; + } +} + +void Surface::shareSurface(Surface *surface) { + deallocateSurface(); + + if (surface) { + _ownsSurface = false; + _surface = surface->getSurface(); + surface->getSurfaceBounds(_bounds); + } +} + +void Surface::allocateSurface(const Common::Rect &bounds) { + deallocateSurface(); + + if (bounds.isEmpty()) + return; + + _bounds = bounds; + _surface = new Graphics::Surface(); + _surface->create(bounds.width(), bounds.height(), g_system->getScreenFormat()); + _ownsSurface = true; +} + +void Surface::getImageFromPICTFile(const Common::String &fileName) { + Common::File pict; + if (!pict.open(fileName)) + error("Could not open picture '%s'", fileName.c_str()); + + getImageFromPICTStream(&pict); +} + +void Surface::getImageFromPICTResource(Common::MacResManager *resFork, uint16 id) { + Common::SeekableReadStream *res = resFork->getResource(MKTAG('P', 'I', 'C', 'T'), id); + if (!res) + error("Could not open PICT resource %d from '%s'", id, resFork->getBaseFileName().c_str()); + + getImageFromPICTStream(res); + delete res; +} + +void Surface::getImageFromPICTStream(Common::SeekableReadStream *stream) { + Graphics::PICTDecoder pict; + + if (!pict.loadStream(*stream)) + error("Failed to load PICT image"); + + _surface = pict.getSurface()->convertTo(g_system->getScreenFormat(), pict.getPalette()); + _ownsSurface = true; + _bounds = Common::Rect(0, 0, _surface->w, _surface->h); +} + +void Surface::getImageFromMovieFrame(Video::SeekableVideoDecoder *video, TimeValue time) { + video->seekToTime(Audio::Timestamp(0, time, 600)); + const Graphics::Surface *frame = video->decodeNextFrame(); + + if (frame) { + if (!_surface) + _surface = new Graphics::Surface(); + + _surface->copyFrom(*frame); + _ownsSurface = true; + _bounds = Common::Rect(0, 0, _surface->w, _surface->h); + } else { + deallocateSurface(); + } +} + +void Surface::copyToCurrentPort() const { + copyToCurrentPort(_bounds); +} + +void Surface::copyToCurrentPortTransparent() const { + copyToCurrentPortTransparent(_bounds); +} + +void Surface::copyToCurrentPort(const Common::Rect &rect) const { + copyToCurrentPort(rect, rect); +} + +void Surface::copyToCurrentPortTransparent(const Common::Rect &rect) const { + copyToCurrentPortTransparent(rect, rect); +} + +void Surface::copyToCurrentPort(const Common::Rect &srcRect, const Common::Rect &dstRect) const { + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getCurSurface(); + byte *src = (byte *)_surface->getBasePtr(srcRect.left, srcRect.top); + byte *dst = (byte *)screen->getBasePtr(dstRect.left, dstRect.top); + + int lineSize = srcRect.width() * _surface->format.bytesPerPixel; + + for (int y = 0; y < srcRect.height(); y++) { + memcpy(dst, src, lineSize); + src += _surface->pitch; + dst += screen->pitch; + } +} + +void Surface::copyToCurrentPortTransparent(const Common::Rect &srcRect, const Common::Rect &dstRect) const { + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getCurSurface(); + byte *src = (byte *)_surface->getBasePtr(srcRect.left, srcRect.top); + byte *dst = (byte *)screen->getBasePtr(dstRect.left, dstRect.top); + + int lineSize = srcRect.width() * _surface->format.bytesPerPixel; + + for (int y = 0; y < srcRect.height(); y++) { + for (int x = 0; x < srcRect.width(); x++) { + if (g_system->getScreenFormat().bytesPerPixel == 2) { + uint16 color = READ_UINT16(src); + if (!isTransparent(color)) + memcpy(dst, src, 2); + } else if (g_system->getScreenFormat().bytesPerPixel == 4) { + uint32 color = READ_UINT32(src); + if (!isTransparent(color)) + memcpy(dst, src, 4); + } + + src += g_system->getScreenFormat().bytesPerPixel; + dst += g_system->getScreenFormat().bytesPerPixel; + } + + src += _surface->pitch - lineSize; + dst += screen->pitch - lineSize; + } +} + +void Surface::copyToCurrentPortMasked(const Common::Rect &srcRect, const Common::Rect &dstRect, const Surface *mask) const { + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getCurSurface(); + byte *src = (byte *)_surface->getBasePtr(srcRect.left, srcRect.top); + byte *dst = (byte *)screen->getBasePtr(dstRect.left, dstRect.top); + + int lineSize = srcRect.width() * _surface->format.bytesPerPixel; + + for (int y = 0; y < srcRect.height(); y++) { + byte *maskSrc = (byte *)mask->getSurface()->getBasePtr(0, y); + + for (int x = 0; x < srcRect.width(); x++) { + if (g_system->getScreenFormat().bytesPerPixel == 2) { + uint16 color = READ_UINT16(maskSrc); + if (!isTransparent(color)) + memcpy(dst, src, 2); + } else if (g_system->getScreenFormat().bytesPerPixel == 4) { + uint32 color = READ_UINT32(maskSrc); + if (!isTransparent(color)) + memcpy(dst, src, 4); + } + + src += g_system->getScreenFormat().bytesPerPixel; + maskSrc += g_system->getScreenFormat().bytesPerPixel; + dst += g_system->getScreenFormat().bytesPerPixel; + } + + src += _surface->pitch - lineSize; + dst += screen->pitch - lineSize; + } +} + +void Surface::copyToCurrentPortTransparentGlow(const Common::Rect &srcRect, const Common::Rect &dstRect) const { + // This is the same as copyToCurrentPortTransparent(), but turns the red value of each + // pixel all the way up. + + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getCurSurface(); + byte *src = (byte *)_surface->getBasePtr(srcRect.left, srcRect.top); + byte *dst = (byte *)screen->getBasePtr(dstRect.left, dstRect.top); + + int lineSize = srcRect.width() * _surface->format.bytesPerPixel; + + for (int y = 0; y < srcRect.height(); y++) { + for (int x = 0; x < srcRect.width(); x++) { + if (g_system->getScreenFormat().bytesPerPixel == 2) { + uint16 color = READ_UINT16(src); + if (!isTransparent(color)) + WRITE_UINT16(dst, getGlowColor(color)); + } else if (g_system->getScreenFormat().bytesPerPixel == 4) { + uint32 color = READ_UINT32(src); + if (!isTransparent(color)) + WRITE_UINT32(dst, getGlowColor(color)); + } + + src += g_system->getScreenFormat().bytesPerPixel; + dst += g_system->getScreenFormat().bytesPerPixel; + } + + src += _surface->pitch - lineSize; + dst += screen->pitch - lineSize; + } +} + +void Surface::scaleTransparentCopy(const Common::Rect &srcRect, const Common::Rect &dstRect) const { + // I'm doing simple linear scaling here + // dstRect(x, y) = srcRect(x * srcW / dstW, y * srcH / dstH); + + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getCurSurface(); + + int srcW = srcRect.width(); + int srcH = srcRect.height(); + int dstW = dstRect.width(); + int dstH = dstRect.height(); + + for (int y = 0; y < dstH; y++) { + for (int x = 0; x < dstW; x++) { + if (g_system->getScreenFormat().bytesPerPixel == 2) { + uint16 color = READ_UINT16((byte *)_surface->getBasePtr( + x * srcW / dstW + srcRect.left, + y * srcH / dstH + srcRect.top)); + if (!isTransparent(color)) + WRITE_UINT16((byte *)screen->getBasePtr(x + dstRect.left, y + dstRect.top), color); + } else if (g_system->getScreenFormat().bytesPerPixel == 4) { + uint32 color = READ_UINT32((byte *)_surface->getBasePtr( + x * srcW / dstW + srcRect.left, + y * srcH / dstH + srcRect.top)); + if (!isTransparent(color)) + WRITE_UINT32((byte *)screen->getBasePtr(x + dstRect.left, y + dstRect.top), color); + } + } + } +} + +void Surface::scaleTransparentCopyGlow(const Common::Rect &srcRect, const Common::Rect &dstRect) const { + // This is the same as scaleTransparentCopy(), but turns the red value of each + // pixel all the way up. + + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getCurSurface(); + + int srcW = srcRect.width(); + int srcH = srcRect.height(); + int dstW = dstRect.width(); + int dstH = dstRect.height(); + + for (int y = 0; y < dstH; y++) { + for (int x = 0; x < dstW; x++) { + if (g_system->getScreenFormat().bytesPerPixel == 2) { + uint16 color = READ_UINT16((byte *)_surface->getBasePtr( + x * srcW / dstW + srcRect.left, + y * srcH / dstH + srcRect.top)); + if (!isTransparent(color)) + WRITE_UINT16((byte *)screen->getBasePtr(x + dstRect.left, y + dstRect.top), getGlowColor(color)); + } else if (g_system->getScreenFormat().bytesPerPixel == 4) { + uint32 color = READ_UINT32((byte *)_surface->getBasePtr( + x * srcW / dstW + srcRect.left, + y * srcH / dstH + srcRect.top)); + if (!isTransparent(color)) + WRITE_UINT32((byte *)screen->getBasePtr(x + dstRect.left, y + dstRect.top), getGlowColor(color)); + } + } + } +} + +uint32 Surface::getGlowColor(uint32 color) const { + // Can't just 'or' it on like the original did :P + byte r, g, b; + g_system->getScreenFormat().colorToRGB(color, r, g, b); + return g_system->getScreenFormat().RGBToColor(0xff, g, b); +} + +bool Surface::isTransparent(uint32 color) const { + // HACK: Seems we're truncating some color data somewhere... + uint32 transColor1 = g_system->getScreenFormat().RGBToColor(0xff, 0xff, 0xff); + uint32 transColor2 = g_system->getScreenFormat().RGBToColor(0xf8, 0xf8, 0xf8); + + return color == transColor1 || color == transColor2; +} + +PixelImage::PixelImage() { + _transparent = false; +} + +void PixelImage::drawImage(const Common::Rect &sourceBounds, const Common::Rect &destBounds) { + if (!isSurfaceValid()) + return; + + // Draw from sourceBounds to destBounds based on _transparent + if (_transparent) + copyToCurrentPortTransparent(sourceBounds, destBounds); + else + copyToCurrentPort(sourceBounds, destBounds); +} + +void Frame::initFromPICTFile(const Common::String &fileName, bool transparent) { + getImageFromPICTFile(fileName); + _transparent = transparent; +} + +void Frame::initFromPICTResource(Common::MacResManager *resFork, uint16 id, bool transparent) { + getImageFromPICTResource(resFork, id); + _transparent = transparent; +} + +void Frame::initFromMovieFrame(Video::SeekableVideoDecoder *video, TimeValue time, bool transparent) { + getImageFromMovieFrame(video, time); + _transparent = transparent; +} + +void Picture::draw(const Common::Rect &r) { + Common::Rect surfaceBounds; + getSurfaceBounds(surfaceBounds); + Common::Rect r1 = r; + + Common::Rect bounds; + getBounds(bounds); + surfaceBounds.moveTo(bounds.left, bounds.top); + r1 = r1.findIntersectingRect(surfaceBounds); + getSurfaceBounds(surfaceBounds); + + Common::Rect r2 = r1; + r2.translate(surfaceBounds.left - bounds.left, surfaceBounds.top - bounds.top); + drawImage(r2, r1); +} + +void Picture::initFromPICTFile(const Common::String &fileName, bool transparent) { + Frame::initFromPICTFile(fileName, transparent); + + Common::Rect surfaceBounds; + getSurfaceBounds(surfaceBounds); + sizeElement(surfaceBounds.width(), surfaceBounds.height()); +} + +void Picture::initFromPICTResource(Common::MacResManager *resFork, uint16 id, bool transparent) { + Frame::initFromPICTResource(resFork, id, transparent); + + Common::Rect surfaceBounds; + getSurfaceBounds(surfaceBounds); + sizeElement(surfaceBounds.width(), surfaceBounds.height()); +} + +void Picture::initFromMovieFrame(Video::SeekableVideoDecoder *video, TimeValue time, bool transparent) { + Frame::initFromMovieFrame(video, time, transparent); + + Common::Rect surfaceBounds; + getSurfaceBounds(surfaceBounds); + sizeElement(surfaceBounds.width(), surfaceBounds.height()); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/surface.h b/engines/pegasus/surface.h new file mode 100644 index 0000000000..34a88dbd53 --- /dev/null +++ b/engines/pegasus/surface.h @@ -0,0 +1,140 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_SURFACE_H +#define PEGASUS_SURFACE_H + +#include "common/rect.h" +#include "common/str.h" + +#include "pegasus/elements.h" +#include "pegasus/types.h" + +namespace Common { + class MacResManager; +} + +namespace Graphics { + struct Surface; +} + +namespace Video { + class SeekableVideoDecoder; +} + +namespace Pegasus { + +// Surface bounds are always normalized. + +class Surface { +public: + Surface(); + virtual ~Surface(); + + virtual void allocateSurface(const Common::Rect &); + virtual void deallocateSurface(); + virtual void shareSurface(Surface *surface); + bool isSurfaceValid() const { return _surface != 0; } + + Graphics::Surface *getSurface() const { return _surface; } + void getSurfaceBounds(Common::Rect &r) { r = _bounds; } + + // None of the copyToCurrentPort* functions do any sanity checks. + // It's up to clients to make sure that the Surface is valid. + void copyToCurrentPort() const; + void copyToCurrentPortTransparent() const; + void copyToCurrentPort(const Common::Rect &) const; + void copyToCurrentPortTransparent(const Common::Rect &) const; + void copyToCurrentPort(const Common::Rect &, const Common::Rect &) const; + void copyToCurrentPortTransparent(const Common::Rect &, const Common::Rect &) const; + void copyToCurrentPortMasked(const Common::Rect &, const Common::Rect &, const Surface *) const; + void copyToCurrentPortTransparentGlow(const Common::Rect &, const Common::Rect &) const; + void scaleTransparentCopy(const Common::Rect &, const Common::Rect &) const; + void scaleTransparentCopyGlow(const Common::Rect &, const Common::Rect &) const; + + virtual void getImageFromPICTFile(const Common::String &fileName); + virtual void getImageFromPICTResource(Common::MacResManager *resFork, uint16 id); + virtual void getImageFromMovieFrame(Video::SeekableVideoDecoder *, TimeValue); + +protected: + bool _ownsSurface; + Graphics::Surface *_surface; + Common::Rect _bounds; + +private: + void getImageFromPICTStream(Common::SeekableReadStream *stream); + + uint32 getGlowColor(uint32 color) const; + bool isTransparent(uint32 color) const; +}; + +class PixelImage : public Surface { +public: + PixelImage(); + virtual ~PixelImage() {} + + void drawImage(const Common::Rect &, const Common::Rect &); + +protected: + virtual void setTransparent(bool transparent) { _transparent = transparent; } + + bool _transparent; +}; + +class Frame : public PixelImage { +public: + Frame() {} + virtual ~Frame() {} + + virtual void initFromPICTFile(const Common::String &fileName, bool transparent = false); + virtual void initFromPICTResource(Common::MacResManager *resFork, uint16 id, bool transparent = false); + virtual void initFromMovieFrame(Video::SeekableVideoDecoder *, TimeValue, bool transparent = false); +}; + +class SpriteFrame : public Frame { +friend class Sprite; +public: + SpriteFrame() { _referenceCount = 0; } + virtual ~SpriteFrame() {} + +protected: + uint32 _referenceCount; +}; + +class Picture : public DisplayElement, public Frame { +public: + Picture(const DisplayElementID id) : DisplayElement(id) {} + virtual ~Picture() {} + + virtual void initFromPICTFile(const Common::String &fileName, bool transparent = false); + virtual void initFromPICTResource(Common::MacResManager *resFork, uint16 id, bool transparent = false); + virtual void initFromMovieFrame(Video::SeekableVideoDecoder *, TimeValue, bool transparent = false); + + virtual void draw(const Common::Rect &); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/timers.cpp b/engines/pegasus/timers.cpp new file mode 100644 index 0000000000..d03ce917ed --- /dev/null +++ b/engines/pegasus/timers.cpp @@ -0,0 +1,429 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "pegasus/pegasus.h" +#include "pegasus/notification.h" +#include "pegasus/timers.h" + +namespace Pegasus { + +Idler::Idler() { + _isIdling = false; + _nextIdler = 0; + _prevIdler = 0; +} + +Idler::~Idler() { + stopIdling(); +} + +void Idler::startIdling() { + if (!isIdling()) { + ((PegasusEngine *)g_engine)->addIdler(this); + _isIdling = true; + } +} + +void Idler::stopIdling() { + if (isIdling()) { + ((PegasusEngine *)g_engine)->removeIdler(this); + _isIdling = false; + } +} + +TimeBase::TimeBase(const TimeScale preferredScale) { + _preferredScale = preferredScale; + _callBackList = 0; + _paused = false; + _flags = 0; + _lastMillis = 0; + _time = 0; + _rate = 0; + _startTime = 0; + _startScale = 1; + _stopTime = 0xffffffff; + _stopScale = 1; + _master = 0; + _pausedRate = 0; + _pauseStart = 0; + + ((PegasusEngine *)g_engine)->addTimeBase(this); +} + +TimeBase::~TimeBase() { + if (_master) + _master->_slaves.remove(this); + + ((PegasusEngine *)g_engine)->removeTimeBase(this); + disposeAllCallBacks(); + + // TODO: Remove slaves? Make them remove themselves? +} + +void TimeBase::setTime(const TimeValue time, const TimeScale scale) { + _time = Common::Rational(time, (scale == 0) ? _preferredScale : scale); + _lastMillis = 0; +} + +TimeValue TimeBase::getTime(const TimeScale scale) { + return _time.getNumerator() * ((scale == 0) ? _preferredScale : scale) / _time.getDenominator(); +} + +void TimeBase::setRate(const Common::Rational rate) { + _rate = rate; + + if (_rate == 0) + _paused = false; +} + +Common::Rational TimeBase::getEffectiveRate() const { + return _rate * ((_master == 0) ? 1 : _master->getEffectiveRate()); +} + +void TimeBase::start() { + if (_paused) + _pausedRate = 1; + else + setRate(1); +} + +void TimeBase::stop() { + setRate(0); + _paused = false; +} + +void TimeBase::pause() { + if (isRunning() && !_paused) { + _pausedRate = getRate(); + stop(); + _paused = true; + _pauseStart = g_system->getMillis(); + } +} + +void TimeBase::resume() { + if (_paused) { + setRate(_pausedRate); + _paused = false; + + if (isRunning()) + _lastMillis += g_system->getMillis() - _pauseStart; + } +} + +bool TimeBase::isRunning() { + if (_paused && _pausedRate != 0) + return true; + + Common::Rational rate = getRate(); + + if (rate == 0) + return false; + + if (getFlags() & kLoopTimeBase) + return true; + + if (rate > 0) + return getTime() != getStop(); + + return getTime() != getStart(); +} + +void TimeBase::setStart(const TimeValue startTime, const TimeScale scale) { + _startTime = startTime; + _startScale = (scale == 0) ? _preferredScale : scale; +} + +TimeValue TimeBase::getStart(const TimeScale scale) const { + if (scale) + return _startTime * scale / _startScale; + + return _startTime * _preferredScale / _startScale; +} + +void TimeBase::setStop(const TimeValue stopTime, const TimeScale scale) { + _stopTime = stopTime; + _stopScale = (scale == 0) ? _preferredScale : scale; +} + +TimeValue TimeBase::getStop(const TimeScale scale) const { + if (scale) + return _stopTime * scale / _stopScale; + + return _stopTime * _preferredScale / _stopScale; +} + +void TimeBase::setSegment(const TimeValue startTime, const TimeValue stopTime, const TimeScale scale) { + setStart(startTime, scale); + setStop(stopTime, scale); +} + +void TimeBase::getSegment(TimeValue &startTime, TimeValue &stopTime, const TimeScale scale) const { + startTime = getStart(scale); + stopTime = getStop(scale); +} + +TimeValue TimeBase::getDuration(const TimeScale scale) const { + TimeValue startTime, stopTime; + getSegment(startTime, stopTime, scale); + return stopTime - startTime; +} + +void TimeBase::setMasterTimeBase(TimeBase *tb) { + // TODO: We're just ignoring the master (except for effective rate) + // for now to simplify things + if (_master) + _master->_slaves.remove(this); + + _master = tb; + + if (_master) + _master->_slaves.push_back(this); +} + +void TimeBase::updateTime() { + if (_lastMillis == 0) { + _lastMillis = g_system->getMillis(); + } else { + uint32 curTime = g_system->getMillis(); + if (_lastMillis == curTime) // No change + return; + + _time += Common::Rational(curTime - _lastMillis, 1000) * getEffectiveRate(); + _lastMillis = curTime; + } +} + +void TimeBase::checkCallBacks() { + // Nothing to do if we're paused or not running + if (_paused || !isRunning()) + return; + + Common::Rational startTime = Common::Rational(_startTime, _startScale); + Common::Rational stopTime = Common::Rational(_stopTime, _stopScale); + + // First step: update the times + updateTime(); + + // Clip time to the boundaries + if (_time >= stopTime) + _time = stopTime; + else if (_time <= startTime) + _time = startTime; + + // TODO: Update the slaves? + + Common::Rational time = Common::Rational(getTime(), getScale()); + + // Check if we've triggered any callbacks + for (TimeBaseCallBack *runner = _callBackList; runner != 0; runner = runner->_nextCallBack) { + if (runner->_hasBeenTriggered) + continue; + + if (runner->_type == kCallBackAtTime && runner->_trigger == kTriggerTimeFwd) { + if (getTime() >= (runner->_param2 * _preferredScale / runner->_param3) && getRate() > 0) { + uint param2 = runner->_param2, param3 = runner->_param3; + runner->callBack(); + // HACK: Only stop future time forward callbacks if the parameters have not been changed + // This fixes striding callbacks. Since only striding callbacks do this kind of + // craziness, I'm not too worried about this. + runner->_hasBeenTriggered = (runner->_param2 == param2 && runner->_param3 == param3); + } + } else if (runner->_type == kCallBackAtExtremes) { + if (runner->_trigger == kTriggerAtStop) { + if (time == stopTime) { + runner->callBack(); + runner->_hasBeenTriggered = true; + } + } else if (runner->_trigger == kTriggerAtStart) { + if (time == startTime) { + runner->callBack(); + runner->_hasBeenTriggered = true; + } + } + } + } + + if (getFlags() & kLoopTimeBase) { + // Loop if necessary + if (getRate() < 0 && time == startTime) + setTime(_stopTime, _stopScale); + else if (getRate() > 0 && time == stopTime) + setTime(_startTime, _startScale); + } else { + // Stop at the end + if ((getRate() > 0 && time == stopTime) || (getRate() < 0 && time == startTime)) + stop(); + } +} + +// Protected functions only called by TimeBaseCallBack. + +void TimeBase::addCallBack(TimeBaseCallBack *callBack) { + callBack->_nextCallBack = _callBackList; + _callBackList = callBack; +} + +void TimeBase::removeCallBack(TimeBaseCallBack *callBack) { + if (_callBackList == callBack) { + _callBackList = callBack->_nextCallBack; + } else { + TimeBaseCallBack *runner, *prevRunner; + + for (runner = _callBackList->_nextCallBack, prevRunner = _callBackList; runner != callBack; prevRunner = runner, runner = runner->_nextCallBack) + ; + + prevRunner->_nextCallBack = runner->_nextCallBack; + } + + callBack->_nextCallBack = 0; +} + +void TimeBase::disposeAllCallBacks() { + TimeBaseCallBack *nextRunner; + + for (TimeBaseCallBack *runner = _callBackList; runner != 0; runner = nextRunner) { + nextRunner = runner->_nextCallBack; + runner->disposeCallBack(); + runner->_nextCallBack = 0; + } + + _callBackList = 0; +} + +TimeBaseCallBack::TimeBaseCallBack() { + _timeBase = 0; + _nextCallBack = 0; + _trigger = kTriggerNone; + _type = kCallBackNone; + _hasBeenTriggered = false; +} + +TimeBaseCallBack::~TimeBaseCallBack() { + releaseCallBack(); +} + +void TimeBaseCallBack::initCallBack(TimeBase *tb, CallBackType type) { + releaseCallBack(); + _timeBase = tb; + _timeBase->addCallBack(this); + _type = type; +} + +void TimeBaseCallBack::releaseCallBack() { + if (_timeBase) + _timeBase->removeCallBack(this); + disposeCallBack(); +} + +void TimeBaseCallBack::disposeCallBack() { + _timeBase = 0; + _hasBeenTriggered = false; +} + +void TimeBaseCallBack::scheduleCallBack(CallBackTrigger trigger, uint32 param2, uint32 param3) { + // TODO: Rename param2/param3? + _trigger = trigger; + _param2 = param2; + _param3 = param3; + _hasBeenTriggered = false; +} + +void TimeBaseCallBack::cancelCallBack() { + _trigger = kTriggerNone; + _hasBeenTriggered = false; +} + +IdlerTimeBase::IdlerTimeBase() { + _lastTime = 0xffffffff; + startIdling(); +} + +void IdlerTimeBase::useIdleTime() { + uint32 currentTime = getTime(); + if (currentTime != _lastTime) { + _lastTime = currentTime; + timeChanged(_lastTime); + } +} + +NotificationCallBack::NotificationCallBack() { + _callBackFlag = 0; + _notifier = 0; +} + +void NotificationCallBack::callBack() { + if (_notifier) + _notifier->setNotificationFlags(_callBackFlag, _callBackFlag); +} + +static const NotificationFlags kFuseExpiredFlag = 1; + +Fuse::Fuse() : _fuseNotification(0, (NotificationManager *)((PegasusEngine *)g_engine)) { + _fuseNotification.notifyMe(this, kFuseExpiredFlag, kFuseExpiredFlag); + _fuseCallBack.setNotification(&_fuseNotification); + _fuseCallBack.initCallBack(&_fuseTimer, kCallBackAtExtremes); + _fuseCallBack.setCallBackFlag(kFuseExpiredFlag); +} + +void Fuse::primeFuse(const TimeValue frequency, const TimeScale scale) { + stopFuse(); + _fuseTimer.setScale(scale); + _fuseTimer.setSegment(0, frequency); + _fuseTimer.setTime(0); +} + +void Fuse::lightFuse() { + if (!_fuseTimer.isRunning()) { + _fuseCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _fuseTimer.start(); + } +} + +void Fuse::stopFuse() { + _fuseTimer.stop(); + _fuseCallBack.cancelCallBack(); + // Make sure the fuse has not triggered but not been caught yet... + _fuseNotification.setNotificationFlags(0, 0xffffffff); +} + +void Fuse::advanceFuse(const TimeValue time) { + if (_fuseTimer.isRunning()) { + _fuseTimer.stop(); + _fuseTimer.setTime(_fuseTimer.getTime() + time); + _fuseTimer.start(); + } +} + +TimeValue Fuse::getTimeRemaining() { + return _fuseTimer.getStop() - _fuseTimer.getTime(); +} + +void Fuse::receiveNotification(Notification *, const NotificationFlags) { + stopFuse(); + invokeAction(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/timers.h b/engines/pegasus/timers.h new file mode 100644 index 0000000000..1dd32de3c9 --- /dev/null +++ b/engines/pegasus/timers.h @@ -0,0 +1,256 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_TIMERS_H +#define PEGASUS_TIMERS_H + +#include "common/list.h" +#include "common/rational.h" + +#include "pegasus/constants.h" +#include "pegasus/notification.h" +#include "pegasus/util.h" + +namespace Pegasus { + +class Idler { +friend class PegasusEngine; + +public: + Idler(); + virtual ~Idler(); + + virtual void startIdling(); + virtual void stopIdling(); + bool isIdling() const { return _isIdling; } + +protected: + virtual void useIdleTime() {} + + bool _isIdling; + Idler *_nextIdler, *_prevIdler; +}; + +enum { + kLoopTimeBase = 1, + kPalindromeLoopTimeBase = 2, + kMaintainTimeBaseZero = 4 +}; + +class TimeBaseCallBack; + +class TimeBase { +friend class TimeBaseCallBack; +public: + TimeBase(const TimeScale = kDefaultTimeScale); + virtual ~TimeBase(); + + virtual void setTime(const TimeValue, const TimeScale = 0); + virtual TimeValue getTime(const TimeScale = 0); + + virtual void setScale(const TimeScale scale) { _preferredScale = scale; } + virtual TimeScale getScale() const { return _preferredScale; } + + virtual void setRate(const Common::Rational); + virtual Common::Rational getRate() const { return _rate; } + + virtual void start(); + virtual void stop(); + virtual bool isRunning(); + + virtual void pause(); + virtual void resume(); + bool isPaused() const { return _paused; } + + virtual void setFlags(const uint32 flags) { _flags = flags; } + virtual uint32 getFlags() const { return _flags; } + + virtual void setStart(const TimeValue, const TimeScale = 0); + virtual TimeValue getStart(const TimeScale = 0) const; + + virtual void setStop(const TimeValue, const TimeScale = 0); + virtual TimeValue getStop(const TimeScale = 0) const; + + virtual void setSegment(const TimeValue, const TimeValue, const TimeScale = 0); + virtual void getSegment(TimeValue&, TimeValue&, const TimeScale = 0) const; + + virtual TimeValue getDuration(const TimeScale = 0) const; + + virtual void setMasterTimeBase(TimeBase *timeBase); + + void disposeAllCallBacks(); + + // ScummVM's API additions (to replace the need for actual timers) + virtual void checkCallBacks(); + +protected: + void addCallBack(TimeBaseCallBack *); + void removeCallBack(TimeBaseCallBack *); + virtual void updateTime(); + + TimeBase *_master; + TimeScale _preferredScale; + TimeBaseCallBack *_callBackList; + Common::Rational _rate, _pausedRate; + bool _paused; + uint32 _startTime, _startScale; + uint32 _stopTime, _stopScale; + uint32 _flags; + + Common::Rational _time; + uint32 _lastMillis, _pauseStart; + +private: + Common::Rational getEffectiveRate() const; + + Common::List<TimeBase *> _slaves; +}; + +// Type passed to initCallBack() +enum CallBackType { + kCallBackNone = 0, + kCallBackAtTime = 1, + kCallBackAtExtremes = 4 +}; + +// Trigger passed to scheduleCallBack() +enum CallBackTrigger { + kTriggerNone = 0, + + // AtTime flags + kTriggerTimeFwd = 1, + + // AtExtremes flags + kTriggerAtStart = 1, + kTriggerAtStop = 2 +}; + +class TimeBaseCallBack { +friend class TimeBase; + +public: + TimeBaseCallBack(); + virtual ~TimeBaseCallBack(); + + void initCallBack(TimeBase *, CallBackType type); + + void releaseCallBack(); + + void scheduleCallBack(CallBackTrigger trigger, uint32 param2, uint32 param3); + void cancelCallBack(); + +protected: + virtual void callBack() = 0; + + TimeBase *_timeBase; + + // Owned and operated by TimeBase; + TimeBaseCallBack *_nextCallBack; + + // Our storage of the QuickTime timer crap + CallBackType _type; + CallBackTrigger _trigger; + uint32 _param2, _param3; + bool _hasBeenTriggered; + +private: + void disposeCallBack(); +}; + +class IdlerTimeBase : public Idler, public TimeBase { +public: + IdlerTimeBase(); + virtual ~IdlerTimeBase() { stopIdling(); } + + TimeValue getLastTime() const { return _lastTime; } + +protected: + virtual void useIdleTime(); + virtual void timeChanged(const TimeValue) {} + + TimeValue _lastTime; + +}; + +class NotificationCallBack : public TimeBaseCallBack { +public: + NotificationCallBack(); + virtual ~NotificationCallBack() {} + + void setNotification(Notification *notifier) { _notifier = notifier; } + + void setCallBackFlag(const NotificationFlags flag) { _callBackFlag = flag; } + NotificationFlags getCallBackFlag() const { return _callBackFlag; } + +protected: + void callBack(); + + Notification *_notifier; + NotificationFlags _callBackFlag; +}; + +class DynamicElement : public TimeBase { +public: + TimeValue percentSeconds(const uint32 percent) { return getScale() * percent / 100; } +}; + +class Fuse : private NotificationReceiver { +public: + Fuse(); + virtual ~Fuse() {} + + void primeFuse(const TimeValue, const TimeScale = 1); // An appropriately named function :P + void lightFuse(); + void stopFuse(); + bool isFuseLit() { return _fuseTimer.isRunning() || _fuseTimer.isPaused(); } + void advanceFuse(const TimeValue); + TimeValue getTimeRemaining(); + TimeScale getFuseScale() { return _fuseTimer.getScale(); } + + void pauseFuse() { _fuseTimer.pause(); } + void resumeFuse() { _fuseTimer.resume(); } + bool isFusePaused() { return _fuseTimer.isPaused(); } + +protected: + virtual void receiveNotification(Notification *, const NotificationFlags); + virtual void invokeAction() {} + + TimeBase _fuseTimer; + NotificationCallBack _fuseCallBack; + Notification _fuseNotification; +}; + +class FuseFunction : public Fuse, public FunctionPtr { +public: + FuseFunction() {} + virtual ~FuseFunction() {} + +protected: + virtual void invokeAction() { callFunction(); } +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/transition.cpp b/engines/pegasus/transition.cpp new file mode 100644 index 0000000000..24e9aaf4a2 --- /dev/null +++ b/engines/pegasus/transition.cpp @@ -0,0 +1,150 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/system.h" + +#include "pegasus/transition.h" + +namespace Pegasus { + +ScreenFader::ScreenFader() { + _fadeTarget = getBlack(); + // Initially, assume screens are on at full brightness. + Fader::setFaderValue(100); +} + +void ScreenFader::doFadeOutSync(const TimeValue duration, const TimeValue scale, const uint32 fadeTarget) { + _fadeTarget = fadeTarget; + + FaderMoveSpec spec; + spec.makeTwoKnotFaderSpec(scale, 0, getFaderValue(), duration, 0); + startFaderSync(spec); +} + +void ScreenFader::doFadeInSync(const TimeValue duration, const TimeValue scale, const uint32 fadeTarget) { + _fadeTarget = fadeTarget; + + FaderMoveSpec spec; + spec.makeTwoKnotFaderSpec(scale, 0, getFaderValue(), duration, 100); + startFaderSync(spec); +} + +void ScreenFader::setFaderValue(const int32 value) { + if (value != getFaderValue()) { + Fader::setFaderValue(value); + + // TODO: Gamma fading + } +} + +uint32 ScreenFader::getBlack() { + return g_system->getScreenFormat().RGBToColor(0, 0, 0); +} + +Transition::Transition(const DisplayElementID id) : FaderAnimation(id) { + _outPicture = 0; + _inPicture = 0; +} + +void Transition::setBounds(const Common::Rect &r) { + FaderAnimation::setBounds(r); + _boundsWidth = _bounds.width(); + _boundsHeight = _bounds.height(); +} + +void Transition::setInAndOutElements(DisplayElement *inElement, DisplayElement *outElement) { + _inPicture = inElement; + _outPicture = outElement; + + Common::Rect r; + + if (_outPicture) + _outPicture->getBounds(r); + else if (_inPicture) + _inPicture->getBounds(r); + + setBounds(r); +} + +void Slide::draw(const Common::Rect &r) { + Common::Rect oldBounds, newBounds; + + adjustSlideRects(oldBounds, newBounds); + drawElements(r, oldBounds, newBounds); +} + +void Slide::adjustSlideRects(Common::Rect &oldBounds, Common::Rect &newBounds) { + oldBounds = _bounds; + newBounds = _bounds; +} + +void Slide::drawElements(const Common::Rect &drawRect, const Common::Rect &oldBounds, const Common::Rect &newBounds) { + drawSlideElement(drawRect, oldBounds, _outPicture); + drawSlideElement(drawRect, newBounds, _inPicture); +} + +void Slide::drawSlideElement(const Common::Rect &drawRect, const Common::Rect &oldBounds, DisplayElement *picture) { + if (picture && drawRect.intersects(oldBounds)) { + picture->moveElementTo(oldBounds.left, oldBounds.top); + picture->draw(drawRect.findIntersectingRect(oldBounds)); + } +} + +void Push::adjustSlideRects(Common::Rect &oldBounds, Common::Rect &newBounds) { + switch (_direction & kSlideHorizMask) { + case kSlideLeftMask: + newBounds.left = oldBounds.right = _bounds.right - pegasusRound(getFaderValue() * _boundsWidth, kTransitionRange); + newBounds.right = newBounds.left + _boundsWidth; + oldBounds.left = oldBounds.right - _boundsWidth; + break; + case kSlideRightMask: + oldBounds.left = newBounds.right = _bounds.left + pegasusRound(getFaderValue() * _boundsWidth, kTransitionRange); + oldBounds.right = oldBounds.left + _boundsWidth; + newBounds.left = newBounds.right - _boundsWidth; + break; + default: + newBounds.left = oldBounds.left = _bounds.left; + newBounds.right = oldBounds.right = _bounds.right; + break; + } + switch (_direction & kSlideVertMask) { + case kSlideDownMask: + oldBounds.top = newBounds.bottom = _bounds.top + pegasusRound(getFaderValue() * _boundsHeight, kTransitionRange); + oldBounds.bottom = oldBounds.top + _boundsHeight; + newBounds.top = newBounds.bottom - _boundsHeight; + break; + case kSlideUpMask: + newBounds.top = oldBounds.bottom = _bounds.bottom - pegasusRound(getFaderValue() * _boundsHeight, kTransitionRange); + newBounds.bottom = newBounds.top + _boundsHeight; + oldBounds.top = oldBounds.bottom - _boundsHeight; + break; + default: + newBounds.top = oldBounds.top = _bounds.top; + newBounds.bottom = oldBounds.bottom = _bounds.bottom; + break; + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/transition.h b/engines/pegasus/transition.h new file mode 100644 index 0000000000..5f2b797d88 --- /dev/null +++ b/engines/pegasus/transition.h @@ -0,0 +1,105 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_TRANSITION_H +#define PEGASUS_TRANSITION_H + +#include "pegasus/fader.h" + +namespace Pegasus { + +class ScreenFader : public Fader { +public: + ScreenFader(); + virtual ~ScreenFader() {} + + void doFadeOutSync(const TimeValue = kOneSecondPerThirtyTicks, const TimeScale = kThirtyTicksPerSecond, const uint32 = getBlack()); + void doFadeInSync(const TimeValue = kHalfSecondPerThirtyTicks, const TimeScale = kThirtyTicksPerSecond, const uint32 = getBlack()); + + void setFaderValue(const int32); + +protected: + uint32 _fadeTarget; + +private: + static uint32 getBlack(); +}; + +// Transitions are faders that range over [0,1000], which makes their +// "resolution" one tenth of a percent + +static const int kTransitionBottom = 0; +static const int kTransitionTop = 1000; + +static const int kTransitionRange = kTransitionTop - kTransitionBottom; + +class Transition : public FaderAnimation { +public: + Transition(const DisplayElementID id); + virtual ~Transition() {} + + virtual void setBounds(const Common::Rect &); + + virtual void setInAndOutElements(DisplayElement *, DisplayElement *); + DisplayElement *getInElement() { return _inPicture; } + DisplayElement *getOutElement() { return _outPicture; } + +protected: + DisplayElement *_outPicture; + DisplayElement *_inPicture; + + CoordType _boundsWidth, _boundsHeight; +}; + +class Slide : public Transition { +public: + Slide(const DisplayElementID id) : Transition(id) {} + virtual ~Slide() {} + + virtual void setSlideDirection(SlideDirection dir) { _direction = dir; } + virtual void draw(const Common::Rect &); + + virtual void setDirection(const SlideDirection dir) { _direction = dir; } + +protected: + virtual void adjustSlideRects(Common::Rect &, Common::Rect &); + virtual void drawElements(const Common::Rect &, const Common::Rect &, const Common::Rect &); + virtual void drawSlideElement(const Common::Rect &, const Common::Rect &, DisplayElement *); + + SlideDirection _direction; +}; + +class Push : public Slide { +public: + Push(const DisplayElementID id) : Slide(id) {} + virtual ~Push() {} + +protected: + virtual void adjustSlideRects(Common::Rect &, Common::Rect &); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/types.h b/engines/pegasus/types.h new file mode 100644 index 0000000000..73a4e98491 --- /dev/null +++ b/engines/pegasus/types.h @@ -0,0 +1,161 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_TYPES_H +#define PEGASUS_TYPES_H + +#include "common/scummsys.h" + +namespace Pegasus { + +// TODO: Probably all of these don't really need to be typedef'd... + +typedef int32 DisplayElementID; +typedef int32 DisplayOrder; + +typedef int16 HotSpotID; +typedef uint32 HotSpotFlags; + +typedef byte ButtonState; +typedef uint32 InputBits; + +typedef int32 NotificationID; +typedef uint32 NotificationFlags; + +// Mac types. +typedef int16 ResIDType; +typedef int16 CoordType; + +enum SlideDirection { + kSlideLeftMask = 1, + kSlideRightMask = kSlideLeftMask << 1, + kSlideUpMask = kSlideRightMask << 1 << 1, + kSlideDownMask = kSlideUpMask << 1, + + kSlideHorizMask = kSlideLeftMask | kSlideRightMask, + kSlideVertMask = kSlideUpMask | kSlideDownMask, + + kSlideUpLeftMask = kSlideLeftMask | kSlideUpMask, + kSlideUpRightMask = kSlideRightMask | kSlideUpMask, + kSlideDownLeftMask = kSlideLeftMask | kSlideDownMask, + kSlideDownRightMask = kSlideRightMask | kSlideDownMask +}; + +// ScummVM QuickTime/QuickDraw replacement types +typedef uint TimeValue; +typedef uint TimeScale; + +typedef int16 GameID; + +typedef GameID ItemID; +typedef GameID ActorID; +typedef GameID RoomID; +typedef GameID NeighborhoodID; +typedef byte AlternateID; +typedef int8 HotSpotActivationID; + +typedef int16 WeightType; + +typedef byte DirectionConstant; +typedef byte TurnDirection; + +// Meant to be room in low 16 bits and direction in high 16 bits. +typedef uint32 RoomViewID; + +#define MakeRoomView(room, direction) (((RoomViewID) (room)) | (((RoomViewID) (direction)) << 16)) + +typedef uint32 ExtraID; + +typedef int16 GameMode; + +typedef int16 WeightType; + +typedef int16 ItemState; + +typedef int8 DeathReason; + +typedef int32 GameMenuCommand; + +typedef int32 GameScoreType; + +typedef long CanMoveForwardReason; + +typedef long CanTurnReason; + +typedef long CanOpenDoorReason; + +enum InventoryResult { + kInventoryOK, + kTooMuchWeight, + kItemNotInInventory +}; + +typedef int32 InteractionID; + +typedef int32 AIConditionID; + +enum EnergyStage { + kStageNoStage, + kStageCasual, // more than 50% energy + kStageWorried, // more than 25% energy + kStageNervous, // more than 5% energy + kStagePanicStricken // less than 5% energy +}; + +enum NoradSubPrepState { + kSubNotPrepped, + kSubPrepped, + kSubDamaged +}; + +enum LowerClientSignature { + kNoClientSignature, + kInventorySignature, + kBiochipSignature, + kAISignature +}; + +enum LowerAreaSignature { + kLeftAreaSignature, + kMiddleAreaSignature, + kRightAreaSignature +}; + +enum AirQuality { + kAirQualityGood, + kAirQualityDirty, + kAirQualityVacuum +}; + +enum DragType { + kDragNoDrag, + kDragInventoryPickup, + kDragBiochipPickup, + kDragInventoryUse +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/util.cpp b/engines/pegasus/util.cpp new file mode 100644 index 0000000000..03bc5729cc --- /dev/null +++ b/engines/pegasus/util.cpp @@ -0,0 +1,95 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/random.h" +#include "common/system.h" +#include "common/util.h" + +#include "pegasus/util.h" + +namespace Pegasus { + +IDObject::IDObject(const int32 id) { + _objectID = id; +} + +IDObject::~IDObject() { +} + +int32 IDObject::getObjectID() const { + return _objectID; +} + +int operator==(const IDObject &arg1, const IDObject &arg2) { + return arg1.getObjectID() == arg2.getObjectID(); +} + +int operator!=(const IDObject &arg1, const IDObject &arg2) { + return arg1.getObjectID() != arg2.getObjectID(); +} + +FunctionPtr::FunctionPtr() { + _function = 0; + _functionArg = 0; +} + +FunctionPtr::~FunctionPtr() { +} + +void FunctionPtr::setFunctionPtr(tFunctionPtr function, void *functionArg) { + _function = function; + _functionArg = functionArg; +} + +void FunctionPtr::callFunction() { + if (_function != 0) + (*_function)(this, _functionArg); +} + +int32 pegasusRound(const int32 a, const int32 b) { + if (b < 0) + if (a < 0) + return -((a - (-b >> 1)) / -b); + else + return -((a + (-b >> 1)) / -b); + else + if (a < 0) + return (a - (b >> 1)) / b; + else + return (a + (b >> 1)) / b; +} + +int32 linearInterp(const int32 start1, const int32 stop1, const int32 current1, const int32 start2, const int32 stop2) { + if (start2 == stop2) + return start2; + else + return start2 + pegasusRound((current1 - start1) * (stop2 - start2), (stop1 - start1)); +} + +uint32 tickCount() { + return g_system->getMillis() * 60 / 1000; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/util.h b/engines/pegasus/util.h new file mode 100644 index 0000000000..57839fca7b --- /dev/null +++ b/engines/pegasus/util.h @@ -0,0 +1,135 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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. + * + */ + +#ifndef PEGASUS_UTIL_H +#define PEGASUS_UTIL_H + +#include "common/stream.h" + +#include "pegasus/types.h" + +namespace Common { + class RandomSource; +} + +namespace Pegasus { + +class IDObject { +public: + IDObject(const int32 id); + ~IDObject(); + + int32 getObjectID() const; + +private: + int32 _objectID; +}; + +class FunctionPtr; + +typedef void (*tFunctionPtr)(FunctionPtr *theFunction, void *functionArg); + +class FunctionPtr { +public: + FunctionPtr(); + virtual ~FunctionPtr(); + + void setFunctionPtr(tFunctionPtr function, void *functionArg); + +protected: + void callFunction(); + + tFunctionPtr _function; + void *_functionArg; +}; + +#define NUM_FLAGS (sizeof(Unit) * 8) +#define BIT_INDEX_SHIFT (sizeof(Unit) + 2 - (sizeof(Unit)) / 3) +#define BIT_INDEX_MASK (NUM_FLAGS - 1) + +template <typename Unit, uint32 kNumFlags> +class FlagsArray { +public: + FlagsArray() { clearAllFlags(); } + void clearAllFlags() { memset(_flags, 0, sizeof(_flags)); } + void setAllFlags() { memset(_flags, ~((Unit)0), sizeof(_flags)); } + void setFlag(uint32 flag) { _flags[flag >> BIT_INDEX_SHIFT] |= 1 << (flag & BIT_INDEX_MASK); } + void clearFlag(uint32 flag) { _flags[flag >> BIT_INDEX_SHIFT] &= ~(1 << (flag & BIT_INDEX_MASK)); } + void setFlag(uint32 flag, bool val) { if (val) setFlag(flag); else clearFlag(flag); } + bool getFlag(uint32 flag) { return (_flags[flag >> BIT_INDEX_SHIFT] & (1 << (flag & BIT_INDEX_MASK))) != 0; } + bool anyFlagSet() { + for (uint32 i = 0; i < sizeof(_flags); i++) + if (_flags[i] != 0) + return true; + return false; + } + + void readFromStream(Common::ReadStream *stream) { + // Shortcut + if (sizeof(Unit) == 1) { + stream->read(_flags, sizeof(_flags)); + return; + } + + for (uint32 i = 0; i < ARRAYSIZE(_flags); i++) { + if (sizeof(Unit) == 2) + _flags[i] = stream->readUint16BE(); + else /* if (sizeof(Unit) == 4) */ + _flags[i] = stream->readUint32BE(); + } + } + + void writeToStream(Common::WriteStream *stream) { + // Shortcut + if (sizeof(Unit) == 1) { + stream->write(_flags, sizeof(_flags)); + return; + } + + for (uint32 i = 0; i < ARRAYSIZE(_flags); i++) { + if (sizeof(Unit) == 2) + stream->writeUint16BE(_flags[i]); + else /* if (sizeof(Unit) == 4) */ + stream->writeUint32BE(_flags[i]); + } + } + +private: + Unit _flags[(kNumFlags - 1) / NUM_FLAGS + 1]; +}; + +#undef NUM_FLAGS +#undef BIT_INDEX_SHIFT +#undef BIT_INDEX_MASK + +int32 linearInterp(const int32 start1, const int32 stop1, const int32 current1, const int32 start2, const int32 stop2); + +int32 pegasusRound(const int32 a, const int32 b); + +uint32 tickCount(); + +} // End of namespace Pegasus + +#endif |