scummvm/engines/pegasus/neighborhood/neighborhood.cpp
2016-09-03 12:46:38 +02:00

1788 lines
53 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 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()) {
InputDevice.pumpEvents();
_vm->checkCallBacks();
_vm->refreshDisplay();
_vm->checkNotifications();
_vm->_system->delayMillis(10);
}
_spotSounds.stopSound();
_spotSounds.playSoundSegment(in, out);
while (_spotSounds.isPlaying()) {
InputDevice.pumpEvents();
_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 {
InputDevice.pumpEvents();
_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->isOldDemo() && (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();
// HACK: Some ambient loops are actually sound effects, like Ares waiting at
// the reactor and Poseidon at the launch console. Detect these and use the
// SFX volume instead of ambience.
if (soundName == "Sounds/Mars/Robot Loop.aiff" ||
soundName == "Sounds/Norad/Breathing Typing.22K.AIFF" ||
soundName == "Sounds/Norad/N54NAS.32K.AIFF")
_loop2Fader.setMasterVolume(_vm->getSoundFXLevel());
else
_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.setVolume(_vm->getSoundFXLevel());
_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()) {
InputDevice.pumpEvents();
_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: The original calls pauseFuse() here, which causes a bug with the robot
// in WSC on the catwalk, causing him never to come after you if you don't act
// against him.
_eventTimer.resumeFuse();
}
bool Neighborhood::timerPaused() {
return _eventTimer.isFusePaused();
}
} // End of namespace Pegasus