606 lines
16 KiB
C++
606 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
|