scummvm/engines/saga/events.cpp
Filippos Karapetis 769dd1d7a2 Several bugfixes:
- 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
2007-08-17 06:08:18 +00:00

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