/* 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; _trackSpot = nullptr; _trackTime = -1; _trackDirection = kTrackDown; } 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); // Clip new time so we don't go past the end of the segment if (newTime >= start + kDurationPerRow) newTime = start + kDurationPerRow - 1; 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); // Clip new time so we don't go past the end of the segment if (newTime >= start + kDurationPerRow) newTime = start + kDurationPerRow - 1; 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; enum { kSplash1End = 4, kSplash2End = 5, kSplash3Start = 8, kSplash3Stop = 9, kSplash4Start = 9, kSplash4Stop = 10, kNewLaunchSiloTime = 10, kSiloDeactivatedTime = 11, kMissileLaunchedTime = 12, kMaxDeactivatedStart = 13, kMaxDeactivatedStop = 23, kGamePlaying = 1, 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::setSoundFXLevel(const uint16 fxLevel) { _monitorMovie.setVolume(fxLevel); } void GlobeGame::openInteraction() { _monitorMovie.initFromMovieFile("Images/Norad Delta/N79 Left Monitor"); _monitorMovie.setVolume(((PegasusEngine *)g_engine)->getSoundFXLevel()); _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.redrawMovieWorld(); _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); _monitorMovie.redrawMovieWorld(); _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; } // fall through // FIXME: fall through intentional? 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); // This sound was left out of the original. _owner->requestSpotSound(kAllSilosDeactivatedIn, kAllSilosDeactivatedOut, 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() + (kSiloDeactivatedOut - kSiloDeactivatedIn), kMaxDeactivatedStop * _monitorMovie.getScale()); _monitorMovie.setTime(kMaxDeactivatedStart * _monitorMovie.getScale() + (kSiloDeactivatedOut - kSiloDeactivatedIn)); _monitorCallBack.setCallBackFlag(kMaxDeactivatedFinished); _monitorCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); _monitorMovie.start(); _owner->requestSpotSound(kMaximumDeactivationIn, kMaximumDeactivationOut, kFilterNoInput, kSpotSoundCompletedFlag); _owner->requestSpotSound(kAllSilosDeactivatedIn, kAllSilosDeactivatedOut, kFilterNoInput, kSpotSoundCompletedFlag); _gameState = kPlayerWon1; } } // End of namespace Pegasus