scummvm/engines/saga/scene.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

1480 lines
39 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$
*
*/
// Scene management module
#include "saga/saga.h"
#include "saga/gfx.h"
#include "saga/animation.h"
#include "saga/console.h"
#include "saga/interface.h"
#include "saga/events.h"
#include "saga/isomap.h"
#include "saga/objectmap.h"
#include "saga/palanim.h"
#include "saga/puzzle.h"
#include "saga/render.h"
#include "saga/script.h"
#include "saga/sound.h"
#include "saga/music.h"
#include "saga/scene.h"
#include "saga/stream.h"
#include "saga/actor.h"
#include "saga/rscfile.h"
#include "saga/sagaresnames.h"
#include "graphics/iff.h"
#include "common/util.h"
namespace Saga {
static int initSceneDoors[SCENE_DOORS_MAX] = {
0, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
static SAGAResourceTypes ITESceneResourceTypes[26] = {
SAGA_ACTOR,
SAGA_OBJECT,
SAGA_BG_IMAGE,
SAGA_BG_MASK,
SAGA_UNKNOWN,
SAGA_STRINGS,
SAGA_OBJECT_MAP,
SAGA_ACTION_MAP,
SAGA_ISO_IMAGES,
SAGA_ISO_MAP,
SAGA_ISO_PLATFORMS,
SAGA_ISO_METATILES,
SAGA_ENTRY,
SAGA_UNKNOWN,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ISO_MULTI,
SAGA_PAL_ANIM,
SAGA_FACES,
SAGA_PALETTE
};
static SAGAResourceTypes IHNMSceneResourceTypes[28] = {
SAGA_ACTOR,
SAGA_UNKNOWN,
SAGA_BG_IMAGE,
SAGA_BG_MASK,
SAGA_UNKNOWN,
SAGA_STRINGS,
SAGA_OBJECT_MAP,
SAGA_ACTION_MAP,
SAGA_ISO_IMAGES,
SAGA_ISO_MAP,
SAGA_ISO_PLATFORMS,
SAGA_ISO_METATILES,
SAGA_ENTRY,
SAGA_UNKNOWN,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ANIM,
SAGA_ISO_MULTI,
SAGA_PAL_ANIM,
SAGA_FACES,
SAGA_PALETTE
};
const char *SAGAResourceTypesString[] = {
"SAGA_UNKNOWN",
"SAGA_ACTOR",
"SAGA_OBJECT",
"SAGA_BG_IMAGE",
"SAGA_BG_MASK",
"SAGA_STRINGS",
"SAGA_OBJECT_MAP",
"SAGA_ACTION_MAP",
"SAGA_ISO_IMAGES",
"SAGA_ISO_MAP",
"SAGA_ISO_PLATFORMS",
"SAGA_ISO_METATILES",
"SAGA_ENTRY",
"SAGA_ANIM",
"SAGA_ISO_MULTI",
"SAGA_PAL_ANIM",
"SAGA_FACES",
"SAGA_PALETTE"
};
Scene::Scene(SagaEngine *vm) : _vm(vm) {
byte *sceneLUTPointer;
size_t sceneLUTLength;
uint32 resourceId;
int i;
// Load scene module resource context
_sceneContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
if (_sceneContext == NULL) {
error("Scene::Scene() scene context not found");
}
// Load scene lookup table
resourceId = _vm->_resource->convertResourceId(_vm->getResourceDescription()->sceneLUTResourceId);
debug(3, "Loading scene LUT from resource %i", resourceId);
_vm->_resource->loadResource(_sceneContext, resourceId, sceneLUTPointer, sceneLUTLength);
if (sceneLUTLength == 0) {
error("Scene::Scene() sceneLUTLength == 0");
}
_sceneCount = sceneLUTLength / 2;
_sceneLUT = (int *)malloc(_sceneCount * sizeof(*_sceneLUT));
if (_sceneLUT == NULL) {
memoryError("Scene::Scene()");
}
MemoryReadStreamEndian readS(sceneLUTPointer, sceneLUTLength, _sceneContext->isBigEndian);
for (i = 0; i < _sceneCount; i++) {
_sceneLUT[i] = readS.readUint16();
debug(8, "sceneNumber %i has resourceId %i", i, _sceneLUT[i]);
}
free(sceneLUTPointer);
#define DUMP_SCENES_LEVEL 10
if (DUMP_SCENES_LEVEL <= gDebugLevel) {
uint j;
int backUpDebugLevel = gDebugLevel;
SAGAResourceTypes *types;
int typesCount;
SAGAResourceTypes resType;
getResourceTypes(types, typesCount);
for (i = 0; i < _sceneCount; i++) {
gDebugLevel = -1;
loadSceneDescriptor(_sceneLUT[i]);
loadSceneResourceList(_sceneDescription.resourceListResourceId);
gDebugLevel = backUpDebugLevel;
debug(DUMP_SCENES_LEVEL, "Dump Scene: number %i, descriptor resourceId %i, resourceList resourceId %i", i, _sceneLUT[i], _sceneDescription.resourceListResourceId);
debug(DUMP_SCENES_LEVEL, "\tresourceListCount %i", (int)_resourceListCount);
for (j = 0; j < _resourceListCount; j++) {
if (_resourceList[j].resourceType >= typesCount) {
error("wrong resource type %i", _resourceList[j].resourceType);
}
resType = types[_resourceList[j].resourceType];
debug(DUMP_SCENES_LEVEL, "\t%s resourceId %i", SAGAResourceTypesString[resType], _resourceList[j].resourceId);
}
free(_resourceList);
}
}
debug(3, "LUT has %d entries.", _sceneCount);
_sceneLoaded = false;
_sceneNumber = 0;
_sceneResourceId = 0;
_inGame = false;
_loadDescription = false;
memset(&_sceneDescription, 0, sizeof(_sceneDescription));
_resourceListCount = 0;
_resourceList = NULL;
_sceneProc = NULL;
_objectMap = new ObjectMap(_vm);
_actionMap = new ObjectMap(_vm);
memset(&_bg, 0, sizeof(_bg));
memset(&_bgMask, 0, sizeof(_bgMask));
}
Scene::~Scene() {
delete _actionMap;
delete _objectMap;
free(_sceneLUT);
}
void Scene::getResourceTypes(SAGAResourceTypes *&types, int &typesCount) {
if (_vm->getGameType() == GType_IHNM) {
typesCount = ARRAYSIZE(IHNMSceneResourceTypes);
types = IHNMSceneResourceTypes;
} else {
typesCount = ARRAYSIZE(ITESceneResourceTypes);
types = ITESceneResourceTypes;
}
}
void Scene::drawTextList(Surface *ds) {
TextListEntry *entry;
for (TextList::iterator textIterator = _textList.begin(); textIterator != _textList.end(); ++textIterator) {
entry = (TextListEntry *)textIterator.operator->();
if (entry->display) {
if (entry->useRect) {
_vm->_font->textDrawRect(entry->font, ds, entry->text, entry->rect, _vm->KnownColor2ColorId(entry->knownColor), _vm->KnownColor2ColorId(entry->effectKnownColor), entry->flags);
} else {
_vm->_font->textDraw(entry->font, ds, entry->text, entry->point, _vm->KnownColor2ColorId(entry->knownColor), _vm->KnownColor2ColorId(entry->effectKnownColor), entry->flags);
}
}
}
}
void Scene::startScene() {
SceneQueueList::iterator queueIterator;
LoadSceneParams *sceneQueue;
Event event;
if (_sceneLoaded) {
error("Scene::start(): Error: Can't start game...scene already loaded");
}
if (_inGame) {
error("Scene::start(): Error: Can't start game...game already started");
}
// Hide cursor during intro
event.type = kEvTOneshot;
event.code = kCursorEvent;
event.op = kEventHide;
_vm->_events->queue(&event);
switch (_vm->getGameType()) {
case GType_ITE:
ITEStartProc();
break;
case GType_IHNM:
IHNMStartProc();
break;
default:
error("Scene::start(): Error: Can't start game... gametype not supported");
break;
}
// Load the head in scene queue
queueIterator = _sceneQueue.begin();
if (queueIterator == _sceneQueue.end()) {
return;
}
sceneQueue = queueIterator.operator->();
loadScene(sceneQueue);
}
void Scene::nextScene() {
SceneQueueList::iterator queueIterator;
LoadSceneParams *sceneQueue;
if (!_sceneLoaded) {
error("Scene::next(): Error: Can't advance scene...no scene loaded");
}
if (_inGame) {
error("Scene::next(): Error: Can't advance scene...game already started");
}
endScene();
// Delete the current head in scene queue
queueIterator = _sceneQueue.begin();
if (queueIterator == _sceneQueue.end()) {
return;
}
queueIterator = _sceneQueue.erase(queueIterator);
if (queueIterator == _sceneQueue.end()) {
return;
}
// Load the head in scene queue
sceneQueue = queueIterator.operator->();
loadScene(sceneQueue);
}
void Scene::skipScene() {
SceneQueueList::iterator queueIterator;
LoadSceneParams *sceneQueue = NULL;
LoadSceneParams *skipQueue = NULL;
if (!_sceneLoaded) {
error("Scene::skip(): Error: Can't skip scene...no scene loaded");
}
if (_inGame) {
error("Scene::skip(): Error: Can't skip scene...game already started");
}
// Walk down scene queue and try to find a skip target
queueIterator = _sceneQueue.begin();
if (queueIterator == _sceneQueue.end()) {
error("Scene::skip(): Error: Can't skip scene...no scenes in queue");
}
++queueIterator;
while (queueIterator != _sceneQueue.end()) {
sceneQueue = queueIterator.operator->();
assert(sceneQueue != NULL);
if (sceneQueue->sceneSkipTarget) {
skipQueue = sceneQueue;
break;
}
++queueIterator;
}
// If skip target found, remove preceding scenes and load
if (skipQueue != NULL) {
_sceneQueue.erase(_sceneQueue.begin(), queueIterator);
endScene();
loadScene(skipQueue);
}
}
static struct SceneSubstitutes {
int sceneId;
const char *message;
const char *title;
const char *image;
} sceneSubstitutes[] = {
{
7,
"Tycho says he knows much about the northern lands. Can Rif convince "
"the Dog to share this knowledge?",
"The Home of Tycho Northpaw",
"tycho.bbm"
},
{
27,
"The scene of the crime may hold many clues, but will the servants of "
"the Sanctuary trust Rif?",
"The Sanctuary of the Orb",
"sanctuar.bbm"
},
{
5,
"The Rats hold many secrets that could guide Rif on his quest -- assuming "
"he can get past the doorkeeper.",
"The Rat Complex",
"ratdoor.bbm"
},
{
2,
"The Ferrets enjoy making things and have the materials to do so. How can "
"that help Rif?",
"The Ferret Village",
"ferrets.bbm"
},
{
67,
"What aid can the noble King of the Elks provide to Rif and his companions?",
"The Realm of the Forest King",
"elkenter.bbm"
},
{
3,
"The King holds Rif's sweetheart hostage. Will the Boar provide any "
"assistance to Rif?",
"The Great Hall of the Boar King",
"boarhall.bbm"
}
};
void Scene::changeScene(int16 sceneNumber, int actorsEntrance, SceneTransitionType transitionType, int chapter) {
debug(5, "Scene::changeScene(%d, %d, %d, %d)", sceneNumber, actorsEntrance, transitionType, chapter);
// This is used for latter ITE demos where all places on world map except
// Tent Faire are substituted with LBM picture and short description
if (_vm->getFeatures() & GF_SCENE_SUBSTITUTES) {
for (int i = 0; i < ARRAYSIZE(sceneSubstitutes); i++) {
if (sceneSubstitutes[i].sceneId == sceneNumber) {
Surface *backBuffer = _vm->_gfx->getBackBuffer();
Surface bbmBuffer;
byte *pal, *colors;
Common::File file;
Rect rect;
PalEntry cPal[PAL_ENTRIES];
_vm->_interface->setMode(kPanelSceneSubstitute);
if (file.open(sceneSubstitutes[i].image)) {
Graphics::decodePBM(file, bbmBuffer, pal);
colors = pal;
rect.setWidth(bbmBuffer.w);
rect.setHeight(bbmBuffer.h);
backBuffer->blit(rect, (const byte*)bbmBuffer.pixels);
for (int j = 0; j < PAL_ENTRIES; j++) {
cPal[j].red = *pal++;
cPal[j].green = *pal++;
cPal[j].blue = *pal++;
}
free(colors);
_vm->_gfx->setPalette(cPal);
}
_vm->_interface->setStatusText("Click or Press Return to continue. Press Q to quit.", 96);
_vm->_font->textDrawRect(kKnownFontMedium, backBuffer, sceneSubstitutes[i].title,
Common::Rect(0, 7, _vm->getDisplayWidth(), 27), _vm->KnownColor2ColorId(kKnownColorBrightWhite), _vm->KnownColor2ColorId(kKnownColorBlack), kFontOutline);
_vm->_font->textDrawRect(kKnownFontMedium, backBuffer, sceneSubstitutes[i].message,
Common::Rect(24, getHeight() - 33, _vm->getDisplayWidth() - 11,
getHeight()), _vm->KnownColor2ColorId(kKnownColorBrightWhite), _vm->KnownColor2ColorId(kKnownColorBlack), kFontOutline);
return;
}
}
}
LoadSceneParams sceneParams;
sceneParams.actorsEntrance = actorsEntrance;
sceneParams.loadFlag = kLoadBySceneNumber;
sceneParams.sceneDescriptor = sceneNumber;
sceneParams.transitionType = transitionType;
sceneParams.sceneProc = NULL;
sceneParams.sceneSkipTarget = false;
sceneParams.chapter = chapter;
if (sceneNumber != -2) {
endScene();
}
loadScene(&sceneParams);
}
void Scene::getSlopes(int &beginSlope, int &endSlope) {
beginSlope = getHeight() - _sceneDescription.beginSlope;
endSlope = getHeight() - _sceneDescription.endSlope;
}
void Scene::getBGInfo(BGInfo &bgInfo) {
bgInfo.buffer = _bg.buf;
bgInfo.bufferLength = _bg.buf_len;
bgInfo.bounds.left = 0;
bgInfo.bounds.top = 0;
if (_bg.w < _vm->getDisplayWidth()) {
bgInfo.bounds.left = (_vm->getDisplayWidth() - _bg.w) / 2;
}
if (_bg.h < getHeight()) {
bgInfo.bounds.top = (getHeight() - _bg.h) / 2;
}
bgInfo.bounds.setWidth(_bg.w);
bgInfo.bounds.setHeight(_bg.h);
}
bool Scene::canWalk(const Point &testPoint) {
int maskType;
if (!_bgMask.loaded) {
return true;
}
if (!validBGMaskPoint(testPoint)) {
return true;
}
maskType = getBGMaskType(testPoint);
return getDoorState(maskType) == 0;
}
bool Scene::offscreenPath(Point &testPoint) {
Point point;
if (!_bgMask.loaded) {
return false;
}
point.x = clamp( 0, testPoint.x, _bgMask.w - 1 );
point.y = clamp( 0, testPoint.y, _bgMask.h - 1 );
if (point == testPoint) {
return false;
}
if (point.y >= _bgMask.h - 1) {
point.y = _bgMask.h - 2;
}
testPoint = point;
return true;
}
void Scene::getBGMaskInfo(int &width, int &height, byte *&buffer, size_t &bufferLength) {
if (!_bgMask.loaded) {
error("Scene::getBGMaskInfo _bgMask not loaded");
}
width = _bgMask.w;
height = _bgMask.h;
buffer = _bgMask.buf;
bufferLength = _bgMask.buf_len;
}
void Scene::initDoorsState() {
memcpy(_sceneDoors, initSceneDoors, sizeof (_sceneDoors) );
}
void Scene::loadScene(LoadSceneParams *loadSceneParams) {
size_t i;
Event event;
Event *q_event;
static PalEntry current_pal[PAL_ENTRIES];
if (loadSceneParams->transitionType == kTransitionFade)
_vm->_interface->setFadeMode(kFadeOut);
// Change the cursor to an hourglass in IHNM
event.type = kEvTOneshot;
event.code = kCursorEvent;
event.op = kEventSetBusyCursor;
event.time = 0;
_vm->_events->queue(&event);
_chapterPointsChanged = false;
if ((_vm->getGameType() == GType_IHNM) && (loadSceneParams->chapter != NO_CHAPTER_CHANGE)) {
if (loadSceneParams->loadFlag != kLoadBySceneNumber) {
error("loadScene wrong usage");
}
if (loadSceneParams->chapter == 6)
_vm->_interface->setLeftPortrait(0);
_vm->_anim->freeCutawayList();
// FIXME: Freed script modules are not reloaded correctly when changing chapters.
// This is apparent when returning back to the character selection screen,
// where the scene script module is loaded incorrectly
// Don't free them for now, but free them on game exit, like ITE.
// This has no impact on the game itself (other than increased memory usage),
// as each chapter uses a different module slot
// TODO: Find out why the script modules are not loaded correctly when
// changing chapters and uncomment this again
//_vm->_script->freeModules();
// deleteAllScenes();
// installSomeAlarm()
_vm->_interface->clearInventory();
_vm->_resource->loadGlobalResources(loadSceneParams->chapter, loadSceneParams->actorsEntrance);
_vm->_interface->addToInventory(IHNM_OBJ_PROFILE);
_vm->_interface->activate();
if (loadSceneParams->chapter == 8 || loadSceneParams->chapter == -1) {
if (_vm->getGameId() != GID_IHNM_DEMO)
_vm->_interface->setMode(kPanelChapterSelection);
else
_vm->_interface->setMode(kPanelNull);
} else {
_vm->_interface->setMode(kPanelMain);
}
_inGame = true;
_vm->_script->setVerb(_vm->_script->getVerbType(kVerbWalkTo));
if (loadSceneParams->sceneDescriptor == -2) {
_vm->_interface->setFadeMode(kNoFade);
return;
}
}
if (_sceneLoaded) {
error("Scene::loadScene(): Error, a scene is already loaded");
}
_loadDescription = true;
if (_vm->getGameType() == GType_IHNM) {
if (loadSceneParams->loadFlag == kLoadBySceneNumber) // When will we get rid of it?
if (loadSceneParams->sceneDescriptor <= 0)
loadSceneParams->sceneDescriptor = _vm->_resource->_metaResource.sceneIndex;
}
switch (loadSceneParams->loadFlag) {
case kLoadByResourceId:
_sceneNumber = 0; // original assign zero for loaded by resource id
_sceneResourceId = loadSceneParams->sceneDescriptor;
break;
case kLoadBySceneNumber:
_sceneNumber = loadSceneParams->sceneDescriptor;
_sceneResourceId = getSceneResourceId(_sceneNumber);
break;
case kLoadByDescription:
_sceneNumber = -1;
_sceneResourceId = -1;
assert(loadSceneParams->sceneDescription != NULL);
assert(loadSceneParams->sceneDescription->resourceList != NULL);
_loadDescription = false;
_sceneDescription = *loadSceneParams->sceneDescription;
_resourceList = loadSceneParams->sceneDescription->resourceList;
_resourceListCount = loadSceneParams->sceneDescription->resourceListCount;
break;
}
debug(3, "Loading scene number %d:", _sceneNumber);
if (_vm->getGameId() == GID_IHNM_DEMO && (_sceneNumber >= 144 && _sceneNumber <= 149)) {
// WORKAROUND for the non-interactive part of the IHNM demo: When restarting the
// non-interactive demo, opcode sfMainMode is incorrectly called. Therefore, if any
// of the scenes of the non-interactive demo are loaded (scenes 144-149), set panel
// to null and lock the user interface
_vm->_interface->deactivate();
_vm->_interface->setMode(kPanelNull);
}
// Load scene descriptor and resource list resources
if (_loadDescription) {
debug(3, "Loading scene resource %i", _sceneResourceId);
loadSceneDescriptor(_sceneResourceId);
loadSceneResourceList(_sceneDescription.resourceListResourceId);
} else {
debug(3, "Loading memory scene resource");
}
// Load resources from scene resource list
for (i = 0; i < _resourceListCount; i++) {
if (!_resourceList[i].invalid) {
_vm->_resource->loadResource(_sceneContext, _resourceList[i].resourceId,
_resourceList[i].buffer, _resourceList[i].size);
if (_resourceList[i].size >= 6) {
if (!memcmp(_resourceList[i].buffer, "DUMMY!", 6)) {
_resourceList[i].invalid = true;
warning("DUMMY resource %i", _resourceList[i].resourceId);
}
}
}
}
// Process resources from scene resource list
processSceneResources();
if (_sceneDescription.flags & kSceneFlagISO) {
_outsetSceneNumber = _sceneNumber;
_sceneClip.left = 0;
_sceneClip.top = 0;
_sceneClip.right = _vm->getDisplayWidth();
_sceneClip.bottom = getHeight();
} else {
BGInfo backGroundInfo;
getBGInfo(backGroundInfo);
_sceneClip = backGroundInfo.bounds;
if (!(_bg.w < _vm->getDisplayWidth() || _bg.h < getHeight()))
_outsetSceneNumber = _sceneNumber;
}
_sceneLoaded = true;
q_event = NULL;
//fix placard bug
//i guess we should remove RF_PLACARD flag - and use _interface->getMode()
event.type = kEvTOneshot;
event.code = kGraphicsEvent;
event.op = kEventClearFlag;
event.param = RF_PLACARD;
q_event = _vm->_events->chain(q_event, &event);
if (loadSceneParams->transitionType == kTransitionFade) {
_vm->_interface->setFadeMode(kFadeOut);
// Fade to black out
_vm->_gfx->getCurrentPal(current_pal);
event.type = kEvTImmediate;
event.code = kPalEvent;
event.op = kEventPalToBlack;
event.time = 0;
event.duration = kNormalFadeDuration;
event.data = current_pal;
q_event = _vm->_events->queue(&event);
// set fade mode
event.type = kEvTImmediate;
event.code = kInterfaceEvent;
event.op = kEventSetFadeMode;
event.param = kNoFade;
event.time = 0;
event.duration = 0;
q_event = _vm->_events->chain(q_event, &event);
// Display scene background, but stay with black palette
event.type = kEvTImmediate;
event.code = kBgEvent;
event.op = kEventDisplay;
event.param = kEvPNoSetPalette;
event.time = 0;
event.duration = 0;
q_event = _vm->_events->chain(q_event, &event);
}
// Start the scene pre script, but stay with black palette
if (_sceneDescription.startScriptEntrypointNumber > 0) {
event.type = kEvTOneshot;
event.code = kScriptEvent;
event.op = kEventExecBlocking;
event.time = 0;
event.param = _sceneDescription.scriptModuleNumber;
event.param2 = _sceneDescription.startScriptEntrypointNumber;
event.param3 = 0; // Action
event.param4 = _sceneNumber; // Object
event.param5 = loadSceneParams->actorsEntrance; // With Object
event.param6 = 0; // Actor
q_event = _vm->_events->chain(q_event, &event);
}
if (loadSceneParams->transitionType == kTransitionFade) {
// set fade mode
event.type = kEvTImmediate;
event.code = kInterfaceEvent;
event.op = kEventSetFadeMode;
event.param = kFadeIn;
event.time = 0;
event.duration = 0;
q_event = _vm->_events->chain(q_event, &event);
// Fade in from black to the scene background palette
event.type = kEvTImmediate;
event.code = kPalEvent;
event.op = kEventBlackToPal;
event.time = 0;
event.duration = kNormalFadeDuration;
event.data = _bg.pal;
q_event = _vm->_events->chain(q_event, &event);
// set fade mode
event.type = kEvTImmediate;
event.code = kInterfaceEvent;
event.op = kEventSetFadeMode;
event.param = kNoFade;
event.time = 0;
event.duration = 0;
q_event = _vm->_events->chain(q_event, &event);
}
if (loadSceneParams->sceneProc == NULL) {
if (!_inGame && _vm->getGameType() == GType_ITE) {
_inGame = true;
_vm->_interface->setMode(kPanelMain);
}
_vm->_sound->stopAll();
// FIXME: Does IHNM use scene background music, or is all the
// music scripted? At the very least, it shouldn't try
// to start song 0 at the beginning of the game, since
// it's the end credits music.
if (_vm->getGameType() == GType_ITE) {
if (_sceneDescription.musicResourceId >= 0) {
event.type = kEvTOneshot;
event.code = kMusicEvent;
event.param = _sceneDescription.musicResourceId;
event.param2 = MUSIC_DEFAULT;
event.op = kEventPlay;
event.time = 0;
_vm->_events->queue(&event);
} else {
event.type = kEvTOneshot;
event.code = kMusicEvent;
event.op = kEventStop;
event.time = 0;
_vm->_events->queue(&event);
}
}
// Set scene background
event.type = kEvTOneshot;
event.code = kBgEvent;
event.op = kEventDisplay;
event.param = kEvPSetPalette;
event.time = 0;
_vm->_events->queue(&event);
// Begin palette cycle animation if present
event.type = kEvTOneshot;
event.code = kPalAnimEvent;
event.op = kEventCycleStart;
event.time = 0;
q_event = _vm->_events->queue(&event);
// Start the scene main script
if (_sceneDescription.sceneScriptEntrypointNumber > 0) {
event.type = kEvTOneshot;
event.code = kScriptEvent;
event.op = kEventExecNonBlocking;
event.time = 0;
event.param = _sceneDescription.scriptModuleNumber;
event.param2 = _sceneDescription.sceneScriptEntrypointNumber;
event.param3 = _vm->_script->getVerbType(kVerbEnter); // Action
event.param4 = _sceneNumber; // Object
event.param5 = loadSceneParams->actorsEntrance; // With Object
event.param6 = 0; // Actor
_vm->_events->queue(&event);
}
debug(3, "Scene started");
} else {
loadSceneParams->sceneProc(SCENE_BEGIN, this);
}
// We probably don't want "followers" to go into scene -1 , 0. At the very
// least we don't want garbage to be drawn that early in the ITE intro.
if (_sceneNumber > 0 && _sceneNumber != ITE_SCENE_PUZZLE)
_vm->_actor->updateActorsScene(loadSceneParams->actorsEntrance);
if (_sceneNumber == ITE_SCENE_PUZZLE)
_vm->_puzzle->execute();
if (getFlags() & kSceneFlagShowCursor) {
// Activate user interface
event.type = kEvTOneshot;
event.code = kInterfaceEvent;
event.op = kEventActivate;
event.time = 0;
_vm->_events->queue(&event);
}
// Change the cursor back to a crosshair in IHNM
event.type = kEvTOneshot;
event.code = kCursorEvent;
event.op = kEventSetNormalCursor;
event.time = 0;
_vm->_events->queue(&event);
}
void Scene::loadSceneDescriptor(uint32 resourceId) {
byte *sceneDescriptorData;
size_t sceneDescriptorDataLength;
memset(&_sceneDescription, 0, sizeof(_sceneDescription));
if (resourceId == 0) {
return;
}
_vm->_resource->loadResource(_sceneContext, resourceId, sceneDescriptorData, sceneDescriptorDataLength);
if (sceneDescriptorDataLength == 16) {
MemoryReadStreamEndian readS(sceneDescriptorData, sceneDescriptorDataLength, _sceneContext->isBigEndian);
_sceneDescription.flags = readS.readSint16();
_sceneDescription.resourceListResourceId = readS.readSint16();
_sceneDescription.endSlope = readS.readSint16();
_sceneDescription.beginSlope = readS.readSint16();
_sceneDescription.scriptModuleNumber = readS.readUint16();
_sceneDescription.sceneScriptEntrypointNumber = readS.readUint16();
_sceneDescription.startScriptEntrypointNumber = readS.readUint16();
_sceneDescription.musicResourceId = readS.readSint16();
}
free(sceneDescriptorData);
}
void Scene::loadSceneResourceList(uint32 resourceId) {
byte *resourceListData;
size_t resourceListDataLength;
size_t i;
_resourceListCount = 0;
_resourceList = NULL;
if (resourceId == 0) {
return;
}
// Load the scene resource table
_vm->_resource->loadResource(_sceneContext, resourceId, resourceListData, resourceListDataLength);
if ((resourceListDataLength % SAGA_RESLIST_ENTRY_LEN) == 0) {
MemoryReadStreamEndian readS(resourceListData, resourceListDataLength, _sceneContext->isBigEndian);
// Allocate memory for scene resource list
_resourceListCount = resourceListDataLength / SAGA_RESLIST_ENTRY_LEN;
debug(3, "Scene resource list contains %i entries", (int)_resourceListCount);
_resourceList = (SceneResourceData *)calloc(_resourceListCount, sizeof(*_resourceList));
// Load scene resource list from raw scene
// resource table
debug(3, "Loading scene resource list");
for (i = 0; i < _resourceListCount; i++) {
_resourceList[i].resourceId = readS.readUint16();
_resourceList[i].resourceType = readS.readUint16();
// demo version may contain invalid resourceId
_resourceList[i].invalid = !_vm->_resource->validResourceId(_sceneContext, _resourceList[i].resourceId);
}
}
free(resourceListData);
}
void Scene::processSceneResources() {
byte *resourceData;
size_t resourceDataLength;
const byte *palPointer;
size_t i;
SAGAResourceTypes *types;
int typesCount;
SAGAResourceTypes resType;
getResourceTypes(types, typesCount);
// Process the scene resource list
for (i = 0; i < _resourceListCount; i++) {
if (_resourceList[i].invalid) {
continue;
}
resourceData = _resourceList[i].buffer;
resourceDataLength = _resourceList[i].size;
if (_resourceList[i].resourceType >= typesCount) {
error("Scene::processSceneResources() wrong resource type %i", _resourceList[i].resourceType);
}
resType = types[_resourceList[i].resourceType];
switch (resType) {
case SAGA_UNKNOWN:
warning("UNKNOWN resourceType %i", _resourceList[i].resourceType);
break;
case SAGA_ACTOR:
//for (a = actorsInScene; a; a = a->nextInScene)
// if (a->obj.figID == glist->file_id)
// if (_vm->getGameType() == GType_ITE ||
// ((a->obj.flags & ACTORF_FINAL_FACE) & 0xff))
// a->sprites = (xSpriteSet *)glist->offset;
warning("STUB: unimplemeted handler of SAGA_ACTOR resource");
break;
case SAGA_OBJECT:
break;
case SAGA_BG_IMAGE: // Scene background resource
if (_bg.loaded) {
error("Scene::processSceneResources() Multiple background resources encountered");
}
debug(3, "Loading background resource.");
_bg.res_buf = resourceData;
_bg.res_len = resourceDataLength;
_bg.loaded = 1;
if (_vm->decodeBGImage(_bg.res_buf,
_bg.res_len,
&_bg.buf,
&_bg.buf_len,
&_bg.w,
&_bg.h) != SUCCESS) {
error("Scene::processSceneResources() Error loading background resource %i", _resourceList[i].resourceId);
}
palPointer = _vm->getImagePal(_bg.res_buf, _bg.res_len);
memcpy(_bg.pal, palPointer, sizeof(_bg.pal));
break;
case SAGA_BG_MASK: // Scene background mask resource
if (_bgMask.loaded) {
error("Scene::ProcessSceneResources(): Duplicate background mask resource encountered");
}
debug(3, "Loading BACKGROUND MASK resource.");
_bgMask.res_buf = resourceData;
_bgMask.res_len = resourceDataLength;
_bgMask.loaded = 1;
_vm->decodeBGImage(_bgMask.res_buf, _bgMask.res_len, &_bgMask.buf,
&_bgMask.buf_len, &_bgMask.w, &_bgMask.h, true);
// At least in ITE the mask needs to be clipped.
_bgMask.w = MIN(_bgMask.w, _vm->getDisplayWidth());
_bgMask.h = MIN(_bgMask.h, getHeight());
debug(4, "BACKGROUND MASK width=%d height=%d length=%d", _bgMask.w, _bgMask.h, (int)_bgMask.buf_len);
break;
case SAGA_STRINGS:
debug(3, "Loading scene strings resource...");
_vm->loadStrings(_sceneStrings, resourceData, resourceDataLength);
break;
case SAGA_OBJECT_MAP:
debug(3, "Loading object map resource...");
_objectMap->load(resourceData, resourceDataLength);
break;
case SAGA_ACTION_MAP:
debug(3, "Loading action map resource...");
_actionMap->load(resourceData, resourceDataLength);
break;
case SAGA_ISO_IMAGES:
if (!(_sceneDescription.flags & kSceneFlagISO)) {
error("Scene::ProcessSceneResources(): not Iso mode");
}
debug(3, "Loading isometric images resource.");
_vm->_isoMap->loadImages(resourceData, resourceDataLength);
break;
case SAGA_ISO_MAP:
if (!(_sceneDescription.flags & kSceneFlagISO)) {
error("Scene::ProcessSceneResources(): not Iso mode");
}
debug(3, "Loading isometric map resource.");
_vm->_isoMap->loadMap(resourceData, resourceDataLength);
break;
case SAGA_ISO_PLATFORMS:
if (!(_sceneDescription.flags & kSceneFlagISO)) {
error("Scene::ProcessSceneResources(): not Iso mode");
}
debug(3, "Loading isometric platforms resource.");
_vm->_isoMap->loadPlatforms(resourceData, resourceDataLength);
break;
case SAGA_ISO_METATILES:
if (!(_sceneDescription.flags & kSceneFlagISO)) {
error("Scene::ProcessSceneResources(): not Iso mode");
}
debug(3, "Loading isometric metatiles resource.");
_vm->_isoMap->loadMetaTiles(resourceData, resourceDataLength);
break;
case SAGA_ANIM:
{
uint16 animId = _resourceList[i].resourceType - 14;
debug(3, "Loading animation resource animId=%i", animId);
_vm->_anim->load(animId, resourceData, resourceDataLength);
}
break;
case SAGA_ENTRY:
debug(3, "Loading entry list resource...");
loadSceneEntryList(resourceData, resourceDataLength);
break;
case SAGA_ISO_MULTI:
if (!(_sceneDescription.flags & kSceneFlagISO)) {
error("Scene::ProcessSceneResources(): not Iso mode");
}
debug(3, "Loading isometric multi resource.");
_vm->_isoMap->loadMulti(resourceData, resourceDataLength);
break;
case SAGA_PAL_ANIM:
debug(3, "Loading palette animation resource.");
_vm->_palanim->loadPalAnim(resourceData, resourceDataLength);
break;
case SAGA_FACES:
if (_vm->getGameType() == GType_ITE)
_vm->_interface->loadScenePortraits(_resourceList[i].resourceId);
break;
case SAGA_PALETTE:
{
PalEntry pal[PAL_ENTRIES];
byte *palPtr = resourceData;
if (resourceDataLength < 3 * PAL_ENTRIES)
error("Too small scene palette %i", (int)resourceDataLength);
for (uint16 c = 0; c < PAL_ENTRIES; c++) {
pal[c].red = *palPtr++;
pal[c].green = *palPtr++;
pal[c].blue = *palPtr++;
}
_vm->_gfx->setPalette(pal);
}
break;
default:
error("Scene::ProcessSceneResources() Encountered unknown resource type %i", _resourceList[i].resourceType);
break;
}
}
}
void Scene::draw() {
Surface *backBuffer;
Surface *backGroundSurface;
Rect rect;
backBuffer = _vm->_gfx->getBackBuffer();
backGroundSurface = _vm->_render->getBackGroundSurface();
if (_sceneDescription.flags & kSceneFlagISO) {
_vm->_isoMap->adjustScroll(false);
_vm->_isoMap->draw(backBuffer);
} else {
backGroundSurface->getRect(rect);
if (_sceneClip.bottom < rect.bottom) {
rect.bottom = getHeight();
}
backBuffer->blit(rect, (const byte *)backGroundSurface->pixels);
}
}
void Scene::endScene() {
Surface *backBuffer;
Surface *backGroundSurface;
Rect rect;
size_t i;
if (!_sceneLoaded)
return;
debug(3, "Ending scene...");
if (_sceneProc != NULL) {
_sceneProc(SCENE_END, this);
}
//
_vm->_script->abortAllThreads();
_vm->_script->_skipSpeeches = false;
// Copy current screen to render buffer so inset rooms will get proper background
backGroundSurface = _vm->_render->getBackGroundSurface();
if (!(_sceneDescription.flags & kSceneFlagISO) && !_vm->_scene->isInIntro()) {
BGInfo bgInfo;
_vm->_scene->getBGInfo(bgInfo);
backGroundSurface->blit(bgInfo.bounds, bgInfo.buffer);
} else {
backBuffer = _vm->_gfx->getBackBuffer();
backBuffer->getRect(rect);
backGroundSurface->blit(rect, (const byte *)backBuffer->pixels);
}
// Free scene background
if (_bg.loaded) {
free(_bg.buf);
_bg.loaded = 0;
}
// Free scene background mask
if (_bgMask.loaded) {
free(_bgMask.buf);
_bgMask.loaded = 0;
}
// Free scene resource list
for (i = 0; i < _resourceListCount; i++) {
free(_resourceList[i].buffer);
}
if (_loadDescription) {
free(_resourceList);
}
// Free animation info list
_vm->_anim->reset();
_vm->_palanim->freePalAnim();
_objectMap->freeMem();
_actionMap->freeMem();
_entryList.freeMem();
_sceneStrings.freeMem();
_vm->_isoMap->freeMem();
_vm->_events->clearList();
_textList.clear();
_sceneLoaded = false;
}
void Scene::restoreScene() {
// There is no implementation for tiled scenes, since this function is only used
// in IHNM, which has no tiled scenes
Event event;
_vm->_gfx->showCursor(false);
_vm->_gfx->restorePalette();
event.type = kEvTImmediate;
event.code = kBgEvent;
event.op = kEventDisplay;
event.param = kEvPNoSetPalette;
event.time = 0;
event.duration = 0;
_vm->_events->queue(&event);
_vm->_gfx->showCursor(true);
}
void Scene::cmdSceneChange(int argc, const char **argv) {
int scene_num = 0;
scene_num = atoi(argv[1]);
if ((scene_num < 1) || (scene_num >= _sceneCount)) {
_vm->_console->DebugPrintf("Invalid scene number.\n");
return;
}
clearSceneQueue();
changeScene(scene_num, 0, kTransitionNoFade);
}
void Scene::cmdActionMapInfo() {
_actionMap->cmdInfo();
}
void Scene::cmdObjectMapInfo() {
_objectMap->cmdInfo();
}
void Scene::loadSceneEntryList(const byte* resourcePointer, size_t resourceLength) {
int i;
_entryList.entryListCount = resourceLength / 8;
MemoryReadStreamEndian readS(resourcePointer, resourceLength, _sceneContext->isBigEndian);
if (_entryList.entryList)
error("Scene::loadSceneEntryList entryList != NULL");
_entryList.entryList = (SceneEntry *) malloc(_entryList.entryListCount * sizeof(*_entryList.entryList));
if (_entryList.entryList == NULL) {
memoryError("Scene::loadSceneEntryList");
}
for (i = 0; i < _entryList.entryListCount; i++) {
_entryList.entryList[i].location.x = readS.readSint16();
_entryList.entryList[i].location.y = readS.readSint16();
_entryList.entryList[i].location.z = readS.readSint16();
_entryList.entryList[i].facing = readS.readUint16();
}
}
void Scene::clearPlacard() {
static PalEntry cur_pal[PAL_ENTRIES];
PalEntry *pal;
Event event;
Event *q_event;
_vm->_interface->restoreMode();
_vm->_gfx->getCurrentPal(cur_pal);
event.type = kEvTImmediate;
event.code = kPalEvent;
event.op = kEventPalToBlack;
event.time = 0;
event.duration = kNormalFadeDuration;
event.data = cur_pal;
q_event = _vm->_events->queue(&event);
event.type = kEvTOneshot;
event.code = kGraphicsEvent;
event.op = kEventClearFlag;
event.param = RF_PLACARD;
q_event = _vm->_events->chain(q_event, &event);
event.type = kEvTOneshot;
event.code = kTextEvent;
event.op = kEventRemove;
event.data = _vm->_script->getPlacardTextEntry();
q_event = _vm->_events->chain(q_event, &event);
_vm->_scene->getBGPal(pal);
event.type = kEvTImmediate;
event.code = kPalEvent;
event.op = kEventBlackToPal;
event.time = 0;
event.duration = kNormalFadeDuration;
event.data = pal;
q_event = _vm->_events->chain(q_event, &event);
event.type = kEvTOneshot;
event.code = kCursorEvent;
event.op = kEventShow;
q_event = _vm->_events->chain(q_event, &event);
event.type = kEvTOneshot;
event.code = kScriptEvent;
event.op = kEventThreadWake;
event.param = kWaitTypePlacard;
q_event = _vm->_events->chain(q_event, &event);
}
void Scene::showPsychicProfile(const char *text) {
int textHeight;
static PalEntry cur_pal[PAL_ENTRIES];
PalEntry *pal;
TextListEntry textEntry;
Event event;
Event *q_event;
if (_vm->_interface->getMode() == kPanelPlacard)
return;
_vm->_interface->rememberMode();
_vm->_interface->setMode(kPanelPlacard);
_vm->_gfx->savePalette();
event.type = kEvTOneshot;
event.code = kCursorEvent;
event.op = kEventHide;
q_event = _vm->_events->queue(&event);
_vm->_gfx->getCurrentPal(cur_pal);
event.type = kEvTImmediate;
event.code = kPalEvent;
event.op = kEventPalToBlack;
event.time = 0;
event.duration = kNormalFadeDuration;
event.data = cur_pal;
q_event = _vm->_events->chain(q_event, &event);
event.type = kEvTOneshot;
event.code = kInterfaceEvent;
event.op = kEventClearStatus;
q_event = _vm->_events->chain(q_event, &event);
event.type = kEvTOneshot;
event.code = kGraphicsEvent;
event.op = kEventSetFlag;
event.param = RF_PLACARD;
q_event = _vm->_events->chain(q_event, &event);
// Set the background and palette for the psychic profile
event.type = kEvTOneshot;
event.code = kPsychicProfileBgEvent;
q_event = _vm->_events->chain(q_event, &event);
_vm->_scene->_textList.clear();
if (text != NULL) {
textHeight = _vm->_font->getHeight(kKnownFontVerb, text, 226, kFontCentered);
textEntry.knownColor = kKnownColorBlack;
textEntry.useRect = true;
textEntry.rect.left = 245;
textEntry.rect.setHeight(210 + 76);
textEntry.rect.setWidth(226);
textEntry.rect.top = 210 - textHeight;
textEntry.font = kKnownFontVerb;
textEntry.flags = (FontEffectFlags)(kFontCentered);
textEntry.text = text;
TextListEntry *_psychicProfileTextEntry = _vm->_scene->_textList.addEntry(textEntry);
event.type = kEvTOneshot;
event.code = kTextEvent;
event.op = kEventDisplay;
event.data = _psychicProfileTextEntry;
q_event = _vm->_events->chain(q_event, &event);
}
_vm->_scene->getBGPal(pal);
event.type = kEvTImmediate;
event.code = kPalEvent;
event.op = kEventBlackToPal;
event.time = 0;
event.duration = kNormalFadeDuration;
event.data = pal;
q_event = _vm->_events->chain(q_event, &event);
event.type = kEvTOneshot;
event.code = kScriptEvent;
event.op = kEventThreadWake;
event.param = kWaitTypePlacard;
q_event = _vm->_events->chain(q_event, &event);
}
void Scene::clearPsychicProfile() {
if (_vm->_interface->getMode() == kPanelPlacard || _vm->getGameId() == GID_IHNM_DEMO) {
_vm->_scene->clearPlacard();
_vm->_scene->_textList.clear();
_vm->_actor->showActors(false);
_vm->_gfx->restorePalette();
_vm->_scene->restoreScene();
_vm->_interface->activate();
}
}
void Scene::showIHNMDemoSpecialScreen() {
_vm->_gfx->showCursor(true);
_vm->_interface->clearInventory();
_vm->_scene->changeScene(150, 0, kTransitionFade);
}
} // End of namespace Saga