mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
Although MTI had Print in its MTIKIT plugin, most of the content from MTIKIT, including Print, is found in the EXTRAS and EXPRMNTL plugins of many mtTropolis 1.1 titles.
605 lines
16 KiB
C++
605 lines
16 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* 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/file.h"
|
|
#include "common/random.h"
|
|
|
|
#include "mtropolis/plugin/mti.h"
|
|
#include "mtropolis/plugins.h"
|
|
|
|
#include "mtropolis/miniscript.h"
|
|
|
|
#include "video/mpegps_decoder.h"
|
|
|
|
#include "graphics/managed_surface.h"
|
|
|
|
#include "common/file.h"
|
|
namespace MTropolis {
|
|
|
|
namespace MTI {
|
|
|
|
|
|
/*
|
|
Board layout:
|
|
|
|
Layer 0:
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12
|
|
+-----+-----+-----+-----+-----+-----+
|
|
0 + 0 | 1 | 2 | 3 | 4 | 5 |
|
|
1 +-----+-----+-----+-----+-----+-----+
|
|
2 +-----+ 7 | 8 | 9 | 10 +-----+-----+
|
|
3 | 6 +-----+-----+-----+-----+ 11 | 12 |
|
|
4 +-----+ 13 | 14 | 15 | 16 +-----+-----+
|
|
5 +-----+-----+-----+-----+-----+-----+
|
|
6 + 17 | 18 | 19 | 20 | 21 | 22 |
|
|
+-----+-----+-----+-----+-----+-----+
|
|
|
|
Layer 1:
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12
|
|
|
|
0
|
|
1 +-----+-----+
|
|
2 | 23 | 24 |
|
|
3 +-----+-----+
|
|
4 | 25 | 26 |
|
|
5 +-----+-----+
|
|
6
|
|
|
|
Layer 2:
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12
|
|
|
|
0
|
|
1
|
|
2 +-----+
|
|
3 | 27 |
|
|
4 +-----+
|
|
5
|
|
6
|
|
|
|
*/
|
|
|
|
ShanghaiModifier::TileCoordinate ShanghaiModifier::_tileCoordinates[ShanghaiModifier::kNumTiles] = {
|
|
{0, 0, 0},
|
|
{2, 0, 0},
|
|
{4, 0, 0},
|
|
{6, 0, 0},
|
|
{8, 0, 0},
|
|
{10, 0, 0},
|
|
|
|
{0, 3, 0},
|
|
{2, 2, 0},
|
|
{4, 2, 0},
|
|
{6, 2, 0},
|
|
{8, 2, 0},
|
|
{10, 3, 0},
|
|
{12, 3, 0},
|
|
|
|
{2, 4, 0},
|
|
{4, 4, 0},
|
|
{6, 4, 0},
|
|
{8, 4, 0},
|
|
|
|
{0, 6, 0},
|
|
{2, 6, 0},
|
|
{4, 6, 0},
|
|
{6, 6, 0},
|
|
{8, 6, 0},
|
|
{10, 6, 0},
|
|
|
|
{4, 2, 0},
|
|
{6, 2, 0},
|
|
|
|
{4, 4, 1},
|
|
{6, 4, 1},
|
|
|
|
{5, 3, 2},
|
|
};
|
|
|
|
ShanghaiModifier::ShanghaiModifier() {
|
|
for (uint x = 0; x < kBoardSizeX; x++)
|
|
for (uint y = 0; y < kBoardSizeY; y++)
|
|
for (uint z = 0; z < kBoardSizeZ; z++)
|
|
_tileAtCoordinate[x][y][z] = -1;
|
|
|
|
for (uint i = 0; i < kNumTiles; i++) {
|
|
const TileCoordinate &coord = _tileCoordinates[i];
|
|
assert(coord.x < kBoardSizeX);
|
|
assert(coord.y < kBoardSizeY);
|
|
assert(coord.z < kBoardSizeZ);
|
|
_tileAtCoordinate[coord.x][coord.y][coord.z] = i;
|
|
}
|
|
}
|
|
|
|
ShanghaiModifier::~ShanghaiModifier() {
|
|
}
|
|
|
|
bool ShanghaiModifier::respondsToEvent(const Event &evt) const {
|
|
if (_resetTileSetWhen.respondsTo(evt))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
VThreadState ShanghaiModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
|
|
if (_resetTileSetWhen.respondsTo(msg->getEvent())) {
|
|
uint tileFaces[kNumTiles];
|
|
|
|
resetTiles(*runtime->getRandom(), tileFaces);
|
|
|
|
Modifier *varMod = this->_tileSetRef.resolution.lock().get();
|
|
|
|
if (varMod == nullptr || !varMod->isVariable()) {
|
|
warning("Shanghai reset var ref was unavailable");
|
|
return kVThreadError;
|
|
}
|
|
|
|
VariableModifier *var = static_cast<VariableModifier *>(varMod);
|
|
|
|
Common::SharedPtr<DynamicList> list(new DynamicList());
|
|
|
|
for (uint i = 0; i < kNumTiles; i++) {
|
|
DynamicValue tileValue;
|
|
tileValue.setInt(tileFaces[i]);
|
|
|
|
list->setAtIndex(i, tileValue);
|
|
}
|
|
|
|
DynamicValue listValue;
|
|
listValue.setList(list);
|
|
|
|
MiniscriptThread thread(runtime, nullptr, nullptr, nullptr, this);
|
|
var->varSetValue(&thread, listValue);
|
|
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
void ShanghaiModifier::disable(Runtime *runtime) {
|
|
}
|
|
|
|
bool ShanghaiModifier::load(const PlugInModifierLoaderContext &context, const Data::MTI::ShanghaiModifier &data) {
|
|
if (data.resetWhen.type != Data::PlugInTypeTaggedValue::kEvent)
|
|
return false;
|
|
|
|
if (!_resetTileSetWhen.load(data.resetWhen.value.asEvent))
|
|
return false;
|
|
|
|
if (data.tileSetVar.type != Data::PlugInTypeTaggedValue::kVariableReference)
|
|
return false;
|
|
|
|
_tileSetRef = VarReference(data.tileSetVar.value.asVarRefGUID, "");
|
|
|
|
return true;
|
|
}
|
|
|
|
void ShanghaiModifier::linkInternalReferences(ObjectLinkingScope *scope) {
|
|
_tileSetRef.linkInternalReferences(scope);
|
|
}
|
|
|
|
void ShanghaiModifier::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
|
|
_tileSetRef.visitInternalReferences(visitor);
|
|
}
|
|
|
|
void ShanghaiModifier::resetTiles(Common::RandomSource &rng, uint (&tileFaces)[kNumTiles]) const {
|
|
uint possibleFaces[kNumFaces];
|
|
uint numPossibleFaces = kNumFaces;
|
|
|
|
for (uint i = 0; i < kNumFaces; i++)
|
|
possibleFaces[i] = i + 1;
|
|
|
|
uint facesToInsert[kNumTiles / 2];
|
|
uint numFacesToInsert = kNumTiles / 2;
|
|
|
|
// Pick random faces, each one gets inserted twice
|
|
for (uint i = 0; i < kNumTiles / 4u; i++) {
|
|
uint faceToInsert = selectAndRemoveOne(rng, possibleFaces, numPossibleFaces);
|
|
facesToInsert[i * 2 + 0] = faceToInsert;
|
|
facesToInsert[i * 2 + 1] = faceToInsert;
|
|
}
|
|
|
|
// We build the board by adding all tiles and then randomly picking 2 exposed tiles and
|
|
// assigning them a matching pair. A pair is only valid if the resulting board state has
|
|
// valid moves.
|
|
BoardState_t boardState = emptyBoardState();
|
|
for (uint i = 0; i < kNumTiles; i++)
|
|
boardState = boardState | boardStateBit(i);
|
|
|
|
for (uint pair = 0; pair < kNumTiles / 2u; pair++) {
|
|
uint exposedTiles[kNumTiles];
|
|
uint numExposedTiles = 0;
|
|
|
|
for (uint i = 0; i < kNumTiles; i++) {
|
|
if (boardState & boardStateBit(i)) {
|
|
if (tileIsExposed(boardState, i))
|
|
exposedTiles[numExposedTiles++] = i;
|
|
}
|
|
}
|
|
|
|
uint firstExposedTile = selectAndRemoveOne(rng, exposedTiles, numExposedTiles);
|
|
|
|
BoardState_t withFirstRemoved = boardState ^ boardStateBit(firstExposedTile);
|
|
|
|
uint secondExposedTile = selectAndRemoveOne(rng, exposedTiles, numExposedTiles);
|
|
BoardState_t withBothRemoved = withFirstRemoved ^ boardStateBit(secondExposedTile);
|
|
|
|
if (numExposedTiles > 0) {
|
|
// If this isn't the last move, validate that this won't result in a stuck board state (e.g. only one tile exposed)
|
|
// If it would result in such a state, pick a different move.
|
|
for (;;) {
|
|
if (boardStateHasValidMove(withBothRemoved))
|
|
break;
|
|
|
|
if (numExposedTiles == 0) {
|
|
error("Shanghai board creation failed, board state was %x, removed %u to produce board state %x", static_cast<uint>(boardState), firstExposedTile, static_cast<uint>(withFirstRemoved));
|
|
break;
|
|
}
|
|
|
|
secondExposedTile = selectAndRemoveOne(rng, exposedTiles, numExposedTiles);
|
|
withBothRemoved = withFirstRemoved ^ boardStateBit(secondExposedTile);
|
|
}
|
|
}
|
|
|
|
boardState = withBothRemoved;
|
|
|
|
uint faceToInsert = selectAndRemoveOne(rng, facesToInsert, numFacesToInsert);
|
|
tileFaces[firstExposedTile] = faceToInsert;
|
|
tileFaces[secondExposedTile] = faceToInsert;
|
|
|
|
debug(2, "Shanghai randomizer: Move %u is %u + %u", pair, firstExposedTile, secondExposedTile);
|
|
}
|
|
}
|
|
|
|
uint ShanghaiModifier::selectAndRemoveOne(Common::RandomSource &rng, uint *valuesList, uint &listSize) {
|
|
if (listSize == 0) {
|
|
error("Internal error: selectAndRemoveOne ran out of values");
|
|
return 0;
|
|
}
|
|
|
|
if (listSize == 1) {
|
|
listSize = 0;
|
|
return valuesList[0];
|
|
}
|
|
|
|
uint selectedIndex = rng.getRandomNumber(listSize - 1);
|
|
uint selectedValue = valuesList[selectedIndex];
|
|
|
|
valuesList[selectedIndex] = valuesList[listSize - 1];
|
|
listSize--;
|
|
|
|
return selectedValue;
|
|
}
|
|
|
|
bool ShanghaiModifier::boardStateHasValidMove(BoardState_t boardState) const {
|
|
uint numExposedTiles = 0;
|
|
for (uint i = 0; i < kNumTiles; i++) {
|
|
if (boardState & boardStateBit(i)) {
|
|
if (tileIsExposed(boardState, i)) {
|
|
numExposedTiles++;
|
|
if (numExposedTiles == 2)
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ShanghaiModifier::tileIsExposed(BoardState_t boardState, uint tile) const {
|
|
uint tileX = _tileCoordinates[tile].x;
|
|
uint tileY = _tileCoordinates[tile].y;
|
|
uint tileZ = _tileCoordinates[tile].z;
|
|
|
|
uint blockMinY = tileY;
|
|
uint blockMaxY = tileY;
|
|
if (blockMinY > 0)
|
|
blockMinY--;
|
|
if (blockMaxY < kBoardSizeY - 1u)
|
|
blockMaxY++;
|
|
|
|
bool blockedOnLeft = false;
|
|
|
|
if (tileX >= 2) {
|
|
// Check for left-side blocks
|
|
for (uint y = blockMinY; y <= blockMaxY; y++) {
|
|
if (tileExistsAtCoordinate(boardState, tileX - 2, y, tileZ)) {
|
|
blockedOnLeft = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (blockedOnLeft) {
|
|
bool blockedOnRight = false;
|
|
|
|
// Check for right-side blocks
|
|
if (tileX < kBoardSizeX - 2u) {
|
|
for (uint y = blockMinY; y <= blockMaxY; y++) {
|
|
if (tileExistsAtCoordinate(boardState, tileX + 2, y, tileZ)) {
|
|
blockedOnRight = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tile is blocked on left and right
|
|
if (blockedOnRight)
|
|
return false;
|
|
}
|
|
|
|
// Check upper blocks
|
|
uint blockMinX = tileX;
|
|
uint blockMaxX = tileX;
|
|
if (blockMinX > 0)
|
|
blockMinX--;
|
|
if (blockMaxX < kBoardSizeX - 1u)
|
|
blockMaxX++;
|
|
|
|
for (uint z = tileZ + 1; z < kBoardSizeZ; z++) {
|
|
for (uint x = blockMinX; x <= blockMaxX; x++) {
|
|
for (uint y = blockMinY; y <= blockMaxY; y++) {
|
|
if (tileExistsAtCoordinate(boardState, x, y, z))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ShanghaiModifier::tileExistsAtCoordinate(BoardState_t boardState, uint x, uint y, uint z) const {
|
|
assert(x < kBoardSizeX);
|
|
assert(y < kBoardSizeY);
|
|
assert(z < kBoardSizeZ);
|
|
|
|
int8 tile = _tileAtCoordinate[x][y][z];
|
|
|
|
if (tile < 0)
|
|
return false;
|
|
|
|
if (boardState & boardStateBit(static_cast<uint>(tile)))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
ShanghaiModifier::BoardState_t ShanghaiModifier::boardStateBit(uint bit) {
|
|
return static_cast<BoardState_t>(1) << bit;
|
|
}
|
|
|
|
ShanghaiModifier::BoardState_t ShanghaiModifier::emptyBoardState() {
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
void ShanghaiModifier::debugInspect(IDebugInspectionReport *report) const {
|
|
}
|
|
#endif
|
|
|
|
Common::SharedPtr<Modifier> ShanghaiModifier::shallowClone() const {
|
|
return Common::SharedPtr<Modifier>(new ShanghaiModifier(*this));
|
|
}
|
|
|
|
const char *ShanghaiModifier::getDefaultName() const {
|
|
return "Shanghai Modifier"; // ???
|
|
}
|
|
|
|
class MPEGVideoPlayer : public IPostEffect, public IPlayMediaSignalReceiver {
|
|
public:
|
|
explicit MPEGVideoPlayer(Runtime *runtime, const Common::SharedPtr<Video::VideoDecoder> &videoDecoder, IMPEGVideoCompletionNotifier *completionNotifier);
|
|
~MPEGVideoPlayer();
|
|
|
|
static Common::SharedPtr<MPEGVideoPlayer> createForVideoID(Runtime *runtime, int videoID, IMPEGVideoCompletionNotifier *completionNotifier);
|
|
|
|
void playMedia(Runtime *runtime, Project *project) override;
|
|
void renderPostEffect(Graphics::ManagedSurface &surface) const override;
|
|
|
|
private:
|
|
Runtime *_runtime;
|
|
Project *_project;
|
|
IMPEGVideoCompletionNotifier *_completionNotifier;
|
|
|
|
const Graphics::Surface *_displayingSurface;
|
|
Common::SharedPtr<Video::VideoDecoder> _decoder;
|
|
Common::SharedPtr<PlayMediaSignaller> _playMediaReceiver;
|
|
bool _finished;
|
|
};
|
|
|
|
|
|
MPEGVideoPlayer::MPEGVideoPlayer(Runtime *runtime, const Common::SharedPtr<Video::VideoDecoder> &videoDecoder, IMPEGVideoCompletionNotifier *completionNotifier)
|
|
: _runtime(runtime), _project(nullptr), _decoder(videoDecoder), _finished(false), _displayingSurface(nullptr), _completionNotifier(completionNotifier) {
|
|
_project = runtime->getProject();
|
|
|
|
runtime->addPostEffect(this);
|
|
_playMediaReceiver = _project->notifyOnPlayMedia(this);
|
|
}
|
|
|
|
MPEGVideoPlayer::~MPEGVideoPlayer() {
|
|
_playMediaReceiver->removeReceiver(this);
|
|
_runtime->removePostEffect(this);
|
|
}
|
|
|
|
Common::SharedPtr<MPEGVideoPlayer> MPEGVideoPlayer::createForVideoID(Runtime *runtime, int videoID, IMPEGVideoCompletionNotifier *completionNotifier) {
|
|
#ifdef USE_MPEG2
|
|
Common::String videoPath = Common::String::format("video/%i.vob", videoID);
|
|
|
|
Common::SharedPtr<Video::VideoDecoder> decoder(new Video::MPEGPSDecoder());
|
|
if (!decoder->loadFile(Common::Path(videoPath)))
|
|
return nullptr;
|
|
|
|
decoder->start();
|
|
|
|
return Common::SharedPtr<MPEGVideoPlayer>(new MPEGVideoPlayer(runtime, decoder, completionNotifier));
|
|
#else
|
|
return nullptr;
|
|
#endif
|
|
}
|
|
|
|
void MPEGVideoPlayer::playMedia(Runtime *runtime, Project *project) {
|
|
if (_finished)
|
|
return;
|
|
|
|
while (_decoder->getTimeToNextFrame() == 0) {
|
|
const Graphics::Surface *newFrame = _decoder->decodeNextFrame();
|
|
if (newFrame) {
|
|
_displayingSurface = newFrame;
|
|
_runtime->setSceneGraphDirty();
|
|
} else {
|
|
_finished = true;
|
|
_displayingSurface = nullptr;
|
|
_completionNotifier->onVideoCompleted();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MPEGVideoPlayer::renderPostEffect(Graphics::ManagedSurface &surface) const {
|
|
if (_displayingSurface) {
|
|
const Graphics::Surface *surf = _displayingSurface;
|
|
|
|
Graphics::ManagedSurface *mainWindowSurf = _runtime->getMainWindow().lock()->getSurface().get();
|
|
|
|
int32 topLeftX = (mainWindowSurf->w - surf->w) / 2;
|
|
int32 topLeftY = (mainWindowSurf->h - surf->h) / 2;
|
|
|
|
Common::Rect fullVideoRect(topLeftX, topLeftY, topLeftX + surf->w, topLeftY + surf->h);
|
|
Common::Rect clippedVideoRect = fullVideoRect;
|
|
clippedVideoRect.clip(Common::Rect(0, 0, mainWindowSurf->w, mainWindowSurf->h));
|
|
|
|
Common::Rect videoSrcRect(0, 0, surf->w, surf->h);
|
|
videoSrcRect.left += clippedVideoRect.left - fullVideoRect.left;
|
|
videoSrcRect.right += clippedVideoRect.right - fullVideoRect.right;
|
|
videoSrcRect.top += clippedVideoRect.top - fullVideoRect.top;
|
|
videoSrcRect.bottom += clippedVideoRect.bottom - fullVideoRect.bottom;
|
|
|
|
mainWindowSurf->blitFrom(*surf, videoSrcRect, clippedVideoRect);
|
|
}
|
|
}
|
|
SampleModifier::SampleModifier() : _videoNumber(0), _runtime(nullptr), _isPlaying(false) {
|
|
}
|
|
|
|
SampleModifier::~SampleModifier() {
|
|
stopPlaying();
|
|
}
|
|
|
|
bool SampleModifier::respondsToEvent(const Event &evt) const {
|
|
return _executeWhen.respondsTo(evt);
|
|
}
|
|
|
|
VThreadState SampleModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
|
|
if (_executeWhen.respondsTo(msg->getEvent())) {
|
|
_runtime = runtime;
|
|
|
|
stopPlaying();
|
|
_vidPlayer.reset();
|
|
|
|
_vidPlayer = MPEGVideoPlayer::createForVideoID(runtime, _videoNumber, this);
|
|
if (_vidPlayer) {
|
|
runtime->addMouseBlocker();
|
|
runtime->getMainWindow().lock()->setMouseVisible(false);
|
|
runtime->setSceneGraphDirty();
|
|
_keySignaller = _runtime->getProject()->notifyOnKeyboardEvent(this);
|
|
_isPlaying = true;
|
|
} else {
|
|
warning("Attempted to play MPEG video %i but player setup failed", static_cast<int>(_videoNumber));
|
|
}
|
|
|
|
return kVThreadReturn;
|
|
}
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
void SampleModifier::disable(Runtime *runtime) {
|
|
stopPlaying();
|
|
_vidPlayer.reset();
|
|
}
|
|
|
|
bool SampleModifier::load(const PlugInModifierLoaderContext &context, const Data::MTI::SampleModifier &data) {
|
|
if (data.executeWhen.type != Data::PlugInTypeTaggedValue::kEvent)
|
|
return false;
|
|
|
|
if (data.videoNumber.type != Data::PlugInTypeTaggedValue::kInteger)
|
|
return false;
|
|
|
|
_videoNumber = data.videoNumber.value.asInt;
|
|
|
|
if (!_executeWhen.load(data.executeWhen.value.asEvent))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void SampleModifier::onVideoCompleted() {
|
|
stopPlaying();
|
|
}
|
|
|
|
void SampleModifier::onKeyboardEvent(Runtime *runtime, const KeyboardInputEvent &keyEvt) {
|
|
if (keyEvt.getKeyEventType() == Common::EVENT_KEYDOWN && keyEvt.getKeyState().keycode == Common::KEYCODE_SPACE) {
|
|
_vidPlayer.reset();
|
|
stopPlaying();
|
|
}
|
|
}
|
|
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
void SampleModifier::debugInspect(IDebugInspectionReport *report) const {
|
|
}
|
|
#endif
|
|
|
|
Common::SharedPtr<Modifier> SampleModifier::shallowClone() const {
|
|
return Common::SharedPtr<Modifier>(new SampleModifier(*this));
|
|
}
|
|
|
|
const char *SampleModifier::getDefaultName() const {
|
|
return "Sample Modifier";
|
|
}
|
|
|
|
void SampleModifier::stopPlaying() {
|
|
if (_isPlaying) {
|
|
_runtime->removeMouseBlocker();
|
|
_runtime->getMainWindow().lock()->setMouseVisible(true);
|
|
_keySignaller->removeReceiver(this);
|
|
_isPlaying = false;
|
|
}
|
|
}
|
|
|
|
MTIPlugIn::MTIPlugIn()
|
|
: _shanghaiModifierFactory(this), _sampleModifierFactory(this) {
|
|
}
|
|
|
|
void MTIPlugIn::registerModifiers(IPlugInModifierRegistrar *registrar) const {
|
|
registrar->registerPlugInModifier("Shanghai", &_shanghaiModifierFactory);
|
|
registrar->registerPlugInModifier("Sample", &_sampleModifierFactory);
|
|
}
|
|
|
|
|
|
} // namespace MTI
|
|
|
|
namespace PlugIns {
|
|
|
|
Common::SharedPtr<PlugIn> createMTI() {
|
|
return Common::SharedPtr<PlugIn>(new MTI::MTIPlugIn());
|
|
}
|
|
|
|
} // End of namespace PlugIns
|
|
|
|
} // End of namespace MTropolis
|