diff options
Diffstat (limited to 'engines/pegasus/neighborhood')
92 files changed, 31448 insertions, 0 deletions
diff --git a/engines/pegasus/neighborhood/caldoria/caldoria.cpp b/engines/pegasus/neighborhood/caldoria/caldoria.cpp new file mode 100644 index 0000000000..140e6e8093 --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoria.cpp @@ -0,0 +1,1962 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/system.h" +#include "video/qt_decoder.h" + +#include "pegasus/cursor.h" +#include "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/interface.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/biochipitem.h" +#include "pegasus/neighborhood/caldoria/caldoria.h" +#include "pegasus/neighborhood/caldoria/caldoria4dsystem.h" +#include "pegasus/neighborhood/caldoria/caldoriabomb.h" +#include "pegasus/neighborhood/caldoria/caldoriamessages.h" +#include "pegasus/neighborhood/caldoria/caldoriamirror.h" +#include "pegasus/neighborhood/tsa/fulltsa.h" + +namespace Pegasus { + +static const int16 kVidPhoneAngle = 30; +static const int16 kReplicatorAngle = 50; +static const int16 kDrawersAngle = -30; +static const int16 kCaldoria53Angle = 45; +static const int16 kCaldoria55Angle = -45; + +static const TimeValue kSinclairInterruptionTime1 = 2955; +static const TimeValue kSinclairInterruptionTime2 = 6835; +static const TimeValue kSinclairInterruptionTime3 = 9835; +static const TimeValue kSinclairInterruptionTime4 = 12555; + +static const InputBits kPullbackInterruptFilter = kFilterAllInput; +static const InputBits kRecalibrationInterruptFilter = kFilterAllInput; + +static const TimeValue kCaldoriaReplicatorIntroIn = 4933; +static const TimeValue kCaldoriaReplicatorIntroOut = 6557; + +static const TimeValue kCaldoriaReplicatorWrongChoiceIn = 6557; +static const TimeValue kCaldoriaReplicatorWrongChoiceOut = 8586; + +static const TimeValue kCaldoriaReplicatorOJChoiceIn = 8586; +static const TimeValue kCaldoriaReplicatorOJChoiceOut = 11687; + +static const TimeValue kCaldoriaMessagesIntroIn = 11687; +static const TimeValue kCaldoriaMessagesIntroOut = 13641; + +static const TimeValue kCaldoriaFirstMessageIn = 13641; +static const TimeValue kCaldoriaFirstMessageOut = 14203; + +static const TimeValue kCaldoriaSecondMessageIn = 14203; +static const TimeValue kCaldoriaSecondMessageOut = 14750; + +static const TimeValue kCaldoriaDoorCloseIn = 14750; +static const TimeValue kCaldoriaDoorCloseOut = 15472; + +static const TimeValue kCaldoriaElevatorCloseIn = 15472; +static const TimeValue kCaldoriaElevatorCloseOut = 16336; + +static const TimeValue kCaldoriaShowerCloseIn = 16336; +static const TimeValue kCaldoriaShowerCloseOut = 17101; + +static const TimeValue kCaldoriaGTDoorCloseIn = 17101; +static const TimeValue kCaldoriaGTDoorCloseOut = 18523; + +static const TimeValue kCaldoriaNobodyHomeIn = 18523; +static const TimeValue kCaldoriaNobodyHomeOut = 21469; + +static const TimeValue kCaldoriaNoOtherFloorIn = 21469; +static const TimeValue kCaldoriaNoOtherFloorOut = 28013; + +static const TimeValue kCaldoria4DInstructionsIn = 28013; +static const TimeValue kCaldoria4DInstructionsOut = 29730; + +static const TimeValue kCaldoriaDrinkOJIn = 33910; +static const TimeValue kCaldoriaDrinkOJOut = 35846; + +static const TimeValue kCaldoriaNoOtherDestinationIn = 35846; +static const TimeValue kCaldoriaNoOtherDestinationOut = 37877; + +static const TimeValue kCaldoriaUhghIn = 37877; +static const TimeValue kCaldoriaUhghOut = 38025; + +static const TimeValue kCaldoriaSinclairShootsOSIn = 38025; +static const TimeValue kCaldoriaSinclairShootsOSOut = 40649; + +static const TimeValue kCaldoriaScreamingAfterIn = 40649; +static const TimeValue kCaldoriaScreamingAfterOut = 47661; + +static const TimeValue k4FloorTime = 0; + +static const TimeValue k4To1Start = 40; +static const TimeValue k4To1Stop = 7720; + +static const TimeValue k4To5Start = 7720; +static const TimeValue k4To5Stop = 10280; + +static const TimeValue k4To2Time = 10280; + +static const TimeValue k4To3Time = 10320; + +static const TimeValue k1FloorTime = 10360; + +static const TimeValue k1To4Start = 10400; +static const TimeValue k1To4Stop = 18080; + +static const TimeValue k1To5Start = 18080; +static const TimeValue k1To5Stop = 28320; + +static const TimeValue k1To2Time = 28320; + +static const TimeValue k1To3Time = 28360; + +static const TimeValue k5FloorTime = 28400; + +static const TimeValue k5To1Start = 28440; +static const TimeValue k5To1Stop = 38680; + +static const TimeValue k5To4Start = 38680; +static const TimeValue k5To4Stop = 41240; + +static const TimeValue k5To2Time = 41240; + +static const TimeValue k5To3Time = 41280; + +// FuseFunction functions... + +const NotificationFlags kSinclairLoopDoneFlag = kLastNeighborhoodNotificationFlag << 1; + +SinclairCallBack::SinclairCallBack(Caldoria *caldoria) { + _caldoria = caldoria; +} + +void SinclairCallBack::callBack() { + _caldoria->checkInterruptSinclair(); +} + +Caldoria::Caldoria(InputHandler* nextHandler, PegasusEngine *owner) + : Neighborhood(nextHandler, owner, "Caldoria", kCaldoriaID), _sinclairInterrupt(this) { + setIsItemTaken(kKeyCard); + setIsItemTaken(kOrangeJuiceGlassEmpty); + GameState.setTakenItemID(kOrangeJuiceGlassFull, GameState.isTakenItemID(kOrangeJuiceGlassEmpty)); + _zoomOutSpot = 0; + _gunSprite = 0; +} + +Caldoria::~Caldoria() { + _sinclairInterrupt.releaseCallBack(); +} + +void Caldoria::init() { + Neighborhood::init(); + + // We need this notification flag as well. + _neighborhoodNotification.notifyMe(this, kSinclairLoopDoneFlag, kSinclairLoopDoneFlag); + + _sinclairInterrupt.initCallBack(&_navMovie, kCallBackAtTime); + + forceStridingStop(kCaldoria55, kSouth, kAltCaldoriaSinclairDown); + forceStridingStop(kCaldoria50, kNorth, kAltCaldoriaSinclairDown); +} + +void Caldoria::start() { + g_energyMonitor->stopEnergyDraining(); + + if (!GameState.getCaldoriaSeenPullback()) { + _vm->_gfx->doFadeOutSync(kOneSecond * kFifteenTicksPerSecond, kFifteenTicksPerSecond); + + g_system->delayMillis(2 * 1000); + + Video::VideoDecoder *pullbackMovie = new Video::QuickTimeDecoder(); + + if (!pullbackMovie->loadFile("Images/Caldoria/Pullback.movie")) + error("Could not load pullback movie"); + + // Draw the first frame so we can fade to it + const Graphics::Surface *frame = pullbackMovie->decodeNextFrame(); + assert(frame); + assert(frame->format == g_system->getScreenFormat()); + g_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, 64, 112, frame->w, frame->h); + _vm->_gfx->doFadeInSync(kTwoSeconds * kFifteenTicksPerSecond, kFifteenTicksPerSecond); + + bool saveAllowed = _vm->swapSaveAllowed(false); + bool openAllowed = _vm->swapLoadAllowed(false); + + bool skipped = false; + Input input; + + pullbackMovie->start(); + + while (!_vm->shouldQuit() && !pullbackMovie->endOfVideo()) { + if (pullbackMovie->needsUpdate()) { + frame = pullbackMovie->decodeNextFrame(); + + if (frame) { + g_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, 64, 112, frame->w, frame->h); + g_system->updateScreen(); + } + } + + InputDevice.getInput(input, kPullbackInterruptFilter); + if (input.anyInput() || _vm->saveRequested() || _vm->loadRequested()) { + skipped = true; + break; + } + + g_system->delayMillis(10); + } + + delete pullbackMovie; + + if (_vm->shouldQuit()) + return; + + _vm->swapSaveAllowed(saveAllowed); + _vm->swapLoadAllowed(openAllowed); + + ExtraTable::Entry entry; + + if (!skipped) { + _vm->_gfx->doFadeOutSync(kThreeSeconds * kFifteenTicksPerSecond, kFifteenTicksPerSecond, false); + g_system->delayMillis(3 * 1000 / 2); + getExtraEntry(kCaldoria00WakeUp1, entry); + _navMovie.setTime(entry.movieStart); + _navMovie.redrawMovieWorld(); + _navMovie.show(); + _vm->refreshDisplay(); + _vm->_gfx->doFadeInSync(kOneSecond * kFifteenTicksPerSecond, kFifteenTicksPerSecond, false); + } else { + getExtraEntry(kCaldoria00WakeUp1, entry); + _navMovie.setTime(entry.movieStart); + _navMovie.redrawMovieWorld(); + _navMovie.show(); + } + + GameState.setCaldoriaSeenPullback(true); + } + + Neighborhood::start(); +} + +void Caldoria::flushGameState() { + GameState.setCaldoriaFuseTimeLimit(_utilityFuse.getTimeRemaining()); +} + +class AIBombActiveCondition : public AICondition { +public: + AIBombActiveCondition() {} + + bool fireCondition(); +}; + +// Return true if player is on 53 east and Sinclair is shot. +bool AIBombActiveCondition::fireCondition() { + return GameState.getCurrentRoom() == kCaldoria53 && GameState.getCurrentDirection() == kEast && + GameState.getCaldoriaSinclairShot(); +} + +void Caldoria::setUpAIRules() { + Neighborhood::setUpAIRules(); + + if (g_AIArea) { + if (GameState.allTimeZonesFinished()) { + AIPlayMessageAction *messageAction = new AIPlayMessageAction("Images/AI/Caldoria/X49NB1", false); + AILocationCondition *locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kCaldoria49, kNorth)); + AIRule *rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Caldoria/X56EH1", false); + AIBombActiveCondition *activeCondition = new AIBombActiveCondition(); + rule = new AIRule(activeCondition, messageAction); + g_AIArea->addAIRule(rule); + } else { + AIPlayMessageAction *messageAction = new AIPlayMessageAction("Images/AI/Caldoria/XAB2", false); + AITimerCondition *timerCondition = new AITimerCondition(kLateWarning3TimeLimit, 1, true); + AILocationCondition *locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kCaldoria44, kEast)); + AINotCondition *notCondition = new AINotCondition(locCondition); + AIAndCondition *andCondition = new AIAndCondition(timerCondition, notCondition); + AIRule *rule = new AIRule(andCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Caldoria/XAB1", false); + timerCondition = new AITimerCondition(kLateWarning2TimeLimit, 1, true); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kCaldoria44, kEast)); + notCondition = new AINotCondition(locCondition); + andCondition = new AIAndCondition(timerCondition, notCondition); + rule = new AIRule(andCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Caldoria/XA44EB", false); + locCondition = new AILocationCondition(3); + locCondition->addLocation(MakeRoomView(kCaldoria01, kNorth)); + locCondition->addLocation(MakeRoomView(kCaldoria01, kEast)); + locCondition->addLocation(MakeRoomView(kCaldoria01, kSouth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Caldoria/X42WH1", false); + AICondition *condition = makeLocationAndDoesntHaveItemCondition(kCaldoria44, kEast, kKeyCard); + rule = new AIRule(condition, messageAction); + g_AIArea->addAIRule(rule); + + AIActivateRuleAction *ruleAction = new AIActivateRuleAction(rule); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kCaldoria42, kEast)); + rule = new AIRule(locCondition, ruleAction); + g_AIArea->addAIRule(rule); + } + } +} + +uint16 Caldoria::getDateResID() const { + return kDate2318ID; +} + +TimeValue Caldoria::getViewTime(const RoomID room, const DirectionConstant direction) { + ExtraTable::Entry extra; + uint32 extraID = 0xffffffff; + + switch (room) { + case kCaldoria00: + if (direction == kEast && _privateFlags.getFlag(kCaldoriaPrivate4DSystemOpenFlag)) + extraID = k4DEnvironOpenView; + break; + case kCaldoriaDrawers: + if (direction == kNorth && _privateFlags.getFlag(kCaldoriaPrivateRightDrawerOpenFlag)) { + if (GameState.isTakenItemID(kKeyCard)) + extraID = kRightDrawerOpenViewNoKeys; + else + extraID = kRightDrawerOpenViewWithKeys; + } + break; + case kCaldoria16: + if (direction == kSouth && GameState.getCaldoriaSeenSinclairInElevator()) + extraID = kCaldoria16SouthViewWithElevator; + break; + case kCaldoriaReplicator: + if (GameState.getCaldoriaMadeOJ() && !(GameState.isTakenItemID(kOrangeJuiceGlassEmpty) || GameState.isTakenItemID(kOrangeJuiceGlassFull))) + extraID = kReplicatorNorthViewWithOJ; + break; + case kCaldoriaKiosk: + case kCaldoriaBinoculars: + return 0xffffffff; + case kCaldoria48: + if (direction == kNorth && GameState.getCaldoriaRoofDoorOpen()) + extraID = kCa48NorthExplosion; + break; + } + + if (extraID == 0xffffffff) + return Neighborhood::getViewTime(room, direction); + + getExtraEntry(extraID, extra); + return extra.movieEnd - 1; +} + +void Caldoria::startSpotOnceOnly(TimeValue startTime, TimeValue stopTime) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kCaldoria13, kEast): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen13CarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen13CarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria14, kEast): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen14CarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen14CarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria18, kWest): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen18CarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen18CarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria23, kSouth): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen23CarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen23CarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria33, kSouth): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen33CarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen33CarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria36, kNorth): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen36CarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen36CarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria41, kNorth): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen41NorthCarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen41NorthCarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria41, kEast): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen41EastCarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen41EastCarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + case MakeRoomView(kCaldoria41, kWest): + if (!_privateFlags.getFlag(kCaldoriaPrivateSeen41WestCarFlag) && _vm->getRandomBit() == 0) { + _privateFlags.setFlag(kCaldoriaPrivateSeen41WestCarFlag, true); + Neighborhood::startSpotOnceOnly(startTime, stopTime); + } + break; + default: + Neighborhood::startSpotOnceOnly(startTime, stopTime); + break; + } +} + +void Caldoria::findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &entry) { + Neighborhood::findSpotEntry(room, direction, flags, entry); + + switch (room) { + case kCaldoria00: + if (direction == kEast && (!GameState.getCaldoriaINNAnnouncing() || GameState.getCaldoriaSeenINN())) + entry.clear(); + break; + case kCaldoriaVidPhone: + if (direction == kNorth && GameState.getCaldoriaSeenMessages()) + entry.clear(); + break; + case kCaldoria44: + if (direction == kEast && GameState.getLastRoom() != kCaldoria42) + entry.clear(); + break; + } +} + +void Caldoria::startExitMovie(const ExitTable::Entry &exitEntry) { + switch (GameState.getCurrentRoom()) { + case kCaldoria05: + case kCaldoria07: + if (GameState.getCurrentDirection() == kWest) + closeCroppedMovie(); + // fall through + case kCaldoria11: + if (GameState.getCurrentDirection() == kEast) + closeCroppedMovie(); + break; + case kCaldoria13: + case kCaldoria14: + if (GameState.getCurrentDirection() == kNorth) + closeCroppedMovie(); + break; + } + + Neighborhood::startExitMovie(exitEntry); +} + +void Caldoria::startZoomMovie(const ZoomTable::Entry &zoomEntry) { + switch (GameState.getCurrentRoom()) { + case kCaldoria12: + if (GameState.getCurrentDirection() == kNorth) + closeCroppedMovie(); + break; + } + + Neighborhood::startZoomMovie(zoomEntry); +} + +void Caldoria::startDoorOpenMovie(const TimeValue startTime, const TimeValue stopTime) { + if (GameState.getCurrentRoom() == kCaldoria27 || GameState.getCurrentRoom() == kCaldoria28 || GameState.getCurrentRoom() == kCaldoria45) + // Must be opening elevator door. + closeCroppedMovie(); + + if (GameState.getCurrentRoom() == kCaldoria44 && GameState.getLastRoom() != kCaldoria42) + startExtraSequence(kArriveAtCaldoriaFromTSA, kDoorOpenCompletedFlag, false); + else + Neighborhood::startDoorOpenMovie(startTime, stopTime); +} + +void Caldoria::startTurnPush(const TurnDirection turnDirection, const TimeValue newViewTime, const DirectionConstant destDirection) { + switch (GameState.getCurrentRoom()) { + case kCaldoria05: + case kCaldoria07: + if (GameState.getCurrentDirection() == kWest) + closeCroppedMovie(); + break; + case kCaldoria11: + if (GameState.getCurrentDirection() == kEast) + closeCroppedMovie(); + break; + case kCaldoria12: + case kCaldoria13: + case kCaldoria14: + case kCaldoria27: + case kCaldoria28: + case kCaldoria45: + if (GameState.getCurrentDirection() == kNorth) + closeCroppedMovie(); + break; + case kCaldoria48: + if (_croppedMovie.isSurfaceValid()) + closeCroppedMovie(); + break; + } + + Neighborhood::startTurnPush(turnDirection, newViewTime, destDirection); +} + +void Caldoria::bumpIntoWall() { + requestSpotSound(kCaldoriaUhghIn, kCaldoriaUhghOut, kFilterNoInput, 0); + Neighborhood::bumpIntoWall(); +} + +void Caldoria::closeDoorOffScreen(const RoomID room, const DirectionConstant direction) { + switch (room) { + case kCaldoria08: + if (direction == kNorth) + playSpotSoundSync(kCaldoriaShowerCloseIn, kCaldoriaShowerCloseOut); + else + playSpotSoundSync(kCaldoriaDoorCloseIn, kCaldoriaDoorCloseOut); + break; + case kCaldoria09: + playSpotSoundSync(kCaldoriaShowerCloseIn, kCaldoriaShowerCloseOut); + break; + case kCaldoria16: + case kCaldoria38: + case kCaldoria46: + case kCaldoria27: + case kCaldoria28: + case kCaldoria45: + playSpotSoundSync(kCaldoriaElevatorCloseIn, kCaldoriaElevatorCloseOut); + break; + case kCaldoria44: + case kCaldoria42: + if (GameState.getCurrentRoom() == kCaldoria42) + playSpotSoundSync(kCaldoriaGTDoorCloseIn, kCaldoriaGTDoorCloseOut); + break; + default: + playSpotSoundSync(kCaldoriaDoorCloseIn, kCaldoriaDoorCloseOut); + break; + } +} + +int16 Caldoria::getStaticCompassAngle(const RoomID room, const DirectionConstant dir) { + int16 result = Neighborhood::getStaticCompassAngle(room, dir); + + switch (room) { + case kCaldoriaVidPhone: + result += kVidPhoneAngle; + break; + case kCaldoriaReplicator: + result += kReplicatorAngle; + break; + case kCaldoriaDrawers: + result += kDrawersAngle; + break; + case kCaldoria53: + result += kCaldoria53Angle; + break; + case kCaldoria55: + result += kCaldoria55Angle; + break; + } + + return result; +} + +void Caldoria::getExitCompassMove(const ExitTable::Entry &exitEntry, FaderMoveSpec &compassMove) { + Neighborhood::getExitCompassMove(exitEntry, compassMove); + + switch (MakeRoomView(exitEntry.room, exitEntry.direction)) { + case MakeRoomView(kCaldoria08, kNorth): + case MakeRoomView(kCaldoria09, kSouth): + compassMove.insertFaderKnot((exitEntry.movieStart + exitEntry.movieEnd) >> 1, compassMove.getNthKnotValue(0) + 30); + break; + case MakeRoomView(kCaldoria10, kEast): + compassMove.insertFaderKnot(exitEntry.movieStart + 4 * kCaldoriaFrameDuration, 90); + compassMove.insertFaderKnot(exitEntry.movieStart + 19 * kCaldoriaFrameDuration, -90); + break; + case MakeRoomView(kCaldoria42, kWest): + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), exitEntry.movieStart, -90, exitEntry.movieEnd, 90); + compassMove.insertFaderKnot(exitEntry.movieStart + 3 * kCaldoriaFrameDuration, -90); + compassMove.insertFaderKnot(exitEntry.movieStart + 33 * kCaldoriaFrameDuration, 90); + break; + case MakeRoomView(kCaldoria54, kEast): + if (getCurrentAlternate() != kAltCaldoriaSinclairDown) { + compassMove.insertFaderKnot(exitEntry.movieStart + 16 * kCaldoriaFrameDuration, 135); + compassMove.insertFaderKnot(exitEntry.movieEnd, 135); + } + break; + case MakeRoomView(kCaldoria55, kNorth): + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), exitEntry.movieStart, 315, exitEntry.movieEnd, 270); + break; + } +} + +void Caldoria::getZoomCompassMove(const ZoomTable::Entry &zoomEntry, FaderMoveSpec &compassMove) { + Neighborhood::getZoomCompassMove(zoomEntry, compassMove); + + switch (zoomEntry.hotspot) { + case kCaBathroomToiletSpotID: + compassMove.insertFaderKnot(zoomEntry.movieStart + 4 * kCaldoriaFrameDuration, 90); + compassMove.insertFaderKnot(zoomEntry.movieStart + 19 * kCaldoriaFrameDuration, -90); + compassMove.insertFaderKnot(zoomEntry.movieEnd, -90); + break; + } +} + +void Caldoria::getExtraCompassMove(const ExtraTable::Entry &entry, FaderMoveSpec &compassMove) { + switch (entry.extra) { + case kCaldoria00WakeUp1: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, 90, entry.movieEnd, 180); + compassMove.insertFaderKnot(entry.movieStart + 1000, 90); + compassMove.insertFaderKnot(entry.movieStart + 1640, 120); + compassMove.insertFaderKnot(entry.movieStart + 2240, 135); + compassMove.insertFaderKnot(entry.movieStart + 2640, 180); + break; + case kCaldoria00WakeUp2: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, 180, entry.movieEnd, 90); + compassMove.insertFaderKnot(entry.movieStart + 560, 90); + break; + case kCaldoria56BombStage1: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, 90, entry.movieEnd, 10); + compassMove.insertFaderKnot(entry.movieStart + 31 * kCaldoriaFrameDuration, 60); + compassMove.insertFaderKnot(entry.movieStart + 49 * kCaldoriaFrameDuration, 60); + compassMove.insertFaderKnot(entry.movieStart + 66 * kCaldoriaFrameDuration, 10); + break; + case kCaldoria56BombStage7: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, 10, entry.movieEnd, 90); + compassMove.insertFaderKnot(entry.movieStart + 131 * kCaldoriaFrameDuration, 10); + compassMove.insertFaderKnot(entry.movieStart + 148 * kCaldoriaFrameDuration, 60); + compassMove.insertFaderKnot(entry.movieStart + 165 * kCaldoriaFrameDuration, 60); + compassMove.insertFaderKnot(entry.movieEnd - 5 * kCaldoriaFrameDuration, 90); + break; + default: + Neighborhood::getExtraCompassMove(entry, compassMove); + break; + } +} + +void Caldoria::loadAmbientLoops() { + RoomID room = GameState.getCurrentRoom(); + + if (room == kCaldoria00 && GameState.getCaldoriaWokenUp()) + loadLoopSound1("Sounds/Caldoria/Apartment Music.AIFF", 0x100 / 4); + else if (room >= kCaldoria01 && room <= kCaldoria14) + loadLoopSound1("Sounds/Caldoria/Apartment Music.AIFF", 0x100 / 4); + else if (room == kCaldoria27 || room == kCaldoria28 || room == kCaldoria45) + loadLoopSound1("Sounds/Caldoria/Elevator Loop.AIFF", 0x100 / 5); + else if (room == kCaldoria44) + loadLoopSound1("Sounds/Caldoria/TSA Hum Loop.AIFF"); + else if (room >= kCaldoria15 && room <= kCaldoria48) + loadLoopSound1("Sounds/Caldoria/Industrial Nuage.aiff", 2 * 0x100 / 3); + else if (room >= kCaldoria49 && room <= kCaldoria56) + loadLoopSound1("Sounds/Caldoria/A50NLB00.22K.AIFF", 0x100 / 4); +} + +void Caldoria::checkContinuePoint(const RoomID room, const DirectionConstant direction) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kCaldoria06, kSouth): + case MakeRoomView(kCaldoria13, kNorth): + case MakeRoomView(kCaldoria16, kSouth): + case MakeRoomView(kCaldoria38, kEast): + case MakeRoomView(kCaldoria38, kWest): + case MakeRoomView(kCaldoria40, kNorth): + case MakeRoomView(kCaldoria44, kEast): + case MakeRoomView(kCaldoria48, kNorth): + case MakeRoomView(kCaldoria49, kNorth): + makeContinuePoint(); + break; + } +} + +void Caldoria::spotCompleted() { + Neighborhood::spotCompleted(); + if (GameState.getCurrentRoom() == kCaldoriaBinoculars) + startExtraSequence(kBinocularsZoomInOnShip, kExtraCompletedFlag, kFilterNoInput); +} + +void Caldoria::arriveAt(const RoomID room, const DirectionConstant direction) { + switch (room) { + case kCaldoria56: + if (!GameState.getCaldoriaGunAimed()) + // Fall through... + case kCaldoria49: + case kCaldoria50: + case kCaldoria51: + case kCaldoria52: + case kCaldoria53: + case kCaldoria54: + case kCaldoria55: + if (GameState.getCaldoriaSinclairShot()) + setCurrentAlternate(kAltCaldoriaSinclairDown); + break; + } + + Neighborhood::arriveAt(room, direction); + Input dummy; + + switch (room) { + case kCaldoria00: + arriveAtCaldoria00(); + break; + case kCaldoria05: + if (direction == kWest && GameState.getCaldoriaINNAnnouncing()) + loopCroppedMovie("Images/Caldoria/A05 Light Loop", kCaldoriaA05LightLoopLeft, kCaldoriaA05LightLoopTop); + break; + case kCaldoria07: + if (direction == kWest && GameState.getCaldoriaINNAnnouncing()) + loopCroppedMovie("Images/Caldoria/A07 Light Loop", kCaldoriaA07LightLoopLeft, kCaldoriaA07LightLoopTop); + break; + case kCaldoria09: + _lastExtra = 0xffffffff; + break; + case kCaldoriaToilet: + GameState.setScoringReadPaper(true); + break; + case kCaldoriaReplicator: + setCurrentActivation(kActivateReplicatorReady); + requestSpotSound(kCaldoriaReplicatorIntroIn, kCaldoriaReplicatorIntroOut, kFilterNoInput, 0); + break; + case kCaldoria11: + setCurrentAlternate(kAltCaldoriaNormal); + if (direction == kEast && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A11 Message Machine Loop", kCaldoria11MessageLoopLeft, kCaldoria11MessageLoopTop); + break; + case kCaldoria12: + if (direction == kNorth && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A12 Message Machine Loop", kCaldoria12MessageLoopLeft, kCaldoria12MessageLoopTop); + break; + case kCaldoriaDrawers: + setCurrentActivation(kActivateDrawersClosed); + break; + case kCaldoria13: + GameState.setCaldoriaINNAnnouncing(true); + if (direction == kNorth && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A13 Message Machine Loop", kCaldoria13MessageLoopLeft, kCaldoria13MessageLoopTop); + break; + case kCaldoria14: + if (direction == kNorth && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A14 Message Machine Loop", kCaldoria14MessageLoopLeft, kCaldoria14MessageLoopTop); + break; + case kCaldoria08: + if (direction == kWest) + setCurrentActivation(kActivateMirrorReady); + // Fall through... + case kCaldoria15: + GameState.setCaldoriaINNAnnouncing(true); + break; + case kCaldoria27: + case kCaldoria28: + case kCaldoria45: + if (GameState.getCurrentDirection() == kNorth) + openDoor(); + break; + case kCaldoriaBinoculars: + GameState.setScoringLookThroughTelescope(true); + break; + case kCaldoriaKiosk: + GameState.setScoringSawCaldoriaKiosk(true); + startExtraSequenceSync(kCaldoriaKioskVideo, kFilterAllInput); + downButton(dummy); + break; + case kCaldoria44: + arriveAtCaldoria44(); + break; + case kCaldoria49: + arriveAtCaldoria49(); + break; + case kCaldoria53: + if (direction == kEast && !GameState.getCaldoriaSinclairShot()) + zoomToSinclair(); + break; + case kCaldoria50: + if (direction == kNorth && !GameState.getCaldoriaSinclairShot()) + setUpSinclairLoops(); + break; + case kCaldoria54: + if (direction == kSouth && !GameState.getCaldoriaSinclairShot()) + setUpSinclairLoops(); + break; + case kCaldoria56: + arriveAtCaldoria56(); + break; + case kCaldoriaDeathRoom: + arriveAtCaldoriaDeath(); + break; + } + + checkSinclairShootsOS(); + setUpRoofTop(); +} + +void Caldoria::doAIRecalibration() { + GameState.setCaldoriaDidRecalibration(true); + + if (!g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Caldoria/XA01EB1", true, kRecalibrationInterruptFilter)) + return; + + g_interface->calibrateEnergyBar(); + if (!g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Caldoria/XA01EB4", true, kRecalibrationInterruptFilter)) + return; + + g_interface->raiseInventoryDrawerSync(); + if (!g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Caldoria/XA01EB6", true, kRecalibrationInterruptFilter)) { + g_interface->lowerInventoryDrawerSync(); + return; + } + + g_interface->lowerInventoryDrawerSync(); + g_interface->raiseBiochipDrawerSync(); + + if (!g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Caldoria/XA01EB5", true, kRecalibrationInterruptFilter)) { + g_interface->lowerBiochipDrawerSync(); + return; + } + + g_interface->lowerBiochipDrawerSync(); + + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Caldoria/XA01EB8", false, kRecalibrationInterruptFilter); +} + +void Caldoria::arriveAtCaldoria00() { + if (GameState.getCurrentDirection() == kEast) { + if (GameState.getCaldoriaWokenUp()) { + if (!GameState.getCaldoriaDidRecalibration()) + doAIRecalibration(); + setCurrentActivation(kActivate4DClosed); + } else { + // Good morning, sleeping beauty + ExtraTable::Entry extra; + getExtraEntry(kCaldoria00WakeUp1, extra); + + if (_navMovie.getTime() != extra.movieStart) { + _navMovie.setTime(extra.movieStart); + _navMovie.redrawMovieWorld(); + } + + startExtraSequenceSync(kCaldoria00WakeUp1, kFilterNoInput); + GameState.setCaldoriaWokenUp(true); + playCroppedMovieOnce("Images/Caldoria/VidPhone.movie", kCaldoriaVidPhoneLeft, kCaldoriaVidPhoneTop, kFilterAllInput); + startExtraSequence(kCaldoria00WakeUp2, kExtraCompletedFlag, kFilterNoInput); + } + } +} + +bool Caldoria::wantsCursor() { + return GameState.getCaldoriaDidRecalibration(); +} + +void Caldoria::arriveAtCaldoria44() { + if (GameState.getLastNeighborhood() != kCaldoriaID) { + openDoor(); + } else { + setCurrentActivation(kActivateReadyForCard); + loopExtraSequence(kCaldoriaTransporterArrowLoop, 0); + } +} + +void Caldoria::arriveAtCaldoria49() { + if (GameState.getLastRoom() == kCaldoria48) + setCurrentAlternate(kAltCaldoriaNormal); + + // Need to force the loop to play. + if (GameState.getCurrentDirection() == kNorth) { + GameState.setCaldoriaFuseTimeLimit(kSinclairShootsTimeLimit); + startExtraSequence(kCa49NorthVoiceAnalysis, kExtraCompletedFlag, kFilterNoInput); + } +} + +void Caldoria::arriveAtCaldoria56() { + if (!GameState.getCaldoriaBombDisarmed()) { + _privateFlags.setFlag(kCaldoriaPrivateZoomingToBombFlag, true); + + if (GameState.getCurrentDirection() == kNorth) { + turnRight(); + } else if (GameState.getCurrentDirection() == kSouth) { + turnLeft(); + } else if (GameState.getCurrentDirection() == kEast) { + _privateFlags.setFlag(kCaldoriaPrivateZoomingToBombFlag, false); + newInteraction(kCaldoriaBombInteractionID); + } + } +} + +void Caldoria::arriveAtCaldoriaDeath() { + if (GameState.getLastRoom() == kCaldoria49) { + if (GameState.getCaldoriaSinclairShot()) { + die(kDeathNuclearExplosion); + } else { + playSpotSoundSync(kCaldoriaSinclairShootsOSIn, kCaldoriaSinclairShootsOSOut); + playSpotSoundSync(kCaldoriaScreamingAfterIn, kCaldoriaScreamingAfterOut); + die(kDeathSinclairShotDelegate); + } + } else { + die(kDeathShotBySinclair); + } +} + +void Caldoria::setUpRoofTop() { + switch (GameState.getCurrentRoom()) { + case kCaldoria48: + if (GameState.getCurrentDirection() == kNorth) { + if (GameState.getCaldoriaRoofDoorOpen()) { + setCurrentAlternate(kAltCaldoriaRoofDoorBlown); + } else if (GameState.getCaldoriaDoorBombed()) { + // Long enough for AI hints...? + _utilityFuse.primeFuse(kCardBombCountDownTime); + _utilityFuse.setFunctor(new Common::Functor0Mem<void, Caldoria>(this, &Caldoria::doorBombTimerExpired)); + _utilityFuse.lightFuse(); + + loopCroppedMovie("Images/Caldoria/A48 Bomb Loop", kCaldoria48CardBombLoopLeft, kCaldoria48CardBombLoopTop); + } else { + setCurrentActivation(kActivateRoofSlotEmpty); + } + } + break; + case kCaldoria56: + if (GameState.getCurrentDirection() == kEast && GameState.getCaldoriaGunAimed()) + startExtraSequence(kCa53EastShootSinclair, kExtraCompletedFlag, false); + else + // Fall through... + case kCaldoria49: + case kCaldoria50: + case kCaldoria51: + case kCaldoria52: + case kCaldoria53: + case kCaldoria54: + case kCaldoria55: + if (!GameState.getCaldoriaSinclairShot()) { + if (GameState.getCaldoriaSawVoiceAnalysis() && !_utilityFuse.isFuseLit()) { + _utilityFuse.primeFuse(GameState.getCaldoriaFuseTimeLimit()); + _utilityFuse.setFunctor(new Common::Functor0Mem<void, Caldoria>(this, &Caldoria::sinclairTimerExpired)); + _utilityFuse.lightFuse(); + } + } else { + setCurrentAlternate(kAltCaldoriaSinclairDown); + } + break; + } +} + +void Caldoria::downButton(const Input &input) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kCaldoria01, kEast): + GameState.setCaldoriaWokenUp(true); + startExtraSequence(kCaldoria00SitDown, kExtraCompletedFlag, kFilterNoInput); + break; + default: + Neighborhood::downButton(input); + break; + } +} + +void Caldoria::turnTo(const DirectionConstant direction) { + Neighborhood::turnTo(direction); + + switch (GameState.getCurrentRoom()) { + case kCaldoria00: + if (direction == kEast) + setCurrentActivation(kActivate4DClosed); + break; + case kCaldoria01: + if (direction == kEast) { + GameState.setCaldoriaWokenUp(true); + startExtraSequence(kCaldoria00SitDown, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kCaldoria05: + if (direction == kWest && GameState.getCaldoriaINNAnnouncing()) + loopCroppedMovie("Images/Caldoria/A05 Light Loop", kCaldoriaA05LightLoopLeft, kCaldoriaA05LightLoopTop); + break; + case kCaldoria07: + if (direction == kWest && GameState.getCaldoriaINNAnnouncing()) + loopCroppedMovie("Images/Caldoria/A07 Light Loop", kCaldoriaA07LightLoopLeft, kCaldoriaA07LightLoopTop); + break; + case kCaldoria08: + if (direction == kWest) + setCurrentActivation(kActivateMirrorReady); + break; + case kCaldoria09: + _lastExtra = 0xffffffff; + break; + case kCaldoria11: + if (direction == kEast && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A11 Message Machine Loop", kCaldoria11MessageLoopLeft, kCaldoria11MessageLoopTop); + break; + case kCaldoria12: + if (direction == kNorth && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A12 Message Machine Loop", kCaldoria12MessageLoopLeft, kCaldoria12MessageLoopTop); + break; + case kCaldoria13: + if (direction == kNorth && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A13 Message Machine Loop", kCaldoria13MessageLoopLeft, kCaldoria13MessageLoopTop); + break; + case kCaldoria14: + if (direction == kNorth && !GameState.getCaldoriaSeenMessages()) + loopCroppedMovie("Images/Caldoria/A14 Message Machine Loop", kCaldoria14MessageLoopLeft, kCaldoria14MessageLoopTop); + break; + case kCaldoria27: + case kCaldoria28: + case kCaldoria45: + if (direction == kNorth) + openElevatorMovie(); + else + closeCroppedMovie(); + break; + case kCaldoria48: + if (direction == kNorth && !GameState.getCaldoriaDoorBombed()) + setCurrentActivation(kActivateRoofSlotEmpty); + break; + case kCaldoria53: + if (GameState.getCurrentDirection() == kEast && !GameState.getCaldoriaSinclairShot()) + zoomToSinclair(); + break; + case kCaldoria50: + if (direction == kNorth && !GameState.getCaldoriaSinclairShot()) + setUpSinclairLoops(); + break; + case kCaldoria54: + if (direction == kSouth && !GameState.getCaldoriaSinclairShot()) + setUpSinclairLoops(); + break; + case kCaldoria56: + if (_privateFlags.getFlag(kCaldoriaPrivateZoomingToBombFlag)) { + _privateFlags.setFlag(kCaldoriaPrivateZoomingToBombFlag, false); + newInteraction(kCaldoriaBombInteractionID); + } else if (GameState.getCaldoriaBombDisarmed()) { + _vm->playEndMessage(); + } + break; + } + + checkSinclairShootsOS(); +} + +void Caldoria::zoomTo(const Hotspot *zoomOutSpot) { + // Need to set _zoomOutSpot here because we may come through + // this function another way, say by pressing the down arrow, + // that doesn't involve the ClickInHotSpot function. + _zoomOutSpot = zoomOutSpot; + + if (zoomOutSpot->getObjectID() == kCaldoriaDrawersOutSpotID) { + if (_privateFlags.getFlag(kCaloriaPrivateLeftDrawerOpenFlag)) { + _privateFlags.setFlag(kCaloriaPrivateLeftDrawerOpenFlag, false); + startExtraSequence(kLeftDrawerClose, kExtraCompletedFlag, kFilterNoInput); + } else if (_privateFlags.getFlag(kCaldoriaPrivateRightDrawerOpenFlag)) { + _privateFlags.setFlag(kCaldoriaPrivateRightDrawerOpenFlag, false); + if (GameState.isTakenItemID(kKeyCard)) + startExtraSequence(kRightDrawerCloseNoKeys, kExtraCompletedFlag, false); + else + startExtraSequence(kRightDrawerCloseWithKeys, kExtraCompletedFlag, false); + } else { + Neighborhood::zoomTo(zoomOutSpot); + } + } else { + Neighborhood::zoomTo(zoomOutSpot); + } +} + +void Caldoria::setUpSinclairLoops() { + _navMovie.stop(); + scheduleNavCallBack(kSinclairLoopDoneFlag); + _sinclairLoopCount = 0; + _numSinclairLoops = 2; + _navMovie.start(); +} + +void Caldoria::zoomToSinclair() { + _utilityFuse.stopFuse(); + _privateFlags.setFlag(kCaldoriaPrivateReadyToShootFlag, true); + setCurrentActivation(kActivateZoomedOnSinclair); + + ExtraTable::Entry entry; + getExtraEntry(kCa53EastZoomToSinclair, entry); + _sinclairInterrupt.scheduleCallBack(kTriggerTimeFwd, entry.movieStart + kSinclairInterruptionTime1, _navMovie.getScale()); + startExtraSequence(kCa53EastZoomToSinclair, kExtraCompletedFlag, kFilterAllInput); +} + +void Caldoria::receiveNotification(Notification *notification, const NotificationFlags flags) { + Neighborhood::receiveNotification(notification, flags); + + if ((flags & kExtraCompletedFlag) != 0) { + InventoryItem *item; + _interruptionFilter = kFilterAllInput; + + switch (_lastExtra) { + case kCaldoria00WakeUp2: + makeContinuePoint(); + // Force ArriveAt to do its thing... + GameState.setCurrentRoom(kNoRoomID); + arriveAt(kCaldoria00, kEast); + break; + case k4DEnvironOpenToINN: + GameState.setCaldoriaSeenINN(true); + GameState.setScoringSawINN(true); + // Fall through to k4DEnvironOpen... + case k4DEnvironOpen: + _privateFlags.setFlag(kCaldoriaPrivate4DSystemOpenFlag, true); + setCurrentActivation(kActivate4DOpen); + newInteraction(kCaldoria4DInteractionID); + break; + case kCaldoriaShowerUp: + GameState.setScoringTookShower(true); + GameState.setCaldoriaDoneHygiene(true); + break; + case kLeftDrawerClose: + case kRightDrawerCloseNoKeys: + case kRightDrawerCloseWithKeys: + if (_zoomOutSpot && _zoomOutSpot->getObjectID() == kCaldoriaDrawersOutSpotID) { + Input input; + clickInHotspot(input, _zoomOutSpot); + } + break; + case kCreateOrangeJuice: + setCurrentActivation(kActivateOJOnThePad); + requestSpotSound(kCaldoriaReplicatorOJChoiceIn, kCaldoriaReplicatorOJChoiceOut, kFilterNoInput, 0); + break; + case kCaldoria00SitDown: + arriveAt(kCaldoria00, kEast); + break; + case kCaldoria16ElevatorUp: + startExtraSequence(kCaldoria16ElevatorDown, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoria16ElevatorDown: + GameState.setCaldoriaSeenSinclairInElevator(true); + _privateFlags.setFlag(kCaldoriaPrivateCanOpenElevatorDoorFlag, true); + openDoor(); + break; + case kCaldoriaFourthToGround: + case kCaldoriaRoofToGround: + arriveAt(kCaldoria28, GameState.getCurrentDirection()); + break; + case kCaldoriaFourthToRoof: + case kCaldoriaGroundToRoof: + arriveAt(kCaldoria45, GameState.getCurrentDirection()); + break; + case kCaldoriaGroundToFourth: + case kCaldoriaRoofToFourth: + arriveAt(kCaldoria27, GameState.getCurrentDirection()); + break; + case kCaGTCardSwipe: + item = (InventoryItem *)_vm->getAllItems().findItemByID(kKeyCard); + _vm->addItemToInventory(item); + setCurrentActivation(kActivateReadyToTransport); + break; + case kCaGTFryTheFly: + case kCaGTGoToTSA: + _vm->jumpToNewEnvironment(kFullTSAID, kTSA00, kNorth); + break; + case kCaGTGoToTokyo: + playDeathExtra(kCaGTArriveAtTokyo, kDeathUncreatedInCaldoria); + break; + case kCaGTGoToBeach: + playDeathExtra(kCaGTArriveAtBeach, kDeathUncreatedInCaldoria); + break; + case kCa48NorthExplosion: + // Current biochip must be the shield if we got here. + _vm->getCurrentBiochip()->setItemState(kShieldNormal); + break; + case kBinocularsZoomInOnShip: + setCurrentActivation(kActivateFocusedOnShip); + break; + case kCa49NorthVoiceAnalysis: + _utilityFuse.primeFuse(kSinclairShootsTimeLimit); + _utilityFuse.setFunctor(new Common::Functor0Mem<void, Caldoria>(this, &Caldoria::sinclairTimerExpired)); + _utilityFuse.lightFuse(); + GameState.setCaldoriaSawVoiceAnalysis(true); + break; + case kCa53EastZoomToSinclair: + if (GameState.getCaldoriaSinclairShot()) { + delete _gunSprite; + _gunSprite = 0; + startExtraSequence(kCa53EastShootSinclair, kExtraCompletedFlag, false); + } else { + playDeathExtra(kCa53EastDeath2, kDeathSinclairShotDelegate); + } + break; + case kCa53EastShootSinclair: + _vm->addItemToInventory((InventoryItem *)_vm->getAllItems().findItemByID(kStunGun)); + startExtraSequence(kCa53EastZoomOutFromSinclair, kExtraCompletedFlag, false); + GameState.setScoringStunnedSinclair(true); + break; + case kCa53EastZoomOutFromSinclair: + setCurrentAlternate(kAltCaldoriaSinclairDown); + updateViewFrame(); + makeContinuePoint(); + break; + } + } else if ((flags & kSpotSoundCompletedFlag) != 0) { + switch (GameState.getCurrentRoom()) { + case kCaldoria20: + case kCaldoria21: + case kCaldoria26: + case kCaldoria29: + case kCaldoria34: + case kCaldoria35: + updateViewFrame(); + break; + case kCaldoria27: + case kCaldoria28: + case kCaldoria45: + updateElevatorMovie(); + break; + case kCaldoriaReplicator: + emptyOJGlass(); + break; + } + } else if ((flags & kSinclairLoopDoneFlag) != 0) { + if (++_sinclairLoopCount == _numSinclairLoops) { + switch (GameState.getCurrentRoom()) { + case kCaldoria50: + playDeathExtra(kCa50SinclairShoots, kDeathShotBySinclair); + break; + case kCaldoria54: + playDeathExtra(kCa54SouthDeath, kDeathShotBySinclair); + break; + } + } else { + _navMovie.stop(); + scheduleNavCallBack(kSinclairLoopDoneFlag); + _navMovie.start(); + } + } + + g_AIArea->checkMiddleArea(); +} + +InputBits Caldoria::getInputFilter() { + InputBits result = Neighborhood::getInputFilter(); + + switch (GameState.getCurrentRoom()) { + case kCaldoria00: + if (_privateFlags.getFlag(kCaldoriaPrivate4DSystemOpenFlag)) + result &= ~kFilterAllDirections; + break; + case kCaldoriaBinoculars: + if (getCurrentActivation() == kActivateNotFocusedOnShip) + result &= ~(kFilterDownButton | kFilterDownAuto); + break; + case kCaldoria53: + if (_privateFlags.getFlag(kCaldoriaPrivateReadyToShootFlag) && !GameState.getCaldoriaSinclairShot()) + result &= ~kFilterAllDirections; + break; + case kCaldoria48: + if (GameState.getCaldoriaDoorBombed()) + result &= ~kFilterAllDirections; + } + + return result; +} + +void Caldoria::activateHotspots() { + Neighborhood::activateHotspots(); + + switch (GameState.getCurrentRoom()) { + case kCaldoriaDrawers: + if (getCurrentActivation() == kActivateRightOpen) { + if (GameState.isTakenItemID(kKeyCard)) { + _vm->getAllHotspots().activateOneHotspot(kCaldoriaRightDrawerNoKeysCloseSpotID); + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaRightDrawerWithKeysCloseSpotID); + } else { + _vm->getAllHotspots().activateOneHotspot(kCaldoriaRightDrawerWithKeysCloseSpotID); + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaRightDrawerNoKeysCloseSpotID); + } + } + case kCaldoriaReplicator: + if (GameState.getCaldoriaMadeOJ()) + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaMakeOJSpotID); + break; + case kCaldoria27: + if (GameState.isCurrentDoorOpen()) { + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaFourthFloorElevator1); + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaFourthFloorElevator2); + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaFourthFloorElevator3); + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaFourthFloorElevator4); + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaFourthFloorElevator5); + } + break; + case kCaldoria28: + if (GameState.isCurrentDoorOpen()) { + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaGroundElevator1); + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaGroundElevator2); + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaGroundElevator3); + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaGroundElevator4); + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaGroundElevator5); + } + break; + case kCaldoria45: + if (GameState.isCurrentDoorOpen()) { + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaRoofElevator1); + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaRoofElevator2); + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaRoofElevator3); + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaRoofElevator4); + _vm->getAllHotspots().deactivateOneHotspot(kCaldoriaRoofElevator5); + } + break; + } +} + +void Caldoria::clickInHotspot(const Input &input, const Hotspot *spot) { + switch (spot->getObjectID()) { + case kCa4DEnvironOpenSpotID: + if (!GameState.getCaldoriaINNAnnouncing() || GameState.getCaldoriaSeenINN()) { + startExtraSequence(k4DEnvironOpen, kExtraCompletedFlag, kFilterNoInput); + } else { + // This trick depends on the following sequences being in order in the + // world movie: + // k4DEnvironOpenToINN + // k4DINNInterruption + // k4DINNIntro + // k4DINNMarkJohnson + // k4DINNMeganLove + // k4DINNFadeOut + // k4DEnvironOpenFromINN + loadLoopSound1(""); + loadLoopSound2(""); + startExtraLongSequence(k4DEnvironOpenToINN, k4DEnvironOpenFromINN, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kCa4DEnvironCloseSpotID: + ((Caldoria4DSystem *)_currentInteraction)->shutDown4DSystem(); + break; + case kCaBathroomMirrorSpotID: + newInteraction(kCaldoriaMirrorInteractionID); + break; + case kCaShowerSpotID: + requestExtraSequence(kCaldoriaShowerTitle, 0, kFilterNoInput); + requestExtraSequence(kCaldoriaShowerButton, 0, kFilterNoInput); + requestExtraSequence(kCaldoriaShowerDown, 0, kFilterNoInput); + requestExtraSequence(kCaldoriaShowerUp, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaLeftDrawerOpenSpotID: + _privateFlags.setFlag(kCaloriaPrivateLeftDrawerOpenFlag, true); + setCurrentActivation(kActivateLeftOpen); + startExtraSequence(kLeftDrawerOpen, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaLeftDrawerCloseSpotID: + _privateFlags.setFlag(kCaloriaPrivateLeftDrawerOpenFlag, false); + setCurrentActivation(kActivateDrawersClosed); + startExtraSequence(kLeftDrawerClose, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaRightDrawerOpenSpotID: + _privateFlags.setFlag(kCaldoriaPrivateRightDrawerOpenFlag, true); + setCurrentActivation(kActivateRightOpen); + if (GameState.isTakenItemID(kKeyCard)) + startExtraSequence(kRightDrawerOpenNoKeys, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kRightDrawerOpenWithKeys, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaRightDrawerWithKeysCloseSpotID: + _privateFlags.setFlag(kCaldoriaPrivateRightDrawerOpenFlag, false); + setCurrentActivation(kActivateDrawersClosed); + startExtraSequence(kRightDrawerCloseWithKeys, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaRightDrawerNoKeysCloseSpotID: + _privateFlags.setFlag(kCaldoriaPrivateRightDrawerOpenFlag, false); + setCurrentActivation(kActivateDrawersClosed); + startExtraSequence(kRightDrawerCloseNoKeys, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaMakeStickyBunsSpotID: + requestSpotSound(kCaldoriaReplicatorWrongChoiceIn, kCaldoriaReplicatorWrongChoiceOut, kFilterNoInput, 0); + break; + case kCaldoriaMakeOJSpotID: + GameState.setCaldoriaMadeOJ(true); + startExtraSequence(kCreateOrangeJuice, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaBedroomVidPhoneActivationSpotID: + newInteraction(kCaldoriaMessagesInteractionID); + break; + case kCaldoriaFourthFloorElevatorSpotID: + if (!GameState.getCaldoriaSeenSinclairInElevator()) { + startExtraSequence(kCaldoria16ElevatorUp, kExtraCompletedFlag, kFilterNoInput); + } else { + _privateFlags.setFlag(kCaldoriaPrivateCanOpenElevatorDoorFlag, true); + openDoor(); + } + break; + case kCaldoriaGroundElevatorSpotID: + _privateFlags.setFlag(kCaldoriaPrivateCanOpenElevatorDoorFlag, true); + openDoor(); + break; + case kCaldoriaRoofElevatorSpotID: + _privateFlags.setFlag(kCaldoriaPrivateCanOpenElevatorDoorFlag, true); + openDoor(); + break; + case kCaldoriaFourthFloorElevator1: + case kCaldoriaFourthFloorElevator2: + case kCaldoriaFourthFloorElevator3: + case kCaldoriaFourthFloorElevator4: + case kCaldoriaFourthFloorElevator5: + // Assumes that elevator hot spots are consecutive. + takeElevator(4, spot->getObjectID() - kCaldoriaFourthFloorElevator1 + 1); + break; + case kCaldoriaGroundElevator1: + case kCaldoriaGroundElevator2: + case kCaldoriaGroundElevator3: + case kCaldoriaGroundElevator4: + case kCaldoriaGroundElevator5: + // Assumes that elevator hot spots are consecutive. + takeElevator(1, spot->getObjectID() - kCaldoriaGroundElevator1 + 1); + break; + case kCaldoriaRoofElevator1: + case kCaldoriaRoofElevator2: + case kCaldoriaRoofElevator3: + case kCaldoriaRoofElevator4: + case kCaldoriaRoofElevator5: + // Assumes that elevator hot spots are consecutive. + takeElevator(5, spot->getObjectID() - kCaldoriaRoofElevator1 + 1); + break; + case kCaldoriaGTTokyoSpotID: + startExtraSequence(kCaGTGoToTokyo, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaGTTSASpotID: + GameState.setScoringGoToTSA(true); + startExtraLongSequence(kCaGTFryTheFly, kCaGTGoToTSA, kExtraCompletedFlag, false); + break; + case kCaldoriaGTBeachSpotID: + startExtraSequence(kCaGTGoToBeach, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaGTOtherSpotID: + showExtraView(kCaGTOtherChoice); + playSpotSoundSync(kCaldoriaNoOtherDestinationIn, kCaldoriaNoOtherDestinationOut); + showExtraView(kCaGTCardSwipe); + break; + case kCaldoriaZoomInOnShipSpotID: + startExtraSequence(kBinocularsZoomInOnShip, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoriaRoofDoorSpotID: + startExtraSequence(kCa48NorthRooftopClosed, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaldoria20DoorbellSpotID: + case kCaldoria21DoorbellSpotID: + case kCaldoria26DoorbellSpotID: + case kCaldoria29DoorbellSpotID: + case kCaldoria34DoorbellSpotID: + case kCaldoria35DoorbellSpotID: + clickOnDoorbell(spot->getObjectID()); + break; + default: + Neighborhood::clickInHotspot(input, spot); + break; + } +} + +void Caldoria::clickOnDoorbell(const HotSpotID doorBellSpotID) { + uint32 extra; + ExtraTable::Entry entry; + + switch (doorBellSpotID) { + case kCaldoria20DoorbellSpotID: + extra = kCaldoria20Doorbell; + break; + case kCaldoria21DoorbellSpotID: + extra = kCaldoria21Doorbell; + break; + case kCaldoria26DoorbellSpotID: + extra = kCaldoria26Doorbell; + break; + case kCaldoria29DoorbellSpotID: + extra = kCaldoria29Doorbell; + break; + case kCaldoria34DoorbellSpotID: + extra = kCaldoria34Doorbell; + break; + case kCaldoria35DoorbellSpotID: + extra = kCaldoria35Doorbell; + break; + default: + error("Invalid doorbell hotspot"); + } + + getExtraEntry(extra, entry); + showViewFrame(entry.movieStart); + requestSpotSound(kCaldoriaNobodyHomeIn, kCaldoriaNobodyHomeOut, kFilterNoInput, kSpotSoundCompletedFlag); +} + +CanOpenDoorReason Caldoria::canOpenDoor(DoorTable::Entry &entry) { + switch (GameState.getCurrentRoom()) { + case kCaldoria16: + case kCaldoria38: + case kCaldoria46: + if (GameState.getCurrentDirection() == kSouth && !_privateFlags.getFlag(kCaldoriaPrivateCanOpenElevatorDoorFlag)) + return kCantOpenLocked; + break; + } + + return Neighborhood::canOpenDoor(entry); +} + +void Caldoria::doorOpened() { + Neighborhood::doorOpened(); + _privateFlags.setFlag(kCaldoriaPrivateCanOpenElevatorDoorFlag, false); +} + +GameInteraction *Caldoria::makeInteraction(const InteractionID interactionID) { + switch (interactionID) { + case kCaldoria4DInteractionID: + return new Caldoria4DSystem(this); + case kCaldoriaBombInteractionID: + return new CaldoriaBomb(this, _vm); + case kCaldoriaMessagesInteractionID: + return new CaldoriaMessages(this, kCaldoriaMessagesNotificationID, _vm); + case kCaldoriaMirrorInteractionID: + return new CaldoriaMirror(this); + } + + return 0; +} + +void Caldoria::newInteraction(const InteractionID interactionID) { + Neighborhood::newInteraction(interactionID); + + if (!_currentInteraction) { + if (_privateFlags.getFlag(kCaldoriaPrivate4DSystemOpenFlag)) { + _privateFlags.setFlag(kCaldoriaPrivate4DSystemOpenFlag, false); + setCurrentActivation(kActivate4DClosed); + startExtraSequence(k4DEnvironClose, kExtraCompletedFlag, kFilterNoInput); + } else if (GameState.getCaldoriaBombDisarmed()) { + turnLeft(); + } + } +} + +// Only called when trying to pick up an item and the player can't (because +// the inventory is too full or because the player lets go of the item before +// dropping it into the inventory). +Hotspot *Caldoria::getItemScreenSpot(Item *item, DisplayElement *element) { + HotSpotID destSpotID = kNoHotSpotID; + + switch (item->getObjectID()) { + case kKeyCard: + destSpotID = kCaldoriaKeyCardSpotID; + break; + case kOrangeJuiceGlassEmpty: + case kOrangeJuiceGlassFull: + destSpotID = kCaldoriaOrangeJuiceSpotID; + break; + } + + if (destSpotID == kNoHotSpotID) + return Neighborhood::getItemScreenSpot(item, element); + + return _vm->getAllHotspots().findHotspotByID(destSpotID); +} + +void Caldoria::pickedUpItem(Item *item) { + switch (item->getObjectID()) { + case kKeyCard: + GameState.setScoringGotKeyCard(true); + break; + case kOrangeJuiceGlassFull: + setCurrentActivation(kActivateReplicatorReady); + requestSpotSound(kCaldoriaDrinkOJIn, kCaldoriaDrinkOJOut, kFilterNoInput, kSpotSoundCompletedFlag); + break; + case kStunGun: + GameState.setCaldoriaGunAimed(false); + break; + } +} + +void Caldoria::dropItemIntoRoom(Item *item, Hotspot *dropSpot) { + switch (item->getObjectID()) { + case kKeyCard: + Neighborhood::dropItemIntoRoom(item, dropSpot); + if (dropSpot->getObjectID() == kCaldoriaGTCardDropSpotID) + startExtraSequence(kCaGTCardSwipe, kExtraCompletedFlag, kFilterNoInput); + break; + case kOrangeJuiceGlassEmpty: + Neighborhood::dropItemIntoRoom(item, dropSpot); + if (dropSpot->getObjectID() == kCaldoriaOrangeJuiceDropSpotID) { + GameState.setCaldoriaMadeOJ(false); + startExtraSequence(kDisposeOrangeJuice, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kCardBomb: + GameState.setCaldoriaDoorBombed(true); + setCurrentActivation(kActivateHotSpotAlways); + Neighborhood::dropItemIntoRoom(item, dropSpot); + // Long enough for AI hints...? + _utilityFuse.primeFuse(kCardBombCountDownTime); + _utilityFuse.setFunctor(new Common::Functor0Mem<void, Caldoria>(this, &Caldoria::doorBombTimerExpired)); + _utilityFuse.lightFuse(); + GameState.setCaldoriaFuseTimeLimit(kCardBombCountDownTime); + loopCroppedMovie("Images/Caldoria/A48 Bomb Loop", kCaldoria48CardBombLoopLeft, kCaldoria48CardBombLoopTop); + GameState.setScoringUsedCardBomb(true); + break; + case kStunGun: + GameState.setCaldoriaGunAimed(true); + GameState.setCaldoriaSinclairShot(true); + _gunSprite = item->getDragSprite(0); + _gunSprite->setCurrentFrameIndex(1); + _gunSprite->setDisplayOrder(kDragSpriteOrder); + _gunSprite->moveElementTo(kCaldoriaGunSpriteLeft, kCaldoriaGunSpriteTop); + _gunSprite->startDisplaying(); + _gunSprite->show(); + break; + default: + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + } +} + +void Caldoria::takeElevator(uint startFloor, uint endFloor) { + _croppedMovie.stop(); + _croppedMovie.setSegment(0, _croppedMovie.getDuration()); + + switch (startFloor) { + case 1: + switch (endFloor) { + case 1: + // Do nothing. + break; + case 2: + _croppedMovie.setTime(k1To2Time); + requestSpotSound(kCaldoriaNoOtherDestinationIn, kCaldoriaNoOtherDestinationOut, kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 3: + _croppedMovie.setTime(k1To3Time); + requestSpotSound(kCaldoriaNoOtherDestinationIn, kCaldoriaNoOtherDestinationOut, kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 4: + _croppedMovie.setSegment(k1To4Start, k1To4Stop); + _croppedMovie.setTime(k1To4Start); + startExtraSequence(kCaldoriaGroundToFourth, kExtraCompletedFlag, false); + _croppedMovie.start(); + break; + case 5: + _croppedMovie.setSegment(k1To5Start, k1To5Stop); + _croppedMovie.setTime(k1To5Start); + startExtraSequence(kCaldoriaGroundToRoof, kExtraCompletedFlag, false); + _croppedMovie.start(); + break; + } + break; + case 4: + switch (endFloor) { + case 1: + _croppedMovie.setSegment(k4To1Start, k4To1Stop); + _croppedMovie.setTime(k4To1Start); + startExtraSequence(kCaldoriaFourthToGround, kExtraCompletedFlag, false); + _croppedMovie.start(); + break; + case 2: + _croppedMovie.setTime(k4To2Time); + requestSpotSound(kCaldoriaNoOtherDestinationIn, kCaldoriaNoOtherDestinationOut, kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 3: + _croppedMovie.setTime(k4To3Time); + requestSpotSound(kCaldoriaNoOtherDestinationIn, kCaldoriaNoOtherDestinationOut, kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 4: + // Do nothing. + break; + case 5: + _croppedMovie.setSegment(k4To5Start, k4To5Stop); + _croppedMovie.setTime(k4To5Start); + startExtraSequence(kCaldoriaFourthToRoof, kExtraCompletedFlag, false); + _croppedMovie.start(); + break; + } + break; + case 5: + switch (endFloor) { + case 1: + _croppedMovie.setSegment(k5To1Start, k5To1Stop); + _croppedMovie.setTime(k5To1Start); + startExtraSequence(kCaldoriaRoofToGround, kExtraCompletedFlag, false); + _croppedMovie.start(); + break; + case 2: + _croppedMovie.setTime(k5To2Time); + requestSpotSound(kCaldoriaNoOtherDestinationIn, kCaldoriaNoOtherDestinationOut, kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 3: + _croppedMovie.setTime(k5To3Time); + requestSpotSound(kCaldoriaNoOtherDestinationIn, kCaldoriaNoOtherDestinationOut, kFilterNoInput, kSpotSoundCompletedFlag); + break; + case 4: + _croppedMovie.setSegment(k5To4Start, k5To4Stop); + _croppedMovie.setTime(k5To4Start); + startExtraSequence(kCaldoriaRoofToFourth, kExtraCompletedFlag, false); + _croppedMovie.start(); + break; + case 5: + // Do nothing. + break; + } + break; + }; +} + +void Caldoria::updateElevatorMovie() { + TimeValue time = 0xffffffff; + + if (GameState.getCurrentDirection() == kNorth) { + switch (GameState.getCurrentRoom()) { + case kCaldoria27: + time = k4FloorTime; + break; + case kCaldoria28: + time = k1FloorTime; + break; + case kCaldoria45: + time = k5FloorTime; + break; + } + } + + _croppedMovie.stop(); + + if (time == 0xffffffff) { + _croppedMovie.hide(); + } else { + _croppedMovie.stop(); + _croppedMovie.setSegment(0, _croppedMovie.getDuration()); + _croppedMovie.setTime(time); + _croppedMovie.redrawMovieWorld(); + _croppedMovie.show(); + + // *** Why do I need this? + // clone2727: "don't ask me!" + _navMovie.redrawMovieWorld(); + } +} + +void Caldoria::openElevatorMovie() { + if (!_croppedMovie.isSurfaceValid()) + openCroppedMovie("Images/Caldoria/Caldoria Elevator.movie", kCaldoriaElevatorLeft, kCaldoriaElevatorTop); + + updateElevatorMovie(); +} + +void Caldoria::emptyOJGlass() { + GameState.setTakenItemID(kOrangeJuiceGlassFull, false); + GameState.setTakenItemID(kOrangeJuiceGlassEmpty, true); + _vm->removeItemFromInventory((InventoryItem *)_vm->getAllItems().findItemByID(kOrangeJuiceGlassFull)); + _vm->addItemToInventory((InventoryItem *)_vm->getAllItems().findItemByID(kOrangeJuiceGlassEmpty)); +} + +void Caldoria::doorBombTimerExpired() { + closeCroppedMovie(); + + if (GameState.getShieldOn()) { + _vm->getCurrentBiochip()->setItemState(kShieldCardBomb); + setCurrentAlternate(kAltCaldoriaRoofDoorBlown); + startExtraSequence(kCa48NorthExplosion, kExtraCompletedFlag, kFilterNoInput); + GameState.setScoringShieldedCardBomb(true); + GameState.setCaldoriaDoorBombed(false); + GameState.setCaldoriaRoofDoorOpen(true); + } else { + playDeathExtra(kCa48NorthExplosionDeath, kDeathCardBomb); + } +} + +void Caldoria::sinclairTimerExpired() { + _privateFlags.setFlag(kCaldoriaPrivateSinclairTimerExpiredFlag, true); + checkSinclairShootsOS(); +} + +void Caldoria::checkSinclairShootsOS() { + if (_privateFlags.getFlag(kCaldoriaPrivateSinclairTimerExpiredFlag)) + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kCaldoria49, kNorth): + case MakeRoomView(kCaldoria49, kSouth): + case MakeRoomView(kCaldoria49, kEast): + case MakeRoomView(kCaldoria49, kWest): + case MakeRoomView(kCaldoria50, kSouth): + case MakeRoomView(kCaldoria50, kEast): + case MakeRoomView(kCaldoria50, kWest): + case MakeRoomView(kCaldoria51, kNorth): + case MakeRoomView(kCaldoria51, kSouth): + case MakeRoomView(kCaldoria51, kWest): + case MakeRoomView(kCaldoria52, kNorth): + case MakeRoomView(kCaldoria52, kSouth): + case MakeRoomView(kCaldoria52, kWest): + case MakeRoomView(kCaldoria53, kNorth): + case MakeRoomView(kCaldoria53, kSouth): + case MakeRoomView(kCaldoria53, kWest): + case MakeRoomView(kCaldoria54, kNorth): + case MakeRoomView(kCaldoria54, kEast): + case MakeRoomView(kCaldoria54, kWest): + playSpotSoundSync(kCaldoriaSinclairShootsOSIn, kCaldoriaSinclairShootsOSOut); + playSpotSoundSync(kCaldoriaScreamingAfterIn, kCaldoriaScreamingAfterOut); + die(kDeathSinclairShotDelegate); + break; + } +} + +void Caldoria::checkInterruptSinclair() { + if (GameState.getCaldoriaSinclairShot()) { + _navMovie.stop(); + _neighborhoodNotification.setNotificationFlags(kExtraCompletedFlag, kExtraCompletedFlag); + g_AIArea->unlockAI(); + } else { + uint32 currentTime = _navMovie.getTime(); + + ExtraTable::Entry entry; + getExtraEntry(kCa53EastZoomToSinclair, entry); + + if (currentTime < entry.movieStart + kSinclairInterruptionTime2) + _sinclairInterrupt.scheduleCallBack(kTriggerTimeFwd, entry.movieStart + kSinclairInterruptionTime2, + _navMovie.getScale()); + else if (currentTime < entry.movieStart + kSinclairInterruptionTime3) + _sinclairInterrupt.scheduleCallBack(kTriggerTimeFwd, entry.movieStart + kSinclairInterruptionTime3, + _navMovie.getScale()); + else if (currentTime < entry.movieStart + kSinclairInterruptionTime4) + _sinclairInterrupt.scheduleCallBack(kTriggerTimeFwd, entry.movieStart + kSinclairInterruptionTime4, + _navMovie.getScale()); + } +} + +Common::String Caldoria::getBriefingMovie() { + Common::String movieName = Neighborhood::getBriefingMovie(); + + if (movieName.empty()) { + if (GameState.allTimeZonesFinished()) + return "Images/AI/Caldoria/XA02"; + + return "Images/AI/Caldoria/XA01"; + } + + return movieName; +} + +Common::String Caldoria::getEnvScanMovie() { + Common::String movieName = Neighborhood::getEnvScanMovie(); + + if (movieName.empty()) { + RoomID room = GameState.getCurrentRoom(); + + if (room >= kCaldoria00 && room <= kCaldoria14) { + // Inside apartment. + if (GameState.getCaldoriaDoneHygiene()) + return "Images/AI/Caldoria/XAE2"; + + return "Images/AI/Caldoria/XAE1"; + } else if (room >= kCaldoria15 && room <= kCaldoria48) { + // Wandering the halls... + return "Images/AI/Caldoria/XAE3"; + } else { + // Must be the roof. + return "Images/AI/Caldoria/XAEH2"; + } + } + + return movieName; +} + +uint Caldoria::getNumHints() { + uint numHints = Neighborhood::getNumHints(); + + if (numHints == 0) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kCaldoria44, kEast): + if (!GameState.isTakenItemID(kKeyCard) && GameState.getOpenDoorRoom() == kNoRoomID) + numHints = 1; + break; + case MakeRoomView(kCaldoria48, kNorth): + if (!GameState.getCaldoriaRoofDoorOpen()) { + if (_croppedMovie.isRunning()) // Bomb must be looping. + numHints = 3; + else if (GameState.isTakenItemID(kCardBomb)) + numHints = 1; + } + break; + case MakeRoomView(kCaldoria49, kEast): + case MakeRoomView(kCaldoria54, kEast): + numHints = 1; + break; + case MakeRoomView(kCaldoria49, kNorth): + numHints = 1; + break; + } + } + + return numHints; +} + +Common::String Caldoria::getHintMovie(uint hintNum) { + Common::String movieName = Neighborhood::getHintMovie(hintNum); + + if (movieName.empty()) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kCaldoria44, kEast): + return "Images/AI/Caldoria/X42WH2"; + case MakeRoomView(kCaldoria48, kNorth): + if (_croppedMovie.isRunning()) { // Bomb must be looping. + if (hintNum == 1) + return "Images/AI/Caldoria/X48ND1"; + else if (hintNum == 2) + return "Images/AI/Caldoria/X48ND2"; + else if (GameState.isTakenItemID(kShieldBiochip)) + return "Images/AI/Caldoria/X48ND3"; + + // *** Doesn't work yet, need global movies. + break; + } + + return "Images/AI/Globals/XGLOB1A"; + case MakeRoomView(kCaldoria49, kEast): + case MakeRoomView(kCaldoria54, kEast): + return "Images/AI/Caldoria/X49E"; + case MakeRoomView(kCaldoria49, kNorth): + return "Images/AI/Caldoria/X49NB2"; + } + } + + return movieName; +} + +void Caldoria::updateCursor(const Common::Point where, const Hotspot *cursorSpot) { + if (cursorSpot) { + switch (cursorSpot->getObjectID()) { + case kCa4DEnvironCloseSpotID: + _vm->_cursor->setCurrentFrameIndex(2); + return; + case kCaldoriaKioskSpotID: + _vm->_cursor->setCurrentFrameIndex(3); + return; + } + } + + Neighborhood::updateCursor(where, cursorSpot); +} + +Common::String Caldoria::getNavMovieName() { + return "Images/Caldoria/Caldoria.movie"; +} + +Common::String Caldoria::getSoundSpotsName() { + return "Sounds/Caldoria/Caldoria Spots"; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/caldoria/caldoria.h b/engines/pegasus/neighborhood/caldoria/caldoria.h new file mode 100644 index 0000000000..3d6a155170 --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoria.h @@ -0,0 +1,523 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIA_H +#define PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIA_H + +#include "pegasus/neighborhood/neighborhood.h" + +namespace Pegasus { + +static const TimeScale kCaldoriaMovieScale = 600; +static const TimeScale kCaldoriaFramesPerSecond = 15; +static const TimeScale kCaldoriaFrameDuration = 40; + +// Alternate IDs. + +static const AlternateID kAltCaldoriaNormal = 0; +static const AlternateID kAltCaldoriaRoofDoorBlown = 2; +static const AlternateID kAltCaldoriaSinclairDown = 3; + +// Room IDs. + +static const RoomID kCaldoria00 = 1; +static const RoomID kCaldoria01 = 2; +static const RoomID kCaldoria02 = 3; +static const RoomID kCaldoria03 = 4; +static const RoomID kCaldoria04 = 5; +static const RoomID kCaldoria05 = 6; +static const RoomID kCaldoria06 = 7; +static const RoomID kCaldoria07 = 8; +static const RoomID kCaldoria08 = 9; +static const RoomID kCaldoria09 = 10; +static const RoomID kCaldoria10 = 11; +static const RoomID kCaldoriaToilet = 12; +static const RoomID kCaldoria11 = 13; +static const RoomID kCaldoria12 = 14; +static const RoomID kCaldoriaVidPhone = 15; +static const RoomID kCaldoriaReplicator = 16; +static const RoomID kCaldoriaDrawers = 17; +static const RoomID kCaldoria13 = 18; +static const RoomID kCaldoria14 = 19; +static const RoomID kCaldoria15 = 20; +static const RoomID kCaldoria16 = 21; +static const RoomID kCaldoria17 = 22; +static const RoomID kCaldoria18 = 23; +static const RoomID kCaldoria19 = 24; +static const RoomID kCaldoria20 = 25; +static const RoomID kCaldoria21 = 26; +static const RoomID kCaldoria22 = 27; +static const RoomID kCaldoria23 = 28; +static const RoomID kCaldoria24 = 29; +static const RoomID kCaldoria25 = 30; +static const RoomID kCaldoria26 = 31; +static const RoomID kCaldoria27 = 32; +static const RoomID kCaldoria28 = 33; +static const RoomID kCaldoria29 = 34; +static const RoomID kCaldoria30 = 35; +static const RoomID kCaldoria31 = 36; +static const RoomID kCaldoria32 = 37; +static const RoomID kCaldoria33 = 38; +static const RoomID kCaldoria34 = 39; +static const RoomID kCaldoria35 = 40; +static const RoomID kCaldoria36 = 41; +static const RoomID kCaldoria37 = 42; +static const RoomID kCaldoria38 = 43; +static const RoomID kCaldoria39 = 44; +static const RoomID kCaldoria40 = 45; +static const RoomID kCaldoria41 = 46; +static const RoomID kCaldoriaBinoculars = 47; +static const RoomID kCaldoria42 = 48; +static const RoomID kCaldoriaKiosk = 49; +static const RoomID kCaldoria44 = 50; +static const RoomID kCaldoria45 = 51; +static const RoomID kCaldoria46 = 52; +static const RoomID kCaldoria47 = 53; +static const RoomID kCaldoria48 = 54; +static const RoomID kCaldoria49 = 55; +static const RoomID kCaldoria50 = 56; +static const RoomID kCaldoria51 = 57; +static const RoomID kCaldoria52 = 58; +static const RoomID kCaldoria53 = 59; +static const RoomID kCaldoria54 = 60; +static const RoomID kCaldoria55 = 61; +static const RoomID kCaldoria56 = 62; +static const RoomID kCaldoriaDeathRoom = 0; + +// Hot Spot Activation IDs. + +static const HotSpotActivationID kActivate4DClosed = 1; +static const HotSpotActivationID kActivate4DOpen = 2; +static const HotSpotActivationID kActivateMirrorReady = 3; +static const HotSpotActivationID kActivateStylistReady = 4; +static const HotSpotActivationID kActivateReplicatorReady = 5; +static const HotSpotActivationID kActivateOJOnThePad = 6; +static const HotSpotActivationID kActivateDrawersClosed = 7; +static const HotSpotActivationID kActivateRightOpen = 8; +static const HotSpotActivationID kActivateLeftOpen = 9; +static const HotSpotActivationID kActivateFocusedOnShip = 10; +static const HotSpotActivationID kActivateNotFocusedOnShip = 11; +static const HotSpotActivationID kActivateReadyForCard = 12; +static const HotSpotActivationID kActivateReadyToTransport = 13; +static const HotSpotActivationID kActivateRoofSlotEmpty = 14; +static const HotSpotActivationID kActivateZoomedOnSinclair = 15; + +// Hot Spot IDs. + +static const HotSpotID kCa4DEnvironOpenSpotID = 5000; +static const HotSpotID kCa4DEnvironCloseSpotID = 5001; +static const HotSpotID kCa4DVisualSpotID = 5002; +static const HotSpotID kCa4DAudioSpotID = 5003; +static const HotSpotID kCa4DChoice1SpotID = 5004; +static const HotSpotID kCa4DChoice2SpotID = 5005; +static const HotSpotID kCa4DChoice3SpotID = 5006; +static const HotSpotID kCa4DChoice4SpotID = 5007; +static const HotSpotID kCaBathroomMirrorSpotID = 5008; +static const HotSpotID kCaHairStyle1SpotID = 5009; +static const HotSpotID kCaHairStyle2SpotID = 5010; +static const HotSpotID kCaHairStyle3SpotID = 5011; +static const HotSpotID kCaShowerSpotID = 5012; +static const HotSpotID kCaBathroomToiletSpotID = 5013; +static const HotSpotID kCaldoriaVidPhoneSpotID = 5014; +static const HotSpotID kCaldoriaReplicatorSpotID = 5015; +static const HotSpotID kCaldoriaDrawersSpotID = 5016; +static const HotSpotID kCaldoriaVidPhoneOutSpotID = 5017; +static const HotSpotID kCaBedroomVidPhoneActivationSpotID = 5018; +static const HotSpotID kCaldoriaReplicatorOutSpotID = 5019; +static const HotSpotID kCaldoriaMakeOJSpotID = 5020; +static const HotSpotID kCaldoriaMakeStickyBunsSpotID = 5021; +static const HotSpotID kCaldoriaOrangeJuiceSpotID = 5022; +static const HotSpotID kCaldoriaOrangeJuiceDropSpotID = 5023; +static const HotSpotID kCaldoriaDrawersOutSpotID = 5024; +static const HotSpotID kCaldoriaLeftDrawerOpenSpotID = 5025; +static const HotSpotID kCaldoriaRightDrawerOpenSpotID = 5026; +static const HotSpotID kCaldoriaKeyCardSpotID = 5027; +static const HotSpotID kCaldoriaLeftDrawerCloseSpotID = 5028; +static const HotSpotID kCaldoriaRightDrawerWithKeysCloseSpotID = 5029; +static const HotSpotID kCaldoriaRightDrawerNoKeysCloseSpotID = 5030; +static const HotSpotID kCaldoriaFourthFloorElevatorSpotID = 5031; +static const HotSpotID kCaldoria20DoorbellSpotID = 5032; +static const HotSpotID kCaldoria21DoorbellSpotID = 5033; +static const HotSpotID kCaldoria26DoorbellSpotID = 5034; +static const HotSpotID kCaldoriaFourthFloorElevator1 = 5035; +static const HotSpotID kCaldoriaFourthFloorElevator2 = 5036; +static const HotSpotID kCaldoriaFourthFloorElevator3 = 5037; +static const HotSpotID kCaldoriaFourthFloorElevator4 = 5038; +static const HotSpotID kCaldoriaFourthFloorElevator5 = 5039; +static const HotSpotID kCaldoriaGroundElevator1 = 5040; +static const HotSpotID kCaldoriaGroundElevator2 = 5041; +static const HotSpotID kCaldoriaGroundElevator3 = 5042; +static const HotSpotID kCaldoriaGroundElevator4 = 5043; +static const HotSpotID kCaldoriaGroundElevator5 = 5044; +static const HotSpotID kCaldoria29DoorbellSpotID = 5045; +static const HotSpotID kCaldoria34DoorbellSpotID = 5046; +static const HotSpotID kCaldoria35DoorbellSpotID = 5047; +static const HotSpotID kCaldoriaGroundElevatorSpotID = 5048; +static const HotSpotID kCaldoriaBinocularZoomInSpotID = 5049; +static const HotSpotID kCaldoriaBinocularsOutSpotID = 5050; +static const HotSpotID kCaldoriaZoomInOnShipSpotID = 5051; +static const HotSpotID kCaldoriaKioskSpotID = 5052; +static const HotSpotID kCaldoriaKioskOutSpotID = 5053; +static const HotSpotID kCaldoriaKioskInfoSpotID = 5054; +static const HotSpotID kCaldoriaGTCardDropSpotID = 5055; +static const HotSpotID kCaldoriaGTTokyoSpotID = 5056; +static const HotSpotID kCaldoriaGTTSASpotID = 5057; +static const HotSpotID kCaldoriaGTBeachSpotID = 5058; +static const HotSpotID kCaldoriaGTOtherSpotID = 5059; +static const HotSpotID kCaldoriaRoofElevator1 = 5060; +static const HotSpotID kCaldoriaRoofElevator2 = 5061; +static const HotSpotID kCaldoriaRoofElevator3 = 5062; +static const HotSpotID kCaldoriaRoofElevator4 = 5063; +static const HotSpotID kCaldoriaRoofElevator5 = 5064; +static const HotSpotID kCaldoriaRoofElevatorSpotID = 5065; +static const HotSpotID kCaldoriaRoofDoorSpotID = 5066; +static const HotSpotID kCaldoriaRoofCardDropSpotID = 5067; +static const HotSpotID kCaldoria53EastSinclairTargetSpotID = 5068; + +// Extra sequence IDs. + +static const ExtraID kCaldoriaWakeUpView1 = 0; +static const ExtraID kCaldoria00WakeUp1 = 1; +static const ExtraID kCaldoria00WakeUp2 = 2; +static const ExtraID kCaldoria00SitDown = 3; +static const ExtraID k4DEnvironOpenToINN = 4; +static const ExtraID k4DINNInterruption = 5; +static const ExtraID k4DINNIntro = 6; +static const ExtraID k4DINNMarkJohnson = 7; +static const ExtraID k4DINNMeganLove = 8; +static const ExtraID k4DINNFadeOut = 9; +static const ExtraID k4DEnvironOpenFromINN = 10; +static const ExtraID k4DEnvironOpen = 11; +static const ExtraID k4DEnvironOpenView = 12; +static const ExtraID k4DEnvironClose = 13; +static const ExtraID k4DIslandLoop = 14; +static const ExtraID k4DDesertLoop = 15; +static const ExtraID k4DMountainLoop = 16; +static const ExtraID k4DIsland1ToIsland0 = 17; +static const ExtraID k4DIsland2ToIsland0 = 18; +static const ExtraID k4DIsland0ToDesert0 = 19; +static const ExtraID k4DIsland1ToDesert0 = 20; +static const ExtraID k4DIsland2ToDesert0 = 21; +static const ExtraID k4DIsland0ToMountain0 = 22; +static const ExtraID k4DIsland1ToMountain0 = 23; +static const ExtraID k4DIsland2ToMountain0 = 24; +static const ExtraID k4DDesert0ToIsland0 = 25; +static const ExtraID k4DDesert1ToIsland0 = 26; +static const ExtraID k4DDesert2ToIsland0 = 27; +static const ExtraID k4DDesert0ToMountain0 = 28; +static const ExtraID k4DDesert1ToMountain0 = 29; +static const ExtraID k4DDesert2ToMountain0 = 30; +static const ExtraID k4DMountain0ToIsland0 = 31; +static const ExtraID k4DMountain1ToIsland0 = 32; +static const ExtraID k4DMountain2ToIsland0 = 33; +static const ExtraID k4DMountain0ToDesert0 = 34; +static const ExtraID k4DMountain1ToDesert0 = 35; +static const ExtraID k4DMountain2ToDesert0 = 36; +static const ExtraID kCaBathroomGreeting = 37; +static const ExtraID kCaBathroomBodyFat = 38; +static const ExtraID kCaBathroomStylistIntro = 39; +static const ExtraID kCaBathroomRetrothrash = 40; +static const ExtraID kCaBathroomRetrothrashReturn = 41; +static const ExtraID kCaBathroomGeoWave = 42; +static const ExtraID kCaBathroomGeoWaveReturn = 43; +static const ExtraID kCaBathroomAgencyStandard = 44; +static const ExtraID kCaldoriaShowerTitle = 45; +static const ExtraID kCaldoriaShowerButton = 46; +static const ExtraID kCaldoriaShowerDown = 47; +static const ExtraID kCaldoriaShowerUp = 48; +static const ExtraID kCaBedroomVidPhone = 49; +static const ExtraID kCaBedroomMessage1 = 50; +static const ExtraID kCaBedroomMessage2 = 51; +static const ExtraID kCreateOrangeJuice = 52; +static const ExtraID kDisposeOrangeJuice = 53; +static const ExtraID kReplicatorNorthViewWithOJ = 54; +static const ExtraID kLeftDrawerOpen = 55; +static const ExtraID kLeftDrawerClose = 56; +static const ExtraID kRightDrawerOpenWithKeys = 57; +static const ExtraID kRightDrawerCloseWithKeys = 58; +static const ExtraID kRightDrawerOpenNoKeys = 59; +static const ExtraID kRightDrawerCloseNoKeys = 60; +static const ExtraID kRightDrawerOpenViewWithKeys = 61; +static const ExtraID kRightDrawerOpenViewNoKeys = 62; +static const ExtraID kCaldoria16ElevatorUp = 63; +static const ExtraID kCaldoria16ElevatorDown = 64; +static const ExtraID kCaldoria16SouthViewWithElevator = 65; +static const ExtraID kCaldoria20Doorbell = 66; +static const ExtraID kCaldoria21Doorbell = 67; +static const ExtraID kCaldoria26Doorbell = 68; +static const ExtraID kCaldoriaFourthToGround = 69; +static const ExtraID kCaldoriaRoofToFourth = 70; +static const ExtraID kCaldoriaRoofToGround = 71; +static const ExtraID kCaldoriaGroundToFourth = 72; +static const ExtraID kCaldoriaGroundToRoof = 73; +static const ExtraID kCaldoriaFourthToRoof = 74; +static const ExtraID kCaldoria29Doorbell = 75; +static const ExtraID kCaldoria34Doorbell = 76; +static const ExtraID kCaldoria35Doorbell = 77; +static const ExtraID kBinocularsZoomInOnShip = 78; +static const ExtraID kCaldoriaKioskVideo = 79; +static const ExtraID kCaldoriaTransporterArrowLoop = 80; +static const ExtraID kArriveAtCaldoriaFromTSA = 81; +static const ExtraID kCaGTOtherChoice = 82; +static const ExtraID kCaGTCardSwipe = 83; +static const ExtraID kCaGTSelectTSA = 84; +static const ExtraID kCaGTFryTheFly = 85; +static const ExtraID kCaGTGoToTSA = 86; +static const ExtraID kCaGTSelectBeach = 87; +static const ExtraID kCaGTGoToBeach = 88; +static const ExtraID kCaGTArriveAtBeach = 89; +static const ExtraID kCaGTSelectTokyo = 90; +static const ExtraID kCaGTGoToTokyo = 91; +static const ExtraID kCaGTArriveAtTokyo = 92; +static const ExtraID kCa48NorthRooftopClosed = 93; +static const ExtraID kCa48NorthExplosion = 94; +static const ExtraID kCa48NorthExplosionDeath = 95; +static const ExtraID kCa49NorthVoiceAnalysis = 96; +static const ExtraID kCa50SinclairShoots = 97; +static const ExtraID kCa53EastZoomToSinclair = 98; +static const ExtraID kCa53EastDeath2 = 99; +static const ExtraID kCa53EastShootSinclair = 100; +static const ExtraID kCa53EastZoomOutFromSinclair = 101; +static const ExtraID kCa54SouthDeath = 102; +static const ExtraID kCaldoria56BombStage1 = 103; +static const ExtraID kCaldoria56BombStage2 = 104; +static const ExtraID kCaldoria56BombStage3 = 105; +static const ExtraID kCaldoria56BombStage4 = 106; +static const ExtraID kCaldoria56BombStage5 = 107; +static const ExtraID kCaldoria56BombStage6 = 108; +static const ExtraID kCaldoria56BombStage7 = 109; +static const ExtraID kCaldoria56BombExplodes = 110; + +// Caldoria interactions. + +static const InteractionID kCaldoria4DInteractionID = 0; +static const InteractionID kCaldoriaBombInteractionID = 1; +static const InteractionID kCaldoriaMessagesInteractionID = 2; +static const InteractionID kCaldoriaMirrorInteractionID = 3; + +// Caldoria: + +static const DisplayOrder kVidPhoneOrder = kMonitorLayer; +static const DisplayOrder k4DSpritesOrder = kMonitorLayer; +static const DisplayOrder kCaldoriaMessagesOrder = kMonitorLayer; +static const DisplayOrder kCaldoriaElevatorOrder = kMonitorLayer; +static const DisplayOrder kCaldoriaA05LightLoopOrder = kMonitorLayer; +static const DisplayOrder kCaldoriaA07LightLoopOrder = kMonitorLayer; +static const DisplayOrder kCaldoriaBombGridOrder = kMonitorLayer; +static const DisplayOrder kCaldoriaBombTimerOrder = kCaldoriaBombGridOrder + 1; + +///////////////////////////////////////////// +// +// Caldoria + +static const CoordType kCaldoriaVidPhoneLeft = kNavAreaLeft + 105; +static const CoordType kCaldoriaVidPhoneTop = kNavAreaTop + 28; + +static const CoordType kCaldoria4DSpritesLeft = kNavAreaLeft + 10; +static const CoordType kCaldoria4DSpritesTop = kNavAreaTop + 142; + +static const CoordType kCaldoriaMessageLeft = kNavAreaLeft + 202; +static const CoordType kCaldoriaMessageTop = kNavAreaTop + 26; + +static const CoordType kCaldoriaElevatorLeft = kNavAreaLeft + 407; +static const CoordType kCaldoriaElevatorTop = kNavAreaTop + 138; + +static const CoordType kCaldoriaA05LightLoopLeft = kNavAreaLeft + 213; +static const CoordType kCaldoriaA05LightLoopTop = kNavAreaTop + 215; + +static const CoordType kCaldoriaA07LightLoopLeft = kNavAreaLeft + 414; +static const CoordType kCaldoriaA07LightLoopTop = kNavAreaTop + 215; + +static const CoordType kCaldoriaGunSpriteLeft = kNavAreaLeft + 276; +static const CoordType kCaldoriaGunSpriteTop = kNavAreaTop + 115; + +static const CoordType kCaldoria11MessageLoopLeft = kNavAreaLeft + 135; +static const CoordType kCaldoria11MessageLoopTop = kNavAreaTop + 214; + +static const CoordType kCaldoria12MessageLoopLeft = kNavAreaLeft + 209; +static const CoordType kCaldoria12MessageLoopTop = kNavAreaTop + 170; + +static const CoordType kCaldoria13MessageLoopLeft = kNavAreaLeft + 480; +static const CoordType kCaldoria13MessageLoopTop = kNavAreaTop + 191; + +static const CoordType kCaldoria14MessageLoopLeft = kNavAreaLeft + 248; +static const CoordType kCaldoria14MessageLoopTop = kNavAreaTop + 191; + +static const CoordType kCaldoria48CardBombLoopLeft = kNavAreaLeft + 337; +static const CoordType kCaldoria48CardBombLoopTop = kNavAreaTop + 205; + +static const CoordType kCaldoriaBombGridLeft = kNavAreaLeft + 290; +static const CoordType kCaldoriaBombGridTop = kNavAreaTop + 58; + +static const CoordType kCaldoriaBombTimerLeft = kNavAreaLeft + 58; +static const CoordType kCaldoriaBombTimerTop = kNavAreaTop + 204; + +// Caldoria display IDs. + +static const DisplayElementID kCaldoriaVidPhoneID = kNeighborhoodDisplayID; +static const DisplayElementID kCaldoria4DSpritesID = kCaldoriaVidPhoneID + 1; +static const DisplayElementID kCaldoriaMessagesID = kCaldoria4DSpritesID + 1; +static const DisplayElementID kCaldoriaUtilityID = kCaldoriaMessagesID + 1; +static const DisplayElementID kCaldoriaBombGridID = kCaldoriaUtilityID + 1; +static const DisplayElementID kCaldoriaBombTimerID = kCaldoriaBombGridID + 1; + +static const TimeValue kCaldoria4DBlankChoiceIn = 29730; +static const TimeValue kCaldoria4DBlankChoiceOut = 33910; + +class Caldoria; + +class SinclairCallBack : public TimeBaseCallBack { +public: + SinclairCallBack(Caldoria *); + ~SinclairCallBack() {} + +protected: + virtual void callBack(); + + Caldoria *_caldoria; +}; + +class Caldoria : public Neighborhood { +friend class SinclairCallBack; + +public: + Caldoria(InputHandler *, PegasusEngine *); + virtual ~Caldoria(); + + virtual uint16 getDateResID() const; + + void pickedUpItem(Item *); + + virtual GameInteraction *makeInteraction(const InteractionID); + + virtual Common::String getBriefingMovie(); + virtual Common::String getEnvScanMovie(); + virtual uint getNumHints(); + virtual Common::String getHintMovie(uint); + void loadAmbientLoops(); + bool wantsCursor(); + void flushGameState(); + + void checkContinuePoint(const RoomID, const DirectionConstant); + +protected: + enum { + kCaldoriaPrivate4DSystemOpenFlag, + kCaloriaPrivateLeftDrawerOpenFlag, + kCaldoriaPrivateRightDrawerOpenFlag, + kCaldoriaPrivateReadyToShootFlag, + kCaldoriaPrivateZoomingToBombFlag, + kCaldoriaPrivateCanOpenElevatorDoorFlag, + kCaldoriaPrivateSinclairTimerExpiredFlag, + kCaldoriaPrivateSeen13CarFlag, + kCaldoriaPrivateSeen14CarFlag, + kCaldoriaPrivateSeen18CarFlag, + kCaldoriaPrivateSeen23CarFlag, + kCaldoriaPrivateSeen33CarFlag, + kCaldoriaPrivateSeen36CarFlag, + kCaldoriaPrivateSeen41NorthCarFlag, + kCaldoriaPrivateSeen41EastCarFlag, + kCaldoriaPrivateSeen41WestCarFlag, + kNumCaldoriaPrivateFlags + }; + + void init(); + void start(); + + void setUpRoofTop(); + + void setUpAIRules(); + void doAIRecalibration(); + TimeValue getViewTime(const RoomID, const DirectionConstant); + void findSpotEntry(const RoomID, const DirectionConstant, SpotFlags, SpotTable::Entry &); + void startSpotOnceOnly(TimeValue, TimeValue); + void startExitMovie(const ExitTable::Entry &); + void startZoomMovie(const ZoomTable::Entry &); + void startDoorOpenMovie(const TimeValue, const TimeValue); + void startTurnPush(const TurnDirection, const TimeValue, const DirectionConstant); + void bumpIntoWall(); + int16 getStaticCompassAngle(const RoomID, const DirectionConstant); + void getExitCompassMove(const ExitTable::Entry &, FaderMoveSpec &); + void getZoomCompassMove(const ZoomTable::Entry &, FaderMoveSpec &); + void getExtraCompassMove(const ExtraTable::Entry &, FaderMoveSpec &); + void spotCompleted(); + void arriveAt(const RoomID, const DirectionConstant); + void arriveAtCaldoria00(); + void arriveAtCaldoriaToilet(); + void arriveAtCaldoria44(); + void arriveAtCaldoria49(); + void arriveAtCaldoria56(); + void arriveAtCaldoriaDeath(); + void turnTo(const DirectionConstant); + void zoomTo(const Hotspot *); + void downButton(const Input &); + void receiveNotification(Notification *, const NotificationFlags); + InputBits getInputFilter(); + void activateHotspots(); + void clickInHotspot(const Input &, const Hotspot *); + void newInteraction(const InteractionID); + + void clickOnDoorbell(const HotSpotID); + + Hotspot *getItemScreenSpot(Item *, DisplayElement *); + void dropItemIntoRoom(Item *, Hotspot *); + void takeElevator(uint, uint); + void updateElevatorMovie(); + void openElevatorMovie(); + void emptyOJGlass(); + void closeDoorOffScreen(const RoomID, const DirectionConstant); + void doorBombTimerExpired(); + void sinclairTimerExpired(); + void checkSinclairShootsOS(); + void setUpSinclairLoops(); + void zoomToSinclair(); + void playEndMessage(); + void checkInterruptSinclair(); + + CanOpenDoorReason canOpenDoor(DoorTable::Entry &); + void doorOpened(); + + void updateCursor(const Common::Point, const Hotspot *); + + FlagsArray<uint16, kNumCaldoriaPrivateFlags> _privateFlags; + + const Hotspot *_zoomOutSpot; + + FuseFunction _utilityFuse; + + long _sinclairLoopCount; + long _numSinclairLoops; + + Sprite *_gunSprite; + + SinclairCallBack _sinclairInterrupt; + + Common::String getSoundSpotsName(); + Common::String getNavMovieName(); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.cpp b/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.cpp new file mode 100644 index 0000000000..0494753661 --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.cpp @@ -0,0 +1,370 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/neighborhood/caldoria/caldoria.h" +#include "pegasus/neighborhood/caldoria/caldoria4dsystem.h" + +namespace Pegasus { + +static const TimeValue kSwitchableSlop = 3 * kCaldoriaFrameDuration; +// Two seconds - some slop +static const TimeValue kSwitchableDuration = kCaldoriaMovieScale * 2 - kSwitchableSlop; +// Twelve frames + some slop +static const TimeValue kNonswitchableDuration = kCaldoriaFrameDuration * 12 + kSwitchableSlop; + +static const TimeValue kSwitchable1Start = 0; +static const TimeValue kSwitchable1Stop = kSwitchable1Start + kSwitchableDuration; + +static const TimeValue kSwitchable2Start = kSwitchable1Stop + kNonswitchableDuration; +static const TimeValue kSwitchable2Stop = kSwitchable2Start + kSwitchableDuration; + +static const TimeValue kSwitchable3Start = kSwitchable2Stop + kNonswitchableDuration; +static const TimeValue kSwitchable3Stop = kSwitchable3Start + kSwitchableDuration; + +static const NotificationFlags kVidPhoneDoneFlag = 1; + +static const TimeValue kRockMusicLoopIn = 0; +static const TimeValue kRockMusicLoopOut = 2088; + +static const TimeValue kOrchestralMusicLoopIn = 2088; +static const TimeValue kOrchestralMusicLoopOut = 4985; + +static const TimeValue kRhythmsMusicLoopIn = 4985; +static const TimeValue kRhythmsMusicLoopOut = 6824; + +static const TimeValue kAcousticMusicLoopIn = 6824; +static const TimeValue kAcousticMusicLoopOut = 9387; + +enum { + k4DVideoMenu, + k4DAudioMenu, + k4DShuttingDown, + + // These constants are the exact frame numbers of the sprite movie. + k4DRockChoice = 0, + k4DOrchestralChoice, + k4DRhythmsChoice, + k4DAcousticChoice, + k4DIslandChoice, + k4DDesertChoice, + k4DMountainChoice, + + k4DFirstVideoChoice = k4DIslandChoice +}; + +static const ExtraID s_transitionExtras0[3][3] = { + { 0xffffffff, k4DIsland0ToDesert0, k4DIsland0ToMountain0 }, + { k4DDesert0ToIsland0, 0xffffffff, k4DDesert0ToMountain0 }, + { k4DMountain0ToIsland0, k4DMountain0ToDesert0, 0xffffffff } +}; + +static const ExtraID s_transitionExtras1[3][3] = { + { 0xffffffff, k4DIsland1ToDesert0, k4DIsland1ToMountain0 }, + { k4DDesert1ToIsland0, 0xffffffff, k4DDesert1ToMountain0 }, + { k4DMountain1ToIsland0, k4DMountain1ToDesert0, 0xffffffff } +}; + +static const ExtraID s_transitionExtras2[3][3] = { + { 0xffffffff, k4DIsland2ToDesert0, k4DIsland2ToMountain0 }, + { k4DDesert2ToIsland0, 0xffffffff, k4DDesert2ToMountain0 }, + { k4DMountain2ToIsland0, k4DMountain2ToDesert0, 0xffffffff } +}; + +static const ExtraID s_shutDownExtras[3][3] = { + { 0xffffffff, k4DIsland1ToIsland0, k4DIsland2ToIsland0 }, + { k4DDesert0ToIsland0, k4DDesert1ToIsland0, k4DDesert2ToIsland0 }, + { k4DMountain0ToIsland0, k4DMountain1ToIsland0, k4DMountain2ToIsland0 } +}; + +Caldoria4DSystem::Caldoria4DSystem(Neighborhood *owner) : GameInteraction(kCaldoria4DInteractionID, owner), + _4DSpritesMovie(kCaldoria4DSpritesID) { + g_AIArea->lockAIOut(); +} + +Caldoria4DSystem::~Caldoria4DSystem() { + g_AIArea->unlockAI(); +} + +void Caldoria4DSystem::openInteraction() { + _whichMenu = k4DVideoMenu; + _videoChoice = k4DIslandChoice; + _audioChoice = k4DRockChoice; + _clickedHotspotID = kNoHotSpotID; + + _4DSpritesMovie.initFromMovieFile("Images/Caldoria/4D Sprites", true); + _4DSpritesMovie.moveElementTo(kCaldoria4DSpritesLeft, kCaldoria4DSpritesTop); + _4DSpritesMovie.setDisplayOrder(k4DSpritesOrder); + _4DSpritesMovie.startDisplaying(); + _4DSpritesMovie.show(); + + _4DSpritesScale = _4DSpritesMovie.getScale(); + + _neighborhoodNotification = _owner->getNeighborhoodNotification(); + _neighborhoodNotification->notifyMe(this, kExtraCompletedFlag, kExtraCompletedFlag); + + startIdling(); +} + +void Caldoria4DSystem::loopExtra(const ExtraID extraID) { + ExtraTable::Entry extraEntry; + + _owner->getExtraEntry(extraID, extraEntry); + _loopStart = extraEntry.movieStart; + _owner->loopExtraSequence(extraID); +} + +void Caldoria4DSystem::useIdleTime() { + if (_whichMenu == k4DShuttingDown) { + TimeValue movieTime = _owner->getNavMovie()->getTime() - _loopStart; + ExtraID extraID; + + if (movieTime < kSwitchable1Stop) + extraID = s_shutDownExtras[_videoChoice - k4DFirstVideoChoice][0]; + else if (movieTime >= kSwitchable2Start && movieTime < kSwitchable2Stop) + extraID = s_shutDownExtras[_videoChoice - k4DFirstVideoChoice][1]; + else if (movieTime >= kSwitchable3Start && movieTime < kSwitchable3Stop) + extraID = s_shutDownExtras[_videoChoice - k4DFirstVideoChoice][2]; + else + extraID = 0xffffffff; + + if (extraID != 0xffffffff) { + setSpritesMovie(); + _loopStart = 0; + _owner->startExtraSequence(extraID, kExtraCompletedFlag, kFilterNoInput); + } + } else if (_clickedHotspotID != kNoHotSpotID) { + TimeValue movieTime = _owner->getNavMovie()->getTime() - _loopStart; + ExtraID extraID; + + if (movieTime < kSwitchable1Stop) { + extraID = s_transitionExtras0[_videoChoice - k4DFirstVideoChoice][_clickedHotspotID - kCa4DChoice1SpotID]; + _clickedHotspotID = kNoHotSpotID; + } else if (movieTime >= kSwitchable2Start && movieTime < kSwitchable2Stop) { + extraID = s_transitionExtras1[_videoChoice - k4DFirstVideoChoice][_clickedHotspotID - kCa4DChoice1SpotID]; + _clickedHotspotID = kNoHotSpotID; + } else if (movieTime >= kSwitchable3Start && movieTime < kSwitchable3Stop) { + extraID = s_transitionExtras2[_videoChoice - k4DFirstVideoChoice][_clickedHotspotID - kCa4DChoice1SpotID]; + _clickedHotspotID = kNoHotSpotID; + } else + extraID = 0xffffffff; + + if (extraID != 0xffffffff) { + switch (extraID) { + case k4DDesert0ToIsland0: + case k4DMountain0ToIsland0: + case k4DDesert1ToIsland0: + case k4DMountain1ToIsland0: + case k4DDesert2ToIsland0: + case k4DMountain2ToIsland0: + _videoChoice = k4DIslandChoice; + break; + case k4DIsland0ToDesert0: + case k4DMountain0ToDesert0: + case k4DIsland1ToDesert0: + case k4DMountain1ToDesert0: + case k4DIsland2ToDesert0: + case k4DMountain2ToDesert0: + _videoChoice = k4DDesertChoice; + break; + case k4DDesert0ToMountain0: + case k4DIsland0ToMountain0: + case k4DIsland1ToMountain0: + case k4DDesert1ToMountain0: + case k4DIsland2ToMountain0: + case k4DDesert2ToMountain0: + _videoChoice = k4DMountainChoice; + break; + } + + setSpritesMovie(); + _loopStart = 0; + _owner->startExtraSequence(extraID, kExtraCompletedFlag, kFilterNoInput); + } + } +} + +void Caldoria4DSystem::initInteraction() { + setSpritesMovie(); + + _owner->loadLoopSound1("Sounds/Caldoria/Rock.aiff"); + loopExtra(k4DIslandLoop); +} + +void Caldoria4DSystem::closeInteraction() { + stopIdling(); + _neighborhoodNotification->cancelNotification(this); + _4DSpritesMovie.releaseMovie(); + _owner->loadAmbientLoops(); +} + +void Caldoria4DSystem::setSpritesMovie() { + if (_whichMenu == k4DShuttingDown) + _4DSpritesMovie.setTime(_4DSpritesScale * k4DIslandChoice); + else if (_whichMenu == k4DVideoMenu) + _4DSpritesMovie.setTime(_4DSpritesScale * _videoChoice); + else if (_whichMenu == k4DAudioMenu) + _4DSpritesMovie.setTime(_4DSpritesScale * _audioChoice); + + _4DSpritesMovie.redrawMovieWorld(); +} + +void Caldoria4DSystem::handleInput(const Input &input, const Hotspot *cursorSpot) { + if (input.downButtonAnyDown()) + return; + if (input.anyDirectionInput()) + shutDown4DSystem(); + else + GameInteraction::handleInput(input, cursorSpot); +} + +void Caldoria4DSystem::activateHotspots() { + GameInteraction::activateHotspots(); + if (_whichMenu == k4DAudioMenu) + g_allHotspots.activateOneHotspot(kCa4DChoice4SpotID); +} + +void Caldoria4DSystem::clickInHotspot(const Input &input, const Hotspot *spot) { + switch (spot->getObjectID()) { + case kCa4DVisualSpotID: + if (_whichMenu == k4DAudioMenu) { + _whichMenu = k4DVideoMenu; + setSpritesMovie(); + } + break; + case kCa4DAudioSpotID: + if (_whichMenu == k4DVideoMenu) { + _whichMenu = k4DAudioMenu; + setSpritesMovie(); + } + break; + case kCa4DChoice1SpotID: + if (_whichMenu == k4DVideoMenu) + makeIslandChoice(); + else if (_whichMenu == k4DAudioMenu) + makeRockChoice(); + break; + case kCa4DChoice2SpotID: + if (_whichMenu == k4DVideoMenu) + makeDesertChoice(); + else if (_whichMenu == k4DAudioMenu) + makeOrchestralChoice(); + break; + case kCa4DChoice3SpotID: + if (_whichMenu == k4DVideoMenu) + makeMountainChoice(); + else if (_whichMenu == k4DAudioMenu) + makeRhythmsChoice(); + break; + case kCa4DChoice4SpotID: + if (_whichMenu == k4DAudioMenu) + makeAcousticChoice(); + else + _owner->playSpotSoundSync(kCaldoria4DBlankChoiceIn, kCaldoria4DBlankChoiceOut); + break; + default: + GameInteraction::clickInHotspot(input, spot); + } +} + +void Caldoria4DSystem::receiveNotification(Notification *, const NotificationFlags) { + if (_whichMenu == k4DShuttingDown) { + _owner->requestDeleteCurrentInteraction(); + } else { + uint32 extraID; + + switch (_videoChoice) { + case k4DIslandChoice: + extraID = k4DIslandLoop; + break; + case k4DDesertChoice: + extraID = k4DDesertLoop; + break; + case k4DMountainChoice: + extraID = k4DMountainLoop; + break; + default: + extraID = 0xffffffff; + break; + } + + if (extraID != 0xffffffff) + loopExtra(extraID); + } +} + +void Caldoria4DSystem::makeIslandChoice() { + if (_videoChoice != k4DIslandChoice && _clickedHotspotID == kNoHotSpotID) + _clickedHotspotID = kCa4DChoice1SpotID; +} + +void Caldoria4DSystem::makeDesertChoice() { + if (_videoChoice != k4DDesertChoice && _clickedHotspotID == kNoHotSpotID) + _clickedHotspotID = kCa4DChoice2SpotID; +} + +void Caldoria4DSystem::makeMountainChoice() { + if (_videoChoice != k4DMountainChoice && _clickedHotspotID == kNoHotSpotID) + _clickedHotspotID = kCa4DChoice3SpotID; +} + +void Caldoria4DSystem::makeRockChoice() { + if (_audioChoice != k4DRockChoice) { + _audioChoice = k4DRockChoice; + setSpritesMovie(); + _owner->loadLoopSound1("Sounds/Caldoria/Rock.aiff"); + } +} + +void Caldoria4DSystem::makeOrchestralChoice() { + if (_audioChoice != k4DOrchestralChoice) { + _audioChoice = k4DOrchestralChoice; + setSpritesMovie(); + _owner->loadLoopSound1("Sounds/Caldoria/Orchestral.aiff"); + } +} + +void Caldoria4DSystem::makeRhythmsChoice() { + if (_audioChoice != k4DRhythmsChoice) { + _audioChoice = k4DRhythmsChoice; + setSpritesMovie(); + _owner->loadLoopSound1("Sounds/Caldoria/Rhythms.aiff"); + } +} + +void Caldoria4DSystem::makeAcousticChoice() { + if (_audioChoice != k4DAcousticChoice) { + _audioChoice = k4DAcousticChoice; + setSpritesMovie(); + _owner->loadLoopSound1("Sounds/Caldoria/Acoustic.aiff"); + } +} + +void Caldoria4DSystem::shutDown4DSystem() { + _whichMenu = k4DShuttingDown; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.h b/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.h new file mode 100644 index 0000000000..1c5fa44b90 --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.h @@ -0,0 +1,78 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIA4DSYSTEM_H +#define PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIA4DSYSTEM_H + +#include "pegasus/interaction.h" +#include "pegasus/movie.h" +#include "pegasus/notification.h" +#include "pegasus/timers.h" + +namespace Pegasus { + +class Neighborhood; + +class Caldoria4DSystem : public GameInteraction, private Idler, public NotificationReceiver { +public: + Caldoria4DSystem(Neighborhood *); + virtual ~Caldoria4DSystem(); + + void shutDown4DSystem(); + +protected: + void openInteraction(); + void initInteraction(); + void closeInteraction(); + + void handleInput(const Input &, const Hotspot *); + void activateHotspots(); + void clickInHotspot(const Input &, const Hotspot *); + void receiveNotification(Notification *, const NotificationFlags); + void setSpritesMovie(); + void makeIslandChoice(); + void makeRockChoice(); + void makeMountainChoice(); + void makeOrchestralChoice(); + void makeDesertChoice(); + void makeRhythmsChoice(); + void makeAcousticChoice(); + + void useIdleTime(); + void loopExtra(const ExtraID); + + Movie _4DSpritesMovie; + TimeScale _4DSpritesScale; + uint _whichMenu; + uint _videoChoice; + uint _audioChoice; + Notification *_neighborhoodNotification; + TimeValue _loopStart; + HotSpotID _clickedHotspotID; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/caldoria/caldoriabomb.cpp b/engines/pegasus/neighborhood/caldoria/caldoriabomb.cpp new file mode 100644 index 0000000000..abf34d3863 --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoriabomb.cpp @@ -0,0 +1,1442 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/caldoria/caldoria.h" +#include "pegasus/neighborhood/caldoria/caldoriabomb.h" + +namespace Pegasus { + +// Bomb game PICTs: + +static const uint16 kYellowBombPICTBaseID = 700; +static const uint16 kRedBombPICTBaseID = 709; +static const uint16 kTimerLeftPICTID = 718; +static const uint16 kTimerRightPICTID = 719; + +static const uint32 kFlashOnTime = 20; +static const uint32 kFlashOffTime = 10; + +static const uint32 kOnTime1 = kFlashOnTime; +static const uint32 kOffTime1 = kOnTime1 + kFlashOffTime; +static const uint32 kOnTime2 = kOffTime1 + kFlashOnTime; +static const uint32 kOffTime2 = kOnTime2 + kFlashOffTime; +static const uint32 kOnTime3 = kOffTime2 + kFlashOnTime; +static const uint32 kOffTime3 = kOnTime3 + kFlashOffTime; +static const uint32 kOnTime4 = kOffTime3 + kFlashOnTime; + +static const HotSpotID kVertextHotSpotBaseID = 10000; + +static const CoordType kVertextHotSpotWidth = 24; +static const CoordType kVertextHotSpotHeight = 24; + +static const NotificationFlags kBombTimerExpiredFlag = 1; + +static const VertexType kBombLevelOne[] = { + 0, 1, 0, 1, 0, // hot vertices first. + 1, 1, 0, 1, 1, + 1, 1, 0, 1, 0, + 1, 1, 0, 1, 1, + 0, 1, 0, 1, 0, + + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + + 9, // 9 edges in this level + + kEdgeOneFourth, + 3, + 1, 2, 3, + 0, 0, + + kEdgeOneFourth, + 5, + 5, 6, 7, 8, 9, + 0, 0, 0, 0, + + kEdgeOneFourth, + 4, + 10, 11, 12, 13, + 0, 0, 0, + + kEdgeOneFourth, + 5, + 15, 16, 17, 18, 19, + 0, 0, 0, 0, + + kEdgeOneFourth, + 3, + 21, 22, 23, + 0, 0, + + kEdgeOneHalf, + 3, + 5, 10, 15, + 0, 0, + + kEdgeOneHalf, + 5, + 1, 6, 11, 16, 21, + 0, 0, 0, 0, + + kEdgeOneHalf, + 5, + 3, 8, 13, 18, 23, + 0, 0, 0, 0, + + kEdgeOneHalf, + 3, + 9, 14, 19, + 0, 0 +}; + +static const VertexType kBombLevelTwo[] = { + 0, 1, 0, 1, 0, + 1, 1, 1, 0, 1, + 0, 0, 0, 1, 0, + 1, 1, 1, 0, 1, + 0, 1, 0, 1, 0, + + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + + 15, + + kEdgeOneEighth, + 2, + 5, 1, + 0, + + kEdgeOneEighth, + 3, + 17, 13, 9, + 0, 0, + + kEdgeOneEighth, + 2, + 23, 19, + 0, + + kEdgeThreeEighths, + 2, + 3, 9, + 0, + + kEdgeThreeEighths, + 3, + 7, 13, 19, + 0, 0, + + kEdgeThreeEighths, + 2, + 15, 21, + 0, + + kEdgeOneFourth, + 3, + 1, 2, 3, + 0, 0, + + kEdgeOneFourth, + 4, + 6, 7, 8, 9, + 0, 0, 0, + + kEdgeOneFourth, + 4, + 16, 17, 18, 19, + 0, 0, 0, + + kEdgeOneFourth, + 3, + 21, 22, 23, + 0, 0, + + kEdgeOneHalf, + 3, + 5, 10, 15, + 0, 0, + + kEdgeOneHalf, + 2, + 1, 6, + 0, + + kEdgeOneHalf, + 3, + 7, 12, 17, + 0, 0, + + kEdgeOneHalf, + 3, + 9, 14, 19, + 0, 0, + + kEdgeOneHalf, + 2, + 16, 21, + 0 +}; + +static const VertexType kBombLevelThree[] = { + 0, 1, 0, 1, 0, + 1, 1, 1, 1, 1, + 0, 1, 1, 0, 0, + 1, 1, 1, 1, 1, + 0, 1, 0, 1, 0, + + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + + 22, + + kEdgeThreeSixteenths, + 3, + 15, 12, 9, + 0, 0, + + kEdgeFiveSixteenths, + 3, + 5, 12, 19, + 0, 0, + + kEdgeOneEighth, + 2, + 5, 1, + 0, + + kEdgeOneEighth, + 2, + 7, 3, + 0, + + kEdgeOneEighth, + 2, + 15, 11, + 0, + + kEdgeOneEighth, + 2, + 21, 17, + 0, + + kEdgeOneEighth, + 2, + 23, 19, + 0, + + kEdgeThreeEighths, + 2, + 1, 7, + 0, + + kEdgeThreeEighths, + 2, + 3, 9, + 0, + + kEdgeThreeEighths, + 2, + 5, 11, + 0, + + kEdgeThreeEighths, + 2, + 15, 21, + 0, + + kEdgeThreeEighths, + 2, + 17, 23, + 0, + + kEdgeOneFourth, + 3, + 1, 2, 3, + 0, 0, + + kEdgeOneFourth, + 2, + 5, 6, + 0, + + kEdgeOneFourth, + 2, + 8, 9, + 0, + + kEdgeOneFourth, + 2, + 15, 16, + 0, + + kEdgeOneFourth, + 2, + 18, 19, + 0, + + kEdgeOneFourth, + 3, + 21, 22, 23, + 0, 0, + + kEdgeOneHalf, + 2, + 1, 6, + 0, + + kEdgeOneHalf, + 2, + 3, 8, + 0, + + kEdgeOneHalf, + 2, + 16, 21, + 0, + + kEdgeOneHalf, + 2, + 18, 23, + 0 +}; + +static const VertexType kBombLevelFour[] = { + 1, 1, 1, 1, 0, + 1, 1, 0, 1, 1, + 1, 0, 1, 0, 1, + 1, 1, 0, 1, 1, + 0, 1, 1, 1, 1, + + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + + 19, + + kEdgeOneEighth, + 2, + 5, 1, + 0, + + kEdgeOneEighth, + 3, + 10, 6, 2, + 0, 0, + + kEdgeOneEighth, + 3, + 16, 12, 8, + 0, 0, + + kEdgeOneEighth, + 3, + 22, 18, 14, + 0, 0, + + kEdgeOneEighth, + 2, + 23, 19, + 0, + + kEdgeThreeEighths, + 3, + 2, 8, 14, + 0, 0, + + kEdgeThreeEighths, + 3, + 10, 16, 22, + 0, 0, + + kEdgeOneFourth, + 4, + 0, 1, 2, 3, + 0, 0, 0, + + kEdgeOneFourth, + 2, + 5, 6, + 0, + + kEdgeOneFourth, + 2, + 8, 9, + 0, + + kEdgeOneFourth, + 2, + 15, 16, + 0, + + kEdgeOneFourth, + 2, + 18, 19, + 0, + + kEdgeOneFourth, + 4, + 21, 22, 23, 24, + 0, 0, 0, + + kEdgeOneHalf, + 4, + 0, 5, 10, 15, + 0, 0, 0, + + kEdgeOneHalf, + 2, + 1, 6, + 0, + + kEdgeOneHalf, + 2, + 3, 8, + 0, + + kEdgeOneHalf, + 4, + 9, 14, 19, 24, + 0, 0, 0, + + kEdgeOneHalf, + 2, + 16, 21, + 0, + + kEdgeOneHalf, + 2, + 18, 23, + 0 +}; + +static const VertexType kBombLevelFive[] = { + 0, 1, 0, 1, 0, + 1, 1, 1, 1, 1, + 0, 1, 1, 1, 0, + 1, 1, 1, 1, 1, + 0, 1, 0, 1, 0, + + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + + 19, + + kEdgeOneEighth, + 2, + 5, 1, + 0, + + kEdgeOneEighth, + 2, + 7, 3, + 0, + + kEdgeOneEighth, + 2, + 13, 9, + 0, + + kEdgeOneEighth, + 2, + 15, 11, + 0, + + kEdgeOneEighth, + 2, + 21, 17, + 0, + + kEdgeOneEighth, + 2, + 23, 19, + 0, + + kEdgeThreeEighths, + 2, + 1, 7, + 0, + + kEdgeThreeEighths, + 4, + 5, 11, 17, 23, + 0, 0, 0, + + kEdgeThreeEighths, + 3, + 6, 12, 18, + 0, 0, + + kEdgeThreeEighths, + 2, + 13, 19, + 0, + + kEdgeThreeEighths, + 2, + 15, 21, + 0, + + kEdgeOneFourth, + 5, + 5, 6, 7, 8, 9, + 0, 0, 0, 0, + + kEdgeOneFourth, + 3, + 15, 16, 17, + 0, 0, + + kEdgeOneFourth, + 2, + 18, 19, + 0, + + kEdgeOneFourth, + 3, + 21, 22, 23, + 0, 0, + + kEdgeOneHalf, + 3, + 5, 10, 15, + 0, 0, + + kEdgeOneHalf, + 2, + 1, 6, + 0, + + kEdgeOneHalf, + 3, + 11, 16, 21, + 0, 0, + + kEdgeOneHalf, + 5, + 3, 8, 13, 18, 23, + 0, 0, 0, 0 +}; + +static const VertexType kBombLevelSix[] = { + 0, 1, 1, 1, 0, + 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, + 1, 1, 1, 1, 1, + 0, 1, 1, 1, 0, + + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + + 25, + + kEdgeOneSixteenth, + 2, + 10, 1, + 0, + + kEdgeOneSixteenth, + 2, + 23, 14, + 0, + + kEdgeSevenSixteenths, + 2, + 3, 14, + 0, + + kEdgeSevenSixteenths, + 2, + 10, 21, + 0, + + kEdgeOneEighth, + 2, + 5, 1, + 0, + + kEdgeOneEighth, + 3, + 10, 6, 2, + 0, 0, + + kEdgeOneEighth, + 2, + 7, 3, + 0, + + kEdgeOneEighth, + 2, + 21, 17, + 0, + + kEdgeOneEighth, + 3, + 22, 18, 14, + 0, 0, + + kEdgeOneEighth, + 2, + 23, 19, + 0, + + kEdgeThreeEighths, + 2, + 1, 7, + 0, + + kEdgeThreeEighths, + 3, + 2, 8, 14, + 0, 0, + + kEdgeThreeEighths, + 2, + 3, 9, + 0, + + kEdgeThreeEighths, + 3, + 10, 16, 22, + 0, 0, + + kEdgeThreeEighths, + 2, + 15, 21, + 0, + + kEdgeThreeEighths, + 2, + 17, 23, + 0, + + kEdgeOneFourth, + 3, + 1, 2, 3, + 0, 0, + + kEdgeOneFourth, + 3, + 6, 7, 8, + 0, 0, + + kEdgeOneFourth, + 3, + 16, 17, 18, + 0, 0, + + kEdgeOneFourth, + 3, + 21, 22, 23, + 0, 0, + + kEdgeOneHalf, + 3, + 5, 10, 15, + 0, 0, + + kEdgeOneHalf, + 3, + 6, 11, 16, + 0, 0, + + kEdgeOneHalf, + 5, + 2, 7, 12, 17, 22, + 0, 0, 0, 0, + + kEdgeOneHalf, + 3, + 8, 13, 18, + 0, 0, + + kEdgeOneHalf, + 3, + 9, 14, 19, + 0, 0 +}; + +static const CoordType kBombGridWidth = 140; +static const CoordType kBombGridHeight = 140; + +static const CoordType kDotOriginX = 0; +static const CoordType kDotOriginY = 0; + +static const CoordType kVertOriginX = 2; +static const CoordType kVertOriginY = 6; + +static const CoordType kHorizOriginX = 6; +static const CoordType kHorizOriginY = 2; + +static const CoordType kDiagOriginX = 6; +static const CoordType kDiagOriginY = 6; + +static const int g_originsX[] = { + kDiagOriginX, + kDiagOriginX, + kDiagOriginX, + kHorizOriginX, + kDiagOriginX, + kDiagOriginX, + kDiagOriginX, + kVertOriginX +}; + +static const int g_originsY[] = { + kDiagOriginY - 64, + kDiagOriginY - 32, + kDiagOriginY - 32, + kHorizOriginY, + kDiagOriginY, + kDiagOriginY, + kDiagOriginY, + kVertOriginY +}; + +struct HotVerticesList { + int numHotVerts; + VertexType hotVerts[25]; +}; + +CoordType vertToX(VertexType vertex) { + return (vertex % 5) * 32; +} + +CoordType vertToY(VertexType vertex) { + return (vertex / 5) * 32; +} + +// This function returns the number of edges in the bomb edge list. +VertexType getNumEdges(BombEdgeList edges) { + return edges[50]; +} + +// These four functions return pointers into the given edge list. + +// getFirstEdge and getNextEdge can be used to iterate across all edges +// in an edge list. These functions can be used to walk all the edges +// in a bomb edge list for drawing. +VertexType *getFirstEdge(BombEdgeList edges) { + return &edges[51]; +} + +VertexType *getNextEdge(VertexType *anEdge) { + return anEdge + *(anEdge + 1) * 2 + 1; +} + +// getVertices returns a pointer to all of the vertices that should are +// hot. These vertices indicate all the vertices that should be drawn in +// the game. +VertexType *getVertices(BombEdgeList edges) { + return &edges[0]; +} + +// getUsedVertices returns a pointer to the "used" vertices area: the +// area that keeps track of which vertices have been set by the +// setVertexUsed used function. +VertexType *getUsedVertices(BombEdgeList edges) { + return &edges[25]; +} + +// Useful for saving. Saving the state of the bomb game is as simple as writing +// out the edge list. +int getEdgeListSize(BombEdgeList edges) { + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + + while (numEdges--) + anEdge = getNextEdge(anEdge); + + return anEdge - edges + 4; +} + +// Returns true if the given vertex lies on the given edge. +bool vertexOnEdge(VertexType *anEdge, VertexType whichVertex) { + VertexType numVerts = *++anEdge; + + while (numVerts--) + if (*++anEdge == whichVertex) + return true; + + return false; +} + +// Given an edge list and a from vertex, this function constructs a list +// of all vertices that may be clicked on. +// if fromVertex == -1, all vertices are eligible. +// otherwise, only vertices on a line from fromVertex are eligible. +void makeHotVertexList(BombEdgeList edges, VertexType fromVertex, HotVerticesList &hotVertices) { + hotVertices.numHotVerts = 0; + + if (fromVertex == -1) { + for (VertexType i = 0; i < 25; i++) + if (edges[i]) + hotVertices.hotVerts[hotVertices.numHotVerts++] = i; + } else { + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + hotVertices.hotVerts[hotVertices.numHotVerts++] = fromVertex; + + while (numEdges--) { + if (vertexOnEdge(anEdge, fromVertex)) { + VertexType *p = anEdge + 1; + VertexType numVerts = *p; + + while (numVerts--) + if (*++p != fromVertex) + hotVertices.hotVerts[hotVertices.numHotVerts++] = *p; + } + + anEdge = getNextEdge(anEdge); + } + } +} + +// Set all edges in the edge list to the value passed in "edgeVal". +// For drawing purposes, 0 can mean don't draw, and 1 and higher can +// represent different colors. +void setAllEdgesUsed(BombEdgeList edges, VertexType used) { + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + + while (numEdges--) { + VertexType *p1 = anEdge + 1; + VertexType numVerts = *p1; + p1 += numVerts + 1; + + while (--numVerts) + *p1++ = used; + + anEdge = getNextEdge(anEdge); + } + + VertexType *p1 = edges; + VertexType *p2 = getUsedVertices(edges); + + for (VertexType i = 0; i < 25; i++, p1++, p2++) + if (*p1) + *p2 = used; +} + +// Same as setAllEdgesUsed, but only affects edges that are already set +// to a non-zero value. +void setAllUsedEdgesUsed(BombEdgeList edges, VertexType used) { + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + + while (numEdges--) { + VertexType *p = anEdge + 1; + VertexType numVerts = *p; + p += numVerts + 1; + + while (--numVerts) { + if (*p) + *p = used; + ++p; + } + + anEdge = getNextEdge(anEdge); + } + + VertexType *p = getUsedVertices(edges); + for (VertexType i = 0; i < 25; i++, p++) + if (*p) + *p = used; +} + +// Replace all edges with value "value" with the new value "used". +void replaceUsedEdges(BombEdgeList edges, VertexType value, VertexType used) { + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + + while (numEdges--) { + VertexType *p = anEdge + 1; + VertexType numVerts = *p; + p += numVerts + 1; + + while (--numVerts) { + if (*p == value) + *p = used; + + p++; + } + + anEdge = getNextEdge(anEdge); + } + + VertexType *p = getUsedVertices(edges); + for (VertexType i = 0; i < 25; i++, p++) + if (*p == value) + *p = used; +} + +// Set a vertex's value to "used". +void setVertexUsed(BombEdgeList edges, VertexType whichVertex, VertexType value) { + *(getUsedVertices(edges) + whichVertex) = value; +} + +// Mark an edge in the given list between the two vertices as "used". This marks +// all inbetween vertices as well, even if the vertex is not marked as a "hot" +// vertex in the hot vertex section. Returns true if doing this operation +// crosses an already marked edge. +bool setEdgeUsed(BombEdgeList edges, VertexType fromVertex, VertexType toVertex) { + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + bool crossed = false; + + while (numEdges--) { + VertexType *p = anEdge; + VertexType numVerts = *++p; + VertexType *fromPtr = 0; + VertexType *toPtr = 0; + VertexType i = numVerts; + p++; + + while (i--) { + if (*p == fromVertex) + fromPtr = p; + else if (*p == toVertex) + toPtr = p; + + if (fromPtr && toPtr) { + // Found the edge... + if (fromPtr > toPtr) { + p = fromPtr; + fromPtr = toPtr; + toPtr = p; + } + + p = fromPtr + numVerts; + + for (i = toPtr - fromPtr; i > 0; i--, p++) { + ++(*p); + + if (*p == 2) + crossed = true; + } + + VertexType *verts = getVertices(edges); + VertexType *usedVerts = getUsedVertices(edges); + *(usedVerts + *fromPtr) = 1; + + for (p = fromPtr + 1; p != toPtr; p++) + if (*(verts + *p)) + *(usedVerts + *p) = 1; + + *(usedVerts + *toPtr) = 1; + return crossed; + } + + p++; + } + + anEdge = getNextEdge(anEdge); + } + + return false; +} + +// Return true if all edges are used. Can be used to determine when the bomb +// game is over. +bool allEdgesUsed(BombEdgeList edges) { + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + + while (numEdges--) { + VertexType *p = anEdge + 1; + VertexType numVerts = *p; + p += numVerts + 1; + + while (--numVerts) { + if (!*p) + return false; + + ++p; + } + + anEdge = getNextEdge(anEdge); + } + + return true; +} + +BombGrid::BombGrid(const DisplayElementID id) : Picture(id) { + Common::Rect bounds(0, 0, kBombGridWidth, kBombGridHeight); + + allocateSurface(bounds); + setBounds(bounds); + _surface->fillRect(bounds, g_system->getScreenFormat().RGBToColor(0xff, 0xff, 0xff)); + + _transparent = true; + + _yellowDot.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID, true); + _yellowOneSixteenth.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 1, true); + _yellowOneEighth.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 2, true); + _yellowThreeSixteenths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 3, true); + _yellowOneFourth.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 4, true); + _yellowFiveSixteenths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 5, true); + _yellowThreeEighths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 6, true); + _yellowSevenSixteenths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 7, true); + _yellowOneHalf.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kYellowBombPICTBaseID + 8, true); + + _redDot.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID, true); + _redOneSixteenth.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 1, true); + _redOneEighth.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 2, true); + _redThreeSixteenths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 3, true); + _redOneFourth.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 4, true); + _redFiveSixteenths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 5, true); + _redThreeEighths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 6, true); + _redSevenSixteenths.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 7, true); + _redOneHalf.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kRedBombPICTBaseID + 8, true); +} + +void BombGrid::drawEdges(BombEdgeList edges) { + GraphicsManager *gfx = ((PegasusEngine *)g_engine)->_gfx; + gfx->setCurSurface(_surface); + + _surface->fillRect(Common::Rect(0, 0, kBombGridWidth, kBombGridHeight), g_system->getScreenFormat().RGBToColor(0xff, 0xff, 0xff)); + + Frame *yellowStuff = &_yellowDot; + Frame *redStuff = &_redDot; + VertexType numEdges = getNumEdges(edges); + VertexType *anEdge = getFirstEdge(edges); + VertexType i, *p; + + Common::Rect bounds; + getSurfaceBounds(bounds); + + while (numEdges--) { + p = anEdge; + VertexType edgeDirection = *p++; + VertexType numVerts = *p++; + VertexType numSegs = numVerts - 1; + + for (i = 0; i < numSegs; i++, p++) { + if (*(p + numVerts) > 0 && *(p + numVerts) < 4) { + Frame *drawStuff; + + if (*(p + numVerts) == 2) + drawStuff = redStuff; + else + drawStuff = yellowStuff; + + int x = vertToX(*p) + g_originsX[edgeDirection]; + int y = vertToY(*p) + g_originsY[edgeDirection]; + + Common::Rect r1; + drawStuff[edgeDirection + 1].getSurfaceBounds(r1); + Common::Rect r2 = r1; + r2.moveTo(x, y); + drawStuff[edgeDirection + 1].drawImage(r1, r2); + } + } + + anEdge = getNextEdge(anEdge); + } + + for (i = 0, p = getUsedVertices(edges); i < 25; i++, p++) { + if (*p > 0 && *p < 4) { + Frame *drawStuff; + + if (*p == 2) + drawStuff = redStuff; + else + drawStuff = yellowStuff; + + int x = vertToX(i) + kDotOriginX; + int y = vertToY(i) + kDotOriginY; + + Common::Rect r1; + drawStuff->getSurfaceBounds(r1); + Common::Rect r2 = r1; + r2.moveTo(x, y); + drawStuff->drawImage(r1, r2); + } + } + + triggerRedraw(); + gfx->setCurSurface(gfx->getWorkArea()); +} + +BombTimer::BombTimer(const DisplayElementID id) : IdlerAnimation(id) { + _middle = -1; + _leftImage.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kTimerLeftPICTID); + _rightImage.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kTimerRightPICTID); + + Common::Rect r; + _leftImage.getSurfaceBounds(r); + setBounds(r); +} + +void BombTimer::draw(const Common::Rect &updateRect) { + Common::Rect bounds; + getBounds(bounds); + + Common::Rect r1 = bounds; + r1.right = _middle; + r1 = r1.findIntersectingRect(updateRect); + + if (!r1.isEmpty()) { + Common::Rect r2 = r1; + r2.moveTo(r1.left - bounds.left, r1.top - bounds.top); + _leftImage.copyToCurrentPort(r2, r1); + } + + r1 = bounds; + r1.left = _middle; + r1 = r1.findIntersectingRect(updateRect); + + if (!r1.isEmpty()) { + Common::Rect r2 = r1; + r2.moveTo(r1.left - bounds.left, r1.top - bounds.top); + _rightImage.copyToCurrentPort(r2, r1); + } +} + +void BombTimer::timeChanged(const TimeValue newTime) { + Common::Rect bounds; + getBounds(bounds); + + int newMiddle = bounds.right - bounds.width() * newTime / getDuration(); + if (newMiddle != _middle) { + _middle = newMiddle; + triggerRedraw(); + } +} + +#define CREATE_BOMB_LEVEL(num, data) \ + _bombLevel[num] = new VertexType[sizeof(data)]; \ + memcpy(_bombLevel[num], data, sizeof(data)) + +CaldoriaBomb::CaldoriaBomb(Neighborhood *owner, NotificationManager *manager) : + GameInteraction(kCaldoriaBombInteractionID, owner), _grid(kCaldoriaBombGridID), + _timer(kCaldoriaBombTimerID), _timerNotification(kCaldoriaBombTimerNotificationID, manager) { + CREATE_BOMB_LEVEL(0, kBombLevelOne); + CREATE_BOMB_LEVEL(1, kBombLevelTwo); + CREATE_BOMB_LEVEL(2, kBombLevelThree); + CREATE_BOMB_LEVEL(3, kBombLevelFour); + CREATE_BOMB_LEVEL(4, kBombLevelFive); + CREATE_BOMB_LEVEL(5, kBombLevelSix); + _currentLevel = 0; +} + +#undef CREATE_BOMB_LEVEL + +CaldoriaBomb::~CaldoriaBomb() { + for (int i = 0; i < 6; i++) + delete[] _bombLevel[i]; +} + +void CaldoriaBomb::openInteraction() { + _grid.moveElementTo(kCaldoriaBombGridLeft, kCaldoriaBombGridTop); + _grid.setDisplayOrder(kCaldoriaBombGridOrder); + _grid.startDisplaying(); + + _timer.moveElementTo(kCaldoriaBombTimerLeft, kCaldoriaBombTimerTop); + _timer.setDisplayOrder(kCaldoriaBombTimerOrder); + _timer.startDisplaying(); + _timer.setSegment(0, kTenMinutesPerFifteenTicks, kFifteenTicksPerSecond); + _timer.setTime(0); + + _timerNotification.notifyMe(this, kBombTimerExpiredFlag, kBombTimerExpiredFlag); + _timerCallBack.setNotification(&_timerNotification); + _timerCallBack.initCallBack(&_timer, kCallBackAtExtremes); + _timerCallBack.setCallBackFlag(kBombTimerExpiredFlag); + + Common::Rect r(0, 0, kVertextHotSpotWidth, kVertextHotSpotHeight); + + for (VertexType i = 0; i < 25; i++) { + _vertexHotspot[i] = new Hotspot(i + kVertextHotSpotBaseID); + r.moveTo(vertToX(i) + kCaldoriaBombGridLeft - kVertextHotSpotWidth / 2 + 6, + vertToY(i) + kCaldoriaBombGridTop - kVertextHotSpotHeight / 2 + 6); + _vertexHotspot[i]->setArea(r); + _vertexHotspot[i]->setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + g_allHotspots.push_back(_vertexHotspot[i]); + } + + _neighborhoodNotification = _owner->getNeighborhoodNotification(); + _neighborhoodNotification->notifyMe(this, kExtraCompletedFlag, kExtraCompletedFlag); +} + +void CaldoriaBomb::initInteraction() { + _owner->loadLoopSound1(""); + _owner->startExtraSequence(kCaldoria56BombStage1, kExtraCompletedFlag, kFilterNoInput); +} + +void CaldoriaBomb::closeInteraction() { + _timer.stop(); + _timer.hide(); + _timer.stopDisplaying(); + _grid.hide(); + _grid.stopDisplaying(); + + // The original did not do this, but we need it here + // Not sure why the original worked without this; probably + // related to the way the List code worked in CodeWarrior. + // If this is not here, the notifications will later attempt + // to remove itself from this receiver causing a very nasty + // crash. + _timerNotification.cancelNotification(this); + _neighborhoodNotification->cancelNotification(this); +} + +void CaldoriaBomb::startBombAmbient(Common::String ambient) { + _owner->loadLoopSound1(ambient); +} + +void CaldoriaBomb::receiveNotification(Notification *notification, const NotificationFlags) { + if (notification == _neighborhoodNotification) { + switch (_owner->getLastExtra()) { + case kCaldoria56BombStage1: + _grid.show(); + _timer.show(); + _timerCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _timer.start(); + _currentLevel = 0; + _lastVertex = -1; + startBombAmbient("Sounds/Caldoria/BmbLoop1.22K.AIFF"); + break; + case kCaldoria56BombStage2: + case kCaldoria56BombStage3: + case kCaldoria56BombStage4: + case kCaldoria56BombStage5: + case kCaldoria56BombStage6: + _grid.show(); + _currentLevel++; + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -1; + startBombAmbient(Common::String::format("Sounds/Caldoria/BmbLoop%d.22K.AIFF", _owner->getLastExtra() - kCaldoria56BombStage1 + 1)); + break; + case kCaldoria56BombStage7: + _owner->requestDeleteCurrentInteraction(); + GameState.setCaldoriaBombDisarmed(true); + GameState.setScoringDisarmedNuke(true); + _owner->loadAmbientLoops(); + break; + } + } else if (notification == &_timerNotification) { + _grid.hide(); + _timer.stop(); + _timer.hide(); + _owner->loadLoopSound1(""); + _owner->playDeathExtra(kCaldoria56BombExplodes, kDeathNuclearExplosion); + } +} + +void CaldoriaBomb::activateHotspots() { + GameInteraction::activateHotspots(); + + if (_currentLevel != -1 && _lastVertex >= -1) { + HotVerticesList hotVertices; + makeHotVertexList(_bombLevel[_currentLevel], _lastVertex, hotVertices); + + for (VertexType i = 0; i < hotVertices.numHotVerts; i++) + g_allHotspots.activateOneHotspot(hotVertices.hotVerts[i] + kVertextHotSpotBaseID); + } +} + +void CaldoriaBomb::clickInHotspot(const Input &input, const Hotspot *hotspot) { + int clickedVertex = (int)hotspot->getObjectID() - (int)kVertextHotSpotBaseID; + + if (clickedVertex >= 0 && clickedVertex < 25) { + if (_lastVertex != -1 && setEdgeUsed(_bombLevel[_currentLevel], _lastVertex, clickedVertex)) { + clickedVertex = -2; + _flashTime = tickCount(); + } else if (allEdgesUsed(_bombLevel[_currentLevel])) { + setVertexUsed(_bombLevel[_currentLevel], clickedVertex, 1); + clickedVertex = -20; + _flashTime = tickCount(); + } else { + setVertexUsed(_bombLevel[_currentLevel], clickedVertex, 2); + } + + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = clickedVertex; + } else { + GameInteraction::clickInHotspot(input, hotspot); + } +} + +InputBits CaldoriaBomb::getInputFilter() { + // Disallow arrow buttons. + return GameInteraction::getInputFilter() & kFilterAllButtons; +} + +void CaldoriaBomb::handleInput(const Input &input, const Hotspot *hotspot) { + GameInteraction::handleInput(input, hotspot); + + switch (_lastVertex) { + case -2: // Flash back to yellow. + if (tickCount() > _flashTime + kOnTime1) { + replaceUsedEdges(_bombLevel[_currentLevel], 2, 3); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -3; + } + break; + case -3: // Flash back to red. + if (tickCount() > _flashTime + kOffTime1) { + replaceUsedEdges(_bombLevel[_currentLevel], 3, 2); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -4; + } + break; + case -4: // Flash all to yellow. + if (tickCount() > _flashTime + kOnTime2) { + setAllUsedEdgesUsed(_bombLevel[_currentLevel], 1); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -5; + } + break; + case -5: // Flash all to red. + if (tickCount() > _flashTime + kOffTime2) { + setAllUsedEdgesUsed(_bombLevel[_currentLevel], 2); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -6; + } + break; + case -6: // Flash all to yellow. + if (tickCount() > _flashTime + kOnTime3) { + setAllUsedEdgesUsed(_bombLevel[_currentLevel], 1); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -7; + } + break; + case -7: // Flash all to red. + if (tickCount() > _flashTime + kOffTime3) { + setAllUsedEdgesUsed(_bombLevel[_currentLevel], 2); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -8; + } + break; + case -8: // Restore to normal. + if (tickCount() > _flashTime + kOnTime4) { + setAllEdgesUsed(_bombLevel[_currentLevel], 0); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -1; + } + break; + + // Flash grid after success. + case -20: // Flash off. + if (tickCount() > _flashTime + kOnTime1) { + setAllEdgesUsed(_bombLevel[_currentLevel], 4); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -21; + } + break; + case -21: // Flash on. + if (tickCount() > _flashTime + kOffTime1) { + setAllEdgesUsed(_bombLevel[_currentLevel], 1); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -22; + } + break; + case -22: // Flash off. + if (tickCount() > _flashTime + kOnTime2) { + setAllEdgesUsed(_bombLevel[_currentLevel], 4); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -23; + } + break; + case -23: // Flash on. + if (tickCount() > _flashTime + kOffTime2) { + setAllEdgesUsed(_bombLevel[_currentLevel], 1); + _grid.drawEdges(_bombLevel[_currentLevel]); + _lastVertex = -24; + } + break; + case -24: + if (tickCount() > _flashTime + kOnTime3) { + _grid.hide(); + _lastVertex = -1; + _owner->loadLoopSound1(""); + + switch (_currentLevel) { + case 0: + _owner->startExtraSequence(kCaldoria56BombStage2, kExtraCompletedFlag, kFilterNoInput); + break; + case 1: + _owner->startExtraSequence(kCaldoria56BombStage3, kExtraCompletedFlag, kFilterNoInput); + break; + case 2: + _owner->startExtraSequence(kCaldoria56BombStage4, kExtraCompletedFlag, kFilterNoInput); + break; + case 3: + _owner->startExtraSequence(kCaldoria56BombStage5, kExtraCompletedFlag, kFilterNoInput); + break; + case 4: + _owner->startExtraSequence(kCaldoria56BombStage6, kExtraCompletedFlag, kFilterNoInput); + break; + case 5: + _timer.stop(); + _grid.hide(); + _timer.hide(); + _owner->startExtraSequence(kCaldoria56BombStage7, kExtraCompletedFlag, kFilterNoInput); + break; + } + } + break; + } +} + +long CaldoriaBomb::getNumHints() { + return 2; +} + +Common::String CaldoriaBomb::getHintMovie(uint hintNum) { + return (hintNum == 1) ? "Images/AI/Caldoria/X56EH2" : "Images/AI/Caldoria/X56EH3"; +} + +bool CaldoriaBomb::canSolve() { + return true; +} + +void CaldoriaBomb::doSolve() { + _timer.stop(); + _grid.hide(); + _timer.hide(); + _owner->loadLoopSound1(""); + _owner->startExtraSequence(kCaldoria56BombStage7, kExtraCompletedFlag, kFilterNoInput); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/caldoria/caldoriabomb.h b/engines/pegasus/neighborhood/caldoria/caldoriabomb.h new file mode 100644 index 0000000000..5bb39b4122 --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoriabomb.h @@ -0,0 +1,156 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIABOMB_H +#define PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIABOMB_H + +#include "pegasus/interaction.h" +#include "pegasus/notification.h" +#include "pegasus/surface.h" + +namespace Pegasus { + +/* + Edge list is arranged as follows: + + all values in the edge list are bytes. + + all vertices are numbers between 0 and 24. x coordinate of vertex is vertex % 5, + and y coordinate is vertex / 5. + + an edge is + a direction code + a number of vertices in the edge + an array of vertices -- all vertices along the edge, whether or not they're + clickable. + an array of bools (bytes) indicating that a portion of the edge is + traversed (and should be drawn). the number of bools is one less than + the number of vertices. + + an edge list is + an array of 25 bools indicating which vertex is clickable. + an array of 25 bools indicating which vertex is used (drawn). + a number of edges + an array of edges. + + a hot vertex list is + a number of vertices + an array of 25 vertices + +*/ + +typedef int8 VertexType; +typedef VertexType *BombEdgeList; + +static const VertexType kEdgeOneSixteenth = 0; +static const VertexType kEdgeOneEighth = 1; +static const VertexType kEdgeThreeSixteenths = 2; +static const VertexType kEdgeOneFourth = 3; +static const VertexType kEdgeFiveSixteenths = 4; +static const VertexType kEdgeThreeEighths = 5; +static const VertexType kEdgeSevenSixteenths = 6; +static const VertexType kEdgeOneHalf = 7; + +class BombTimer : public IdlerAnimation { +public: + BombTimer(const DisplayElementID); + virtual ~BombTimer() {} + + void draw(const Common::Rect &); + +protected: + void timeChanged(const TimeValue); + + int _middle; + Surface _leftImage, _rightImage; +}; + +class BombGrid : public Picture { +public: + BombGrid(const DisplayElementID); + virtual ~BombGrid() {} + + void drawEdges(BombEdgeList); + +protected: + Frame _yellowDot; + Frame _yellowOneSixteenth; + Frame _yellowOneEighth; + Frame _yellowThreeSixteenths; + Frame _yellowOneFourth; + Frame _yellowFiveSixteenths; + Frame _yellowThreeEighths; + Frame _yellowSevenSixteenths; + Frame _yellowOneHalf; + Frame _redDot; + Frame _redOneSixteenth; + Frame _redOneEighth; + Frame _redThreeSixteenths; + Frame _redOneFourth; + Frame _redFiveSixteenths; + Frame _redThreeEighths; + Frame _redSevenSixteenths; + Frame _redOneHalf; +}; + +class Hotspot; + +class CaldoriaBomb : public GameInteraction, public NotificationReceiver { +public: + CaldoriaBomb(Neighborhood *, NotificationManager *); + virtual ~CaldoriaBomb(); + + long getNumHints(); + Common::String getHintMovie(uint); + void doSolve(); + bool canSolve(); + +protected: + void openInteraction(); + void initInteraction(); + void closeInteraction(); + void receiveNotification(Notification *, const NotificationFlags); + void activateHotspots(); + void clickInHotspot(const Input &, const Hotspot *); + void handleInput(const Input &, const Hotspot *); + InputBits getInputFilter(); + void startBombAmbient(Common::String); + + Notification *_neighborhoodNotification; + BombGrid _grid; + BombTimer _timer; + BombEdgeList _bombLevel[6]; + int _currentLevel, _flashTime; + Hotspot *_vertexHotspot[25]; + VertexType _lastVertex; + Notification _timerNotification; + NotificationCallBack _timerCallBack; + + TimeValue _readTime; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/caldoria/caldoriamessages.cpp b/engines/pegasus/neighborhood/caldoria/caldoriamessages.cpp new file mode 100644 index 0000000000..a3ce97d438 --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoriamessages.cpp @@ -0,0 +1,115 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/gamestate.h" +#include "pegasus/neighborhood/neighborhood.h" +#include "pegasus/neighborhood/caldoria/caldoria.h" +#include "pegasus/neighborhood/caldoria/caldoriamessages.h" + +namespace Pegasus { + +static const NotificationFlags kMessageDoneFlag = 1; + +CaldoriaMessages::CaldoriaMessages(Neighborhood *owner, const NotificationID id, NotificationManager *manager) : + GameInteraction(kCaldoriaMessagesInteractionID, owner), Notification(id, manager), _messageMovie(kCaldoriaMessagesID) { +} + +void CaldoriaMessages::openInteraction() { + _neighborhoodNotification = GameInteraction::_owner->getNeighborhoodNotification(); + _neighborhoodNotification->notifyMe(this, kExtraCompletedFlag, kExtraCompletedFlag); + _messageCallBack.setNotification(this); + notifyMe(this, kMessageDoneFlag, kMessageDoneFlag); + _messageCallBack.setCallBackFlag(kMessageDoneFlag); + _messageNumber = 1; +} + +void CaldoriaMessages::initInteraction() { + GameInteraction::_owner->startExtraSequence(kCaBedroomVidPhone, kExtraCompletedFlag, kFilterNoInput); +} + +void CaldoriaMessages::closeInteraction() { + cancelNotification(this); + _neighborhoodNotification->cancelNotification(this); +} + +void CaldoriaMessages::receiveNotification(Notification *notification, const NotificationFlags) { + if (notification == _neighborhoodNotification) { + switch (GameInteraction::_owner->getLastExtra()) { + case kCaBedroomVidPhone: + GameInteraction::_owner->showExtraView(kCaBedroomMessage1); + break; + case kCaBedroomMessage1: + play1Message(1); + break; + case kCaBedroomMessage2: + play1Message(2); + break; + } + } else { + _messageCallBack.releaseCallBack(); + _messageMovie.releaseMovie(); + + uint32 extraID = (_messageNumber == 1) ? kCaBedroomMessage1 : kCaBedroomMessage2; + GameInteraction::_owner->showExtraView(extraID); + allowInput(true); + } +} + +void CaldoriaMessages::clickInHotspot(const Input &input, const Hotspot *spot) { + uint32 extraID; + + switch (spot->getObjectID()) { + case kCaBedroomVidPhoneActivationSpotID: + extraID = (_messageNumber == 1) ? kCaBedroomMessage1 : kCaBedroomMessage2; + GameInteraction::_owner->startExtraSequence(extraID, kExtraCompletedFlag, kFilterNoInput); + break; + default: + GameInteraction::clickInHotspot(input, spot); + break; + } +} + +void CaldoriaMessages::play1Message(uint messageNumber) { + if (messageNumber == 1) { + _messageMovie.initFromMovieFile("Images/Caldoria/A12NVA.movie"); + _messageNumber = 2; + } else { + _messageMovie.initFromMovieFile("Images/Caldoria/A12NVB.movie"); + _messageNumber = 1; + GameState.setCaldoriaSeenMessages(true); + } + + _messageMovie.moveElementTo(kCaldoriaMessageLeft, kCaldoriaMessageTop); + _messageMovie.setDisplayOrder(kCaldoriaMessagesOrder); + _messageMovie.startDisplaying(); + _messageCallBack.initCallBack(&_messageMovie, kCallBackAtExtremes); + _messageCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + allowInput(false); + _messageMovie.show(); + _messageMovie.redrawMovieWorld(); + _messageMovie.start(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/caldoria/caldoriamessages.h b/engines/pegasus/neighborhood/caldoria/caldoriamessages.h new file mode 100644 index 0000000000..955fe10ce9 --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoriamessages.h @@ -0,0 +1,60 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIAMESSAGES_H +#define PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIAMESSAGES_H + +#include "pegasus/input.h" +#include "pegasus/interaction.h" +#include "pegasus/movie.h" +#include "pegasus/notification.h" +#include "pegasus/timers.h" + +namespace Pegasus { + +class Neighborhood; + +class CaldoriaMessages : public GameInteraction, public Notification, public NotificationReceiver { +public: + CaldoriaMessages(Neighborhood *, const NotificationID, NotificationManager *); + virtual ~CaldoriaMessages() {} + +protected: + void openInteraction(); + void initInteraction(); + void closeInteraction(); + void receiveNotification(Notification *, const NotificationFlags); + void clickInHotspot(const Input &, const Hotspot *); + void play1Message(uint); + + Movie _messageMovie; + NotificationCallBack _messageCallBack; + Notification *_neighborhoodNotification; + uint _messageNumber; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/caldoria/caldoriamirror.cpp b/engines/pegasus/neighborhood/caldoria/caldoriamirror.cpp new file mode 100644 index 0000000000..ff4d1811d0 --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoriamirror.cpp @@ -0,0 +1,135 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/neighborhood.h" +#include "pegasus/neighborhood/caldoria/caldoria.h" +#include "pegasus/neighborhood/caldoria/caldoriamirror.h" + +namespace Pegasus { + +CaldoriaMirror::CaldoriaMirror(Neighborhood *owner) : GameInteraction(kCaldoriaMirrorInteractionID, owner) { +} + +void CaldoriaMirror::openInteraction() { + _neighborhoodNotification = _owner->getNeighborhoodNotification(); + _neighborhoodNotification->notifyMe(this, kExtraCompletedFlag, kExtraCompletedFlag); +} + +void CaldoriaMirror::initInteraction() { + _owner->setCurrentActivation(kActivateMirrorReady); + _owner->startExtraSequence(kCaBathroomGreeting, kExtraCompletedFlag, kFilterNoInput); +} + +void CaldoriaMirror::closeInteraction() { + _neighborhoodNotification->cancelNotification(this); +} + +void CaldoriaMirror::handleInput(const Input &input, const Hotspot *cursorSpot) { + if (_owner->getLastExtra() == (uint32)kCaBathroomAgencyStandard || !input.anyDirectionInput()) + GameInteraction::handleInput(input, cursorSpot); +} + +void CaldoriaMirror::activateHotspots() { + GameInteraction::activateHotspots(); + + switch (_owner->getLastExtra()) { + case kCaBathroomGreeting: + case kCaBathroomBodyFat: + case kCaBathroomRetrothrash: + case kCaBathroomGeoWave: + g_allHotspots.activateOneHotspot(kCaBathroomMirrorSpotID); + g_allHotspots.deactivateOneHotspot(kCaHairStyle1SpotID); + g_allHotspots.deactivateOneHotspot(kCaHairStyle2SpotID); + g_allHotspots.deactivateOneHotspot(kCaHairStyle3SpotID); + break; + case kCaBathroomStylistIntro: + case kCaBathroomRetrothrashReturn: + case kCaBathroomGeoWaveReturn: + g_allHotspots.activateOneHotspot(kCaHairStyle1SpotID); + g_allHotspots.activateOneHotspot(kCaHairStyle2SpotID); + g_allHotspots.activateOneHotspot(kCaHairStyle3SpotID); + g_allHotspots.deactivateOneHotspot(kCaBathroomMirrorSpotID); + break; + } +} + +void CaldoriaMirror::clickInHotspot(const Input &input, const Hotspot *spot) { + switch (spot->getObjectID()) { + case kCaBathroomMirrorSpotID: + switch (_owner->getLastExtra()) { + case kCaBathroomGreeting: + _owner->startExtraSequence(kCaBathroomBodyFat, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaBathroomBodyFat: + _owner->startExtraSequence(kCaBathroomStylistIntro, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaBathroomRetrothrash: + _owner->startExtraSequence(kCaBathroomRetrothrashReturn, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaBathroomGeoWave: + _owner->startExtraSequence(kCaBathroomGeoWaveReturn, kExtraCompletedFlag, kFilterNoInput); + break; + } + break; + case kCaHairStyle1SpotID: + _owner->startExtraSequence(kCaBathroomRetrothrash, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaHairStyle2SpotID: + _owner->startExtraSequence(kCaBathroomAgencyStandard, kExtraCompletedFlag, kFilterNoInput); + break; + case kCaHairStyle3SpotID: + _owner->startExtraSequence(kCaBathroomGeoWave, kExtraCompletedFlag, kFilterNoInput); + break; + default: + GameInteraction::clickInHotspot(input, spot); + break; + } +} + +void CaldoriaMirror::receiveNotification(Notification *, const NotificationFlags) { + switch (_owner->getLastExtra()) { + case kCaBathroomRetrothrash: + case kCaBathroomGeoWave: + _owner->setCurrentActivation(kActivateMirrorReady); + break; + case kCaBathroomStylistIntro: + case kCaBathroomRetrothrashReturn: + case kCaBathroomGeoWaveReturn: + _owner->setCurrentActivation(kActivateStylistReady); + break; + case kCaBathroomAgencyStandard: + _owner->setCurrentActivation(kActivateHotSpotAlways); + _owner->requestDeleteCurrentInteraction(); + GameState.setScoringFixedHair(true); + GameState.setCaldoriaDoneHygiene(true); + break; + } + + allowInput(true); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/caldoria/caldoriamirror.h b/engines/pegasus/neighborhood/caldoria/caldoriamirror.h new file mode 100644 index 0000000000..1ca47ec774 --- /dev/null +++ b/engines/pegasus/neighborhood/caldoria/caldoriamirror.h @@ -0,0 +1,54 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIAMIRROR_H +#define PEGASUS_NEIGHBORHOOD_CALDORIA_CALDORIAMIRROR_H + +#include "pegasus/interaction.h" +#include "pegasus/notification.h" + +namespace Pegasus { + +class CaldoriaMirror : public GameInteraction, public NotificationReceiver { +public: + CaldoriaMirror(Neighborhood *); + virtual ~CaldoriaMirror() {} + +protected: + void openInteraction(); + void initInteraction(); + void closeInteraction(); + + void handleInput(const Input &, const Hotspot *); + void activateHotspots(); + void clickInHotspot(const Input &, const Hotspot *); + void receiveNotification(Notification *, const NotificationFlags); + + Notification *_neighborhoodNotification; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/door.cpp b/engines/pegasus/neighborhood/door.cpp new file mode 100644 index 0000000000..f7ec7559fc --- /dev/null +++ b/engines/pegasus/neighborhood/door.cpp @@ -0,0 +1,64 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/door.h" + +namespace Pegasus { + +void DoorTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].room = stream->readUint16BE(); + _entries[i].direction = stream->readByte(); + _entries[i].altCode = stream->readByte(); + _entries[i].movieStart = stream->readUint32BE(); + _entries[i].movieEnd = stream->readUint32BE(); + _entries[i].flags = stream->readByte(); + stream->readByte(); // alignment + debug(0, "Door[%d]: %d %d %d %d %d %d", i, _entries[i].room, _entries[i].direction, + _entries[i].altCode, _entries[i].movieStart, _entries[i].movieEnd, + _entries[i].flags); + } +} + +void DoorTable::clear() { + _entries.clear(); +} + +DoorTable::Entry DoorTable::findEntry(RoomID room, DirectionConstant direction, AlternateID altCode) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].room == room && _entries[i].direction == direction && _entries[i].altCode == altCode) + return _entries[i]; + + return Entry(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/door.h b/engines/pegasus/neighborhood/door.h new file mode 100644 index 0000000000..8ea757559a --- /dev/null +++ b/engines/pegasus/neighborhood/door.h @@ -0,0 +1,90 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_DOOR_H +#define PEGASUS_NEIGHBORHOOD_DOOR_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +typedef byte DoorFlags; + +enum { + kDoorPresentBit, // Bit set if there is a door here. + kDoorLockedBit // Bit set if door is locked, clear if unlocked. +}; + +static const DoorFlags kNoDoorFlags = 0; +static const DoorFlags kDoorPresentMask = 1 << kDoorPresentBit; +static const DoorFlags kDoorLockedMask = 1 << kDoorLockedBit; + +class DoorTable { +public: + DoorTable() {} + ~DoorTable() {} + + static uint32 getResTag() { return MKTAG('D', 'o', 'o', 'r'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry() { clear(); } + bool isEmpty() { return movieStart == 0xffffffff; } + void clear() { + room = kNoRoomID; + direction = kNoDirection; + altCode = kNoAlternateID; + movieStart = 0xffffffff; + movieEnd = 0xffffffff; + flags = kNoDoorFlags; + } + + RoomID room; + DirectionConstant direction; + AlternateID altCode; + TimeValue movieStart; + TimeValue movieEnd; + DoorFlags flags; + }; + + Entry findEntry(RoomID room, DirectionConstant direction, AlternateID altCode); + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif + diff --git a/engines/pegasus/neighborhood/exit.cpp b/engines/pegasus/neighborhood/exit.cpp new file mode 100644 index 0000000000..f0dfff12d3 --- /dev/null +++ b/engines/pegasus/neighborhood/exit.cpp @@ -0,0 +1,70 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/exit.h" + +namespace Pegasus { + +void ExitTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].room = stream->readUint16BE(); + _entries[i].direction = stream->readByte(); + _entries[i].altCode = stream->readByte(); + _entries[i].movieStart = stream->readUint32BE(); + _entries[i].movieEnd = stream->readUint32BE(); + _entries[i].exitEnd = stream->readUint32BE(); + _entries[i].exitLoop = stream->readUint32BE(); + _entries[i].exitRoom = stream->readUint16BE(); + _entries[i].exitDirection = stream->readByte(); + stream->readByte(); // alignment + + _entries[i].originalEnd = _entries[i].exitEnd; + + debug(0, "Exit[%d]: %d %d %d %d %d %d %d %d %d", i, _entries[i].room, _entries[i].direction, + _entries[i].altCode, _entries[i].movieStart, _entries[i].movieEnd, _entries[i].exitEnd, + _entries[i].exitLoop, _entries[i].exitRoom, _entries[i].exitDirection); + } +} + +void ExitTable::clear() { + _entries.clear(); +} + +ExitTable::Entry ExitTable::findEntry(RoomID room, DirectionConstant direction, AlternateID altCode) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].room == room && _entries[i].direction == direction && _entries[i].altCode == altCode) + return _entries[i]; + + return Entry(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/exit.h b/engines/pegasus/neighborhood/exit.h new file mode 100644 index 0000000000..17150892f9 --- /dev/null +++ b/engines/pegasus/neighborhood/exit.h @@ -0,0 +1,93 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_EXIT_H +#define PEGASUS_NEIGHBORHOOD_EXIT_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +class ExitTable { +public: + ExitTable() {} + ~ExitTable() {} + + static uint32 getResTag() { return MKTAG('E', 'x', 'i', 't'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry() { clear(); } + bool isEmpty() { return movieStart == 0xffffffff; } + void clear() { + room = kNoRoomID; + direction = kNoDirection; + altCode = kNoAlternateID; + movieStart = 0xffffffff; + movieEnd = 0xffffffff; + exitEnd = 0xffffffff; + originalEnd = 0xffffffff; + exitLoop = 0xffffffff; + exitRoom = kNoRoomID; + exitDirection = kNoDirection; + } + + RoomID room; + DirectionConstant direction; + AlternateID altCode; + TimeValue movieStart; + TimeValue movieEnd; + // exitEnd is the end of the optimized run of walks. + TimeValue exitEnd; + TimeValue originalEnd; + // exitLoop is the loop start time of the optimized run of walks if the run + // loops back on itself (so far, only in TSA). + TimeValue exitLoop; + RoomID exitRoom; + DirectionConstant exitDirection; + }; + + Entry findEntry(RoomID room, DirectionConstant direction, AlternateID altCode); + + typedef Common::Array<Entry>::iterator iterator; + iterator begin() { return _entries.begin(); } + iterator end() { return _entries.end(); } + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/extra.cpp b/engines/pegasus/neighborhood/extra.cpp new file mode 100644 index 0000000000..b8c4e5b510 --- /dev/null +++ b/engines/pegasus/neighborhood/extra.cpp @@ -0,0 +1,58 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/extra.h" + +namespace Pegasus { + +void ExtraTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].extra = stream->readUint32BE(); + _entries[i].movieStart = stream->readUint32BE(); + _entries[i].movieEnd = stream->readUint32BE(); + debug(0, "Extra[%d]: %d %d %d", i, _entries[i].extra, _entries[i].movieStart, _entries[i].movieEnd); + } +} + +void ExtraTable::clear() { + _entries.clear(); +} + +ExtraTable::Entry ExtraTable::findEntry(ExtraID extra) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].extra == extra) + return _entries[i]; + + return Entry(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/extra.h b/engines/pegasus/neighborhood/extra.h new file mode 100644 index 0000000000..14fcff1009 --- /dev/null +++ b/engines/pegasus/neighborhood/extra.h @@ -0,0 +1,67 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_EXTRA_H +#define PEGASUS_NEIGHBORHOOD_EXTRA_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +class ExtraTable { +public: + ExtraTable() {} + ~ExtraTable() {} + + static uint32 getResTag() { return MKTAG('X', 't', 'r', 'a'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry() { movieStart = 0xffffffff; } + bool isEmpty() { return movieStart == 0xffffffff; } + + ExtraID extra; + TimeValue movieStart; + TimeValue movieEnd; + }; + + Entry findEntry(ExtraID extra); + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/hotspotinfo.cpp b/engines/pegasus/neighborhood/hotspotinfo.cpp new file mode 100644 index 0000000000..c7524f3a0f --- /dev/null +++ b/engines/pegasus/neighborhood/hotspotinfo.cpp @@ -0,0 +1,65 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/hotspotinfo.h" + +namespace Pegasus { + +void HotspotInfoTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].hotspot = stream->readUint16BE(); + _entries[i].hotspotActivation = stream->readSByte(); + stream->readByte(); // alignment + _entries[i].hotspotRoom = stream->readUint16BE(); + _entries[i].hotspotDirection = stream->readByte(); + stream->readByte(); // alignment + _entries[i].hotspotExtra = stream->readUint32BE(); + _entries[i].hotspotItem = stream->readUint16BE(); + debug(0, "Hotspot[%d]: %d %d %d %d %d %d", i, _entries[i].hotspot, _entries[i].hotspotActivation, + _entries[i].hotspotRoom, _entries[i].hotspotDirection, _entries[i].hotspotExtra, + _entries[i].hotspotItem); + } +} + +void HotspotInfoTable::clear() { + _entries.clear(); +} + +HotspotInfoTable::Entry HotspotInfoTable::findEntry(HotSpotID hotspot) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].hotspot == hotspot) + return _entries[i]; + + return Entry(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/hotspotinfo.h b/engines/pegasus/neighborhood/hotspotinfo.h new file mode 100644 index 0000000000..965f445ba8 --- /dev/null +++ b/engines/pegasus/neighborhood/hotspotinfo.h @@ -0,0 +1,77 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_HOTSPOTINFO_H +#define PEGASUS_NEIGHBORHOOD_HOTSPOTINFO_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +class HotspotInfoTable { +public: + HotspotInfoTable() {} + ~HotspotInfoTable() {} + + static uint32 getResTag() { return MKTAG('H', 'S', 'I', 'n'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry() { hotspotRoom = kNoRoomID; } + bool isEmpty() { return hotspotRoom == kNoRoomID; } + + HotSpotID hotspot; + HotSpotActivationID hotspotActivation; + // Location hot spot lives in: + RoomID hotspotRoom; + DirectionConstant hotspotDirection; + // Extra to play if this is a "play extra" hot spot. + ExtraID hotspotExtra; + // Item corresponding to this hot spot if it is an item-related hot spot. + ItemID hotspotItem; + }; + + Entry findEntry(HotSpotID hotspot); + + typedef Common::Array<Entry>::iterator iterator; + iterator begin() { return _entries.begin(); } + iterator end() { return _entries.end(); } + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/constants.h b/engines/pegasus/neighborhood/mars/constants.h new file mode 100644 index 0000000000..82a7f03b68 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/constants.h @@ -0,0 +1,941 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_CONSTANTS_H +#define PEGASUS_NEIGHBORHOOD_MARS_CONSTANTS_H + +#include "pegasus/constants.h" + +namespace Pegasus { + +// Element Coordinates + +static const CoordType kUndoHiliteLeft = kNavAreaLeft + 140; +static const CoordType kUndoHiliteTop = kNavAreaTop + 36; + +static const CoordType kCurrentGuessLeft = kNavAreaLeft + 146; +static const CoordType kCurrentGuessTop = kNavAreaTop + 90; + +static const CoordType kReactorChoiceHiliteLeft = kNavAreaLeft + 116; +static const CoordType kReactorChoiceHiliteTop = kNavAreaTop + 158; + +static const CoordType kReactorHistoryLeft = kNavAreaLeft + 302; +static const CoordType kReactorHistoryTop = kNavAreaTop + 39; + +static const CoordType kAnswerLeft = kNavAreaLeft + 304; +static const CoordType kAnswerTop = kNavAreaTop + 180; + +static const CoordType kShuttle1Left = 0; +static const CoordType kShuttle1Top = 0; + +static const CoordType kShuttle2Left = 0; +static const CoordType kShuttle2Top = 96; + +static const CoordType kShuttle3Left = 500; +static const CoordType kShuttle3Top = 96; + +static const CoordType kShuttle4Left = 0; +static const CoordType kShuttle4Top = 320; + +static const CoordType kShuttleWindowLeft = 140; +static const CoordType kShuttleWindowTop = 96; +static const CoordType kShuttleWindowWidth = 360; +static const CoordType kShuttleWindowHeight = 224; + +static const CoordType kShuttleWindowMidH = (kShuttleWindowLeft * 2 + kShuttleWindowWidth) / 2; +static const CoordType kShuttleWindowMidV = (kShuttleWindowTop * 2 + kShuttleWindowHeight) / 2; + +static const CoordType kShuttleLeftLeft = 0; +static const CoordType kShuttleLeftTop = 128; + +static const CoordType kShuttleRightLeft = 506; +static const CoordType kShuttleRightTop = 128; + +static const CoordType kShuttleLowerLeftLeft = 74; +static const CoordType kShuttleLowerLeftTop = 358; + +static const CoordType kShuttleLowerRightLeft = 486; +static const CoordType kShuttleLowerRightTop = 354; + +static const CoordType kShuttleCenterLeft = 260; +static const CoordType kShuttleCenterTop = 336; + +static const CoordType kShuttleUpperLeftLeft = 30; +static const CoordType kShuttleUpperLeftTop = 32; + +static const CoordType kShuttleUpperRightLeft = 506; +static const CoordType kShuttleUpperRightTop = 52; + +static const CoordType kShuttleLeftEnergyLeft = 110; +static const CoordType kShuttleLeftEnergyTop = 186; + +static const CoordType kShuttleRightEnergyLeft = 510; +static const CoordType kShuttleRightEnergyTop = 186; + +static const CoordType kShuttleEnergyLeft = 186; +static const CoordType kShuttleEnergyTop = 60; +static const CoordType kShuttleEnergyWidth = 252; +static const CoordType kShuttleEnergyHeight = 22; + +static const CoordType kPlanetStartLeft = kShuttleWindowLeft; +static const CoordType kPlanetStartTop = kShuttleWindowTop + kShuttleWindowHeight; + +static const CoordType kPlanetStopLeft = kShuttleWindowLeft; +static const CoordType kPlanetStopTop = kShuttleWindowTop + kShuttleWindowHeight - 100; + +static const CoordType kShuttleTractorLeft = kShuttleWindowLeft + 6; +static const CoordType kShuttleTractorTop = kShuttleWindowTop + 56; +static const CoordType kShuttleTractorWidth = 348; +static const CoordType kShuttleTractorHeight = 112; + +static const CoordType kShuttleJunkLeft = kShuttleWindowLeft + 6; +static const CoordType kShuttleJunkTop = kShuttleWindowTop + 6; + +static const DisplayOrder kShuttlePlanetOrder = kInterfaceLayer; +static const DisplayOrder kShuttleAlienShipOrder = kShuttlePlanetOrder + 1; +static const DisplayOrder kShuttleRobotShipOrder = kShuttleAlienShipOrder + 1; +static const DisplayOrder kShuttleTractorBeamMovieOrder = kShuttleRobotShipOrder + 1; +static const DisplayOrder kShuttleWeaponBackOrder = kShuttleTractorBeamMovieOrder + 1; +static const DisplayOrder kShuttleJunkOrder = kShuttleWeaponBackOrder + 1; +static const DisplayOrder kShuttleWeaponFrontOrder = kShuttleJunkOrder + 1; +static const DisplayOrder kShuttleTractorBeamOrder = kShuttleWeaponFrontOrder + 1; +static const DisplayOrder kShuttleHUDOrder = kShuttleTractorBeamOrder + 1; +static const DisplayOrder kShuttleBackgroundOrder = kShuttleHUDOrder + 1; +static const DisplayOrder kShuttleMonitorOrder = kShuttleBackgroundOrder + 1; +static const DisplayOrder kShuttleStatusOrder = kShuttleMonitorOrder + 1; + +static const TimeValue kShuttleSwingStart = 0; +static const TimeValue kShuttleSwingStop = 5 * 600; + +static const TimeValue kCanyonChaseStart = kShuttleSwingStop; +static const TimeValue kCanyonChaseStop = 60 * 600 + 43 * 600 + 14 * 40; + +static const TimeValue kLaunchTubeReachedTime = 60 * 600 + 38 * 600 - kCanyonChaseStart; +static const TimeValue kCanyonChaseFinishedTime = kCanyonChaseStop - kCanyonChaseStart - + kLaunchTubeReachedTime; + +// Left shuttle. + +static const TimeValue kShuttleLeftIntroStart = 0; +static const TimeValue kShuttleLeftIntroStop = 400; + +static const TimeValue kShuttleLeftBlankTime = 400; + +static const TimeValue kShuttleLeftNormalTime = 440; + +static const TimeValue kShuttleLeftAutoTestTime = 480; + +static const TimeValue kShuttleLeftDamagedTime = 520; + +static const TimeValue kShuttleLeftDampingTime = 560; + +static const TimeValue kShuttleLeftGravitonTime = 600; + +static const TimeValue kShuttleLeftTractorTime = 640; + +// Right shuttle. + +static const TimeValue kShuttleRightIntroStart = 0; +static const TimeValue kShuttleRightIntroStop = 400; + +static const TimeValue kShuttleRightDestroyedStart = 400; +static const TimeValue kShuttleRightDestroyedStop = 840; + +static const TimeValue kShuttleRightBlankTime = 840; + +static const TimeValue kShuttleRightNormalTime = 880; + +static const TimeValue kShuttleRightDamagedTime = 920; + +static const TimeValue kShuttleRightTargetLockTime = 960; + +static const TimeValue kShuttleRightGravitonTime = 1000; + +static const TimeValue kShuttleRightOverloadTime = 1040; + +// Lower Left shuttle. + +static const TimeValue kShuttleLowerLeftCollisionTime = 0; + +static const TimeValue kShuttleLowerLeftTubeTime = 40; + +static const TimeValue kShuttleLowerLeftAutopilotTime = 80; + +// Lower Right shuttle. + +static const TimeValue kShuttleLowerRightOffTime = 0; + +static const TimeValue kShuttleLowerRightTrackingTime = 40; + +static const TimeValue kShuttleLowerRightTransportTime = 80; + +static const TimeValue kShuttleLowerRightTransportHiliteTime = 120; + +// Center shuttle. + +static const TimeValue kShuttleCenterBoardingTime = 0; + +static const TimeValue kShuttleCenterCheckTime = 40; + +static const TimeValue kShuttleCenterNavCompTime = 80; + +static const TimeValue kShuttleCenterCommTime = 120; + +static const TimeValue kShuttleCenterWeaponsTime = 160; + +static const TimeValue kShuttleCenterAllSystemsTime = 200; + +static const TimeValue kShuttleCenterSecureLooseTime = 240; + +static const TimeValue kShuttleCenterAutoTestTime = 280; + +static const TimeValue kShuttleCenterLaunchTime = 320; + +static const TimeValue kShuttleCenterEnterTubeTime = 360; + +static const TimeValue kShuttleCenterTargetSightedTime = 400; + +static const TimeValue kShuttleCenterVerifyingTime = 440; + +static const TimeValue kShuttleCenterScanningTime = 480; + +static const TimeValue kShuttleCenterSafeTime = 520; + +// Upper Left shuttle. + +static const TimeValue kShuttleUpperLeftDimTime = 0; + +static const TimeValue kShuttleUpperLeftDampingTime = 40; + +static const TimeValue kShuttleUpperLeftGravitonTime = 80; + +static const TimeValue kShuttleUpperLeftTractorTime = 120; + +// Upper Right shuttle. + +static const TimeValue kShuttleUpperRightLockedTime = 0; + +static const TimeValue kShuttleUpperRightArmedTime = 40; + +static const TimeValue kShuttleUpperRightAlienDestroyedTime = 80; + +static const TimeValue kShuttleUpperRightOverloadTime = 120; + +static const TimeValue kShuttleUpperRightTargetDestroyedTime = 160; + +// Shuttle distance + +static const int kShuttleDistance = 500; + +static const int kJunkMaxDistance = kShuttleDistance; +static const int kJunkMinDistance = 40; + +static const int kEnergyBeamMaxDistance = kShuttleDistance; +static const int kEnergyBeamMinDistance = 40; + +static const int kGravitonMaxDistance = kShuttleDistance; +static const int kGravitonMinDistance = 40; + +static const TimeValue kMarsOxyMaskOnIn = 0; +static const TimeValue kMarsOxyMaskOnOut = 1560; + +static const TimeValue kMarsAirlockButtonBeepIn = 1560; +static const TimeValue kMarsAirlockButtonBeepOut = 1620; + +static const TimeValue kMarsColorMatchingButtonBeepIn = 1620; +static const TimeValue kMarsColorMatchingButtonBeepOut = 1680; + +static const TimeValue kMarsKioskBeepIn = 1680; +static const TimeValue kMarsKioskBeepOut = 1740; + +static const TimeValue kMarsBumpIntoWallIn = 1740; +static const TimeValue kMarsBumpIntoWallOut = 1888; + +static const TimeValue kMarsGantryDoorCloseIn = 1888; +static const TimeValue kMarsGantryDoorCloseOut = 2866; + +static const TimeValue kMarsTransportDoorCloseIn = 2866; +static const TimeValue kMarsTransportDoorCloseOut = 3593; + +static const TimeValue kMarsAirlockPressurizeIn = 3593; +static const TimeValue kMarsAirlockPressurizeOut = 4766; + +static const TimeValue kMarsBigAirlockDoorCloseIn = 4766; +static const TimeValue kMarsBigAirlockDoorCloseOut = 7872; + +static const TimeValue kMarsSmallAirlockDoorCloseIn = 7872; +static const TimeValue kMarsSmallAirlockDoorCloseOut = 10000; + +static const TimeValue kMarsMazeDoorCloseIn = 10000; +static const TimeValue kMarsMazeDoorCloseOut = 10969; + +static const TimeValue kMarsRobotTakesTransportIn = 10969; +static const TimeValue kMarsRobotTakesTransportOut = 12802; + +static const TimeValue kMarsPodDepartedUpperPlatformIn = 12802; +static const TimeValue kMarsPodDepartedUpperPlatformOut = 15783; + +static const TimeValue kMarsPodDepartedLowerPlatformIn = 15783; +static const TimeValue kMarsPodDepartedLowerPlatformOut = 18736; + +static const TimeValue kMarsPodArrivedUpperPlatformIn = 18736; +static const TimeValue kMarsPodArrivedUpperPlatformOut = 21605; + +static const TimeValue kMarsCheckInRequiredIn = 21605; +static const TimeValue kMarsCheckInRequiredOut = 27463; + +static const TimeValue kMarsCantOpenShuttleIn = 27463; +static const TimeValue kMarsCantOpenShuttleOut = 29214; + +static const TimeValue kMarsShuttleLockOverrideIn = 29214; +static const TimeValue kMarsShuttleLockOverrideOut = 30330; + +static const TimeValue kMarsNoShuttleIn = 30330; +static const TimeValue kMarsNoShuttleOut = 31502; + +static const TimeValue kMustBeUnlockedIn = 31502; +static const TimeValue kMustBeUnlockedOut = 33960; + +static const TimeValue kColorMatchBlueIn = 33960; +static const TimeValue kColorMatchBlueOut = 34240; + +static const TimeValue kColorMatchRedIn = 34240; +static const TimeValue kColorMatchRedOut = 34538; + +static const TimeValue kColorMatchGreenIn = 34538; +static const TimeValue kColorMatchGreenOut = 34827; + +static const TimeValue kColorMatchYellowIn = 34827; +static const TimeValue kColorMatchYellowOut = 35162; + +static const TimeValue kColorMatchPurpleIn = 35162; +static const TimeValue kColorMatchPurpleOut = 35426; + +static const TimeValue kColorMatchZeroNodesIn = 35426; +static const TimeValue kColorMatchZeroNodesOut = 36376; + +static const TimeValue kColorMatchOneNodeIn = 36376; +static const TimeValue kColorMatchOneNodeOut = 37209; + +static const TimeValue kColorMatchTwoNodesIn = 37209; +static const TimeValue kColorMatchTwoNodesOut = 37983; + +static const TimeValue kColorMatchThreeNodesIn = 37983; +static const TimeValue kColorMatchThreeNodesOut = 38784; + +static const TimeValue kMarsShuttle1DepartedIn = 38784; +static const TimeValue kMarsShuttle1DepartedOut = 40323; + +static const TimeValue kMarsShuttle2DepartedIn = 40323; +static const TimeValue kMarsShuttle2DepartedOut = 41824; + +static const TimeValue kShuttleCockpitIn = 41824; +static const TimeValue kShuttleCockpitOut = 43126; + +static const TimeValue kShuttleOnboardIn = 43126; +static const TimeValue kShuttleOnboardOut = 44284; + +static const TimeValue kShuttleNavigationIn = 44284; +static const TimeValue kShuttleNavigationOut = 46049; + +static const TimeValue kShuttleCommunicationIn = 46049; +static const TimeValue kShuttleCommunicationOut = 47288; + +static const TimeValue kShuttleAutoTestingIn = 47288; +static const TimeValue kShuttleAutoTestingOut = 48179; + +static const TimeValue kMarsThrusterAutoTestIn = 48179; +static const TimeValue kMarsThrusterAutoTestOut = 49979; + +static const TimeValue kShuttleAllSystemsIn = 49979; +static const TimeValue kShuttleAllSystemsOut = 51065; + +static const TimeValue kShuttleSecureLooseIn = 51065; +static const TimeValue kShuttleSecureLooseOut = 52346; + +static const TimeValue kShuttlePrepareForDropIn = 52346; +static const TimeValue kShuttlePrepareForDropOut = 53216; + +static const TimeValue kShuttleAllClearIn = 53216; +static const TimeValue kShuttleAllClearOut = 54031; + +static const TimeValue kShuttleConfiguringIn = 54031; +static const TimeValue kShuttleConfiguringOut = 54994; + +static const TimeValue kShuttleGeneratingIn = 54994; +static const TimeValue kShuttleGeneratingOut = 56033; + +static const TimeValue kShuttleBreakawayIn = 56033; +static const TimeValue kShuttleBreakawayOut = 57346; + +static const TimeValue kMarsAtmosphericBreakawayIn = 57346; +static const TimeValue kMarsAtmosphericBreakawayOut = 59237; + +static const TimeValue kMarsCockpitChatterIn = 59237; +static const TimeValue kMarsCockpitChatterOut = 70344; + +static const TimeValue kShuttleDamperDescIn = 70344; +static const TimeValue kShuttleDamperDescOut = 73262; + +static const TimeValue kShuttleGravitonDescIn = 73262; +static const TimeValue kShuttleGravitonDescOut = 75296; + +static const TimeValue kShuttleTractorDescIn = 75296; +static const TimeValue kShuttleTractorDescOut = 78381; + +static const TimeValue kShuttleTargetSightedIn = 78381; +static const TimeValue kShuttleTargetSightedOut = 79074; + +static const TimeValue kShuttleAutopilotEngagedIn = 79074; +static const TimeValue kShuttleAutopilotEngagedOut = 80414; + +static const TimeValue kMarsEDBBlastIn = 80414; +static const TimeValue kMarsEDBBlastOut = 80705; + +static const TimeValue kMarsGravitonBlastIn = 80705; +static const TimeValue kMarsGravitonBlastOut = 81199; + +static const TimeValue kMarsJunkCollisionIn = 81199; +static const TimeValue kMarsJunkCollisionOut = 81961; + +static const TimeValue kShuttleGravitonIn = 81961; +static const TimeValue kShuttleGravitonOut = 82587; + +static const TimeValue kShuttleDampingBeamIn = 82587; +static const TimeValue kShuttleDampingBeamOut = 83331; + +static const TimeValue kShuttleTractorBeamIn = 83331; +static const TimeValue kShuttleTractorBeamOut = 83802; + +static const TimeValue kShuttleHullBreachIn = 83802; +static const TimeValue kShuttleHullBreachOut = 84721; + +static const TimeValue kShuttleWingDamageIn = 84721; +static const TimeValue kShuttleWingDamageOut = 85640; + +static const TimeValue kShuttleHullDamageIn = 85640; +static const TimeValue kShuttleHullDamageOut = 86513; + +static const TimeValue kShuttleEnergyTooLowIn = 86513; +static const TimeValue kShuttleEnergyTooLowOut = 87578; + +static const TimeValue kShuttleTractorLimitedIn = 87578; +static const TimeValue kShuttleTractorLimitedOut = 89164; + +static const TimeValue kShuttleCantHoldIn = 89164; +static const TimeValue kShuttleCantHoldOut = 90945; + +static const TimeValue kShuttleBrokeFreeIn = 90945; +static const TimeValue kShuttleBrokeFreeOut = 92322; + +static const TimeValue kShuttleDestroyedIn = 92322; +static const TimeValue kShuttleDestroyedOut = 93189; + +static const TimeValue kShuttleCoordinatesIn = 93189; +static const TimeValue kShuttleCoordinatesOut = 94018; + +static const TimeValue kShuttleScanningIn = 94018; +static const TimeValue kShuttleScanningOut = 94975; + +static const TimeValue kShuttleSafeIn = 94975; +static const TimeValue kShuttleSafeOut = 96176; + +static const TimeValue kShuttleOverloadedIn = 96176; +static const TimeValue kShuttleOverloadedOut = 101308; + +static const TimeScale kMarsMovieScale = 600; +static const TimeScale kMarsFramesPerSecond = 15; +static const TimeScale kMarsFrameDuration = 40; + +// Alternate IDs. + +static const AlternateID kAltMarsNormal = 0; +static const AlternateID kAltMarsPodAtMars34 = 1; +static const AlternateID kAltMarsTookCard = 2; +static const AlternateID kAltMars35AirlockEast = 3; +static const AlternateID kAltMars35AirlockWest = 4; +static const AlternateID kAltMarsPodAtMars45 = 5; +static const AlternateID kAltMarsTookMask = 6; +static const AlternateID kAltMarsMaskOnFiller = 7; +static const AlternateID kAltMars60AirlockEast = 8; +static const AlternateID kAltMars60AirlockWest = 9; + +// Room IDs. + +static const RoomID kMars0A = 0; +static const RoomID kMars00 = 1; +static const RoomID kMars01 = 2; +static const RoomID kMars02 = 3; +static const RoomID kMars03 = 4; +static const RoomID kMars04 = 5; +static const RoomID kMars05 = 6; +static const RoomID kMars06 = 7; +static const RoomID kMars07 = 8; +static const RoomID kMars08 = 9; +static const RoomID kMars09 = 10; +static const RoomID kMars10 = 11; +static const RoomID kMars11 = 12; +static const RoomID kMars12 = 13; +static const RoomID kMars13 = 14; +static const RoomID kMars14 = 15; +static const RoomID kMars15 = 16; +static const RoomID kMars16 = 17; +static const RoomID kMars17 = 18; +static const RoomID kMars18 = 19; +static const RoomID kMars19 = 20; +static const RoomID kMars20 = 21; +static const RoomID kMars21 = 22; +static const RoomID kMars22 = 23; +static const RoomID kMars23 = 24; +static const RoomID kMars24 = 25; +static const RoomID kMars25 = 26; +static const RoomID kMars26 = 27; +static const RoomID kMars27 = 28; +static const RoomID kMars28 = 29; +static const RoomID kMars29 = 30; +static const RoomID kMars30 = 31; +static const RoomID kMars31 = 32; +static const RoomID kMars31South = 33; +static const RoomID kMars32 = 34; +static const RoomID kMars33 = 35; +static const RoomID kMars33North = 36; +static const RoomID kMars34 = 37; +static const RoomID kMars35 = 38; +static const RoomID kMars36 = 39; +static const RoomID kMars37 = 40; +static const RoomID kMars38 = 41; +static const RoomID kMars39 = 42; +static const RoomID kMars41 = 43; +static const RoomID kMars42 = 44; +static const RoomID kMars43 = 45; +static const RoomID kMars44 = 46; +static const RoomID kMars45 = 47; +static const RoomID kMars46 = 48; +static const RoomID kMars47 = 49; +static const RoomID kMars48 = 50; +static const RoomID kMars49 = 51; +static const RoomID kMars50 = 52; +static const RoomID kMars51 = 53; +static const RoomID kMars52 = 54; +static const RoomID kMars54 = 55; +static const RoomID kMars56 = 56; +static const RoomID kMars58 = 57; +static const RoomID kMars60 = 58; +static const RoomID kMarsRobotShuttle = 59; +static const RoomID kMarsMaze004 = 60; +static const RoomID kMarsMaze005 = 61; +static const RoomID kMarsMaze006 = 62; +static const RoomID kMarsMaze007 = 63; +static const RoomID kMarsMaze008 = 64; +static const RoomID kMarsMaze009 = 65; +static const RoomID kMarsMaze010 = 66; +static const RoomID kMarsMaze011 = 67; +static const RoomID kMarsMaze012 = 68; +static const RoomID kMarsMaze015 = 69; +static const RoomID kMarsMaze016 = 70; +static const RoomID kMarsMaze017 = 71; +static const RoomID kMarsMaze018 = 72; +static const RoomID kMarsMaze019 = 73; +static const RoomID kMarsMaze020 = 74; +static const RoomID kMarsMaze021 = 75; +static const RoomID kMarsMaze022 = 76; +static const RoomID kMarsMaze023 = 77; +static const RoomID kMarsMaze024 = 78; +static const RoomID kMarsMaze025 = 79; +static const RoomID kMarsMaze026 = 80; +static const RoomID kMarsMaze027 = 81; +static const RoomID kMarsMaze028 = 82; +static const RoomID kMarsMaze031 = 83; +static const RoomID kMarsMaze032 = 84; +static const RoomID kMarsMaze033 = 85; +static const RoomID kMarsMaze034 = 86; +static const RoomID kMarsMaze035 = 87; +static const RoomID kMarsMaze036 = 88; +static const RoomID kMarsMaze037 = 89; +static const RoomID kMarsMaze038 = 90; +static const RoomID kMarsMaze039 = 91; +static const RoomID kMarsMaze042 = 92; +static const RoomID kMarsMaze043 = 93; +static const RoomID kMarsMaze044 = 94; +static const RoomID kMarsMaze045 = 95; +static const RoomID kMarsMaze046 = 96; +static const RoomID kMarsMaze047 = 97; +static const RoomID kMarsMaze049 = 98; +static const RoomID kMarsMaze050 = 99; +static const RoomID kMarsMaze051 = 100; +static const RoomID kMarsMaze052 = 101; +static const RoomID kMarsMaze053 = 102; +static const RoomID kMarsMaze054 = 103; +static const RoomID kMarsMaze055 = 104; +static const RoomID kMarsMaze056 = 105; +static const RoomID kMarsMaze057 = 106; +static const RoomID kMarsMaze058 = 107; +static const RoomID kMarsMaze059 = 108; +static const RoomID kMarsMaze060 = 109; +static const RoomID kMarsMaze061 = 110; +static const RoomID kMarsMaze063 = 111; +static const RoomID kMarsMaze064 = 112; +static const RoomID kMarsMaze065 = 113; +static const RoomID kMarsMaze066 = 114; +static const RoomID kMarsMaze067 = 115; +static const RoomID kMarsMaze068 = 116; +static const RoomID kMarsMaze069 = 117; +static const RoomID kMarsMaze070 = 118; +static const RoomID kMarsMaze071 = 119; +static const RoomID kMarsMaze072 = 120; +static const RoomID kMarsMaze074 = 121; +static const RoomID kMarsMaze076 = 122; +static const RoomID kMarsMaze078 = 123; +static const RoomID kMarsMaze079 = 124; +static const RoomID kMarsMaze081 = 125; +static const RoomID kMarsMaze083 = 126; +static const RoomID kMarsMaze084 = 127; +static const RoomID kMarsMaze085 = 128; +static const RoomID kMarsMaze086 = 129; +static const RoomID kMarsMaze087 = 130; +static const RoomID kMarsMaze088 = 131; +static const RoomID kMarsMaze089 = 132; +static const RoomID kMarsMaze090 = 133; +static const RoomID kMarsMaze091 = 134; +static const RoomID kMarsMaze092 = 135; +static const RoomID kMarsMaze093 = 136; +static const RoomID kMarsMaze098 = 137; +static const RoomID kMarsMaze099 = 138; +static const RoomID kMarsMaze100 = 139; +static const RoomID kMarsMaze101 = 140; +static const RoomID kMarsMaze104 = 141; +static const RoomID kMarsMaze105 = 142; +static const RoomID kMarsMaze106 = 143; +static const RoomID kMarsMaze107 = 144; +static const RoomID kMarsMaze108 = 145; +static const RoomID kMarsMaze111 = 146; +static const RoomID kMarsMaze113 = 147; +static const RoomID kMarsMaze114 = 148; +static const RoomID kMarsMaze115 = 149; +static const RoomID kMarsMaze116 = 150; +static const RoomID kMarsMaze117 = 151; +static const RoomID kMarsMaze118 = 152; +static const RoomID kMarsMaze119 = 153; +static const RoomID kMarsMaze120 = 154; +static const RoomID kMarsMaze121 = 155; +static const RoomID kMarsMaze122 = 156; +static const RoomID kMarsMaze123 = 157; +static const RoomID kMarsMaze124 = 158; +static const RoomID kMarsMaze125 = 159; +static const RoomID kMarsMaze126 = 160; +static const RoomID kMarsMaze127 = 161; +static const RoomID kMarsMaze128 = 162; +static const RoomID kMarsMaze129 = 163; +static const RoomID kMarsMaze130 = 164; +static const RoomID kMarsMaze131 = 165; +static const RoomID kMarsMaze132 = 166; +static const RoomID kMarsMaze133 = 167; +static const RoomID kMarsMaze136 = 168; +static const RoomID kMarsMaze137 = 169; +static const RoomID kMarsMaze138 = 170; +static const RoomID kMarsMaze139 = 171; +static const RoomID kMarsMaze140 = 172; +static const RoomID kMarsMaze141 = 173; +static const RoomID kMarsMaze142 = 174; +static const RoomID kMarsMaze143 = 175; +static const RoomID kMarsMaze144 = 176; +static const RoomID kMarsMaze145 = 177; +static const RoomID kMarsMaze146 = 178; +static const RoomID kMarsMaze147 = 179; +static const RoomID kMarsMaze148 = 180; +static const RoomID kMarsMaze149 = 181; +static const RoomID kMarsMaze152 = 182; +static const RoomID kMarsMaze153 = 183; +static const RoomID kMarsMaze154 = 184; +static const RoomID kMarsMaze155 = 185; +static const RoomID kMarsMaze156 = 186; +static const RoomID kMarsMaze157 = 187; +static const RoomID kMarsMaze159 = 188; +static const RoomID kMarsMaze160 = 189; +static const RoomID kMarsMaze161 = 190; +static const RoomID kMarsMaze162 = 191; +static const RoomID kMarsMaze163 = 192; +static const RoomID kMarsMaze164 = 193; +static const RoomID kMarsMaze165 = 194; +static const RoomID kMarsMaze166 = 195; +static const RoomID kMarsMaze167 = 196; +static const RoomID kMarsMaze168 = 197; +static const RoomID kMarsMaze169 = 198; +static const RoomID kMarsMaze170 = 199; +static const RoomID kMarsMaze171 = 200; +static const RoomID kMarsMaze172 = 201; +static const RoomID kMarsMaze173 = 202; +static const RoomID kMarsMaze174 = 203; +static const RoomID kMarsMaze175 = 204; +static const RoomID kMarsMaze177 = 205; +static const RoomID kMarsMaze178 = 206; +static const RoomID kMarsMaze179 = 207; +static const RoomID kMarsMaze180 = 208; +static const RoomID kMarsMaze181 = 209; +static const RoomID kMarsMaze182 = 210; +static const RoomID kMarsMaze183 = 211; +static const RoomID kMarsMaze184 = 212; +static const RoomID kMarsMaze187 = 213; +static const RoomID kMarsMaze188 = 214; +static const RoomID kMarsMaze189 = 215; +static const RoomID kMarsMaze190 = 216; +static const RoomID kMarsMaze191 = 217; +static const RoomID kMarsMaze192 = 218; +static const RoomID kMarsMaze193 = 219; +static const RoomID kMarsMaze194 = 220; +static const RoomID kMarsMaze195 = 221; +static const RoomID kMarsMaze198 = 222; +static const RoomID kMarsMaze199 = 223; +static const RoomID kMarsMaze200 = 224; +static const RoomID kMarsDeathRoom = 225; + +// Hot Spot Activation IDs. + +static const HotSpotActivationID kActivationReadyForKiosk = 1; +static const HotSpotActivationID kActivationKioskChoice = 2; +static const HotSpotActivationID kActivationTunnelMapReady = 3; +static const HotSpotActivationID kActivateMarsPodClosed = 4; +static const HotSpotActivationID kActivateMarsPodOpen = 5; +static const HotSpotActivationID kActivateReadyToPressurizeAirlock = 6; +static const HotSpotActivationID kActivateAirlockPressurized = 7; +static const HotSpotActivationID kActivateMaskOnHolder = 8; +static const HotSpotActivationID kActivateMaskOnFiller = 9; +static const HotSpotActivationID kActivateReactorPlatformOut = 10; +static const HotSpotActivationID kActivateReactorPlatformIn = 11; +static const HotSpotActivationID kActivateReactorAskLowerScreen = 12; +static const HotSpotActivationID kActivateReactorReadyForNitrogen = 13; +static const HotSpotActivationID kActivateReactorReadyForCrowBar = 14; +static const HotSpotActivationID kActivateReactorAskOperation = 15; +static const HotSpotActivationID kActivateReactorRanEvaluation = 16; +static const HotSpotActivationID kActivateReactorRanDiagnostics = 17; +static const HotSpotActivationID kActivateReactorAnalyzed = 18; +static const HotSpotActivationID kActivateReactorInstructions = 19; +static const HotSpotActivationID kActivateReactorInGame = 20; +static const HotSpotActivationID kActivateReactorBombSafe = 21; +static const HotSpotActivationID kActivateReactorBombExposed = 22; +static const HotSpotActivationID kActivationRobotHeadClosed = 23; +static const HotSpotActivationID kActivationRobotHeadOpen = 24; + +// Hot Spot IDs. + +static const HotSpotID kMars11NorthKioskSpotID = 5000; +static const HotSpotID kMars11NorthKioskSightsSpotID = 5001; +static const HotSpotID kMars11NorthKioskColonySpotID = 5002; +static const HotSpotID kMars12NorthKioskSpotID = 5003; +static const HotSpotID kMars12NorthKioskSightsSpotID = 5004; +static const HotSpotID kMars12NorthKioskColonySpotID = 5005; +static const HotSpotID kMars31SouthSpotID = 5006; +static const HotSpotID kMars31SouthOutSpotID = 5007; +static const HotSpotID kMars31SouthCardSpotID = 5008; +static const HotSpotID kMars33NorthSpotID = 5009; +static const HotSpotID kMars33NorthOutSpotID = 5010; +static const HotSpotID kMars33NorthMonitorSpotID = 5011; +static const HotSpotID kMars34NorthCardDropSpotID = 5012; +static const HotSpotID kMars34SouthOpenStorageSpotID = 5013; +static const HotSpotID kMars34SouthCloseStorageSpotID = 5014; +static const HotSpotID kMars34SouthCrowbarSpotID = 5015; +static const HotSpotID kMars35EastPressurizeSpotID = 5016; +static const HotSpotID kMars35EastSpinSpotID = 5017; +static const HotSpotID kMars35WestPressurizeSpotID = 5018; +static const HotSpotID kMars35WestSpinSpotID = 5019; +static const HotSpotID kMars45NorthOpenStorageSpotID = 5020; +static const HotSpotID kMars45NorthCloseStorageSpotID = 5021; +static const HotSpotID kMars45NorthCrowbarSpotID = 5022; +static const HotSpotID kAttackRobotHotSpotID = 5023; +static const HotSpotID kMars49AirMaskSpotID = 5024; +static const HotSpotID kMars49AirMaskFilledSpotID = 5025; +static const HotSpotID kMars49AirFillingDropSpotID = 5026; +static const HotSpotID kMars52MoveLeftSpotID = 5027; +static const HotSpotID kMars52MoveRightSpotID = 5028; +static const HotSpotID kMars52ExtractSpotID = 5029; +static const HotSpotID kMars53RetractSpotID = 5030; +static const HotSpotID kMars54MoveLeftSpotID = 5031; +static const HotSpotID kMars54MoveRightSpotID = 5032; +static const HotSpotID kMars54ExtractSpotID = 5033; +static const HotSpotID kMars55RetractSpotID = 5034; +static const HotSpotID kMars56MoveLeftSpotID = 5035; +static const HotSpotID kMars56MoveRightSpotID = 5036; +static const HotSpotID kMars56ExtractSpotID = 5037; +static const HotSpotID kMars57RetractSpotID = 5038; +static const HotSpotID kMars57LowerScreenSpotID = 5039; +static const HotSpotID kMars57Retract2SpotID = 5040; +static const HotSpotID kMars57DropNitrogenSpotID = 5041; +static const HotSpotID kMars57DropCrowBarSpotID = 5042; +static const HotSpotID kMars57CantOpenPanelSpotID = 5043; +static const HotSpotID kMars57ShieldEvaluationSpotID = 5044; +static const HotSpotID kMars57MeasureOutputSpotID = 5045; +static const HotSpotID kMars57RunDiagnosticsSpotID = 5046; +static const HotSpotID kMars57BackToOperationMenuSpotID = 5047; +static const HotSpotID kMars57AnalyzeObjectSpotID = 5048; +static const HotSpotID kMars57RemoveObjectMenuSpotID = 5049; +static const HotSpotID kMars57CircuitLinkSpotID = 5050; +static const HotSpotID kMars57CancelCircuitLinkSpotID = 5051; +static const HotSpotID kMars57GameInstructionsSpotID = 5052; +static const HotSpotID kMars57UndoMoveSpotID = 5053; +static const HotSpotID kMars57RedMoveSpotID = 5054; +static const HotSpotID kMars57YellowMoveSpotID = 5055; +static const HotSpotID kMars57GreenMoveSpotID = 5056; +static const HotSpotID kMars57BlueMoveSpotID = 5057; +static const HotSpotID kMars57PurpleMoveSpotID = 5058; +static const HotSpotID kMars57LowerScreenSafelySpotID = 5059; +static const HotSpotID kMars57GrabBombSpotID = 5060; +static const HotSpotID kMars58MoveLeftSpotID = 5061; +static const HotSpotID kMars58MoveRightSpotID = 5062; +static const HotSpotID kMars58ExtractSpotID = 5063; +static const HotSpotID kMars59RetractSpotID = 5064; +static const HotSpotID kMars60EastPressurizeSpotID = 5065; +static const HotSpotID kMars60EastSpinSpotID = 5066; +static const HotSpotID kMars60WestPressurizeSpotID = 5067; +static const HotSpotID kMars60WestSpinSpotID = 5068; +static const HotSpotID kRobotShuttleOpenHeadSpotID = 5069; +static const HotSpotID kRobotShuttleMapChipSpotID = 5070; +static const HotSpotID kRobotShuttleOpticalChipSpotID = 5071; +static const HotSpotID kRobotShuttleShieldChipSpotID = 5072; + +// Extra sequence IDs. + +static const ExtraID kMarsArrivalFromTSA = 0; +static const ExtraID kMars0AWatchShuttleDepart = 1; +static const ExtraID kRobotThrowsPlayer = 2; +static const ExtraID kMarsInfoKioskIntro = 3; +static const ExtraID kMarsColonyInfo = 4; +static const ExtraID kMarsSightsInfo = 5; +static const ExtraID kRobotOnWayToShuttle = 6; +static const ExtraID kMars31SouthZoomInNoCard = 7; +static const ExtraID kMars31SouthViewNoCard = 8; +static const ExtraID kMars31SouthZoomOutNoCard = 9; +static const ExtraID kMars31SouthZoomViewNoCard = 10; +static const ExtraID kMars33SlideShow1 = 11; +static const ExtraID kMars33SlideShow2 = 12; +static const ExtraID kMars33SlideShow3 = 13; +static const ExtraID kMars33SlideShow4 = 14; +static const ExtraID kMars34SpotOpenWithBar = 15; +static const ExtraID kMars34SpotCloseWithBar = 16; +static const ExtraID kMars34SpotOpenNoBar = 17; +static const ExtraID kMars34SpotCloseNoBar = 18; +static const ExtraID kMars34ViewOpenWithBar = 19; +static const ExtraID kMars34ViewOpenNoBar = 20; +static const ExtraID kMars34NorthPodGreeting = 21; +static const ExtraID kMarsTurnOnPod = 22; +static const ExtraID kMarsTakePodToMars45 = 23; +static const ExtraID kMars35WestSpinAirlockToEast = 24; +static const ExtraID kMars35EastSpinAirlockToWest = 25; +static const ExtraID kMars45SpotOpenWithBar = 26; +static const ExtraID kMars45SpotCloseWithBar = 27; +static const ExtraID kMars45SpotOpenNoBar = 28; +static const ExtraID kMars45SpotCloseNoBar = 29; +static const ExtraID kMars45ViewOpenWithBar = 30; +static const ExtraID kMars45ViewOpenNoBar = 31; +static const ExtraID kMars48RobotApproaches = 32; +static const ExtraID kMars48RobotKillsPlayer = 33; +static const ExtraID kMars48RobotLoops = 34; +static const ExtraID kMars48RobotView = 35; +static const ExtraID kMars48RobotDefends = 36; +static const ExtraID kMars49SouthViewMaskFilling = 37; +static const ExtraID kMars52SpinLeft = 38; +static const ExtraID kMars52SpinRight = 39; +static const ExtraID kMars52Extend = 40; +static const ExtraID kMars53Retract = 41; +static const ExtraID kMars54SpinLeft = 42; +static const ExtraID kMars54SpinRight = 43; +static const ExtraID kMars54Extend = 44; +static const ExtraID kMars55Retract = 45; +static const ExtraID kMars56SpinLeft = 46; +static const ExtraID kMars56SpinRight = 47; +static const ExtraID kMars56ExtendWithBomb = 48; +static const ExtraID kMars56ExtendNoBomb = 49; +static const ExtraID kMars57RetractWithBomb = 50; +static const ExtraID kMars57RetractNoBomb = 51; +static const ExtraID kMars57LowerScreenClosed = 52; +static const ExtraID kMars57CantOpenPanel = 53; +static const ExtraID kMars57FreezeLock = 54; +static const ExtraID kMars57BreakLock = 55; +static const ExtraID kMars57LockFrozenView = 56; +static const ExtraID kMars57ThawLock = 57; +static const ExtraID kMars57OpenPanel = 58; +static const ExtraID kMars57OpenPanelChoices = 59; +static const ExtraID kMars57ShieldEvaluation = 60; +static const ExtraID kMars57MeasureOutput = 61; +static const ExtraID kMars57ShieldOkayLoop = 62; +static const ExtraID kMars57RunDiagnostics = 63; +static const ExtraID kMars57BombExplodes = 64; +static const ExtraID kMars57BombAnalysis = 65; +static const ExtraID kMars57DontLink = 66; +static const ExtraID kMars57CircuitLink = 67; +static const ExtraID kMars57GameLevel1 = 68; +static const ExtraID kMars57GameLevel2 = 69; +static const ExtraID kMars57GameLevel3 = 70; +static const ExtraID kMars57BombExplodesInGame = 71; +static const ExtraID kMars57GameSolved = 72; +static const ExtraID kMars57ExposeBomb = 73; +static const ExtraID kMars57BackToNormal = 74; +static const ExtraID kMars57ViewOpenNoBomb = 75; +static const ExtraID kMars58SpinLeft = 76; +static const ExtraID kMars58SpinRight = 77; +static const ExtraID kMars58Extend = 78; +static const ExtraID kMars59Retract = 79; +static const ExtraID kMars60WestSpinAirlockToEast = 80; +static const ExtraID kMars60EastSpinAirlockToWest = 81; +static const ExtraID kMarsRobotHeadOpen = 82; +static const ExtraID kMarsRobotHeadClose = 83; +static const ExtraID kMarsRobotHead000 = 84; +static const ExtraID kMarsRobotHead001 = 85; +static const ExtraID kMarsRobotHead010 = 86; +static const ExtraID kMarsRobotHead011 = 87; +static const ExtraID kMarsRobotHead100 = 88; +static const ExtraID kMarsRobotHead101 = 89; +static const ExtraID kMarsRobotHead110 = 90; +static const ExtraID kMarsRobotHead111 = 91; +static const ExtraID kMarsMaze007RobotApproach = 92; +static const ExtraID kMarsMaze007RobotLoop = 93; +static const ExtraID kMarsMaze007RobotDeath = 94; +static const ExtraID kMarsMaze015SouthRobotApproach = 95; +static const ExtraID kMarsMaze015SouthRobotLoop = 96; +static const ExtraID kMarsMaze015SouthRobotDeath = 97; +static const ExtraID kMarsMaze101EastRobotApproach = 98; +static const ExtraID kMarsMaze101EastRobotLoop = 99; +static const ExtraID kMarsMaze101EastRobotDeath = 100; +static const ExtraID kMarsMaze104WestLoop = 101; +static const ExtraID kMarsMaze104WestDeath = 102; +static const ExtraID kMarsMaze133SouthApproach = 103; +static const ExtraID kMarsMaze133SouthLoop = 104; +static const ExtraID kMarsMaze133SouthDeath = 105; +static const ExtraID kMarsMaze136NorthApproach = 106; +static const ExtraID kMarsMaze136NorthLoop = 107; +static const ExtraID kMarsMaze136NorthDeath = 108; +static const ExtraID kMarsMaze184WestLoop = 109; +static const ExtraID kMarsMaze184WestDeath = 110; +static const ExtraID kMars200DeathInBucket = 111; + +static const ResIDType kReactorUndoHilitePICTID = 900; + +static const int16 kMars52Compass = 90; +static const int16 kMars54Compass = 180; +static const int16 kMars56Compass = 270; +static const int16 kMars58Compass = 0; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/energybeam.cpp b/engines/pegasus/neighborhood/mars/energybeam.cpp new file mode 100644 index 0000000000..964c8ba381 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/energybeam.cpp @@ -0,0 +1,70 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/mars/energybeam.h" + +namespace Pegasus { + +static const TimeValue kEnergyBeamTime = kOneSecond * kShuttleWeaponScale / 2; + +static const CoordType kEnergyBeamOriginH = kShuttleWindowMidH; +static const CoordType kEnergyBeamOriginV = kShuttleWindowTop + kShuttleWindowHeight; + +static const float kBeamXOrigin = convertScreenHToSpaceX(kEnergyBeamOriginH, kEnergyBeamMinDistance); +static const float kBeamYOrigin = convertScreenVToSpaceY(kEnergyBeamOriginV, kEnergyBeamMinDistance); +static const float kBeamZOrigin = kEnergyBeamMinDistance; + +EnergyBeam::EnergyBeam() { + _weaponDuration = kEnergyBeamTime; + setSegment(0, kEnergyBeamTime); + _weaponOrigin = Point3D(kBeamXOrigin, kBeamYOrigin, kBeamZOrigin); +} + +void EnergyBeam::draw(const Common::Rect &) { + static const int kBeamColorRed1 = 224; + static const int kBeamColorRed2 = 64; + + Graphics::Surface *surface = ((PegasusEngine *)g_engine)->_gfx->getWorkArea(); + + byte red = linearInterp(0, kEnergyBeamTime, _lastTime, kBeamColorRed1, kBeamColorRed2); + uint32 color = surface->format.RGBToColor(red, 0, 0); + + Point3D startPoint; + if (_weaponTime < 0.1) + startPoint = _weaponOrigin; + else + linearInterp(_weaponOrigin, _weaponTarget, _weaponTime - 0.1, startPoint); + + Common::Point lineStart; + project3DTo2D(startPoint, lineStart); + + Common::Point lineEnd; + project3DTo2D(_weaponLocation, lineEnd); + + surface->drawThickLine(lineStart.x, lineStart.y, lineEnd.x, lineEnd.y, 2, 1, color); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/energybeam.h b/engines/pegasus/neighborhood/mars/energybeam.h new file mode 100644 index 0000000000..715ed4b01d --- /dev/null +++ b/engines/pegasus/neighborhood/mars/energybeam.h @@ -0,0 +1,43 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_ENERGYBEAM_H +#define PEGASUS_NEIGHBORHOOD_MARS_ENERGYBEAM_H + +#include "pegasus/neighborhood/mars/shuttleweapon.h" + +namespace Pegasus { + +class EnergyBeam : public ShuttleWeapon { +public: + EnergyBeam(); + virtual ~EnergyBeam() {} + + void draw(const Common::Rect &); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/gravitoncannon.cpp b/engines/pegasus/neighborhood/mars/gravitoncannon.cpp new file mode 100644 index 0000000000..d04b3d08b2 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/gravitoncannon.cpp @@ -0,0 +1,134 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/neighborhood/mars/gravitoncannon.h" +#include "pegasus/neighborhood/mars/robotship.h" +#include "pegasus/neighborhood/mars/spacejunk.h" + +namespace Pegasus { + +static const TimeValue kGravitonTime = kOneSecond * kShuttleWeaponScale; + +static const CoordType kGravitonOriginH = kShuttleWindowLeft - 1; +static const CoordType kGravitonOriginV = kShuttleWindowMidV; + +static const float kGravitonXOrigin = convertScreenHToSpaceX(kGravitonOriginH, kGravitonMinDistance); +static const float kGravitonYOrigin = convertScreenVToSpaceY(kGravitonOriginV, kGravitonMinDistance); +static const float kGravitonZOrigin = kGravitonMinDistance; + +// Width of graviton sprite... +static const CoordType kGravitonMaxScreenWidth = 78; +static const CoordType kGravitonMaxScreenHeight = 46; + +static const float kGravitonWidth = convertScreenHToSpaceX(kShuttleWindowMidH + kGravitonMaxScreenWidth / 2, kGravitonMinDistance) + - convertScreenHToSpaceX(kShuttleWindowMidH - kGravitonMaxScreenWidth / 2, kGravitonMinDistance); +static const float kGravitonHeight = convertScreenVToSpaceY(kShuttleWindowMidV - kGravitonMaxScreenHeight / 2, kGravitonMinDistance) + - convertScreenVToSpaceY(kShuttleWindowMidV + kGravitonMaxScreenHeight / 2, kGravitonMinDistance); + +GravitonCannon::GravitonCannon() { + _weaponDuration = kGravitonTime; + setSegment(0, kGravitonTime); + _weaponOrigin = Point3D(kGravitonXOrigin, kGravitonYOrigin, kGravitonZOrigin); + _rightOrigin = Point3D(-kGravitonXOrigin, kGravitonYOrigin, kGravitonZOrigin); +} + +void GravitonCannon::initShuttleWeapon() { + ShuttleWeapon::initShuttleWeapon(); + _gravitonImage.getImageFromPICTFile("Images/Mars/Graviton Cannon"); + _gravitonImage.getSurfaceBounds(_gravitonBounds); +} + +void GravitonCannon::cleanUpShuttleWeapon() { + _gravitonImage.deallocateSurface(); + ShuttleWeapon::cleanUpShuttleWeapon(); +} + +void GravitonCannon::draw(const Common::Rect &) { + // Left graviton... + Point3D pt3D = _weaponLocation; + pt3D.translate(-kGravitonWidth / 2, kGravitonHeight / 2, 0); + Common::Point pt2D; + project3DTo2D(pt3D, pt2D); + Common::Rect gravitonRect; + gravitonRect.left = pt2D.x; + gravitonRect.top = pt2D.y; + + pt3D.translate(kGravitonWidth, -kGravitonHeight, 0); + project3DTo2D(pt3D, pt2D); + gravitonRect.right = pt2D.x; + gravitonRect.bottom = pt2D.y; + + _gravitonImage.scaleTransparentCopy(_gravitonBounds, gravitonRect); + + // Right graviton... + pt3D = _rightLocation; + pt3D.translate(-kGravitonWidth / 2, kGravitonHeight / 2, 0); + project3DTo2D(pt3D, pt2D); + gravitonRect.left = pt2D.x; + gravitonRect.top = pt2D.y; + + pt3D.translate(kGravitonWidth, -kGravitonHeight, 0); + project3DTo2D(pt3D, pt2D); + gravitonRect.right = pt2D.x; + gravitonRect.bottom = pt2D.y; + + _gravitonImage.scaleTransparentCopy(_gravitonBounds, gravitonRect); +} + +void GravitonCannon::updateWeaponPosition() { + ShuttleWeapon::updateWeaponPosition(); + if (_weaponTime != 1.0) + linearInterp(_rightOrigin, _weaponTarget, _weaponTime, _rightLocation); +} + +bool GravitonCannon::collisionWithJunk(Common::Point &impactPoint) { + if (getDisplayOrder() == kShuttleWeaponFrontOrder) { + Point3D junkPosition; + g_spaceJunk->getJunkPosition(junkPosition); + + if (junkPosition.z < _weaponLocation.z) { + setDisplayOrder(kShuttleWeaponBackOrder); + project3DTo2D(_weaponLocation, impactPoint); + + if (g_spaceJunk->pointInJunk(impactPoint)) + return true; + + project3DTo2D(_rightLocation, impactPoint); + return g_spaceJunk->pointInJunk(impactPoint); + } + } + + return false; +} + +void GravitonCannon::hitJunk(Common::Point impactPoint) { + g_spaceJunk->hitByGravitonCannon(impactPoint); +} + +void GravitonCannon::hitShuttle(Common::Point impactPoint) { + g_robotShip->hitByGravitonCannon(impactPoint); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/gravitoncannon.h b/engines/pegasus/neighborhood/mars/gravitoncannon.h new file mode 100644 index 0000000000..b94fd55e5b --- /dev/null +++ b/engines/pegasus/neighborhood/mars/gravitoncannon.h @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_GRAVITONCANNON_H +#define PEGASUS_NEIGHBORHOOD_MARS_GRAVITONCANNON_H + +#include "pegasus/surface.h" +#include "pegasus/neighborhood/mars/shuttleweapon.h" + +namespace Pegasus { + +class GravitonCannon : public ShuttleWeapon { +public: + GravitonCannon(); + virtual ~GravitonCannon() {} + + void initShuttleWeapon(); + void cleanUpShuttleWeapon(); + + void draw(const Common::Rect &); + +protected: + virtual void updateWeaponPosition(); + virtual bool collisionWithJunk(Common::Point &impactPoint); + virtual void hitJunk(Common::Point impactPoint); + virtual void hitShuttle(Common::Point impactPoint); + + Surface _gravitonImage; + Common::Rect _gravitonBounds; + Point3D _rightOrigin, _rightLocation; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/hermite.cpp b/engines/pegasus/neighborhood/mars/hermite.cpp new file mode 100644 index 0000000000..7f631b369d --- /dev/null +++ b/engines/pegasus/neighborhood/mars/hermite.cpp @@ -0,0 +1,76 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/neighborhood/mars/hermite.h" + +namespace Pegasus { + +CoordType hermite(CoordType p1, CoordType p4, CoordType r1, CoordType r4, int32 time, int32 duration) { + float t = (float)time / duration; + float tsq = t * t; + float tcu = t * tsq; + float tcu2 = tcu + tcu; + float tsq2 = tsq + tsq; + float tsq3 = tsq2 + tsq; + return (CoordType)((tcu2 - tsq3 + 1) * p1 + (tsq3 - tcu2) * p4 + (tcu - tsq2 + t) * r1 + (tcu - tsq) * r4); +} + +CoordType dHermite(CoordType p1, CoordType p4, CoordType r1, CoordType r4, int32 time, int32 duration) { + float t = (float)time / duration; + float t2 = t + t; + float t4 = t2 + t2; + float t6 = t4 + t2; + float tsq = t * t; + float tsq3 = tsq + tsq + tsq; + float tsq6 = tsq3 + tsq3; + return (CoordType)((tsq6 - t6) * p1 + (t6 - tsq6) * p4 + (tsq3 - t4 + 1) * r1 + (tsq3 - t2) * r4); +} + +void hermite(Common::Point p1, Common::Point p4, Common::Point r1, Common::Point r4, int32 time, int32 duration, Common::Point &result) { + float t = (float)time / duration; + float tsq = t * t; + float tcu = t * tsq; + float tcu2 = tcu + tcu; + float tsq2 = tsq + tsq; + float tsq3 = tsq2 + tsq; + + result.x = (int16)((tcu2 - tsq3 + 1) * p1.x + (tsq3 - tcu2) * p4.x + (tcu - tsq2 + t) * r1.x + (tcu - tsq) * r4.x); + result.y = (int16)((tcu2 - tsq3 + 1) * p1.y + (tsq3 - tcu2) * p4.y + (tcu - tsq2 + t) * r1.y + (tcu - tsq) * r4.y); +} + +void dHermite(Common::Point p1, Common::Point p4, Common::Point r1, Common::Point r4, int32 time, int32 duration, Common::Point &result) { + float t = (float)time / duration; + float t2 = t + t; + float t4 = t2 + t2; + float t6 = t4 + t2; + float tsq = t * t; + float tsq3 = tsq + tsq + tsq; + float tsq6 = tsq3 + tsq3; + + result.x = (int16)((tsq6 - t6) * p1.x + (t6 - tsq6) * p4.x + (tsq3 - t4 + 1) * r1.x + (tsq3 - t2) * r4.x); + result.y = (int16)((tsq6 - t6) * p1.y + (t6 - tsq6) * p4.y + (tsq3 - t4 + 1) * r1.y + (tsq3 - t2) * r4.y); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/hermite.h b/engines/pegasus/neighborhood/mars/hermite.h new file mode 100644 index 0000000000..44cb3a5a11 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/hermite.h @@ -0,0 +1,41 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_HERMITE_H +#define PEGASUS_NEIGHBORHOOD_MARS_HERMITE_H + +#include "common/rect.h" +#include "pegasus/types.h" + +namespace Pegasus { + +CoordType hermite(CoordType p1, CoordType p4, CoordType r1, CoordType r4, int32 t, int32 duration); +CoordType dHermite(CoordType p1, CoordType p4, CoordType r1, CoordType r4, int32 t, int32 duration); +void hermite(Common::Point p1, Common::Point p4, Common::Point r1, Common::Point r4, int32 t, int32 duration, Common::Point &result); +void dHermite(Common::Point p1, Common::Point p4, Common::Point r1, Common::Point r4, int32 t, int32 duration, Common::Point &result); + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/mars.cpp b/engines/pegasus/neighborhood/mars/mars.cpp new file mode 100644 index 0000000000..34c9e3d0f8 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/mars.cpp @@ -0,0 +1,3735 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/events.h" +#include "video/qt_decoder.h" + +#include "pegasus/cursor.h" +#include "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/opticalchip.h" +#include "pegasus/items/biochips/shieldchip.h" +#include "pegasus/items/inventory/airmask.h" +#include "pegasus/neighborhood/mars/mars.h" + +namespace Pegasus { + +// This should really be 22.5. +// Probably no one will know the difference. +static const int16 kMarsShieldPanelOffsetAngle = 22; + +static const CanMoveForwardReason kCantMoveRobotBlocking = kCantMoveLastReason + 1; + +static const NotificationFlags kTimeForCanyonChaseFlag = kLastNeighborhoodNotificationFlag << 1; +static const NotificationFlags kExplosionFinishedFlag = kTimeForCanyonChaseFlag << 1; +static const NotificationFlags kTimeToTransportFlag = kExplosionFinishedFlag << 1; + +static const NotificationFlags kMarsNotificationFlags = kTimeForCanyonChaseFlag | + kExplosionFinishedFlag | + kTimeToTransportFlag; + +static const TimeValue kLittleExplosionStart = 0 * 40; +static const TimeValue kLittleExplosionStop = 24 * 40; + +static const TimeValue kBigExplosionStart = 24 * 40; +static const TimeValue kBigExplosionStop = 62 * 40; + +enum { + kMaze007RobotLoopingEvent, + kMaze015RobotLoopingEvent, + kMaze101RobotLoopingEvent, + kMaze104RobotLoopingEvent, + kMaze133RobotLoopingEvent, + kMaze136RobotLoopingEvent, + kMaze184RobotLoopingEvent +}; + +enum { + kMaze007RobotLoopingTime = (64 + 96) * kMarsFrameDuration, + kMaze015RobotLoopingTime = (64 + 93) * kMarsFrameDuration, + kMaze101RobotLoopingTime = (64 + 45) * kMarsFrameDuration, + kMaze104RobotLoopingTime = 96 * kMarsFrameDuration, + kMaze133RobotLoopingTime = (64 + 96) * kMarsFrameDuration, + kMaze136RobotLoopingTime = (64 + 96) * kMarsFrameDuration, + kMaze184RobotLoopingTime = 96 * kMarsFrameDuration +}; + +// I've made a couple macros for these rects so we don't +// have to globally construct them or whatnot +#define kShuttleEnergyBeamBounds Common::Rect(24, 27, 24 + 112, 27 + 46) +#define kShuttleGravitonBounds Common::Rect(24, 73, 24 + 112, 73 + 30) +#define kShuttleTractorBounds Common::Rect(24, 103, 24 + 112, 103 + 30) +#define kShuttleTransportBounds Common::Rect(484, 353, 89 + 484, 79 + 353) + +void MarsTimerEvent::fire() { + mars->marsTimerExpired(*this); +} + +Mars::Mars(InputHandler *nextHandler, PegasusEngine *owner) : Neighborhood(nextHandler, owner, "Mars", kMarsID), + _guessObject(kNoDisplayElement), _undoPict(kNoDisplayElement), _guessHistory(kNoDisplayElement), + _choiceHighlight(kNoDisplayElement), _shuttleInterface1(kNoDisplayElement), _shuttleInterface2(kNoDisplayElement), + _shuttleInterface3(kNoDisplayElement), _shuttleInterface4(kNoDisplayElement), _canyonChaseMovie(kNoDisplayElement), + _leftShuttleMovie(kNoDisplayElement), _rightShuttleMovie(kNoDisplayElement), _lowerLeftShuttleMovie(kNoDisplayElement), + _lowerRightShuttleMovie(kNoDisplayElement), _centerShuttleMovie(kNoDisplayElement), + _upperLeftShuttleMovie(kNoDisplayElement), _upperRightShuttleMovie(kNoDisplayElement), + _leftDamageShuttleMovie(kNoDisplayElement), _rightDamageShuttleMovie(kNoDisplayElement), _explosions(kNoDisplayElement), + _planetMovie(kNoDisplayElement), _junk(kNoDisplayElement), _energyChoiceSpot(kShuttleEnergySpotID), + _gravitonChoiceSpot(kShuttleGravitonSpotID), _tractorChoiceSpot(kShuttleTractorSpotID), + _shuttleViewSpot(kShuttleViewSpotID), _shuttleTransportSpot(kShuttleTransportSpotID) { + _noAirFuse.setFunctor(new Common::Functor0Mem<void, Mars>(this, &Mars::airStageExpired)); + setIsItemTaken(kMarsCard); + setIsItemTaken(kAirMask); + setIsItemTaken(kCrowbar); + setIsItemTaken(kCardBomb); +} + +Mars::~Mars() { + _vm->getAllHotspots().remove(&_energyChoiceSpot); + _vm->getAllHotspots().remove(&_gravitonChoiceSpot); + _vm->getAllHotspots().remove(&_tractorChoiceSpot); + _vm->getAllHotspots().remove(&_shuttleViewSpot); + _vm->getAllHotspots().remove(&_shuttleTransportSpot); +} + +void Mars::init() { + Neighborhood::init(); + + Hotspot *attackSpot = _vm->getAllHotspots().findHotspotByID(kAttackRobotHotSpotID); + attackSpot->setMaskedHotspotFlags(kDropItemSpotFlag, kDropItemSpotFlag); + _attackingItem = NULL; + + forceStridingStop(kMars08, kNorth, kAltMarsNormal); + + _neighborhoodNotification.notifyMe(this, kMarsNotificationFlags, kMarsNotificationFlags); + + _explosionCallBack.setNotification(&_neighborhoodNotification); + _explosionCallBack.setCallBackFlag(kExplosionFinishedFlag); + + _weaponSelection = kNoWeapon; +} + +void Mars::flushGameState() { + g_energyMonitor->saveCurrentEnergyValue(); +} + +void Mars::start() { + g_energyMonitor->stopEnergyDraining(); + g_energyMonitor->restoreLastEnergyValue(); + _vm->resetEnergyDeathReason(); + g_energyMonitor->startEnergyDraining(); + Neighborhood::start(); +} + +class AirMaskCondition : public AICondition { +public: + AirMaskCondition(const uint32); + + virtual bool fireCondition(); + +protected: + uint32 _airThreshold; + uint32 _lastAirLevel; +}; + +AirMaskCondition::AirMaskCondition(const uint32 airThreshold) { + _airThreshold = airThreshold; + _lastAirLevel = g_airMask->getAirLeft(); +} + +bool AirMaskCondition::fireCondition() { + bool result = g_airMask && g_airMask->isAirMaskOn() && + g_airMask->getAirLeft() <= _airThreshold && _lastAirLevel > _airThreshold; + + _lastAirLevel = g_airMask->getAirLeft(); + return result; +} + +void Mars::setUpAIRules() { + Neighborhood::setUpAIRules(); + + // Don't add these rules if we're going to the robot's shuttle... + if (g_AIArea && !GameState.getMarsReadyForShuttleTransport()) { + AIPlayMessageAction *messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB1E", false); + AILocationCondition *locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kMars47, kSouth)); + AIRule *rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Mars/XM27NB", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kMars27, kNorth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Mars/XM27NB", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kMars28, kNorth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Mars/XM41ED", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kMars19, kEast)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + AIDeactivateRuleAction *deactivate = new AIDeactivateRuleAction(rule); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kMars35, kWest)); + rule = new AIRule(locCondition, deactivate); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Mars/XM41ED", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kMars48, kWest)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + AirMaskCondition *airMask50Condition = new AirMaskCondition(50); + messageAction = new AIPlayMessageAction("Images/AI/Mars/XMMAZB1", false); + AIRule *rule50 = new AIRule(airMask50Condition, messageAction); + + AirMaskCondition *airMask25Condition = new AirMaskCondition(25); + AICompoundAction *compound = new AICompoundAction(); + messageAction = new AIPlayMessageAction("Images/AI/Mars/XMMAZB2", false); + compound->addAction(messageAction); + deactivate = new AIDeactivateRuleAction(rule50); + compound->addAction(deactivate); + AIRule *rule25 = new AIRule(airMask25Condition, compound); + + AirMaskCondition *airMask5Condition = new AirMaskCondition(5); + compound = new AICompoundAction; + messageAction = new AIPlayMessageAction("Images/AI/Mars/XMMAZB3", false); + compound->addAction(messageAction); + deactivate = new AIDeactivateRuleAction(rule50); + compound->addAction(deactivate); + deactivate = new AIDeactivateRuleAction(rule25); + compound->addAction(deactivate); + AIRule *rule5 = new AIRule(airMask5Condition, compound); + + g_AIArea->addAIRule(rule5); + g_AIArea->addAIRule(rule25); + g_AIArea->addAIRule(rule50); + + messageAction = new AIPlayMessageAction("Images/AI/Mars/XM51ND", false); + AIDoorOpenedCondition *doorOpen = new AIDoorOpenedCondition(MakeRoomView(kMars51, kEast)); + rule = new AIRule(doorOpen, messageAction); + g_AIArea->addAIRule(rule); + } +} + +uint16 Mars::getDateResID() const { + return kDate2185ID; +} + +TimeValue Mars::getViewTime(const RoomID room, const DirectionConstant direction) { + ExtraTable::Entry extra; + SpotTable::Entry spotEntry; + uint32 extraID = 0xffffffff; + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kMars0A, kNorth): + if (!GameState.getMarsSeenTimeStream()) { + getExtraEntry(kMarsArrivalFromTSA, extra); + return extra.movieStart; + } + break; + case MakeRoomView(kMars31South, kSouth): + if (GameState.isTakenItemID(kMarsCard)) + extraID = kMars31SouthZoomViewNoCard; + break; + case MakeRoomView(kMars31, kSouth): + if (GameState.isTakenItemID(kMarsCard)) + extraID = kMars31SouthViewNoCard; + break; + case MakeRoomView(kMars34, kSouth): + if (_privateFlags.getFlag(kMarsPrivatePodStorageOpenFlag)) { + if (GameState.isTakenItemID(kCrowbar)) + extraID = kMars34ViewOpenNoBar; + else + extraID = kMars34ViewOpenWithBar; + } + break; + case MakeRoomView(kMars36, kSouth): + case MakeRoomView(kMars37, kSouth): + case MakeRoomView(kMars38, kSouth): + findSpotEntry(room, direction, kSpotOnTurnMask | kSpotLoopsMask, spotEntry); + return spotEntry.movieStart; + case MakeRoomView(kMars45, kNorth): + if (_privateFlags.getFlag(kMarsPrivatePodStorageOpenFlag)) { + if (GameState.isTakenItemID(kCrowbar)) + extraID = kMars45ViewOpenNoBar; + else + extraID = kMars45ViewOpenWithBar; + } + break; + case MakeRoomView(kMars48, kEast): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) + extraID = kMars48RobotView; + break; + case MakeRoomView(kMars56, kEast): + if (_privateFlags.getFlag(kMarsPrivateBombExposedFlag)) { + if (_privateFlags.getFlag(kMarsPrivateDraggingBombFlag)) + extraID = kMars57ViewOpenNoBomb; + else + extraID = kMars57ExposeBomb; + } else if (GameState.getMarsLockBroken()) { + extraID = kMars57OpenPanelChoices; + } else if (GameState.getMarsLockFrozen()) { + extraID = kMars57LockFrozenView; + } + break; + case MakeRoomView(kMarsRobotShuttle, kEast): + if (getCurrentActivation() == kActivationRobotHeadOpen) { + extraID = kMarsRobotHead111; + + if (_privateFlags.getFlag(kMarsPrivateGotMapChipFlag)) + extraID -= 1; + if (_privateFlags.getFlag(kMarsPrivateGotOpticalChipFlag)) + extraID -= 2; + if (_privateFlags.getFlag(kMarsPrivateGotShieldChipFlag)) + extraID -= 4; + } + break; + } + + if (extraID == 0xffffffff) + return Neighborhood::getViewTime(room, direction); + + getExtraEntry(extraID, extra); + return extra.movieEnd - 1; +} + +void Mars::getZoomEntry(const HotSpotID spotID, ZoomTable::Entry &entry) { + Neighborhood::getZoomEntry(spotID, entry); + + uint32 extraID = 0xffffffff; + + switch (spotID) { + case kMars31SouthSpotID: + if (GameState.getCurrentDirection() == kSouth && GameState.isTakenItemID(kMarsCard)) + extraID = kMars31SouthZoomInNoCard; + break; + case kMars31SouthOutSpotID: + if (GameState.getCurrentDirection() == kSouth && GameState.isTakenItemID(kMarsCard)) + extraID = kMars31SouthZoomOutNoCard; + break; + } + + if (extraID != 0xffffffff) { + ExtraTable::Entry extra; + getExtraEntry(extraID, extra); + entry.movieStart = extra.movieStart; + entry.movieEnd = extra.movieEnd; + } +} + +void Mars::findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &entry) { + Neighborhood::findSpotEntry(room, direction, flags, entry); + + if ((flags & (kSpotOnArrivalMask | kSpotOnTurnMask)) != 0) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars27, kNorth): + if (GameState.getMarsSeenThermalScan()) + entry.clear(); + else + GameState.setMarsSeenThermalScan(true); + break; + case MakeRoomView(kMars28, kNorth): + if (GameState.getMarsSeenThermalScan()) + entry.clear(); + else + GameState.setMarsSeenThermalScan(true); + break; + } + } +} + +CanMoveForwardReason Mars::canMoveForward(ExitTable::Entry &entry) { + CanMoveForwardReason reason = Neighborhood::canMoveForward(entry); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars48, kEast): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) + reason = kCantMoveRobotBlocking; + break; + case MakeRoomView(kMars48, kSouth): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) + _utilityFuse.stopFuse(); + break; + } + + return reason; +} + +void Mars::cantMoveThatWay(CanMoveForwardReason reason) { + if (reason == kCantMoveRobotBlocking) { + startExtraSequence(kMars48RobotKillsPlayer, kExtraCompletedFlag, kFilterNoInput); + loadLoopSound2(""); + } else { + Neighborhood::cantMoveThatWay(reason); + } +} + +void Mars::moveForward() { + if (GameState.getCurrentRoom() == kMars02 || (GameState.getCurrentRoom() >= kMars05 && GameState.getCurrentRoom() <= kMars08)) + loadLoopSound2(""); + + Neighborhood::moveForward(); +} + +void Mars::bumpIntoWall() { + requestSpotSound(kMarsBumpIntoWallIn, kMarsBumpIntoWallOut, kFilterNoInput, 0); + Neighborhood::bumpIntoWall(); +} + +CanOpenDoorReason Mars::canOpenDoor(DoorTable::Entry &entry) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars05, kEast): + case MakeRoomView(kMars06, kEast): + case MakeRoomView(kMars07, kEast): + if (!GameState.getMarsSecurityDown()) + return kCantOpenLocked; + break; + case MakeRoomView(kMarsMaze037, kWest): + case MakeRoomView(kMarsMaze038, kEast): + if (GameState.getMarsMazeDoorPair1()) + return kCantOpenLocked; + break; + case MakeRoomView(kMarsMaze050, kNorth): + case MakeRoomView(kMarsMaze058, kSouth): + if (!GameState.getMarsMazeDoorPair1()) + return kCantOpenLocked; + break; + case MakeRoomView(kMarsMaze047, kNorth): + case MakeRoomView(kMarsMaze142, kSouth): + if (GameState.getMarsMazeDoorPair2()) + return kCantOpenLocked; + break; + case MakeRoomView(kMarsMaze057, kNorth): + case MakeRoomView(kMarsMaze136, kSouth): + if (!GameState.getMarsMazeDoorPair2()) + return kCantOpenLocked; + break; + case MakeRoomView(kMarsMaze120, kWest): + case MakeRoomView(kMarsMaze121, kEast): + if (GameState.getMarsMazeDoorPair3()) + return kCantOpenLocked; + break; + case MakeRoomView(kMarsMaze081, kNorth): + case MakeRoomView(kMarsMaze083, kSouth): + if (!GameState.getMarsMazeDoorPair3()) + return kCantOpenLocked; + break; + } + + return Neighborhood::canOpenDoor(entry); +} + +void Mars::cantOpenDoor(CanOpenDoorReason reason) { + switch (GameState.getCurrentRoom()) { + case kMars05: + case kMars06: + case kMars07: + playSpotSoundSync(kMarsCantOpenShuttleIn, kMarsCantOpenShuttleOut); + break; + default: + Neighborhood::cantOpenDoor(reason); + break; + } +} + +void Mars::openDoor() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars06, kEast): + case MakeRoomView(kMars07, kEast): + if (GameState.getMarsSecurityDown()) + playSpotSoundSync(kMarsNoShuttleIn, kMarsNoShuttleOut); + break; + case MakeRoomView(kMars47, kSouth): + if (GameState.isTakenItemID(kAirMask)) + setCurrentAlternate(kAltMarsTookMask); + else + setCurrentAlternate(kAltMarsNormal); + break; + case MakeRoomView(kMars48, kNorth): + if (GameState.getMarsPodAtUpperPlatform()) + setCurrentAlternate(kAltMarsNormal); + else + setCurrentAlternate(kAltMarsPodAtMars45); + break; + case MakeRoomView(kMars48, kEast): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) { + die(kDeathDidntGetOutOfWay); + return; + } + break; + } + + Neighborhood::openDoor(); +} + +void Mars::doorOpened() { + switch (GameState.getCurrentRoom()) { + case kMars27: + case kMars28: + if (GameState.getCurrentDirection() == kNorth) + _vm->die(kDeathArrestedInMars); + else + Neighborhood::doorOpened(); + break; + case kMars41: + case kMars42: + if (GameState.getCurrentDirection() == kEast) + _vm->die(kDeathWrongShuttleLock); + else + Neighborhood::doorOpened(); + break; + case kMars51: + Neighborhood::doorOpened(); + setUpReactorEnergyDrain(); + + if (g_AIArea) + g_AIArea->checkRules(); + break; + case kMars19: + if (GameState.getCurrentDirection() == kEast) + GameState.setMarsAirlockOpen(true); + + Neighborhood::doorOpened(); + break; + case kMars48: + if (GameState.getCurrentDirection() == kWest) + GameState.setMarsAirlockOpen(true); + + Neighborhood::doorOpened(); + break; + default: + Neighborhood::doorOpened(); + break; + } +} + +void Mars::setUpReactorEnergyDrain() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars51, kEast): + if (GameState.isCurrentDoorOpen()) { + if (g_energyMonitor->getEnergyDrainRate() == kEnergyDrainNormal) { + if (GameState.getShieldOn()) { + g_shield->setItemState(kShieldRadiation); + g_energyMonitor->setEnergyDrainRate(kMarsReactorEnergyDrainWithShield); + } else { + g_energyMonitor->setEnergyDrainRate(kMarsReactorEnergyDrainNoShield); + } + _vm->setEnergyDeathReason(kDeathReactorBurn); + } + } else { + if (g_energyMonitor->getEnergyDrainRate() != kEnergyDrainNormal) { + if (GameState.getShieldOn()) + g_shield->setItemState(kShieldNormal); + g_energyMonitor->setEnergyDrainRate(kEnergyDrainNormal); + _vm->resetEnergyDeathReason(); + } + } + break; + case MakeRoomView(kMars52, kNorth): + case MakeRoomView(kMars52, kSouth): + case MakeRoomView(kMars52, kEast): + case MakeRoomView(kMars52, kWest): + case MakeRoomView(kMars54, kNorth): + case MakeRoomView(kMars54, kSouth): + case MakeRoomView(kMars54, kEast): + case MakeRoomView(kMars54, kWest): + case MakeRoomView(kMars56, kNorth): + case MakeRoomView(kMars56, kSouth): + case MakeRoomView(kMars56, kEast): + case MakeRoomView(kMars56, kWest): + case MakeRoomView(kMars58, kNorth): + case MakeRoomView(kMars58, kSouth): + case MakeRoomView(kMars58, kEast): + case MakeRoomView(kMars58, kWest): + if (g_energyMonitor->getEnergyDrainRate() == kEnergyDrainNormal) { + if (GameState.getShieldOn()) { + g_shield->setItemState(kShieldRadiation); + g_energyMonitor->setEnergyDrainRate(kMarsReactorEnergyDrainWithShield); + } else { + g_energyMonitor->setEnergyDrainRate(kMarsReactorEnergyDrainNoShield); + } + _vm->setEnergyDeathReason(kDeathReactorBurn); + } + break; + default: + if (g_energyMonitor->getEnergyDrainRate() != kEnergyDrainNormal) { + if (GameState.getShieldOn()) + g_shield->setItemState(kShieldNormal); + g_energyMonitor->setEnergyDrainRate(kEnergyDrainNormal); + _vm->resetEnergyDeathReason(); + } + break; + } +} + +void Mars::closeDoorOffScreen(const RoomID room, const DirectionConstant direction) { + switch (room) { + case kMars51: + playSpotSoundSync(kMarsGantryDoorCloseIn, kMarsGantryDoorCloseOut); + if (GameState.getShieldOn()) + g_shield->setItemState(kShieldNormal); + g_energyMonitor->setEnergyDrainRate(kEnergyDrainNormal); + _vm->resetEnergyDeathReason(); + break; + case kMars05: + case kMars06: + case kMars07: + case kMars13: + case kMars22: + case kMars47: + case kMars52: + playSpotSoundSync(kMarsGantryDoorCloseIn, kMarsGantryDoorCloseOut); + break; + case kMars18: + case kMars32: + playSpotSoundSync(kMarsTransportDoorCloseIn, kMarsTransportDoorCloseOut); + break; + case kMars19: + if (GameState.getCurrentRoom() != kMars35) { + playSpotSoundSync(kMarsBigAirlockDoorCloseIn, kMarsBigAirlockDoorCloseOut); + GameState.setMarsAirlockOpen(false); + } + break; + case kMars36: + if (GameState.getCurrentRoom() != kMars35) + playSpotSoundSync(kMarsSmallAirlockDoorCloseIn, kMarsSmallAirlockDoorCloseOut); + break; + case kMars48: + if (direction == kWest) { + if (GameState.getCurrentRoom() != kMars60) { + playSpotSoundSync(kMarsSmallAirlockDoorCloseIn, kMarsSmallAirlockDoorCloseOut); + GameState.setMarsAirlockOpen(false); + } + } else { + playSpotSoundSync(kMarsGantryDoorCloseIn, kMarsGantryDoorCloseOut); + } + break; + case kMars41: + case kMars42: + case kMars43: + if (direction == kWest) + playSpotSoundSync(kMarsGantryDoorCloseIn, kMarsGantryDoorCloseOut); + break; + case kMarsMaze037: + case kMarsMaze038: + case kMarsMaze012: + case kMarsMaze066: + case kMarsMaze050: + case kMarsMaze058: + case kMarsMaze057: + case kMarsMaze136: + case kMarsMaze047: + case kMarsMaze142: + case kMarsMaze133: + case kMarsMaze132: + case kMarsMaze113: + case kMarsMaze114: + case kMarsMaze120: + case kMarsMaze121: + case kMarsMaze081: + case kMarsMaze083: + case kMarsMaze088: + case kMarsMaze089: + case kMarsMaze179: + case kMarsMaze180: + playSpotSoundSync(kMarsMazeDoorCloseIn, kMarsMazeDoorCloseOut); + break; + } +} + +void Mars::checkAirlockDoors() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars19, kWest): + case MakeRoomView(kMars18, kWest): + case MakeRoomView(kMars17, kWest): + case MakeRoomView(kMars16, kWest): + case MakeRoomView(kMars15, kWest): + case MakeRoomView(kMars14, kWest): + case MakeRoomView(kMars12, kWest): + case MakeRoomView(kMars11, kWest): + case MakeRoomView(kMars10, kWest): + if (GameState.getMarsInAirlock()) { + playSpotSoundSync(kMarsBigAirlockDoorCloseIn, kMarsBigAirlockDoorCloseOut); + GameState.setMarsInAirlock(false); + } + break; + case MakeRoomView(kMars36, kEast): + case MakeRoomView(kMars37, kEast): + case MakeRoomView(kMars38, kEast): + case MakeRoomView(kMars39, kEast): + case MakeRoomView(kMars48, kEast): + case MakeRoomView(kMars50, kEast): + case MakeRoomView(kMars51, kEast): + case MakeRoomView(kMars52, kEast): + if (GameState.getMarsInAirlock()) { + playSpotSoundSync(kMarsSmallAirlockDoorCloseIn, kMarsSmallAirlockDoorCloseOut); + GameState.setMarsInAirlock(false); + } + break; + case MakeRoomView(kMars35, kWest): + case MakeRoomView(kMars35, kEast): + case MakeRoomView(kMars60, kWest): + case MakeRoomView(kMars60, kEast): + GameState.setMarsInAirlock(true); + break; + default: + GameState.setMarsInAirlock(false); + break; + } +} + +int16 Mars::getStaticCompassAngle(const RoomID room, const DirectionConstant dir) { + int16 angle = Neighborhood::getStaticCompassAngle(room, dir); + + switch (MakeRoomView(room, dir)) { + case MakeRoomView(kMars0A, kNorth): + angle -= 20; + break; + case MakeRoomView(kMars23, kNorth): + case MakeRoomView(kMars23, kSouth): + case MakeRoomView(kMars23, kEast): + case MakeRoomView(kMars23, kWest): + case MakeRoomView(kMars26, kNorth): + case MakeRoomView(kMars26, kSouth): + case MakeRoomView(kMars26, kEast): + case MakeRoomView(kMars26, kWest): + angle += 30; + break; + case MakeRoomView(kMars24, kNorth): + case MakeRoomView(kMars24, kSouth): + case MakeRoomView(kMars24, kEast): + case MakeRoomView(kMars24, kWest): + case MakeRoomView(kMars25, kNorth): + case MakeRoomView(kMars25, kSouth): + case MakeRoomView(kMars25, kEast): + case MakeRoomView(kMars25, kWest): + angle -= 30; + break; + case MakeRoomView(kMars54, kNorth): + case MakeRoomView(kMars54, kSouth): + case MakeRoomView(kMars54, kEast): + case MakeRoomView(kMars54, kWest): + angle += 90; + break; + case MakeRoomView(kMars56, kNorth): + case MakeRoomView(kMars56, kSouth): + case MakeRoomView(kMars56, kEast): + case MakeRoomView(kMars56, kWest): + angle += 180; + break; + case MakeRoomView(kMars58, kNorth): + case MakeRoomView(kMars58, kSouth): + case MakeRoomView(kMars58, kEast): + case MakeRoomView(kMars58, kWest): + angle -= 90; + break; + } + + return angle; +} + +void Mars::getExitCompassMove(const ExitTable::Entry &exitEntry, FaderMoveSpec &compassMove) { + Neighborhood::getExitCompassMove(exitEntry, compassMove); + + if (exitEntry.room == kMars43 && exitEntry.direction == kEast) { + compassMove.insertFaderKnot(exitEntry.movieStart + 16 * kMarsFrameDuration, 90); + compassMove.insertFaderKnot(exitEntry.movieStart + 32 * kMarsFrameDuration, 270); + } else if (exitEntry.room == kMars46 && exitEntry.direction == kWest && exitEntry.altCode != kAltMarsPodAtMars45) { + compassMove.makeTwoKnotFaderSpec(kMarsMovieScale, exitEntry.movieStart, 270, exitEntry.movieEnd, 360); + compassMove.insertFaderKnot(exitEntry.movieStart + 43 * kMarsFrameDuration, 270); + compassMove.insertFaderKnot(exitEntry.movieStart + 58 * kMarsFrameDuration, 360); + } +} + +void Mars::getExtraCompassMove(const ExtraTable::Entry &entry, FaderMoveSpec &compassMove) { + switch (entry.extra) { + case kMarsTakePodToMars45: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, 0, entry.movieEnd, 180); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 3), 30); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 11), 10); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 14), 40); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 16), 30); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 23), 100); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 31), 70); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 34), 100); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 37), 85); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 42), 135); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 44), 125); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 46), 145); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 49), 160); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * (kMarsFramesPerSecond * 51), 180); + break; + case kMars35WestSpinAirlockToEast: + case kMars60WestSpinAirlockToEast: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, 90, entry.movieEnd, 270); + compassMove.insertFaderKnot(entry.movieStart + kMarsMovieScale, 90); + compassMove.insertFaderKnot(entry.movieStart + kMarsMovieScale * 3, 270); + break; + case kMars35EastSpinAirlockToWest: + case kMars60EastSpinAirlockToWest: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, 270, entry.movieEnd, 90); + compassMove.insertFaderKnot(entry.movieStart + kMarsMovieScale, 270); + compassMove.insertFaderKnot(entry.movieStart + kMarsMovieScale * 3, 90); + break; + case kMars52SpinLeft: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars52Compass, entry.movieEnd, kMars54Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars52Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars54Compass); + break; + case kMars52SpinRight: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars52Compass, entry.movieEnd, kMars58Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars52Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars58Compass); + break; + case kMars52Extend: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars52Compass, + entry.movieEnd, kMars52Compass + kMarsShieldPanelOffsetAngle); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars52Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 60, kMars52Compass + kMarsShieldPanelOffsetAngle); + break; + case kMars53Retract: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, + kMars52Compass + kMarsShieldPanelOffsetAngle, entry.movieEnd, kMars52Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars52Compass + kMarsShieldPanelOffsetAngle); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 60, kMars52Compass); + break; + case kMars56ExtendWithBomb: + case kMars56ExtendNoBomb: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars56Compass, + entry.movieEnd, kMars56Compass - kMarsShieldPanelOffsetAngle); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars56Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 60, kMars56Compass - kMarsShieldPanelOffsetAngle); + break; + case kMars57RetractWithBomb: + case kMars57RetractNoBomb: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, + kMars56Compass - kMarsShieldPanelOffsetAngle, entry.movieEnd, kMars56Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars56Compass - kMarsShieldPanelOffsetAngle); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 60, kMars56Compass); + break; + case kMars54SpinLeft: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars54Compass, entry.movieEnd, kMars56Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars54Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars56Compass); + break; + case kMars54SpinRight: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars54Compass, entry.movieEnd, kMars52Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars54Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars52Compass); + break; + case kMars56SpinLeft: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars56Compass, + entry.movieEnd, kMars58Compass + 360); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars56Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars58Compass + 360); + break; + case kMars56SpinRight: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars56Compass, entry.movieEnd, kMars54Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars56Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars54Compass); + break; + case kMars58SpinLeft: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, kMars58Compass, + entry.movieEnd, kMars52Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars58Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars52Compass); + break; + case kMars58SpinRight: + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), entry.movieStart, + kMars58Compass + 360, entry.movieEnd, kMars56Compass); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 10, kMars58Compass + 360); + compassMove.insertFaderKnot(entry.movieStart + kMarsFrameDuration * 110, kMars56Compass); + break; + default: + Neighborhood::getExtraCompassMove(entry, compassMove); + } +} + +void Mars::loadAmbientLoops() { + RoomID room = GameState.getCurrentRoom(); + + if ((room >= kMars0A && room <= kMars21) || (room >= kMars41 && room <= kMars43)) { + if (GameState.getMarsSeenTimeStream()) + loadLoopSound1("Sounds/Mars/Gantry Ambient.22K.8.AIFF"); + } else if (room >= kMars22 && room <= kMars31South) { + loadLoopSound1("Sounds/Mars/Reception.02.22K.8.AIFF", 0x100 / 4); + } else if (room >= kMars32 && room <= kMars34) { + loadLoopSound1("Sounds/Mars/Pod Room Ambient.22K.8.AIFF"); + } else if (room == kMars35) { + if (getAirQuality(room) == kAirQualityVacuum) + loadLoopSound1("Sounds/Mars/Gear Room Ambient.22K.8.AIFF"); + else + loadLoopSound1("Sounds/Mars/Gantry Ambient.22K.8.AIFF", 0x100 / 2); + } else if (room >= kMars36 && room <= kMars39) { + loadLoopSound1("Sounds/Mars/Gear Room Ambient.22K.8.AIFF"); + } else if (room >= kMars45 && room <= kMars51) { + loadLoopSound1("Sounds/Mars/Lower Mars Ambient.22K.8.AIFF"); + } else if (room >= kMars52 && room <= kMars58) { + loadLoopSound1("Sounds/Mars/ReactorLoop.22K.8.AIFF"); + } else if (room == kMars60) { + if (getAirQuality(room) == kAirQualityVacuum) + loadLoopSound1("Sounds/Mars/Mars Maze Ambient.22K.8.AIFF"); + else + loadLoopSound1("Sounds/Mars/Lower Mars Ambient.22K.8.AIFF", 0x100 / 2); + } else if (room >= kMarsMaze004 && room <= kMarsMaze200) { + loadLoopSound1("Sounds/Mars/Mars Maze Ambient.22K.8.AIFF"); + } else if (room == kMarsRobotShuttle) { + loadLoopSound1("Sounds/Mars/Robot Shuttle.22K.8.AIFF"); + } + + if (!_noAirFuse.isFuseLit()) { + switch (room) { + case kMars02: + case kMars05: + case kMars06: + case kMars07: + case kMars08: + loadLoopSound2("Sounds/Mars/Gantry Loop.aiff", 0x100, 0, 0); + break; + // Robot at maze 48 + case kMarsMaze037: + if (GameState.isCurrentDoorOpen()) + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 2); + else + loadLoopSound2(""); + break; + case kMarsMaze038: + case kMarsMaze039: + case kMarsMaze049: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100); + break; + case kMarsMaze050: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 * 3 / 4); + break; + case kMarsMaze051: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 2); + break; + case kMarsMaze052: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 4); + break; + case kMarsMaze042: + case kMarsMaze053: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 8); + break; + case kMarsMaze058: + if (GameState.isCurrentDoorOpen()) + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 4); + else + loadLoopSound2(""); + break; + // Robot at 151 + case kMarsMaze148: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100); + break; + case kMarsMaze147: + case kMarsMaze149: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 * 3 / 4); + break; + case kMarsMaze146: + case kMarsMaze152: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 2); + break; + case kMarsMaze145: + case kMarsMaze153: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 4); + break; + // Robots at 80 and 82. + case kMarsMaze079: + case kMarsMaze081: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100); + break; + case kMarsMaze078: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 * 3 / 4); + break; + case kMarsMaze083: + if (GameState.isCurrentDoorOpen()) + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 * 3 / 4); + else + loadLoopSound2(""); + break; + case kMarsMaze118: + case kMarsMaze076: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 2); + break; + case kMarsMaze074: + case kMarsMaze117: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 4); + break; + // Robot at 94 + case kMarsMaze093: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100); + break; + case kMarsMaze091: + case kMarsMaze092: + case kMarsMaze098: + case kMarsMaze101: + case kMarsMaze100: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 * 3 / 4); + break; + case kMarsMaze090: + case kMarsMaze099: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 2); + break; + case kMarsMaze089: + if (GameState.isCurrentDoorOpen()) + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 2); + break; + case kMarsMaze178: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 4); + break; + // Robot at 197 + case kMarsMaze191: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100); + break; + case kMarsMaze190: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 * 3 / 4); + break; + case kMarsMaze198: + case kMarsMaze189: + loadLoopSound2("Sounds/Mars/Maze Sparks.22K.AIFF", 0x100 / 2); + break; + default: + loadLoopSound2(""); + break; + } + } +} + +void Mars::checkContinuePoint(const RoomID room, const DirectionConstant direction) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kMars02, kSouth): + case MakeRoomView(kMars19, kEast): + case MakeRoomView(kMars22, kNorth): + case MakeRoomView(kMars43, kEast): + case MakeRoomView(kMars51, kEast): + case MakeRoomView(kMars56, kEast): + case MakeRoomView(kMars60, kWest): + case MakeRoomView(kMarsMaze004, kWest): + case MakeRoomView(kMarsMaze009, kWest): + case MakeRoomView(kMarsMaze012, kWest): + case MakeRoomView(kMarsMaze037, kWest): + case MakeRoomView(kMarsMaze047, kNorth): + case MakeRoomView(kMarsMaze052, kWest): + case MakeRoomView(kMarsMaze057, kNorth): + case MakeRoomView(kMarsMaze071, kWest): + case MakeRoomView(kMarsMaze081, kNorth): + case MakeRoomView(kMarsMaze088, kWest): + case MakeRoomView(kMarsMaze093, kWest): + case MakeRoomView(kMarsMaze115, kNorth): + case MakeRoomView(kMarsMaze120, kWest): + case MakeRoomView(kMarsMaze126, kEast): + case MakeRoomView(kMarsMaze133, kNorth): + case MakeRoomView(kMarsMaze144, kNorth): + case MakeRoomView(kMarsMaze156, kEast): + case MakeRoomView(kMarsMaze162, kNorth): + case MakeRoomView(kMarsMaze177, kWest): + case MakeRoomView(kMarsMaze180, kNorth): + case MakeRoomView(kMarsMaze187, kWest): + case MakeRoomView(kMarsMaze199, kWest): + makeContinuePoint(); + break; + case MakeRoomView(kMars05, kEast): + case MakeRoomView(kMars06, kEast): + case MakeRoomView(kMars07, kEast): + if (GameState.getMarsSecurityDown()) + makeContinuePoint(); + break; + case MakeRoomView(kMars46, kSouth): + if (!GameState.getMarsSeenRobotAtReactor()) + makeContinuePoint(); + break; + case MakeRoomView(kMars46, kWest): + if (GameState.getMarsAvoidedReactorRobot()) + makeContinuePoint(); + break; + } +} + +void Mars::launchMaze007Robot() { + startExtraLongSequence(kMarsMaze007RobotApproach, kMarsMaze007RobotDeath, kExtraCompletedFlag, kFilterAllInput); + scheduleEvent(kMaze007RobotLoopingTime, kMarsMovieScale, kMaze007RobotLoopingEvent); +} + +void Mars::launchMaze015Robot() { + startExtraLongSequence(kMarsMaze015SouthRobotApproach, kMarsMaze015SouthRobotDeath, kExtraCompletedFlag, kFilterAllInput); + scheduleEvent(kMaze015RobotLoopingTime, kMarsMovieScale, kMaze015RobotLoopingEvent); +} + +void Mars::launchMaze101Robot() { + startExtraLongSequence(kMarsMaze101EastRobotApproach, kMarsMaze101EastRobotDeath, kExtraCompletedFlag, kFilterAllInput); + scheduleEvent(kMaze101RobotLoopingTime, kMarsMovieScale, kMaze101RobotLoopingEvent); +} + +void Mars::launchMaze104Robot() { + startExtraLongSequence(kMarsMaze104WestLoop, kMarsMaze104WestDeath, kExtraCompletedFlag, kFilterAllInput); + scheduleEvent(kMaze104RobotLoopingTime, kMarsMovieScale, kMaze104RobotLoopingEvent); +} + +void Mars::launchMaze133Robot() { + startExtraLongSequence(kMarsMaze133SouthApproach, kMarsMaze133SouthDeath, kExtraCompletedFlag, kFilterAllInput); + scheduleEvent(kMaze133RobotLoopingTime, kMarsMovieScale, kMaze133RobotLoopingEvent); +} + +void Mars::launchMaze136Robot() { + startExtraLongSequence(kMarsMaze136NorthApproach, kMarsMaze136NorthDeath, kExtraCompletedFlag, kFilterAllInput); + scheduleEvent(kMaze136RobotLoopingTime, kMarsMovieScale, kMaze136RobotLoopingEvent); +} + +void Mars::launchMaze184Robot() { + startExtraLongSequence(kMarsMaze184WestLoop, kMarsMaze184WestDeath, kExtraCompletedFlag, kFilterAllInput); + scheduleEvent(kMaze184RobotLoopingTime, kMarsMovieScale, kMaze184RobotLoopingEvent); +} + +void Mars::timerExpired(const uint32 eventType) { + switch (eventType) { + case kMaze007RobotLoopingEvent: + case kMaze015RobotLoopingEvent: + case kMaze101RobotLoopingEvent: + case kMaze104RobotLoopingEvent: + case kMaze133RobotLoopingEvent: + case kMaze136RobotLoopingEvent: + case kMaze184RobotLoopingEvent: + _interruptionFilter = kFilterNoInput; + break; + } +} + +void Mars::arriveAt(const RoomID room, const DirectionConstant direction) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kMars18, kNorth): + if (GameState.getMarsPodAtUpperPlatform()) + setCurrentAlternate(kAltMarsPodAtMars34); + break; + case MakeRoomView(kMars27, kEast): + case MakeRoomView(kMars29, kEast): + if (GameState.isTakenItemID(kMarsCard)) + setCurrentAlternate(kAltMarsTookCard); + else + setCurrentAlternate(kAltMarsNormal); + break; + case MakeRoomView(kMars35, kEast): + case MakeRoomView(kMars35, kWest): + if (GameState.getMarsAirlockOpen()) + setCurrentAlternate(kAltMars35AirlockWest); + else + setCurrentAlternate(kAltMars35AirlockEast); + break; + case MakeRoomView(kMars60, kEast): + case MakeRoomView(kMars60, kWest): + if (GameState.getMarsAirlockOpen()) + setCurrentAlternate(kAltMars60AirlockEast); + else + setCurrentAlternate(kAltMars60AirlockWest); + break; + case MakeRoomView(kMars45, kNorth): + case MakeRoomView(kMars45, kSouth): + case MakeRoomView(kMars45, kEast): + case MakeRoomView(kMars45, kWest): + GameState.setMarsPodAtUpperPlatform(false); + setCurrentAlternate(kAltMarsPodAtMars45); + break; + case MakeRoomView(kMars46, kNorth): + case MakeRoomView(kMars46, kSouth): + case MakeRoomView(kMars46, kEast): + case MakeRoomView(kMars46, kWest): + case MakeRoomView(kMars47, kNorth): + case MakeRoomView(kMars47, kSouth): + case MakeRoomView(kMars47, kEast): + case MakeRoomView(kMars47, kWest): + if (GameState.getMarsPodAtUpperPlatform()) + setCurrentAlternate(kAltMarsNormal); + else + setCurrentAlternate(kAltMarsPodAtMars45); + break; + case MakeRoomView(kMars48, kNorth): + case MakeRoomView(kMars48, kSouth): + case MakeRoomView(kMars48, kEast): + case MakeRoomView(kMars48, kWest): + case MakeRoomView(kMars49, kNorth): + case MakeRoomView(kMars49, kEast): + case MakeRoomView(kMars49, kWest): + if (GameState.isTakenItemID(kAirMask)) + setCurrentAlternate(kAltMarsTookMask); + else + setCurrentAlternate(kAltMarsNormal); + break; + case MakeRoomView(kMars49, kSouth): + if (GameState.getMarsMaskOnFiller()) + setCurrentAlternate(kAltMarsMaskOnFiller); + else if (GameState.isTakenItemID(kAirMask)) + setCurrentAlternate(kAltMarsTookMask); + else + setCurrentAlternate(kAltMarsNormal); + break; + } + + Neighborhood::arriveAt(room, direction); + checkAirlockDoors(); + setUpReactorEnergyDrain(); + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kMars0A, kNorth): + if (!GameState.getMarsSeenTimeStream()) + startExtraLongSequence(kMarsArrivalFromTSA, kMars0AWatchShuttleDepart, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kMars07, kSouth): + case MakeRoomView(kMars13, kNorth): + if (!GameState.getMarsHeardCheckInMessage()) { + playSpotSoundSync(kMarsCheckInRequiredIn, kMarsCheckInRequiredOut); + GameState.setMarsHeardCheckInMessage(true); + } + break; + case MakeRoomView(kMars44, kWest): + if (GameState.getMarsReadyForShuttleTransport()) + startUpFromFinishedSpaceChase(); + else if (GameState.getMarsFinishedCanyonChase()) + startUpFromSpaceChase(); + else + _neighborhoodNotification.setNotificationFlags(kTimeForCanyonChaseFlag, kTimeForCanyonChaseFlag); + break; + case MakeRoomView(kMars10, kNorth): + if (!GameState.getMarsRobotThrownPlayer()) + startExtraSequence(kRobotThrowsPlayer, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kMars11, kSouth): + case MakeRoomView(kMars12, kSouth): + setCurrentActivation(kActivationReadyForKiosk); + break; + case MakeRoomView(kMars15, kWest): + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSecurityDown()) { + playSpotSoundSync(kMarsShuttle2DepartedIn, kMarsShuttle2DepartedOut); + restoreStriding(kMars17, kWest, kAltMarsNormal); + GameState.setMarsSecurityDown(true); + } + break; + case MakeRoomView(kMars17, kNorth): + case MakeRoomView(kMars17, kSouth): + case MakeRoomView(kMars17, kEast): + case MakeRoomView(kMars17, kWest): + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSecurityDown()) + forceStridingStop(kMars17, kWest, kAltMarsNormal); + + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSawRobotLeave()) { + startExtraSequence(kRobotOnWayToShuttle, kExtraCompletedFlag, kFilterNoInput); + restoreStriding(kMars19, kWest, kAltMarsNormal); + GameState.setMarsSawRobotLeave(true); + } + break; + case MakeRoomView(kMars19, kNorth): + case MakeRoomView(kMars19, kSouth): + case MakeRoomView(kMars19, kWest): + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSawRobotLeave()) + forceStridingStop(kMars19, kWest, kAltMarsNormal); + + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSecurityDown()) + forceStridingStop(kMars17, kWest, kAltMarsNormal); + break; + case MakeRoomView(kMars19, kEast): + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSawRobotLeave()) + forceStridingStop(kMars19, kWest, kAltMarsNormal); + + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSecurityDown()) + forceStridingStop(kMars17, kWest, kAltMarsNormal); + break; + case MakeRoomView(kMars32, kNorth): + if (!GameState.getMarsPodAtUpperPlatform()) { + playSpotSoundSync(kMarsPodArrivedUpperPlatformIn, kMarsPodArrivedUpperPlatformOut); + GameState.setMarsPodAtUpperPlatform(true); + } + break; + case MakeRoomView(kMars33North, kNorth): + setCurrentActivation(kActivationTunnelMapReady); + // Fall through... + case MakeRoomView(kMars33, kSouth): + case MakeRoomView(kMars33, kEast): + case MakeRoomView(kMars33, kWest): + case MakeRoomView(kMars32, kSouth): + case MakeRoomView(kMars32, kEast): + case MakeRoomView(kMars32, kWest): + if (!GameState.getMarsPodAtUpperPlatform()) + GameState.setMarsPodAtUpperPlatform(true); + break; + case MakeRoomView(kMars34, kNorth): + startExtraSequence(kMars34NorthPodGreeting, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kMars34, kSouth): + case MakeRoomView(kMars45, kNorth): + setCurrentActivation(kActivateMarsPodClosed); + break; + case MakeRoomView(kMars35, kWest): + if (GameState.getMarsThreadedMaze() && !GameState.getMarsSecurityDown()) + forceStridingStop(kMars19, kWest, kAltMarsNormal); + // Fall through... + case MakeRoomView(kMars60, kEast): + if (!GameState.getMarsAirlockOpen()) + setCurrentActivation(kActivateReadyToPressurizeAirlock); + break; + case MakeRoomView(kMars35, kEast): + case MakeRoomView(kMars60, kWest): + if (GameState.getMarsAirlockOpen()) + setCurrentActivation(kActivateReadyToPressurizeAirlock); + break; + case MakeRoomView(kMars39, kWest): + if (GameState.getLastRoom() == kMarsMaze200) + GameState.setMarsPodAtUpperPlatform(false); + break; + case MakeRoomView(kMars45, kSouth): + // Set up maze doors here. + // Doing it here makes sure that it will be the same if the player comes + // back out of the maze and goes back in, but will vary if + // the player comes back down to the maze a second time. + GameState.setMarsMazeDoorPair1(_vm->getRandomBit()); + GameState.setMarsMazeDoorPair2(_vm->getRandomBit()); + GameState.setMarsMazeDoorPair3(_vm->getRandomBit()); + GameState.setMarsArrivedBelow(true); + break; + case MakeRoomView(kMars48, kEast): + if (!GameState.getMarsSeenRobotAtReactor()) { + // Preload the looping sound... + loadLoopSound2("Sounds/Mars/Robot Loop.aiff", 0, 0, 0); + startExtraSequence(kMars48RobotApproaches, kExtraCompletedFlag, kFilterNoInput); + } else if (!GameState.getMarsAvoidedReactorRobot()) { + loadLoopSound2("Sounds/Mars/Robot Loop.aiff", 0x100, 0, 0); + loopExtraSequence(kMars48RobotLoops); + _utilityFuse.primeFuse(kMarsRobotPatienceLimit); + _utilityFuse.setFunctor(new Common::Functor0Mem<void, Mars>(this, &Mars::robotTiredOfWaiting)); + _utilityFuse.lightFuse(); + } + break; + case MakeRoomView(kMars48, kSouth): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) { + loadLoopSound2("Sounds/Mars/Robot Loop.aiff", 0x100, 0, 0); + _utilityFuse.primeFuse(kMarsRobotPatienceLimit); + _utilityFuse.setFunctor(new Common::Functor0Mem<void, Mars>(this, &Mars::robotTiredOfWaiting)); + _utilityFuse.lightFuse(); + } + break; + case MakeRoomView(kMars49, kSouth): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) { + playSpotSoundSync(kMarsRobotTakesTransportIn, kMarsRobotTakesTransportOut); + playSpotSoundSync(kMarsPodDepartedLowerPlatformIn, kMarsPodDepartedLowerPlatformOut); + GameState.setMarsAvoidedReactorRobot(true); + GameState.setMarsPodAtUpperPlatform(true); + GameState.setScoringAvoidedRobot(); + } + + if (GameState.isTakenItemID(kAirMask)) + setCurrentActivation(kActivateHotSpotAlways); + else if (GameState.getMarsMaskOnFiller()) + setCurrentActivation(kActivateMaskOnFiller); + else + setCurrentActivation(kActivateMaskOnHolder); + break; + case MakeRoomView(kMars51, kWest): + case MakeRoomView(kMars50, kWest): + case MakeRoomView(kMars48, kWest): + if (GameState.getShieldOn()) + g_shield->setItemState(kShieldNormal); + g_energyMonitor->setEnergyDrainRate(kEnergyDrainNormal); + _vm->resetEnergyDeathReason(); + break; + case MakeRoomView(kMars52, kNorth): + case MakeRoomView(kMars52, kSouth): + case MakeRoomView(kMars52, kEast): + case MakeRoomView(kMars52, kWest): + case MakeRoomView(kMars54, kNorth): + case MakeRoomView(kMars54, kSouth): + case MakeRoomView(kMars54, kEast): + case MakeRoomView(kMars54, kWest): + case MakeRoomView(kMars56, kNorth): + case MakeRoomView(kMars56, kSouth): + case MakeRoomView(kMars56, kWest): + case MakeRoomView(kMars58, kNorth): + case MakeRoomView(kMars58, kSouth): + case MakeRoomView(kMars58, kEast): + case MakeRoomView(kMars58, kWest): + setCurrentActivation(kActivateReactorPlatformOut); + break; + case MakeRoomView(kMars56, kEast): + if (GameState.getMarsLockBroken()) { + setCurrentActivation(kActivateReactorAskOperation); + _privateFlags.setFlag(kMarsPrivatePlatformZoomedInFlag, true); + } else if (GameState.getMarsLockFrozen()) { + setCurrentActivation(kActivateReactorReadyForCrowBar); + _privateFlags.setFlag(kMarsPrivatePlatformZoomedInFlag, true); + _utilityFuse.primeFuse(kLockFreezeTimeLmit); + _utilityFuse.setFunctor(new Common::Functor0Mem<void, Mars>(this, &Mars::lockThawed)); + _utilityFuse.lightFuse(); + } else { + setCurrentActivation(kActivateReactorPlatformOut); + } + break; + case MakeRoomView(kMarsRobotShuttle, kEast): + setCurrentActivation(kActivationRobotHeadClosed); + break; + case MakeRoomView(kMarsMaze007, kNorth): + launchMaze007Robot(); + break; + case MakeRoomView(kMarsMaze015, kSouth): + launchMaze015Robot(); + break; + case MakeRoomView(kMarsMaze101, kEast): + launchMaze101Robot(); + break; + case MakeRoomView(kMarsMaze104, kWest): + launchMaze104Robot(); + break; + case MakeRoomView(kMarsMaze133, kSouth): + launchMaze133Robot(); + break; + case MakeRoomView(kMarsMaze136, kNorth): + launchMaze136Robot(); + break; + case MakeRoomView(kMarsMaze184, kWest): + launchMaze184Robot(); + break; + case MakeRoomView(kMarsMaze199, kSouth): + GameState.setScoringThreadedMaze(); + GameState.setMarsThreadedMaze(true); + break; + case MakeRoomView(kMarsDeathRoom, kNorth): + case MakeRoomView(kMarsDeathRoom, kSouth): + case MakeRoomView(kMarsDeathRoom, kEast): + case MakeRoomView(kMarsDeathRoom, kWest): + switch (GameState.getLastRoom()) { + case kMars39: + die(kDeathDidntLeaveBucket); + break; + case kMars46: + die(kDeathRunOverByPod); + break; + } + break; + } + + checkAirMask(); +} + +void Mars::shieldOn() { + setUpReactorEnergyDrain(); +} + +void Mars::shieldOff() { + setUpReactorEnergyDrain(); +} + +void Mars::turnTo(const DirectionConstant direction) { + switch (MakeRoomView(GameState.getCurrentRoom(), direction)) { + case MakeRoomView(kMars27, kNorth): + case MakeRoomView(kMars27, kSouth): + case MakeRoomView(kMars27, kEast): + case MakeRoomView(kMars29, kNorth): + case MakeRoomView(kMars29, kSouth): + case MakeRoomView(kMars29, kEast): + if (GameState.isTakenItemID(kMarsCard)) + setCurrentAlternate(kAltMarsTookCard); + break; + case MakeRoomView(kMars35, kNorth): + case MakeRoomView(kMars35, kSouth): + case MakeRoomView(kMars60, kNorth): + case MakeRoomView(kMars60, kSouth): + if (getCurrentActivation() == kActivateAirlockPressurized) + playSpotSoundSync(kMarsAirlockPressurizeIn, kMarsAirlockPressurizeOut); + break; + } + + Neighborhood::turnTo(direction); + + switch (MakeRoomView(GameState.getCurrentRoom(), direction)) { + case MakeRoomView(kMars11, kSouth): + case MakeRoomView(kMars12, kSouth): + setCurrentActivation(kActivationReadyForKiosk); + break; + case MakeRoomView(kMars18, kNorth): + if (GameState.getMarsPodAtUpperPlatform()) + setCurrentAlternate(kAltMarsPodAtMars34); + break; + case MakeRoomView(kMars22, kSouth): + if (!GameState.getMarsHeardCheckInMessage()) { + playSpotSoundSync(kMarsCheckInRequiredIn, kMarsCheckInRequiredOut); + GameState.setMarsHeardCheckInMessage(true); + } + break; + case MakeRoomView(kMars34, kSouth): + case MakeRoomView(kMars45, kNorth): + setCurrentActivation(kActivateMarsPodClosed); + break; + case MakeRoomView(kMars34, kNorth): + startExtraSequence(kMars34NorthPodGreeting, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kMars35, kEast): + case MakeRoomView(kMars60, kWest): + if (GameState.getMarsAirlockOpen()) + setCurrentActivation(kActivateReadyToPressurizeAirlock); + break; + case MakeRoomView(kMars60, kEast): + if (!GameState.getMarsAirlockOpen()) + setCurrentActivation(kActivateReadyToPressurizeAirlock); + break; + case MakeRoomView(kMars35, kWest): + if (!GameState.getMarsAirlockOpen()) + setCurrentActivation(kActivateReadyToPressurizeAirlock); + + // Do this here because this will be called after spinning the airlock after + // going through the gear room. + if (GameState.getMarsThreadedMaze()) + GameState.setScoringThreadedGearRoom(); + break; + case MakeRoomView(kMars48, kNorth): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) + die(kDeathDidntGetOutOfWay); + break; + case MakeRoomView(kMars48, kEast): + if (!GameState.getMarsSeenRobotAtReactor()) { + // Preload the looping sound... + loadLoopSound2("Sounds/Mars/Robot Loop.aiff", 0, 0, 0); + startExtraSequence(kMars48RobotApproaches, kExtraCompletedFlag, kFilterNoInput); + } else if (!GameState.getMarsAvoidedReactorRobot()) { + loopExtraSequence(kMars48RobotLoops); + } else if (GameState.isTakenItemID(kAirMask)) { + setCurrentAlternate(kAltMarsTookMask); + } else { + setCurrentAlternate(kAltMarsNormal); + } + break; + case MakeRoomView(kMars48, kWest): + if (GameState.getMarsSeenRobotAtReactor() && !GameState.getMarsAvoidedReactorRobot()) + die(kDeathDidntGetOutOfWay); + else if (GameState.isTakenItemID(kAirMask)) + setCurrentAlternate(kAltMarsTookMask); + else + setCurrentAlternate(kAltMarsNormal); + break; + case MakeRoomView(kMars49, kSouth): + if (GameState.isTakenItemID(kAirMask)) + setCurrentActivation(kActivateHotSpotAlways); + else + setCurrentActivation(kActivateMaskOnHolder); + break; + case MakeRoomView(kMars52, kNorth): + case MakeRoomView(kMars52, kSouth): + case MakeRoomView(kMars52, kEast): + case MakeRoomView(kMars52, kWest): + case MakeRoomView(kMars54, kNorth): + case MakeRoomView(kMars54, kSouth): + case MakeRoomView(kMars54, kEast): + case MakeRoomView(kMars54, kWest): + case MakeRoomView(kMars56, kNorth): + case MakeRoomView(kMars56, kSouth): + case MakeRoomView(kMars56, kEast): + case MakeRoomView(kMars56, kWest): + case MakeRoomView(kMars58, kNorth): + case MakeRoomView(kMars58, kSouth): + case MakeRoomView(kMars58, kEast): + case MakeRoomView(kMars58, kWest): + setCurrentActivation(kActivateReactorPlatformOut); + break; + case MakeRoomView(kMarsMaze007, kNorth): + launchMaze007Robot(); + break; + case MakeRoomView(kMarsMaze015, kSouth): + launchMaze015Robot(); + break; + case MakeRoomView(kMarsMaze101, kEast): + launchMaze101Robot(); + break; + case MakeRoomView(kMarsMaze104, kWest): + launchMaze104Robot(); + break; + case MakeRoomView(kMarsMaze133, kSouth): + launchMaze133Robot(); + break; + case MakeRoomView(kMarsMaze136, kNorth): + launchMaze136Robot(); + break; + case MakeRoomView(kMarsMaze184, kWest): + launchMaze184Robot(); + break; + } +} + +void Mars::activateOneHotspot(HotspotInfoTable::Entry &entry, Hotspot *hotspot) { + switch (hotspot->getObjectID()) { + case kMars57RedMoveSpotID: + case kMars57YellowMoveSpotID: + case kMars57GreenMoveSpotID: + if (!_choiceHighlight.choiceHighlighted(hotspot->getObjectID() - kMars57RedMoveSpotID)) + hotspot->setActive(); + break; + case kMars57BlueMoveSpotID: + if (_reactorStage >= 2 && !_choiceHighlight.choiceHighlighted(3)) + hotspot->setActive(); + break; + case kMars57PurpleMoveSpotID: + if (_reactorStage == 3 && !_choiceHighlight.choiceHighlighted(4)) + hotspot->setActive(); + break; + default: + Neighborhood::activateOneHotspot(entry, hotspot); + break; + } +} + +void Mars::activateHotspots() { + InventoryItem *item; + + Neighborhood::activateHotspots(); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars48, kEast): + if ((_navMovie.getFlags() & kLoopTimeBase) != 0 && _vm->getDragType() == kDragInventoryUse) + _vm->getAllHotspots().activateOneHotspot(kAttackRobotHotSpotID); + break; + case MakeRoomView(kMars56, kEast): + switch (getCurrentActivation()) { + case kActivateReactorReadyForNitrogen: + item = (InventoryItem *)_vm->getAllItems().findItemByID(kNitrogenCanister); + if (item->getItemState() != kNitrogenFull) + _vm->getAllHotspots().deactivateOneHotspot(kMars57DropNitrogenSpotID); + // Fall through... + case kActivateReactorReadyForCrowBar: + _vm->getAllHotspots().activateOneHotspot(kMars57CantOpenPanelSpotID); + break; + } + break; + case MakeRoomView(kMarsRobotShuttle, kEast): + if (_privateFlags.getFlag(kMarsPrivateGotMapChipFlag)) + _vm->getAllHotspots().deactivateOneHotspot(kRobotShuttleMapChipSpotID); + else + _vm->getAllHotspots().activateOneHotspot(kRobotShuttleMapChipSpotID); + + if (_privateFlags.getFlag(kMarsPrivateGotOpticalChipFlag)) + _vm->getAllHotspots().deactivateOneHotspot(kRobotShuttleOpticalChipSpotID); + else + _vm->getAllHotspots().activateOneHotspot(kRobotShuttleOpticalChipSpotID); + + if (_privateFlags.getFlag(kMarsPrivateGotShieldChipFlag)) + _vm->getAllHotspots().deactivateOneHotspot(kRobotShuttleShieldChipSpotID); + else + _vm->getAllHotspots().activateOneHotspot(kRobotShuttleShieldChipSpotID); + break; + default: + if (_privateFlags.getFlag(kMarsPrivateInSpaceChaseFlag)) { + if (GameState.getMarsReadyForShuttleTransport()) { + _shuttleTransportSpot.setActive(); + } else { + _energyChoiceSpot.setActive(); + _gravitonChoiceSpot.setActive(); + _tractorChoiceSpot.setActive(); + if (_weaponSelection != kNoWeapon) + _shuttleViewSpot.setActive(); + } + } + break; + } +} + +void Mars::clickInHotspot(const Input &input, const Hotspot *clickedSpot) { + switch (clickedSpot->getObjectID()) { + case kMars11NorthKioskSpotID: + case kMars12NorthKioskSpotID: + playSpotSoundSync(kMarsKioskBeepIn, kMarsKioskBeepOut); + Neighborhood::clickInHotspot(input, clickedSpot); + break; + case kMars11NorthKioskSightsSpotID: + case kMars12NorthKioskSightsSpotID: + playSpotSoundSync(kMarsKioskBeepIn, kMarsKioskBeepOut); + if (!startExtraSequenceSync(kMarsSightsInfo, kFilterAllInput)) + showExtraView(kMarsInfoKioskIntro); + break; + case kMars11NorthKioskColonySpotID: + case kMars12NorthKioskColonySpotID: + playSpotSoundSync(kMarsKioskBeepIn, kMarsKioskBeepOut); + if (!startExtraSequenceSync(kMarsColonyInfo, kFilterAllInput)) + showExtraView(kMarsInfoKioskIntro); + break; + case kMars33NorthMonitorSpotID: + switch (_lastExtra) { + case kMars33SlideShow1: + startExtraSequence(kMars33SlideShow2, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars33SlideShow2: + startExtraSequence(kMars33SlideShow3, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars33SlideShow3: + startExtraSequence(kMars33SlideShow4, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars33SlideShow4: + // Should never happen... + default: + startExtraSequence(kMars33SlideShow1, kExtraCompletedFlag, kFilterNoInput); + break; + } + break; + case kMars34SouthOpenStorageSpotID: + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars34SpotOpenNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars34SpotOpenWithBar, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars34SouthCloseStorageSpotID: + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars34SpotCloseNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars34SpotCloseWithBar, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars35WestPressurizeSpotID: + case kMars35EastPressurizeSpotID: + case kMars60WestPressurizeSpotID: + case kMars60EastPressurizeSpotID: + playSpotSoundSync(kMarsAirlockButtonBeepIn, kMarsAirlockButtonBeepOut); + playSpotSoundSync(kMarsAirlockPressurizeIn, kMarsAirlockPressurizeOut); + setCurrentActivation(kActivateAirlockPressurized); + break; + case kMars45NorthOpenStorageSpotID: + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars45SpotOpenNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars45SpotOpenWithBar, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars45NorthCloseStorageSpotID: + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars45SpotCloseNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars45SpotCloseWithBar, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars56ExtractSpotID: + if (GameState.isTakenItemID(kCardBomb)) { + startExtraSequence(kMars56ExtendNoBomb, kExtraCompletedFlag, kFilterNoInput); + setCurrentActivation(kActivateReactorPlatformIn); + } else { + startExtraSequence(kMars56ExtendWithBomb, kExtraCompletedFlag, kFilterNoInput); + setCurrentActivation(kActivateReactorAskLowerScreen); + } + break; + case kMars57UndoMoveSpotID: + playSpotSoundSync(kMarsColorMatchingButtonBeepIn, kMarsColorMatchingButtonBeepOut); + doUndoOneGuess(); + break; + case kMars57RedMoveSpotID: + playSpotSoundSync(kMarsColorMatchingButtonBeepIn, kMarsColorMatchingButtonBeepOut); + doReactorGuess(0); + break; + case kMars57YellowMoveSpotID: + playSpotSoundSync(kMarsColorMatchingButtonBeepIn, kMarsColorMatchingButtonBeepOut); + doReactorGuess(1); + break; + case kMars57GreenMoveSpotID: + playSpotSoundSync(kMarsColorMatchingButtonBeepIn, kMarsColorMatchingButtonBeepOut); + doReactorGuess(2); + break; + case kMars57BlueMoveSpotID: + playSpotSoundSync(kMarsColorMatchingButtonBeepIn, kMarsColorMatchingButtonBeepOut); + doReactorGuess(3); + break; + case kMars57PurpleMoveSpotID: + playSpotSoundSync(kMarsColorMatchingButtonBeepIn, kMarsColorMatchingButtonBeepOut); + doReactorGuess(4); + break; + case kShuttleEnergySpotID: + case kShuttleGravitonSpotID: + case kShuttleTractorSpotID: + case kShuttleViewSpotID: + case kShuttleTransportSpotID: + spaceChaseClick(input, clickedSpot->getObjectID()); + break; + default: + Neighborhood::clickInHotspot(input, clickedSpot); + break; + } +} + +InputBits Mars::getInputFilter() { + InputBits result = Neighborhood::getInputFilter(); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars49, kSouth): + if (GameState.getMarsMaskOnFiller()) + // Can't move when mask is on filler. + result &= ~kFilterAllDirections; + break; + case MakeRoomView(kMars52, kNorth): + case MakeRoomView(kMars52, kSouth): + case MakeRoomView(kMars52, kEast): + case MakeRoomView(kMars52, kWest): + case MakeRoomView(kMars54, kNorth): + case MakeRoomView(kMars54, kSouth): + case MakeRoomView(kMars54, kEast): + case MakeRoomView(kMars54, kWest): + case MakeRoomView(kMars56, kNorth): + case MakeRoomView(kMars56, kSouth): + case MakeRoomView(kMars56, kEast): + case MakeRoomView(kMars56, kWest): + case MakeRoomView(kMars58, kNorth): + case MakeRoomView(kMars58, kSouth): + case MakeRoomView(kMars58, kEast): + case MakeRoomView(kMars58, kWest): + if (_privateFlags.getFlag(kMarsPrivatePlatformZoomedInFlag)) + // Can't move when platform is extended. + result &= ~kFilterAllDirections; + break; + case MakeRoomView(kMars44, kWest): + if (_canyonChaseMovie.isMovieValid() && _canyonChaseMovie.isRunning()) + result &= ~kFilterAllDirections; + break; + } + + return result; +} + +// Only called when trying to pick up an item and the player can't (because +// the inventory is too full or because the player lets go of the item before +// dropping it into the inventory). +Hotspot *Mars::getItemScreenSpot(Item *item, DisplayElement *element) { + HotSpotID destSpotID; + + switch (item->getObjectID()) { + case kCardBomb: + destSpotID = kMars57GrabBombSpotID; + break; + case kMarsCard: + destSpotID = kMars31SouthCardSpotID; + break; + case kAirMask: + if (GameState.getMarsMaskOnFiller()) + destSpotID = kMars49AirFillingDropSpotID; + else + destSpotID = kMars49AirMaskSpotID; + break; + case kCrowbar: + if (GameState.getCurrentRoom() == kMars34) + destSpotID = kMars34SouthCrowbarSpotID; + else + destSpotID = kMars45NorthCrowbarSpotID; + break; + case kMapBiochip: + destSpotID = kRobotShuttleMapChipSpotID; + break; + case kOpticalBiochip: + destSpotID = kRobotShuttleOpticalChipSpotID; + break; + case kShieldBiochip: + destSpotID = kRobotShuttleShieldChipSpotID; + break; + default: + destSpotID = kNoHotSpotID; + break; + } + + if (destSpotID == kNoHotSpotID) + return Neighborhood::getItemScreenSpot(item, element); + + return _vm->getAllHotspots().findHotspotByID(destSpotID); +} + +void Mars::takeItemFromRoom(Item *item) { + switch (item->getObjectID()) { + case kAirMask: + setCurrentAlternate(kAltMarsTookMask); + break; + case kCardBomb: + _privateFlags.setFlag(kMarsPrivateDraggingBombFlag, true); + break; + case kMapBiochip: + _privateFlags.setFlag(kMarsPrivateGotMapChipFlag, true); + break; + case kShieldBiochip: + _privateFlags.setFlag(kMarsPrivateGotShieldChipFlag, true); + break; + case kOpticalBiochip: + _privateFlags.setFlag(kMarsPrivateGotOpticalChipFlag, true); + break; + } + + Neighborhood::takeItemFromRoom(item); +} + +void Mars::pickedUpItem(Item *item) { + switch (item->getObjectID()) { + case kAirMask: + setCurrentActivation(kActivateHotSpotAlways); + if (!GameState.getScoringGotOxygenMask()) { + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Mars/XM48SB", false, kWarningInterruption); + GameState.setScoringGotOxygenMask(); + } + break; + case kCrowbar: + GameState.setScoringGotCrowBar(); + g_AIArea->checkMiddleArea(); + break; + case kMarsCard: + GameState.setScoringGotMarsCard(); + g_AIArea->checkMiddleArea(); + break; + case kCardBomb: + GameState.setScoringGotCardBomb(); + if (GameState.getMarsLockBroken()) { + startExtraSequence(kMars57BackToNormal, kExtraCompletedFlag, kFilterNoInput); + GameState.setMarsLockBroken(false); + } + + _privateFlags.setFlag(kMarsPrivateDraggingBombFlag, false); + break; + case kMapBiochip: + if (_privateFlags.getFlag(kMarsPrivateGotMapChipFlag) && + _privateFlags.getFlag(kMarsPrivateGotShieldChipFlag) && + _privateFlags.getFlag(kMarsPrivateGotOpticalChipFlag)) { + GameState.setMarsFinished(true); + GameState.setScoringMarsGandhi(); + startExtraSequence(kMarsRobotHeadClose, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kShieldBiochip: + if (_privateFlags.getFlag(kMarsPrivateGotMapChipFlag) && + _privateFlags.getFlag(kMarsPrivateGotShieldChipFlag) && + _privateFlags.getFlag(kMarsPrivateGotOpticalChipFlag)) { + GameState.setMarsFinished(true); + GameState.setScoringMarsGandhi(); + startExtraSequence(kMarsRobotHeadClose, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kOpticalBiochip: + g_opticalChip->addAries(); + GameState.setScoringGotMarsOpMemChip(); + + if (_privateFlags.getFlag(kMarsPrivateGotMapChipFlag) && + _privateFlags.getFlag(kMarsPrivateGotShieldChipFlag) && + _privateFlags.getFlag(kMarsPrivateGotOpticalChipFlag)) { + GameState.setMarsFinished(true); + GameState.setScoringMarsGandhi(); + startExtraSequence(kMarsRobotHeadClose, kExtraCompletedFlag, kFilterNoInput); + } + break; + } +} + +void Mars::dropItemIntoRoom(Item *item, Hotspot *dropSpot) { + if (dropSpot->getObjectID() == kAttackRobotHotSpotID) { + _attackingItem = (InventoryItem *)item; + startExtraSequence(kMars48RobotDefends, kExtraCompletedFlag, kFilterNoInput); + loadLoopSound2(""); + } else { + switch (item->getObjectID()) { + case kMarsCard: + Neighborhood::dropItemIntoRoom(item, dropSpot); + if (dropSpot && dropSpot->getObjectID() == kMars34NorthCardDropSpotID) + startExtraSequence(kMarsTurnOnPod, kExtraCompletedFlag, kFilterNoInput); + break; + case kNitrogenCanister: + Neighborhood::dropItemIntoRoom(item, dropSpot); + if (dropSpot && dropSpot->getObjectID() == kMars57DropNitrogenSpotID) + startExtraSequence(kMars57FreezeLock, kExtraCompletedFlag, kFilterNoInput); + break; + case kCrowbar: + _utilityFuse.stopFuse(); + Neighborhood::dropItemIntoRoom(item, dropSpot); + if (dropSpot && dropSpot->getObjectID() == kMars57DropCrowBarSpotID) + startExtraSequence(kMars57BreakLock, kExtraCompletedFlag, kFilterNoInput); + break; + case kAirMask: + if (dropSpot) { + if (dropSpot->getObjectID() == kMars49AirFillingDropSpotID) { + if (!GameState.getMarsMaskOnFiller()) { + Neighborhood::dropItemIntoRoom(item, dropSpot); + startExtraSequence(kMars49SouthViewMaskFilling, kExtraCompletedFlag, kFilterNoInput); + } else { + setCurrentActivation(kActivateMaskOnFiller); + setCurrentAlternate(kAltMarsMaskOnFiller); + Neighborhood::dropItemIntoRoom(item, dropSpot); + } + } else if (dropSpot->getObjectID() == kMars49AirMaskSpotID) { + setCurrentAlternate(kAltMarsNormal); + setCurrentActivation(kActivateMaskOnHolder); + Neighborhood::dropItemIntoRoom(item, dropSpot); + } + } + break; + case kCardBomb: + _privateFlags.setFlag(kMarsPrivateDraggingBombFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + case kMapBiochip: + _privateFlags.setFlag(kMarsPrivateGotMapChipFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + case kShieldBiochip: + _privateFlags.setFlag(kMarsPrivateGotShieldChipFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + case kOpticalBiochip: + _privateFlags.setFlag(kMarsPrivateGotOpticalChipFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + default: + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + } + } +} + +void Mars::robotTiredOfWaiting() { + if (GameState.getCurrentRoomAndView() == MakeRoomView(kMars48, kEast)) { + if (_attackingItem) { + startExtraSequence(kMars48RobotKillsPlayer, kExtraCompletedFlag, kFilterNoInput); + loadLoopSound2(""); + } else { + _privateFlags.setFlag(kMarsPrivateRobotTiredOfWaitingFlag, true); + } + } else { + die(kDeathDidntGetOutOfWay); + } +} + +void Mars::turnLeft() { + if (isEventTimerRunning()) + cancelEvent(); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars34, kSouth): + if (_privateFlags.getFlag(kMarsPrivatePodStorageOpenFlag)) { + _privateFlags.setFlag(kMarsPrivatePodTurnLeftFlag, true); + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars34SpotCloseNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars34SpotCloseWithBar, kExtraCompletedFlag, kFilterNoInput); + } else { + Neighborhood::turnLeft(); + } + break; + case MakeRoomView(kMars45, kNorth): + if (_privateFlags.getFlag(kMarsPrivatePodStorageOpenFlag)) { + _privateFlags.setFlag(kMarsPrivatePodTurnLeftFlag, true); + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars45SpotCloseNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars45SpotCloseWithBar, kExtraCompletedFlag, kFilterNoInput); + } else { + Neighborhood::turnLeft(); + } + break; + default: + Neighborhood::turnLeft(); + break; + } +} + +void Mars::turnRight() { + if (isEventTimerRunning()) + cancelEvent(); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars34, kSouth): + if (_privateFlags.getFlag(kMarsPrivatePodStorageOpenFlag)) { + _privateFlags.setFlag(kMarsPrivatePodTurnRightFlag, true); + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars34SpotCloseNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars34SpotCloseWithBar, kExtraCompletedFlag, kFilterNoInput); + } else { + Neighborhood::turnRight(); + } + break; + case MakeRoomView(kMars45, kNorth): + if (_privateFlags.getFlag(kMarsPrivatePodStorageOpenFlag)) { + _privateFlags.setFlag(kMarsPrivatePodTurnRightFlag, true); + if (GameState.isTakenItemID(kCrowbar)) + startExtraSequence(kMars45SpotCloseNoBar, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMars45SpotCloseWithBar, kExtraCompletedFlag, kFilterNoInput); + } else { + Neighborhood::turnRight(); + } + break; + default: + Neighborhood::turnRight(); + break; + } +} + +void Mars::receiveNotification(Notification *notification, const NotificationFlags flag) { + InventoryItem *item; + + Neighborhood::receiveNotification(notification, flag); + + if ((flag & kExtraCompletedFlag) != 0) { + _interruptionFilter = kFilterAllInput; + + switch (_lastExtra) { + case kMarsArrivalFromTSA: + GameState.setMarsSeenTimeStream(true); + loadAmbientLoops(); + playSpotSoundSync(kMarsShuttle1DepartedIn, kMarsShuttle1DepartedOut); + makeContinuePoint(); + break; + case kRobotThrowsPlayer: + GameState.setMarsRobotThrownPlayer(true); + GameState.setScoringThrownByRobot(); + restoreStriding(kMars08, kNorth, kAltMarsNormal); + arriveAt(kMars08, kNorth); + if (!GameState.getMarsHeardUpperPodMessage()) { + playSpotSoundSync(kMarsPodDepartedUpperPlatformIn, + kMarsPodDepartedUpperPlatformOut); + GameState.setMarsHeardUpperPodMessage(true); + } + break; + case kMarsInfoKioskIntro: + GameState.setScoringSawMarsKiosk(); + setCurrentActivation(kActivationKioskChoice); + break; + case kMars33SlideShow4: + GameState.setScoringSawTransportMap(); + setCurrentActivation(kActivateHotSpotAlways); + break; + case kMars34SpotOpenNoBar: + case kMars34SpotOpenWithBar: + case kMars45SpotOpenNoBar: + case kMars45SpotOpenWithBar: + _privateFlags.setFlag(kMarsPrivatePodStorageOpenFlag, true); + setCurrentActivation(kActivateMarsPodOpen); + break; + case kMars34SpotCloseNoBar: + case kMars34SpotCloseWithBar: + case kMars45SpotCloseNoBar: + case kMars45SpotCloseWithBar: + _privateFlags.setFlag(kMarsPrivatePodStorageOpenFlag, false); + setCurrentActivation(kActivateMarsPodClosed); + if (_privateFlags.getFlag(kMarsPrivatePodTurnLeftFlag)) { + _privateFlags.setFlag(kMarsPrivatePodTurnLeftFlag, false); + turnLeft(); + } else if (_privateFlags.getFlag(kMarsPrivatePodTurnRightFlag)) { + _privateFlags.setFlag(kMarsPrivatePodTurnRightFlag, false); + turnRight(); + } + break; + case kMarsTurnOnPod: + item = (InventoryItem *)_vm->getAllItems().findItemByID(kMarsCard); + _vm->addItemToInventory(item); + GameState.setScoringTurnedOnTransport(); + loadLoopSound1(""); + loadLoopSound2(""); + startExtraSequence(kMarsTakePodToMars45, kExtraCompletedFlag, kFilterNoInput); + break; + case kMarsTakePodToMars45: + arriveAt(kMars45, kSouth); + break; + case kMars35WestSpinAirlockToEast: + GameState.setMarsAirlockOpen(false); + setCurrentAlternate(kAltMars35AirlockEast); + turnTo(kWest); + setCurrentActivation(kActivateReadyToPressurizeAirlock); + g_airMask->airQualityChanged(); + checkAirMask(); + loadAmbientLoops(); + break; + case kMars35EastSpinAirlockToWest: + GameState.setMarsAirlockOpen(true); + setCurrentAlternate(kAltMars35AirlockWest); + turnTo(kEast); + setCurrentActivation(kActivateReadyToPressurizeAirlock); + g_airMask->airQualityChanged(); + checkAirMask(); + loadAmbientLoops(); + break; + case kMars48RobotApproaches: + loadLoopSound2("Sounds/Mars/Robot Loop.aiff", 0x100, 0, 0); + GameState.setMarsSeenRobotAtReactor(true); + loopExtraSequence(kMars48RobotLoops); + _utilityFuse.primeFuse(kMarsRobotPatienceLimit); + _utilityFuse.setFunctor(new Common::Functor0Mem<void, Mars>(this, &Mars::robotTiredOfWaiting)); + _utilityFuse.lightFuse(); + break; + case kMars48RobotDefends: + _vm->addItemToInventory(_attackingItem); + _attackingItem = 0; + if (_privateFlags.getFlag(kMarsPrivateRobotTiredOfWaitingFlag)) { + startExtraSequence(kMars48RobotKillsPlayer, kExtraCompletedFlag, kFilterNoInput); + loadLoopSound2("", 0x100, 0, 0); + } else { + loadLoopSound2("Sounds/Mars/Robot Loop.aiff", 0x100, 0, 0); + loopExtraSequence(kMars48RobotLoops, kExtraCompletedFlag); + } + break; + case kMars48RobotKillsPlayer: + loadLoopSound2(""); + die(kDeathDidntGetOutOfWay); + break; + case kMars49SouthViewMaskFilling: + setCurrentActivation(kActivateMaskOnFiller); + setCurrentAlternate(kAltMarsMaskOnFiller); + GameState.setMarsMaskOnFiller(true); + break; + case kMars58SpinLeft: + case kMars54SpinRight: + GameState.setScoringActivatedPlatform(); + arriveAt(kMars52, kEast); + break; + case kMars52SpinLeft: + case kMars56SpinRight: + GameState.setScoringActivatedPlatform(); + arriveAt(kMars54, kEast); + break; + case kMars54SpinLeft: + case kMars58SpinRight: + GameState.setScoringActivatedPlatform(); + arriveAt(kMars56, kEast); + break; + case kMars56SpinLeft: + case kMars52SpinRight: + GameState.setScoringActivatedPlatform(); + arriveAt(kMars58, kEast); + break; + case kMars52Extend: + case kMars54Extend: + case kMars56ExtendNoBomb: + case kMars58Extend: + GameState.setScoringActivatedPlatform(); + setCurrentActivation(kActivateReactorPlatformIn); + _privateFlags.setFlag(kMarsPrivatePlatformZoomedInFlag, true); + break; + case kMars53Retract: + case kMars55Retract: + case kMars57RetractWithBomb: + case kMars57RetractNoBomb: + case kMars59Retract: + GameState.setScoringActivatedPlatform(); + setCurrentActivation(kActivateReactorPlatformOut); + _privateFlags.setFlag(kMarsPrivatePlatformZoomedInFlag, false); + break; + case kMars56ExtendWithBomb: + playSpotSoundSync(kMustBeUnlockedIn, kMustBeUnlockedOut); + GameState.setScoringActivatedPlatform(); + _privateFlags.setFlag(kMarsPrivatePlatformZoomedInFlag, true); + break; + case kMars57CantOpenPanel: + GameState.setScoringActivatedPlatform(); + setCurrentActivation(kActivateReactorAskLowerScreen); + break; + case kMars57LowerScreenClosed: + case kMars57ThawLock: + setCurrentActivation(kActivateReactorReadyForNitrogen); + GameState.setMarsLockFrozen(false); + break; + case kMars57FreezeLock: + item = (InventoryItem *)_vm->getAllItems().findItemByID(kNitrogenCanister); + item->setItemState(kNitrogenEmpty); + _vm->addItemToInventory(item); + setCurrentActivation(kActivateReactorReadyForCrowBar); + GameState.setScoringUsedLiquidNitrogen(); + GameState.setMarsLockFrozen(true); + showExtraView(kMars57LockFrozenView); + _utilityFuse.primeFuse(kLockFreezeTimeLmit); + _utilityFuse.setFunctor(new Common::Functor0Mem<void, Mars>(this, &Mars::lockThawed)); + _utilityFuse.lightFuse(); + break; + case kMars57BreakLock: + item = (InventoryItem *)_vm->getAllItems().findItemByID(kCrowbar); + _vm->addItemToInventory(item); + GameState.setScoringUsedCrowBar(); + GameState.setMarsLockBroken(true); + GameState.setMarsLockFrozen(false); + startExtraLongSequence(kMars57OpenPanel, kMars57OpenPanelChoices, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars57OpenPanel: + case kMars57OpenPanelChoices: + setCurrentActivation(kActivateReactorAskOperation); + break; + case kMars57ShieldEvaluation: + case kMars57MeasureOutput: + setCurrentActivation(kActivateReactorRanEvaluation); + loopExtraSequence(kMars57ShieldOkayLoop); + break; + case kMars57RunDiagnostics: + setCurrentActivation(kActivateReactorRanDiagnostics); + GameState.setScoringFoundCardBomb(); + break; + case kMars57BombExplodes: + case kMars57BombExplodesInGame: + die(kDeathDidntDisarmMarsBomb); + break; + case kMars57BombAnalysis: + setCurrentActivation(kActivateReactorAnalyzed); + break; + case kMars57DontLink: + startExtraSequence(kMars57OpenPanelChoices, kExtraCompletedFlag, kFilterNoInput); + break; + case kMars57CircuitLink: + setCurrentActivation(kActivateReactorInstructions); + break; + case kMars57GameLevel1: + setUpReactorLevel1(); + break; + case kMars57GameLevel2: + case kMars57GameLevel3: + setUpNextReactorLevel(); + break; + case kMars57GameSolved: + setCurrentActivation(kActivateReactorBombSafe); + break; + case kMars57ExposeBomb: + setCurrentActivation(kActivateReactorBombExposed); + _privateFlags.setFlag(kMarsPrivateBombExposedFlag, true); + break; + case kMars57BackToNormal: + setCurrentActivation(kActivateReactorPlatformIn); + _privateFlags.setFlag(kMarsPrivateBombExposedFlag, false); + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Mars/XM51SW", false, kWarningInterruption); + break; + case kMars60WestSpinAirlockToEast: + GameState.setMarsAirlockOpen(true); + setCurrentAlternate(kAltMars60AirlockEast); + turnTo(kWest); + setCurrentActivation(kActivateReadyToPressurizeAirlock); + g_airMask->airQualityChanged(); + checkAirMask(); + loadAmbientLoops(); + break; + case kMars60EastSpinAirlockToWest: + GameState.setMarsAirlockOpen(false); + setCurrentAlternate(kAltMars60AirlockWest); + turnTo(kEast); + setCurrentActivation(kActivateReadyToPressurizeAirlock); + g_airMask->airQualityChanged(); + checkAirMask(); + loadAmbientLoops(); + break; + case kMarsRobotHeadOpen: + setCurrentActivation(kActivationRobotHeadOpen); + break; + case kMarsRobotHeadClose: + recallToTSASuccess(); + break; + case kMarsMaze007RobotApproach: + case kMarsMaze015SouthRobotApproach: + case kMarsMaze101EastRobotApproach: + case kMarsMaze104WestLoop: + case kMarsMaze133SouthApproach: + case kMarsMaze136NorthApproach: + case kMarsMaze184WestLoop: + die(kDeathGroundByMazebot); + break; + } + } else if ((flag & kTimeForCanyonChaseFlag) != 0) { + doCanyonChase(); + } else if ((flag & kExplosionFinishedFlag) != 0) { + _explosions.stop(); + _explosions.hide(); + if (g_robotShip->isDead()) { + GameState.setMarsFinished(true); + _centerShuttleMovie.hide(); + _upperRightShuttleMovie.show(); + _upperRightShuttleMovie.setTime(kShuttleUpperRightTargetDestroyedTime); + _upperRightShuttleMovie.redrawMovieWorld(); + _rightDamageShuttleMovie.hide(); + playMovieSegment(&_rightShuttleMovie, kShuttleRightDestroyedStart, kShuttleRightDestroyedStop); + playSpotSoundSync(kShuttleDestroyedIn, kShuttleDestroyedOut); + throwAwayMarsShuttle(); + reinstateMonocleInterface(); + recallToTSASuccess(); + } + } else if ((flag & kTimeToTransportFlag) != 0) { + transportToRobotShip(); + } + + if (g_AIArea) + g_AIArea->checkMiddleArea(); +} + +void Mars::spotCompleted() { + Neighborhood::spotCompleted(); + + if (GameState.getCurrentRoom() == kMarsRobotShuttle) + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/Mars/XN59WD", false, kWarningInterruption); +} + +void Mars::doCanyonChase() { + GameState.setScoringEnteredShuttle(); + setNextHandler(_vm); + throwAwayInterface(); + + _vm->_cursor->hide(); + + // Open the spot sounds movie again... + _spotSounds.initFromQuickTime(getSoundSpotsName()); + _spotSounds.setVolume(_vm->getSoundFXLevel()); + + Video::VideoDecoder *video = new Video::QuickTimeDecoder(); + if (!video->loadFile("Images/Mars/M44ESA.movie")) + error("Could not load interface->shuttle transition video"); + + video->start(); + + while (!_vm->shouldQuit() && !video->endOfVideo()) { + if (video->needsUpdate()) { + const Graphics::Surface *frame = video->decodeNextFrame(); + + if (frame) + _vm->drawScaledFrame(frame, 0, 0); + } + + Common::Event event; + while (g_system->getEventManager()->pollEvent(event)) + ; + + g_system->delayMillis(10); + } + + delete video; + + if (_vm->shouldQuit()) + return; + + initOnePicture(&_shuttleInterface1, "Images/Mars/MCmain1.pict", kShuttleBackgroundOrder, kShuttle1Left, + kShuttle1Top, true); + initOnePicture(&_shuttleInterface2, "Images/Mars/MCmain2.pict", kShuttleBackgroundOrder, kShuttle2Left, + kShuttle2Top, true); + initOnePicture(&_shuttleInterface3, "Images/Mars/MCmain3.pict", kShuttleBackgroundOrder, kShuttle3Left, + kShuttle3Top, true); + initOnePicture(&_shuttleInterface4, "Images/Mars/MCmain4.pict", kShuttleBackgroundOrder, kShuttle4Left, + kShuttle4Top, true); + + initOneMovie(&_canyonChaseMovie, "Images/Mars/Canyon.movie", + kShuttleMonitorOrder, kShuttleWindowLeft, kShuttleWindowTop, true); + _canyonChaseMovie.setVolume(_vm->getSoundFXLevel()); + + loadLoopSound1("Sounds/Mars/Inside Cockpit.22K.8.AIFF"); + + // Swing shuttle around... + playMovieSegment(&_canyonChaseMovie, kShuttleSwingStart, kShuttleSwingStop); + + initOneMovie(&_leftShuttleMovie, "Images/Mars/Left Shuttle.movie", + kShuttleMonitorOrder, kShuttleLeftLeft, kShuttleLeftTop, false); + + initOneMovie(&_rightShuttleMovie, "Images/Mars/Right Shuttle.movie", + kShuttleMonitorOrder, kShuttleRightLeft, kShuttleRightTop, false); + + initOneMovie(&_lowerLeftShuttleMovie, "Images/Mars/Lower Left Shuttle.movie", kShuttleMonitorOrder, + kShuttleLowerLeftLeft, kShuttleLowerLeftTop, false); + + initOneMovie(&_lowerRightShuttleMovie, "Images/Mars/Lower Right Shuttle.movie", kShuttleMonitorOrder, + kShuttleLowerRightLeft, kShuttleLowerRightTop, false); + + initOneMovie(&_centerShuttleMovie, "Images/Mars/Center Shuttle.movie", + kShuttleMonitorOrder, kShuttleCenterLeft, kShuttleCenterTop, false); + + initOneMovie(&_upperLeftShuttleMovie, "Images/Mars/Upper Left Shuttle.movie", kShuttleMonitorOrder, + kShuttleUpperLeftLeft, kShuttleUpperLeftTop, false); + + initOneMovie(&_upperRightShuttleMovie, "Images/Mars/Upper Right Shuttle.movie", kShuttleMonitorOrder, + kShuttleUpperRightLeft, kShuttleUpperRightTop, false); + + initOneMovie(&_leftDamageShuttleMovie, "Images/Mars/Left Damage Shuttle.movie", + kShuttleStatusOrder, kShuttleLeftEnergyLeft, kShuttleLeftEnergyTop, false); + + initOneMovie(&_rightDamageShuttleMovie, "Images/Mars/Right Damage Shuttle.movie", + kShuttleStatusOrder, kShuttleRightEnergyLeft, kShuttleRightEnergyTop, false); + + _centerShuttleMovie.show(); + _centerShuttleMovie.setTime(kShuttleCenterBoardingTime); + playSpotSoundSync(kShuttleCockpitIn, kShuttleCockpitOut); + + _centerShuttleMovie.setTime(kShuttleCenterCheckTime); + playSpotSoundSync(kShuttleOnboardIn, kShuttleOnboardOut); + + _shuttleEnergyMeter.initShuttleEnergyMeter(); + _shuttleEnergyMeter.powerUpMeter(); + while (_shuttleEnergyMeter.isFading()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + g_system->updateScreen(); + } + + _leftShuttleMovie.show(); + playMovieSegment(&_leftShuttleMovie, kShuttleLeftIntroStart, kShuttleLeftIntroStop); + + _leftShuttleMovie.setTime(kShuttleLeftNormalTime); + _leftShuttleMovie.redrawMovieWorld(); + + _leftDamageShuttleMovie.show(); + playMovieSegment(&_leftDamageShuttleMovie); + + // Take it down a tick initially. This sets the time to the time of the last tick, + // so that subsequence drops will drop it down a tick. + _leftDamageShuttleMovie.setTime(_leftDamageShuttleMovie.getTime() - 40); + _leftDamageShuttleMovie.redrawMovieWorld(); + + _lowerRightShuttleMovie.show(); + _lowerRightShuttleMovie.setTime(kShuttleLowerRightOffTime); + _lowerRightShuttleMovie.redrawMovieWorld(); + _centerShuttleMovie.setTime(kShuttleCenterNavCompTime); + _centerShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttleNavigationIn, kShuttleNavigationOut); + + _centerShuttleMovie.setTime(kShuttleCenterCommTime); + _centerShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttleCommunicationIn, kShuttleCommunicationOut); + + _centerShuttleMovie.setTime(kShuttleCenterAllSystemsTime); + _centerShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttleAllSystemsIn, kShuttleAllSystemsOut); + + _centerShuttleMovie.setTime(kShuttleCenterSecureLooseTime); + _centerShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttleSecureLooseIn, kShuttleSecureLooseOut); + + _centerShuttleMovie.setTime(kShuttleCenterAutoTestTime); + _centerShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttleAutoTestingIn, kShuttleAutoTestingOut); + + _leftShuttleMovie.setTime(kShuttleLeftAutoTestTime); + _leftShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kMarsThrusterAutoTestIn, kMarsThrusterAutoTestOut); + _leftShuttleMovie.setTime(kShuttleLeftNormalTime); + _leftShuttleMovie.redrawMovieWorld(); + + _centerShuttleMovie.setTime(kShuttleCenterLaunchTime); + _centerShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttlePrepareForDropIn, kShuttlePrepareForDropOut); + + playSpotSoundSync(kShuttleAllClearIn, kShuttleAllClearOut); + + _centerShuttleMovie.setTime(kShuttleCenterEnterTubeTime); + _centerShuttleMovie.redrawMovieWorld(); + + _lowerLeftShuttleMovie.show(); + _lowerLeftShuttleMovie.setTime(kShuttleLowerLeftCollisionTime); + + loadLoopSound1(""); + + _canyonChaseMovie.setSegment(kCanyonChaseStart, kCanyonChaseStop); + _canyonChaseMovie.start(); + + startMarsTimer(kLaunchTubeReachedTime, kMovieTicksPerSecond, kMarsLaunchTubeReached); +} + +void Mars::startUpFromFinishedSpaceChase() { + setNextHandler(_vm); + throwAwayInterface(); + + initOnePicture(&_shuttleInterface1, "Images/Mars/MCmain1.pict", kShuttleBackgroundOrder, kShuttle1Left, + kShuttle1Top, true); + initOnePicture(&_shuttleInterface2, "Images/Mars/MCmain2.pict", kShuttleBackgroundOrder, kShuttle2Left, + kShuttle2Top, true); + initOnePicture(&_shuttleInterface3, "Images/Mars/MCmain3.pict", kShuttleBackgroundOrder, kShuttle3Left, + kShuttle3Top, true); + initOnePicture(&_shuttleInterface4, "Images/Mars/MCmain4.pict", kShuttleBackgroundOrder, kShuttle4Left, + kShuttle4Top, true); + + initOneMovie(&_leftShuttleMovie, "Images/Mars/Left Shuttle.movie", + kShuttleMonitorOrder, kShuttleLeftLeft, kShuttleLeftTop, false); + + initOneMovie(&_rightShuttleMovie, "Images/Mars/Right Shuttle.movie", + kShuttleMonitorOrder, kShuttleRightLeft, kShuttleRightTop, false); + + initOneMovie(&_lowerLeftShuttleMovie, "Images/Mars/Lower Left Shuttle.movie", kShuttleMonitorOrder, + kShuttleLowerLeftLeft, kShuttleLowerLeftTop, false); + + initOneMovie(&_lowerRightShuttleMovie, "Images/Mars/Lower Right Shuttle.movie", kShuttleMonitorOrder, + kShuttleLowerRightLeft, kShuttleLowerRightTop, false); + + initOneMovie(&_centerShuttleMovie, "Images/Mars/Center Shuttle.movie", + kShuttleMonitorOrder, kShuttleCenterLeft, kShuttleCenterTop, false); + + initOneMovie(&_upperLeftShuttleMovie, "Images/Mars/Upper Left Shuttle.movie", kShuttleMonitorOrder, + kShuttleUpperLeftLeft, kShuttleUpperLeftTop, false); + + initOneMovie(&_upperRightShuttleMovie, "Images/Mars/Upper Right Shuttle.movie", kShuttleMonitorOrder, + kShuttleUpperRightLeft, kShuttleUpperRightTop, false); + + initOneMovie(&_leftDamageShuttleMovie, "Images/Mars/Left Damage Shuttle.movie", + kShuttleStatusOrder, kShuttleLeftEnergyLeft, kShuttleLeftEnergyTop, false); + + initOneMovie(&_rightDamageShuttleMovie, "Images/Mars/Right Damage Shuttle.movie", + kShuttleStatusOrder, kShuttleRightEnergyLeft, kShuttleRightEnergyTop, false); + + _centerShuttleMovie.show(); + + _shuttleEnergyMeter.initShuttleEnergyMeter(); + _shuttleEnergyMeter.setEnergyValue(kFullShuttleEnergy); + + _leftShuttleMovie.show(); + _leftShuttleMovie.setTime(kShuttleLeftNormalTime); + _leftShuttleMovie.redrawMovieWorld(); + + _leftDamageShuttleMovie.show(); + _leftDamageShuttleMovie.setTime(_leftDamageShuttleMovie.getDuration() - 40); + _leftDamageShuttleMovie.redrawMovieWorld(); + + _lowerRightShuttleMovie.show(); + + _lowerLeftShuttleMovie.show(); + + loadLoopSound1("Sounds/Mars/Space Ambient.22K.8.AIFF"); + + initOneMovie(&_junk, "Images/Mars/Junk.movie", kShuttleJunkOrder, kShuttleJunkLeft, + kShuttleJunkTop, false); + + initOneMovie(&_explosions, "Images/Mars/Explosions.movie", kShuttleWeaponFrontOrder, 0, 0, false); + _explosionCallBack.initCallBack(&_explosions, kCallBackAtExtremes); + + _energyBeam.initShuttleWeapon(); + _gravitonCannon.initShuttleWeapon(); + + _upperLeftShuttleMovie.show(); + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftDimTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + + _rightShuttleMovie.show(); + _rightShuttleMovie.setTime(kShuttleRightIntroStop - 1); + _rightShuttleMovie.redrawMovieWorld(); + + _rightDamageShuttleMovie.show(); + _rightDamageShuttleMovie.setTime(40); + _rightDamageShuttleMovie.redrawMovieWorld(); + + _lowerLeftShuttleMovie.setTime(kShuttleLowerLeftAutopilotTime); + _lowerLeftShuttleMovie.redrawMovieWorld(); + + _shuttleTransportSpot.setArea(kShuttleTransportBounds); + _shuttleTransportSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + _vm->getAllHotspots().push_back(&_shuttleTransportSpot); + + _privateFlags.setFlag(kMarsPrivateInSpaceChaseFlag, true); + + _upperRightShuttleMovie.show(); + _upperRightShuttleMovie.setTime(kShuttleUpperRightOverloadTime); + _upperRightShuttleMovie.redrawMovieWorld(); + + _centerShuttleMovie.setTime(kShuttleCenterSafeTime); + _centerShuttleMovie.redrawMovieWorld(); + + _lowerRightShuttleMovie.setTime(kShuttleLowerRightTransportTime); + _lowerRightShuttleMovie.redrawMovieWorld(); + + initOneMovie(&_canyonChaseMovie, "Images/Mars/M98EAS.movie", kShuttleTractorBeamMovieOrder, + kShuttleWindowLeft, kShuttleWindowTop, true); + _canyonChaseMovie.setTime(_canyonChaseMovie.getDuration()); + _canyonChaseMovie.redrawMovieWorld(); +} + +void Mars::startUpFromSpaceChase() { + setNextHandler(_vm); + throwAwayInterface(); + + // Open the spot sounds movie again... + _spotSounds.initFromQuickTime(getSoundSpotsName()); + _spotSounds.setVolume(_vm->getSoundFXLevel());; + + initOnePicture(&_shuttleInterface1, "Images/Mars/MCmain1.pict", kShuttleBackgroundOrder, kShuttle1Left, + kShuttle1Top, true); + initOnePicture(&_shuttleInterface2, "Images/Mars/MCmain2.pict", kShuttleBackgroundOrder, kShuttle2Left, + kShuttle2Top, true); + initOnePicture(&_shuttleInterface3, "Images/Mars/MCmain3.pict", kShuttleBackgroundOrder, kShuttle3Left, + kShuttle3Top, true); + initOnePicture(&_shuttleInterface4, "Images/Mars/MCmain4.pict", kShuttleBackgroundOrder, kShuttle4Left, + kShuttle4Top, true); + + initOneMovie(&_leftShuttleMovie, "Images/Mars/Left Shuttle.movie", + kShuttleMonitorOrder, kShuttleLeftLeft, kShuttleLeftTop, false); + + initOneMovie(&_rightShuttleMovie, "Images/Mars/Right Shuttle.movie", + kShuttleMonitorOrder, kShuttleRightLeft, kShuttleRightTop, false); + + initOneMovie(&_lowerLeftShuttleMovie, "Images/Mars/Lower Left Shuttle.movie", kShuttleMonitorOrder, + kShuttleLowerLeftLeft, kShuttleLowerLeftTop, false); + + initOneMovie(&_lowerRightShuttleMovie, "Images/Mars/Lower Right Shuttle.movie", kShuttleMonitorOrder, + kShuttleLowerRightLeft, kShuttleLowerRightTop, false); + + initOneMovie(&_centerShuttleMovie, "Images/Mars/Center Shuttle.movie", + kShuttleMonitorOrder, kShuttleCenterLeft, kShuttleCenterTop, false); + + initOneMovie(&_upperLeftShuttleMovie, "Images/Mars/Upper Left Shuttle.movie", kShuttleMonitorOrder, + kShuttleUpperLeftLeft, kShuttleUpperLeftTop, false); + + initOneMovie(&_upperRightShuttleMovie, "Images/Mars/Upper Right Shuttle.movie", kShuttleMonitorOrder, + kShuttleUpperRightLeft, kShuttleUpperRightTop, false); + + initOneMovie(&_leftDamageShuttleMovie, "Images/Mars/Left Damage Shuttle.movie", + kShuttleStatusOrder, kShuttleLeftEnergyLeft, kShuttleLeftEnergyTop, false); + + initOneMovie(&_rightDamageShuttleMovie, "Images/Mars/Right Damage Shuttle.movie", + kShuttleStatusOrder, kShuttleRightEnergyLeft, kShuttleRightEnergyTop, false); + + _centerShuttleMovie.show(); + + _shuttleEnergyMeter.initShuttleEnergyMeter(); + _shuttleEnergyMeter.setEnergyValue(kFullShuttleEnergy); + + _leftShuttleMovie.show(); + _leftShuttleMovie.setTime(kShuttleLeftNormalTime); + _leftShuttleMovie.redrawMovieWorld(); + + _leftDamageShuttleMovie.show(); + _leftDamageShuttleMovie.setTime(_leftDamageShuttleMovie.getDuration() - 40); + _leftDamageShuttleMovie.redrawMovieWorld(); + + _lowerRightShuttleMovie.show(); + + _lowerLeftShuttleMovie.show(); + + loadLoopSound1("Sounds/Mars/Space Ambient.22K.8.AIFF"); + + initOneMovie(&_planetMovie, "Images/Mars/Planet.movie", kShuttlePlanetOrder, + kPlanetStartLeft, kPlanetStartTop, true); + _planetMovie.setFlags(kLoopTimeBase); + + initOneMovie(&_junk, "Images/Mars/Junk.movie", kShuttleJunkOrder, kShuttleJunkLeft, + kShuttleJunkTop, false); + + initOneMovie(&_explosions, "Images/Mars/Explosions.movie", kShuttleWeaponFrontOrder, 0, 0, false); + _explosionCallBack.initCallBack(&_explosions, kCallBackAtExtremes); + + _energyBeam.initShuttleWeapon(); + _gravitonCannon.initShuttleWeapon(); + + _upperLeftShuttleMovie.show(); + + _robotShip.initRobotShip(); + + _planetMovie.start(); + _planetMover.startMoving(&_planetMovie); + + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftDimTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + + _centerShuttleMovie.setTime(kShuttleCenterTargetSightedTime); + _centerShuttleMovie.redrawMovieWorld(); + + _lowerRightShuttleMovie.setTime(kShuttleLowerRightTrackingTime); + _lowerRightShuttleMovie.redrawMovieWorld(); + + _rightShuttleMovie.show(); + _rightShuttleMovie.setTime(kShuttleRightIntroStop - 1); + _rightShuttleMovie.redrawMovieWorld(); + + _rightDamageShuttleMovie.show(); + _rightDamageShuttleMovie.setTime(_rightDamageShuttleMovie.getDuration() - 40); + _rightDamageShuttleMovie.redrawMovieWorld(); + + _lowerLeftShuttleMovie.setTime(kShuttleLowerLeftAutopilotTime); + _lowerLeftShuttleMovie.redrawMovieWorld(); + + _robotShip.startMoving(); + + _shuttleHUD.initShuttleHUD(); + + _tractorBeam.startDisplaying(); + + _energyChoiceSpot.setArea(kShuttleEnergyBeamBounds); + _energyChoiceSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + _vm->getAllHotspots().push_back(&_energyChoiceSpot); + _gravitonChoiceSpot.setArea(kShuttleGravitonBounds); + _gravitonChoiceSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + _vm->getAllHotspots().push_back(&_gravitonChoiceSpot); + _tractorChoiceSpot.setArea(kShuttleTractorBounds); + _tractorChoiceSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + _vm->getAllHotspots().push_back(&_tractorChoiceSpot); + _shuttleViewSpot.setArea(kShuttleWindowLeft, kShuttleWindowTop, + kShuttleWindowLeft + kShuttleWindowWidth, kShuttleWindowTop + kShuttleWindowHeight); + _shuttleViewSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + _vm->getAllHotspots().push_back(&_shuttleViewSpot); + _shuttleTransportSpot.setArea(kShuttleTransportBounds); + _shuttleTransportSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + _vm->getAllHotspots().push_back(&_shuttleTransportSpot); + + _privateFlags.setFlag(kMarsPrivateInSpaceChaseFlag, true); + + startMarsTimer(kSpaceChaseTimeLimit, kOneTickPerSecond, kMarsSpaceChaseFinished); +} + +void Mars::setSoundFXLevel(const uint16 level) { + Neighborhood::setSoundFXLevel(level); + + if (_canyonChaseMovie.isMovieValid()) + _canyonChaseMovie.setVolume(level); + + if (_explosions.isMovieValid()) + _explosions.setVolume(level); +} + +void Mars::startMarsTimer(TimeValue time, TimeScale scale, MarsTimerCode code) { + _utilityFuse.primeFuse(time, scale); + _marsEvent.mars = this; + _marsEvent.event = code; + _utilityFuse.setFunctor(new Common::Functor0Mem<void, MarsTimerEvent>(&_marsEvent, &MarsTimerEvent::fire)); + _utilityFuse.lightFuse(); +} + +void Mars::marsTimerExpired(MarsTimerEvent &event) { + Common::Rect r; + uint16 x, y; + + switch (event.event) { + case kMarsLaunchTubeReached: + _lowerLeftShuttleMovie.setTime(kShuttleLowerLeftTubeTime); + _lowerLeftShuttleMovie.redrawMovieWorld(); + startMarsTimer(kCanyonChaseFinishedTime, kMovieTicksPerSecond, kMarsCanyonChaseFinished); + break; + case kMarsCanyonChaseFinished: + GameState.setScoringEnteredLaunchTube(); + + while (_canyonChaseMovie.isRunning()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + _canyonChaseMovie.stop(); + _canyonChaseMovie.stopDisplaying(); + _canyonChaseMovie.releaseMovie(); + + _vm->_gfx->enableErase(); + + loadLoopSound1("Sounds/Mars/Space Ambient.22K.8.AIFF"); + + playSpotSoundSync(kShuttleConfiguringIn, kShuttleConfiguringOut); + playSpotSoundSync(kShuttleGeneratingIn, kShuttleGeneratingOut); + playSpotSoundSync(kShuttleBreakawayIn, kShuttleBreakawayOut); + playSpotSoundSync(kMarsAtmosphericBreakawayIn, kMarsAtmosphericBreakawayOut); + + initOneMovie(&_planetMovie, "Images/Mars/Planet.movie", kShuttlePlanetOrder, kPlanetStartLeft, kPlanetStartTop, true); + _planetMovie.setFlags(kLoopTimeBase); + + initOneMovie(&_junk, "Images/Mars/Junk.movie", kShuttleJunkOrder, kShuttleJunkLeft, kShuttleJunkTop, false); + + initOneMovie(&_explosions, "Images/Mars/Explosions.movie", kShuttleWeaponFrontOrder, 0, 0, false); + _explosionCallBack.initCallBack(&_explosions, kCallBackAtExtremes); + + _energyBeam.initShuttleWeapon(); + _gravitonCannon.initShuttleWeapon(); + + _centerShuttleMovie.setTime(kShuttleCenterWeaponsTime); + _centerShuttleMovie.redrawMovieWorld(); + + _upperLeftShuttleMovie.show(); + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftDampingTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + + _robotShip.initRobotShip(); + + _planetMovie.start(); + _planetMover.startMoving(&_planetMovie); + + playSpotSoundSync(kShuttleDamperDescIn, kShuttleDamperDescOut); + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftGravitonTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + + playSpotSoundSync(kShuttleGravitonDescIn, kShuttleGravitonDescOut); + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftTractorTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + + playSpotSoundSync(kShuttleTractorDescIn, kShuttleTractorDescOut); + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftDimTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + + _centerShuttleMovie.setTime(kShuttleCenterTargetSightedTime); + _centerShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttleTargetSightedIn, kShuttleTargetSightedOut); + + _lowerRightShuttleMovie.setTime(kShuttleLowerRightTrackingTime); + _lowerRightShuttleMovie.redrawMovieWorld(); + _rightShuttleMovie.show(); + playMovieSegment(&_rightShuttleMovie, kShuttleRightIntroStart, kShuttleRightIntroStop); + + _rightDamageShuttleMovie.show(); + playMovieSegment(&_rightDamageShuttleMovie); + + // Take it down a tick initially. This sets the time to the time of the last tick, + // so that subsequence drops will drop it down a tick. + _rightDamageShuttleMovie.setTime(_rightDamageShuttleMovie.getTime() - 40); + _rightDamageShuttleMovie.redrawMovieWorld(); + + _lowerLeftShuttleMovie.setTime(kShuttleLowerLeftAutopilotTime); + _lowerLeftShuttleMovie.redrawMovieWorld(); + playSpotSoundSync(kShuttleAutopilotEngagedIn, kShuttleAutopilotEngagedOut); + + _robotShip.startMoving(); + + _shuttleHUD.initShuttleHUD(); + + _tractorBeam.startDisplaying(); + + _energyChoiceSpot.setArea(kShuttleEnergyBeamBounds); + _energyChoiceSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + _vm->getAllHotspots().push_back(&_energyChoiceSpot); + _gravitonChoiceSpot.setArea(kShuttleGravitonBounds); + _gravitonChoiceSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + _vm->getAllHotspots().push_back(&_gravitonChoiceSpot); + _tractorChoiceSpot.setArea(kShuttleTractorBounds); + _tractorChoiceSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + _vm->getAllHotspots().push_back(&_tractorChoiceSpot); + _shuttleViewSpot.setArea(kShuttleWindowLeft, kShuttleWindowTop, + kShuttleWindowLeft + kShuttleWindowWidth, kShuttleWindowTop + kShuttleWindowHeight); + _shuttleViewSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + _vm->getAllHotspots().push_back(&_shuttleViewSpot); + _shuttleTransportSpot.setArea(kShuttleTransportBounds); + _shuttleTransportSpot.setHotspotFlags(kNeighborhoodSpotFlag | kClickSpotFlag); + _vm->getAllHotspots().push_back(&_shuttleTransportSpot); + + _privateFlags.setFlag(kMarsPrivateInSpaceChaseFlag, true); + + playSpotSoundSync(kMarsCockpitChatterIn, kMarsCockpitChatterOut); + + GameState.setMarsFinishedCanyonChase(true); + + startMarsTimer(kSpaceChaseTimeLimit, kOneTickPerSecond, kMarsSpaceChaseFinished); + + _vm->_cursor->hideUntilMoved(); + break; + case kMarsSpaceChaseFinished: + // Player failed to stop the robot in time... + _interruptionFilter = kFilterNoInput; + + _rightShuttleMovie.setTime(kShuttleRightTargetLockTime); + _rightShuttleMovie.redrawMovieWorld(); + + _upperRightShuttleMovie.show(); + _upperRightShuttleMovie.setTime(kShuttleUpperRightLockedTime); + _upperRightShuttleMovie.redrawMovieWorld(); + + _rightShuttleMovie.setTime(kShuttleRightGravitonTime); + _rightShuttleMovie.redrawMovieWorld(); + _upperRightShuttleMovie.setTime(kShuttleUpperRightArmedTime); + _upperRightShuttleMovie.redrawMovieWorld(); + + _vm->delayShell(3, 1); + + x = _vm->getRandomNumber(19); + y = _vm->getRandomNumber(19); + + r = Common::Rect(kShuttleWindowMidH - x, kShuttleWindowMidV - y, + kShuttleWindowMidH - x + 20, kShuttleWindowMidV - y + 20); + showBigExplosion(r, kShuttleAlienShipOrder); + + while (_explosions.isRunning()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + g_system->delayMillis(10); + } + + throwAwayMarsShuttle(); + reinstateMonocleInterface(); + recallToTSAFailure(); + break; + default: + break; + } + + _interruptionFilter = kFilterAllInput; +} + +void Mars::throwAwayMarsShuttle() { + _shuttleInterface1.deallocateSurface(); + _shuttleInterface1.stopDisplaying(); + _shuttleInterface2.deallocateSurface(); + _shuttleInterface2.stopDisplaying(); + _shuttleInterface3.deallocateSurface(); + _shuttleInterface3.stopDisplaying(); + _shuttleInterface4.deallocateSurface(); + _shuttleInterface4.stopDisplaying(); + + _spotSounds.disposeSound(); + + _canyonChaseMovie.releaseMovie(); + _canyonChaseMovie.stopDisplaying(); + _leftShuttleMovie.releaseMovie(); + _leftShuttleMovie.stopDisplaying(); + _rightShuttleMovie.releaseMovie(); + _rightShuttleMovie.stopDisplaying(); + _lowerLeftShuttleMovie.releaseMovie(); + _lowerLeftShuttleMovie.stopDisplaying(); + _lowerRightShuttleMovie.releaseMovie(); + _lowerRightShuttleMovie.stopDisplaying(); + _centerShuttleMovie.releaseMovie(); + _centerShuttleMovie.stopDisplaying(); + _upperLeftShuttleMovie.releaseMovie(); + _upperLeftShuttleMovie.stopDisplaying(); + _upperRightShuttleMovie.releaseMovie(); + _upperRightShuttleMovie.stopDisplaying(); + _leftDamageShuttleMovie.releaseMovie(); + _leftDamageShuttleMovie.stopDisplaying(); + _rightDamageShuttleMovie.releaseMovie(); + _rightDamageShuttleMovie.stopDisplaying(); + + _shuttleEnergyMeter.disposeShuttleEnergyMeter(); + _robotShip.cleanUpRobotShip(); + _shuttleHUD.cleanUpShuttleHUD(); + _tractorBeam.stopDisplaying(); + _junk.releaseMovie(); + _junk.stopDisplaying(); + _energyBeam.cleanUpShuttleWeapon(); + _gravitonCannon.cleanUpShuttleWeapon(); + _vm->getAllHotspots().remove(&_energyChoiceSpot); + _vm->getAllHotspots().remove(&_gravitonChoiceSpot); + _vm->getAllHotspots().remove(&_tractorChoiceSpot); + _vm->getAllHotspots().remove(&_shuttleViewSpot); + _vm->getAllHotspots().remove(&_shuttleTransportSpot); + _explosions.releaseMovie(); + _explosions.stopDisplaying(); + + loadLoopSound1(""); +} + +void Mars::transportToRobotShip() { + throwAwayMarsShuttle(); + + Video::VideoDecoder *video = new Video::QuickTimeDecoder(); + if (!video->loadFile("Images/Mars/M98EAE.movie")) + error("Could not load shuttle->interface transition video"); + + video->start(); + + while (!_vm->shouldQuit() && !video->endOfVideo()) { + if (video->needsUpdate()) { + const Graphics::Surface *frame = video->decodeNextFrame(); + + if (frame) + _vm->drawScaledFrame(frame, 0, 0); + } + + Common::Event event; + while (g_system->getEventManager()->pollEvent(event)) + ; + + g_system->delayMillis(10); + } + + delete video; + + if (_vm->shouldQuit()) + return; + + reinstateMonocleInterface(); + + g_energyMonitor->stopEnergyDraining(); + g_energyMonitor->restoreLastEnergyValue(); + _vm->resetEnergyDeathReason(); + g_energyMonitor->startEnergyDraining(); + + arriveAt(kMarsRobotShuttle, kEast); + + _navMovie.stop(); + _navMovie.setTime(_navMovie.getStart()); + _navMovie.start(); +} + +const int kRobotTooStrong = 1; +const int kTractorTooWeak = 2; +const int kCapturedRobotShip = 3; + +void Mars::spaceChaseClick(const Input &input, const HotSpotID id) { + Common::Point pt; + + switch (id) { + case kShuttleEnergySpotID: + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftDampingTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + _leftShuttleMovie.setTime(kShuttleLeftDampingTime); + _leftShuttleMovie.redrawMovieWorld(); + _shuttleHUD.hide(); + _weaponSelection = kEnergyBeam; + playSpotSoundSync(kShuttleDampingBeamIn, kShuttleDampingBeamOut); + break; + case kShuttleGravitonSpotID: + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftGravitonTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + _leftShuttleMovie.setTime(kShuttleLeftGravitonTime); + _leftShuttleMovie.redrawMovieWorld(); + _shuttleHUD.hide(); + _weaponSelection = kGravitonCannon; + playSpotSoundSync(kShuttleGravitonIn, kShuttleGravitonOut); + break; + case kShuttleTractorSpotID: + _upperLeftShuttleMovie.setTime(kShuttleUpperLeftTractorTime); + _upperLeftShuttleMovie.redrawMovieWorld(); + _leftShuttleMovie.setTime(kShuttleLeftTractorTime); + _leftShuttleMovie.redrawMovieWorld(); + _shuttleHUD.show(); + _weaponSelection = kTractorBeam; + playSpotSoundSync(kShuttleTractorBeamIn, kShuttleTractorBeamOut); + break; + case kShuttleViewSpotID: + switch (_weaponSelection) { + case kEnergyBeam: + if (_shuttleEnergyMeter.getEnergyValue() < kMinDampingEnergy) { + playSpotSoundSync(kShuttleEnergyTooLowIn, kShuttleEnergyTooLowOut); + } else { + if (_energyBeam.canFireWeapon()) { + _shuttleEnergyMeter.dropEnergyValue(kMinDampingEnergy); + input.getInputLocation(pt); + _energyBeam.fireWeapon(pt.x, pt.y); + playSpotSoundSync(kMarsEDBBlastIn, kMarsEDBBlastOut); + } + } + break; + case kGravitonCannon: + if (_shuttleEnergyMeter.getEnergyValue() < kMinGravitonEnergy) { + playSpotSoundSync(kShuttleEnergyTooLowIn, kShuttleEnergyTooLowOut); + } else { + if (_gravitonCannon.canFireWeapon()) { + _shuttleEnergyMeter.dropEnergyValue(kMinGravitonEnergy); + input.getInputLocation(pt); + _gravitonCannon.fireWeapon(pt.x, pt.y); + playSpotSoundSync(kMarsGravitonBlastIn, kMarsGravitonBlastOut); + } + } + break; + case kTractorBeam: + if (_shuttleHUD.isTargetLocked()) { + // play tractor beam sound? + _utilityFuse.stopFuse(); + + _tractorBeam.show(); + + int capture; + if (_rightDamageShuttleMovie.getTime() > 40) { + capture = kRobotTooStrong; + } else if (!_shuttleEnergyMeter.enoughEnergyForTractorBeam()) { + capture = kTractorTooWeak; + } else { + _robotShip.snareByTractorBeam(); + capture = kCapturedRobotShip; + _planetMover.dropPlanetOutOfSight(); + } + + _shuttleEnergyMeter.drainForTractorBeam(); + + while (_shuttleEnergyMeter.isFading()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + _shuttleEnergyMeter.setEnergyValue(_shuttleEnergyMeter.getEnergyValue()); + + switch (capture) { + case kRobotTooStrong: + _tractorBeam.hide(); + playSpotSoundSync(kShuttleBrokeFreeIn, kShuttleBrokeFreeOut); + _utilityFuse.lightFuse(); + break; + case kTractorTooWeak: + playSpotSoundSync(kShuttleCantHoldIn, kShuttleCantHoldOut); + _tractorBeam.hide(); + _utilityFuse.lightFuse(); + break; + case kCapturedRobotShip: + _tractorBeam.hide(); + _shuttleHUD.hide(); + _robotShip.cleanUpRobotShip(); + _planetMovie.stop(); + _planetMovie.stopDisplaying(); + _planetMovie.releaseMovie(); + + // Shameless reuse of a variable :P + initOneMovie(&_canyonChaseMovie, "Images/Mars/M98EAS.movie", kShuttleTractorBeamMovieOrder, + kShuttleWindowLeft, kShuttleWindowTop, true); + _canyonChaseMovie.redrawMovieWorld(); + playMovieSegment(&_canyonChaseMovie, 0, _canyonChaseMovie.getDuration()); + + // wait here until any junk clears... + while (_junk.junkFlying()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + _upperRightShuttleMovie.show(); + _upperRightShuttleMovie.setTime(kShuttleUpperRightOverloadTime); + _upperRightShuttleMovie.redrawMovieWorld(); + + playSpotSoundSync(kShuttleOverloadedIn, kShuttleOverloadedOut); + _centerShuttleMovie.setTime(kShuttleCenterVerifyingTime); + _centerShuttleMovie.redrawMovieWorld(); + + playSpotSoundSync(kShuttleCoordinatesIn, kShuttleCoordinatesOut); + _centerShuttleMovie.setTime(kShuttleCenterScanningTime); + _centerShuttleMovie.redrawMovieWorld(); + + playSpotSoundSync(kShuttleScanningIn, kShuttleScanningOut); + _centerShuttleMovie.setTime(kShuttleCenterSafeTime); + _centerShuttleMovie.redrawMovieWorld(); + + playSpotSoundSync(kShuttleSafeIn, kShuttleSafeOut); + _lowerRightShuttleMovie.setTime(kShuttleLowerRightTransportTime); + _lowerRightShuttleMovie.redrawMovieWorld(); + GameState.setMarsReadyForShuttleTransport(true); + break; + } + } else { + playSpotSoundSync(kShuttleTractorLimitedIn, kShuttleTractorLimitedOut); + } + break; + default: + break; + } + break; + case kShuttleTransportSpotID: + _lowerRightShuttleMovie.setTime(kShuttleLowerRightTransportHiliteTime); + _lowerRightShuttleMovie.redrawMovieWorld(); + _neighborhoodNotification.setNotificationFlags(kTimeToTransportFlag, kTimeToTransportFlag); + break; + } +} + +void Mars::showBigExplosion(const Common::Rect &r, const DisplayOrder order) { + if (_explosions.isMovieValid()) { + _explosions.setDisplayOrder(order); + + Common::Rect r2 = r; + int dx = r.width() / 2; + int dy = r.height() / 2; + r2.left -= dx; + r2.right += dx; + r2.top -= dy; + r2.bottom += dy; + + _explosions.setBounds(r2); + _explosions.show(); + _explosions.stop(); + _explosions.setSegment(kBigExplosionStart, kBigExplosionStop); + _explosions.setTime(kBigExplosionStart); + _explosionCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _explosions.start(); + } +} + +void Mars::showLittleExplosion(const Common::Rect &r, const DisplayOrder order) { + if (_explosions.isMovieValid()) { + _explosions.setDisplayOrder(order); + + Common::Rect r2 = r; + int dx = r.width() / 2; + int dy = r.height() / 2; + r2.left -= dx; + r2.right += dx; + r2.top -= dy; + r2.bottom += dy; + _explosions.setBounds(r2); + + _explosions.show(); + _explosions.stop(); + _explosions.setSegment(kLittleExplosionStart, kLittleExplosionStop); + _explosions.setTime(kLittleExplosionStart); + _explosionCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _explosions.start(); + } +} + +void Mars::hitByJunk() { + _leftDamageShuttleMovie.setTime(_leftDamageShuttleMovie.getTime() - 40); + _leftDamageShuttleMovie.redrawMovieWorld(); + + playSpotSoundSync(kMarsJunkCollisionIn, kMarsJunkCollisionOut); + + if (_leftDamageShuttleMovie.getTime() == 0) { + die(kDeathRanIntoSpaceJunk); + } else { + TimeValue t = _leftDamageShuttleMovie.getTime() / 40; + + if (t == 1) + playSpotSoundSync(kShuttleHullBreachIn, kShuttleHullBreachOut); + + t = _leftShuttleMovie.getTime(); + _leftShuttleMovie.setTime(kShuttleLeftDamagedTime); + _leftShuttleMovie.redrawMovieWorld(); + _vm->delayShell(1, 3); + _leftShuttleMovie.setTime(t); + _leftShuttleMovie.redrawMovieWorld(); + } +} + +void Mars::setUpNextDropTime() { + _robotShip.setUpNextDropTime(); +} + +void Mars::decreaseRobotShuttleEnergy(const int delta, Common::Point impactPoint) { + _rightDamageShuttleMovie.setTime(_rightDamageShuttleMovie.getTime() - 40 * delta); + _rightDamageShuttleMovie.redrawMovieWorld(); + + if (_rightDamageShuttleMovie.getTime() == 0) { + Common::Rect r; + _robotShip.getShuttleBounds(r); + int size = MAX(r.width(), r.height()); + r = Common::Rect::center(impactPoint.x, impactPoint.y, size, size); + _robotShip.killRobotShip(); + showBigExplosion(r, kShuttleRobotShipOrder); + } else if (delta > 1) { + Common::Rect r; + _robotShip.getShuttleBounds(r); + int size = MIN(r.width(), r.height()); + r = Common::Rect::center(impactPoint.x, impactPoint.y, size, size); + showLittleExplosion(r, kShuttleWeaponBackOrder); + TimeValue t = _rightShuttleMovie.getTime(); + _rightShuttleMovie.setTime(kShuttleRightDamagedTime); + _rightShuttleMovie.redrawMovieWorld(); + _vm->delayShell(1, 3); + _rightShuttleMovie.setTime(t); + _rightShuttleMovie.redrawMovieWorld(); + } + + if (_rightDamageShuttleMovie.getTime() <= 40) { + GameState.setScoringStoppedRobotsShuttle(); + if (!GameState.getMarsHitRobotWithCannon()) + GameState.setScoringMarsGandhi(); + } +} + +void Mars::updateCursor(const Common::Point cursorLocation, const Hotspot *cursorSpot) { + if (cursorSpot && cursorSpot->getObjectID() == kShuttleViewSpotID) { + if (_weaponSelection != kNoWeapon) + _vm->_cursor->setCurrentFrameIndex(6); + else + _vm->_cursor->setCurrentFrameIndex(0); + } else { + Neighborhood::updateCursor(cursorLocation, cursorSpot); + } +} + +AirQuality Mars::getAirQuality(const RoomID room) { + if ((room >= kMars36 && room <= kMars39) || (room >= kMarsMaze004 && room <= kMarsMaze200)) + return kAirQualityVacuum; + if (room == kMars35 && !GameState.getMarsAirlockOpen()) + return kAirQualityVacuum; + if (room == kMars60 && !GameState.getMarsAirlockOpen()) + return kAirQualityVacuum; + + return Neighborhood::getAirQuality(room); +} + +// Start up panting sound if necessary. + +void Mars::checkAirMask() { + Neighborhood::checkAirMask(); + + if (getAirQuality(GameState.getCurrentRoom()) == kAirQualityVacuum) { + if (g_airMask->isAirMaskOn()) { + if (_noAirFuse.isFuseLit()) { + _noAirFuse.stopFuse(); + loadLoopSound2(""); + loadAmbientLoops(); + playSpotSoundSync(kMarsOxyMaskOnIn, kMarsOxyMaskOnOut); + } + } else { + if (!_noAirFuse.isFuseLit()) { + loadLoopSound2("Sounds/Mars/SukWind1.22K.AIFF"); + _noAirFuse.primeFuse(kVacuumSurvivalTimeLimit); + _noAirFuse.lightFuse(); + } + } + } else { + if (_noAirFuse.isFuseLit()) { + _noAirFuse.stopFuse(); + loadLoopSound2(""); + loadAmbientLoops(); + } + } +} + +void Mars::airStageExpired() { + if (((PegasusEngine *)g_engine)->playerHasItemID(kAirMask)) + die(kDeathNoAirInMaze); + else + die(kDeathNoMaskInMaze); +} + +void Mars::lockThawed() { + startExtraSequence(kMars57ThawLock, kExtraCompletedFlag, kFilterNoInput); +} + +void Mars::setUpReactorLevel1() { + _reactorStage = 1; + makeColorSequence(); + _guessObject.initReactorGuess(); + _undoPict.initFromPICTResource(_vm->_resFork, kReactorUndoHilitePICTID); + _undoPict.setDisplayOrder(kMonitorLayer); + _undoPict.moveElementTo(kUndoHiliteLeft, kUndoHiliteTop); + _undoPict.startDisplaying(); + _guessHistory.initReactorHistory(); + _choiceHighlight.initReactorChoiceHighlight(); + setCurrentActivation(kActivateReactorInGame); + _bombFuse.primeFuse(kColorMatchingTimeLimit); + _bombFuse.setFunctor(new Common::Functor0Mem<void, Mars>(this, &Mars::bombExplodesInGame)); + _bombFuse.lightFuse(); +} + +void Mars::setUpNextReactorLevel() { + _guessObject.show(); + _guessHistory.show(); + _guessHistory.clearHistory(); + _choiceHighlight.show(); + _reactorStage++; + makeColorSequence(); +} + +void Mars::makeColorSequence() { + int32 code[5]; + int32 highest = _reactorStage + 2; + + for (int32 i = 0; i < highest; i++) + code[i] = i; + + _vm->shuffleArray(code, highest); + _currentGuess[0] = -1; + _currentGuess[1] = -1; + _currentGuess[2] = -1; + _nextGuess = 0; + _guessObject.setGuess(-1, -1, -1); + _guessHistory.setAnswer(code[0], code[1], code[2]); +} + +void Mars::doUndoOneGuess() { + if (_nextGuess > 0) { + _undoPict.show(); + _vm->delayShell(1, 2); + _undoPict.hide(); + _nextGuess--; + _currentGuess[_nextGuess] = -1; + _guessObject.setGuess(_currentGuess[0], _currentGuess[1], _currentGuess[2]); + _choiceHighlight.resetHighlight(); + + if (_currentGuess[0] != -1) { + _choiceHighlight.highlightChoice(_currentGuess[0]); + + if (_currentGuess[1] != -1) { + _choiceHighlight.highlightChoice(_currentGuess[1]); + + if (_currentGuess[2] != -1) + _choiceHighlight.highlightChoice(_currentGuess[2]); + } + } + } +} + +void Mars::doReactorGuess(int32 guess) { + _choiceHighlight.highlightChoice(guess); + _currentGuess[_nextGuess] = guess; + _guessObject.setGuess(_currentGuess[0], _currentGuess[1], _currentGuess[2]); + + switch (guess) { + case 0: + playSpotSoundSync(kColorMatchRedIn, kColorMatchRedOut); + break; + case 1: + playSpotSoundSync(kColorMatchYellowIn, kColorMatchYellowOut); + break; + case 2: + playSpotSoundSync(kColorMatchGreenIn, kColorMatchGreenOut); + break; + case 3: + playSpotSoundSync(kColorMatchBlueIn, kColorMatchBlueOut); + break; + case 4: + playSpotSoundSync(kColorMatchPurpleIn, kColorMatchPurpleOut); + break; + } + + _nextGuess++; + + if (_nextGuess == 3) { + _vm->delayShell(1, 2); + _nextGuess = 0; + _guessHistory.addGuess(_currentGuess[0], _currentGuess[1], _currentGuess[2]); + + switch (_guessHistory.getCurrentNumCorrect()) { + case 0: + playSpotSoundSync(kColorMatchZeroNodesIn, kColorMatchZeroNodesOut); + break; + case 1: + playSpotSoundSync(kColorMatchOneNodeIn, kColorMatchOneNodeOut); + break; + case 2: + playSpotSoundSync(kColorMatchTwoNodesIn, kColorMatchTwoNodesOut); + break; + case 3: + playSpotSoundSync(kColorMatchThreeNodesIn, kColorMatchThreeNodesOut); + break; + } + + _currentGuess[0] = -1; + _currentGuess[1] = -1; + _currentGuess[2] = -1; + _guessObject.setGuess(-1, -1, -1); + _choiceHighlight.resetHighlight(); + + if (_guessHistory.isSolved()) { + _guessHistory.showAnswer(); + _vm->delayShell(1, 2); + _guessObject.hide(); + _guessHistory.hide(); + _choiceHighlight.hide(); + + switch (_reactorStage) { + case 1: + startExtraSequence(kMars57GameLevel2, kExtraCompletedFlag, kFilterNoInput); + break; + case 2: + startExtraSequence(kMars57GameLevel3, kExtraCompletedFlag, kFilterNoInput); + break; + case 3: + _bombFuse.stopFuse(); + _guessObject.disposeReactorGuess(); + _undoPict.deallocateSurface(); + _guessHistory.disposeReactorHistory(); + _choiceHighlight.disposeReactorChoiceHighlight(); + GameState.setScoringDisarmedCardBomb(); + startExtraSequence(kMars57GameSolved, kExtraCompletedFlag, kFilterNoInput); + break; + } + } else if (_guessHistory.getNumGuesses() >= 5) { + _vm->delayShell(2, 1); + bombExplodesInGame(); + } + } +} + +void Mars::bombExplodesInGame() { + _guessObject.disposeReactorGuess(); + _undoPict.deallocateSurface(); + _guessHistory.disposeReactorHistory(); + _choiceHighlight.disposeReactorChoiceHighlight(); + startExtraSequence(kMars57BombExplodesInGame, kExtraCompletedFlag, kFilterNoInput); +} + +void Mars::didntFindBomb() { + die(kDeathDidntFindMarsBomb); +} + +Common::String Mars::getBriefingMovie() { + Common::String movieName = Neighborhood::getBriefingMovie(); + + if (!movieName.empty()) + return movieName; + + return "Images/AI/Mars/XM01"; +} + +Common::String Mars::getEnvScanMovie() { + Common::String movieName = Neighborhood::getEnvScanMovie(); + + if (movieName.empty()) { + RoomID room = GameState.getCurrentRoom(); + + if (room >= kMars0A && room <= kMars21) + return "Images/AI/Mars/XME1"; + else if (room >= kMars22 && room <= kMars31South) + return "Images/AI/Mars/XME2"; + else if (room >= kMars52 && room <= kMars58) + return "Images/AI/Mars/XMREACE"; + + return "Images/AI/Mars/XME3"; + } + + return movieName; +} + +uint Mars::getNumHints() { + uint numHints = Neighborhood::getNumHints(); + + if (numHints == 0) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars27, kNorth): + case MakeRoomView(kMars28, kNorth): + case MakeRoomView(kMars49, kSouth): + numHints = 1; + break; + case MakeRoomView(kMars31, kSouth): + case MakeRoomView(kMars31South, kSouth): + if (!GameState.isTakenItemID(kMarsCard)) + numHints = 1; + break; + case MakeRoomView(kMars34, kNorth): + if (!GameState.isTakenItemID(kMarsCard)) + numHints = 2; + break; + case MakeRoomView(kMars34, kSouth): + case MakeRoomView(kMars45, kNorth): + if (!GameState.isTakenItemID(kCrowbar)) + numHints = 1; + break; + case MakeRoomView(kMars51, kEast): + if (GameState.isCurrentDoorOpen() && !GameState.getShieldOn()) { + if (GameState.isTakenItemID(kShieldBiochip)) + numHints = 1; + else + numHints = 2; + } + break; + case MakeRoomView(kMars52, kNorth): + case MakeRoomView(kMars52, kSouth): + case MakeRoomView(kMars52, kEast): + case MakeRoomView(kMars52, kWest): + case MakeRoomView(kMars54, kNorth): + case MakeRoomView(kMars54, kSouth): + case MakeRoomView(kMars54, kEast): + case MakeRoomView(kMars54, kWest): + case MakeRoomView(kMars56, kNorth): + case MakeRoomView(kMars56, kSouth): + case MakeRoomView(kMars56, kWest): + case MakeRoomView(kMars58, kNorth): + case MakeRoomView(kMars58, kSouth): + case MakeRoomView(kMars58, kEast): + case MakeRoomView(kMars58, kWest): + if (!GameState.getShieldOn()) { + if (GameState.isTakenItemID(kShieldBiochip)) + numHints = 1; + else + numHints = 2; + } + break; + case MakeRoomView(kMars56, kEast): + if (getCurrentActivation() == kActivateReactorReadyForNitrogen) { + if ((ExtraID)_lastExtra == kMars57LowerScreenClosed) + numHints = 3; + } else if (getCurrentActivation() == kActivateReactorPlatformOut) { + if (!GameState.getShieldOn()) { + if (GameState.isTakenItemID(kShieldBiochip)) + numHints = 1; + else + numHints = 2; + } + } + break; + } + } + + return numHints; +} + +Common::String Mars::getHintMovie(uint hintNum) { + Common::String movieName = Neighborhood::getHintMovie(hintNum); + + if (movieName.empty()) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kMars27, kNorth): + case MakeRoomView(kMars28, kNorth): + return "Images/AI/Globals/XGLOB5C"; + case MakeRoomView(kMars31, kSouth): + case MakeRoomView(kMars31South, kSouth): + case MakeRoomView(kMars34, kSouth): + case MakeRoomView(kMars45, kNorth): + return "Images/AI/Globals/XGLOB1C"; + case MakeRoomView(kMars34, kNorth): + if (hintNum == 1) + return "Images/AI/Globals/XGLOB2C"; + + return "Images/AI/Globals/XGLOB3G"; + case MakeRoomView(kMars49, kSouth): + if (GameState.isTakenItemID(kAirMask)) + return "Images/AI/Globals/XGLOB3E"; + + return "Images/AI/Globals/XGLOB1C"; + case MakeRoomView(kMars51, kEast): + if (GameState.isTakenItemID(kShieldBiochip)) + return "Images/AI/Mars/XM52NW"; + + if (hintNum == 1) + return "Images/AI/Globals/XGLOB2D"; + + return "Images/AI/Globals/XGLOB3F"; + case MakeRoomView(kMars52, kNorth): + case MakeRoomView(kMars52, kSouth): + case MakeRoomView(kMars52, kEast): + case MakeRoomView(kMars52, kWest): + case MakeRoomView(kMars54, kNorth): + case MakeRoomView(kMars54, kSouth): + case MakeRoomView(kMars54, kEast): + case MakeRoomView(kMars54, kWest): + case MakeRoomView(kMars56, kNorth): + case MakeRoomView(kMars56, kSouth): + case MakeRoomView(kMars56, kWest): + case MakeRoomView(kMars58, kNorth): + case MakeRoomView(kMars58, kSouth): + case MakeRoomView(kMars58, kEast): + case MakeRoomView(kMars58, kWest): + if (hintNum == 1) { + if (GameState.isTakenItemID(kShieldBiochip)) + return "Images/AI/Mars/XM52NW"; + + return "Images/AI/Globals/XGLOB2D"; + } + + return "Images/AI/Globals/XGLOB3F"; + case MakeRoomView(kMars56, kEast): + if (getCurrentActivation() == kActivateReactorReadyForNitrogen) + return Common::String::format("Images/AI/Mars/XM57SD%d", hintNum); + + if (hintNum == 1) { + if (GameState.isTakenItemID(kShieldBiochip)) + return "Images/AI/Mars/XM52NW"; + + return "Images/AI/Globals/XGLOB2D"; + } + + return "Images/AI/Globals/XGLOB3F"; + } + } + + return movieName; +} + +bool Mars::inColorMatchingGame() { + return _guessObject.isDisplaying(); +} + +bool Mars::canSolve() { + return GameState.getCurrentRoomAndView() == MakeRoomView(kMars56, kEast) && (getCurrentActivation() == kActivateReactorReadyForNitrogen || + getCurrentActivation() == kActivateReactorReadyForCrowBar || inColorMatchingGame()); +} + +void Mars::doSolve() { + if (getCurrentActivation() == kActivateReactorReadyForNitrogen || getCurrentActivation() == kActivateReactorReadyForCrowBar) { + _utilityFuse.stopFuse(); + GameState.setMarsLockBroken(true); + GameState.setMarsLockFrozen(false); + startExtraLongSequence(kMars57OpenPanel, kMars57OpenPanelChoices, kExtraCompletedFlag, kFilterNoInput); + } else if (inColorMatchingGame()) { + _bombFuse.stopFuse(); + _guessObject.disposeReactorGuess(); + _undoPict.deallocateSurface(); + _guessHistory.disposeReactorHistory(); + _choiceHighlight.disposeReactorChoiceHighlight(); + startExtraSequence(kMars57GameSolved, kExtraCompletedFlag, kFilterNoInput); + } +} + +Common::String Mars::getSoundSpotsName() { + return "Sounds/Mars/Mars Spots"; +} + +Common::String Mars::getNavMovieName() { + return "Images/Mars/Mars.movie"; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/mars.h b/engines/pegasus/neighborhood/mars/mars.h new file mode 100644 index 0000000000..0859522890 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/mars.h @@ -0,0 +1,238 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_MARS_H +#define PEGASUS_NEIGHBORHOOD_MARS_MARS_H + +#include "pegasus/neighborhood/neighborhood.h" +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/mars/energybeam.h" +#include "pegasus/neighborhood/mars/gravitoncannon.h" +#include "pegasus/neighborhood/mars/planetmover.h" +#include "pegasus/neighborhood/mars/reactor.h" +#include "pegasus/neighborhood/mars/robotship.h" +#include "pegasus/neighborhood/mars/shuttleenergymeter.h" +#include "pegasus/neighborhood/mars/shuttlehud.h" +#include "pegasus/neighborhood/mars/spacejunk.h" +#include "pegasus/neighborhood/mars/tractorbeam.h" + +namespace Pegasus { + +class InventoryItem; +class Mars; + +enum MarsTimerCode { + kMarsLaunchTubeReached, + kMarsCanyonChaseFinished, + kMarsSpaceChaseFinished // Player ran out of time... +}; + +struct MarsTimerEvent { + Mars *mars; + MarsTimerCode event; + + void fire(); +}; + +enum ShuttleWeaponSelection { + kNoWeapon, + kEnergyBeam, + kGravitonCannon, + kTractorBeam +}; + +class Mars : public Neighborhood { +friend struct MarsTimerEvent; +public: + Mars(InputHandler *, PegasusEngine *); + virtual ~Mars(); + + void flushGameState(); + + virtual uint16 getDateResID() const; + + virtual AirQuality getAirQuality(const RoomID); + + void checkAirMask(); + + void showBigExplosion(const Common::Rect &, const DisplayOrder); + void showLittleExplosion(const Common::Rect &, const DisplayOrder); + void hitByJunk(); + void decreaseRobotShuttleEnergy(const int, Common::Point impactPoint); + void setUpNextDropTime(); + + Common::String getBriefingMovie(); + Common::String getEnvScanMovie(); + uint getNumHints(); + Common::String getHintMovie(uint); + + virtual void shieldOn(); + virtual void shieldOff(); + + void checkContinuePoint(const RoomID, const DirectionConstant); + + void setSoundFXLevel(const uint16); + + bool canSolve(); + void doSolve(); + + bool inColorMatchingGame(); + +protected: + enum { + kMarsPrivatePodStorageOpenFlag, + kMarsPrivatePodTurnLeftFlag, + kMarsPrivatePodTurnRightFlag, + kMarsPrivateRobotTiredOfWaitingFlag, + kMarsPrivatePlatformZoomedInFlag, + kMarsPrivateBombExposedFlag, + kMarsPrivateDraggingBombFlag, + kMarsPrivateInSpaceChaseFlag, + kMarsPrivateGotMapChipFlag, + kMarsPrivateGotOpticalChipFlag, + kMarsPrivateGotShieldChipFlag, + kNumMarsPrivateFlags + }; + + void init(); + void start(); + void setUpAIRules(); + void arriveAt(const RoomID, const DirectionConstant); + void takeItemFromRoom(Item *); + void dropItemIntoRoom(Item *, Hotspot *); + void activateHotspots(); + void activateOneHotspot(HotspotInfoTable::Entry &, Hotspot *); + void clickInHotspot(const Input &, const Hotspot *); + InputBits getInputFilter(); + + TimeValue getViewTime(const RoomID, const DirectionConstant); + void getZoomEntry(const HotSpotID, ZoomTable::Entry &); + void findSpotEntry(const RoomID, const DirectionConstant, SpotFlags, SpotTable::Entry &); + CanOpenDoorReason canOpenDoor(DoorTable::Entry &); + void openDoor(); + void closeDoorOffScreen(const RoomID, const DirectionConstant); + int16 getStaticCompassAngle(const RoomID, const DirectionConstant); + void getExitCompassMove(const ExitTable::Entry &, FaderMoveSpec &); + void getExtraCompassMove(const ExtraTable::Entry &, FaderMoveSpec &); + void turnTo(const DirectionConstant); + void receiveNotification(Notification *, const NotificationFlags); + void doorOpened(); + void setUpReactorEnergyDrain(); + Hotspot *getItemScreenSpot(Item *, DisplayElement *); + void lockThawed(); + void robotTiredOfWaiting(); + + void setUpReactorLevel1(); + void setUpNextReactorLevel(); + void makeColorSequence(); + void doUndoOneGuess(); + void doReactorGuess(int32 guess); + void bombExplodesInGame(); + void didntFindBomb(); + CanMoveForwardReason canMoveForward(ExitTable::Entry &); + void cantMoveThatWay(CanMoveForwardReason); + void moveForward(); + void bumpIntoWall(); + void turnLeft(); + void turnRight(); + void airStageExpired(); + void loadAmbientLoops(); + void checkAirlockDoors(); + void pickedUpItem(Item *item); + void cantOpenDoor(CanOpenDoorReason); + void launchMaze007Robot(); + void launchMaze015Robot(); + void launchMaze101Robot(); + void launchMaze104Robot(); + void launchMaze133Robot(); + void launchMaze136Robot(); + void launchMaze184Robot(); + void timerExpired(const uint32); + void spotCompleted(); + + void doCanyonChase(void); + void startMarsTimer(TimeValue, TimeScale, MarsTimerCode); + void marsTimerExpired(MarsTimerEvent &); + void throwAwayMarsShuttle(); + void startUpFromFinishedSpaceChase(); + void startUpFromSpaceChase(); + void transportToRobotShip(); + void spaceChaseClick(const Input &, const HotSpotID); + void updateCursor(const Common::Point, const Hotspot *); + + Common::String getSoundSpotsName(); + Common::String getNavMovieName(); + + InventoryItem *_attackingItem; + FuseFunction _bombFuse; + FuseFunction _noAirFuse; + FuseFunction _utilityFuse; + FlagsArray<byte, kNumMarsPrivateFlags> _privateFlags; + uint _reactorStage, _nextGuess; + int32 _currentGuess[3]; + ReactorGuess _guessObject; + Picture _undoPict; + ReactorHistory _guessHistory; + ReactorChoiceHighlight _choiceHighlight; + + Picture _shuttleInterface1; + Picture _shuttleInterface2; + Picture _shuttleInterface3; + Picture _shuttleInterface4; + Movie _canyonChaseMovie; + + MarsTimerEvent _marsEvent; + + Movie _leftShuttleMovie; + Movie _rightShuttleMovie; + Movie _lowerLeftShuttleMovie; + Movie _lowerRightShuttleMovie; + Movie _centerShuttleMovie; + Movie _upperLeftShuttleMovie; + Movie _upperRightShuttleMovie; + Movie _leftDamageShuttleMovie; + Movie _rightDamageShuttleMovie; + ShuttleEnergyMeter _shuttleEnergyMeter; + Movie _planetMovie; + PlanetMover _planetMover; + RobotShip _robotShip; + ShuttleHUD _shuttleHUD; + TractorBeam _tractorBeam; + SpaceJunk _junk; + EnergyBeam _energyBeam; + GravitonCannon _gravitonCannon; + Hotspot _energyChoiceSpot; + Hotspot _gravitonChoiceSpot; + Hotspot _tractorChoiceSpot; + Hotspot _shuttleViewSpot; + Hotspot _shuttleTransportSpot; + ShuttleWeaponSelection _weaponSelection; + ScalingMovie _explosions; + NotificationCallBack _explosionCallBack; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/planetmover.cpp b/engines/pegasus/neighborhood/mars/planetmover.cpp new file mode 100644 index 0000000000..a340120c12 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/planetmover.cpp @@ -0,0 +1,104 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/movie.h" +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/mars/hermite.h" +#include "pegasus/neighborhood/mars/planetmover.h" +#include "pegasus/neighborhood/mars/shuttleenergymeter.h" + +namespace Pegasus { + +static const TimeScale kRovingScale = kTractorBeamScale; +static const TimeValue kRovingTime = kTenSeconds * kRovingScale; +static const TimeValue kRovingSlop = kTwoSeconds * kRovingScale; + +static const CoordType kMaxVelocity = 20; + +PlanetMover::PlanetMover() { + setScale(kRovingScale); + _dropping = false; + _planetMovie = 0; +} + +void PlanetMover::startMoving(Movie *planetMovie) { + _planetMovie = planetMovie; + _p4 = kPlanetStartTop; + _r4 = ((PegasusEngine *)g_engine)->getRandomNumber(kMaxVelocity - 1); + if (_r4 + _p4 < kPlanetStopTop) + _r4 = kPlanetStopTop - _p4; + newDestination(); +} + +void PlanetMover::stopMoving() { + stop(); +} + +void PlanetMover::dropPlanetOutOfSight() { + stop(); + CoordType currentLoc = hermite(_p1, _p4, _r1, _r4, _lastTime, _duration); + CoordType currentV = dHermite(_p1, _p4, _r1, _r4, _lastTime, _duration); + _p1 = currentLoc; + _r1 = currentV; + _p4 = kPlanetStartTop; + _r4 = 0; + _duration = kTractorBeamTime - kTractorBeamScale; + _dropping = true; + setSegment(0, _duration); + setTime(0); + start(); +} + +void PlanetMover::newDestination() { + _p1 = _p4; + _r1 = _r4; + + _p4 = kPlanetStopTop + ((PegasusEngine *)g_engine)->getRandomNumber(kPlanetStartTop - kPlanetStopTop - 1); + _r4 = ((PegasusEngine *)g_engine)->getRandomNumber(kMaxVelocity - 1); + + if (_r4 + _p4 < kPlanetStopTop) + _r4 = kPlanetStopTop - _p4; + + stop(); + _duration = kRovingTime + ((PegasusEngine *)g_engine)->getRandomNumber(kRovingSlop - 1); + setSegment(0, _duration); + setTime(0); + start(); +} + +void PlanetMover::timeChanged(const TimeValue) { + if (_planetMovie) { + _planetMovie->moveElementTo(kPlanetStartLeft, hermite(_p1, _p4, _r1, _r4, _lastTime, _duration)); + if (_lastTime == _duration) { + if (_dropping) + stop(); + else + newDestination(); + } + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/planetmover.h b/engines/pegasus/neighborhood/mars/planetmover.h new file mode 100644 index 0000000000..2c195387e8 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/planetmover.h @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_PLANETMOVER_H +#define PEGASUS_NEIGHBORHOOD_MARS_PLANETMOVER_H + +#include "pegasus/timers.h" + +namespace Pegasus { + +class Movie; + +class PlanetMover : IdlerTimeBase { +public: + PlanetMover(); + virtual ~PlanetMover() {} + + void startMoving(Movie *); + void stopMoving(); + + void dropPlanetOutOfSight(); + +protected: + void newDestination(); + virtual void timeChanged(const TimeValue); + + Movie *_planetMovie; + CoordType _p1, _p4, _r1, _r4; + TimeValue _duration; + bool _dropping; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/reactor.cpp b/engines/pegasus/neighborhood/mars/reactor.cpp new file mode 100644 index 0000000000..334fb98879 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/reactor.cpp @@ -0,0 +1,297 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * aint32 with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/mars/reactor.h" + +namespace Pegasus { + +static const CoordType kCurrentGuessWidth = 121; +static const CoordType kCurrentGuessHeight = 23; + +static const CoordType kOneGuessWidth = 25; +static const CoordType kOneGuessHeight = 23; + +static const ResIDType kReactorChoicesPICTID = 905; + +static const CoordType kCurrentGuessLeft = kNavAreaLeft + 146; +static const CoordType kCurrentGuessTop = kNavAreaTop + 90; + +ReactorGuess::ReactorGuess(const DisplayElementID id) : DisplayElement(id) { + setBounds(kCurrentGuessLeft, kCurrentGuessTop, kCurrentGuessLeft + kCurrentGuessWidth, + kCurrentGuessTop + kCurrentGuessHeight); + setDisplayOrder(kMonitorLayer); + _currentGuess[0] = -1; + _currentGuess[1] = -1; + _currentGuess[2] = -1; +} + +void ReactorGuess::initReactorGuess() { + _colors.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kReactorChoicesPICTID); + startDisplaying(); + show(); +} + +void ReactorGuess::disposeReactorGuess() { + stopDisplaying(); + _colors.deallocateSurface(); +} + +void ReactorGuess::setGuess(int32 a, int32 b, int32 c) { + _currentGuess[0] = a; + _currentGuess[1] = b; + _currentGuess[2] = c; + triggerRedraw(); +} + +void ReactorGuess::draw(const Common::Rect &) { + if (_colors.isSurfaceValid()) { + Common::Rect r1(0, 0, kOneGuessWidth, kOneGuessHeight); + Common::Rect r2 = r1; + + for (int i = 0; i < 3; i++) { + if (_currentGuess[i] >= 0) { + r1.moveTo(kOneGuessWidth * _currentGuess[i], 0); + r2.moveTo(kCurrentGuessLeft + 48 * i, kCurrentGuessTop); + _colors.copyToCurrentPortTransparent(r1, r2); + } + } + } +} + +static const CoordType kReactorChoiceHiliteWidth = 166; +static const CoordType kReactorChoiceHiliteHeight = 26; + +static const CoordType kChoiceHiliteLefts[6] = { + 0, + 34, + 34 + 34, + 34 + 34 + 32, + 34 + 34 + 32 + 34, + 34 + 34 + 32 + 34 + 32 +}; + +static const ResIDType kReactorChoiceHilitePICTID = 901; + +static const CoordType kReactorChoiceHiliteLeft = kNavAreaLeft + 116; +static const CoordType kReactorChoiceHiliteTop = kNavAreaTop + 158; + +ReactorChoiceHighlight::ReactorChoiceHighlight(const DisplayElementID id) : DisplayElement(id) { + setBounds(kReactorChoiceHiliteLeft, kReactorChoiceHiliteTop, kReactorChoiceHiliteLeft + kReactorChoiceHiliteWidth, + kReactorChoiceHiliteTop + kReactorChoiceHiliteHeight); + setDisplayOrder(kMonitorLayer); +} + +void ReactorChoiceHighlight::initReactorChoiceHighlight() { + _colors.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kReactorChoiceHilitePICTID); + startDisplaying(); + show(); +} + +void ReactorChoiceHighlight::disposeReactorChoiceHighlight() { + stopDisplaying(); + _colors.deallocateSurface(); +} + +void ReactorChoiceHighlight::draw(const Common::Rect &) { + if (_colors.isSurfaceValid()) { + for (int i = 0; i < 5; ++i) { + if (_choices.getFlag(i)) { + Common::Rect r1(0, 0, kChoiceHiliteLefts[i + 1] - kChoiceHiliteLefts[i], kReactorChoiceHiliteHeight); + Common::Rect r2 = r1; + r1.moveTo(kChoiceHiliteLefts[i], 0); + r2.moveTo(kReactorChoiceHiliteLeft + kChoiceHiliteLefts[i], kReactorChoiceHiliteTop); + _colors.copyToCurrentPort(r1, r2); + } + } + } +} + +static const CoordType kReactorHistoryWidth = 128; +static const CoordType kReactorHistoryHeight = 168; + +static const CoordType kColorWidths[5] = { 24, 25, 25, 26, 27 }; +static const CoordType kColorHeights[5] = { 14, 15, 17, 17, 19}; + +static const CoordType kHistoryLefts[5][3] = { + { 302 + kNavAreaLeft, 329 + kNavAreaLeft, 357 + kNavAreaLeft }, + { 302 + kNavAreaLeft, 331 + kNavAreaLeft, 360 + kNavAreaLeft }, + { 303 + kNavAreaLeft, 333 + kNavAreaLeft, 363 + kNavAreaLeft }, + { 304 + kNavAreaLeft, 335 + kNavAreaLeft, 366 + kNavAreaLeft }, + { 305 + kNavAreaLeft, 337 + kNavAreaLeft, 369 + kNavAreaLeft } +}; + +static const CoordType kHistoryTops[5] = { + 39 + kNavAreaTop, + 61 + kNavAreaTop, + 84 + kNavAreaTop, + 110 + kNavAreaTop, + 137 + kNavAreaTop +}; + +static const CoordType kOneAnswerWidth = 35; +static const CoordType kOneAnswerHeight = 27; + +static const CoordType kDigitWidth = 16; +static const CoordType kDigitHeight = 12; + +static const CoordType kCorrectCountLefts[5] = { + 388 + kNavAreaLeft, + 392 + kNavAreaLeft, + 398 + kNavAreaLeft, + 402 + kNavAreaLeft, + 406 + kNavAreaLeft +}; + +static const CoordType kCorrectCountTops[5] = { + 40 + kNavAreaTop, + 62 + kNavAreaTop, + 86 + kNavAreaTop, + 112 + kNavAreaTop, + 140 + kNavAreaTop +}; + +static const ResIDType kReactorDigitsPICTID = 902; +static const ResIDType kReactorHistoryPICTID = 903; +static const ResIDType kReactorAnswerPICTID = 904; + +static const CoordType kReactorHistoryLeft = kNavAreaLeft + 302; +static const CoordType kReactorHistoryTop = kNavAreaTop + 39; + +static const CoordType kAnswerLeft = kNavAreaLeft + 304; +static const CoordType kAnswerTop = kNavAreaTop + 180; + +ReactorHistory::ReactorHistory(const DisplayElementID id) : DisplayElement(id) { + setBounds(kReactorHistoryLeft, kReactorHistoryTop, kReactorHistoryLeft + kReactorHistoryWidth, + kReactorHistoryTop + kReactorHistoryHeight); + setDisplayOrder(kMonitorLayer); + _numGuesses = 0; + _answer[0] = -1; + _answer[1] = -1; + _answer[2] = -1; + _showAnswer = false; +} + +void ReactorHistory::initReactorHistory() { + _colors.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kReactorHistoryPICTID); + _digits.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kReactorDigitsPICTID); + _answerColors.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kReactorAnswerPICTID); + startDisplaying(); + show(); +} + +void ReactorHistory::disposeReactorHistory() { + stopDisplaying(); + _colors.deallocateSurface(); +} + +void ReactorHistory::addGuess(int32 a, int32 b, int32 c) { + _history[_numGuesses][0] = a; + _history[_numGuesses][1] = b; + _history[_numGuesses][2] = c; + _numGuesses++; + triggerRedraw(); +} + +void ReactorHistory::clearHistory() { + _numGuesses = 0; + _showAnswer = false; + triggerRedraw(); +} + +void ReactorHistory::setAnswer(int32 a, int32 b, int32 c) { + _answer[0] = a; + _answer[1] = b; + _answer[2] = c; +} + +void ReactorHistory::showAnswer() { + _showAnswer = true; + triggerRedraw(); +} + +bool ReactorHistory::isSolved() { + for (int i = 0; i < _numGuesses; i++) + if (_history[i][0] == _answer[0] && _history[i][1] == _answer[1] && _history[i][2] == _answer[2]) + return true; + + return false; +} + +void ReactorHistory::draw(const Common::Rect &) { + static const CoordType kColorTops[5] = { + 0, + kColorHeights[0], + kColorHeights[0] + kColorHeights[1], + kColorHeights[0] + kColorHeights[1] + kColorHeights[2], + kColorHeights[0] + kColorHeights[1] + kColorHeights[2] + kColorHeights[3], + }; + + if (_colors.isSurfaceValid() && _digits.isSurfaceValid()) { + for (int i = 0; i < _numGuesses; ++i) { + Common::Rect r1(0, 0, kColorWidths[i], kColorHeights[i]); + Common::Rect r2 = r1; + Common::Rect r3(0, 0, kDigitWidth, kDigitHeight); + Common::Rect r4 = r3; + int correct = 0; + + for (int j = 0; j < 3; ++j) { + r1.moveTo(kColorWidths[i] * _history[i][j], kColorTops[i]); + r2.moveTo(kHistoryLefts[i][j], kHistoryTops[i]); + _colors.copyToCurrentPortTransparent(r1, r2); + + if (_history[i][j] == _answer[j]) + correct++; + } + + r3.moveTo(kDigitWidth * correct, 0); + r4.moveTo(kCorrectCountLefts[i], kCorrectCountTops[i]); + _digits.copyToCurrentPort(r3, r4); + } + + if (_showAnswer && _answerColors.isSurfaceValid()) { + Common::Rect r1(0, 0, kOneAnswerWidth, kOneAnswerHeight); + Common::Rect r2 = r1; + + for (int i = 0; i < 3; i++) { + r1.moveTo(kOneAnswerWidth * _answer[i], 0); + r2.moveTo(kAnswerLeft + 34 * i, kAnswerTop); + _answerColors.copyToCurrentPortTransparent(r1, r2); + } + } + } +} + +int32 ReactorHistory::getCurrentNumCorrect() { + int correct = 0; + + for (int i = 0; i < 3; i++) + if (_history[_numGuesses - 1][i] == _answer[i]) + correct++; + + return correct; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/reactor.h b/engines/pegasus/neighborhood/mars/reactor.h new file mode 100644 index 0000000000..86338f8266 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/reactor.h @@ -0,0 +1,108 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_REACTOR_H +#define PEGASUS_NEIGHBORHOOD_MARS_REACTOR_H + +#include "pegasus/elements.h" +#include "pegasus/surface.h" +#include "pegasus/util.h" + +namespace Pegasus { + +class ReactorGuess : public DisplayElement { +public: + ReactorGuess(const DisplayElementID); + virtual ~ReactorGuess() {} + + void initReactorGuess(); + void disposeReactorGuess(); + + void setGuess(int32, int32, int32); + + void draw(const Common::Rect &); + +protected: + int32 _currentGuess[3]; + + Surface _colors; +}; + +class ReactorChoiceHighlight : public DisplayElement { +public: + ReactorChoiceHighlight(const DisplayElementID); + virtual ~ReactorChoiceHighlight() {} + + void initReactorChoiceHighlight(); + void disposeReactorChoiceHighlight(); + + void resetHighlight() { + _choices.clearAllFlags(); + triggerRedraw(); + } + + bool choiceHighlighted(uint32 whichChoice) { return _choices.getFlag(whichChoice); } + + void draw(const Common::Rect &); + + void highlightChoice(uint32 whichChoice) { + _choices.setFlag(whichChoice); + triggerRedraw(); + } + +protected: + Surface _colors; + FlagsArray<byte, 5> _choices; +}; + +class ReactorHistory : public DisplayElement { +public: + ReactorHistory(const DisplayElementID); + virtual ~ReactorHistory() {} + + void initReactorHistory(); + void disposeReactorHistory(); + + void draw(const Common::Rect &); + + void addGuess(int32, int32, int32); + int32 getNumGuesses() { return _numGuesses; } + void clearHistory(); + void setAnswer(int32, int32, int32); + void showAnswer(); + bool isSolved(); + int32 getCurrentNumCorrect(); + +protected: + Surface _colors, _digits, _answerColors; + int32 _answer[3]; + int32 _history[5][3]; + int32 _numGuesses; + bool _showAnswer; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/robotship.cpp b/engines/pegasus/neighborhood/mars/robotship.cpp new file mode 100644 index 0000000000..1f4bbc1779 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/robotship.cpp @@ -0,0 +1,267 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/mars/hermite.h" +#include "pegasus/neighborhood/mars/mars.h" +#include "pegasus/neighborhood/mars/robotship.h" +#include "pegasus/neighborhood/mars/spacechase3d.h" +#include "pegasus/neighborhood/mars/spacejunk.h" + +namespace Pegasus { + +static const TimeScale kRovingScale = kTractorBeamScale; +static const TimeValue kRovingTime = kSixSeconds * kRovingScale; +static const TimeValue kRovingSlop = kThreeSeconds * kRovingScale; + +static const int kNumSpriteColumns = 15; +static const int kNumSpriteRows = 16; + +static const CoordType kInitialLocationLeft = kShuttleWindowLeft - 50; +static const CoordType kInitialLocationTop = kShuttleWindowTop - 50; +static const CoordType kInitialLocationWidth = kShuttleWindowWidth + 100; +static const CoordType kInitialLocationHeight = kShuttleWindowHeight + 100; + +static const CoordType kVelocityVectorLength = 100; +static const CoordType kVelocityVectorSlop = 50; + +static const CoordType kRovingLeft = kShuttleWindowLeft + 20; +static const CoordType kRovingTop = kShuttleWindowTop + 20; +static const CoordType kRovingWidth = kShuttleWindowMidH - kRovingLeft; +static const CoordType kRovingHeight = kShuttleWindowMidV - kRovingTop; + +RobotShip *g_robotShip = 0; + +RobotShip::RobotShip() : _spritesMovie(kNoDisplayElement) { + g_robotShip = this; + _shipRange = Common::Rect(kShuttleWindowLeft, kShuttleWindowTop, kShuttleWindowLeft + kShuttleWindowWidth, + kShuttleWindowTop + kShuttleWindowHeight); + setScale(kRovingScale); + _currentLocation.x = 0; + _currentLocation.y = 0; + _snaring = false; + _dropJunkFuse.setFunctor(new Common::Functor0Mem<void, RobotShip>(this, &RobotShip::timeToDropJunk)); + _duration = 0xFFFFFFFF; +} + +RobotShip::~RobotShip() { + g_robotShip = 0; +} + +void RobotShip::initRobotShip() { + _spritesMovie.initFromMovieFile("Images/Mars/Ship.movie", true); + _spritesMovie.setDisplayOrder(kShuttleRobotShipOrder); + _spritesMovie.moveElementTo(kPlanetStartLeft, kPlanetStartTop); + _spritesMovie.startDisplaying(); + _spritesMovie.show(); + + Common::Rect r; + _spritesMovie.getBounds(r); + _shipWidth = r.width(); + _shipHeight = r.height(); + _dead = false; +} + +void RobotShip::cleanUpRobotShip() { + _dropJunkFuse.stopFuse(); + _spritesMovie.stopDisplaying(); + _spritesMovie.releaseMovie(); +} + +void RobotShip::startMoving() { + if (((PegasusEngine *)g_engine)->getRandomBit()) { + _p4.x = kInitialLocationLeft + ((PegasusEngine *)g_engine)->getRandomNumber(kInitialLocationWidth - 1); + if (((PegasusEngine *)g_engine)->getRandomBit()) + _p4.y = kInitialLocationTop; + else + _p4.y = kInitialLocationTop + kInitialLocationHeight; + } else { + _p4.y = kInitialLocationTop + ((PegasusEngine *)g_engine)->getRandomNumber(kInitialLocationHeight - 1); + if (((PegasusEngine *)g_engine)->getRandomBit()) + _p4.x = kInitialLocationLeft; + else + _p4.x = kInitialLocationLeft + kInitialLocationWidth; + } + + makeVelocityVector(_p4.x, _p4.y, kShuttleWindowLeft + kShuttleWindowWidth / 2, + kShuttleWindowTop + kShuttleWindowHeight / 2, _r4); + newDestination(); + setUpNextDropTime(); +} + +void RobotShip::killRobotShip() { + cleanUpRobotShip(); + _dead = true; +} + +void RobotShip::setUpNextDropTime() { + if (!isSnared()) { + _dropJunkFuse.primeFuse(kJunkDropBaseTime + ((PegasusEngine *)g_engine)->getRandomNumber(kJunkDropSlopTime)); + _dropJunkFuse.lightFuse(); + } +} + +void RobotShip::timeToDropJunk() { + if (g_spaceJunk) { + CoordType x, y; + _spritesMovie.getCenter(x, y); + g_spaceJunk->launchJunk(((PegasusEngine *)g_engine)->getRandomNumber(24), x, y); + } +} + +void RobotShip::newDestination() { + _p1 = _p4; + _r1 = _r4; + + _p4.x = kRovingLeft + ((PegasusEngine *)g_engine)->getRandomNumber(kRovingWidth - 1); + _p4.y = kRovingTop + ((PegasusEngine *)g_engine)->getRandomNumber(kRovingHeight - 1); + + if (((PegasusEngine *)g_engine)->getRandomNumber(7) < 6) { + if (!sameSign(_p4.x - kShuttleWindowMidH, kShuttleWindowMidH - _p1.x)) { + if (sign(_p4.x - kShuttleWindowMidH) > 0) + _p4.x -= kRovingWidth; + else + _p4.x += kRovingWidth; + } + } + + if (((PegasusEngine *)g_engine)->getRandomNumber(7) < 6) { + if (!sameSign(_p4.y - kShuttleWindowMidV, kShuttleWindowMidV - _p1.y)) { + if (sign(_p4.y - kShuttleWindowMidV) > 0) + _p4.y -= kRovingHeight; + else + _p4.y += kRovingHeight; + } + } + + makeVelocityVector(_p4.x, _p4.y, kShuttleWindowLeft + kShuttleWindowWidth / 2, + kShuttleWindowTop + kShuttleWindowHeight / 2, _r4); + stop(); + _duration = kRovingTime + ((PegasusEngine *)g_engine)->getRandomNumber(kRovingSlop - 1); + setSegment(0, _duration); + setTime(0); + start(); +} + +void RobotShip::moveRobotTo(CoordType x, CoordType y) { + _currentLocation.x = x; + _currentLocation.y = y; + + if (_spritesMovie.isMovieValid()) { + _spritesMovie.moveElementTo(x - (_shipWidth >> 1), y - (_shipHeight >> 1)); + + if (x < _shipRange.left) + x = 0; + else if (x > _shipRange.right - 1) + x = _shipRange.width() - 1; + else + x -= _shipRange.left; + + if (y < _shipRange.top) + y = 0; + else if (y > _shipRange.bottom - 1) + y = _shipRange.height() - 1; + else + y -= _shipRange.top; + + x = kNumSpriteColumns * x / _shipRange.width(); + y = kNumSpriteRows * y / _shipRange.height(); + + _spritesMovie.setTime(40 * (x + y * kNumSpriteColumns)); + _spritesMovie.redrawMovieWorld(); + } +} + +bool RobotShip::pointInShuttle(Common::Point &pt) { + Common::Rect r; + _spritesMovie.getBounds(r); + + int dx = r.width() / 4; + int dy = r.height() / 6; + + r.left += dx; + r.right -= dx; + r.top += dy; + r.bottom -= dy; + + return r.contains(pt); +} + +void RobotShip::hitByEnergyBeam(Common::Point impactPoint) { + ((Mars *)g_neighborhood)->decreaseRobotShuttleEnergy(1, impactPoint); + setGlowing(true); + ((PegasusEngine *)g_engine)->delayShell(1, 3); + setGlowing(false); +} + +void RobotShip::hitByGravitonCannon(Common::Point impactPoint) { + GameState.setMarsHitRobotWithCannon(true); + ((Mars *)g_neighborhood)->decreaseRobotShuttleEnergy(6, impactPoint); +} + +void RobotShip::snareByTractorBeam() { + _dropJunkFuse.stopFuse(); + stop(); + + Common::Point currentV; + dHermite(_p1, _p4, _r1, _r4, _lastTime, _duration, currentV); + + _p1 = _currentLocation; + _r1 = currentV; + _p4.x = kShuttleWindowMidH; + _p4.y = kShuttleWindowMidV; + _r4.x = 0; + _r4.y = 0; + _duration = kTractorBeamTime; + _snaring = true; + setSegment(0, _duration); + setTime(0); + start(); +} + +void RobotShip::timeChanged(const TimeValue) { + Common::Point newLocation; + hermite(_p1, _p4, _r1, _r4, _lastTime, _duration, newLocation); + moveRobotTo(newLocation.x, newLocation.y); + + if (_lastTime == _duration) { + if (_snaring) + stop(); + else + newDestination(); + } +} + +void RobotShip::makeVelocityVector(CoordType x1, CoordType y1, CoordType x2, CoordType y2, Common::Point &vector) { + CoordType length = ((PegasusEngine *)g_engine)->getRandomNumber(kVelocityVectorSlop - 1) + kVelocityVectorLength; + vector.x = x2 - x1; + vector.y = y2 - y1; + float oldLength = sqrt((float)(vector.x * vector.x + vector.y * vector.y)); + vector.x = (int)(vector.x * length / oldLength); + vector.y = (int)(vector.y * length / oldLength); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/robotship.h b/engines/pegasus/neighborhood/mars/robotship.h new file mode 100644 index 0000000000..04be3ea56e --- /dev/null +++ b/engines/pegasus/neighborhood/mars/robotship.h @@ -0,0 +1,84 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_ROBOTSHIP_H +#define PEGASUS_NEIGHBORHOOD_MARS_ROBOTSHIP_H + +#include "pegasus/movie.h" + +namespace Pegasus { + +static const CoordType kShuttleMovieWidth = 114; +static const CoordType kShuttleMovieHeight = 42; + +class RobotShip : IdlerTimeBase { +public: + RobotShip(); + virtual ~RobotShip(); + + void initRobotShip(); + void cleanUpRobotShip(); + + void startMoving(); + + void killRobotShip(); + + bool pointInShuttle(Common::Point&); + + void hitByEnergyBeam(Common::Point impactPoint); + void hitByGravitonCannon(Common::Point impactPoint); + + void getShuttleBounds(Common::Rect &r) { _spritesMovie.getBounds(r); } + + void setGlowing(const bool glowing) { _spritesMovie.setGlowing(glowing); } + + void snareByTractorBeam(); + bool isSnared() { return _snaring && getTime() == _duration; } + + bool isDead() { return _dead; } + + void setUpNextDropTime(); + +protected: + void newDestination(); + void moveRobotTo(CoordType, CoordType); + void timeToDropJunk(); + virtual void timeChanged(const TimeValue); + void makeVelocityVector(CoordType, CoordType, CoordType, CoordType, Common::Point &); + + GlowingMovie _spritesMovie; + Common::Rect _shipRange; + int _shipWidth, _shipHeight; + Common::Point _p1, _p4, _r1, _r4, _currentLocation; + FuseFunction _dropJunkFuse; + TimeValue _duration; + bool _snaring, _dead; +}; + +extern RobotShip *g_robotShip; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/shuttleenergymeter.cpp b/engines/pegasus/neighborhood/mars/shuttleenergymeter.cpp new file mode 100644 index 0000000000..cd08dbae6a --- /dev/null +++ b/engines/pegasus/neighborhood/mars/shuttleenergymeter.cpp @@ -0,0 +1,116 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/mars/shuttleenergymeter.h" + +namespace Pegasus { + +ShuttleEnergyMeter::ShuttleEnergyMeter() : FaderAnimation(kNoDisplayElement) { + setBounds(kShuttleEnergyLeft, kShuttleEnergyTop, kShuttleEnergyLeft + kShuttleEnergyWidth, + kShuttleEnergyTop + kShuttleEnergyHeight); + setDisplayOrder(kShuttleStatusOrder); + setFaderValue(0); +} + +void ShuttleEnergyMeter::initShuttleEnergyMeter() { + _meterImage.getImageFromPICTFile("Images/Mars/Shuttle Energy.pict"); + _lowWarning.getImageFromPICTFile("Images/Mars/Shuttle Low Energy.pict"); + startDisplaying(); + show(); +} + +void ShuttleEnergyMeter::disposeShuttleEnergyMeter() { + stopFader(); + hide(); + stopDisplaying(); + _meterImage.deallocateSurface(); + _lowWarning.deallocateSurface(); +} + +void ShuttleEnergyMeter::draw(const Common::Rect &) { + int32 currentValue = getFaderValue(); + + Common::Rect r1, r2, bounds; + getBounds(bounds); + + if (currentValue < kLowShuttleEnergy) { + _lowWarning.getSurfaceBounds(r1); + r2 = r1; + r2.moveTo(bounds.left, bounds.top); + _lowWarning.copyToCurrentPort(r1, r2); + } + + _meterImage.getSurfaceBounds(r1); + r1.right = r1.left + r1.width() * currentValue / kFullShuttleEnergy; + r2 = r1; + r2.moveTo(bounds.left + 102, bounds.top + 6); + _meterImage.copyToCurrentPort(r1, r2); +} + +void ShuttleEnergyMeter::powerUpMeter() { + FaderMoveSpec moveSpec; + moveSpec.makeTwoKnotFaderSpec(kThirtyTicksPerSecond, 0, 0, 45, kFullShuttleEnergy); + startFader(moveSpec); +} + +void ShuttleEnergyMeter::setEnergyValue(const int32 value) { + stopFader(); + FaderMoveSpec moveSpec; + moveSpec.makeTwoKnotFaderSpec(kFifteenTicksPerSecond, value * 3, value, kFullShuttleEnergy * 3, kFullShuttleEnergy); + startFader(moveSpec); +} + +void ShuttleEnergyMeter::drainForTractorBeam() { + stopFader(); + TimeValue startTime = 0, stopTime; + int32 startValue = getFaderValue(), stopValue; + + if (startValue < kTractorBeamEnergy) { + stopTime = startValue * kTractorBeamTime / kTractorBeamEnergy; + stopValue = 0; + } else { + stopTime = kTractorBeamTime; + stopValue = startValue - kTractorBeamEnergy; + } + + FaderMoveSpec moveSpec; + moveSpec.makeTwoKnotFaderSpec(kTractorBeamScale, startTime, startValue, stopTime, stopValue); + startFader(moveSpec); +} + +int32 ShuttleEnergyMeter::getEnergyValue() const { + return getFaderValue(); +} + +void ShuttleEnergyMeter::dropEnergyValue(const int32 delta) { + setEnergyValue(getFaderValue() - delta); +} + +bool ShuttleEnergyMeter::enoughEnergyForTractorBeam() const { + return getEnergyValue() >= kTractorBeamEnergy; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/shuttleenergymeter.h b/engines/pegasus/neighborhood/mars/shuttleenergymeter.h new file mode 100644 index 0000000000..51161e094e --- /dev/null +++ b/engines/pegasus/neighborhood/mars/shuttleenergymeter.h @@ -0,0 +1,73 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_SHUTTLEENERGYMETER_H +#define PEGASUS_NEIGHBORHOOD_MARS_SHUTTLEENERGYMETER_H + +#include "pegasus/fader.h" +#include "pegasus/surface.h" + +namespace Pegasus { + +static const int32 kFullShuttleEnergy = 100; +// Low is 20 percent +static const int32 kLowShuttleEnergy = kFullShuttleEnergy * 20 / 100; + +static const int32 kMinDampingEnergy = 15; +static const int32 kMinGravitonEnergy = 63; + +static const TimeScale kTractorBeamScale = kFifteenTicksPerSecond; +static const TimeValue kTractorBeamTime = kFiveSeconds * kTractorBeamScale; +static const int32 kTractorBeamEnergy = kLowShuttleEnergy; + +class ShuttleEnergyMeter : public FaderAnimation { +public: + ShuttleEnergyMeter(); + ~ShuttleEnergyMeter() {} + + void initShuttleEnergyMeter(); + void disposeShuttleEnergyMeter(); + + void powerUpMeter(); + + void setEnergyValue(const int32); + int32 getEnergyValue() const; + + void dropEnergyValue(const int32); + + void drainForTractorBeam(); + + bool enoughEnergyForTractorBeam() const; + + void draw(const Common::Rect &); + +protected: + Surface _meterImage; + Surface _lowWarning; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/shuttlehud.cpp b/engines/pegasus/neighborhood/mars/shuttlehud.cpp new file mode 100644 index 0000000000..11e826278b --- /dev/null +++ b/engines/pegasus/neighborhood/mars/shuttlehud.cpp @@ -0,0 +1,246 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/mars/robotship.h" +#include "pegasus/neighborhood/mars/shuttlehud.h" + +namespace Pegasus { + +static const CoordType kHUDTargetGridLeft = kShuttleWindowLeft + 16; +static const CoordType kHUDTargetGridTop = kShuttleWindowTop + 8; +static const CoordType kHUDTargetGridWidth = 328; +static const CoordType kHUDTargetGridHeight = 206; + +static const CoordType kHUDRS232Left = kHUDTargetGridLeft + 264; +static const CoordType kHUDRS232Top = kHUDTargetGridTop + 2; + +static const CoordType kHUDLockLeft = kShuttleWindowLeft + 101; +static const CoordType kHUDLockTop = kShuttleWindowTop + 49; +static const CoordType kHUDLockWidth = 145; +static const CoordType kHUDLockHeight = 124; + +static const CoordType kTractorLockWidth = 50; +static const CoordType kTractorLockHeight = 30; + +static const CoordType kTractorLockLeft = kShuttleWindowMidH - kTractorLockWidth / 2; +static const CoordType kTractorLockTop = kShuttleWindowMidV - kTractorLockHeight / 2; +static const CoordType kTractorLockRight = kTractorLockLeft + kTractorLockWidth; +static const CoordType kTractorLockBottom = kTractorLockTop + kTractorLockHeight; + +static const uint16 s_RS232Data[] = { + 0xF0E1, 0xCE70, + 0xF9E1, 0xEF78, + 0x4900, 0x2108, + 0x79C0, 0xE738, + 0x70E1, 0xC770, + 0x5821, 0x0140, + 0x4DE1, 0xEF78, + 0x45C1, 0xEE78 +}; + +static const uint16 s_lockData[] = { + 0xE007, 0xFE1F, 0xF8E0, 0x7000, + 0xE00F, 0xFF3F, 0xFCE0, 0xE000, + 0xE00E, 0x0738, 0x1CE1, 0xC000, + 0xE00E, 0x0738, 0x00FF, 0x8000, + 0xE00E, 0x0738, 0x00FF, 0x0000, + 0xE00E, 0x0738, 0x00E3, 0x8000, + 0xE00E, 0x0738, 0x1CE1, 0xC000, + 0xFFCF, 0xFF3F, 0xFCE0, 0xE000, + 0xFFC7, 0xFE1F, 0xF8E0, 0x7000 +}; + +#define drawHUDLockLine(x1, y1, x2, y2, penX, penY, color) \ + screen->drawThickLine((x1) + kHUDLockLeft, (y1) + kHUDLockTop, \ + (x2) + kHUDLockLeft, (y2) + kHUDLockTop, penX, penY, color) + +#define drawHUDLockArrows(offset, color) \ + drawHUDLockLine(63, 0 + (offset), 68, 5 + (offset), 1, 3, color); \ + drawHUDLockLine(71, 8 + (offset), 77, 14 + (offset), 1, 3, color); \ + drawHUDLockLine(78, 14 + (offset), 84, 8 + (offset), 1, 3, color); \ + drawHUDLockLine(87, 5 + (offset), 92, 0 + (offset), 1, 3, color); \ + drawHUDLockLine(63, 121 - (offset), 68, 116 - (offset), 1, 3, color); \ + drawHUDLockLine(71, 113 - (offset), 77, 107 - (offset), 1, 3, color); \ + drawHUDLockLine(78, 107 - (offset), 84, 113 - (offset), 1, 3, color); \ + drawHUDLockLine(87, 116 - (offset), 92, 121 - (offset), 1, 3, color); \ +\ + drawHUDLockLine(13 + (offset), 47, 18 + (offset), 52, 3, 1, color); \ + drawHUDLockLine(21 + (offset), 55, 27 + (offset), 61, 3, 1, color); \ + drawHUDLockLine(27 + (offset), 62, 21 + (offset), 68, 3, 1, color); \ + drawHUDLockLine(18 + (offset), 71, 13 + (offset), 76, 3, 1, color); \ + drawHUDLockLine(142 - (offset), 47, 137 - (offset), 52, 3, 1, color); \ + drawHUDLockLine(134 - (offset), 55, 128 - (offset), 61, 3, 1, color); \ + drawHUDLockLine(128 - (offset), 62, 134 - (offset), 68, 3, 1, color); \ + drawHUDLockLine(137 - (offset), 71, 142 - (offset), 76, 3, 1, color) + +ShuttleHUD::ShuttleHUD() : DisplayElement(kNoDisplayElement) { + _lightGreen = g_system->getScreenFormat().RGBToColor(0, 204, 0); + _gridDarkGreen = g_system->getScreenFormat().RGBToColor(0, 85, 0); + _lockDarkGreen1 = g_system->getScreenFormat().RGBToColor(0, 68, 0); + _lockDarkGreen2 = g_system->getScreenFormat().RGBToColor(0, 65, 0); + + _targetLocked = false; + setBounds(kShuttleWindowLeft, kShuttleWindowTop, kShuttleWindowLeft + kShuttleWindowWidth, + kShuttleWindowTop + kShuttleWindowHeight); + setDisplayOrder(kShuttleHUDOrder); +} + +void ShuttleHUD::initShuttleHUD() { + startDisplaying(); + startIdling(); +} + +void ShuttleHUD::cleanUpShuttleHUD() { + stopIdling(); + stopDisplaying(); +} + +void ShuttleHUD::showTargetGrid() { + show(); +} + +void ShuttleHUD::hideTargetGrid() { + hide(); + unlockOnTarget(); +} + +void ShuttleHUD::useIdleTime() { + if (isVisible()) { + Common::Rect r; + g_robotShip->getShuttleBounds(r); + if (r.left < kTractorLockRight && r.right > kTractorLockLeft && r.top < kTractorLockBottom && r.bottom > kTractorLockTop) + lockOnTarget(); + else + unlockOnTarget(); + } +} + +void ShuttleHUD::lockOnTarget() { + if (!_targetLocked) { + _targetLocked = true; + triggerRedraw(); + } +} + +void ShuttleHUD::unlockOnTarget() { + if (_targetLocked) { + _targetLocked = false; + triggerRedraw(); + } +} + +void ShuttleHUD::draw(const Common::Rect &) { + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getWorkArea(); + + for (int y = 0; y < 35; y++) { + Common::Rect r; + + if (y & 1) { + if (y == 17) { + r = Common::Rect(0, 0, 4, 2); + r.moveTo(kHUDTargetGridLeft + 8, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 12, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + + r = Common::Rect(0, 0, 6, 2); + r.moveTo(kHUDTargetGridLeft + 2, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _lightGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 8, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _lightGreen); + + r = Common::Rect(0, 0, 23, 2); + r.moveTo(kHUDTargetGridLeft + 12, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _lightGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 35, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _lightGreen); + } else if (y == 1 || y == 15 || y == 19 || y == 33) { + r = Common::Rect(0, 0, 4, 2); + r.moveTo(kHUDTargetGridLeft + 2, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 6, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + + r = Common::Rect(0, 0, 15, 2); + r.moveTo(kHUDTargetGridLeft + 8, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 23, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + } else { + r = Common::Rect(0, 0, 4, 2); + r.moveTo(kHUDTargetGridLeft + 2, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 6, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + + r = Common::Rect(0, 0, 10, 2); + r.moveTo(kHUDTargetGridLeft + 8, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 18, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + } + } else { + r = Common::Rect(0, 0, 2, 2); + r.moveTo(kHUDTargetGridLeft, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 2, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + + r = Common::Rect(0, 0, 4, 2); + r.moveTo(kHUDTargetGridLeft + 8, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + r.moveTo(kHUDTargetGridLeft + kHUDTargetGridWidth - 12, y * 6 + kHUDTargetGridTop); + screen->fillRect(r, _gridDarkGreen); + } + } + + drawOneBitImageOr(screen, s_RS232Data, 2, Common::Rect(kHUDRS232Left, kHUDRS232Top, + kHUDRS232Left + 29, kHUDRS232Top + 8), _gridDarkGreen); + + if (_targetLocked) { + drawHUDLockArrows(0, _lockDarkGreen2); + drawHUDLockArrows(12, _lockDarkGreen1); + drawHUDLockArrows(24, _lightGreen); + drawOneBitImageOr(screen, s_lockData, 4, Common::Rect(kHUDLockLeft, kHUDLockTop + 115, + kHUDLockLeft + 52, kHUDLockTop + 115 + 9), _lightGreen); + } +} + +void ShuttleHUD::drawOneBitImageOr(Graphics::Surface *screen, const uint16 *data, int pitch, const Common::Rect &bounds, uint32 color) { + for (int y = 0; y < bounds.height(); y++) { + for (int x = 0; x < bounds.width(); x++) { + if ((data[y * pitch + x / 16] & (1 << (15 - (x % 16)))) != 0) { + if (screen->format.bytesPerPixel == 2) + WRITE_UINT16((byte *)screen->getBasePtr(x + bounds.left, y + bounds.top), color); + else + WRITE_UINT32((byte *)screen->getBasePtr(x + bounds.left, y + bounds.top), color); + } + } + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/shuttlehud.h b/engines/pegasus/neighborhood/mars/shuttlehud.h new file mode 100644 index 0000000000..f7dbbaeae8 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/shuttlehud.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_MARS_SHUTTLEHUD_H +#define PEGASUS_NEIGHBORHOOD_MARS_SHUTTLEHUD_H + +#include "pegasus/elements.h" +#include "pegasus/timers.h" + +namespace Pegasus { + +class ShuttleHUD : public DisplayElement, public Idler { +public: + ShuttleHUD(); + + void showTargetGrid(); + void hideTargetGrid(); + + void initShuttleHUD(); + void cleanUpShuttleHUD(); + + bool isTargetLocked() { return _targetLocked; } + + void draw(const Common::Rect &); + +protected: + void useIdleTime(); + void lockOnTarget(); + void unlockOnTarget(); + void drawOneBitImageOr(Graphics::Surface *, const uint16 *, int, const Common::Rect &, uint32); + + bool _targetLocked; + uint32 _lightGreen, _gridDarkGreen, _lockDarkGreen1, _lockDarkGreen2; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/shuttleweapon.cpp b/engines/pegasus/neighborhood/mars/shuttleweapon.cpp new file mode 100644 index 0000000000..b4c360b280 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/shuttleweapon.cpp @@ -0,0 +1,129 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/mars/robotship.h" +#include "pegasus/neighborhood/mars/shuttleweapon.h" +#include "pegasus/neighborhood/mars/spacejunk.h" + +namespace Pegasus { + +ShuttleWeapon::ShuttleWeapon() : IdlerAnimation(kNoDisplayElement) { + setScale(kShuttleWeaponScale); + _weaponDuration = kShuttleWeaponScale * 2; + setSegment(0, _weaponDuration); + setBounds(kShuttleWindowLeft, kShuttleWindowTop, kShuttleWindowLeft + kShuttleWindowWidth, + kShuttleWindowTop + kShuttleWindowHeight); + setDisplayOrder(kShuttleWeaponFrontOrder); +} + +void ShuttleWeapon::initShuttleWeapon() { + startDisplaying(); +} + +void ShuttleWeapon::cleanUpShuttleWeapon() { + stop(); + hide(); + stopDisplaying(); +} + +bool ShuttleWeapon::canFireWeapon() { + return !isRunning(); +} + +void ShuttleWeapon::fireWeapon(const CoordType hStop, const CoordType vStop) { + if (!isRunning()) { + stop(); + setTime(0); + show(); + + Common::Point pt2D(hStop, vStop); + project2DTo3D(pt2D, kShuttleDistance, _weaponTarget); + _weaponTime = 0; + setDisplayOrder(kShuttleWeaponFrontOrder); + start(); + } +} + +void ShuttleWeapon::updateWeaponPosition() { + _weaponTime = (float)_lastTime / _weaponDuration; + linearInterp(_weaponOrigin, _weaponTarget, _weaponTime, _weaponLocation); + + if (_weaponTime == 1.0) { + stop(); + hide(); + } else { + triggerRedraw(); + } +} + +void ShuttleWeapon::timeChanged(const TimeValue) { + updateWeaponPosition(); + + bool hit = false; + Common::Point impactPoint; + + if (g_spaceJunk->isJunkFlying()) { + hit = collisionWithJunk(impactPoint); + if (hit) { + stop(); + hide(); + hitJunk(impactPoint); + } + } + + if (!hit && _weaponTime == 1.0 && collisionWithShuttle(impactPoint)) + hitShuttle(impactPoint); +} + +bool ShuttleWeapon::collisionWithJunk(Common::Point &impactPoint) { + if (getDisplayOrder() == kShuttleWeaponFrontOrder) { + Point3D junkPosition; + g_spaceJunk->getJunkPosition(junkPosition); + + if (junkPosition.z < _weaponLocation.z) { + setDisplayOrder(kShuttleWeaponBackOrder); + project3DTo2D(_weaponLocation, impactPoint); + return g_spaceJunk->pointInJunk(impactPoint); + } + } + + return false; +} + +bool ShuttleWeapon::collisionWithShuttle(Common::Point &impactPoint) { + project3DTo2D(_weaponLocation, impactPoint); + return g_robotShip->pointInShuttle(impactPoint); +} + +void ShuttleWeapon::hitJunk(Common::Point impactPoint) { + g_spaceJunk->hitByEnergyBeam(impactPoint); +} + +void ShuttleWeapon::hitShuttle(Common::Point impactPoint) { + g_robotShip->hitByEnergyBeam(impactPoint); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/shuttleweapon.h b/engines/pegasus/neighborhood/mars/shuttleweapon.h new file mode 100644 index 0000000000..38529c8919 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/shuttleweapon.h @@ -0,0 +1,68 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_SHUTTLEWEAPON_H +#define PEGASUS_NEIGHBORHOOD_MARS_SHUTTLEWEAPON_H + +#include "pegasus/elements.h" +#include "pegasus/neighborhood/mars/spacechase3d.h" + +namespace Pegasus { + +// Can fire multiple times? +// For now, no... +// clone2727 adds: And now forever + +static const TimeScale kShuttleWeaponScale = kFifteenTicksPerSecond; + +class ShuttleWeapon : public IdlerAnimation { +public: + ShuttleWeapon(); + virtual ~ShuttleWeapon() {} + + virtual void initShuttleWeapon(); + virtual void cleanUpShuttleWeapon(); + + virtual void fireWeapon(const CoordType, const CoordType); + + bool canFireWeapon(); + +protected: + void timeChanged(const TimeValue); + virtual void updateWeaponPosition(); + virtual bool collisionWithJunk(Common::Point &impactPoint); + bool collisionWithShuttle(Common::Point &impactPoint); + virtual void hitJunk(Common::Point impactPoint); + virtual void hitShuttle(Common::Point impactPoint); + + Point3D _weaponOrigin, _weaponTarget; + Point3D _weaponLocation; + float _weaponTime; + TimeValue _weaponDuration; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/spacechase3d.cpp b/engines/pegasus/neighborhood/mars/spacechase3d.cpp new file mode 100644 index 0000000000..05f8233763 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/spacechase3d.cpp @@ -0,0 +1,106 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/neighborhood/mars/spacechase3d.h" + +namespace Pegasus { + +void project3DTo2D(const Point3D &pt3D, Common::Point &pt2D) { + pt2D.x = (int)convertSpaceXToScreenH(pt3D.x, pt3D.z); + pt2D.y = (int)convertSpaceYToScreenV(pt3D.y, pt3D.z); +} + +void project2DTo3D(const Common::Point &pt2D, const float screenDistance, Point3D &pt3D) { + pt3D.x = convertScreenHToSpaceX(pt2D.x, screenDistance); + pt3D.y = convertScreenVToSpaceY(pt2D.y, screenDistance); + pt3D.z = screenDistance; +} + +void linearInterp(const Point3D &pt1, const Point3D &pt2, const float t, Point3D &pt3) { + pt3.x = pt1.x + (pt2.x - pt1.x) * t; + pt3.y = pt1.y + (pt2.y - pt1.y) * t; + pt3.z = pt1.z + (pt2.z - pt1.z) * t; +} + +void linearInterp(const Point3D &pt1, const float x2, const float y2, const float z2, const float t, Point3D &pt3) { + pt3.x = pt1.x + (x2 - pt1.x) * t; + pt3.y = pt1.y + (y2 - pt1.y) * t; + pt3.z = pt1.z + (z2 - pt1.z) * t; +} + +void linearInterp(const float x1, const float y1, const float z1, const Point3D &pt2, const float t, Point3D &pt3) { + pt3.x = x1 + (pt2.x - x1) * t; + pt3.y = y1 + (pt2.y - y1) * t; + pt3.z = z1 + (pt2.z - z1) * t; +} + +void linearInterp(const float x1, const float y1, const float z1, const float x2, const float y2, const float z2, + const float t, Point3D &pt3) { + pt3.x = x1 + (x2 - x1) * t; + pt3.y = y1 + (y2 - y1) * t; + pt3.z = z1 + (z2 - z1) * t; +} + +void linearInterp(const Common::Point &pt1, const Common::Point &pt2, const float t, Common::Point &pt3) { + pt3.x = (int)(pt1.x + (pt2.x - pt1.x) * t); + pt3.y = (int)(pt1.y + (pt2.y - pt1.y) * t); +} + +void linearInterp(const Common::Point &pt1, const float h2, const float v2, const float t, Common::Point &pt3) { + pt3.x = (int)(pt1.x + (h2 - pt1.x) * t); + pt3.y = (int)(pt1.y + (v2 - pt1.y) * t); +} + +void linearInterp(const float h1, const float v1, const Common::Point &pt2, const float t, Common::Point &pt3) { + pt3.x = (int)(h1 + (pt2.x - h1) * t); + pt3.y = (int)(v1 + (pt2.y - v1) * t); +} + +void linearInterp(const float h1, const float v1, const float h2, const float v2, const float t, Common::Point &pt3) { + pt3.x = (int)(h1 + (h2 - h1) * t); + pt3.y = (int)(v1 + (v2 - v1) * t); +} + +float linearInterp(const float arg1, const float arg2, const float t) { + return arg1 + (arg2 - arg1) * t; +} + +bool isNegative(int a) { + return a < 0; +} + +bool isPositive(int a) { + return a > 0; +} + +int sign(int a) { + return isNegative(a) ? -1 : isPositive(a) ? 1 : 0; +} + +bool sameSign(int a, int b) { + return sign(a) == sign(b); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/spacechase3d.h b/engines/pegasus/neighborhood/mars/spacechase3d.h new file mode 100644 index 0000000000..f6815e69bd --- /dev/null +++ b/engines/pegasus/neighborhood/mars/spacechase3d.h @@ -0,0 +1,91 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_SPACECHASE3D_H +#define PEGASUS_NEIGHBORHOOD_MARS_SPACECHASE3D_H + +#include "pegasus/neighborhood/mars/constants.h" + +namespace Pegasus { + +// This is approximately right for a field of view of 72 degrees +// (Should be set to the tangent of FOV). +//static const float kTangentFOV = 0.76254; +static const float kTangentFOV = 1.0; + +// Define these as macros and they can be used to define constants... +#define convertSpaceXToScreenH(x, z) \ + ((x) / (z) * (kScreenWidth / (2 * kTangentFOV)) + kShuttleWindowMidH) + +#define convertSpaceYToScreenV(y, z) \ + (kShuttleWindowMidV - (y) / (z) * (kScreenWidth / (2 * kTangentFOV))) + +#define convertScreenHToSpaceX(x, d) \ + (((2.0 * kTangentFOV) / kScreenWidth) * ((float)(x) - kShuttleWindowMidH) * (d)) + +#define convertScreenVToSpaceY(y, d) \ + (((2.0 * kTangentFOV) / kScreenWidth) * ((float)kShuttleWindowMidV - (y)) * (d)) + +struct Point3D { + float x, y, z; + + Point3D() : x(0), y(0), z(0) {} + Point3D(float x1, float y1, float z1) : x(x1), y(y1), z(z1) {} + bool operator==(const Point3D &p) const { return x == p.x && y == p.y && z == p.z; } + bool operator!=(const Point3D &p) const { return x != p.x || y != p.y || z != p.z; } + + void translate(float dx, float dy, float dz) { + x += dx; + y += dy; + z += dz; + } +}; + +static const int kScreenWidth = kShuttleWindowWidth; + +bool isNegative(int a); +bool isPositive(int a); +int sign(int a); +bool sameSign(int a, int b); + +void project3DTo2D(const Point3D &pt3D, Common::Point &pt2D); +void project2DTo3D(const Common::Point &pt2D, const float screenDistance, Point3D &pt3D); + +void linearInterp(const Point3D &pt1, const Point3D &pt2, const float t, Point3D &pt3); +void linearInterp(const Point3D &pt1, const float x2, const float y2, const float z2, const float t, Point3D &pt3); +void linearInterp(const float x1, const float y1, const float z1, const Point3D &pt2, const float t, Point3D &pt3); +void linearInterp(const float x1, const float y1, const float z1, const float x2, + const float y2, const float z2, const float t, Point3D &pt3); + +void linearInterp(const Common::Point &pt1, const Common::Point &pt2, const float t, Common::Point &pt3); +void linearInterp(const Common::Point &pt1, const float h2, const float v2, const float t, Common::Point &pt3); +void linearInterp(const float h1, const float v1, const Common::Point &pt2, const float t, Common::Point &pt3); +void linearInterp(const float h1, const float v1, const float h2, const float v2, const float t, Common::Point &pt3); + +float linearInterp(const float arg1, const float arg2, const float t); + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/spacejunk.cpp b/engines/pegasus/neighborhood/mars/spacejunk.cpp new file mode 100644 index 0000000000..3912e659c2 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/spacejunk.cpp @@ -0,0 +1,212 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/mars/mars.h" +#include "pegasus/neighborhood/mars/spacejunk.h" + +namespace Pegasus { + +static const CoordType kMaxBounceSize = 90; +static const CoordType kBounceTargetHRange = 640 - kMaxBounceSize - 2; +static const CoordType kBounceTargetVRange = 480 - kMaxBounceSize - 2; + +static const float kJunkXTarget = 0; +static const float kJunkYTarget = 0; +static const float kJunkZTarget = kJunkMinDistance; + +SpaceJunk *g_spaceJunk = 0; + +SpaceJunk::SpaceJunk(const DisplayElementID id) : ScalingMovie(id) { + _timer.setScale(kJunkTimeScale); + _bouncing = false; + g_spaceJunk = this; +} + +SpaceJunk::~SpaceJunk() { + g_spaceJunk = 0; +} + +void SpaceJunk::launchJunk(int16 whichJunk, CoordType xOrigin, CoordType yOrigin) { + _bouncing = false; + TimeValue startTime = whichJunk * 16 * 40; + TimeValue stopTime = startTime + 16 * 40; + + _launchPoint = Point3D(convertScreenHToSpaceX(xOrigin, kJunkMaxDistance), + convertScreenVToSpaceY(yOrigin, kJunkMaxDistance), kJunkMaxDistance); + startIdling(); + stop(); + setFlags(0); + setSegment(startTime, stopTime); + setFlags(kLoopTimeBase); + setTime(startTime); + start(); + show(); + _timer.stop(); + _timer.setSegment(0, kJunkTravelTime); + _timer.setTime(0); + + // Force it to set up correctly from the get-go + useIdleTime(); + + _timer.start(); +} + +void SpaceJunk::setCenter(const CoordType centerX, const CoordType centerY) { + _center.x = centerX; + _center.y = centerY; + + Common::Rect r; + getBounds(r); + r.moveTo(CLIP<int>(centerX - (r.width() >> 1), 0, 640 - r.width()), CLIP<int>(centerY - (r.height() >> 1), 0, 480 - r.height())); + setBounds(r); +} + +void SpaceJunk::setScaleSize(const CoordType size) { + Common::Rect r; + r.left = _center.x - (size >> 1); + r.top = _center.y - (size >> 1); + r.right = r.left + size; + r.bottom = r.top + size; + setBounds(r); +} + +void SpaceJunk::useIdleTime() { + if (_bouncing) { + TimeValue time = _timer.getTime(); + Common::Point pt; + pt.x = linearInterp(0, _bounceTime, time, _bounceStart.x, _bounceStop.x); + pt.y = linearInterp(0, _bounceTime, time, _bounceStart.y, _bounceStop.y); + CoordType size = linearInterp(0, _bounceTime, time, _bounceSizeStart, _bounceSizeStop); + setCenter(pt.x, pt.y); + setScaleSize(size); + + if (time == _bounceTime) { + stop(); + stopIdling(); + hide(); + ((Mars *)g_neighborhood)->setUpNextDropTime(); + } + } else { + float t = (float)_timer.getTime() / kJunkTravelTime; + linearInterp(_launchPoint, kJunkXTarget, kJunkYTarget, kJunkZTarget, t, _junkPosition); + + Common::Point pt2D; + project3DTo2D(_junkPosition, pt2D); + setCenter(pt2D.x, pt2D.y); + setScaleSize((int)(convertSpaceYToScreenV(_junkPosition.y - kJunkSize / 2, _junkPosition.z) - + convertSpaceYToScreenV(_junkPosition.y + kJunkSize / 2, _junkPosition.z))); + + if (t == 1.0) { + rebound(kCollisionReboundTime); + ((Mars *)g_neighborhood)->hitByJunk(); + } + } +} + +bool SpaceJunk::pointInJunk(const Common::Point &pt) { + Common::Rect r; + getBounds(r); + + int dx = r.width() / 4; + int dy = r.height() / 4; + + r.left += dx; + r.right -= dx; + r.top += dy; + r.top -= dy; + + return r.contains(pt); +} + +void SpaceJunk::rebound(const TimeValue reboundTime) { + Common::Rect bounds; + getBounds(bounds); + + _bounceStart.x = (bounds.left + bounds.right) >> 1; + _bounceStart.y = (bounds.top + bounds.bottom) >> 1; + + PegasusEngine *vm = (PegasusEngine *)g_engine; + + switch (vm->getRandomNumber(3)) { + case 0: + _bounceStop.x = kMaxBounceSize / 2 + 1 + vm->getRandomNumber(kBounceTargetHRange - 1); + _bounceStop.y = kMaxBounceSize / 2 + 1; + break; + case 1: + _bounceStop.x = kMaxBounceSize / 2 + 1 + vm->getRandomNumber(kBounceTargetHRange - 1); + _bounceStop.y = 480 - kMaxBounceSize / 2 + 1; + break; + case 2: + _bounceStop.x = kMaxBounceSize / 2 + 1; + _bounceStop.y = kMaxBounceSize / 2 + 1 + vm->getRandomNumber(kBounceTargetVRange - 1); + break; + case 3: + _bounceStop.x = 640 - kMaxBounceSize / 2 + 1; + _bounceStop.y = kMaxBounceSize / 2 + 1 + vm->getRandomNumber(kBounceTargetVRange - 1); + break; + } + + _bounceSizeStart = bounds.width(); + _bounceSizeStop = MIN(_bounceSizeStart, kMaxBounceSize); + + _timer.stop(); + _timer.setSegment(0, reboundTime); + _bounceTime = reboundTime; + _timer.setTime(0); + _timer.start(); + + _bouncing = true; +} + +void SpaceJunk::hitByEnergyBeam(Common::Point) { + rebound(kWeaponReboundTime); + setGlowing(true); + ((PegasusEngine *)g_engine)->delayShell(1, 3); + setGlowing(false); +} + +void SpaceJunk::hitByGravitonCannon(Common::Point impactPoint) { + stop(); + stopIdling(); + hide(); + + Common::Rect r; + getBounds(r); + r = Common::Rect::center(impactPoint.x, impactPoint.y, r.width(), r.height()); + + ((Mars *)g_neighborhood)->showBigExplosion(r, kShuttleJunkOrder); + ((Mars *)g_neighborhood)->setUpNextDropTime(); +} + +void SpaceJunk::getJunkPosition(Point3D &position) { + position = _junkPosition; +} + +bool SpaceJunk::isJunkFlying() { + return isIdling(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/spacejunk.h b/engines/pegasus/neighborhood/mars/spacejunk.h new file mode 100644 index 0000000000..2ec9192513 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/spacejunk.h @@ -0,0 +1,78 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_SPACEJUNK_H +#define PEGASUS_NEIGHBORHOOD_MARS_SPACEJUNK_H + +#include "pegasus/movie.h" +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/mars/spacechase3d.h" + +namespace Pegasus { + +static const CoordType kJunkMaxScreenSize = 250; + +static const float kJunkSize = convertScreenVToSpaceY(kShuttleWindowMidV - kJunkMaxScreenSize / 2, kJunkMinDistance) - + convertScreenVToSpaceY(kShuttleWindowMidV + kJunkMaxScreenSize / 2, kJunkMinDistance); + +class SpaceJunk : public ScalingMovie, public Idler { +public: + SpaceJunk(const DisplayElementID); + virtual ~SpaceJunk(); + + void setCenter(const CoordType, const CoordType); + void setScaleSize(const CoordType); + + void useIdleTime(); + + void launchJunk(int16, CoordType, CoordType); + + void getJunkPosition(Point3D &); + bool isJunkFlying(); + + bool pointInJunk(const Common::Point &); + + void hitByEnergyBeam(Common::Point impactPoint); + void hitByGravitonCannon(Common::Point impactPoint); + + bool junkFlying() { return _timer.isRunning(); } + +protected: + void rebound(const TimeValue); + + TimeBase _timer; + Point3D _launchPoint, _junkPosition; + Common::Point _center; + bool _bouncing; + Common::Point _bounceStart, _bounceStop; + CoordType _bounceSizeStart, _bounceSizeStop; + TimeValue _bounceTime; +}; + +extern SpaceJunk *g_spaceJunk; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/mars/tractorbeam.cpp b/engines/pegasus/neighborhood/mars/tractorbeam.cpp new file mode 100644 index 0000000000..d3f9c94328 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/tractorbeam.cpp @@ -0,0 +1,139 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/pegasus.h" +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/mars/tractorbeam.h" + +namespace Pegasus { + +TractorBeam::TractorBeam() : DisplayElement(kNoDisplayElement) { + setBounds(kShuttleTractorLeft, kShuttleTractorTop, kShuttleTractorLeft + kShuttleTractorWidth, + kShuttleTractorTop + kShuttleTractorHeight); + setDisplayOrder(kShuttleTractorBeamOrder); + +} + +static const int kHalfWidth = kShuttleTractorWidth >> 1; +static const int kHalfHeight = kShuttleTractorHeight >> 1; + +static const int kW3Vert = kHalfHeight * kHalfHeight * kHalfHeight; +static const int kW3Div2Vert = kW3Vert >> 1; + +static const int kW3Horiz = kHalfWidth * kHalfWidth * kHalfWidth; +static const int kW3Div2Horiz = kW3Horiz >> 1; + +static const int kMaxLevel = 50; + +static const int kAVert = -2 * kMaxLevel; +static const int kBVert = 3 * kMaxLevel * kHalfHeight; + +#define READ_PIXEL(ptr) \ + if (screen->format.bytesPerPixel == 2) \ + color = READ_UINT16(ptr); \ + else \ + color = READ_UINT32(ptr); \ + screen->format.colorToRGB(color, r, g, b) + +#define WRITE_PIXEL(ptr) \ + color = screen->format.RGBToColor(r, g, b); \ + if (screen->format.bytesPerPixel == 2) \ + WRITE_UINT16(ptr, color); \ + else \ + WRITE_UINT32(ptr, color) + +#define DO_BLEND(ptr) \ + READ_PIXEL(ptr); \ + g += (((0xff - g) * blendHoriz) >> 8); \ + b += (((0xff - b) * blendHoriz) >> 8); \ + WRITE_PIXEL(ptr) + +void TractorBeam::draw(const Common::Rect &) { + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getWorkArea(); + + // Set up vertical DDA. + int blendVert = 0; + int dVert = 0; + int d1Vert = kAVert + kBVert; + int d2Vert = 6 * kAVert + 2 * kBVert; + int d3Vert = 6 * kAVert; + + byte *rowPtrTop = (byte *)screen->getBasePtr(_bounds.left, _bounds.top); + byte *rowPtrBottom = (byte *)screen->getBasePtr(_bounds.left, _bounds.top + ((kHalfHeight << 1) - 1)); + + for (int y = kHalfHeight; y > 0; y--) { + // Set up horizontal DDA + int A = -2 * blendVert; + int B = 3 * blendVert * kHalfWidth; + int blendHoriz = 0; + int dHoriz = 0; + int d1Horiz = A + B; + int d2Horiz = 6 * A + 2 * B; + int d3Horiz = 6 * A; + + byte *pTopLeft = rowPtrTop; + byte *pTopRight = rowPtrTop + (kHalfWidth * 2 - 1) * screen->format.bytesPerPixel; + byte *pBottomLeft = rowPtrBottom; + byte *pBottomRight = rowPtrBottom + (kHalfWidth * 2 - 1) * screen->format.bytesPerPixel; + + for (int x = kHalfWidth; x > 0; x--) { + byte r, g, b; + uint32 color; + + DO_BLEND(pTopLeft); + DO_BLEND(pTopRight); + DO_BLEND(pBottomLeft); + DO_BLEND(pBottomRight); + + pTopLeft += screen->format.bytesPerPixel; + pBottomLeft += screen->format.bytesPerPixel; + pTopRight -= screen->format.bytesPerPixel; + pBottomRight -= screen->format.bytesPerPixel; + + while (dHoriz > kW3Div2Horiz) { + blendHoriz++; + dHoriz -= kW3Horiz; + } + + dHoriz += d1Horiz; + d1Horiz += d2Horiz; + d2Horiz += d3Horiz; + } + + rowPtrTop += screen->pitch; + rowPtrBottom -= screen->pitch; + + while (dVert > kW3Div2Vert) { + blendVert++; + dVert -= kW3Vert; + } + + dVert += d1Vert; + d1Vert += d2Vert; + d2Vert += d3Vert; + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/mars/tractorbeam.h b/engines/pegasus/neighborhood/mars/tractorbeam.h new file mode 100644 index 0000000000..cd87992d11 --- /dev/null +++ b/engines/pegasus/neighborhood/mars/tractorbeam.h @@ -0,0 +1,43 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_MARS_TRACTORBEAM_H +#define PEGASUS_NEIGHBORHOOD_MARS_TRACTORBEAM_H + +#include "pegasus/elements.h" + +namespace Pegasus { + +class TractorBeam : public DisplayElement { +public: + TractorBeam(); + virtual ~TractorBeam() {} + + void draw(const Common::Rect &); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/neighborhood.cpp b/engines/pegasus/neighborhood/neighborhood.cpp new file mode 100644 index 0000000000..07be62c957 --- /dev/null +++ b/engines/pegasus/neighborhood/neighborhood.cpp @@ -0,0 +1,1774 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/debug.h" +#include "common/stream.h" + +#include "pegasus/compass.h" +#include "pegasus/cursor.h" +#include "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/graphics.h" +#include "pegasus/input.h" +#include "pegasus/interaction.h" +#include "pegasus/interface.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/mapchip.h" +#include "pegasus/neighborhood/neighborhood.h" +#include "pegasus/neighborhood/tsa/fulltsa.h" +#include "pegasus/neighborhood/tsa/tinytsa.h" + +namespace Pegasus { + +StriderCallBack::StriderCallBack(Neighborhood *neighborhood) { + _neighborhood = neighborhood; +} + +void StriderCallBack::callBack() { + _neighborhood->checkStriding(); +} + +static const TimeValue kStridingSlop = 39; + +Neighborhood *g_neighborhood = 0; + +Neighborhood::Neighborhood(InputHandler *nextHandler, PegasusEngine *vm, const Common::String &resName, NeighborhoodID id) + : InputHandler(nextHandler), IDObject(id), _vm(vm), _resName(resName), _navMovie(kNavMovieID), _stridingCallBack(this), + _neighborhoodNotification(kNeighborhoodNotificationID, (NotificationManager *)vm), _pushIn(kNoDisplayElement), + _turnPush(kTurnPushID), _croppedMovie(kCroppedMovieID) { + GameState.setOpenDoorLocation(kNoRoomID, kNoDirection); + _currentAlternate = 0; + _currentActivation = kActivateHotSpotAlways; + _interruptionFilter = kFilterAllInput; + allowInput(true); + resetLastExtra(); + g_neighborhood = this; + _currentInteraction = 0; + _doneWithInteraction = false; + _croppedMovie.setDisplayOrder(kCroppedMovieLayer); +} + +Neighborhood::~Neighborhood() { + for (HotspotIterator it = _neighborhoodHotspots.begin(); it != _neighborhoodHotspots.end(); it++) + _vm->getAllHotspots().remove(*it); + + _neighborhoodHotspots.deleteHotspots(); + g_neighborhood = 0; + + loadLoopSound1(""); + loadLoopSound2(""); + newInteraction(kNoInteractionID); + + if (g_AIArea) + g_AIArea->removeAllRules(); +} + +void Neighborhood::init() { + _neighborhoodNotification.notifyMe(this, kNeighborhoodFlags, kNeighborhoodFlags); + _navMovieCallBack.setNotification(&_neighborhoodNotification); + _turnPushCallBack.setNotification(&_neighborhoodNotification); + _delayCallBack.setNotification(&_neighborhoodNotification); + _spotSoundCallBack.setNotification(&_neighborhoodNotification); + + debug(0, "Loading '%s' neighborhood resources", _resName.c_str()); + + Common::SeekableReadStream *stream = _vm->_resFork->getResource(_doorTable.getResTag(), _resName); + if (!stream) + error("Failed to load doors"); + _doorTable.loadFromStream(stream); + delete stream; + + stream = _vm->_resFork->getResource(_exitTable.getResTag(), _resName); + if (!stream) + error("Failed to load exits"); + _exitTable.loadFromStream(stream); + delete stream; + + stream = _vm->_resFork->getResource(_extraTable.getResTag(), _resName); + if (!stream) + error("Failed to load extras"); + _extraTable.loadFromStream(stream); + delete stream; + + stream = _vm->_resFork->getResource(_hotspotInfoTable.getResTag(), _resName); + if (!stream) + error("Failed to load hotspot info"); + _hotspotInfoTable.loadFromStream(stream); + delete stream; + + stream = _vm->_resFork->getResource(_spotTable.getResTag(), _resName); + if (!stream) + error("Failed to load spots"); + _spotTable.loadFromStream(stream); + delete stream; + + stream = _vm->_resFork->getResource(_turnTable.getResTag(), _resName); + if (!stream) + error("Failed to load turns"); + _turnTable.loadFromStream(stream); + delete stream; + + stream = _vm->_resFork->getResource(_viewTable.getResTag(), _resName); + if (!stream) + error("Failed to load views"); + _viewTable.loadFromStream(stream); + delete stream; + + stream = _vm->_resFork->getResource(_zoomTable.getResTag(), _resName); + if (!stream) + error("Failed to load zooms"); + _zoomTable.loadFromStream(stream); + delete stream; + + createNeighborhoodSpots(); + + _navMovie.initFromMovieFile(getNavMovieName()); + _navMovie.setVolume(_vm->getSoundFXLevel()); + + Common::String soundSpotsName = getSoundSpotsName(); + if (soundSpotsName.empty()) { + _spotSounds.disposeSound(); + } else { + _spotSounds.initFromQuickTime(getSoundSpotsName()); + _spotSounds.setVolume(_vm->getSoundFXLevel()); + } + + _navMovie.setDisplayOrder(kNavMovieOrder); + _navMovie.startDisplaying(); + + Common::Rect bounds; + _navMovie.getBounds(bounds); + _pushIn.allocateSurface(bounds); + + _turnPush.setInAndOutElements(&_pushIn, &_navMovie); + _turnPush.setDisplayOrder(kTurnPushOrder); + _turnPush.startDisplaying(); + _navMovieCallBack.initCallBack(&_navMovie, kCallBackAtExtremes); + _stridingCallBack.initCallBack(&_navMovie, kCallBackAtTime); + _turnPushCallBack.initCallBack(&_turnPush, kCallBackAtExtremes); + _delayCallBack.initCallBack(&_delayTimer, kCallBackAtExtremes); + _spotSoundCallBack.initCallBack(&_spotSounds, kCallBackAtExtremes); + + setUpAIRules(); + + if (g_compass) + g_compass->setFaderValue(getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection())); + + _soundLoop1.attachFader(&_loop1Fader); + _soundLoop2.attachFader(&_loop2Fader); + startIdling(); +} + +void Neighborhood::start() { + GameState.setCurrentRoom(GameState.getLastRoom()); + GameState.setCurrentDirection(GameState.getLastDirection()); + arriveAt(GameState.getNextRoom(), GameState.getNextDirection()); +} + +void Neighborhood::receiveNotification(Notification *, const NotificationFlags flags) { + if ((flags & (kNeighborhoodMovieCompletedFlag | kTurnCompletedFlag)) != 0 && g_AIArea) + g_AIArea->unlockAI(); + if (flags & kMoveForwardCompletedFlag) + arriveAt(GameState.getNextRoom(), GameState.getNextDirection()); + if (flags & kTurnCompletedFlag) + turnTo(GameState.getNextDirection()); + if (flags & kSpotCompletedFlag) + spotCompleted(); + if (flags & kDoorOpenCompletedFlag) + doorOpened(); + if (flags & kActionRequestCompletedFlag) + popActionQueue(); + if (flags & kDeathExtraCompletedFlag) + die(_extraDeathReason); +} + +void Neighborhood::arriveAt(const RoomID room, const DirectionConstant direction) { + if (g_map) + g_map->moveToMapLocation(GameState.getCurrentNeighborhood(), room, direction); + + GameState.setCurrentNeighborhood(getObjectID()); + + _currentActivation = kActivateHotSpotAlways; + _interruptionFilter = kFilterAllInput; + + if (room != GameState.getCurrentRoom() || direction != GameState.getCurrentDirection()) { + GameState.setCurrentRoom(room); + GameState.setCurrentDirection(direction); + loadAmbientLoops(); + activateCurrentView(room, direction, kSpotOnArrivalMask); + } else { + loadAmbientLoops(); + showViewFrame(getViewTime(GameState.getCurrentRoom(), GameState.getCurrentDirection())); + } + + if (GameState.getOpenDoorRoom() != kNoRoomID) { + // Arriving always closes a door. + loadAmbientLoops(); + closeDoorOffScreen(GameState.getOpenDoorRoom(), GameState.getOpenDoorDirection()); + GameState.setOpenDoorLocation(kNoRoomID, kNoDirection); + } + + if (g_compass) + g_compass->setFaderValue(getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection())); + + if (g_AIArea) + g_AIArea->checkMiddleArea(); + + checkContinuePoint(room, direction); +} + +// These functions can be overridden to tweak the exact frames used. + +void Neighborhood::getExitEntry(const RoomID room, const DirectionConstant direction, ExitTable::Entry &entry) { + entry = _exitTable.findEntry(room, direction, _currentAlternate); + + if (entry.isEmpty()) + entry = _exitTable.findEntry(room, direction, kNoAlternateID); +} + +TimeValue Neighborhood::getViewTime(const RoomID room, const DirectionConstant direction) { + if (GameState.getOpenDoorRoom() == room && GameState.getOpenDoorDirection() == direction) { + // If we get here, the door entry for this location must exist. + DoorTable::Entry doorEntry = _doorTable.findEntry(room, direction, _currentAlternate); + + if (doorEntry.isEmpty()) + doorEntry = _doorTable.findEntry(room, direction, kNoAlternateID); + + return doorEntry.movieEnd - 1; + } + + ViewTable::Entry viewEntry = _viewTable.findEntry(room, direction, _currentAlternate); + + if (viewEntry.isEmpty()) + viewEntry = _viewTable.findEntry(room, direction, kNoAlternateID); + + return viewEntry.time; +} + +void Neighborhood::getDoorEntry(const RoomID room, const DirectionConstant direction, DoorTable::Entry &doorEntry) { + doorEntry = _doorTable.findEntry(room, direction, _currentAlternate); + + if (doorEntry.isEmpty()) + doorEntry = _doorTable.findEntry(room, direction, kNoAlternateID); +} + +DirectionConstant Neighborhood::getTurnEntry(const RoomID room, const DirectionConstant direction, const TurnDirection turnDirection) { + TurnTable::Entry turnEntry = _turnTable.findEntry(room, direction, turnDirection, _currentAlternate); + + if (turnEntry.isEmpty()) + turnEntry = _turnTable.findEntry(room, direction, turnDirection, kNoAlternateID); + + return turnEntry.endDirection; +} + +void Neighborhood::findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &spotEntry) { + spotEntry = _spotTable.findEntry(room, direction, flags, _currentAlternate); + + if (spotEntry.isEmpty()) + spotEntry = _spotTable.findEntry(room, direction, flags, kNoAlternateID); +} + +void Neighborhood::getZoomEntry(const HotSpotID id, ZoomTable::Entry &zoomEntry) { + zoomEntry = _zoomTable.findEntry(id); +} + +void Neighborhood::getHotspotEntry(const HotSpotID id, HotspotInfoTable::Entry &hotspotEntry) { + hotspotEntry = _hotspotInfoTable.findEntry(id); +} + +void Neighborhood::getExtraEntry(const uint32 id, ExtraTable::Entry &extraEntry) { + extraEntry = _extraTable.findEntry(id); +} + +///////////////////////////////////////////// +// +// "Can" functions: Called to see whether or not a user is allowed to do something + +CanMoveForwardReason Neighborhood::canMoveForward(ExitTable::Entry &entry) { + DoorTable::Entry door; + + getExitEntry(GameState.getCurrentRoom(), GameState.getCurrentDirection(), entry); + getDoorEntry(GameState.getCurrentRoom(), GameState.getCurrentDirection(), door); + + // Fixed this so that doors that don't lead anywhere can be opened, but not walked + // through. + if (door.flags & kDoorPresentMask) { + if (GameState.isCurrentDoorOpen()) { + if (entry.exitRoom == kNoRoomID) + return kCantMoveBlocked; + else + return kCanMoveForward; + } else if (door.flags & kDoorLockedMask) { + return kCantMoveDoorLocked; + } else { + return kCantMoveDoorClosed; + } + } else if (entry.exitRoom == kNoRoomID) { + return kCantMoveBlocked; + } + + return kCanMoveForward; +} + +CanTurnReason Neighborhood::canTurn(TurnDirection turnDirection, DirectionConstant &nextDir) { + nextDir = getTurnEntry(GameState.getCurrentRoom(), GameState.getCurrentDirection(), turnDirection); + + if (nextDir == kNoDirection) + return kCantTurnNoTurn; + + return kCanTurn; +} + +CanOpenDoorReason Neighborhood::canOpenDoor(DoorTable::Entry &entry) { + getDoorEntry(GameState.getCurrentRoom(), GameState.getCurrentDirection(), entry); + + if (entry.flags & kDoorPresentMask) { + if (GameState.isCurrentDoorOpen()) + return kCantOpenAlreadyOpen; + + if (entry.flags & kDoorLockedMask) + return kCantOpenLocked; + + return kCanOpenDoor; + } + + return kCantOpenNoDoor; +} + +void Neighborhood::createNeighborhoodSpots() { + Common::SeekableReadStream *hotspotList = _vm->_resFork->getResource(MKTAG('H', 'S', 'L', 's'), _resName); + if (!hotspotList) + error("Could not load neighborhood hotspots"); + + uint32 hotspotCount = hotspotList->readUint32BE(); + + while (hotspotCount--) { + uint16 id = hotspotList->readUint16BE(); + uint32 flags = hotspotList->readUint32BE(); + uint32 rgnSize = hotspotList->readUint32BE(); + + int32 startPos = hotspotList->pos(); + + debug(0, "Hotspot %d:", id); + Region region(hotspotList); + + hotspotList->seek(startPos + rgnSize); + + Hotspot *hotspot = new Hotspot(id); + hotspot->setHotspotFlags(flags); + hotspot->setArea(region); + + _vm->getAllHotspots().push_back(hotspot); + _neighborhoodHotspots.push_back(hotspot); + } + + delete hotspotList; +} + +void Neighborhood::popActionQueue() { + if (!_actionQueue.empty()) { + QueueRequest topRequest = _actionQueue.pop(); + + switch (topRequest.requestType) { + case kNavExtraRequest: + _navMovie.stop(); + break; + case kSpotSoundRequest: + _spotSounds.stopSound(); + break; + case kDelayRequest: + _delayTimer.stop(); + break; + } + + serviceActionQueue(); + } +} + +void Neighborhood::serviceActionQueue() { + if (!_actionQueue.empty()) { + QueueRequest &topRequest = _actionQueue.front(); + + if (!topRequest.playing) { + topRequest.playing = true; + switch (topRequest.requestType) { + case kNavExtraRequest: + startExtraSequence(topRequest.extra, topRequest.flags, topRequest.interruptionFilter); + break; + case kSpotSoundRequest: + _spotSounds.stopSound(); + _spotSounds.playSoundSegment(topRequest.start, topRequest.stop); + _interruptionFilter = topRequest.interruptionFilter; + _spotSoundCallBack.setCallBackFlag(topRequest.flags); + _spotSoundCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + break; + case kDelayRequest: + _delayTimer.stop(); + _delayCallBack.setCallBackFlag(topRequest.flags); + _delayTimer.setSegment(0, topRequest.start, topRequest.stop); + _delayTimer.setTime(0); + _delayCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + _interruptionFilter = topRequest.interruptionFilter; + _delayTimer.start(); + break; + } + } + } else { + _interruptionFilter = kFilterAllInput; + } +} + +void Neighborhood::requestAction(const QueueRequestType requestType, const ExtraID extra, const TimeValue in, const TimeValue out, + const InputBits interruptionFilter, const NotificationFlags flags) { + + QueueRequest request; + + request.requestType = requestType; + request.extra = extra; + request.start = in; + request.stop = out; + request.interruptionFilter = interruptionFilter; + request.playing = false; + request.flags = flags | kActionRequestCompletedFlag; + request.notification = &_neighborhoodNotification; + _actionQueue.push(request); + if (_actionQueue.size() == 1) + serviceActionQueue(); +} + +void Neighborhood::requestExtraSequence(const ExtraID whichExtra, const NotificationFlags flags, const InputBits interruptionFilter) { + requestAction(kNavExtraRequest, whichExtra, 0, 0, interruptionFilter, flags); +} + +void Neighborhood::requestSpotSound(const TimeValue in, const TimeValue out, const InputBits interruptionFilter, const NotificationFlags flags) { + requestAction(kSpotSoundRequest, 0xFFFFFFFF, in, out, interruptionFilter, flags); +} + +void Neighborhood::playSpotSoundSync(const TimeValue in, const TimeValue out) { + // Let the action queue play out first... + while (!actionQueueEmpty()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->checkNotifications(); + _vm->_system->delayMillis(10); + } + + _spotSounds.stopSound(); + _spotSounds.playSoundSegment(in, out); + + while (_spotSounds.isPlaying()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } +} + +void Neighborhood::requestDelay(const TimeValue delayDuration, const TimeScale delayScale, const InputBits interruptionFilter, const NotificationFlags flags) { + requestAction(kDelayRequest, 0xFFFFFFFF, delayDuration, delayScale, interruptionFilter, flags); +} + +bool operator==(const QueueRequest &arg1, const QueueRequest &arg2) { + return arg1.requestType == arg2.requestType && arg1.extra == arg2.extra && + arg1.start == arg2.start && arg1.stop == arg2.stop; +} + +bool operator!=(const QueueRequest &arg1, const QueueRequest &arg2) { + return !operator==(arg1, arg2); +} + +Common::String Neighborhood::getBriefingMovie() { + if (_currentInteraction) + return _currentInteraction->getBriefingMovie(); + + return Common::String(); +} + +Common::String Neighborhood::getEnvScanMovie() { + if (_currentInteraction) + return _currentInteraction->getEnvScanMovie(); + + return Common::String(); +} + +uint Neighborhood::getNumHints() { + if (_currentInteraction) + return _currentInteraction->getNumHints(); + + return 0; +} + +Common::String Neighborhood::getHintMovie(uint hintNum) { + if (_currentInteraction) + return _currentInteraction->getHintMovie(hintNum); + + return Common::String(); +} + +bool Neighborhood::canSolve() { + if (_currentInteraction) + return _currentInteraction->canSolve(); + + return false; +} + +void Neighborhood::doSolve() { + if (_currentInteraction) + _currentInteraction->doSolve(); +} + +bool Neighborhood::okayToJump() { + return !_vm->playerHasItemID(kGasCanister) && !_vm->playerHasItemID(kMachineGun); +} + +AirQuality Neighborhood::getAirQuality(const RoomID) { + return kAirQualityGood; +} + +void Neighborhood::checkStriding() { + if (stillMoveForward()) { + ExitTable::Entry nextExit; + getExitEntry(GameState.getNextRoom(), GameState.getNextDirection(), nextExit); + keepStriding(nextExit); + } else { + stopStriding(); + } +} + +bool Neighborhood::stillMoveForward() { + Input input; + + InputHandler::readInputDevice(input); + return input.upButtonAnyDown(); +} + +void Neighborhood::keepStriding(ExitTable::Entry &nextExitEntry) { + FaderMoveSpec compassMove; + + if (g_map) + g_map->moveToMapLocation(GameState.getCurrentNeighborhood(), GameState.getNextRoom(), GameState.getNextDirection()); + + if (g_compass) + getExitCompassMove(nextExitEntry, compassMove); + + GameState.setCurrentRoom(GameState.getNextRoom()); + GameState.setCurrentDirection(GameState.getNextDirection()); + GameState.setNextRoom(nextExitEntry.exitRoom); + GameState.setNextDirection(nextExitEntry.exitDirection); + + if (nextExitEntry.movieEnd == nextExitEntry.exitEnd) + scheduleNavCallBack(kNeighborhoodMovieCompletedFlag | kMoveForwardCompletedFlag); + else + scheduleStridingCallBack(nextExitEntry.movieEnd - kStridingSlop, kStrideCompletedFlag); + + if (g_compass) + g_compass->startFader(compassMove); +} + +void Neighborhood::stopStriding() { + _navMovie.stop(); + _neighborhoodNotification.setNotificationFlags(kNeighborhoodMovieCompletedFlag | + kMoveForwardCompletedFlag, kNeighborhoodMovieCompletedFlag | kMoveForwardCompletedFlag); +} + +// Compass support +int16 Neighborhood::getStaticCompassAngle(const RoomID, const DirectionConstant dir) { + // North, south, east, west + static const int16 compassAngles[] = { 0, 180, 90, 270 }; + return compassAngles[dir]; +} + +void Neighborhood::getExitCompassMove(const ExitTable::Entry &exitEntry, FaderMoveSpec &compassMove) { + int32 startAngle = getStaticCompassAngle(exitEntry.room, exitEntry.direction); + int32 stopAngle = getStaticCompassAngle(exitEntry.exitRoom, exitEntry.exitDirection); + + if (startAngle > stopAngle) { + if (stopAngle + 180 < startAngle) + stopAngle += 360; + } else { + if (startAngle + 180 < stopAngle) + startAngle += 360; + } + + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), exitEntry.movieStart, startAngle, exitEntry.movieEnd, stopAngle); +} + +void Neighborhood::scheduleNavCallBack(NotificationFlags flags) { + _navMovieCallBack.cancelCallBack(); + + if (flags != 0) { + _navMovieCallBack.setCallBackFlag(flags); + _navMovieCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + } +} + +void Neighborhood::scheduleStridingCallBack(const TimeValue strideStop, NotificationFlags flags) { + _stridingCallBack.cancelCallBack(); + + if (flags != 0) + _stridingCallBack.scheduleCallBack(kTriggerTimeFwd, strideStop, _navMovie.getScale()); +} + +void Neighborhood::moveNavTo(const CoordType h, const CoordType v) { + CoordType oldH, oldV; + _navMovie.getLocation(oldH, oldV); + + CoordType offH = h - oldH; + CoordType offV = v - oldV; + + _navMovie.moveElementTo(h, v); + _turnPush.moveElementTo(h, v); + + if (offH != 0 || offV != 0) + for (HotspotList::iterator it = _neighborhoodHotspots.begin(); it != _neighborhoodHotspots.end(); it++) + if ((*it)->getHotspotFlags() & kNeighborhoodSpotFlag) + (*it)->moveSpot(offH, offV); +} + +void Neighborhood::activateHotspots() { + InputHandler::activateHotspots(); + + for (HotspotInfoTable::iterator it = _hotspotInfoTable.begin(); it != _hotspotInfoTable.end(); it++) { + HotspotInfoTable::Entry entry = *it; + + if (entry.hotspotRoom == GameState.getCurrentRoom() && entry.hotspotDirection == GameState.getCurrentDirection() + && (entry.hotspotActivation == _currentActivation || entry.hotspotActivation == kActivateHotSpotAlways)) { + Hotspot *hotspot = _vm->getAllHotspots().findHotspotByID(entry.hotspot); + if (hotspot) + activateOneHotspot(entry, hotspot); + } + } +} + +void Neighborhood::clickInHotspot(const Input &input, const Hotspot *clickedSpot) { + HotSpotFlags flags = clickedSpot->getHotspotFlags(); + + if ((flags & (kPickUpItemSpotFlag | kPickUpBiochipSpotFlag)) != 0) { + ItemID itemID = kNoItemID; + + for (HotspotInfoTable::iterator it = _hotspotInfoTable.begin(); it != _hotspotInfoTable.end(); it++) { + if (it->hotspot == clickedSpot->getObjectID()) { + itemID = it->hotspotItem; + break; + } + } + + if (itemID != kNoItemID) { + Item *draggingItem = _vm->getAllItems().findItemByID(itemID); + + if (draggingItem) { + takeItemFromRoom(draggingItem); + + if ((flags & kPickUpItemSpotFlag) != 0) + _vm->dragItem(input, draggingItem, kDragInventoryPickup); + else + _vm->dragItem(input, draggingItem, kDragBiochipPickup); + } + } + } else { + // Check other flags here? + if ((flags & kZoomSpotFlags) != 0) { + zoomTo(clickedSpot); + } else if ((flags & kPlayExtraSpotFlag) != 0) { + HotspotInfoTable::Entry hotspotEntry; + getHotspotEntry(clickedSpot->getObjectID(), hotspotEntry); + startExtraSequence(hotspotEntry.hotspotExtra, kExtraCompletedFlag, kFilterNoInput); + } else if ((flags & kOpenDoorSpotFlag) != 0) { + openDoor(); + } else { + InputHandler::clickInHotspot(input, clickedSpot); + } + } +} + +void Neighborhood::cantMoveThatWay(CanMoveForwardReason reason) { + switch (reason) { + case kCantMoveDoorClosed: + case kCantMoveDoorLocked: + openDoor(); + break; + case kCantMoveBlocked: + zoomUpOrBump(); + break; + default: + bumpIntoWall(); + break; + } +} + +void Neighborhood::cantOpenDoor(CanOpenDoorReason) { + bumpIntoWall(); +} + +void Neighborhood::turnTo(const DirectionConstant direction) { + if (g_map) + g_map->moveToMapLocation(GameState.getCurrentNeighborhood(), GameState.getCurrentRoom(), direction); + + // clone2727 says: Is this necessary? + _vm->_gfx->setCurSurface(_navMovie.getSurface()); + _pushIn.copyToCurrentPort(); + _vm->_gfx->setCurSurface(_vm->_gfx->getWorkArea()); + + // Added 2/10/97. Shouldn't this be here? Shouldn't we set the current activation to + // always when turning to a new view? + _currentActivation = kActivateHotSpotAlways; + + _interruptionFilter = kFilterAllInput; + + if (direction != GameState.getCurrentDirection()) { + GameState.setCurrentDirection(direction); + activateCurrentView(GameState.getCurrentRoom(), direction, kSpotOnTurnMask); + } else { + showViewFrame(getViewTime(GameState.getCurrentRoom(), GameState.getCurrentDirection())); + } + + if (GameState.getOpenDoorRoom() != kNoRoomID) { + // Turning always closes a door. + loadAmbientLoops(); + closeDoorOffScreen(GameState.getOpenDoorRoom(), GameState.getOpenDoorDirection()); + GameState.setOpenDoorLocation(kNoRoomID, kNoDirection); + } + + if (g_AIArea) + g_AIArea->checkMiddleArea(); + + checkContinuePoint(GameState.getCurrentRoom(), direction); + + _vm->_cursor->hideUntilMoved(); +} + +void Neighborhood::spotCompleted() { + _interruptionFilter = kFilterAllInput; + showViewFrame(getViewTime(GameState.getCurrentRoom(), GameState.getCurrentDirection())); +} + +void Neighborhood::doorOpened() { + _interruptionFilter = kFilterAllInput; + + // 2/23/97 + // Fixes funny bug with doors that are opened by dropping things on them... + setCurrentActivation(kActivateHotSpotAlways); + + GameState.setOpenDoorLocation(GameState.getCurrentRoom(), GameState.getCurrentDirection()); + + SpotTable::Entry entry; + findSpotEntry(GameState.getCurrentRoom(), GameState.getCurrentDirection(), kSpotOnDoorOpenMask, entry); + + if (entry.dstFlags & kSpotOnDoorOpenMask) { + startSpotOnceOnly(entry.movieStart, entry.movieEnd); + } else { + findSpotEntry(GameState.getCurrentRoom(), GameState.getCurrentDirection(), kSpotOnDoorOpenMask | kSpotLoopsMask, entry); + + if (entry.dstFlags & kSpotOnDoorOpenMask) + startSpotLoop(entry.movieStart, entry.movieEnd); + } + + loadAmbientLoops(); + + if (g_map) + g_map->moveToMapLocation(GameState.getCurrentNeighborhood(), GameState.getNextRoom(), GameState.getNextDirection()); + + if (g_AIArea) + g_AIArea->checkMiddleArea(); +} + +void Neighborhood::moveForward() { + ExitTable::Entry exitEntry; + CanMoveForwardReason moveReason = canMoveForward(exitEntry); + + if (moveReason == kCanMoveForward) + startExitMovie(exitEntry); + else + cantMoveThatWay(moveReason); +} + +void Neighborhood::turn(const TurnDirection turnDirection) { + DirectionConstant nextDir; + CanTurnReason turnReason = canTurn(turnDirection, nextDir); + + if (turnReason == kCanTurn) + startTurnPush(turnDirection, getViewTime(GameState.getCurrentRoom(), nextDir), nextDir); + else + cantTurnThatWay(turnReason); +} + +void Neighborhood::turnLeft() { + turn(kTurnLeft); +} + +void Neighborhood::turnRight() { + turn(kTurnRight); +} + +void Neighborhood::turnUp() { + turn(kTurnUp); +} + +void Neighborhood::turnDown() { + turn(kTurnDown); +} + +void Neighborhood::openDoor() { + DoorTable::Entry door; + CanOpenDoorReason doorReason = canOpenDoor(door); + + if (doorReason == kCanOpenDoor) + startDoorOpenMovie(door.movieStart, door.movieEnd); + else + cantOpenDoor(doorReason); +} + +void Neighborhood::zoomTo(const Hotspot *hotspot) { + ZoomTable::Entry zoomEntry; + getZoomEntry(hotspot->getObjectID(), zoomEntry); + if (!zoomEntry.isEmpty()) + startZoomMovie(zoomEntry); +} + +void Neighborhood::updateViewFrame() { + showViewFrame(getViewTime(GameState.getCurrentRoom(), GameState.getCurrentDirection())); +} + +void Neighborhood::startSpotLoop(TimeValue startTime, TimeValue stopTime, NotificationFlags flags) { + _turnPush.hide(); + startMovieSequence(startTime, stopTime, flags, true, kFilterAllInput); +} + +void Neighborhood::showViewFrame(TimeValue viewTime) { + if ((int32)viewTime >= 0) { + _turnPush.hide(); + _navMovie.stop(); + _navMovie.setFlags(0); + _navMovie.setSegment(0, _navMovie.getDuration()); + _navMovie.setTime(viewTime); + + Common::Rect pushBounds; + _turnPush.getBounds(pushBounds); + + _navMovie.moveElementTo(pushBounds.left, pushBounds.top); + _navMovie.show(); + _navMovie.redrawMovieWorld(); + } +} + +void Neighborhood::startExtraSequence(const ExtraID extraID, const NotificationFlags flags, const InputBits interruptionFilter) { + ExtraTable::Entry entry; + getExtraEntry(extraID, entry); + + if (entry.movieStart != 0xffffffff) + playExtraMovie(entry, flags, interruptionFilter); +} + +bool Neighborhood::startExtraSequenceSync(const ExtraID extraID, const InputBits interruptionFilter) { + InputDevice.waitInput(interruptionFilter); + return prepareExtraSync(extraID) && waitMovieFinish(&_navMovie, interruptionFilter); +} + +void Neighborhood::loopExtraSequence(const uint32 extraID, NotificationFlags flags) { + ExtraTable::Entry entry; + getExtraEntry(extraID, entry); + + if (entry.movieStart != 0xffffffff) { + _lastExtra = extraID; + startSpotLoop(entry.movieStart, entry.movieEnd, flags); + } +} + +bool Neighborhood::navMoviePlaying() { + return _navMovie.isRunning(); +} + +void Neighborhood::playDeathExtra(ExtraID extra, DeathReason deathReason) { + _extraDeathReason = deathReason; + startExtraSequence(extra, kDeathExtraCompletedFlag, kFilterNoInput); +} + +void Neighborhood::die(const DeathReason deathReason) { + loadLoopSound1(""); + loadLoopSound2(""); + _vm->die(deathReason); +} + +void Neighborhood::setSoundFXLevel(const uint16 fxLevel) { + if (_navMovie.isSurfaceValid()) + _navMovie.setVolume(fxLevel); + if (_spotSounds.isSoundLoaded()) + _spotSounds.setVolume(fxLevel); + if (_currentInteraction) + _currentInteraction->setSoundFXLevel(fxLevel); +} + +void Neighborhood::setAmbienceLevel(const uint16 ambientLevel) { + if (_soundLoop1.isSoundLoaded()) + _loop1Fader.setMasterVolume(_vm->getAmbienceLevel()); + if (_soundLoop2.isSoundLoaded()) + _loop2Fader.setMasterVolume(_vm->getAmbienceLevel()); + if (_currentInteraction) + _currentInteraction->setAmbienceLevel(ambientLevel); +} + +// Force the exit taken from (room, direction, alternate) to come to a stop. +void Neighborhood::forceStridingStop(const RoomID room, const DirectionConstant direction, const AlternateID alternate) { + ExitTable::Entry entry = _exitTable.findEntry(room, direction, alternate); + + if (entry.movieStart != 0xffffffff) { + TimeValue strideStop = entry.exitEnd; + TimeValue exitStop = entry.movieEnd; + + if (strideStop != exitStop) { + for (ExitTable::iterator it = _exitTable.begin(); it != _exitTable.end(); it++) { + entry = *it; + + if (entry.exitEnd == strideStop && entry.movieEnd <= exitStop) { + entry.exitEnd = exitStop; + *it = entry; + } + } + } + } +} + +// Restore the exit taken from (room, direction, alternate) to stride. +void Neighborhood::restoreStriding(const RoomID room, const DirectionConstant direction, const AlternateID alternate) { + ExitTable::Entry entry = _exitTable.findEntry(room, direction, alternate); + + if (entry.movieStart != 0xffffffff) { + TimeValue strideStop = entry.exitEnd; + TimeValue exitStop = entry.movieEnd; + + if (strideStop != entry.originalEnd) { + for (ExitTable::iterator it = _exitTable.begin(); it != _exitTable.end(); it++) { + entry = *it; + + if (entry.exitEnd == strideStop && entry.movieEnd <= exitStop) { + entry.exitEnd = entry.originalEnd; + *it = entry; + } + } + } + } +} + +HotspotInfoTable::Entry *Neighborhood::findHotspotEntry(const HotSpotID id) { + for (HotspotInfoTable::iterator it = _hotspotInfoTable.begin(); it != _hotspotInfoTable.end(); it++) + if (it->hotspot == id) + return &(*it); + + return 0; +} + +void Neighborhood::hideNav() { + _isRunning = _navMovie.isRunning(); + _navMovie.stop(); + _navMovie.hide(); + _turnPush.stopFader(); + _turnPush.hide(); +} + +void Neighborhood::showNav() { + _navMovie.show(); + _turnPush.hide(); + if (_isRunning) + _navMovie.start(); +} + +void Neighborhood::startExitMovie(const ExitTable::Entry &exitEntry) { + FaderMoveSpec compassMove; + + if (g_compass) + getExitCompassMove(exitEntry, compassMove); + + GameState.setNextRoom(exitEntry.exitRoom); + GameState.setNextDirection(exitEntry.exitDirection); + + if (exitEntry.movieEnd == exitEntry.exitEnd) // Just a walk. + startMovieSequence(exitEntry.movieStart, exitEntry.movieEnd, kMoveForwardCompletedFlag, kFilterNoInput, false); + else // We're stridin'! + startMovieSequence(exitEntry.movieStart, exitEntry.exitEnd, kStrideCompletedFlag, kFilterNoInput, false, exitEntry.movieEnd); + + if (g_compass) + g_compass->startFader(compassMove); +} + +void Neighborhood::startZoomMovie(const ZoomTable::Entry &zoomEntry) { + FaderMoveSpec compassMove; + + if (g_compass) + getZoomCompassMove(zoomEntry, compassMove); + + GameState.setNextRoom(zoomEntry.room); + GameState.setNextDirection(zoomEntry.direction); + + startMovieSequence(zoomEntry.movieStart, zoomEntry.movieEnd, kMoveForwardCompletedFlag, kFilterNoInput, false); + + if (g_compass) + g_compass->startFader(compassMove); +} + +void Neighborhood::startDoorOpenMovie(const TimeValue startTime, const TimeValue stopTime) { + startMovieSequence(startTime, stopTime, kDoorOpenCompletedFlag, kFilterNoInput, false); +} + +void Neighborhood::startTurnPush(const TurnDirection turnDirection, const TimeValue newView, const DirectionConstant nextDir) { + if (g_AIArea) + g_AIArea->lockAIOut(); + + _vm->_cursor->hide(); + + GameState.setNextDirection(nextDir); + + _interruptionFilter = kFilterNoInput; + _turnPush.stopFader(); + + // Set up callback. + _turnPushCallBack.setCallBackFlag(kTurnCompletedFlag); + _turnPushCallBack.scheduleCallBack(kTriggerAtStop, 0, 0); + + // Stop nav movie. + _navMovie.stop(); + _navMovie.setFlags(0); + + // Set segment of nav movie to whole movie, so that subsequent initFromMovieFrame + // will work. + _navMovie.setSegment(0, _navMovie.getDuration()); + + _pushIn.initFromMovieFrame(_navMovie.getMovie(), newView); + + _navMovie.hide(); + + switch (turnDirection) { + case kTurnLeft: + _turnPush.setSlideDirection(kSlideRightMask); + break; + case kTurnRight: + _turnPush.setSlideDirection(kSlideLeftMask); + break; + case kTurnUp: + _turnPush.setSlideDirection(kSlideDownMask); + break; + case kTurnDown: + _turnPush.setSlideDirection(kSlideUpMask); + break; + } + + _turnPush.show(); + + FaderMoveSpec moveSpec; + moveSpec.makeTwoKnotFaderSpec(60, 0, 0, 15, 1000); + _turnPush.startFader(moveSpec); + + if (g_compass) { + _turnPush.pauseFader(); + + int32 startAngle = getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection()); + int32 stopAngle = getStaticCompassAngle(GameState.getCurrentRoom(), nextDir); + + if (turnDirection == kTurnLeft) { + if (startAngle < stopAngle) + startAngle += 360; + } else { + if (stopAngle < startAngle) + stopAngle += 360; + } + + FaderMoveSpec turnSpec; + _turnPush.getCurrentFaderMove(turnSpec); + + FaderMoveSpec compassMove; + compassMove.makeTwoKnotFaderSpec(turnSpec.getFaderScale(), turnSpec.getNthKnotTime(0), startAngle, turnSpec.getNthKnotTime(1), stopAngle); + g_compass->startFader(compassMove); + } + + _turnPushCallBack.cancelCallBack(); + _turnPush.continueFader(); + + do { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } while (_turnPush.isFading()); + + _turnPush.stopFader(); + _neighborhoodNotification.setNotificationFlags(kTurnCompletedFlag, kTurnCompletedFlag); +} + +void Neighborhood::playExtraMovie(const ExtraTable::Entry &extraEntry, const NotificationFlags flags, const InputBits interruptionInput) { + FaderMoveSpec compassMove; + + if (g_compass) + getExtraCompassMove(extraEntry, compassMove); + + _lastExtra = extraEntry.extra; + _turnPush.hide(); + startMovieSequence(extraEntry.movieStart, extraEntry.movieEnd, flags, false, interruptionInput); + + if (g_compass) + g_compass->startFader(compassMove); +} + +void Neighborhood::activateCurrentView(const RoomID room, const DirectionConstant direction, SpotFlags flag) { + SpotTable::Entry entry; + findSpotEntry(room, direction, flag, entry); + + if (entry.dstFlags & flag) { + startSpotOnceOnly(entry.movieStart, entry.movieEnd); + } else { + findSpotEntry(room, direction, flag | kSpotLoopsMask, entry); + + if (entry.dstFlags & flag) + startSpotLoop(entry.movieStart, entry.movieEnd); + else + showViewFrame(getViewTime(room, direction)); + } +} + +void Neighborhood::activateOneHotspot(HotspotInfoTable::Entry &entry, Hotspot *hotspot) { + switch (_vm->getDragType()) { + case kDragInventoryUse: + if ((hotspot->getHotspotFlags() & kDropItemSpotFlag) != 0 && + _vm->getDraggingItem()->getObjectID() == entry.hotspotItem) + hotspot->setActive(); + break; + case kDragInventoryPickup: + case kDragBiochipPickup: + // Do nothing -- neighborhoods activate no hot spots in this case... + break; + default: + if ((hotspot->getHotspotFlags() & kPickUpBiochipSpotFlag) != 0) { + Item *item = _vm->getAllItems().findItemByID(entry.hotspotItem); + if (item && item->getItemNeighborhood() == getObjectID()) + hotspot->setActive(); + } else { + HotSpotFlags flags = hotspot->getHotspotFlags(); + + if ((flags & kNeighborhoodSpotFlag) != 0) { + if (flags & kOpenDoorSpotFlag) { + if (!GameState.isCurrentDoorOpen()) + hotspot->setActive(); + } else if ((flags & (kZoomSpotFlags | kClickSpotFlag | kPlayExtraSpotFlag)) != 0) { + hotspot->setActive(); + } else if ((flags & kPickUpItemSpotFlag) != 0) { + // Changed this 2/19/96 + // Should only light up this hot spot if the item's taken flag is not + // set. It's not based on neighborhood ID since that can be reset by the + // destroying process. + + if (!GameState.isTakenItemID(entry.hotspotItem)) + hotspot->setActive(); + } + } + } + break; + } +} + +void Neighborhood::startSpotOnceOnly(TimeValue startTime, TimeValue stopTime) { + _turnPush.hide(); + startMovieSequence(startTime, stopTime, kSpotCompletedFlag, kFilterNoInput, false); +} + +void Neighborhood::startMovieSequence(const TimeValue startTime, const TimeValue stopTime, NotificationFlags flags, bool loopSequence, + const InputBits interruptionInput, const TimeValue strideStop) { + if (!loopSequence && g_AIArea) + g_AIArea->lockAIOut(); + + _interruptionFilter = interruptionInput; + + // Stop the movie before doing anything else + _navMovie.stop(); + + Common::Rect pushBounds; + _turnPush.getBounds(pushBounds); + + _navMovie.moveElementTo(pushBounds.left, pushBounds.top); + _navMovie.show(); + _navMovie.setFlags(0); + _navMovie.setSegment(startTime, stopTime); + _navMovie.setTime(startTime); + + if (loopSequence) + _navMovie.setFlags(kLoopTimeBase); + else + flags |= kNeighborhoodMovieCompletedFlag; + + if (strideStop != 0xffffffff) + // Subtract a little slop from the striding stop time to keep from "pumping" at the + // end of a walk. + // 40 is one frame (scale == 600, 15 fps). + scheduleStridingCallBack(strideStop - kStridingSlop, flags); + else + scheduleNavCallBack(flags); + + _navMovie.start(); +} + +void Neighborhood::throwAwayInterface() { + _doorTable.clear(); + _exitTable.clear(); + _extraTable.clear(); + _hotspotInfoTable.clear(); + _spotTable.clear(); + _turnTable.clear(); + _viewTable.clear(); + _zoomTable.clear(); + + _navMovie.stopDisplaying(); + _navMovie.releaseMovie(); + _pushIn.deallocateSurface(); + _turnPush.stopDisplaying(); + _turnPush.setInAndOutElements(0, 0); + _turnPush.disposeAllCallBacks(); + + for (HotspotList::iterator it = _neighborhoodHotspots.begin(); it != _neighborhoodHotspots.end(); it++) + _vm->getAllHotspots().remove(*it); + + _neighborhoodHotspots.deleteHotspots(); + _spotSounds.disposeSound(); + _delayTimer.disposeAllCallBacks(); + + if (g_AIArea) { + g_AIArea->saveAIState(); + g_AIArea->removeAllRules(); + } + + if (_currentInteraction) + newInteraction(kNoInteractionID); + + _croppedMovie.releaseMovie(); + + loadLoopSound1(""); + loadLoopSound2(""); + + if (g_energyMonitor) { + g_energyMonitor->stopEnergyDraining(); + g_energyMonitor->saveCurrentEnergyValue(); + } + + delete g_interface; +} + +bool Neighborhood::prepareExtraSync(const ExtraID extraID) { + ExtraTable::Entry extraEntry; + FaderMoveSpec compassMove; + + if (g_compass) { + getExtraEntry(extraID, extraEntry); + getExtraCompassMove(extraEntry, compassMove); + } + + ExtraTable::Entry entry; + getExtraEntry(extraID, entry); + bool result; + + if (entry.movieStart != 0xffffffff) { + _turnPush.hide(); + + // Stop the movie before doing anything else + _navMovie.stop(); + + Common::Rect pushBounds; + _turnPush.getBounds(pushBounds); + _navMovie.moveElementTo(pushBounds.left, pushBounds.top); + + _navMovie.show(); + _navMovie.setFlags(0); + _navMovie.setSegment(entry.movieStart, entry.movieEnd); + _navMovie.setTime(entry.movieStart); + _navMovie.start(); + result = true; + } else { + result = false; + } + + if (result && g_compass) + g_compass->startFader(compassMove); + + return result; +} + +bool Neighborhood::waitMovieFinish(Movie *movie, const InputBits interruptionFilter) { + Input input; + bool result = true; + bool saveAllowed = _vm->swapSaveAllowed(false); + bool openAllowed = _vm->swapLoadAllowed(false); + + while (movie->isRunning()) { + InputDevice.getInput(input, interruptionFilter); + + if (input.anyInput() || _vm->shouldQuit()) { + result = false; + break; + } + + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + movie->stop(); + _vm->swapSaveAllowed(saveAllowed); + _vm->swapLoadAllowed(openAllowed); + + return result; +} + +InputBits Neighborhood::getInputFilter() { + return _interruptionFilter & InputHandler::getInputFilter(); +} + +void Neighborhood::getZoomCompassMove(const ZoomTable::Entry &zoomEntry, FaderMoveSpec &compassMove) { + int32 startAngle = getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection()); + int32 stopAngle = getStaticCompassAngle(zoomEntry.room, zoomEntry.direction); + + if (startAngle > stopAngle) { + if (stopAngle + 180 < startAngle) + stopAngle += 360; + } else { + if (startAngle + 180 < stopAngle) + startAngle += 360; + } + + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), zoomEntry.movieStart, startAngle, zoomEntry.movieEnd, stopAngle); +} + +void Neighborhood::getExtraCompassMove(const ExtraTable::Entry &, FaderMoveSpec &compassMove) { + compassMove.makeOneKnotFaderSpec(g_compass->getFaderValue()); +} + +void Neighborhood::setUpAIRules() { + // Set up default rules here: + // -- Energy warning rules. + + if (g_AIArea) { + g_AIArea->forceAIUnlocked(); + + if (!_vm->isDemo() && (getObjectID() == kPrehistoricID || getObjectID() == kNoradAlphaID || + getObjectID() == kNoradDeltaID || getObjectID() == kMarsID || getObjectID() == kWSCID)) { + + AIEnergyMonitorCondition *condition50 = new AIEnergyMonitorCondition(kWorriedEnergy); + AIPlayMessageAction *message = new AIPlayMessageAction("Images/AI/Globals/XGLOB4A", false); + AIRule *rule50 = new AIRule(condition50, message); + + AIEnergyMonitorCondition *condition25 = new AIEnergyMonitorCondition(kNervousEnergy); + AICompoundAction *compound = new AICompoundAction(); + message = new AIPlayMessageAction("Images/AI/Globals/XGLOB4B", false); + compound->addAction(message); + AIDeactivateRuleAction *deactivate = new AIDeactivateRuleAction(rule50); + compound->addAction(deactivate); + AIRule *rule25 = new AIRule(condition25, compound); + + AIEnergyMonitorCondition *condition5 = new AIEnergyMonitorCondition(kPanicStrickenEnergy); + compound = new AICompoundAction(); + message = new AIPlayMessageAction("Images/AI/Globals/XGLOB4C", false); + compound->addAction(message); + deactivate = new AIDeactivateRuleAction(rule50); + compound->addAction(deactivate); + deactivate = new AIDeactivateRuleAction(rule25); + compound->addAction(deactivate); + AIRule *rule5 = new AIRule(condition5, compound); + + g_AIArea->addAIRule(rule5); + g_AIArea->addAIRule(rule25); + g_AIArea->addAIRule(rule50); + } + } +} + +GameInteraction *Neighborhood::makeInteraction(const InteractionID interactionID) { + if (interactionID == kNoInteractionID) + return 0; + + return new GameInteraction(interactionID, this); +} + +void Neighborhood::newInteraction(const InteractionID interactionID) { + GameInteraction *interaction = makeInteraction(interactionID); + _doneWithInteraction = false; + + if (_currentInteraction) { + _currentInteraction->stopInteraction(); + delete _currentInteraction; + } + + _currentInteraction = interaction; + + if (_currentInteraction) + _currentInteraction->startInteraction(); + + if (g_AIArea) + g_AIArea->checkMiddleArea(); +} + +void Neighborhood::bumpIntoWall() { + _vm->_gfx->shakeTheWorld(15, 30); +} + +void Neighborhood::zoomUpOrBump() { + Hotspot *zoomSpot = 0; + + for (HotspotList::iterator it = _vm->getAllHotspots().begin(); it != _vm->getAllHotspots().end(); it++) { + Hotspot *hotspot = *it; + + if ((hotspot->getHotspotFlags() & (kNeighborhoodSpotFlag | kZoomInSpotFlag)) == (kNeighborhoodSpotFlag | kZoomInSpotFlag)) { + HotspotInfoTable::Entry *entry = findHotspotEntry(hotspot->getObjectID()); + + if (entry && entry->hotspotRoom == GameState.getCurrentRoom() && entry->hotspotDirection == GameState.getCurrentDirection()) { + if (zoomSpot) { + zoomSpot = 0; + break; + } else { + zoomSpot = hotspot; + } + } + } + } + + if (zoomSpot) + zoomTo(zoomSpot); + else + bumpIntoWall(); +} + +void Neighborhood::loadLoopSound1(const Common::String &soundName, uint16 volume, TimeValue fadeOut, TimeValue fadeIn, TimeScale fadeScale) { + FaderMoveSpec faderMove; + + if (!loop1Loaded(soundName)) { + _loop1SoundString = soundName; + + if (_soundLoop1.isSoundLoaded()) { + faderMove.makeTwoKnotFaderSpec(fadeScale, 0, _loop1Fader.getFaderValue(), fadeOut, 0); + _loop1Fader.startFaderSync(faderMove); + } + + if (!_loop1SoundString.empty()) { + _soundLoop1.initFromAIFFFile(_loop1SoundString); + _soundLoop1.loopSound(); + _loop1Fader.setMasterVolume(_vm->getAmbienceLevel()); + _loop1Fader.setFaderValue(0); + faderMove.makeTwoKnotFaderSpec(fadeScale, 0, 0, fadeIn, volume); + _loop1Fader.startFaderSync(faderMove); + } else { + _soundLoop1.disposeSound(); + } + } else if (_loop1Fader.getFaderValue() != volume) { + faderMove.makeTwoKnotFaderSpec(fadeScale, 0, _loop1Fader.getFaderValue(), fadeIn, volume); + _loop1Fader.startFaderSync(faderMove); + } +} + +void Neighborhood::loadLoopSound2(const Common::String &soundName, uint16 volume, TimeValue fadeOut, TimeValue fadeIn, TimeScale fadeScale) { + FaderMoveSpec faderMove; + + if (!loop2Loaded(soundName)) { + _loop2SoundString = soundName; + + if (_soundLoop2.isSoundLoaded()) { + faderMove.makeTwoKnotFaderSpec(fadeScale, 0, _loop2Fader.getFaderValue(), fadeOut, 0); + _loop2Fader.startFaderSync(faderMove); + } + + if (!_loop2SoundString.empty()) { + _soundLoop2.initFromAIFFFile(_loop2SoundString); + _soundLoop2.loopSound(); + _loop2Fader.setMasterVolume(_vm->getAmbienceLevel()); + _loop2Fader.setFaderValue(0); + faderMove.makeTwoKnotFaderSpec(fadeScale, 0, 0, fadeIn, volume); + _loop2Fader.startFaderSync(faderMove); + } else { + _soundLoop2.disposeSound(); + } + } else if (_loop2Fader.getFaderValue() != volume) { + faderMove.makeTwoKnotFaderSpec(fadeScale, 0, _loop2Fader.getFaderValue(), fadeIn, volume); + _loop2Fader.startFaderSync(faderMove); + } +} + +void Neighborhood::takeItemFromRoom(Item *item) { + item->setItemRoom(kNoNeighborhoodID, kNoRoomID, kNoDirection); + // Also set the taken item flag. Do this before updating the view frame. + GameState.setTakenItem(item, true); + updateViewFrame(); +} + +void Neighborhood::dropItemIntoRoom(Item *item, Hotspot *) { + item->setItemRoom(getObjectID(), GameState.getCurrentRoom(), GameState.getCurrentDirection()); + // Also set the taken item flag. Do this before updating the view frame. + GameState.setTakenItem(item, false); + updateViewFrame(); +} + +void Neighborhood::makeContinuePoint() { + _vm->makeContinuePoint(); +} + +void Neighborhood::startLoop1Fader(const FaderMoveSpec &faderMove) { + _loop1Fader.startFader(faderMove); +} + +void Neighborhood::startLoop2Fader(const FaderMoveSpec &faderMove) { + _loop2Fader.startFader(faderMove); +} + +// *** Revised 6/13/96 to use the last frame of the extra sequence. +// Necessary for Cinepak buildup. +void Neighborhood::showExtraView(uint32 extraID) { + ExtraTable::Entry entry; + getExtraEntry(extraID, entry); + + if (entry.movieEnd != 0xffffffff) + showViewFrame(entry.movieEnd - 1); +} + +void Neighborhood::startExtraLongSequence(const uint32 firstExtra, const uint32 lastExtra, NotificationFlags flags, + const InputBits interruptionFilter) { + ExtraTable::Entry firstEntry, lastEntry; + getExtraEntry(firstExtra, firstEntry); + + if (firstEntry.movieStart != 0xffffffff) { + getExtraEntry(lastExtra, lastEntry); + _lastExtra = firstExtra; + _turnPush.hide(); + startMovieSequence(firstEntry.movieStart, lastEntry.movieEnd, flags, kFilterNoInput, interruptionFilter); + } +} + +void Neighborhood::openCroppedMovie(const Common::String &movieName, CoordType left, CoordType top) { + if (_croppedMovie.isMovieValid()) + closeCroppedMovie(); + + _croppedMovie.initFromMovieFile(movieName); + _croppedMovie.moveElementTo(left, top); + _croppedMovie.startDisplaying(); + _croppedMovie.show(); +} + +void Neighborhood::loopCroppedMovie(const Common::String &movieName, CoordType left, CoordType top) { + openCroppedMovie(movieName, left, top); + _croppedMovie.redrawMovieWorld(); + _croppedMovie.setFlags(kLoopTimeBase); + _croppedMovie.start(); +} + +void Neighborhood::closeCroppedMovie() { + _croppedMovie.releaseMovie(); +} + +void Neighborhood::playCroppedMovieOnce(const Common::String &movieName, CoordType left, CoordType top, const InputBits interruptionFilter) { + openCroppedMovie(movieName, left, top); + _croppedMovie.redrawMovieWorld(); + _croppedMovie.start(); + + InputBits oldInterruptionFilter = _interruptionFilter; + if (oldInterruptionFilter != kFilterNoInput) + _interruptionFilter = kFilterNoInput; + + bool saveAllowed = _vm->swapSaveAllowed(false); + bool openAllowed = _vm->swapLoadAllowed(false); + + Input input; + while (_croppedMovie.isRunning() && !_vm->shouldQuit()) { + _vm->processShell(); + InputDevice.getInput(input, interruptionFilter); + if (input.anyInput() || _vm->saveRequested() || _vm->loadRequested() || _vm->shouldQuit()) + break; + _vm->_system->delayMillis(10); + } + + if (oldInterruptionFilter != kFilterNoInput) + _interruptionFilter = oldInterruptionFilter; + + closeCroppedMovie(); + _vm->swapSaveAllowed(saveAllowed); + _vm->swapLoadAllowed(openAllowed); +} + +void Neighborhood::playMovieSegment(Movie *movie, TimeValue startTime, TimeValue stopTime) { + TimeValue oldStart, oldStop; + movie->getSegment(oldStart, oldStop); + + if (stopTime == 0xffffffff) + stopTime = movie->getDuration(); + + movie->setSegment(startTime, stopTime); + movie->setTime(startTime); + movie->start(); + + while (movie->isRunning()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + movie->stop(); + movie->setSegment(oldStart, oldStop); +} + +void Neighborhood::recallToTSASuccess() { + if (GameState.allTimeZonesFinished()) + _vm->jumpToNewEnvironment(kFullTSAID, kTSA37, kNorth); + else + _vm->jumpToNewEnvironment(kTinyTSAID, kTinyTSA37, kNorth); +} + +void Neighborhood::recallToTSAFailure() { + _vm->jumpToNewEnvironment(kTinyTSAID, kTinyTSA37, kNorth); +} + +void Neighborhood::handleInput(const Input &input, const Hotspot *cursorSpot) { + if (_vm->getGameMode() == kModeNavigation) { + if (input.upButtonAnyDown()) + upButton(input); + else if (input.downButtonAnyDown()) + downButton(input); + else if (input.leftButtonAnyDown()) + leftButton(input); + else if (input.rightButtonAnyDown()) + rightButton(input); + } + + InputHandler::handleInput(input, cursorSpot); +} + +void Neighborhood::setHotspotFlags(const HotSpotID id, const HotSpotFlags flags) { + Hotspot *hotspot = _vm->getAllHotspots().findHotspotByID(id); + hotspot->setMaskedHotspotFlags(flags, flags); +} + +void Neighborhood::setIsItemTaken(const ItemID id) { + GameState.setTakenItemID(id, _vm->playerHasItemID(id)); +} + +void Neighborhood::upButton(const Input &) { + moveForward(); +} + +void Neighborhood::leftButton(const Input &) { + turnLeft(); +} + +void Neighborhood::rightButton(const Input &) { + turnRight(); +} + +void Neighborhood::downButton(const Input &) { + if (_inputHandler->wantsCursor()) { + _vm->getAllHotspots().deactivateAllHotspots(); + _inputHandler->activateHotspots(); + + for (HotspotList::iterator it = _vm->getAllHotspots().begin(); it != _vm->getAllHotspots().end(); it++) { + Hotspot *hotspot = *it; + + if (hotspot->isSpotActive() && (hotspot->getHotspotFlags() & (kNeighborhoodSpotFlag | kZoomOutSpotFlag)) == (kNeighborhoodSpotFlag | kZoomOutSpotFlag)) { + HotspotInfoTable::Entry *entry = findHotspotEntry(hotspot->getObjectID()); + + if (entry && entry->hotspotRoom == GameState.getCurrentRoom() && entry->hotspotDirection == GameState.getCurrentDirection()) { + Input scratch; + _inputHandler->clickInHotspot(scratch, hotspot); + return; + } + } + } + } +} + +void Neighborhood::initOnePicture(Picture *picture, const Common::String &pictureName, DisplayOrder order, CoordType left, CoordType top, bool show) { + picture->initFromPICTFile(pictureName); + picture->setDisplayOrder(order); + picture->moveElementTo(left, top); + picture->startDisplaying(); + if (show) + picture->show(); +} + +void Neighborhood::initOneMovie(Movie *movie, const Common::String &movieName, DisplayOrder order, CoordType left, CoordType top, bool show) { + movie->initFromMovieFile(movieName); + movie->setDisplayOrder(order); + movie->moveElementTo(left, top); + movie->startDisplaying(); + + if (show) + movie->show(); + + movie->redrawMovieWorld(); +} + +void Neighborhood::reinstateMonocleInterface() { + _vm->_gfx->disableErase(); + + _vm->createInterface(); + + if (g_AIArea) + setNextHandler(g_AIArea); + + init(); + + moveNavTo(kNavAreaLeft, kNavAreaTop); + + if (g_interface) + g_interface->setDate(getDateResID()); + + if (g_AIArea) + g_AIArea->restoreAIState(); +} + +void Neighborhood::useIdleTime() { + if (_doneWithInteraction) { + newInteraction(kNoInteractionID); + loadAmbientLoops(); + } +} + +void Neighborhood::timerFunction() { + timerExpired(getTimerEvent()); +} + +void Neighborhood::scheduleEvent(const TimeValue time, const TimeScale scale, const uint32 eventType) { + _eventTimer.stopFuse(); + _eventTimer.primeFuse(time, scale); + _timerEvent = eventType; + _eventTimer.setFunctor(new Common::Functor0Mem<void, Neighborhood>(this, &Neighborhood::timerFunction)); + _eventTimer.lightFuse(); +} + +void Neighborhood::cancelEvent() { + _eventTimer.stopFuse(); +} + +void Neighborhood::pauseTimer() { + _eventTimer.pauseFuse(); +} + +void Neighborhood::resumeTimer() { + // NOTE: Yes, this function calls pauseFuse! + // Looks like an original game bug, will need + // to investigate how this affects gameplay. + _eventTimer.pauseFuse(); +} + +bool Neighborhood::timerPaused() { + return _eventTimer.isFusePaused(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/neighborhood.h b/engines/pegasus/neighborhood/neighborhood.h new file mode 100644 index 0000000000..3c1c5eac92 --- /dev/null +++ b/engines/pegasus/neighborhood/neighborhood.h @@ -0,0 +1,408 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_H +#define PEGASUS_NEIGHBORHOOD_H + +#include "common/queue.h" +#include "common/str.h" + +#include "pegasus/fader.h" +#include "pegasus/hotspot.h" +#include "pegasus/input.h" +#include "pegasus/movie.h" +#include "pegasus/notification.h" +#include "pegasus/sound.h" +#include "pegasus/timers.h" +#include "pegasus/transition.h" +#include "pegasus/util.h" +#include "pegasus/neighborhood/door.h" +#include "pegasus/neighborhood/exit.h" +#include "pegasus/neighborhood/extra.h" +#include "pegasus/neighborhood/hotspotinfo.h" +#include "pegasus/neighborhood/spot.h" +#include "pegasus/neighborhood/turn.h" +#include "pegasus/neighborhood/view.h" +#include "pegasus/neighborhood/zoom.h" + +namespace Pegasus { + +class PegasusEngine; + +// Pegasus Prime neighborhood id's +static const NeighborhoodID kCaldoriaID = 0; +static const NeighborhoodID kFullTSAID = 1; +static const NeighborhoodID kFinalTSAID = 2; +static const NeighborhoodID kTinyTSAID = 3; +static const NeighborhoodID kPrehistoricID = 4; +static const NeighborhoodID kMarsID = 5; +static const NeighborhoodID kWSCID = 6; +static const NeighborhoodID kNoradAlphaID = 7; +static const NeighborhoodID kNoradDeltaID = 8; +// The sub chase is not really a neighborhood, but we define a constant that is used +// to allow an easy transition out of Norad Alpha. +static const NeighborhoodID kNoradSubChaseID = 1000; + +static const TimeScale kDefaultLoopFadeScale = kThirtyTicksPerSecond; +static const TimeValue kDefaultLoopFadeOut = kHalfSecondPerThirtyTicks; +static const TimeValue kDefaultLoopFadeIn = kHalfSecondPerThirtyTicks; + +enum QueueRequestType { + kNavExtraRequest, + kSpotSoundRequest, + kDelayRequest +}; + +// For delay requests, start is interpreted as the total delay and stop is interpreted +// as the scale the delay is in. +// For extra requests, start and stop are not used. +struct QueueRequest { + QueueRequestType requestType; + ExtraID extra; + TimeValue start, stop; + InputBits interruptionFilter; + bool playing; + NotificationFlags flags; + Notification *notification; +}; + +bool operator==(const QueueRequest &arg1, const QueueRequest &arg2); +bool operator!=(const QueueRequest &arg1, const QueueRequest &arg2); + +class GameInteraction; +class Item; +class Neighborhood; + +class StriderCallBack : public TimeBaseCallBack { +public: + StriderCallBack(Neighborhood *); + virtual ~StriderCallBack() {} + +protected: + virtual void callBack(); + + Neighborhood *_neighborhood; +}; + +typedef Common::Queue<QueueRequest> NeighborhoodActionQueue; + +class Neighborhood : public IDObject, public NotificationReceiver, public InputHandler, public Idler { +friend class StriderCallBack; + +public: + Neighborhood(InputHandler *nextHandler, PegasusEngine *vm, const Common::String &resName, NeighborhoodID id); + virtual ~Neighborhood(); + + virtual void init(); + virtual void start(); + virtual void moveNavTo(const CoordType, const CoordType); + virtual void checkContinuePoint(const RoomID, const DirectionConstant) = 0; + void makeContinuePoint(); + + virtual void activateHotspots(); + virtual void clickInHotspot(const Input &, const Hotspot *); + + virtual CanMoveForwardReason canMoveForward(ExitTable::Entry &entry); + virtual CanTurnReason canTurn(TurnDirection turn, DirectionConstant &nextDir); + virtual CanOpenDoorReason canOpenDoor(DoorTable::Entry &entry); + + virtual void cantMoveThatWay(CanMoveForwardReason); + virtual void cantTurnThatWay(CanTurnReason) {} + virtual void cantOpenDoor(CanOpenDoorReason); + virtual void arriveAt(const RoomID room, const DirectionConstant direction); + virtual void turnTo(const DirectionConstant); + virtual void spotCompleted(); + virtual void doorOpened(); + virtual void closeDoorOffScreen(const RoomID, const DirectionConstant) {} + + virtual void moveForward(); + virtual void turn(const TurnDirection); + virtual void turnLeft(); + virtual void turnRight(); + virtual void turnUp(); + virtual void turnDown(); + virtual void openDoor(); + virtual void zoomTo(const Hotspot *); + + virtual void updateViewFrame(); + + void requestExtraSequence(const ExtraID, const NotificationFlags, const InputBits interruptionFilter); + void requestSpotSound(const TimeValue, const TimeValue, const InputBits interruptionFilter, const NotificationFlags); + void playSpotSoundSync(const TimeValue in, const TimeValue out); + void requestDelay(const TimeValue, const TimeScale, const InputBits interruptionFilter, const NotificationFlags); + + Notification *getNeighborhoodNotification() { return &_neighborhoodNotification; } + + virtual void getExtraEntry(const uint32 id, ExtraTable::Entry &extraEntry); + virtual void startSpotLoop(TimeValue, TimeValue, NotificationFlags = 0); + virtual bool actionQueueEmpty() { return _actionQueue.empty(); } + virtual void showViewFrame(TimeValue); + virtual void findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &spotEntry); + virtual void startExtraSequence(const ExtraID, const NotificationFlags, const InputBits interruptionFilter); + bool startExtraSequenceSync(const ExtraID, const InputBits); + virtual void loopExtraSequence(const uint32, NotificationFlags = 0); + int32 getLastExtra() const { return _lastExtra; } + virtual void scheduleNavCallBack(NotificationFlags); + + Movie *getNavMovie() { return &_navMovie; } + bool navMoviePlaying(); + + void setCurrentAlternate(const AlternateID alt) { _currentAlternate = alt; } + AlternateID getCurrentAlternate() const { return _currentAlternate; } + + void setCurrentActivation(const HotSpotActivationID a) { _currentActivation = a; } + HotSpotActivationID getCurrentActivation() { return _currentActivation; } + + virtual void playDeathExtra(ExtraID, DeathReason); + virtual void die(const DeathReason); + + virtual void setSoundFXLevel(const uint16); + virtual void setAmbienceLevel(const uint16); + + void forceStridingStop(const RoomID, const DirectionConstant, const AlternateID); + void restoreStriding(const RoomID, const DirectionConstant, const AlternateID); + + HotspotInfoTable::Entry *findHotspotEntry(const HotSpotID); + + Push *getTurnPush() { return &_turnPush; } + Picture *getTurnPushPicture() { return &_pushIn; } + + void hideNav(); + void showNav(); + + virtual void loadAmbientLoops() {} + + virtual void flushGameState() {} + + virtual Common::String getBriefingMovie(); + virtual Common::String getEnvScanMovie(); + virtual uint getNumHints(); + virtual Common::String getHintMovie(uint); + virtual bool canSolve(); + virtual void prepareForAIHint(const Common::String &) {} + virtual void cleanUpAfterAIHint(const Common::String &) {} + virtual void doSolve(); + + virtual bool okayToJump(); + + virtual AirQuality getAirQuality(const RoomID); + virtual void checkAirMask() {} + virtual void checkFlashlight() {} + virtual void shieldOn() {} + virtual void shieldOff() {} + + virtual void loadLoopSound1(const Common::String &, const uint16 volume = 0x100, + const TimeValue fadeOut = kDefaultLoopFadeOut, const TimeValue fadeIn = kDefaultLoopFadeIn, + const TimeScale fadeScale = kDefaultLoopFadeScale); + virtual void loadLoopSound2(const Common::String &, const uint16 volume = 0x100, + const TimeValue fadeOut = kDefaultLoopFadeOut, const TimeValue fadeIn = kDefaultLoopFadeIn, + const TimeScale fadeScale = kDefaultLoopFadeScale); + bool loop1Loaded(const Common::String &soundName) { return _loop1SoundString == soundName; } + bool loop2Loaded(const Common::String &soundName) { return _loop2SoundString == soundName; } + void startLoop1Fader(const FaderMoveSpec &); + void startLoop2Fader(const FaderMoveSpec &); + + virtual void takeItemFromRoom(Item *); + virtual void dropItemIntoRoom(Item *, Hotspot *); + virtual Hotspot *getItemScreenSpot(Item *, DisplayElement *) { return 0; } + + virtual GameInteraction *makeInteraction(const InteractionID); + virtual void requestDeleteCurrentInteraction() { _doneWithInteraction = true; } + + virtual uint16 getDateResID() const = 0; + + virtual void showExtraView(uint32); + virtual void startExtraLongSequence(const uint32, const uint32, NotificationFlags, const InputBits interruptionFilter); + + void openCroppedMovie(const Common::String &, CoordType, CoordType); + void loopCroppedMovie(const Common::String &, CoordType, CoordType); + void closeCroppedMovie(); + void playCroppedMovieOnce(const Common::String &, CoordType, CoordType, const InputBits interruptionFilter = kFilterNoInput); + + void playMovieSegment(Movie *, TimeValue = 0, TimeValue = 0xffffffff); + + virtual void recallToTSASuccess(); + virtual void recallToTSAFailure(); + + virtual void pickedUpItem(Item *) {} + + virtual void handleInput(const Input &, const Hotspot *); +protected: + PegasusEngine *_vm; + Common::String _resName; + + virtual Common::String getSoundSpotsName() = 0; + virtual Common::String getNavMovieName() = 0; + + // Notification function. + virtual void receiveNotification(Notification *, const NotificationFlags); + + // Map info functions. + virtual void getExitEntry(const RoomID room, const DirectionConstant direction, ExitTable::Entry &entry); + virtual TimeValue getViewTime(const RoomID room, const DirectionConstant direction); + virtual void getDoorEntry(const RoomID room, const DirectionConstant direction, DoorTable::Entry &doorEntry); + virtual DirectionConstant getTurnEntry(const RoomID room, const DirectionConstant direction, const TurnDirection turn); + virtual void getZoomEntry(const HotSpotID id, ZoomTable::Entry &zoomEntry); + virtual void getHotspotEntry(const HotSpotID id, HotspotInfoTable::Entry &hotspotEntry); + + // Nav movie sequences. + virtual void startExitMovie(const ExitTable::Entry &); + virtual void keepStriding(ExitTable::Entry &); + virtual void stopStriding(); + virtual void checkStriding(); + virtual bool stillMoveForward(); + virtual void scheduleStridingCallBack(const TimeValue, NotificationFlags flags); + virtual void startZoomMovie(const ZoomTable::Entry &); + virtual void startDoorOpenMovie(const TimeValue, const TimeValue); + virtual void startTurnPush(const TurnDirection, const TimeValue, const DirectionConstant); + virtual void playExtraMovie(const ExtraTable::Entry &, const NotificationFlags, const InputBits interruptionFilter); + + virtual void activateCurrentView(const RoomID, const DirectionConstant, SpotFlags); + + virtual void activateOneHotspot(HotspotInfoTable::Entry &, Hotspot *); + + virtual void startSpotOnceOnly(TimeValue, TimeValue); + + virtual void startMovieSequence(const TimeValue, const TimeValue, NotificationFlags, + bool loopSequence, const InputBits interruptionFilter, const TimeValue strideStop = 0xffffffff); + + virtual void createNeighborhoodSpots(); + + void resetLastExtra() { _lastExtra = -1; } + + virtual void throwAwayInterface(); + + // Action queue stuff + void popActionQueue(); + void serviceActionQueue(); + void requestAction(const QueueRequestType, const ExtraID, const TimeValue, const TimeValue, const InputBits, const NotificationFlags); + + virtual bool prepareExtraSync(const ExtraID); + virtual bool waitMovieFinish(Movie *, const InputBits); + + virtual InputBits getInputFilter(); + + // Misc. + virtual int16 getStaticCompassAngle(const RoomID, const DirectionConstant dir); + virtual void getExitCompassMove(const ExitTable::Entry &, FaderMoveSpec &); + virtual void getZoomCompassMove(const ZoomTable::Entry &, FaderMoveSpec&); + virtual void getExtraCompassMove(const ExtraTable::Entry &, FaderMoveSpec&); + + virtual void setUpAIRules(); + virtual void setHotspotFlags(const HotSpotID, const HotSpotFlags); + virtual void setIsItemTaken(const ItemID); + + virtual void upButton(const Input &); + virtual void leftButton(const Input &); + virtual void rightButton(const Input &); + virtual void downButton(const Input &); + + void initOnePicture(Picture *, const Common::String &, DisplayOrder, CoordType, CoordType, bool); + void initOneMovie(Movie *, const Common::String &, DisplayOrder, CoordType, CoordType, bool); + + void reinstateMonocleInterface(); + + virtual void newInteraction(const InteractionID); + virtual void useIdleTime(); + virtual void bumpIntoWall(); + virtual void zoomUpOrBump(); + + void scheduleEvent(const TimeValue, const TimeScale, const uint32); + void cancelEvent(); + virtual void timerExpired(const uint32) {} + bool isEventTimerRunning() { return _eventTimer.isFuseLit(); } + uint32 getTimerEvent() { return _timerEvent; } + void timerFunction(); + + void pauseTimer(); + void resumeTimer(); + bool timerPaused(); + + // Navigation Data + DoorTable _doorTable; + ExitTable _exitTable; + ExtraTable _extraTable; + HotspotInfoTable _hotspotInfoTable; + SpotTable _spotTable; + TurnTable _turnTable; + ViewTable _viewTable; + ZoomTable _zoomTable; + AlternateID _currentAlternate; + HotSpotActivationID _currentActivation; + + int32 _lastExtra; + DeathReason _extraDeathReason; + + // Graphics + Movie _navMovie; + Picture _pushIn; + Push _turnPush; + + // Callbacks + Notification _neighborhoodNotification; + NotificationCallBack _navMovieCallBack; + StriderCallBack _stridingCallBack; + NotificationCallBack _turnPushCallBack; + NotificationCallBack _spotSoundCallBack; + NotificationCallBack _delayCallBack; + + // Hotspots + HotspotList _neighborhoodHotspots; + + // Sounds + SoundTimeBase _spotSounds; + + // Action queue + NeighborhoodActionQueue _actionQueue; + TimeBase _delayTimer; + + // Interruptibility... + InputBits _interruptionFilter; + + // Nav hiding (for info support...) + bool _isRunning; + + GameInteraction *_currentInteraction; + bool _doneWithInteraction; + Movie _croppedMovie; + + Sound _soundLoop1; + Common::String _loop1SoundString; + SoundFader _loop1Fader; + + Sound _soundLoop2; + Common::String _loop2SoundString; + SoundFader _loop2Fader; + + // The event timer... + FuseFunction _eventTimer; + uint32 _timerEvent; +}; + +extern Neighborhood *g_neighborhood; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/norad/alpha/ecrmonitor.cpp b/engines/pegasus/neighborhood/norad/alpha/ecrmonitor.cpp new file mode 100644 index 0000000000..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 diff --git a/engines/pegasus/neighborhood/prehistoric/prehistoric.cpp b/engines/pegasus/neighborhood/prehistoric/prehistoric.cpp new file mode 100644 index 0000000000..814d7717de --- /dev/null +++ b/engines/pegasus/neighborhood/prehistoric/prehistoric.cpp @@ -0,0 +1,689 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/compass.h" +#include "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_action.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/ai/ai_condition.h" +#include "pegasus/ai/ai_rule.h" +#include "pegasus/neighborhood/prehistoric/prehistoric.h" + +namespace Pegasus { + +static const int16 s_prehistoricCompass[kPrehistoric25 + 1][4] = { + { 0, 170, 90, 270 }, // kPrehistoric01 + { 0, 180, 90, 270 }, // kPrehistoric02 + { 10, 180, 90, 270 }, // kPrehistoric03 + { 10, 190, 90, 270 }, // kPrehistoric04 + { 10, 195, 90, 270 }, // kPrehistoric05 + { 20, 210, 90, 270 }, // kPrehistoric06 + { 20, 200, 130, 276 }, // kPrehistoric07 + { 20, 176, 110, 260 }, // kPrehistoric08 + { 20, 200, 100, 270 }, // kPrehistoric09 + { 14, 186, 100, 280 }, // kPrehistoric10 + { 26, 206, 116, 296 }, // kPrehistoric11 + { 60, 226, 140, 320 }, // kPrehistoric12 + { 0, 180, 80, 270 }, // kPrehistoric13 + { 14, 200, 106, 286 }, // kPrehistoric14 + { -10, 174, 80, 260 }, // kPrehistoric15 + { 54, 236, 140, 210 }, // kPrehistoric16 + { -24, 160, 70, 250 }, // kPrehistoric17 + { 26, 206, 140, 296 }, // kPrehistoric18 + { -16, 160, 70, 250 }, // kPrehistoric19 + { -16, 160, 70, 250 }, // kPrehistoric20 + { -10, 160, 90, 250 }, // kPrehistoric21 + { -20, 160, 70, 244 }, // kPrehistoric22 + { -20, 160, 70, 244 }, // kPrehistoric22North + { 60, 234, 150, 330 }, // kPrehistoric23 + { 50, 230, 140, 320 }, // kPrehistoric24 + { 60, 240, 140, 330 } // kPrehistoric25 +}; + +static const TimeValue kPrehistoricFlashlightClickIn = 0; +static const TimeValue kPrehistoricFlashlightClickOut = 138; + +static const TimeValue kPrehistoricBumpIntoWallIn = 138; +static const TimeValue kPrehistoricBumpIntoWallOut = 291; + +static const TimeValue kBridgeRetractIn = 291; +static const TimeValue kBridgeRetractOut = 1499; + +static const TimeValue kPrehistoricWarningTimeLimit = kTenMinutes; + +Prehistoric::Prehistoric(InputHandler *nextHandler, PegasusEngine *owner) : Neighborhood(nextHandler, owner, "Prehistoric", kPrehistoricID) { + setIsItemTaken(kHistoricalLog); +} + +uint16 Prehistoric::getDateResID() const { + return kDatePrehistoricID; +} + +void Prehistoric::init() { + Neighborhood::init(); + + // Forces a stop so the flashlight can turn off... + forceStridingStop(kPrehistoric12, kSouth, kNoAlternateID); +} + +void Prehistoric::start() { + if (g_energyMonitor) { + g_energyMonitor->stopEnergyDraining(); + g_energyMonitor->restoreLastEnergyValue(); + _vm->resetEnergyDeathReason(); + g_energyMonitor->startEnergyDraining(); + } + + Neighborhood::start(); +} + +class FinishPrehistoricAction : public AIPlayMessageAction { +public: + FinishPrehistoricAction() : AIPlayMessageAction("Images/AI/Prehistoric/XP25W", false) {} + ~FinishPrehistoricAction() {} + + void performAIAction(AIRule *); + +}; + +void FinishPrehistoricAction::performAIAction(AIRule *rule) { + AIPlayMessageAction::performAIAction(rule); + ((PegasusEngine *)g_engine)->die(kPlayerWonGame); +} + +void Prehistoric::setUpAIRules() { + Neighborhood::setUpAIRules(); + + if (g_AIArea) { + if (_vm->isDemo()) { + FinishPrehistoricAction *doneAction = new FinishPrehistoricAction(); + AIHasItemCondition *hasLogCondition = new AIHasItemCondition(kHistoricalLog); + AIRule *rule = new AIRule(hasLogCondition, doneAction); + g_AIArea->addAIRule(rule); + } else { + AIPlayMessageAction *messageAction = new AIPlayMessageAction("Images/AI/Prehistoric/XP1NB", false); + AILocationCondition *locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kPrehistoric16, kNorth)); + AIRule *rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Prehistoric/XP2SB", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kPrehistoric01, kSouth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Prehistoric/XP2SB", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kPrehistoric08, kEast)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Prehistoric/XP2SB", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kPrehistoric25, kWest)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Prehistoric/XP16NB", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kPrehistoric23, kNorth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Prehistoric/XP18NB", false); + AITimerCondition *timerCondition = new AITimerCondition(kPrehistoricWarningTimeLimit, 1, true); + rule = new AIRule(timerCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Prehistoric/XP25W", false); + AIHasItemCondition *hasLogCondition = new AIHasItemCondition(kHistoricalLog); + rule = new AIRule(hasLogCondition, messageAction); + g_AIArea->addAIRule(rule); + } + } +} + +TimeValue Prehistoric::getViewTime(const RoomID room, const DirectionConstant direction) { + ExtraTable::Entry extra; + uint32 extraID = 0xffffffff; + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kPrehistoric02, kSouth): + if (!GameState.getPrehistoricSeenTimeStream()) { + getExtraEntry(kPreArrivalFromTSA, extra); + return extra.movieStart; + } + break; + case MakeRoomView(kPrehistoric25, kEast): + if (_privateFlags.getFlag(kPrehistoricPrivateVaultOpenFlag)) { + if (_vm->itemInLocation(kHistoricalLog, kPrehistoricID, kPrehistoric25, kEast)) + extraID = kPre25EastViewWithLog; + else + extraID = kPre25EastViewNoLog; + } + break; + } + + if (extraID == 0xffffffff) + return Neighborhood::getViewTime(room, direction); + + getExtraEntry(extraID, extra); + return extra.movieEnd - 1; +} + + +void Prehistoric::findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &entry) { + Neighborhood::findSpotEntry(room, direction, flags, entry); + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kPrehistoric01, kSouth): + case MakeRoomView(kPrehistoric25, kSouth): + entry.clear(); + break; + case MakeRoomView(kPrehistoric01, kEast): + if (GameState.getPrehistoricSeenFlyer1()) + entry.clear(); + else + GameState.setPrehistoricSeenFlyer1(true); + break; + case MakeRoomView(kPrehistoric08, kEast): + if (GameState.getPrehistoricSeenFlyer2()) + entry.clear(); + else + GameState.setPrehistoricSeenFlyer2(true); + break; + } +} + +int16 Prehistoric::getStaticCompassAngle(const RoomID room, const DirectionConstant dir) { + if (room == kPrehistoricDeath) + return g_compass->getFaderValue(); + + return s_prehistoricCompass[room][dir]; +} + +void Prehistoric::getExitCompassMove(const ExitTable::Entry &exitEntry, FaderMoveSpec &compassMove) { + uint32 angle; + Neighborhood::getExitCompassMove(exitEntry, compassMove); + + switch (MakeRoomView(exitEntry.room, exitEntry.direction)) { + case MakeRoomView(kPrehistoric01, kNorth): + compassMove.insertFaderKnot(exitEntry.movieStart + (exitEntry.movieEnd - exitEntry.movieStart) / 2, -10); + break; + case MakeRoomView(kPrehistoric06, kEast): + compassMove.insertFaderKnot(exitEntry.movieStart + (exitEntry.movieEnd - exitEntry.movieStart) / 4, 95); + compassMove.insertFaderKnot(exitEntry.movieStart + (exitEntry.movieEnd - exitEntry.movieStart) / 4 * 1, 100); + break; + case MakeRoomView(kPrehistoric18, kEast): + if (getCurrentAlternate() == kAltPrehistoricBridgeSet) { + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 11, 145); + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 26, 145); + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 39, 148); + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 114, 140); + } else { + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 10, 140); + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 16, 145); + compassMove.insertFaderKnot(exitEntry.movieEnd, 145); + } + break; + case MakeRoomView(kPrehistoric23, kWest): + angle = compassMove.getNthKnotValue(0); + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 17, angle); + compassMove.insertFaderKnot(exitEntry.movieStart + kPrehistoricFrameDuration * 32, angle - 90); + compassMove.insertFaderKnot(exitEntry.movieEnd, angle - 90); + break; + } +} + +void Prehistoric::turnTo(const DirectionConstant newDirection) { + setCurrentAlternate(kAltPrehistoricNormal); + _privateFlags.setFlag(kPrehistoricPrivateVaultOpenFlag, false); + Neighborhood::turnTo(newDirection); + + Item *keyCard; + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kPrehistoric18, kEast): + zoomToVault(); + break; + case MakeRoomView(kPrehistoric18, kNorth): + case MakeRoomView(kPrehistoric18, kSouth): + if (_privateFlags.getFlag(kPrehistoricPrivateExtendedBridgeFlag)) { + playSpotSoundSync(kBridgeRetractIn, kBridgeRetractOut); + _privateFlags.setFlag(kPrehistoricPrivateExtendedBridgeFlag, false); + loadAmbientLoops(); + } + // fall through + case MakeRoomView(kPrehistoric25, kEast): + setCurrentActivation(kActivationVaultClosed); + break; + case MakeRoomView(kPrehistoric16, kNorth): + case MakeRoomView(kPrehistoric21, kWest): + keyCard = _vm->getAllItems().findItemByID(kKeyCard); + if (keyCard->getItemState() == kFlashlightOff) { + keyCard->setItemState(kFlashlightOn); + playSpotSoundSync(kPrehistoricFlashlightClickIn, kPrehistoricFlashlightClickOut); + } + break; + case MakeRoomView(kPrehistoric16, kEast): + case MakeRoomView(kPrehistoric16, kWest): + case MakeRoomView(kPrehistoric21, kNorth): + case MakeRoomView(kPrehistoric21, kSouth): + keyCard = _vm->getAllItems().findItemByID(kKeyCard); + if (keyCard->getItemState() == kFlashlightOn) { + keyCard->setItemState(kFlashlightOff); + playSpotSoundSync(kPrehistoricFlashlightClickIn, kPrehistoricFlashlightClickOut); + } + break; + } +} + +void Prehistoric::zoomToVault() { + if (!GameState.getPrehistoricSeenBridgeZoom()) + startExtraSequence(kPre18EastZoom, kExtraCompletedFlag, kFilterNoInput); +} + +void Prehistoric::checkContinuePoint(const RoomID room, const DirectionConstant direction) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kPrehistoric08, kEast): + case MakeRoomView(kPrehistoric18, kSouth): + case MakeRoomView(kPrehistoric16, kNorth): + case MakeRoomView(kPrehistoric21, kNorth): + case MakeRoomView(kPrehistoric25, kNorth): + makeContinuePoint(); + break; + } +} + +void Prehistoric::arriveAt(const RoomID room, const DirectionConstant direction) { + Item *keyCard; + + if (MakeRoomView(room, direction) == MakeRoomView(kPrehistoric25, kEast) && + _privateFlags.getFlag(kPrehistoricPrivateExtendedBridgeFlag)) { + _navMovie.stop(); + playSpotSoundSync(kBridgeRetractIn, kBridgeRetractOut); + _privateFlags.setFlag(kPrehistoricPrivateExtendedBridgeFlag, false); + } + + Neighborhood::arriveAt(room, direction); + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kPrehistoricDeath, kNorth): + case MakeRoomView(kPrehistoricDeath, kSouth): + case MakeRoomView(kPrehistoricDeath, kEast): + case MakeRoomView(kPrehistoricDeath, kWest): + if (GameState.getLastRoom() == kPrehistoric23) + die(kDeathEatenByDinosaur); + else + die(kDeathFallOffCliff); + break; + case MakeRoomView(kPrehistoric02, kSouth): + if (!GameState.getPrehistoricSeenTimeStream()) { + GameState.setPrehistoricTriedToExtendBridge(false); + GameState.setPrehistoricSeenFlyer1(false); + GameState.setPrehistoricSeenFlyer2(false); + GameState.setPrehistoricSeenBridgeZoom(false); + GameState.setPrehistoricBreakerThrown(false); + startExtraSequence(kPreArrivalFromTSA, kExtraCompletedFlag, kFilterNoInput); + } + break; + case MakeRoomView(kPrehistoric18, kEast): + zoomToVault(); + break; + case MakeRoomView(kPrehistoric16, kNorth): + keyCard = _vm->getAllItems().findItemByID(kKeyCard); + + if (keyCard->getItemState() == kFlashlightOff) { + keyCard->setItemState(kFlashlightOn); + playSpotSoundSync(kPrehistoricFlashlightClickIn, kPrehistoricFlashlightClickOut); + } + + if (g_AIArea) + g_AIArea->checkRules(); + break; + case MakeRoomView(kPrehistoric01, kSouth): + case MakeRoomView(kPrehistoric23, kNorth): + if (g_AIArea) + g_AIArea->checkRules(); + break; + case MakeRoomView(kPrehistoric08, kSouth): + case MakeRoomView(kPrehistoric10, kSouth): + case MakeRoomView(kPrehistoric12, kSouth): + case MakeRoomView(kPrehistoric13, kNorth): + case MakeRoomView(kPrehistoric14, kSouth): + case MakeRoomView(kPrehistoric15, kNorth): + case MakeRoomView(kPrehistoric16, kSouth): + case MakeRoomView(kPrehistoric17, kNorth): + case MakeRoomView(kPrehistoric18, kSouth): + case MakeRoomView(kPrehistoric19, kNorth): + case MakeRoomView(kPrehistoric20, kNorth): + case MakeRoomView(kPrehistoric21, kEast): + keyCard = _vm->getAllItems().findItemByID(kKeyCard); + + if (keyCard->getItemState() == kFlashlightOn) { + keyCard->setItemState(kFlashlightOff); + playSpotSoundSync(kPrehistoricFlashlightClickIn, kPrehistoricFlashlightClickOut); + } + break; + case MakeRoomView(kPrehistoric25, kEast): + setCurrentActivation(kActivationVaultClosed); + break; + } +} + +void Prehistoric::loadAmbientLoops() { + RoomID room = GameState.getCurrentRoom(); + + switch (room) { + case kPrehistoric02: + // 1/4 volume. + if (GameState.getPrehistoricSeenTimeStream()) + loadLoopSound1("Sounds/Prehistoric/P02SAL00.22k.AIFF", 64); + break; + case kPrehistoric01: + case kPrehistoric03: + case kPrehistoric04: + case kPrehistoric05: + case kPrehistoric06: + case kPrehistoric07: + case kPrehistoric09: + case kPrehistoric11: + case kPrehistoric13: + case kPrehistoric15: + case kPrehistoric17: + case kPrehistoric19: + case kPrehistoric20: + // 1/4 volume. + loadLoopSound1("Sounds/Prehistoric/P02SAL00.22k.AIFF", 64); + break; + case kPrehistoric08: + case kPrehistoric10: + case kPrehistoric12: + case kPrehistoric14: + case kPrehistoric16: + case kPrehistoric18: + case kPrehistoric21: + // 3/16 volume. + loadLoopSound1("Sounds/Prehistoric/P02SAL00.22k.AIFF", 48); + break; + case kPrehistoric25: + // 1/8 volume. + loadLoopSound1("Sounds/Prehistoric/P02SAL00.22k.AIFF", 32); + break; + case kPrehistoric22: + case kPrehistoric22North: + case kPrehistoric23: + case kPrehistoric24: + case kPrehistoricDeath: + // 0 volume. + loadLoopSound1(""); + break; + } + + switch (room) { + case kPrehistoric02: + case kPrehistoric03: + case kPrehistoric04: + case kPrehistoric05: + case kPrehistoric06: + case kPrehistoric07: + case kPrehistoric08: + case kPrehistoric09: + case kPrehistoric10: + case kPrehistoric11: + case kPrehistoric12: + case kPrehistoric13: + case kPrehistoric14: + case kPrehistoric15: + case kPrehistoric16: + case kPrehistoric17: + case kPrehistoric19: + case kPrehistoric20: + case kPrehistoric21: + case kPrehistoricDeath: + loadLoopSound2(""); + break; + case kPrehistoric01: + case kPrehistoric25: + loadLoopSound2("Sounds/Prehistoric/VolcLoop.22K.AIFF", 64); + break; + case kPrehistoric18: + if (_privateFlags.getFlag(kPrehistoricPrivateExtendedBridgeFlag)) + loadLoopSound2("Sounds/Prehistoric/P18EAL00.22k.AIFF", 0x100, 0, 0); + else + loadLoopSound2(""); + break; + case kPrehistoric23: + case kPrehistoric24: + case kPrehistoric22: + case kPrehistoric22North: + loadLoopSound2("Sounds/Prehistoric/P24NAL00.22k.AIFF", 64); + break; + } +} + +void Prehistoric::activateHotspots() { + Neighborhood::activateHotspots(); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kPrehistoric18, kEast): + if (!_privateFlags.getFlag(kPrehistoricPrivateExtendedBridgeFlag)) + _vm->getAllHotspots().activateOneHotspot(kPre18EastSpotID); + break; + case MakeRoomView(kPrehistoric22North, kNorth): + _vm->getAllHotspots().activateOneHotspot(kPre22NorthBreakerSpotID); + break; + } +} + +void Prehistoric::clickInHotspot(const Input &input, const Hotspot *spot) { + switch (spot->getObjectID()) { + case kPre18EastSpotID: + if (GameState.getPrehistoricBreakerThrown()) + startExtraSequence(kPre18EastBridgeOn, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kPre18EastBridgeOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kPre22NorthBreakerSpotID: + startExtraSequence(kPre22ThrowBreaker, kExtraCompletedFlag, kFilterNoInput); + break; + default: + Neighborhood::clickInHotspot(input, spot); + break; + } +} + +void Prehistoric::receiveNotification(Notification *notification, const NotificationFlags flags) { + Neighborhood::receiveNotification(notification, flags); + + if ((flags & kExtraCompletedFlag) != 0) { + _interruptionFilter = kFilterAllInput; + + switch (_lastExtra) { + case kPreArrivalFromTSA: + GameState.setPrehistoricSeenTimeStream(true); + loadAmbientLoops(); + makeContinuePoint(); + break; + case kPre18EastZoom: + startExtraSequence(kPre18EastZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kPre18EastZoomOut: + GameState.setPrehistoricSeenBridgeZoom(true); + break; + case kPre18EastBridgeOn: + _privateFlags.setFlag(kPrehistoricPrivateExtendedBridgeFlag, true); + setCurrentAlternate(kAltPrehistoricBridgeSet); + GameState.setPrehistoricTriedToExtendBridge(false); + loadAmbientLoops(); + GameState.setScoringExtendedBridge(true); + break; + case kPre18EastBridgeOut: + GameState.setPrehistoricTriedToExtendBridge(true); + if (g_AIArea) + g_AIArea->checkMiddleArea(); + break; + case kPre22ThrowBreaker: + GameState.setPrehistoricBreakerThrown(true); + GameState.setScoringThrewBreaker(true); + break; + case kPre25EastUnlockingVaultNoLog: + case kPre25EastUnlockingVaultWithLog: + _vm->addItemToInventory((InventoryItem *)_vm->getAllItems().findItemByID(kJourneymanKey)); + break; + } + } + + g_AIArea->checkMiddleArea(); +} + +Common::String Prehistoric::getBriefingMovie() { + Common::String movieName = Neighborhood::getBriefingMovie(); + + if (movieName.empty()) + movieName = "Images/AI/Prehistoric/XPE"; + + return movieName; +} + +Common::String Prehistoric::getEnvScanMovie() { + Common::String movieName = Neighborhood::getEnvScanMovie(); + + if (movieName.empty()) { + if (!_vm->isDemo()) { + switch (GameState.getCurrentRoom()) { + case kPrehistoric16: + case kPrehistoric23: + case kPrehistoric24: + return "Images/AI/Prehistoric/XP7WB"; + } + } + + return "Images/AI/Prehistoric/XP17NB"; + } + + return movieName; +} + +uint Prehistoric::getNumHints() { + uint numHints = Neighborhood::getNumHints(); + + if (numHints == 0) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kPrehistoric18, kEast): + if (!GameState.getPrehistoricBreakerThrown() && GameState.getPrehistoricTriedToExtendBridge() && + !_privateFlags.getFlag(kPrehistoricPrivateExtendedBridgeFlag)) + numHints = 1; + break; + case MakeRoomView(kPrehistoric25, kEast): + if (!_privateFlags.getFlag(kPrehistoricPrivateVaultOpenFlag)) + numHints = 1; + break; + } + } + + return numHints; +} + +Common::String Prehistoric::getHintMovie(uint hintNum) { + Common::String movieName = Neighborhood::getHintMovie(hintNum); + + if (movieName.empty()) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kPrehistoric18, kEast): + return "Images/AI/Prehistoric/XP18WD"; + case MakeRoomView(kPrehistoric25, kEast): + return "Images/AI/Globals/XGLOB1A"; + } + } + + return movieName; +} + +bool Prehistoric::canSolve() { + return GameState.getCurrentRoomAndView() == MakeRoomView(kPrehistoric18, kEast) && + !GameState.getPrehistoricBreakerThrown() && + GameState.getPrehistoricTriedToExtendBridge() && + !_privateFlags.getFlag(kPrehistoricPrivateExtendedBridgeFlag); +} + +void Prehistoric::doSolve() { + GameState.setPrehistoricBreakerThrown(true); + startExtraSequence(kPre18EastBridgeOn, kExtraCompletedFlag, kFilterNoInput); +} + +Hotspot *Prehistoric::getItemScreenSpot(Item *item, DisplayElement *element) { + if (item->getObjectID() == kHistoricalLog) + return _vm->getAllHotspots().findHotspotByID(kPrehistoricHistoricalLogSpotID); + + return Neighborhood::getItemScreenSpot(item, element); +} + +void Prehistoric::pickedUpItem(Item *item) { + switch (item->getObjectID()) { + case kHistoricalLog: + GameState.setScoringGotHistoricalLog(true); + break; + } + + Neighborhood::pickedUpItem(item); +} + +void Prehistoric::dropItemIntoRoom(Item *item, Hotspot *dropSpot) { + switch (item->getObjectID()) { + case kJourneymanKey: + Neighborhood::dropItemIntoRoom(item, dropSpot); + + if (GameState.isTakenItemID(kHistoricalLog)) + startExtraLongSequence(kPre25EastUnlockingVaultNoLog, kPre25EastVaultOpenNoLog, kExtraCompletedFlag, kFilterNoInput); + else + startExtraLongSequence(kPre25EastUnlockingVaultWithLog, kPre25EastVaultOpenWithLog, kExtraCompletedFlag, kFilterNoInput); + + _privateFlags.setFlag(kPrehistoricPrivateVaultOpenFlag, true); + setCurrentActivation(kActivationVaultOpen); + break; + default: + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + } +} + +void Prehistoric::bumpIntoWall() { + requestSpotSound(kPrehistoricBumpIntoWallIn, kPrehistoricBumpIntoWallOut, kFilterAllInput, 0); + Neighborhood::bumpIntoWall(); +} + +Common::String Prehistoric::getNavMovieName() { + return "Images/Prehistoric/Prehistoric.movie"; +} + +Common::String Prehistoric::getSoundSpotsName() { + return "Sounds/Prehistoric/Prehistoric Spots"; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/prehistoric/prehistoric.h b/engines/pegasus/neighborhood/prehistoric/prehistoric.h new file mode 100644 index 0000000000..17f9993014 --- /dev/null +++ b/engines/pegasus/neighborhood/prehistoric/prehistoric.h @@ -0,0 +1,158 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_PREHISTORIC_H +#define PEGASUS_NEIGHBORHOOD_PREHISTORIC_H + +#include "pegasus/neighborhood/neighborhood.h" + +namespace Pegasus { + +static const TimeScale kPrehistoricMovieScale = 600; +static const TimeScale kPrehistoricFramesPerSecond = 15; +static const TimeScale kPrehistoricFrameDuration = 40; + +// Alternate IDs. + +static const AlternateID kAltPrehistoricNormal = 0; +static const AlternateID kAltPrehistoricBridgeSet = 1; + +// Room IDs. + +static const RoomID kPrehistoric01 = 0; +static const RoomID kPrehistoric02 = 1; +static const RoomID kPrehistoric03 = 2; +static const RoomID kPrehistoric04 = 3; +static const RoomID kPrehistoric05 = 4; +static const RoomID kPrehistoric06 = 5; +static const RoomID kPrehistoric07 = 6; +static const RoomID kPrehistoric08 = 7; +static const RoomID kPrehistoric09 = 8; +static const RoomID kPrehistoric10 = 9; +static const RoomID kPrehistoric11 = 10; +static const RoomID kPrehistoric12 = 11; +static const RoomID kPrehistoric13 = 12; +static const RoomID kPrehistoric14 = 13; +static const RoomID kPrehistoric15 = 14; +static const RoomID kPrehistoric16 = 15; +static const RoomID kPrehistoric17 = 16; +static const RoomID kPrehistoric18 = 17; +static const RoomID kPrehistoric19 = 18; +static const RoomID kPrehistoric20 = 19; +static const RoomID kPrehistoric21 = 20; +static const RoomID kPrehistoric22 = 21; +static const RoomID kPrehistoric22North = 22; +static const RoomID kPrehistoric23 = 23; +static const RoomID kPrehistoric24 = 24; +static const RoomID kPrehistoric25 = 25; +static const RoomID kPrehistoricDeath = 26; + +// Hot Spot Activation IDs. + +static const HotSpotActivationID kActivationVaultClosed = 1; +static const HotSpotActivationID kActivationVaultOpen = 2; + +// Hot Spot IDs. + +static const HotSpotID kPre18EastSpotID = 5000; +static const HotSpotID kPre22NorthSpotID = 5001; +static const HotSpotID kPre22NorthOutSpotID = 5002; +static const HotSpotID kPre22NorthBreakerSpotID = 5003; +static const HotSpotID kPrehistoricKeyDropSpotID = 5004; +static const HotSpotID kPrehistoricHistoricalLogSpotID = 5005; + +// Extra sequence IDs. + +static const ExtraID kPreArrivalFromTSA = 0; +static const ExtraID kPre18EastBridgeOut = 1; +static const ExtraID kPre18EastBridgeOn = 2; +static const ExtraID kPre18EastZoom = 3; +static const ExtraID kPre18EastZoomOut = 4; +static const ExtraID kPre22ThrowBreaker = 5; +static const ExtraID kPre25EastUnlockingVaultWithLog = 6; +static const ExtraID kPre25EastVaultOpenWithLog = 7; +static const ExtraID kPre25EastViewWithLog = 8; +static const ExtraID kPre25EastUnlockingVaultNoLog = 9; +static const ExtraID kPre25EastVaultOpenNoLog = 10; +static const ExtraID kPre25EastViewNoLog = 11; + +class PegasusEngine; + +class Prehistoric : public Neighborhood { +public: + Prehistoric(InputHandler *, PegasusEngine *); + virtual ~Prehistoric() {} + + virtual uint16 getDateResID() const; + virtual void init(); + + virtual void arriveAt(const RoomID, const DirectionConstant); + virtual void activateHotspots(); + virtual void clickInHotspot(const Input &, const Hotspot *); + Common::String getBriefingMovie(); + Common::String getEnvScanMovie(); + uint getNumHints(); + Common::String getHintMovie(uint); + + Hotspot *getItemScreenSpot(Item *, DisplayElement *); + void dropItemIntoRoom(Item *, Hotspot *); + void pickedUpItem(Item *); + + void start(); + + void bumpIntoWall(); + + void checkContinuePoint(const RoomID, const DirectionConstant); + + bool canSolve(); + void doSolve(); + +protected: + enum { + kPrehistoricPrivateVaultOpenFlag, + kPrehistoricPrivateExtendedBridgeFlag, + kNumPrehistoricPrivateFlags + }; + + void setUpAIRules(); + int16 getStaticCompassAngle(const RoomID, const DirectionConstant); + void getExitCompassMove(const ExitTable::Entry &, FaderMoveSpec &); + virtual void receiveNotification(Notification *, const NotificationFlags); + void turnTo(const DirectionConstant); + void zoomToVault(); + TimeValue getViewTime(const RoomID, const DirectionConstant); + void findSpotEntry(const RoomID, const DirectionConstant, SpotFlags, SpotTable::Entry &); + + void loadAmbientLoops(); + + FlagsArray<byte, kNumPrehistoricPrivateFlags> _privateFlags; + + Common::String getNavMovieName(); + Common::String getSoundSpotsName(); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/spot.cpp b/engines/pegasus/neighborhood/spot.cpp new file mode 100644 index 0000000000..f285bf9bc2 --- /dev/null +++ b/engines/pegasus/neighborhood/spot.cpp @@ -0,0 +1,70 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/spot.h" + +namespace Pegasus { + +void SpotTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].room = stream->readUint16BE(); + _entries[i].direction = stream->readByte(); + _entries[i].srcFlags = stream->readByte(); + _entries[i].altCode = stream->readByte(); + stream->readByte(); // alignment + _entries[i].movieStart = stream->readUint32BE(); + _entries[i].movieEnd = stream->readUint32BE(); + _entries[i].dstFlags = stream->readByte(); + stream->readByte(); // alignment + debug(0, "Spot[%d]: %d %d %d %d %d %d %d", i, _entries[i].room, _entries[i].direction, + _entries[i].srcFlags, _entries[i].altCode, _entries[i].movieStart, + _entries[i].movieEnd, _entries[i].dstFlags); + } +} + +void SpotTable::clear() { + _entries.clear(); +} + +// Two SpotTable::Entries are equal if +// In addition to having their rooms, directions and alt codes identical... +// They are both either loops or once only animations AND +// They overlap in at least one of the on arrival, on turn and on door open bits. +SpotTable::Entry SpotTable::findEntry(RoomID room, DirectionConstant direction, SpotFlags srcFlags, AlternateID altCode) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].room == room && _entries[i].direction == direction && _entries[i].altCode == altCode && (_entries[i].srcFlags & kSpotLoopsMask) == (srcFlags & kSpotLoopsMask) && ((_entries[i].srcFlags & srcFlags) & kSpotTriggers) != 0) + return _entries[i]; + + return Entry(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/spot.h b/engines/pegasus/neighborhood/spot.h new file mode 100644 index 0000000000..a985420b7c --- /dev/null +++ b/engines/pegasus/neighborhood/spot.h @@ -0,0 +1,97 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_SPOT_H +#define PEGASUS_NEIGHBORHOOD_SPOT_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +typedef byte SpotFlags; + +enum { + kSpotLoopsBit, // Loop or once only? + kSpotOnArrivalBit, + kSpotOnTurnBit, + kSpotOnDoorOpenBit +}; + +static const SpotFlags kNoSpotFlags = 0; +static const SpotFlags kSpotLoopsMask = 1 << kSpotLoopsBit; +static const SpotFlags kSpotOnArrivalMask = 1 << kSpotOnArrivalBit; +static const SpotFlags kSpotOnTurnMask = 1 << kSpotOnTurnBit; +static const SpotFlags kSpotOnDoorOpenMask = 1 << kSpotOnDoorOpenBit; + +static const SpotFlags kSpotTriggers = kSpotOnArrivalMask | kSpotOnTurnMask | kSpotOnDoorOpenMask; + +class SpotTable { +public: + SpotTable() {} + ~SpotTable() {} + + static uint32 getResTag() { return MKTAG('S', 'p', 'o', 't'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry() { clear(); } + bool isEmpty() { return movieStart == 0xffffffff; } + void clear() { + room = kNoRoomID; + direction = kNoDirection; + srcFlags = kNoSpotFlags; + altCode = kNoAlternateID; + movieStart = 0xffffffff; + movieEnd = 0xffffffff; + dstFlags = kNoSpotFlags; + } + + RoomID room; + DirectionConstant direction; + SpotFlags srcFlags; + AlternateID altCode; + TimeValue movieStart; + TimeValue movieEnd; + SpotFlags dstFlags; + }; + + Entry findEntry(RoomID room, DirectionConstant direction, SpotFlags srcFlags, AlternateID altCode); + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/tsa/fulltsa.cpp b/engines/pegasus/neighborhood/tsa/fulltsa.cpp new file mode 100644 index 0000000000..b598841b45 --- /dev/null +++ b/engines/pegasus/neighborhood/tsa/fulltsa.cpp @@ -0,0 +1,3023 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/cursor.h" +#include "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/aichip.h" +#include "pegasus/items/biochips/opticalchip.h" +#include "pegasus/neighborhood/caldoria/caldoria.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/prehistoric/prehistoric.h" +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/tsa/fulltsa.h" +#include "pegasus/neighborhood/wsc/wsc.h" + +namespace Pegasus { + +// TSA PICTs: + +static const ResIDType kTBPCloseBoxPICTID = 800; +static const ResIDType kTBPRewindPICTID = 801; +static const ResIDType kUnresolvedPICTID = 802; +static const ResIDType kResolvedPICTID = 803; +static const ResIDType kJumpMenuPICTID = 804; +static const ResIDType kJumpMenuHilitedPICTID = 805; +static const ResIDType kExitPICTID = 806; +static const ResIDType kExitHilitedPICTID = 807; +static const ResIDType kLeftRipPICTID = 808; +static const ResIDType kComparisonCloseBoxPICTID = 809; +static const ResIDType kComparisonLeftRewindPICTID = 810; +static const ResIDType kComparisonRightRewindPICTID = 811; +static const ResIDType kComparisonHiliteNoradPICTID = 812; +static const ResIDType kComparisonHiliteMarsPICTID = 813; +static const ResIDType kComparisonHiliteCaldoriaPICTID = 814; +static const ResIDType kComparisonHiliteWSCPICTID = 815; +static const ResIDType kComparisonChancesNoradPICTID = 816; +static const ResIDType kComparisonChancesMarsPICTID = 817; +static const ResIDType kComparisonChancesCaldoriaPICTID = 818; +static const ResIDType kComparisonChancesWSCPICTID = 819; +static const ResIDType kRedirectionCCRolloverPICTID = 820; +static const ResIDType kRedirectionRRRolloverPICTID = 821; +static const ResIDType kRedirectionFDRolloverPICTID = 822; +static const ResIDType kRedirectionCCDoorPICTID = 823; +static const ResIDType kRedirectionRRDoorPICTID = 824; +static const ResIDType kRedirectionFDDoorPICTID = 825; +static const ResIDType kRedirectionSecuredPICTID = 826; +static const ResIDType kRedirectionNewTargetPICTID = 827; +static const ResIDType kRedirectionClosePICTID = 828; + +static const int16 kCompassShift = 15; + +static const TimeScale kFullTSAMovieScale = 600; +static const TimeScale kFullTSAFramesPerSecond = 15; +static const TimeScale kFullTSAFrameDuration = 40; + +// Alternate IDs. +static const AlternateID kAltTSANormal = 0; +static const AlternateID kAltTSARobotsAtReadyRoom = 1; +static const AlternateID kAltTSARobotsAtFrontDoor = 2; +static const AlternateID kAltTSARedAlert = 3; + +// Room IDs. +static const RoomID kTSA01 = 1; +static const RoomID kTSA02 = 2; +static const RoomID kTSA03 = 3; +static const RoomID kTSA04 = 4; +static const RoomID kTSA05 = 5; +static const RoomID kTSA0A = 6; +static const RoomID kTSA06 = 7; +static const RoomID kTSA07 = 8; +static const RoomID kTSA08 = 9; +static const RoomID kTSA09 = 10; +static const RoomID kTSA10 = 11; +static const RoomID kTSA11 = 12; +static const RoomID kTSA12 = 13; +static const RoomID kTSA13 = 14; +static const RoomID kTSA14 = 15; +static const RoomID kTSA15 = 16; +static const RoomID kTSA16 = 17; +static const RoomID kTSA17 = 18; +static const RoomID kTSA18 = 19; +static const RoomID kTSA19 = 20; +static const RoomID kTSA0B = 21; +static const RoomID kTSA21Cyan = 22; +static const RoomID kTSA22Cyan = 23; +static const RoomID kTSA23Cyan = 24; +static const RoomID kTSA24Cyan = 25; +static const RoomID kTSA25Cyan = 26; +static const RoomID kTSA21Red = 27; +static const RoomID kTSA23Red = 29; +static const RoomID kTSA24Red = 30; +static const RoomID kTSA25Red = 31; +static const RoomID kTSA26 = 32; +static const RoomID kTSA27 = 33; +static const RoomID kTSA28 = 34; +static const RoomID kTSA29 = 35; +static const RoomID kTSA30 = 36; +static const RoomID kTSA31 = 37; +static const RoomID kTSA32 = 38; +static const RoomID kTSA33 = 39; +static const RoomID kTSA34 = 40; +static const RoomID kTSA35 = 41; +static const RoomID kTSADeathRoom = 43; + +// Hot Spot Activation IDs. +static const HotSpotActivationID kActivateTSAReadyForCard = 1; +static const HotSpotActivationID kActivateTSAReadyToTransport = 2; +static const HotSpotActivationID kActivateTSARobotsAwake = 3; +static const HotSpotActivationID kActivateTSA0BZoomedOut = 4; +static const HotSpotActivationID kActivateTSA0BZoomedIn = 5; +static const HotSpotActivationID kActivateTSA0BComparisonVideo = 6; +static const HotSpotActivationID kActivationLogReaderOpen = 7; +static const HotSpotActivationID kActivateTSA0BTBPVideo = 8; +static const HotSpotActivationID kActivationDoesntHaveKey = 9; +static const HotSpotActivationID kActivationKeyVaultOpen = 10; +static const HotSpotActivationID kActivationDoesntHaveChips = 11; +static const HotSpotActivationID kActivationChipVaultOpen = 12; +static const HotSpotActivationID kActivationJumpToPrehistoric = 13; +static const HotSpotActivationID kActivationJumpToNorad = 14; +static const HotSpotActivationID kActivationJumpToMars = 15; +static const HotSpotActivationID kActivationJumpToWSC = 16; +static const HotSpotActivationID kActivationReadyToExit = 17; +static const HotSpotActivationID kActivationReadyForJumpMenu = 18; +static const HotSpotActivationID kActivationMainJumpMenu = 19; + +// Hot Spot IDs. +static const HotSpotID kTSAGTCardDropSpotID = 5000; +static const HotSpotID kTSAGTTokyoSpotID = 5001; +static const HotSpotID kTSAGTCaldoriaSpotID = 5002; +static const HotSpotID kTSAGTBeachSpotID = 5003; +static const HotSpotID kTSAGTOtherSpotID = 5004; +static const HotSpotID kTSA02DoorSpotID = 5005; +static const HotSpotID kTSA03EastJimenezSpotID = 5006; +static const HotSpotID kTSA03WestCrenshawSpotID = 5007; +static const HotSpotID kTSA04EastMatsumotoSpotID = 5008; +static const HotSpotID kTSA04WestCastilleSpotID = 5009; +static const HotSpotID kTSA05EastSinclairSpotID = 5010; +static const HotSpotID kTSA05WestWhiteSpotID = 5011; +static const HotSpotID kTSA0AEastSpotID = 5012; +static const HotSpotID kTSA0AWastSpotID = 5013; +static const HotSpotID kTSA0BEastMonitorSpotID = 5014; +static const HotSpotID kTSA0BEastMonitorOutSpotID = 5015; +static const HotSpotID kTSA0BEastCompareNoradSpotID = 5016; +static const HotSpotID kTSA0BEastCompareMarsSpotID = 5017; +static const HotSpotID kTSA0BEastCompareCaldoriaSpotID = 5018; +static const HotSpotID kTSA0BEastCompareWSCSpotID = 5019; +static const HotSpotID kTSA0BEastLeftRewindSpotID = 5020; +static const HotSpotID kTSA0BEastLeftPlaySpotID = 5021; +static const HotSpotID kTSA0BEastRightRewindSpotID = 5022; +static const HotSpotID kTSA0BEastRightPlaySpotID = 5023; +static const HotSpotID kTSA0BEastCloseVideoSpotID = 5024; +static const HotSpotID kTSA0BNorthMonitorSpotID = 5025; +static const HotSpotID kTSA0BNorthMonitorOutSpotID = 5026; +static const HotSpotID kTSA0BNorthHistLogSpotID = 5027; +static const HotSpotID kTSA0BNorthRobotsToCommandCenterSpotID = 5028; +static const HotSpotID kTSA0BNorthRobotsToReadyRoomSpotID = 5029; +static const HotSpotID kTSA0BNorthRobotsToFrontDoorSpotID = 5030; +static const HotSpotID kTSA0BWestMonitorSpotID = 5031; +static const HotSpotID kTSA0BWestMonitorOutSpotID = 5032; +static const HotSpotID kTSA0BWestTheorySpotID = 5033; +static const HotSpotID kTSA0BWestBackgroundSpotID = 5034; +static const HotSpotID kTSA0BWestProcedureSpotID = 5035; +static const HotSpotID kTSA0BWestCloseVideoSpotID = 5036; +static const HotSpotID kTSA0BWestPlayVideoSpotID = 5037; +static const HotSpotID kTSA0BWestRewindVideoSpotID = 5038; +static const HotSpotID kTSA22EastMonitorSpotID = 5039; +static const HotSpotID kTSA22EastKeySpotID = 5040; +static const HotSpotID kTSA23WestMonitorSpotID = 5041; +static const HotSpotID kTSA23WestChipsSpotID = 5042; +static const HotSpotID kTSA34NorthDoorSpotID = 5043; +static const HotSpotID kTSA37NorthJumpToPrehistoricSpotID = 5044; +static const HotSpotID kTSA37NorthJumpToNoradSpotID = 5045; +static const HotSpotID kTSA37NorthCancelNoradSpotID = 5046; +static const HotSpotID kTSA37NorthJumpToMarsSpotID = 5047; +static const HotSpotID kTSA37NorthCancelMarsSpotID = 5048; +static const HotSpotID kTSA37NorthJumpToWSCSpotID = 5049; +static const HotSpotID kTSA37NorthCancelWSCSpotID = 5050; +static const HotSpotID kTSA37NorthExitSpotID = 5051; +static const HotSpotID kTSA37NorthJumpMenuSpotID = 5052; +static const HotSpotID kTSA37NorthNoradMenuSpotID = 5053; +static const HotSpotID kTSA37NorthMarsMenuSpotID = 5054; +static const HotSpotID kTSA37NorthWSCMenuSpotID = 5055; + +// Extra sequence IDs. +static const ExtraID kTSATransporterArrowLoop = 0; +static const ExtraID kTSAArriveFromCaldoria = 1; +static const ExtraID kTSAGTOtherChoice = 2; +static const ExtraID kTSAGTCardSwipe = 3; +static const ExtraID kTSAGTSelectCaldoria = 4; +static const ExtraID kTSAGTGoToCaldoria = 5; +static const ExtraID kTSAGTSelectBeach = 6; +static const ExtraID kTSAGTGoToBeach = 7; +static const ExtraID kTSAGTArriveAtBeach = 8; +static const ExtraID kTSAGTSelectTokyo = 9; +static const ExtraID kTSAGTGoToTokyo = 10; +static const ExtraID kTSAGTArriveAtTokyo = 11; +static const ExtraID kTSA02NorthZoomIn = 12; +static const ExtraID kTSA02NorthTenSecondDoor = 13; +static const ExtraID kTSA02NorthZoomOut = 14; +static const ExtraID kTSA02NorthDoorWithAgent3 = 15; +static const ExtraID kTSA03JimenezZoomIn = 16; +static const ExtraID kTSA03JimenezSpeech = 17; +static const ExtraID kTSA03JimenezZoomOut = 18; +static const ExtraID kTSA03CrenshawZoomIn = 19; +static const ExtraID kTSA03CrenshawSpeech = 20; +static const ExtraID kTSA03CrenshawZoomOut = 21; +static const ExtraID kTSA03SouthRobotDeath = 22; +static const ExtraID kTSA04NorthRobotGreeting = 23; +static const ExtraID kTSA04MatsumotoZoomIn = 24; +static const ExtraID kTSA04MatsumotoSpeech = 25; +static const ExtraID kTSA04MatsumotoZoomOut = 26; +static const ExtraID kTSA04CastilleZoomIn = 27; +static const ExtraID kTSA04CastilleSpeech = 28; +static const ExtraID kTSA04CastilleZoomOut = 29; +static const ExtraID kTSA05SinclairZoomIn = 30; +static const ExtraID kTSA05SinclairSpeech = 31; +static const ExtraID kTSA05SinclairZoomOut = 32; +static const ExtraID kTSA05WhiteZoomIn = 33; +static const ExtraID kTSA05WhiteSpeech = 34; +static const ExtraID kTSA05WhiteZoomOut = 35; +static const ExtraID kTSA0AEastRobot = 36; +static const ExtraID kTSA0AWestRobot = 37; +static const ExtraID kTSA16NorthRobotDeath = 38; +static const ExtraID kTSA0BEastZoomIn = 39; +static const ExtraID kTSA0BEastZoomedView = 40; +static const ExtraID kTSA0BEastZoomOut = 41; +static const ExtraID kTSA0BEastTurnLeft = 42; +static const ExtraID kTSA0BComparisonStartup = 43; +static const ExtraID kTSA0BComparisonView0000 = 44; +static const ExtraID kTSA0BComparisonView0002 = 45; +static const ExtraID kTSA0BComparisonView0020 = 46; +static const ExtraID kTSA0BComparisonView0022 = 47; +static const ExtraID kTSA0BComparisonView0200 = 48; +static const ExtraID kTSA0BComparisonView0202 = 49; +static const ExtraID kTSA0BComparisonView0220 = 50; +static const ExtraID kTSA0BComparisonView0222 = 51; +static const ExtraID kTSA0BComparisonView2000 = 52; +static const ExtraID kTSA0BComparisonView2002 = 53; +static const ExtraID kTSA0BComparisonView2020 = 54; +static const ExtraID kTSA0BComparisonView2022 = 55; +static const ExtraID kTSA0BComparisonView2200 = 56; +static const ExtraID kTSA0BComparisonView2202 = 57; +static const ExtraID kTSA0BComparisonView2220 = 58; +static const ExtraID kTSA0BComparisonView2222 = 59; +static const ExtraID kTSA0BNoradComparisonView = 60; +static const ExtraID kTSA0BNoradUnaltered = 61; +static const ExtraID kTSA0BNoradAltered = 62; +static const ExtraID kTSA0BMarsComparisonView = 63; +static const ExtraID kTSA0BMarsUnaltered = 64; +static const ExtraID kTSA0BMarsAltered = 65; +static const ExtraID kTSA0BWSCComparisonView = 66; +static const ExtraID kTSA0BWSCUnaltered = 67; +static const ExtraID kTSA0BWSCAltered = 68; +static const ExtraID kTSA0BCaldoriaComparisonView = 69; +static const ExtraID kTSA0BCaldoriaUnaltered = 70; +static const ExtraID kTSA0BCaldoriaAltered = 71; +static const ExtraID kTSA0BNorthZoomIn = 72; +static const ExtraID kTSA0BNorthZoomedView = 73; +static const ExtraID kTSA0BNorthZoomOut = 74; +static const ExtraID kTSA0BNorthTurnLeft = 75; +static const ExtraID kTSA0BNorthTurnRight = 76; +static const ExtraID kTSA0BNorthHistLogOpen = 77; +static const ExtraID kTSA0BNorthHistLogClose = 78; +static const ExtraID kTSA0BNorthHistLogCloseWithLog = 79; +static const ExtraID kTSA0BNorthCantChangeHistory = 80; +static const ExtraID kTSA0BNorthYoureBusted = 81; +static const ExtraID kTSA0BNorthFinallyHappened = 82; +static const ExtraID kTSA0BShowRip1 = 83; +static const ExtraID kTSA0BNorthRipView1 = 84; +static const ExtraID kTSA0BShowRip2 = 85; +static const ExtraID kTSA0BShowGuardRobots = 86; +static const ExtraID kTSA0BAIInterruption = 87; +static const ExtraID kTSA0BRobotsToCommandCenter = 88; +static const ExtraID kTSA0BNorthRobotsAtCCView = 89; +static const ExtraID kTSA0BNorthRobotsAtRRView = 90; +static const ExtraID kTSA0BNorthRobotsAtFDView = 91; +static const ExtraID kTSA0BRobotsFromCommandCenterToReadyRoom = 92; +static const ExtraID kTSA0BRobotsFromReadyRoomToCommandCenter = 93; +static const ExtraID kTSA0BRobotsFromCommandCenterToFrontDoor = 94; +static const ExtraID kTSA0BRobotsFromFrontDoorToCommandCenter = 95; +static const ExtraID kTSA0BRobotsFromFrontDoorToReadyRoom = 96; +static const ExtraID kTSA0BRobotsFromReadyRoomToFrontDoor = 97; +static const ExtraID kTSA0BWestZoomIn = 98; +static const ExtraID kTSA0BWestZoomedView = 99; +static const ExtraID kTSA0BWestZoomOut = 100; +static const ExtraID kTSA0BWestTurnRight = 101; +static const ExtraID kTSA0BTBPTheoryHighlight = 102; +static const ExtraID kTSA0BTBPBackgroundHighlight = 103; +static const ExtraID kTSA0BTBPProcedureHighlight = 104; +static const ExtraID kTSA0BTBPTheory = 105; +static const ExtraID kTSA0BTBPBackground = 106; +static const ExtraID kTSA0BTBPProcedure = 107; +static const ExtraID kTSA0BRipAlarmScreen = 108; +static const ExtraID kTSA22RedEastZoomInSequence = 109; +static const ExtraID kTSA22RedEastVaultViewWithKey = 110; +static const ExtraID kTSA22RedEastVaultViewNoKey = 111; +static const ExtraID kTSA23RedWestVaultZoomInSequence = 112; +static const ExtraID kTSA23RedWestVaultViewWithChips = 113; +static const ExtraID kTSA23RedWestVaultViewNoChips = 114; +static const ExtraID kTSA25NorthDeniedNoKey = 115; +static const ExtraID kTSA25NorthDeniedNoChip = 116; +static const ExtraID kTSA25NorthPutOnSuit = 117; +static const ExtraID kTSA25NorthAlreadyHaveSuit = 118; +static const ExtraID kTSA25NorthDescending1 = 119; +static const ExtraID kTSA25NorthDescending2 = 120; +static const ExtraID kTSA37HorseToAI1 = 121; +static const ExtraID kTSA37PegasusAI1 = 122; +static const ExtraID kTSA37AI1ToCommissioner1 = 123; +static const ExtraID kTSA37Commissioner1 = 124; +static const ExtraID kTSA37Commissioner1ToZoom = 125; +static const ExtraID kTSA37ZoomToPrehistoric = 126; +static const ExtraID kTSA37PrehistoricToAI2 = 127; +static const ExtraID kTSA37PegasusAI2 = 128; +static const ExtraID kTSA37AI2ToPrehistoric = 129; +static const ExtraID kTSA37PrehistoricToDepart = 130; +static const ExtraID kTSA37PegasusDepart = 131; +static const ExtraID kTSA37TimeJumpToPegasus = 132; +static const ExtraID kTSA37RecallToDownload = 133; +static const ExtraID kTSA37DownloadToColonel1 = 134; +static const ExtraID kTSA37Colonel1 = 135; +static const ExtraID kTSA37Colonel1ToReviewRequired = 136; +static const ExtraID kTSA37ReviewRequiredToExit = 137; +static const ExtraID kTSA37ExitHilited = 138; +static const ExtraID kTSA37ExitToHorse = 139; +static const ExtraID kTSA37HorseToColonel2 = 140; +static const ExtraID kTSA37Colonel2 = 141; +static const ExtraID kTSA37PegasusAI3 = 142; +static const ExtraID kTSA37AI3ToHorse = 143; +static const ExtraID kTSA37HorseToZoom = 144; +static const ExtraID kTSA37ZoomToMainMenu = 145; +static const ExtraID kTSA37MainMenuToAI4 = 146; +static const ExtraID kTSA37PegasusAI4 = 147; +static const ExtraID kTSA37AI4ToMainMenu = 148; +static const ExtraID kTSA37JumpMenu000 = 149; +static const ExtraID kTSA37JumpMenu001 = 150; +static const ExtraID kTSA37JumpMenu010 = 151; +static const ExtraID kTSA37JumpMenu011 = 152; +static const ExtraID kTSA37JumpMenu100 = 153; +static const ExtraID kTSA37JumpMenu101 = 154; +static const ExtraID kTSA37JumpMenu110 = 155; +static const ExtraID kTSA37JumpMenu111 = 156; +static const ExtraID kTSA37JumpToWSCMenu = 157; +static const ExtraID kTSA37CancelWSC = 158; +static const ExtraID kTSA37JumpToWSC = 159; +static const ExtraID kTSA37WSCToAI5 = 160; +static const ExtraID kTSA37PegasusAI5 = 161; +static const ExtraID kTSA37AI5ToWSC = 162; +static const ExtraID kTSA37WSCToDepart = 163; +static const ExtraID kTSA37JumpToMarsMenu = 164; +static const ExtraID kTSA37CancelMars = 165; +static const ExtraID kTSA37JumpToMars = 166; +static const ExtraID kTSA37MarsToAI6 = 167; +static const ExtraID kTSA37PegasusAI6 = 168; +static const ExtraID kTSA37AI6ToMars = 169; +static const ExtraID kTSA37MarsToDepart = 170; +static const ExtraID kTSA37JumpToNoradMenu = 171; +static const ExtraID kTSA37CancelNorad = 172; +static const ExtraID kTSA37JumpToNorad = 173; +static const ExtraID kTSA37NoradToAI7 = 174; +static const ExtraID kTSA37PegasusAI7 = 175; +static const ExtraID kTSA37AI7ToNorad = 176; +static const ExtraID kTSA37NoradToDepart = 177; +static const ExtraID kTSA37EnvironmentalScan = 178; +static const ExtraID kTSA37DownloadToMainMenu = 179; +static const ExtraID kTSA37DownloadToOpMemReview = 180; +static const ExtraID kTSA37OpMemReviewToMainMenu = 181; +static const ExtraID kTSA37OpMemReviewToAllClear = 182; +static const ExtraID kTSA37AllClearToCongratulations = 183; +static const ExtraID kTSA37Congratulations = 184; +static const ExtraID kTSA37CongratulationsToExit = 185; + +const DisplayOrder kRipTimerOrder = kMonitorLayer; + + +const CoordType kUnresolvedLeft = kNavAreaLeft + 14; +const CoordType kUnresolvedTop = kNavAreaTop + 236; + +const CoordType kResolvedLeft = kNavAreaLeft + 36; +const CoordType kResolvedTop = kNavAreaTop + 236; + +const CoordType kJumpMenuLeft = kNavAreaLeft + 360; +const CoordType kJumpMenuTop = kNavAreaTop + 202; + +const CoordType kJumpMenuHilitedLeft = kNavAreaLeft + 354; +const CoordType kJumpMenuHilitedTop = kNavAreaTop + 196; + +const CoordType kExitLeft = kNavAreaLeft + 360; +const CoordType kExitTop = kNavAreaTop + 216; + +const CoordType kExitHilitedLeft = kNavAreaLeft + 354; +const CoordType kExitHilitedTop = kNavAreaTop + 210; + +const CoordType kRipTimerLeft = kNavAreaLeft + 95; +const CoordType kRipTimerTop = kNavAreaTop + 87; + +const CoordType kTBPCloseLeft = kNavAreaLeft + 30; +const CoordType kTBPCloseTop = kNavAreaTop + 16; + +const CoordType kTBPRewindLeft = kNavAreaLeft + 86; +const CoordType kTBPRewindTop = kNavAreaTop + 218; + +const CoordType kComparisonCloseLeft = kNavAreaLeft + 50; +const CoordType kComparisonCloseTop = kNavAreaTop + 14; + +const CoordType kComparisonLeftRewindLeft = kNavAreaLeft + 96; +const CoordType kComparisonLeftRewindTop = kNavAreaTop + 190; + +const CoordType kComparisonRightRewindLeft = kNavAreaLeft + 282; +const CoordType kComparisonRightRewindTop = kNavAreaTop + 190; + +const CoordType kComparisonHiliteSpriteLeft = kNavAreaLeft + 45; +const CoordType kComparisonHiliteSpriteTop = kNavAreaTop + 65; + +const CoordType kComparisonHiliteNoradLeft = kNavAreaLeft + 45; +const CoordType kComparisonHiliteNoradTop = kNavAreaTop + 65; + +const CoordType kComparisonHiliteMarsLeft = kNavAreaLeft + 45 + 4; +const CoordType kComparisonHiliteMarsTop = kNavAreaTop + 65 + 23; + +const CoordType kComparisonHiliteCaldoriaLeft = kNavAreaLeft + 45 + 7; +const CoordType kComparisonHiliteCaldoriaTop = kNavAreaTop + 65 + 46; + +const CoordType kComparisonHiliteWSCLeft = kNavAreaLeft + 45 + 11; +const CoordType kComparisonHiliteWSCTop = kNavAreaTop + 65 + 68; + +const CoordType kComparisonChancesSpriteLeft = kNavAreaLeft + 148; +const CoordType kComparisonChancesSpriteTop = kNavAreaTop + 162; + +const CoordType kComparisonChancesNoradLeft = kNavAreaLeft + 148; +const CoordType kComparisonChancesNoradTop = kNavAreaTop + 162; + +const CoordType kComparisonChancesMarsLeft = kNavAreaLeft + 148; +const CoordType kComparisonChancesMarsTop = kNavAreaTop + 162; + +const CoordType kComparisonChancesCaldoriaLeft = kNavAreaLeft + 148; +const CoordType kComparisonChancesCaldoriaTop = kNavAreaTop + 162 + 1; + +const CoordType kComparisonChancesWSCLeft = kNavAreaLeft + 148; +const CoordType kComparisonChancesWSCTop = kNavAreaTop + 162; + +const CoordType kRedirectionSprite1Left = kNavAreaLeft + 58; +const CoordType kRedirectionSprite1Top = kNavAreaTop + 16; + +const CoordType kRedirectionSprite2Left = kNavAreaLeft + 36; +const CoordType kRedirectionSprite2Top = kNavAreaTop + 166; + +const CoordType kRedirectionCCRolloverLeft = kNavAreaLeft + 58; +const CoordType kRedirectionCCRolloverTop = kNavAreaTop + 16; + +const CoordType kRedirectionRRRolloverLeft = kNavAreaLeft + 430; +const CoordType kRedirectionRRRolloverTop = kNavAreaTop + 30; + +const CoordType kRedirectionFDRolloverLeft = kNavAreaLeft + 278; +const CoordType kRedirectionFDRolloverTop = kNavAreaTop + 160; + +const CoordType kRedirectionCCDoorLeft = kNavAreaLeft + 174; +const CoordType kRedirectionCCDoorTop = kNavAreaTop + 36; + +const CoordType kRedirectionRRDoorLeft = kNavAreaLeft + 418; +const CoordType kRedirectionRRDoorTop = kNavAreaTop + 32; + +const CoordType kRedirectionFDDoorLeft = kNavAreaLeft + 298; +const CoordType kRedirectionFDDoorTop = kNavAreaTop + 240; + +const CoordType kRedirectionSecuredLeft = kNavAreaLeft + 36; +const CoordType kRedirectionSecuredTop = kNavAreaTop + 166; + +const CoordType kRedirectionNewTargetLeft = kNavAreaLeft + 36; +const CoordType kRedirectionNewTargetTop = kNavAreaTop + 166; + +const CoordType kRedirectionCloseLeft = kNavAreaLeft + 56; +const CoordType kRedirectionCloseTop = kNavAreaTop + 220; + +static const TimeValue kTSABumpIntoWallIn = 0; +static const TimeValue kTSABumpIntoWallOut = 148; + +static const TimeValue kTSAGTDoorCloseIn = 148; +static const TimeValue kTSAGTDoorCloseOut = 1570; + +static const TimeValue kTSANoOtherDestinationIn = 1570; +static const TimeValue kTSANoOtherDestinationOut = 3601; + +static const TimeValue kTSAEntryDoorCloseIn = 3601; +static const TimeValue kTSAEntryDoorCloseOut = 4200; + +static const TimeValue kTSAInsideDoorCloseIn = 4200; +static const TimeValue kTSAInsideDoorCloseOut = 4800; + +static const TimeValue kTSAVaultCloseIn = 4800; +static const TimeValue kTSAVaultCloseOut = 5388; + +static const TimeValue kTSAPegasusDoorCloseIn = 5388; +static const TimeValue kTSAPegasusDoorCloseOut = 6457; + +static const bool kPegasusUnresolved = false; +static const bool kPegasusResolved = true; +static const bool kPegasusCantExit = false; +static const bool kPegasusCanExit = true; + +// Monitor modes +enum { + kMonitorNeutral = 0, + kMonitorTheory = 1, + kMonitorProcedure = 2, + kMonitorBackground = 3, + kMonitorNoradComparison = 4, + kMonitorMarsComparison = 5, + kMonitorCaldoriaComparison = 6, + kMonitorWSCComparison = 7, + + kRawModeMask = 0x0F, + kPlayingTBPMask = 0x10, + kPlayingLeftComparisonMask = 0x20, + kPlayingRightComparisonMask = 0x40, + + kPlayingAnyMask = kPlayingTBPMask | + kPlayingLeftComparisonMask | + kPlayingRightComparisonMask, + + kMonitorPlayingTheory = kMonitorTheory | kPlayingTBPMask, + kMonitorPlayingProcedure = kMonitorProcedure | kPlayingTBPMask, + kMonitorPlayingBackground = kMonitorBackground | kPlayingTBPMask, + + kMonitorPlayingLeftNoradComparison = kMonitorNoradComparison | + kPlayingLeftComparisonMask, + kMonitorPlayingRightNoradComparison = kMonitorNoradComparison | + kPlayingRightComparisonMask, + kMonitorPlayingLeftMarsComparison = kMonitorMarsComparison | + kPlayingLeftComparisonMask, + kMonitorPlayingRightMarsComparison = kMonitorMarsComparison | + kPlayingRightComparisonMask, + kMonitorPlayingLeftCaldoriaComparison = kMonitorCaldoriaComparison | + kPlayingLeftComparisonMask, + kMonitorPlayingRightCaldoriaComparison = kMonitorCaldoriaComparison | + kPlayingRightComparisonMask, + kMonitorPlayingLeftWSCComparison = kMonitorWSCComparison | + kPlayingLeftComparisonMask, + kMonitorPlayingRightWSCComparison = kMonitorWSCComparison | + kPlayingRightComparisonMask +}; + +static const ExtraID s_historicalLogViews[16] = { + kTSA0BComparisonView0000, + kTSA0BComparisonView0002, + kTSA0BComparisonView0020, + kTSA0BComparisonView0022, + kTSA0BComparisonView0200, + kTSA0BComparisonView0202, + kTSA0BComparisonView0220, + kTSA0BComparisonView0222, + kTSA0BComparisonView2000, + kTSA0BComparisonView2002, + kTSA0BComparisonView2020, + kTSA0BComparisonView2022, + kTSA0BComparisonView2200, + kTSA0BComparisonView2202, + kTSA0BComparisonView2220, + kTSA0BComparisonView2222 +}; + +static const int kRedirectionCCRolloverSprite = 0; +static const int kRedirectionRRRolloverSprite = 1; +static const int kRedirectionFDRolloverSprite = 2; +static const int kRedirectionCCDoorSprite = 3; +static const int kRedirectionRRDoorSprite = 4; +static const int kRedirectionFDDoorSprite = 5; +static const int kRedirectionCloseSprite = 6; +static const int kRedirectionSecuredSprite = 0; +static const int kRedirectionNewTargetSprite = 1; + +void RipTimer::initImage() { + _middle = -1; + + _timerImage.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kLeftRipPICTID); + + Common::Rect r; + _timerImage.getSurfaceBounds(r); + setBounds(r); +} + +void RipTimer::releaseImage() { + _timerImage.deallocateSurface(); +} + +void RipTimer::draw(const Common::Rect &updateRect) { + Common::Rect bounds; + getBounds(bounds); + + Common::Rect r1 = bounds; + r1.right = _middle; + r1 = updateRect.findIntersectingRect(r1); + + if (!r1.isEmpty()) { + Common::Rect r2 = r1; + r2.moveTo(r1.left - _bounds.left, r1.top - bounds.top); + _timerImage.copyToCurrentPort(r2, r1); + } +} + +void RipTimer::timeChanged(const TimeValue newTime) { + Common::Rect bounds; + getBounds(bounds); + + CoordType newMiddle = bounds.left + bounds.width() * newTime / getDuration(); + + if (newMiddle != _middle) { + _middle = newMiddle; + triggerRedraw(); + } + + if (newTime == getStop()) + ((PegasusEngine *)g_engine)->die(kDeathUncreatedInTSA); +} + +FullTSA::FullTSA(InputHandler *nextHandler, PegasusEngine *owner) : Neighborhood(nextHandler, owner, "Full TSA", kFullTSAID), + _ripTimer(kNoDisplayElement), _sprite1(kNoDisplayElement), _sprite2(kNoDisplayElement), _sprite3(kNoDisplayElement) { + setIsItemTaken(kJourneymanKey); + setIsItemTaken(kPegasusBiochip); + setIsItemTaken(kMapBiochip); +} + +void FullTSA::init() { + Neighborhood::init(); + _ripTimer.setDisplayOrder(kRipTimerOrder); + _ripTimer.startDisplaying(); + + if (!GameState.getTSASeenRobotGreeting()) + forceStridingStop(kTSA03, kNorth, kNoAlternateID); + + _sprite1.setDisplayOrder(kMonitorLayer); + _sprite1.startDisplaying(); + _sprite2.setDisplayOrder(kMonitorLayer); + _sprite2.startDisplaying(); + _sprite3.setDisplayOrder(kMonitorLayer); + _sprite3.startDisplaying(); + + // Fix a mistake in the world builder tables. + HotspotInfoTable::Entry *entry = findHotspotEntry(kTSA23WestChipsSpotID); + entry->hotspotItem = kPegasusBiochip; +} + +void FullTSA::dieUncreatedInTSA() { + die(kDeathUncreatedInTSA); +} + +void FullTSA::start() { + g_energyMonitor->stopEnergyDraining(); + + if (!GameState.getScoringEnterTSA()) { + _utilityFuse.primeFuse(GameState.getTSAFuseTimeLimit()); + _utilityFuse.setFunctor(new Common::Functor0Mem<void, FullTSA>(this, &FullTSA::dieUncreatedInTSA)); + _utilityFuse.lightFuse(); + } else if (GameState.getTSAState() == kTSAPlayerDetectedRip || GameState.getTSAState() == kTSAPlayerNeedsHistoricalLog) { + _ripTimer.initImage(); + _ripTimer.moveElementTo(kRipTimerLeft, kRipTimerTop); + _ripTimer.setSegment(0, kRipTimeLimit, kRipTimeScale); + _ripTimer.setTime(GameState.getRipTimerTime()); + _ripTimer.start(); + } + + Neighborhood::start(); +} + +void FullTSA::flushGameState() { + GameState.setRipTimerTime(_ripTimer.getTime()); + GameState.setTSAFuseTimeLimit(_utilityFuse.getTimeRemaining()); +} + +Common::String FullTSA::getBriefingMovie() { + Common::String movieName = Neighborhood::getBriefingMovie(); + + if (movieName.empty()) { + RoomID room = GameState.getCurrentRoom(); + + switch (GameState.getTSAState()) { + case kTSAPlayerNotArrived: + case kTSAPlayerForcedReview: + if (room >= kTSA16 && room <= kTSA0B) + return "Images/AI/TSA/XT01A"; + + return "Images/AI/TSA/XT01"; + case kTSAPlayerDetectedRip: + case kTSAPlayerNeedsHistoricalLog: + return "Images/AI/TSA/XT02"; + case kTSAPlayerGotHistoricalLog: + case kTSAPlayerInstalledHistoricalLog: + return "Images/AI/TSA/XT03"; + default: + switch (getCurrentActivation()) { + case kActivationJumpToPrehistoric: + g_AIChip->showBriefingClicked(); + startExtraSequenceSync(kTSA37PegasusAI2, kHintInterruption); + startExtraSequenceSync(kTSA37AI2ToPrehistoric, kFilterNoInput); + g_AIChip->clearClicked(); + break; + case kActivationJumpToNorad: + g_AIChip->showBriefingClicked(); + startExtraSequenceSync(kTSA37PegasusAI7, kHintInterruption); + startExtraSequenceSync(kTSA37AI7ToNorad, kFilterNoInput); + g_AIChip->clearClicked(); + break; + case kActivationJumpToMars: + g_AIChip->showBriefingClicked(); + startExtraSequenceSync(kTSA37PegasusAI6, kHintInterruption); + startExtraSequenceSync(kTSA37AI6ToMars, kFilterNoInput); + g_AIChip->clearClicked(); + break; + case kActivationJumpToWSC: + g_AIChip->showBriefingClicked(); + startExtraSequenceSync(kTSA37PegasusAI5, kHintInterruption); + startExtraSequenceSync(kTSA37AI5ToWSC, kFilterNoInput); + g_AIChip->clearClicked(); + break; + default: + if (GameState.allTimeZonesFinished()) + return "Images/AI/TSA/XT05"; + + return "Images/AI/TSA/XT04"; + } + break; + } + } + + return movieName; +} + +Common::String FullTSA::getEnvScanMovie() { + Common::String movieName = Neighborhood::getEnvScanMovie(); + + if (movieName.empty()) { + switch (GameState.getTSAState()) { + case kTSAPlayerNotArrived: + case kTSAPlayerForcedReview: + case kTSAPlayerDetectedRip: + case kTSAPlayerNeedsHistoricalLog: + return "Images/AI/TSA/XTE1"; + default: + if (GameState.getCurrentRoom() == kTSA37) { + g_AIChip->showEnvScanClicked(); + startExtraSequenceSync(kTSA37EnvironmentalScan, kHintInterruption); + + switch (getCurrentActivation()) { + case kActivationJumpToPrehistoric: + startExtraSequenceSync(kTSA37AI2ToPrehistoric, kFilterNoInput); + break; + case kActivationJumpToNorad: + startExtraSequenceSync(kTSA37AI7ToNorad, kFilterNoInput); + showExtraView(kTSA37JumpToNoradMenu); + break; + case kActivationJumpToMars: + startExtraSequenceSync(kTSA37AI6ToMars, kFilterNoInput); + showExtraView(kTSA37JumpToMarsMenu); + break; + case kActivationJumpToWSC: + startExtraSequenceSync(kTSA37AI5ToWSC, kFilterNoInput); + showExtraView(kTSA37JumpToWSCMenu); + break; + default: + startExtraSequenceSync(kTSA37AI4ToMainMenu, kFilterNoInput); + break; + } + + g_AIChip->clearClicked(); + } else if (GameState.allTimeZonesFinished()) { + return "Images/AI/TSA/XTE1"; + } else { + return "Images/AI/TSA/XTE2"; + } + break; + } + } + + return movieName; +} + +uint FullTSA::getNumHints() { + uint numHints = Neighborhood::getNumHints(); + + if (numHints == 0) { + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + if (GameState.getCurrentRoom() == kTSA0B && GameState.getTSA0BZoomedIn()) + numHints = 3; + break; + } + } + + return numHints; +} + +Common::String FullTSA::getHintMovie(uint hintNum) { + Common::String movieName = Neighborhood::getHintMovie(hintNum); + + if (movieName.empty()) + movieName = Common::String::format("Images/AI/TSA/XT20NH%d", hintNum); + + return movieName; +} + +void FullTSA::loadAmbientLoops() { + RoomID room = GameState.getCurrentRoom(); + + switch (GameState.getTSAState()) { + case kTSAPlayerDetectedRip: + case kTSAPlayerNeedsHistoricalLog: + if ((room >= kTSA16 && room <= kTSA0B) || (room >= kTSA21Cyan && room <= kTSA24Cyan) || (room >= kTSA21Red && room <= kTSA24Red)) + loadLoopSound1("Sounds/TSA/TSA CLAXON.22K.AIFF", 0x100 / 4, 0, 0); + else if (room == kTSA25Cyan || room == kTSA25Red) + loadLoopSound1("Sounds/TSA/TSA CLAXON.22K.AIFF", 0x100 / 6, 0, 0); + else + loadLoopSound1("Sounds/TSA/TSA EchoClaxon.22K.AIFF", 0x100 / 4, 0, 0); + break; + default: + if (room >= kTSA00 && room <= kTSA02) + loadLoopSound1("Sounds/TSA/T01NAE.NEW.22K.AIFF"); + else if (room >= kTSA03 && room <= kTSA15) + loadLoopSound1("Sounds/TSA/T01NAE.NEW.22K.AIFF"); + else if (room >= kTSA16 && room <= kTSA0B) + loadLoopSound1("Sounds/TSA/T14SAEO1.22K.AIFF"); + else if (room >= kTSA21Cyan && room <= kTSA25Red) + loadLoopSound1("Sounds/TSA/T15SAE01.22K.AIFF"); + else if (room >= kTSA26 && room <= kTSA37) + loadLoopSound1("Sounds/TSA/T01NAE.NEW.22K.AIFF"); + break; + } +} + +short FullTSA::getStaticCompassAngle(const RoomID room, const DirectionConstant dir) { + int16 result = Neighborhood::getStaticCompassAngle(room, dir); + + switch (room) { + case kTSA08: + result += kCompassShift; + break; + case kTSA09: + result -= kCompassShift; + break; + case kTSA10: + result += kCompassShift * 2; + break; + case kTSA11: + case kTSA22Cyan: + case kTSA22Red: + result -= kCompassShift * 2; + break; + case kTSA12: + result += kCompassShift * 3; + break; + case kTSA13: + result -= kCompassShift * 3; + break; + case kTSA14: + case kTSA16: + case kTSA17: + case kTSA18: + case kTSA19: + result += kCompassShift * 4; + break; + case kTSA0B: + result += kCompassShift * 4; + + if (dir == kWest) + result += 30; + else if (dir == kEast) + result -= 30; + break; + case kTSA33: + result += kCompassShift * 4; + break; + case kTSA15: + case kTSA21Cyan: + case kTSA24Cyan: + case kTSA25Cyan: + case kTSA21Red: + case kTSA24Red: + case kTSA25Red: + case kTSA26: + case kTSA27: + case kTSA28: + case kTSA29: + case kTSA30: + result -= kCompassShift * 4; + break; + case kTSA23Cyan: + case kTSA23Red: + result -= kCompassShift * 6; + break; + case kTSA32: + result -= kCompassShift * 8; + break; + case kTSA34: + result -= kCompassShift * 12; + break; + case kTSA35: + result += kCompassShift * 8; + break; + case kTSA37: + result -= kCompassShift * 2; + break; + } + + return result; +} + +void FullTSA::getExitCompassMove(const ExitTable::Entry &exitEntry, FaderMoveSpec &compassMove) { + Neighborhood::getExitCompassMove(exitEntry, compassMove); + + switch (MakeRoomView(exitEntry.room, exitEntry.direction)) { + case MakeRoomView(kTSA01, kSouth): + compassMove.insertFaderKnot(exitEntry.movieStart, -180); + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 3, -180); + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 33, + getStaticCompassAngle(exitEntry.exitRoom, exitEntry.exitDirection)); + break; + case MakeRoomView(kTSA11, kEast): + if (getCurrentAlternate() == kAltTSARobotsAtReadyRoom) { + compassMove.makeTwoKnotFaderSpec(kFullTSAMovieScale, exitEntry.movieStart, + getStaticCompassAngle(kTSA11, kEast), exitEntry.movieEnd, + getStaticCompassAngle(kTSA13, kEast)); + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 13, compassMove.getNthKnotValue(1)); + } + break; + case MakeRoomView(kTSA34, kNorth): + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 48, + getStaticCompassAngle(exitEntry.room, exitEntry.direction)); + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 68, + getStaticCompassAngle(exitEntry.exitRoom, exitEntry.exitDirection)); + break; + case MakeRoomView(kTSA37, kNorth): + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 38, + getStaticCompassAngle(exitEntry.room, exitEntry.direction)); + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 64, + getStaticCompassAngle(exitEntry.room, exitEntry.direction) + kCompassShift * 3 / 2); + compassMove.insertFaderKnot(exitEntry.movieStart + kFullTSAFrameDuration * 105, + getStaticCompassAngle(exitEntry.exitRoom, exitEntry.exitDirection)); + break; + } +} + +void FullTSA::getExtraCompassMove(const ExtraTable::Entry &extraEntry, FaderMoveSpec &compassMove) { + int16 angle; + + switch (extraEntry.extra) { + case kTSA0BEastTurnLeft: + case kTSA0BNorthTurnLeft: + angle =getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection()); + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), extraEntry.movieStart, angle, + extraEntry.movieEnd, angle - 60); + break; + case kTSA0BNorthTurnRight: + case kTSA0BWestTurnRight: + angle = getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection()); + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), extraEntry.movieStart, angle, + extraEntry.movieEnd, angle + 60); + break; + case kTSA22RedEastZoomInSequence: + angle = getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection()); + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), extraEntry.movieStart, angle, + extraEntry.movieEnd, angle); + compassMove.insertFaderKnot(extraEntry.movieStart + 1200, angle - kCompassShift * 2); + compassMove.insertFaderKnot(extraEntry.movieStart + 8160, angle - kCompassShift * 2); + compassMove.insertFaderKnot(extraEntry.movieStart + 9840, angle); + break; + case kTSA23RedWestVaultZoomInSequence: + angle = getStaticCompassAngle(GameState.getCurrentRoom(), GameState.getCurrentDirection()); + compassMove.makeTwoKnotFaderSpec(_navMovie.getScale(), extraEntry.movieStart, angle, + extraEntry.movieEnd, angle); + compassMove.insertFaderKnot(extraEntry.movieStart + 1200, angle - kCompassShift * 2); + compassMove.insertFaderKnot(extraEntry.movieStart + 10100, angle - kCompassShift * 2); + compassMove.insertFaderKnot(extraEntry.movieStart + 11880, angle); + break; + default: + Neighborhood::getExtraCompassMove(extraEntry, compassMove); + break; + } +} + +uint16 FullTSA::getDateResID() const { + return kDate2318ID; +} + +TimeValue FullTSA::getViewTime(const RoomID room, const DirectionConstant direction) { + ExtraID extraID = 0xffffffff; + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kTSA0B, kEast): + if (GameState.getTSA0BZoomedIn()) + switch (GameState.getTSAState()) { + case kTSAPlayerInstalledHistoricalLog: + case kTSABossSawHistoricalLog: + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + extraID = s_historicalLogViews[getHistoricalLogIndex()]; + break; + default: + extraID = kTSA0BEastZoomedView; + break; + } + break; + case MakeRoomView(kTSA0B, kNorth): + if (GameState.getTSA0BZoomedIn()) + switch (GameState.getTSAState()) { + case kTSAPlayerNeedsHistoricalLog: + extraID = kTSA0BNorthRipView1; + break; + default: + extraID = kTSA0BNorthZoomedView; + break; + } + break; + case MakeRoomView(kTSA0B, kWest): + if (GameState.getTSA0BZoomedIn()) + extraID = kTSA0BWestZoomedView; + break; + case MakeRoomView(kTSA22Red, kEast): + if (_privateFlags.getFlag(kTSAPrivateKeyVaultOpenFlag)) { + if (_vm->itemInLocation(kJourneymanKey, kFullTSAID, kTSA22Red, kEast)) + extraID = kTSA22RedEastVaultViewWithKey; + else + extraID = kTSA22RedEastVaultViewNoKey; + } + break; + case MakeRoomView(kTSA23Red, kWest): + if (_privateFlags.getFlag(kTSAPrivateChipVaultOpenFlag)) { + if (_vm->itemInLocation(kPegasusBiochip, kFullTSAID, kTSA23Red, kWest)) + extraID = kTSA23RedWestVaultViewWithChips; + else + extraID = kTSA23RedWestVaultViewNoChips; + } + break; + case MakeRoomView(kTSA37, kNorth): + switch (GameState.getTSAState()) { + case kTSAPlayerGotHistoricalLog: + extraID = kTSA37ReviewRequiredToExit; + break; + case kPlayerFinishedWithTSA: + extraID = kTSA37CongratulationsToExit; + break; + default: + extraID = kTSA37AI3ToHorse; + break; + } + break; + } + + if (extraID != 0xffffffff) { + ExtraTable::Entry entry; + getExtraEntry(extraID, entry); + return entry.movieEnd - 1; + } + + return Neighborhood::getViewTime(room, direction); +} + +void FullTSA::findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &entry) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kTSA0B, kNorth): + case MakeRoomView(kTSA0B, kEast): + case MakeRoomView(kTSA0B, kWest): + if (!GameState.getTSA0BZoomedIn()) + Neighborhood::findSpotEntry(room, direction, flags, entry); + break; + default: + Neighborhood::findSpotEntry(room, direction, flags, entry); + break; + } +} + +void FullTSA::getExtraEntry(const uint32 id, ExtraTable::Entry &extraEntry) { + Neighborhood::getExtraEntry(id, extraEntry); + + if (id == kTSA0BShowGuardRobots) + extraEntry.movieStart += kFullTSAFrameDuration * 3; +} + +void FullTSA::pickedUpItem(Item *item) { + BiochipItem *biochip; + + switch (item->getObjectID()) { + case kJourneymanKey: + GameState.setScoringGotJourneymanKey(true); + break; + case kPegasusBiochip: + biochip = (BiochipItem *)_vm->getAllItems().findItemByID(kMapBiochip); + _vm->addItemToBiochips(biochip); + GameState.setScoringGotPegasusBiochip(true); + break; + } +} + +void FullTSA::playExtraMovie(const ExtraTable::Entry &extraEntry, const NotificationFlags flags, const InputBits interruptionInput) { + switch (extraEntry.extra) { + case kTSA0BNorthZoomIn: + if (_privateFlags.getFlag(kTSAPrivateLogReaderOpenFlag)) { + _privateFlags.setFlag(kTSAPrivateLogReaderOpenFlag, false); + requestExtraSequence(kTSA0BNorthHistLogClose, 0, kFilterNoInput); + requestExtraSequence(kTSA0BNorthZoomIn, kExtraCompletedFlag, kFilterNoInput); + } else { + Neighborhood::playExtraMovie(extraEntry, flags, interruptionInput); + } + break; + case kTSA0BNorthZoomOut: + if (_ripTimer.isVisible()) + _ripTimer.hide(); + + shutDownRobotMonitor(); + Neighborhood::playExtraMovie(extraEntry, flags, interruptionInput); + break; + case kTSA0BEastZoomOut: + shutDownComparisonMonitor(); + Neighborhood::playExtraMovie(extraEntry, flags, interruptionInput); + break; + default: + Neighborhood::playExtraMovie(extraEntry, flags, interruptionInput); + break; + } +} + +void FullTSA::startDoorOpenMovie(const TimeValue startTime, const TimeValue stopTime) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kTSA00, kNorth): + if (GameState.getLastNeighborhood() != kFullTSAID) { + startExtraSequence(kTSAArriveFromCaldoria, kDoorOpenCompletedFlag, kFilterNoInput); + return; + } + break; + case MakeRoomView(kTSA02, kNorth): + if (!GameState.getTSAIDedAtDoor()) { + GameState.setTSAIDedAtDoor(true); + requestExtraSequence(kTSA02NorthZoomIn, 0, kFilterNoInput); + requestExtraSequence(kTSA02NorthTenSecondDoor, 0, kFilterNoInput); + + if (GameState.getTSASeenAgent3AtDoor()) { + requestExtraSequence(kTSA02NorthZoomOut, kExtraCompletedFlag, kFilterNoInput); + } else { + GameState.setTSASeenAgent3AtDoor(true); + requestExtraSequence(kTSA02NorthZoomOut, 0, kFilterNoInput); + requestExtraSequence(kTSA02NorthDoorWithAgent3, kDoorOpenCompletedFlag, kFilterNoInput); + } + return; + } + break; + case MakeRoomView(kTSA03, kSouth): + if (GameState.getTSAState() == kRobotsAtFrontDoor) { + playDeathExtra(kTSA03SouthRobotDeath, kDeathShotByTSARobots); + return; + } + break; + case MakeRoomView(kTSA16, kNorth): + if (GameState.getTSAState() == kRobotsAtCommandCenter) { + playDeathExtra(kTSA16NorthRobotDeath, kDeathShotByTSARobots); + return; + } + break; + } + + Neighborhood::startDoorOpenMovie(startTime, stopTime); +} + +InputBits FullTSA::getInputFilter() { + InputBits result = Neighborhood::getInputFilter(); + + switch (GameState.getCurrentRoom()) { + case kTSA0B: + if (GameState.getT0BMonitorMode() != kMonitorNeutral) + // Only allow a click. + result &= JMPPPInput::getClickInputFilter(); + break; + case kTSA37: + // Can't move forward in Pegasus. Only press the exit button. + result &= ~(kFilterUpButton | kFilterUpAuto); + break; + } + + return result; +} + +void FullTSA::turnLeft() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kTSA15, kNorth): + if (GameState.getTSAState() == kTSAPlayerNeedsHistoricalLog) + setCurrentAlternate(kAltTSANormal); + break; + case MakeRoomView(kTSA0B, kNorth): + if (_ripTimer.isVisible()) + _ripTimer.hide(); + releaseSprites(); + break; + case MakeRoomView(kTSA0B, kEast): + shutDownComparisonMonitor(); + break; + } + + Neighborhood::turnLeft(); +} + +void FullTSA::turnRight() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kTSA15, kSouth): + if (GameState.getTSAState() == kTSAPlayerNeedsHistoricalLog) + setCurrentAlternate(kAltTSANormal); + break; + case MakeRoomView(kTSA0B, kNorth): + if (_ripTimer.isVisible()) + _ripTimer.hide(); + releaseSprites(); + break; + case MakeRoomView(kTSA0B, kEast): + shutDownComparisonMonitor(); + break; + } + + Neighborhood::turnRight(); +} + +void FullTSA::openDoor() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kTSA15, kSouth): + if (GameState.getTSAState() == kTSAPlayerNeedsHistoricalLog || GameState.getTSAState() == kRobotsAtFrontDoor) + setCurrentAlternate(kAltTSARedAlert); + break; + } + + Neighborhood::openDoor(); +} + +CanMoveForwardReason FullTSA::canMoveForward(ExitTable::Entry &entry) { + if (GameState.getCurrentRoomAndView() == MakeRoomView(kTSA25Red, kNorth)) + return kCantMoveBlocked; + + return Neighborhood::canMoveForward(entry); +} + +CanOpenDoorReason FullTSA::canOpenDoor(DoorTable::Entry &entry) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kTSA02, kNorth): + if (!GameState.getTSAFrontDoorUnlockedOutside()) + return kCantOpenLocked; + break; + case MakeRoomView(kTSA03, kSouth): + if (!GameState.getTSAFrontDoorUnlockedInside()) + return kCantOpenLocked; + break; + case MakeRoomView(kTSA16, kNorth): + if (GameState.getTSACommandCenterLocked()) + return kCantOpenLocked; + break; + } + + return Neighborhood::canOpenDoor(entry); +} + +void FullTSA::bumpIntoWall() { + requestSpotSound(kTSABumpIntoWallIn, kTSABumpIntoWallOut, kFilterAllInput, 0); + Neighborhood::bumpIntoWall(); +} + +void FullTSA::downButton(const Input &input) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kTSA0B, kEast): + if (GameState.getTSA0BZoomedIn()) + startExtraSequence(kTSA0BEastZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kTSA0B, kNorth): + if (GameState.getTSA0BZoomedIn()) + startExtraSequence(kTSA0BNorthZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kTSA0B, kWest): + if (GameState.getTSA0BZoomedIn() && GameState.getT0BMonitorMode() == kMonitorNeutral) + startExtraSequence(kTSA0BWestZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + default: + Neighborhood::downButton(input); + } +} + +void FullTSA::activateOneHotspot(HotspotInfoTable::Entry &entry, Hotspot *spot) { + switch (spot->getObjectID()) { + case kTSA0BEastLeftRewindSpotID: + case kTSA0BEastLeftPlaySpotID: + if (_privateFlags.getFlag(kTSAPrivatePlayingRightComparisonFlag)) + spot->setInactive(); + else + Neighborhood::activateOneHotspot(entry, spot); + break; + case kTSA0BEastRightRewindSpotID: + case kTSA0BEastRightPlaySpotID: + if (_privateFlags.getFlag(kTSAPrivatePlayingLeftComparisonFlag)) + spot->setInactive(); + else + Neighborhood::activateOneHotspot(entry, spot); + break; + default: + Neighborhood::activateOneHotspot(entry, spot); + break; + } +} + +void FullTSA::activateHotspots() { + Neighborhood::activateHotspots(); + + switch (MakeRoomView(GameState.getCurrentRoom(), GameState.getCurrentDirection())) { + case MakeRoomView(kTSA02, kNorth): + if (!GameState.getTSAFrontDoorUnlockedOutside()) + _vm->getAllHotspots().activateOneHotspot(kTSA02DoorSpotID); + break; + case MakeRoomView(kTSA0B, kEast): + if (GameState.getTSA0BZoomedIn()) + switch (GameState.getTSAState()) { + case kTSAPlayerInstalledHistoricalLog: + case kTSABossSawHistoricalLog: + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + if (getCurrentActivation() != kActivateTSA0BComparisonVideo) { + _vm->getAllHotspots().activateOneHotspot(kTSA0BEastCompareNoradSpotID); + _vm->getAllHotspots().activateOneHotspot(kTSA0BEastCompareMarsSpotID); + _vm->getAllHotspots().activateOneHotspot(kTSA0BEastCompareCaldoriaSpotID); + _vm->getAllHotspots().activateOneHotspot(kTSA0BEastCompareWSCSpotID); + } + break; + } + break; + case MakeRoomView(kTSA0B, kNorth): + if (GameState.getTSA0BZoomedIn()) + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + _vm->getAllHotspots().activateOneHotspot(kTSA0BNorthRobotsToCommandCenterSpotID); + _vm->getAllHotspots().activateOneHotspot(kTSA0BNorthRobotsToReadyRoomSpotID); + _vm->getAllHotspots().activateOneHotspot(kTSA0BNorthRobotsToFrontDoorSpotID); + break; + } + break; + } +} + +void FullTSA::clickInHotspot(const Input &input, const Hotspot *clickedSpot) { + switch (clickedSpot->getObjectID()) { + case kTSAGTOtherSpotID: + showExtraView(kTSAGTOtherChoice); + playSpotSoundSync(kTSANoOtherDestinationIn, kTSANoOtherDestinationOut); + showExtraView(kTSAGTCardSwipe); + break; + case kTSA02DoorSpotID: + GameState.setTSAFrontDoorUnlockedOutside(true); + Neighborhood::clickInHotspot(input, clickedSpot); + break; + case kTSA03EastJimenezSpotID: + startExtraLongSequence(kTSA03JimenezZoomIn, kTSA03JimenezZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA03WestCrenshawSpotID: + startExtraLongSequence(kTSA03CrenshawZoomIn, kTSA03CrenshawZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA04EastMatsumotoSpotID: + startExtraLongSequence(kTSA04MatsumotoZoomIn, kTSA04MatsumotoZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA04WestCastilleSpotID: + startExtraLongSequence(kTSA04CastilleZoomIn, kTSA04CastilleZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA05EastSinclairSpotID: + startExtraLongSequence(kTSA05SinclairZoomIn, kTSA05SinclairZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA05WestWhiteSpotID: + startExtraLongSequence(kTSA05WhiteZoomIn, kTSA05WhiteZoomOut, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA0BEastCompareNoradSpotID: + initializeComparisonMonitor(kMonitorNoradComparison, kTSA0BNoradComparisonView); + break; + case kTSA0BEastCompareMarsSpotID: + initializeComparisonMonitor(kMonitorMarsComparison, kTSA0BMarsComparisonView); + break; + case kTSA0BEastCompareCaldoriaSpotID: + initializeComparisonMonitor(kMonitorCaldoriaComparison, kTSA0BCaldoriaComparisonView); + break; + case kTSA0BEastCompareWSCSpotID: + initializeComparisonMonitor(kMonitorWSCComparison, kTSA0BWSCComparisonView); + break; + case kTSA0BEastCloseVideoSpotID: + _navMovie.stop(); + _sprite3.show(); + _vm->delayShell(1, 2); + _sprite3.hide(); + initializeComparisonMonitor(kMonitorNeutral, 0); + break; + case kTSA0BEastLeftPlaySpotID: + playLeftComparison(); + break; + case kTSA0BEastRightPlaySpotID: + playRightComparison(); + break; + + // Command center + case kTSA0BWestTheorySpotID: + initializeTBPMonitor(kMonitorTheory, kTSA0BTBPTheoryHighlight); + break; + case kTSA0BWestBackgroundSpotID: + initializeTBPMonitor(kMonitorBackground, kTSA0BTBPBackgroundHighlight); + break; + case kTSA0BWestProcedureSpotID: + initializeTBPMonitor(kMonitorProcedure, kTSA0BTBPProcedureHighlight); + break; + case kTSA0BWestCloseVideoSpotID: + _navMovie.stop(); + _sprite2.show(); + _vm->delayShell(1, 2); + _sprite2.hide(); + initializeTBPMonitor(kMonitorNeutral, 0); + break; + case kTSA0BWestPlayVideoSpotID: + playTBPMonitor(); + break; + case kTSA0BEastLeftRewindSpotID: + case kTSA0BEastRightRewindSpotID: + case kTSA0BWestRewindVideoSpotID: + if ((GameState.getT0BMonitorMode() & kPlayingAnyMask) != 0) { + bool playing = _navMovie.isRunning(); + if (playing) + _navMovie.stop(); + + if (clickedSpot->getObjectID() == kTSA0BEastRightRewindSpotID) + _sprite2.show(); + else + _sprite1.show(); + + _vm->delayShell(1, 2); + + if (clickedSpot->getObjectID() == kTSA0BEastRightRewindSpotID) + _sprite2.hide(); + else + _sprite1.hide(); + + _navMovie.setTime(GameState.getT0BMonitorStart()); + + if (playing) { + _navMovie.start(); + } else { + _privateFlags.setFlag(kTSAPrivatePlayingLeftComparisonFlag, false); + _privateFlags.setFlag(kTSAPrivatePlayingRightComparisonFlag, false); + } + } + break; + case kTSA22EastMonitorSpotID: + requestExtraSequence(kTSA22RedEastZoomInSequence, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA23WestMonitorSpotID: + requestExtraSequence(kTSA23RedWestVaultZoomInSequence, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA0BNorthRobotsToCommandCenterSpotID: + _sprite1.setCurrentFrameIndex(kRedirectionCCDoorSprite); + _sprite1.show(); + _vm->delayShell(1, 2); + _sprite1.hide(); + + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + // Nothing + break; + case kRobotsAtFrontDoor: + GameState.setTSAState(kRobotsAtCommandCenter); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromFrontDoorToCommandCenter, kExtraCompletedFlag, kFilterNoInput); + break; + case kRobotsAtReadyRoom: + GameState.setTSAState(kRobotsAtCommandCenter); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromReadyRoomToCommandCenter, kExtraCompletedFlag, kFilterNoInput); + break; + } + break; + case kTSA0BNorthRobotsToReadyRoomSpotID: + _sprite1.setCurrentFrameIndex(kRedirectionRRDoorSprite); + _sprite1.show(); + _vm->delayShell(1, 2); + _sprite1.hide(); + + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + GameState.setTSAState(kRobotsAtReadyRoom); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromCommandCenterToReadyRoom, kExtraCompletedFlag, kFilterNoInput); + break; + case kRobotsAtFrontDoor: + GameState.setTSAState(kRobotsAtReadyRoom); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromFrontDoorToReadyRoom, kExtraCompletedFlag, kFilterNoInput); + break; + case kRobotsAtReadyRoom: + // Nothing + break; + } + break; + case kTSA0BNorthRobotsToFrontDoorSpotID: + _sprite1.setCurrentFrameIndex(kRedirectionFDDoorSprite); + _sprite1.show(); + _vm->delayShell(1, 2); + _sprite1.hide(); + + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + GameState.setTSAState(kRobotsAtFrontDoor); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromCommandCenterToFrontDoor, kExtraCompletedFlag, kFilterNoInput); + break; + case kRobotsAtFrontDoor: + // Nothing + break; + case kRobotsAtReadyRoom: + GameState.setTSAState(kRobotsAtFrontDoor); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromReadyRoomToFrontDoor, kExtraCompletedFlag, kFilterNoInput); + break; + } + break; + + // Pegasus + case kTSA37NorthJumpToPrehistoricSpotID: + startExtraSequence(kTSA37PegasusDepart, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA37NorthExitSpotID: + _sprite2.setCurrentFrameIndex(1); + _vm->delayShell(1, 2); + releaseSprites(); + moveForward(); + break; + case kTSA37NorthJumpMenuSpotID: + _sprite2.setCurrentFrameIndex(1); + _vm->delayShell(1, 2); + releaseSprites(); + break; + case kTSA37NorthJumpToNoradSpotID: + GameState.setTSAState(kPlayerOnWayToNorad); + requestExtraSequence(kTSA37JumpToNorad, 0, kFilterNoInput); + + if (!GameState.getBeenToNorad()) { + requestExtraSequence(kTSA37NoradToAI7, 0, kFilterNoInput); + requestExtraSequence(kTSA37PegasusAI7, 0, kFilterNoInput); + requestExtraSequence(kTSA37AI7ToNorad, 0, kFilterNoInput); + GameState.setBeenToNorad(true); + } + + requestExtraSequence(kTSA37NoradToDepart, 0, kFilterNoInput); + requestExtraSequence(kTSA37PegasusDepart, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA37NorthJumpToMarsSpotID: + GameState.setTSAState(kPlayerOnWayToMars); + requestExtraSequence(kTSA37JumpToMars, 0, kFilterNoInput); + + if (!GameState.getBeenToMars()) { + requestExtraSequence(kTSA37MarsToAI6, 0, kFilterNoInput); + requestExtraSequence(kTSA37PegasusAI6, 0, kFilterNoInput); + requestExtraSequence(kTSA37AI6ToMars, 0, kFilterNoInput); + GameState.setBeenToMars(true); + } + + requestExtraSequence(kTSA37MarsToDepart, 0, kFilterNoInput); + requestExtraSequence(kTSA37PegasusDepart, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA37NorthJumpToWSCSpotID: + GameState.setTSAState(kPlayerOnWayToWSC); + requestExtraSequence(kTSA37JumpToWSC, 0, kFilterNoInput); + + if (!GameState.getBeenToWSC()) { + requestExtraSequence(kTSA37WSCToAI5, 0, kFilterNoInput); + requestExtraSequence(kTSA37PegasusAI5, 0, kFilterNoInput); + requestExtraSequence(kTSA37AI5ToWSC, 0, kFilterNoInput); + GameState.setBeenToWSC(true); + } + + requestExtraSequence(kTSA37WSCToDepart, 0, kFilterNoInput); + requestExtraSequence(kTSA37PegasusDepart, kExtraCompletedFlag, kFilterNoInput); + break; + default: + Neighborhood::clickInHotspot(input, clickedSpot); + break; + } +} + +void FullTSA::showMainJumpMenu() { + ExtraID jumpMenuView = kTSA37JumpMenu000; + + if (GameState.getNoradFinished()) + jumpMenuView += 4; + if (GameState.getMarsFinished()) + jumpMenuView += 2; + if (GameState.getWSCFinished()) + jumpMenuView += 1; + + showExtraView(jumpMenuView); + setCurrentActivation(kActivationMainJumpMenu); +} + +void FullTSA::playTBPMonitor() { + InputDevice.waitInput(kFilterAllButtons); + + if ((GameState.getT0BMonitorMode() & kPlayingTBPMask) == 0) { + ExtraID extra; + + switch (GameState.getT0BMonitorMode() & kRawModeMask) { + case kMonitorTheory: + GameState.setTSASeenTheory(true); + extra = kTSA0BTBPTheory; + GameState.setScoringSawTheory(true); + break; + case kMonitorBackground: + GameState.setTSASeenBackground(true); + extra = kTSA0BTBPBackground; + GameState.setScoringSawBackground(true); + break; + case kMonitorProcedure: + GameState.setTSASeenProcedure(true); + extra = kTSA0BTBPProcedure; + GameState.setScoringSawProcedure(true); + break; + default: + error("Invalid monitor mode"); + } + + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() | kPlayingTBPMask); + + ExtraTable::Entry entry; + getExtraEntry(extra, entry); + _lastExtra = extra; + + GameState.setT0BMonitorStart(entry.movieStart + kFullTSAFrameDuration * 5); + startMovieSequence(GameState.getT0BMonitorStart(), entry.movieEnd, kExtraCompletedFlag, false, kFilterAllInput); + } else if (_navMovie.isRunning()) { + _navMovie.stop(); + } else { + _navMovie.start(); + } +} + +void FullTSA::initializeTBPMonitor(const int newMode, const ExtraID highlightExtra) { + GameState.setT0BMonitorMode(newMode); + + if (newMode != kMonitorNeutral) { + showExtraView(highlightExtra); + _vm->delayShell(1, 2); + setCurrentActivation(kActivateTSA0BTBPVideo); + _sprite1.addPICTResourceFrame(kTBPRewindPICTID, false, 0, 0); + _sprite1.moveElementTo(kTBPRewindLeft, kTBPRewindTop); + _sprite1.setCurrentFrameIndex(0); + _sprite2.addPICTResourceFrame(kTBPCloseBoxPICTID, false, 0, 0); + _sprite2.moveElementTo(kTBPCloseLeft, kTBPCloseTop); + _sprite2.setCurrentFrameIndex(0); + playTBPMonitor(); + } else { + if (GameState.getTSAState() == kTSAPlayerForcedReview && GameState.getTSASeenTheory() && + GameState.getTSASeenBackground() && GameState.getTSASeenProcedure()) { + setOffRipAlarm(); + } else { + setCurrentActivation(kActivateTSA0BZoomedIn); + updateViewFrame(); + } + + releaseSprites(); + } + + _interruptionFilter = kFilterAllInput; +} + +void FullTSA::startUpComparisonMonitor() { + releaseSprites(); + + _sprite1.addPICTResourceFrame(kComparisonHiliteNoradPICTID, false, + kComparisonHiliteNoradLeft - kComparisonHiliteSpriteLeft, + kComparisonHiliteNoradTop - kComparisonHiliteSpriteTop); + _sprite1.addPICTResourceFrame(kComparisonHiliteMarsPICTID, false, + kComparisonHiliteMarsLeft - kComparisonHiliteSpriteLeft, + kComparisonHiliteMarsTop - kComparisonHiliteSpriteTop); + _sprite1.addPICTResourceFrame(kComparisonHiliteCaldoriaPICTID, false, + kComparisonHiliteCaldoriaLeft - kComparisonHiliteSpriteLeft, + kComparisonHiliteCaldoriaTop - kComparisonHiliteSpriteTop); + _sprite1.addPICTResourceFrame(kComparisonHiliteWSCPICTID, false, + kComparisonHiliteWSCLeft - kComparisonHiliteSpriteLeft, + kComparisonHiliteWSCTop - kComparisonHiliteSpriteTop); + + _sprite1.setCurrentFrameIndex(0); + _sprite1.moveElementTo(kComparisonHiliteSpriteLeft, kComparisonHiliteSpriteTop); + + _sprite2.addPICTResourceFrame(kComparisonChancesNoradPICTID, false, + kComparisonChancesNoradLeft - kComparisonChancesSpriteLeft, + kComparisonChancesNoradTop - kComparisonChancesSpriteTop); + _sprite2.addPICTResourceFrame(kComparisonChancesMarsPICTID, false, + kComparisonChancesMarsLeft - kComparisonChancesSpriteLeft, + kComparisonChancesMarsTop - kComparisonChancesSpriteTop); + _sprite2.addPICTResourceFrame(kComparisonChancesCaldoriaPICTID, false, + kComparisonChancesCaldoriaLeft - kComparisonChancesSpriteLeft, + kComparisonChancesCaldoriaTop - kComparisonChancesSpriteTop); + _sprite2.addPICTResourceFrame(kComparisonChancesWSCPICTID, false, + kComparisonChancesWSCLeft - kComparisonChancesSpriteLeft, + kComparisonChancesWSCTop - kComparisonChancesSpriteTop); + + _sprite2.setCurrentFrameIndex(0); + _sprite2.moveElementTo(kComparisonChancesSpriteLeft, kComparisonChancesSpriteTop); + updateViewFrame(); +} + +void FullTSA::shutDownComparisonMonitor() { + releaseSprites(); +} + +void FullTSA::initializeComparisonMonitor(const int newMode, const ExtraID comparisonView) { + GameState.setT0BMonitorMode(newMode); + _privateFlags.setFlag(kTSAPrivatePlayingLeftComparisonFlag, false); + _privateFlags.setFlag(kTSAPrivatePlayingRightComparisonFlag, false); + + if (newMode != kMonitorNeutral) { + shutDownComparisonMonitor(); + setCurrentActivation(kActivateTSA0BComparisonVideo); + _sprite1.addPICTResourceFrame(kComparisonLeftRewindPICTID, false, 0, 0); + _sprite1.moveElementTo(kComparisonLeftRewindLeft, kComparisonLeftRewindTop); + _sprite1.setCurrentFrameIndex(0); + _sprite2.addPICTResourceFrame(kComparisonRightRewindPICTID, false, 0, 0); + _sprite2.moveElementTo(kComparisonRightRewindLeft, kComparisonRightRewindTop); + _sprite2.setCurrentFrameIndex(0); + _sprite3.addPICTResourceFrame(kComparisonCloseBoxPICTID, false, 0, 0); + _sprite3.moveElementTo(kComparisonCloseLeft, kComparisonCloseTop); + _sprite3.setCurrentFrameIndex(0); + showExtraView(comparisonView); + } else { + if (GameState.getTSAState() == kTSAPlayerInstalledHistoricalLog && + GameState.getTSASeenNoradNormal() && + GameState.getTSASeenNoradAltered() && + GameState.getTSASeenMarsNormal() && + GameState.getTSASeenMarsAltered() && + GameState.getTSASeenCaldoriaNormal() && + GameState.getTSASeenCaldoriaAltered() && + GameState.getTSASeenWSCNormal() && + GameState.getTSASeenWSCAltered()) { + GameState.setTSAState(kTSABossSawHistoricalLog); + requestExtraSequence(kTSA0BEastZoomOut, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kTSA0BEastTurnLeft, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kTSA0BNorthZoomIn, kExtraCompletedFlag, kFilterNoInput); + } else { + setCurrentActivation(kActivateTSA0BZoomedIn); + releaseSprites(); + startUpComparisonMonitor(); + } + } + + _interruptionFilter = kFilterAllInput; +} + +void FullTSA::playLeftComparison() { + InputDevice.waitInput(kFilterAllButtons); + + if ((GameState.getT0BMonitorMode() & kPlayingLeftComparisonMask) == 0) { + ExtraID extra; + + switch (GameState.getT0BMonitorMode() & kRawModeMask) { + case kMonitorNoradComparison: + GameState.setTSASeenNoradAltered(true); + extra = kTSA0BNoradAltered; + GameState.setScoringSawNoradAltered(true); + break; + case kMonitorMarsComparison: + GameState.setTSASeenMarsAltered(true); + extra = kTSA0BMarsAltered; + GameState.setScoringSawMarsAltered(true); + break; + case kMonitorCaldoriaComparison: + GameState.setTSASeenCaldoriaAltered(true); + extra = kTSA0BCaldoriaAltered; + GameState.setScoringSawCaldoriaAltered(true); + break; + case kMonitorWSCComparison: + GameState.setTSASeenWSCAltered(true); + extra = kTSA0BWSCAltered; + GameState.setScoringSawWSCAltered(true); + break; + default: + error("Invalid monitor mode"); + } + + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() | kPlayingLeftComparisonMask); + + ExtraTable::Entry entry; + getExtraEntry(extra, entry); + _lastExtra = extra; + + // skip first five frames of movie + // (this is a dissolve that doesn't belong...) + GameState.setT0BMonitorStart(entry.movieStart + kFullTSAFrameDuration * 5); + _privateFlags.setFlag(kTSAPrivatePlayingLeftComparisonFlag); + + // Allow clicking... + startMovieSequence(GameState.getT0BMonitorStart(), entry.movieEnd, + kExtraCompletedFlag, false, JMPPPInput::getClickInputFilter()); + } else if (_navMovie.isRunning()) { + _navMovie.stop(); + } else { + _navMovie.start(); + } +} + +void FullTSA::playRightComparison() { + InputDevice.waitInput(kFilterAllButtons); + + if ((GameState.getT0BMonitorMode() & kPlayingRightComparisonMask) == 0) { + ExtraID extra; + + switch (GameState.getT0BMonitorMode() & kRawModeMask) { + case kMonitorNoradComparison: + GameState.setTSASeenNoradNormal(true); + extra = kTSA0BNoradUnaltered; + GameState.setScoringSawNoradNormal(true); + break; + case kMonitorMarsComparison: + GameState.setTSASeenMarsNormal(true); + extra = kTSA0BMarsUnaltered; + GameState.setScoringSawMarsNormal(true); + break; + case kMonitorCaldoriaComparison: + GameState.setTSASeenCaldoriaNormal(true); + extra = kTSA0BCaldoriaUnaltered; + GameState.setScoringSawCaldoriaNormal(true); + break; + case kMonitorWSCComparison: + GameState.setTSASeenWSCNormal(true); + extra = kTSA0BWSCUnaltered; + GameState.setScoringSawWSCNormal(true); + break; + default: + error("Invalid monitor mode"); + } + + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() | kPlayingRightComparisonMask); + + ExtraTable::Entry entry; + getExtraEntry(extra, entry); + _lastExtra = extra; + + // skip first five frames of movie + // (this is a dissolve that doesn't belong...) + GameState.setT0BMonitorStart(entry.movieStart + kFullTSAFrameDuration * 5); + _privateFlags.setFlag(kTSAPrivatePlayingRightComparisonFlag); + + // Allow clicking... + startMovieSequence(GameState.getT0BMonitorStart(), entry.movieEnd, + kExtraCompletedFlag, false, JMPPPInput::getClickInputFilter()); + } else if (_navMovie.isRunning()) { + _navMovie.stop(); + } else { + _navMovie.start(); + } +} + +// When this function is called, the player is zoomed up on the center monitor, and the +// TSA state is kTSABossSawHistoricalLog. +void FullTSA::startRobotGame() { + requestExtraSequence(kTSA0BNorthCantChangeHistory, 0, kFilterNoInput); + requestExtraSequence(kTSA0BAIInterruption, 0, kFilterNoInput); + requestExtraSequence(kTSA0BShowGuardRobots, 0, kFilterNoInput); + requestExtraSequence(kTSA0BRobotsToCommandCenter, kExtraCompletedFlag, kFilterNoInput); +} + +void FullTSA::startUpRobotMonitor() { + releaseSprites(); + + _sprite1.addPICTResourceFrame(kRedirectionCCRolloverPICTID, true, + kRedirectionCCRolloverLeft - kRedirectionSprite1Left, + kRedirectionCCRolloverTop - kRedirectionSprite1Top); + _sprite1.addPICTResourceFrame(kRedirectionRRRolloverPICTID, true, + kRedirectionRRRolloverLeft - kRedirectionSprite1Left, + kRedirectionRRRolloverTop - kRedirectionSprite1Top); + _sprite1.addPICTResourceFrame(kRedirectionFDRolloverPICTID, false, + kRedirectionFDRolloverLeft - kRedirectionSprite1Left, + kRedirectionFDRolloverTop - kRedirectionSprite1Top); + _sprite1.addPICTResourceFrame(kRedirectionCCDoorPICTID, true, + kRedirectionCCDoorLeft - kRedirectionSprite1Left, + kRedirectionCCDoorTop - kRedirectionSprite1Top); + _sprite1.addPICTResourceFrame(kRedirectionRRDoorPICTID, true, + kRedirectionRRDoorLeft - kRedirectionSprite1Left, + kRedirectionRRDoorTop - kRedirectionSprite1Top); + _sprite1.addPICTResourceFrame(kRedirectionFDDoorPICTID, false, + kRedirectionFDDoorLeft - kRedirectionSprite1Left, + kRedirectionFDDoorTop - kRedirectionSprite1Top); + _sprite1.addPICTResourceFrame(kRedirectionClosePICTID, false, + kRedirectionCloseLeft - kRedirectionSprite1Left, + kRedirectionCloseTop - kRedirectionSprite1Top); + _sprite1.moveElementTo(kRedirectionSprite1Left, kRedirectionSprite1Top); + + _sprite2.addPICTResourceFrame(kRedirectionSecuredPICTID, false, + kRedirectionSecuredLeft - kRedirectionSprite2Left, + kRedirectionSecuredTop - kRedirectionSprite2Top); + _sprite2.addPICTResourceFrame(kRedirectionNewTargetPICTID, false, + kRedirectionNewTargetLeft - kRedirectionSprite2Left, + kRedirectionNewTargetTop - kRedirectionSprite2Top); + _sprite2.moveElementTo(kRedirectionSprite2Left, kRedirectionSprite2Top); + + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + showExtraView(kTSA0BNorthRobotsAtCCView); + break; + case kRobotsAtFrontDoor: + showExtraView(kTSA0BNorthRobotsAtFDView); + break; + case kRobotsAtReadyRoom: + showExtraView(kTSA0BNorthRobotsAtRRView); + break; + } +} + +void FullTSA::shutDownRobotMonitor() { + releaseSprites(); +} + +// Assume this is called only when zoomed in at T0B west +void FullTSA::setOffRipAlarm() { + GameState.setTSAState(kTSAPlayerDetectedRip); + _ripTimer.initImage(); + _ripTimer.moveElementTo(kRipTimerLeft, kRipTimerTop); + _ripTimer.setSegment(0, kRipTimeLimit, kRipTimeScale); + _ripTimer.start(); + loadAmbientLoops(); + startExtraSequenceSync(kTSA0BRipAlarmScreen, kFilterNoInput); + _vm->delayShell(2, 1); // Two seconds.. + requestExtraSequence(kTSA0BWestZoomOut, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kTSA0BWestTurnRight, 0, kFilterNoInput); + requestExtraSequence(kTSA0BNorthZoomIn, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kTSA0BNorthFinallyHappened, 0, kFilterNoInput); + requestExtraSequence(kTSA0BShowRip1, kExtraCompletedFlag, kFilterNoInput); +} + +void FullTSA::checkContinuePoint(const RoomID room, const DirectionConstant direction) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kTSA04, kNorth): + case MakeRoomView(kTSA14, kEast): + case MakeRoomView(kTSA15, kWest): + case MakeRoomView(kTSA16, kNorth): + case MakeRoomView(kTSA16, kSouth): + case MakeRoomView(kTSA21Cyan, kSouth): + case MakeRoomView(kTSA21Red, kSouth): + case MakeRoomView(kTSA26, kNorth): + makeContinuePoint(); + break; + } +} + +void FullTSA::arriveAt(const RoomID room, const DirectionConstant direction) { + checkRobotLocations(room, direction); + Neighborhood::arriveAt(room, direction); + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kTSADeathRoom, kNorth): + case MakeRoomView(kTSADeathRoom, kSouth): + case MakeRoomView(kTSADeathRoom, kEast): + case MakeRoomView(kTSADeathRoom, kWest): + die(kDeathShotByTSARobots); + break; + case MakeRoomView(kTSA00, kNorth): + if (GameState.getLastNeighborhood() != kFullTSAID) { + makeContinuePoint(); + openDoor(); + } else { + setCurrentActivation(kActivateTSAReadyForCard); + loopExtraSequence(kTSATransporterArrowLoop, 0); + } + break; + case MakeRoomView(kTSA03, kNorth): + case MakeRoomView(kTSA05, kNorth): + case MakeRoomView(kTSA0A, kNorth): + case MakeRoomView(kTSA06, kNorth): + case MakeRoomView(kTSA07, kNorth): + if (_utilityFuse.isFuseLit()) + _utilityFuse.stopFuse(); + GameState.setScoringEnterTSA(true); + break; + case MakeRoomView(kTSA04, kNorth): + if (_utilityFuse.isFuseLit()) + _utilityFuse.stopFuse(); + if (!GameState.getTSASeenRobotGreeting()) + startExtraSequence(kTSA04NorthRobotGreeting, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kTSA03, kSouth): + GameState.setTSAFrontDoorUnlockedInside(GameState.getTSAState() == kRobotsAtFrontDoor || GameState.allTimeZonesFinished()); + break; + case MakeRoomView(kTSA0A, kEast): + case MakeRoomView(kTSA0A, kWest): + if (GameState.getTSAState() == kTSAPlayerNotArrived) + setCurrentActivation(kActivateTSARobotsAwake); + break; + case MakeRoomView(kTSA0B, kNorth): + if (GameState.getTSA0BZoomedIn()) { + setCurrentActivation(kActivateTSA0BZoomedIn); + + switch (GameState.getTSAState()) { + case kTSAPlayerNeedsHistoricalLog: + _ripTimer.show(); + break; + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + startUpRobotMonitor(); + break; + } + } else { + setCurrentActivation(kActivateTSA0BZoomedOut); + + switch (GameState.getTSAState()) { + case kTSAPlayerNotArrived: + requestExtraSequence(kTSA0BNorthZoomIn, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kTSA0BNorthYoureBusted, 0, kFilterNoInput); + requestExtraSequence(kTSA0BNorthZoomOut, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kTSA0BNorthTurnLeft, 0, kFilterNoInput); + requestExtraSequence(kTSA0BWestZoomIn, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSAPlayerGotHistoricalLog: + startExtraSequence(kTSA0BNorthHistLogOpen, kExtraCompletedFlag, kFilterNoInput); + break; + } + } + break; + case MakeRoomView(kTSA0B, kSouth): + GameState.setTSA0BZoomedIn(false); + setCurrentActivation(kActivateTSA0BZoomedOut); + break; + case MakeRoomView(kTSA0B, kWest): + if (GameState.getTSA0BZoomedIn()) { + setCurrentActivation(kActivateTSA0BZoomedIn); + initializeTBPMonitor(kMonitorNeutral, 0); + } else { + setCurrentActivation(kActivateTSA0BZoomedOut); + } + break; + case MakeRoomView(kTSA0B, kEast): + if (GameState.getTSA0BZoomedIn()) { + setCurrentActivation(kActivateTSA0BZoomedIn); + + switch (GameState.getTSAState()) { + case kTSAPlayerInstalledHistoricalLog: + case kTSABossSawHistoricalLog: + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + initializeComparisonMonitor(kMonitorNeutral, 0); + break; + } + } else { + setCurrentActivation(kActivateTSA0BZoomedOut); + } + break; + case MakeRoomView(kTSA21Red, kSouth): + if (GameState.getTSAState() == kRobotsAtFrontDoor) + GameState.setScoringWentToReadyRoom2(true); + break; + case MakeRoomView(kTSA22Red, kEast): + if (!_vm->playerHasItemID(kJourneymanKey)) + setCurrentActivation(kActivationDoesntHaveKey); + break; + case MakeRoomView(kTSA23Red, kWest): + if (!_vm->playerHasItemID(kPegasusBiochip)) + setCurrentActivation(kActivationDoesntHaveChips); + break; + case MakeRoomView(kTSA25Red, kNorth): + arriveAtTSA25Red(); + break; + case MakeRoomView(kTSA34, kSouth): + if (GameState.getLastRoom() == kTSA37) + closeDoorOffScreen(kTSA37, kNorth); + break; + case MakeRoomView(kTSA37, kNorth): + arriveAtTSA37(); + break; + } +} + +void FullTSA::checkRobotLocations(const RoomID room, const DirectionConstant dir) { + switch (room) { + case kTSA03: + case kTSA04: + case kTSA05: + case kTSA06: + case kTSA0A: + case kTSA07: + case kTSA08: + case kTSA09: + case kTSA10: + case kTSA11: + case kTSA12: + case kTSA13: + case kTSA14: + case kTSA15: + switch (GameState.getTSAState()) { + case kRobotsAtFrontDoor: + setCurrentAlternate(kAltTSARobotsAtFrontDoor); + break; + case kRobotsAtReadyRoom: + setCurrentAlternate(kAltTSARobotsAtReadyRoom); + break; + } + break; + case kTSA16: + if (dir == kNorth) { + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + if (!_privateFlags.getFlag(kTSAPrivateSeenRobotWarningFlag)) { + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/TSA/XT11WB", false, kWarningInterruption); + _privateFlags.setFlag(kTSAPrivateSeenRobotWarningFlag, true); + } + break; + case kRobotsAtFrontDoor: + setCurrentAlternate(kAltTSARobotsAtFrontDoor); + break; + case kRobotsAtReadyRoom: + setCurrentAlternate(kAltTSARobotsAtReadyRoom); + break; + } + } + break; + } +} + +void FullTSA::arriveAtTSA25Red() { + if (!_vm->playerHasItemID(kJourneymanKey)) + startExtraSequence(kTSA25NorthDeniedNoKey, kExtraCompletedFlag, kFilterNoInput); + else if (!_vm->playerHasItemID(kPegasusBiochip)) + startExtraSequence(kTSA25NorthDeniedNoChip, kExtraCompletedFlag, kFilterNoInput); + else if (GameState.getTSABiosuitOn()) + startExtraSequence(kTSA25NorthAlreadyHaveSuit, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kTSA25NorthPutOnSuit, kExtraCompletedFlag, kFilterNoInput); +} + +void FullTSA::arriveAtTSA37() { + _ripTimer.stop(); + _ripTimer.releaseImage(); + + switch (GameState.getTSAState()) { + case kTSAPlayerNeedsHistoricalLog: + startExtraLongSequence(kTSA37HorseToAI1, kTSA37AI2ToPrehistoric, kExtraCompletedFlag, kFilterNoInput); + break; + case kPlayerOnWayToPrehistoric: + setCurrentActivation(kActivationJumpToPrehistoric); + showExtraView(kTSA37AI2ToPrehistoric); + break; + case kTSAPlayerGotHistoricalLog: + initializePegasusButtons(false); + break; + case kPlayerWentToPrehistoric: + case kPlayerOnWayToNorad: + case kPlayerOnWayToMars: + case kPlayerOnWayToWSC: + startExtraSequence(kTSA37TimeJumpToPegasus, kExtraCompletedFlag, kFilterNoInput); + break; + case kRobotsAtFrontDoor: + startExtraLongSequence(kTSA37HorseToColonel2, kTSA37AI4ToMainMenu, kExtraCompletedFlag, kFilterNoInput); + break; + case kPlayerLockedInPegasus: + showMainJumpMenu(); + break; + case kPlayerFinishedWithTSA: + initializePegasusButtons(true); + break; + } +} + +void FullTSA::turnTo(const DirectionConstant newDirection) { + Neighborhood::turnTo(newDirection); + + switch (MakeRoomView(GameState.getCurrentRoom(), newDirection)) { + case MakeRoomView(kTSA03, kSouth): + if (GameState.getTSAState() == kRobotsAtFrontDoor || GameState.allTimeZonesFinished()) + GameState.setTSAFrontDoorUnlockedInside(true); + else + GameState.setTSAFrontDoorUnlockedInside(false); + break; + case MakeRoomView(kTSA0A, kEast): + case MakeRoomView(kTSA0A, kWest): + setCurrentActivation(kActivateTSARobotsAwake); + break; + case MakeRoomView(kTSA0B, kEast): + if (GameState.getTSA0BZoomedIn()) + setCurrentActivation(kActivateTSA0BZoomedIn); + else + setCurrentActivation(kActivateTSA0BZoomedOut); + + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() & ~kPlayingAnyMask); + + if (_privateFlags.getFlag(kTSAPrivateLogReaderOpenFlag)) + _privateFlags.setFlag(kTSAPrivateLogReaderOpenFlag, false); + + switch (GameState.getTSAState()) { + case kTSAPlayerInstalledHistoricalLog: + case kTSABossSawHistoricalLog: + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + if (GameState.getTSA0BZoomedIn()) + startUpComparisonMonitor(); + break; + } + break; + case MakeRoomView(kTSA0B, kNorth): + if (GameState.getTSA0BZoomedIn()) + setCurrentActivation(kActivateTSA0BZoomedIn); + else + setCurrentActivation(kActivateTSA0BZoomedOut); + + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() & ~kPlayingAnyMask); + + switch (GameState.getTSAState()) { + case kTSAPlayerNeedsHistoricalLog: + if (GameState.getTSA0BZoomedIn()) + _ripTimer.show(); + break; + case kTSAPlayerGotHistoricalLog: + if (!GameState.getTSA0BZoomedIn()) + startExtraSequence(kTSA0BNorthHistLogOpen, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSAPlayerInstalledHistoricalLog: + if (GameState.getTSA0BZoomedIn()) { + if ((GameState.getTSASeenNoradNormal() || GameState.getTSASeenNoradAltered()) && + (GameState.getTSASeenMarsNormal() || GameState.getTSASeenMarsAltered()) && + (GameState.getTSASeenCaldoriaNormal() || GameState.getTSASeenCaldoriaAltered()) && + (GameState.getTSASeenWSCNormal() || GameState.getTSASeenWSCAltered())) { + GameState.setTSAState(kTSABossSawHistoricalLog); + startRobotGame(); + } + } + break; + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + if (GameState.getTSA0BZoomedIn()) + startExtraSequence(kTSA0BShowGuardRobots, kExtraCompletedFlag, kFilterNoInput); + break; + } + break; + case MakeRoomView(kTSA0B, kWest): + if (GameState.getTSA0BZoomedIn()) + setCurrentActivation(kActivateTSA0BZoomedIn); + else + setCurrentActivation(kActivateTSA0BZoomedOut); + + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() & ~kPlayingAnyMask); + + if (_privateFlags.getFlag(kTSAPrivateLogReaderOpenFlag)) + _privateFlags.setFlag(kTSAPrivateLogReaderOpenFlag, false); + + if (GameState.getTSA0BZoomedIn()) + initializeTBPMonitor(kMonitorNeutral, 0); + break; + case MakeRoomView(kTSA0B, kSouth): + GameState.setTSA0BZoomedIn(false); + setCurrentActivation(kActivateTSA0BZoomedOut); + break; + case MakeRoomView(kTSA16, kNorth): + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + if (!_privateFlags.getFlag(kTSAPrivateSeenRobotWarningFlag)) { + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/TSA/XT11WB", false, kWarningInterruption); + _privateFlags.setFlag(kTSAPrivateSeenRobotWarningFlag, true); + } + break; + case kRobotsAtFrontDoor: + setCurrentAlternate(kAltTSARobotsAtFrontDoor); + break; + case kRobotsAtReadyRoom: + setCurrentAlternate(kAltTSARobotsAtReadyRoom); + break; + } + break; + case MakeRoomView(kTSA22Red, kEast): + if (!_vm->playerHasItemID(kJourneymanKey)) + setCurrentActivation(kActivationDoesntHaveKey); + break; + case MakeRoomView(kTSA22Red, kNorth): + case MakeRoomView(kTSA22Red, kSouth): + if (_privateFlags.getFlag(kTSAPrivateKeyVaultOpenFlag)) { + playSpotSoundSync(kTSAVaultCloseIn, kTSAVaultCloseOut); + _privateFlags.setFlag(kTSAPrivateKeyVaultOpenFlag, false); + } + + setCurrentActivation(kActivateHotSpotAlways); + break; + case MakeRoomView(kTSA23Red, kWest): + if (!_vm->playerHasItemID(kPegasusBiochip)) + setCurrentActivation(kActivationDoesntHaveChips); + break; + case MakeRoomView(kTSA23Red, kNorth): + case MakeRoomView(kTSA23Red, kSouth): + if (_privateFlags.getFlag(kTSAPrivateChipVaultOpenFlag)) { + playSpotSoundSync(kTSAVaultCloseIn, kTSAVaultCloseOut); + _privateFlags.setFlag(kTSAPrivateChipVaultOpenFlag, false); + } + + setCurrentActivation(kActivateHotSpotAlways); + break; + } + + // Make sure the TBP monitor is forced neutral. + GameState.setT0BMonitorMode(kMonitorNeutral); +} + +void FullTSA::closeDoorOffScreen(const RoomID room, const DirectionConstant) { + switch (room) { + case kTSA00: + case kTSA01: + if (GameState.getCurrentRoom() == kTSA01 || GameState.getCurrentRoom() == kTSA02) + playSpotSoundSync(kTSAGTDoorCloseIn, kTSAGTDoorCloseOut); + break; + case kTSA02: + case kTSA03: + playSpotSoundSync(kTSAEntryDoorCloseIn, kTSAEntryDoorCloseOut); + break; + case kTSA14: + case kTSA15: + case kTSA16: + case kTSA21Cyan: + case kTSA21Red: + playSpotSoundSync(kTSAInsideDoorCloseIn, kTSAInsideDoorCloseOut); + break; + case kTSA34: + case kTSA37: + playSpotSoundSync(kTSAPegasusDoorCloseIn, kTSAPegasusDoorCloseOut); + break; + } +} + +void FullTSA::receiveNotification(Notification *notification, const NotificationFlags flags) { + ExtraID lastExtra = _lastExtra; + + if ((flags & kExtraCompletedFlag) != 0) { + switch (lastExtra) { + case kTSA0BEastTurnLeft: + // Need to check this here because turnTo will call _navMovie.stop, + // so it has to happen before Neighborhood::receiveNotification, + // which may end up starting another sequence... + turnTo(kNorth); + break; + } + } + + Neighborhood::receiveNotification(notification, flags); + + InventoryItem *item; + + if ((flags & kExtraCompletedFlag) != 0) { + // Only allow input if we're not in the middle of series of queue requests. + if (actionQueueEmpty()) + _interruptionFilter = kFilterAllInput; + + switch (lastExtra) { + case kTSAGTCardSwipe: + item = (InventoryItem *)_vm->getAllItems().findItemByID(kKeyCard); + _vm->addItemToInventory(item); + setCurrentActivation(kActivateTSAReadyToTransport); + break; + case kTSAGTGoToCaldoria: + _vm->jumpToNewEnvironment(kCaldoriaID, kCaldoria44, kEast); + + if (GameState.allTimeZonesFinished()) + GameState.setScoringWentAfterSinclair(true); + break; + case kTSAGTGoToTokyo: + case kTSAGTGoToBeach: + if (GameState.allTimeZonesFinished()) + die(kDeathSinclairShotDelegate); + else + die(kDeathUncreatedInTSA); + break; + case kTSA02NorthZoomOut: + openDoor(); + break; + + // Hall of suspects. + case kTSA04NorthRobotGreeting: + GameState.setTSASeenRobotGreeting(true); + restoreStriding(kTSA03, kNorth, kNoAlternateID); + break; + case kTSA03JimenezZoomIn: + GameState.setScoringSawBust1(true); + break; + case kTSA03CrenshawZoomIn: + GameState.setScoringSawBust2(true); + break; + case kTSA04MatsumotoZoomIn: + GameState.setScoringSawBust3(true); + break; + case kTSA04CastilleZoomIn: + GameState.setScoringSawBust4(true); + break; + case kTSA05SinclairZoomIn: + GameState.setScoringSawBust5(true); + break; + case kTSA05WhiteZoomIn: + GameState.setScoringSawBust6(true); + break; + + // Command center + // Historical comparison... + case kTSA0BEastZoomIn: + GameState.setTSA0BZoomedIn(true); + setCurrentActivation(kActivateTSA0BZoomedIn); + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() & ~kPlayingAnyMask); + + switch (GameState.getTSAState()) { + case kTSAPlayerInstalledHistoricalLog: + case kTSABossSawHistoricalLog: + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + startUpComparisonMonitor(); + break; + } + break; + case kTSA0BEastZoomOut: + GameState.setTSA0BZoomedIn(false); + setCurrentActivation(kActivateTSA0BZoomedOut); + + switch (GameState.getTSAState()) { + case kTSABossSawHistoricalLog: + // Prevent current view from activating. + break; + default: + activateCurrentView(GameState.getCurrentRoom(), GameState.getCurrentDirection(), + kSpotOnTurnMask); + break; + } + break; + case kTSA0BComparisonStartup: + if ((flags & kActionRequestCompletedFlag) != 0) { + _privateFlags.setFlag(kTSAPrivateLogReaderOpenFlag, false); + GameState.setTSAState(kTSAPlayerInstalledHistoricalLog); + turnTo(kEast); + } + + startUpComparisonMonitor(); + break; + case kTSA0BNoradAltered: + case kTSA0BMarsAltered: + case kTSA0BCaldoriaAltered: + case kTSA0BWSCAltered: + case kTSA0BNoradUnaltered: + case kTSA0BMarsUnaltered: + case kTSA0BCaldoriaUnaltered: + case kTSA0BWSCUnaltered: + initializeComparisonMonitor(kMonitorNeutral, 0); + break; + + // Center monitor. + case kTSA0BNorthZoomIn: + GameState.setTSA0BZoomedIn(true); + setCurrentActivation(kActivateTSA0BZoomedIn); + GameState.setT0BMonitorMode(GameState.getT0BMonitorMode() & ~kPlayingAnyMask); + + switch (GameState.getTSAState()) { + case kTSAPlayerNeedsHistoricalLog: + startExtraSequence(kTSA0BShowRip1, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSABossSawHistoricalLog: + case kTSAPlayerInstalledHistoricalLog: + if ((GameState.getTSASeenNoradNormal() || GameState.getTSASeenNoradAltered()) && + (GameState.getTSASeenMarsNormal() || GameState.getTSASeenMarsAltered()) && + (GameState.getTSASeenCaldoriaNormal() || GameState.getTSASeenCaldoriaAltered()) && + (GameState.getTSASeenWSCNormal() || GameState.getTSASeenWSCAltered())) { + GameState.setTSAState(kTSABossSawHistoricalLog); + startRobotGame(); + } + break; + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + startExtraSequence(kTSA0BShowGuardRobots, kExtraCompletedFlag, kFilterNoInput); + break; + } + break; + case kTSA0BNorthZoomOut: + GameState.setTSA0BZoomedIn(false); + setCurrentActivation(kActivateTSA0BZoomedOut); + break; + case kTSA0BShowRip1: + GameState.setTSAState(kTSAPlayerNeedsHistoricalLog); + GameState.setTSACommandCenterLocked(false); + + if ((flags & kActionRequestCompletedFlag) != 0) + turnTo(kNorth); + + _ripTimer.show(); + break; + case kTSA0BNorthHistLogOpen: + setCurrentActivation(kActivationLogReaderOpen); + _privateFlags.setFlag(kTSAPrivateLogReaderOpenFlag, true); + break; + case kTSA0BRobotsToCommandCenter: + GameState.setTSAState(kRobotsAtCommandCenter); + // Fall through + case kTSA0BShowGuardRobots: + startUpRobotMonitor(); + // Fall through + case kTSA0BRobotsFromCommandCenterToReadyRoom: + case kTSA0BRobotsFromReadyRoomToCommandCenter: + case kTSA0BRobotsFromCommandCenterToFrontDoor: + case kTSA0BRobotsFromFrontDoorToCommandCenter: + case kTSA0BRobotsFromFrontDoorToReadyRoom: + case kTSA0BRobotsFromReadyRoomToFrontDoor: + _sprite2.setCurrentFrameIndex(kRedirectionSecuredSprite); + _sprite2.show(); + break; + + // TBP monitor. + case kTSA0BWestZoomIn: + GameState.setTSA0BZoomedIn(true); + setCurrentActivation(kActivateTSA0BZoomedIn); + + if (GameState.getTSAState() == kTSAPlayerNotArrived) { + turnTo(kWest); + GameState.setTSACommandCenterLocked(true); + GameState.setTSAState(kTSAPlayerForcedReview); + } + + initializeTBPMonitor(kMonitorNeutral, 0); + break; + case kTSA0BWestZoomOut: + GameState.setTSA0BZoomedIn(false); + setCurrentActivation(kActivateTSA0BZoomedOut); + GameState.setT0BMonitorMode(kMonitorNeutral); + + switch (GameState.getTSAState()) { + case kTSAPlayerDetectedRip: + // Keep the current view from activating. + break; + default: + activateCurrentView(GameState.getCurrentRoom(), GameState.getCurrentDirection(), + kSpotOnTurnMask); + break; + } + break; + case kTSA0BTBPTheory: + case kTSA0BTBPBackground: + case kTSA0BTBPProcedure: + initializeTBPMonitor(kMonitorNeutral, 0); + break; + + // Ready room + case kTSA22RedEastZoomInSequence: + _privateFlags.setFlag(kTSAPrivateKeyVaultOpenFlag, true); + setCurrentActivation(kActivationKeyVaultOpen); + break; + case kTSA23RedWestVaultZoomInSequence: + _privateFlags.setFlag(kTSAPrivateChipVaultOpenFlag, true); + setCurrentActivation(kActivationChipVaultOpen); + break; + case kTSA25NorthPutOnSuit: + GameState.setTSABiosuitOn(true); + GameState.setScoringGotBiosuit(true); + // Fall through... + case kTSA25NorthAlreadyHaveSuit: + requestExtraSequence(kTSA25NorthDescending1, 0, kFilterNoInput); + requestExtraSequence(kTSA25NorthDescending2, kExtraCompletedFlag, kFilterNoInput); + break; + case kTSA25NorthDescending2: + arriveAt(kTSA26, kNorth); + break; + + // Pegasus. + case kTSA37HorseToAI1: + case kTSA37AI2ToPrehistoric: + setCurrentActivation(kActivationJumpToPrehistoric); + GameState.setTSAState(kPlayerOnWayToPrehistoric); + break; + case kTSA37PegasusDepart: + _vm->setLastEnergyValue(kFullEnergy); + + switch (GameState.getTSAState()) { + case kPlayerOnWayToPrehistoric: + _vm->jumpToNewEnvironment(kPrehistoricID, kPrehistoric02, kSouth); + GameState.setPrehistoricSeenTimeStream(false); + GameState.setPrehistoricSeenFlyer1(false); + GameState.setPrehistoricSeenFlyer2(false); + GameState.setPrehistoricSeenBridgeZoom(false); + GameState.setPrehistoricBreakerThrown(false); + GameState.setScoringGoToPrehistoric(true); + GameState.setTSAState(kPlayerWentToPrehistoric); + break; + case kPlayerOnWayToNorad: + _vm->jumpToNewEnvironment(kNoradAlphaID, kNorad01, kSouth); + GameState.setNoradSeenTimeStream(false); + GameState.setNoradGassed(true); + GameState.setNoradFillingStationOn(false); + GameState.setNoradN22MessagePlayed(false); + GameState.setNoradPlayedGlobeGame(false); + GameState.setNoradBeatRobotWithClaw(false); + GameState.setNoradBeatRobotWithDoor(false); + GameState.setNoradRetScanGood(false); + GameState.setNoradWaitingForLaser(false); + GameState.setNoradSubRoomPressure(9); + GameState.setNoradSubPrepState(kSubNotPrepped); + break; + case kPlayerOnWayToMars: + _vm->jumpToNewEnvironment(kMarsID, kMars0A, kNorth); + GameState.setMarsSeenTimeStream(false); + GameState.setMarsHeardUpperPodMessage(false); + GameState.setMarsRobotThrownPlayer(false); + GameState.setMarsHeardCheckInMessage(false); + GameState.setMarsPodAtUpperPlatform(false); + GameState.setMarsSeenThermalScan(false); + GameState.setMarsArrivedBelow(false); + GameState.setMarsSeenRobotAtReactor(false); + GameState.setMarsAvoidedReactorRobot(false); + GameState.setMarsLockFrozen(false); + GameState.setMarsLockBroken(false); + GameState.setMarsSecurityDown(false); + GameState.setMarsAirlockOpen(false); + GameState.setMarsReadyForShuttleTransport(false); + GameState.setMarsFinishedCanyonChase(false); + GameState.setMarsThreadedMaze(false); + break; + case kPlayerOnWayToWSC: + _vm->jumpToNewEnvironment(kWSCID, kWSC01, kWest); + GameState.setWSCSeenTimeStream(false); + GameState.setWSCPoisoned(false); + GameState.setWSCAnsweredAboutDart(false); + GameState.setWSCRemovedDart(false); + GameState.setWSCAnalyzerOn(false); + GameState.setWSCDartInAnalyzer(false); + GameState.setWSCAnalyzedDart(false); + GameState.setWSCPickedUpAntidote(false); + GameState.setWSCSawMorph(false); + GameState.setWSCDesignedAntidote(false); + GameState.setWSCOfficeMessagesOpen(false); + GameState.setWSCSeenNerd(false); + GameState.setWSCHeardPage1(false); + GameState.setWSCHeardPage2(false); + GameState.setWSCHeardCheckIn(false); + GameState.setWSCDidPlasmaDodge(false); + GameState.setWSCSeenSinclairLecture(false); + GameState.setWSCBeenAtWSC93(false); + GameState.setWSCCatwalkDark(false); + GameState.setWSCRobotDead(false); + GameState.setWSCRobotGone(false); + break; + }; + break; + case kTSA37TimeJumpToPegasus: + if (g_energyMonitor) + g_energyMonitor->stopEnergyDraining(); + + switch (GameState.getTSAState()) { + case kPlayerWentToPrehistoric: + arriveFromPrehistoric(); + break; + case kPlayerOnWayToNorad: + arriveFromNorad(); + break; + case kPlayerOnWayToMars: + arriveFromMars(); + break; + case kPlayerOnWayToWSC: + arriveFromWSC(); + break; + default: + break; + } + break; + case kTSA37DownloadToOpMemReview: + switch (GameState.getTSAState()) { + case kPlayerOnWayToNorad: + g_opticalChip->playOpMemMovie(kPoseidonSpotID); + break; + case kPlayerOnWayToMars: + g_opticalChip->playOpMemMovie(kAriesSpotID); + break; + case kPlayerOnWayToWSC: + g_opticalChip->playOpMemMovie(kMercurySpotID); + break; + } + + if (GameState.allTimeZonesFinished()) { + requestExtraSequence(kTSA37OpMemReviewToAllClear, 0, kFilterNoInput); + requestExtraSequence(kTSA37AllClearToCongratulations, 0, kFilterNoInput); + requestExtraSequence(kTSA37Congratulations, 0, kFilterNoInput); + requestExtraSequence(kTSA37CongratulationsToExit, kExtraCompletedFlag, kFilterNoInput); + } else { + requestExtraSequence(kTSA37OpMemReviewToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kTSA37RecallToDownload: + case kTSA37ReviewRequiredToExit: + GameState.setTSAState(kTSAPlayerGotHistoricalLog); + initializePegasusButtons(kPegasusUnresolved); + break; + case kTSA37ZoomToMainMenu: + case kTSA37HorseToColonel2: + case kTSA37DownloadToMainMenu: + case kTSA37OpMemReviewToMainMenu: + case kTSA37AI4ToMainMenu: + GameState.setTSAState(kPlayerLockedInPegasus); + showMainJumpMenu(); + makeContinuePoint(); + break; + case kTSA37JumpToNoradMenu: + setCurrentActivation(kActivationJumpToNorad); + break; + case kTSA37JumpToMarsMenu: + setCurrentActivation(kActivationJumpToMars); + break; + case kTSA37JumpToWSCMenu: + setCurrentActivation(kActivationJumpToWSC); + break; + case kTSA37CancelNorad: + case kTSA37CancelMars: + case kTSA37CancelWSC: + showMainJumpMenu(); + break; + case kTSA37CongratulationsToExit: + GameState.setTSAState(kPlayerFinishedWithTSA); + initializePegasusButtons(true); + break; + } + } + + g_AIArea->checkMiddleArea(); +} + +void FullTSA::arriveFromPrehistoric() { + if (_vm->playerHasItemID(kHistoricalLog)) { + GameState.setScoringFinishedPrehistoric(); + requestExtraSequence(kTSA37RecallToDownload, 0, kFilterNoInput); + requestExtraSequence(kTSA37DownloadToColonel1, 0, kFilterNoInput); + requestExtraSequence(kTSA37Colonel1, 0, kFilterNoInput); + requestExtraSequence(kTSA37Colonel1ToReviewRequired, 0, kFilterNoInput); + requestExtraSequence(kTSA37ReviewRequiredToExit, kExtraCompletedFlag, kFilterNoInput); + } else { + // Make sure rip timer is going... + startExtraSequence(kTSA37DownloadToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } +} + +void FullTSA::arriveFromNorad() { + requestExtraSequence(kTSA37RecallToDownload, 0, kFilterNoInput); + + if (GameState.getNoradFinished() && !GameState.getScoringFinishedNorad()) { + GameState.setScoringFinishedNorad(); + requestExtraSequence(kTSA37DownloadToOpMemReview, kExtraCompletedFlag, kFilterNoInput); + } else { + requestExtraSequence(kTSA37DownloadToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } +} + +void FullTSA::arriveFromMars() { + requestExtraSequence(kTSA37RecallToDownload, 0, kFilterNoInput); + + if (GameState.getMarsFinished() && !GameState.getScoringFinishedMars()) { + GameState.setScoringFinishedMars(); + requestExtraSequence(kTSA37DownloadToOpMemReview, kExtraCompletedFlag, kFilterNoInput); + } else { + requestExtraSequence(kTSA37DownloadToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } +} + +void FullTSA::arriveFromWSC() { + requestExtraSequence(kTSA37RecallToDownload, 0, kFilterNoInput); + + if (GameState.getWSCFinished() && !GameState.getScoringFinishedWSC()) { + GameState.setScoringFinishedWSC(); + requestExtraSequence(kTSA37DownloadToOpMemReview, kExtraCompletedFlag, kFilterNoInput); + } else { + requestExtraSequence(kTSA37DownloadToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } +} + +void FullTSA::initializePegasusButtons(bool resolved) { + if (resolved) { + _sprite1.addPICTResourceFrame(kResolvedPICTID, false, 0, 0); + _sprite1.moveElementTo(kResolvedLeft, kResolvedTop); + } else { + _sprite1.addPICTResourceFrame(kUnresolvedPICTID, false, 0, 0); + _sprite1.moveElementTo(kUnresolvedLeft, kUnresolvedTop); + } + + _sprite1.setCurrentFrameIndex(0); + _sprite1.show(); + + _sprite2.addPICTResourceFrame(kExitPICTID, false, kExitLeft - kExitHilitedLeft, kExitTop - kExitHilitedTop); + _sprite2.addPICTResourceFrame(kExitHilitedPICTID, false, 0, 0); + _sprite2.moveElementTo(kExitHilitedLeft, kExitHilitedTop); + setCurrentActivation(kActivationReadyToExit); + _sprite2.setCurrentFrameIndex(0); + _sprite2.show(); +} + +Hotspot *FullTSA::getItemScreenSpot(Item *item, DisplayElement *element) { + switch (item->getObjectID()) { + case kJourneymanKey: + return _vm->getAllHotspots().findHotspotByID(kTSA22EastKeySpotID); + break; + case kPegasusBiochip: + return _vm->getAllHotspots().findHotspotByID(kTSA23WestChipsSpotID); + break; + } + + return Neighborhood::getItemScreenSpot(item, element); +} + +void FullTSA::dropItemIntoRoom(Item *item, Hotspot *dropSpot) { + Neighborhood::dropItemIntoRoom(item, dropSpot); + + switch (item->getObjectID()) { + case kKeyCard: + if (dropSpot->getObjectID() == kTSAGTCardDropSpotID) + startExtraSequence(kTSAGTCardSwipe, kExtraCompletedFlag, kFilterNoInput); + break; + case kHistoricalLog: + if (dropSpot->getObjectID() == kTSA0BNorthHistLogSpotID) { + requestExtraSequence(kTSA0BNorthHistLogCloseWithLog, 0, kFilterNoInput); + requestExtraSequence(kTSA0BNorthTurnRight, 0, kFilterNoInput); + requestExtraSequence(kTSA0BEastZoomIn, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kTSA0BComparisonStartup, kExtraCompletedFlag, kFilterNoInput); + GameState.setScoringPutLogInReader(true); + } + break; + } +} + +uint FullTSA::getHistoricalLogIndex() { + uint index; + + if (GameState.getTSASeenNoradNormal() && GameState.getTSASeenNoradAltered()) + index = 8; + else + index = 0; + + if (GameState.getTSASeenMarsNormal() && GameState.getTSASeenMarsAltered()) + index += 4; + + if (GameState.getTSASeenCaldoriaNormal() && GameState.getTSASeenCaldoriaAltered()) + index += 2; + + if (GameState.getTSASeenWSCNormal() && GameState.getTSASeenWSCAltered()) + index += 1; + + return index; +} + +void FullTSA::handleInput(const Input &input, const Hotspot *cursorSpot) { + switch (MakeRoomView(GameState.getCurrentRoom(), GameState.getCurrentDirection())) { + case MakeRoomView(kTSA0B, kEast): + if (GameState.getTSA0BZoomedIn() && !_navMovie.isRunning() && GameState.getT0BMonitorMode() == kMonitorNeutral) { + switch (GameState.getTSAState()) { + case kTSAPlayerInstalledHistoricalLog: + case kTSABossSawHistoricalLog: + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + if (cursorSpot) { + switch (cursorSpot->getObjectID()) { + case kTSA0BEastCompareNoradSpotID: + _sprite1.setCurrentFrameIndex(0); + _sprite2.setCurrentFrameIndex(0); + _sprite1.show(); + _sprite2.show(); + break; + case kTSA0BEastCompareMarsSpotID: + _sprite1.setCurrentFrameIndex(1); + _sprite2.setCurrentFrameIndex(1); + _sprite1.show(); + _sprite2.show(); + break; + case kTSA0BEastCompareCaldoriaSpotID: + _sprite1.setCurrentFrameIndex(2); + _sprite2.setCurrentFrameIndex(2); + _sprite1.show(); + _sprite2.show(); + break; + case kTSA0BEastCompareWSCSpotID: + _sprite1.setCurrentFrameIndex(3); + _sprite2.setCurrentFrameIndex(3); + _sprite1.show(); + _sprite2.show(); + break; + default: + _sprite1.hide(); + _sprite2.hide(); + break; + } + } else { + _sprite1.hide(); + _sprite2.hide(); + } + break; + } + } + break; + case MakeRoomView(kTSA0B, kNorth): + if (GameState.getTSA0BZoomedIn() && !_navMovie.isRunning()) { + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + case kRobotsAtFrontDoor: + case kRobotsAtReadyRoom: + if (cursorSpot) { + switch (cursorSpot->getObjectID()) { + case kTSA0BNorthRobotsToCommandCenterSpotID: + _sprite1.setCurrentFrameIndex(kRedirectionCCRolloverSprite); + _sprite1.show(); + break; + case kTSA0BNorthRobotsToReadyRoomSpotID: + _sprite1.setCurrentFrameIndex(kRedirectionRRRolloverSprite); + _sprite1.show(); + break; + case kTSA0BNorthRobotsToFrontDoorSpotID: + _sprite1.setCurrentFrameIndex(kRedirectionFDRolloverSprite); + _sprite1.show(); + break; + default: + _sprite1.hide(); + break; + } + } else { + _sprite1.hide(); + } + break; + } + } + break; + } + + Neighborhood::handleInput(input, cursorSpot); +} + +void FullTSA::releaseSprites() { + _sprite1.hide(); + _sprite2.hide(); + _sprite3.hide(); + _sprite1.discardFrames(); + _sprite2.discardFrames(); + _sprite3.discardFrames(); +} + +bool FullTSA::canSolve() { + return GameState.getCurrentRoomAndView() == MakeRoomView(kTSA0B, kNorth) && + GameState.getTSA0BZoomedIn() && + (GameState.getTSAState() == kRobotsAtCommandCenter || + GameState.getTSAState() == kRobotsAtFrontDoor || + GameState.getTSAState() == kRobotsAtReadyRoom); +} + +void FullTSA::doSolve() { + // REROUTING ROBOTS + + _sprite1.setCurrentFrameIndex(kRedirectionFDDoorSprite); + _sprite1.show(); + _vm->delayShell(1, 2); + _sprite1.hide(); + + switch (GameState.getTSAState()) { + case kRobotsAtCommandCenter: + GameState.setTSAState(kRobotsAtFrontDoor); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromCommandCenterToFrontDoor, kExtraCompletedFlag, kFilterNoInput); + break; + case kRobotsAtFrontDoor: + // Nothing + break; + case kRobotsAtReadyRoom: + GameState.setTSAState(kRobotsAtFrontDoor); + _sprite2.setCurrentFrameIndex(kRedirectionNewTargetSprite); + startExtraSequence(kTSA0BRobotsFromReadyRoomToFrontDoor, kExtraCompletedFlag, kFilterNoInput); + break; + } +} + +void FullTSA::updateCursor(const Common::Point where, const Hotspot *cursorSpot) { + if (cursorSpot) { + switch (cursorSpot->getObjectID()) { + case kTSA0BEastMonitorSpotID: + case kTSA0BNorthMonitorSpotID: + case kTSA0BWestMonitorSpotID: + case kTSA22EastMonitorSpotID: + case kTSA23WestMonitorSpotID: + _vm->_cursor->setCurrentFrameIndex(1); + return; + case kTSA0BEastMonitorOutSpotID: + case kTSA0BNorthMonitorOutSpotID: + case kTSA0BWestMonitorOutSpotID: + _vm->_cursor->setCurrentFrameIndex(2); + return; + } + } + + Neighborhood::updateCursor(where, cursorSpot); +} + +Common::String FullTSA::getNavMovieName() { + return "Images/TSA/Full TSA.movie"; +} + +Common::String FullTSA::getSoundSpotsName() { + return "Sounds/TSA/TSA Spots"; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/tsa/fulltsa.h b/engines/pegasus/neighborhood/tsa/fulltsa.h new file mode 100644 index 0000000000..a646d57e6c --- /dev/null +++ b/engines/pegasus/neighborhood/tsa/fulltsa.h @@ -0,0 +1,159 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_TSA_FULLTSA_H +#define PEGASUS_NEIGHBORHOOD_TSA_FULLTSA_H + +#include "pegasus/neighborhood/neighborhood.h" + +namespace Pegasus { + +class RipTimer : public IdlerAnimation { +public: + RipTimer(const DisplayElementID id) : IdlerAnimation(id) {} + virtual ~RipTimer() {} + + void initImage(); + void releaseImage(); + + void draw(const Common::Rect &); + +protected: + void timeChanged(const TimeValue); + + CoordType _middle; + Surface _timerImage; +}; + +// Room IDs. + +static const RoomID kTSA00 = 0; +static const RoomID kTSA22Red = 28; +static const RoomID kTSA37 = 42; + +class FullTSA : public Neighborhood { +public: + FullTSA(InputHandler *, PegasusEngine *); + virtual ~FullTSA() {} + + virtual void init(); + + void start(); + + virtual uint16 getDateResID() const; + + void flushGameState(); + + void checkContinuePoint(const RoomID, const DirectionConstant); + + bool canSolve(); + void doSolve(); + + void updateCursor(const Common::Point, const Hotspot *); + +protected: + enum { + kTSAPrivateLogReaderOpenFlag, + kTSAPrivateKeyVaultOpenFlag, + kTSAPrivateChipVaultOpenFlag, + kTSAPrivatePlayingLeftComparisonFlag, + kTSAPrivatePlayingRightComparisonFlag, + kTSAPrivateSeenRobotWarningFlag, + kNumTSAPrivateFlags + }; + + Common::String getBriefingMovie(); + Common::String getEnvScanMovie(); + uint getNumHints(); + Common::String getHintMovie(uint); + void loadAmbientLoops(); + virtual void clickInHotspot(const Input &, const Hotspot *); + + virtual int16 getStaticCompassAngle(const RoomID, const DirectionConstant); + void activateOneHotspot(HotspotInfoTable::Entry &, Hotspot *spot); + virtual void activateHotspots(); + void getExitCompassMove(const ExitTable::Entry &, FaderMoveSpec &); + void dropItemIntoRoom(Item *, Hotspot *); + void downButton(const Input &); + void startDoorOpenMovie(const TimeValue, const TimeValue); + TimeValue getViewTime(const RoomID, const DirectionConstant); + void findSpotEntry(const RoomID, const DirectionConstant, SpotFlags, SpotTable::Entry &); + void turnTo(const DirectionConstant); + CanMoveForwardReason canMoveForward(ExitTable::Entry &); + CanOpenDoorReason canOpenDoor(DoorTable::Entry &); + void bumpIntoWall(); + void initializeTBPMonitor(const int, const ExtraID); + void playTBPMonitor(); + void getExtraCompassMove(const ExtraTable::Entry &, FaderMoveSpec &); + Hotspot *getItemScreenSpot(Item *, DisplayElement *); + void openDoor(); + void turnRight(); + void turnLeft(); + void closeDoorOffScreen(const RoomID, const DirectionConstant); + void playExtraMovie(const ExtraTable::Entry &, const NotificationFlags, const InputBits interruptionInput); + void handleInput(const Input &, const Hotspot *); + void arriveAtTSA25Red(); + void startUpComparisonMonitor(); + void shutDownComparisonMonitor(); + void initializeComparisonMonitor(const int, const ExtraID); + void playLeftComparison(); + void playRightComparison(); + void startRobotGame(); + void setOffRipAlarm(); + uint getHistoricalLogIndex(); + void startUpRobotMonitor(); + void shutDownRobotMonitor(); + void pickedUpItem(Item *item); + void arriveFromPrehistoric(); + + void arriveFromNorad(); + void arriveFromMars(); + void arriveFromWSC(); + + InputBits getInputFilter(); + void arriveAt(const RoomID, const DirectionConstant); + void initializePegasusButtons(bool); + void releaseSprites(); + void showMainJumpMenu(); + void arriveAtTSA37(); + void receiveNotification(Notification *, const NotificationFlags); + void checkRobotLocations(const RoomID, const DirectionConstant); + void getExtraEntry(const uint32, ExtraTable::Entry &); + + Sprite _sprite1, _sprite2, _sprite3; + FuseFunction _utilityFuse; + RipTimer _ripTimer; + + FlagsArray<byte, kNumTSAPrivateFlags> _privateFlags; + + Common::String getNavMovieName(); + Common::String getSoundSpotsName(); + + void dieUncreatedInTSA(); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/tsa/tinytsa.cpp b/engines/pegasus/neighborhood/tsa/tinytsa.cpp new file mode 100644 index 0000000000..4f109620c1 --- /dev/null +++ b/engines/pegasus/neighborhood/tsa/tinytsa.cpp @@ -0,0 +1,453 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/aichip.h" +#include "pegasus/items/biochips/opticalchip.h" +#include "pegasus/neighborhood/mars/constants.h" +#include "pegasus/neighborhood/norad/constants.h" +#include "pegasus/neighborhood/tsa/tinytsa.h" +#include "pegasus/neighborhood/wsc/wsc.h" + +namespace Pegasus { + +static const int16 kCompassShift = 30; + +static const TimeScale kTinyTSAMovieScale = 600; +static const TimeScale kTinyTSAFramesPerSecond = 15; +static const TimeScale kTinyTSAFrameDuration = 40; + +// Alternate IDs. +static const AlternateID kAltTinyTSANormal = 0; + +// Hot Spot Activation IDs. +static const HotSpotActivationID kActivationTinyTSAJumpToNorad = 1; +static const HotSpotActivationID kActivationTinyTSAJumpToMars = 2; +static const HotSpotActivationID kActivationTinyTSAJumpToWSC = 3; +static const HotSpotActivationID kActivationTinyTSAReadyForJumpMenu = 4; +static const HotSpotActivationID kActivationTinyTSAMainJumpMenu = 5; + +// Hot Spot IDs. +static const HotSpotID kTinyTSA37NorthJumpToNoradSpotID = 5000; +static const HotSpotID kTinyTSA37NorthCancelNoradSpotID = 5001; +static const HotSpotID kTinyTSA37NorthJumpToMarsSpotID = 5002; +static const HotSpotID kTinyTSA37NorthCancelMarsSpotID = 5003; +static const HotSpotID kTinyTSA37NorthJumpToWSCSpotID = 5004; +static const HotSpotID kTinyTSA37NorthCancelWSCSpotID = 5005; +static const HotSpotID kTinyTSA37NorthJumpMenuSpotID = 5006; +static const HotSpotID kTinyTSA37NorthNoradMenuSpotID = 5007; +static const HotSpotID kTinyTSA37NorthMarsMenuSpotID = 5008; +static const HotSpotID kTinyTSA37NorthWSCMenuSpotID = 5009; + +// Extra sequence IDs. +static const ExtraID kTinyTSA37PegasusDepart = 0; +static const ExtraID kTinyTSA37TimeJumpToPegasus = 1; +static const ExtraID kTinyTSA37RecallToDownload = 2; +static const ExtraID kTinyTSA37ExitHilited = 3; +static const ExtraID kTinyTSA37ExitToHorse = 4; +static const ExtraID kTinyTSA37JumpMenu000 = 5; +static const ExtraID kTinyTSA37JumpMenu001 = 6; +static const ExtraID kTinyTSA37JumpMenu010 = 7; +static const ExtraID kTinyTSA37JumpMenu011 = 8; +static const ExtraID kTinyTSA37JumpMenu100 = 9; +static const ExtraID kTinyTSA37JumpMenu101 = 10; +static const ExtraID kTinyTSA37JumpMenu110 = 11; +static const ExtraID kTinyTSA37JumpMenu111 = 12; +static const ExtraID kTinyTSA37JumpToWSCMenu = 13; +static const ExtraID kTinyTSA37CancelWSC = 14; +static const ExtraID kTinyTSA37JumpToWSC = 15; +static const ExtraID kTinyTSA37WSCToAI5 = 16; +static const ExtraID kTinyTSA37PegasusAI5 = 17; +static const ExtraID kTinyTSA37AI5ToWSC = 18; +static const ExtraID kTinyTSA37WSCToDepart = 19; +static const ExtraID kTinyTSA37JumpToMarsMenu = 20; +static const ExtraID kTinyTSA37CancelMars = 21; +static const ExtraID kTinyTSA37JumpToMars = 22; +static const ExtraID kTinyTSA37MarsToAI6 = 23; +static const ExtraID kTinyTSA37PegasusAI6 = 24; +static const ExtraID kTinyTSA37AI6ToMars = 25; +static const ExtraID kTinyTSA37MarsToDepart = 26; +static const ExtraID kTinyTSA37JumpToNoradMenu = 27; +static const ExtraID kTinyTSA37CancelNorad = 28; +static const ExtraID kTinyTSA37JumpToNorad = 29; +static const ExtraID kTinyTSA37NoradToAI7 = 30; +static const ExtraID kTinyTSA37PegasusAI7 = 31; +static const ExtraID kTinyTSA37AI7ToNorad = 32; +static const ExtraID kTinyTSA37NoradToDepart = 33; +static const ExtraID kTinyTSA37EnvironmentalScan = 34; +static const ExtraID kTinyTSA37DownloadToMainMenu = 35; +static const ExtraID kTinyTSA37DownloadToOpMemReview = 36; +static const ExtraID kTinyTSA37OpMemReviewToMainMenu = 37; + +TinyTSA::TinyTSA(InputHandler *nextHandler, PegasusEngine *owner) : Neighborhood(nextHandler, owner, "Tiny TSA", kTinyTSAID) { +} + +void TinyTSA::start() { + g_energyMonitor->stopEnergyDraining(); + Neighborhood::start(); +} + +Common::String TinyTSA::getBriefingMovie() { + Common::String movieName = Neighborhood::getBriefingMovie(); + + if (movieName.empty()) { + switch (getCurrentActivation()) { + case kActivationTinyTSAJumpToNorad: + g_AIChip->showBriefingClicked(); + startExtraSequenceSync(kTinyTSA37PegasusAI7, kHintInterruption); + startExtraSequenceSync(kTinyTSA37AI7ToNorad, kFilterNoInput); + g_AIChip->clearClicked(); + movieName = ""; + break; + case kActivationTinyTSAJumpToMars: + g_AIChip->showBriefingClicked(); + startExtraSequenceSync(kTinyTSA37PegasusAI6, kHintInterruption); + startExtraSequenceSync(kTinyTSA37AI6ToMars, kFilterNoInput); + g_AIChip->clearClicked(); + movieName = ""; + break; + case kActivationTinyTSAJumpToWSC: + g_AIChip->showBriefingClicked(); + startExtraSequenceSync(kTinyTSA37PegasusAI5, kHintInterruption); + startExtraSequenceSync(kTinyTSA37AI5ToWSC, kFilterNoInput); + g_AIChip->clearClicked(); + movieName = ""; + break; + default: + movieName = "Images/AI/TSA/XT04"; + break; + } + } + + return movieName; +} + +Common::String TinyTSA::getEnvScanMovie() { + Common::String movieName = Neighborhood::getEnvScanMovie(); + + if (movieName.empty()) { + g_AIChip->showEnvScanClicked(); + startExtraSequenceSync(kTinyTSA37EnvironmentalScan, kHintInterruption); + + switch (getCurrentActivation()) { + case kActivationTinyTSAJumpToNorad: + startExtraSequenceSync(kTinyTSA37AI7ToNorad, kFilterNoInput); + showExtraView(kTinyTSA37JumpToNoradMenu); + break; + case kActivationTinyTSAJumpToMars: + startExtraSequenceSync(kTinyTSA37AI6ToMars, kFilterNoInput); + showExtraView(kTinyTSA37JumpToMarsMenu); + break; + case kActivationTinyTSAJumpToWSC: + startExtraSequenceSync(kTinyTSA37AI5ToWSC, kFilterNoInput); + showExtraView(kTinyTSA37JumpToWSCMenu); + break; + default: + showMainJumpMenu(); + break; + } + + g_AIChip->clearClicked(); + } + + return movieName; +} + +void TinyTSA::loadAmbientLoops() { + loadLoopSound1("Sounds/TSA/T01NAE.NEW.22K.AIFF"); +} + +int16 TinyTSA::getStaticCompassAngle(const RoomID room, const DirectionConstant dir) { + return Neighborhood::getStaticCompassAngle(room, dir) - kCompassShift; +} + +uint16 TinyTSA::getDateResID() const { + return kDate2318ID; +} + +InputBits TinyTSA::getInputFilter() { + // Can't move forward... + return Neighborhood::getInputFilter() & ~(kFilterUpButton | kFilterUpAuto); +} + +void TinyTSA::clickInHotspot(const Input &input, const Hotspot *clickedSpot) { + if (clickedSpot) { + switch (clickedSpot->getObjectID()) { + case kTinyTSA37NorthJumpMenuSpotID: + // This hotspot isn't accessable from Tiny TSA + warning("jump menu spot"); + return; + case kTinyTSA37NorthJumpToNoradSpotID: + GameState.setTSAState(kPlayerOnWayToNorad); + requestExtraSequence(kTinyTSA37JumpToNorad, 0, kFilterNoInput); + if (!GameState.getBeenToNorad()) { + requestExtraSequence(kTinyTSA37NoradToAI7, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37PegasusAI7, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37AI7ToNorad, 0, kFilterNoInput); + GameState.setBeenToNorad(true); + } + + requestExtraSequence(kTinyTSA37NoradToDepart, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37PegasusDepart, kExtraCompletedFlag, kFilterNoInput); + return; + case kTinyTSA37NorthJumpToMarsSpotID: + GameState.setTSAState(kPlayerOnWayToMars); + requestExtraSequence(kTinyTSA37JumpToMars, 0, kFilterNoInput); + if (!GameState.getBeenToMars()) { + requestExtraSequence(kTinyTSA37MarsToAI6, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37PegasusAI6, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37AI6ToMars, 0, kFilterNoInput); + GameState.setBeenToMars(true); + } + + requestExtraSequence(kTinyTSA37MarsToDepart, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37PegasusDepart, kExtraCompletedFlag, kFilterNoInput); + return; + case kTinyTSA37NorthJumpToWSCSpotID: + GameState.setTSAState(kPlayerOnWayToWSC); + requestExtraSequence(kTinyTSA37JumpToWSC, 0, kFilterNoInput); + if (!GameState.getBeenToWSC()) { + requestExtraSequence(kTinyTSA37WSCToAI5, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37PegasusAI5, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37AI5ToWSC, 0, kFilterNoInput); + GameState.setBeenToWSC(true); + } + + requestExtraSequence(kTinyTSA37WSCToDepart, 0, kFilterNoInput); + requestExtraSequence(kTinyTSA37PegasusDepart, kExtraCompletedFlag, kFilterNoInput); + return; + } + } + + Neighborhood::clickInHotspot(input, clickedSpot); +} + +void TinyTSA::showMainJumpMenu() { + ExtraID jumpMenuView = kTinyTSA37JumpMenu000; + + if (GameState.getNoradFinished()) + jumpMenuView += 4; + if (GameState.getMarsFinished()) + jumpMenuView += 2; + if (GameState.getWSCFinished()) + jumpMenuView += 1; + + showExtraView(jumpMenuView); + setCurrentActivation(kActivationTinyTSAMainJumpMenu); +} + +void TinyTSA::checkContinuePoint(const RoomID, const DirectionConstant) { + makeContinuePoint(); +} + +void TinyTSA::arriveAt(const RoomID room, const DirectionConstant direction) { + Neighborhood::arriveAt(room, direction); + + switch (GameState.getTSAState()) { + case kPlayerOnWayToNorad: + case kPlayerOnWayToMars: + case kPlayerOnWayToWSC: + startExtraSequence(kTinyTSA37TimeJumpToPegasus, kExtraCompletedFlag, kFilterNoInput); + break; + case kPlayerLockedInPegasus: + showMainJumpMenu(); + break; + } +} + +void TinyTSA::receiveNotification(Notification *notification, const NotificationFlags flags) { + ExtraID lastExtra = _lastExtra; + + Neighborhood::receiveNotification(notification, flags); + + if ((flags & kExtraCompletedFlag) != 0) { + // Only allow input if we're not in the middle of series of queue requests. + if (actionQueueEmpty()) + _interruptionFilter = kFilterAllInput; + + switch (lastExtra) { + case kTinyTSA37PegasusDepart: + _vm->setLastEnergyValue(kFullEnergy); + + switch (GameState.getTSAState()) { + case kPlayerOnWayToNorad: + _vm->jumpToNewEnvironment(kNoradAlphaID, kNorad01, kSouth); + GameState.setNoradSeenTimeStream(false); + GameState.setNoradGassed(true); + GameState.setNoradFillingStationOn(false); + GameState.setNoradN22MessagePlayed(false); + GameState.setNoradPlayedGlobeGame(false); + GameState.setNoradBeatRobotWithClaw(false); + GameState.setNoradBeatRobotWithDoor(false); + GameState.setNoradRetScanGood(false); + GameState.setNoradWaitingForLaser(false); + GameState.setNoradSubRoomPressure(9); + GameState.setNoradSubPrepState(kSubNotPrepped); + break; + case kPlayerOnWayToMars: + _vm->jumpToNewEnvironment(kMarsID, kMars0A, kNorth); + GameState.setMarsSeenTimeStream(false); + GameState.setMarsHeardUpperPodMessage(false); + GameState.setMarsRobotThrownPlayer(false); + GameState.setMarsHeardCheckInMessage(false); + GameState.setMarsPodAtUpperPlatform(false); + GameState.setMarsSeenThermalScan(false); + GameState.setMarsArrivedBelow(false); + GameState.setMarsSeenRobotAtReactor(false); + GameState.setMarsAvoidedReactorRobot(false); + GameState.setMarsLockFrozen(false); + GameState.setMarsLockBroken(false); + GameState.setMarsSecurityDown(false); + GameState.setMarsAirlockOpen(false); + GameState.setMarsReadyForShuttleTransport(false); + GameState.setMarsFinishedCanyonChase(false); + GameState.setMarsThreadedMaze(false); + break; + case kPlayerOnWayToWSC: + _vm->jumpToNewEnvironment(kWSCID, kWSC01, kWest); + GameState.setWSCSeenTimeStream(false); + GameState.setWSCPoisoned(false); + GameState.setWSCAnsweredAboutDart(false); + GameState.setWSCDartInAnalyzer(false); + GameState.setWSCRemovedDart(false); + GameState.setWSCAnalyzerOn(false); + GameState.setWSCAnalyzedDart(false); + GameState.setWSCPickedUpAntidote(false); + GameState.setWSCSawMorph(false); + GameState.setWSCDesignedAntidote(false); + GameState.setWSCOfficeMessagesOpen(false); + GameState.setWSCSeenNerd(false); + GameState.setWSCHeardPage1(false); + GameState.setWSCHeardPage2(false); + GameState.setWSCHeardCheckIn(false); + GameState.setWSCDidPlasmaDodge(false); + GameState.setWSCSeenSinclairLecture(false); + GameState.setWSCBeenAtWSC93(false); + GameState.setWSCCatwalkDark(false); + GameState.setWSCRobotDead(false); + GameState.setWSCRobotGone(false); + break; + }; + break; + case kTinyTSA37TimeJumpToPegasus: + if (g_energyMonitor) + g_energyMonitor->stopEnergyDraining(); + + switch (GameState.getTSAState()) { + case kPlayerOnWayToNorad: + arriveFromNorad(); + break; + case kPlayerOnWayToMars: + arriveFromMars(); + break; + case kPlayerOnWayToWSC: + arriveFromWSC(); + break; + default: + break; + } + break; + case kTinyTSA37DownloadToOpMemReview: + switch (GameState.getTSAState()) { + case kPlayerOnWayToNorad: + g_opticalChip->playOpMemMovie(kPoseidonSpotID); + break; + case kPlayerOnWayToMars: + g_opticalChip->playOpMemMovie(kAriesSpotID); + break; + case kPlayerOnWayToWSC: + g_opticalChip->playOpMemMovie(kMercurySpotID); + break; + } + + requestExtraSequence(kTinyTSA37OpMemReviewToMainMenu, kExtraCompletedFlag, kFilterNoInput); + break; + case kTinyTSA37DownloadToMainMenu: + case kTinyTSA37OpMemReviewToMainMenu: + GameState.setTSAState(kPlayerLockedInPegasus); + showMainJumpMenu(); + makeContinuePoint(); + break; + case kTinyTSA37JumpToNoradMenu: + setCurrentActivation(kActivationTinyTSAJumpToNorad); + break; + case kTinyTSA37JumpToMarsMenu: + setCurrentActivation(kActivationTinyTSAJumpToMars); + break; + case kTinyTSA37JumpToWSCMenu: + setCurrentActivation(kActivationTinyTSAJumpToWSC); + break; + case kTinyTSA37CancelNorad: + case kTinyTSA37CancelMars: + case kTinyTSA37CancelWSC: + showMainJumpMenu(); + break; + } + } + + g_AIArea->checkMiddleArea(); +} + +void TinyTSA::arriveFromNorad() { + requestExtraSequence(kTinyTSA37RecallToDownload, 0, kFilterNoInput); + + if (GameState.getNoradFinished() && !GameState.getScoringFinishedNorad()) { + GameState.setScoringFinishedNorad(); + requestExtraSequence(kTinyTSA37DownloadToOpMemReview, kExtraCompletedFlag, kFilterNoInput); + } else { + requestExtraSequence(kTinyTSA37DownloadToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } +} + +void TinyTSA::arriveFromMars() { + requestExtraSequence(kTinyTSA37RecallToDownload, 0, kFilterNoInput); + + if (GameState.getMarsFinished() && !GameState.getScoringFinishedMars()) { + GameState.setScoringFinishedMars(); + requestExtraSequence(kTinyTSA37DownloadToOpMemReview, kExtraCompletedFlag, kFilterNoInput); + } else { + requestExtraSequence(kTinyTSA37DownloadToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } +} + +void TinyTSA::arriveFromWSC() { + requestExtraSequence(kTinyTSA37RecallToDownload, 0, kFilterNoInput); + + if (GameState.getWSCFinished() && !GameState.getScoringFinishedWSC()) { + GameState.setScoringFinishedWSC(); + requestExtraSequence(kTinyTSA37DownloadToOpMemReview, kExtraCompletedFlag, kFilterNoInput); + } else { + requestExtraSequence(kTinyTSA37DownloadToMainMenu, kExtraCompletedFlag, kFilterNoInput); + } +} + +Common::String TinyTSA::getNavMovieName() { + return "Images/TSA/Tiny TSA.movie"; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/tsa/tinytsa.h b/engines/pegasus/neighborhood/tsa/tinytsa.h new file mode 100644 index 0000000000..2dc234675d --- /dev/null +++ b/engines/pegasus/neighborhood/tsa/tinytsa.h @@ -0,0 +1,71 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_TSA_TINYTSA_H +#define PEGASUS_NEIGHBORHOOD_TSA_TINYTSA_H + +#include "pegasus/neighborhood/neighborhood.h" + +namespace Pegasus { + +// Room IDs. + +static const RoomID kTinyTSA37 = 0; + +class TinyTSA : public Neighborhood { +public: + TinyTSA(InputHandler *, PegasusEngine *); + virtual ~TinyTSA() {} + + virtual uint16 getDateResID() const; + + void start(); + + void checkContinuePoint(const RoomID, const DirectionConstant); + +protected: + Common::String getBriefingMovie(); + Common::String getEnvScanMovie(); + void loadAmbientLoops(); + virtual void clickInHotspot(const Input &, const Hotspot *); + + virtual int16 getStaticCompassAngle(const RoomID, const DirectionConstant); + + void arriveFromNorad(); + void arriveFromMars(); + void arriveFromWSC(); + + InputBits getInputFilter(); + void arriveAt(const RoomID, const DirectionConstant); + void showMainJumpMenu(); + void receiveNotification(Notification *, const NotificationFlags); + + Common::String getNavMovieName(); + Common::String getSoundSpotsName() { return ""; } +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/turn.cpp b/engines/pegasus/neighborhood/turn.cpp new file mode 100644 index 0000000000..1157796f55 --- /dev/null +++ b/engines/pegasus/neighborhood/turn.cpp @@ -0,0 +1,63 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/turn.h" + +namespace Pegasus { + +void TurnTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].room = stream->readUint16BE(); + _entries[i].direction = stream->readByte(); + _entries[i].turnDirection = stream->readByte(); + _entries[i].altCode = stream->readByte(); + stream->readByte(); // alignment + _entries[i].endDirection = stream->readByte(); + stream->readByte(); // alignment + debug(0, "Turn[%d]: %d %d %d %d %d", i, _entries[i].room, _entries[i].direction, + _entries[i].turnDirection, _entries[i].altCode, _entries[i].endDirection); + } +} + +void TurnTable::clear() { + _entries.clear(); +} + +TurnTable::Entry TurnTable::findEntry(RoomID room, DirectionConstant direction, TurnDirection turnDirection, AlternateID altCode) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].room == room && _entries[i].direction == direction && _entries[i].turnDirection == turnDirection && _entries[i].altCode == altCode) + return _entries[i]; + + return Entry(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/turn.h b/engines/pegasus/neighborhood/turn.h new file mode 100644 index 0000000000..329b03eddb --- /dev/null +++ b/engines/pegasus/neighborhood/turn.h @@ -0,0 +1,69 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_TURN_H +#define PEGASUS_NEIGHBORHOOD_TURN_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +class TurnTable { +public: + TurnTable() {} + ~TurnTable() {} + + static uint32 getResTag() { return MKTAG('T', 'u', 'r', 'n'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry() { endDirection = kNoDirection; } + bool isEmpty() { return endDirection == kNoDirection; } + + RoomID room; + DirectionConstant direction; + TurnDirection turnDirection; + AlternateID altCode; + DirectionConstant endDirection; + }; + + Entry findEntry(RoomID room, DirectionConstant direction, TurnDirection turnDirection, AlternateID altCode); + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/view.cpp b/engines/pegasus/neighborhood/view.cpp new file mode 100644 index 0000000000..4e46f5374e --- /dev/null +++ b/engines/pegasus/neighborhood/view.cpp @@ -0,0 +1,60 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/view.h" + +namespace Pegasus { + +void ViewTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].room = stream->readUint16BE(); + _entries[i].direction = stream->readByte(); + _entries[i].altCode = stream->readByte(); + _entries[i].time = stream->readUint32BE(); + debug(0, "View[%d]: %d %d %d %d", i, _entries[i].room, _entries[i].direction, + _entries[i].altCode, _entries[i].time); + } +} + +void ViewTable::clear() { + _entries.clear(); +} + +ViewTable::Entry ViewTable::findEntry(RoomID room, DirectionConstant direction, AlternateID altCode) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].room == room && _entries[i].direction == direction && _entries[i].altCode == altCode) + return _entries[i]; + + return Entry(); +} + +} // End of namespace pegasus diff --git a/engines/pegasus/neighborhood/view.h b/engines/pegasus/neighborhood/view.h new file mode 100644 index 0000000000..3397508b61 --- /dev/null +++ b/engines/pegasus/neighborhood/view.h @@ -0,0 +1,68 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_VIEW_H +#define PEGASUS_NEIGHBORHOOD_VIEW_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +class ViewTable { +public: + ViewTable() {} + ~ViewTable() {} + + static uint32 getResTag() { return MKTAG('V', 'i', 'e', 'w'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry() { time = 0xffffffff; } + bool isEmpty() { return time == 0xffffffff; } + + RoomID room; + DirectionConstant direction; + AlternateID altCode; + TimeValue time; + }; + + Entry findEntry(RoomID room, DirectionConstant direction, AlternateID altCode); + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/wsc/moleculebin.cpp b/engines/pegasus/neighborhood/wsc/moleculebin.cpp new file mode 100644 index 0000000000..210c0ad313 --- /dev/null +++ b/engines/pegasus/neighborhood/wsc/moleculebin.cpp @@ -0,0 +1,127 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/graphics.h" +#include "pegasus/neighborhood/wsc/moleculebin.h" +#include "pegasus/neighborhood/wsc/wsc.h" + +namespace Pegasus { + +static const CoordType kMoleculeBinWidth = 138; +static const CoordType kMoleculeBinHeight = 128; + +static const CoordType kMoleculeWidth = 66; +static const CoordType kMoleculeHeight = 40; + +static const CoordType kMoleculeBinLeft = kNavAreaLeft + 286; +static const CoordType kMoleculeBinTop = kNavAreaLeft + 96; + +// Layouts: + +MoleculeBin::MoleculeBin() : DisplayElement(kNoDisplayElement) { + _highlightColor = g_system->getScreenFormat().RGBToColor(0xff, 0xff, 102); + _selectedMolecule = -1; +} + +void MoleculeBin::initMoleculeBin() { + if (!isDisplaying()) { + for (int i = 0; i < 6; i++) + _binLayout[i] = i; + + resetBin(); + _binImages.getImageFromPICTFile("Images/World Science Center/Molecules"); + setDisplayOrder(kWSCMoleculeBinOrder); + setBounds(kMoleculeBinLeft, kMoleculeBinTop, kMoleculeBinLeft + kMoleculeBinWidth, + kMoleculeBinTop + kMoleculeBinHeight); + startDisplaying(); + show(); + } +} + +void MoleculeBin::cleanUpMoleculeBin() { + if (isDisplaying()) { + stopDisplaying(); + _binImages.deallocateSurface(); + } +} + +void MoleculeBin::setBinLayout(const uint32 *layout) { + for (int i = 0; i < 6; i++) + _binLayout[i] = layout[i]; +} + +void MoleculeBin::highlightMolecule(const uint32 whichMolecule) { + if (!_moleculeFlags.getFlag(whichMolecule)) { + _moleculeFlags.setFlag(whichMolecule, true); + triggerRedraw(); + } +} + +bool MoleculeBin::isMoleculeHighlighted(uint32 whichMolecule) { + return _moleculeFlags.getFlag(whichMolecule); +} + +void MoleculeBin::selectMolecule(const int whichMolecule) { + if (_selectedMolecule != whichMolecule) { + _selectedMolecule = whichMolecule; + triggerRedraw(); + } +} + +void MoleculeBin::resetBin() { + _moleculeFlags.clearAllFlags(); + _selectedMolecule = -1; + triggerRedraw(); +} + +void MoleculeBin::draw(const Common::Rect &) { + Common::Rect r1(0, 0, kMoleculeWidth, kMoleculeHeight); + Common::Rect r2 = r1; + + for (int i = 0; i < 6; i++) { + r1.moveTo(i * (kMoleculeWidth * 2), 0); + + if (_moleculeFlags.getFlag(_binLayout[i])) + r1.translate(kMoleculeWidth, 0); + + r2.moveTo((_binLayout[i] & 1) * (kMoleculeWidth + 2) + _bounds.left + 2, + (_binLayout[i] >> 1) * (kMoleculeHeight + 2) + _bounds.top + 2); + + _binImages.copyToCurrentPort(r1, r2); + } + + if (_selectedMolecule >= 0) { + r2.moveTo((_selectedMolecule & 1) * (kMoleculeWidth + 2) + _bounds.left + 2, + (_selectedMolecule >> 1) * (kMoleculeHeight + 2) + _bounds.top + 2); + + Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getWorkArea(); + + screen->frameRect(r2, _highlightColor); + r2.grow(1); + screen->frameRect(r2, _highlightColor); + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/wsc/moleculebin.h b/engines/pegasus/neighborhood/wsc/moleculebin.h new file mode 100644 index 0000000000..3de4b5ed2a --- /dev/null +++ b/engines/pegasus/neighborhood/wsc/moleculebin.h @@ -0,0 +1,72 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_WSC_MOLECULEBIN_H +#define PEGASUS_NEIGHBORHOOD_WSC_MOLECULEBIN_H + +#include "pegasus/elements.h" +#include "pegasus/surface.h" +#include "pegasus/util.h" + +namespace Pegasus { + +enum { + kMolecule1, + kMolecule2, + kMolecule3, + kMolecule4, + kMolecule5, + kMolecule6 +}; + +class MoleculeBin : public DisplayElement { +public: + MoleculeBin(); + virtual ~MoleculeBin() {} + + void initMoleculeBin(); + void cleanUpMoleculeBin(); + + void setBinLayout(const uint32 *); + + void highlightMolecule(const uint32 whichMolecule); + void selectMolecule(const int whichMolecule); + void resetBin(); + + bool isMoleculeHighlighted(uint32); + +protected: + void draw(const Common::Rect &); + + Surface _binImages; + FlagsArray<byte, kMolecule6 + 1> _moleculeFlags; + int _selectedMolecule; + uint32 _binLayout[6]; + uint32 _highlightColor; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/wsc/wsc.cpp b/engines/pegasus/neighborhood/wsc/wsc.cpp new file mode 100644 index 0000000000..f3bf113333 --- /dev/null +++ b/engines/pegasus/neighborhood/wsc/wsc.cpp @@ -0,0 +1,2542 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "pegasus/energymonitor.h" +#include "pegasus/gamestate.h" +#include "pegasus/pegasus.h" +#include "pegasus/ai/ai_area.h" +#include "pegasus/items/biochips/opticalchip.h" +#include "pegasus/items/biochips/shieldchip.h" +#include "pegasus/neighborhood/wsc/wsc.h" + +namespace Pegasus { + +static const CanMoveForwardReason kCantMoveWatchingDiagnosis = kCantMoveLastReason + 1; + +static const CanTurnReason kCantTurnWatchingDiagnosis = kCantTurnLastReason + 1; +static const CanTurnReason kCantTurnWatchingAnalysis = kCantTurnWatchingDiagnosis + 1; +static const CanTurnReason kCantTurnInMoleculeGame = kCantTurnWatchingAnalysis + 1; + +static const TimeScale kMoleculesMovieScale = 600; +static const TimeValue kMoleculeLoopTime = 4 * kMoleculesMovieScale; +static const TimeValue kMoleculeFailTime = 2 * kMoleculesMovieScale; + +enum { + kMoleculeLoop0Time = 0, + kMoleculeFail0Time = kMoleculeLoop0Time + kMoleculeLoopTime, + kMoleculeLoop1Time = kMoleculeFail0Time + kMoleculeFailTime, + kMoleculeFail1Time = kMoleculeLoop1Time + kMoleculeLoopTime, + kMoleculeLoop2Time = kMoleculeFail1Time + kMoleculeFailTime, + kMoleculeFail2Time = kMoleculeLoop2Time + kMoleculeLoopTime, + kMoleculeLoop3Time = kMoleculeFail2Time + kMoleculeFailTime, + kMoleculeFail3Time = kMoleculeLoop3Time + kMoleculeLoopTime, + kMoleculeLoop4Time = kMoleculeFail3Time + kMoleculeFailTime, + kMoleculeFail4Time = kMoleculeLoop4Time + kMoleculeLoopTime, + kMoleculeLoop5Time = kMoleculeFail4Time + kMoleculeFailTime, + kMoleculeFail5Time = kMoleculeLoop5Time + kMoleculeLoopTime, + kMoleculeLoop6Time = kMoleculeFail5Time + kMoleculeFailTime +}; + +static const TimeValue s_moleculeLoopTimes[] = { + kMoleculeLoop0Time, + kMoleculeLoop1Time, + kMoleculeLoop2Time, + kMoleculeLoop3Time, + kMoleculeLoop4Time, + kMoleculeLoop5Time, + kMoleculeLoop6Time +}; + +static const TimeValue s_moleculeFailTimes[] = { + kMoleculeFail0Time, + kMoleculeFail1Time, + kMoleculeFail2Time, + kMoleculeFail3Time, + kMoleculeFail4Time, + kMoleculeFail5Time +}; + +static const int16 kAuditoriumAngleOffset = 5; + +static const int kPlasmaEnergyWithShield = kMaxJMPEnergy * 10 / 100; +static const int kPlasmaEnergyNoShield = kMaxJMPEnergy * 20 / 100; + +static const int kTimerEventPlasmaHit = 0; +static const int kTimerEventPlayerGawkingAtRobot = 1; +static const int kTimerEventPlayerGawkingAtRobot2 = 2; + +static const TimeValue kWSCMolecule1In = 0; +static const TimeValue kWSCMolecule1Out = 937; + +static const TimeValue kWSCMolecule2In = 937; +static const TimeValue kWSCMolecule2Out = 1864; + +static const TimeValue kWSCMolecule3In = 1864; +static const TimeValue kWSCMolecule3Out = 2790; + +static const TimeValue kWSCClick1In = 2790; +static const TimeValue kWSCClick1Out = 2890; + +static const TimeValue kWSCClick2In = 2890; +static const TimeValue kWSCClick2Out = 3059; + +static const TimeValue kWSCClick3In = 3059; +static const TimeValue kWSCClick3Out = 3156; + +static const TimeValue kWSCFlashlightClickIn = 3156; +static const TimeValue kWSCFlashlightClickOut = 3211; + +static const TimeValue kWSCBumpIntoWallIn = 3211; +static const TimeValue kWSCBumpIntoWallOut = 3514; + +static const TimeValue kWSCCantTransportIn = 3514; +static const TimeValue kWSCCantTransportOut = 7791; + +static const TimeValue kHernandezNotHomeIn = 7791; +static const TimeValue kHernandezNotHomeOut = 10199; + +static const TimeValue kWashingtonNotHomeIn = 10199; +static const TimeValue kWashingtonNotHomeOut = 12649; + +static const TimeValue kSullivanNotHomeIn = 12649; +static const TimeValue kSullivanNotHomeOut = 15031; + +static const TimeValue kNakamuraNotHomeIn = 15031; +static const TimeValue kNakamuraNotHomeOut = 17545; + +static const TimeValue kGrailisNotHomeIn = 17545; +static const TimeValue kGrailisNotHomeOut = 19937; + +static const TimeValue kTheriaultNotHomeIn = 19937; +static const TimeValue kTheriaultNotHomeOut = 22395; + +static const TimeValue kGlennerNotHomeIn = 22395; +static const TimeValue kGlennerNotHomeOut = 24770; + +static const TimeValue kSinclairNotHomeIn = 24770; +static const TimeValue kSinclairNotHomeOut = 27328; + +static const TimeValue kWSCLabClosedIn = 27328; +static const TimeValue kWSCLabClosedOut = 28904; + +static const TimeValue kSlidingDoorCloseIn = 28904; +static const TimeValue kSlidingDoorCloseOut = 29295; + +static const TimeValue kSlimyDoorCloseIn = 29295; +static const TimeValue kSlimyDoorCloseOut = 29788; + +static const TimeValue kPaging1In = 29788; +static const TimeValue kPaging1Out = 32501; + +static const TimeValue kPaging2In = 32501; +static const TimeValue kPaging2Out = 34892; + +static const TimeValue kCheckInIn = 34892; +static const TimeValue kCheckInOut = 37789; + +static const TimeValue kDrinkAntidoteIn = 37789; +static const TimeValue kDrinkAntidoteOut = 39725; + +static const TimeScale kWSCMovieScale = 600; +static const TimeScale kWSCFramesPerSecond = 15; +static const TimeScale kWSCFrameDuration = 40; + +// Alternate IDs. +static const AlternateID kAltWSCNormal = 0; +static const AlternateID kAltWSCTookMachineGun = 1; +static const AlternateID kAltWSCW0ZDoorOpen = 2; +static const AlternateID kAltWSCPeopleAtW19North = 3; + +// Room IDs. +static const RoomID kWSC02 = 1; +static const RoomID kWSC03 = 4; +static const RoomID kWSC04 = 5; +static const RoomID kWSC06 = 6; +static const RoomID kWSC07 = 7; +static const RoomID kWSC08 = 8; +static const RoomID kWSC09 = 9; +static const RoomID kWSC10 = 10; +static const RoomID kWSC11 = 11; +static const RoomID kWSC13 = 12; +static const RoomID kWSC14 = 13; +static const RoomID kWSC15 = 14; +static const RoomID kWSC16 = 15; +static const RoomID kWSC17 = 16; +static const RoomID kWSC18 = 17; +static const RoomID kWSC19 = 18; +static const RoomID kWSC20 = 19; +static const RoomID kWSC21 = 20; +static const RoomID kWSC22 = 21; +static const RoomID kWSC23 = 22; +static const RoomID kWSC24 = 23; +static const RoomID kWSC25 = 24; +static const RoomID kWSC26 = 25; +static const RoomID kWSC27 = 26; +static const RoomID kWSC28 = 27; +static const RoomID kWSC29 = 28; +static const RoomID kWSC31 = 29; +static const RoomID kWSC32 = 30; +static const RoomID kWSC33 = 31; +static const RoomID kWSC34 = 32; +static const RoomID kWSC35 = 33; +static const RoomID kWSC36 = 34; +static const RoomID kWSC37 = 35; +static const RoomID kWSC38 = 36; +static const RoomID kWSC39 = 37; +static const RoomID kWSC40 = 38; +static const RoomID kWSC41 = 39; +static const RoomID kWSC42 = 40; +static const RoomID kWSC43 = 41; +static const RoomID kWSC44 = 42; +static const RoomID kWSC45 = 43; +static const RoomID kWSC46 = 44; +static const RoomID kWSC47 = 45; +static const RoomID kWSC48 = 46; +static const RoomID kWSC49 = 47; +static const RoomID kWSC50 = 48; +static const RoomID kWSC52 = 49; +static const RoomID kWSC53 = 50; +static const RoomID kWSC54 = 51; +static const RoomID kWSC55 = 52; +static const RoomID kWSC56 = 53; +static const RoomID kWSC57 = 54; +static const RoomID kWSC58 = 55; +static const RoomID kWSC60 = 56; +static const RoomID kWSC60East = 57; +static const RoomID kWSC60North = 58; +static const RoomID kWSC61 = 59; +static const RoomID kWSC61South = 60; +static const RoomID kWSC61West = 61; +static const RoomID kWSC63 = 63; +static const RoomID kWSC64 = 64; +static const RoomID kWSC65 = 65; +static const RoomID kWSC65Screen = 66; +static const RoomID kWSC66 = 67; +static const RoomID kWSC67 = 68; +static const RoomID kWSC68 = 69; +static const RoomID kWSC69 = 70; +static const RoomID kWSC70 = 71; +static const RoomID kWSC71 = 72; +static const RoomID kWSC72 = 73; +static const RoomID kWSC73 = 74; +static const RoomID kWSC74 = 75; +static const RoomID kWSC75 = 76; +static const RoomID kWSC0Z = 77; +static const RoomID kWSC76 = 78; +static const RoomID kWSC77 = 79; +static const RoomID kWSC78 = 80; +static const RoomID kWSC79 = 81; +static const RoomID kWSC80 = 82; +static const RoomID kWSC81 = 83; +static const RoomID kWSC82 = 84; +static const RoomID kWSC83 = 85; +static const RoomID kWSC84 = 86; +static const RoomID kWSC85 = 87; +static const RoomID kWSC86 = 88; +static const RoomID kWSC87 = 89; +static const RoomID kWSC88 = 90; +static const RoomID kWSC89 = 91; +static const RoomID kWSC90 = 92; +static const RoomID kWSC91 = 93; +static const RoomID kWSC92 = 94; +static const RoomID kWSC93 = 95; +static const RoomID kWSC94 = 96; +static const RoomID kWSC95 = 97; +static const RoomID kWSC96 = 98; +static const RoomID kWSC97 = 99; +static const RoomID kWSC98 = 100; +static const RoomID kWSCDeathRoom = 101; + +// Hot Spot Activation IDs. +static const HotSpotActivationID kActivationZoomedInToAnalyzer = 1; +static const HotSpotActivationID kActivationShotByRobot = 2; +static const HotSpotActivationID kActivationWarnedAboutPoison = 3; +static const HotSpotActivationID kActivationMorphScreenOff = 4; +static const HotSpotActivationID kActivationReadyForMorph = 5; +static const HotSpotActivationID kActivationMorphLooping = 6; +static const HotSpotActivationID kActivationMorphInterrupted = 7; +static const HotSpotActivationID kActivationW03NorthOff = 8; +static const HotSpotActivationID kActivationW03NorthReadyForInstructions = 9; +static const HotSpotActivationID kActivationW03NorthSawInstructions = 10; +static const HotSpotActivationID kActivationW03NorthInGame = 11; +static const HotSpotActivationID kActivationReadyForSynthesis = 12; +static const HotSpotActivationID kActivationSynthesizerLooping = 13; +static const HotSpotActivationID kActivationReadyForMap = 14; +static const HotSpotActivationID kActivationSinclairOfficeLocked = 15; +static const HotSpotActivationID kActivationW58SouthDoorLocked = 16; +static const HotSpotActivationID kActivationW61SouthOff = 17; +static const HotSpotActivationID kActivationW61SouthOn = 18; +static const HotSpotActivationID kActivationW61MessagesOff = 19; +static const HotSpotActivationID kActivationW61MessagesOn = 20; +static const HotSpotActivationID kActivationWSCRobotHeadOpen = 21; +static const HotSpotActivationID kActivationRobotTurning = 22; +static const HotSpotActivationID kActivationRobotDead = 23; +static const HotSpotActivationID kActivationRobotGone = 24; + +// Hot Spot IDs. +static const HotSpotID kWSCDropDartSpotID = 5000; +static const HotSpotID kWSCTurnOnAnalyzerSpotID = 5001; +static const HotSpotID kWSCAnalyzerScreenSpotID = 5002; +static const HotSpotID kWSCSpinRobotSpotID = 5003; +static const HotSpotID kWSC01YesSpotID = 5004; +static const HotSpotID kWSC01NoSpotID = 5005; +static const HotSpotID kWSC01AcknowledgeWarningSpotID = 5006; +static const HotSpotID kWSC02SouthMorphSpotID = 5007; +static const HotSpotID kWSC02SouthMessagesSpotID = 5008; +static const HotSpotID kWSC02SouthMorphOutSpotID = 5009; +static const HotSpotID kWSC02ActivateMorphScreenSpotID = 5010; +static const HotSpotID kWSC02SouthStartMorphSpotID = 5011; +static const HotSpotID kWSC02SouthInterruptMorphSpotID = 5012; +static const HotSpotID kWSC02SouthMorphFinishedSpotID = 5013; +static const HotSpotID kWSC02SouthTakeArgonSpotID = 5014; +static const HotSpotID kWSC02SouthMessagesOutSpotID = 5015; +static const HotSpotID kWSC02SouthTakeNitrogenSpotID = 5016; +static const HotSpotID kWSC02SouthPlayMessagesSpotID = 5017; +static const HotSpotID kWSC03NorthActivateScreenSpotID = 5018; +static const HotSpotID kWSC03NorthBuildMoleculeSpotID = 5019; +static const HotSpotID kWSC03NorthProceedSpotID = 5020; +static const HotSpotID kWSC03NorthMolecule1SpotID = 5021; +static const HotSpotID kWSC03NorthMolecule2SpotID = 5022; +static const HotSpotID kWSC03NorthMolecule3SpotID = 5023; +static const HotSpotID kWSC03NorthMolecule4SpotID = 5024; +static const HotSpotID kWSC03NorthMolecule5SpotID = 5025; +static const HotSpotID kWSC03NorthMolecule6SpotID = 5026; +static const HotSpotID kWSC03SouthActivateSynthesizerSpotID = 5027; +static const HotSpotID kWSC03SouthPickUpAntidoteSpotID = 5028; +static const HotSpotID kWSC07SouthMapSpotID = 5029; +static const HotSpotID kW42EastUnlockDoorSpotID = 5030; +static const HotSpotID kW56NorthMapSpotID = 5031; +static const HotSpotID kW58SouthPryDoorSpotID = 5032; +static const HotSpotID kWSC60EastSpotID = 5033; +static const HotSpotID kWSC60NorthSpotID = 5034; +static const HotSpotID kWSC60EastOutSpotID = 5035; +static const HotSpotID kWSC60NorthOutSpotID = 5036; +static const HotSpotID kWSC61EastSpotID = 5037; +static const HotSpotID kWSC61SouthSpotID = 5038; +static const HotSpotID kW61SouthMachineGunSpotID = 5039; +static const HotSpotID kW61SouthDropMachineGunSpotID = 5040; +static const HotSpotID kWSC61WestSpotID = 5041; +static const HotSpotID kWSC61SouthOutSpotID = 5042; +static const HotSpotID kW61SouthActivateSpotID = 5043; +static const HotSpotID kW61SmartAlloysSpotID = 5044; +static const HotSpotID kW61MorphingSpotID = 5045; +static const HotSpotID kW61TimeBendingSpotID = 5046; +static const HotSpotID kWSC61WestOutSpotID = 5047; +static const HotSpotID kW61TurnOnMessagesSpotID = 5048; +static const HotSpotID kW61WhiteMessageSpotID = 5049; +static const HotSpotID kW61WalchekMessageSpotID = 5050; +static const HotSpotID kWSC65SouthScreenSpotID = 5051; +static const HotSpotID kWSC65SouthScreenOutSpotID = 5052; +static const HotSpotID kW98RetinalChipSpotID = 5053; +static const HotSpotID kW98MapChipSpotID = 5054; +static const HotSpotID kW98OpticalChipSpotID = 5055; +static const HotSpotID kW98DropArgonSpotID = 5056; +static const HotSpotID kW98GrabCableSpotID = 5057; +static const HotSpotID kW98OpenRobotSpotID = 5058; +static const HotSpotID kW98StunGunSpotID = 5059; + +// Extra sequence IDs. +static const ExtraID kWSCArrivalFromTSA = 0; +static const ExtraID kWSCShotByRobot = 1; +static const ExtraID kWSCDartScan1 = 2; +static const ExtraID kWSCDartScan2 = 3; +static const ExtraID kWSCDartScanNo = 4; +static const ExtraID kWSCDartScan3 = 5; +static const ExtraID kWSCAnalyzerPowerUp = 6; +static const ExtraID kWSCAnalyzerPowerUpWithDart = 7; +static const ExtraID kWSCDropDartIntoAnalyzer = 8; +static const ExtraID kWSCAnalyzeDart = 9; +static const ExtraID kWSCZoomOutFromAnalyzer = 10; +static const ExtraID kWSCSpinRobot = 11; +static const ExtraID kWSC02MorphZoomNoArgon = 12; +static const ExtraID kWSC02MessagesZoomNoNitrogen = 13; +static const ExtraID kWSC02ZoomOutNoArgon = 14; +static const ExtraID kWSC02TurnOnMorphScreen = 15; +static const ExtraID kWSC02DropToMorphExperiment = 16; +static const ExtraID kWSC02MorphLoop = 17; +static const ExtraID kWSC02MorphInterruption = 18; +static const ExtraID kWSC02MorphFinished = 19; +static const ExtraID kWSC02TurnOffMorphScreen = 20; +static const ExtraID kWSC02SouthViewNoArgon = 21; +static const ExtraID kMessagesMovedToOffice = 22; +static const ExtraID kMessagesOff = 23; +static const ExtraID kMessagesZoomOutNoNitrogen = 24; +static const ExtraID kMessagesMovedToOfficeNoNitrogen = 25; +static const ExtraID kMessagesOffNoNitrogen = 26; +static const ExtraID kMessagesViewNoNitrogen = 27; +static const ExtraID kMessagesViewMachineOnNoNitrogen = 28; +static const ExtraID kW03NorthActivate = 29; +static const ExtraID kW03NorthGetData = 30; +static const ExtraID kW03NorthInstructions = 31; +static const ExtraID kW03NorthPrepMolecule1 = 32; +static const ExtraID kW03NorthPrepMolecule2 = 33; +static const ExtraID kW03NorthPrepMolecule3 = 34; +static const ExtraID kW03NorthFinishSynthesis = 35; +static const ExtraID kW03SouthCreateAntidote = 36; +static const ExtraID kW03SouthAntidoteLoop = 37; +static const ExtraID kW03SouthDeactivate = 38; +static const ExtraID kW03SouthViewNoAntidote = 39; +static const ExtraID kWSC07SouthMap = 40; +static const ExtraID kW17WestPeopleCrossing = 41; +static const ExtraID kW17WestPeopleCrossingView = 42; +static const ExtraID kW21SouthPeopleCrossing = 43; +static const ExtraID kW24SouthPeopleCrossing = 44; +static const ExtraID kW34EastPeopleCrossing = 45; +static const ExtraID kW36WestPeopleCrossing = 46; +static const ExtraID kW38NorthPeopleCrossing = 47; +static const ExtraID kW46SouthPeopleCrossing = 48; +static const ExtraID kW49NorthPeopleCrossing = 49; +static const ExtraID kW49NorthPeopleCrossingView = 50; +static const ExtraID kWSC56SouthMap = 51; +static const ExtraID kNerdAtTheDoor1 = 52; +static const ExtraID kNerdAtTheDoor2 = 53; +static const ExtraID kW61SouthZoomInNoGun = 54; +static const ExtraID kW61Brochure = 55; +static const ExtraID kW61SouthScreenOnWithGun = 56; +static const ExtraID kW61SouthScreenOffWithGun = 57; +static const ExtraID kW61SouthSmartAlloysWithGun = 58; +static const ExtraID kW61SouthMorphingWithGun = 59; +static const ExtraID kW61SouthTimeBendingWithGun = 60; +static const ExtraID kW61SouthZoomOutNoGun = 61; +static const ExtraID kW61SouthScreenOnNoGun = 62; +static const ExtraID kW61SouthScreenOffNoGun = 63; +static const ExtraID kW61SouthSmartAlloysNoGun = 64; +static const ExtraID kW61SouthMorphingNoGun = 65; +static const ExtraID kW61SouthTimeBendingNoGun = 66; +static const ExtraID kW61MessagesOn = 67; +static const ExtraID kW61MessagesOff = 68; +static const ExtraID kW61WhiteMessage = 69; +static const ExtraID kW61WalchekMessage = 70; +static const ExtraID kW61WalchekEasterEgg1 = 71; +static const ExtraID kW62SouthPlasmaRobotAppears = 72; +static const ExtraID kW62ZoomToRobot = 73; +static const ExtraID kW62ZoomOutFromRobot = 74; +static const ExtraID kW62PlasmaDodgeSurvive = 75; +static const ExtraID kW62PlasmaDodgeDie = 76; +static const ExtraID kW65SouthSinclairLecture = 77; +static const ExtraID kW73WestPeopleCrossing = 78; +static const ExtraID kW73WestPeopleCrossingView = 79; +static const ExtraID kW0ZSpottedByWomen = 80; +static const ExtraID kW95RobotShoots = 81; +static const ExtraID kW98MorphsToRobot = 82; +static const ExtraID kW98RobotShoots = 83; +static const ExtraID kW98RobotShocked = 84; +static const ExtraID kW98RobotGassed = 85; +static const ExtraID kW98RobotHeadOpensDark = 86; +static const ExtraID kW98RobotHead000Dark = 87; +static const ExtraID kW98RobotHead001Dark = 88; +static const ExtraID kW98RobotHead010Dark = 89; +static const ExtraID kW98RobotHead011Dark = 90; +static const ExtraID kW98RobotHead100Dark = 91; +static const ExtraID kW98RobotHead101Dark = 92; +static const ExtraID kW98RobotHead110Dark = 93; +static const ExtraID kW98RobotHead111Dark = 94; +static const ExtraID kW98RobotHeadClosesDark = 95; +static const ExtraID kW98WestViewWithGunDark = 96; +static const ExtraID kW98WestViewNoGunDark = 97; +static const ExtraID kW98RobotHeadOpensLight = 98; +static const ExtraID kW98RobotHead000Light = 99; +static const ExtraID kW98RobotHead001Light = 100; +static const ExtraID kW98RobotHead010Light = 101; +static const ExtraID kW98RobotHead011Light = 102; +static const ExtraID kW98RobotHead100Light = 103; +static const ExtraID kW98RobotHead101Light = 104; +static const ExtraID kW98RobotHead110Light = 105; +static const ExtraID kW98RobotHead111Light = 106; +static const ExtraID kW98RobotHeadClosesLight = 107; +static const ExtraID kW98WestViewWithGunLight = 108; +static const ExtraID kW98WestViewNoGunLight = 109; + +static const CoordType kMoleculesMovieLeft = kNavAreaLeft + 112; +static const CoordType kMoleculesMovieTop = kNavAreaTop + 40; + +WSC::WSC(InputHandler *nextHandler, PegasusEngine *owner) : Neighborhood(nextHandler, owner, "WSC", kWSCID), + _moleculesMovie(kNoDisplayElement) { + setIsItemTaken(kArgonCanister); + setIsItemTaken(kSinclairKey); + setIsItemTaken(kNitrogenCanister); + setIsItemTaken(kPoisonDart); + setIsItemTaken(kAntidote); + setIsItemTaken(kMachineGun); + setIsItemTaken(kStunGun); + + GameState.setTakenItemID(kArgonPickup, GameState.isTakenItemID(kArgonCanister) && + GameState.isTakenItemID(kSinclairKey)); +} + +uint16 WSC::getDateResID() const { + return kDate2310ID; +} + +void WSC::init() { + Neighborhood::init(); + + _cachedZoomSpot = 0; + _argonSprite = 0; + + // HACK: Fix the drag item for picking up the Sinclair Key Card + HotspotInfoTable::Entry *entry = findHotspotEntry(kWSC02SouthTakeArgonSpotID); + entry->hotspotItem = kArgonPickup; +} + +void WSC::flushGameState() { + g_energyMonitor->saveCurrentEnergyValue(); +} + +void WSC::start() { + if (g_energyMonitor) { + g_energyMonitor->stopEnergyDraining(); + g_energyMonitor->restoreLastEnergyValue(); + _vm->resetEnergyDeathReason(); + g_energyMonitor->startEnergyDraining(); + } + + if (!GameState.getWSCDidPlasmaDodge()) + forceStridingStop(kWSC58, kSouth, kAltWSCNormal); + + Neighborhood::start(); +} + +class PryDoorMessage : public AIPlayMessageAction { +public: + PryDoorMessage() : AIPlayMessageAction("Images/AI/WSC/XW59SD3", false) {} + +protected: + virtual void performAIAction(AIRule *); +}; + +void PryDoorMessage::performAIAction(AIRule *rule) { + if (((PegasusEngine *)g_engine)->playerHasItemID(kShieldBiochip) + && ((PegasusEngine *)g_engine)->getCurrentBiochip()->getObjectID() != kShieldBiochip) + AIPlayMessageAction::performAIAction(rule); +} + +void WSC::setUpAIRules() { + Neighborhood::setUpAIRules(); + + if (g_AIArea) { + AIPlayMessageAction *messageAction = new AIPlayMessageAction("Images/AI/WSC/XW1WB1", false); + AILastExtraCondition *extraCondition = new AILastExtraCondition(kWSCDartScan1); + AIRule *rule = new AIRule(extraCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB5A", false); + AILocationCondition *locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC06, kNorth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB5A", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC10, kWest)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB5A", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC28, kWest)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB5A", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC49, kWest)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB5A", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC65, kSouth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB5A", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC73, kSouth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB5A", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC79, kWest)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/WSC/XW59SD1", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC58, kSouth)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + + PryDoorMessage *pryDoorMessage = new PryDoorMessage(); + AIDoorOpenedCondition *doorCondition = new AIDoorOpenedCondition(MakeRoomView(kWSC58, kSouth)); + rule = new AIRule(doorCondition, pryDoorMessage); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/WSC/XW61E", false); + AIHasItemCondition *itemCondition = new AIHasItemCondition(kMachineGun); + rule = new AIRule(itemCondition, messageAction); + g_AIArea->addAIRule(rule); + + messageAction = new AIPlayMessageAction("Images/AI/Globals/XGLOB1E", false); + locCondition = new AILocationCondition(1); + locCondition->addLocation(MakeRoomView(kWSC95, kWest)); + rule = new AIRule(locCondition, messageAction); + g_AIArea->addAIRule(rule); + } +} + +Common::String WSC::getBriefingMovie() { + return "Images/AI/WSC/XWO"; +} + +Common::String WSC::getEnvScanMovie() { + RoomID room = GameState.getCurrentRoom(); + + if (room >= kWSC01 && room <= kWSC04) + return "Images/AI/WSC/XWE1"; + else if (room >= kWSC06 && room <= kWSC58) + return "Images/AI/WSC/XWE2"; + else if (room >= kWSC60 && room <= kWSC61West) + return "Images/AI/WSC/XWE3"; + else if (room >= kWSC64 && room <= kWSC98) + return "Images/AI/WSC/XWE4"; + + return "Images/AI/WSC/XWE5"; +} + +uint WSC::getNumHints() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC10, kWest): + case MakeRoomView(kWSC28, kWest): + case MakeRoomView(kWSC49, kWest): + case MakeRoomView(kWSC65, kSouth): + case MakeRoomView(kWSC75, kSouth): + case MakeRoomView(kWSC79, kWest): + return 2; + case MakeRoomView(kWSC02, kSouth): + if (_vm->getEnergyDeathReason() == kDeathDidntStopPoison && + !_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag) && + !GameState.getWSCDesignedAntidote()) + return 3; + else if (!GameState.getScoringGotNitrogenCanister() || + !GameState.getScoringGotSinclairKey()) + return 1; + break; + case MakeRoomView(kWSC03, kNorth): + if (inSynthesizerGame() || (_vm->getEnergyDeathReason() == kDeathDidntStopPoison && + !_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag) && + !GameState.getWSCDesignedAntidote())) + return 3; + break; + case MakeRoomView(kWSC01, kNorth): + case MakeRoomView(kWSC01, kSouth): + case MakeRoomView(kWSC01, kEast): + case MakeRoomView(kWSC01, kWest): + case MakeRoomView(kWSC02, kNorth): + case MakeRoomView(kWSC02, kEast): + case MakeRoomView(kWSC02, kWest): + case MakeRoomView(kWSC02Morph, kNorth): + case MakeRoomView(kWSC02Morph, kEast): + case MakeRoomView(kWSC02Morph, kWest): + case MakeRoomView(kWSC02Messages, kNorth): + case MakeRoomView(kWSC02Messages, kEast): + case MakeRoomView(kWSC02Messages, kWest): + case MakeRoomView(kWSC03, kSouth): + case MakeRoomView(kWSC03, kEast): + case MakeRoomView(kWSC03, kWest): + case MakeRoomView(kWSC04, kNorth): + case MakeRoomView(kWSC04, kSouth): + case MakeRoomView(kWSC04, kEast): + case MakeRoomView(kWSC04, kWest): + if (_vm->getEnergyDeathReason() == kDeathDidntStopPoison && + !_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag) && + !GameState.getWSCDesignedAntidote()) + return 3; + break; + case MakeRoomView(kWSC02Messages, kSouth): + if (_vm->getEnergyDeathReason() == kDeathDidntStopPoison && + !_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag) && + !GameState.getWSCDesignedAntidote()) + return 3; + else if (!GameState.getScoringGotNitrogenCanister()) + return 1; + break; + case MakeRoomView(kWSC02Morph, kSouth): + if (_vm->getEnergyDeathReason() == kDeathDidntStopPoison && + !_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag) && + !GameState.getWSCDesignedAntidote()) + return 3; + else if (!GameState.getScoringGotSinclairKey()) + return 1; + break; + case MakeRoomView(kWSC42, kEast): + if (!GameState.isCurrentDoorOpen()) + return 1; + break; + case MakeRoomView(kWSC58, kSouth): + if (GameState.isCurrentDoorOpen()) { + if (GameState.getWSCDidPlasmaDodge()) + return 0; + else + return 1; + } else if (_vm->playerHasItemID(kCrowbar)) + return 2; + + return 3; + case MakeRoomView(kWSC61, kEast): + if (!GameState.getScoringSawBrochure()) + return 1; + break; + case MakeRoomView(kWSC61, kSouth): + if (!GameState.getScoringSawSinclairEntry1() || + !GameState.getScoringSawSinclairEntry2() || + !GameState.getScoringSawSinclairEntry3()) + return 1; + break; + case MakeRoomView(kWSC98, kWest): + if (getCurrentActivation() == kActivationRobotTurning) + return 1; + break; + } + + return 0; +} + +Common::String WSC::getHintMovie(uint hintNum) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC10, kWest): + case MakeRoomView(kWSC28, kWest): + case MakeRoomView(kWSC49, kWest): + case MakeRoomView(kWSC65, kSouth): + case MakeRoomView(kWSC75, kSouth): + case MakeRoomView(kWSC79, kWest): + if (hintNum == 1) + return "Images/AI/Globals/XGLOB5B"; + + return "Images/AI/Globals/XGLOB5C"; + case MakeRoomView(kWSC02, kSouth): + if (_vm->getEnergyDeathReason() == kDeathDidntStopPoison && + !_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag) && + !GameState.getWSCDesignedAntidote()) + return Common::String::format("Images/AI/WSC/XWPH%d", hintNum); + + return "Images/AI/Globals/XGLOB1C"; + case MakeRoomView(kWSC61, kEast): + case MakeRoomView(kWSC61, kSouth): + return "Images/AI/Globals/XGLOB1C"; + case MakeRoomView(kWSC42, kEast): + if (_vm->playerHasItemID(kSinclairKey)) + return "Images/AI/Globals/XGLOB1A"; + + return "Images/AI/Globals/XGLOB2C"; + case MakeRoomView(kWSC58, kSouth): + switch (hintNum) { + case 1: + if (GameState.isCurrentDoorOpen()) { + // Only get here if we haven't done the plasma dodge game... + if (_vm->playerHasItemID(kShieldBiochip)) + return "Images/AI/Globals/XGLOB1A"; + else + return "Images/AI/Globals/XGLOB3F"; + } else if (_vm->playerHasItemID(kCrowbar)) { + return "Images/AI/Globals/XGLOB1A"; + } + + return "Images/AI/Globals/XGLOB1B"; + case 2: + // Only get here if the door is still locked... + if (_vm->playerHasItemID(kCrowbar)) + return "Images/AI/WSC/XW59SD2"; + + return "Images/AI/Globals/XGLOB2D"; + case 3: + // Only get here if the door is still locked and we don't have the + // crowbar... + return "Images/AI/WSC/XW59SD2"; + } + break; + case MakeRoomView(kWSC03, kNorth): + if (inSynthesizerGame()) + return Common::String::format("Images/AI/WSC/XW03NH%d", hintNum); + + return Common::String::format("Images/AI/WSC/XWPH%d", hintNum); + case MakeRoomView(kWSC01, kNorth): + case MakeRoomView(kWSC01, kSouth): + case MakeRoomView(kWSC01, kEast): + case MakeRoomView(kWSC01, kWest): + case MakeRoomView(kWSC02, kNorth): + case MakeRoomView(kWSC02, kEast): + case MakeRoomView(kWSC02, kWest): + case MakeRoomView(kWSC02Morph, kNorth): + case MakeRoomView(kWSC02Morph, kEast): + case MakeRoomView(kWSC02Morph, kWest): + case MakeRoomView(kWSC02Messages, kNorth): + case MakeRoomView(kWSC02Messages, kEast): + case MakeRoomView(kWSC02Messages, kWest): + case MakeRoomView(kWSC03, kSouth): + case MakeRoomView(kWSC03, kEast): + case MakeRoomView(kWSC03, kWest): + case MakeRoomView(kWSC04, kNorth): + case MakeRoomView(kWSC04, kSouth): + case MakeRoomView(kWSC04, kEast): + case MakeRoomView(kWSC04, kWest): + // analyzer hint + return Common::String::format("Images/AI/WSC/XWPH%d", hintNum); + case MakeRoomView(kWSC02Messages, kSouth): + case MakeRoomView(kWSC02Morph, kSouth): + if (_vm->getEnergyDeathReason() == kDeathDidntStopPoison && + !_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag) && + !GameState.getWSCDesignedAntidote()) + // analyzer hint + return Common::String::format("Images/AI/WSC/XWPH%d", hintNum); + + return "Images/AI/Globals/XGLOB1C"; + case MakeRoomView(kWSC98, kWest): + return "Images/AI/WSC/XW98WH2"; + } + + return ""; +} + +void WSC::prepareForAIHint(const Common::String &movieName) { + if (movieName == "Images/AI/WSC/XW98WH2" && isEventTimerRunning()) + pauseTimer(); +} + +void WSC::cleanUpAfterAIHint(const Common::String &movieName) { + if (movieName == "Images/AI/WSC/XW98WH2" && isEventTimerRunning()) + resumeTimer(); +} + +bool WSC::okayToJump() { + if (GameState.getWSCPoisoned()) { + die(kDeathDidntStopPoison); + return false; + } + + bool result = Neighborhood::okayToJump(); + if (!result) + playSpotSoundSync(kWSCCantTransportIn, kWSCCantTransportOut); + + return result; +} + +TimeValue WSC::getViewTime(const RoomID room, const DirectionConstant direction) { + ExtraID viewExtra = 0xffffffff; + ExtraTable::Entry extra; + + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kWSC01, kWest): + if (!GameState.getWSCSeenTimeStream()) { + getExtraEntry(kWSCArrivalFromTSA, extra); + return extra.movieStart; + } else if (GameState.getWSCPoisoned() && !GameState.getWSCAnsweredAboutDart()) { + viewExtra = kWSCDartScan1; + } + break; + case MakeRoomView(kWSC02Morph, kSouth): + if (GameState.isTakenItemID(kArgonPickup) || GameState.isTakenItemID(kArgonCanister)) + viewExtra = kWSC02SouthViewNoArgon; + break; + case MakeRoomView(kWSC02Messages, kSouth): + if (GameState.isTakenItemID(kNitrogenCanister)) { + if (_privateFlags.getFlag(kWSCPrivateLabMessagesOpenFlag)) + viewExtra = kMessagesViewMachineOnNoNitrogen; + else + viewExtra = kMessagesViewNoNitrogen; + } + break; + case MakeRoomView(kWSC03, kSouth): + if (_privateFlags.getFlag(kWSCDraggingAntidoteFlag)) + viewExtra = kW03SouthViewNoAntidote; + break; + case MakeRoomView(kWSC17, kWest): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt17WestFlag)) + viewExtra = kW17WestPeopleCrossingView; + break; + case MakeRoomView(kWSC49, kNorth): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt49NorthFlag)) + viewExtra = kW49NorthPeopleCrossingView; + break; + case MakeRoomView(kWSC73, kWest): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt73WestFlag)) + viewExtra = kW73WestPeopleCrossingView; + break; + case MakeRoomView(kWSC98, kWest): + if (GameState.getWSCRobotDead()) { + if (GameState.getWSCRobotGone()) { + if (GameState.isTakenItemID(kStunGun)) { + if (GameState.getWSCCatwalkDark()) + viewExtra = kW98WestViewNoGunDark; + else + viewExtra = kW98WestViewNoGunLight; + } else { + if (GameState.getWSCCatwalkDark()) + viewExtra = kW98WestViewWithGunDark; + else + viewExtra = kW98WestViewWithGunLight; + } + } else if (_privateFlags.getFlag(kWSCPrivateRobotHeadOpenFlag)) { + if (GameState.getWSCCatwalkDark()) + viewExtra = kW98RobotHead111Dark; + else + viewExtra = kW98RobotHead111Light; + + if (_privateFlags.getFlag(kWSCPrivateGotRetScanChipFlag)) + viewExtra -= 1; + if (_privateFlags.getFlag(kWSCPrivateGotMapChipFlag)) + viewExtra -= 2; + if (_privateFlags.getFlag(kWSCPrivateGotOpticalChipFlag)) + viewExtra -= 4; + } else if (GameState.getWSCRobotDead()) { + // Should only happen on loading a saved game, so it can take its time. + if (GameState.getWSCCatwalkDark()) + viewExtra = kW98RobotShocked; + else + viewExtra = kW98RobotGassed; + } + } + break; + } + + if (viewExtra != 0xffffffff) { + getExtraEntry(viewExtra, extra); + return extra.movieEnd - 1; + } + + return Neighborhood::getViewTime(room, direction); +} + +void WSC::findSpotEntry(const RoomID room, const DirectionConstant direction, SpotFlags flags, SpotTable::Entry &spotEntry) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kWSC58, kSouth): + case MakeRoomView(kWSC79, kWest): + if ((flags & kSpotOnTurnMask) != 0) { + spotEntry.clear(); + return; + } + break; + } + + Neighborhood::findSpotEntry(room, direction, flags, spotEntry); +} + +void WSC::getZoomEntry(const HotSpotID id, ZoomTable::Entry &zoomEntry) { + Neighborhood::getZoomEntry(id, zoomEntry); + + ExtraTable::Entry extra; + ExtraID zoomExtra = 0xffffffff; + + switch (id) { + case kWSC02SouthMessagesSpotID: + if (GameState.isTakenItemID(kNitrogenCanister)) + zoomExtra = kWSC02MessagesZoomNoNitrogen; + break; + case kWSC02SouthMessagesOutSpotID: + if (GameState.isTakenItemID(kNitrogenCanister)) + zoomExtra = kMessagesZoomOutNoNitrogen; + break; + case kWSC02SouthMorphSpotID: + if (GameState.isTakenItemID(kArgonCanister)) + zoomExtra = kWSC02MorphZoomNoArgon; + break; + case kWSC02SouthMorphOutSpotID: + if (GameState.isTakenItemID(kArgonCanister)) + zoomExtra = kWSC02ZoomOutNoArgon; + break; + case kWSC61SouthSpotID: + if (GameState.isTakenItemID(kMachineGun)) + zoomExtra = kW61SouthZoomInNoGun; + break; + case kWSC61SouthOutSpotID: + if (GameState.isTakenItemID(kMachineGun)) + zoomExtra = kW61SouthZoomOutNoGun; + break; + } + + if (zoomExtra != 0xffffffff) { + getExtraEntry(zoomExtra, extra); + zoomEntry.movieStart = extra.movieStart; + zoomEntry.movieEnd = extra.movieEnd; + } +} + +void WSC::getExtraEntry(const uint32 id, ExtraTable::Entry &extraEntry) { + switch (id) { + case kWSCZoomOutFromAnalyzer: + Neighborhood::getExtraEntry(kWSCZoomOutFromAnalyzer, extraEntry); + extraEntry.movieEnd = extraEntry.movieStart + 14 * kWSCFrameDuration; + break; + case kW61WalchekMessage: + if (GameState.getEasterEgg()) + Neighborhood::getExtraEntry(kW61WalchekEasterEgg1, extraEntry); + else + Neighborhood::getExtraEntry(id, extraEntry); + break; + case kW61SouthScreenOnWithGun: + if (GameState.isTakenItemID(kMachineGun)) + Neighborhood::getExtraEntry(id, extraEntry); + else + Neighborhood::getExtraEntry(kW61SouthScreenOnNoGun, extraEntry); + break; + case kW61SouthSmartAlloysWithGun: + if (GameState.isTakenItemID(kMachineGun)) + Neighborhood::getExtraEntry(id, extraEntry); + else + Neighborhood::getExtraEntry(kW61SouthSmartAlloysNoGun, extraEntry); + break; + case kW61SouthMorphingWithGun: + if (GameState.isTakenItemID(kMachineGun)) + Neighborhood::getExtraEntry(id, extraEntry); + else + Neighborhood::getExtraEntry(kW61SouthMorphingNoGun, extraEntry); + break; + case kW61SouthTimeBendingWithGun: + if (GameState.isTakenItemID(kMachineGun)) + Neighborhood::getExtraEntry(id, extraEntry); + else + Neighborhood::getExtraEntry(kW61SouthTimeBendingNoGun, extraEntry); + break; + case kW98RobotHeadOpensLight: + if (GameState.getWSCCatwalkDark()) + Neighborhood::getExtraEntry(kW98RobotHeadOpensDark, extraEntry); + else + Neighborhood::getExtraEntry(id, extraEntry); + break; + default: + Neighborhood::getExtraEntry(id, extraEntry); + break; + } +} + +CanMoveForwardReason WSC::canMoveForward(ExitTable::Entry &entry) { + if (GameState.getCurrentRoomAndView() == MakeRoomView(kWSC01, kWest) && + getCurrentActivation() != kActivateHotSpotAlways) + return kCantMoveWatchingDiagnosis; + + return Neighborhood::canMoveForward(entry); +} + +// Also add cases here for compound analyzer... +CanTurnReason WSC::canTurn(TurnDirection turnDirection, DirectionConstant &nextDir) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC01, kWest): + if (getCurrentActivation() != kActivateHotSpotAlways) + return kCantTurnWatchingDiagnosis; + break; + case MakeRoomView(kWSC01, kEast): + if (getCurrentActivation() != kActivateHotSpotAlways) + return kCantTurnWatchingAnalysis; + break; + case MakeRoomView(kWSC03, kNorth): + if (_privateFlags.getFlag(kWSCPrivateInMoleculeGameFlag)) + return kCantTurnInMoleculeGame; + break; + } + + return Neighborhood::canTurn(turnDirection, nextDir); +} + +CanOpenDoorReason WSC::canOpenDoor(DoorTable::Entry &entry) { + switch (GameState.getCurrentRoom()) { + case kWSC42: + if (!_privateFlags.getFlag(kWSCPrivateSinclairOfficeOpenFlag)) + return kCantOpenLocked; + break; + case kWSC58: + if (!_privateFlags.getFlag(kWSCPrivate58SouthOpenFlag)) + return kCantOpenLocked; + break; + } + + return Neighborhood::canOpenDoor(entry); +} + +void WSC::bumpIntoWall() { + requestSpotSound(kWSCBumpIntoWallIn, kWSCBumpIntoWallOut, kFilterAllInput, 0); + Neighborhood::bumpIntoWall(); +} + +void WSC::closeDoorOffScreen(const RoomID room, const DirectionConstant) { + Item *keyCard; + + switch (room) { + case kWSC58: + case kWSC62: + case kWSC63: + case kWSC64: + case kWSC85: + case kWSC86: + case kWSC88: + case kWSC89: + playSpotSoundSync(kSlidingDoorCloseIn, kSlidingDoorCloseOut); + break; + case kWSC81: + case kWSC82: + case kWSC92: + case kWSC93: + keyCard = _vm->getAllItems().findItemByID(kKeyCard); + if (keyCard->getItemState() == kFlashlightOn && (GameState.getCurrentRoom() == kWSC81 || + GameState.getCurrentRoom() == kWSC93)) { + keyCard->setItemState(kFlashlightOff); + playSpotSoundSync(kWSCFlashlightClickIn, kWSCFlashlightClickOut); + } else if (keyCard->getItemState() == kFlashlightOff && (GameState.getCurrentRoom() == kWSC82 || + GameState.getCurrentRoom() == kWSC92)) { + keyCard->setItemState(kFlashlightOn); + playSpotSoundSync(kWSCFlashlightClickIn, kWSCFlashlightClickOut); + } + + playSpotSoundSync(kSlimyDoorCloseIn, kSlimyDoorCloseOut); + break; + default: + playSpotSoundSync(kSlimyDoorCloseIn, kSlimyDoorCloseOut); + break; + } +} + +void WSC::cantMoveThatWay(CanMoveForwardReason reason) { + if (reason != kCantMoveWatchingDiagnosis) + Neighborhood::cantMoveThatWay(reason); +} + +void WSC::cantOpenDoor(CanOpenDoorReason reason) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC22, kWest): + playSpotSoundSync(kNakamuraNotHomeIn, kNakamuraNotHomeOut); + break; + case MakeRoomView(kWSC23, kEast): + playSpotSoundSync(kHernandezNotHomeIn, kHernandezNotHomeOut); + break; + case MakeRoomView(kWSC26, kWest): + playSpotSoundSync(kGrailisNotHomeIn, kGrailisNotHomeOut); + break; + case MakeRoomView(kWSC27, kEast): + playSpotSoundSync(kWashingtonNotHomeIn, kWashingtonNotHomeOut); + break; + case MakeRoomView(kWSC32, kWest): + playSpotSoundSync(kTheriaultNotHomeIn, kTheriaultNotHomeOut); + break; + case MakeRoomView(kWSC33, kEast): + playSpotSoundSync(kSullivanNotHomeIn, kSullivanNotHomeOut); + break; + case MakeRoomView(kWSC41, kWest): + playSpotSoundSync(kGlennerNotHomeIn, kGlennerNotHomeOut); + break; + case MakeRoomView(kWSC42, kEast): + playSpotSoundSync(kSinclairNotHomeIn, kSinclairNotHomeOut); + break; + case MakeRoomView(kWSC15, kWest): + case MakeRoomView(kWSC25, kWest): + case MakeRoomView(kWSC33, kWest): + case MakeRoomView(kWSC41, kEast): + case MakeRoomView(kWSC46, kWest): + playSpotSoundSync(kWSCLabClosedIn, kWSCLabClosedOut); + break; + default: + Neighborhood::cantOpenDoor(reason); + break; + } +} + +void WSC::doorOpened() { + Neighborhood::doorOpened(); + + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC42, kEast): + _vm->addItemToInventory((InventoryItem *)_vm->getAllItems().findItemByID(kSinclairKey)); + break; + case MakeRoomView(kWSC58, kSouth): + GameState.setScoringUsedCrowBarInWSC(); + _vm->addItemToInventory((InventoryItem *)_vm->getAllItems().findItemByID(kCrowbar)); + break; + case MakeRoomView(kWSC06, kNorth): + case MakeRoomView(kWSC79, kWest): + die(kDeathArrestedInWSC); + break; + case MakeRoomView(kWSC60, kWest): + if (_vm->itemInInventory(kMachineGun)) + startExtraSequence(kNerdAtTheDoor2, kExtraCompletedFlag, kFilterNoInput); + else if (!GameState.getWSCSeenNerd()) + startExtraSequence(kNerdAtTheDoor1, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC95, kWest): + GameState.setScoringOpenedCatwalk(); + scheduleEvent(kGawkAtRobotTime, 1, kTimerEventPlayerGawkingAtRobot); + break; + } +} + +void WSC::turnLeft() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC17, kNorth): + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt17WestFlag) && _vm->getRandomNumber(2) == 0) + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt17WestFlag, true); + break; + case MakeRoomView(kWSC49, kEast): + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt49NorthFlag) && _vm->getRandomNumber(2) == 0) + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt49NorthFlag, true); + break; + case MakeRoomView(kWSC73, kNorth): + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt73WestFlag) && _vm->getRandomNumber(2) == 0) + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt73WestFlag, true); + break; + case MakeRoomView(kWSC73, kWest): + if (!GameState.getWSCBeenAtWSC93()) + setCurrentAlternate(kAltWSCW0ZDoorOpen); + break; + case MakeRoomView(kWSC95, kWest): + cancelEvent(); + break; + } + + Neighborhood::turnLeft(); +} + +void WSC::turnRight() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC17, kSouth): + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt17WestFlag) && _vm->getRandomNumber(2) == 0) + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt17WestFlag, true); + break; + case MakeRoomView(kWSC49, kWest): + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt49NorthFlag) && _vm->getRandomNumber(2) == 0) + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt49NorthFlag, true); + break; + case MakeRoomView(kWSC73, kSouth): + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt73WestFlag) && _vm->getRandomNumber(2) == 0) + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt73WestFlag, true); + break; + case MakeRoomView(kWSC73, kEast): + if (!GameState.getWSCBeenAtWSC93()) + setCurrentAlternate(kAltWSCW0ZDoorOpen); + break; + case MakeRoomView(kWSC95, kWest): + cancelEvent(); + break; + } + + Neighborhood::turnRight(); +} + +void WSC::moveForward() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC19, kNorth): + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt19NorthFlag)) + setCurrentAlternate(kAltWSCPeopleAtW19North); + break; + case MakeRoomView(kWSC95, kWest): + cancelEvent(); + break; + } + + Neighborhood::moveForward(); +} + +void WSC::zoomTo(const Hotspot *hotspot) { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC02Messages, kSouth): + if (_privateFlags.getFlag(kWSCPrivateLabMessagesOpenFlag)) { + _cachedZoomSpot = hotspot; + if (GameState.isTakenItemID(kNitrogenCanister)) + startExtraSequence(kMessagesOffNoNitrogen, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMessagesOff, kExtraCompletedFlag, kFilterNoInput); + return; + } + break; + case MakeRoomView(kWSC61West, kWest): + if (GameState.getWSCOfficeMessagesOpen()) { + _cachedZoomSpot = hotspot; + startExtraSequence(kW61MessagesOff, kExtraCompletedFlag, kFilterNoInput); + return; + } + break; + case MakeRoomView(kWSC61South, kSouth): + if (_privateFlags.getFlag(kWSCPrivateOfficeLogOpenFlag)) { + _cachedZoomSpot = hotspot; + if (GameState.isTakenItemID(kMachineGun)) + startExtraSequence(kW61SouthScreenOffNoGun, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kW61SouthScreenOffWithGun, kExtraCompletedFlag, kFilterNoInput); + return; + } + break; + } + + Neighborhood::zoomTo(hotspot); +} + +void WSC::startExtraSequence(const ExtraID extraID, const NotificationFlags flags, const InputBits interruptionFilter) { + if (extraID == kW61Brochure) + loadLoopSound1(""); + + Neighborhood::startExtraSequence(extraID, flags, interruptionFilter); +} + +int16 WSC::getStaticCompassAngle(const RoomID room, const DirectionConstant dir) { + int16 angle = Neighborhood::getStaticCompassAngle(room, dir); + + switch (room) { + case kWSC02Messages: + angle -= 50; + break; + case kWSC02Morph: + angle += 5; + break; + case kWSC60East: + angle -= 10; + break; + case kWSC66: + angle -= kAuditoriumAngleOffset; + break; + case kWSC67: + angle += kAuditoriumAngleOffset; + break; + case kWSC68: + angle -= kAuditoriumAngleOffset * 2; + break; + case kWSC69: + angle += kAuditoriumAngleOffset * 2; + break; + case kWSC70: + angle -= kAuditoriumAngleOffset * 3; + break; + case kWSC71: + angle += kAuditoriumAngleOffset * 3; + break; + case kWSC72: + if (dir == kEast || dir == kWest) + angle -= kAuditoriumAngleOffset * 4; + break; + case kWSC73: + if (dir == kEast || dir == kWest) + angle += kAuditoriumAngleOffset * 4; + break; + } + + return angle; +} + +void WSC::getExitCompassMove(const ExitTable::Entry &exitEntry, FaderMoveSpec &compassMove) { + Neighborhood::getExitCompassMove(exitEntry, compassMove); + + if (exitEntry.room == kWSC65 && exitEntry.direction == kSouth) { + compassMove.insertFaderKnot(exitEntry.movieStart + 100 * kWSCFrameDuration, 180); + compassMove.insertFaderKnot(exitEntry.movieStart + 108 * kWSCFrameDuration, 150); + compassMove.insertFaderKnot(exitEntry.movieEnd, 150); + } +} + +void WSC::getExtraCompassMove(const ExtraTable::Entry &entry, FaderMoveSpec &compassMove) { + switch (entry.extra) { + case kW61Brochure: + compassMove.insertFaderKnot(entry.movieStart + 15 * kWSCFrameDuration, 85); + compassMove.insertFaderKnot(entry.movieEnd - 15 * kWSCFrameDuration, 85); + compassMove.insertFaderKnot(entry.movieEnd, 90); + break; + default: + Neighborhood::getExtraCompassMove(entry, compassMove); + break; + } +} + +void WSC::loadAmbientLoops() { + RoomID room = GameState.getCurrentRoom(); + + if (room >= kWSC01 && room <= kWSC04) { + if (GameState.getWSCSeenTimeStream()) + loadLoopSound1("Sounds/World Science Center/WLabLoop.22K.AIFF", 0x100 / 2); + } else if ((room >= kWSC06 && room <= kWSC58) || (room >= kWSC62 && room <= kWSC63)) + loadLoopSound1("Sounds/World Science Center/Organic Walls.22K.AIFF", 0x100 / 2); + else if (room >= kWSC82 && room <= kWSC92) + loadLoopSound1("Sounds/World Science Center/Creature Feature.22K.AIFF"); + else if ((room >= kWSC60 && room <= kWSC61West) || (room >= kWSC64 && room <= kWSC81) || + (room >= kWSC93 && room <= kWSC97)) + loadLoopSound1("Sounds/World Science Center/The Other Side.22K.AIFF", 0x100 / 12); + else if (room == kWSC98) + loadLoopSound1("Sounds/World Science Center/WCatLoop.22K.AIFF"); +} + +void WSC::checkContinuePoint(const RoomID room, const DirectionConstant direction) { + switch (MakeRoomView(room, direction)) { + case MakeRoomView(kWSC07, kNorth): + case MakeRoomView(kWSC11, kSouth): + case MakeRoomView(kWSC13, kSouth): + case MakeRoomView(kWSC13, kWest): + case MakeRoomView(kWSC16, kWest): + case MakeRoomView(kWSC17, kEast): + case MakeRoomView(kWSC19, kWest): + case MakeRoomView(kWSC28, kNorth): + case MakeRoomView(kWSC28, kSouth): + case MakeRoomView(kWSC28, kEast): + case MakeRoomView(kWSC28, kWest): + case MakeRoomView(kWSC29, kNorth): + case MakeRoomView(kWSC29, kSouth): + case MakeRoomView(kWSC29, kEast): + case MakeRoomView(kWSC29, kWest): + case MakeRoomView(kWSC40, kEast): + case MakeRoomView(kWSC42, kEast): + case MakeRoomView(kWSC49, kWest): + case MakeRoomView(kWSC49, kNorth): + case MakeRoomView(kWSC50, kNorth): + case MakeRoomView(kWSC55, kEast): + case MakeRoomView(kWSC65, kSouth): + case MakeRoomView(kWSC65, kEast): + case MakeRoomView(kWSC65, kWest): + case MakeRoomView(kWSC72, kEast): + case MakeRoomView(kWSC72, kSouth): + case MakeRoomView(kWSC73, kWest): + case MakeRoomView(kWSC73, kSouth): + case MakeRoomView(kWSC79, kWest): + case MakeRoomView(kWSC81, kEast): + case MakeRoomView(kWSC93, kNorth): + case MakeRoomView(kWSC95, kWest): + makeContinuePoint(); + break; + case MakeRoomView(kWSC58, kSouth): + if (!GameState.getWSCDidPlasmaDodge()) + makeContinuePoint(); + break; + case MakeRoomView(kWSC60, kWest): + if (_vm->playerHasItemID(kMachineGun)) + makeContinuePoint(); + break; + } +} + +void WSC::arriveAt(const RoomID room, const DirectionConstant dir) { + switch (MakeRoomView(room, dir)) { + case MakeRoomView(kWSC60, kNorth): + case MakeRoomView(kWSC60, kSouth): + case MakeRoomView(kWSC60, kEast): + case MakeRoomView(kWSC60, kWest): + case MakeRoomView(kWSC60East, kNorth): + case MakeRoomView(kWSC60East, kSouth): + case MakeRoomView(kWSC60East, kEast): + case MakeRoomView(kWSC60East, kWest): + case MakeRoomView(kWSC60North, kNorth): + case MakeRoomView(kWSC60North, kSouth): + case MakeRoomView(kWSC60North, kEast): + case MakeRoomView(kWSC60North, kWest): + case MakeRoomView(kWSC61, kNorth): + case MakeRoomView(kWSC61, kSouth): + case MakeRoomView(kWSC61, kEast): + case MakeRoomView(kWSC61, kWest): + case MakeRoomView(kWSC61South, kNorth): + case MakeRoomView(kWSC61South, kSouth): + case MakeRoomView(kWSC61South, kEast): + case MakeRoomView(kWSC61South, kWest): + case MakeRoomView(kWSC61West, kNorth): + case MakeRoomView(kWSC61West, kSouth): + case MakeRoomView(kWSC61West, kEast): + case MakeRoomView(kWSC61West, kWest): + if (GameState.isTakenItemID(kMachineGun)) + setCurrentAlternate(kAltWSCTookMachineGun); + else + setCurrentAlternate(kAltWSCNormal); + break; + case MakeRoomView(kWSC73, kSouth): + case MakeRoomView(kWSC75, kNorth): + case MakeRoomView(kWSC75, kSouth): + case MakeRoomView(kWSC75, kEast): + case MakeRoomView(kWSC75, kWest): + if (!GameState.getWSCBeenAtWSC93()) + setCurrentAlternate(kAltWSCW0ZDoorOpen); + break; + } + + Neighborhood::arriveAt(room, dir); + + switch (MakeRoomView(room, dir)) { + case MakeRoomView(kWSC01, kWest): + if (!GameState.getWSCSeenTimeStream()) { + requestExtraSequence(kWSCArrivalFromTSA, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kWSCShotByRobot, 0, kFilterNoInput); + requestExtraSequence(kWSCDartScan1, kExtraCompletedFlag, kFilterNoInput); + } else if (GameState.getWSCPoisoned() && !GameState.getWSCAnsweredAboutDart()) { + setCurrentActivation(kActivationShotByRobot); + } + break; + case MakeRoomView(kWSC01, kEast): + if (GameState.getWSCDartInAnalyzer()) + requestExtraSequence(kWSCDropDartIntoAnalyzer, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC02Morph, kSouth): + setCurrentActivation(kActivationMorphScreenOff); + break; + case MakeRoomView(kWSC03, kNorth): + setCurrentActivation(kActivationW03NorthOff); + break; + case MakeRoomView(kWSC03, kSouth): + if (GameState.getWSCDesignedAntidote() && !GameState.getWSCPickedUpAntidote()) + setCurrentActivation(kActivationReadyForSynthesis); + break; + case MakeRoomView(kWSC16, kNorth): + if (getCurrentAlternate() == kAltWSCPeopleAtW19North) { + setCurrentAlternate(kAltWSCNormal); + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt19NorthFlag, true); + } + break; + case MakeRoomView(kWSC07, kSouth): + case MakeRoomView(kWSC56, kNorth): + setCurrentActivation(kActivationReadyForMap); + break; + case MakeRoomView(kWSC42, kWest): + setCurrentAlternate(kAltWSCNormal); + break; + case MakeRoomView(kWSC42, kEast): + _privateFlags.setFlag(kWSCPrivateSinclairOfficeOpenFlag, false); + setCurrentActivation(kActivationSinclairOfficeLocked); + break; + case MakeRoomView(kWSC58, kSouth): + setCurrentActivation(kActivationW58SouthDoorLocked); + _privateFlags.setFlag(kWSCPrivate58SouthOpenFlag, false); + break; + case MakeRoomView(kWSC60, kEast): + GameState.setScoringEnteredSinclairOffice(); + break; + case MakeRoomView(kWSC61West, kWest): + setCurrentActivation(kActivationW61MessagesOff); + break; + case MakeRoomView(kWSC61South, kSouth): + setCurrentActivation(kActivationW61SouthOff); + break; + case MakeRoomView(kWSC62, kSouth): + if (!GameState.getWSCDidPlasmaDodge()) { + g_AIArea->lockAIOut(); + loadLoopSound1("Sounds/World Science Center/Plasma Rock.22K.AIFF"); + requestExtraSequence(kW62SouthPlasmaRobotAppears, 0, kFilterNoInput); + requestExtraSequence(kW62ZoomToRobot, 0, kFilterNoInput); + requestExtraSequence(kW62ZoomOutFromRobot, kExtraCompletedFlag, kFilterNoInput); + } + break; + case MakeRoomView(kWSC65Screen, kSouth): + if (!GameState.getWSCSeenSinclairLecture()) { + GameState.setWSCSeenSinclairLecture(true); + startExtraSequence(kW65SouthSinclairLecture, kExtraCompletedFlag, kFilterAllInput); + } + break; + case MakeRoomView(kWSC66, kWest): + case MakeRoomView(kWSC67, kEast): + if (!GameState.getWSCHeardPage2()) { + playSpotSoundSync(kPaging2In, kPaging2Out); + GameState.setWSCHeardPage2(true); + } + case MakeRoomView(kWSC10, kNorth): + case MakeRoomView(kWSC26, kSouth): + case MakeRoomView(kWSC72, kWest): + case MakeRoomView(kWSC83, kWest): + if (!GameState.getWSCHeardCheckIn()) { + playSpotSoundSync(kCheckInIn, kCheckInOut); + GameState.setWSCHeardCheckIn(true); + } + break; + case MakeRoomView(kWSC0Z, kSouth): + if (getCurrentAlternate() == kAltWSCW0ZDoorOpen) + turnLeft(); + break; + case MakeRoomView(kWSC93, kEast): + GameState.setWSCBeenAtWSC93(true); + break; + case MakeRoomView(kWSC98, kWest): + if (!GameState.getWSCRobotDead()) { + scheduleEvent(kGawkAtRobotTime2, 1, kTimerEventPlayerGawkingAtRobot2); + setCurrentActivation(kActivationRobotTurning); + if (g_AIArea) + g_AIArea->checkMiddleArea(); + } else if (!GameState.getWSCRobotGone()) { + setCurrentActivation(kActivationRobotDead); + } else { + if (GameState.getWSCCatwalkDark()) { + // Change the gun hot spot... + _vm->getAllHotspots().setHotspotRect(kW98StunGunSpotID, Common::Rect(181 + kNavAreaLeft, + 99 + kNavAreaTop,372 + kNavAreaLeft, 149 + kNavAreaTop)); + } + setCurrentActivation(kActivationRobotGone); + } + break; + case MakeRoomView(kWSCDeathRoom, kNorth): + case MakeRoomView(kWSCDeathRoom, kSouth): + case MakeRoomView(kWSCDeathRoom, kEast): + case MakeRoomView(kWSCDeathRoom, kWest): + die(kDeathArrestedInWSC); + break; + } + + checkPeopleCrossing(); + setUpPoison(); +} + +void WSC::turnTo(const DirectionConstant direction) { + Neighborhood::turnTo(direction); + + switch (MakeRoomView(GameState.getCurrentRoom(), direction)) { + case MakeRoomView(kWSC01, kNorth): + case MakeRoomView(kWSC01, kSouth): + GameState.setWSCAnalyzerOn(false); + break; + case MakeRoomView(kWSC03, kNorth): + setCurrentActivation(kActivationW03NorthOff); + break; + case MakeRoomView(kWSC03, kSouth): + if (GameState.getWSCDesignedAntidote() && !GameState.getWSCPickedUpAntidote()) + setCurrentActivation(kActivationReadyForSynthesis); + break; + case MakeRoomView(kWSC07, kSouth): + case MakeRoomView(kWSC56, kNorth): + setCurrentActivation(kActivationReadyForMap); + break; + case MakeRoomView(kWSC18, kSouth): + case MakeRoomView(kWSC57, kEast): + case MakeRoomView(kWSC75, kEast): + case MakeRoomView(kWSC90, kSouth): + if (!GameState.getWSCHeardCheckIn()) { + playSpotSoundSync(kCheckInIn, kCheckInOut); + GameState.setWSCHeardCheckIn(true); + } + break; + case MakeRoomView(kWSC56, kSouth): + if (!GameState.getWSCHeardPage1()) { + playSpotSoundSync(kPaging1In, kPaging1Out); + GameState.setWSCHeardPage1(true); + } + // clone2727 says: This falls through?!??! WTF? + case MakeRoomView(kWSC42, kEast): + _privateFlags.setFlag(kWSCPrivateSinclairOfficeOpenFlag, false); + setCurrentActivation(kActivationSinclairOfficeLocked); + break; + case MakeRoomView(kWSC58, kSouth): + setCurrentActivation(kActivationW58SouthDoorLocked); + _privateFlags.setFlag(kWSCPrivate58SouthOpenFlag, false); + break; + case MakeRoomView(kWSC73, kWest): + setCurrentAlternate(kAltWSCNormal); + break; + case MakeRoomView(kWSC0Z, kEast): + if (getCurrentAlternate() == kAltWSCW0ZDoorOpen) + startExtraSequence(kW0ZSpottedByWomen, kExtraCompletedFlag, kFilterNoInput); + break; + } + + checkPeopleCrossing(); +} + +void WSC::receiveNotification(Notification *notification, const NotificationFlags flags) { + int32 currentEnergy; + Item *item; + + if (flags & kExtraCompletedFlag) { + _interruptionFilter = kFilterAllInput; + + switch (_lastExtra) { + case kWSCArrivalFromTSA: + GameState.setWSCSeenTimeStream(true); + loadAmbientLoops(); + break; + case kWSCDartScan1: + setCurrentActivation(kActivationShotByRobot); + GameState.setWSCPoisoned(true); + setUpPoison(); + makeContinuePoint(); + break; + case kWSCDartScan2: + _vm->addItemToInventory((InventoryItem *)_vm->getAllItems().findItemByID(kPoisonDart)); + GameState.setScoringRemovedDart(); + GameState.setWSCRemovedDart(true); + setUpPoison(); + g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/WSC/XW1WB2", false, kHintInterruption); + // Fall through... + case kWSCDartScanNo: + GameState.setWSCAnsweredAboutDart(true); + startExtraSequence(kWSCDartScan3, kExtraCompletedFlag, kFilterNoInput); + break; + case kWSCDartScan3: + setCurrentActivation(kActivateHotSpotAlways); + break; + case kWSCAnalyzerPowerUp: + case kWSCAnalyzerPowerUpWithDart: + GameState.setWSCAnalyzerOn(true); + break; + case kWSCDropDartIntoAnalyzer: + setCurrentActivation(kActivationZoomedInToAnalyzer); + break; + case kWSCAnalyzeDart: + GameState.setWSCAnalyzedDart(true); + GameState.setScoringAnalyzedDart(); + break; + case kWSCZoomOutFromAnalyzer: + setCurrentActivation(kActivateHotSpotAlways); + GameState.setWSCAnalyzerOn(false); + GameState.setWSCDartInAnalyzer(false); + updateViewFrame(); + break; + case kMessagesMovedToOffice: + case kMessagesMovedToOfficeNoNitrogen: + _privateFlags.setFlag(kWSCPrivateLabMessagesOpenFlag, true); + GameState.setScoringPlayedWithMessages(); + break; + case kMessagesOff: + case kMessagesOffNoNitrogen: + _privateFlags.setFlag(kWSCPrivateLabMessagesOpenFlag, false); + if (_cachedZoomSpot) { + zoomTo(_cachedZoomSpot); + _cachedZoomSpot = 0; + } + break; + case kWSC02TurnOnMorphScreen: + setCurrentActivation(kActivationReadyForMorph); + break; + case kWSC02DropToMorphExperiment: + loopExtraSequence(kWSC02MorphLoop, kExtraCompletedFlag); + setCurrentActivation(kActivationMorphLooping); + break; + case kWSC02MorphLoop: + if (_privateFlags.getFlag(kWSCPrivateInterruptedMorphFlag)) + startExtraSequence(kWSC02MorphInterruption, kExtraCompletedFlag, kFilterNoInput); + else + scheduleNavCallBack(kExtraCompletedFlag); + break; + case kWSC02MorphInterruption: + setCurrentActivation(kActivationMorphInterrupted); + GameState.setScoringSawMorphExperiment(); + break; + case kWSC02TurnOffMorphScreen: + setCurrentActivation(kActivationMorphScreenOff); + GameState.setWSCSawMorph(true); + break; + case kW03NorthActivate: + if (GameState.getWSCAnalyzedDart() && !GameState.getWSCDesignedAntidote()) + startExtraSequence(kW03NorthGetData, kExtraCompletedFlag, kFilterNoInput); + else + setCurrentActivation(kActivateHotSpotAlways); + break; + case kW03NorthGetData: + setCurrentActivation(kActivationW03NorthReadyForInstructions); + break; + case kW03NorthInstructions: + setCurrentActivation(kActivationW03NorthSawInstructions); + break; + case kW03NorthPrepMolecule1: + setUpMoleculeGame(); + break; + case kW03NorthPrepMolecule2: + case kW03NorthPrepMolecule3: + nextMoleculeGameLevel(); + break; + case kW03NorthFinishSynthesis: + setCurrentActivation(kActivateHotSpotAlways); + _privateFlags.setFlag(kWSCPrivateInMoleculeGameFlag, false); + GameState.setWSCDesignedAntidote(true); + GameState.setScoringBuiltAntidote(); + break; + case kW03SouthCreateAntidote: + setCurrentActivation(kActivationSynthesizerLooping); + loopExtraSequence(kW03SouthAntidoteLoop); + break; + case kW03SouthDeactivate: + setCurrentActivation(kActivateHotSpotAlways); + break; + case kWSC07SouthMap: + case kWSC56SouthMap: + setCurrentActivation(kActivateHotSpotAlways); + GameState.setScoringSawWSCDirectory(); + break; + case kNerdAtTheDoor1: + GameState.setWSCSeenNerd(true); + break; + case kNerdAtTheDoor2: + die(kDeathArrestedInWSC); + break; + case kW61Brochure: + GameState.setScoringSawBrochure(); + loadAmbientLoops(); + break; + case kW61SouthSmartAlloysWithGun: + case kW61SouthSmartAlloysNoGun: + GameState.setScoringSawSinclairEntry1(); + break; + case kW61SouthMorphingWithGun: + case kW61SouthMorphingNoGun: + GameState.setScoringSawSinclairEntry2(); + break; + case kW61SouthTimeBendingWithGun: + case kW61SouthTimeBendingNoGun: + GameState.setScoringSawSinclairEntry3(); + break; + case kW61MessagesOn: + GameState.setWSCOfficeMessagesOpen(true); + setCurrentActivation(kActivationW61MessagesOn); + break; + case kW61MessagesOff: + GameState.setWSCOfficeMessagesOpen(false); + setCurrentActivation(kActivationW61MessagesOff); + if (_cachedZoomSpot) { + zoomTo(_cachedZoomSpot); + _cachedZoomSpot = 0; + } + break; + case kW61SouthScreenOnWithGun: + case kW61SouthScreenOnNoGun: + _privateFlags.setFlag(kWSCPrivateOfficeLogOpenFlag, true); + setCurrentActivation(kActivationW61SouthOn); + break; + case kW61SouthScreenOffWithGun: + case kW61SouthScreenOffNoGun: + _privateFlags.setFlag(kWSCPrivateOfficeLogOpenFlag, false); + setCurrentActivation(kActivationW61SouthOff); + if (_cachedZoomSpot) { + zoomTo(_cachedZoomSpot); + _cachedZoomSpot = 0; + } + break; + case kW62ZoomOutFromRobot: + // Handle action queue before starting new movie sequences. + Neighborhood::receiveNotification(notification, flags); + _energyDrainRate = g_energyMonitor->getEnergyDrainRate(); + g_energyMonitor->setEnergyDrainRate(0); + currentEnergy = g_energyMonitor->getCurrentEnergy(); + _vm->setEnergyDeathReason(kDeathHitByPlasma); + + if (GameState.getShieldOn()) + currentEnergy -= kPlasmaEnergyWithShield; + else + currentEnergy -= kPlasmaEnergyNoShield; + + if (currentEnergy <= 0) + startExtraSequence(kW62PlasmaDodgeDie, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kW62PlasmaDodgeSurvive, kExtraCompletedFlag, kFilterNoInput); + + scheduleEvent(kPlasmaImpactTime, kOneTickPerSecond, kTimerEventPlasmaHit); + break; + case kW62PlasmaDodgeDie: + g_energyMonitor->setEnergyValue(0); + break; + case kW62PlasmaDodgeSurvive: + if (GameState.getShieldOn()) { + g_shield->setItemState(kShieldNormal); + g_energyMonitor->drainEnergy(kPlasmaEnergyWithShield); + } else { + g_energyMonitor->drainEnergy(kPlasmaEnergyNoShield); + } + + g_energyMonitor->setEnergyDrainRate(_energyDrainRate); + g_AIArea->unlockAI(); + GameState.setScoringFinishedPlasmaDodge(); + GameState.setWSCDidPlasmaDodge(true); + restoreStriding(kWSC58, kSouth, kAltWSCNormal); + loadAmbientLoops(); + break; + case kW0ZSpottedByWomen: + die(kDeathArrestedInWSC); + break; + case kW17WestPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt17WestFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt17WestFlag, false); + break; + case kW21SouthPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt21SouthFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt21SouthFlag, true); + break; + case kW24SouthPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt24SouthFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt24SouthFlag, true); + break; + case kW34EastPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt34EastFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt34EastFlag, true); + break; + case kW36WestPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt36WestFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt36WestFlag, true); + break; + case kW38NorthPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt38NorthFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt38NorthFlag, true); + break; + case kW46SouthPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt46SouthFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt46SouthFlag, true); + break; + case kW49NorthPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt49NorthFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt49NorthFlag, false); + break; + case kW73WestPeopleCrossing: + _privateFlags.setFlag(kWSCPrivateSeenPeopleAt73WestFlag, true); + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt73WestFlag, false); + break; + case kW95RobotShoots: + case kW98RobotShoots: + die(kDeathShotOnCatwalk); + break; + case kW98MorphsToRobot: + if (_argonSprite) { + delete _argonSprite; _argonSprite = 0; + startExtraSequence(kW98RobotGassed, kExtraCompletedFlag, kFilterNoInput); + } else if (_privateFlags.getFlag(kWSCPrivateClickedCatwalkCableFlag)) { + startExtraSequence(kW98RobotShocked, kExtraCompletedFlag, kFilterNoInput); + } else { + startExtraSequence(kW98RobotShoots, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kW98RobotShocked: + GameState.setWSCCatwalkDark(true); + // Change the gun hot spot... + _vm->getAllHotspots().setHotspotRect(kW98StunGunSpotID, Common::Rect(181 + kNavAreaLeft, 99 + kNavAreaTop, + 372 + kNavAreaLeft, 149 + kNavAreaTop)); + setCurrentActivation(kActivationRobotDead); + GameState.setWSCRobotDead(true); + GameState.setScoringStoppedWSCRobot(); + + // Video is not present + //g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/WSC/XN59WD", false, kWarningInterruption); + break; + case kW98RobotGassed: + item = (Item *)_vm->getAllItems().findItemByID(kArgonCanister); + _vm->addItemToInventory((InventoryItem *)item); + setCurrentActivation(kActivationRobotDead); + GameState.setWSCRobotDead(true); + GameState.setScoringStoppedWSCRobot(); + + // Video is not present + //g_AIArea->playAIMovie(kRightAreaSignature, "Images/AI/WSC/XN59WD", false, kWarningInterruption); + break; + case kW98RobotHeadOpensLight: + case kW98RobotHeadOpensDark: + setCurrentActivation(kActivationWSCRobotHeadOpen); + _privateFlags.setFlag(kWSCPrivateRobotHeadOpenFlag, true); + break; + case kW98RobotHeadClosesDark: + case kW98RobotHeadClosesLight: + setCurrentActivation(kActivationRobotGone); + _privateFlags.setFlag(kWSCPrivateRobotHeadOpenFlag, false); + GameState.setWSCRobotGone(true); + break; + } + } + + Neighborhood::receiveNotification(notification, flags); + g_AIArea->checkMiddleArea(); +} + +void WSC::timerExpired(const uint32 event) { + switch (event) { + case kTimerEventPlasmaHit: + if (GameState.getShieldOn()) + g_shield->setItemState(kShieldPlasma); + break; + case kTimerEventPlayerGawkingAtRobot: + startExtraSequence(kW95RobotShoots, kExtraCompletedFlag, kFilterNoInput); + break; + case kTimerEventPlayerGawkingAtRobot2: + startExtraSequence(kW98MorphsToRobot, kExtraCompletedFlag, kFilterAllInput); + break; + } +} + +void WSC::setUpMoleculeGame() { + _privateFlags.setFlag(kWSCPrivateInMoleculeGameFlag, true); + setCurrentActivation(kActivationW03NorthInGame); + initOneMovie(&_moleculesMovie, "Images/World Science Center/Molecules.movie", + kWSCMoleculesMovieOrder, kMoleculesMovieLeft, kMoleculesMovieTop, true); + _moleculesMovie.redrawMovieWorld(); + _moleculeBin.initMoleculeBin(); + _moleculeGameLevel = 0; + nextMoleculeGameLevel(); +} + +void WSC::nextMoleculeGameLevel() { + _moleculeGameLevel++; + + for (byte i = 0; i < 6; ++i) + _levelArray[i] = i; + + _vm->shuffleArray((int32 *)_levelArray, 6); + _moleculeBin.setBinLayout(_levelArray); + startMoleculeGameLevel(); +} + +void WSC::startMoleculeGameLevel() { + _moleculeBin.resetBin(); + _numCorrect = 0; + _moleculesMovie.stop(); + _moleculesMovie.setFlags(0); + _moleculesMovie.setSegment(s_moleculeLoopTimes[0], s_moleculeLoopTimes[0] + kMoleculeLoopTime); + _moleculesMovie.setTime(s_moleculeLoopTimes[0]); + _moleculesMovie.setFlags(kLoopTimeBase); + _moleculesMovie.show(); + + switch (_moleculeGameLevel) { + case 1: + playSpotSoundSync(kWSCMolecule1In, kWSCMolecule1Out); + break; + case 2: + playSpotSoundSync(kWSCMolecule2In, kWSCMolecule2Out); + break; + case 3: + playSpotSoundSync(kWSCMolecule3In, kWSCMolecule3Out); + break; + } + + _moleculesMovie.start(); +} + +void WSC::moleculeGameClick(const HotSpotID id) { + uint32 molecule = id - kWSC03NorthMolecule1SpotID; + + _moleculeBin.highlightMolecule(molecule); + _moleculeBin.selectMolecule(molecule); + + if (molecule == _levelArray[_numCorrect]) { + playSpotSoundSync(kWSCClick2In, kWSCClick2Out); + _numCorrect++; + _moleculesMovie.stop(); + _moleculesMovie.setFlags(0); + + TimeValue time = _moleculesMovie.getTime(); + _moleculesMovie.setSegment(s_moleculeLoopTimes[_numCorrect], s_moleculeLoopTimes[_numCorrect] + kMoleculeLoopTime); + _moleculesMovie.setTime(s_moleculeLoopTimes[_numCorrect] + time - s_moleculeLoopTimes[_numCorrect - 1]); + + if (_numCorrect == 6) { + _moleculesMovie.start(); + + while (_moleculesMovie.isRunning()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + _moleculesMovie.stop(); + _moleculesMovie.hide(); + + switch (_moleculeGameLevel) { + case 1: + startExtraSequence(kW03NorthPrepMolecule2, kExtraCompletedFlag, kFilterNoInput); + break; + case 2: + startExtraSequence(kW03NorthPrepMolecule3, kExtraCompletedFlag, kFilterNoInput); + break; + case 3: + _moleculesMovie.releaseMovie(); + _moleculeBin.cleanUpMoleculeBin(); + requestExtraSequence(kW03NorthFinishSynthesis, kExtraCompletedFlag, kFilterNoInput); + break; + } + } else { + _moleculesMovie.setFlags(kLoopTimeBase); + _moleculesMovie.start(); + } + } else { + // FAIL + playSpotSoundSync(kWSCClick3In, kWSCClick3Out); + + _moleculesMovie.stop(); + _moleculesMovie.setFlags(0); + _moleculesMovie.start(); + + while (_moleculesMovie.isRunning()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + _moleculesMovie.stop(); + _moleculesMovie.setFlags(0); + _moleculesMovie.setSegment(s_moleculeFailTimes[_numCorrect], s_moleculeFailTimes[_numCorrect] + kMoleculeFailTime); + _moleculesMovie.setTime(s_moleculeFailTimes[_numCorrect]); + _moleculesMovie.start(); + + + while (_moleculesMovie.isRunning()) { + _vm->checkCallBacks(); + _vm->refreshDisplay(); + _vm->_system->delayMillis(10); + } + + _moleculesMovie.stop(); + startMoleculeGameLevel(); + } +} + +void WSC::activateOneHotspot(HotspotInfoTable::Entry &entry, Hotspot *hotspot) { + Neighborhood::activateOneHotspot(entry, hotspot); + + Item *argonCanister; + + switch (hotspot->getObjectID()) { + case kWSCTurnOnAnalyzerSpotID: + if (GameState.getWSCAnalyzerOn()) + hotspot->setInactive(); + break; + case kWSC02SouthTakeArgonSpotID: + if (!GameState.getWSCSawMorph() || GameState.isTakenItemID(kArgonCanister)) + hotspot->setInactive(); + break; + case kWSC02ActivateMorphScreenSpotID: + if (GameState.getWSCSawMorph()) + hotspot->setInactive(); + break; + case kWSC03NorthMolecule1SpotID: + case kWSC03NorthMolecule2SpotID: + case kWSC03NorthMolecule3SpotID: + case kWSC03NorthMolecule4SpotID: + case kWSC03NorthMolecule5SpotID: + case kWSC03NorthMolecule6SpotID: + if (_moleculeBin.isMoleculeHighlighted(hotspot->getObjectID() - kWSC03NorthMolecule1SpotID)) + hotspot->setInactive(); + break; + case kWSC03SouthPickUpAntidoteSpotID: + if (getCurrentActivation() == kActivationSynthesizerLooping) + hotspot->setActive(); + break; + case kW98DropArgonSpotID: + argonCanister = _vm->getAllItems().findItemByID(kArgonCanister); + if (argonCanister->getItemState() != kArgonFull) + hotspot->setInactive(); + break; + } +} + +void WSC::activateHotspots() { + Neighborhood::activateHotspots(); + + if (GameState.getCurrentRoomAndView() == MakeRoomView(kWSC98, kWest) && _privateFlags.getFlag(kWSCPrivateRobotHeadOpenFlag)) { + if (_privateFlags.getFlag(kWSCPrivateGotRetScanChipFlag)) + _vm->getAllHotspots().deactivateOneHotspot(kW98RetinalChipSpotID); + else + _vm->getAllHotspots().activateOneHotspot(kW98RetinalChipSpotID); + + if (_privateFlags.getFlag(kWSCPrivateGotMapChipFlag)) + _vm->getAllHotspots().deactivateOneHotspot(kW98MapChipSpotID); + else + _vm->getAllHotspots().activateOneHotspot(kW98MapChipSpotID); + + if (_privateFlags.getFlag(kWSCPrivateGotOpticalChipFlag)) + _vm->getAllHotspots().deactivateOneHotspot(kW98OpticalChipSpotID); + else + _vm->getAllHotspots().activateOneHotspot(kW98OpticalChipSpotID); + } +} + +void WSC::clickInHotspot(const Input &input, const Hotspot *clickedSpot) { + if (JMPPPInput::isEasterEggModifierInput(input)) + GameState.setEasterEgg(true); + + if (clickedSpot) { + switch (clickedSpot->getObjectID()) { + case kWSCAnalyzerScreenSpotID: + requestExtraSequence(kWSCAnalyzeDart, kExtraCompletedFlag, kFilterNoInput); + requestExtraSequence(kWSCZoomOutFromAnalyzer, kExtraCompletedFlag, kFilterNoInput); + break; + case kWSC02SouthPlayMessagesSpotID: + if (GameState.isTakenItemID(kNitrogenCanister)) { + if (_lastExtra == (uint32)kMessagesMovedToOfficeNoNitrogen) + startExtraSequence(kMessagesOffNoNitrogen, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMessagesMovedToOfficeNoNitrogen, kExtraCompletedFlag, kFilterNoInput); + } else { + if (_lastExtra == (uint32)kMessagesMovedToOffice) + startExtraSequence(kMessagesOff, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kMessagesMovedToOffice, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kWSC02SouthInterruptMorphSpotID: + _privateFlags.setFlag(kWSCPrivateInterruptedMorphFlag, true); + break; + case kWSC02SouthMorphFinishedSpotID: + requestExtraSequence(kWSC02MorphFinished, 0, kFilterNoInput); + requestExtraSequence(kWSC02TurnOffMorphScreen, kExtraCompletedFlag, kFilterNoInput); + break; + case kWSC03NorthMolecule1SpotID: + case kWSC03NorthMolecule2SpotID: + case kWSC03NorthMolecule3SpotID: + case kWSC03NorthMolecule4SpotID: + case kWSC03NorthMolecule5SpotID: + case kWSC03NorthMolecule6SpotID: + moleculeGameClick(clickedSpot->getObjectID()); + break; + case kW98GrabCableSpotID: + if (isEventTimerRunning()) { + cancelEvent(); + startExtraSequence(kW98MorphsToRobot, kExtraCompletedFlag, kFilterAllInput); + } + + _privateFlags.setFlag(kWSCPrivateClickedCatwalkCableFlag, true); + break; + default: + Neighborhood::clickInHotspot(input, clickedSpot); + break; + } + } else { + Neighborhood::clickInHotspot(input, clickedSpot); + } + + GameState.setEasterEgg(false); +} + +void WSC::dropItemIntoRoom(Item *item, Hotspot *dropSpot) { + CoordType h, v; + + switch (item->getObjectID()) { + case kPoisonDart: + Neighborhood::dropItemIntoRoom(item, dropSpot); + GameState.setWSCDartInAnalyzer(true); + if (dropSpot && dropSpot->getObjectID() == kWSCDropDartSpotID) { + if (!GameState.getWSCAnalyzerOn()) + requestExtraSequence(kWSCAnalyzerPowerUpWithDart, kExtraCompletedFlag, kFilterNoInput); + + requestExtraSequence(kWSCDropDartIntoAnalyzer, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kAntidote: + _privateFlags.setFlag(kWSCDraggingAntidoteFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + loopExtraSequence(kW03SouthAntidoteLoop); + break; + case kSinclairKey: + Neighborhood::dropItemIntoRoom(item, dropSpot); + _privateFlags.setFlag(kWSCPrivateSinclairOfficeOpenFlag, true); + openDoor(); + break; + case kCrowbar: + Neighborhood::dropItemIntoRoom(item, dropSpot); + _privateFlags.setFlag(kWSCPrivate58SouthOpenFlag, true); + openDoor(); + break; + case kMachineGun: + setCurrentAlternate(kAltWSCNormal); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + case kArgonCanister: + item->setItemState(kArgonEmpty); + _argonSprite = item->getDragSprite(0); + _argonSprite->setCurrentFrameIndex(1); + _argonSprite->setDisplayOrder(kDragSpriteOrder); + dropSpot->getCenter(h, v); + _argonSprite->centerElementAt(h, v); + _argonSprite->startDisplaying(); + _argonSprite->show(); + + if (isEventTimerRunning()) { + cancelEvent(); + startExtraSequence(kW98MorphsToRobot, kExtraCompletedFlag, kFilterAllInput); + } + break; + case kRetinalScanBiochip: + _privateFlags.setFlag(kWSCPrivateGotRetScanChipFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + case kMapBiochip: + _privateFlags.setFlag(kWSCPrivateGotMapChipFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + case kOpticalBiochip: + _privateFlags.setFlag(kWSCPrivateGotOpticalChipFlag, false); + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + default: + Neighborhood::dropItemIntoRoom(item, dropSpot); + break; + } +} + +void WSC::takeItemFromRoom(Item *item) { + switch (item->getObjectID()) { + case kAntidote: + _privateFlags.setFlag(kWSCDraggingAntidoteFlag, true); + Neighborhood::takeItemFromRoom(item); + break; + case kMachineGun: + setCurrentAlternate(kAltWSCTookMachineGun); + Neighborhood::takeItemFromRoom(item); + break; + case kRetinalScanBiochip: + _privateFlags.setFlag(kWSCPrivateGotRetScanChipFlag, true); + Neighborhood::takeItemFromRoom(item); + break; + case kMapBiochip: + _privateFlags.setFlag(kWSCPrivateGotMapChipFlag, true); + Neighborhood::takeItemFromRoom(item); + break; + case kOpticalBiochip: + _privateFlags.setFlag(kWSCPrivateGotOpticalChipFlag, true); + Neighborhood::takeItemFromRoom(item); + break; + default: + Neighborhood::takeItemFromRoom(item); + break; + } +} + +Hotspot *WSC::getItemScreenSpot(Item *item, DisplayElement *element) { + HotSpotID destSpotID; + + switch (item->getObjectID()) { + case kNitrogenCanister: + destSpotID = kWSC02SouthTakeNitrogenSpotID; + break; + case kArgonPickup: + destSpotID = kWSC02SouthTakeArgonSpotID; + break; + case kAntidote: + destSpotID = kWSC03SouthPickUpAntidoteSpotID; + break; + case kMachineGun: + destSpotID = kW61SouthMachineGunSpotID; + break; + case kRetinalScanBiochip: + destSpotID = kW98RetinalChipSpotID; + break; + case kMapBiochip: + destSpotID = kW98MapChipSpotID; + break; + case kOpticalBiochip: + destSpotID = kW98OpticalChipSpotID; + break; + default: + destSpotID = kNoHotSpotID; + break; + } + + if (destSpotID == kNoHotSpotID) + return Neighborhood::getItemScreenSpot(item, element); + + return _vm->getAllHotspots().findHotspotByID(destSpotID); +} + +void WSC::pickedUpItem(Item *item) { + switch (item->getObjectID()) { + case kAntidote: + if (!GameState.getWSCPickedUpAntidote()) { + GameState.setWSCPoisoned(false); + GameState.setWSCRemovedDart(false); + GameState.setWSCPickedUpAntidote(true); + _privateFlags.setFlag(kWSCDraggingAntidoteFlag, false); + playSpotSoundSync(kDrinkAntidoteIn, kDrinkAntidoteOut); + setUpPoison(); + startExtraSequence(kW03SouthDeactivate, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kArgonPickup: + _vm->removeItemFromInventory((InventoryItem *)item); + item = (Item *)_vm->getAllItems().findItemByID(kArgonCanister); + _vm->addItemToInventory((InventoryItem *)item); + item = (Item *)_vm->getAllItems().findItemByID(kSinclairKey); + _vm->addItemToInventory((InventoryItem *)item); + _vm->getAllHotspots().setHotspotRect(kWSC02SouthMorphOutSpotID, + Common::Rect(kNavAreaLeft, kNavAreaTop, 512 + kNavAreaLeft, 256 + kNavAreaTop)); + break; + case kArgonCanister: + GameState.setScoringGotArgonCanister(); + break; + case kSinclairKey: + GameState.setScoringGotSinclairKey(); + break; + case kNitrogenCanister: + GameState.setScoringGotNitrogenCanister(); + break; + case kRetinalScanBiochip: + if (_privateFlags.getFlag(kWSCPrivateGotMapChipFlag) && _privateFlags.getFlag(kWSCPrivateGotOpticalChipFlag)) { + if (GameState.getWSCCatwalkDark()) + startExtraSequence(kW98RobotHeadClosesDark, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kW98RobotHeadClosesLight, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kMapBiochip: + if (_privateFlags.getFlag(kWSCPrivateGotRetScanChipFlag) && _privateFlags.getFlag(kWSCPrivateGotOpticalChipFlag)) { + if (GameState.getWSCCatwalkDark()) + startExtraSequence(kW98RobotHeadClosesDark, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kW98RobotHeadClosesLight, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kOpticalBiochip: + g_opticalChip->addMercury(); + GameState.setScoringGotWSCOpMemChip(); + if (_privateFlags.getFlag(kWSCPrivateGotRetScanChipFlag) && _privateFlags.getFlag(kWSCPrivateGotMapChipFlag)) { + if (GameState.getWSCCatwalkDark()) + startExtraSequence(kW98RobotHeadClosesDark, kExtraCompletedFlag, kFilterNoInput); + else + startExtraSequence(kW98RobotHeadClosesLight, kExtraCompletedFlag, kFilterNoInput); + } + break; + case kStunGun: + GameState.setWSCFinished(true); + + if (!GameState.getWSCCatwalkDark()) + GameState.setScoringWSCGandhi(); + + recallToTSASuccess(); + break; + } +} + +void WSC::checkPeopleCrossing() { + switch (GameState.getCurrentRoomAndView()) { + case MakeRoomView(kWSC17, kWest): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt17WestFlag)) + startExtraSequence(kW17WestPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC21, kSouth): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt21SouthFlag)) + startExtraSequence(kW21SouthPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC24, kSouth): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt24SouthFlag)) + startExtraSequence(kW24SouthPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC34, kEast): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt34EastFlag)) + startExtraSequence(kW34EastPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC36, kWest): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt36WestFlag)) + startExtraSequence(kW36WestPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC38, kNorth): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt38NorthFlag)) + startExtraSequence(kW38NorthPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC46, kSouth): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt46SouthFlag)) + startExtraSequence(kW46SouthPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC49, kNorth): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt49NorthFlag)) + startExtraSequence(kW49NorthPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + case MakeRoomView(kWSC73, kWest): + if (_privateFlags.getFlag(kWSCPrivateNeedPeopleAt73WestFlag)) + startExtraSequence(kW73WestPeopleCrossing, kExtraCompletedFlag, kFilterNoInput); + break; + default: + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt21SouthFlag) && _vm->getRandomNumber(2) == 0) { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt21SouthFlag, true); + forceStridingStop(kWSC18, kSouth, kAltWSCNormal); + } else { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt21SouthFlag, false); + restoreStriding(kWSC18, kSouth, kAltWSCNormal); + } + + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt19NorthFlag) && _vm->getRandomNumber(2) == 0) { + forceStridingStop(kWSC22, kNorth, kAltWSCNormal); + } else { + restoreStriding(kWSC22, kNorth, kAltWSCNormal); + } + + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt24SouthFlag) && _vm->getRandomNumber(2) == 0) { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt24SouthFlag, true); + forceStridingStop(kWSC22, kSouth, kAltWSCNormal); + } else { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt24SouthFlag, false); + restoreStriding(kWSC22, kSouth, kAltWSCNormal); + } + + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt34EastFlag) && _vm->getRandomNumber(2) == 0) { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt34EastFlag, true); + forceStridingStop(kWSC28, kEast, kAltWSCNormal); + } else { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt34EastFlag, false); + restoreStriding(kWSC28, kEast, kAltWSCNormal); + } + + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt36WestFlag) && _vm->getRandomNumber(2) == 0) { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt36WestFlag, true); + forceStridingStop(kWSC40, kWest, kAltWSCNormal); + } else { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt36WestFlag, false); + restoreStriding(kWSC40, kWest, kAltWSCNormal); + } + + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt38NorthFlag) && _vm->getRandomNumber(2) == 0) { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt38NorthFlag, true); + forceStridingStop(kWSC42, kNorth, kAltWSCNormal); + } else { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt38NorthFlag, false); + restoreStriding(kWSC42, kNorth, kAltWSCNormal); + } + + if (!_privateFlags.getFlag(kWSCPrivateSeenPeopleAt46SouthFlag) && _vm->getRandomNumber(2) == 0) { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt46SouthFlag, true); + forceStridingStop(kWSC44, kSouth, kAltWSCNormal); + } else { + _privateFlags.setFlag(kWSCPrivateNeedPeopleAt46SouthFlag, false); + restoreStriding(kWSC44, kSouth, kAltWSCNormal); + } + break; + } +} + +void WSC::setUpPoison() { + if (GameState.getWSCPoisoned()) { + if (GameState.getWSCRemovedDart()) { + if (g_energyMonitor->getEnergyDrainRate() != kWSCPoisonEnergyDrainNoDart) { + g_energyMonitor->setEnergyDrainRate(kWSCPoisonEnergyDrainNoDart); + _vm->setEnergyDeathReason(kDeathDidntStopPoison); + } + } else { + if (g_energyMonitor->getEnergyDrainRate() != kWSCPoisonEnergyDrainWithDart) { + g_energyMonitor->setEnergyDrainRate(kWSCPoisonEnergyDrainWithDart); + _vm->setEnergyDeathReason(kDeathDidntStopPoison); + } + } + } else if (g_energyMonitor->getEnergyDrainRate() != kEnergyDrainNormal) { + g_energyMonitor->setEnergyDrainRate(kEnergyDrainNormal); + _vm->resetEnergyDeathReason(); + } +} + +bool WSC::inSynthesizerGame() { + return _moleculesMovie.isMovieValid(); +} + +bool WSC::canSolve() { + return (inSynthesizerGame() || (GameState.getCurrentRoom() == kWSC98 && !GameState.getWSCRobotDead())); +} + +void WSC::doSolve() { + if (inSynthesizerGame()) { + _moleculesMovie.releaseMovie(); + _moleculeBin.cleanUpMoleculeBin(); + requestExtraSequence(kW03NorthFinishSynthesis, kExtraCompletedFlag, kFilterNoInput); + } else if (GameState.getCurrentRoom() == kWSC98 && !GameState.getWSCRobotDead()) { + cancelEvent(); + startExtraSequence(kW98RobotShocked, kExtraCompletedFlag, kFilterNoInput); + } +} + +Common::String WSC::getNavMovieName() { + return "Images/World Science Center/WSC.movie"; +} + +Common::String WSC::getSoundSpotsName() { + return "Sounds/World Science Center/WSC Spots"; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/wsc/wsc.h b/engines/pegasus/neighborhood/wsc/wsc.h new file mode 100644 index 0000000000..d9634b3539 --- /dev/null +++ b/engines/pegasus/neighborhood/wsc/wsc.h @@ -0,0 +1,166 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_WSC_WSC_H +#define PEGASUS_NEIGHBORHOOD_WSC_WSC_H + +#include "pegasus/neighborhood/neighborhood.h" +#include "pegasus/neighborhood/wsc/moleculebin.h" + +namespace Pegasus { + +static const DisplayOrder kWSCMoleculeBinOrder = kMonitorLayer; +static const DisplayOrder kWSCMoleculesMovieOrder = kWSCMoleculeBinOrder + 1; + +static const RoomID kWSC01 = 0; +static const RoomID kWSC02Morph = 2; +static const RoomID kWSC02Messages = 3; +static const RoomID kWSC62 = 62; + +class WSC : public Neighborhood { +public: + WSC(InputHandler *, PegasusEngine *); + virtual ~WSC() {} + + void flushGameState(); + + virtual uint16 getDateResID() const; + + bool okayToJump(); + + void checkContinuePoint(const RoomID, const DirectionConstant); + + bool inSynthesizerGame(); + + bool canSolve(); + void doSolve(); + + virtual void prepareForAIHint(const Common::String &); + virtual void cleanUpAfterAIHint(const Common::String &); + + void init(); + void start(); + +protected: + enum { + kWSCDraggingAntidoteFlag, + + kWSCPrivateLabMessagesOpenFlag, + kWSCPrivateInterruptedMorphFlag, + kWSCPrivateInMoleculeGameFlag, + kWSCPrivateSinclairOfficeOpenFlag, + kWSCPrivateOfficeLogOpenFlag, + kWSCPrivate58SouthOpenFlag, + kWSCPrivateClickedCatwalkCableFlag, + kWSCPrivateRobotHeadOpenFlag, + + kWSCPrivateSeenPeopleAt17WestFlag, + kWSCPrivateSeenPeopleAt19NorthFlag, + kWSCPrivateSeenPeopleAt21SouthFlag, + kWSCPrivateSeenPeopleAt24SouthFlag, + kWSCPrivateSeenPeopleAt34EastFlag, + kWSCPrivateSeenPeopleAt36WestFlag, + kWSCPrivateSeenPeopleAt38NorthFlag, + kWSCPrivateSeenPeopleAt46SouthFlag, + kWSCPrivateSeenPeopleAt49NorthFlag, + kWSCPrivateSeenPeopleAt73WestFlag, + + kWSCPrivateNeedPeopleAt17WestFlag, + kWSCPrivateNeedPeopleAt21SouthFlag, + kWSCPrivateNeedPeopleAt24SouthFlag, + kWSCPrivateNeedPeopleAt34EastFlag, + kWSCPrivateNeedPeopleAt36WestFlag, + kWSCPrivateNeedPeopleAt38NorthFlag, + kWSCPrivateNeedPeopleAt46SouthFlag, + kWSCPrivateNeedPeopleAt49NorthFlag, + kWSCPrivateNeedPeopleAt73WestFlag, + + kWSCPrivateGotRetScanChipFlag, + kWSCPrivateGotMapChipFlag, + kWSCPrivateGotOpticalChipFlag, + + kNumWSCPrivateFlags + }; + + void arriveAt(const RoomID, const DirectionConstant); + void turnTo(const DirectionConstant); + void receiveNotification(Notification *, const NotificationFlags); + void dropItemIntoRoom(Item *, Hotspot *); + void clickInHotspot(const Input &, const Hotspot *); + TimeValue getViewTime(const RoomID, const DirectionConstant); + void getZoomEntry(const HotSpotID, ZoomTable::Entry &); + CanMoveForwardReason canMoveForward(ExitTable::Entry &entry); + void cantMoveThatWay(CanMoveForwardReason reason); + CanTurnReason canTurn(TurnDirection turn, DirectionConstant &nextDir); + void zoomTo(const Hotspot *hotspot); + void activateOneHotspot(HotspotInfoTable::Entry &, Hotspot *); + void setUpMoleculeGame(); + void nextMoleculeGameLevel(); + void startMoleculeGameLevel(); + void moleculeGameClick(const HotSpotID); + void loadAmbientLoops(); + CanOpenDoorReason canOpenDoor(DoorTable::Entry &); + void cantOpenDoor(CanOpenDoorReason); + void pickedUpItem(Item *); + void doorOpened(); + void startExtraSequence(const ExtraID, const NotificationFlags, const InputBits); + void getExtraEntry(const uint32, ExtraTable::Entry &); + void takeItemFromRoom(Item *item); + void checkPeopleCrossing(); + void turnLeft(); + void turnRight(); + void moveForward(); + Hotspot *getItemScreenSpot(Item *, DisplayElement *); + int16 getStaticCompassAngle(const RoomID, const DirectionConstant); + void getExitCompassMove(const ExitTable::Entry &exitEntry, FaderMoveSpec &compassMove); + void getExtraCompassMove(const ExtraTable::Entry &entry, FaderMoveSpec &compassMove); + void bumpIntoWall(); + void activateHotspots(); + void setUpAIRules(); + Common::String getBriefingMovie(); + Common::String getEnvScanMovie(); + uint getNumHints(); + Common::String getHintMovie(uint); + void closeDoorOffScreen(const RoomID, const DirectionConstant); + void setUpPoison(); + void findSpotEntry(const RoomID, const DirectionConstant, SpotFlags, SpotTable::Entry &); + void timerExpired(const uint32); + + Common::String getSoundSpotsName(); + Common::String getNavMovieName(); + + FlagsArray<byte, kNumWSCPrivateFlags> _privateFlags; + const Hotspot *_cachedZoomSpot; + MoleculeBin _moleculeBin; + int32 _moleculeGameLevel, _numCorrect; + Movie _moleculesMovie; + uint32 _levelArray[6]; + Common::Rational _energyDrainRate; + Sprite *_argonSprite; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/neighborhood/zoom.cpp b/engines/pegasus/neighborhood/zoom.cpp new file mode 100644 index 0000000000..478ec6e493 --- /dev/null +++ b/engines/pegasus/neighborhood/zoom.cpp @@ -0,0 +1,74 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "pegasus/neighborhood/zoom.h" + +namespace Pegasus { + +void ZoomTable::loadFromStream(Common::SeekableReadStream *stream) { + uint32 count = stream->readUint32BE(); + _entries.resize(count); + + for (uint32 i = 0; i < count; i++) { + _entries[i].hotspot = stream->readUint16BE(); + _entries[i].movieStart = stream->readUint32BE(); + _entries[i].movieEnd = stream->readUint32BE(); + _entries[i].room = stream->readUint16BE(); + _entries[i].direction = stream->readByte(); + debug(0, "Zoom[%d]: %d %d %d %d %d", i, _entries[i].hotspot, _entries[i].movieStart, + _entries[i].movieEnd, _entries[i].room, _entries[i].direction); + stream->readByte(); // alignment + } +} + +void ZoomTable::clear() { + _entries.clear(); +} + +ZoomTable::Entry::Entry() { + clear(); +} + +void ZoomTable::Entry::clear() { + hotspot = kNoHotSpotID; + movieStart = 0xffffffff; + movieEnd = 0xffffffff; + room = kNoRoomID; + direction = kNoDirection; +} + +ZoomTable::Entry ZoomTable::findEntry(HotSpotID hotspot) { + for (uint32 i = 0; i < _entries.size(); i++) + if (_entries[i].hotspot == hotspot) + return _entries[i]; + + return Entry(); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/neighborhood/zoom.h b/engines/pegasus/neighborhood/zoom.h new file mode 100644 index 0000000000..8bcf8974f8 --- /dev/null +++ b/engines/pegasus/neighborhood/zoom.h @@ -0,0 +1,70 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef PEGASUS_NEIGHBORHOOD_ZOOM_H +#define PEGASUS_NEIGHBORHOOD_ZOOM_H + +#include "common/array.h" +#include "common/endian.h" + +#include "pegasus/constants.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Pegasus { + +class ZoomTable { +public: + ZoomTable() {} + ~ZoomTable() {} + + static uint32 getResTag() { return MKTAG('Z', 'o', 'o', 'm'); } + + void loadFromStream(Common::SeekableReadStream *stream); + void clear(); + + struct Entry { + Entry(); + void clear(); + bool isEmpty() { return movieStart == 0xffffffff; } + + HotSpotID hotspot; + TimeValue movieStart; + TimeValue movieEnd; + RoomID room; + DirectionConstant direction; + }; + + Entry findEntry(HotSpotID hotspot); + +private: + Common::Array<Entry> _entries; +}; + +} // End of namespace Pegasus + +#endif |