diff options
Diffstat (limited to 'engines/pegasus/neighborhood/norad')
27 files changed, 8143 insertions, 0 deletions
diff --git a/engines/pegasus/neighborhood/norad/alpha/ecrmonitor.cpp b/engines/pegasus/neighborhood/norad/alpha/ecrmonitor.cpp new file mode 100644 index 0000000000..e2a0267231 --- /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..169f75f7d2 --- /dev/null +++ b/engines/pegasus/neighborhood/norad/alpha/fillingstation.cpp @@ -0,0 +1,445 @@ +/* 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); + _rightSideMovie.redrawMovieWorld(); + _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..e4a5e26473 --- /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 = _vm->getAllHotspots().findHotspotByID(kN01GasCanisterSpotID); + hotspot->setMaskedHotspotFlags(kPickUpItemSpotFlag, kPickUpItemSpotFlag); + HotspotInfoTable::Entry *hotspotEntry = findHotspotEntry(kN01GasCanisterSpotID); + hotspotEntry->hotspotItem = kGasCanister; + + hotspot = _vm->getAllHotspots().findHotspotByID(kN01ArgonCanisterSpotID); + hotspot->setMaskedHotspotFlags(kPickUpItemSpotFlag, kPickUpItemSpotFlag); + hotspotEntry = findHotspotEntry(kN01ArgonCanisterSpotID); + hotspotEntry->hotspotItem = kArgonCanister; + + hotspot = _vm->getAllHotspots().findHotspotByID(kN01NitrogenCanisterSpotID); + hotspot->setMaskedHotspotFlags(kPickUpItemSpotFlag, kPickUpItemSpotFlag); + hotspotEntry = findHotspotEntry(kN01NitrogenCanisterSpotID); + hotspotEntry->hotspotItem = kNitrogenCanister; + + hotspot = _vm->getAllHotspots().findHotspotByID(kN01AirMaskSpotID); + hotspot->setMaskedHotspotFlags(kPickUpItemSpotFlag, kPickUpItemSpotFlag); + hotspotEntry = findHotspotEntry(kN01AirMaskSpotID); + hotspotEntry->hotspotItem = kAirMask; + + hotspot = _vm->getAllHotspots().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 *)_vm->getAllItems().findItemByID(kGasCanister); + item->getItemRoom(itemNeighborhood, itemRoom, itemDirection); + + if (itemNeighborhood == getObjectID()) { + _fillingStationItem = item; + } else { + item = (Item *)_vm->getAllItems().findItemByID(kAirMask); + item->getItemRoom(itemNeighborhood, itemRoom, itemDirection); + + if (itemNeighborhood == getObjectID()) { + _fillingStationItem = item; + } else { + item = (Item *)_vm->getAllItems().findItemByID(kNitrogenCanister); + item->getItemRoom(itemNeighborhood, itemRoom, itemDirection); + + if (itemNeighborhood == getObjectID()) { + _fillingStationItem = item; + } else { + item = (Item *)_vm->getAllItems().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) + _vm->getAllHotspots().activateOneHotspot(kN01GasOutletSpotID); + } + } else { + HotSpotID spotID; + + if (_fillingStationItem) { + switch (_fillingStationItem->getObjectID()) { + case kArgonCanister: + spotID = kN01ArgonCanisterSpotID; + _vm->getAllHotspots().deactivateOneHotspot(kNorad01GasOutSpotID); + break; + case kGasCanister: + spotID = kN01GasCanisterSpotID; + break; + case kAirMask: + spotID = kN01AirMaskSpotID; + _vm->getAllHotspots().deactivateOneHotspot(kNorad01GasOutSpotID); + break; + case kNitrogenCanister: + spotID = kN01NitrogenCanisterSpotID; + _vm->getAllHotspots().deactivateOneHotspot(kNorad01GasOutSpotID); + break; + default: + // Should never happen. + spotID = kNoHotSpotID; + break; + } + _vm->getAllHotspots().activateOneHotspot(spotID); + } + } + break; + case MakeRoomView(kNorad10, kEast): + if (GameState.isCurrentDoorOpen()) + _vm->getAllHotspots().deactivateOneHotspot(kNorad10DoorSpotID); + break; + case MakeRoomView(kNorad21, kWest): + if (GameState.isCurrentDoorOpen()) + _vm->getAllHotspots().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 _vm->getAllHotspots().findHotspotByID(kN01GasCanisterSpotID); + case kAirMask: + return _vm->getAllHotspots().findHotspotByID(kN01AirMaskSpotID); + case kArgonCanister: + return _vm->getAllHotspots().findHotspotByID(kN01ArgonCanisterSpotID); + case kNitrogenCanister: + return _vm->getAllHotspots().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..582d6c2bb3 --- /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..5a717a84e7 --- /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..87c7b3bd4e --- /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..7865bbb442 --- /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..06e40c2b3a --- /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..73ed48866f --- /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..f2ea53ff89 --- /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 = _vm->getAllHotspots().findHotspotByID(kNorad68WestSpotID); + hotspot->setMaskedHotspotFlags(kZoomInSpotFlag, kZoomInSpotFlag | kZoomOutSpotFlag); + + hotspot = _vm->getAllHotspots().findHotspotByID(kNorad79WestSpotID); + hotspot->setMaskedHotspotFlags(kZoomInSpotFlag, kZoomInSpotFlag | kZoomOutSpotFlag); + + hotspot = _vm->getAllHotspots().findHotspotByID(kDelta59RobotShieldBiochipSpotID); + hotspot->setMaskedHotspotFlags(kPickUpBiochipSpotFlag, kPickUpBiochipSpotFlag); + HotspotInfoTable::Entry *hotspotEntry = findHotspotEntry(kDelta59RobotShieldBiochipSpotID); + hotspotEntry->hotspotItem = kShieldBiochip; + + hotspot = _vm->getAllHotspots().findHotspotByID(kDelta59RobotOpMemBiochipSpotID); + hotspot->setMaskedHotspotFlags(kPickUpBiochipSpotFlag, kPickUpBiochipSpotFlag); + hotspotEntry = findHotspotEntry(kDelta59RobotOpMemBiochipSpotID); + hotspotEntry->hotspotItem = kOpticalBiochip; + + hotspot = _vm->getAllHotspots().findHotspotByID(kDelta59RobotRetinalBiochipSpotID); + hotspot->setMaskedHotspotFlags(kPickUpBiochipSpotFlag, kPickUpBiochipSpotFlag); + hotspotEntry = findHotspotEntry(kDelta59RobotRetinalBiochipSpotID); + hotspotEntry->hotspotItem = kRetinalScanBiochip; + + hotspot = _vm->getAllHotspots().findHotspotByID(kDelta60RobotShieldBiochipSpotID); + hotspot->setMaskedHotspotFlags(kPickUpBiochipSpotFlag, kPickUpBiochipSpotFlag); + hotspotEntry = findHotspotEntry(kDelta60RobotShieldBiochipSpotID); + hotspotEntry->hotspotItem = kShieldBiochip; + + hotspot = _vm->getAllHotspots().findHotspotByID(kDelta60RobotOpMemBiochipSpotID); + hotspot->setMaskedHotspotFlags(kPickUpBiochipSpotFlag, kPickUpBiochipSpotFlag); + hotspotEntry = findHotspotEntry(kDelta60RobotOpMemBiochipSpotID); + hotspotEntry->hotspotItem = kOpticalBiochip; + + hotspot = _vm->getAllHotspots().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, _vm->getAllHotspots().findHotspotByID(kNorad59WestSpotID)); + } else { + Norad::openDoor(); + } +} + +void NoradDelta::activateHotspots() { + Norad::activateHotspots(); + + if (GameState.getCurrentRoom() == kNorad59West && GameState.getCurrentDirection() == kWest && GameState.getNoradBeatRobotWithDoor()) { + _vm->getAllHotspots().deactivateOneHotspot(kNorad59WestOutSpotID); + + if (_privateFlags.getFlag(kNoradPrivateRobotHeadOpenFlag)) { + if (!_privateFlags.getFlag(kNoradPrivateGotShieldChipFlag)) + _vm->getAllHotspots().activateOneHotspot(kDelta59RobotShieldBiochipSpotID); + else + _vm->getAllHotspots().deactivateOneHotspot(kDelta59RobotShieldBiochipSpotID); + + if (!_privateFlags.getFlag(kNoradPrivateGotOpticalChipFlag)) + _vm->getAllHotspots().activateOneHotspot(kDelta59RobotOpMemBiochipSpotID); + else + _vm->getAllHotspots().deactivateOneHotspot(kDelta59RobotOpMemBiochipSpotID); + + if (!_privateFlags.getFlag(kNoradPrivateGotRetScanChipFlag)) + _vm->getAllHotspots().activateOneHotspot(kDelta59RobotRetinalBiochipSpotID); + else + _vm->getAllHotspots().deactivateOneHotspot(kDelta59RobotRetinalBiochipSpotID); + } else + _vm->getAllHotspots().activateOneHotspot(kDelta59RobotHeadSpotID); + } else if (GameState.getCurrentRoom() == kNorad60West && GameState.getCurrentDirection() == kWest && + GameState.getNoradBeatRobotWithClaw()) { + _vm->getAllHotspots().deactivateOneHotspot(kNorad60MonitorOutSpotID); + + if (_privateFlags.getFlag(kNoradPrivateRobotHeadOpenFlag)) { + if (!_privateFlags.getFlag(kNoradPrivateGotShieldChipFlag)) + _vm->getAllHotspots().activateOneHotspot(kDelta60RobotShieldBiochipSpotID); + else + _vm->getAllHotspots().deactivateOneHotspot(kDelta60RobotShieldBiochipSpotID); + + if (!_privateFlags.getFlag(kNoradPrivateGotOpticalChipFlag)) + _vm->getAllHotspots().activateOneHotspot(kDelta60RobotOpMemBiochipSpotID); + else + _vm->getAllHotspots().deactivateOneHotspot(kDelta60RobotOpMemBiochipSpotID); + + if (!_privateFlags.getFlag(kNoradPrivateGotRetScanChipFlag)) + _vm->getAllHotspots().activateOneHotspot(kDelta60RobotRetinalBiochipSpotID); + else + _vm->getAllHotspots().deactivateOneHotspot(kDelta60RobotRetinalBiochipSpotID); + } else { + _vm->getAllHotspots().activateOneHotspot(kDelta60RobotHeadSpotID); + } + } else if (GameState.getCurrentRoomAndView() == MakeRoomView(kNorad50, kEast)) { + if (GameState.isCurrentDoorOpen()) + _vm->getAllHotspots().deactivateOneHotspot(kNorad50DoorSpotID); + } else if (GameState.getCurrentRoomAndView() == MakeRoomView(kNorad59, kWest)) { + if (GameState.isCurrentDoorOpen()) + _vm->getAllHotspots().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 _vm->getAllHotspots().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 *)_vm->getAllItems().findItemByID(kRetinalScanBiochip)); + + BiochipItem *biochip = _vm->getCurrentBiochip(); + if (biochip != 0 && biochip->getObjectID() != kRetinalScanBiochip && g_interface) + g_interface->setCurrentBiochipID(kRetinalScanBiochip); + + Hotspot *spot = _vm->getAllHotspots().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..578f062dea --- /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..3cd77cc54b --- /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..1751f4dcb6 --- /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..24aa488534 --- /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..d1378567d3 --- /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..b2018bfcf7 --- /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..5aac19dcbe --- /dev/null +++ b/engines/pegasus/neighborhood/norad/pressuretracker.cpp @@ -0,0 +1,87 @@ +/* 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/pegasus.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..6ca7252e22 --- /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..d48481e925 --- /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(); +} + +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) { + GameState.setScoringPlayedWithClaw(true); + + 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..6ce599db42 --- /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..97079a9f53 --- /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..82e86ecae2 --- /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 |