mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-26 19:55:44 +00:00
769dd1d7a2
- The spiritual barometer display in IHNM is now updated only when necessary, to speed drawing up. This also corrects an issue where the spiritual barometer display was updated only after changing a scene - sf92 is sfDemoSetInteractive - It's now possible to use dashes and underscores in savegames - Screen fading when changing scenes is now done correctly: the interface will no longer be incorrectly briefly shown while the screen is fading to black - The interface mode is now correctly set in the non-interactive part of the IHNM demo - sfScriptGotoScene does not have a transition parameter, therefore that parameter has been removed svn-id: r28643
674 lines
16 KiB
C++
674 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 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$
|
|
*
|
|
*/
|
|
|
|
// Event management module
|
|
|
|
#include "saga/saga.h"
|
|
#include "saga/gfx.h"
|
|
|
|
#include "saga/animation.h"
|
|
#include "saga/console.h"
|
|
#include "saga/scene.h"
|
|
#include "saga/interface.h"
|
|
#include "saga/palanim.h"
|
|
#include "saga/render.h"
|
|
#include "saga/sndres.h"
|
|
#include "saga/rscfile.h"
|
|
#include "saga/music.h"
|
|
#include "saga/actor.h"
|
|
|
|
#include "saga/events.h"
|
|
|
|
namespace Saga {
|
|
|
|
Events::Events(SagaEngine *vm) : _vm(vm), _initialized(false) {
|
|
debug(8, "Initializing event subsystem...");
|
|
_initialized = true;
|
|
}
|
|
|
|
Events::~Events(void) {
|
|
debug(8, "Shutting down event subsystem...");
|
|
freeList();
|
|
}
|
|
|
|
// Function to process event list once per frame.
|
|
// First advances event times, then processes each event with the appropriate
|
|
// handler depending on the type of event.
|
|
int Events::handleEvents(long msec) {
|
|
Event *event_p;
|
|
|
|
long delta_time;
|
|
int result;
|
|
|
|
// Advance event times
|
|
processEventTime(msec);
|
|
|
|
// Process each event in list
|
|
for (EventList::iterator eventi = _eventList.begin(); eventi != _eventList.end(); ++eventi) {
|
|
event_p = (Event *)eventi.operator->();
|
|
|
|
// Call the appropriate event handler for the specific event type
|
|
switch (event_p->type) {
|
|
|
|
case kEvTOneshot:
|
|
result = handleOneShot(event_p);
|
|
break;
|
|
|
|
case kEvTContinuous:
|
|
result = handleContinuous(event_p);
|
|
break;
|
|
|
|
case kEvTInterval:
|
|
result = handleInterval(event_p);
|
|
break;
|
|
|
|
case kEvTImmediate:
|
|
result = handleImmediate(event_p);
|
|
break;
|
|
|
|
default:
|
|
result = kEvStInvalidCode;
|
|
warning("Invalid event code encountered");
|
|
break;
|
|
}
|
|
|
|
// Process the event appropriately based on result code from
|
|
// handler
|
|
if ((result == kEvStDelete) || (result == kEvStInvalidCode)) {
|
|
// If there is no event chain, delete the base event.
|
|
if (event_p->chain == NULL) {
|
|
eventi = _eventList.eraseAndPrev(eventi);
|
|
} else {
|
|
// If there is an event chain present, move the next event
|
|
// in the chain up, adjust it by the previous delta time,
|
|
// and reprocess the event
|
|
delta_time = event_p->time;
|
|
Event *from_chain = event_p->chain;
|
|
memcpy(event_p, from_chain, sizeof(*event_p));
|
|
free(from_chain);
|
|
|
|
event_p->time += delta_time;
|
|
--eventi;
|
|
}
|
|
} else if (result == kEvStBreak) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
int Events::handleContinuous(Event *event) {
|
|
double event_pc = 0.0; // Event completion percentage
|
|
int event_done = 0;
|
|
|
|
Surface *backGroundSurface;
|
|
BGInfo bgInfo;
|
|
Rect rect;
|
|
if (event->duration != 0) {
|
|
event_pc = ((double)event->duration - event->time) / event->duration;
|
|
} else {
|
|
event_pc = 1.0;
|
|
}
|
|
|
|
if (event_pc >= 1.0) {
|
|
// Cap percentage to 100
|
|
event_pc = 1.0;
|
|
event_done = 1;
|
|
}
|
|
|
|
if (event_pc < 0.0) {
|
|
// Event not signaled, skip it
|
|
return kEvStContinue;
|
|
} else if (!(event->code & kEvFSignaled)) {
|
|
// Signal event
|
|
event->code |= kEvFSignaled;
|
|
event_pc = 0.0;
|
|
}
|
|
|
|
switch (event->code & EVENT_MASK) {
|
|
case kPalEvent:
|
|
switch (event->op) {
|
|
case kEventBlackToPal:
|
|
_vm->_gfx->blackToPal((PalEntry *)event->data, event_pc);
|
|
break;
|
|
|
|
case kEventPalToBlack:
|
|
_vm->_gfx->palToBlack((PalEntry *)event->data, event_pc);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case kTransitionEvent:
|
|
switch (event->op) {
|
|
case kEventDissolve:
|
|
backGroundSurface = _vm->_render->getBackGroundSurface();
|
|
_vm->_scene->getBGInfo(bgInfo);
|
|
rect.left = rect.top = 0;
|
|
rect.right = bgInfo.bounds.width();
|
|
rect.bottom = bgInfo.bounds.height();
|
|
backGroundSurface->transitionDissolve(bgInfo.buffer, rect, 0, event_pc);
|
|
break;
|
|
case kEventDissolveBGMask:
|
|
// we dissolve it centered.
|
|
// set flag of Dissolve to 1. It is a hack to simulate zero masking.
|
|
int w, h;
|
|
byte *maskBuffer;
|
|
size_t len;
|
|
|
|
backGroundSurface = _vm->_render->getBackGroundSurface();
|
|
_vm->_scene->getBGMaskInfo(w, h, maskBuffer, len);
|
|
rect.left = (_vm->getDisplayWidth() - w) / 2;
|
|
rect.top = (_vm->getDisplayHeight() - h) / 2;
|
|
rect.setWidth(w);
|
|
rect.setHeight(h);
|
|
|
|
backGroundSurface->transitionDissolve( maskBuffer, rect, 1, event_pc);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
|
|
}
|
|
|
|
if (event_done) {
|
|
return kEvStDelete;
|
|
}
|
|
|
|
return kEvStContinue;
|
|
}
|
|
|
|
int Events::handleImmediate(Event *event) {
|
|
double event_pc = 0.0; // Event completion percentage
|
|
bool event_done = false;
|
|
|
|
// Duration might be 0 so dont do division then
|
|
if (event->duration != 0) {
|
|
event_pc = ((double)event->duration - event->time) / event->duration;
|
|
} else {
|
|
// Just make sure that event_pc is 1.0 so event_done is true
|
|
event_pc = 1.0;
|
|
}
|
|
|
|
if (event_pc >= 1.0) {
|
|
// Cap percentage to 100
|
|
event_pc = 1.0;
|
|
event_done = true;
|
|
}
|
|
|
|
if (event_pc < 0.0) {
|
|
// Event not signaled, skip it
|
|
return kEvStBreak;
|
|
} else if (!(event->code & kEvFSignaled)) {
|
|
// Signal event
|
|
event->code |= kEvFSignaled;
|
|
event_pc = 0.0;
|
|
}
|
|
|
|
switch (event->code & EVENT_MASK) {
|
|
case kPalEvent:
|
|
switch (event->op) {
|
|
case kEventBlackToPal:
|
|
_vm->_gfx->blackToPal((PalEntry *)event->data, event_pc);
|
|
break;
|
|
|
|
case kEventPalToBlack:
|
|
_vm->_gfx->palToBlack((PalEntry *)event->data, event_pc);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case kScriptEvent:
|
|
case kBgEvent:
|
|
case kInterfaceEvent:
|
|
case kSceneEvent:
|
|
case kAnimEvent:
|
|
case kCutawayEvent:
|
|
handleOneShot(event);
|
|
event_done = true;
|
|
break;
|
|
default:
|
|
warning("Unhandled Immediate event type (%d)", event->code & EVENT_MASK);
|
|
break;
|
|
|
|
}
|
|
|
|
if (event_done) {
|
|
return kEvStDelete;
|
|
}
|
|
|
|
return kEvStBreak;
|
|
}
|
|
|
|
int Events::handleOneShot(Event *event) {
|
|
Surface *backBuffer;
|
|
ScriptThread *sthread;
|
|
Rect rect;
|
|
|
|
|
|
if (event->time > 0) {
|
|
return kEvStContinue;
|
|
}
|
|
|
|
// Event has been signaled
|
|
|
|
switch (event->code & EVENT_MASK) {
|
|
case kTextEvent:
|
|
switch (event->op) {
|
|
case kEventDisplay:
|
|
((TextListEntry *)event->data)->display = true;
|
|
break;
|
|
case kEventRemove:
|
|
_vm->_scene->_textList.remove((TextListEntry *)event->data);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
case kSoundEvent:
|
|
_vm->_sound->stopSound();
|
|
if (event->op == kEventPlay)
|
|
_vm->_sndRes->playSound(event->param, event->param2, event->param3 != 0);
|
|
break;
|
|
case kVoiceEvent:
|
|
_vm->_sndRes->playVoice(event->param);
|
|
break;
|
|
case kMusicEvent:
|
|
_vm->_music->stop();
|
|
if (event->op == kEventPlay)
|
|
_vm->_music->play(event->param, (MusicFlags)event->param2);
|
|
break;
|
|
case kBgEvent:
|
|
{
|
|
Surface *backGroundSurface;
|
|
BGInfo bgInfo;
|
|
|
|
if (!(_vm->_scene->getFlags() & kSceneFlagISO)) {
|
|
|
|
backBuffer = _vm->_gfx->getBackBuffer();
|
|
backGroundSurface = _vm->_render->getBackGroundSurface();
|
|
_vm->_scene->getBGInfo(bgInfo);
|
|
|
|
backGroundSurface->blit(bgInfo.bounds, bgInfo.buffer);
|
|
|
|
// If it is inset scene then draw black border
|
|
if (bgInfo.bounds.width() < _vm->getDisplayWidth() || bgInfo.bounds.height() < _vm->_scene->getHeight()) {
|
|
Common::Rect rect1(2, bgInfo.bounds.height() + 4);
|
|
Common::Rect rect2(bgInfo.bounds.width() + 4, 2);
|
|
Common::Rect rect3(2, bgInfo.bounds.height() + 4);
|
|
Common::Rect rect4(bgInfo.bounds.width() + 4, 2);
|
|
rect1.moveTo(bgInfo.bounds.left - 2, bgInfo.bounds.top - 2);
|
|
rect2.moveTo(bgInfo.bounds.left - 2, bgInfo.bounds.top - 2);
|
|
rect3.moveTo(bgInfo.bounds.right, bgInfo.bounds.top - 2);
|
|
rect4.moveTo(bgInfo.bounds.left - 2, bgInfo.bounds.bottom);
|
|
|
|
backGroundSurface->drawRect(rect1, kITEColorBlack);
|
|
backGroundSurface->drawRect(rect2, kITEColorBlack);
|
|
backGroundSurface->drawRect(rect3, kITEColorBlack);
|
|
backGroundSurface->drawRect(rect4, kITEColorBlack);
|
|
}
|
|
|
|
if (event->param == kEvPSetPalette) {
|
|
PalEntry *palPointer;
|
|
|
|
if (_vm->getGameType() == GType_IHNM) {
|
|
if (_vm->_spiritualBarometer > 255)
|
|
_vm->_gfx->setPaletteColor(kIHNMColorPortrait, 0xff, 0xff, 0xff);
|
|
else
|
|
_vm->_gfx->setPaletteColor(kIHNMColorPortrait,
|
|
_vm->_spiritualBarometer * _vm->_interface->_portraitBgColor.red / 256,
|
|
_vm->_spiritualBarometer * _vm->_interface->_portraitBgColor.green / 256,
|
|
_vm->_spiritualBarometer * _vm->_interface->_portraitBgColor.blue / 256);
|
|
}
|
|
|
|
_vm->_scene->getBGPal(palPointer);
|
|
_vm->_gfx->setPalette(palPointer);
|
|
}
|
|
}
|
|
_vm->_actor->showActors(true);
|
|
}
|
|
break;
|
|
case kPsychicProfileBgEvent:
|
|
{
|
|
ResourceContext *context = _vm->_resource->getContext(GAME_RESOURCEFILE);
|
|
|
|
byte *resourceData;
|
|
size_t resourceDataLength;
|
|
|
|
_vm->_resource->loadResource(context, _vm->getResourceDescription()->psychicProfileResourceId, resourceData, resourceDataLength);
|
|
|
|
byte *buf;
|
|
size_t buflen;
|
|
int width;
|
|
int height;
|
|
|
|
_vm->decodeBGImage(resourceData, resourceDataLength, &buf, &buflen, &width, &height);
|
|
|
|
const PalEntry *palette = (const PalEntry *)_vm->getImagePal(resourceData, resourceDataLength);
|
|
|
|
Surface *bgSurface = _vm->_render->getBackGroundSurface();
|
|
const Rect profileRect(width, height);
|
|
|
|
bgSurface->blit(profileRect, buf);
|
|
_vm->_frameCount++;
|
|
|
|
_vm->_gfx->setPalette(palette);
|
|
|
|
free(buf);
|
|
free(resourceData);
|
|
|
|
// Draw the scene. It won't be drawn by Render::drawScene(), as the RF_PLACARD is set
|
|
_vm->_scene->draw();
|
|
}
|
|
break;
|
|
case kAnimEvent:
|
|
switch (event->op) {
|
|
case kEventPlay:
|
|
_vm->_anim->play(event->param, event->time, true);
|
|
break;
|
|
case kEventStop:
|
|
_vm->_anim->stop(event->param);
|
|
break;
|
|
case kEventFrame:
|
|
_vm->_anim->play(event->param, event->time, false);
|
|
break;
|
|
case kEventSetFlag:
|
|
_vm->_anim->setFlag(event->param, event->param2);
|
|
break;
|
|
case kEventClearFlag:
|
|
_vm->_anim->clearFlag(event->param, event->param2);
|
|
break;
|
|
case kEventResumeAll:
|
|
_vm->_anim->resumeAll();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case kSceneEvent:
|
|
switch (event->op) {
|
|
case kEventDraw:
|
|
{
|
|
Surface *backGroundSurface;
|
|
BGInfo bgInfo;
|
|
|
|
backBuffer = _vm->_gfx->getBackBuffer();
|
|
backGroundSurface = _vm->_render->getBackGroundSurface();
|
|
_vm->_scene->getBGInfo(bgInfo);
|
|
backGroundSurface->blit(bgInfo.bounds, bgInfo.buffer);
|
|
|
|
_vm->_scene->draw();
|
|
}
|
|
break;
|
|
case kEventEnd:
|
|
_vm->_scene->nextScene();
|
|
return kEvStBreak;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case kPalAnimEvent:
|
|
switch (event->op) {
|
|
case kEventCycleStart:
|
|
_vm->_palanim->cycleStart();
|
|
break;
|
|
case kEventCycleStep:
|
|
_vm->_palanim->cycleStep(event->time);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case kInterfaceEvent:
|
|
switch (event->op) {
|
|
case kEventActivate:
|
|
_vm->_interface->activate();
|
|
break;
|
|
case kEventDeactivate:
|
|
_vm->_interface->deactivate();
|
|
break;
|
|
case kEventSetStatus:
|
|
_vm->_interface->setStatusText((const char*)event->data);
|
|
_vm->_interface->drawStatusBar();
|
|
break;
|
|
case kEventClearStatus:
|
|
_vm->_interface->setStatusText("");
|
|
_vm->_interface->drawStatusBar();
|
|
break;
|
|
case kEventSetFadeMode:
|
|
_vm->_interface->setFadeMode(event->param);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case kScriptEvent:
|
|
switch (event->op) {
|
|
case kEventExecBlocking:
|
|
case kEventExecNonBlocking:
|
|
debug(6, "Exec module number %ld script entry number %ld", event->param, event->param2);
|
|
|
|
sthread = _vm->_script->createThread(event->param, event->param2);
|
|
if (sthread == NULL) {
|
|
_vm->_console->DebugPrintf("Thread creation failed.\n");
|
|
break;
|
|
}
|
|
|
|
sthread->_threadVars[kThreadVarAction] = event->param3;
|
|
sthread->_threadVars[kThreadVarObject] = event->param4;
|
|
sthread->_threadVars[kThreadVarWithObject] = event->param5;
|
|
sthread->_threadVars[kThreadVarActor] = event->param6;
|
|
|
|
if (event->op == kEventExecBlocking)
|
|
_vm->_script->completeThread();
|
|
|
|
break;
|
|
case kEventThreadWake:
|
|
_vm->_script->wakeUpThreads(event->param);
|
|
break;
|
|
}
|
|
break;
|
|
case kCursorEvent:
|
|
switch (event->op) {
|
|
case kEventShow:
|
|
_vm->_gfx->showCursor(true);
|
|
break;
|
|
case kEventHide:
|
|
_vm->_gfx->showCursor(false);
|
|
break;
|
|
case kEventSetNormalCursor:
|
|
// in ITE and IHNM demo there is just one cursor
|
|
if (_vm->getGameType() == GType_IHNM && _vm->getGameId() != GID_IHNM_DEMO)
|
|
_vm->_gfx->setCursor(kCursorNormal);
|
|
break;
|
|
case kEventSetBusyCursor:
|
|
// in ITE and IHNM demo there is just one cursor
|
|
if (_vm->getGameType() == GType_IHNM && _vm->getGameId() != GID_IHNM_DEMO)
|
|
_vm->_gfx->setCursor(kCursorBusy);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case kGraphicsEvent:
|
|
switch (event->op) {
|
|
case kEventFillRect:
|
|
rect.top = event->param2;
|
|
rect.bottom = event->param3;
|
|
rect.left = event->param4;
|
|
rect.right = event->param5;
|
|
((Surface *)event->data)->drawRect(rect, event->param);
|
|
break;
|
|
case kEventSetFlag:
|
|
_vm->_render->setFlag(event->param);
|
|
break;
|
|
case kEventClearFlag:
|
|
_vm->_render->clearFlag(event->param);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
case kCutawayEvent:
|
|
switch (event->op) {
|
|
case kEventClear:
|
|
_vm->_anim->clearCutaway();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return kEvStDelete;
|
|
}
|
|
|
|
int Events::handleInterval(Event *event) {
|
|
return kEvStDelete;
|
|
}
|
|
|
|
// Schedules an event in the event list; returns a pointer to the scheduled
|
|
// event suitable for chaining if desired.
|
|
Event *Events::queue(Event *event) {
|
|
Event *queuedEvent;
|
|
|
|
queuedEvent = _eventList.pushBack(*event).operator->();
|
|
initializeEvent(queuedEvent);
|
|
|
|
return queuedEvent;
|
|
}
|
|
|
|
// Places a 'add_event' on the end of an event chain given by 'head_event'
|
|
// (head_event may be in any position in the event chain)
|
|
Event *Events::chain(Event *headEvent, Event *addEvent) {
|
|
if (headEvent == NULL) {
|
|
return queue(addEvent);
|
|
}
|
|
|
|
Event *walkEvent;
|
|
for (walkEvent = headEvent; walkEvent->chain != NULL; walkEvent = walkEvent->chain) {
|
|
continue;
|
|
}
|
|
|
|
walkEvent->chain = (Event *)malloc(sizeof(*walkEvent->chain));
|
|
*walkEvent->chain = *addEvent;
|
|
initializeEvent(walkEvent->chain);
|
|
|
|
return walkEvent->chain;
|
|
}
|
|
|
|
int Events::initializeEvent(Event *event) {
|
|
event->chain = NULL;
|
|
switch (event->type) {
|
|
case kEvTOneshot:
|
|
break;
|
|
case kEvTContinuous:
|
|
case kEvTImmediate:
|
|
event->time += event->duration;
|
|
break;
|
|
case kEvTInterval:
|
|
break;
|
|
default:
|
|
return FAILURE;
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
int Events::clearList() {
|
|
Event *chain_walk;
|
|
Event *next_chain;
|
|
Event *event_p;
|
|
|
|
// Walk down event list
|
|
for (EventList::iterator eventi = _eventList.begin(); eventi != _eventList.end(); ++eventi) {
|
|
event_p = (Event *)eventi.operator->();
|
|
|
|
// Only remove events not marked kEvFNoDestory (engine events)
|
|
if (!(event_p->code & kEvFNoDestory)) {
|
|
// Remove any events chained off this one */
|
|
for (chain_walk = event_p->chain; chain_walk != NULL; chain_walk = next_chain) {
|
|
next_chain = chain_walk->chain;
|
|
free(chain_walk);
|
|
}
|
|
eventi = _eventList.eraseAndPrev(eventi);
|
|
}
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
// Removes all events from the list (even kEvFNoDestory)
|
|
int Events::freeList() {
|
|
Event *chain_walk;
|
|
Event *next_chain;
|
|
Event *event_p;
|
|
|
|
// Walk down event list
|
|
EventList::iterator eventi = _eventList.begin();
|
|
while (eventi != _eventList.end()) {
|
|
event_p = (Event *)eventi.operator->();
|
|
|
|
// Remove any events chained off this one */
|
|
for (chain_walk = event_p->chain; chain_walk != NULL; chain_walk = next_chain) {
|
|
next_chain = chain_walk->chain;
|
|
free(chain_walk);
|
|
}
|
|
eventi=_eventList.erase(eventi);
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
// Walks down the event list, updating event times by 'msec'.
|
|
int Events::processEventTime(long msec) {
|
|
Event *event_p;
|
|
uint16 event_count = 0;
|
|
|
|
for (EventList::iterator eventi = _eventList.begin(); eventi != _eventList.end(); ++eventi) {
|
|
event_p = (Event *)eventi.operator->();
|
|
|
|
event_p->time -= msec;
|
|
event_count++;
|
|
|
|
if (event_p->type == kEvTImmediate)
|
|
break;
|
|
|
|
if (event_count > EVENT_WARNINGCOUNT) {
|
|
warning("Event list exceeds %u", EVENT_WARNINGCOUNT);
|
|
}
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
} // End of namespace Saga
|