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 @@
engines/mediastation/metaengine.cpp

View File

@@ -0,0 +1,494 @@
/* 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 "common/util.h"
#include "mediastation/actor.h"
#include "mediastation/actors/camera.h"
#include "mediastation/actors/stage.h"
#include "mediastation/debugchannels.h"
#include "mediastation/mediascript/scriptconstants.h"
#include "mediastation/mediastation.h"
namespace MediaStation {
Actor::~Actor() {
for (auto it = _eventHandlers.begin(); it != _eventHandlers.end(); ++it) {
Common::Array<EventHandler *> &handlersForType = it->_value;
for (EventHandler *handler : handlersForType) {
delete handler;
}
handlersForType.clear();
}
_eventHandlers.clear();
}
void Actor::initFromParameterStream(Chunk &chunk) {
ActorHeaderSectionType paramType = kActorHeaderEmptySection;
while (true) {
paramType = static_cast<ActorHeaderSectionType>(chunk.readTypedUint16());
if (paramType == 0) {
break;
} else {
readParameter(chunk, paramType);
}
}
}
void Actor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderEventHandler: {
EventHandler *eventHandler = new EventHandler(chunk);
Common::Array<EventHandler *> &eventHandlersForType = _eventHandlers.getOrCreateVal(eventHandler->_type);
// This is not a hashmap because we don't want to have to hash ScriptValues.
for (EventHandler *existingEventHandler : eventHandlersForType) {
if (existingEventHandler->_argumentValue == eventHandler->_argumentValue) {
error("%s: Event handler for %s (%s) already exists", __func__,
eventTypeToStr(eventHandler->_type), eventHandler->_argumentValue.getDebugString().c_str());
}
}
eventHandlersForType.push_back(eventHandler);
break;
}
default:
error("Got unimplemented actor parameter 0x%x", static_cast<uint>(paramType));
}
}
void Actor::loadIsComplete() {
if (_loadIsComplete) {
warning("%s: Called more than once for actor %d", __func__, _id);
}
_loadIsComplete = true;
}
ScriptValue Actor::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
warning("%s: Got unimplemented method call 0x%x (%s)", __func__, static_cast<uint>(methodId), builtInMethodToStr(methodId));
return ScriptValue();
}
void Actor::processTimeEventHandlers() {
// TODO: Replace with a queue.
uint currentTime = g_system->getMillis();
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;
bool timeEventAlreadyProcessed = timeEventInMilliseconds < _lastProcessedTime;
bool timeEventNeedsToBeProcessed = timeEventInMilliseconds <= currentTime - _startTime;
if (!timeEventAlreadyProcessed && timeEventNeedsToBeProcessed) {
debugC(5, kDebugScript, "Actor::processTimeEventHandlers(): Running On Time handler for time %d ms", timeEventInMilliseconds);
timeEvent->execute(_id);
}
}
_lastProcessedTime = currentTime - _startTime;
}
void Actor::runEventHandlerIfExists(EventType eventType, const ScriptValue &arg) {
const Common::Array<EventHandler *> &eventHandlers = _eventHandlers.getValOrDefault(eventType);
for (EventHandler *eventHandler : eventHandlers) {
const ScriptValue &argToCheck = eventHandler->_argumentValue;
if (arg.getType() != argToCheck.getType()) {
warning("Got event handler arg type %s, expected %s",
scriptValueTypeToStr(arg.getType()), scriptValueTypeToStr(argToCheck.getType()));
continue;
}
if (arg == argToCheck) {
debugC(5, kDebugScript, "Executing handler for event type %s on actor %d", eventTypeToStr(eventType), _id);
eventHandler->execute(_id);
return;
}
}
debugC(5, kDebugScript, "No event handler for event type %s on actor %d", eventTypeToStr(eventType), _id);
}
void Actor::runEventHandlerIfExists(EventType eventType) {
ScriptValue scriptValue;
runEventHandlerIfExists(eventType, scriptValue);
}
SpatialEntity::~SpatialEntity() {
if (_parentStage != nullptr) {
_parentStage->removeChildSpatialEntity(this);
}
}
ScriptValue SpatialEntity::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
ScriptValue returnValue;
switch (methodId) {
case kSpatialMoveToMethod: {
assert(args.size() == 2);
int16 x = static_cast<int16>(args[0].asFloat());
int16 y = static_cast<int16>(args[1].asFloat());
moveTo(x, y);
break;
}
case kSpatialMoveToByOffsetMethod: {
assert(args.size() == 2);
int16 dx = static_cast<int16>(args[0].asFloat());
int16 dy = static_cast<int16>(args[1].asFloat());
int16 newX = _boundingBox.left + dx;
int16 newY = _boundingBox.top + dy;
moveTo(newX, newY);
break;
}
case kSpatialZMoveToMethod: {
assert(args.size() == 1);
int zIndex = static_cast<int>(args[0].asFloat());
setZIndex(zIndex);
break;
}
case kSpatialCenterMoveToMethod: {
assert(args.size() == 2);
int16 x = static_cast<int16>(args[0].asFloat());
int16 y = static_cast<int16>(args[1].asFloat());
moveToCentered(x, y);
break;
}
case kGetLeftXMethod:
assert(args.empty());
returnValue.setToFloat(_boundingBox.left);
break;
case kGetTopYMethod:
assert(args.empty());
returnValue.setToFloat(_boundingBox.top);
break;
case kGetWidthMethod:
assert(args.empty());
returnValue.setToFloat(_boundingBox.width());
break;
case kGetHeightMethod:
assert(args.empty());
returnValue.setToFloat(_boundingBox.height());
break;
case kGetCenterXMethod: {
assert(args.empty());
int centerX = _boundingBox.left + (_boundingBox.width() / 2);
returnValue.setToFloat(centerX);
break;
}
case kGetCenterYMethod: {
assert(args.empty());
int centerY = _boundingBox.top + (_boundingBox.height() / 2);
returnValue.setToFloat(centerY);
break;
}
case kGetZCoordinateMethod:
assert(args.empty());
returnValue.setToFloat(_zIndex);
break;
case kSetDissolveFactorMethod: {
assert(args.size() == 1);
double dissolveFactor = args[0].asFloat();
setDissolveFactor(dissolveFactor);
break;
}
case kIsVisibleMethod:
assert(args.empty());
returnValue.setToBool(isVisible());
break;
case kSetMousePositionMethod: {
assert(args.size() == 2);
int16 x = static_cast<int16>(args[0].asFloat());
int16 y = static_cast<int16>(args[1].asFloat());
setMousePosition(x, y);
break;
}
case kGetXScaleMethod1:
case kGetXScaleMethod2:
assert(args.empty());
returnValue.setToFloat(_scaleX);
break;
case kSetScaleMethod:
assert(args.size() == 1);
invalidateLocalBounds();
_scaleX = _scaleY = args[0].asFloat();
invalidateLocalBounds();
break;
case kSetXScaleMethod:
assert(args.size() == 1);
invalidateLocalBounds();
_scaleX = args[0].asFloat();
invalidateLocalBounds();
break;
case kGetYScaleMethod:
assert(args.empty());
returnValue.setToFloat(_scaleY);
break;
case kSetYScaleMethod:
assert(args.size() == 1);
invalidateLocalBounds();
_scaleY = args[0].asFloat();
invalidateLocalBounds();
break;
default:
Actor::callMethod(methodId, args);
}
return returnValue;
}
void SpatialEntity::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
switch (paramType) {
case kActorHeaderBoundingBox:
_originalBoundingBox = chunk.readTypedRect();
setAdjustedBounds(kWrapNone);
break;
case kActorHeaderDissolveFactor:
_dissolveFactor = chunk.readTypedDouble();
break;
case kActorHeaderZIndex:
_zIndex = chunk.readTypedGraphicUnit();
break;
case kActorHeaderTransparency:
_hasTransparency = static_cast<bool>(chunk.readTypedByte());
break;
case kActorHeaderChildActorId:
_stageId = chunk.readTypedUint16();
break;
case kActorHeaderScaleXAndY:
_scaleX = _scaleY = chunk.readTypedDouble();
break;
case kActorHeaderScaleX:
_scaleX = chunk.readTypedDouble();
break;
case kActorHeaderScaleY:
_scaleY = chunk.readTypedDouble();
break;
default:
Actor::readParameter(chunk, paramType);
}
}
void SpatialEntity::loadIsComplete() {
Actor::loadIsComplete();
if (_stageId != 0) {
Actor *pendingParentStageActor = g_engine->getActorById(_stageId);
if (pendingParentStageActor == nullptr) {
error("%s: Actor %d doesn't exist", __func__, _stageId);
} else if (pendingParentStageActor->type() != kActorTypeStage) {
error("%s: Requested parent stage %d is not a stage", __func__, _stageId);
}
StageActor *pendingParentStage = static_cast<StageActor *>(pendingParentStageActor);
pendingParentStage->addChildSpatialEntity(this);
}
}
void SpatialEntity::invalidateMouse() {
// TODO: Invalidate the mouse properly when we have custom events.
// For now, we simulate the mouse update event with a mouse moved event.
Common::Event mouseEvent;
mouseEvent.type = Common::EVENT_MOUSEMOVE;
mouseEvent.mouse = g_system->getEventManager()->getMousePos();
g_system->getEventManager()->pushEvent(mouseEvent);
}
void SpatialEntity::moveTo(int16 x, int16 y) {
Common::Point dest(x, y);
if (dest == _boundingBox.origin()) {
// We aren't actually moving anywhere.
return;
}
if (isVisible()) {
invalidateLocalBounds();
}
_originalBoundingBox.moveTo(dest);
setAdjustedBounds(kWrapNone);
if (isVisible()) {
invalidateLocalBounds();
}
if (interactsWithMouse()) {
invalidateMouse();
}
}
void SpatialEntity::moveToCentered(int16 x, int16 y) {
int16 targetX = x - (_boundingBox.width() / 2);
int16 targetY = y - (_boundingBox.height() / 2);
moveTo(targetX, targetY);
}
void SpatialEntity::setBounds(const Common::Rect &bounds) {
if (_boundingBox == bounds) {
// We aren't actually moving anywhere.
return;
}
if (isVisible()) {
invalidateLocalBounds();
}
_originalBoundingBox = bounds;
setAdjustedBounds(kWrapNone);
if (isVisible()) {
invalidateLocalBounds();
}
if (interactsWithMouse()) {
invalidateMouse();
}
}
void SpatialEntity::setZIndex(int zIndex) {
if (_zIndex == zIndex) {
// We aren't actually moving anywhere.
return;
}
_zIndex = zIndex;
invalidateLocalZIndex();
if (interactsWithMouse()) {
invalidateMouse();
}
}
void SpatialEntity::setMousePosition(int16 x, int16 y) {
if (_parentStage) {
_parentStage->setMousePosition(x, y);
}
}
void SpatialEntity::setDissolveFactor(double dissolveFactor) {
dissolveFactor = CLIP(dissolveFactor, 0.0, 1.0);
if (dissolveFactor != _dissolveFactor) {
_dissolveFactor = dissolveFactor;
invalidateLocalBounds();
}
}
void SpatialEntity::invalidateLocalBounds() {
if (_parentStage != nullptr) {
_parentStage->setAdjustedBounds(kWrapNone);
_parentStage->invalidateRect(getBbox());
} else {
error("%s: No parent stage for entity %d", __func__, _id);
}
}
void SpatialEntity::invalidateLocalZIndex() {
warning("STUB: %s", __func__);
}
void SpatialEntity::setAdjustedBounds(CylindricalWrapMode alignmentMode) {
_boundingBox = _originalBoundingBox;
if (_parentStage == nullptr) {
return;
}
Common::Point offset(0, 0);
Common::Point stageExtent = _parentStage->extent();
switch (alignmentMode) {
case kWrapRight: {
offset.x = stageExtent.x;
offset.y = 0;
break;
}
case kWrapLeft: {
offset.x = -stageExtent.x;
offset.y = 0;
break;
}
case kWrapBottom: {
offset.x = 0;
offset.y = stageExtent.y;
break;
}
case kWrapLeftTop: {
offset.x = 0;
offset.y = -stageExtent.y;
break;
}
case kWrapTop: {
offset.x = stageExtent.x;
offset.y = stageExtent.y;
break;
}
case kWrapRightBottom: {
offset.x = -stageExtent.x;
offset.y = -stageExtent.y;
break;
}
case kWrapRightTop: {
offset.x = -stageExtent.x;
offset.y = stageExtent.y;
break;
}
case kWrapLeftBottom: {
offset.x = stageExtent.x;
offset.y = -stageExtent.y;
break;
}
case kWrapNone:
default:
// No offset adjustment.
break;
}
if (alignmentMode != kWrapNone) {
// TODO: Implement this once we have a title that actually uses it.
warning("%s: Actor %d: Wrapping mode %d not handled yet: (%d, %d, %d, %d) -= (%d, %d)", __func__, _id, static_cast<uint>(alignmentMode), PRINT_RECT(_boundingBox), offset.x, offset.y);
}
if (_scaleX != 0.0 || _scaleY != 0.0) {
// TODO: Implement this once we have a title that actually uses it.
warning("%s: Scale not handled yet (scaleX: %f, scaleY: %f)", __func__, _scaleX, _scaleY);
}
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,275 @@
/* 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_ACTOR_H
#define MEDIASTATION_ACTOR_H
#include "common/events.h"
#include "common/keyboard.h"
#include "mediastation/datafile.h"
#include "mediastation/mediascript/eventhandler.h"
#include "mediastation/mediascript/scriptconstants.h"
#include "mediastation/mediascript/scriptvalue.h"
namespace MediaStation {
class DisplayContext;
class SpatialEntity;
class StageActor;
enum ActorType {
kActorTypeEmpty = 0x0000,
kActorTypeScreen = 0x0001, // SCR
kActorTypeStage = 0x0002, // STG
kActorTypePath = 0x0004, // PTH
kActorTypeSound = 0x0005, // SND
kActorTypeTimer = 0x0006, // TMR
kActorTypeImage = 0x0007, // IMG
kActorTypeHotspot = 0x000b, // HSP
kActorTypeSprite = 0x000e, // SPR
kActorTypeLKZazu = 0x000f,
kActorTypeLKConstellations = 0x0010,
kActorTypeDocument = 0x0011,
kActorTypeImageSet = 0x001d,
kActorTypeCursor = 0x000c, // CSR
kActorTypePrinter = 0x0019, // PRT
kActorTypeMovie = 0x0016, // MOV
kActorTypePalette = 0x0017,
kActorTypeText = 0x001a, // TXT
kActorTypeFont = 0x001b, // FON
kActorTypeCamera = 0x001c, // CAM
kActorTypeDiskImageActor = 0x001d,
kActorTypeCanvas = 0x001e, // CVS
kActorTypeXsnd = 0x001f,
kActorTypeXsndMidi = 0x0020,
kActorTypeRecorder = 0x0021,
kActorTypeFunction = 0x0069 // FUN
};
enum ActorHeaderSectionType {
kActorHeaderEmptySection = 0x0000,
kActorHeaderEventHandler = 0x0017,
kActorHeaderChildActorId = 0x0019,
kActorHeaderActorId = 0x001a,
kActorHeaderChannelIdent = 0x001b,
kActorHeaderMovieAnimationChannelIdent = 0x06a4,
kActorHeaderMovieAudioChannelIdent = 0x06a5,
kActorHeaderActorReference = 0x077b,
kActorHeaderBoundingBox = 0x001c,
kActorHeaderMouseActiveArea = 0x001d,
kActorHeaderZIndex = 0x001e,
kActorHeaderStartup = 0x001f,
kActorHeaderTransparency = 0x0020,
kActorHeaderHasOwnSubfile = 0x0021,
kActorHeaderCursorResourceId = 0x0022,
kActorHeaderFrameRate = 0x0024,
kActorHeaderLoadType = 0x0032,
kActorHeaderSoundInfo = 0x0033,
kActorHeaderMovieLoadType = 0x0037,
kActorHeaderSpriteChunkCount = 0x03e8,
kActorHeaderPalette = 0x05aa,
kActorHeaderDissolveFactor = 0x05dc,
kActorHeaderGetOffstageEvents = 0x05dd,
kActorHeaderX = 0x05de,
kActorHeaderY = 0x05df,
kActorHeaderScaleXAndY = 0x77a,
kActorHeaderScaleX = 0x77c,
kActorHeaderScaleY = 0x77d,
kActorHeaderUnk0 = 0x7d0,
kActorHeaderActorName = 0x0bb8,
// PATH FIELDS.
kActorHeaderStartPoint = 0x060e,
kActorHeaderEndPoint = 0x060f,
kActorHeaderPathTotalSteps = 0x0610,
kActorHeaderStepRate = 0x0611,
kActorHeaderDuration = 0x0612,
// CAMERA FIELDS.
kActorHeaderCameraViewportOrigin = 0x076f,
kActorHeaderCameraLensOpen = 0x0770,
kActorHeaderCameraImageActor = 0x77b,
// CANVAS FIELDS.
kActorHeaderCanvasUnk1 = 0x491,
kActorHeaderCanvasDissolveFactor = 0x493,
kActorHeaderCanvasUnk2 = 0x494,
kActorHeaderCanvasUnk3 = 0x495,
// STAGE FIELDS.
kActorHeaderStageExtent = 0x0771,
kActorHeaderCylindricalX = 0x0772,
kActorHeaderCylindricalY = 0x0773,
// TEXT FIELDS.
kActorHeaderEditable = 0x03eb,
kActorHeaderFontId = 0x0258,
kActorHeaderInitialText = 0x0259,
kActorHeaderTextMaxLength = 0x25a,
kActorHeaderTextJustification = 0x025b,
kActorHeaderTextPosition = 0x25f,
kActorHeaderTextUnk1 = 0x262,
kActorHeaderTextUnk2 = 0x263,
kActorHeaderTextCharacterClass = 0x0266,
// SPRITE FIELDS.
kActorHeaderSpriteClip = 0x03e9,
kActorHeaderCurrentSpriteClip = 0x03ea
};
enum CylindricalWrapMode : int;
struct MouseActorState {
SpatialEntity *keyDown = nullptr;
// There is no key up event.
SpatialEntity *mouseDown = nullptr;
SpatialEntity *mouseUp = nullptr;
SpatialEntity *mouseMoved = nullptr;
SpatialEntity *mouseExit = nullptr;
SpatialEntity *mouseEnter = nullptr;
SpatialEntity *mouseOutOfFocus = nullptr;
};
enum MouseEventFlag {
kNoFlag = 0x00,
kMouseDownFlag = 0x01,
kMouseUpFlag = 0x02,
kMouseMovedFlag = 0x04,
kMouseExitFlag = 0x10,
kMouseEnterFlag = 0x08,
kMouseUnk1Flag = 0x20,
kMouseOutOfFocusFlag = 0x40,
kKeyDownFlag = 0x80,
// There is no key up event.
};
class Actor {
public:
Actor(ActorType type) : _type(type) {};
virtual ~Actor();
// Does any needed frame drawing, audio playing, event handlers, etc.
virtual void process() { return; }
// Runs built-in bytecode methods.
virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args);
virtual bool isSpatialActor() const { return false; }
virtual void initFromParameterStream(Chunk &chunk);
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType);
virtual void loadIsComplete();
void processTimeEventHandlers();
void runEventHandlerIfExists(EventType eventType, const ScriptValue &arg);
void runEventHandlerIfExists(EventType eventType);
ActorType type() const { return _type; }
uint id() const { return _id; }
uint contextId() const { return _contextId; }
void setId(uint id) { _id = id; }
void setContextId(uint id) { _contextId = id; }
protected:
ActorType _type = kActorTypeEmpty;
bool _loadIsComplete = false;
uint _id = 0;
uint _contextId = 0;
uint _startTime = 0;
uint _lastProcessedTime = 0;
uint _duration = 0;
Common::HashMap<uint, Common::Array<EventHandler *> > _eventHandlers;
};
class SpatialEntity : public Actor {
public:
SpatialEntity(ActorType type) : Actor(type) {};
~SpatialEntity();
virtual void draw(DisplayContext &displayContext) { return; }
virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
virtual void readParameter(Chunk &chunk, ActorHeaderSectionType paramType) override;
virtual void loadIsComplete() override;
virtual void preload(const Common::Rect &rect) {};
virtual bool isRectInMemory(const Common::Rect &rect) { return true; }
virtual bool isLoading() { return false; }
virtual bool isSpatialActor() const override { return true; }
virtual bool isVisible() const { return _isVisible; }
virtual Common::Rect getBbox() const { return _boundingBox; }
int zIndex() const { return _zIndex; }
virtual void invalidateMouse();
virtual bool interactsWithMouse() const { return false; }
virtual uint16 findActorToAcceptMouseEvents(
const Common::Point &point,
uint16 eventMask,
MouseActorState &state,
bool inBounds) { return kNoFlag; }
virtual uint16 findActorToAcceptKeyboardEvents(
uint16 asciiCode,
uint16 eventMask,
MouseActorState &state) { return kNoFlag; }
virtual void mouseDownEvent(const Common::Event &event) { return; }
virtual void mouseUpEvent(const Common::Event &event) { return; }
virtual void mouseEnteredEvent(const Common::Event &event) { return; }
virtual void mouseExitedEvent(const Common::Event &event) { return; }
virtual void mouseMovedEvent(const Common::Event &event) { return; }
virtual void mouseOutOfFocusEvent(const Common::Event &event) { return; }
virtual void keyboardEvent(const Common::Event &event) { return; }
void setParentStage(StageActor *parentStage) { _parentStage = parentStage; }
void setToNoParentStage() { _parentStage = nullptr; }
StageActor *getParentStage() const { return _parentStage; }
virtual void invalidateLocalBounds();
virtual void setAdjustedBounds(CylindricalWrapMode alignmentMode);
protected:
uint _stageId = 0;
int _zIndex = 0;
double _dissolveFactor = 0.0;
double _scaleX = 0.0;
double _scaleY = 0.0;
Common::Rect _boundingBox;
Common::Rect _originalBoundingBox;
bool _isVisible = false;
bool _hasTransparency = false;
bool _getOffstageEvents = false;
StageActor *_parentStage = nullptr;
void moveTo(int16 x, int16 y);
void moveToCentered(int16 x, int16 y);
void setBounds(const Common::Rect &bounds);
void setZIndex(int zIndex);
virtual void setMousePosition(int16 x, int16 y);
virtual void setDissolveFactor(double dissolveFactor);
virtual void invalidateLocalZIndex();
};
} // End of namespace MediaStation
#endif

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

View File

@@ -0,0 +1,88 @@
/* 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 "audio/decoders/adpcm.h"
#include "audio/decoders/raw.h"
#include "mediastation/audio.h"
#include "mediastation/debugchannels.h"
#include "mediastation/mediastation.h"
namespace MediaStation {
AudioSequence::~AudioSequence() {
g_engine->_mixer->stopHandle(_handle);
for (Audio::SeekableAudioStream *stream : _streams) {
delete stream;
}
_streams.clear();
}
void AudioSequence::play() {
_handle = Audio::SoundHandle();
if (!_streams.empty()) {
Audio::QueuingAudioStream *audio = Audio::makeQueuingAudioStream(22050, false);
for (Audio::SeekableAudioStream *stream : _streams) {
stream->rewind();
audio->queueAudioStream(stream, DisposeAfterUse::NO);
}
g_engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, audio, -1, Audio::Mixer::kMaxChannelVolume, DisposeAfterUse::YES);
audio->finish();
}
}
void AudioSequence::stop() {
g_engine->_mixer->stopHandle(_handle);
_handle = Audio::SoundHandle();
}
void AudioSequence::readParameters(Chunk &chunk) {
_chunkCount = chunk.readTypedUint16();
_rate = chunk.readTypedUint32();
_channelCount = chunk.readTypedUint16();
_bitsPerSample = chunk.readTypedUint16();
}
void AudioSequence::readChunk(Chunk &chunk) {
Common::SeekableReadStream *buffer = chunk.readStream(chunk._length);
Audio::SeekableAudioStream *stream = nullptr;
switch (_bitsPerSample) {
case 16:
stream = Audio::makeRawStream(buffer, _rate, Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN);
break;
case 4: // IMA ADPCM-encoded, raw nibbles (no headers).
stream = Audio::makeADPCMStream(buffer, DisposeAfterUse::YES, 0, Audio::kADPCMDVI, _rate, 1, 8);
break;
default:
error("%s: Unknown audio encoding 0x%x", __func__, static_cast<uint>(_bitsPerSample));
}
_streams.push_back(stream);
debugC(5, kDebugLoading, "Finished reading audio chunk (@0x%llx)", static_cast<long long int>(chunk.pos()));
}
bool AudioSequence::isActive() {
return g_engine->_mixer->isSoundHandleActive(_handle);
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,58 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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_AUDIO_H
#define MEDIASTATION_AUDIO_H
#include "common/array.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "mediastation/datafile.h"
namespace MediaStation {
class AudioSequence {
public:
AudioSequence() {};
~AudioSequence();
void play();
void stop();
void readParameters(Chunk &chunk);
void readChunk(Chunk &chunk);
bool isActive();
bool isEmpty() { return _streams.empty(); }
uint _rate = 0;
uint _channelCount = 0;
uint _bitsPerSample = 0;
uint _chunkCount = 0;
private:
Common::Array<Audio::SeekableAudioStream *> _streams;
Audio::SoundHandle _handle;
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,73 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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/bitmap.h"
#include "mediastation/debugchannels.h"
namespace MediaStation {
BitmapHeader::BitmapHeader(Chunk &chunk) {
uint headerSizeInBytes = chunk.readTypedUint16();
_dimensions = chunk.readTypedGraphicSize();
_compressionType = static_cast<BitmapCompressionType>(chunk.readTypedUint16());
_stride = chunk.readTypedUint16();
debugC(5, kDebugLoading, "BitmapHeader::BitmapHeader(): headerSize: %d, _compressionType = 0x%x, _stride = %d",
headerSizeInBytes, static_cast<uint>(_compressionType), _stride);
}
Bitmap::Bitmap(Chunk &chunk, BitmapHeader *bitmapHeader) : _bitmapHeader(bitmapHeader) {
if (stride() < width()) {
warning("%s: Got stride less than width", __func__);
}
_unk1 = chunk.readUint16LE();
if (chunk.bytesRemaining() > 0) {
if (isCompressed()) {
_compressedStream = chunk.readStream(chunk.bytesRemaining());
} else {
_image.create(stride(), height(), Graphics::PixelFormat::createFormatCLUT8());
if (getCompressionType() == kUncompressedTransparentBitmap)
_image.setTransparentColor(0);
byte *pixels = static_cast<byte *>(_image.getPixels());
chunk.read(pixels, stride() * height());
if (chunk.bytesRemaining() > 0) {
warning("%s: %d bytes remaining in bitmap chunk after reading image data", __func__, chunk.bytesRemaining());
chunk.skip(chunk.bytesRemaining());
}
}
}
}
Bitmap::~Bitmap() {
delete _bitmapHeader;
_bitmapHeader = nullptr;
delete _compressedStream;
_compressedStream = nullptr;
}
bool Bitmap::isCompressed() const {
return (getCompressionType() != kUncompressedBitmap) && \
(getCompressionType() != kUncompressedTransparentBitmap);
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,71 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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_BITMAP_H
#define MEDIASTATION_BITMAP_H
#include "common/rect.h"
#include "graphics/managed_surface.h"
#include "mediastation/datafile.h"
#include "mediastation/actor.h"
namespace MediaStation {
enum BitmapCompressionType {
kUncompressedBitmap = 0,
kRle8BitmapCompression = 1,
kCccBitmapCompression = 5,
kCccTransparentBitmapCompression = 6,
kUncompressedTransparentBitmap = 7,
};
class BitmapHeader {
public:
BitmapHeader(Chunk &chunk);
Common::Point _dimensions;
BitmapCompressionType _compressionType = kUncompressedBitmap;
int16 _stride = 0;
};
class Bitmap {
public:
Bitmap(Chunk &chunk, BitmapHeader *bitmapHeader);
virtual ~Bitmap();
bool isCompressed() const;
BitmapCompressionType getCompressionType() const { return _bitmapHeader->_compressionType; }
int16 width() const { return _bitmapHeader->_dimensions.x; }
int16 height() const { return _bitmapHeader->_dimensions.y; }
int16 stride() const { return _bitmapHeader->_stride; }
Common::SeekableReadStream *_compressedStream = nullptr;
Graphics::ManagedSurface _image;
private:
BitmapHeader *_bitmapHeader = nullptr;
uint _unk1 = 0;
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,255 @@
/* 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/boot.h"
#include "mediastation/debugchannels.h"
#include "mediastation/mediastation.h"
namespace MediaStation {
#pragma region ContextReference
ContextReference::ContextReference(Chunk &chunk) {
// Read the file number.
ContextReferenceSectionType sectionType = getSectionType(chunk);
if (kContextReferenceContextId != sectionType) {
error("%s: Got unexpected section type %d", __func__, static_cast<uint>(sectionType));
}
_contextId = chunk.readTypedUint16();
sectionType = getSectionType(chunk);
if (kContextReferenceStreamId != sectionType) {
error("%s: Got unexpected section type %d", __func__, static_cast<uint>(sectionType));
}
_streamId = chunk.readTypedUint16();
// Read the context name. Only some titles have context names,
// and unfortunately we can't determine which just by relying
// on the title compiler version number.
sectionType = getSectionType(chunk);
if (kContextReferenceName == sectionType) {
_name = chunk.readTypedString();
sectionType = getSectionType(chunk);
}
// Read the parent context IDs. We don't know how many file
// references there are beforehand, so we'll just read until
// we get something else.
uint rewindOffset = chunk.pos();
while (kContextReferenceParentContextId == sectionType) {
int fileReference = chunk.readTypedUint16();
_parentContextIds.push_back(fileReference);
rewindOffset = chunk.pos();
sectionType = getSectionType(chunk);
}
chunk.seek(rewindOffset);
}
ContextReferenceSectionType ContextReference::getSectionType(Chunk &chunk) {
return static_cast<ContextReferenceSectionType>(chunk.readTypedUint16());
}
#pragma endregion
#pragma region ScreenReference
ScreenReference::ScreenReference(Chunk &chunk) {
// Make sure this declaration isn't empty.
ScreenReferenceSectionType sectionType = getSectionType(chunk);
if (kScreenReferenceScreenId != sectionType) {
error("%s: Got unexpected section type %d", __func__, static_cast<uint>(sectionType));
}
_screenActorId = chunk.readTypedUint16();
sectionType = getSectionType(chunk);
if (kScreenReferenceContextId != sectionType) {
error("%s: Got unexpected section type %d", __func__, static_cast<uint>(sectionType));
}
_contextId = chunk.readTypedUint16();
}
ScreenReferenceSectionType ScreenReference::getSectionType(Chunk &chunk) {
return static_cast<ScreenReferenceSectionType>(chunk.readTypedUint16());
}
#pragma endregion
#pragma region FileInfo
FileInfo::FileInfo(Chunk &chunk) {
// Read the file ID.
FileInfoSectionType sectionType = getSectionType(chunk);
if (kFileInfoFileId != sectionType) {
error("%s: Got unexpected section type %d", __func__, static_cast<uint>(sectionType));
}
_id = chunk.readTypedUint16();
// Read the intended file location.
sectionType = getSectionType(chunk);
if (kFileInfoFileNameAndType != sectionType) {
error("%s: Got unexpected section type %d", __func__, static_cast<uint>(sectionType));
}
_intendedLocation = static_cast<IntendedFileLocation>(chunk.readTypedUint16());
// Since the platforms that Media Station originally targeted were case-insensitive,
// the case of these filenames might not match the case of the files actually in
// the directory. All files should be matched case-insensitively.
_name = chunk.readTypedFilename();
}
FileInfoSectionType FileInfo::getSectionType(Chunk &chunk) {
return static_cast<FileInfoSectionType>(chunk.readTypedUint16());
}
#pragma endregion
#pragma region StreamInfo
StreamInfo::StreamInfo(Chunk &chunk) {
// Read the actor ID.
StreamInfoSectionType sectionType = getSectionType(chunk);
if (kStreamInfoActorId != sectionType) {
error("%s: Got unexpected section type %d", __func__, static_cast<uint>(sectionType));
}
_actorId = chunk.readTypedUint16();
// Read the file ID.
sectionType = getSectionType(chunk);
if (kStreamInfoFileId != sectionType) {
error("%s: Expected section type FILE_ID, got 0x%x", __func__, static_cast<uint>(sectionType));
}
_fileId = chunk.readTypedUint16();
// Read the start offset from the absolute start of the file.
sectionType = getSectionType(chunk);
if (kStreamInfoStartOffset != sectionType) {
error("%s: Expected section type START_OFFSET, got 0x%x", __func__, static_cast<uint>(sectionType));
}
_startOffsetInFile = chunk.readTypedUint32();
}
StreamInfoSectionType StreamInfo::getSectionType(Chunk &chunk) {
return static_cast<StreamInfoSectionType>(chunk.readTypedUint16());
}
#pragma endregion
#pragma region CursorDeclaration
CursorDeclaration::CursorDeclaration(Chunk &chunk) {
uint16 unk1 = chunk.readTypedUint16(); // Always 0x0001
_id = chunk.readTypedUint16();
_unk = chunk.readTypedUint16();
_name = chunk.readTypedFilename();
debugC(5, kDebugLoading, " - CursorDeclaration(): unk1 = 0x%x, id = 0x%x, unk = 0x%x, name = %s", unk1, _id, _unk, _name.c_str());
}
#pragma endregion
#pragma region Boot
void MediaStationEngine::readDocumentDef(Chunk &chunk) {
BootSectionType sectionType = kBootLastSection;
while (true) {
sectionType = static_cast<BootSectionType>(chunk.readTypedUint16());
if (sectionType == kBootLastSection) {
break;
}
readDocumentInfoFromStream(chunk, sectionType);
}
}
void MediaStationEngine::readDocumentInfoFromStream(Chunk &chunk, BootSectionType sectionType) {
switch (sectionType) {
case kBootVersionInformation:
readVersionInfoFromStream(chunk);
break;
case kBootContextReference:
readContextReferencesFromStream(chunk);
break;
case kBootScreenReference:
readScreenReferencesFromStream(chunk);
break;
case kBootFileInfo:
readAndAddFileMaps(chunk);
break;
case kBootStreamInfo:
readAndAddStreamMaps(chunk);
break;
case kBootUnk1:
_unk1 = chunk.readTypedUint16();
break;
case kBootFunctionTableSize:
_functionTableSize = chunk.readTypedUint16();
break;
case kBootUnk3:
_unk3 = chunk.readTypedUint16();
break;
default:
// See if any registered parameter clients know how to
// handle this parameter.
readUnrecognizedFromStream(chunk, static_cast<uint>(sectionType));
}
}
void MediaStationEngine::readVersionInfoFromStream(Chunk &chunk) {
_gameTitle = chunk.readTypedString();
_versionInfo = chunk.readTypedVersion();
_engineInfo = chunk.readTypedString();
_sourceString = chunk.readTypedString();
}
void MediaStationEngine::readContextReferencesFromStream(Chunk &chunk) {
uint flag = chunk.readTypedUint16();
while (flag != 0) {
ContextReference contextReference(chunk);
_contextReferences.setVal(contextReference._contextId, contextReference);
flag = chunk.readTypedUint16();
}
}
void MediaStationEngine::readScreenReferencesFromStream(Chunk &chunk) {
uint flag = chunk.readTypedUint16();
while (flag != 0) {
ScreenReference screenDeclaration(chunk);
_screenReferences.setVal(screenDeclaration._screenActorId, screenDeclaration);
flag = chunk.readTypedUint16();
}
}
void MediaStationEngine::readAndAddFileMaps(Chunk &chunk) {
uint flag = chunk.readTypedUint16();
while (flag != 0) {
FileInfo fileDeclaration(chunk);
_fileMap.setVal(fileDeclaration._id, fileDeclaration);
flag = chunk.readTypedUint16();
}
}
void MediaStationEngine::readAndAddStreamMaps(Chunk &chunk) {
uint flag = chunk.readTypedUint16();
while (flag != 0) {
StreamInfo subfileDeclaration(chunk);
_streamMap.setVal(subfileDeclaration._actorId, subfileDeclaration);
flag = chunk.readTypedUint16();
}
}
#pragma endregion
} // End of namespace MediaStation

161
engines/mediastation/boot.h Normal file
View File

@@ -0,0 +1,161 @@
/* 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_BOOT_H
#define MEDIASTATION_BOOT_H
#include "common/path.h"
#include "common/str.h"
#include "common/array.h"
#include "common/hashmap.h"
#include "mediastation/datafile.h"
namespace MediaStation {
enum ContextReferenceSectionType {
kContextReferencePlaceholder = 0x0003,
kContextReferenceContextId = 0x0004,
kContextReferenceStreamId = 0x0005,
kContextReferenceParentContextId = 0x0006,
kContextReferenceName = 0x0bb8
};
class ContextReference {
public:
ContextReference(Chunk &chunk);
ContextReference() {};
uint _contextId = 0;
uint _streamId = 0;
Common::String _name;
Common::Array<uint> _parentContextIds;
private:
ContextReferenceSectionType getSectionType(Chunk &chunk);
};
enum ScreenReferenceSectionType {
kScreenReferenceScreenId = 0x0009,
kScreenReferenceContextId = 0x0004
};
class ScreenReference {
public:
ScreenReference(Chunk &chunk);
ScreenReference() {};
uint _screenActorId = 0;
uint _contextId = 0;
private:
ScreenReferenceSectionType getSectionType(Chunk &chunk);
};
enum FileInfoSectionType {
kFileInfoEmptySection = 0x0000,
kFileInfoFileId = 0x002b,
kFileInfoFileNameAndType = 0x002d
};
// Indicates where a file is intended to be stored.
// NOTE: This might not be correct and this might be a more general "file type".
enum IntendedFileLocation {
kFileLocationEmpty = 0x0000,
// Usually all files that have numbers remain on the CD-ROM.
kFileIntendedOnCdRom = 0x0007,
// These UNKs only appear in George Shrinks.
kFileIntendedForUnk1 = 0x0008,
kFileIntendedForUnk2 = 0x0009,
// Usually only INSTALL.CXT is copied to the hard disk.
kFileIntendedOnHardDisk = 0x000b
};
class FileInfo {
public:
FileInfo(Chunk &chunk);
FileInfo() {};
uint _id = 0;
IntendedFileLocation _intendedLocation = kFileLocationEmpty;
Common::String _name;
private:
FileInfoSectionType getSectionType(Chunk &chunk);
};
enum StreamInfoSectionType {
kStreamInfoEmptySection = 0x0000,
kStreamInfoActorId = 0x002a,
kStreamInfoFileId = 0x002b,
kStreamInfoStartOffset = 0x002c
};
class StreamInfo {
public:
StreamInfo(Chunk &chunk);
StreamInfo() {};
uint _actorId = 0;
uint _fileId = 0;
uint _startOffsetInFile = 0;
private:
StreamInfoSectionType getSectionType(Chunk &chunk);
};
// Declares a cursor, which is stored as a cursor resource in the game executable.
class CursorDeclaration {
public:
CursorDeclaration(Chunk &chunk);
CursorDeclaration() {};
uint _id = 0;
uint _unk = 0;
Common::String _name;
};
class EngineResourceDeclaration {
public:
EngineResourceDeclaration(Common::String resourceName, int resourceId) : _name(resourceName), _id(resourceId) {};
EngineResourceDeclaration() {};
Common::String _name;
int _id = 0;
};
enum BootSectionType {
kBootLastSection = 0x0000,
kBootContextReference = 0x0002,
kBootVersionInformation = 0x0190,
kBootUnk1 = 0x0191,
kBootFunctionTableSize = 0x0192,
kBootUnk3 = 0x0193,
kBootEngineResource = 0x0bba,
kBootEngineResourceId = 0x0bbb,
kBootScreenReference = 0x0007,
kBootFileInfo = 0x000a,
kBootStreamInfo = 0x000b,
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,306 @@
/* 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/clients.h"
#include "mediastation/context.h"
#include "mediastation/mediastation.h"
namespace MediaStation {
bool DeviceOwner::attemptToReadFromStream(Chunk &chunk, uint sectionType) {
bool handledParam = true;
switch (sectionType) {
case kDeviceOwnerAllowMultipleSounds:
_allowMultipleSounds = chunk.readTypedByte();
break;
case kDeviceOwnerAllowMultipleStreams:
_allowMultipleStreams = chunk.readTypedByte();
break;
default:
handledParam = false;
}
return handledParam;
}
bool Document::attemptToReadFromStream(Chunk &chunk, uint sectionType) {
bool handledParam = true;
switch (sectionType) {
case kDocumentContextLoadComplete:
readContextLoadComplete(chunk);
break;
case kDocumentStartupInformation:
readStartupInformation(chunk);
break;
default:
handledParam = false;
}
return handledParam;
}
void Document::readStartupInformation(Chunk &chunk) {
DocumentSectionType sectionType = static_cast<DocumentSectionType>(chunk.readTypedUint16());
debugC(5, kDebugLoading, "%s: sectionType = 0x%x", __func__, static_cast<uint>(sectionType));
switch (sectionType) {
case kDocumentEntryScreen: {
uint entryPointScreenId = chunk.readTypedUint16();
if (_entryPointScreenId == 0) {
// We don't want to reset the overridden screen entry point.
_entryPointScreenId = entryPointScreenId;
}
break;
}
default:
error("%s: Unhandled section type 0x%x", __func__, static_cast<uint>(sectionType));
}
}
void Document::readContextLoadComplete(Chunk &chunk) {
uint contextId = chunk.readTypedUint16();
debugC(5, kDebugLoading, "%s: Context %d", __func__, contextId);
if (contextId == _loadingContextId) {
contextLoadDidComplete();
}
if (_loadingScreenActorId != 0) {
uint loadingScreenActorContextId = contextIdForScreenActorId(_loadingScreenActorId);
if (contextId == loadingScreenActorContextId) {
screenLoadDidComplete();
}
}
}
void Document::beginTitle(uint overriddenEntryPointScreenId) {
_entryPointStreamId = MediaStationEngine::BOOT_STREAM_ID;
if (overriddenEntryPointScreenId != 0) {
// This lets us override the default entry screen for development purposes.
_entryPointScreenId = overriddenEntryPointScreenId;
_entryPointScreenIdWasOverridden = true;
}
startFeed(_entryPointStreamId);
}
void Document::startContextLoad(uint contextId) {
debugC(5, kDebugLoading, "%s: Loading context %d", __func__, contextId);
Context *existingContext = g_engine->_loadedContexts.getValOrDefault(contextId);
if (existingContext == nullptr) {
if (_loadingContextId == 0) {
const ContextReference &contextRef = g_engine->contextRefWithId(contextId);
if (contextRef._contextId != 0) {
_loadingContextId = contextId;
startFeed(contextRef._streamId);
}
} else {
addToContextLoadQueue(contextId);
}
} else {
if (_currentScreenActorId != 0 && contextId != _loadingContextId) {
Actor *currentScreen = g_engine->getActorById(_currentScreenActorId);
ScriptValue arg;
arg.setToActorId(contextId);
currentScreen->runEventHandlerIfExists(kContextLoadCompleteEvent2, arg);
}
if (_loadingContextId == 0) {
checkQueuedContextLoads();
}
}
}
bool Document::isContextLoadInProgress(uint contextId) {
if (contextId == 0) {
// If we don't have a valid context ID, just check if we are loading any context.
return _loadingContextId != 0;
} else {
// If the context ID is valid, check if we are loading specifically that context.
return contextId == _loadingContextId;
}
}
void Document::branchToScreen() {
if (_loadingScreenActorId == 0) {
_loadingScreenActorId = _requestedScreenBranchId;
_requestedScreenBranchId = 0;
uint contextId = contextIdForScreenActorId(_loadingScreenActorId);
blowAwayCurrentScreen();
preloadParentContexts(contextId);
addToContextLoadQueue(contextId);
if (_loadingContextId == 0) {
checkQueuedContextLoads();
}
}
}
void Document::scheduleScreenBranch(uint screenActorId) {
// This is to support not immediately branching to the wrong screen
// when we are starting at a user-defined context. This is because the click
// handler usually points to the main menu screen rather than the screen
// we're starting at. It would be way too complicated to find the right variable
// and change it at runtime, so we will just branch to the screen we are already on.
// (We have to branch to something because by this point we have already faded out the screen and such,
// so we need to run another screen entry event to bring things back in again.)
if (_entryPointScreenIdWasOverridden) {
_entryPointScreenIdWasOverridden = false;
screenActorId = _currentScreenActorId;
}
_requestedScreenBranchId = screenActorId;
}
void Document::scheduleContextRelease(uint contextId) {
if (!g_engine->contextIsLocked(contextId)) {
_requestedContextReleaseId.push_back(contextId);
}
}
void Document::streamDidClose(uint streamId) {
bool currentStreamIsTargetStream = _currentStreamFeed != nullptr && streamId == _currentStreamFeed->_id;
if (!currentStreamIsTargetStream) {
return;
}
_currentStreamFeed = nullptr;
}
void Document::streamDidFinish(uint streamId) {
bool currentStreamIsTargetStream = _currentStreamFeed != nullptr && streamId == _currentStreamFeed->_id;
if (currentStreamIsTargetStream) {
stopFeed();
if (streamId == _entryPointStreamId) {
_requestedScreenBranchId = _entryPointScreenId;
branchToScreen();
} else {
checkQueuedContextLoads();
}
}
}
void Document::contextLoadDidComplete() {
if (_currentScreenActorId != 0) {
ScriptValue arg;
arg.setToActorId(_loadingContextId);
Actor *currentScreen = g_engine->getActorById(_currentScreenActorId);
if (currentScreen != nullptr) {
currentScreen->runEventHandlerIfExists(kContextLoadCompleteEvent, arg);
}
}
_loadingContextId = 0;
}
void Document::screenLoadDidComplete() {
_currentScreenActorId = _loadingScreenActorId;
Actor *currentScreen = g_engine->getActorById(_loadingScreenActorId);
currentScreen->runEventHandlerIfExists(kScreenEntryEvent);
_loadingScreenActorId = 0;
}
void Document::process() {
if (!_requestedContextReleaseId.empty()) {
for (uint contextId : _requestedContextReleaseId) {
g_engine->destroyContext(contextId);
}
_requestedContextReleaseId.clear();
}
if (_requestedScreenBranchId != 0) {
branchToScreen();
}
}
void Document::blowAwayCurrentScreen() {
if (_currentScreenActorId != 0) {
uint contextId = contextIdForScreenActorId(_currentScreenActorId);
if (contextId != 0) {
Actor *currentScreen = g_engine->getActorById(_currentScreenActorId);
currentScreen->runEventHandlerIfExists(kScreenExitEvent);
g_engine->destroyContext(contextId);
}
}
}
uint Document::contextIdForScreenActorId(uint screenActorId) {
ScreenReference screenRef = g_engine->screenRefWithId(screenActorId);
return screenRef._contextId;
}
void Document::startFeed(uint streamId) {
// The original had some more stuff here, including cache management and device ownership,
// but since we don't need these things right now, this function is rather empty.
_currentStreamFeed = g_engine->getStreamFeedManager()->openStreamFeed(streamId);
_currentStreamFeed->readData();
}
void Document::stopFeed() {
_currentStreamFeed->stopFeed();
g_engine->getStreamFeedManager()->closeStreamFeed(_currentStreamFeed);
_currentStreamFeed = nullptr;
}
void Document::preloadParentContexts(uint contextId) {
ContextReference contextReference = g_engine->contextRefWithId(contextId);
for (uint parentContextId : contextReference._parentContextIds) {
if (parentContextId != 0) {
Context *existingContext = g_engine->_loadedContexts.getValOrDefault(parentContextId);
if (existingContext == nullptr && parentContextId != contextId) {
debugC(5, kDebugLoading, "%s: Loading parent context %d", __func__, parentContextId);
addToContextLoadQueue(parentContextId);
}
}
}
}
void Document::addToContextLoadQueue(uint contextId) {
if (!isContextLoadQueued(contextId)) {
_contextLoadQueue.push_back(contextId);
} else {
warning("%s: Context %d already queued for load", __func__, contextId);
}
}
bool Document::isContextLoadQueued(uint contextId) {
for (uint queuedContextId : _contextLoadQueue) {
if (queuedContextId == contextId) {
return true;
}
}
return false;
}
void Document::checkQueuedContextLoads() {
while (!_contextLoadQueue.empty()) {
uint contextId = _contextLoadQueue.front();
_contextLoadQueue.erase(_contextLoadQueue.begin());
startContextLoad(contextId);
}
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,108 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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_CLIENTS_H
#define MEDIASTATION_CLIENTS_H
#include "mediastation/datafile.h"
namespace MediaStation {
class ParameterClient {
public:
ParameterClient() {};
virtual ~ParameterClient() {};
virtual bool attemptToReadFromStream(Chunk &chunk, uint sectionType) = 0;
};
enum DeviceOwnerSectionType {
kDeviceOwnerAllowMultipleSounds = 0x35,
kDeviceOwnerAllowMultipleStreams = 0x36,
};
class DeviceOwner : public ParameterClient {
public:
virtual bool attemptToReadFromStream(Chunk &chunk, uint sectionType) override;
bool _allowMultipleSounds = false;
bool _allowMultipleStreams = false;
};
enum DocumentSectionType {
kDocumentContextLoadComplete = 0x10,
kDocumentStartupInformation = 0x2e,
kDocumentEntryScreen = 0x2f,
};
class Document : public ParameterClient {
public:
virtual bool attemptToReadFromStream(Chunk &chunk, uint sectionType) override;
void readStartupInformation(Chunk &chunk);
void readContextLoadComplete(Chunk &chunk);
void beginTitle(uint overriddenEntryPointScreenId = 0);
void startContextLoad(uint contextId);
bool isContextLoadInProgress(uint contextId);
void branchToScreen();
void scheduleScreenBranch(uint screenActorId);
void scheduleContextRelease(uint contextId);
void streamDidClose(uint streamId);
void streamDidFinish(uint streamId);
// These implementations are left empty because they are empty in the original,
// but they are kept because they are technically still defined in the original.
void streamDidOpen(uint streamId) {};
void streamWillRead(uint streamId) {};
void process();
uint contextIdForScreenActorId(uint screenActorId);
private:
uint _currentScreenActorId = 0;
StreamFeed *_currentStreamFeed = nullptr;
Common::Array<uint> _requestedContextReleaseId;
Common::Array<uint> _contextLoadQueue;
uint _requestedScreenBranchId = 0;
bool _entryPointScreenIdWasOverridden = false;
uint _entryPointScreenId = 0;
uint _entryPointStreamId = 0;
uint _loadingContextId = 0;
uint _loadingScreenActorId = 0;
void contextLoadDidComplete();
void screenLoadDidComplete();
void startFeed(uint streamId);
// This is named stopFeed in the original, but it is a bit of a misnomer
// because it also closes the stream feed. In the lower-level stream feed manager
// and stream feeds themselves, stopping the stream feed and closing it is
// two separate operations.
void stopFeed();
void blowAwayCurrentScreen();
void preloadParentContexts(uint contextId);
void addToContextLoadQueue(uint contextId);
bool isContextLoadQueued(uint contextId);
void checkQueuedContextLoads();
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
add_engine mediastation "Media Station" no "" "" "highres"

View File

@@ -0,0 +1,250 @@
/* 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/context.h"
#include "mediastation/debugchannels.h"
#include "mediastation/bitmap.h"
#include "mediastation/mediascript/collection.h"
#include "mediastation/mediascript/function.h"
#include "mediastation/actors/camera.h"
#include "mediastation/actors/canvas.h"
#include "mediastation/actors/palette.h"
#include "mediastation/actors/image.h"
#include "mediastation/actors/path.h"
#include "mediastation/actors/sound.h"
#include "mediastation/actors/movie.h"
#include "mediastation/actors/sprite.h"
#include "mediastation/actors/stage.h"
#include "mediastation/actors/hotspot.h"
#include "mediastation/actors/timer.h"
#include "mediastation/actors/screen.h"
#include "mediastation/actors/font.h"
#include "mediastation/actors/text.h"
namespace MediaStation {
Context::~Context() {
for (auto it = _variables.begin(); it != _variables.end(); ++it) {
delete it->_value;
}
_variables.clear();
}
void MediaStationEngine::readControlCommands(Chunk &chunk) {
ContextSectionType sectionType = kContextEndOfSection;
do {
sectionType = static_cast<ContextSectionType>(chunk.readTypedUint16());
debugC(5, kDebugLoading, "%s: sectionType = 0x%x (@0x%llx)", __func__, static_cast<uint>(sectionType), static_cast<long long int>(chunk.pos()));
if (sectionType != kContextEndOfSection) {
readCommandFromStream(chunk, sectionType);
}
} while (sectionType != kContextEndOfSection);
}
void MediaStationEngine::readCreateContextData(Chunk &chunk) {
uint contextId = chunk.readTypedUint16();
debugC(5, kDebugLoading, "%s: Context %d", __func__, contextId);
Context *context = _loadedContexts.getValOrDefault(contextId);
if (context == nullptr) {
context = new Context();
context->_id = contextId;
_loadedContexts.setVal(contextId, context);
}
}
void MediaStationEngine::readDestroyContextData(Chunk &chunk) {
uint contextId = chunk.readTypedUint16();
debugC(5, kDebugLoading, "%s: Context %d", __func__, contextId);
destroyContext(contextId);
}
void MediaStationEngine::readDestroyActorData(Chunk &chunk) {
uint actorId = chunk.readTypedUint16();
debugC(5, kDebugLoading, "%s: Actor %d", __func__, actorId);
destroyActor(actorId);
}
void MediaStationEngine::readActorLoadComplete(Chunk &chunk) {
uint actorId = chunk.readTypedUint16();
debugC(5, kDebugLoading, "%s: Actor %d", __func__, actorId);
Actor *actor = g_engine->getActorById(actorId);
actor->loadIsComplete();
}
void MediaStationEngine::readCreateActorData(Chunk &chunk) {
uint contextId = chunk.readTypedUint16();
ActorType type = static_cast<ActorType>(chunk.readTypedUint16());
uint id = chunk.readTypedUint16();
debugC(5, kDebugLoading, "%s: Actor %d, type 0x%x", __func__, id, static_cast<uint>(type));
Actor *actor = nullptr;
switch (type) {
case kActorTypeImage:
actor = new ImageActor();
break;
case kActorTypeMovie:
actor = new StreamMovieActor();
break;
case kActorTypeSound:
actor = new SoundActor();
break;
case kActorTypePalette:
actor = new PaletteActor();
break;
case kActorTypePath:
actor = new PathActor();
break;
case kActorTypeTimer:
actor = new TimerActor();
break;
case kActorTypeHotspot:
actor = new HotspotActor();
break;
case kActorTypeSprite:
actor = new SpriteMovieActor();
break;
case kActorTypeCanvas:
actor = new CanvasActor();
break;
case kActorTypeCamera:
actor = new CameraActor();
break;
case kActorTypeStage:
actor = new StageActor();
break;
case kActorTypeScreen:
actor = new ScreenActor();
break;
case kActorTypeFont:
actor = new FontActor();
break;
case kActorTypeText:
actor = new TextActor();
break;
default:
error("%s: No class for actor type 0x%x (@0x%llx)", __func__, static_cast<uint>(type), static_cast<long long int>(chunk.pos()));
}
actor->setId(id);
actor->setContextId(contextId);
actor->initFromParameterStream(chunk);
g_engine->registerActor(actor);
}
void MediaStationEngine::readCreateVariableData(Chunk &chunk) {
uint contextId = chunk.readTypedUint16();
uint id = chunk.readTypedUint16();
if (g_engine->getVariable(id) != nullptr) {
error("%s: Global variable %d already exists", __func__, id);
}
ScriptValue *value = new ScriptValue(&chunk);
Context *context = _loadedContexts.getValOrDefault(contextId);
if (context == nullptr) {
error("%s: Context %d does not exist or has not been loaded yet in this title", __func__, contextId);
}
context->_variables.setVal(id, value);
debugC(5, kDebugScript, "%s: %d (type: %s)", __func__, id, scriptValueTypeToStr(value->getType()));
}
void MediaStationEngine::readHeaderSections(Subfile &subfile, Chunk &chunk) {
do {
ChannelClient *actor = g_engine->getChannelClientByChannelIdent(chunk._id);
if (actor == nullptr) {
error("%s: Client \"%s\" (0x%x) does not exist or has not been read yet in this title. (@0x%llx)", __func__, tag2str(chunk._id), chunk._id, static_cast<long long int>(chunk.pos()));
}
if (chunk.bytesRemaining() > 0) {
actor->readChunk(chunk);
}
if (chunk.bytesRemaining() != 0) {
warning("%s: %d bytes remaining at end of chunk", __func__, chunk.bytesRemaining());
}
if (!subfile.atEnd()) {
chunk = subfile.nextChunk();
}
} while (!subfile.atEnd());
}
void MediaStationEngine::readContextNameData(Chunk &chunk) {
uint contextId = chunk.readTypedUint16();
debugC(5, kDebugLoading, "%s: Context %d", __func__, contextId);
Context *context = _loadedContexts.getValOrDefault(contextId);
if (context == nullptr) {
error("%s: Context %d does not exist or has not been loaded yet in this title", __func__, contextId);
}
context->_name = chunk.readTypedString();
}
void MediaStationEngine::readCommandFromStream(Chunk &chunk, ContextSectionType sectionType) {
switch (sectionType) {
case kContextCreateData:
readCreateContextData(chunk);
break;
case kContextDestroyData:
readDestroyContextData(chunk);
break;
case kContextCreateActorData:
readCreateActorData(chunk);
break;
case kContextDestroyActorData:
readDestroyActorData(chunk);
break;
case kContextActorLoadComplete:
readActorLoadComplete(chunk);
break;
case kContextCreateVariableData:
readCreateVariableData(chunk);
break;
case kContextNameData:
readContextNameData(chunk);
break;
default:
readUnrecognizedFromStream(chunk, static_cast<uint>(sectionType));
break;
}
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,69 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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_CONTEXT_H
#define MEDIASTATION_CONTEXT_H
#include "common/str.h"
#include "common/path.h"
#include "common/hashmap.h"
#include "graphics/palette.h"
#include "mediastation/datafile.h"
#include "mediastation/actor.h"
namespace MediaStation {
enum StreamType {
kDocumentDefStream = 0x01,
kControlCommandsStream = 0x0D,
};
enum ContextSectionType {
kContextEndOfSection = 0x00,
kContextControlCommands = 0x0d,
kContextCreateData = 0x0e,
kContextDestroyData = 0x0f,
kContextLoadCompleteSection = 0x10,
kContextCreateActorData = 0x11,
kContextDestroyActorData = 0x12,
kContextActorLoadComplete = 0x13,
kContextCreateVariableData = 0x14,
kContextFunctionSection = 0x31,
kContextNameData = 0xbb8
};
class Context {
public:
~Context();
Common::String _name;
Common::HashMap<uint, ScriptValue *> _variables;
// This is not an internal file ID, but the number of the file
// as it appears in the filename. For instance, the context in
// "100.cxt" would have file number 100.
uint _id = 0;
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,3 @@
begin_section("Media Station");
add_person("Nathanael Gentry", "npjg", "");
end_section();

View File

@@ -0,0 +1,272 @@
/* 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/cursors.h"
#include "mediastation/debugchannels.h"
#include "mediastation/mediastation.h"
#include "common/system.h"
#include "common/file.h"
#include "common/formats/winexe_ne.h"
#include "common/formats/winexe_pe.h"
#include "graphics/cursorman.h"
namespace MediaStation {
CursorManager::~CursorManager() {
// It is up to the platform-specific cursor managers
// to actually delete their resources.
_cursors.clear();
}
bool CursorManager::attemptToReadFromStream(Chunk &chunk, uint param) {
bool handledParam = true;
switch (param) {
case kCursorManagerInit:
init(chunk);
break;
case kCursorManagerNewCursor:
newCursor(chunk);
break;
case kCursorManagerDisposeCursor:
disposeCursor(chunk);
break;
default:
handledParam = false;
}
return handledParam;
}
void CursorManager::init(Chunk &chunk) {
_baseCursorId = chunk.readTypedUint16();
_maxCursorId = chunk.readTypedUint16();
if (_maxCursorId < _baseCursorId || _baseCursorId == 0) {
error("%s: Got invalid cursor IDs", __func__);
}
}
void CursorManager::newCursor(Chunk &chunk) {
CursorType cursorType = static_cast<CursorType>(chunk.readTypedUint16());
uint16 cursorId = chunk.readTypedUint16();
switch (cursorType) {
case kPlatformCursor: {
uint16 platformCursorId = chunk.readTypedUint16();
newPlatformCursor(cursorId, platformCursorId);
break;
}
case kResourceCursor: {
// This first value isn't actually used.
chunk.readTypedUint16();
Common::String resourceName = chunk.readTypedFilename();
newResourceCursor(cursorId, resourceName);
break;
}
default:
error("%s: Got unknown cursor type %d", __func__, static_cast<uint>(cursorType));
}
}
void CursorManager::disposeCursor(Chunk &chunk) {
uint16 cursorId = chunk.readTypedUint16();
_cursors.erase(cursorId);
// We don't actually delete the underlying platform-specific
// cursor, just remove it from the hashmap. Otherwise, we'd
// mess up the platform-specific storage.
}
void CursorManager::newPlatformCursor(uint16 platformCursorId, uint16 cursorId) {
if (cursorId < _baseCursorId || cursorId > _maxCursorId || cursorId == 0) {
error("%s: Got invalid cursor ID %d", __func__, static_cast<uint>(cursorId));
}
warning("STUB: %s: Platform cursor %d, internal cursor %d", __func__, platformCursorId, cursorId);
// TODO: To implement this, we need have the default platform cursors for Windows and Mac.
}
void CursorManager::newResourceCursor(uint16 cursorId, const Common::String &resourceName) {
if (cursorId < _baseCursorId || cursorId > _maxCursorId || cursorId == 0) {
error("%s: Got invalid cursor ID %d", __func__, static_cast<uint>(cursorId));
}
Graphics::Cursor *cursor = loadResourceCursor(resourceName);
_cursors.setVal(cursorId, cursor);
}
void CursorManager::showCursor() {
CursorMan.showMouse(true);
}
void CursorManager::hideCursor() {
CursorMan.showMouse(false);
}
void CursorManager::registerAsPermanent(uint16 id) {
if (id != 0) {
_permanentCursorId = id;
}
}
void CursorManager::setAsPermanent(uint16 id) {
bool cursorAlreadySet = _currentCursorId == id && _permanentCursorId == id;
bool cursorIsEmpty = id == 0;
if (cursorAlreadySet || cursorIsEmpty) {
return;
}
_permanentCursorId = id;
_currentCursorId = id;
resetCurrent();
}
void CursorManager::setAsTemporary(uint16 id) {
bool cursorAlreadySet = _currentCursorId == id;
bool cursorIsEmpty = id == 0;
if (cursorAlreadySet || cursorIsEmpty) {
return;
}
_currentCursorId = id;
resetCurrent();
}
void CursorManager::unsetPermanent() {
_permanentCursorId = 0;
_currentCursorId = 0;
}
void CursorManager::unsetTemporary() {
if (_currentCursorId != _permanentCursorId) {
_currentCursorId = _permanentCursorId;
resetCurrent();
}
}
void CursorManager::resetCurrent() {
if (_currentCursorId != 0) {
Graphics::Cursor *cursor = _cursors.getVal(_currentCursorId);
CursorMan.replaceCursor(cursor);
}
}
void CursorManager::setDefaultCursor() {
Graphics::Cursor *cursor = Graphics::makeDefaultWinCursor();
CursorMan.replaceCursor(cursor);
delete cursor;
}
WindowsCursorManager::WindowsCursorManager(const Common::Path &appName) : CursorManager(appName) {
if (appName.empty()) {
error("%s: No executable to load cursors from", __func__);
} else if (!Common::File::exists(appName)) {
error("%s: Executable %s doesn't exist", __func__, appName.toString().c_str());
}
Common::WinResources *exe = Common::WinResources::createFromEXE(appName);
if (exe == nullptr || !exe->loadFromEXE(appName)) {
error("%s: Could not load resources from executable %s", __func__, appName.toString().c_str());
}
const Common::Array<Common::WinResourceID> cursorGroups = exe->getIDList(Common::kWinGroupCursor);
for (Common::WinResourceID cursorGroup : cursorGroups) {
Common::String resourceString = cursorGroup.getString();
if (resourceString.empty()) {
warning("%s: Got Windows cursor group with no string ID", __func__);
continue;
}
Graphics::WinCursorGroup *group = Graphics::WinCursorGroup::createCursorGroup(exe, cursorGroup);
_cursorGroups.setVal(resourceString, group);
}
delete exe;
}
WindowsCursorManager::~WindowsCursorManager() {
for (auto it = _cursorGroups.begin(); it != _cursorGroups.end(); ++it) {
delete it->_value;
}
_cursorGroups.clear();
// We don't need to delete items in _cursors itself,
// because those cursors are part of _cursorGroups.
}
Graphics::Cursor *WindowsCursorManager::loadResourceCursor(const Common::String &name) {
// Search for case-insensitive match since resource names are expected to be case-insensitive.
for (auto it = _cursorGroups.begin(); it != _cursorGroups.end(); ++it) {
if (it->_key.equalsIgnoreCase(name)) {
Graphics::Cursor *cursor = it->_value->cursors[0].cursor;
return cursor;
}
}
error("%s: Reqested Windows cursor %s not found", __func__, name.c_str());
}
MacCursorManager::MacCursorManager(const Common::Path &appName) : CursorManager(appName) {
if (appName.empty()) {
error("%s: No file to load cursors from", __func__);
} else if (!Common::File::exists(appName)) {
error("%s: File %s doesn't exist", __func__, appName.toString().c_str());
}
_resFork = new Common::MacResManager();
if (!_resFork->open(appName) || !_resFork->hasResFork()) {
error("%s: Could not load resource fork from %s", __func__, appName.toString().c_str());
}
}
MacCursorManager::~MacCursorManager() {
for (auto it = _cursors.begin(); it != _cursors.end(); ++it) {
delete it->_value;
}
delete _resFork;
_resFork = nullptr;
}
Graphics::Cursor *MacCursorManager::loadResourceCursor(const Common::String &name) {
// Try to load a color cursor first.
Common::SeekableReadStream *stream = _resFork->getResource(MKTAG('c', 'r', 's', 'r'), name);
if (stream == nullptr) {
// Fall back to attempting to load a mnochrome cursor.
stream = _resFork->getResource(MKTAG('C', 'U', 'R', 'S'), name);
}
// Make sure we got a resource.
if (stream == nullptr) {
error("%s: Reqested Mac cursor %s not found", __func__, name.c_str());
}
Graphics::MacCursor *macCursor = new Graphics::MacCursor();
if (!macCursor->readFromStream(*stream)) {
error("%s: Error parsing cursor %s from stream", __func__, name.c_str());
}
delete stream;
return macCursor;
}
} // End of namespace MediaStation

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/>.
*
*/
#ifndef MEDIASTATION_CURSORS_H
#define MEDIASTATION_CURSORS_H
#include "common/platform.h"
#include "common/scummsys.h"
#include "common/hashmap.h"
#include "common/str.h"
#include "common/macresman.h"
#include "common/formats/winexe.h"
#include "graphics/wincursor.h"
#include "graphics/maccursor.h"
#include "mediastation/clients.h"
#include "mediastation/datafile.h"
namespace MediaStation {
enum CursorManagerCommandType {
kCursorManagerInit = 0x0c,
kCursorManagerNewCursor = 0x15,
kCursorManagerDisposeCursor = 0x16,
};
enum CursorType {
kPlatformCursor = 0,
kResourceCursor = 1,
};
// Media Station stores cursors in the executable as named resources.
class CursorManager : public ParameterClient {
public:
CursorManager(const Common::Path &appName) : _appName(appName) {}
virtual ~CursorManager();
virtual bool attemptToReadFromStream(Chunk &chunk, uint param) override;
void init(Chunk &chunk);
void newCursor(Chunk &chunk);
void disposeCursor(Chunk &chunk);
void newPlatformCursor(uint16 platformCursorId, uint16 cursorId);
void newResourceCursor(uint16 cursorId, const Common::String &resourceName);
// Some engine versions also have newBufferCursor that seems to be unused.
void showCursor();
void hideCursor();
virtual void resetCurrent();
void registerAsPermanent(uint16 id);
void setAsPermanent(uint16 id);
void setAsTemporary(uint16 id);
void unsetPermanent();
void unsetTemporary();
protected:
Common::Path _appName;
uint16 _baseCursorId = 0;
uint16 _maxCursorId = 0;
uint16 _currentCursorId = 0;
uint16 _permanentCursorId = 0;
// The original used an array with computed indices, but we use a hashmap for simplicity.
Common::HashMap<uint16, Graphics::Cursor *> _cursors;
virtual Graphics::Cursor *loadResourceCursor(const Common::String &name) = 0;
void setDefaultCursor();
};
class WindowsCursorManager : public CursorManager {
public:
WindowsCursorManager(const Common::Path &appName);
~WindowsCursorManager() override;
virtual Graphics::Cursor *loadResourceCursor(const Common::String &name) override;
private:
Common::HashMap<Common::String, Graphics::WinCursorGroup *> _cursorGroups;
};
class MacCursorManager : public CursorManager {
public:
explicit MacCursorManager(const Common::Path &appName);
~MacCursorManager() override;
virtual Graphics::Cursor *loadResourceCursor(const Common::String &name) override;
private:
Common::MacResManager *_resFork;
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,331 @@
/* 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/datafile.h"
#include "mediastation/debugchannels.h"
#include "mediastation/mediastation.h"
namespace MediaStation {
void ParameterReadStream::readAndVerifyType(DatumType type) {
DatumType actualType = static_cast<DatumType>(readUint16LE());
if (actualType != type) {
error("%s: Expected datum type %d, got %d (@0x%llx)", __func__, type, actualType, static_cast<long long int>(pos()));
}
}
byte ParameterReadStream::readTypedByte() {
readAndVerifyType(kDatumTypeUint8);
return readByte();
}
uint16 ParameterReadStream::readTypedUint16() {
readAndVerifyType(kDatumTypeUint16);
return readUint16LE();
}
uint32 ParameterReadStream::readTypedUint32() {
readAndVerifyType(kDatumTypeUint32);
return readUint32LE();
}
int8 ParameterReadStream::readTypedSByte() {
readAndVerifyType(kDatumTypeInt8);
return readSByte();
}
int16 ParameterReadStream::readTypedSint16() {
readAndVerifyType(kDatumTypeInt16);
return readSint16LE();
}
int32 ParameterReadStream::readTypedSint32() {
readAndVerifyType(kDatumTypeInt32);
return readSint32LE();
}
float ParameterReadStream::readTypedFloat() {
readAndVerifyType(kDatumTypeFloat);
return readFloatLE();
}
double ParameterReadStream::readTypedDouble() {
readAndVerifyType(kDatumTypeDouble);
return readDoubleLE();
}
Common::String ParameterReadStream::readTypedFilename() {
readAndVerifyType(kDatumTypeFilename);
uint size = readTypedUint32();
return readString('\0', size);
}
Common::Rect ParameterReadStream::readTypedRect() {
readAndVerifyType(kDatumTypeRect);
Common::Point leftTop = readTypedPoint();
Common::Point dimensions = readTypedGraphicSize();
return Common::Rect(leftTop, dimensions.x, dimensions.y);
}
Common::Point ParameterReadStream::readTypedPoint() {
readAndVerifyType(kDatumTypePoint);
int16 x = readTypedGraphicUnit();
int16 y = readTypedGraphicUnit();
return Common::Point(x, y);
}
Common::Point ParameterReadStream::readTypedGraphicSize() {
readAndVerifyType(kDatumTypeGraphicSize);
int16 width = readTypedGraphicUnit();
int16 height = readTypedGraphicUnit();
return Common::Point(width, height);
}
int16 ParameterReadStream::readTypedGraphicUnit() {
readAndVerifyType(kDatumTypeGraphicUnit);
return readSint16LE();
}
double ParameterReadStream::readTypedTime() {
readAndVerifyType(kDatumTypeTime);
return readDoubleLE();
}
Common::String ParameterReadStream::readTypedString() {
readAndVerifyType(kDatumTypeString);
uint size = readTypedUint32();
return readString('\0', size);
}
VersionInfo ParameterReadStream::readTypedVersion() {
readAndVerifyType(kDatumTypeVersion);
VersionInfo version;
version.major = readTypedUint16();
version.minor = readTypedUint16();
version.patch = readTypedUint16();
return version;
}
ChannelIdent ParameterReadStream::readTypedChannelIdent() {
readAndVerifyType(kDatumTypeChannelIdent);
// This one is always BE.
return readUint32BE();
}
Polygon ParameterReadStream::readTypedPolygon() {
Polygon polygon;
uint totalPoints = readTypedUint16();
for (uint i = 0; i < totalPoints; ++i) {
Common::Point point = readTypedGraphicSize();
polygon.push_back(point);
}
return polygon;
}
Chunk::Chunk(Common::SeekableReadStream *stream) : _parentStream(stream) {
_id = _parentStream->readUint32BE();
_length = _parentStream->readUint32LE();
_dataStartOffset = pos();
_dataEndOffset = _dataStartOffset + _length;
debugC(5, kDebugLoading, "Chunk::Chunk(): Got chunk with ID \"%s\" and size 0x%x", tag2str(_id), _length);
}
uint32 Chunk::bytesRemaining() {
return _dataEndOffset - pos();
}
uint32 Chunk::read(void *dataPtr, uint32 dataSize) {
if (pos() > _dataEndOffset) {
uint overrun = pos() - _dataEndOffset;
error("%s: Attempted to read 0x%x bytes at a location 0x%x bytes past end of chunk (@0x%llx)", __func__, dataSize, overrun, static_cast<long long int>(pos()));
} else {
return _parentStream->read(dataPtr, dataSize);
}
}
bool Chunk::seek(int64 offset, int whence) {
bool result = _parentStream->seek(offset, whence);
if (result == false)
return false;
if (pos() < _dataStartOffset) {
uint overrun = _dataStartOffset - offset;
error("Attempted to seek 0x%x bytes before start of chunk (@0x%llx)", overrun, static_cast<long long int>(pos()));
} else if (pos() > _dataEndOffset) {
uint overrun = offset - _dataEndOffset;
error("Attempted to seek 0x%x bytes past end of chunk (@0x%llx)", overrun, static_cast<long long int>(pos()));
}
return true;
}
Subfile::Subfile(Common::SeekableReadStream *stream) : _stream(stream) {
// Verify file signature.
debugC(5, kDebugLoading, "\n*** Subfile::Subfile(): Got new subfile (@0x%llx) ***", static_cast<long long int>(_stream->pos()));
_rootChunk = nextChunk();
if (_rootChunk._id != MKTAG('R', 'I', 'F', 'F'))
error("%s: Expected \"RIFF\" chunk, got %s (@0x%llx)", __func__, tag2str(_rootChunk._id), static_cast<long long int>(_stream->pos()));
_stream->skip(4); // IMTS
// Read the RATE chunk.
// This chunk should always contain just one piece of data,
// the "rate" (whatever that is). Usually it is zero.
// TODO: Figure out what this actually is.
Chunk rateChunk = nextChunk();
if (rateChunk._id != MKTAG('r', 'a', 't', 'e'))
error("%s: Expected \"rate\" chunk, got %s (@0x%llx)", __func__, tag2str(_rootChunk._id), static_cast<long long int>(_stream->pos()));
_rate = _stream->readUint32LE();
// Queue up the first data chunk.
// First, we need to read past the LIST chunk.
nextChunk();
if (_stream->readUint32BE() != MKTAG('d', 'a', 't', 'a'))
error("%s: Expected \"data\" as first bytes of subfile, got %s @0x%llx)", __func__, tag2str(rateChunk._id), static_cast<long long int>(_stream->pos()));
}
Chunk Subfile::nextChunk() {
// Chunks always start on even-indexed bytes.
if (_stream->pos() & 1)
_stream->skip(1);
_currentChunk = Chunk(_stream);
return _currentChunk;
}
bool Subfile::atEnd() {
// There might be a padding byte at the end of the subfile, so
// we need to check for that.
if (_rootChunk.pos() % 2 == 0) {
return _rootChunk.bytesRemaining() == 0;
} else {
return _rootChunk.bytesRemaining() == 1;
}
}
void CdRomStream::openStream(uint streamId) {
const StreamInfo &streamInfo = g_engine->streamInfoForIdent(streamId);
if (streamInfo._fileId == 0) {
error("%s: Stream %d not found in current title", __func__, streamId);
}
const FileInfo &fileInfo = g_engine->fileInfoForIdent(streamInfo._fileId);
if (fileInfo._id == 0) {
error("%s: File %d for stream %d not found in current title", __func__, streamInfo._fileId, streamId);
}
bool requestedStreamAlreadyOpen = isOpen() && _fileId == streamInfo._fileId;
if (requestedStreamAlreadyOpen) {
seek(streamInfo._startOffsetInFile);
} else {
if (isOpen()) {
close();
}
Common::Path filename(fileInfo._name);
if (!open(filename)) {
error("%s: Failed to open %s", __func__, filename.toString().c_str());
}
seek(streamInfo._startOffsetInFile);
_fileId = streamInfo._fileId;
}
}
Subfile CdRomStream::getNextSubfile() {
return Subfile(_handle);
}
void ChannelClient::registerWithStreamManager() {
g_engine->getStreamFeedManager()->registerChannelClient(this);
}
void ChannelClient::unregisterWithStreamManager() {
g_engine->getStreamFeedManager()->unregisterChannelClient(this);
}
ImtStreamFeed::ImtStreamFeed(uint actorId) : StreamFeed(actorId) {
_stream = new CdRomStream();
}
ImtStreamFeed::~ImtStreamFeed() {
delete _stream;
_stream = nullptr;
}
void ImtStreamFeed::closeFeed() {
_stream->closeStream();
g_engine->getDocument()->streamDidClose(_id);
}
void ImtStreamFeed::openFeed(uint streamId, uint startOffset) {
// For CXT files, there is a 0x10-byte header before the first stream, but this header
// isn't actually used by the engine. This header is not present in BOOT.STM.
// [0x00-0x04] Byte order - either II\0\0 for Intel byte order or MM\0\0 for Motorola byte order.
// Motorola byte order never actually seems to be used for data files, even on big-endian
// platforms. Other parts of the engine perform the byte swapping on the fly.
// [0x05-0x08] Unknown.
// [0x09-0x0c] Stream (subfile) count in this file, uint32le.
// [0x0d-0x10] Total file size, uint32le.
// The original had an intermediary StreamProgress class here, with a StreamProgressClient
// class, but there as only
g_engine->getDocument()->streamDidOpen(streamId);
_stream->openStream(streamId);
}
void ImtStreamFeed::readData() {
Subfile subfile = _stream->getNextSubfile();
Chunk chunk = subfile.nextChunk();
g_engine->getDocument()->streamWillRead(_id);
g_engine->readHeaderSections(subfile, chunk);
g_engine->getDocument()->streamDidFinish(_id);
}
StreamFeedManager::~StreamFeedManager() {
for (auto it = _streamFeeds.begin(); it != _streamFeeds.end(); ++it) {
delete it->_value;
}
_streamFeeds.clear();
}
void StreamFeedManager::closeStreamFeed(StreamFeed *streamFeed) {
streamFeed->closeFeed();
_streamFeeds.erase(streamFeed->_id);
delete streamFeed;
}
void StreamFeedManager::registerChannelClient(ChannelClient *client) {
if (_channelClients.getValOrDefault(client->channelIdent()) != nullptr) {
error("%s: Channel ident %d already has a client", __func__, client->channelIdent());
}
_channelClients.setVal(client->channelIdent(), client);
}
void StreamFeedManager::unregisterChannelClient(ChannelClient *client) {
_channelClients.erase(client->channelIdent());
}
ImtStreamFeed *StreamFeedManager::openStreamFeed(uint actorId, uint offsetInStream, uint maxBytesToRead) {
ImtStreamFeed *streamFeed = new ImtStreamFeed(actorId);
streamFeed->openFeed(actorId, offsetInStream);
_streamFeeds.setVal(actorId, streamFeed);
return streamFeed;
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,228 @@
/* 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_DATAFILE_H
#define MEDIASTATION_DATAFILE_H
#include "common/file.h"
#include "common/stream.h"
#include "common/path.h"
#include "common/rect.h"
#include "common/str.h"
namespace MediaStation {
// The version number of this engine,
// in the form 4.0r8 (major . minor r revision).
struct VersionInfo {
uint16 major = 0;
uint16 minor = 0;
uint16 patch = 0;
};
typedef Common::Array<Common::Point> Polygon;
// A Media Station datafile consists of one or more RIFF-style "subfiles". Aside
// from some oddness at the start of the subfile, each subfile is basically
// standard sequence of chunks inside a LIST chunk, like you'd see in any RIFF
// file. These chunks have special IDs:
// - igod: Indicates a chunk that contains metadata about actor(s) in metadata sections.
// - a000, where 000 is a string that represents a 3-digit hexadecimal number.
// Indicates a chunk that contains actor data (sounds and bitmaps).
enum DatumType {
kDatumTypeEmpty = 0x00,
kDatumTypeUint8 = 0x02,
kDatumTypeUint16 = 0x03,
kDatumTypeUint32 = 0x04,
kDatumTypeInt8 = 0x05,
kDatumTypeInt16 = 0x06,
kDatumTypeInt32 = 0x07,
kDatumTypeFloat = 0x08,
kDatumTypeDouble = 0x09,
kDatumTypeFilename = 0x0a,
kDatumTypeRect = 0x0d,
kDatumTypePoint = 0x0e,
kDatumTypeGraphicSize = 0x0f,
kDatumTypeGraphicUnit = 0x10,
kDatumTypeTime = 0x11,
kDatumTypeString = 0x12,
kDatumTypeVersion = 0x13,
kDatumTypeChannelIdent = 0x1b,
kDatumTypePolygon = 0x1d,
};
class ParameterReadStream : public Common::SeekableReadStream {
public:
// Data files are internally little-endian, even on game versions targeting
// big-endian systems. The original engine has code for swapping byte order
// at runtime when needed. All of these internally assume the data files are
// stored little-endian on disk.
byte readTypedByte();
uint16 readTypedUint16();
uint32 readTypedUint32();
int8 readTypedSByte();
int16 readTypedSint16();
int32 readTypedSint32();
float readTypedFloat();
double readTypedDouble();
Common::String readTypedFilename();
Common::Rect readTypedRect();
Common::Point readTypedPoint();
Common::Point readTypedGraphicSize();
int16 readTypedGraphicUnit();
double readTypedTime();
Common::String readTypedString();
VersionInfo readTypedVersion();
uint32 readTypedChannelIdent();
Polygon readTypedPolygon();
private:
void readAndVerifyType(DatumType type);
};
class Chunk : public ParameterReadStream {
public:
Chunk() = default;
Chunk(Common::SeekableReadStream *stream);
uint32 bytesRemaining();
uint32 _id = 0;
uint32 _length = 0;
// ReadStream implementation
virtual bool eos() const { return _parentStream->eos(); };
virtual bool err() const { return _parentStream->err(); };
virtual void clearErr() { _parentStream->clearErr(); };
virtual uint32 read(void *dataPtr, uint32 dataSize);
virtual int64 pos() const { return _parentStream->pos(); };
virtual int64 size() const { return _parentStream->size(); };
virtual bool skip(uint32 offset) { return seek(offset, SEEK_CUR); };
virtual bool seek(int64 offset, int whence = SEEK_SET);
private:
Common::SeekableReadStream *_parentStream = nullptr;
uint32 _dataStartOffset = 0;
uint32 _dataEndOffset = 0;
};
class Subfile {
public:
Subfile() = default;
Subfile(Common::SeekableReadStream *stream);
Chunk nextChunk();
bool atEnd();
Chunk _currentChunk;
uint32 _rate;
private:
Common::SeekableReadStream *_stream = nullptr;
Chunk _rootChunk;
};
// The stream loading class hierarchy presented below is a bit complex for reading directly
// from streams, like we can do on modern computers, without needing to worry about
// buffering from CD-ROM. But we are staying close to the original logic and class
// hierarchy where possible, so some of that original architecture is reflected here.
typedef uint32 ChannelIdent;
class CdRomStream : public Common::File {
public:
CdRomStream() {};
void openStream(uint streamId);
void closeStream() { close(); }
Subfile getNextSubfile();
private:
uint _fileId = 0;
};
class ChannelClient {
public:
virtual ~ChannelClient() {};
void setChannelIdent(ChannelIdent channelIdent) { _channelIdent = channelIdent; }
ChannelIdent channelIdent() const { return _channelIdent; }
virtual void readChunk(Chunk &chunk) {};
void registerWithStreamManager();
void unregisterWithStreamManager();
protected:
ChannelIdent _channelIdent = 0;
};
class StreamFeed {
public:
StreamFeed(uint streamId) : _id(streamId) {};
virtual ~StreamFeed() {};
virtual void openFeed(uint streamId, uint startOffset) = 0;
// The original also has forceCloseFeed, which doesn't do some other cleanup
// that the regular closeFeed does. However, since we are not doing caching and
// some other functionality in the original, we don't need this.
virtual void closeFeed() = 0;
virtual void stopFeed() = 0;
virtual void readData() = 0;
uint _id = 0;
protected:
CdRomStream *_stream = nullptr;
};
class ImtStreamFeed : public StreamFeed {
public:
ImtStreamFeed(uint streamId);
~ImtStreamFeed();
virtual void openFeed(uint streamId, uint startOffset) override;
virtual void closeFeed() override;
// This implementation is currently empty because all this has to do with read timing.
virtual void stopFeed() override {};
virtual void readData() override;
};
class StreamFeedManager {
public:
~StreamFeedManager();
void registerChannelClient(ChannelClient *client);
void unregisterChannelClient(ChannelClient *client);
ChannelClient *channelClientForChannel(uint clientId) { return _channelClients.getValOrDefault(clientId); }
ImtStreamFeed *openStreamFeed(uint streamId, uint offsetInStream = 0, uint maxBytesToRead = 0);
void closeStreamFeed(StreamFeed *streamFeed);
private:
Common::HashMap<uint, StreamFeed *> _streamFeeds;
Common::HashMap<ChannelIdent, ChannelClient *> _channelClients;
};
} // End of namespace MediaStation
#endif

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_DEBUG_CHANNELS_H
#define MEDIASTATION_DEBUG_CHANNELS_H
// This is a convenience so we don't have to include
// two files in every translation unit where we want
// debugging support.
#include "common/debug.h"
namespace MediaStation {
// TODO: Finish comments that describe the various debug levels.
enum DebugChannels {
kDebugGraphics = 1,
kDebugCamera,
kDebugPath,
kDebugScan,
kDebugScript,
kDebugEvents,
kDebugLoading,
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,52 @@
/* 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 "base/plugins.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/md5.h"
#include "common/str-array.h"
#include "common/util.h"
#include "mediastation/detection.h"
#include "mediastation/detection_tables.h"
#include "mediastation/debugchannels.h"
#include "mediastation/mediastation.h"
const DebugChannelDef MediaStationMetaEngineDetection::debugFlagList[] = {
{ MediaStation::kDebugGraphics, "graphics", "Graphics debug level" },
{ MediaStation::kDebugCamera, "camera", "Camera panning debug level" },
{ MediaStation::kDebugPath, "path", "Pathfinding debug level" },
{ MediaStation::kDebugScan, "scan", "Scan for unrecognised games" },
{ MediaStation::kDebugScript, "script", "Enable debug script dump" },
{ MediaStation::kDebugEvents, "events", "Events processing" },
{ MediaStation::kDebugLoading, "loading", "File loading" },
DEBUG_CHANNEL_END
};
MediaStationMetaEngineDetection::MediaStationMetaEngineDetection() : AdvancedMetaEngineDetection(
MediaStation::gameDescriptions, MediaStation::mediastationGames) {
_maxScanDepth = 3;
_directoryGlobs = MediaStation::directoryGlobs;
}
REGISTER_PLUGIN_STATIC(MEDIASTATION_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, MediaStationMetaEngineDetection);

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_DETECTION_H
#define MEDIASTATION_DETECTION_H
#include "engines/advancedDetector.h"
namespace MediaStation {
extern const PlainGameDescriptor mediastationGames[];
extern const ADGameDescription gameDescriptions[];
#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1
} // End of namespace MediaStation
class MediaStationMetaEngineDetection : public AdvancedMetaEngineDetection<ADGameDescription> {
static const DebugChannelDef debugFlagList[];
public:
MediaStationMetaEngineDetection();
~MediaStationMetaEngineDetection() override {}
const char *getName() const override {
return "mediastation";
}
const char *getEngineName() const override {
return "Media Station";
}
const char *getOriginalCopyright() const override {
return "(C) 1994 - 1999 Media Station, Inc.";
}
const DebugChannelDef *getDebugChannels() const override {
return debugFlagList;
}
};
#endif // MEDIASTATION_DETECTION_H

View File

@@ -0,0 +1,730 @@
/* 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/>.
*
*/
namespace MediaStation {
const PlainGameDescriptor mediastationGames[] = {
{ "georgeshrinks", "George Shrinks Interactive Storybook" },
{ "mousecookie", "If You Give a Mouse a Cookie Interactive Storybook" },
{ "lionking", "Disney's Animated Storybook: The Lion King" },
{ "janpienkowski", "Jan Pieńkowski Haunted House" },
{ "lambchop", "Lamb Chop Loves Music" },
{ "frogprince", "Fractured Fairy Tales: The Frog Prince" },
{ "honeytree", "Disney's Animated Storybook: Winnie the Pooh and the Honey Tree" },
{ "notredame", "Disney's Animated Storybook: The Hunchback of Notre Dame" },
{ "puzzlecastle", "Puzzle Castle" },
{ "ibmcrayola", "IBM/Crayola Print Factory" },
{ "ibmcrayolaholiday", "IBM/Crayola Print Factory Holiday Activity" },
{ "101dalmatians", "Disney's Animated Storybook: 101 Dalmatians" },
{ "herculesasb", "Disney's Animated Storybook: Hercules" },
{ "pocahontas", "Disney's Animated Storybook: Pocahontas"},
{ "barbieasrapunzel", "Magic Fairy Tales: Barbie as Rapunzel" },
{ "tonkasearchandrescue", "Tonka Search and Rescue" },
{ "arielstorystudio", "Disney presents Ariel's Story Studio" },
{ "tonkagarage", "Tonka Garage" },
{ "dwpickyeater", "D.W. the Picky Eater" },
{ "tonkaworkshop", "Tonka Workshop" },
{ "tonkaraceway", "Tonka Raceway" },
{ "stuartlittlebigcity", "Stuart Little: Big City Adventures"},
{ nullptr, nullptr }
};
// In these entries, the executable must always be listed first.
// The title version can be obtained by pressing Ctrl-V while running
// the original interpreter. Some titles include a built-in language code
// (e.g. "v1.0/DE" or "v1.0/US") but others do not (e.g. "v1.1").
const ADGameDescription gameDescriptions[] = {
// George Shrinks Interactive Storybook
// This title seems to be Windows-only.
{
"georgeshrinks",
"v1.0",
AD_ENTRY3s(
"GEORGE.EX_", "ae70a2efbe5fbe66ad7bb9f269ea0a2f", 139674, // Packed executable
"BOOT.STM", "5b7c08398fe6ae016db9d94ad9240241", 6744,
"103.CXT", "e7d563ff79f1b1416e5f1e0c803f78ec", 1474802
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// If You Give a Mouse a Cookie Interactive Storybook
{
"mousecookie",
"v2.0",
AD_ENTRY3s(
"MOUSECKE.EXE", "58350e268ec0cdf4fa21281a9d83fd80", 329568,
"BOOT.STM", "11d11b2067519d8368175cc8e8caa94f", 59454,
"100.CXT", "cac48b9bb5f327d035a831cd15f1688c", 1762032
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"mousecookie",
"v2.0",
AD_ENTRY3s(
"If You Give a Mouse a Cookie", "r:d23bc24ab6da4d97547a6f2c35946f12", 265297,
"BOOT.STM", "11d11b2067519d8368175cc8e8caa94f", 59454,
"100.CXT", "cac48b9bb5f327d035a831cd15f1688c", 1762032
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Disney's Animated Storybook: The Lion King
{
"lionking",
"v2.0/GB",
AD_ENTRY3s(
"LIONKING.EXE", "3239451c477eaa16015110502be031a5", 363232,
"BOOT.STM", "dd83fd1fb899b680f00c586404cc7b7c", 23610,
"100.CXT", "d5dc4d49df2ea6f2ff0aa33a3f385506", 1455740
),
Common::EN_GRB,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"lionking",
"v2.0/GB",
AD_ENTRY3s(
"The Lion King", "r:24a4a5f56188c78d2ef16cd1646379f6", 311245,
"BOOT.STM", "dd83fd1fb899b680f00c586404cc7b7c", 23610,
"100.CXT", "d5dc4d49df2ea6f2ff0aa33a3f385506", 1455740
),
Common::EN_GRB,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Jan Pieńkowski Haunted House
{
"janpienkowski",
"v1.0",
AD_ENTRY3s(
"HAUNTED.EXE", "869bcbae21bcfd87a63474f9efb042df", 330336,
"BOOT.STM", "d01f338d6a3912056aff8f9022867bba", 137830,
"100.CXT", "6154f6efef3a8e54476c10d522dcec20", 1164800
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"janpienkowski",
"v1.0",
AD_ENTRY3s(
"Haunted House", "r:9d4e1623d86d0474d3813e2a9ddf51e0", 266166,
"BOOT.STM", "d01f338d6a3912056aff8f9022867bba", 137830,
"100.CXT", "6154f6efef3a8e54476c10d522dcec20", 1164800
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Lamb Chop Loves Music
{
"lambchop",
"v1.0",
AD_ENTRY3s(
"LCMUSIC.EXE", "1830080b410abd103c5064f583bdca1e", 329504,
"BOOT.STM", "c90200e52bcaad52524520d461caef2b", 29884,
"100.CXT", "ce40843604b8c52701694cd543072a88", 3253600
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"lambchop",
"v1.0",
AD_ENTRY3s(
"Lamb Chop Loves Music", "r:5ae7f4d1c5c74470c8f09ca37238c33b", 265388,
"BOOT.STM", "c90200e52bcaad52524520d461caef2b", 29884,
"100.CXT", "ce40843604b8c52701694cd543072a88", 3253600
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Fractured Fairy Tales: The Frog Prince
{
"frogprince",
"v1.1",
AD_ENTRY3s(
"FPRINCE.EXE", "cd7aff763bb4879cc3a11def90dd7cb7", 513984,
"BOOT.STM", "1c6d14c87790d009702be8ba4e4e5906", 13652,
"100.CXT", "a5ec9a32c3741a20b82e1793e76234b2", 1630762
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"frogprince",
"v1.0",
AD_ENTRY3s(
"FPRINCE.EXE", "a8f3a63ef2032de300f46397cea625df", 518400,
"BOOT.STM", "1c6d14c87790d009702be8ba4e4e5906", 13652,
"100.CXT", "a5ec9a32c3741a20b82e1793e76234b2", 1630762
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"frogprince",
"v1.0",
AD_ENTRY3s(
"The Frog Prince", "r:d77fd2a5dfb81a7fde03d175f93244bd", 1005600,
"BOOT.STM", "1c6d14c87790d009702be8ba4e4e5906", 13652,
"100.CXT", "a5ec9a32c3741a20b82e1793e76234b2", 1630762
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Disney's Animated Storybook: Winnie the Pooh and the Honey Tree
{
"honeytree",
"v2.0/US", // Also includes Spanish as an in-game language option.
AD_ENTRY3s(
"WPHTASB.EXE", "916666c49efeeaeae61eb669405fc66f", 433024,
"BOOT.STM", "9b9f528bf9c9b8ebe194b0c47dbe485e", 55422,
"100.CXT", "30f010077fd0489933989a562db81ad6", 1971940
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"honeytree",
"v2.0/US", // Also includes Spanish as an in-game language option.
AD_ENTRY3s(
"Pooh and the Honey Tree", "r:3f0077774418305ccd4c20cb6fa4c765", 764753,
"BOOT.STM", "9b9f528bf9c9b8ebe194b0c47dbe485e", 55422,
"100.CXT", "30f010077fd0489933989a562db81ad6", 1971940
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"honeytree",
"v2.0/SE", // Also includes English as an in-game language option.
AD_ENTRY3s(
"WPHTASB.EXE", "8cbb2b365a1073ca5c8c1fde301094ea", 443008,
"BOOT.STM", "448efdc32f4ce36b5e4f91b34f44437a", 55284,
"100.CXT", "76bd87d2692b6ab7b034bbc50957156b", 1966658
),
Common::SV_SWE,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"honeytree",
"v2.0/SE", // Also includes English as an in-game language option.
AD_ENTRY3s(
"Nalle Puh", "r:3c061bff404de99c69a0b0187018a7da", 765688,
"BOOT.STM", "448efdc32f4ce36b5e4f91b34f44437a", 55284,
"100.CXT", "00239a03730b8e1c3c730050b8872d33", 2267499
),
Common::SV_SWE,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Disney's Animated Storybook: The Hunchback of Notre Dame
{
"notredame",
"v1.0/US",
AD_ENTRY3s(
"HB_ASB.EXE", "f3f2e83562d7941a99d299ae31600f07", 533120,
"BOOT.STM", "7949e1253a62531e53963a2fffe57211", 55300,
"100.CXT", "54c11a94888a1b747e1c8935b7315889", 4766278
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"notredame",
"v1.0/US",
AD_ENTRY3s(
"Hunchback Animated StoryBook", "r:711b5eba6477f801177cd9095a51e1a5", 1044920,
"BOOT.STM", "7949e1253a62531e53963a2fffe57211", 55300,
"100.CXT", "54c11a94888a1b747e1c8935b7315889", 4766278
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Puzzle Castle
{
"puzzlecastle",
"v1.0",
AD_ENTRY3s(
"PZCASTLE.EXE", "ce44597dcbad42f2396d4963c06714d5", 528224,
"BOOT.STM", "7b0faf38da2d76df40b4085eed6f4fc8", 22080,
"100.CXT", "ebc4b6247b742733c81456dfd299aa55", 3346944
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"puzzlecastle",
"v1.0",
AD_ENTRY3s(
"Puzzle Castle", "r:aa2c5c12ec9e0d3cba54efcbd143f8e0", 959690,
"BOOT.STM", "7b0faf38da2d76df40b4085eed6f4fc8", 22080,
"100.CXT", "ebc4b6247b742733c81456dfd299aa55", 3346944
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"puzzlecastle",
"v1.0 Demo",
AD_ENTRY3s(
"DEMO.EXE", "63dedc1e2cdf8a39725ef9ca99273cc4", 514496,
"BOOT.STM", "b7ce005e0d67021f792ebb73e7fbe34c", 5960,
"100.CXT", "cc64a6fcb3af2736d622658cff3ef2b5", 1262
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE | ADGF_DEMO,
GUIO1(GUIO_NOASPECT)
},
{
"puzzlecastle",
"v1.0 Demo",
AD_ENTRY3s(
"Puzzle Castle Demo", "r:f9f4715f39e084ac80e6b222cb69b20e", 959563,
"BOOT.STM", "b7ce005e0d67021f792ebb73e7fbe34c", 5960,
"100.CXT", "cc64a6fcb3af2736d622658cff3ef2b5", 1262
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE | ADGF_DEMO,
GUIO1(GUIO_NOASPECT)
},
// IBM/Crayola Print Factory
{
"ibmcrayola",
"v1.0/US",
AD_ENTRY3s(
"PRINTFAC.EXE", "2571746dcb8b8d386f2ef07255e715ba", 721248,
"BOOT.STM", "359542015c6665c70252cf21a8467cdb", 11044,
"100.CXT", "42bffe4165640dd1e64a6e8565f48af3", 5125226
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"ibmcrayola",
"v1.0/US",
AD_ENTRY3s(
"IBM and Crayola Print Factory", "r:f30fbc530b26feb0ed23164e632c1a88", 1583870,
"BOOT.STM", "359542015c6665c70252cf21a8467cdb", 11044,
"100.CXT", "42bffe4165640dd1e64a6e8565f48af3", 5125226
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// IBM/Crayola Print Factory Holiday Activity Pack
{
"ibmcrayolaholiday",
"v1.0/US",
AD_ENTRY3s(
"HOLIDAY.EXE", "10b70a2cb94f92295d26f43540129f14", 742048,
"BOOT.STM", "50f30298bf700f357d98c4390f75cb7a", 10932,
"100.CXT", "8110f70f1d01d0f42cac9b1bb6d2de12", 4967390
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"ibmcrayolaholiday",
"v1.0/US",
AD_ENTRY3s(
"HOLIDAY.EXE", "r:bf5345cf76a4ee24823634b420739771", 1576023,
"BOOT.STM", "50f30298bf700f357d98c4390f75cb7a", 10932,
"100.CXT", "8110f70f1d01d0f42cac9b1bb6d2de12", 4967390
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Disney's Animated Storybook: 101 Dalmatians
{
"101dalmatians",
"v1.0/US",
AD_ENTRY3s(
"101_ASB.EXE", "42d7d258652bdc7ecd0e39e8b326bc38", 528736,
"BOOT.STM", "ee6725a718cbce640d02acec2b84825f", 47970,
"100.CXT", "2df853283a3fd2d079b06bc27b50527f", 6784502
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"101dalmatians",
"v1.0/US",
AD_ENTRY3s(
"101 Dalmatians StoryBook", "r:9ec3f0b3fd6c28650e00475b6bf08615", 1042456,
"BOOT.STM", "ee6725a718cbce640d02acec2b84825f", 47970,
"100.CXT", "2df853283a3fd2d079b06bc27b50527f", 6784502
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Disney's Animated Storybook: Hercules
{
"herculesasb",
"v1.0/US",
AD_ENTRY3s(
"HERC_ASB.EXE", "23663fabde2db43a2e8f6a23e7495e01", 543040,
"BOOT.STM", "afc773416e46e30873f743e234794957", 26924,
"100.CXT", "56875e1640320909e9697f11b5a8c9a6", 4895998
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"herculesasb",
"v1.0/US",
AD_ENTRY3s(
"Hercules Animated Storybook", "r:d870f6a3ab564f25428c7a7179cc1e53", 1173749,
"BOOT.STM", "afc773416e46e30873f743e234794957", 26924,
"100.CXT", "56875e1640320909e9697f11b5a8c9a6", 4895998
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Disney's Animated Storybook: Pocahontas
{
"pocahontas",
"v1.0/SE", // Also includes English as an in-game language option
AD_ENTRY3s(
"POCA_ASB.EXE", "b5dc94c806c1122a69b19a2ef9113d3c", 389440,
"BOOT.STM", "dfb9353599cf8ca0f95095353041ef54", 337944,
"100.CXT", "7f9fae41a07e8e1cc467d1b2be37ef9d", 1117144
),
Common::SV_SWE,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"pocahontas",
"v1.0/SE", // Also includes English as an in-game language option
AD_ENTRY3s(
"Pocahontas", "r:6b0d52f286954d7dbb5fd703ff696b40", 326402,
"BOOT.STM", "dfb9353599cf8ca0f95095353041ef54", 337944,
"100.CXT", "7f9fae41a07e8e1cc467d1b2be37ef9d", 1117144
),
Common::SV_SWE,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"pocahontas",
"v1.1.01 English",
AD_ENTRY3s(
"POCA_ASB.EXE", "0287ca22a971d305e9a67fc4fc87239d", 367552,
"BOOT.STM", "d35338f9f014b73a8471523f3b141f2d", 324138,
"100.CXT", "8d7b65d4c50515c8b28a91980017d327", 1107900
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"pocahontas",
"v1.1.01 English",
AD_ENTRY3s(
"Pocahontas", "r:6b0d52f286954d7dbb5fd703ff696b40", 319909,
"BOOT.STM", "d35338f9f014b73a8471523f3b141f2d", 324138,
"100.CXT", "8d7b65d4c50515c8b28a91980017d327", 1107900
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Magic Fairy Tales: Barbie as Rapunzel
{
"barbieasrapunzel",
"v1.0",
AD_ENTRY3s(
"RAPUNZEL.EXE", "e47a752fe748258ebc0f5ee6f31b385b", 535840,
"BOOT.STM", "eef6bdf54d2ae25af0ec29361fd4c126", 17530,
"100.CXT", "f0bcc27b61bfb33328db2dd537b2b6e3", 1688902
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"barbieasrapunzel",
"v1.0",
AD_ENTRY3s(
"Rapunzel", "r:b8b5fc7def85c07ed4c673e8a80ed338", 1043127,
"BOOT.STM", "eef6bdf54d2ae25af0ec29361fd4c126", 17530,
"100.CXT", "f0bcc27b61bfb33328db2dd537b2b6e3", 1688902
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Tonka Search and Rescue
{
"tonkasearchandrescue",
"v1.0/US",
AD_ENTRY3s(
"TONKA_SR.EXE", "46037a28e0cbf6df9ed3218e58ee1ae2", 561984, // 32-bit (PE)
"BOOT.STM", "90c5f17734219c3a442316d21e6833f8", 25362,
"100.CXT", "85a05487b6c499ba3ce86d043305ddfd", 6410562
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"tonkasearchandrescue",
"v1.0/US",
AD_ENTRY3s(
"Tonka Search and Rescue", "r:432c202bcc7b259c2927e0f6fb1bbd64", 567011,
"BOOT.STM", "90c5f17734219c3a442316d21e6833f8", 25362,
"100.CXT", "85a05487b6c499ba3ce86d043305ddfd", 6410562
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Disney presents Ariel's Story Studio
{
"arielstorystudio",
"v1.0/US",
AD_ENTRY3s(
"ARIEL_SS.EXE", "bb2afc5205a852e59d77631c454fde5d", 606720,
"BOOT.STM", "297670b908f887ed6c97b364406575d0", 65480,
"100.CXT", "c12c5b784ad931eca293a9816c11043b", 6532022
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"arielstorystudio",
"v1.0/US",
AD_ENTRY3s(
"Ariel's Story Studio", "r:7a21e3080828b50c43e4b7805617db9c", 1523037,
"BOOT.STM", "297670b908f887ed6c97b364406575d0", 65480,
"100.CXT", "c12c5b784ad931eca293a9816c11043b", 6532022
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"arielstorystudio",
"v1.1/US",
AD_ENTRY3s(
"MERMAID.EXE", "2eabe2910cf5a2df32dcc889ebd90cea", 634240,
"BOOT.STM", "7d53a551efde620fe5b332d7b1f009ab", 65450,
"100.CXT", "993252bca0aa6791ca3da30b1ae6f5f8", 6532022
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Tonka Garage
// This title seems to be Windows-only.
{
"tonkagarage",
"v1.1/US",
AD_ENTRY3s(
"TONKA_GR.EXE", "4e7e75ac11c996454b334f9add38c691", 1297408, // 32-bit (PE)
"BOOT.STM", "fc8863bb302e94d3b778b3a97556601b", 25208,
"100.CXT", "13683c2a06275920181d9dda5b2b69e7", 2691398
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// D.W. the Picky Eater (Living Books)
{
"dwpickyeater",
"v1.0/US (32-bit)",
AD_ENTRY3s(
"DW_32.EXE", "3612aa19a2809f9cb6ee48046e5d7068", 1079296, // 32-bit (PE)
"BOOT.STM", "80cc94e3e894ee8c5a22a9c07a33d891", 26402,
"100.CXT", "e65e359ab25d7a639cf369a01b9a21c0", 2163750
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"dwpickyeater",
"v1.0/US (16-bit)",
AD_ENTRY3s(
"DW_16.EXE", "56553be9eee6130b4458fe9dfa2bcacf", 1007184,
"BOOT.STM", "80cc94e3e894ee8c5a22a9c07a33d891", 26402,
"100.CXT", "e65e359ab25d7a639cf369a01b9a21c0", 2163750
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
{
"dwpickyeater",
"v1.0/US",
AD_ENTRY3s(
"D.W. the Picky Eater", "r:4f85d13c1d241ffd37787884d0da2aef", 2502801,
"BOOT.STM", "80cc94e3e894ee8c5a22a9c07a33d891", 26402,
"100.CXT", "e65e359ab25d7a639cf369a01b9a21c0", 2163750
),
Common::EN_USA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Tonka Workshop
// This title seems to be Windows-only.
{
"tonkaworkshop",
"v1.0/US",
AD_ENTRY3s(
"TONKA_W.EXE", "f3e480c57967093b87db68cb8f3f3a18", 1097728, // 32-bit (PE)
"BOOT.STM", "15e6d32925f557f3196fd0bb79b25375", 38190,
"100.CXT", "1cb35998f2e044eee59a96120b3bda6c", 2691398
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Tonka Raceway
// This title seems to be Windows-only.
{
"tonkaraceway",
"v1.0/US",
AD_ENTRY3s(
"TONKA_RACEWAY.EXE", "cccd33d4d9e824bada6a1ca115794226", 1735680, // 32-bit (PE)
"BOOT.STM", "da512cb9bcd18465294e544ed790881c", 12272,
"100.CXT", "30802327b29fbfa722a707c3d3b0f8f8", 2691398
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
// Stuart Little: Big City Adventures
// This title seems to be Windows-only.
{
"stuartlittlebigcity",
"v1.0/US",
AD_ENTRY3s(
"STUARTCD.EXE", "8aaa593c9a1a17a0e41f424d046b3de8", 1191936, // 32-bit (PE)
"BOOT.STM", "992787bf30104a4b7aa2ead64dda21ff", 10974,
"100.CXT", "21f44a1d1de6abf8bd67341c155dfead", 2691398
),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NOASPECT)
},
AD_TABLE_END_MARKER
};
} // End of namespace MediaStation

View File

@@ -0,0 +1,763 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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_DISSOLVE_PATTERNS_H
#define MEDIASTATION_DISSOLVE_PATTERNS_H
namespace MediaStation {
static const byte DISSOLVE_PATTERN_00[] = {
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
static const byte DISSOLVE_PATTERN_01[] = {
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
};
static const byte DISSOLVE_PATTERN_02[] = {
1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
};
static const byte DISSOLVE_PATTERN_03[] = {
1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
};
static const byte DISSOLVE_PATTERN_04[] = {
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
};
static const byte DISSOLVE_PATTERN_05[] = {
1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
};
static const byte DISSOLVE_PATTERN_06[] = {
1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1,
1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0,
0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1,
1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0,
0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1,
1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1,
1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1,
};
static const byte DISSOLVE_PATTERN_07[] = {
1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1,
1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0,
0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0,
0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0,
};
static const byte DISSOLVE_PATTERN_08[] = {
1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0,
0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1,
1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0,
0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
};
static const byte DISSOLVE_PATTERN_09[] = {
1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
};
static const byte DISSOLVE_PATTERN_0a[] = {
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0,
1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,
};
static const byte DISSOLVE_PATTERN_0b[] = {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
};
static const byte DISSOLVE_PATTERN_0c[] = {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
};
static const byte DISSOLVE_PATTERN_0d[] = {
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0,
1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,
};
static const byte DISSOLVE_PATTERN_0e[] = {
1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
};
static const byte DISSOLVE_PATTERN_0f[] = {
1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0,
0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1,
1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0,
0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
};
static const byte DISSOLVE_PATTERN_10[] = {
1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1,
1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0,
0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0,
0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0,
};
static const byte DISSOLVE_PATTERN_11[] = {
1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1,
1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0,
0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1,
1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0,
0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1,
1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1,
1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1,
};
static const byte DISSOLVE_PATTERN_12[] = {
1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
};
static const byte DISSOLVE_PATTERN_13[] = {
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
};
static const byte DISSOLVE_PATTERN_14[] = {
1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
};
static const byte DISSOLVE_PATTERN_15[] = {
1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
};
static const byte DISSOLVE_PATTERN_16[] = {
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
};
static const byte DISSOLVE_PATTERN_17[] = {
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
static const byte DISSOLVE_PATTERN_18[] = {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
};
struct DissolvePattern {
uint16 widthHeight;
uint16 threshold;
const byte *data;
};
static const byte DISSOLVE_PATTERN_COUNT = 0x19;
static const DissolvePattern DISSOLVE_PATTERNS[] = {
{0x19, 1, DISSOLVE_PATTERN_00},
{0x19, 1, DISSOLVE_PATTERN_01},
{0x19, 1, DISSOLVE_PATTERN_02},
{0x19, 1, DISSOLVE_PATTERN_03},
{0x19, 1, DISSOLVE_PATTERN_04},
{0x19, 1, DISSOLVE_PATTERN_05},
{0x19, 1, DISSOLVE_PATTERN_06},
{0x19, 1, DISSOLVE_PATTERN_07},
{0x19, 1, DISSOLVE_PATTERN_08},
{0x19, 1, DISSOLVE_PATTERN_09},
{0x19, 1, DISSOLVE_PATTERN_0a},
{0x19, 1, DISSOLVE_PATTERN_0b},
{0x19, 0, DISSOLVE_PATTERN_0c},
{0x19, 0, DISSOLVE_PATTERN_0d},
{0x19, 0, DISSOLVE_PATTERN_0e},
{0x19, 0, DISSOLVE_PATTERN_0f},
{0x19, 0, DISSOLVE_PATTERN_10},
{0x19, 0, DISSOLVE_PATTERN_11},
{0x19, 0, DISSOLVE_PATTERN_12},
{0x19, 0, DISSOLVE_PATTERN_13},
{0x19, 0, DISSOLVE_PATTERN_14},
{0x19, 0, DISSOLVE_PATTERN_15},
{0x19, 0, DISSOLVE_PATTERN_16},
{0x19, 0, DISSOLVE_PATTERN_17},
{0x18, 1, DISSOLVE_PATTERN_18},
};
} // End of namespace MediaStation
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,269 @@
/* 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_GRAPHICS_H
#define MEDIASTATION_GRAPHICS_H
#include "common/rect.h"
#include "common/array.h"
#include "common/stack.h"
#include "graphics/managed_surface.h"
#include "graphics/screen.h"
#include "mediastation/clients.h"
#include "mediastation/mediascript/scriptvalue.h"
namespace MediaStation {
class MediaStationEngine;
struct DissolvePattern;
class Bitmap;
enum BlitMode {
kUncompressedBlit = 0x00,
kRle8Blit = 0x01,
kClipEnabled = 0x04,
kUncompressedTransparentBlit = 0x08,
kPartialDissolve = 0x10,
kFullDissolve = 0x20,
kCccBlit = 0x40,
kCccTransparentBlit = 0x80
};
enum TransitionType {
kTransitionFadeToBlack = 300,
kTransitionFadeToPalette = 301,
kTransitionSetToPalette = 302,
kTransitionSetToBlack = 303,
kTransitionFadeToColor = 304,
kTransitionSetToColor = 305,
kTransitionSetToPercentOfPalette = 306,
kTransitionFadeToPaletteObject = 307,
kTransitionSetToPaletteObject = 308,
kTransitionSetToPercentOfPaletteObject = 309,
kTransitionColorShiftCurrentPalette = 310,
kTransitionCircleOut = 328
};
enum VideoDisplayManagerSectionType {
kVideoDisplayManagerUpdateDirty = 0x578,
kVideoDisplayManagerUpdateAll = 0x579,
kVideoDisplayManagerEffectTransition = 0x57a,
kVideoDisplayManagerSetTime = 0x57b,
kVideoDisplayManagerLoadPalette = 0x5aa,
};
class Region {
public:
void addRect(const Common::Rect &rect);
bool intersects(const Common::Rect &rect);
void operator&=(const Common::Rect &rect);
void operator+=(const Common::Point &point);
Common::Array<Common::Rect> _rects;
Common::Rect _bounds;
};
class Clip {
public:
Clip() {}
Clip(const Common::Rect &rect);
void addToRegion(const Region &region);
void addToRegion(const Common::Rect &rect);
bool clipIntersectsRect(const Common::Rect &rect);
void intersectWithRegion(const Common::Rect &rect);
void makeEmpty();
Region _region;
Common::Rect _bounds;
};
class DisplayContext {
public:
bool clipIsEmpty();
void intersectClipWith(const Common::Rect &rect);
bool rectIsInClip(const Common::Rect &rect);
void setClipTo(Region region);
void emptyCurrentClip();
void addClip();
Clip *currentClip();
Clip *previousClip();
void verifyClipSize();
// These are not named as such in the original, but are helper functions
// for things that likely were macros or something similar in the original.
void pushOrigin();
void popOrigin();
Common::Point _origin;
Graphics::ManagedSurface *_destImage = nullptr;
private:
Common::Stack<Common::Point> _origins;
Common::Stack<Clip> _clips;
};
class VideoDisplayManager : public ParameterClient {
public:
VideoDisplayManager(MediaStationEngine *vm);
~VideoDisplayManager();
virtual bool attemptToReadFromStream(Chunk &chunk, uint sectionType) override;
void updateScreen() { _screen->update(); }
Graphics::Palette *getRegisteredPalette() { return _registeredPalette; }
void setRegisteredPalette(Graphics::Palette *palette) { _registeredPalette = palette; }
void imageBlit(
Common::Point destinationPoint,
const Bitmap *image,
double dissolveFactor,
DisplayContext *displayContext,
Graphics::ManagedSurface *destinationImage = nullptr);
void imageDeltaBlit(
Common::Point deltaFramePos,
const Common::Point &keyFrameOffset,
const Bitmap *deltaFrame,
const Bitmap *keyFrame,
const double dissolveFactor,
DisplayContext *displayContext);
void effectTransition(Common::Array<ScriptValue> &args);
void setTransitionOnSync(Common::Array<ScriptValue> &args) { _scheduledTransitionOnSync = args; }
void doTransitionOnSync();
void performUpdateDirty();
void performUpdateAll();
void setGammaValues(double red, double green, double blue);
void getDefaultGammaValues(double &red, double &green, double &blue);
void getGammaValues(double &red, double &green, double &blue);
DisplayContext _displayContext;
private:
MediaStationEngine *_vm = nullptr;
Graphics::Screen *_screen = nullptr;
Graphics::Palette *_registeredPalette = nullptr;
Common::Array<ScriptValue> _scheduledTransitionOnSync;
double _defaultTransitionTime = 0.0;
const double DEFAULT_GAMMA_VALUE = 1.0;
double _redGamma = 1.0;
double _greenGamma = 1.0;
double _blueGamma = 1.0;
void readAndEffectTransition(Chunk &chunk);
void readAndRegisterPalette(Chunk &chunk);
// Blitting methods.
// blitRectsClip encompasses the functionality of both opaqueBlitRectsClip
// and transparentBlitRectsClip in the disasm.
void blitRectsClip(
Graphics::ManagedSurface *dest,
const Common::Point &destLocation,
const Graphics::ManagedSurface &source,
const Common::Array<Common::Rect> &dirtyRegion);
void rleBlitRectsClip(
Graphics::ManagedSurface *dest,
const Common::Point &destLocation,
const Bitmap *source,
const Common::Array<Common::Rect> &dirtyRegion);
Graphics::ManagedSurface decompressRle8Bitmap(
const Bitmap *source,
const Graphics::ManagedSurface *keyFrame = nullptr,
const Common::Point *keyFrameOffset = nullptr);
void dissolveBlitRectsClip(
Graphics::ManagedSurface *dest,
const Common::Point &destPos,
const Bitmap *source,
const Common::Array<Common::Rect> &dirtyRegion,
const uint dissolveFactor);
void dissolveBlit1Rect(
Graphics::ManagedSurface *dest,
const Common::Rect &areaToRedraw,
const Common::Point &originOnScreen,
const Bitmap *source,
const Common::Rect &dirtyRegion,
const DissolvePattern &pattern);
void fullDeltaRleBlitRectsClip(
Graphics::ManagedSurface *destinationImage,
const Common::Point &deltaFramePos,
const Common::Point &keyFrameOffset,
const Bitmap *deltaFrame,
const Bitmap *keyFrame,
const Common::Array<Common::Rect> &dirtyRegion);
void deltaRleBlitRectsClip(
Graphics::ManagedSurface *destinationImage,
const Common::Point &deltaFramePos,
const Bitmap *deltaFrame,
const Bitmap *keyFrame,
const Common::Array<Common::Rect> &dirtyRegion);
void deltaRleBlit1Rect(
Graphics::ManagedSurface *destinationImage,
const Common::Point &destinationPoint,
const Bitmap *sourceImage,
const Bitmap *deltaImage,
const Common::Rect &dirtyRect);
// Transition methods.
const double DEFAULT_FADE_TRANSITION_TIME_IN_SECONDS = 0.5;
const byte DEFAULT_PALETTE_TRANSITION_START_INDEX = 0x01;
const byte DEFAULT_PALETTE_TRANSITION_COLOR_COUNT = 0xFE;
void fadeToBlack(Common::Array<ScriptValue> &args);
void fadeToRegisteredPalette(Common::Array<ScriptValue> &args);
void setToRegisteredPalette(Common::Array<ScriptValue> &args);
void setToBlack(Common::Array<ScriptValue> &args);
void fadeToColor(Common::Array<ScriptValue> &args);
void setToColor(Common::Array<ScriptValue> &args);
void setToPercentOfPalette(Common::Array<ScriptValue> &args);
void fadeToPaletteObject(Common::Array<ScriptValue> &args);
void setToPaletteObject(Common::Array<ScriptValue> &args);
void setToPercentOfPaletteObject(Common::Array<ScriptValue> &args);
void colorShiftCurrentPalette(Common::Array<ScriptValue> &args);
void circleOut(Common::Array<ScriptValue> &args);
void _setPalette(Graphics::Palette &palette, uint startIndex, uint colorCount);
void _setPaletteToColor(Graphics::Palette &targetPalette, byte r, byte g, byte b);
uint _limitColorRange(uint &startIndex, uint &colorCount);
byte _interpolateColorComponent(byte source, byte target, double progress);
void _fadeToColor(byte r, byte g, byte b, double fadeTime, uint startIndex, uint colorCount);
void _setToColor(byte r, byte g, byte b, uint startIndex, uint colorCount);
void _setPercentToColor(double percent, byte r, byte g, byte b, uint startIndex, uint colorCount);
void _fadeToPalette(double fadeTime, Graphics::Palette &targetPalette, uint startIndex, uint colorCount);
void _setToPercentPalette(double percent, Graphics::Palette &currentPalette, Graphics::Palette &targetPalette,
uint startIndex, uint colorCount);
void _fadeToRegisteredPalette(double fadeTime, uint startIndex, uint colorCount);
void _setToRegisteredPalette(uint startIndex, uint colorCount);
void _colorShiftCurrentPalette(uint startIndex, uint shiftAmount, uint colorCount);
void _fadeToPaletteObject(uint paletteId, double fadeTime, uint startIndex, uint colorCount);
void _setToPaletteObject(uint paletteId, uint startIndex, uint colorCount);
void _setPercentToPaletteObject(double percent, uint paletteId, uint startIndex, uint colorCount);
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,598 @@
/* 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 "common/ptr.h"
#include "mediastation/mediastation.h"
#include "mediastation/mediascript/codechunk.h"
#include "mediastation/mediascript/collection.h"
#include "mediastation/debugchannels.h"
namespace MediaStation {
CodeChunk::CodeChunk(Chunk &chunk) {
uint lengthInBytes = chunk.readTypedUint32();
debugC(5, kDebugLoading, "CodeChunk::CodeChunk(): Length 0x%x (@0x%llx)", lengthInBytes, static_cast<long long int>(chunk.pos()));
_bytecode = static_cast<ParameterReadStream *>(chunk.readStream(lengthInBytes));
}
ScriptValue CodeChunk::executeNextBlock() {
uint blockSize = _bytecode->readTypedUint32();
int64 startingPos = _bytecode->pos();
debugC(7, kDebugScript, "%s: Entering new block (blockSize: %d, startingPos: %lld)",
__func__, blockSize, static_cast<long long int>(startingPos));
ScriptValue returnValue;
ExpressionType expressionType = static_cast<ExpressionType>(_bytecode->readTypedUint16());
while (expressionType != kExpressionTypeEmpty && !_returnImmediately) {
returnValue = evaluateExpression(expressionType);
expressionType = static_cast<ExpressionType>(_bytecode->readTypedUint16());
if (expressionType == kExpressionTypeEmpty) {
debugC(7, kDebugScript, "%s: Done executing block due to end of chunk", __func__);
}
if (_returnImmediately) {
debugC(7, kDebugScript, "%s: Done executing block due to script requesting immediate return", __func__);
}
}
// Verify we consumed the right number of script bytes.
if (!_returnImmediately) {
uint bytesRead = _bytecode->pos() - startingPos;
if (bytesRead != blockSize) {
error("%s: Expected to have read %d script bytes, actually read %d", __func__, blockSize, bytesRead);
}
}
return returnValue;
}
void CodeChunk::skipNextBlock() {
uint lengthInBytes = _bytecode->readTypedUint32();
_bytecode->skip(lengthInBytes);
}
ScriptValue CodeChunk::execute(Common::Array<ScriptValue> *args) {
_args = args;
ScriptValue returnValue = executeNextBlock();
// Rewind the stream once we're finished, in case we need to execute
// this code again!
_bytecode->seek(0);
_returnImmediately = false;
_locals.clear();
// We don't own the args, so we will prevent a potentially out-of-scope
// variable from being re-accessed.
_args = nullptr;
return returnValue;
}
ScriptValue CodeChunk::evaluateExpression() {
ExpressionType expressionType = static_cast<ExpressionType>(_bytecode->readTypedUint16());
ScriptValue returnValue = evaluateExpression(expressionType);
return returnValue;
}
ScriptValue CodeChunk::evaluateExpression(ExpressionType expressionType) {
debugCN(5, kDebugScript, "(%s) ", expressionTypeToStr(expressionType));
ScriptValue returnValue;
switch (expressionType) {
case kExpressionTypeEmpty:
break;
case kExpressionTypeOperation:
returnValue = evaluateOperation();
break;
case kExpressionTypeValue:
returnValue = evaluateValue();
break;
case kExpressionTypeVariable:
returnValue = evaluateVariable();
break;
default:
error("%s: Got unimplemented expression type %s (%d)", __func__,
expressionTypeToStr(expressionType), static_cast<uint>(expressionType));
}
return returnValue;
}
ScriptValue CodeChunk::evaluateOperation() {
Opcode opcode = static_cast<Opcode>(_bytecode->readTypedUint16());
debugCN(5, kDebugScript, "%s ", opcodeToStr(opcode));
ScriptValue returnValue;
switch (opcode) {
case kOpcodeIf:
evaluateIf();
break;
case kOpcodeIfElse:
evaluateIfElse();
break;
case kOpcodeAssignVariable:
evaluateAssign();
break;
case kOpcodeOr:
case kOpcodeXor:
case kOpcodeAnd:
case kOpcodeEquals:
case kOpcodeNotEquals:
case kOpcodeLessThan:
case kOpcodeGreaterThan:
case kOpcodeLessThanOrEqualTo:
case kOpcodeGreaterThanOrEqualTo:
case kOpcodeAdd:
case kOpcodeSubtract:
case kOpcodeMultiply:
case kOpcodeDivide:
case kOpcodeModulo:
returnValue = evaluateBinaryOperation(opcode);
break;
case kOpcodeNegate:
returnValue = evaluateUnaryOperation();
break;
case kOpcodeCallFunction:
returnValue = evaluateFunctionCall();
break;
case kOpcodeCallMethod:
returnValue = evaluateMethodCall();
break;
case kOpcodeDeclareLocals:
evaluateDeclareLocals();
break;
case kOpcodeReturn:
returnValue = evaluateReturn();
break;
case kOpcodeReturnNoValue:
evaluateReturnNoValue();
break;
case kOpcodeWhile:
evaluateWhileLoop();
break;
case kOpcodeCallFunctionInVariable:
returnValue = evaluateFunctionCall(true);
break;
case kOpcodeCallMethodInVariable:
returnValue = evaluateMethodCall(true);
break;
default:
error("%s: Got unimplemented opcode %s (%d)", __func__, opcodeToStr(opcode), static_cast<uint>(opcode));
}
return returnValue;
}
ScriptValue CodeChunk::evaluateValue() {
OperandType operandType = static_cast<OperandType>(_bytecode->readTypedUint16());
debugCN(5, kDebugScript, "%s ", operandTypeToStr(operandType));
ScriptValue returnValue;
switch (operandType) {
case kOperandTypeBool: {
int b = _bytecode->readTypedByte();
if (b != 0 && b != 1) {
error("%s: Got invalid literal bool value %d", __func__, b);
}
debugC(5, kDebugScript, "%d ", b);
returnValue.setToBool(b == 1 ? true : false);
return returnValue;
}
case kOperandTypeFloat: {
double f = _bytecode->readTypedDouble();
debugC(5, kDebugScript, "%f ", f);
returnValue.setToFloat(f);
return returnValue;
}
case kOperandTypeInt: {
int i = _bytecode->readTypedSint32();
debugC(5, kDebugScript, "%d ", i);
// Ints are stored internally as doubles.
returnValue.setToFloat(static_cast<double>(i));
return returnValue;
}
case kOperandTypeString: {
// This is indeed a raw string, not a string wrapped in a datum!
uint size = _bytecode->readTypedUint16();
Common::String string = _bytecode->readString('\0', size);
debugC(5, kDebugScript, "%s ", string.c_str());
returnValue.setToString(string);
return returnValue;
}
case kOperandTypeParamToken: {
uint literal = _bytecode->readTypedUint16();
debugC(5, kDebugScript, "%d ", literal);
returnValue.setToParamToken(literal);
return returnValue;
}
case kOperandTypeActorId: {
uint actorId = _bytecode->readTypedUint16();
debugC(5, kDebugScript, "%d ", actorId);
returnValue.setToActorId(actorId);
return returnValue;
}
case kOperandTypeTime: {
double d = _bytecode->readTypedTime();
debugC(5, kDebugScript, "%f ", d);
returnValue.setToTime(d);
return returnValue;
}
case kOperandTypeVariable: {
returnValue = ScriptValue(_bytecode);
return returnValue;
}
case kOperandTypeFunctionId: {
uint functionId = _bytecode->readTypedUint16();
debugC(5, kDebugScript, "%d ", functionId);
returnValue.setToFunctionId(functionId);
return returnValue;
}
case kOperandTypeMethodId: {
BuiltInMethod methodId = static_cast<BuiltInMethod>(_bytecode->readTypedUint16());
debugC(5, kDebugScript, "%s ", builtInMethodToStr(methodId));
returnValue.setToMethodId(methodId);
return returnValue;
}
default:
error("%s: Got unknown ScriptValue type %s (%d)", __func__, operandTypeToStr(operandType), static_cast<uint>(operandType));
}
}
ScriptValue CodeChunk::evaluateVariable() {
ScriptValue *variable = readAndReturnVariable();
return *variable;
}
ScriptValue *CodeChunk::readAndReturnVariable() {
uint id = _bytecode->readTypedUint16();
VariableScope scope = static_cast<VariableScope>(_bytecode->readTypedUint16());
debugC(5, kDebugScript, "%d (%s)", id, variableScopeToStr(scope));
ScriptValue returnValue;
switch (scope) {
case kVariableScopeGlobal: {
ScriptValue *variable = g_engine->getVariable(id);
if (variable == nullptr) {
error("%s: Global variable %d doesn't exist", __func__, id);
}
return variable;
}
case kVariableScopeLocal: {
uint index = id - 1;
return &_locals.operator[](index);
}
case kVariableScopeIndirectParameter: {
ScriptValue indexValue = evaluateExpression();
uint index = static_cast<uint>(indexValue.asFloat() + id);
return &_args->operator[](index);
}
case kVariableScopeParameter: {
uint index = id - 1;
if (_args == nullptr) {
error("%s: Requested a parameter in a code chunk that has no parameters", __func__);
}
return &_args->operator[](index);
}
default:
error("%s: Got unknown variable scope %s (%d)", __func__, variableScopeToStr(scope), static_cast<uint>(scope));
}
}
void CodeChunk::evaluateIf() {
debugCN(5, kDebugScript, "\n condition: ");
ScriptValue condition = evaluateExpression();
if (condition.getType() != kScriptValueTypeBool) {
error("%s: Expected bool condition, got %s", __func__, scriptValueTypeToStr(condition.getType()));
}
if (condition.asBool()) {
executeNextBlock();
} else {
skipNextBlock();
}
}
void CodeChunk::evaluateIfElse() {
debugCN(5, kDebugScript, "\n condition: ");
ScriptValue condition = evaluateExpression();
if (condition.getType() != kScriptValueTypeBool) {
error("%s: Expected bool condition, got %s", __func__, scriptValueTypeToStr(condition.getType()));
}
if (condition.asBool()) {
executeNextBlock();
skipNextBlock();
} else {
skipNextBlock();
executeNextBlock();
}
}
ScriptValue CodeChunk::evaluateAssign() {
debugCN(5, kDebugScript, "Variable ");
ScriptValue *targetVariable = readAndReturnVariable();
debugC(5, kDebugScript, " Value: ");
ScriptValue value = evaluateExpression();
if (value.getType() == kScriptValueTypeEmpty) {
error("%s: Attempt to assign an empty value to a variable", __func__);
}
if (targetVariable != nullptr) {
*targetVariable = value;
return value;
} else {
error("%s: Attempt to assign to null variable", __func__);
}
}
ScriptValue CodeChunk::evaluateBinaryOperation(Opcode op) {
debugCN(5, kDebugScript, "\n lhs: ");
ScriptValue value1 = evaluateExpression();
debugCN(5, kDebugScript, " rhs: ");
ScriptValue value2 = evaluateExpression();
ScriptValue returnValue;
switch (op) {
case kOpcodeOr:
returnValue.setToBool(value1 || value2);
break;
case kOpcodeXor:
returnValue.setToBool(value1 ^ value2);
break;
case kOpcodeAnd:
returnValue.setToBool(value1 && value2);
break;
case kOpcodeEquals:
returnValue.setToBool(value1 == value2);
break;
case kOpcodeNotEquals:
returnValue.setToBool(value1 != value2);
break;
case kOpcodeLessThan:
returnValue.setToBool(value1 < value2);
break;
case kOpcodeGreaterThan:
returnValue.setToBool(value1 > value2);
break;
case kOpcodeLessThanOrEqualTo:
returnValue.setToBool(value1 <= value2);
break;
case kOpcodeGreaterThanOrEqualTo:
returnValue.setToBool(value1 >= value2);
break;
case kOpcodeAdd:
returnValue = value1 + value2;
break;
case kOpcodeSubtract:
returnValue = value1 - value2;
break;
case kOpcodeMultiply:
returnValue = value1 * value2;
break;
case kOpcodeDivide:
returnValue = value1 / value2;
break;
case kOpcodeModulo:
returnValue = value1 % value2;
break;
default:
error("%s: Got unknown binary operation opcode %s", __func__, opcodeToStr(op));
}
return returnValue;
}
ScriptValue CodeChunk::evaluateUnaryOperation() {
// The only supported unary operation seems to be negation.
ScriptValue value = evaluateExpression();
debugCN(5, kDebugScript, " value: ");
return -value;
}
ScriptValue CodeChunk::evaluateFunctionCall(bool isIndirect) {
uint functionId, paramCount = 0;
if (isIndirect) {
paramCount = _bytecode->readTypedUint16();
ScriptValue value = evaluateExpression();
functionId = value.asFunctionId();
} else {
functionId = _bytecode->readTypedUint16();
paramCount = _bytecode->readTypedUint16();
}
return evaluateFunctionCall(functionId, paramCount);
}
ScriptValue CodeChunk::evaluateFunctionCall(uint functionId, uint paramCount) {
debugC(5, kDebugScript, "%d (%d params)", functionId, paramCount);
Common::Array<ScriptValue> args;
for (uint i = 0; i < paramCount; i++) {
debugCN(5, kDebugScript, " Param %d: ", i);
ScriptValue arg = evaluateExpression();
args.push_back(arg);
}
ScriptValue returnValue = g_engine->getFunctionManager()->call(functionId, args);
return returnValue;
}
ScriptValue CodeChunk::evaluateMethodCall(bool isIndirect) {
BuiltInMethod method;
uint paramCount = 0;
if (isIndirect) {
paramCount = _bytecode->readTypedUint16();
ScriptValue value = evaluateExpression();
method = value.asMethodId();
} else {
method = static_cast<BuiltInMethod>(_bytecode->readTypedUint16());
paramCount = _bytecode->readTypedUint16();
}
return evaluateMethodCall(method, paramCount);
}
ScriptValue CodeChunk::evaluateMethodCall(BuiltInMethod method, uint paramCount) {
// In Media Station, all methods are built-in - there aren't
// custom objects or methods individual titles can
// define. Functions, however, CAN be title-defined.
// But here, we're only looking for built-in methods.
debugC(5, kDebugScript, "%s (%d params)", builtInMethodToStr(method), paramCount);
debugCN(5, kDebugScript, " Self: ");
ScriptValue target = evaluateExpression();
Common::Array<ScriptValue> args;
for (uint i = 0; i < paramCount; i++) {
debugCN(5, kDebugScript, " Param %d: ", i);
ScriptValue arg = evaluateExpression();
args.push_back(arg);
}
ScriptValue returnValue;
switch (target.getType()) {
case kScriptValueTypeActorId: {
if (target.asActorId() == 0) {
// It seems to be valid to call a method on a null actor ID, in
// which case nothing happens. Still issue warning for traceability.
warning("%s: Attempt to call method on a null actor ID", __func__);
return returnValue;
} else {
// This is a regular actor that we can process directly.
uint actorId = target.asActorId();
Actor *targetActor = g_engine->getActorById(actorId);
if (targetActor == nullptr) {
error("%s: Attempt to call method on actor ID %d, which isn't loaded", __func__, target.asActorId());
}
returnValue = targetActor->callMethod(method, args);
return returnValue;
}
}
case kScriptValueTypeCollection: {
Common::SharedPtr<Collection> collection = target.asCollection();
returnValue = collection->callMethod(method, args);
return returnValue;
}
default:
error("Attempt to call method on unimplemented value type %s (%d)",
scriptValueTypeToStr(target.getType()), static_cast<uint>(target.getType()));
}
}
void CodeChunk::evaluateDeclareLocals() {
uint localVariableCount = _bytecode->readTypedUint16();
if (localVariableCount <= 0) {
error("Got non-positive local variable count");
}
debugC(5, kDebugScript, "%d", localVariableCount);
_locals = Common::Array<ScriptValue>(localVariableCount);
}
ScriptValue CodeChunk::evaluateReturn() {
ScriptValue returnValue = evaluateExpression();
_returnImmediately = true;
return returnValue;
}
void CodeChunk::evaluateReturnNoValue() {
_returnImmediately = true;
}
void CodeChunk::evaluateWhileLoop() {
uint loopStartPosition = _bytecode->pos();
uint iterationCount = 0;
while (true) {
// Seek to the top of the loop bytecode.
_bytecode->seek(loopStartPosition);
ScriptValue condition = evaluateExpression();
if (condition.getType() != kScriptValueTypeBool) {
error("Expected loop condition to be bool, not %s", scriptValueTypeToStr(condition.getType()));
}
if (++iterationCount >= MAX_LOOP_ITERATION_COUNT) {
error("Exceeded max loop iteration count");
}
if (condition.asBool()) {
executeNextBlock();
} else {
skipNextBlock();
break;
}
}
}
CodeChunk::~CodeChunk() {
_locals.clear();
// We don't own the args, so we don't need to delete it.
_args = nullptr;
delete _bytecode;
_bytecode = nullptr;
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,75 @@
/* 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_MEDIASCRIPT_CODECHUNK_H
#define MEDIASTATION_MEDIASCRIPT_CODECHUNK_H
#include "common/array.h"
#include "mediastation/datafile.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
class CodeChunk {
public:
CodeChunk(Chunk &chunk);
~CodeChunk();
ScriptValue executeNextBlock();
ScriptValue execute(Common::Array<ScriptValue> *args = nullptr);
private:
void skipNextBlock();
ScriptValue evaluateExpression();
ScriptValue evaluateExpression(ExpressionType expressionType);
ScriptValue evaluateOperation();
ScriptValue evaluateValue();
ScriptValue evaluateVariable();
ScriptValue *readAndReturnVariable();
void evaluateIf();
void evaluateIfElse();
ScriptValue evaluateAssign();
ScriptValue evaluateBinaryOperation(Opcode op);
ScriptValue evaluateUnaryOperation();
ScriptValue evaluateFunctionCall(bool isIndirect = false);
ScriptValue evaluateFunctionCall(uint functionId, uint paramCount);
ScriptValue evaluateMethodCall(bool isIndirect = false);
ScriptValue evaluateMethodCall(BuiltInMethod method, uint paramCount);
void evaluateDeclareLocals();
ScriptValue evaluateReturn();
void evaluateReturnNoValue();
void evaluateWhileLoop();
static const uint MAX_LOOP_ITERATION_COUNT = 1000;
bool _returnImmediately = false;
Common::Array<ScriptValue> _locals;
Common::Array<ScriptValue> *_args = nullptr;
ParameterReadStream *_bytecode = nullptr;
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,173 @@
/* 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/mediascript/collection.h"
#include "mediastation/mediascript/scriptvalue.h"
#include "mediastation/mediascript/codechunk.h"
#include "mediastation/debugchannels.h"
namespace MediaStation {
ScriptValue Collection::callMethod(BuiltInMethod method, Common::Array<ScriptValue> &args) {
ScriptValue returnValue;
switch (method) {
case kAppendMethod:
for (ScriptValue value : args) {
push_back(value);
}
break;
case kApplyMethod:
apply(args);
break;
case kCountMethod:
assert(args.empty());
returnValue.setToFloat(size());
break;
case kDeleteFirstMethod:
assert(args.empty());
returnValue = remove_at(0);
break;
case kDeleteLastMethod:
assert(args.empty());
returnValue = remove_at(size() - 1);
break;
case kEmptyMethod:
assert(args.empty());
clear();
break;
case kGetAtMethod: {
assert(args.size() == 1);
uint index = static_cast<uint>(args[0].asFloat());
returnValue = operator[](index);
break;
}
case kIsEmptyMethod:
assert(args.empty());
returnValue.setToBool(empty());
break;
case kJumbleMethod:
assert(args.empty());
jumble();
break;
case kSeekMethod: {
assert(args.size() == 1);
int index = seek(args[0]);
returnValue.setToFloat(index);
break;
}
case kSendMethod:
send(args);
break;
case kDeleteAtMethod: {
assert(args.size() == 1);
uint index = static_cast<uint>(args[0].asFloat());
returnValue = remove_at(index);
break;
}
case kInsertAtMethod: {
assert(args.size() == 2);
uint index = static_cast<uint>(args[1].asFloat());
insert_at(index, args[0]);
break;
}
case kReplaceAtMethod: {
assert(args.size() == 2);
uint index = static_cast<uint>(args[1].asFloat());
operator[](index) = args[0];
break;
}
case kPrependListMethod:
insert_at(0, args);
break;
case kSortMethod:
assert(args.empty());
Common::sort(begin(), end());
break;
default:
error("%s: Attempt to call unimplemented method %s (%d)", __func__, builtInMethodToStr(method), static_cast<uint>(method));
}
return returnValue;
}
void Collection::apply(const Common::Array<ScriptValue> &args) {
// Calls a function with each element of the collection as the first arg.
Common::Array<ScriptValue> argsToApply = args;
uint functionId = args[0].asFunctionId();
for (const ScriptValue &item : *this) {
argsToApply[0] = item;
g_engine->getFunctionManager()->call(functionId, argsToApply);
}
}
void Collection::send(const Common::Array<ScriptValue> &args) {
Common::Array<ScriptValue> argsToSend(args.size() - 1);
if (argsToSend.size() > 0) {
for (uint i = 1; i < args.size(); i++) {
argsToSend[i - 1] = args[i];
}
}
BuiltInMethod methodToSend = static_cast<BuiltInMethod>(args[0].asMethodId());
Common::Array<ScriptValue> sendArgs;
for (const ScriptValue &item : *this) {
uint actorId = item.asActorId();
Actor *targetActor = g_engine->getActorById(actorId);
if (targetActor != nullptr) {
targetActor->callMethod(methodToSend, argsToSend);
}
}
}
int Collection::seek(const ScriptValue &item) {
// Search from back to front.
for (int i = size() - 1; i >= 0; i--) {
if (item == operator[](i)) {
return i;
}
}
return -1;
}
void Collection::jumble() {
for (uint i = size() - 1; i > 0; --i) {
uint j = g_engine->_randomSource.getRandomNumber(size() - 1);
SWAP(operator[](i), operator[](j));
}
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,49 @@
/* 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_MEDIASCRIPT_COLLECTION_H
#define MEDIASTATION_MEDIASCRIPT_COLLECTION_H
#include "common/ptr.h"
#include "common/str.h"
#include "common/array.h"
#include "mediastation/datafile.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
class ScriptValue;
class Collection : public Common::Array<ScriptValue> {
public:
ScriptValue callMethod(BuiltInMethod method, Common::Array<ScriptValue> &args);
private:
void apply(const Common::Array<ScriptValue> &values);
void send(const Common::Array<ScriptValue> &values);
int seek(const ScriptValue &itemToFind);
void jumble();
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,56 @@
/* 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/mediascript/eventhandler.h"
#include "mediastation/debugchannels.h"
namespace MediaStation {
EventHandler::EventHandler(Chunk &chunk) {
_type = static_cast<EventType>(chunk.readTypedUint16());
debugC(5, kDebugLoading, "EventHandler::EventHandler(): Type %s (%d) (@0x%llx)",
eventTypeToStr(_type), static_cast<uint>(_type), static_cast<long long int>(chunk.pos()));
_argumentValue = ScriptValue(&chunk);
_code = new CodeChunk(chunk);
}
ScriptValue EventHandler::execute(uint actorId) {
// TODO: The actorId is only passed in for debug visibility, there should be
// a better way to handle that.
Common::String actorAndType = Common::String::format("(actor %d) (type = %s)", actorId, eventTypeToStr(_type));
Common::String argValue = Common::String::format("(%s)", _argumentValue.getDebugString().c_str());
debugC(5, kDebugScript, "\n********** EVENT HANDLER %s %s **********", actorAndType.c_str(), argValue.c_str());
// The only argument that can be provided to an
// event handler is the _argumentValue.
ScriptValue returnValue = _code->execute();
debugC(5, kDebugScript, "********** END EVENT HANDLER %s %s **********", actorAndType.c_str(), argValue.c_str());
return returnValue;
}
EventHandler::~EventHandler() {
delete _code;
_code = nullptr;
}
} // End of namespace MediaStation

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/>.
*
*/
#ifndef MEDIASTATION_MEDIASCRIPT_EVENTHANDLER_H
#define MEDIASTATION_MEDIASCRIPT_EVENTHANDLER_H
#include "common/str.h"
#include "mediastation/datafile.h"
#include "mediastation/mediascript/codechunk.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
class EventHandler {
public:
EventHandler(Chunk &chunk);
~EventHandler();
ScriptValue execute(uint actorId);
EventType _type;
ScriptValue _argumentValue;
private:
CodeChunk *_code = nullptr;
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,567 @@
/* 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/mediascript/function.h"
#include "mediastation/debugchannels.h"
#include "mediastation/mediastation.h"
namespace MediaStation {
ScriptFunction::ScriptFunction(Chunk &chunk) {
_contextId = chunk.readTypedUint16();
// In PROFILE._ST (only present in some titles), the function ID is reported
// with 19900 added, so function 100 would be reported as 20000. But in
// bytecode, the zero-based ID is used, so that's what we'll store here.
_id = chunk.readTypedUint16();
_code = new CodeChunk(chunk);
}
ScriptFunction::~ScriptFunction() {
delete _code;
_code = nullptr;
}
ScriptValue ScriptFunction::execute(Common::Array<ScriptValue> &args) {
debugC(5, kDebugScript, "\n********** SCRIPT FUNCTION %d **********", _id);
ScriptValue returnValue = _code->execute(&args);
debugC(5, kDebugScript, "********** END SCRIPT FUNCTION **********");
return returnValue;
}
FunctionManager::~FunctionManager() {
for (auto it = _functions.begin(); it != _functions.end(); ++it) {
delete it->_value;
}
_functions.clear();
}
bool FunctionManager::attemptToReadFromStream(Chunk &chunk, uint sectionType) {
bool handledParam = true;
switch (sectionType) {
case 0x31: {
ScriptFunction *function = new ScriptFunction(chunk);
_functions.setVal(function->_id, function);
break;
}
default:
handledParam = false;
}
return handledParam;
}
ScriptValue FunctionManager::call(uint functionId, Common::Array<ScriptValue> &args) {
ScriptValue returnValue;
// The original had a complex function registration system that I deemed too uselessly complex to
// reimplement. First, we try executing the title-defined function. We try this first because
// later engine versions used some functions IDs that previously mapped to built-in functions in
// earlier engine versions. So we will try executing the title-defined function first and only then
// fall back to the built-in functions.
ScriptFunction *scriptFunction = _functions.getValOrDefault(functionId);
if (scriptFunction != nullptr) {
returnValue = scriptFunction->execute(args);
return returnValue;
}
// If there was no title-defined function, next check for built-in functions.
switch (functionId) {
case kRandomFunction:
case kLegacy_RandomFunction:
assert(args.size() == 2);
script_Random(args, returnValue);
break;
case kTimeOfDayFunction:
case kLegacy_TimeOfDayFunction:
script_TimeOfDay(args, returnValue);
break;
case kEffectTransitionFunction:
case kLegacy_EffectTransitionFunction:
g_engine->getDisplayManager()->effectTransition(args);
break;
case kEffectTransitionOnSyncFunction:
case kLegacy_EffectTransitionOnSyncFunction:
g_engine->getDisplayManager()->setTransitionOnSync(args);
break;
case kPlatformFunction:
case kLegacy_PlatformFunction:
assert(args.empty());
script_GetPlatform(args, returnValue);
break;
case kSquareRootFunction:
case kLegacy_SquareRootFunction:
assert(args.size() == 1);
script_SquareRoot(args, returnValue);
break;
case kGetUniqueRandomFunction:
case kLegacy_GetUniqueRandomFunction:
assert(args.size() >= 2);
script_GetUniqueRandom(args, returnValue);
break;
case kCurrentRunTimeFunction:
script_CurrentRunTime(args, returnValue);
break;
case kSetGammaCorrectionFunction:
script_SetGammaCorrection(args, returnValue);
break;
case kGetDefaultGammaCorrectionFunction:
script_GetDefaultGammaCorrection(args, returnValue);
break;
case kGetCurrentGammaCorrectionFunction:
script_GetCurrentGammaCorrection(args, returnValue);
break;
case kSetAudioVolumeFunction:
assert(args.size() == 1);
script_SetAudioVolume(args, returnValue);
break;
case kGetAudioVolumeFunction:
assert(args.empty());
script_GetAudioVolume(args, returnValue);
break;
case kSystemLanguagePreferenceFunction:
case kLegacy_SystemLanguagePreferenceFunction:
script_SystemLanguagePreference(args, returnValue);
break;
case kSetRegistryFunction:
script_SetRegistry(args, returnValue);
break;
case kGetRegistryFunction:
script_GetRegistry(args, returnValue);
break;
case kSetProfileFunction:
script_SetProfile(args, returnValue);
break;
case kMazeGenerateFunction:
script_MazeGenerate(args, returnValue);
break;
case kMazeApplyMoveMaskFunction:
script_MazeApplyMoveMask(args, returnValue);
break;
case kMazeSolveFunction:
script_MazeSolve(args, returnValue);
break;
case kBeginTimedIntervalFunction:
script_BeginTimedInterval(args, returnValue);
break;
case kEndTimedIntervalFunction:
script_EndTimedInterval(args, returnValue);
break;
case kDrawingFunction:
script_Drawing(args, returnValue);
break;
case kLegacy_DebugPrintFunction:
script_DebugPrint(args, returnValue);
break;
default:
// If we got here, that means there was neither a title-defined nor a built-in function
// for this ID, so we can now declare it unimplemented. This is a warning instead of an error
// so execution can continue, but if the function is expected to return anything, there will
// likely be an error about attempting to assign a null value to a variable.
warning("%s: Unimplemented function 0x%02x", __func__, functionId);
}
return returnValue;
}
void FunctionManager::script_GetPlatform(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
Common::Platform platform = g_engine->getPlatform();
switch (platform) {
case Common::Platform::kPlatformWindows:
returnValue.setToParamToken(kPlatformParamTokenWindows);
break;
case Common::Platform::kPlatformMacintosh:
returnValue.setToParamToken(kPlatformParamTokenWindows);
break;
default:
warning("%s: Unknown platform %d", __func__, static_cast<int>(platform));
returnValue.setToParamToken(kPlatformParamTokenUnknown);
}
}
void FunctionManager::script_Random(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
// This function takes in a range, and then generates a random value within that range.
ScriptValue bottomArg = args[0];
ScriptValue topArg = args[1];
if (bottomArg.getType() != topArg.getType()) {
error("%s: Both arguments must be of same type", __func__);
}
ScriptValueType type = args[0].getType();
double bottom = 0.0;
double top = 0.0;
bool treatAsInteger = false;
switch (type) {
case kScriptValueTypeFloat: {
// For numeric values, treat them as integers (floor values).
bottom = floor(bottomArg.asFloat());
top = floor(topArg.asFloat());
treatAsInteger = true;
break;
}
case kScriptValueTypeBool: {
// Convert boolean values to numbers.
bottom = bottomArg.asBool() ? 1.0 : 0.0;
top = topArg.asBool() ? 1.0 : 0.0;
treatAsInteger = true;
break;
}
case kScriptValueTypeTime: {
// Treat time values as capable of having fractional seconds.
bottom = bottomArg.asTime();
top = topArg.asTime();
treatAsInteger = false;
break;
}
default:
error("%s: Invalid argument type: %s", __func__, scriptValueTypeToStr(type));
}
// Ensure proper inclusive ordering of bottom and top.
if (top < bottom) {
SWAP(top, bottom);
}
// Calculate random value in range.
double range = top - bottom;
uint randomValue = g_engine->_randomSource.getRandomNumber(UINT32_MAX);
double randomFloat = (static_cast<double>(randomValue) * range) / static_cast<double>(UINT32_MAX) + bottom;
if (treatAsInteger) {
randomFloat = floor(randomFloat);
}
// Set result based on original argument type.
switch (type) {
case kScriptValueTypeFloat:
returnValue.setToFloat(randomFloat);
break;
case kScriptValueTypeBool: {
bool boolResult = (randomFloat != 0.0);
returnValue.setToBool(boolResult);
break;
}
case kScriptValueTypeTime:
returnValue.setToTime(randomFloat);
break;
default:
error("%s: Invalid argument type: %s", __func__, scriptValueTypeToStr(type));
}
}
void FunctionManager::script_TimeOfDay(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
warning("STUB: TimeOfDay");
}
void FunctionManager::script_SquareRoot(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
if (args[0].getType() != kScriptValueTypeFloat) {
error("%s: Numeric value required", __func__);
}
double value = args[0].asFloat();
if (value < 0.0) {
error("%s: Argument must be nonnegative", __func__);
}
double result = sqrt(value);
returnValue.setToFloat(result);
}
void FunctionManager::script_GetUniqueRandom(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
// Unlike the regular Random which simply returns any random number in a range, GetUniqueRandom allows the caller
// to specify numbers that should NOT be returned (the third arg and onward), making it useful for generating random
// values that haven't been used before or avoiding specific unwanted values.
for (ScriptValue arg : args) {
if (arg.getType() != kScriptValueTypeFloat) {
error("%s: All arguments must be numeric", __func__);
}
}
// The original forces that the list of excluded numbers (and the range to choose from)
// can be at max 100 numbers. With the two args for the range, the max is thus 102.
const uint MAX_ARGS_SIZE = 102;
if (args.size() > MAX_ARGS_SIZE) {
args.resize(MAX_ARGS_SIZE);
}
// Ensure that the range is properly constructed.
double bottom = floor(args[0].asFloat());
double top = floor(args[1].asFloat());
if (top < bottom) {
SWAP(top, bottom);
}
// Build list of unused (non-excluded) numbers in the range. For this numeric type,
// everything is treated as an integer (even though it's stored as a double).
Common::Array<double> unusedNumbers;
for (double currentValue = bottom; currentValue < top; currentValue += 1.0) {
// Check if this value appears in the exclusion list (args 2 onwards).
bool isExcluded = false;
for (uint i = 2; i < args.size(); i++) {
if (args[i].asFloat() == currentValue) {
isExcluded = true;
break;
}
}
if (!isExcluded) {
unusedNumbers.push_back(currentValue);
}
}
if (unusedNumbers.size() > 0) {
uint randomIndex = g_engine->_randomSource.getRandomNumberRng(0, unusedNumbers.size());
returnValue.setToFloat(unusedNumbers[randomIndex]);
} else {
warning("%s: No unused numbers to choose from", __func__);
}
}
void FunctionManager::script_CurrentRunTime(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
// The current runtime is expected to be returned in seconds.
const uint MILLISECONDS_IN_ONE_SECOND = 1000;
double runtimeInSeconds = g_system->getMillis() / MILLISECONDS_IN_ONE_SECOND;
returnValue.setToFloat(runtimeInSeconds);
}
void FunctionManager::script_SetGammaCorrection(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
if (args.size() != 1 && args.size() != 3) {
warning("%s: Expected 1 or 3 arguments, got %u", __func__, args.size());
return;
}
double red = 1.0;
double green = 1.0;
double blue = 1.0;
if (args.size() >= 3) {
if (args[0].getType() != kScriptValueTypeFloat ||
args[1].getType() != kScriptValueTypeFloat ||
args[2].getType() != kScriptValueTypeFloat) {
warning("%s: Expected float arguments", __func__);
return;
}
red = args[0].asFloat();
green = args[1].asFloat();
blue = args[2].asFloat();
} else if (args.size() >= 1) {
if (args[0].getType() != kScriptValueTypeCollection) {
warning("%s: Expected collection argument", __func__);
return;
}
Common::SharedPtr<Collection> collection = args[0].asCollection();
if (collection->size() != 3) {
warning("%s: Collection must contain exactly 3 elements, got %u", __func__, collection->size());
return;
}
if (collection->operator[](0).getType() != kScriptValueTypeFloat ||
collection->operator[](1).getType() != kScriptValueTypeFloat ||
collection->operator[](2).getType() != kScriptValueTypeFloat) {
warning("%s: Expected float arguments", __func__);
return;
}
red = collection->operator[](0).asFloat();
green = collection->operator[](1).asFloat();
blue = collection->operator[](2).asFloat();
}
g_engine->getDisplayManager()->setGammaValues(red, green, blue);
}
void FunctionManager::script_GetDefaultGammaCorrection(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
if (args.size() != 0) {
warning("%s: Expected 0 arguments, got %u", __func__, args.size());
return;
}
double red, green, blue;
g_engine->getDisplayManager()->getDefaultGammaValues(red, green, blue);
Common::SharedPtr<Collection> collection = Common::SharedPtr<Collection>(new Collection());
ScriptValue redValue;
redValue.setToFloat(red);
collection->push_back(redValue);
ScriptValue greenValue;
greenValue.setToFloat(green);
collection->push_back(greenValue);
ScriptValue blueValue;
blueValue.setToFloat(blue);
collection->push_back(blueValue);
returnValue.setToCollection(collection);
}
void FunctionManager::script_GetCurrentGammaCorrection(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
if (args.size() != 0) {
warning("%s: Expected 0 arguments, got %u", __func__, args.size());
return;
}
double red, green, blue;
g_engine->getDisplayManager()->getGammaValues(red, green, blue);
Common::SharedPtr<Collection> collection = Common::SharedPtr<Collection>(new Collection());
ScriptValue redValue;
redValue.setToFloat(red);
collection->push_back(redValue);
ScriptValue greenValue;
greenValue.setToFloat(green);
collection->push_back(greenValue);
ScriptValue blueValue;
blueValue.setToFloat(blue);
collection->push_back(blueValue);
returnValue.setToCollection(collection);
}
void FunctionManager::script_SetAudioVolume(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
if (args[0].getType() != kScriptValueTypeFloat) {
warning("%s: Expected float argument", __func__);
return;
}
// Convert from 0.0 - 1.0 to ScummVM's mixer range.
double volume = args[0].asFloat();
volume = CLIP(volume, 0.0, 1.0);
int mixerVolume = static_cast<int>(volume * Audio::Mixer::kMaxMixerVolume);
g_system->getMixer()->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, mixerVolume);
}
void FunctionManager::script_GetAudioVolume(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
// Convert from ScummVM's mixer range to 0.0 - 1.0.
int mixerVolume = g_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kPlainSoundType);
double volume = static_cast<double>(mixerVolume) / static_cast<double>(Audio::Mixer::kMaxMixerVolume);
CLIP(volume, 0.0, 1.0);
returnValue.setToFloat(volume);
}
void FunctionManager::script_SystemLanguagePreference(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
warning("STUB: SystemLanguagePreference");
}
void FunctionManager::script_SetRegistry(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
warning("STUB: SetRegistry");
}
void FunctionManager::script_GetRegistry(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
warning("STUB: GetRegistry");
}
void FunctionManager::script_SetProfile(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
warning("STUB: SetProfile");
}
void FunctionManager::script_DebugPrint(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
// The original reports time in seconds, but milliseconds is fine.
// The "IMT @ clock ..." format is from the original's debug printing style.
Common::String output = Common::String::format("IMT @ clock %d", g_system->getMillis());
for (uint i = 0; i < args.size(); i++) {
// Append all provided arguments.
if (i != 0) {
output += ", ";
} else {
output += " ";
}
output += args[i].getDebugString();
}
debug("%s", output.c_str());
}
void FunctionManager::script_MazeGenerate(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
warning("STUB: MazeGenerate");
}
void FunctionManager::script_MazeApplyMoveMask(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
warning("STUB: MazeApplyMoveMask");
}
void FunctionManager::script_MazeSolve(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
warning("STUB: MazeSolve");
}
void FunctionManager::script_BeginTimedInterval(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
warning("STUB: BeginTimedInterval");
}
void FunctionManager::script_EndTimedInterval(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
warning("STUB: EndTimedInterval");
}
void FunctionManager::script_Drawing(Common::Array<ScriptValue> &args, ScriptValue &returnValue) {
warning("STUB: Drawing");
}
void FunctionManager::deleteFunctionsForContext(uint contextId) {
// Collect function IDs to delete first.
Common::Array<ScriptFunction *> functionsToDelete;
for (auto it = _functions.begin(); it != _functions.end(); ++it) {
ScriptFunction *scriptFunction = it->_value;
if (scriptFunction->_contextId == contextId) {
functionsToDelete.push_back(scriptFunction);
}
}
// Now delete them.
for (ScriptFunction *scriptFunction : functionsToDelete) {
_functions.erase(scriptFunction->_id);
delete scriptFunction;
}
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,96 @@
/* 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_MEDIASCRIPT_FUNCTION_H
#define MEDIASTATION_MEDIASCRIPT_FUNCTION_H
#include "common/array.h"
#include "common/hashmap.h"
#include "mediastation/clients.h"
#include "mediastation/datafile.h"
#include "mediastation/mediascript/codechunk.h"
namespace MediaStation {
enum Platform {
kPlatformParamTokenUnknown = 0,
kPlatformParamTokenWindows = 0x76D,
kPlatformParakTokenMacintosh = 0x76E
};
class ScriptFunction {
public:
ScriptFunction(Chunk &chunk);
~ScriptFunction();
ScriptValue execute(Common::Array<ScriptValue> &args);
uint _contextId = 0;
uint _id = 0;
private:
CodeChunk *_code = nullptr;
};
class FunctionManager : public ParameterClient {
public:
FunctionManager() {};
virtual ~FunctionManager();
virtual bool attemptToReadFromStream(Chunk &chunk, uint sectionType) override;
ScriptValue call(uint functionId, Common::Array<ScriptValue> &args);
void deleteFunctionsForContext(uint contextId);
private:
Common::HashMap<uint, ScriptFunction *> _functions;
void script_GetPlatform(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_Random(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_TimeOfDay(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_SquareRoot(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_GetUniqueRandom(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_CurrentRunTime(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_SetGammaCorrection(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_GetDefaultGammaCorrection(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_GetCurrentGammaCorrection(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_SetAudioVolume(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_GetAudioVolume(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_SystemLanguagePreference(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_SetRegistry(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_GetRegistry(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_SetProfile(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_DebugPrint(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
// 101 Dalmatians.
void script_MazeGenerate(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_MazeApplyMoveMask(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_MazeSolve(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_BeginTimedInterval(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
void script_EndTimedInterval(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
// IBM/Crayola.
void script_Drawing(Common::Array<ScriptValue> &args, ScriptValue &returnValue);
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,491 @@
/* 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/mediascript/scriptconstants.h"
namespace MediaStation {
const char *expressionTypeToStr(ExpressionType type) {
switch (type) {
case kExpressionTypeEmpty:
return "Empty";
case kExpressionTypeVariable:
return "Variable";
case kExpressionTypeValue:
return "Value";
case kExpressionTypeOperation:
return "Operation";
default:
return "UNKNOWN";
}
}
const char *opcodeToStr(Opcode opcode) {
switch (opcode) {
case kOpcodeIf:
return "If";
case kOpcodeIfElse:
return "IfElse";
case kOpcodeAssignVariable:
return "AssignVariable";
case kOpcodeOr:
return "Or";
case kOpcodeXor:
return "Xor";
case kOpcodeAnd:
return "And";
case kOpcodeEquals:
return "==";
case kOpcodeNotEquals:
return "!=";
case kOpcodeLessThan:
return "<";
case kOpcodeGreaterThan:
return ">";
case kOpcodeLessThanOrEqualTo:
return "<=";
case kOpcodeGreaterThanOrEqualTo:
return ">=";
case kOpcodeAdd:
return "+";
case kOpcodeSubtract:
return "-";
case kOpcodeMultiply:
return "*";
case kOpcodeDivide:
return "/";
case kOpcodeModulo:
return "%";
case kOpcodeNegate:
return "-";
case kOpcodeCallFunction:
return "CallFunction";
case kOpcodeCallMethod:
return "CallMethod";
case kOpcodeDeclareLocals:
return "DeclareLocals";
case kOpcodeReturn:
return "Return";
case kOpcodeReturnNoValue:
return "ReturnNoValue";
case kOpcodeWhile:
return "While";
case kOpcodeCallFunctionInVariable:
return "CallFunctionInVariable";
case kOpcodeCallMethodInVariable:
return "CallMethodInVariable";
default:
return "UNKNOWN";
}
}
const char *variableScopeToStr(VariableScope scope) {
switch (scope) {
case kVariableScopeLocal:
return "Local";
case kVariableScopeParameter:
return "Parameter";
case kVariableScopeIndirectParameter:
return "IndirectParameter";
case kVariableScopeGlobal:
return "Global";
default:
return "UNKNOWN";
}
}
const char *builtInFunctionToStr(BuiltInFunction function) {
switch (function) {
case kRandomFunction:
return "Random";
case kTimeOfDayFunction:
return "TimeOfDay";
case kEffectTransitionFunction:
return "EffectTransition";
case kEffectTransitionOnSyncFunction:
return "EffectTransitionOnSync";
case kPlatformFunction:
return "Platform";
case kSquareRootFunction:
return "SquareRoot";
case kGetUniqueRandomFunction:
return "GetUniqueRandom";
case kCurrentRunTimeFunction:
return "CurrentRunTime";
case kSetGammaCorrectionFunction:
return "SetGammaCorrection";
case kGetDefaultGammaCorrectionFunction:
return "GetDefaultGammaCorrection";
case kGetCurrentGammaCorrectionFunction:
return "GetCurrentGammaCorrection";
case kSetAudioVolumeFunction:
return "SetAudioVolume";
case kGetAudioVolumeFunction:
return "GetAudioVolume";
case kSystemLanguagePreferenceFunction:
return "SystemLanguagePreference";
case kSetRegistryFunction:
return "SetRegistry";
case kGetRegistryFunction:
return "GetRegistry";
case kSetProfileFunction:
return "SetProfile";
case kMazeGenerateFunction:
return "MazeGenerate";
case kMazeApplyMoveMaskFunction:
return "MazeApplyMoveMask";
case kMazeSolveFunction:
return "MazeSolve";
case kBeginTimedIntervalFunction:
return "BeginTimedInterval";
case kEndTimedIntervalFunction:
return "EndTimedInterval";
case kDrawingFunction:
return "Drawing";
case kLegacy_RandomFunction:
return "Legacy Random";
case kLegacy_TimeOfDayFunction:
return "Legacy TimeOfDay";
case kLegacy_EffectTransitionFunction:
return "Legacy EffectTransition";
case kLegacy_EffectTransitionOnSyncFunction:
return "Legacy EffectTransitionOnSync";
case kLegacy_PlatformFunction:
return "Legacy Platform";
case kLegacy_SquareRootFunction:
return "Legacy SquareRoot";
case kLegacy_GetUniqueRandomFunction:
return "Legacy GetUniqueRandom";
case kLegacy_DebugPrintFunction:
return "DebugPrint";
case kLegacy_SystemLanguagePreferenceFunction:
return "Legacy SystemLanguagePreference";
default:
return "UNKNOWN";
}
}
const char *builtInMethodToStr(BuiltInMethod method) {
switch (method) {
case kCursorSetMethod:
return "CursorSet";
case kSpatialHideMethod:
return "SpatialHide";
case kSpatialMoveToMethod:
return "SpatialMoveTo";
case kSpatialMoveToByOffsetMethod:
return "SpatialMoveToByOffset";
case kSpatialZMoveToMethod:
return "SpatialZMoveTo";
case kSpatialCenterMoveToMethod:
return "SpatialCenterMoveTo";
case kSpatialShowMethod:
return "SpatialShow";
case kTimePlayMethod:
return "TimePlay";
case kTimeStopMethod:
return "TimeStop";
case kIsPlayingMethod:
return "IsPlaying/SetMultipleStreams";
case kSetDissolveFactorMethod:
return "SetDissolveFactor";
case kMouseActivateMethod:
return "MouseActivate";
case kMouseDeactivateMethod:
return "MouseDeactivate";
case kGetLeftXMethod:
return "GetLeftX";
case kGetTopYMethod:
return "GetTopY";
case kTriggerAbsXPositionMethod:
return "TriggerAbsXPosition";
case kTriggerAbsYPositionMethod:
return "TriggerAbsYPosition";
case kIsActiveMethod:
return "IsActive";
case kGetWidthMethod:
return "GetWidth";
case kGetHeightMethod:
return "GetHeight";
case kGetCenterXMethod:
return "GetCenterX";
case kGetCenterYMethod:
return "GetCenterY";
case kGetZCoordinateMethod:
return "GetZCoordinate";
case kIsPointInsideMethod:
return "IsPointInside";
case kGetMouseXOffsetMethod:
return "GetMouseXOffset";
case kGetMouseYOffsetMethod:
return "GetMouseYOffset";
case kIsVisibleMethod:
return "IsVisible";
case kSetMousePositionMethod:
return "SetMousePosition";
case kGetXScaleMethod1:
case kGetXScaleMethod2:
return "GetXScale";
case kSetScaleMethod:
return "SetScale";
case kSetXScaleMethod:
return "SetXScale";
case kGetYScaleMethod:
return "GetYScale";
case kSetYScaleMethod:
return "SetYScale";
case kMovieResetMethod:
return "MovieReset";
case kSetCurrentClipMethod:
return "SetCurrentClip";
case kIncrementFrameMethod:
return "IncrementFrame";
case kDecrementFrameMethod:
return "DecrementFrame";
case kGetCurrentClipIdMethod:
return "GetCurrentClipId";
case kSetWorldSpaceExtentMethod:
return "SetWorldSpaceExtent";
case kSetBoundsMethod:
return "SetBounds";
case kStageGetWidthMethod:
return "StageGetWidth";
case kStageGetHeightMethod:
return "StageGetHeight";
case kAddToStageMethod:
return "AddToStage\\OpenLens";
case kRemoveFromStageMethod:
return "RemoveFromStage\\CloseLens";
case kAddedToStageMethod:
return "AddedToStage";
case kStartPanMethod:
return "StartPan";
case kStopPanMethod:
return "StopPan";
case kIsPanningMethod:
return "IsPanning";
case kViewportMoveToMethod:
return "ViewportMoveTo";
case kAdjustCameraViewportMethod:
return "AdjustCameraViewport";
case kAdjustCameraViewportSpatialCenterMethod:
return "AdjustCameraViewportSpatialCenter";
case kSetCameraBoundsMethod:
return "SetCameraBounds";
case kXViewportPositionMethod:
return "XViewportPosition";
case kYViewportPositionMethod:
return "YViewportPosition";
case kPanToMethod:
return "PanTo";
case kClearToPaletteMethod:
return "ClearToPalette";
case kDocumentLoadContextMethod:
return "LoadContext";
case kDocumentReleaseContextMethod:
return "ReleaseContext";
case kDocumentBranchToScreenMethod:
return "BranchToScreen";
case kDocumentQuitMethod:
return "Quit";
case kDocumentContextLoadInProgressMethod:
return "ContextLoadInProgress";
case kDocumentSetMultipleSoundsMethod:
return "SetMultipleSounds";
case kDocumentContextIsLoadedMethod:
return "IsLoaded";
case kSetDurationMethod:
return "SetDuration";
case kPercentCompleteMethod:
return "PercentComplete";
case kTextMethod:
return "Text";
case kSetTextMethod:
return "SetText";
case kSetMaximumTextLengthMethod:
return "SetMaximumTextLength";
case kAppendMethod:
return "Append";
case kApplyMethod:
return "Apply";
case kCountMethod:
return "Count";
case kDeleteFirstMethod:
return "DeleteFirst";
case kDeleteLastMethod:
return "DeleteLast";
case kEmptyMethod:
return "Empty";
case kGetAtMethod:
return "GetAt";
case kIsEmptyMethod:
return "IsEmpty";
case kJumbleMethod:
return "Jumble";
case kSeekMethod:
return "Seek";
case kSendMethod:
return "Send";
case kDeleteAtMethod:
return "DeleteAt";
case kInsertAtMethod:
return "InsertAt";
case kReplaceAtMethod:
return "ReplaceAt";
case kPrependListMethod:
return "PrependList";
case kSortMethod:
return "Sort";
default:
return "UNKNOWN";
}
}
const char *eventTypeToStr(EventType type) {
switch (type) {
case kTimerEvent:
return "Timer";
case kMouseDownEvent:
return "MouseDown";
case kMouseUpEvent:
return "MouseUp";
case kMouseMovedEvent:
return "MouseMoved";
case kMouseEnteredEvent:
return "MouseEntered";
case kMouseExitedEvent:
return "MouseExited";
case kKeyDownEvent:
return "KeyDown";
case kSoundEndEvent:
return "SoundEnd";
case kSoundAbortEvent:
return "SoundAbort";
case kSoundFailureEvent:
return "SoundFailure";
case kSoundStoppedEvent:
return "SoundStopped";
case kSoundBeginEvent:
return "SoundBegin";
case kMovieEndEvent:
return "MovieEnd";
case kMovieAbortEvent:
return "MovieAbort";
case kMovieFailureEvent:
return "MovieFailure";
case kMovieStoppedEvent:
return "MovieStopped";
case kMovieBeginEvent:
return "MovieBegin";
case kSpriteMovieEndEvent:
return "SpriteMovieEnd";
case kScreenEntryEvent:
return "ScreenEntry";
case kScreenExitEvent:
return "ScreenExit";
case kContextLoadCompleteEvent:
return "ContextLoadComplete";
case kContextLoadCompleteEvent2:
return "ContextLoadComplete2";
case kContextLoadAbortEvent:
return "ContextLoadAbort";
case kContextLoadFailureEvent:
return "ContextLoadFailure";
case kTextInputEvent:
return "TextInput";
case kTextErrorEvent:
return "TextError";
case kCameraPanStepEvent:
return "CameraPanStep";
case kCameraPanAbortEvent:
return "CameraPanAbort";
case kCameraPanEndEvent:
return "CameraPanEnd";
case kPathStepEvent:
return "PathStep";
case kPathStoppedEvent:
return "PathStopped";
case kPathEndEvent:
return "PathEnd";
default:
return "UNKNOWN";
}
}
const char *operandTypeToStr(OperandType type) {
switch (type) {
case kOperandTypeEmpty:
return "Empty";
case kOperandTypeBool:
return "Bool";
case kOperandTypeFloat:
return "Float";
case kOperandTypeInt:
return "Int";
case kOperandTypeString:
return "String";
case kOperandTypeParamToken:
return "DollarSignVariable";
case kOperandTypeActorId:
return "ActorId";
case kOperandTypeTime:
return "Time";
case kOperandTypeVariable:
return "Variable";
case kOperandTypeFunctionId:
return "FunctionId";
case kOperandTypeMethodId:
return "MethodId";
case kOperandTypeCollection:
return "Collection";
default:
return "UNKNOWN";
}
}
const char *scriptValueTypeToStr(ScriptValueType type) {
switch (type) {
case kScriptValueTypeEmpty:
return "Empty";
case kScriptValueTypeFloat:
return "Float";
case kScriptValueTypeBool:
return "Bool";
case kScriptValueTypeTime:
return "Time";
case kScriptValueTypeParamToken:
return "Int";
case kScriptValueTypeActorId:
return "ActorId";
case kScriptValueTypeString:
return "String";
case kScriptValueTypeCollection:
return "Collection";
case kScriptValueTypeFunctionId:
return "FunctionId";
case kScriptValueTypeMethodId:
return "MethodId";
default:
return "UNKNOWN";
}
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,315 @@
/* 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_MEDIASCRIPT_BUILTINS_H
#define MEDIASTATION_MEDIASCRIPT_BUILTINS_H
namespace MediaStation {
enum ExpressionType {
kExpressionTypeEmpty = 0x0000,
kExpressionTypeVariable = 0x0065,
kExpressionTypeValue = 0x0066,
kExpressionTypeOperation = 0x0067,
};
const char *expressionTypeToStr(ExpressionType type);
enum Opcode {
kOpcodeIf = 0xC9,
kOpcodeIfElse = 0xCA,
kOpcodeAssignVariable = 0xCB,
kOpcodeOr = 0xCC,
kOpcodeXor = 0xCD,
kOpcodeAnd = 0xCE,
kOpcodeEquals = 0xCF,
kOpcodeNotEquals = 0xD0,
kOpcodeLessThan = 0xD1,
kOpcodeGreaterThan = 0xD2,
kOpcodeLessThanOrEqualTo = 0xD3,
kOpcodeGreaterThanOrEqualTo = 0xD4,
kOpcodeAdd = 0xD5,
kOpcodeSubtract = 0xD6,
kOpcodeMultiply = 0xD7,
kOpcodeDivide = 0xD8,
kOpcodeModulo = 0xD9,
kOpcodeNegate = 0xDA,
kOpcodeCallFunction = 0xDB,
kOpcodeCallMethod = 0xDC,
kOpcodeDeclareLocals = 0xDD,
kOpcodeReturn = 0xDE,
kOpcodeReturnNoValue = 0xDF,
kOpcodeWhile = 0xE0,
kOpcodeCallFunctionInVariable = 0xE1, // IndirectCall
kOpcodeCallMethodInVariable = 0xE2 // IndirectMsg
};
const char *opcodeToStr(Opcode opcode);
enum VariableScope {
kVariableScopeLocal = 0x1,
kVariableScopeParameter = 0x2,
kVariableScopeIndirectParameter = 0x3,
kVariableScopeGlobal = 0x4
};
const char *variableScopeToStr(VariableScope scope);
enum BuiltInFunction {
kRandomFunction = 0x0A,
kTimeOfDayFunction = 0x0B,
kEffectTransitionFunction = 0x0C,
kEffectTransitionOnSyncFunction = 0x0D,
kPlatformFunction = 0x0E,
kSquareRootFunction = 0x0F,
kGetUniqueRandomFunction = 0x10,
kCurrentRunTimeFunction = 0x11,
kSetGammaCorrectionFunction = 0x12,
kGetDefaultGammaCorrectionFunction = 0x13,
kGetCurrentGammaCorrectionFunction = 0x14,
kSetAudioVolumeFunction = 0x17,
kGetAudioVolumeFunction = 0x18,
kSystemLanguagePreferenceFunction = 0x19,
kSetRegistryFunction = 0x1A,
kGetRegistryFunction = 0x1B,
kSetProfileFunction = 0x1C,
kMazeGenerateFunction = 0x1F,
kMazeApplyMoveMaskFunction = 0x20,
kMazeSolveFunction = 0x21,
kBeginTimedIntervalFunction = 0x22,
kEndTimedIntervalFunction = 0x23,
kDrawingFunction = 0x25,
// Early engine versions (like for Lion King and such), had different opcodes
// for some functions, even though the functions were the same. So those are
// defined here.
kLegacy_RandomFunction = 0x64,
kLegacy_TimeOfDayFunction = 0x65,
kLegacy_EffectTransitionFunction = 0x66,
kLegacy_EffectTransitionOnSyncFunction = 0x67,
kLegacy_PlatformFunction = 0x68,
kLegacy_SquareRootFunction = 0x69,
kLegacy_GetUniqueRandomFunction = 0x6A,
kLegacy_DebugPrintFunction = 0xB4,
kLegacy_SystemLanguagePreferenceFunction = 0xC8,
};
const char *builtInFunctionToStr(BuiltInFunction function);
enum BuiltInMethod {
kInvalidMethod = 0,
// TODO: What object types does CursorSet apply to?
// Currently it's only in var_7be1_cursor_currentTool in
// IBM/Crayola.
kCursorSetMethod = 0xC8,
// SPATIAL ENTITY METHODS.
kSpatialHideMethod = 0xCB,
kSpatialMoveToMethod = 0xCC,
kSpatialMoveToByOffsetMethod = 0xCD,
kSpatialZMoveToMethod = 0xD8,
kSpatialShowMethod = 0xCA,
kTimePlayMethod = 0xCE,
kTimeStopMethod = 0xCF,
kIsPlayingMethod = 0x174,
kSetDissolveFactorMethod = 0xF1,
kSpatialCenterMoveToMethod = 0xE6,
kGetLeftXMethod = 0xE9,
kGetTopYMethod = 0xEA,
kGetWidthMethod = 0xEB,
kGetHeightMethod = 0xEC,
kGetCenterXMethod = 0xED,
kGetCenterYMethod = 0xEE,
kGetZCoordinateMethod = 0xEF,
kIsPointInsideMethod = 0xF6,
kGetMouseXOffsetMethod = 0x108,
kGetMouseYOffsetMethod = 0x109,
kIsVisibleMethod = 0x10D,
kSetMousePositionMethod = 0x129,
// It isn't clear what the difference is meant to be
// between these two, as the code looks the same for both.
kGetXScaleMethod1 = 0x16E,
kGetXScaleMethod2 = 0x17E,
kSetScaleMethod = 0x16F,
kSetXScaleMethod = 0x17F,
kGetYScaleMethod = 0x180,
kSetYScaleMethod = 0x181,
// HOTSPOT METHODS.
// NOTE: IDs 0xD2 and 0xD3 seem to be double-assigned
// between two hotspot methods and two stage methods.
kMouseActivateMethod = 0xD2,
kMouseDeactivateMethod = 0xD3,
kTriggerAbsXPositionMethod = 0x141,
kTriggerAbsYPositionMethod = 0x142,
kIsActiveMethod = 0x173,
// SPRITE METHODS.
kMovieResetMethod = 0xDB,
kSetCurrentClipMethod = 0xDC,
kIncrementFrameMethod = 0xDD,
kDecrementFrameMethod = 0xDE,
kGetCurrentClipIdMethod = 0xF0,
// STAGE METHODS.
// NOTE: IDs 0xD2 and 0xD3 seem to be double-assigned
// between two hotspot methods and two stage methods.
kAddActorToStageMethod = 0xD2,
kRemoveActorFromStageMethod = 0xD3,
kSetWorldSpaceExtentMethod = 0x16B,
kSetBoundsMethod = 0x11F,
kStageSetSizeMethod = 0x16B,
kStageGetWidthMethod = 0x16C,
kStageGetHeightMethod = 0x16D,
// CAMERA METHODS.
// NOTE: IDs 0x15A and 0x15B seem to be double-assigned
// between two camera methods and two printer methods.
kAddToStageMethod = 0x15A,
kRemoveFromStageMethod = 0x15B,
kAddedToStageMethod = 0x15C,
kStartPanMethod = 0x15D,
kStopPanMethod = 0x15E,
kIsPanningMethod = 0x15F,
kViewportMoveToMethod = 0x160,
kAdjustCameraViewportMethod = 0x161,
kAdjustCameraViewportSpatialCenterMethod = 0x162,
kSetCameraBoundsMethod = 0x163,
kXViewportPositionMethod = 0x164,
kYViewportPositionMethod = 0x165,
kPanToMethod = 0x172,
// CANVAS METHODS.
kClearToPaletteMethod = 0x17B,
// DOCUMENT METHODS.
kDocumentBranchToScreenMethod = 0xC9,
kDocumentQuitMethod = 0xD9,
kDocumentContextLoadInProgressMethod = 0x169,
kDocumentSetMultipleStreamsMethod = 0x174,
kDocumentSetMultipleSoundsMethod = 0x175,
kDocumentLoadContextMethod = 0x176,
kDocumentReleaseContextMethod = 0x177,
kDocumentContextIsLoadedMethod = 0x178,
// PATH METHODS.
kSetDurationMethod = 0x106,
kPercentCompleteMethod = 0x107,
// TEXT METHODS.
kTextMethod = 0x122,
kSetTextMethod = 0x123,
kSetMaximumTextLengthMethod = 0x125,
// COLLECTION METHODS.
// These are arrays used in Media Script.
kAppendMethod = 0xF7,
kApplyMethod = 0xF8,
kCountMethod = 0xF9,
kDeleteFirstMethod = 0xFA,
kDeleteLastMethod = 0xFB,
kEmptyMethod = 0xFC,
kGetAtMethod = 0xFD,
kIsEmptyMethod = 0xFE,
kJumbleMethod = 0xFF,
kSeekMethod = 0x100,
kSendMethod = 0x101,
kDeleteAtMethod = 0x102,
kInsertAtMethod = 0x103,
kReplaceAtMethod = 0x104,
kPrependListMethod = 0x105,
kSortMethod = 0x10A,
// PRINTER METHODS.
// NOTE: IDs 0x15A and 0x15B seem to be double-assigned
// between two camera methods and two printer methods.
kOpenLensMethod = 0x15A,
kCloseLensMethod = 0x15B,
};
const char *builtInMethodToStr(BuiltInMethod method);
enum EventType {
kTimerEvent = 0x05,
kMouseDownEvent = 0x06,
kMouseUpEvent = 0x07,
kMouseMovedEvent = 0x08,
kMouseEnteredEvent = 0x09,
kMouseExitedEvent = 0x0A,
kKeyDownEvent = 0x0D,
kSoundEndEvent = 0x0E,
kMovieEndEvent = 0x0F,
kPathEndEvent = 0x10,
kScreenEntryEvent = 0x11,
kSoundAbortEvent = 0x13,
kSoundFailureEvent = 0x14,
kMovieAbortEvent = 0x15,
kMovieFailureEvent = 0x16,
kSpriteMovieEndEvent = 0x17,
kScreenExitEvent = 0x1B,
kPathStepEvent = 0x1C,
kSoundStoppedEvent = 0x1D,
kSoundBeginEvent = 0x1E,
kMovieStoppedEvent = 0x1F,
kMovieBeginEvent = 0x20,
kPathStoppedEvent = 0x21,
kTextInputEvent = 0x25,
kTextErrorEvent = 0x26,
kCameraPanStepEvent = 0x29,
kCameraPanEndEvent = 0x2A,
kCameraPanAbortEvent = 0x2B,
kContextLoadCompleteEvent = 0x2C,
// TODO: These last 3 events appear as valid event types, but I haven't found
// scripts that actually use them. So the names might be wrong.
kContextLoadCompleteEvent2 = 0x2D,
kContextLoadAbortEvent = 0x2E,
kContextLoadFailureEvent = 0x2F,
};
const char *eventTypeToStr(EventType type);
enum OperandType {
kOperandTypeEmpty = 0x0,
kOperandTypeBool = 0x97,
kOperandTypeFloat = 0x98,
kOperandTypeInt = 0x99,
kOperandTypeString = 0x9A,
kOperandTypeParamToken = 0x9B,
kOperandTypeActorId = 0x9C,
kOperandTypeTime = 0x9D,
kOperandTypeVariable = 0x9E,
kOperandTypeFunctionId = 0x9F,
kOperandTypeMethodId = 0xA0,
kOperandTypeCollection = 0xA1
};
const char *operandTypeToStr(OperandType type);
enum ScriptValueType {
kScriptValueTypeEmpty = 0x0,
kScriptValueTypeFloat = 0x1,
kScriptValueTypeBool = 0x2,
kScriptValueTypeTime = 0x3,
kScriptValueTypeParamToken = 0x4,
kScriptValueTypeActorId = 0x5,
kScriptValueTypeString = 0x6,
kScriptValueTypeCollection = 0x7,
kScriptValueTypeFunctionId = 0x8,
kScriptValueTypeMethodId = 0x9
};
const char *scriptValueTypeToStr(ScriptValueType type);
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,574 @@
/* 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/mediascript/scriptvalue.h"
#include "mediastation/mediascript/function.h"
namespace MediaStation {
ScriptValue::ScriptValue(ParameterReadStream *stream) {
_type = static_cast<ScriptValueType>(stream->readTypedByte());
switch (_type) {
case kScriptValueTypeEmpty:
break;
case kScriptValueTypeFloat: {
double d = stream->readTypedDouble();
setToFloat(d);
break;
}
case kScriptValueTypeBool: {
uint rawValue = stream->readTypedByte();
if (rawValue != 0 && rawValue != 1) {
error("%s: Got invalid literal bool value %d", __func__, rawValue);
}
setToBool(rawValue);
break;
}
case kScriptValueTypeTime: {
double d = stream->readTypedTime();
setToFloat(d);
break;
}
case kScriptValueTypeParamToken: {
uint paramToken = stream->readTypedUint16();
setToParamToken(paramToken);
break;
}
case kScriptValueTypeActorId: {
uint actorId = stream->readTypedUint16();
setToActorId(actorId);
break;
}
case kScriptValueTypeString: {
uint size = stream->readTypedUint16();
Common::String string = stream->readString('\0', size);
setToString(string);
break;
}
case kScriptValueTypeCollection: {
uint totalItems = stream->readTypedUint16();
Common::SharedPtr<Collection> collection(new Collection);
for (uint i = 0; i < totalItems; i++) {
ScriptValue collectionValue = ScriptValue(stream);
collection->push_back(collectionValue);
}
setToCollection(collection);
break;
}
case kScriptValueTypeFunctionId: {
uint functionId = stream->readTypedUint16();
setToFunctionId(functionId);
break;
}
case kScriptValueTypeMethodId: {
BuiltInMethod methodId = static_cast<BuiltInMethod>(stream->readTypedUint16());
setToMethodId(methodId);
break;
}
default:
error("%s: Got unknown script value type %s", __func__, scriptValueTypeToStr(_type));
}
}
void ScriptValue::setToFloat(uint i) {
setToFloat(static_cast<double>(i));
}
void ScriptValue::setToFloat(int i) {
setToFloat(static_cast<double>(i));
}
void ScriptValue::setToFloat(double d) {
_type = kScriptValueTypeFloat;
_u.d = d;
}
double ScriptValue::asFloat() const {
if (_type == kScriptValueTypeFloat) {
return _u.d;
} else {
issueValueMismatchWarning(kScriptValueTypeFloat);
return 0.0;
}
}
void ScriptValue::setToBool(bool b) {
_type = kScriptValueTypeBool;
_u.b = b;
}
bool ScriptValue::asBool() const {
if (_type == kScriptValueTypeBool) {
return _u.b;
} else {
issueValueMismatchWarning(kScriptValueTypeBool);
return false;
}
}
void ScriptValue::setToTime(double d) {
_type = kScriptValueTypeTime;
_u.d = d;
}
double ScriptValue::asTime() const {
if (_type == kScriptValueTypeTime) {
return _u.d;
} else {
issueValueMismatchWarning(kScriptValueTypeTime);
return 0.0;
}
}
void ScriptValue::setToParamToken(uint paramToken) {
_type = kScriptValueTypeParamToken;
_u.paramToken = paramToken;
}
uint ScriptValue::asParamToken() const {
if (_type == kScriptValueTypeParamToken) {
return _u.paramToken;
} else {
issueValueMismatchWarning(kScriptValueTypeParamToken);
return 0;
}
}
void ScriptValue::setToActorId(uint actorId) {
_type = kScriptValueTypeActorId;
_u.actorId = actorId;
}
uint ScriptValue::asActorId() const {
if (_type == kScriptValueTypeActorId) {
return _u.actorId;
} else {
issueValueMismatchWarning(kScriptValueTypeActorId);
return 0;
}
}
void ScriptValue::setToString(const Common::String &string) {
_type = kScriptValueTypeString;
_string = string;
}
Common::String ScriptValue::asString() const {
if (_type == kScriptValueTypeString) {
return _string;
} else {
return Common::String("");
}
}
void ScriptValue::setToCollection(Common::SharedPtr<Collection> collection) {
_type = kScriptValueTypeCollection;
_collection = collection;
}
Common::SharedPtr<Collection> ScriptValue::asCollection() const {
if (_type == kScriptValueTypeCollection) {
return _collection;
} else {
issueValueMismatchWarning(kScriptValueTypeCollection);
return nullptr;
}
}
void ScriptValue::setToFunctionId(uint functionId) {
_type = kScriptValueTypeFunctionId;
_u.functionId = functionId;
}
uint ScriptValue::asFunctionId() const {
if (_type == kScriptValueTypeFunctionId) {
return _u.functionId;
} else {
issueValueMismatchWarning(kScriptValueTypeFunctionId);
return 0;
}
}
void ScriptValue::setToMethodId(BuiltInMethod methodId) {
_type = kScriptValueTypeMethodId;
_u.methodId = methodId;
}
BuiltInMethod ScriptValue::asMethodId() const {
if (_type == kScriptValueTypeMethodId) {
return _u.methodId;
} else {
issueValueMismatchWarning(kScriptValueTypeMethodId);
return kInvalidMethod;
}
}
Common::String ScriptValue::getDebugString() {
switch (getType()) {
case kScriptValueTypeEmpty:
return "empty";
case kScriptValueTypeFloat:
return Common::String::format("float: %f", asFloat());
case kScriptValueTypeActorId:
return Common::String::format("actor: %d", asActorId());
case kScriptValueTypeTime:
return Common::String::format("time: %f", asTime());
case kScriptValueTypeParamToken:
return Common::String::format("token: %d", asParamToken());
default:
return Common::String::format("arg type %s", scriptValueTypeToStr(getType()));
}
}
bool ScriptValue::compare(Opcode op, const ScriptValue &lhs, const ScriptValue &rhs) {
if (lhs.getType() != rhs.getType()) {
warning("%s: Attempt to compare mismatched types %s and %s", __func__, scriptValueTypeToStr(lhs.getType()), scriptValueTypeToStr(rhs.getType()));
}
switch (lhs.getType()) {
case kScriptValueTypeEmpty:
return compareEmptyValues(op);
case kScriptValueTypeFloat:
return compare(op, lhs.asFloat(), rhs.asFloat());
break;
case kScriptValueTypeBool:
return compare(op, lhs.asBool(), rhs.asBool());
break;
case kScriptValueTypeTime:
return compare(op, lhs.asTime(), rhs.asTime());
break;
case kScriptValueTypeParamToken:
return compare(op, lhs.asParamToken(), rhs.asParamToken());
break;
case kScriptValueTypeActorId:
return compare(op, lhs.asActorId(), rhs.asActorId());
break;
case kScriptValueTypeString:
return compareStrings(op, lhs.asString(), rhs.asString());
break;
case kScriptValueTypeCollection:
return compare(op, lhs.asCollection(), rhs.asCollection());
break;
case kScriptValueTypeFunctionId:
return compare(op, lhs.asFunctionId(), rhs.asFunctionId());
break;
case kScriptValueTypeMethodId:
return compare(op, static_cast<uint>(lhs.asMethodId()), static_cast<uint>(rhs.asMethodId()));
break;
default:
error("%s: Got unknown script value type %d", __func__, lhs.getType());
}
}
bool ScriptValue::compareEmptyValues(Opcode op) {
// Empty values are considered equal.
switch (op) {
case kOpcodeEquals:
return true;
case kOpcodeNotEquals:
return false;
default:
warning("%s: Got invalid empty value operation %s", __func__, opcodeToStr(op));
return false;
}
}
bool ScriptValue::compareStrings(Opcode op, const Common::String &left, const Common::String &right) {
switch (op) {
case kOpcodeEquals:
return (left == right);
case kOpcodeNotEquals:
return (left != right);
case kOpcodeLessThan:
return (left < right);
case kOpcodeGreaterThan:
return (left > right);
case kOpcodeLessThanOrEqualTo:
return (left <= right);
case kOpcodeGreaterThanOrEqualTo:
return (left >= right);
default:
error("%s: Got invalid string operation %s", __func__, opcodeToStr(op));
}
}
bool ScriptValue::compare(Opcode op, uint left, uint right) {
switch (op) {
case kOpcodeEquals:
return (left == right);
case kOpcodeNotEquals:
return (left != right);
default:
error("%s: Got invalid param token operation %s", __func__, opcodeToStr(op));
}
}
bool ScriptValue::compare(Opcode op, bool left, bool right) {
switch (op) {
case kOpcodeEquals:
return (left == right);
case kOpcodeNotEquals:
return (left != right);
default:
error("%s: Got invalid bool operation %s", __func__, opcodeToStr(op));
}
}
bool ScriptValue::compare(Opcode op, double left, double right) {
switch (op) {
case kOpcodeEquals:
return (left == right);
case kOpcodeNotEquals:
return (left != right);
case kOpcodeLessThan:
return (left < right);
case kOpcodeGreaterThan:
return (left > right);
case kOpcodeLessThanOrEqualTo:
return (left <= right);
case kOpcodeGreaterThanOrEqualTo:
return (left >= right);
default:
error("%s: Got invalid float operation %s", __func__, opcodeToStr(op));
}
}
bool ScriptValue::compare(Opcode op, Common::SharedPtr<Collection> left, Common::SharedPtr<Collection> right) {
switch (op) {
case kOpcodeEquals:
return (left == right);
case kOpcodeNotEquals:
return (left != right);
default:
error("%s: Got invalid collection operation %s", __func__, opcodeToStr(op));
}
}
ScriptValue ScriptValue::evalMathOperation(Opcode op, const ScriptValue &left, const ScriptValue &right) {
ScriptValue returnValue;
double result = 0.0;
switch (left.getType()) {
case kScriptValueTypeFloat: {
if (right.getType() == kScriptValueTypeTime) {
result = binaryMathOperation(op, left.asFloat(), right.asTime());
} else if (right.getType() == kScriptValueTypeFloat) {
result = binaryMathOperation(op, left.asFloat(), right.asFloat());
} else {
error("%s: Attempted to do math operation on unsupported value type %s", __func__, scriptValueTypeToStr(right.getType()));
}
returnValue.setToFloat(result);
break;
}
case kScriptValueTypeTime: {
if (right.getType() == kScriptValueTypeTime) {
result = binaryMathOperation(op, left.asTime(), right.asTime());
} else if (right.getType() == kScriptValueTypeFloat) {
result = binaryMathOperation(op, left.asTime(), right.asFloat());
} else {
error("%s: Attempted to do math operation on unsupported value type %s", __func__, scriptValueTypeToStr(right.getType()));
}
returnValue.setToFloat(result);
break;
}
case kScriptValueTypeString: {
returnValue.setToString(left.asString() + right.asString());
break;
}
default:
error("%s: Attempted to do math operation on unsupported value type %s", __func__, scriptValueTypeToStr(right.getType()));
}
return returnValue;
}
double ScriptValue::binaryMathOperation(Opcode op, double left, double right) {
switch (op) {
case kOpcodeAdd:
return left + right;
case kOpcodeSubtract:
return left - right;
case kOpcodeMultiply:
return left * right;
case kOpcodeDivide:
if (right != 0.0) {
return left / right;
} else {
error("%s: Division by zero", __func__);
}
case kOpcodeModulo:
if (right != 0.0) {
return fmod(left, right);
} else {
error("%s: Division by zero", __func__);
}
default:
error("%s: Got unvalid binary math operation %s", __func__, opcodeToStr(op));
}
}
bool ScriptValue::operator==(const ScriptValue &other) const {
return compare(kOpcodeEquals, *this, other);
}
bool ScriptValue::operator!=(const ScriptValue &other) const {
return compare(kOpcodeNotEquals, *this, other);
}
bool ScriptValue::operator<(const ScriptValue &other) const {
return compare(kOpcodeLessThan, *this, other);
}
bool ScriptValue::operator>(const ScriptValue &other) const {
return compare(kOpcodeGreaterThan, *this, other);
}
bool ScriptValue::operator<=(const ScriptValue &other) const {
return compare(kOpcodeLessThanOrEqualTo, *this, other);
}
bool ScriptValue::operator>=(const ScriptValue &other) const {
return compare(kOpcodeGreaterThanOrEqualTo, *this, other);
}
bool ScriptValue::operator||(const ScriptValue &other) const {
if (getType() != kScriptValueTypeBool || other.getType() != kScriptValueTypeBool) {
error("%s: Expected bools for binary comparison, got %s and %s", __func__, scriptValueTypeToStr(getType()), scriptValueTypeToStr(other.getType()));
}
return asBool() || other.asBool();
}
bool ScriptValue::operator^(const ScriptValue &other) const {
if (getType() != kScriptValueTypeBool || other.getType() != kScriptValueTypeBool) {
error("%s: Expected bools for binary comparison, got %s and %s", __func__, scriptValueTypeToStr(getType()), scriptValueTypeToStr(other.getType()));
}
return asBool() ^ other.asBool();
}
bool ScriptValue::operator&&(const ScriptValue &other) const {
if (getType() != kScriptValueTypeBool || other.getType() != kScriptValueTypeBool) {
error("%s: Expected bools for binary comparison, got %s and %s", __func__, scriptValueTypeToStr(getType()), scriptValueTypeToStr(other.getType()));
}
return asBool() && other.asBool();
}
ScriptValue ScriptValue::operator+(const ScriptValue &other) const {
return evalMathOperation(kOpcodeAdd, *this, other);
}
ScriptValue ScriptValue::operator-(const ScriptValue &other) const {
return evalMathOperation(kOpcodeSubtract, *this, other);
}
ScriptValue ScriptValue::operator*(const ScriptValue &other) const {
return evalMathOperation(kOpcodeMultiply, *this, other);
}
ScriptValue ScriptValue::operator/(const ScriptValue &other) const {
return evalMathOperation(kOpcodeDivide, *this, other);
}
ScriptValue ScriptValue::operator%(const ScriptValue &other) const {
return evalMathOperation(kOpcodeModulo, *this, other);
}
ScriptValue ScriptValue::operator-() const {
ScriptValue returnValue;
switch (getType()) {
case kScriptValueTypeFloat:
returnValue.setToFloat(-asFloat());
break;
case kScriptValueTypeTime:
returnValue.setToTime(-asTime());
break;
default:
error("%s: Attempted to negate type %s", __func__, scriptValueTypeToStr(getType()));
}
return returnValue;
}
void ScriptValue::issueValueMismatchWarning(ScriptValueType expectedType) const {
// The original just blithely returns 0 (or equivalent) when you call a
// getter for the wrong type (for instance, calling asFloat() on a bool),
// but for debugging purposes we'll issue a warning.
warning("%s: Script value type mismatch: Expected %s, got %s", __func__, scriptValueTypeToStr(expectedType), scriptValueTypeToStr(_type));
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,122 @@
/* 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_MEDIASCRIPT_SCRIPTVALUE_H
#define MEDIASTATION_MEDIASCRIPT_SCRIPTVALUE_H
#include "common/ptr.h"
#include "common/str.h"
#include "mediastation/datafile.h"
#include "mediastation/mediascript/scriptconstants.h"
#include "mediastation/mediascript/collection.h"
namespace MediaStation {
class Actor;
class ScriptValue {
public:
ScriptValue() : _type(kScriptValueTypeEmpty) {}
ScriptValue(ParameterReadStream *stream);
ScriptValueType getType() const { return _type; }
void setToFloat(uint i);
void setToFloat(int i);
void setToFloat(double d);
double asFloat() const;
int asIntFromFloat() const;
void setToBool(bool b);
bool asBool() const;
void setToTime(double d);
double asTime() const;
void setToParamToken(uint paramToken);
uint asParamToken() const;
void setToActorId(uint actorId);
uint asActorId() const;
void setToString(const Common::String &string);
Common::String asString() const;
void setToCollection(Common::SharedPtr<Collection> collection);
Common::SharedPtr<Collection> asCollection() const;
void setToFunctionId(uint functionId);
uint asFunctionId() const;
void setToMethodId(BuiltInMethod methodId);
BuiltInMethod asMethodId() const;
Common::String getDebugString();
bool operator==(const ScriptValue &other) const;
bool operator!=(const ScriptValue &other) const;
bool operator<(const ScriptValue &other) const;
bool operator>(const ScriptValue &other) const;
bool operator<=(const ScriptValue &other) const;
bool operator>=(const ScriptValue &other) const;
bool operator||(const ScriptValue &other) const;
bool operator^(const ScriptValue &other) const;
bool operator&&(const ScriptValue &other) const;
ScriptValue operator+(const ScriptValue &other) const;
ScriptValue operator-(const ScriptValue &other) const;
ScriptValue operator*(const ScriptValue &other) const;
ScriptValue operator/(const ScriptValue &other) const;
ScriptValue operator%(const ScriptValue &other) const;
ScriptValue operator-() const;
private:
ScriptValueType _type = kScriptValueTypeEmpty;
union {
double d = 0;
bool b;
uint paramToken;
uint actorId;
uint functionId;
BuiltInMethod methodId;
} _u;
Common::String _string;
Common::SharedPtr<Collection> _collection;
static bool compare(Opcode op, const ScriptValue &left, const ScriptValue &right);
static bool compareEmptyValues(Opcode op);
static bool compareStrings(Opcode op, const Common::String &left, const Common::String &right);
static bool compare(Opcode op, uint left, uint right);
static bool compare(Opcode op, bool left, bool right);
static bool compare(Opcode op, double left, double right);
static bool compare(Opcode op, Common::SharedPtr<Collection> left, Common::SharedPtr<Collection> right);
static ScriptValue evalMathOperation(Opcode op, const ScriptValue &left, const ScriptValue &right);
static double binaryMathOperation(Opcode op, double left, double right);
void issueValueMismatchWarning(ScriptValueType actualType) const;
};
} // End of namespace MediaStation
#endif

View File

@@ -0,0 +1,397 @@
/* 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 "common/config-manager.h"
#include "mediastation/mediastation.h"
#include "mediastation/debugchannels.h"
#include "mediastation/detection.h"
#include "mediastation/boot.h"
#include "mediastation/context.h"
#include "mediastation/actor.h"
#include "mediastation/actors/document.h"
#include "mediastation/actors/movie.h"
#include "mediastation/actors/screen.h"
#include "mediastation/actors/palette.h"
#include "mediastation/actors/hotspot.h"
#include "mediastation/actors/stage.h"
#include "mediastation/mediascript/scriptconstants.h"
namespace MediaStation {
MediaStationEngine *g_engine;
MediaStationEngine::MediaStationEngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst),
_gameDescription(gameDesc),
_randomSource("MediaStation") {
g_engine = this;
_gameDataDir = Common::FSNode(ConfMan.getPath("path"));
SearchMan.addDirectory(_gameDataDir, 0, 3);
for (uint i = 0; MediaStation::directoryGlobs[i]; i++) {
Common::String directoryGlob = directoryGlobs[i];
SearchMan.addSubDirectoryMatching(_gameDataDir, directoryGlob, 0, 5);
}
_channelIdent = MKTAG('i', 'g', 'o', 'd'); // ImtGod
}
MediaStationEngine::~MediaStationEngine() {
for (auto it = _loadedContexts.begin(); it != _loadedContexts.end(); ++it) {
destroyContext(it->_value->_id, false);
}
_loadedContexts.clear();
// Only delete the document actor.
// The root stage is deleted from stage director, and
// the other actors are deleted from their contexts.
destroyActor(DocumentActor::DOCUMENT_ACTOR_ID);
delete _displayManager;
_displayManager = nullptr;
delete _cursorManager;
_cursorManager = nullptr;
delete _functionManager;
_functionManager = nullptr;
delete _document;
_document = nullptr;
delete _deviceOwner;
_deviceOwner = nullptr;
delete _stageDirector;
_stageDirector = nullptr;
unregisterWithStreamManager();
delete _streamFeedManager;
_streamFeedManager = nullptr;
_contextReferences.clear();
_streamMap.clear();
_engineResourceDeclarations.clear();
_screenReferences.clear();
_fileMap.clear();
_actors.clear();
}
Actor *MediaStationEngine::getActorById(uint actorId) {
return _actors.getValOrDefault(actorId);
}
SpatialEntity *MediaStationEngine::getSpatialEntityById(uint spatialEntityId) {
Actor *actor = getActorById(spatialEntityId);
if (actor != nullptr) {
if (!actor->isSpatialActor()) {
error("%s: Actor %d is not a spatial actor", __func__, spatialEntityId);
}
return static_cast<SpatialEntity *>(actor);
}
return nullptr;
}
ChannelClient *MediaStationEngine::getChannelClientByChannelIdent(uint channelIdent) {
return _streamFeedManager->channelClientForChannel(channelIdent);
}
ScriptValue *MediaStationEngine::getVariable(uint variableId) {
for (auto it = _loadedContexts.begin(); it != _loadedContexts.end(); ++it) {
ScriptValue *variable = it->_value->_variables.getValOrDefault(variableId);
if (variable != nullptr) {
return variable;
}
}
return nullptr;
}
uint32 MediaStationEngine::getFeatures() const {
return _gameDescription->flags;
}
Common::String MediaStationEngine::getGameId() const {
return _gameDescription->gameId;
}
Common::Platform MediaStationEngine::getPlatform() const {
return _gameDescription->platform;
}
const char *MediaStationEngine::getAppName() const {
return _gameDescription->filesDescriptions[0].fileName;
}
bool MediaStationEngine::isFirstGenerationEngine() {
return _versionInfo.major == 0;
}
Common::Error MediaStationEngine::run() {
initDisplayManager();
initCursorManager();
initFunctionManager();
initDocument();
initDeviceOwner();
initStageDirector();
initStreamFeedManager();
setupInitialStreamMap();
if (ConfMan.hasKey("entry_context")) {
// For development purposes, we can choose to start at an arbitrary context
// in this title. This might not work in all cases.
uint entryContextId = ConfMan.get("entry_context").asUint64();
warning("%s: Starting at user-requested context %d", __func__, entryContextId);
_document->beginTitle(entryContextId);
} else {
_document->beginTitle();
}
runEventLoop();
return Common::kNoError;
}
void MediaStationEngine::runEventLoop() {
while (true) {
dispatchSystemEvents();
if (shouldQuit()) {
break;
}
_document->process();
debugC(5, kDebugGraphics, "***** START SCREEN UPDATE ***");
for (auto it = _actors.begin(); it != _actors.end(); ++it) {
it->_value->process();
}
draw();
debugC(5, kDebugGraphics, "***** END SCREEN UPDATE ***");
g_system->delayMillis(10);
}
}
void MediaStationEngine::initDisplayManager() {
_displayManager = new VideoDisplayManager(this);
_parameterClients.push_back(_displayManager);
}
void MediaStationEngine::initCursorManager() {
if (getPlatform() == Common::kPlatformWindows) {
_cursorManager = new WindowsCursorManager(getAppName());
} else if (getPlatform() == Common::kPlatformMacintosh) {
_cursorManager = new MacCursorManager(getAppName());
} else {
error("%s: Attempted to use unsupported platform %s", __func__, Common::getPlatformDescription(getPlatform()));
}
_parameterClients.push_back(_cursorManager);
_cursorManager->showCursor();
}
void MediaStationEngine::initFunctionManager() {
_functionManager = new FunctionManager();
_parameterClients.push_back(_functionManager);
}
void MediaStationEngine::initDocument() {
_document = new Document();
_parameterClients.push_back(_document);
DocumentActor *documentActor = new DocumentActor;
registerActor(documentActor);
}
void MediaStationEngine::initDeviceOwner() {
_deviceOwner = new DeviceOwner();
_parameterClients.push_back(_deviceOwner);
}
void MediaStationEngine::initStageDirector() {
_stageDirector = new StageDirector;
}
void MediaStationEngine::initStreamFeedManager() {
_streamFeedManager = new StreamFeedManager;
registerWithStreamManager();
}
void MediaStationEngine::setupInitialStreamMap() {
StreamInfo streamInfo;
streamInfo._actorId = 0;
streamInfo._fileId = MediaStationEngine::BOOT_STREAM_ID;
streamInfo._startOffsetInFile = 0;
_streamMap.setVal(streamInfo._fileId, streamInfo);
const Common::String BOOT_STM_FILENAME("BOOT.STM");
FileInfo fileInfo;
fileInfo._id = MediaStationEngine::BOOT_STREAM_ID;
fileInfo._name = BOOT_STM_FILENAME;
_fileMap.setVal(fileInfo._id, fileInfo);
}
void MediaStationEngine::dispatchSystemEvents() {
while (g_system->getEventManager()->pollEvent(_event)) {
debugC(9, kDebugEvents, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
debugC(9, kDebugEvents, "@@@@ Dispatching system events");
debugC(9, kDebugEvents, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
switch (_event.type) {
case Common::EVENT_MOUSEMOVE:
_stageDirector->handleMouseMovedEvent(_event);
break;
case Common::EVENT_KEYDOWN:
_stageDirector->handleKeyboardEvent(_event);
break;
case Common::EVENT_LBUTTONDOWN:
_stageDirector->handleMouseDownEvent(_event);
break;
case Common::EVENT_LBUTTONUP:
_stageDirector->handleMouseUpEvent(_event);
break;
case Common::EVENT_FOCUS_LOST:
_stageDirector->handleMouseOutOfFocusEvent(_event);
break;
case Common::EVENT_RBUTTONDOWN:
// We are using the right button as a quick exit since the Media
// Station engine doesn't seem to use the right button itself.
warning("%s: EVENT_RBUTTONDOWN: Quitting for development purposes", __func__);
quitGame();
break;
default:
// Avoid warnings about unimplemented cases by having an explicit
// default case.
break;
}
}
}
void MediaStationEngine::draw(bool dirtyOnly) {
if (dirtyOnly) {
_stageDirector->drawDirtyRegion();
} else {
_stageDirector->drawAll();
}
_displayManager->updateScreen();
_displayManager->doTransitionOnSync();
}
void MediaStationEngine::registerActor(Actor *actorToAdd) {
if (getActorById(actorToAdd->id())) {
error("%s: Actor with ID 0x%d was already defined in this title", __func__, actorToAdd->id());
}
_actors.setVal(actorToAdd->id(), actorToAdd);
}
void MediaStationEngine::destroyActor(uint actorId) {
Actor *actorToDestroy = getActorById(actorId);
if (actorToDestroy) {
delete _actors[actorId];
_actors.erase(actorId);
} else {
warning("%s: Actor %d is not currently loaded", __func__, actorId);
}
}
void MediaStationEngine::destroyContext(uint contextId, bool eraseFromLoadedContexts) {
debugC(5, kDebugScript, "%s: Destroying context %d", __func__, contextId);
Context *context = _loadedContexts.getValOrDefault(contextId);
if (context == nullptr) {
error("%s: Attempted to unload context %d that is not currently loaded", __func__, contextId);
}
getRootStage()->deleteChildrenFromContextId(contextId);
destroyActorsInContext(contextId);
_functionManager->deleteFunctionsForContext(contextId);
delete context;
if (eraseFromLoadedContexts) {
// If we are deleting all contexts at once, we don't want to actually do this,
// as it will mess up our iterators - the whole structure should be cleared after this.
_loadedContexts.erase(contextId);
}
}
bool MediaStationEngine::contextIsLocked(uint contextId) {
for (auto it = _loadedContexts.begin(); it != _loadedContexts.end(); ++it) {
uint id = it->_key;
ContextReference contextReference = _contextReferences.getVal(id);
for (uint childContextId : contextReference._parentContextIds) {
if (childContextId == contextId) {
return true;
}
}
}
return false;
}
void MediaStationEngine::destroyActorsInContext(uint contextId) {
// Collect actors to remove first, then delete them.
// This is necessary because calling erase on a hashmap invalidates
// the iterators, so collecting them all first makes more sense.
Common::Array<uint> actorsToRemove;
for (auto it = _actors.begin(); it != _actors.end(); ++it) {
uint actorContextId = it->_value->contextId();
if (actorContextId == contextId) {
actorsToRemove.push_back(it->_key);
}
}
// Now remove the collected actors.
for (uint actorId : actorsToRemove) {
destroyActor(actorId);
}
}
void MediaStationEngine::readUnrecognizedFromStream(Chunk &chunk, uint sectionType) {
bool paramHandled = false;
for (ParameterClient *client : g_engine->_parameterClients) {
if (client->attemptToReadFromStream(chunk, sectionType)) {
paramHandled = true;
break;
}
}
if (!paramHandled) {
warning("%s: Parameter %d not handled", __func__, sectionType);
}
}
void MediaStationEngine::readChunk(Chunk &chunk) {
StreamType streamType = static_cast<StreamType>(chunk.readTypedUint16());
switch (streamType) {
case kDocumentDefStream:
readDocumentDef(chunk);
break;
case kControlCommandsStream:
readControlCommands(chunk);
break;
default:
error("%s: Unhandled section type 0x%x", __func__, static_cast<uint>(streamType));
}
}
} // End of namespace MediaStation

View File

@@ -0,0 +1,203 @@
/* 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_H
#define MEDIASTATION_H
#include "audio/mixer.h"
#include "common/scummsys.h"
#include "common/system.h"
#include "common/error.h"
#include "common/fs.h"
#include "common/hash-str.h"
#include "common/random.h"
#include "common/serializer.h"
#include "common/events.h"
#include "common/util.h"
#include "engines/engine.h"
#include "engines/savestate.h"
#include "mediastation/clients.h"
#include "mediastation/detection.h"
#include "mediastation/datafile.h"
#include "mediastation/boot.h"
#include "mediastation/context.h"
#include "mediastation/actor.h"
#include "mediastation/cursors.h"
#include "mediastation/graphics.h"
#include "mediastation/mediascript/function.h"
#include "mediastation/actors/stage.h"
namespace MediaStation {
struct MediaStationGameDescription;
class HotspotActor;
class RootStage;
class Bitmap;
// Most Media Station titles follow this file structure from the root directory
// of the CD-ROM:
// - [TITLE].EXE (main game executable, name vares based on game)
// - DATA/ (subdirectory that holds actual game data including bytecode)
// - 100.CXT
// - ... other CXTs, varies per title
static const char *const directoryGlobs[] = {
"DATA", // For most titles
"program", // For D.W. the Picky Eater
"PZDATA", // For Puzzle Castle demo
nullptr
};
// As this is currently structured, some of the methods in the main engine class are from
// the RT_ImtGod class in the original, and others are from the RT_App class in the original.
// In the interest of avoiding more indirection than is already present in the original, we will
// just keep these together for now.
class MediaStationEngine : public Engine, public ChannelClient {
public:
MediaStationEngine(OSystem *syst, const ADGameDescription *gameDesc);
~MediaStationEngine() override;
uint32 getFeatures() const;
Common::String getGameId() const;
Common::Platform getPlatform() const;
const char *getAppName() const;
bool hasFeature(EngineFeature f) const override {
return
(f == kSupportsReturnToLauncher);
};
bool isFirstGenerationEngine();
void dispatchSystemEvents();
void draw(bool dirtyOnly = true);
void registerActor(Actor *actorToAdd);
void destroyActor(uint actorId);
void destroyContext(uint contextId, bool eraseFromLoadedContexts = true);
bool contextIsLocked(uint contextId);
void readUnrecognizedFromStream(Chunk &chunk, uint sectionType);
void readHeaderSections(Subfile &subfile, Chunk &chunk);
Actor *getActorById(uint actorId);
SpatialEntity *getSpatialEntityById(uint spatialEntityId);
ChannelClient *getChannelClientByChannelIdent(uint channelIdent);
ScriptValue *getVariable(uint variableId);
VideoDisplayManager *getDisplayManager() { return _displayManager; }
CursorManager *getCursorManager() { return _cursorManager; }
FunctionManager *getFunctionManager() { return _functionManager; }
RootStage *getRootStage() { return _stageDirector->getRootStage(); }
StreamFeedManager *getStreamFeedManager() { return _streamFeedManager; }
Document *getDocument() { return _document; }
const FileInfo &fileInfoForIdent(uint fileId) { return _fileMap.getValOrDefault(fileId); }
const StreamInfo &streamInfoForIdent(uint streamId) { return _streamMap.getValOrDefault(streamId); }
const ScreenReference &screenRefWithId(uint screenActorId) { return _screenReferences.getValOrDefault(screenActorId); }
const ContextReference &contextRefWithId(uint contextId) { return _contextReferences.getValOrDefault(contextId); }
Common::Array<ParameterClient *> _parameterClients;
Common::HashMap<uint, Context *> _loadedContexts;
SpatialEntity *getMouseInsideHotspot() { return _mouseInsideHotspot; }
void setMouseInsideHotspot(SpatialEntity *entity) { _mouseInsideHotspot = entity; }
void clearMouseInsideHotspot() { _mouseInsideHotspot = nullptr; }
SpatialEntity *getMouseDownHotspot() { return _mouseDownHotspot; }
void setMouseDownHotspot(SpatialEntity *entity) { _mouseDownHotspot = entity; }
void clearMouseDownHotspot() { _mouseDownHotspot = nullptr; }
Common::RandomSource _randomSource;
static const uint SCREEN_WIDTH = 640;
static const uint SCREEN_HEIGHT = 480;
static const uint BOOT_STREAM_ID = 1;
protected:
Common::Error run() override;
private:
Common::Event _event;
Common::FSNode _gameDataDir;
const ADGameDescription *_gameDescription;
VideoDisplayManager *_displayManager = nullptr;
CursorManager *_cursorManager = nullptr;
FunctionManager *_functionManager = nullptr;
Document *_document = nullptr;
DeviceOwner *_deviceOwner = nullptr;
StageDirector *_stageDirector = nullptr;
StreamFeedManager *_streamFeedManager = nullptr;
Common::HashMap<uint, Actor *> _actors;
SpatialEntity *_mouseInsideHotspot = nullptr;
SpatialEntity *_mouseDownHotspot = nullptr;
Common::String _gameTitle;
VersionInfo _versionInfo;
Common::String _engineInfo;
Common::String _sourceString;
Common::HashMap<uint, ContextReference> _contextReferences;
Common::HashMap<uint, ScreenReference> _screenReferences;
Common::HashMap<uint, FileInfo> _fileMap;
Common::HashMap<uint, StreamInfo> _streamMap;
Common::HashMap<uint, EngineResourceDeclaration> _engineResourceDeclarations;
uint _unk1 = 0;
uint _functionTableSize = 0;
uint _unk3 = 0;
void initDisplayManager();
void initCursorManager();
void initFunctionManager();
void initDocument();
void initDeviceOwner();
void initStageDirector();
void initStreamFeedManager();
void setupInitialStreamMap();
void runEventLoop();
virtual void readChunk(Chunk &chunk) override;
void readDocumentDef(Chunk &chunk);
void readDocumentInfoFromStream(Chunk &chunk, BootSectionType sectionType);
void readVersionInfoFromStream(Chunk &chunk);
void readContextReferencesFromStream(Chunk &chunk);
void readScreenReferencesFromStream(Chunk &chunk);
void readAndAddFileMaps(Chunk &chunk);
void readAndAddStreamMaps(Chunk &chunk);
void readControlCommands(Chunk &chunk);
void readCommandFromStream(Chunk &chunk, ContextSectionType sectionType);
void readCreateContextData(Chunk &chunk);
void readDestroyContextData(Chunk &chunk);
void readCreateActorData(Chunk &chunk);
void readDestroyActorData(Chunk &chunk);
void readActorLoadComplete(Chunk &chunk);
void readCreateVariableData(Chunk &chunk);
void readContextNameData(Chunk &chunk);
void destroyActorsInContext(uint contextId);
};
extern MediaStationEngine *g_engine;
#define SHOULD_QUIT ::MediaStation::g_engine->shouldQuit()
} // End of namespace MediaStation
#endif // MEDIASTATION_H

View File

@@ -0,0 +1,58 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 "common/translation.h"
#include "mediastation/metaengine.h"
#include "mediastation/detection.h"
#include "mediastation/mediastation.h"
namespace MediaStation {
static const ADExtraGuiOptionsMap optionsList[] = {
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
} // End of namespace MediaStation
const char *MediaStationMetaEngine::getName() const {
return "mediastation";
}
const ADExtraGuiOptionsMap *MediaStationMetaEngine::getAdvancedExtraGuiOptions() const {
return MediaStation::optionsList;
}
Common::Error MediaStationMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
*engine = new MediaStation::MediaStationEngine(syst, desc);
return Common::kNoError;
}
bool MediaStationMetaEngine::hasFeature(MetaEngineFeature f) const {
return checkExtendedSaves(f) ||
(f == kSupportsLoadingDuringStartup);
}
#if PLUGIN_ENABLED_DYNAMIC(MEDIASTATION)
REGISTER_PLUGIN_DYNAMIC(MEDIASTATION, PLUGIN_TYPE_ENGINE, MediaStationMetaEngine);
#else
REGISTER_PLUGIN_STATIC(MEDIASTATION, PLUGIN_TYPE_ENGINE, MediaStationMetaEngine);
#endif

View File

@@ -0,0 +1,43 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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_METAENGINE_H
#define MEDIASTATION_METAENGINE_H
#include "engines/advancedDetector.h"
class MediaStationMetaEngine : public AdvancedMetaEngine<ADGameDescription> {
public:
const char *getName() const override;
Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
/**
* Determine whether the engine supports the specified MetaEngine feature.
*
* Used by e.g. the launcher to determine whether to enable the Load button.
*/
bool hasFeature(MetaEngineFeature f) const override;
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override;
};
#endif // MEDIASTATION_METAENGINE_H

View File

@@ -0,0 +1,46 @@
MODULE := engines/mediastation
MODULE_OBJS = \
actor.o \
actors/camera.o \
actors/canvas.o \
actors/document.o \
actors/font.o \
actors/hotspot.o \
actors/image.o \
actors/movie.o \
actors/palette.o \
actors/path.o \
actors/screen.o \
actors/sound.o \
actors/sprite.o \
actors/stage.o \
actors/text.o \
actors/timer.o \
audio.o \
bitmap.o \
boot.o \
clients.o \
context.o \
cursors.o \
datafile.o \
graphics.o \
mediascript/codechunk.o \
mediascript/collection.o \
mediascript/eventhandler.o \
mediascript/function.o \
mediascript/scriptconstants.o \
mediascript/scriptvalue.o \
mediastation.o \
metaengine.o
# This module can be built as a plugin
ifeq ($(ENABLE_MEDIASTATION), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o