/* 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. * * 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 "common/config-manager.h" #include "engines/engine.h" #include "graphics/palette.h" #include "tsage/tsage.h" #include "tsage/core.h" #include "tsage/dialogs.h" #include "tsage/events.h" #include "tsage/scenes.h" #include "tsage/staticres.h" #include "tsage/globals.h" #include "tsage/sound.h" namespace tSage { // The engine uses ScumMVM screen buffering, so all logic is hardcoded to use pane buffer 0 #define CURRENT_PANENUM 0 /*--------------------------------------------------------------------------*/ InvObject::InvObject(int sceneNumber, int rlbNum, int cursorNum, CursorType cursorId, const Common::String description) : _sceneNumber(sceneNumber), _rlbNum(rlbNum), _cursorNum(cursorNum), _cursorId(cursorId), _description(description) { _displayResNum = 3; _iconResNum = 5; // Decode the image for the inventory item to get it's display bounds uint size; byte *imgData = _resourceManager->getSubResource(_displayResNum, _rlbNum, _cursorNum, &size); GfxSurface s = surfaceFromRes(imgData); _bounds = s.getBounds(); DEALLOCATE(imgData); } void InvObject::setCursor() { _globals->_events._currentCursor = _cursorId; if (_iconResNum != -1) { GfxSurface s = surfaceFromRes(_iconResNum, _rlbNum, _cursorNum); Graphics::Surface src = s.lockSurface(); _globals->_events.setCursor(src, s._transColor, s._centroid, _cursorId); } } /*--------------------------------------------------------------------------*/ InvObjectList::InvObjectList() { _selectedItem = NULL; } void InvObjectList::synchronize(Serializer &s) { SavedObject::synchronize(s); SYNC_POINTER(_selectedItem); } /*--------------------------------------------------------------------------*/ void EventHandler::dispatch() { if (_action) _action->dispatch(); } void EventHandler::setAction(Action *action, EventHandler *endHandler, ...) { if (_action) { _action->_endHandler = NULL; _action->remove(); } _action = action; if (action) { va_list va; va_start(va, endHandler); _action->attached(this, endHandler, va); va_end(va); } } /*--------------------------------------------------------------------------*/ Action::Action() { _actionIndex = 0; _owner = NULL; _endHandler = NULL; _attached = false; } void Action::synchronize(Serializer &s) { EventHandler::synchronize(s); if (s.getVersion() == 1) remove(); SYNC_POINTER(_owner); s.syncAsSint32LE(_actionIndex); s.syncAsSint32LE(_delayFrames); s.syncAsUint32LE(_startFrame); s.syncAsByte(_attached); SYNC_POINTER(_endHandler); } void Action::remove() { if (_action) _action->remove(); if (_owner) { _owner->_action = NULL; _owner = NULL; } else { _globals->_sceneManager.removeAction(this); } _attached = false; if (_endHandler) _endHandler->signal(); } void Action::process(Event &event) { if (_action) _action->process(event); } void Action::dispatch() { if (_action) _action->dispatch(); if (_delayFrames) { uint32 frameNumber = _globals->_events.getFrameNumber(); if (frameNumber >= _startFrame) { _delayFrames -= frameNumber - _startFrame; _startFrame = frameNumber; if (_delayFrames <= 0) { _delayFrames = 0; signal(); } } } } void Action::attached(EventHandler *newOwner, EventHandler *endHandler, va_list va) { _actionIndex = 0; _delayFrames = 0; _startFrame = _globals->_events.getFrameNumber(); _owner = newOwner; _endHandler = endHandler; _attached = true; signal(); } void Action::setDelay(int numFrames) { _delayFrames = numFrames; _startFrame = _globals->_events.getFrameNumber(); } /*--------------------------------------------------------------------------*/ ObjectMover::~ObjectMover() { if (_sceneObject->_mover == this) _sceneObject->_mover = NULL; } void ObjectMover::synchronize(Serializer &s) { EventHandler::synchronize(s); s.syncAsSint16LE(_destPosition.x); s.syncAsSint16LE(_destPosition.y); s.syncAsSint16LE(_moveDelta.x); s.syncAsSint16LE(_moveDelta.y); s.syncAsSint16LE(_moveSign.x); s.syncAsSint16LE(_moveSign.y); s.syncAsSint32LE(_minorDiff); s.syncAsSint32LE(_majorDiff); s.syncAsSint32LE(_changeCtr); SYNC_POINTER(_action); SYNC_POINTER(_sceneObject); } void ObjectMover::remove() { if (_sceneObject->_mover == this) _sceneObject->_mover = NULL; delete this; } void ObjectMover::dispatch() { Common::Point currPos = _sceneObject->_position; int yDiff = _sceneObject->_yDiff; if (dontMove()) return; _sceneObject->_regionIndex = 0; if (_moveDelta.x >= _moveDelta.y) { int xAmount = _moveSign.x * _sceneObject->_moveDiff.x * _sceneObject->_percent / 100; if (!xAmount) xAmount = _moveSign.x; currPos.x += xAmount; int yAmount = ABS(_destPosition.y - currPos.y); int yChange = _majorDiff / ABS(xAmount); int ySign; if (!yChange) ySign = _moveSign.y; else { int v = yAmount / yChange; _changeCtr += yAmount % yChange; if (_changeCtr >= yChange) { ++v; _changeCtr -= yChange; } ySign = _moveSign.y * v; } currPos.y += ySign; _majorDiff -= ABS(xAmount); } else { int yAmount = _moveSign.y * _sceneObject->_moveDiff.y * _sceneObject->_percent / 100; if (!yAmount) yAmount = _moveSign.y; currPos.y += yAmount; int xAmount = ABS(_destPosition.x - currPos.x); int xChange = _majorDiff / ABS(yAmount); int xSign; if (!xChange) xSign = _moveSign.x; else { int v = xAmount / xChange; _changeCtr += xAmount % xChange; if (_changeCtr >= xChange) { ++v; _changeCtr -= xChange; } xSign = _moveSign.x * v; } currPos.x += xSign; _majorDiff -= ABS(yAmount); } _sceneObject->_regionIndex = _sceneObject->checkRegion(currPos); if (!_sceneObject->_regionIndex) { _sceneObject->setPosition(currPos, yDiff); _sceneObject->getHorizBounds(); if (dontMove()) { _sceneObject->_position = _destPosition; endMove(); } } else { endMove(); } } void ObjectMover::setup(const Common::Point &destPos) { _sceneObject->calcAngle(destPos); if ((_sceneObject->_objectWrapper) && !(_sceneObject->_flags & OBJFLAG_SUPPRESS_DISPATCH)) _sceneObject->_objectWrapper->dispatch(); // Get the difference int diffX = destPos.x - _sceneObject->_position.x; int diffY = destPos.y - _sceneObject->_position.y; int xSign = (diffX < 0) ? -1 : (diffX > 0 ? 1 : 0); int ySign = (diffY < 0) ? -1 : (diffY > 0 ? 1 : 0); diffX = ABS(diffX); diffY = ABS(diffY); if (diffX < diffY) { _minorDiff = diffX / 2; _majorDiff = diffY; } else { _minorDiff = diffY / 2; _majorDiff = diffX; } // Set the destination position _destPosition = destPos; _moveDelta = Common::Point(diffX, diffY); _moveSign = Common::Point(xSign, ySign); _changeCtr = 0; if (!diffX && !diffY) // Object is already at the correct destination endMove(); } bool ObjectMover::dontMove() const { return (_majorDiff <= 0); } void ObjectMover::endMove() { EventHandler *actionP = _action; remove(); if (actionP) actionP->signal(); } /*--------------------------------------------------------------------------*/ ObjectMover2::ObjectMover2() : ObjectMover() { _destObject = NULL; } void ObjectMover2::synchronize(Serializer &s) { ObjectMover::synchronize(s); SYNC_POINTER(_destObject); s.syncAsSint32LE(_minArea); s.syncAsSint32LE(_maxArea); } void ObjectMover2::dispatch() { int area = _sceneObject->getSpliceArea(_destObject); if (area > _maxArea) { // Setup again for the new destination setup(_destObject->_position); ObjectMover::dispatch(); } else if (area >= _minArea) { // Keep dispatching ObjectMover::dispatch(); } else { // Within minimum, so end move endMove(); } } void ObjectMover2::startMove(SceneObject *sceneObj, va_list va) { // Set up fields _sceneObject = sceneObj; _minArea = va_arg(va, int); _maxArea = va_arg(va, int); _destObject = va_arg(va, SceneObject *); setup(_destObject->_position); } void ObjectMover2::endMove() { _sceneObject->_regionIndex = 0x40; } /*--------------------------------------------------------------------------*/ void ObjectMover3::dispatch() { int area = _sceneObject->getSpliceArea(_destObject); if (area <= _minArea) { endMove(); } else { setup(_destObject->_position); ObjectMover::dispatch(); } } void ObjectMover3::startMove(SceneObject *sceneObj, va_list va) { _sceneObject = sceneObj; _destObject = va_arg(va, SceneObject *); _minArea = va_arg(va, int); _action = va_arg(va, Action *); setup(_destObject->_position); } void ObjectMover3::endMove() { ObjectMover::endMove(); } /*--------------------------------------------------------------------------*/ void NpcMover::startMove(SceneObject *sceneObj, va_list va) { _sceneObject = sceneObj; Common::Point *destPos = va_arg(va, Common::Point *); _action = va_arg(va, Action *); setup(*destPos); } /*--------------------------------------------------------------------------*/ void PlayerMover::synchronize(Serializer &s) { NpcMover::synchronize(s); s.syncAsSint16LE(_finalDest.x); s.syncAsSint16LE(_finalDest.y); s.syncAsSint32LE(_routeIndex); for (int i = 0; i < MAX_ROUTE_SIZE; ++i) { s.syncAsSint16LE(_routeList[i].x); s.syncAsSint16LE(_routeList[i].y); } } void PlayerMover::startMove(SceneObject *sceneObj, va_list va) { _sceneObject = sceneObj; Common::Point *pt = va_arg(va, Common::Point *); _finalDest = *pt; _action = va_arg(va, Action *); setDest(_finalDest); } void PlayerMover::endMove() { while (++_routeIndex != 0) { if ((_routeList[_routeIndex].x == ROUTE_END_VAL) || (_routeList[_routeIndex].y == ROUTE_END_VAL) || (_sceneObject->_regionIndex)) { // Movement route is completely finished ObjectMover::endMove(); return; } if ((_routeList[_routeIndex].x != _sceneObject->_position.x) || (_routeList[_routeIndex].y != _sceneObject->_position.y)) break; } // Set up the new interim destination along the route _globals->_walkRegions._routeEnds.moveSrc = _globals->_walkRegions._routeEnds.moveDest; _globals->_walkRegions._routeEnds.moveDest = _routeList[_routeIndex]; setup(_routeList[_routeIndex]); dispatch(); } void PlayerMover::setDest(const Common::Point &destPos) { _routeList[0] = _sceneObject->_position; if (_globals->_walkRegions._resNum == -1) { // Scene has no walk regions defined, so player can walk anywhere directly _routeList[0] = destPos; _routeList[1] = Common::Point(ROUTE_END_VAL, ROUTE_END_VAL); } else { // Figure out a path to the destination (or as close as possible to it) pathfind(_routeList, _sceneObject->_position, destPos, _globals->_walkRegions._routeEnds); } _routeIndex = 0; _globals->_walkRegions._routeEnds.moveSrc = _sceneObject->_position; _globals->_walkRegions._routeEnds.moveDest = _routeList[0]; setup(_routeList[0]); } #define REGION_LIST_SIZE 20 void PlayerMover::pathfind(Common::Point *routeList, Common::Point srcPos, Common::Point destPos, RouteEnds routeEnds) { Common::List regionIndexes; RouteEnds tempRouteEnds; int routeRegions[REGION_LIST_SIZE]; Common::Point objPos; // Get the region the source is in int srcRegion = _globals->_walkRegions.indexOf(srcPos); if (srcRegion == -1) { srcRegion = findClosestRegion(srcPos, regionIndexes); } // Main loop for building up the path routeRegions[0] = 0; while (!routeRegions[0]) { // Check the destination region int destRegion = _globals->_walkRegions.indexOf(destPos, ®ionIndexes); if ((srcRegion == -1) && (destRegion == -1)) { // Both source and destination are outside walkable areas } else if (srcRegion == -1) { // Source is outside walkable areas tempRouteEnds = routeEnds; objPos = _sceneObject->_position; Common::Point newPos; findLinePoint(&tempRouteEnds, &objPos, 1, &newPos); int srcId = _globals->_walkRegions.indexOf(newPos); if (srcId == -1) { tempRouteEnds.moveDest = tempRouteEnds.moveSrc; tempRouteEnds.moveSrc = routeEnds.moveDest; findLinePoint(&tempRouteEnds, &objPos, 1, &newPos); srcRegion = _globals->_walkRegions.indexOf(newPos); if (srcRegion == -1) srcRegion = checkMover(srcPos, destPos); } } else if (destRegion == -1) { // Destination is outside walkable areas destRegion = findClosestRegion(destPos, regionIndexes); if (destRegion == -1) { // No further route found, so end it *routeList++ = srcPos; break; } else { _finalDest = destPos; } } if (srcRegion == destRegion) { *routeList++ = (srcRegion == -1) ? srcPos : destPos; break; } bool tempVar; // This is used only as internal state for the function. calculateRestOfRoute(routeRegions, srcRegion, destRegion, tempVar); // Empty route? if (!routeRegions[0]) { regionIndexes.push_back(destRegion); continue; } // field 0 holds the start, and field 1 holds the destination WRField18 &currSrcField = _globals->_walkRegions._field18[0]; WRField18 &currDestField = _globals->_walkRegions._field18[1]; currSrcField._pt1 = srcPos; currSrcField._pt2 = srcPos; currDestField._pt1 = destPos; currDestField._pt2 = destPos; int tempList[REGION_LIST_SIZE]; tempList[0] = 0; int endIndex = 0; int idx = 1; // Find the indexes for each entry in the found route. do { int breakEntry = routeRegions[idx]; int breakEntry2 = routeRegions[idx + 1]; int listIndex = 0; while (_globals->_walkRegions._idxList[_globals->_walkRegions[breakEntry]._idxListIndex + listIndex] != breakEntry2) ++listIndex; tempList[idx] = _globals->_walkRegions._idxList2[_globals->_walkRegions[breakEntry]._idxList2Index + listIndex]; ++endIndex; } while (routeRegions[++idx] != destRegion); tempList[idx] = 1; for (int listIndex = 1; listIndex <= endIndex; ++listIndex) { int thisIdx = tempList[listIndex]; int nextIdx = tempList[listIndex + 1]; WRField18 &thisField = _globals->_walkRegions._field18[thisIdx]; WRField18 &nextField = _globals->_walkRegions._field18[nextIdx]; if (sub_F8E5_calculatePoint(currSrcField._pt1, nextField._pt1, thisField._pt1, thisField._pt2) && sub_F8E5_calculatePoint(currSrcField._pt1, nextField._pt2, thisField._pt1, thisField._pt2)) continue; Common::Point tempPt; if (sub_F8E5_calculatePoint(currSrcField._pt1, currDestField._pt1, thisField._pt1, thisField._pt2, &tempPt)) { // Add point to the route list currSrcField._pt1 = tempPt; *routeList++ = tempPt; } else { int dist1 = (findDistance(currSrcField._pt1, thisField._pt1) << 1) + (findDistance(thisField._pt1, currDestField._pt1) << 1) + findDistance(thisField._pt1, nextField._pt1) + findDistance(thisField._pt1, nextField._pt2); int dist2 = (findDistance(currSrcField._pt1, thisField._pt2) << 1) + (findDistance(thisField._pt2, currDestField._pt2) << 1) + findDistance(thisField._pt2, nextField._pt1) + findDistance(thisField._pt2, nextField._pt2); // Do 1 step of movement, storing the new position in objPos. if (dist1 < dist2) { doStepsOfNpcMovement(thisField._pt1, thisField._pt2, 1, objPos); } else { doStepsOfNpcMovement(thisField._pt2, thisField._pt1, 1, objPos); } // Update the current position. currSrcField._pt1 = objPos; *routeList++ = objPos; } } // Add in the route entry *routeList++ = currDestField._pt1; } // Mark the end of the path *routeList = Common::Point(ROUTE_END_VAL, ROUTE_END_VAL); } int PlayerMover::regionIndexOf(const Common::Point &pt) { for (uint idx = 0; idx < _globals->_walkRegions._regionList.size(); ++idx) { if (_globals->_walkRegions._regionList[idx].contains(pt)) return idx + 1; } return 0; } int PlayerMover::findClosestRegion(Common::Point &pt, const Common::List &indexList) { int newY = pt.y; int result = 0; for (int idx = 1; idx < SCREEN_WIDTH; ++idx, newY += idx) { int newX = pt.x + idx; result = regionIndexOf(newX, pt.y); if ((result == 0) || contains(indexList, result)) { newY = pt.y + idx; result = regionIndexOf(newX, newY); if ((result == 0) || contains(indexList, result)) { newX -= idx; result = regionIndexOf(newX, newY); if ((result == 0) || contains(indexList, result)) { newX -= idx; result = regionIndexOf(newX, newY); if ((result == 0) || contains(indexList, result)) { newY -= idx; result = regionIndexOf(newX, newY); if ((result == 0) || contains(indexList, result)) { newY -= idx; result = regionIndexOf(newX, newY); if ((result == 0) || contains(indexList, result)) { newX += idx; result = regionIndexOf(newX, newY); if ((result == 0) || contains(indexList, result)) { newX += idx; result = regionIndexOf(newX, newY); if ((result == 0) || contains(indexList, result)) { continue; } } } } } } } } // Found an index pt.x = newX; pt.y = newY; return result; } return (result == 0) ? -1 : result; } Common::Point *PlayerMover::findLinePoint(RouteEnds *routeEnds, Common::Point *objPos, int length, Common::Point *outPos) { int xp = objPos->x + (((routeEnds->moveDest.y - routeEnds->moveSrc.y) * 9) / 8); int yp = objPos->y - (((routeEnds->moveDest.x - routeEnds->moveSrc.x) * 8) / 9); int xDiff = xp - objPos->x; int yDiff = yp - objPos->y; int xDirection = (xDiff == 0) ? 0 : ((xDiff < 0) ? 1 : -1); int yDirection = (yDiff == 0) ? 0 : ((yDiff < 0) ? 1 : -1); xDiff = ABS(xDiff); yDiff = ABS(yDiff); int majorChange = MAX(xDiff, yDiff) / 2; int outX = objPos->x; int outY = objPos->y; while (length-- > 0) { if (xDiff < yDiff) { outY += yDirection; majorChange += xDiff; if (majorChange > yDiff) { majorChange -= yDiff; outX += xDirection; } } else { outX += xDirection; majorChange += yDiff; if (majorChange > xDiff) { majorChange -= xDiff; outY += yDirection; } } } outPos->x = outX; outPos->y = outY; return outPos; } int PlayerMover::checkMover(Common::Point &srcPos, const Common::Point &destPos) { int regionIndex = 0; Common::Point objPos = _sceneObject->_position; uint32 regionBitList = _sceneObject->_regionBitList; _sceneObject->_regionBitList = 0; _sceneObject->_position.x = srcPos.x; _sceneObject->_position.y = srcPos.y; _sceneObject->_mover = NULL; NpcMover *mover = new NpcMover(); _sceneObject->addMover(mover, &destPos, NULL); // Handle automatic movement of the player until a walkable region is reached, // or the end point of the movement is do { _sceneObject->_mover->dispatch(); // Scan walk regions for point for (uint idx = 0; idx < _globals->_walkRegions._regionList.size(); ++idx) { if (_globals->_walkRegions[idx].contains(_sceneObject->_position)) { regionIndex = idx + 1; srcPos = _sceneObject->_position; break; } } } while ((regionIndex == 0) && (_sceneObject->_mover) && !_vm->shouldQuit()); _sceneObject->_position = objPos; _sceneObject->_regionBitList = regionBitList; if (_sceneObject->_mover) _sceneObject->_mover->remove(); _sceneObject->_mover = this; return regionIndex; } void PlayerMover::doStepsOfNpcMovement(const Common::Point &srcPos, const Common::Point &destPos, int numSteps, Common::Point &ptOut) { Common::Point objPos = _sceneObject->_position; _sceneObject->_position = srcPos; uint32 regionBitList = _sceneObject->_regionBitList; _sceneObject->_position = srcPos; _sceneObject->_mover = NULL; NpcMover *mover = new NpcMover(); _sceneObject->addMover(mover, &destPos, NULL); while ((numSteps > 0) && ((_sceneObject->_position.x != destPos.x) || (_sceneObject->_position.y != destPos.y))) { _sceneObject->_mover->dispatch(); --numSteps; } ptOut = _sceneObject->_position; _sceneObject->_position = objPos; _sceneObject->_regionBitList = regionBitList; if (_sceneObject->_mover) _sceneObject->_mover->remove(); _sceneObject->_mover = this; } int PlayerMover::calculateRestOfRoute(int *routeList, int srcRegion, int destRegion, bool &foundRoute) { // Make a copy of the provided route. The first entry is the size. int tempList[REGION_LIST_SIZE + 1]; foundRoute = false; for (int idx = 0; idx <= *routeList; ++idx) tempList[idx] = routeList[idx]; if (*routeList == REGION_LIST_SIZE) // Sequence too long return 32000; int regionIndex; for (regionIndex = 1; regionIndex <= *tempList; ++regionIndex) { if (routeList[regionIndex] == srcRegion) // Current path returns to original source region, so don't allow it return 32000; } WalkRegion &srcWalkRegion = _globals->_walkRegions[srcRegion]; int distance; if (!routeList[0]) { // The route is empty (new route). distance = 0; } else { // Find the distance from the last region in the route. WalkRegion ®ion = _globals->_walkRegions[routeList[*routeList]]; distance = findDistance(srcWalkRegion._pt, region._pt); } // Add the srcRegion to the end of the route. tempList[++*tempList] = srcRegion; int ourListSize = *tempList; if (srcRegion == destRegion) { // We made a route to the destination; copy that route and return. foundRoute = true; for (int idx = ourListSize; idx <= *tempList; ++idx) { routeList[idx] = tempList[idx]; ++*routeList; } return distance; } else { // Find the first connected region leading to our destination. int foundIndex = 0; int idx = 0; int currDest; while ((currDest = _globals->_walkRegions._idxList[srcWalkRegion._idxListIndex + idx]) != 0) { if (currDest == destRegion) { foundIndex = idx; break; } ++idx; } // Check every connected region until we find a route to the destination (or we have no more to check). int bestDistance = 31990; while (((currDest = _globals->_walkRegions._idxList[srcWalkRegion._idxListIndex + foundIndex]) != 0) && (!foundRoute)) { int newDistance = calculateRestOfRoute(tempList, currDest, destRegion, foundRoute); if ((newDistance <= bestDistance) || foundRoute) { // We found a shorter possible route, or one leading to the destination. // Overwrite the route with this new one. routeList[0] = ourListSize - 1; for (int i = ourListSize; i <= tempList[0]; ++i) { routeList[i] = tempList[i]; ++routeList[0]; } bestDistance = newDistance; } // Truncate our local list to the size it was before the call. tempList[0] = ourListSize; ++foundIndex; } foundRoute = false; return bestDistance + distance; } } int PlayerMover::findDistance(const Common::Point &pt1, const Common::Point &pt2) { int diff = ABS(pt1.x - pt2.x); double xx = diff * diff; diff = ABS(pt1.y - pt2.y); double yy = diff * 8.0 / 7.0; yy *= yy; return (int)sqrt(xx + yy); } bool PlayerMover::sub_F8E5_calculatePoint(const Common::Point &pt1, const Common::Point &pt2, const Common::Point &pt3, const Common::Point &pt4, Common::Point *ptOut) { double diffX1 = pt2.x - pt1.x; double diffY1 = pt2.y - pt1.y; double diffX2 = pt4.x - pt3.x; double diffY2 = pt4.y - pt3.y; double ratio1 = 0.0, ratio2 = 0.0; double adjustedY1 = 0.0, adjustedY2 = 0.0; // Calculate the ratios between the X and Y points. if (diffX1 != 0.0) { ratio1 = diffY1 / diffX1; adjustedY1 = pt1.y - (pt1.x * ratio1); } if (diffX2 != 0.0) { ratio2 = diffY2 / diffX2; adjustedY2 = pt3.y - (pt3.x * ratio2); } if (ratio1 == ratio2) return false; double xPos, yPos; if (diffX1 == 0) { if (diffX2 == 0) return false; xPos = pt1.x; yPos = ratio2 * xPos + adjustedY2; } else { xPos = (diffX2 == 0) ? pt3.x : (adjustedY2 - adjustedY1) / (ratio1 - ratio2); yPos = ratio1 * xPos + adjustedY1; } // This is our candidate point, which we must check for validity. Common::Point tempPt((int)(xPos + 0.5), (int)(yPos + 0.5)); // Is tempPt inside the second bounds? if (!((tempPt.x >= pt3.x) && (tempPt.x <= pt4.x))) if (!((tempPt.x >= pt4.x) && (tempPt.x <= pt3.x))) return false; if (!((tempPt.y >= pt3.y) && (tempPt.y <= pt4.y))) if (!((tempPt.y >= pt4.y) && (tempPt.y <= pt3.y))) return false; // Is tempPt inside the first bounds? if (!((tempPt.x >= pt1.x) && (tempPt.x <= pt2.x))) if (!((tempPt.x >= pt2.x) && (tempPt.x <= pt1.x))) return false; if (!((tempPt.y >= pt1.y) && (tempPt.y <= pt2.y))) if (!((tempPt.y >= pt2.y) && (tempPt.y <= pt1.y))) return false; if (ptOut) *ptOut = tempPt; return true; } /*--------------------------------------------------------------------------*/ void PlayerMover2::synchronize(Serializer &s) { if (s.getVersion() >= 2) PlayerMover::synchronize(s); SYNC_POINTER(_destObject); s.syncAsSint16LE(_maxArea); s.syncAsSint16LE(_minArea); } void PlayerMover2::dispatch() { int total = _sceneObject->getSpliceArea(_destObject); if (total <= _minArea) endMove(); else { setDest(_destObject->_position); ObjectMover::dispatch(); } } void PlayerMover2::startMove(SceneObject *sceneObj, va_list va) { _sceneObject = sceneObj; _maxArea = va_arg(va, int); _minArea = va_arg(va, int); _destObject = va_arg(va, SceneObject *); PlayerMover::setDest(_destObject->_position); } void PlayerMover2::endMove() { _sceneObject->_regionIndex = 0x40; } /*--------------------------------------------------------------------------*/ PaletteModifier::PaletteModifier() { _scenePalette = NULL; _action = NULL; } /*--------------------------------------------------------------------------*/ PaletteModifierCached::PaletteModifierCached(): PaletteModifier() { _step = 0; _percent = 0; } void PaletteModifierCached::setPalette(ScenePalette *palette, int step) { _scenePalette = palette; _step = step; _percent = 100; } void PaletteModifierCached::synchronize(Serializer &s) { PaletteModifier::synchronize(s); s.syncAsByte(_step); s.syncAsSint32LE(_percent); } /*--------------------------------------------------------------------------*/ PaletteRotation::PaletteRotation() : PaletteModifierCached() { _percent = 0; _delayCtr = 0; _frameNumber = _globals->_events.getFrameNumber(); } void PaletteRotation::synchronize(Serializer &s) { PaletteModifierCached::synchronize(s); s.syncAsSint32LE(_delayCtr); s.syncAsUint32LE(_frameNumber); s.syncAsSint32LE(_currIndex); s.syncAsSint32LE(_start); s.syncAsSint32LE(_end); s.syncAsSint32LE(_rotationMode); s.syncAsSint32LE(_duration); s.syncBytes(&_palette[0], 256 * 3); } void PaletteRotation::signal() { if (_delayCtr) { uint32 frameNumber = _globals->_events.getFrameNumber(); if (frameNumber >= _frameNumber) { _delayCtr = frameNumber - _frameNumber; _frameNumber = frameNumber; if (_delayCtr < 0) _delayCtr = 0; } } if (_delayCtr) return; _delayCtr = _percent; if (_step) return; bool flag = true; switch (_rotationMode) { case -1: if (--_currIndex < _start) { flag = decDuration(); if (flag) _currIndex = _end - 1; } break; case 1: if (++_currIndex >= _end) { flag = decDuration(); if (flag) _currIndex = _start; } break; case 2: if (++_currIndex >= _end) { flag = decDuration(); if (flag) { _currIndex = _end - 2; _rotationMode = 3; } } break; case 3: if (--_currIndex < _start) { flag = decDuration(); if (flag) { _currIndex = _start + 1; _rotationMode = 2; } } break; } if (flag) { int count2 = _currIndex - _start; int count = _end - _currIndex; g_system->getPaletteManager()->setPalette((const byte *)&_palette[_currIndex * 3], _start, count); if (count2) { g_system->getPaletteManager()->setPalette((const byte *)&_palette[_start * 3], _start + count, count2); } } } void PaletteRotation::remove() { Action *action = _action; g_system->getPaletteManager()->setPalette((const byte *)&_palette[_start * 3], _start, _end - _start); _scenePalette->_listeners.remove(this); delete this; if (action) action->signal(); } void PaletteRotation::set(ScenePalette *palette, int start, int end, int rotationMode, int duration, Action *action) { _duration = duration; _step = false; _action = action; _scenePalette = palette; Common::copy(&palette->_palette[0], &palette->_palette[256 * 3], &_palette[0]); _start = start; _end = end + 1; _rotationMode = rotationMode; switch (_rotationMode) { case -1: case 3: _currIndex = _end; break; default: _currIndex = _start; break; } } bool PaletteRotation::decDuration() { if (_duration) { if (--_duration == 0) { remove(); return false; } } return true; } void PaletteRotation::setDelay(int amount) { _percent = _delayCtr = amount; } /*--------------------------------------------------------------------------*/ void PaletteFader::synchronize(Serializer &s) { PaletteModifierCached::synchronize(s); s.syncAsSint16LE(_step); s.syncAsSint16LE(_percent); s.syncBytes(&_palette[0], 256 * 3); } void PaletteFader::signal() { _percent -= _step; if (_percent > 0) { _scenePalette->fade((byte *)_palette, true /* 256 */, _percent); } else { remove(); } } void PaletteFader::remove() { // Save of a copy of the object's action, since it will be used after the object is destroyed Action *action = _action; Common::copy(&_palette[0], &_palette[256 * 3], &_scenePalette->_palette[0]); _scenePalette->refresh(); _scenePalette->_listeners.remove(this); delete this; if (action) action->signal(); } /*--------------------------------------------------------------------------*/ ScenePalette::ScenePalette() { // Set a default gradiant range byte *palData = &_palette[0]; for (int idx = 0; idx < 256; ++idx) { *palData++ = idx; *palData++ = idx; *palData++ = idx; } _field412 = 0; } ScenePalette::~ScenePalette() { clearListeners(); } ScenePalette::ScenePalette(int paletteNum) { loadPalette(paletteNum); } bool ScenePalette::loadPalette(int paletteNum) { byte *palData = _resourceManager->getResource(RES_PALETTE, paletteNum, 0, true); if (!palData) return false; int palStart = READ_LE_UINT16(palData); int palSize = READ_LE_UINT16(palData + 2); assert(palSize <= 256); byte *destP = &_palette[palStart * 3]; byte *srcP = palData + 6; Common::copy(&srcP[0], &srcP[palSize * 3], destP); DEALLOCATE(palData); return true; } void ScenePalette::refresh() { // Set indexes for standard colors to closest color in the palette _colors.background = indexOf(255, 255, 255); // White background _colors.foreground = indexOf(0, 0, 0); // Black foreground _redColor = indexOf(180, 0, 0); // Red-ish _greenColor = indexOf(0, 180, 0); // Green-ish _blueColor = indexOf(0, 0, 180); // Blue-ish _aquaColor = indexOf(0, 180, 180); // Aqua _purpleColor = indexOf(180, 0, 180); // Purple _limeColor = indexOf(180, 180, 0); // Lime // Refresh the palette g_system->getPaletteManager()->setPalette((const byte *)&_palette[0], 0, 256); } /** * Loads a section of the palette into the game palette */ void ScenePalette::setPalette(int index, int count) { g_system->getPaletteManager()->setPalette((const byte *)&_palette[index * 3], index, count); } /** * Returns the palette index with the closest matching color to that specified * @param r R component * @param g G component * @param b B component * @param threshold Closeness threshold. * @remarks A threshold may be provided to specify how close the matching color must be */ uint8 ScenePalette::indexOf(uint r, uint g, uint b, int threshold) { int palIndex = -1; byte *palData = &_palette[0]; for (int i = 0; i < 256; ++i) { byte ir = *palData++; byte ig = *palData++; byte ib = *palData++; int rDiff = abs(ir - (int)r); int gDiff = abs(ig - (int)g); int bDiff = abs(ib - (int)b); int idxThreshold = rDiff * rDiff + gDiff * gDiff + bDiff * bDiff; if (idxThreshold < threshold) { threshold = idxThreshold; palIndex = i; } } return palIndex; } /** * Loads the specified range of the palette with the current system palette * @param start Start index * @param count Number of palette entries */ void ScenePalette::getPalette(int start, int count) { g_system->getPaletteManager()->grabPalette((byte *)&_palette[start], start, count); } void ScenePalette::signalListeners() { SynchronizedList::iterator i = _listeners.begin(); while (i != _listeners.end()) { PaletteModifier *obj = *i; ++i; obj->signal(); } } void ScenePalette::clearListeners() { SynchronizedList::iterator i = _listeners.begin(); while (i != _listeners.end()) { PaletteModifier *obj = *i; ++i; obj->remove(); } } void ScenePalette::fade(const byte *adjustData, bool fullAdjust, int percent) { byte tempPalette[256 * 3]; // Ensure the percent adjustment is within 0 - 100% percent = CLIP(percent, 0, 100); for (int palIndex = 0; palIndex < 256; ++palIndex) { const byte *srcP = (const byte *)&_palette[palIndex * 3]; byte *destP = &tempPalette[palIndex * 3]; for (int rgbIndex = 0; rgbIndex < 3; ++rgbIndex, ++srcP, ++destP) { *destP = *srcP - ((*srcP - adjustData[rgbIndex]) * (100 - percent)) / 100; } if (fullAdjust) adjustData += 3; } // Set the altered pale4tte g_system->getPaletteManager()->setPalette((const byte *)&tempPalette[0], 0, 256); g_system->updateScreen(); } PaletteRotation *ScenePalette::addRotation(int start, int end, int rotationMode, int duration, Action *action) { PaletteRotation *obj = new PaletteRotation(); if ((rotationMode == 2) || (rotationMode == 3)) duration <<= 1; obj->set(this, start, end, rotationMode, duration, action); _listeners.push_back(obj); return obj; } PaletteFader *ScenePalette::addFader(const byte *arrBufferRGB, int palSize, int percent, Action *action) { PaletteFader *fader = new PaletteFader(); fader->_action = action; for (int i = 0; i < 256 * 3; i += 3) { fader->_palette[i] = *(arrBufferRGB + 0); fader->_palette[i + 1] = *(arrBufferRGB + 1); fader->_palette[i + 2] = *(arrBufferRGB + 2); if (palSize > 1) arrBufferRGB += 3; } fader->setPalette(this, percent); _globals->_scenePalette._listeners.push_back(fader); return fader; } void ScenePalette::changeBackground(const Rect &bounds, FadeMode fadeMode) { ScenePalette tempPalette; if (_globals->_sceneManager._hasPalette) { if ((fadeMode == FADEMODE_GRADUAL) || (fadeMode == FADEMODE_IMMEDIATE)) { // Fade out any active palette tempPalette.getPalette(); uint32 adjustData = 0; for (int percent = 100; percent >= 0; percent -= 5) { if (fadeMode == FADEMODE_IMMEDIATE) percent = 0; tempPalette.fade((byte *)&adjustData, false, percent); g_system->delayMillis(10); } } else { _globals->_scenePalette.refresh(); _globals->_sceneManager._hasPalette = false; } } _globals->_screenSurface.copyFrom(_globals->_sceneManager._scene->_backSurface, bounds, Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), NULL); for (SynchronizedList::iterator i = tempPalette._listeners.begin(); i != tempPalette._listeners.end(); ++i) delete *i; tempPalette._listeners.clear(); } void ScenePalette::synchronize(Serializer &s) { if (s.getVersion() >= 2) SavedObject::synchronize(s); if (s.getVersion() >= 5) _listeners.synchronize(s); s.syncBytes(_palette, 256 * 3); s.syncAsSint32LE(_colors.foreground); s.syncAsSint32LE(_colors.background); s.syncAsSint32LE(_field412); s.syncAsByte(_redColor); s.syncAsByte(_greenColor); s.syncAsByte(_blueColor); s.syncAsByte(_aquaColor); s.syncAsByte(_purpleColor); s.syncAsByte(_limeColor); } /*--------------------------------------------------------------------------*/ void SceneItem::synchronize(Serializer &s) { EventHandler::synchronize(s); _bounds.synchronize(s); s.syncString(_msg); s.syncAsSint32LE(_fieldE); s.syncAsSint32LE(_field10); s.syncAsSint16LE(_position.x); s.syncAsSint32LE(_position.y); s.syncAsSint16LE(_yDiff); s.syncAsSint32LE(_sceneRegionId); } void SceneItem::remove() { _globals->_sceneItems.remove(this); } void SceneItem::doAction(int action) { const char *msg = NULL; switch ((int)action) { case CURSOR_LOOK: msg = LOOK_SCENE_HOTSPOT; break; case CURSOR_USE: msg = USE_SCENE_HOTSPOT; break; case CURSOR_TALK: msg = TALK_SCENE_HOTSPOT; break; case 0x1000: msg = SPECIAL_SCENE_HOTSPOT; break; default: msg = DEFAULT_SCENE_HOTSPOT; break; } GUIErrorMessage(msg); } bool SceneItem::contains(const Common::Point &pt) { const Rect &sceneBounds = _globals->_sceneManager._scene->_sceneBounds; if (_sceneRegionId == 0) return _bounds.contains(pt.x + sceneBounds.left, pt.y + sceneBounds.top); else return _globals->_sceneRegions.indexOf(Common::Point(pt.x + sceneBounds.left, pt.y + sceneBounds.top)) == _sceneRegionId; } void SceneItem::display(int resNum, int lineNum, ...) { Common::String msg = !resNum ? Common::String() : _resourceManager->getMessage(resNum, lineNum); if (_globals->_sceneObjects->contains(&_globals->_sceneText)) { _globals->_sceneText.remove(); _globals->_sceneObjects->draw(); } GfxFontBackup font; Common::Point pos(160, 100); Rect textRect; int maxWidth = 120; bool keepOnscreen = false; bool centerText = true; if (resNum) { va_list va; va_start(va, lineNum); int mode; do { // Get next instruction mode = va_arg(va, int); switch (mode) { case SET_WIDTH: // Set width maxWidth = va_arg(va, int); _globals->_sceneText._width = maxWidth; break; case SET_X: // Set the X Position pos.x = va_arg(va, int); break; case SET_Y: // Set the Y Position pos.y = va_arg(va, int); break; case SET_FONT: // Set the font number _globals->_sceneText._fontNumber = va_arg(va, int); _globals->gfxManager()._font.setFontNumber(_globals->_sceneText._fontNumber); break; case SET_BG_COLOR: { // Set the background color int bgColor = va_arg(va, int); _globals->gfxManager()._font._colors.background = bgColor; if (!bgColor) _globals->gfxManager().setFillFlag(false); break; } case SET_FG_COLOR: // Set the foreground color _globals->_sceneText._color1 = va_arg(va, int); _globals->gfxManager()._font._colors.foreground = _globals->_sceneText._color1; break; case SET_KEEP_ONSCREEN: // Suppresses immediate display keepOnscreen = va_arg(va, int) != 0; break; case SET_EXT_BGCOLOR: { // Set secondary bg color int v = va_arg(va, int); _globals->_sceneText._color2 = v; _globals->gfxManager()._font._colors2.background = v; break; } case SET_EXT_FGCOLOR: { // Set secondary fg color int v = va_arg(va, int); _globals->_sceneText._color3 = v; _globals->gfxManager()._font._colors.foreground = v; break; } case SET_POS_MODE: // Set whether a custom x/y is used centerText = va_arg(va, int) != 0; break; case SET_TEXT_MODE: // Set the text mode _globals->_sceneText._textMode = (TextAlign)va_arg(va, int); break; default: break; } } while (mode != LIST_END); va_end(va); } if (resNum) { // Get required bounding size _globals->gfxManager().getStringBounds(msg.c_str(), textRect, maxWidth); textRect.center(pos.x, pos.y); textRect.contain(_globals->gfxManager()._bounds); if (centerText) { _globals->_sceneText._color1 = _globals->_sceneText._color2; _globals->_sceneText._color2 = 0; _globals->_sceneText._color3 = 0; } _globals->_sceneText.setup(msg); if (centerText) { _globals->_sceneText.setPosition(Common::Point( _globals->_sceneManager._scene->_sceneBounds.left + textRect.left, _globals->_sceneManager._scene->_sceneBounds.top + textRect.top), 0); } else { _globals->_sceneText.setPosition(pos, 0); } _globals->_sceneText.fixPriority(255); _globals->_sceneObjects->draw(); } // Unless the flag is set to keep the message on-screen, show it until a mouse or keypress, then remove it if (!keepOnscreen && !msg.empty()) { Event event; // Keep event on-screen until a mouse or keypress while (!_vm->getEventManager()->shouldQuit() && !_globals->_events.getEvent(event, EVENT_BUTTON_DOWN | EVENT_KEYPRESS)) { g_system->updateScreen(); g_system->delayMillis(10); } _globals->_sceneText.remove(); } } /*--------------------------------------------------------------------------*/ void SceneHotspot::doAction(int action) { switch ((int)action) { case CURSOR_LOOK: display(1, 0, SET_Y, 20, SET_WIDTH, 200, SET_EXT_BGCOLOR, 7, LIST_END); break; case CURSOR_USE: display(1, 5, SET_Y, 20, SET_WIDTH, 200, SET_EXT_BGCOLOR, 7, LIST_END); break; case CURSOR_TALK: display(1, 15, SET_Y, 20, SET_WIDTH, 200, SET_EXT_BGCOLOR, 7, LIST_END); break; case CURSOR_WALK: break; default: display(2, action, SET_Y, 20, SET_WIDTH, 200, SET_EXT_BGCOLOR, 7, LIST_END); break; } } /*--------------------------------------------------------------------------*/ void NamedHotspot::doAction(int action) { switch (action) { case CURSOR_WALK: // Nothing break; case CURSOR_LOOK: if (_lookLineNum == -1) SceneHotspot::doAction(action); else SceneItem::display(_resnum, _lookLineNum, SET_Y, 20, SET_WIDTH, 200, SET_EXT_BGCOLOR, 7, LIST_END); break; case CURSOR_USE: if (_useLineNum == -1) SceneHotspot::doAction(action); else SceneItem::display(_resnum, _useLineNum, SET_Y, 20, SET_WIDTH, 200, SET_EXT_BGCOLOR, 7, LIST_END); break; default: SceneHotspot::doAction(action); break; } } void NamedHotspot::setup(int ys, int xs, int ye, int xe, const int resnum, const int lookLineNum, const int useLineNum) { setBounds(ys, xe, ye, xs); _resnum = resnum; _lookLineNum = lookLineNum; _useLineNum = useLineNum; _globals->_sceneItems.addItems(this, NULL); } void NamedHotspot::synchronize(Serializer &s) { SceneHotspot::synchronize(s); s.syncAsSint16LE(_resnum); s.syncAsSint16LE(_lookLineNum); s.syncAsSint16LE(_useLineNum); } /*--------------------------------------------------------------------------*/ void SceneObjectWrapper::setSceneObject(SceneObject *so) { _sceneObject = so; so->_strip = 1; so->_flags |= OBJFLAG_PANES; } void SceneObjectWrapper::synchronize(Serializer &s) { EventHandler::synchronize(s); SYNC_POINTER(_sceneObject); } void SceneObjectWrapper::remove() { delete this; } void SceneObjectWrapper::dispatch() { _visageImages.setVisage(_sceneObject->_visage); int frameCount = _visageImages.getFrameCount(); int angle = _sceneObject->_angle; int strip = _sceneObject->_strip; if (frameCount == 4) { if ((angle > 314) || (angle < 45)) strip = 4; if ((angle > 44) && (angle < 135)) strip = 1; if ((angle >= 135) && (angle < 225)) strip = 3; if ((angle >= 225) && (angle < 315)) strip = 2; } else if (frameCount == 8) { if ((angle > 330) || (angle < 30)) strip = 4; if ((angle >= 30) && (angle < 70)) strip = 7; if ((angle >= 70) && (angle < 110)) strip = 1; if ((angle >= 110) && (angle < 150)) strip = 5; if ((angle >= 150) && (angle < 210)) strip = 3; if ((angle >= 210) && (angle < 250)) strip = 6; if ((angle >= 250) && (angle < 290)) strip = 2; if ((angle >= 290) && (angle < 331)) strip = 8; } if (strip > frameCount) strip = frameCount; _sceneObject->setStrip(strip); } /*--------------------------------------------------------------------------*/ SceneObject::SceneObject() : SceneHotspot() { _endAction = NULL; _mover = NULL; _objectWrapper = NULL; _flags = 0; _walkStartFrame = 0; _animateMode = ANIM_MODE_NONE; _updateStartFrame = 0; _moveDiff.x = 5; _moveDiff.y = 3; _numFrames = 10; _numFrames = 10; _moveRate = 10; _regionBitList = 0; _sceneRegionId = 0; _percent = 100; _flags |= OBJFLAG_PANES; _frameChange = 0; _visage = 0; } SceneObject::SceneObject(const SceneObject &so) : SceneHotspot() { *this = so; if (_objectWrapper) // Create a fresh object wrapper for this object _objectWrapper = new SceneObjectWrapper(); } SceneObject::~SceneObject() { delete _mover; delete _objectWrapper; } int SceneObject::getNewFrame() { int frameNum = _frame + _frameChange; if (_frameChange > 0) { if (frameNum > getFrameCount()) { frameNum = 1; if (_animateMode == ANIM_MODE_1) ++frameNum; } } else if (frameNum < 1) { frameNum = getFrameCount(); } return frameNum; } int SceneObject::getFrameCount() { _visageImages.setVisage(_visage, _strip); return _visageImages.getFrameCount(); } void SceneObject::animEnded() { _animateMode = ANIM_MODE_NONE; if (_endAction) _endAction->signal(); } int SceneObject::changeFrame() { int frameNum = _frame; uint32 mouseCtr = _globals->_events.getFrameNumber(); if ((_updateStartFrame <= mouseCtr) || (_animateMode == ANIM_MODE_1)) { if (_numFrames > 0) { int v = 60 / _numFrames; _updateStartFrame = mouseCtr + v; frameNum = getNewFrame(); } } return frameNum; } void SceneObject::setPosition(const Common::Point &p, int yDiff) { _position = p; _yDiff = yDiff; _flags |= OBJFLAG_PANES; } void SceneObject::setZoom(int percent) { assert((percent >= -1) && (percent < 999)); if (percent != _percent) { _percent = percent; _flags |= OBJFLAG_PANES; } } void SceneObject::updateZoom() { changeZoom(_percent); } void SceneObject::changeZoom(int percent) { if (percent == -1) _flags &= ~OBJFLAG_ZOOMED; else { _flags |= OBJFLAG_ZOOMED; setZoom(percent); } } void SceneObject::setStrip(int stripNum) { if (stripNum != _strip) { _strip = stripNum; _flags |= OBJFLAG_PANES; } } void SceneObject::setStrip2(int stripNum) { if (stripNum == -1) _flags &= ~OBJFLAG_SUPPRESS_DISPATCH; else { _flags |= OBJFLAG_SUPPRESS_DISPATCH; setStrip(stripNum); } } void SceneObject::setFrame(int frameNum) { if (frameNum != _frame) { _frame = frameNum; _flags |= OBJFLAG_PANES; } } void SceneObject::setFrame2(int frameNum) { if (frameNum != -1) { _flags |= OBJFLAG_NO_UPDATES; setFrame(frameNum); } else { _flags &= ~OBJFLAG_NO_UPDATES; } } void SceneObject::setPriority(int priority) { if (priority != _priority) { _priority = priority; _flags |= OBJFLAG_PANES; } } void SceneObject::fixPriority(int priority) { if (priority == -1) { _flags &= ~OBJFLAG_FIXED_PRIORITY; } else { _flags |= OBJFLAG_FIXED_PRIORITY; setPriority(priority); } } void SceneObject::setVisage(int visage) { if (visage != _visage) { _visage = visage; _flags |= OBJFLAG_PANES; } } void SceneObject::setObjectWrapper(SceneObjectWrapper *objWrapper) { if (_objectWrapper) _objectWrapper->remove(); _objectWrapper = objWrapper; if (objWrapper) objWrapper->setSceneObject(this); } void SceneObject::addMover(ObjectMover *mover, ...) { if (_mover) _mover->remove(); _mover = mover; if (mover) { // Set up the assigned mover _walkStartFrame = _globals->_events.getFrameNumber(); if (_moveRate != 0) _walkStartFrame = 60 / _moveRate; // Signal the mover that movement is beginning va_list va; va_start(va, mover); mover->startMove(this, va); va_end(va); } } void SceneObject::getHorizBounds() { Rect tempRect; GfxSurface frame = getFrame(); tempRect.resize(frame, _position.x, _position.y - _yDiff, _percent); _xs = tempRect.left; _xe = tempRect.right; } int SceneObject::getRegionIndex() { return _globals->_sceneRegions.indexOf(_position); } int SceneObject::checkRegion(const Common::Point &pt) { Rect tempRect; int regionIndex = 0; // Temporarily change the position Common::Point savedPos = _position; _position = pt; int regIndex = _globals->_sceneRegions.indexOf(pt); if (_regionBitList & (1 << regIndex)) regionIndex = regIndex; // Restore position _position = savedPos; // Get the object's frame bounds GfxSurface frame = getFrame(); tempRect.resize(frame, _position.x, _position.y - _yDiff, _percent); int yPos, newY; if ((_position.y - _yDiff) <= (pt.y - _yDiff)) { yPos = _position.y - _yDiff; newY = pt.y; } else { yPos = pt.y - _yDiff; newY = _position.y; } newY -= _yDiff; SynchronizedList::iterator i; for (i = _globals->_sceneObjects->begin(); (regionIndex == 0) && (i != _globals->_sceneObjects->end()); ++i) { if ((*i) && ((*i)->_flags & OBJFLAG_CHECK_REGION)) { int objYDiff = (*i)->_position.y - _yDiff; if ((objYDiff >= yPos) && (objYDiff <= newY) && ((*i)->_xs < tempRect.right) && ((*i)->_xe > tempRect.left)) { // Found index regionIndex = -1; //****DEBUG*** = *i; break; } } } return regionIndex; } void SceneObject::animate(AnimateMode animMode, ...) { _animateMode = animMode; _updateStartFrame = _globals->_events.getFrameNumber(); if (_numFrames) _updateStartFrame += 60 / _numFrames; va_list va; va_start(va, animMode); switch (_animateMode) { case ANIM_MODE_NONE: _endAction = NULL; break; case ANIM_MODE_1: _frameChange = 1; _field2E = _position; _endAction = 0; break; case ANIM_MODE_2: _frameChange = 1; _endAction = NULL; break; case ANIM_MODE_3: _frameChange = -1; _endAction = NULL; break; case ANIM_MODE_4: _endFrame = va_arg(va, int); _frameChange = va_arg(va, int); _endAction = va_arg(va, Action *); if (_endFrame == _frame) setFrame(getNewFrame()); break; case ANIM_MODE_5: _frameChange = 1; _endFrame = getFrameCount(); _endAction = va_arg(va, Action *); if (_endFrame == _frame) setFrame(getNewFrame()); break; case ANIM_MODE_6: _frameChange = -1; _endAction = va_arg(va, Action *); _endFrame = 1; if (_frame == _endFrame) setFrame(getNewFrame()); break; case ANIM_MODE_7: _endFrame = va_arg(va, int); _endAction = va_arg(va, Action *); _frameChange = 1; break; case ANIM_MODE_8: _field68 = va_arg(va, int); _endAction = va_arg(va, Action *); _frameChange = 1; _endFrame = getFrameCount(); if (_frame == _endFrame) setFrame(getNewFrame()); break; } } SceneObject *SceneObject::clone() const { SceneObject *obj = new SceneObject(*this); return obj; } void SceneObject::checkAngle(const SceneObject *obj) { _angle = GfxManager::getAngle(_position, obj->_position); if (_objectWrapper) _objectWrapper->dispatch(); } void SceneObject::hide() { _flags |= OBJFLAG_HIDE; if (_flags & OBJFLAG_HIDING) _flags |= OBJFLAG_PANES; } void SceneObject::show() { if (_flags & OBJFLAG_HIDE) { _flags &= ~OBJFLAG_HIDE; _flags |= OBJFLAG_PANES; } } int SceneObject::getSpliceArea(const SceneObject *obj) { int xd = ABS(_position.x - obj->_position.x); int yd = ABS(_position.y - obj->_position.y); return (xd * xd + yd) / 2; } void SceneObject::synchronize(Serializer &s) { SceneHotspot::synchronize(s); s.syncAsUint32LE(_updateStartFrame); s.syncAsUint32LE(_walkStartFrame); s.syncAsSint16LE(_field2E.x); s.syncAsSint16LE(_field2E.y); s.syncAsSint16LE(_percent); s.syncAsSint16LE(_priority); s.syncAsSint16LE(_angle); s.syncAsUint32LE(_flags); s.syncAsSint16LE(_xs); s.syncAsSint16LE(_xe); _paneRects[0].synchronize(s); _paneRects[1].synchronize(s); s.syncAsSint32LE(_visage); SYNC_POINTER(_objectWrapper); s.syncAsSint32LE(_strip); SYNC_ENUM(_animateMode, AnimateMode); s.syncAsSint32LE(_frame); s.syncAsSint32LE(_endFrame); s.syncAsSint32LE(_field68); s.syncAsSint32LE(_frameChange); s.syncAsSint32LE(_numFrames); s.syncAsSint32LE(_regionIndex); SYNC_POINTER(_mover); s.syncAsSint16LE(_moveDiff.x); s.syncAsSint16LE(_moveDiff.y); s.syncAsSint32LE(_moveRate); SYNC_POINTER(_endAction); s.syncAsUint32LE(_regionBitList); } void SceneObject::postInit(SceneObjectList *OwnerList) { if (!OwnerList) OwnerList = _globals->_sceneObjects; if (!OwnerList->contains(this)) { _percent = 100; _priority = 255; _flags = 4; _visage = 0; _strip = 1; _frame = 1; _objectWrapper = NULL; _animateMode = ANIM_MODE_NONE; _endAction = 0; _mover = NULL; _yDiff = 0; _moveDiff.x = 5; _moveDiff.y = 3; _moveRate = 10; _regionIndex = 0x40; _numFrames = 10; _regionBitList = 0; OwnerList->push_back(this); _flags |= OBJFLAG_PANES; } } void SceneObject::remove() { SceneItem::remove(); if (_globals->_sceneObjects->contains(this)) // For objects in the object list, flag the object for removal in the next drawing, so that // the drawing code has a chance to restore the area previously covered by the object _flags |= OBJFLAG_PANES | OBJFLAG_REMOVE | OBJFLAG_HIDE; else // Not in the list, so immediately remove the object removeObject(); } void SceneObject::dispatch() { uint32 currTime = _globals->_events.getFrameNumber(); if (_action) _action->dispatch(); if (_mover && (_walkStartFrame <= currTime)) { if (_moveRate) { int frameInc = 60 / _moveRate; _walkStartFrame = currTime + frameInc; } _mover->dispatch(); } if (!(_flags & OBJFLAG_NO_UPDATES)) { switch (_animateMode) { case ANIM_MODE_1: if (isNoMover()) setFrame(1); else if ((_field2E.x != _position.x) || (_field2E.y != _position.y)) { setFrame(changeFrame()); _field2E = _position; } break; case ANIM_MODE_2: case ANIM_MODE_3: setFrame(changeFrame()); break; case ANIM_MODE_4: case ANIM_MODE_5: case ANIM_MODE_6: if (_frame == _endFrame) animEnded(); else setFrame(changeFrame()); break; case ANIM_MODE_7: if (changeFrame() != _frame) { // Pick a new random frame int frameNum = 0; do { int count = getFrameCount(); frameNum = _globals->_randomSource.getRandomNumber(count - 1); } while (frameNum == _frame); setFrame(frameNum); if (_endFrame) { if (--_endFrame == 0) animEnded(); } } break; case ANIM_MODE_8: if (_frame == _endFrame) { if (_frameChange != -1) { _frameChange = -1; _endFrame = 1; setFrame(changeFrame()); } else if (!_field68 || (--_field68 > 0)) { _frameChange = 1; _endFrame = getFrameCount(); setFrame(changeFrame()); } else { animEnded(); } } else { setFrame(changeFrame()); } break; default: break; } } // Handle updating the zoom and/or priority if (!(_flags & OBJFLAG_ZOOMED)) { int yp = CLIP((int)_position.y, 0, 255); setZoom(_globals->_sceneManager._scene->_zoomPercents[yp]); } if (!(_flags & OBJFLAG_FIXED_PRIORITY)) { setPriority(_position.y); } } void SceneObject::calcAngle(const Common::Point &pt) { int newAngle = GfxManager::getAngle(_position, pt); if (newAngle != -1) _angle = newAngle; } void SceneObject::removeObject() { _globals->_sceneItems.remove(this); _globals->_sceneObjects->remove(this); if (_visage) { _visage = 0; } if (_objectWrapper) { _objectWrapper->remove(); _objectWrapper = NULL; } if (_mover) { _mover->remove(); _mover = NULL; } if (_flags & OBJFLAG_CLONED) // Cloned temporary object, so delete it delete this; } GfxSurface SceneObject::getFrame() { _visageImages.setVisage(_visage, _strip); return _visageImages.getFrame(_frame); } void SceneObject::reposition() { GfxSurface frame = getFrame(); _bounds.resize(frame, _position.x, _position.y - _yDiff, _percent); _xs = _bounds.left; _xe = _bounds.right; } /** * Draws an object into the scene */ void SceneObject::draw() { Rect destRect = _bounds; destRect.translate(-_globals->_sceneManager._scene->_sceneBounds.left, -_globals->_sceneManager._scene->_sceneBounds.top); Region *priorityRegion = _globals->_sceneManager._scene->_priorities.find(_priority); GfxSurface frame = getFrame(); _globals->gfxManager().copyFrom(frame, destRect, priorityRegion); } /** * Refreshes the background around the area of a scene object prior to it's being redrawn, * in case it is moving */ void SceneObject::updateScreen() { Rect srcRect = _paneRects[CURRENT_PANENUM]; const Rect &sceneBounds = _globals->_sceneManager._scene->_sceneBounds; srcRect.left = (srcRect.left / 4) * 4; srcRect.right = ((srcRect.right + 3) / 4) * 4; srcRect.clip(_globals->_sceneManager._scene->_sceneBounds); if (srcRect.isValidRect()) { Rect destRect = srcRect; destRect.translate(-sceneBounds.left, -sceneBounds.top); srcRect.translate(-_globals->_sceneOffset.x, -_globals->_sceneOffset.y); _globals->_screenSurface.copyFrom(_globals->_sceneManager._scene->_backSurface, srcRect, destRect); } } void SceneObject::setup(int visage, int stripFrameNum, int frameNum, int posX, int posY, int priority) { postInit(); setVisage(visage); setStrip(stripFrameNum); setFrame(frameNum); setPosition(Common::Point(posX, posY), 0); fixPriority(priority); } /*--------------------------------------------------------------------------*/ void SceneObjectList::draw() { Common::Array objList; int paneNum = 0; int xAmount = 0, yAmount = 0; if (_objList.size() == 0) { // Alternate draw mode if (_globals->_paneRefreshFlag[paneNum] == 1) { // Load the background _globals->_sceneManager._scene->refreshBackground(0, 0); Rect tempRect = _globals->_sceneManager._scene->_sceneBounds; tempRect.translate(-_globals->_sceneOffset.x, -_globals->_sceneOffset.y); ScenePalette::changeBackground(tempRect, _globals->_sceneManager._fadeMode); } else { _globals->_paneRegions[CURRENT_PANENUM].draw(); } _globals->_paneRegions[CURRENT_PANENUM].setRect(0, 0, 0, 0); _globals->_sceneManager.fadeInIfNecessary(); } else { // If there is a scroll follower, check whether it has moved off-screen if (_globals->_scrollFollower) { const Rect &scrollerRect = _globals->_sceneManager._scrollerRect; Common::Point objPos( _globals->_scrollFollower->_position.x - _globals->_sceneManager._scene->_sceneBounds.left, _globals->_scrollFollower->_position.y - _globals->_sceneManager._scene->_sceneBounds.top); int loadCount = 0; if (objPos.x >= scrollerRect.right) { xAmount = 8; loadCount = 20; } if (objPos.x < scrollerRect.left) { xAmount = -8; loadCount = 20; } if (objPos.y >= scrollerRect.bottom) { yAmount = 2; loadCount = 25; } if (objPos.y < scrollerRect.top) { yAmount = -2; loadCount = 25; } if (loadCount > 0) _globals->_sceneManager.setBgOffset(Common::Point(xAmount, yAmount), loadCount); } if (_globals->_sceneManager._sceneLoadCount > 0) { --_globals->_sceneManager._sceneLoadCount; _globals->_sceneManager._scene->loadBackground(_globals->_sceneManager._sceneBgOffset.x, _globals->_sceneManager._sceneBgOffset.y); } // Set up the flag mask uint32 flagMask = (paneNum == 0) ? OBJFLAG_PANE_0 : OBJFLAG_PANE_1; // Initial loop to set up object list and update object position, priority, and flags for (SynchronizedList::iterator i = _globals->_sceneObjects->begin(); i != _globals->_sceneObjects->end(); ++i) { SceneObject *obj = *i; objList.push_back(obj); if (!(obj->_flags & OBJFLAG_HIDE)) obj->_flags &= ~OBJFLAG_HIDING; // Reposition the bounds of the object to match the desired position obj->reposition(); // Handle updating object priority if (!(obj->_flags & OBJFLAG_FIXED_PRIORITY)) { obj->_priority = MIN((int)obj->_position.y, (int)_globals->_sceneManager._scene->_backgroundBounds.bottom - 1); } if ((_globals->_paneRefreshFlag[paneNum] != 0) || !_globals->_paneRegions[paneNum].empty()) { obj->_flags |= flagMask; } } // Check for any intersections, and then sort the object list by priority checkIntersection(objList, objList.size(), CURRENT_PANENUM); sortList(objList); if (_globals->_paneRefreshFlag[paneNum] == 1) { // Load the background _globals->_sceneManager._scene->refreshBackground(0, 0); } _globals->_sceneManager._scene->_sceneBounds.left &= ~3; _globals->_sceneManager._scene->_sceneBounds.right &= ~3; _globals->_sceneOffset.x &= ~3; if (_globals->_paneRefreshFlag[paneNum] != 0) { // Change the background Rect tempRect = _globals->_sceneManager._scene->_sceneBounds; tempRect.translate(-_globals->_sceneOffset.x, -_globals->_sceneOffset.y); ScenePalette::changeBackground(tempRect, _globals->_sceneManager._fadeMode); } else { for (uint objIndex = 0; objIndex < objList.size(); ++objIndex) { SceneObject *obj = objList[objIndex]; if ((obj->_flags & flagMask) && obj->_paneRects[paneNum].isValidRect()) obj->updateScreen(); } _globals->_paneRegions[paneNum].draw(); } _globals->_paneRegions[paneNum].setRect(0, 0, 0, 0); redraw: // Main draw loop for (uint objIndex = 0; objIndex < objList.size(); ++objIndex) { SceneObject *obj = objList[objIndex]; if ((obj->_flags & flagMask) && !(obj->_flags & OBJFLAG_HIDE)) { obj->_paneRects[paneNum] = obj->_bounds; obj->draw(); } } // Update the palette _globals->_sceneManager.fadeInIfNecessary(); _globals->_sceneManager._loadMode = 0; _globals->_paneRefreshFlag[paneNum] = 0; // Loop through the object list, removing any objects and refreshing the screen as necessary for (uint objIndex = 0; objIndex < objList.size(); ++objIndex) { SceneObject *obj = objList[objIndex]; if (obj->_flags & OBJFLAG_HIDE) obj->_flags |= OBJFLAG_HIDING; obj->_flags &= ~flagMask; if (obj->_flags & OBJFLAG_REMOVE) { obj->_flags |= OBJFLAG_PANES; checkIntersection(objList, objIndex, CURRENT_PANENUM); obj->updateScreen(); obj->removeObject(); // FIXME: Currently, removing objects causes screen flickers when the removed object intersects // another drawn object, since the background is briefly redrawn over the object. For now, I'm // using a forced jump back to redraw objects. In the long term, I should figure out how the // original game does this properly objList.remove_at(objIndex); goto redraw; } } } } void SceneObjectList::checkIntersection(Common::Array &ObjList, uint ObjIndex, int PaneNum) { uint32 flagMask = (PaneNum == 0) ? OBJFLAG_PANE_0 : OBJFLAG_PANE_1; SceneObject *obj = (ObjIndex == ObjList.size()) ? NULL : ObjList[ObjIndex]; Rect rect1; for (uint idx = 0; idx < ObjList.size(); ++idx) { SceneObject *currObj = ObjList[idx]; if (ObjIndex == ObjList.size()) { if (currObj->_flags & flagMask) checkIntersection(ObjList, idx, PaneNum); } else if (idx != ObjIndex) { Rect &paneRect = obj->_paneRects[PaneNum]; Rect objBounds = currObj->_bounds; if (paneRect.isValidRect()) objBounds.extend(paneRect); Rect objBounds2 = currObj->_bounds; if (paneRect.isValidRect()) objBounds2.extend(paneRect); objBounds.left &= ~3; objBounds.right += 3; objBounds.right &= ~3; objBounds2.left &= ~3; objBounds2.right += 3; objBounds2.right &= ~3; if (objBounds.intersects(objBounds2) && !(currObj->_flags & flagMask)) { currObj->_flags |= flagMask; checkIntersection(ObjList, idx, PaneNum); } } } } struct SceneObjectLess { bool operator()(const SceneObject *x, const SceneObject *y) const { if (y->_priority > x->_priority) return true; else if ((y->_priority == x->_priority) && (y->_position.y > x->_position.y)) return true; else if ((y->_priority == x->_priority) && (y->_position.y == x->_position.y) && (y->_yDiff > x->_yDiff)) return true; return false; } }; void SceneObjectList::sortList(Common::Array &ObjList) { Common::sort(ObjList.begin(), ObjList.end(), SceneObjectLess()); } void SceneObjectList::activate() { SceneObjectList *objectList = _globals->_sceneObjects; _globals->_sceneObjects = this; _globals->_sceneObjects_queue.push_front(this); // Flag all the objects as modified SynchronizedList::iterator i; for (i = begin(); i != end(); ++i) { (*i)->_flags |= OBJFLAG_PANES; } // Replicate all existing objects on the old object list for (i = objectList->begin(); i != objectList->end(); ++i) { SceneObject *sceneObj = (*i)->clone(); sceneObj->_flags |= OBJFLAG_HIDE | OBJFLAG_REMOVE | OBJFLAG_CLONED; push_front(sceneObj); } } void SceneObjectList::deactivate() { if (_globals->_sceneObjects_queue.size() <= 1) return; SceneObjectList *objectList = *_globals->_sceneObjects_queue.begin(); _globals->_sceneObjects_queue.pop_front(); _globals->_sceneObjects = *_globals->_sceneObjects_queue.begin(); SynchronizedList::iterator i; for (i = objectList->begin(); i != objectList->end(); ++i) { if (!((*i)->_flags & OBJFLAG_CLONED)) { SceneObject *sceneObj = (*i)->clone(); sceneObj->_flags |= OBJFLAG_HIDE | OBJFLAG_REMOVE | OBJFLAG_CLONED; _globals->_sceneObjects->push_front(sceneObj); } } } void SceneObjectList::synchronize(Serializer &s) { if (s.getVersion() >= 2) SavedObject::synchronize(s); _objList.synchronize(s); } /*--------------------------------------------------------------------------*/ SceneText::SceneText() : SceneObject() { _fontNumber = 2; _width = 160; _textMode = ALIGN_LEFT; _color2 = 0; _color3 = 0; } SceneText::~SceneText() { } void SceneText::setup(const Common::String &msg) { GfxManager gfxMan(_textSurface); gfxMan.activate(); Rect textRect; gfxMan._font.setFontNumber(_fontNumber); gfxMan._font._colors.foreground = _color1; gfxMan._font._colors2.background = _color2; gfxMan._font._colors2.foreground = _color3; gfxMan.getStringBounds(msg.c_str(), textRect, _width); _bounds.setWidth(textRect.width()); _bounds.setHeight(textRect.height()); // Set up a new blank surface to hold the text _textSurface.create(textRect.width(), textRect.height()); _textSurface._transColor = 0xff; _textSurface.fillRect(textRect, _textSurface._transColor); // Write the text to the surface gfxMan._bounds = textRect; gfxMan._font.writeLines(msg.c_str(), textRect, _textMode); // Do post-init, which adds this SceneText object to the scene postInit(); gfxMan.deactivate(); } void SceneText::synchronize(Serializer &s) { SceneObject::synchronize(s); s.syncAsSint16LE(_fontNumber); s.syncAsSint16LE(_width); s.syncAsSint16LE(_color1); s.syncAsSint16LE(_color2); s.syncAsSint16LE(_color3); SYNC_ENUM(_textMode, TextAlign); if (s.getVersion() >= 5) _textSurface.synchronize(s); } /*--------------------------------------------------------------------------*/ Visage::Visage() { _resNum = 0; _rlbNum = 0; _data = NULL; } Visage::Visage(const Visage &v) { _resNum = v._resNum; _rlbNum = v._rlbNum; _data = v._data; if (_data) _vm->_memoryManager.incLocks(_data); } Visage &Visage::operator=(const Visage &s) { _resNum = s._resNum; _rlbNum = s._rlbNum; _data = s._data; if (_data) _vm->_memoryManager.incLocks(_data); return *this; } void Visage::setVisage(int resNum, int rlbNum) { if ((_resNum != resNum) || (_rlbNum != rlbNum)) { _resNum = resNum; _rlbNum = rlbNum; DEALLOCATE(_data); _data = _resourceManager->getResource(RES_VISAGE, resNum, rlbNum); assert(_data); } } Visage::~Visage() { DEALLOCATE(_data); } GfxSurface Visage::getFrame(int frameNum) { int numFrames = READ_LE_UINT16(_data); if (frameNum > numFrames) frameNum = numFrames; if (frameNum > 0) --frameNum; int offset = READ_LE_UINT32(_data + 2 + frameNum * 4); byte *frameData = _data + offset; return surfaceFromRes(frameData); } int Visage::getFrameCount() const { return READ_LE_UINT16(_data); } /*--------------------------------------------------------------------------*/ Player::Player(): SceneObject() { _canWalk = false; _uiEnabled = false; _field8C = 0; } void Player::postInit(SceneObjectList *OwnerList) { SceneObject::postInit(); _canWalk = true; _uiEnabled = true; _percent = 100; _field8C = 10; _moveDiff.x = 4; _moveDiff.y = 2; } void Player::disableControl() { _canWalk = false; _uiEnabled = false; _globals->_events.setCursor(CURSOR_NONE); } void Player::enableControl() { _canWalk = true; _uiEnabled = true; _globals->_events.setCursor(CURSOR_WALK); switch (_globals->_events.getCursor()) { case CURSOR_WALK: case CURSOR_LOOK: case CURSOR_USE: case CURSOR_TALK: _globals->_events.setCursor(_globals->_events.getCursor()); break; default: _globals->_events.setCursor(CURSOR_WALK); break; } } void Player::process(Event &event) { if (!event.handled && (event.eventType == EVENT_BUTTON_DOWN) && (_globals->_events.getCursor() == CURSOR_WALK) && _globals->_player._canWalk && (_position != event.mousePos) && _globals->_sceneObjects->contains(this)) { PlayerMover *newMover = new PlayerMover(); Common::Point destPos(event.mousePos.x + _globals->_sceneManager._scene->_sceneBounds.left, event.mousePos.y + _globals->_sceneManager._scene->_sceneBounds.top); addMover(newMover, &destPos, NULL); event.handled = true; } } void Player::synchronize(Serializer &s) { SceneObject::synchronize(s); s.syncAsByte(_canWalk); s.syncAsByte(_uiEnabled); s.syncAsSint16LE(_field8C); } /*--------------------------------------------------------------------------*/ Region::Region(int resNum, int rlbNum, ResourceType ctlType) { _regionId = rlbNum; byte *regionData = _resourceManager->getResource(ctlType, resNum, rlbNum); assert(regionData); load(regionData); DEALLOCATE(regionData); } Region::Region(int regionId, const byte *regionData) { _regionId = regionId; load(regionData); } void Region::load(const byte *regionData) { // Set the region bounds _bounds.top = READ_LE_UINT16(regionData + 6); _bounds.left = READ_LE_UINT16(regionData + 8); _bounds.bottom = READ_LE_UINT16(regionData + 10); _bounds.right = READ_LE_UINT16(regionData + 12); // Special handling for small size regions _regionSize = READ_LE_UINT16(regionData); if (_regionSize == 14) // No line slices return; // Set up the line slices for (int y = 0; y < (_regionSize == 22 ? 1 : _bounds.height()); ++y) { int slicesCount = READ_LE_UINT16(regionData + 16 + y * 4); int slicesOffset = READ_LE_UINT16(regionData + 14 + y * 4); assert(slicesCount < 100); LineSliceSet sliceSet; sliceSet.load(slicesCount, regionData + 14 + slicesOffset); _ySlices.push_back(sliceSet); } } /** * Returns true if the given region contains the specified point * @param pt Specified position */ bool Region::contains(const Common::Point &pt) { // First check if the point falls inside the overall bounding rectangle if (!_bounds.contains(pt) || _ySlices.empty()) return false; // Get the correct Y line to use const LineSliceSet &line = getLineSlices(pt.y); // Loop through the horizontal slice list to see if the point falls in one for (uint idx = 0; idx < line.items.size(); ++idx) { if ((pt.x >= line.items[idx].xs) && (pt.x < line.items[idx].xe)) return true; } return false; } /** * Returns true if the given region is empty */ bool Region::empty() const { return !_bounds.isValidRect() && (_regionSize == 14); } void Region::clear() { _bounds.set(0, 0, 0, 0); _regionId = 0; _regionSize = 0; } void Region::setRect(const Rect &r) { setRect(r.left, r.top, r.right, r.bottom); } void Region::setRect(int xs, int ys, int xe, int ye) { bool validRect = (ys < ye) && (xs < xe); _ySlices.clear(); if (!validRect) { _regionSize = 14; _bounds.set(0, 0, 0, 0); } else { _regionSize = 22; _bounds.set(xs, ys, xe, ye); LineSliceSet sliceSet; sliceSet.load2(1, xs, xe); _ySlices.push_back(sliceSet); } } const LineSliceSet &Region::getLineSlices(int yp) { return _ySlices[(_regionSize == 22) ? 0 : yp - _bounds.top]; } LineSliceSet Region::sectPoints(int yp, const LineSliceSet &sliceSet) { if ((yp < _bounds.top) || (yp >= _bounds.bottom)) return LineSliceSet(); const LineSliceSet &ySet = getLineSlices(yp); return mergeSlices(sliceSet, ySet); } LineSliceSet Region::mergeSlices(const LineSliceSet &set1, const LineSliceSet &set2) { LineSliceSet result; uint set1Index = 0, set2Index = 0; while ((set1Index < set1.items.size()) && (set2Index < set2.items.size())) { if (set1.items[set1Index].xe <= set2.items[set2Index].xs) { ++set1Index; } else if (set2.items[set2Index].xe <= set1.items[set1Index].xs) { ++set2Index; } else { bool set1Flag = set1.items[set1Index].xs >= set2.items[set2Index].xs; const LineSlice &slice = set1Flag ? set1.items[set1Index] : set2.items[set2Index]; result.add(slice.xs, MIN(set1.items[set1Index].xe, set2.items[set2Index].xe)); if (set1Flag) ++set1Index; else ++set2Index; } } return result; } /** * Copies the background covered by the given region to the screen surface */ void Region::draw() { Rect &sceneBounds = _globals->_sceneManager._scene->_sceneBounds; for (int yp = sceneBounds.top; yp < sceneBounds.bottom; ++yp) { // Generate a line slice set LineSliceSet tempSet; tempSet.add(sceneBounds.left, sceneBounds.right); LineSliceSet newSet = sectPoints(yp, tempSet); // Loop through the calculated slices for (uint idx = 0; idx < newSet.items.size(); ++idx) { Rect rect1(newSet.items[idx].xs, yp, newSet.items[idx].xe, yp + 1); rect1.left &= ~3; rect1.right = (rect1.right + 3) & ~3; Rect rect2 = rect1; rect1.translate(-_globals->_sceneOffset.x, -_globals->_sceneOffset.y); rect2.translate(-sceneBounds.left, -sceneBounds.top); _globals->gfxManager().getSurface().copyFrom(_globals->_sceneManager._scene->_backSurface, rect1, rect2); } } } void Region::uniteLine(int yp, LineSliceSet &sliceSet) { // First expand the bounds as necessary to fit in the row if (_ySlices.empty()) { _bounds = Rect(sliceSet.items[0].xs, yp, sliceSet.items[sliceSet.items.size() - 1].xe, yp + 1); _ySlices.push_back(LineSliceSet()); } while (yp < _bounds.top) { _ySlices.insert_at(0, LineSliceSet()); --_bounds.top; } while (yp >= _bounds.bottom) { _ySlices.push_back(LineSliceSet()); ++_bounds.bottom; } // Merge the existing line set into the line LineSliceSet &destSet = _ySlices[yp - _bounds.top]; for (uint srcIndex = 0; srcIndex < sliceSet.items.size(); ++srcIndex) { LineSlice &srcSlice = sliceSet.items[srcIndex]; // Check if overlaps existing slices uint destIndex = 0; while (destIndex < destSet.items.size()) { LineSlice &destSlice = destSet.items[destIndex]; if (((srcSlice.xs >= destSlice.xs) && (srcSlice.xs <= destSlice.xe)) || ((srcSlice.xe >= destSlice.xs) && (srcSlice.xe <= destSlice.xe)) || ((srcSlice.xs < destSlice.xs) && (srcSlice.xe > destSlice.xe))) { // Intersecting, so merge them destSlice.xs = MIN(srcSlice.xs, destSlice.xs); destSlice.xe = MAX(srcSlice.xe, destSlice.xe); break; } ++destIndex; } if (destIndex == destSet.items.size()) { // No intersecting region found, so add it to the list destSet.items.push_back(srcSlice); } } // Check whether to expand the left/bounds bounds if (destSet.items[0].xs < _bounds.left) _bounds.left = destSet.items[0].xs; if (destSet.items[destSet.items.size() - 1].xe > _bounds.right) _bounds.right = destSet.items[destSet.items.size() - 1].xe; } void Region::uniteRect(const Rect &rect) { for (int yp = rect.top; yp < rect.bottom; ++yp) { LineSliceSet sliceSet; sliceSet.add(rect.left, rect.right); uniteLine(yp, sliceSet); } } /*--------------------------------------------------------------------------*/ void SceneRegions::load(int sceneNum) { clear(); bool altRegions = _vm->getFeatures() & GF_ALT_REGIONS; byte *regionData = _resourceManager->getResource(RES_CONTROL, sceneNum, altRegions ? 1 : 9999, true); if (regionData) { int regionCount = READ_LE_UINT16(regionData); for (int regionCtr = 0; regionCtr < regionCount; ++regionCtr) { int regionId = READ_LE_UINT16(regionData + regionCtr * 6 + 2); if (altRegions) { // Load data from within this resource uint32 dataOffset = READ_LE_UINT32(regionData + regionCtr * 6 + 4); push_back(Region(regionId, regionData + dataOffset)); } else { // Load region from a separate resource push_back(Region(sceneNum, regionId)); } } DEALLOCATE(regionData); } } int SceneRegions::indexOf(const Common::Point &pt) { for (SceneRegions::iterator i = begin(); i != end(); ++i) { if ((*i).contains(pt)) return (*i)._regionId; } return 0; } /*--------------------------------------------------------------------------*/ void SceneItemList::addItems(SceneItem *first, ...) { va_list va; va_start(va, first); SceneItem *p = first; while (p) { push_back(p); p = va_arg(va, SceneItem *); } } /*--------------------------------------------------------------------------*/ RegionSupportRec WalkRegion::_processList[PROCESS_LIST_SIZE]; void RegionSupportRec::process() { if (_xDiff < _yDiff) { _halfDiff += _xDiff; if (_halfDiff > _yDiff) { _halfDiff -= _yDiff; _xp += _xDirection; } } else { do { _xp += _xDirection; _halfDiff += _yDiff; } while (_halfDiff <= _xDiff); _halfDiff -= _xDiff; } --_yDiff2; } /*--------------------------------------------------------------------------*/ void WalkRegion::loadRegion(byte *dataP, int size) { // First clear the region clear(); // Decode the data for the region int dataCount, regionHeight; loadProcessList(dataP, size, dataCount, regionHeight); int processIndex = 0, idx2 = 0, count; for (int yp = _processList[0]._yp; yp < regionHeight; ++yp) { process3(yp, dataCount, processIndex, idx2); process4(yp, processIndex, idx2, count); loadRecords(yp, count, processIndex); } } void WalkRegion::loadProcessList(byte *dataP, int dataSize, int &dataIndex, int ®ionHeight) { dataIndex = 0; int x1 = READ_LE_UINT16(dataP + (dataSize - 1) * 4); int y1 = READ_LE_UINT16(dataP + (dataSize - 1) * 4 + 2); regionHeight = y1; for (int idx = 0; idx < dataSize; ++idx) { int xp = READ_LE_UINT16(dataP + idx * 4); int yp = READ_LE_UINT16(dataP + idx * 4 + 2); if (yp != y1) { /* * Commented out: v doesn't seem to be used int v; if (idx == (dataSize - 1)) v = READ_LE_UINT16(dataP + 2); else v = process1(idx, dataP, dataSize); */ process2(dataIndex, x1, y1, xp, yp); ++dataIndex; } // Keep regionHeight as the maximum of any y if (yp > regionHeight) regionHeight = yp; x1 = xp; y1 = yp; } } int WalkRegion::process1(int idx, byte *dataP, int dataSize) { int idx2 = idx + 1; if (idx2 == dataSize) idx2 = 0; while (READ_LE_UINT16(dataP + idx2 * 4 + 2) == READ_LE_UINT16(dataP + idx * 4 + 2)) { if (idx2 == (dataSize - 1)) idx2 = 0; else ++idx2; } return READ_LE_UINT16(dataP + idx2 * 4 + 2); } void WalkRegion::process2(int dataIndex, int x1, int y1, int x2, int y2) { int xDiff = ABS(x2 - x1); int yDiff = ABS(y2 - y1); int halfDiff = MAX(xDiff, yDiff) / 2; int yMax = MIN(y1, y2); while (dataIndex && (_processList[dataIndex - 1]._yp > yMax)) { _processList[dataIndex] = _processList[dataIndex - 1]; --dataIndex; } _processList[dataIndex]._yp = yMax; _processList[dataIndex]._xp = (y1 >= y2) ? x2 : x1; _processList[dataIndex]._xDiff = xDiff; _processList[dataIndex]._yDiff = yDiff; _processList[dataIndex]._halfDiff = halfDiff; int xTemp = (y1 >= y2) ? x1 - x2 : x2 - x1; _processList[dataIndex]._xDirection = (xTemp == 0) ? 0 : ((xTemp < 0) ? -1 : 1); _processList[dataIndex]._yDiff2 = yDiff; } void WalkRegion::process3(int yp, int dataCount, int &idx1, int &idx2) { while ((idx2 < (dataCount - 1)) && (_processList[idx2 + 1]._yp <= yp)) ++idx2; while (!_processList[idx1]._yDiff2) ++idx1; } void WalkRegion::process4(int yp, int idx1, int idx2, int &count) { count = 0; for (int idx = idx1; idx <= idx2; ++idx) { if (_processList[idx]._yDiff2 > 0) ++count; process5(idx, idx1); } } void WalkRegion::process5(int idx1, int idx2) { while ((idx1 > idx2) && (_processList[idx1 - 1]._xp > _processList[idx1]._xp)) { SWAP(_processList[idx1], _processList[idx1 - 1]); --idx1; } } void WalkRegion::loadRecords(int yp, int size, int processIndex) { LineSliceSet sliceSet; int sliceCount = size / 2; for (int idx = 0; idx < sliceCount; ++idx, ++processIndex) { while (!_processList[processIndex]._yDiff2) ++processIndex; int sliceXs = _processList[processIndex]._xp; _processList[processIndex].process(); do { ++processIndex; } while (!_processList[processIndex]._yDiff2); int sliceXe = _processList[processIndex]._xp; _processList[processIndex].process(); sliceSet.items.push_back(LineSlice(sliceXs, sliceXe)); } uniteLine(yp, sliceSet); } /*--------------------------------------------------------------------------*/ void WRField18::load(byte *data) { _pt1.x = READ_LE_UINT16(data); _pt1.y = READ_LE_UINT16(data + 2); _pt2.x = READ_LE_UINT16(data + 4); _pt2.y = READ_LE_UINT16(data + 6); _v = READ_LE_UINT16(data + 8); } /*--------------------------------------------------------------------------*/ void WalkRegions::clear() { _regionList.clear(); _field18.clear(); _idxList.clear(); _idxList2.clear(); } void WalkRegions::load(int sceneNum) { clear(); _resNum = sceneNum; if (_vm->getFeatures() & GF_ALT_REGIONS) { loadRevised(); } else { loadOriginal(); } } /** * This version handles loading walk regions for Ringworld floppy version and Demo #1 */ void WalkRegions::loadOriginal() { byte *regionData = _resourceManager->getResource(RES_WALKRGNS, _resNum, 1, true); if (!regionData) { // No data, so return _resNum = -1; return; } byte *dataP; int dataSize; // Load the field 18 list dataP = _resourceManager->getResource(RES_WALKRGNS, _resNum, 2); dataSize = _vm->_memoryManager.getSize(dataP); assert(dataSize % 10 == 0); byte *p = dataP; for (int idx = 0; idx < (dataSize / 10); ++idx, p += 10) { WRField18 rec; rec.load(p); _field18.push_back(rec); } DEALLOCATE(dataP); // Load the idx list dataP = _resourceManager->getResource(RES_WALKRGNS, _resNum, 3); dataSize = _vm->_memoryManager.getSize(dataP); assert(dataSize % 2 == 0); p = dataP; for (int idx = 0; idx < (dataSize / 2); ++idx, p += 2) _idxList.push_back(READ_LE_UINT16(p)); DEALLOCATE(dataP); // Load the secondary idx list dataP = _resourceManager->getResource(RES_WALKRGNS, _resNum, 4); dataSize = _vm->_memoryManager.getSize(dataP); assert(dataSize % 2 == 0); p = dataP; for (int idx = 0; idx < (dataSize / 2); ++idx, p += 2) _idxList2.push_back(READ_LE_UINT16(p)); DEALLOCATE(dataP); // Handle the loading of the actual regions themselves dataP = _resourceManager->getResource(RES_WALKRGNS, _resNum, 5); byte *pWalkRegion = regionData + 16; byte *srcP = dataP; for (; (int16)READ_LE_UINT16(pWalkRegion) != -20000; pWalkRegion += 16) { WalkRegion wr; // Set the Walk region specific fields wr._pt.x = (int16)READ_LE_UINT16(pWalkRegion); wr._pt.y = (int16)READ_LE_UINT16(pWalkRegion + 2); wr._idxListIndex = READ_LE_UINT32(pWalkRegion + 4); wr._idxList2Index = READ_LE_UINT32(pWalkRegion + 8); // Read in the region data int size = READ_LE_UINT16(srcP); srcP += 2; wr.loadRegion(srcP, size); srcP += size * 4; _regionList.push_back(wr); } DEALLOCATE(dataP); DEALLOCATE(regionData); } /** * This version handles loading walk regions for Ringworld CD version and Demo #2. Given it's the newer * version, it may also be used by future game titles */ void WalkRegions::loadRevised() { byte *regionData = _resourceManager->getResource(RES_WALKRGNS, _resNum, 2, true); if (!regionData) { // No data, so return _resNum = -1; return; } byte *data1P = regionData + READ_LE_UINT32(regionData); byte *data2P = regionData + READ_LE_UINT32(regionData + 4); byte *data3P = regionData + READ_LE_UINT32(regionData + 8); byte *data4P = regionData + READ_LE_UINT32(regionData + 12); byte *regionOffset = regionData + 16; int dataSize; // Load the field 18 list dataSize = READ_LE_UINT32(regionData + 8) - READ_LE_UINT32(regionData + 4); assert(dataSize % 10 == 0); byte *p = data2P; for (int idx = 0; idx < (dataSize / 10); ++idx, p += 10) { WRField18 rec; rec.load(p); _field18.push_back(rec); } // Load the idx list dataSize = READ_LE_UINT32(regionData + 12) - READ_LE_UINT32(regionData + 8); assert(dataSize % 2 == 0); p = data3P; for (int idx = 0; idx < (dataSize / 2); ++idx, p += 2) _idxList.push_back(READ_LE_UINT16(p)); // Load the secondary idx list dataSize = READ_LE_UINT32(regionData + 16) - READ_LE_UINT32(regionData + 12); assert(dataSize % 2 == 0); p = data4P; for (int idx = 0; idx < (dataSize / 2); ++idx, p += 2) _idxList2.push_back(READ_LE_UINT16(p)); // Handle the loading of the actual regions themselves byte *pWalkRegion = data1P + 16; for (; (int16)READ_LE_UINT16(pWalkRegion) != -20000; pWalkRegion += 16, regionOffset += 4) { WalkRegion wr; byte *srcP = regionData + READ_LE_UINT32(regionOffset); // Set the Walk region specific fields wr._pt.x = (int16)READ_LE_UINT16(pWalkRegion); wr._pt.y = (int16)READ_LE_UINT16(pWalkRegion + 2); wr._idxListIndex = READ_LE_UINT32(pWalkRegion + 4); wr._idxList2Index = READ_LE_UINT32(pWalkRegion + 8); // Read in the region data wr._regionId = 0; wr.load(srcP); _regionList.push_back(wr); } DEALLOCATE(regionData); } /** * Returns the index of the walk region that contains the given point * @param pt Point to locate * @param indexList List of region indexes that should be ignored */ int WalkRegions::indexOf(const Common::Point &pt, const Common::List *indexList) { for (uint idx = 0; idx < _regionList.size(); ++idx) { if ((!indexList || !contains(*indexList, int(idx + 1))) && _regionList[idx].contains(pt)) return idx + 1; } return -1; } /*--------------------------------------------------------------------------*/ void ScenePriorities::load(int resNum) { _resNum = resNum; clear(); bool altMode = (_vm->getFeatures() & GF_ALT_REGIONS) != 0; byte *regionData = _resourceManager->getResource(RES_PRIORITY, resNum, altMode ? 1 : 9999, true); if (!regionData) return; int regionCount = READ_LE_UINT16(regionData); for (int regionCtr = 0; regionCtr < regionCount; ++regionCtr) { if (altMode) { // Region data is embedded within the resource uint16 regionId = READ_LE_UINT16(regionData + regionCtr * 6 + 2); uint32 dataOffset = READ_LE_UINT32(regionData + regionCtr * 6 + 4); push_back(Region(regionId, regionData + dataOffset)); } else { // The data contains the index of another resource containing the region data int rlbNum = READ_LE_UINT16(regionData + regionCtr * 6 + 2); push_back(Region(resNum, rlbNum, RES_PRIORITY)); } } DEALLOCATE(regionData); } Region *ScenePriorities::find(int priority) { // If no priority regions are loaded, then return the placeholder region if (empty()) return &_defaultPriorityRegion; if (priority > 255) priority = 255; // Loop through the regions to find the closest for the given priority level int minRegionId = 9998; Region *region = NULL; for (ScenePriorities::iterator i = begin(); i != end(); ++i) { Region *r = &(*i); int regionId = r->_regionId; if ((regionId > priority) && (regionId < minRegionId)) { minRegionId = regionId; region = r; } } assert(region); return region; } /*--------------------------------------------------------------------------*/ void FloatSet::add(double v1, double v2, double v3) { _float1 += v1; _float2 += v2; _float3 += v3; } void FloatSet::proc1(double v) { double diff = (cos(v) * _float1) - (sin(v) * _float2); _float2 = (sin(v) * _float1) + (cos(v) * _float2); _float1 = diff; } double FloatSet::sqrt(FloatSet &floatSet) { double f1Diff = _float1 - floatSet._float1; double f2Diff = _float2 - floatSet._float2; double f3Diff = _float3 - floatSet._float3; return ::sqrt(f1Diff * f1Diff + f2Diff * f2Diff + f3Diff * f3Diff); } /*--------------------------------------------------------------------------*/ GameHandler::GameHandler() : EventHandler() { _nextWaitCtr = 1; _waitCtr.setCtr(1); _field14 = 10; } GameHandler::~GameHandler() { if (_globals) _globals->_game->removeHandler(this); } void GameHandler::execute() { if (_waitCtr.decCtr() == 0) { _waitCtr.setCtr(_nextWaitCtr); dispatch(); } } void GameHandler::synchronize(Serializer &s) { if (s.getVersion() >= 2) EventHandler::synchronize(s); _lockCtr.synchronize(s); _waitCtr.synchronize(s); s.syncAsSint16LE(_nextWaitCtr); s.syncAsSint16LE(_field14); } /*--------------------------------------------------------------------------*/ SceneHandler::SceneHandler() { _saveGameSlot = -1; _loadGameSlot = -1; } void SceneHandler::registerHandler() { postInit(); _globals->_game->addHandler(this); } void SceneHandler::postInit(SceneObjectList *OwnerList) { _delayTicks = 2; _globals->_scenePalette.loadPalette(0); _globals->_scenePalette.refresh(); _globals->_soundManager.postInit(); _globals->_soundManager.buildDriverList(true); _globals->_soundManager.installConfigDrivers(); _globals->_game->start(); } void SceneHandler::process(Event &event) { // Main keypress handler if (!event.handled) { _globals->_game->processEvent(event); if (event.eventType == EVENT_KEYPRESS) _globals->_events.setCursorFromFlag(); } // Check for displaying right-click dialog if ((event.eventType == EVENT_BUTTON_DOWN) && (event.btnState == BTNSHIFT_RIGHT) && _globals->_player._uiEnabled) { RightClickDialog *dlg = new RightClickDialog(); dlg->execute(); delete dlg; event.handled = true; return; } // If there is an active scene, pass the event to it if (_globals->_sceneManager._scene) _globals->_sceneManager._scene->process(event); if (!event.handled) { // Separate check for F5 - Save key if ((event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode == Common::KEYCODE_F5)) { // F5 - Save _globals->_game->saveGame(); event.handled = true; _globals->_events.setCursorFromFlag(); } // Check for debugger if ((event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode == Common::KEYCODE_d) && (event.kbd.flags & Common::KBD_CTRL)) { // Attach to the debugger _vm->_debugger->attach(); _vm->_debugger->onFrame(); } // Mouse press handling if (_globals->_player._uiEnabled && (event.eventType == EVENT_BUTTON_DOWN) && !_globals->_sceneItems.empty()) { // Scan the item list to find one the mouse is within SynchronizedList::iterator i = _globals->_sceneItems.begin(); while ((i != _globals->_sceneItems.end()) && !(*i)->contains(event.mousePos)) ++i; if (i != _globals->_sceneItems.end()) { // Pass the action to the item (*i)->doAction(_globals->_events.getCursor()); event.handled = _globals->_events.getCursor() != CURSOR_WALK; if (_globals->_player._uiEnabled && _globals->_player._canWalk && (_globals->_events.getCursor() != CURSOR_LOOK)) { _globals->_events.setCursor(CURSOR_WALK); } else if (_globals->_player._canWalk && (_globals->_events.getCursor() != CURSOR_LOOK)) { _globals->_events.setCursor(CURSOR_WALK); } else if (_globals->_player._uiEnabled && (_globals->_events.getCursor() != CURSOR_LOOK)) { _globals->_events.setCursor(CURSOR_USE); } } // Handle player processing _globals->_player.process(event); } } } void SceneHandler::dispatch() { // Handle game saving and loading if (_saveGameSlot != -1) { int saveSlot = _saveGameSlot; _saveGameSlot = -1; Common::Error err = _saver->save(saveSlot, _saveName); // FIXME: Make use of the description string in err to enhance // the error reported to the user. if (err.getCode() != Common::kNoError) GUIErrorMessage(SAVE_ERROR_MSG); } if (_loadGameSlot != -1) { int loadSlot = _loadGameSlot; _loadGameSlot = -1; _saver->restore(loadSlot); _globals->_events.setCursorFromFlag(); } _globals->_soundManager.dispatch(); _globals->_scenePalette.signalListeners(); // Dispatch to any objects registered in the scene _globals->_sceneObjects->recurse(SceneHandler::dispatchObject); // If a scene is active, then dispatch to it if (_globals->_sceneManager._scene) _globals->_sceneManager._scene->dispatch(); // Not actually used //_eventListeners.forEach(SceneHandler::handleListener); // Handle pending eents Event event; while (_globals->_events.getEvent(event)) process(event); // Handle drawing the contents of the scene if (_globals->_sceneManager._scene) _globals->_sceneObjects->draw(); // Check to see if any scene change is required _globals->_sceneManager.checkScene(); // Signal the ScummVM debugger _vm->_debugger->onFrame(); // Delay between frames _globals->_events.delay(_delayTicks); } void SceneHandler::dispatchObject(EventHandler *obj) { obj->dispatch(); } void SceneHandler::saveListener(Serializer &ser) { } } // End of namespace tSage