diff options
Diffstat (limited to 'engines/sword25/gfx/animation.cpp')
-rw-r--r-- | engines/sword25/gfx/animation.cpp | 711 |
1 files changed, 711 insertions, 0 deletions
diff --git a/engines/sword25/gfx/animation.cpp b/engines/sword25/gfx/animation.cpp new file mode 100644 index 0000000000..7ec445acb8 --- /dev/null +++ b/engines/sword25/gfx/animation.cpp @@ -0,0 +1,711 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/animation.h" + +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/resmanager.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/callbackregistry.h" +#include "sword25/package/packagemanager.h" +#include "sword25/gfx/image/image.h" +#include "sword25/gfx/animationtemplate.h" +#include "sword25/gfx/animationtemplateregistry.h" +#include "sword25/gfx/animationresource.h" +#include "sword25/gfx/bitmapresource.h" +#include "sword25/gfx/graphicengine.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "ANIMATION" + +Animation::Animation(RenderObjectPtr<RenderObject> parentPtr, const Common::String &fileName) : + TimedRenderObject(parentPtr, RenderObject::TYPE_ANIMATION) { + // Das BS_RenderObject konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. + if (!_initSuccess) + return; + + initMembers(); + + // Vom negativen Fall ausgehen. + _initSuccess = false; + + initializeAnimationResource(fileName); + + // Erfolg signalisieren. + _initSuccess = true; +} + +Animation::Animation(RenderObjectPtr<RenderObject> parentPtr, const AnimationTemplate &templ) : + TimedRenderObject(parentPtr, RenderObject::TYPE_ANIMATION) { + // Das BS_RenderObject konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. + if (!_initSuccess) + return; + + initMembers(); + + // Vom negativen Fall ausgehen. + _initSuccess = false; + + _animationTemplateHandle = AnimationTemplate::create(templ); + + // Erfolg signalisieren. + _initSuccess = true; +} + +Animation::Animation(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle) : + TimedRenderObject(parentPtr, RenderObject::TYPE_ANIMATION, handle) { + // Das BS_RenderObject konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. + if (!_initSuccess) + return; + + initMembers(); + + // Objekt vom Stream laden. + _initSuccess = unpersist(reader); +} + +void Animation::initializeAnimationResource(const Common::String &fileName) { + // Die Resource wird für die gesamte Lebensdauer des Animations-Objektes gelockt. + Resource *resourcePtr = Kernel::GetInstance()->GetResourceManager()->RequestResource(fileName); + if (resourcePtr && resourcePtr->GetType() == Resource::TYPE_ANIMATION) + _animationResourcePtr = static_cast<AnimationResource *>(resourcePtr); + else { + BS_LOG_ERRORLN("The resource \"%s\" could not be requested. The Animation can't be created.", fileName.c_str()); + return; + } + + // Größe und Position der Animation anhand des aktuellen Frames bestimmen. + computeCurrentCharacteristics(); +} + +void Animation::initMembers() { + _currentFrame = 0; + _currentFrameTime = 0; + _direction = FORWARD; + _running = false; + _finished = false; + _relX = 0; + _relY = 0; + _scaleFactorX = 1.0f; + _scaleFactorY = 1.0f; + _modulationColor = 0xffffffff; + _animationResourcePtr = 0; + _animationTemplateHandle = 0; + _framesLocked = false; +} + +Animation::~Animation() { + if (getAnimationDescription()) { + stop(); + getAnimationDescription()->unlock(); + } + + // Delete Callbacks + Common::Array<ANIMATION_CALLBACK_DATA>::iterator it = _deleteCallbacks.begin(); + for (; it != _deleteCallbacks.end(); it++)((*it).Callback)((*it).Data); + +} + +void Animation::play() { + // Wenn die Animation zuvor komplett durchgelaufen ist, wird sie wieder von Anfang abgespielt + if (_finished) + stop(); + + _running = true; + lockAllFrames(); +} + +void Animation::pause() { + _running = false; + unlockAllFrames(); +} + +void Animation::stop() { + _currentFrame = 0; + _currentFrameTime = 0; + _direction = FORWARD; + pause(); +} + +void Animation::setFrame(uint nr) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + + if (nr >= animationDescriptionPtr->getFrameCount()) { + BS_LOG_ERRORLN("Tried to set animation to illegal frame (%d). Value must be between 0 and %d.", + nr, animationDescriptionPtr->getFrameCount()); + return; + } + + _currentFrame = nr; + _currentFrameTime = 0; + computeCurrentCharacteristics(); + forceRefresh(); +} + +bool Animation::doRender() { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + BS_ASSERT(_currentFrame < animationDescriptionPtr->getFrameCount()); + + // Bitmap des aktuellen Frames holen + Resource *pResource = Kernel::GetInstance()->GetResourceManager()->RequestResource(animationDescriptionPtr->getFrame(_currentFrame).fileName); + BS_ASSERT(pResource); + BS_ASSERT(pResource->GetType() == Resource::TYPE_BITMAP); + BitmapResource *pBitmapResource = static_cast<BitmapResource *>(pResource); + + // Framebufferobjekt holen + GraphicEngine *pGfx = static_cast<GraphicEngine *>(Kernel::GetInstance()->GetService("gfx")); + BS_ASSERT(pGfx); + + // Bitmap zeichnen + bool result; + if (isScalingAllowed() && (_width != pBitmapResource->getWidth() || _height != pBitmapResource->getHeight())) { + result = pBitmapResource->blit(_absoluteX, _absoluteY, + (animationDescriptionPtr->getFrame(_currentFrame).flipV ? BitmapResource::FLIP_V : 0) | + (animationDescriptionPtr->getFrame(_currentFrame).flipH ? BitmapResource::FLIP_H : 0), + 0, _modulationColor, _width, _height); + } else { + result = pBitmapResource->blit(_absoluteX, _absoluteY, + (animationDescriptionPtr->getFrame(_currentFrame).flipV ? BitmapResource::FLIP_V : 0) | + (animationDescriptionPtr->getFrame(_currentFrame).flipH ? BitmapResource::FLIP_H : 0), + 0, _modulationColor, -1, -1); + } + + // Resource freigeben + pBitmapResource->release(); + + return result; +} + +void Animation::frameNotification(int timeElapsed) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + BS_ASSERT(timeElapsed >= 0); + + // Nur wenn die Animation läuft wird sie auch weiterbewegt + if (_running) { + // Gesamte vergangene Zeit bestimmen (inkl. Restzeit des aktuellen Frames) + _currentFrameTime += timeElapsed; + + // Anzahl an zu überpringenden Frames bestimmen + int skipFrames = animationDescriptionPtr->getMillisPerFrame() == 0 ? 0 : _currentFrameTime / animationDescriptionPtr->getMillisPerFrame(); + + // Neue Frame-Restzeit bestimmen + _currentFrameTime -= animationDescriptionPtr->getMillisPerFrame() * skipFrames; + + // Neuen Frame bestimmen (je nach aktuellener Abspielrichtung wird addiert oder subtrahiert) + int tmpCurFrame = _currentFrame; + switch (_direction) { + case FORWARD: + tmpCurFrame += skipFrames; + break; + + case BACKWARD: + tmpCurFrame -= skipFrames; + break; + + default: + BS_ASSERT(0); + } + + // Überläufe behandeln + if (tmpCurFrame < 0) { + // Loop-Point Callbacks + for (uint i = 0; i < _loopPointCallbacks.size();) { + if ((_loopPointCallbacks[i].Callback)(_loopPointCallbacks[i].Data) == false) { + _loopPointCallbacks.remove_at(i); + } else + i++; + } + + // Ein Unterlauf darf nur auftreten, wenn der Animationstyp JOJO ist. + BS_ASSERT(animationDescriptionPtr->getAnimationType() == AT_JOJO); + tmpCurFrame = - tmpCurFrame; + _direction = FORWARD; + } else if (static_cast<uint>(tmpCurFrame) >= animationDescriptionPtr->getFrameCount()) { + // Loop-Point Callbacks + for (uint i = 0; i < _loopPointCallbacks.size();) { + if ((_loopPointCallbacks[i].Callback)(_loopPointCallbacks[i].Data) == false) { + _loopPointCallbacks.remove_at(i); + } else + i++; + } + + switch (animationDescriptionPtr->getAnimationType()) { + case AT_ONESHOT: + tmpCurFrame = animationDescriptionPtr->getFrameCount() - 1; + _finished = true; + pause(); + break; + + case AT_LOOP: + tmpCurFrame = tmpCurFrame % animationDescriptionPtr->getFrameCount(); + break; + + case AT_JOJO: + tmpCurFrame = animationDescriptionPtr->getFrameCount() - (tmpCurFrame % animationDescriptionPtr->getFrameCount()) - 1; + _direction = BACKWARD; + break; + + default: + BS_ASSERT(0); + } + } + + if ((int)_currentFrame != tmpCurFrame) { + forceRefresh(); + + if (animationDescriptionPtr->getFrame(_currentFrame).action != "") { + // Action Callbacks + for (uint i = 0; i < _actionCallbacks.size();) { + if ((_actionCallbacks[i].Callback)(_actionCallbacks[i].Data) == false) { + _actionCallbacks.remove_at(i); + } else + i++; + } + } + } + + _currentFrame = static_cast<uint>(tmpCurFrame); + } + + // Größe und Position der Animation anhand des aktuellen Frames bestimmen + computeCurrentCharacteristics(); + + BS_ASSERT(_currentFrame < animationDescriptionPtr->getFrameCount()); + BS_ASSERT(_currentFrameTime >= 0); +} + +void Animation::computeCurrentCharacteristics() { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + const AnimationResource::Frame &curFrame = animationDescriptionPtr->getFrame(_currentFrame); + + Resource *pResource = Kernel::GetInstance()->GetResourceManager()->RequestResource(curFrame.fileName); + BS_ASSERT(pResource); + BS_ASSERT(pResource->GetType() == Resource::TYPE_BITMAP); + BitmapResource *pBitmap = static_cast<BitmapResource *>(pResource); + + // Größe des Bitmaps auf die Animation übertragen + _width = static_cast<int>(pBitmap->getWidth() * _scaleFactorX); + _height = static_cast<int>(pBitmap->getHeight() * _scaleFactorY); + + // Position anhand des Hotspots berechnen und setzen + int posX = _relX + computeXModifier(); + int posY = _relY + computeYModifier(); + + RenderObject::setPos(posX, posY); + + pBitmap->release(); +} + +bool Animation::lockAllFrames() { + if (!_framesLocked) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + for (uint i = 0; i < animationDescriptionPtr->getFrameCount(); ++i) { + if (!Kernel::GetInstance()->GetResourceManager()->RequestResource(animationDescriptionPtr->getFrame(i).fileName)) { + BS_LOG_ERRORLN("Could not lock all animation frames."); + return false; + } + } + + _framesLocked = true; + } + + return true; +} + +bool Animation::unlockAllFrames() { + if (_framesLocked) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + for (uint i = 0; i < animationDescriptionPtr->getFrameCount(); ++i) { + Resource *pResource; + if (!(pResource = Kernel::GetInstance()->GetResourceManager()->RequestResource(animationDescriptionPtr->getFrame(i).fileName))) { + BS_LOG_ERRORLN("Could not unlock all animation frames."); + return false; + } + + // Zwei mal freigeben um den Request von LockAllFrames() und den jetzigen Request aufzuheben + pResource->release(); + if (pResource->GetLockCount()) + pResource->release(); + } + + _framesLocked = false; + } + + return true; +} + +Animation::ANIMATION_TYPES Animation::getAnimationType() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->getAnimationType(); +} + +int Animation::getFPS() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->getFPS(); +} + +int Animation::getFrameCount() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->getFrameCount(); +} + +bool Animation::isScalingAllowed() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->isScalingAllowed(); +} + +bool Animation::isAlphaAllowed() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->isAlphaAllowed(); +} + +bool Animation::isColorModulationAllowed() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->isColorModulationAllowed(); +} + +void Animation::setPos(int relX, int relY) { + _relX = relX; + _relY = relY; + + computeCurrentCharacteristics(); +} + +void Animation::setX(int relX) { + _relX = relX; + + computeCurrentCharacteristics(); +} + +void Animation::setY(int relY) { + _relY = relY; + + computeCurrentCharacteristics(); +} + +void Animation::setAlpha(int alpha) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + if (!animationDescriptionPtr->isAlphaAllowed()) { + BS_LOG_WARNINGLN("Tried to set alpha value on an animation that does not support alpha. Call was ignored."); + return; + } + + uint newModulationColor = (_modulationColor & 0x00ffffff) | alpha << 24; + if (newModulationColor != _modulationColor) { + _modulationColor = newModulationColor; + forceRefresh(); + } +} + +void Animation::setModulationColor(uint modulationColor) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + if (!animationDescriptionPtr->isColorModulationAllowed()) { + BS_LOG_WARNINGLN("Tried to set modulation color on an animation that does not support color modulation. Call was ignored"); + return; + } + + uint newModulationColor = (modulationColor & 0x00ffffff) | (_modulationColor & 0xff000000); + if (newModulationColor != _modulationColor) { + _modulationColor = newModulationColor; + forceRefresh(); + } +} + +void Animation::setScaleFactor(float scaleFactor) { + setScaleFactorX(scaleFactor); + setScaleFactorY(scaleFactor); +} + +void Animation::setScaleFactorX(float scaleFactorX) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + if (!animationDescriptionPtr->isScalingAllowed()) { + BS_LOG_WARNINGLN("Tried to set x scale factor on an animation that does not support scaling. Call was ignored"); + return; + } + + if (scaleFactorX != _scaleFactorX) { + _scaleFactorX = scaleFactorX; + if (_scaleFactorX <= 0.0f) + _scaleFactorX = 0.001f; + forceRefresh(); + computeCurrentCharacteristics(); + } +} + +void Animation::setScaleFactorY(float scaleFactorY) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + if (!animationDescriptionPtr->isScalingAllowed()) { + BS_LOG_WARNINGLN("Tried to set y scale factor on an animation that does not support scaling. Call was ignored"); + return; + } + + if (scaleFactorY != _scaleFactorY) { + _scaleFactorY = scaleFactorY; + if (_scaleFactorY <= 0.0f) + _scaleFactorY = 0.001f; + forceRefresh(); + computeCurrentCharacteristics(); + } +} + +const Common::String &Animation::getCurrentAction() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->getFrame(_currentFrame).action; +} + +int Animation::getX() const { + return _relX; +} + +int Animation::getY() const { + return _relY; +} + +int Animation::getAbsoluteX() const { + return _absoluteX + (_relX - _x); +} + +int Animation::getAbsoluteY() const { + return _absoluteY + (_relY - _y); +} + +int Animation::computeXModifier() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + const AnimationResource::Frame &curFrame = animationDescriptionPtr->getFrame(_currentFrame); + + Resource *pResource = Kernel::GetInstance()->GetResourceManager()->RequestResource(curFrame.fileName); + BS_ASSERT(pResource); + BS_ASSERT(pResource->GetType() == Resource::TYPE_BITMAP); + BitmapResource *pBitmap = static_cast<BitmapResource *>(pResource); + + int result = curFrame.flipV ? - static_cast<int>((pBitmap->getWidth() - 1 - curFrame.hotspotX) * _scaleFactorX) : + - static_cast<int>(curFrame.hotspotX * _scaleFactorX); + + pBitmap->release(); + + return result; +} + +int Animation::computeYModifier() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + const AnimationResource::Frame &curFrame = animationDescriptionPtr->getFrame(_currentFrame); + + Resource *pResource = Kernel::GetInstance()->GetResourceManager()->RequestResource(curFrame.fileName); + BS_ASSERT(pResource); + BS_ASSERT(pResource->GetType() == Resource::TYPE_BITMAP); + BitmapResource *pBitmap = static_cast<BitmapResource *>(pResource); + + int result = curFrame.flipH ? - static_cast<int>((pBitmap->getHeight() - 1 - curFrame.hotspotY) * _scaleFactorY) : + - static_cast<int>(curFrame.hotspotY * _scaleFactorY); + + pBitmap->release(); + + return result; +} + +void Animation::registerActionCallback(ANIMATION_CALLBACK callback, uint data) { + ANIMATION_CALLBACK_DATA cd; + cd.Callback = callback; + cd.Data = data; + _actionCallbacks.push_back(cd); +} + +void Animation::registerLoopPointCallback(ANIMATION_CALLBACK callback, uint data) { + ANIMATION_CALLBACK_DATA cd; + cd.Callback = callback; + cd.Data = data; + _loopPointCallbacks.push_back(cd); +} + +void Animation::registerDeleteCallback(ANIMATION_CALLBACK callback, uint data) { + ANIMATION_CALLBACK_DATA cd; + cd.Callback = callback; + cd.Data = data; + _deleteCallbacks.push_back(cd); +} + +void Animation::persistCallbackVector(OutputPersistenceBlock &writer, const Common::Array<ANIMATION_CALLBACK_DATA> &vector) { + // Anzahl an Callbacks persistieren. + writer.write(vector.size()); + + // Alle Callbacks einzeln persistieren. + Common::Array<ANIMATION_CALLBACK_DATA>::const_iterator it = vector.begin(); + while (it != vector.end()) { + writer.write(CallbackRegistry::getInstance().resolveCallbackPointer((void (*)(int))it->Callback)); + writer.write(it->Data); + + ++it; + } +} + +void Animation::unpersistCallbackVector(InputPersistenceBlock &reader, Common::Array<ANIMATION_CALLBACK_DATA> &vector) { + // Callbackvector leeren. + vector.resize(0); + + // Anzahl an Callbacks einlesen. + uint callbackCount; + reader.read(callbackCount); + + // Alle Callbacks einzeln wieder herstellen. + for (uint i = 0; i < callbackCount; ++i) { + ANIMATION_CALLBACK_DATA callbackData; + + Common::String callbackFunctionName; + reader.read(callbackFunctionName); + callbackData.Callback = reinterpret_cast<ANIMATION_CALLBACK>(CallbackRegistry::getInstance().resolveCallbackFunction(callbackFunctionName)); + + reader.read(callbackData.Data); + + vector.push_back(callbackData); + } +} + +bool Animation::persist(OutputPersistenceBlock &writer) { + bool result = true; + + result &= RenderObject::persist(writer); + + writer.write(_relX); + writer.write(_relY); + writer.write(_scaleFactorX); + writer.write(_scaleFactorY); + writer.write(_modulationColor); + writer.write(_currentFrame); + writer.write(_currentFrameTime); + writer.write(_running); + writer.write(_finished); + writer.write(static_cast<uint>(_direction)); + + // Je nach Animationstyp entweder das Template oder die Ressource speichern. + if (_animationResourcePtr) { + uint marker = 0; + writer.write(marker); + writer.write(_animationResourcePtr->getFileName()); + } else if (_animationTemplateHandle) { + uint marker = 1; + writer.write(marker); + writer.write(_animationTemplateHandle); + } else { + BS_ASSERT(false); + } + + //writer.write(_AnimationDescriptionPtr); + + writer.write(_framesLocked); + persistCallbackVector(writer, _loopPointCallbacks); + persistCallbackVector(writer, _actionCallbacks); + persistCallbackVector(writer, _deleteCallbacks); + + result &= RenderObject::persistChildren(writer); + + return result; +} + +// ----------------------------------------------------------------------------- + +bool Animation::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + result &= RenderObject::unpersist(reader); + + reader.read(_relX); + reader.read(_relY); + reader.read(_scaleFactorX); + reader.read(_scaleFactorY); + reader.read(_modulationColor); + reader.read(_currentFrame); + reader.read(_currentFrameTime); + reader.read(_running); + reader.read(_finished); + uint direction; + reader.read(direction); + _direction = static_cast<Direction>(direction); + + // Animationstyp einlesen. + uint marker; + reader.read(marker); + if (marker == 0) { + Common::String resourceFilename; + reader.read(resourceFilename); + initializeAnimationResource(resourceFilename); + } else if (marker == 1) { + reader.read(_animationTemplateHandle); + } else { + BS_ASSERT(false); + } + + reader.read(_framesLocked); + if (_framesLocked) + lockAllFrames(); + + unpersistCallbackVector(reader, _loopPointCallbacks); + unpersistCallbackVector(reader, _actionCallbacks); + unpersistCallbackVector(reader, _deleteCallbacks); + + result &= RenderObject::unpersistChildren(reader); + + return reader.isGood() && result; +} + +// ----------------------------------------------------------------------------- + +AnimationDescription *Animation::getAnimationDescription() const { + if (_animationResourcePtr) + return _animationResourcePtr; + else + return AnimationTemplateRegistry::getInstance().resolveHandle(_animationTemplateHandle); +} + +} // End of namespace Sword25 |