Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,637 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/actors/camera.h"
#include "mediastation/actors/stage.h"
#include "mediastation/actors/image.h"
#include "mediastation/debugchannels.h"
#include "mediastation/mediastation.h"
#include "common/util.h"
namespace MediaStation {
CameraActor::~CameraActor() {
if (_parentStage != nullptr) {
_parentStage->removeCamera(this);
_parentStage->removeChildSpatialEntity(this);
}
}
void CameraActor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderChannelIdent:
_channelIdent = chunk.readTypedChannelIdent();
registerWithStreamManager();
_image = Common::SharedPtr<ImageAsset>(new ImageAsset);
break;
case kActorHeaderStartup:
_isVisible = static_cast<bool>(chunk.readTypedByte());
break;
case kActorHeaderX:
_offset.x = chunk.readTypedUint16();
break;
case kActorHeaderY:
_offset.y = chunk.readTypedUint16();
break;
case kActorHeaderCameraViewportOrigin: {
Common::Point origin = chunk.readTypedPoint();
setViewportOrigin(origin);
break;
}
case kActorHeaderCameraLensOpen:
_lensOpen = static_cast<bool>(chunk.readTypedByte());
break;
case kActorHeaderCameraImageActor: {
uint actorReference = chunk.readTypedUint16();
Actor *referencedActor = g_engine->getActorById(actorReference);
if (referencedActor == nullptr) {
error("%s: Referenced actor %d doesn't exist or has not been read yet in this title", __func__, actorReference);
}
if (referencedActor->type() != kActorTypeCamera) {
error("%s: Type mismatch of referenced actor %d", __func__, actorReference);
}
CameraActor *referencedImage = static_cast<CameraActor *>(referencedActor);
_image = referencedImage->_image;
break;
}
default:
SpatialEntity::readParameter(chunk, paramType);
}
}
void CameraActor::readChunk(Chunk &chunk) {
BitmapHeader *bitmapHeader = new BitmapHeader(chunk);
_image->bitmap = new Bitmap(chunk, bitmapHeader);
}
ScriptValue CameraActor::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
ScriptValue returnValue;
switch (methodId) {
case kSpatialMoveToMethod:
case kSpatialMoveToByOffsetMethod:
case kSpatialCenterMoveToMethod:
invalidateLocalBounds();
returnValue = SpatialEntity::callMethod(methodId, args);
invalidateLocalBounds();
break;
case kAddToStageMethod:
assert(args.empty());
addToStage();
break;
case kRemoveFromStageMethod: {
bool stopPan = false;
if (args.size() >= 1) {
stopPan = args[0].asBool();
}
removeFromStage(stopPan);
break;
}
case kAddedToStageMethod:
assert(args.empty());
returnValue.setToBool(_addedToStage);
break;
case kStartPanMethod: {
assert(args.size() == 3);
int16 deltaX = static_cast<uint16>(args[0].asFloat());
int16 deltaY = static_cast<int16>(args[1].asFloat());
double duration = args[2].asTime();
_nextViewportOrigin = Common::Point(deltaX, deltaY) + _currentViewportOrigin;
adjustCameraViewport(_nextViewportOrigin);
startPan(deltaX, deltaY, duration);
break;
}
case kStopPanMethod:
assert(args.empty());
stopPan();
break;
case kIsPanningMethod:
assert(args.empty());
returnValue.setToBool(_panState);
break;
case kViewportMoveToMethod: {
assert(args.size() == 2);
int16 x = static_cast<int16>(args[0].asFloat());
int16 y = static_cast<int16>(args[1].asFloat());
_nextViewportOrigin = Common::Point(x, y);
if (!_addedToStage) {
_currentViewportOrigin = _nextViewportOrigin;
} else {
bool viewportMovedSuccessfully = processViewportMove();
if (!viewportMovedSuccessfully) {
startPan(0, 0, 0.0);
}
}
break;
}
case kAdjustCameraViewportMethod: {
assert(args.size() == 2);
int16 xDiff = static_cast<int16>(args[0].asFloat());
int16 yDiff = static_cast<int16>(args[1].asFloat());
Common::Point viewportDelta(xDiff, yDiff);
_nextViewportOrigin = getViewportOrigin() + viewportDelta;
adjustCameraViewport(_nextViewportOrigin);
if (!_addedToStage) {
_currentViewportOrigin = _nextViewportOrigin;
} else {
bool viewportMovedSuccessfully = processViewportMove();
if (!viewportMovedSuccessfully) {
startPan(0, 0, 0.0);
}
}
break;
}
case kAdjustCameraViewportSpatialCenterMethod: {
assert(args.size() == 2);
int16 xDiff = static_cast<int16>(args[0].asFloat());
int16 yDiff = static_cast<int16>(args[1].asFloat());
// Apply centering adjustment, which is indeed based on the entire camera actor's
// bounds, not just the current viewport bounds.
int16 centeredXDiff = xDiff - (getBbox().width() / 2);
int16 centeredYDiff = yDiff - (getBbox().height() / 2);
Common::Point viewportDelta(centeredXDiff, centeredYDiff);
_nextViewportOrigin = getViewportOrigin() + viewportDelta;
adjustCameraViewport(_nextViewportOrigin);
if (!_addedToStage) {
_currentViewportOrigin = _nextViewportOrigin;
} else {
bool viewportMovedSuccessfully = processViewportMove();
if (!viewportMovedSuccessfully) {
startPan(0, 0, 0.0);
}
}
break;
}
case kSetCameraBoundsMethod: {
assert(args.size() == 2);
int16 width = static_cast<int16>(args[0].asFloat());
int16 height = static_cast<int16>(args[1].asFloat());
Common::Rect newBounds(_originalBoundingBox.origin(), width, height);
// invalidateLocalBounds is already called in the setBounds call, but these extra calls are
// in the original, so they are kept.
invalidateLocalBounds();
setBounds(newBounds);
invalidateLocalBounds();
break;
}
case kXViewportPositionMethod:
assert(args.size() == 0);
returnValue.setToFloat(getViewportOrigin().x);
break;
case kYViewportPositionMethod:
assert(args.size() == 0);
returnValue.setToFloat(getViewportOrigin().y);
break;
case kPanToMethod: {
assert(args.size() >= 3);
int16 x = static_cast<int16>(args[0].asFloat());
int16 y = static_cast<int16>(args[1].asFloat());
if (args.size() == 4) {
uint panSteps = static_cast<uint>(args[2].asFloat());
double duration = args[3].asFloat();
panToByStepCount(x, y, panSteps, duration);
} else if (args.size() == 3) {
double duration = args[2].asFloat();
panToByTime(x, y, duration);
} else {
error("%s: Incorrect number of args for method %s", __func__, builtInMethodToStr(methodId));
}
break;
}
default:
returnValue = SpatialEntity::callMethod(methodId, args);
break;
}
return returnValue;
}
void CameraActor::invalidateLocalBounds() {
if (_parentStage != nullptr) {
_parentStage->invalidateLocalBounds();
}
}
void CameraActor::loadIsComplete() {
SpatialEntity::loadIsComplete();
if (_lensOpen) {
addToStage();
}
if (_image != nullptr) {
warning("%s: STUB: Camera image asset not handled yet", __func__);
}
}
void CameraActor::addToStage() {
if (_parentStage != nullptr) {
_parentStage->addCamera(this);
invalidateLocalBounds();
}
}
void CameraActor::removeFromStage(bool shouldStopPan) {
if (_parentStage != nullptr) {
_parentStage->removeCamera(this);
invalidateLocalBounds();
_addedToStage = false;
if (shouldStopPan && _panState != kCameraNotPanning) {
stopPan();
}
}
}
void CameraActor::setViewportOrigin(const Common::Point &newViewpointOrigin) {
_currentViewportOrigin = newViewpointOrigin;
}
Common::Point CameraActor::getViewportOrigin() {
return _currentViewportOrigin;
}
Common::Rect CameraActor::getViewportBounds() {
Common::Rect viewportBounds(getBbox());
viewportBounds.moveTo(_currentViewportOrigin);
return viewportBounds;
}
void CameraActor::drawUsingCamera(DisplayContext &displayContext, const Common::Array<SpatialEntity *> &entitiesToDraw) {
Clip *currentClip = displayContext.currentClip();
if (currentClip != nullptr) {
Clip *previousClip = displayContext.previousClip();
if (previousClip == nullptr) {
currentClip->addToRegion(currentClip->_bounds);
} else {
*currentClip = *previousClip;
}
}
Common::Rect cameraBounds = getBbox();
displayContext.intersectClipWith(cameraBounds);
displayContext.pushOrigin();
Common::Point viewportOrigin = getViewportOrigin();
Common::Point viewportOffset(
-viewportOrigin.x + cameraBounds.left,
-viewportOrigin.y + cameraBounds.top
);
displayContext._origin.x += viewportOffset.x;
displayContext._origin.y += viewportOffset.y;
if (_image != nullptr) {
// TODO: Handle image asset stuff.
warning("%s: Camera image asset not handled yet", __func__);
}
for (SpatialEntity *entityToDraw : entitiesToDraw) {
if (entityToDraw->isVisible()) {
drawObject(displayContext, displayContext, entityToDraw);
}
}
displayContext.popOrigin();
displayContext.emptyCurrentClip();
}
void CameraActor::drawObject(DisplayContext &sourceContext, DisplayContext &destContext, SpatialEntity *objectToDraw) {
if (_parentStage == nullptr) {
warning("%s: No parent stage", __func__);
return;
}
objectToDraw->setAdjustedBounds(kWrapNone);
Common::Rect visibleBounds = objectToDraw->getBbox();
if (sourceContext.rectIsInClip(visibleBounds)) {
objectToDraw->draw(destContext);
}
if (_parentStage->cylindricalX()) {
warning("%s: CylindricalX not handled yet", __func__);
}
if (_parentStage->cylindricalY()) {
warning("%s: CylindricalY not handled yet", __func__);
}
objectToDraw->setAdjustedBounds(kWrapNone);
}
void CameraActor::setXYDelta(uint xDelta, uint yDelta) {
_panDelta.x = xDelta;
_panDelta.y = yDelta;
debugC(6, kDebugCamera, "%s: (%d, %d)", __func__, _panDelta.x, _panDelta.y);
}
void CameraActor::setXYDelta() {
// If we have no parameters for setting the delta,
// just set the delta to 1 in whatever direction we are going.
if (_panStart.x < _panDest.x) {
_panDelta.x = 1;
} else if (_panDest.x < _panStart.x) {
_panDelta.x = -1;
}
if (_panStart.y < _panDest.y) {
_panDelta.y = 1;
} else if (_panDest.y < _panStart.y) {
_panDelta.y = -1;
}
debugC(6, kDebugCamera, "%s: (%d, %d)", __func__, _panDelta.x, _panDelta.y);
}
void CameraActor::panToByTime(int16 x, int16 y, double duration) {
_panState = kCameraPanToByTime;
_panStart = _currentViewportOrigin;
_panDest = Common::Point(x, y);
_panDuration = duration;
_currentPanStep = 1;
_startTime = g_system->getMillis();
_nextPanStepTime = 0;
debugC(6, kDebugCamera, "%s: panStart: (%d, %d); panDest: (%d, %d); panDuration: %f",
__func__, _panStart.x, _panStart.y, _panDest.x, _panDest.y, _panDuration);
setXYDelta();
calcNewViewportOrigin();
}
void CameraActor::panToByStepCount(int16 x, int16 y, uint panSteps, double duration) {
_panState = kCameraPanByStepCount;
_panStart = _currentViewportOrigin;
_panDest = Common::Point(x, y);
_panDuration = duration;
_currentPanStep = 1;
_maxPanStep = panSteps;
_startTime = g_system->getMillis();
_nextPanStepTime = 0;
debugC(6, kDebugCamera, "%s: panStart: (%d, %d); panDest: (%d, %d); panDuration: %f; maxPanStep: %d",
__func__, _panStart.x, _panStart.y, _panDest.x, _panDest.y, _panDuration, _maxPanStep);
setXYDelta();
calcNewViewportOrigin();
}
void CameraActor::startPan(uint xOffset, uint yOffset, double duration) {
_panState = kCameraPanningStarted;
_panDuration = duration;
_startTime = g_system->getMillis();
_nextPanStepTime = 0;
_currentPanStep = 0;
_maxPanStep = 0;
setXYDelta(xOffset, yOffset);
debugC(6, kDebugCamera, "%s: xOffset: %u, yOffset: %u, duration: %f", __func__, xOffset, yOffset, duration);
}
void CameraActor::stopPan() {
_panState = kCameraNotPanning;
_panDuration = 0.0;
_startTime = 0;
_nextPanStepTime = 0;
_currentPanStep = 0;
_maxPanStep = 0;
debugC(6, kDebugCamera, "%s: nextViewportOrigin: (%d, %d); actualViewportOrigin: (%d, %d)",
__func__, _nextViewportOrigin.x, _nextViewportOrigin.y, _currentViewportOrigin.x, _currentViewportOrigin.y);
}
bool CameraActor::continuePan() {
bool panShouldContinue = true;
if (_panState == kCameraPanningStarted) {
if (_panDelta == Common::Point(0, 0)) {
panShouldContinue = false;
}
} else {
if (percentComplete() >= 1.0) {
panShouldContinue = false;
}
}
debugC(6, kDebugCamera, "%s: %s", __func__, panShouldContinue ? "true": "false");
return panShouldContinue;
}
void CameraActor::process() {
// Only process panning if we're actively panning.
if (_panState == kCameraNotPanning) {
return;
}
// Check if it's time for the next pan step.
uint currentTime = g_system->getMillis() - _startTime;
if (currentTime < _nextPanStepTime) {
return;
}
debugC(7, kDebugCamera, "*** START PAN STEP ***");
timerEvent();
debugC(7, kDebugCamera, "*** END PAN STEP ***");
}
void CameraActor::timerEvent() {
if (_parentStage != nullptr) {
if (processViewportMove()) {
processNextPanStep();
if (continuePan()) {
if (cameraWithinStage(_nextViewportOrigin)) {
adjustCameraViewport(_nextViewportOrigin);
// The original had logic to pre-load the items that were going to be scrolled
// into view next, but since we load actors more all-at-once, we don't actually need this.
// The calls that would be made are kept commented out.
// Common::Rect advanceRect = getAdvanceRect();
// _parentStage->preload(advanceRect);
} else {
runEventHandlerIfExists(kCameraPanAbortEvent);
stopPan();
}
} else {
bool success = true;
if (_panState == kCameraPanToByTime) {
_nextViewportOrigin = _panDest;
adjustCameraViewport(_nextViewportOrigin);
success = processViewportMove();
}
if (success) {
runEventHandlerIfExists(kCameraPanEndEvent);
stopPan();
} else {
Common::Rect currentBounds = getBbox();
Common::Rect preloadBounds(_nextViewportOrigin, currentBounds.width(), currentBounds.height());
_parentStage->preload(preloadBounds);
}
}
} else {
Common::Rect currentBounds = getBbox();
Common::Rect preloadBounds(_nextViewportOrigin, currentBounds.width(), currentBounds.height());
_parentStage->preload(preloadBounds);
}
}
}
bool CameraActor::processViewportMove() {
bool isRectInMemory = true;
if (_parentStage != nullptr) {
Common::Rect boundsInViewport = getBbox();
boundsInViewport.moveTo(_nextViewportOrigin);
_parentStage->setCurrentCamera(this);
isRectInMemory = _parentStage->isRectInMemory(boundsInViewport);
if (isRectInMemory) {
invalidateLocalBounds();
setViewportOrigin(_nextViewportOrigin);
invalidateLocalBounds();
}
}
return isRectInMemory;
}
void CameraActor::processNextPanStep() {
// If pan type includes per-step updates (4-arg pan in original engine),
// advance the pan step counter. Then compute the new viewport origin
// and notify any script handlers registered for the pan-step event.
if (_panState == kCameraPanByStepCount) {
_currentPanStep += 1;
}
calcNewViewportOrigin();
runEventHandlerIfExists(kCameraPanStepEvent);
uint stepDurationInMilliseconds = 20; // Visually smooth.
_nextPanStepTime += stepDurationInMilliseconds;
}
void CameraActor::adjustCameraViewport(Common::Point &viewportToAdjust) {
if (_parentStage == nullptr) {
warning("%s: No parent stage", __func__);
return;
}
if (_parentStage->cylindricalX()) {
warning("%s: CylindricalX not handled yet", __func__);
}
if (_parentStage->cylindricalY()) {
warning("%s: CylindricalY not handled yet", __func__);
}
}
void CameraActor::calcNewViewportOrigin() {
if (_panState == kCameraPanningStarted) {
_nextViewportOrigin = _currentViewportOrigin + _panDelta;
debugC(6, kDebugCamera, "%s: (%d, %d) [panDelta: (%d, %d)]",
__func__, _nextViewportOrigin.x, _nextViewportOrigin.y, _panDelta.x, _panDelta.y);
} else {
// Interpolate from the start to the dest based on percent complete.
double progress = percentComplete();
double startX = static_cast<double>(_panStart.x);
double endX = static_cast<double>(_panDest.x);
double interpolatedX = startX + (endX - startX) * progress + 0.5;
_nextViewportOrigin.x = static_cast<int16>(interpolatedX);
double startY = static_cast<double>(_panStart.y);
double endY = static_cast<double>(_panDest.y);
double interpolatedY = startY + (endY - startY) * progress + 0.5;
_nextViewportOrigin.y = static_cast<int16>(interpolatedY);
debugC(6, kDebugCamera, "%s: (%d, %d) [panStart: (%d, %d); panDest: (%d, %d); percentComplete: %f]",
__func__, _nextViewportOrigin.x, _nextViewportOrigin.y, _panStart.x, _panStart.y, _panDest.x, _panDest.y, progress);
}
}
bool CameraActor::cameraWithinStage(const Common::Point &candidate) {
if (_parentStage == nullptr) {
return true;
}
bool result = true;
// We can only be out of horizontal bounds if we have a requested delta and
// are not doing X axis wrapping.
bool canBeOutOfHorizontalBounds = !_parentStage->cylindricalX() && _panDelta.x != 0;
if (canBeOutOfHorizontalBounds) {
int16 candidateRightBoundary = getBbox().width() + candidate.x;
bool cameraPastRightBoundary = _parentStage->extent().x < candidateRightBoundary;
if (cameraPastRightBoundary) {
result = false;
} else if (candidate.x < 0) {
result = false;
}
debugC(6, kDebugCamera, "%s: %s [rightBoundary: %d, extent: %d]", __func__, result ? "true" : "false", candidateRightBoundary, _parentStage->extent().x);
}
// We can only be out of vertical bounds if we have a requested delta and
// are not doing Y axis wrapping.
bool canBeOutOfVerticalBounds = !_parentStage->cylindricalY() && _panDelta.y != 0;
if (canBeOutOfVerticalBounds) {
int16 candidateBottomBoundary = getBbox().height() + candidate.y;
bool cameraPastBottomBoundary = _parentStage->extent().y < candidateBottomBoundary;
if (cameraPastBottomBoundary) {
result = false;
} else if (candidate.y < 0) {
result = false;
}
debugC(6, kDebugCamera, "%s: %s [bottomBoundary: %d, extent: %d]", __func__, result ? "true" : "false", candidateBottomBoundary, _parentStage->extent().y);
}
return result;
}
double CameraActor::percentComplete() {
double percentValue = 0.0;
switch (_panState) {
case kCameraPanByStepCount: {
percentValue = static_cast<double>(_maxPanStep - _currentPanStep) / static_cast<double>(_maxPanStep);
percentValue = 1.0 - percentValue;
break;
}
case kCameraPanToByTime: {
const double MILLISECONDS_IN_ONE_SECOND = 1000.0;
uint currentRuntime = g_system->getMillis();
uint elapsedTime = currentRuntime - _startTime;
double elapsedSeconds = elapsedTime / MILLISECONDS_IN_ONE_SECOND;
percentValue = elapsedSeconds / _panDuration;
break;
}
default:
percentValue = 0.0;
break;
}
percentValue = CLIP<double>(percentValue, 0.0, 1.0);
return percentValue;
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,103 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_CAMERA_H
#define MEDIASTATION_CAMERA_H
#include "mediastation/actor.h"
#include "mediastation/graphics.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
enum CameraPanState {
kCameraNotPanning = 0,
kCameraPanningStarted = 1,
// We pan for a certain total amount of time.
kCameraPanToByTime = 2,
// We pan for a certain number of steps, waiting a given time between each step.
kCameraPanByStepCount = 3
};
struct ImageAsset;
// A Camera's main purpose is panning around a stage that is too large to fit on screen all at once.
class CameraActor : public SpatialEntity, public ChannelClient {
public:
CameraActor() : SpatialEntity(kActorTypeCamera) {};
~CameraActor();
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType) override;
virtual void readChunk(Chunk &chunk) override;
virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
virtual void loadIsComplete() override;
virtual void process() override;
Common::Point getViewportOrigin();
Common::Rect getViewportBounds();
virtual void invalidateLocalBounds() override;
void drawUsingCamera(DisplayContext &displayContext, const Common::Array<SpatialEntity *> &entitiesToDraw);
private:
bool _lensOpen = false;
bool _addedToStage = false;
double _panDuration = 0.0;
uint _currentPanStep = 0;
uint _maxPanStep = 0;
uint _startTime = 0;
uint _nextPanStepTime = 0;
CameraPanState _panState = kCameraNotPanning;
Common::Point _offset;
Common::Point _currentViewportOrigin;
Common::Point _nextViewportOrigin;
Common::Point _panStart;
Common::Point _panDest;
Common::Point _panDelta;
Common::SharedPtr<ImageAsset> _image;
DisplayContext _displayContext;
void addToStage();
void removeFromStage(bool stopPan);
void setViewportOrigin(const Common::Point &newViewportOrigin);
void drawObject(DisplayContext &sourceContext, DisplayContext &destContext, SpatialEntity *objectToDraw);
void setXYDelta(uint xDelta, uint yDelta);
void setXYDelta();
bool cameraWithinStage(const Common::Point &candidate);
void panToByTime(int16 x, int16 y, double duration);
void panToByStepCount(int16 x, int16 y, uint maxPanStep, double duration);
void startPan(uint xOffset, uint yOffset, double duration);
void stopPan();
bool continuePan();
void timerEvent();
bool processViewportMove();
void processNextPanStep();
void adjustCameraViewport(Common::Point &viewportToAdjust);
void calcNewViewportOrigin();
double percentComplete();
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,48 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/actors/canvas.h"
namespace MediaStation {
void CanvasActor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderStartup:
_isVisible = static_cast<bool>(chunk.readTypedByte());
break;
default:
SpatialEntity::readParameter(chunk, paramType);
}
}
ScriptValue CanvasActor::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
switch (methodId) {
case kClearToPaletteMethod: {
error("%s: clearToPalette is not implemented yet", __func__);
}
default:
return SpatialEntity::callMethod(methodId, args);
}
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,41 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_CANVAS_H
#define MEDIASTATION_CANVAS_H
#include "mediastation/actor.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
class CanvasActor : public SpatialEntity {
public:
CanvasActor() : SpatialEntity(kActorTypeCanvas) {};
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType) override;
virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,100 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/mediastation.h"
#include "mediastation/actors/document.h"
namespace MediaStation {
const uint MediaStation::DocumentActor::DOCUMENT_ACTOR_ID;
ScriptValue DocumentActor::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
ScriptValue returnValue;
switch (methodId) {
case kDocumentBranchToScreenMethod:
processBranch(args);
break;
case kDocumentQuitMethod:
g_engine->quitGame();
break;
case kDocumentContextLoadInProgressMethod: {
assert(args.size() == 1);
uint contextId = args[0].asActorId();
bool isLoading = g_engine->getDocument()->isContextLoadInProgress(contextId);
returnValue.setToBool(isLoading);
break;
}
case kDocumentSetMultipleStreamsMethod:
case kDocumentSetMultipleSoundsMethod: {
assert(args.size() == 1);
bool value = args[0].asBool();
warning("%s: STUB: %s: %d", __func__, builtInMethodToStr(methodId), value);
break;
}
case kDocumentLoadContextMethod: {
assert(args.size() == 1);
uint contextId = args[0].asActorId();
g_engine->getDocument()->startContextLoad(contextId);
break;
}
case kDocumentReleaseContextMethod: {
assert(args.size() == 1);
uint contextId = args[0].asActorId();
g_engine->getDocument()->scheduleContextRelease(contextId);
break;
}
case kDocumentContextIsLoadedMethod: {
assert(args.size() == 1);
uint contextId = args[0].asActorId();
// We are looking for the screen actor with the same ID as the context.
Actor *screenActor = g_engine->getActorById(contextId);
bool contextIsLoading = g_engine->getDocument()->isContextLoadInProgress(contextId);
bool contextIsLoaded = (screenActor != nullptr) && !contextIsLoading;
returnValue.setToBool(contextIsLoaded);
break;
}
default:
returnValue = Actor::callMethod(methodId, args);
}
return returnValue;
}
void DocumentActor::processBranch(Common::Array<ScriptValue> &args) {
assert(args.size() >= 1);
uint contextId = args[0].asActorId();
if (args.size() > 1) {
bool disableUpdates = static_cast<bool>(args[1].asParamToken());
if (disableUpdates)
warning("%s: disableUpdates parameter not handled yet", __func__);
}
g_engine->getDocument()->scheduleScreenBranch(contextId);
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,44 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_DOCUMENT_H
#define MEDIASTATION_DOCUMENT_H
#include "mediastation/actor.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
class DocumentActor : public Actor {
public:
static const uint DOCUMENT_ACTOR_ID = 1;
DocumentActor() : Actor(kActorTypeDocument) { _id = DOCUMENT_ACTOR_ID; };
virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
private:
void processBranch(Common::Array<ScriptValue> &args);
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,66 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/debugchannels.h"
#include "mediastation/actors/font.h"
namespace MediaStation {
FontGlyph::FontGlyph(Chunk &chunk, uint asciiCode, uint unk1, uint unk2, BitmapHeader *header) : Bitmap(chunk, header) {
_asciiCode = asciiCode;
_unk1 = unk1;
_unk2 = unk2;
}
FontActor::~FontActor() {
unregisterWithStreamManager();
for (auto it = _glyphs.begin(); it != _glyphs.end(); ++it) {
delete it->_value;
}
_glyphs.clear();
}
void FontActor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderChannelIdent:
_channelIdent = chunk.readTypedChannelIdent();
registerWithStreamManager();
break;
default:
Actor::readParameter(chunk, paramType);
}
}
void FontActor::readChunk(Chunk &chunk) {
debugC(5, kDebugLoading, "FontActor::readChunk(): Reading font glyph (@0x%llx)", static_cast<long long int>(chunk.pos()));
uint asciiCode = chunk.readTypedUint16();
int unk1 = chunk.readTypedUint16();
int unk2 = chunk.readTypedUint16();
BitmapHeader *header = new BitmapHeader(chunk);
FontGlyph *glyph = new FontGlyph(chunk, asciiCode, unk1, unk2, header);
if (_glyphs.getValOrDefault(asciiCode) != nullptr) {
error("%s: Glyph for ASCII code 0x%x already exists", __func__, asciiCode);
}
_glyphs.setVal(asciiCode, glyph);
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,57 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_FONT_H
#define MEDIASTATION_FONT_H
#include "mediastation/actor.h"
#include "mediastation/bitmap.h"
#include "mediastation/datafile.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
class FontGlyph : public Bitmap {
public:
FontGlyph(Chunk &chunk, uint asciiCode, uint unk1, uint unk2, BitmapHeader *header);
uint _asciiCode = 0;
private:
int _unk1 = 0;
int _unk2 = 0;
};
class FontActor : public Actor, public ChannelClient {
public:
FontActor() : Actor(kActorTypeFont) {};
~FontActor();
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType) override;
virtual void readChunk(Chunk &chunk) override;
private:
Common::HashMap<uint, FontGlyph *> _glyphs;
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,252 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/debugchannels.h"
#include "mediastation/actors/hotspot.h"
#include "mediastation/mediastation.h"
namespace MediaStation {
void HotspotActor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderMouseActiveArea: {
uint16 total_points = chunk.readTypedUint16();
for (int i = 0; i < total_points; i++) {
Common::Point point = chunk.readTypedPoint();
_mouseActiveArea.push_back(point);
}
break;
}
case kActorHeaderStartup:
_isActive = static_cast<bool>(chunk.readTypedByte());
break;
case kActorHeaderCursorResourceId:
_cursorResourceId = chunk.readTypedUint16();
break;
case kActorHeaderGetOffstageEvents:
_getOffstageEvents = static_cast<bool>(chunk.readTypedByte());
break;
default:
SpatialEntity::readParameter(chunk, paramType);
}
}
bool HotspotActor::isInside(const Common::Point &pointToCheck) {
// No sense checking the polygon if we're not even in the bbox.
if (!_boundingBox.contains(pointToCheck)) {
return false;
}
// We're in the bbox, but there might not be a polygon to check.
if (_mouseActiveArea.empty()) {
return true;
}
// Polygon intersection code adapted from HADESCH engine, might need more
// refinement once more testing is possible.
Common::Point point = pointToCheck - Common::Point(_boundingBox.left, _boundingBox.top);
int rcross = 0; // Number of right-side overlaps
// Each edge is checked whether it cuts the outgoing stream from the point
Common::Array<Common::Point> _polygon = _mouseActiveArea;
for (unsigned i = 0; i < _polygon.size(); i++) {
const Common::Point &edgeStart = _polygon[i];
const Common::Point &edgeEnd = _polygon[(i + 1) % _polygon.size()];
// A vertex is a point? Then it lies on one edge of the polygon
if (point == edgeStart)
return true;
if ((edgeStart.y > point.y) != (edgeEnd.y > point.y)) {
int term1 = (edgeStart.x - point.x) * (edgeEnd.y - point.y) - (edgeEnd.x - point.x) * (edgeStart.y - point.y);
int term2 = (edgeEnd.y - point.y) - (edgeStart.y - edgeEnd.y);
if ((term1 > 0) == (term2 >= 0))
rcross++;
}
}
// The point is strictly inside the polygon if and only if the number of overlaps is odd
return ((rcross % 2) == 1);
}
ScriptValue HotspotActor::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
ScriptValue returnValue;
switch (methodId) {
case kMouseActivateMethod: {
assert(args.empty());
activate();
return returnValue;
}
case kMouseDeactivateMethod: {
assert(args.empty());
deactivate();
return returnValue;
}
case kIsActiveMethod: {
assert(args.empty());
returnValue.setToBool(_isActive);
return returnValue;
}
case kTriggerAbsXPositionMethod: {
double mouseX = static_cast<double>(g_system->getEventManager()->getMousePos().x);
returnValue.setToFloat(mouseX);
return returnValue;
}
case kTriggerAbsYPositionMethod: {
double mouseY = static_cast<double>(g_system->getEventManager()->getMousePos().y);
returnValue.setToFloat(mouseY);
return returnValue;
}
default:
return SpatialEntity::callMethod(methodId, args);
}
}
uint16 HotspotActor::findActorToAcceptMouseEvents(
const Common::Point &point,
uint16 eventMask,
MouseActorState &state,
bool clipMouseEvents) {
uint16 result = 0;
if (isActive()) {
if (isInside(point)) {
if (eventMask & kMouseDownFlag) {
state.mouseDown = this;
result |= kMouseDownFlag;
}
if (eventMask & kMouseEnterFlag) {
state.mouseEnter = this;
result |= kMouseEnterFlag;
}
if (eventMask & kMouseMovedFlag) {
state.mouseMoved = this;
result |= kMouseMovedFlag;
}
}
if (this == g_engine->getMouseInsideHotspot() && (eventMask & kMouseExitFlag)) {
state.mouseExit = this;
result |= kMouseExitFlag;
}
if (this == g_engine->getMouseDownHotspot() && (eventMask & kMouseUpFlag)) {
state.mouseUp = this;
result |= kMouseUpFlag;
}
} else {
debugC(5, kDebugEvents, "%s: %d: Inactive", __func__, id());
}
return result;
}
void HotspotActor::activate() {
if (!_isActive) {
_isActive = true;
invalidateMouse();
}
}
void HotspotActor::deactivate() {
if (_isActive) {
_isActive = false;
if (g_engine->getMouseDownHotspot() == this) {
g_engine->setMouseDownHotspot(nullptr);
}
if (g_engine->getMouseInsideHotspot() == this) {
g_engine->setMouseDownHotspot(nullptr);
}
invalidateMouse();
}
}
void HotspotActor::mouseDownEvent(const Common::Event &event) {
if (!_isActive) {
warning("%s: Called on inactive hotspot", __func__);
return;
}
g_engine->setMouseDownHotspot(this);
runEventHandlerIfExists(kMouseDownEvent);
}
void HotspotActor::mouseUpEvent(const Common::Event &event) {
if (!_isActive) {
warning("%s: Called on inactive hotspot", __func__);
return;
}
g_engine->setMouseDownHotspot(nullptr);
runEventHandlerIfExists(kMouseUpEvent);
}
void HotspotActor::mouseEnteredEvent(const Common::Event &event) {
if (!_isActive) {
warning("%s: Called on inactive hotspot", __func__);
return;
}
g_engine->setMouseInsideHotspot(this);
if (_cursorResourceId != 0) {
debugC(5, kDebugEvents, "%s: Setting cursor %d for asset %d", __func__, _cursorResourceId, id());
g_engine->getCursorManager()->setAsTemporary(_cursorResourceId);
} else {
debugC(5, kDebugEvents, "%s: Unsetting cursor for asset %d", __func__, id());
g_engine->getCursorManager()->unsetTemporary();
}
runEventHandlerIfExists(kMouseEnteredEvent);
}
void HotspotActor::mouseMovedEvent(const Common::Event &event) {
if (!_isActive) {
warning("%s: Called on inactive hotspot", __func__);
return;
}
runEventHandlerIfExists(kMouseMovedEvent);
}
void HotspotActor::mouseExitedEvent(const Common::Event &event) {
if (!_isActive) {
warning("%s: Called on inactive hotspot", __func__);
return;
}
g_engine->setMouseInsideHotspot(nullptr);
runEventHandlerIfExists(kMouseExitedEvent);
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,68 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_HOTSPOT_H
#define MEDIASTATION_HOTSPOT_H
#include "mediastation/actor.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
class HotspotActor : public SpatialEntity {
public:
HotspotActor() : SpatialEntity(kActorTypeHotspot) {};
virtual ~HotspotActor() { _mouseActiveArea.clear(); }
bool isInside(const Common::Point &pointToCheck);
virtual bool isVisible() const override { return false; }
bool isActive() const { return _isActive; }
virtual bool interactsWithMouse() const override { return isActive(); }
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType) override;
virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
virtual uint16 findActorToAcceptMouseEvents(
const Common::Point &point,
uint16 eventMask,
MouseActorState &state,
bool inBounds) override;
void activate();
void deactivate();
virtual void mouseDownEvent(const Common::Event &event) override;
virtual void mouseUpEvent(const Common::Event &event) override;
virtual void mouseEnteredEvent(const Common::Event &event) override;
virtual void mouseExitedEvent(const Common::Event &event) override;
virtual void mouseMovedEvent(const Common::Event &event) override;
uint _cursorResourceId = 0;
Common::Array<Common::Point> _mouseActiveArea;
private:
bool _isActive = false;
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,128 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/mediastation.h"
#include "mediastation/actors/image.h"
#include "mediastation/debugchannels.h"
namespace MediaStation {
ImageAsset::~ImageAsset() {
delete bitmap;
bitmap = nullptr;
}
ImageActor::~ImageActor() {
unregisterWithStreamManager();
}
void ImageActor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderChannelIdent:
_channelIdent = chunk.readTypedChannelIdent();
registerWithStreamManager();
_asset = Common::SharedPtr<ImageAsset>(new ImageAsset);
break;
case kActorHeaderStartup:
_isVisible = static_cast<bool>(chunk.readTypedByte());
break;
case kActorHeaderLoadType:
_loadType = chunk.readTypedByte();
break;
case kActorHeaderX:
_xOffset = chunk.readTypedUint16();
break;
case kActorHeaderY:
_yOffset = chunk.readTypedUint16();
break;
case kActorHeaderActorReference: {
_actorReference = chunk.readTypedUint16();
Actor *referencedActor = g_engine->getActorById(_actorReference);
if (referencedActor == nullptr) {
error("%s: Referenced actor %d doesn't exist or has not been read yet in this title", __func__, _actorReference);
}
if (referencedActor->type() != kActorTypeImage) {
error("%s: Type mismatch of referenced actor %d", __func__, _actorReference);
}
ImageActor *referencedImage = static_cast<ImageActor *>(referencedActor);
_asset = referencedImage->_asset;
break;
}
default:
SpatialEntity::readParameter(chunk, paramType);
}
}
ScriptValue ImageActor::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
ScriptValue returnValue;
switch (methodId) {
case kSpatialShowMethod: {
assert(args.empty());
spatialShow();
return returnValue;
}
case kSpatialHideMethod: {
assert(args.empty());
spatialHide();
return returnValue;
}
default:
return SpatialEntity::callMethod(methodId, args);
}
}
void ImageActor::draw(DisplayContext &displayContext) {
if (_isVisible) {
Common::Point origin = getBbox().origin();
g_engine->getDisplayManager()->imageBlit(origin, _asset->bitmap, _dissolveFactor, &displayContext);
}
}
void ImageActor::spatialShow() {
_isVisible = true;
invalidateLocalBounds();
}
void ImageActor::spatialHide() {
_isVisible = false;
invalidateLocalBounds();
}
Common::Rect ImageActor::getBbox() const {
Common::Point origin(_xOffset + _boundingBox.left, _yOffset + _boundingBox.top);
Common::Rect bbox(origin, _asset->bitmap->width(), _asset->bitmap->height());
return bbox;
}
void ImageActor::readChunk(Chunk &chunk) {
BitmapHeader *bitmapHeader = new BitmapHeader(chunk);
_asset->bitmap = new Bitmap(chunk, bitmapHeader);
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,68 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_IMAGE_H
#define MEDIASTATION_IMAGE_H
#include "common/ptr.h"
#include "mediastation/actor.h"
#include "mediastation/datafile.h"
#include "mediastation/bitmap.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
// The original had a separate class that did reference counting,
// for sharing an asset across actors, but we can just use a SharedPtr.
struct ImageAsset {
~ImageAsset();
Bitmap *bitmap = nullptr;
};
class ImageActor : public SpatialEntity, public ChannelClient {
public:
ImageActor() : SpatialEntity(kActorTypeImage) {};
virtual ~ImageActor() override;
virtual void readChunk(Chunk &chunk) override;
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType) override;
virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
virtual void draw(DisplayContext &displayContext) override;
virtual Common::Rect getBbox() const override;
private:
Common::SharedPtr<ImageAsset> _asset;
uint _loadType = 0;
int _xOffset = 0;
int _yOffset = 0;
uint _actorReference = 0;
// Script method implementations.
void spatialShow();
void spatialHide();
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,503 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/actors/movie.h"
#include "mediastation/debugchannels.h"
#include "mediastation/mediastation.h"
namespace MediaStation {
MovieFrameHeader::MovieFrameHeader(Chunk &chunk) : BitmapHeader(chunk) {
_index = chunk.readTypedUint32();
debugC(5, kDebugLoading, "MovieFrameHeader::MovieFrameHeader(): _index = 0x%x (@0x%llx)", _index, static_cast<long long int>(chunk.pos()));
_keyframeEndInMilliseconds = chunk.readTypedUint32();
}
MovieFrame::MovieFrame(Chunk &chunk) {
if (g_engine->isFirstGenerationEngine()) {
blitType = static_cast<MovieBlitType>(chunk.readTypedUint16());
startInMilliseconds = chunk.readTypedUint32();
endInMilliseconds = chunk.readTypedUint32();
// These are unsigned in the data files but ScummVM expects signed.
leftTop.x = static_cast<int16>(chunk.readTypedUint16());
leftTop.y = static_cast<int16>(chunk.readTypedUint16());
index = chunk.readTypedUint32();
keyframeIndex = chunk.readTypedUint32();
keepAfterEnd = chunk.readTypedByte();
} else {
layerId = chunk.readTypedUint32();
blitType = static_cast<MovieBlitType>(chunk.readTypedUint16());
startInMilliseconds = chunk.readTypedUint32();
endInMilliseconds = chunk.readTypedUint32();
// These are unsigned in the data files but ScummVM expects signed.
leftTop.x = static_cast<int16>(chunk.readTypedUint16());
leftTop.y = static_cast<int16>(chunk.readTypedUint16());
zIndex = chunk.readTypedSint16();
// This represents the difference between the left-top coordinate of the
// keyframe (if applicable) and the left coordinate of this frame. Zero
// if there is no keyframe.
diffBetweenKeyframeAndFrame.x = chunk.readTypedSint16();
diffBetweenKeyframeAndFrame.y = chunk.readTypedSint16();
index = chunk.readTypedUint32();
keyframeIndex = chunk.readTypedUint32();
keepAfterEnd = chunk.readTypedByte();
debugC(5, kDebugLoading, "MovieFrame::MovieFrame(): _blitType = %d, _startInMilliseconds = %d, \
_endInMilliseconds = %d, _left = %d, _top = %d, _zIndex = %d, _diffBetweenKeyframeAndFrameX = %d, \
_diffBetweenKeyframeAndFrameY = %d, _index = %d, _keyframeIndex = %d, _keepAfterEnd = %d (@0x%llx)",
blitType, startInMilliseconds, endInMilliseconds, leftTop.x, leftTop.y, zIndex, diffBetweenKeyframeAndFrame.x, \
diffBetweenKeyframeAndFrame.y, index, keyframeIndex, keepAfterEnd, static_cast<long long int>(chunk.pos()));
}
}
MovieFrameImage::MovieFrameImage(Chunk &chunk, MovieFrameHeader *header) : Bitmap(chunk, header) {
_bitmapHeader = header;
}
MovieFrameImage::~MovieFrameImage() {
// The base class destructor takes care of deleting the bitmap header, so
// we don't need to delete that here.
}
StreamMovieActor::~StreamMovieActor() {
unregisterWithStreamManager();
if (_streamFeed != nullptr) {
g_engine->getStreamFeedManager()->closeStreamFeed(_streamFeed);
_streamFeed = nullptr;
}
delete _streamFrames;
_streamFrames = nullptr;
delete _streamSound;
_streamSound = nullptr;
}
void StreamMovieActor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderActorId: {
// We already have this actor's ID, so we will just verify it is the same
// as the ID we have already read.
uint32 duplicateActorId = chunk.readTypedUint16();
if (duplicateActorId != _id) {
warning("%s: Duplicate actor ID %d does not match original ID %d", __func__, duplicateActorId, _id);
}
break;
}
case kActorHeaderMovieLoadType:
_loadType = chunk.readTypedByte();
break;
case kActorHeaderChannelIdent:
_channelIdent = chunk.readTypedChannelIdent();
registerWithStreamManager();
break;
case kActorHeaderHasOwnSubfile: {
bool hasOwnSubfile = static_cast<bool>(chunk.readTypedByte());
if (!hasOwnSubfile) {
error("%s: StreamMovieActor doesn't have a subfile", __func__);
}
break;
}
case kActorHeaderStartup:
_isVisible = static_cast<bool>(chunk.readTypedByte());
break;
case kActorHeaderMovieAudioChannelIdent: {
ChannelIdent soundChannelIdent = chunk.readTypedChannelIdent();
_streamSound->setChannelIdent(soundChannelIdent);
_streamSound->registerWithStreamManager();
break;
}
case kActorHeaderMovieAnimationChannelIdent: {
ChannelIdent framesChannelIdent = chunk.readTypedChannelIdent();
_streamFrames->setChannelIdent(framesChannelIdent);
_streamFrames->registerWithStreamManager();
break;
}
case kActorHeaderSoundInfo:
_streamSound->_audioSequence.readParameters(chunk);
break;
default:
SpatialEntity::readParameter(chunk, paramType);
}
}
ScriptValue StreamMovieActor::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
ScriptValue returnValue;
switch (methodId) {
case kTimePlayMethod: {
assert(args.empty());
timePlay();
return returnValue;
}
case kSpatialShowMethod: {
assert(args.empty());
setVisibility(true);
updateFrameState();
return returnValue;
}
case kTimeStopMethod: {
assert(args.empty());
timeStop();
return returnValue;
}
case kSpatialHideMethod: {
assert(args.empty());
setVisibility(false);
return returnValue;
}
case kIsPlayingMethod: {
assert(args.empty());
returnValue.setToBool(_isPlaying);
return returnValue;
}
case kGetLeftXMethod: {
assert(args.empty());
double left = static_cast<double>(_boundingBox.left);
returnValue.setToFloat(left);
return returnValue;
}
case kGetTopYMethod: {
assert(args.empty());
double top = static_cast<double>(_boundingBox.top);
returnValue.setToFloat(top);
return returnValue;
}
default:
return SpatialEntity::callMethod(methodId, args);
}
}
void StreamMovieActor::timePlay() {
if (_streamFeed == nullptr) {
_streamFeed = g_engine->getStreamFeedManager()->openStreamFeed(_id);
_streamFeed->readData();
}
if (_isPlaying) {
return;
}
_streamSound->_audioSequence.play();
_framesNotYetShown = _streamFrames->_frames;
_framesOnScreen.clear();
_isPlaying = true;
_startTime = g_system->getMillis();
_lastProcessedTime = 0;
runEventHandlerIfExists(kMovieBeginEvent);
process();
}
void StreamMovieActor::timeStop() {
if (!_isPlaying) {
return;
}
for (MovieFrame *frame : _framesOnScreen) {
invalidateRect(getFrameBoundingBox(frame));
}
_streamSound->_audioSequence.stop();
_framesNotYetShown.empty();
if (_hasStill) {
_framesNotYetShown = _streamFrames->_frames;
}
_framesOnScreen.clear();
_startTime = 0;
_lastProcessedTime = 0;
_isPlaying = false;
runEventHandlerIfExists(kMovieStoppedEvent);
}
void StreamMovieActor::process() {
if (_isVisible) {
if (_isPlaying) {
processTimeEventHandlers();
}
updateFrameState();
}
}
void StreamMovieActor::setVisibility(bool visibility) {
if (visibility != _isVisible) {
_isVisible = visibility;
invalidateLocalBounds();
}
}
void StreamMovieActor::updateFrameState() {
uint movieTime = 0;
if (_isPlaying) {
uint currentTime = g_system->getMillis();
movieTime = currentTime - _startTime;
}
debugC(5, kDebugGraphics, "StreamMovieActor::updateFrameState (%d): Starting update (movie time: %d)", _id, movieTime);
// This complexity is necessary becuase movies can have more than one frame
// showing at the same time - for instance, a movie background and an
// animation on that background are a part of the saem movie and are on
// screen at the same time, it's just the starting and ending times of one
// can be different from the starting and ending times of another.
//
// We can rely on the frames being ordered in order of their start. First,
// see if there are any new frames to show.
for (auto it = _framesNotYetShown.begin(); it != _framesNotYetShown.end();) {
MovieFrame *frame = *it;
bool isAfterStart = movieTime >= frame->startInMilliseconds;
if (isAfterStart) {
_framesOnScreen.insert(frame);
invalidateRect(getFrameBoundingBox(frame));
// We don't need ++it because we will either have another frame
// that needs to be drawn, or we have reached the end of the new
// frames.
it = _framesNotYetShown.erase(it);
} else {
// We've hit a frame that shouldn't yet be shown.
// Rely on the ordering to not bother with any further frames.
break;
}
}
// Now see if there are any old frames that no longer need to be shown.
for (auto it = _framesOnScreen.begin(); it != _framesOnScreen.end();) {
MovieFrame *frame = *it;
bool isAfterEnd = movieTime >= frame->endInMilliseconds;
if (isAfterEnd) {
invalidateRect(getFrameBoundingBox(frame));
it = _framesOnScreen.erase(it);
if (_framesOnScreen.empty() && movieTime >= _fullTime) {
_isPlaying = false;
if (_hasStill) {
_framesNotYetShown = _streamFrames->_frames;
updateFrameState();
}
runEventHandlerIfExists(kMovieEndEvent);
break;
}
} else {
++it;
}
}
// Show the frames that are currently active, for debugging purposes.
for (MovieFrame *frame : _framesOnScreen) {
debugC(5, kDebugGraphics, " (time: %d ms) Frame %d (%d x %d) @ (%d, %d); start: %d ms, end: %d ms, zIndex = %d", \
movieTime, frame->index, frame->image->width(), frame->image->height(), frame->leftTop.x, frame->leftTop.y, frame->startInMilliseconds, frame->endInMilliseconds, frame->zIndex);
}
}
void StreamMovieActor::draw(DisplayContext &displayContext) {
for (MovieFrame *frame : _framesOnScreen) {
Common::Rect bbox = getFrameBoundingBox(frame);
switch (frame->blitType) {
case kUncompressedMovieBlit:
g_engine->getDisplayManager()->imageBlit(bbox.origin(), frame->image, _dissolveFactor, &displayContext);
break;
case kUncompressedDeltaMovieBlit:
g_engine->getDisplayManager()->imageDeltaBlit(
bbox.origin(), frame->diffBetweenKeyframeAndFrame,
frame->image, frame->keyframeImage, _dissolveFactor, &displayContext);
break;
case kCompressedDeltaMovieBlit:
if (frame->keyframeImage->isCompressed()) {
decompressIntoAuxImage(frame);
}
g_engine->getDisplayManager()->imageDeltaBlit(
bbox.origin(), frame->diffBetweenKeyframeAndFrame,
frame->image, frame->keyframeImage, _dissolveFactor, &displayContext);
break;
default:
error("%s: Got unknown movie frame blit type: %d", __func__, frame->blitType);
}
}
}
Common::Rect StreamMovieActor::getFrameBoundingBox(MovieFrame *frame) {
// Use _boundingBox directly (which may be temporarily offset by camera rendering)
// The camera offset is already applied to _boundingBox by pushBoundingBoxOffset()
Common::Point origin = _boundingBox.origin() + frame->leftTop;
Common::Rect bbox = Common::Rect(origin, frame->image->width(), frame->image->height());
return bbox;
}
StreamMovieActorFrames::~StreamMovieActorFrames() {
unregisterWithStreamManager();
for (MovieFrame *frame : _frames) {
delete frame;
}
_frames.clear();
for (MovieFrameImage *image : _images) {
delete image;
}
_images.clear();
}
void StreamMovieActorFrames::readChunk(Chunk &chunk) {
uint sectionType = chunk.readTypedUint16();
switch ((MovieSectionType)sectionType) {
case kMovieImageDataSection:
readImageData(chunk);
break;
case kMovieFrameDataSection:
readFrameData(chunk);
break;
default:
error("%s: Unknown movie still section type", __func__);
}
for (MovieFrame *frame : _frames) {
if (frame->endInMilliseconds > _parent->_fullTime) {
_parent->_fullTime = frame->endInMilliseconds;
}
if (frame->keepAfterEnd) {
_parent->_hasStill = true;
}
}
if (_parent->_hasStill) {
_parent->_framesNotYetShown = _frames;
}
}
StreamMovieActorSound::~StreamMovieActorSound() {
unregisterWithStreamManager();
}
void StreamMovieActorSound::readChunk(Chunk &chunk) {
_audioSequence.readChunk(chunk);
}
StreamMovieActor::StreamMovieActor() : _framesOnScreen(StreamMovieActor::compareFramesByZIndex), SpatialEntity(kActorTypeMovie) {
_streamFrames = new StreamMovieActorFrames(this);
_streamSound = new StreamMovieActorSound();
}
void StreamMovieActor::readChunk(Chunk &chunk) {
MovieSectionType sectionType = static_cast<MovieSectionType>(chunk.readTypedUint16());
if (sectionType == kMovieRootSection) {
parseMovieHeader(chunk);
} else if (sectionType == kMovieChunkMarkerSection) {
parseMovieChunkMarker(chunk);
} else {
error("%s: Got unused movie chunk header section", __func__);
}
}
void StreamMovieActor::parseMovieHeader(Chunk &chunk) {
_chunkCount = chunk.readTypedUint16();
_frameRate = chunk.readTypedDouble();
debugC(5, kDebugLoading, "%s: chunkCount = 0x%x, frameRate = %f (@0x%llx)", __func__, _chunkCount, _frameRate, static_cast<long long int>(chunk.pos()));
Common::Array<uint> chunkLengths;
for (uint i = 0; i < _chunkCount; i++) {
uint chunkLength = chunk.readTypedUint32();
debugC(5, kDebugLoading, "StreamMovieActor::readSubfile(): chunkLength = 0x%x (@0x%llx)", chunkLength, static_cast<long long int>(chunk.pos()));
chunkLengths.push_back(chunkLength);
}
}
void StreamMovieActor::parseMovieChunkMarker(Chunk &chunk) {
// TODO: There is no warning here because that would spam with thousands of warnings.
// This takes care of scheduling stream load and such - it doesn't actually read from the
// chunk that is passed in. Since we don't need that scheduling since we are currently reading
// the whole movie at once rather than streaming it from the CD-ROM, we don't currently need
// to do much here anyway.
}
void StreamMovieActor::invalidateRect(const Common::Rect &rect) {
invalidateLocalBounds();
}
void StreamMovieActor::decompressIntoAuxImage(MovieFrame *frame) {
const Common::Point origin(0, 0);
frame->keyframeImage->_image.create(frame->keyframeImage->width(), frame->keyframeImage->height(), Graphics::PixelFormat::createFormatCLUT8());
frame->keyframeImage->_image.setTransparentColor(0);
g_engine->getDisplayManager()->imageBlit(origin, frame->keyframeImage, 1.0, nullptr, &frame->keyframeImage->_image);
}
void StreamMovieActorFrames::readImageData(Chunk &chunk) {
MovieFrameHeader *header = new MovieFrameHeader(chunk);
MovieFrameImage *frame = new MovieFrameImage(chunk, header);
_images.push_back(frame);
}
void StreamMovieActorFrames::readFrameData(Chunk &chunk) {
uint frameDataToRead = chunk.readTypedUint16();
for (uint i = 0; i < frameDataToRead; i++) {
MovieFrame *frame = new MovieFrame(chunk);
// We cannot use a hashmap here because multiple frames can have the
// same index, and frames are not necessarily in index order. So we'll
// do a linear search, which is how the original does it.
for (MovieFrameImage *image : _images) {
if (image->index() == frame->index) {
frame->image = image;
break;
}
}
if (frame->keyframeIndex != 0) {
for (MovieFrameImage *image : _images) {
if (image->index() == frame->keyframeIndex) {
frame->keyframeImage = image;
break;
}
}
}
_frames.push_back(frame);
}
}
int StreamMovieActor::compareFramesByZIndex(const MovieFrame *a, const MovieFrame *b) {
if (b->zIndex > a->zIndex) {
return 1;
} else if (a->zIndex > b->zIndex) {
return -1;
} else {
return 0;
}
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,167 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_MOVIE_H
#define MEDIASTATION_MOVIE_H
#include "common/array.h"
#include "mediastation/actor.h"
#include "mediastation/audio.h"
#include "mediastation/datafile.h"
#include "mediastation/bitmap.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
enum MovieBlitType {
kInvalidMovieBlit = 0,
kUncompressedMovieBlit = 1,
kUncompressedDeltaMovieBlit = 2,
kCompressedDeltaMovieBlit = 3,
};
class MovieFrameHeader : public BitmapHeader {
public:
MovieFrameHeader(Chunk &chunk);
uint _index = 0;
uint _keyframeEndInMilliseconds = 0;
};
class MovieFrameImage : public Bitmap {
public:
MovieFrameImage(Chunk &chunk, MovieFrameHeader *header);
virtual ~MovieFrameImage() override;
uint32 index() { return _bitmapHeader->_index; }
private:
MovieFrameHeader *_bitmapHeader = nullptr;
};
enum MovieSectionType {
kMovieRootSection = 0x06a8,
kMovieImageDataSection = 0x06a9,
kMovieFrameDataSection = 0x06aa,
kMovieChunkMarkerSection = 0x06ab
};
struct MovieFrame {
MovieFrame(Chunk &chunk);
uint unk3 = 0;
uint unk4 = 0;
uint layerId = 0;
uint startInMilliseconds = 0;
uint endInMilliseconds = 0;
Common::Point leftTop;
Common::Point diffBetweenKeyframeAndFrame;
MovieBlitType blitType = kInvalidMovieBlit;
int16 zIndex = 0;
uint keyframeIndex = 0;
bool keepAfterEnd = false;
uint index = 0;
MovieFrameImage *image = nullptr;
MovieFrameImage *keyframeImage = nullptr;
};
class StreamMovieActor;
// This is called `RT_stmvFrames` in the original.
class StreamMovieActorFrames : public ChannelClient {
public:
StreamMovieActorFrames(StreamMovieActor *parent) : ChannelClient(), _parent(parent) {}
~StreamMovieActorFrames();
virtual void readChunk(Chunk &chunk) override;
Common::Array<MovieFrame *> _frames;
Common::Array<MovieFrameImage *> _images;
private:
StreamMovieActor *_parent = nullptr;
void readImageData(Chunk &chunk);
void readFrameData(Chunk &chunk);
};
// This is called `RT_stmvSound` in the original.
class StreamMovieActorSound : public ChannelClient {
public:
~StreamMovieActorSound();
virtual void readChunk(Chunk &chunk) override;
AudioSequence _audioSequence;
};
class StreamMovieActor : public SpatialEntity, public ChannelClient {
friend class StreamMovieActorFrames;
friend class StreamMovieActorSound;
public:
StreamMovieActor();
virtual ~StreamMovieActor() override;
virtual void readChunk(Chunk &chunk) override;
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType) override;
virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
virtual void process() override;
virtual void draw(DisplayContext &displayContext) override;
virtual bool isVisible() const override { return _isVisible; }
private:
ImtStreamFeed *_streamFeed = nullptr;
uint _fullTime = 0;
uint _chunkCount = 0;
double _frameRate = 0;
uint _loadType = 0;
bool _isPlaying = false;
bool _hasStill = false;
StreamMovieActorFrames *_streamFrames = nullptr;
StreamMovieActorSound *_streamSound = nullptr;
Common::Array<MovieFrame *> _framesNotYetShown;
Common::SortedArray<MovieFrame *, const MovieFrame *> _framesOnScreen;
// Script method implementations.
void timePlay();
void timeStop();
void setVisibility(bool visibility);
void updateFrameState();
void invalidateRect(const Common::Rect &rect);
void decompressIntoAuxImage(MovieFrame *frame);
void parseMovieHeader(Chunk &chunk);
void parseMovieChunkMarker(Chunk &chunk);
Common::Rect getFrameBoundingBox(MovieFrame *frame);
static int compareFramesByZIndex(const MovieFrame *a, const MovieFrame *b);
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,47 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/mediastation.h"
#include "mediastation/actors/palette.h"
#include "mediastation/debugchannels.h"
namespace MediaStation {
PaletteActor::~PaletteActor() {
delete _palette;
_palette = nullptr;
}
void PaletteActor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderPalette: {
byte *buffer = new byte[Graphics::PALETTE_SIZE];
chunk.read(buffer, Graphics::PALETTE_SIZE);
_palette = new Graphics::Palette(buffer, Graphics::PALETTE_COUNT, DisposeAfterUse::YES);
break;
}
default:
Actor::readParameter(chunk, paramType);
}
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,45 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_PALETTE_H
#define MEDIASTATION_PALETTE_H
#include "graphics/palette.h"
#include "mediastation/actor.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
class PaletteActor : public Actor {
public:
PaletteActor() : Actor(kActorTypePalette) {};
virtual ~PaletteActor() override;
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType) override;
Graphics::Palette *_palette = nullptr;
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,165 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/actors/path.h"
#include "mediastation/mediastation.h"
#include "mediastation/debugchannels.h"
namespace MediaStation {
void PathActor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderStartPoint:
_startPoint = chunk.readTypedPoint();
break;
case kActorHeaderEndPoint:
_endPoint = chunk.readTypedPoint();
break;
case kActorHeaderStepRate: {
double _stepRateFloat = chunk.readTypedDouble();
// This should always be an integer anyway,
// so we'll cast away any fractional part.
_stepRate = static_cast<uint32>(_stepRateFloat);
break;
}
case kActorHeaderDuration:
// These are stored in the file as fractional seconds,
// but we want milliseconds.
_duration = static_cast<uint32>(chunk.readTypedTime() * 1000);
break;
case kActorHeaderPathTotalSteps:
_totalSteps = chunk.readTypedUint16();
break;
default:
Actor::readParameter(chunk, paramType);
}
}
ScriptValue PathActor::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
ScriptValue returnValue;
switch (methodId) {
case kTimePlayMethod: {
assert(args.size() == 0);
timePlay();
return returnValue;
}
case kSetDurationMethod: {
assert(args.size() == 1);
uint durationInMilliseconds = static_cast<uint>(args[0].asTime() * 1000);
setDuration(durationInMilliseconds);
return returnValue;
}
case kPercentCompleteMethod: {
assert(args.size() == 0);
returnValue.setToFloat(percentComplete());
return returnValue;
}
case kIsPlayingMethod: {
assert(args.empty());
returnValue.setToBool(_isPlaying);
return returnValue;
}
default:
return Actor::callMethod(methodId, args);
}
}
void PathActor::timePlay() {
if (_isPlaying) {
return;
}
if (_duration == 0) {
warning("%s: Got zero duration", __func__);
} else if (_stepRate == 0) {
error("%s: Got zero step rate", __func__);
}
_isPlaying = true;
_startTime = g_system->getMillis();
_lastProcessedTime = 0;
_percentComplete = 0;
_nextPathStepTime = 0;
_currentStep = 0;
_totalSteps = (_duration * _stepRate) / 1000;
_stepDurationInMilliseconds = 1000 / _stepRate;
// TODO: Run the path start event. Haven't seen one the wild yet, don't know its ID.
debugC(5, kDebugScript, "Path::timePlay(): No PathStart event handler");
}
void PathActor::process() {
if (!_isPlaying) {
return;
}
uint currentTime = g_system->getMillis();
uint pathTime = currentTime - _startTime;
bool doNextStep = pathTime >= _nextPathStepTime;
if (!doNextStep) {
return;
}
_percentComplete = static_cast<double>(_currentStep + 1) / _totalSteps;
debugC(2, kDebugScript, "Path::timePlay(): Step %d of %d", _currentStep, _totalSteps);
if (_currentStep < _totalSteps) {
// TODO: Actually step the path. It seems they mostly just use this for
// palette animation in the On Step event handler, so nothing is actually drawn on the screen now.
// We don't run a step event for the last step.
runEventHandlerIfExists(kPathStepEvent);
_nextPathStepTime = ++_currentStep * _stepDurationInMilliseconds;
} else {
_isPlaying = false;
_percentComplete = 0;
_nextPathStepTime = 0;
_currentStep = 0;
_totalSteps = 0;
_stepDurationInMilliseconds = 0;
runEventHandlerIfExists(kPathEndEvent);
}
}
void PathActor::setDuration(uint durationInMilliseconds) {
// TODO: Do we need to save the original duration?
debugC(5, kDebugScript, "Path::setDuration(): Setting duration to %d ms", durationInMilliseconds);
_duration = durationInMilliseconds;
}
double PathActor::percentComplete() {
debugC(5, kDebugScript, "Path::percentComplete(): Returning percent complete %f%%", _percentComplete * 100);
return _percentComplete;
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,61 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_PATH_H
#define MEDIASTATION_PATH_H
#include "mediastation/actor.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
class PathActor : public Actor {
public:
PathActor() : Actor(kActorTypePath) {};
virtual void process() override;
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType) override;
virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
private:
double _percentComplete = 0.0;
uint _totalSteps = 0;
uint _currentStep = 0;
uint _nextPathStepTime = 0;
uint _stepDurationInMilliseconds = 0;
bool _isPlaying = false;
Common::Point _startPoint;
Common::Point _endPoint;
uint32 _stepRate = 0;
uint32 _duration = 0;
// Method implementations.
void timePlay();
void setDuration(uint durationInMilliseconds);
double percentComplete();
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,42 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/actors/screen.h"
#include "mediastation/debugchannels.h"
#include "mediastation/mediastation.h"
namespace MediaStation {
void ScreenActor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderCursorResourceId:
_cursorResourceId = chunk.readTypedUint16();
if (_cursorResourceId != 0) {
g_engine->getCursorManager()->registerAsPermanent(_cursorResourceId);
}
break;
default:
Actor::readParameter(chunk, paramType);
}
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,45 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_SCREEN_H
#define MEDIASTATION_SCREEN_H
#include "mediastation/actor.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
// A Screen holds actor data and processes event handlers for a Context.
// The original separated them this way - there is a ContextParameters section,
// then a Screen actor header.
class ScreenActor : public Actor {
public:
ScreenActor() : Actor(kActorTypeScreen) {};
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType) override;
uint _cursorResourceId = 0;
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,149 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/audio.h"
#include "mediastation/debugchannels.h"
#include "mediastation/actors/sound.h"
#include "mediastation/mediastation.h"
namespace MediaStation {
SoundActor::~SoundActor() {
unregisterWithStreamManager();
if (_streamFeed != nullptr) {
g_engine->getStreamFeedManager()->closeStreamFeed(_streamFeed);
_streamFeed = nullptr;
}
}
void SoundActor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderActorId: {
// We already have this actor's ID, so we will just verify it is the same
// as the ID we have already read.
uint32 duplicateActorId = chunk.readTypedUint16();
if (duplicateActorId != _id) {
warning("Duplicate actor ID %d does not match original ID %d", duplicateActorId, _id);
}
break;
}
case kActorHeaderChannelIdent:
_channelIdent = chunk.readTypedChannelIdent();
registerWithStreamManager();
break;
case kActorHeaderHasOwnSubfile:
_hasOwnSubfile = static_cast<bool>(chunk.readTypedByte());
break;
case kActorHeaderSoundInfo:
_sequence.readParameters(chunk);
break;
case kActorHeaderMovieLoadType:
_loadType = chunk.readTypedByte();
break;
default:
Actor::readParameter(chunk, paramType);
}
}
void SoundActor::process() {
if (!_isPlaying) {
return;
}
processTimeEventHandlers();
if (!_sequence.isActive()) {
_isPlaying = false;
_sequence.stop();
runEventHandlerIfExists(kSoundEndEvent);
}
}
void SoundActor::readChunk(Chunk &chunk) {
_isLoadedFromChunk = true;
_sequence.readChunk(chunk);
}
ScriptValue SoundActor::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
ScriptValue returnValue;
switch (methodId) {
case kSpatialShowMethod:
// WORKAROUND: No-op to avoid triggering error on Dalmatians
// timer_6c06_AnsweringMachine, which calls SpatialShow on a sound.
// Since the engine is currently flagging errors on unimplemented
// methods for easier debugging, a no-op is used here to avoid the error.
assert(args.empty());
return returnValue;
case kTimePlayMethod: {
assert(args.empty());
timePlay();
return returnValue;
}
case kTimeStopMethod: {
assert(args.empty());
timeStop();
return returnValue;
}
default:
return Actor::callMethod(methodId, args);
}
}
void SoundActor::timePlay() {
if (_streamFeed == nullptr && !_isLoadedFromChunk) {
_streamFeed = g_engine->getStreamFeedManager()->openStreamFeed(_id);
_streamFeed->readData();
}
if (_isPlaying) {
return;
}
if (_sequence.isEmpty()) {
_isPlaying = false;
return;
}
_isPlaying = true;
_startTime = g_system->getMillis();
_lastProcessedTime = 0;
_sequence.play();
runEventHandlerIfExists(kSoundBeginEvent);
}
void SoundActor::timeStop() {
if (!_isPlaying) {
return;
}
_isPlaying = false;
_sequence.stop();
runEventHandlerIfExists(kSoundStoppedEvent);
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,59 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_ACTORS_SOUND_H
#define MEDIASTATION_ACTORS_SOUND_H
#include "mediastation/actor.h"
#include "mediastation/audio.h"
#include "mediastation/datafile.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
class SoundActor : public Actor, public ChannelClient {
public:
SoundActor() : Actor(kActorTypeSound) {};
~SoundActor();
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType) override;
virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
virtual void process() override;
virtual void readChunk(Chunk &chunk) override;
private:
ImtStreamFeed *_streamFeed = nullptr;
bool _isLoadedFromChunk = false;
uint _loadType = 0;
bool _hasOwnSubfile = false;
bool _isPlaying = false;
AudioSequence _sequence;
// Script method implementations
void timePlay();
void timeStop();
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,387 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/actors/sprite.h"
#include "mediastation/debugchannels.h"
#include "mediastation/mediastation.h"
namespace MediaStation {
SpriteFrameHeader::SpriteFrameHeader(Chunk &chunk) : BitmapHeader(chunk) {
_index = chunk.readTypedUint16();
_offset = chunk.readTypedPoint();
}
SpriteFrame::SpriteFrame(Chunk &chunk, SpriteFrameHeader *header) : Bitmap(chunk, header) {
_bitmapHeader = header;
}
SpriteFrame::~SpriteFrame() {
// The base class destructor takes care of deleting the bitmap header.
}
uint32 SpriteFrame::left() {
return _bitmapHeader->_offset.x;
}
uint32 SpriteFrame::top() {
return _bitmapHeader->_offset.y;
}
Common::Point SpriteFrame::topLeft() {
return Common::Point(left(), top());
}
Common::Rect SpriteFrame::boundingBox() {
return Common::Rect(topLeft(), width(), height());
}
uint32 SpriteFrame::index() {
return _bitmapHeader->_index;
}
SpriteAsset::~SpriteAsset() {
for (SpriteFrame *frame : frames) {
delete frame;
}
}
SpriteMovieActor::~SpriteMovieActor() {
unregisterWithStreamManager();
}
void SpriteMovieActor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderChannelIdent:
_channelIdent = chunk.readTypedChannelIdent();
registerWithStreamManager();
_asset = Common::SharedPtr<SpriteAsset>(new SpriteAsset);
break;
case kActorHeaderFrameRate:
_frameRate = static_cast<uint32>(chunk.readTypedDouble());
break;
case kActorHeaderLoadType:
_loadType = chunk.readTypedByte();
break;
case kActorHeaderStartup:
_isVisible = static_cast<bool>(chunk.readTypedByte());
break;
case kActorHeaderSpriteChunkCount: {
_frameCount = chunk.readTypedUint16();
// Set the default clip.
SpriteClip clip;
clip.id = DEFAULT_CLIP_ID;
clip.firstFrameIndex = 0;
clip.lastFrameIndex = _frameCount - 1;
_clips.setVal(clip.id, clip);
setCurrentClip(clip.id);
break;
}
case kActorHeaderSpriteClip: {
SpriteClip spriteClip;
spriteClip.id = chunk.readTypedUint16();
spriteClip.firstFrameIndex = chunk.readTypedUint16();
spriteClip.lastFrameIndex = chunk.readTypedUint16();
_clips.setVal(spriteClip.id, spriteClip);
break;
}
case kActorHeaderCurrentSpriteClip: {
uint clipId = chunk.readTypedUint16();
setCurrentClip(clipId);
break;
}
case kActorHeaderActorReference: {
_actorReference = chunk.readTypedUint16();
Actor *referencedActor = g_engine->getActorById(_actorReference);
if (referencedActor == nullptr) {
error("%s: Referenced actor %d doesn't exist or has not been read yet in this title", __func__, _actorReference);
}
if (referencedActor->type() != kActorTypeSprite) {
error("%s: Type mismatch of referenced actor %d", __func__, _actorReference);
}
SpriteMovieActor *referencedSprite = static_cast<SpriteMovieActor *>(referencedActor);
_asset = referencedSprite->_asset;
break;
}
default:
SpatialEntity::readParameter(chunk, paramType);
}
}
ScriptValue SpriteMovieActor::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
ScriptValue returnValue;
switch (methodId) {
case kSpatialShowMethod: {
assert(args.empty());
setVisibility(true);
return returnValue;
}
case kSpatialHideMethod: {
assert(args.empty());
setVisibility(false);
return returnValue;
}
case kTimePlayMethod: {
assert(args.empty());
play();
return returnValue;
}
case kTimeStopMethod: {
assert(args.empty());
stop();
return returnValue;
}
case kMovieResetMethod: {
assert(args.empty());
setCurrentFrameToInitial();
return returnValue;
}
case kSetCurrentClipMethod: {
assert(args.size() <= 1);
uint clipId = DEFAULT_CLIP_ID;
if (args.size() == 1) {
clipId = args[0].asParamToken();
}
setCurrentClip(clipId);
return returnValue;
}
case kIncrementFrameMethod: {
assert(args.size() <= 1);
bool loopAround = false;
if (args.size() == 1) {
loopAround = args[0].asBool();
}
bool moreFrames = activateNextFrame();
if (!moreFrames) {
if (loopAround) {
setCurrentFrameToInitial();
}
}
return returnValue;
}
case kDecrementFrameMethod: {
bool shouldSetCurrentFrameToFinal = false;
if (args.size() == 1) {
shouldSetCurrentFrameToFinal = args[0].asBool();
}
bool moreFrames = activatePreviousFrame();
if (!moreFrames) {
if (shouldSetCurrentFrameToFinal) {
setCurrentFrameToFinal();
}
}
return returnValue;
}
case kGetCurrentClipIdMethod: {
returnValue.setToParamToken(_activeClip.id);
return returnValue;
}
case kIsPlayingMethod: {
returnValue.setToBool(_isPlaying);
return returnValue;
}
default:
return SpatialEntity::callMethod(methodId, args);
}
}
bool SpriteMovieActor::activateNextFrame() {
if (_currentFrameIndex < _activeClip.lastFrameIndex) {
_currentFrameIndex++;
dirtyIfVisible();
return true;
}
return false;
}
bool SpriteMovieActor::activatePreviousFrame() {
if (_currentFrameIndex > _activeClip.firstFrameIndex) {
_currentFrameIndex--;
dirtyIfVisible();
return true;
}
return false;
}
void SpriteMovieActor::dirtyIfVisible() {
if (_isVisible) {
invalidateLocalBounds();
}
}
void SpriteMovieActor::setVisibility(bool visibility) {
if (_isVisible != visibility) {
_isVisible = visibility;
invalidateLocalBounds();
}
}
void SpriteMovieActor::play() {
_isPlaying = true;
_startTime = g_system->getMillis();
_lastProcessedTime = 0;
_nextFrameTime = 0;
scheduleNextFrame();
}
void SpriteMovieActor::stop() {
_nextFrameTime = 0;
_isPlaying = false;
}
void SpriteMovieActor::setCurrentClip(uint clipId) {
if (_activeClip.id != clipId) {
if (_clips.contains(clipId)) {
_activeClip = _clips.getVal(clipId);
} else {
_activeClip.id = clipId;
warning("%s: Sprite clip %d not found in sprite %d", __func__, clipId, _id);
}
}
setCurrentFrameToInitial();
}
void SpriteMovieActor::setCurrentFrameToInitial() {
if (_currentFrameIndex != _activeClip.firstFrameIndex) {
_currentFrameIndex = _activeClip.firstFrameIndex;
dirtyIfVisible();
}
}
void SpriteMovieActor::setCurrentFrameToFinal() {
if (_currentFrameIndex != _activeClip.lastFrameIndex) {
_currentFrameIndex = _activeClip.lastFrameIndex;
dirtyIfVisible();
}
}
void SpriteMovieActor::process() {
updateFrameState();
// Sprites don't have time event handlers, separate timers do time handling.
}
void SpriteMovieActor::readChunk(Chunk &chunk) {
// Reads one frame from the sprite.
debugC(5, kDebugLoading, "Sprite::readFrame(): Reading sprite frame (@0x%llx)", static_cast<long long int>(chunk.pos()));
SpriteFrameHeader *header = new SpriteFrameHeader(chunk);
SpriteFrame *frame = new SpriteFrame(chunk, header);
_asset->frames.push_back(frame);
// TODO: Are these in exactly reverse order? If we can just reverse the
// whole thing once.
Common::sort(_asset->frames.begin(), _asset->frames.end(), [](SpriteFrame *a, SpriteFrame *b) {
return a->index() < b->index();
});
}
void SpriteMovieActor::scheduleNextFrame() {
if (!_isPlaying) {
return;
}
if (_currentFrameIndex < _activeClip.lastFrameIndex) {
scheduleNextTimerEvent();
} else {
stop();
}
}
void SpriteMovieActor::scheduleNextTimerEvent() {
uint frameDuration = 1000 / _frameRate;
_nextFrameTime += frameDuration;
}
void SpriteMovieActor::updateFrameState() {
if (!_isPlaying) {
return;
}
uint currentTime = g_system->getMillis() - _startTime;
bool drawNextFrame = currentTime >= _nextFrameTime;
debugC(kDebugGraphics, "nextFrameTime: %d; startTime: %d, currentTime: %d", _nextFrameTime, _startTime, currentTime);
if (drawNextFrame) {
timerEvent();
}
}
void SpriteMovieActor::timerEvent() {
if (!_isPlaying) {
error("%s: Attempt to activate sprite frame when sprite is not playing", __func__);
return;
}
bool result = activateNextFrame();
if (!result) {
stop();
} else {
postMovieEndEventIfNecessary();
scheduleNextFrame();
}
}
void SpriteMovieActor::postMovieEndEventIfNecessary() {
if (_currentFrameIndex != _activeClip.lastFrameIndex) {
return;
}
_isPlaying = false;
_startTime = 0;
_nextFrameTime = 0;
ScriptValue value;
value.setToParamToken(_activeClip.id);
runEventHandlerIfExists(kSpriteMovieEndEvent, value);
}
void SpriteMovieActor::draw(DisplayContext &displayContext) {
SpriteFrame *activeFrame = _asset->frames[_currentFrameIndex];
if (_isVisible) {
Common::Rect frameBbox = activeFrame->boundingBox();
frameBbox.translate(_boundingBox.left, _boundingBox.top);
g_engine->getDisplayManager()->imageBlit(frameBbox.origin(), activeFrame, _dissolveFactor, &displayContext);
}
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,126 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_SPRITE_H
#define MEDIASTATION_SPRITE_H
#include "common/rect.h"
#include "common/array.h"
#include "common/ptr.h"
#include "mediastation/actor.h"
#include "mediastation/datafile.h"
#include "mediastation/bitmap.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
struct SpriteClip {
uint id = 0;
uint firstFrameIndex = 0;
uint lastFrameIndex = 0;
};
class SpriteFrameHeader : public BitmapHeader {
public:
SpriteFrameHeader(Chunk &chunk);
uint _index;
Common::Point _offset;
};
class SpriteFrame : public Bitmap {
public:
SpriteFrame(Chunk &chunk, SpriteFrameHeader *header);
virtual ~SpriteFrame() override;
uint32 left();
uint32 top();
Common::Point topLeft();
Common::Rect boundingBox();
uint32 index();
private:
SpriteFrameHeader *_bitmapHeader = nullptr;
};
// The original had a separate class that did reference counting,
// for sharing an asset across actors, but we can just use a SharedPtr.
struct SpriteAsset {
~SpriteAsset();
Common::Array<SpriteFrame *> frames;
};
// Sprites are somewhat like movies, but they strictly show one frame at a time
// and don't have sound. They are intended for background/recurrent animations.
class SpriteMovieActor : public SpatialEntity, public ChannelClient {
public:
SpriteMovieActor() : SpatialEntity(kActorTypeSprite) {};
~SpriteMovieActor();
virtual void process() override;
virtual void draw(DisplayContext &displayContext) override;
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType) override;
virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
virtual bool isVisible() const override { return _isVisible; }
virtual void readChunk(Chunk &chunk) override;
private:
static const uint DEFAULT_CLIP_ID = 1200;
uint _loadType = 0;
uint _frameRate = 0;
uint _frameCount = 0;
uint _actorReference = 0;
Common::HashMap<uint, SpriteClip> _clips;
Common::SharedPtr<SpriteAsset> _asset;
bool _isPlaying = false;
uint _currentFrameIndex = 0;
uint _nextFrameTime = 0;
SpriteClip _activeClip;
void play();
void stop();
void setCurrentClip(uint clipId);
bool activateNextFrame();
bool activatePreviousFrame();
void dirtyIfVisible();
void setCurrentFrameToInitial();
void setCurrentFrameToFinal();
void scheduleNextFrame();
void scheduleNextTimerEvent();
void postMovieEndEventIfNecessary();
void setVisibility(bool visibility);
void updateFrameState();
void timerEvent();
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,817 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/mediastation.h"
#include "mediastation/actors/camera.h"
#include "mediastation/actors/stage.h"
#include "mediastation/debugchannels.h"
namespace MediaStation {
StageActor::StageActor() : SpatialEntity(kActorTypeStage),
_children(StageActor::compareSpatialActorByZIndex),
_cameras(StageActor::compareSpatialActorByZIndex) {
}
StageActor::~StageActor() {
removeAllChildren();
if (_parentStage != nullptr) {
_parentStage->removeChildSpatialEntity(this);
}
_currentCamera = nullptr;
}
void StageActor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderChildActorId: {
// In stages, this basically has the oppose meaning it has outside of stages. Here,
// it specifies an actor that is a parent of this stage.
uint parentActorId = chunk.readTypedUint16();
_pendingParent = g_engine->getSpatialEntityById(parentActorId);
break;
}
case kActorHeaderStageExtent:
_extent = chunk.readTypedGraphicSize();
break;
case kActorHeaderCylindricalX:
_cylindricalX = static_cast<bool>(chunk.readTypedByte());
break;
case kActorHeaderCylindricalY:
_cylindricalY = static_cast<bool>(chunk.readTypedByte());
break;
default:
SpatialEntity::readParameter(chunk, paramType);
}
}
void StageActor::preload(const Common::Rect &rect) {
if (cylindricalX()) {
preloadTest(rect, kWrapLeft);
}
if (cylindricalY()) {
preloadTest(rect, kWrapTop);
}
if (cylindricalX() && cylindricalY()) {
preloadTest(rect, kWrapLeftTop);
}
preloadTest(rect, kWrapNone);
}
void StageActor::preloadTest(const Common::Rect &rect, CylindricalWrapMode wrapMode) {
for (SpatialEntity *entity : _children) {
entity->setAdjustedBounds(wrapMode);
if (!entity->isRectInMemory(rect) && !entity->isLoading()) {
entity->preload(rect);
}
}
}
bool StageActor::isRectInMemory(const Common::Rect &rect) {
bool result = true;
if (cylindricalX()) {
result = isRectInMemoryTest(rect, kWrapLeft);
}
if (result && cylindricalY()) {
result = isRectInMemoryTest(rect, kWrapTop);
}
if (result && cylindricalY() && cylindricalX()) {
result = isRectInMemoryTest(rect, kWrapLeftTop);
}
if (result) {
result = isRectInMemoryTest(rect, kWrapNone);
}
return result;
}
bool StageActor::isRectInMemoryTest(const Common::Rect &rect, CylindricalWrapMode wrapMode) {
for (SpatialEntity *entity : _children) {
entity->setAdjustedBounds(wrapMode);
if (!entity->isRectInMemory(rect)) {
return false;
}
}
return true;
}
void StageActor::draw(DisplayContext &displayContext) {
Clip *currentClip = displayContext.currentClip();
if (currentClip != nullptr) {
Clip *previousClip = displayContext.previousClip();
if (previousClip == nullptr) {
currentClip->addToRegion(currentClip->_bounds);
} else {
currentClip = previousClip;
}
}
displayContext.intersectClipWith(getBbox());
if (displayContext.clipIsEmpty()) {
return;
}
bool boundsNeedsAdjustment = false;
Common::Rect bounds = getBbox();
if (bounds.left != 0 || bounds.top != 0) {
boundsNeedsAdjustment = true;
}
if (boundsNeedsAdjustment) {
displayContext.pushOrigin();
displayContext._origin.x = bounds.left + displayContext._origin.x;
displayContext._origin.y = bounds.top + displayContext._origin.y;
}
if (_cameras.empty()) {
drawUsingStage(displayContext);
} else {
for (CameraActor *camera : _cameras) {
setCurrentCamera(camera);
camera->drawUsingCamera(displayContext, _children);
}
}
if (boundsNeedsAdjustment) {
displayContext.popOrigin();
}
displayContext.emptyCurrentClip();
}
void StageActor::drawUsingStage(DisplayContext &displayContext) {
for (SpatialEntity *entity : _children) {
entity->setAdjustedBounds(kWrapNone);
if (entity->isVisible()) {
if (displayContext.rectIsInClip(entity->getBbox())) {
debugC(5, kDebugGraphics, "%s: Redrawing actor %d", __func__, entity->id());
entity->draw(displayContext);
}
}
}
}
void StageActor::invalidateRect(const Common::Rect &rect) {
if (_parentStage != nullptr) {
Common::Point origin = _boundingBox.origin();
Common::Rect rectRelativeToParent = rect;
rectRelativeToParent.translate(origin.x, origin.y);
if (_cameras.size() == 0) {
_parentStage->invalidateRect(rectRelativeToParent);
} else {
invalidateUsingCameras(rectRelativeToParent);
}
} else {
error("%s: Attempt to invalidate rect without a parent stage", __func__);
}
}
void StageActor::invalidateUsingCameras(const Common::Rect &rect) {
for (CameraActor *camera : _cameras) {
Common::Rect adjustedRectToInvalidate = rect;
Common::Rect cameraBounds = camera->getBbox();
Common::Rect cameraBoundsInStageCoordinates = cameraBounds;
Common::Rect stageOrigin = getBbox();
cameraBoundsInStageCoordinates.translate(stageOrigin.left, stageOrigin.top);
Common::Point cameraViewportOrigin = camera->getViewportOrigin();
Common::Point viewportOffsetFromCameraBounds(
cameraViewportOrigin.x - cameraBounds.left,
cameraViewportOrigin.y - cameraBounds.top
);
adjustedRectToInvalidate.translate(
-viewportOffsetFromCameraBounds.x,
-viewportOffsetFromCameraBounds.y
);
invalidateObject(adjustedRectToInvalidate, cameraBoundsInStageCoordinates);
}
}
void StageActor::invalidateObject(const Common::Rect &rect, const Common::Rect &visibleRegion) {
Common::Point xyAdjustment(0, 0);
invalidateTest(rect, visibleRegion, xyAdjustment);
if (_cylindricalX) {
xyAdjustment.x = _extent.x;
xyAdjustment.y = 0;
invalidateTest(rect, visibleRegion, xyAdjustment);
xyAdjustment.x = -_extent.x;
xyAdjustment.y = 0;
invalidateTest(rect, visibleRegion, xyAdjustment);
}
if (_cylindricalY) {
xyAdjustment.x = 0;
xyAdjustment.y = _extent.y;
invalidateTest(rect, visibleRegion, xyAdjustment);
xyAdjustment.x = 0;
xyAdjustment.y = -_extent.y;
invalidateTest(rect, visibleRegion, xyAdjustment);
}
if (_cylindricalX && _cylindricalY) {
xyAdjustment.x = _extent.x;
xyAdjustment.y = _extent.y;
invalidateTest(rect, visibleRegion, xyAdjustment);
xyAdjustment.x = -_extent.x;
xyAdjustment.y = -_extent.y;
invalidateTest(rect, visibleRegion, xyAdjustment);
xyAdjustment.x = -_extent.x;
xyAdjustment.y = _extent.y;
invalidateTest(rect, visibleRegion, xyAdjustment);
xyAdjustment.x = _extent.x;
xyAdjustment.y = -_extent.y;
invalidateTest(rect, visibleRegion, xyAdjustment);
}
}
void StageActor::invalidateTest(const Common::Rect &rect, const Common::Rect &visibleRegion, const Common::Point &originAdjustment) {
Common::Rect rectToInvalidateRelative = rect;
rectToInvalidateRelative.translate(-originAdjustment.x, -originAdjustment.y);
rectToInvalidateRelative.clip(visibleRegion);
_parentStage->invalidateRect(rectToInvalidateRelative);
}
void StageActor::loadIsComplete() {
// This is deliberately calling down to Actor, rather than calling
// to SpatialEntity first.
Actor::loadIsComplete();
if (_pendingParent != nullptr) {
if (_pendingParent->type() != kActorTypeStage) {
error("%s: Parent must be a stage", __func__);
}
StageActor *parentStage = static_cast<StageActor *>(_pendingParent);
parentStage->addChildSpatialEntity(this);
_pendingParent = nullptr;
}
if (_extent.x == 0 || _extent.y == 0) {
_extent.x = _boundingBox.width();
_extent.y = _boundingBox.height();
}
}
void StageActor::addActorToStage(uint actorId) {
// If actor has a current parent, remove it from that parent first.
SpatialEntity *spatialEntity = g_engine->getSpatialEntityById(actorId);
StageActor *currentParent = spatialEntity->getParentStage();
if (currentParent != nullptr) {
currentParent->removeChildSpatialEntity(spatialEntity);
}
addChildSpatialEntity(spatialEntity);
}
void StageActor::removeActorFromStage(uint actorId) {
SpatialEntity *spatialEntity = g_engine->getSpatialEntityById(actorId);
StageActor *currentParent = spatialEntity->getParentStage();
if (currentParent == this) {
// Remove the actor from this stage, and add it back to the root stage.
removeChildSpatialEntity(spatialEntity);
RootStage *rootStage = g_engine->getRootStage();
rootStage->addChildSpatialEntity(spatialEntity);
}
}
void StageActor::addCamera(CameraActor *camera) {
_cameras.insert(camera);
}
void StageActor::removeCamera(CameraActor *camera) {
for (auto it = _cameras.begin(); it != _cameras.end(); ++it) {
if (*it == camera) {
_cameras.erase(it);
break;
}
}
}
void StageActor::setCurrentCamera(CameraActor *camera) {
_currentCamera = camera;
}
ScriptValue StageActor::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
ScriptValue returnValue;
switch (methodId) {
case kAddActorToStageMethod: {
assert(args.size() == 1);
uint actorId = args[0].asActorId();
addActorToStage(actorId);
return returnValue;
}
case kRemoveActorFromStageMethod: {
assert(args.size() == 1);
uint actorId = args[0].asActorId();
removeActorFromStage(actorId);
return returnValue;
}
case kSetBoundsMethod: {
assert(args.size() == 4);
int16 x = static_cast<int16>(args[0].asFloat());
int16 y = static_cast<int16>(args[1].asFloat());
int16 width = static_cast<int16>(args[2].asFloat());
int16 height = static_cast<int16>(args[3].asFloat());
Common::Rect newBounds(Common::Point(x, y), width, height);
setBounds(newBounds);
return returnValue;
}
case kStageSetSizeMethod:
assert(args.size() == 2);
_boundingBox.setWidth(static_cast<int16>(args[0].asFloat()));
_boundingBox.setHeight(static_cast<int16>(args[1].asFloat()));
return returnValue;
case kStageGetWidthMethod:
returnValue.setToFloat(_boundingBox.width());
return returnValue;
case kStageGetHeightMethod:
returnValue.setToFloat(_boundingBox.height());
return returnValue;
default:
return SpatialEntity::callMethod(methodId, args);
}
}
void StageActor::addChildSpatialEntity(SpatialEntity *entity) {
if (!assertHasNoParent(entity)) {
error("%s: Attempt to add entity that already has a parent", __func__);
}
entity->setParentStage(this);
_children.insert(entity);
if (isVisible()) {
invalidateLocalBounds();
}
}
void StageActor::removeChildSpatialEntity(SpatialEntity *entity) {
if (!assertIsMyChild(entity)) {
error("%s: Attempt to remove entity that is not a child", __func__);
}
if (isVisible()) {
invalidateLocalBounds();
}
entity->setToNoParentStage();
for (auto it = _children.begin(); it != _children.end(); ++it) {
if (*it == entity) {
_children.erase(it);
break;
}
}
}
uint16 StageActor::queryChildrenAboutMouseEvents(
const Common::Point &point,
uint16 eventMask,
MouseActorState &state,
CylindricalWrapMode wrapMode) {
uint16 result = 0;
Common::Point adjustedPoint = point - _boundingBox.origin();
for (auto childIterator = _children.end(); childIterator != _children.begin();) {
--childIterator; // Decrement first, then dereference
SpatialEntity *child = *childIterator;
debugC(7, kDebugEvents, " %s: Checking actor %d (z-index: %d) (eventMask: 0x%02x) (result: 0x%02x) (wrapMode: %d)", __func__, child->id(), child->zIndex(), eventMask, result, wrapMode);
child->setAdjustedBounds(wrapMode);
uint16 handledEvents = child->findActorToAcceptMouseEvents(adjustedPoint, eventMask, state, true);
child->setAdjustedBounds(kWrapNone);
eventMask &= ~handledEvents;
result |= handledEvents;
}
return result;
}
uint16 StageActor::findActorToAcceptMouseEventsObject(
const Common::Point &point,
uint16 eventMask,
MouseActorState &state,
bool inBounds) {
uint16 result = 0;
uint16 handledEvents = queryChildrenAboutMouseEvents(point, eventMask, state, kWrapNone);
if (handledEvents != 0) {
eventMask &= ~handledEvents;
result |= handledEvents;
}
if ((eventMask != 0) && cylindricalX()) {
handledEvents = queryChildrenAboutMouseEvents(point, eventMask, state, kWrapLeft);
if (handledEvents != 0) {
eventMask &= ~handledEvents;
result |= handledEvents;
}
}
if ((eventMask != 0) && cylindricalX()) {
handledEvents = queryChildrenAboutMouseEvents(point, eventMask, state, kWrapRight);
if (handledEvents != 0) {
eventMask &= ~handledEvents;
result |= handledEvents;
}
}
if ((eventMask != 0) && cylindricalY()) {
handledEvents = queryChildrenAboutMouseEvents(point, eventMask, state, kWrapTop);
if (handledEvents != 0) {
eventMask &= ~handledEvents;
result |= handledEvents;
}
}
if ((eventMask != 0) && cylindricalY()) {
handledEvents = queryChildrenAboutMouseEvents(point, eventMask, state, kWrapBottom);
if (handledEvents != 0) {
eventMask &= ~handledEvents;
result |= handledEvents;
}
}
if ((eventMask != 0) && cylindricalY() && cylindricalX()) {
handledEvents = queryChildrenAboutMouseEvents(point, eventMask, state, kWrapLeftTop);
if (handledEvents != 0) {
eventMask &= ~handledEvents;
result |= handledEvents;
}
}
if ((eventMask != 0) && cylindricalY() && cylindricalX()) {
handledEvents = queryChildrenAboutMouseEvents(point, eventMask, state, kWrapRightBottom);
if (handledEvents != 0) {
result |= handledEvents;
}
}
return result;
}
uint16 StageActor::findActorToAcceptMouseEventsCamera(
const Common::Point &point,
uint16 eventMask,
MouseActorState &state,
bool inBounds) {
uint16 result = 0;
for (CameraActor *camera : _cameras) {
Common::Point mousePosRelativeToCamera = point;
setCurrentCamera(camera);
Common::Point cameraViewportOrigin = camera->getViewportOrigin();
mousePosRelativeToCamera.x += cameraViewportOrigin.x;
mousePosRelativeToCamera.y += cameraViewportOrigin.y;
if (!inBounds) {
Common::Rect viewportBounds = camera->getViewportBounds();
if (!viewportBounds.contains(mousePosRelativeToCamera)) {
inBounds = true;
}
}
result = findActorToAcceptMouseEventsObject(mousePosRelativeToCamera, eventMask, state, inBounds);
}
return result;
}
uint16 StageActor::findActorToAcceptMouseEvents(
const Common::Point &point,
uint16 eventMask,
MouseActorState &state,
bool inBounds) {
debugC(6, kDebugEvents, " --- %s ---", __func__);
Common::Point mousePosAdjustedByStageOrigin = point;
mousePosAdjustedByStageOrigin.x -= _boundingBox.left;
mousePosAdjustedByStageOrigin.y -= _boundingBox.top;
uint16 result;
if (_cameras.empty()) {
if (!inBounds) {
if (!_boundingBox.contains(point)) {
inBounds = true;
}
}
result = queryChildrenAboutMouseEvents(mousePosAdjustedByStageOrigin, eventMask, state, kWrapNone);
} else {
result = findActorToAcceptMouseEventsCamera(mousePosAdjustedByStageOrigin, eventMask, state, inBounds);
}
debugC(6, kDebugEvents, " --- END %s ---", __func__);
return result;
}
uint16 StageActor::findActorToAcceptKeyboardEvents(
uint16 asciiCode,
uint16 eventMask,
MouseActorState &state) {
uint16 result = 0;
for (SpatialEntity *child : _children) {
uint16 handledEvents = child->findActorToAcceptKeyboardEvents(asciiCode, eventMask, state);
if (handledEvents != 0) {
eventMask &= ~handledEvents;
result |= handledEvents;
}
}
return result;
}
bool StageActor::assertHasNoParent(const SpatialEntity *entity) {
// Make sure entity is not in our children.
for (SpatialEntity *child : _children) {
if (child == entity) {
return false;
}
}
// Make sure entity doesn't have a parent.
if (entity->getParentStage() != nullptr) {
return false;
}
return true;
}
void StageActor::removeAllChildren() {
for (SpatialEntity *child : _children) {
child->setToNoParentStage();
}
_children.clear();
}
void StageActor::setMousePosition(int16 x, int16 y) {
if (_parentStage != nullptr) {
x += _boundingBox.left;
y += _boundingBox.top;
_parentStage->setMousePosition(x, y);
}
}
void StageActor::invalidateLocalBounds() {
for (SpatialEntity *child : _children) {
if (child->isVisible()) {
child->invalidateLocalBounds();
}
}
}
void StageActor::invalidateLocalZIndex() {
if (_parentStage != nullptr) {
_parentStage->invalidateZIndexOf(this);
}
}
void StageActor::invalidateZIndexOf(const SpatialEntity *entity) {
if (!assertIsMyChild(entity)) {
error("%s: Attempt to invalidate local z-index of non-child", __func__);
}
// Remove the entity from the sorted array and re-insert it at the correct position.
for (auto it = _children.begin(); it != _children.end(); ++it) {
if (*it == entity) {
_children.erase(it);
_children.insert(const_cast<SpatialEntity *>(entity));
break;
}
}
// Mark the whole stage dirty since z-order changed.
if (isVisible()) {
invalidateLocalBounds();
}
}
int StageActor::compareSpatialActorByZIndex(const SpatialEntity *a, const SpatialEntity *b) {
int diff = b->zIndex() - a->zIndex();
if (diff < 0)
return -1; // a should come before b
else if (diff > 0)
return 1; // b should come before a
else {
// If z-indices are equal, compare pointers for stable sort
return (a < b) ? -1 : 1;
}
}
void StageActor::currentMousePosition(Common::Point &point) {
if (getParentStage() != nullptr) {
getParentStage()->currentMousePosition(point);
point -= getBbox().origin();
}
}
void RootStage::invalidateRect(const Common::Rect &rect) {
Common::Rect rectToAdd = rect;
rectToAdd.clip(_boundingBox);
_dirtyRegion.addRect(rectToAdd);
}
void RootStage::currentMousePosition(Common::Point &point) {
point = g_engine->getEventManager()->getMousePos();
point -= getBbox().origin();
}
void RootStage::drawAll(DisplayContext &displayContext) {
StageActor::draw(displayContext);
}
void RootStage::drawDirtyRegion(DisplayContext &displayContext) {
displayContext.setClipTo(_dirtyRegion);
StageActor::draw(displayContext);
displayContext.emptyCurrentClip();
}
uint16 RootStage::findActorToAcceptMouseEvents(
const Common::Point &point,
uint16 eventMask,
MouseActorState &state,
bool inBounds) {
// Handle any mouse moved events.
uint16 result = StageActor::findActorToAcceptMouseEvents(point, eventMask, state, inBounds);
eventMask &= ~result;
if (eventMask & kMouseEnterFlag) {
state.mouseEnter = this;
result |= kMouseEnterFlag;
}
if (eventMask & kMouseOutOfFocusFlag) {
state.mouseOutOfFocus = this;
result |= kMouseOutOfFocusFlag;
}
return result;
}
void RootStage::mouseEnteredEvent(const Common::Event &event) {
_isMouseInside = true;
g_engine->getCursorManager()->unsetTemporary();
}
void RootStage::mouseExitedEvent(const Common::Event &event) {
_isMouseInside = false;
}
void RootStage::mouseOutOfFocusEvent(const Common::Event &event) {
_isMouseInside = true;
g_engine->getCursorManager()->unsetTemporary();
}
void RootStage::deleteChildrenFromContextId(uint contextId) {
for (auto it = _children.begin(); it != _children.end();) {
uint actorContextId = (*it)->contextId();
if (actorContextId == contextId) {
it = _children.erase(it);
} else {
++it;
}
}
}
void RootStage::setMousePosition(int16 x, int16 y) {
x += _boundingBox.left;
y += _boundingBox.top;
warning("%s: STUB: (%d, %d)", __func__, x, y);
}
StageDirector::StageDirector() {
_rootStage = new RootStage;
Common::Rect rootStageBounds(MediaStationEngine::SCREEN_WIDTH, MediaStationEngine::SCREEN_HEIGHT);
_rootStage->setBounds(rootStageBounds);
g_engine->registerActor(_rootStage);
}
StageDirector::~StageDirector() {
g_engine->destroyActor(RootStage::ROOT_STAGE_ACTOR_ID);
_rootStage = nullptr;
}
void StageDirector::drawAll() {
_rootStage->drawAll(g_engine->getDisplayManager()->_displayContext);
}
void StageDirector::drawDirtyRegion() {
_rootStage->drawDirtyRegion(g_engine->getDisplayManager()->_displayContext);
}
void StageDirector::clearDirtyRegion() {
_rootStage->_dirtyRegion._rects.clear();
_rootStage->_dirtyRegion._bounds = Common::Rect(0, 0, 0, 0);
}
void StageDirector::handleKeyboardEvent(const Common::Event &event) {
MouseActorState state;
uint16 flags = _rootStage->findActorToAcceptKeyboardEvents(event.kbd.ascii, kKeyDownFlag, state);
if (flags & kKeyDownFlag) {
debugC(5, kDebugEvents, "%s: Dispatching to actor %d", __func__, state.keyDown->id());
state.keyDown->keyboardEvent(event);
}
}
void StageDirector::handleMouseDownEvent(const Common::Event &event) {
MouseActorState state;
uint16 flags = _rootStage->findActorToAcceptMouseEvents(event.mouse, kMouseDownFlag, state, false);
if (flags & kMouseDownFlag) {
debugC(5, kDebugEvents, "%s: Dispatching to actor %d", __func__, state.mouseDown->id());
state.mouseDown->mouseDownEvent(event);
}
}
void StageDirector::handleMouseUpEvent(const Common::Event &event) {
MouseActorState state;
uint16 flags = _rootStage->findActorToAcceptMouseEvents(event.mouse, kMouseUpFlag, state, false);
if (flags & kMouseUpFlag) {
debugC(5, kDebugEvents, "%s: Dispatching to actor %d", __func__, state.mouseUp->id());
state.mouseUp->mouseUpEvent(event);
}
}
void StageDirector::handleMouseMovedEvent(const Common::Event &event) {
MouseActorState state;
uint16 flags = _rootStage->findActorToAcceptMouseEvents(
event.mouse,
kMouseEnterFlag | kMouseExitFlag | kMouseMovedFlag,
state, false);
debugC(5, kDebugEvents, "%s: Calling sendMouseEnterExitEvent", __func__);
sendMouseEnterExitEvent(flags, state, event);
if (flags & kMouseMovedFlag) {
debugC(5, kDebugEvents, "%s: Dispatching mouse moved to actor %d", __func__, state.mouseMoved->id());
state.mouseMoved->mouseMovedEvent(event);
}
}
void StageDirector::handleMouseEnterExitEvent(const Common::Event &event) {
MouseActorState state;
uint16 flags = _rootStage->findActorToAcceptMouseEvents(event.mouse, kMouseEnterFlag | kMouseExitFlag, state, false);
sendMouseEnterExitEvent(flags, state, event);
}
void StageDirector::handleMouseOutOfFocusEvent(const Common::Event &event) {
MouseActorState state;
uint16 flags = _rootStage->findActorToAcceptMouseEvents(event.mouse, kMouseExitFlag | kMouseOutOfFocusFlag, state, false);
if (flags & kMouseExitFlag) {
debugC(5, kDebugEvents, "%s: Dispatching mouse enter to actor %d", __func__, state.mouseExit->id());
state.mouseExit->mouseExitedEvent(event);
}
if (flags & kMouseOutOfFocusFlag) {
debugC(5, kDebugEvents, "%s: Dispatching mouse out of focus to actor %d", __func__, state.mouseOutOfFocus->id());
state.mouseOutOfFocus->mouseOutOfFocusEvent(event);
}
}
void StageDirector::sendMouseEnterExitEvent(uint16 flags, MouseActorState &state, const Common::Event &event) {
if (state.mouseMoved != state.mouseEnter || state.mouseMoved != state.mouseExit) {
if (flags & kMouseEnterFlag) {
debugC(5, kDebugEvents, "%s: Dispatching mouse enter to actor %d", __func__, state.mouseEnter->id());
state.mouseEnter->mouseEnteredEvent(event);
}
if (flags & kMouseExitFlag) {
debugC(5, kDebugEvents, "%s: Dispatching mouse exit to actor %d", __func__, state.mouseExit->id());
state.mouseExit->mouseExitedEvent(event);
}
} else {
debugC(5, kDebugEvents, "%s: No hotspot to dispatch to", __func__);
}
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,191 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_STAGE_H
#define MEDIASTATION_STAGE_H
#include "common/events.h"
#include "mediastation/actor.h"
#include "mediastation/graphics.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
// Cylindrical wrapping allows content on a stage to wrap around like a cylinder - for example, when you scroll past the
// right edge, content from the left edge appears, creating the illusion of an infinite looping world.
enum CylindricalWrapMode : int {
kWrapNone = 0, // No offset (default)
kWrapRight = 1, // Right wrap (X + extent.x)
kWrapBottom = 2, // Bottom wrap (Y + extent.y)
kWrapLeftTop = 3, // Left + Top wrap (X - extent.x, Y - extent.y)
kWrapLeft = 4, // Left wrap (X - extent.x)
kWrapRightBottom = 5, // Right + Bottom wrap (X + extent.x, Y + extent.y)
kWrapTop = 6, // Top wrap (Y - extent.y)
kWrapLeftBottom = 7, // Left + Bottom wrap (X - extent.x, Y + extent.y)
kWrapRightTop = 8 // Right + Top wrap (X + extent.x, Y - extent.y)
};
class CameraActor;
class StageActor : public SpatialEntity {
public:
StageActor();
virtual ~StageActor();
virtual void draw(DisplayContext &displayContext) override;
void drawUsingStage(DisplayContext &displayContext);
virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType) override;
virtual void preload(const Common::Rect &rect) override;
virtual bool isVisible() const override { return _children.size() > 0; }
virtual bool isRectInMemory(const Common::Rect &rect) override;
void addChildSpatialEntity(SpatialEntity *entity);
void removeChildSpatialEntity(SpatialEntity *entity);
void addCamera(CameraActor *camera);
void removeCamera(CameraActor *camera);
void setCurrentCamera(CameraActor *camera);
CameraActor *getCurrentCamera() const { return _currentCamera; }
uint16 queryChildrenAboutMouseEvents(
const Common::Point &point,
uint16 eventMask,
MouseActorState &state,
CylindricalWrapMode wrapMode = kWrapNone);
uint16 findActorToAcceptMouseEventsObject(
const Common::Point &point,
uint16 eventMask,
MouseActorState &state,
bool inBounds);
uint16 findActorToAcceptMouseEventsCamera(
const Common::Point &point,
uint16 eventMask,
MouseActorState &state,
bool inBounds);
virtual uint16 findActorToAcceptMouseEvents(
const Common::Point &point,
uint16 eventMask,
MouseActorState &state,
bool inBounds) override;
virtual uint16 findActorToAcceptKeyboardEvents(
uint16 asciiCode,
uint16 eventMask,
MouseActorState &state) override;
virtual void currentMousePosition(Common::Point &point);
virtual void setMousePosition(int16 x, int16 y) override;
void invalidateZIndexOf(const SpatialEntity *entity);
virtual void invalidateLocalBounds() override;
virtual void invalidateRect(const Common::Rect &rect);
bool cylindricalX() const { return _cylindricalX; }
bool cylindricalY() const { return _cylindricalY; }
Common::Point extent() const { return _extent; }
protected:
// Whether or not cameras on this stage should wrap around this stage.
bool _cylindricalX = false;
bool _cylindricalY = false;
Common::Point _extent;
SpatialEntity *_pendingParent = nullptr;
CameraActor *_currentCamera = nullptr;
void addActorToStage(uint actorId);
void removeActorFromStage(uint actorId);
bool isRectInMemoryTest(const Common::Rect &rect, CylindricalWrapMode wrapMode);
void preloadTest(const Common::Rect &rect, CylindricalWrapMode wrapMode);
bool assertHasNoParent(const SpatialEntity *entity);
bool assertHasParentThatIsNotMe(const SpatialEntity *entity) { return !assertIsMyChild(entity); }
bool assertIsMyChild(const SpatialEntity *entity) { return this == entity->getParentStage();}
void removeAllChildren();
virtual void invalidateLocalZIndex() override;
void invalidateUsingCameras(const Common::Rect &rect);
void invalidateObject(const Common::Rect &rect, const Common::Rect &visibleRegion);
// The function is called this in the original; not sure why though.
void invalidateTest(const Common::Rect &rect, const Common::Rect &clip, const Common::Point &originAdjustment);
virtual void loadIsComplete() override;
static int compareSpatialActorByZIndex(const SpatialEntity *a, const SpatialEntity *b);
Common::SortedArray<SpatialEntity *, const SpatialEntity *> _children;
Common::SortedArray<CameraActor *, const SpatialEntity *> _cameras;
};
class RootStage : public StageActor {
public:
friend class StageDirector;
RootStage() : StageActor() { _id = ROOT_STAGE_ACTOR_ID; };
virtual uint16 findActorToAcceptMouseEvents(
const Common::Point &point,
uint16 eventMask,
MouseActorState &state,
bool inBounds) override;
virtual void currentMousePosition(Common::Point &point) override;
virtual void setMousePosition(int16 x, int16 y) override;
virtual void invalidateRect(const Common::Rect &rect) override;
virtual void deleteChildrenFromContextId(uint contextId);
virtual void mouseEnteredEvent(const Common::Event &event) override;
virtual void mouseExitedEvent(const Common::Event &event) override;
virtual void mouseOutOfFocusEvent(const Common::Event &event) override;
void drawAll(DisplayContext &displayContext);
void drawDirtyRegion(DisplayContext &displayContext);
Region _dirtyRegion;
private:
static const uint ROOT_STAGE_ACTOR_ID = 2;
bool _isMouseInside = false;
};
class StageDirector {
public:
StageDirector();
~StageDirector();
RootStage *getRootStage() const { return _rootStage; }
void drawAll();
void drawDirtyRegion();
void clearDirtyRegion();
void handleKeyboardEvent(const Common::Event &event);
void handleMouseDownEvent(const Common::Event &event);
void handleMouseUpEvent(const Common::Event &event);
void handleMouseMovedEvent(const Common::Event &event);
void handleMouseEnterExitEvent(const Common::Event &event);
void handleMouseOutOfFocusEvent(const Common::Event &event);
void sendMouseEnterExitEvent(uint16 flags, MouseActorState &state, const Common::Event &event);
private:
RootStage *_rootStage = nullptr;
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,114 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/actors/text.h"
namespace MediaStation {
void TextActor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderStartup:
_isVisible = static_cast<bool>(chunk.readTypedByte());
break;
case kActorHeaderEditable:
_editable = chunk.readTypedByte();
break;
case kActorHeaderLoadType:
_loadType = chunk.readTypedByte();
break;
case kActorHeaderFontId:
_fontActorId = chunk.readTypedUint16();
break;
case kActorHeaderTextMaxLength:
_maxTextLength = chunk.readTypedUint16();
break;
case kActorHeaderInitialText:
_text = chunk.readTypedString();
break;
case kActorHeaderTextJustification:
_justification = static_cast<TextJustification>(chunk.readTypedUint16());
break;
case kActorHeaderTextPosition:
_position = static_cast<TextPosition>(chunk.readTypedUint16());
break;
case kActorHeaderTextCharacterClass: {
CharacterClass characterClass;
characterClass.firstAsciiCode = chunk.readTypedUint16();
characterClass.lastAsciiCode = chunk.readTypedUint16();
_acceptedInput.push_back(characterClass);
break;
}
default:
SpatialEntity::readParameter(chunk, paramType);
}
}
ScriptValue TextActor::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
ScriptValue returnValue;
switch (methodId) {
case kTextMethod: {
assert(args.empty());
error("%s: Text() method not implemented yet", __func__);
}
case kSetTextMethod: {
assert(args.size() == 1);
error("%s: getText() method not implemented yet", __func__);
}
case kSpatialShowMethod: {
assert(args.empty());
_isVisible = true;
warning("%s: spatialShow method not implemented yet", __func__);
return returnValue;
}
case kSpatialHideMethod: {
assert(args.empty());
_isVisible = false;
warning("%s: spatialHide method not implemented yet", __func__);
return returnValue;
}
default:
return SpatialEntity::callMethod(methodId, args);
}
}
Common::String TextActor::text() const {
return _text;
}
void TextActor::setText(Common::String text) {
error("%s: Setting text not implemented yet", __func__);
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,76 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_TEXT_H
#define MEDIASTATION_TEXT_H
#include "common/str.h"
#include "mediastation/actor.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
enum TextJustification {
kTextJustificationLeft = 0x25c,
kTextJustificationRight = 0x25d,
kTextJustificationCenter = 0x25e
};
enum TextPosition {
kTextPositionMiddle = 0x25e,
kTextPositionTop = 0x260,
kTextPositionBotom = 0x261
};
struct CharacterClass {
uint firstAsciiCode = 0;
uint lastAsciiCode = 0;
};
class TextActor : public SpatialEntity {
public:
TextActor() : SpatialEntity(kActorTypeText) {};
virtual bool isVisible() const override { return _isVisible; }
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType) override;
virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
private:
bool _editable = false;
uint _loadType = 0;
bool _isVisible = false;
Common::String _text;
uint _maxTextLength = 0;
uint _fontActorId = 0;
TextJustification _justification;
TextPosition _position;
Common::Array<CharacterClass> _acceptedInput;
// Method implementations.
Common::String text() const;
void setText(Common::String text);
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,94 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mediastation/mediastation.h"
#include "mediastation/debugchannels.h"
#include "mediastation/actors/timer.h"
namespace MediaStation {
ScriptValue TimerActor::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
ScriptValue returnValue;
switch (methodId) {
case kTimePlayMethod: {
assert(args.size() == 0);
timePlay();
return returnValue;
}
case kTimeStopMethod: {
assert(args.size() == 0);
timeStop();
return returnValue;
}
case kIsPlayingMethod: {
assert(args.size() == 0);
returnValue.setToBool(_isPlaying);
return returnValue;
}
default:
return Actor::callMethod(methodId, args);
}
}
void TimerActor::timePlay() {
_isPlaying = true;
_startTime = g_system->getMillis();
_lastProcessedTime = 0;
// Get the duration of the timer.
// TODO: Is there a better way to find out what the max time is? Do we have to look
// through each of the timer event handlers to figure it out?
_duration = 0;
const Common::Array<EventHandler *> &timeHandlers = _eventHandlers.getValOrDefault(kTimerEvent);
for (EventHandler *timeEvent : timeHandlers) {
// Indeed float, not time.
double timeEventInFractionalSeconds = timeEvent->_argumentValue.asFloat();
uint timeEventInMilliseconds = timeEventInFractionalSeconds * 1000;
if (timeEventInMilliseconds > _duration) {
_duration = timeEventInMilliseconds;
}
}
debugC(5, kDebugScript, "Timer::timePlay(): Now playing for %d ms", _duration);
}
void TimerActor::timeStop() {
if (!_isPlaying) {
return;
}
_isPlaying = false;
_startTime = 0;
_lastProcessedTime = 0;
}
void TimerActor::process() {
if (_isPlaying) {
processTimeEventHandlers();
}
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,47 @@
/* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MEDIASTATION_TIMER_H
#define MEDIASTATION_TIMER_H
#include "mediastation/actor.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
class TimerActor : public Actor {
public:
TimerActor() : Actor(kActorTypeTimer) {};
virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
virtual void process() override;
private:
bool _isPlaying = false;
void timePlay();
void timeStop();
};
} // End of namespace MediaStation
#endif