diff options
Diffstat (limited to 'engines/pegasus/neighborhood/norad/delta/globegame.cpp')
-rw-r--r-- | engines/pegasus/neighborhood/norad/delta/globegame.cpp | 1062 |
1 files changed, 1062 insertions, 0 deletions
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 |