Files
scummvm-cursorfix/engines/pegasus/neighborhood/norad/pressuredoor.cpp
2026-02-02 04:50:13 +01:00

563 lines
19 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* Additional copyright for this file:
* Copyright (C) 1995-1997 Presto Studios, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#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, g_vm),
_doorTracker(this), _utilityNotification(kNoradUtilityNotificationID, g_vm) {
_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(g_vm->_resFork, kLowerPressureUpOffPICTID);
else
frame->initFromPICTResource(g_vm->_resFork, kUpperPressureUpOffPICTID);
_upButton.addFrame(frame, 0, 0);
frame = new SpriteFrame();
if (_isUpperDoor)
frame->initFromPICTResource(g_vm->_resFork, kLowerPressureUpOnPICTID);
else
frame->initFromPICTResource(g_vm->_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(g_vm->_resFork, kLowerPressureDownOffPICTID);
else
frame->initFromPICTResource(g_vm->_resFork, kUpperPressureDownOffPICTID);
_downButton.addFrame(frame, 0, 0);
frame = new SpriteFrame();
if (_isUpperDoor)
frame->initFromPICTResource(g_vm->_resFork, kLowerPressureDownOnPICTID);
else
frame->initFromPICTResource(g_vm->_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);
g_vm->die(kDeathRobotThroughNoradDoor);
break;
case kRobotDying:
_robotState = kRobotDead;
_levelsMovie.stop();
_levelsMovie.setSegment((kNormalSubRoomPressure + kPressureBase) * _levelsScale,
(GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale + 1);
_levelsMovie.setTime((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(-4, 3)); // Should match door tracker.
break;
case kRobotDead:
allowInput(true);
((NoradDelta *)owner)->playerBeatRobotWithDoor();
owner->requestDeleteCurrentInteraction();
break;
default:
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;
default:
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;
default:
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;
default:
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