scummvm/engines/saga/scene.cpp

1522 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::creditsScene() {
// FIXME: Just shutdown for now
_vm->shutDown();
return;
/*
SceneQueueList::iterator queueIterator;
LoadSceneParams *sceneQueue;
Event event;
// End the last game ending scene
_vm->_scene->endScene();
// We're not in the game anymore
_inGame = false;
// Hide cursor during credits
event.type = kEvTOneshot;
event.code = kCursorEvent;
event.op = kEventHide;
_vm->_events->queue(&event);
switch (_vm->getGameType()) {
case GType_ITE:
// Not called by ITE
break;
case GType_IHNM:
IHNMCreditsProc();
break;
default:
error("Scene::creditsScene(): Error: Can't start credits scene... 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 || loadSceneParams->chapter == 8)
_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 (isNonInteractiveIHNMDemoPart()) {
// 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();
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);
}
// Stop showing actors till the next scene's background is drawn from loadScene
_vm->_render->setFlag(RF_DISABLE_ACTORS);
_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->_render->setFlag(RF_DISABLE_ACTORS);
_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