398 lines
11 KiB
C++
398 lines
11 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/config-manager.h"
|
|
|
|
#include "mediastation/mediastation.h"
|
|
#include "mediastation/debugchannels.h"
|
|
#include "mediastation/detection.h"
|
|
#include "mediastation/boot.h"
|
|
#include "mediastation/context.h"
|
|
#include "mediastation/actor.h"
|
|
#include "mediastation/actors/document.h"
|
|
#include "mediastation/actors/movie.h"
|
|
#include "mediastation/actors/screen.h"
|
|
#include "mediastation/actors/palette.h"
|
|
#include "mediastation/actors/hotspot.h"
|
|
#include "mediastation/actors/stage.h"
|
|
#include "mediastation/mediascript/scriptconstants.h"
|
|
|
|
namespace MediaStation {
|
|
|
|
MediaStationEngine *g_engine;
|
|
|
|
MediaStationEngine::MediaStationEngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst),
|
|
_gameDescription(gameDesc),
|
|
_randomSource("MediaStation") {
|
|
g_engine = this;
|
|
|
|
_gameDataDir = Common::FSNode(ConfMan.getPath("path"));
|
|
SearchMan.addDirectory(_gameDataDir, 0, 3);
|
|
for (uint i = 0; MediaStation::directoryGlobs[i]; i++) {
|
|
Common::String directoryGlob = directoryGlobs[i];
|
|
SearchMan.addSubDirectoryMatching(_gameDataDir, directoryGlob, 0, 5);
|
|
}
|
|
|
|
_channelIdent = MKTAG('i', 'g', 'o', 'd'); // ImtGod
|
|
}
|
|
|
|
MediaStationEngine::~MediaStationEngine() {
|
|
for (auto it = _loadedContexts.begin(); it != _loadedContexts.end(); ++it) {
|
|
destroyContext(it->_value->_id, false);
|
|
}
|
|
_loadedContexts.clear();
|
|
|
|
// Only delete the document actor.
|
|
// The root stage is deleted from stage director, and
|
|
// the other actors are deleted from their contexts.
|
|
destroyActor(DocumentActor::DOCUMENT_ACTOR_ID);
|
|
|
|
delete _displayManager;
|
|
_displayManager = nullptr;
|
|
|
|
delete _cursorManager;
|
|
_cursorManager = nullptr;
|
|
|
|
delete _functionManager;
|
|
_functionManager = nullptr;
|
|
|
|
delete _document;
|
|
_document = nullptr;
|
|
|
|
delete _deviceOwner;
|
|
_deviceOwner = nullptr;
|
|
|
|
delete _stageDirector;
|
|
_stageDirector = nullptr;
|
|
|
|
unregisterWithStreamManager();
|
|
delete _streamFeedManager;
|
|
_streamFeedManager = nullptr;
|
|
|
|
_contextReferences.clear();
|
|
_streamMap.clear();
|
|
_engineResourceDeclarations.clear();
|
|
_screenReferences.clear();
|
|
_fileMap.clear();
|
|
_actors.clear();
|
|
}
|
|
|
|
Actor *MediaStationEngine::getActorById(uint actorId) {
|
|
return _actors.getValOrDefault(actorId);
|
|
}
|
|
|
|
SpatialEntity *MediaStationEngine::getSpatialEntityById(uint spatialEntityId) {
|
|
Actor *actor = getActorById(spatialEntityId);
|
|
if (actor != nullptr) {
|
|
if (!actor->isSpatialActor()) {
|
|
error("%s: Actor %d is not a spatial actor", __func__, spatialEntityId);
|
|
}
|
|
return static_cast<SpatialEntity *>(actor);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
ChannelClient *MediaStationEngine::getChannelClientByChannelIdent(uint channelIdent) {
|
|
return _streamFeedManager->channelClientForChannel(channelIdent);
|
|
}
|
|
|
|
ScriptValue *MediaStationEngine::getVariable(uint variableId) {
|
|
for (auto it = _loadedContexts.begin(); it != _loadedContexts.end(); ++it) {
|
|
ScriptValue *variable = it->_value->_variables.getValOrDefault(variableId);
|
|
if (variable != nullptr) {
|
|
return variable;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
uint32 MediaStationEngine::getFeatures() const {
|
|
return _gameDescription->flags;
|
|
}
|
|
|
|
Common::String MediaStationEngine::getGameId() const {
|
|
return _gameDescription->gameId;
|
|
}
|
|
|
|
Common::Platform MediaStationEngine::getPlatform() const {
|
|
return _gameDescription->platform;
|
|
}
|
|
|
|
const char *MediaStationEngine::getAppName() const {
|
|
return _gameDescription->filesDescriptions[0].fileName;
|
|
}
|
|
|
|
bool MediaStationEngine::isFirstGenerationEngine() {
|
|
return _versionInfo.major == 0;
|
|
}
|
|
|
|
Common::Error MediaStationEngine::run() {
|
|
initDisplayManager();
|
|
initCursorManager();
|
|
initFunctionManager();
|
|
initDocument();
|
|
initDeviceOwner();
|
|
initStageDirector();
|
|
initStreamFeedManager();
|
|
setupInitialStreamMap();
|
|
|
|
if (ConfMan.hasKey("entry_context")) {
|
|
// For development purposes, we can choose to start at an arbitrary context
|
|
// in this title. This might not work in all cases.
|
|
uint entryContextId = ConfMan.get("entry_context").asUint64();
|
|
warning("%s: Starting at user-requested context %d", __func__, entryContextId);
|
|
_document->beginTitle(entryContextId);
|
|
} else {
|
|
_document->beginTitle();
|
|
}
|
|
|
|
runEventLoop();
|
|
return Common::kNoError;
|
|
}
|
|
|
|
void MediaStationEngine::runEventLoop() {
|
|
while (true) {
|
|
dispatchSystemEvents();
|
|
if (shouldQuit()) {
|
|
break;
|
|
}
|
|
_document->process();
|
|
|
|
debugC(5, kDebugGraphics, "***** START SCREEN UPDATE ***");
|
|
for (auto it = _actors.begin(); it != _actors.end(); ++it) {
|
|
it->_value->process();
|
|
}
|
|
draw();
|
|
debugC(5, kDebugGraphics, "***** END SCREEN UPDATE ***");
|
|
|
|
g_system->delayMillis(10);
|
|
}
|
|
}
|
|
|
|
void MediaStationEngine::initDisplayManager() {
|
|
_displayManager = new VideoDisplayManager(this);
|
|
_parameterClients.push_back(_displayManager);
|
|
}
|
|
|
|
void MediaStationEngine::initCursorManager() {
|
|
if (getPlatform() == Common::kPlatformWindows) {
|
|
_cursorManager = new WindowsCursorManager(getAppName());
|
|
} else if (getPlatform() == Common::kPlatformMacintosh) {
|
|
_cursorManager = new MacCursorManager(getAppName());
|
|
} else {
|
|
error("%s: Attempted to use unsupported platform %s", __func__, Common::getPlatformDescription(getPlatform()));
|
|
}
|
|
_parameterClients.push_back(_cursorManager);
|
|
_cursorManager->showCursor();
|
|
}
|
|
|
|
void MediaStationEngine::initFunctionManager() {
|
|
_functionManager = new FunctionManager();
|
|
_parameterClients.push_back(_functionManager);
|
|
}
|
|
|
|
void MediaStationEngine::initDocument() {
|
|
_document = new Document();
|
|
_parameterClients.push_back(_document);
|
|
|
|
DocumentActor *documentActor = new DocumentActor;
|
|
registerActor(documentActor);
|
|
}
|
|
|
|
void MediaStationEngine::initDeviceOwner() {
|
|
_deviceOwner = new DeviceOwner();
|
|
_parameterClients.push_back(_deviceOwner);
|
|
}
|
|
|
|
void MediaStationEngine::initStageDirector() {
|
|
_stageDirector = new StageDirector;
|
|
}
|
|
|
|
void MediaStationEngine::initStreamFeedManager() {
|
|
_streamFeedManager = new StreamFeedManager;
|
|
registerWithStreamManager();
|
|
}
|
|
|
|
void MediaStationEngine::setupInitialStreamMap() {
|
|
StreamInfo streamInfo;
|
|
streamInfo._actorId = 0;
|
|
streamInfo._fileId = MediaStationEngine::BOOT_STREAM_ID;
|
|
streamInfo._startOffsetInFile = 0;
|
|
_streamMap.setVal(streamInfo._fileId, streamInfo);
|
|
|
|
const Common::String BOOT_STM_FILENAME("BOOT.STM");
|
|
FileInfo fileInfo;
|
|
fileInfo._id = MediaStationEngine::BOOT_STREAM_ID;
|
|
fileInfo._name = BOOT_STM_FILENAME;
|
|
_fileMap.setVal(fileInfo._id, fileInfo);
|
|
}
|
|
|
|
void MediaStationEngine::dispatchSystemEvents() {
|
|
while (g_system->getEventManager()->pollEvent(_event)) {
|
|
debugC(9, kDebugEvents, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
|
|
debugC(9, kDebugEvents, "@@@@ Dispatching system events");
|
|
debugC(9, kDebugEvents, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
|
|
|
|
switch (_event.type) {
|
|
case Common::EVENT_MOUSEMOVE:
|
|
_stageDirector->handleMouseMovedEvent(_event);
|
|
break;
|
|
|
|
case Common::EVENT_KEYDOWN:
|
|
_stageDirector->handleKeyboardEvent(_event);
|
|
break;
|
|
|
|
case Common::EVENT_LBUTTONDOWN:
|
|
_stageDirector->handleMouseDownEvent(_event);
|
|
break;
|
|
|
|
case Common::EVENT_LBUTTONUP:
|
|
_stageDirector->handleMouseUpEvent(_event);
|
|
break;
|
|
|
|
case Common::EVENT_FOCUS_LOST:
|
|
_stageDirector->handleMouseOutOfFocusEvent(_event);
|
|
break;
|
|
|
|
case Common::EVENT_RBUTTONDOWN:
|
|
// We are using the right button as a quick exit since the Media
|
|
// Station engine doesn't seem to use the right button itself.
|
|
warning("%s: EVENT_RBUTTONDOWN: Quitting for development purposes", __func__);
|
|
quitGame();
|
|
break;
|
|
|
|
default:
|
|
// Avoid warnings about unimplemented cases by having an explicit
|
|
// default case.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MediaStationEngine::draw(bool dirtyOnly) {
|
|
if (dirtyOnly) {
|
|
_stageDirector->drawDirtyRegion();
|
|
} else {
|
|
_stageDirector->drawAll();
|
|
}
|
|
_displayManager->updateScreen();
|
|
_displayManager->doTransitionOnSync();
|
|
}
|
|
|
|
void MediaStationEngine::registerActor(Actor *actorToAdd) {
|
|
if (getActorById(actorToAdd->id())) {
|
|
error("%s: Actor with ID 0x%d was already defined in this title", __func__, actorToAdd->id());
|
|
}
|
|
_actors.setVal(actorToAdd->id(), actorToAdd);
|
|
}
|
|
|
|
void MediaStationEngine::destroyActor(uint actorId) {
|
|
Actor *actorToDestroy = getActorById(actorId);
|
|
if (actorToDestroy) {
|
|
delete _actors[actorId];
|
|
_actors.erase(actorId);
|
|
} else {
|
|
warning("%s: Actor %d is not currently loaded", __func__, actorId);
|
|
}
|
|
}
|
|
|
|
void MediaStationEngine::destroyContext(uint contextId, bool eraseFromLoadedContexts) {
|
|
debugC(5, kDebugScript, "%s: Destroying context %d", __func__, contextId);
|
|
Context *context = _loadedContexts.getValOrDefault(contextId);
|
|
if (context == nullptr) {
|
|
error("%s: Attempted to unload context %d that is not currently loaded", __func__, contextId);
|
|
}
|
|
|
|
getRootStage()->deleteChildrenFromContextId(contextId);
|
|
destroyActorsInContext(contextId);
|
|
_functionManager->deleteFunctionsForContext(contextId);
|
|
|
|
delete context;
|
|
if (eraseFromLoadedContexts) {
|
|
// If we are deleting all contexts at once, we don't want to actually do this,
|
|
// as it will mess up our iterators - the whole structure should be cleared after this.
|
|
_loadedContexts.erase(contextId);
|
|
}
|
|
}
|
|
|
|
bool MediaStationEngine::contextIsLocked(uint contextId) {
|
|
for (auto it = _loadedContexts.begin(); it != _loadedContexts.end(); ++it) {
|
|
uint id = it->_key;
|
|
ContextReference contextReference = _contextReferences.getVal(id);
|
|
for (uint childContextId : contextReference._parentContextIds) {
|
|
if (childContextId == contextId) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MediaStationEngine::destroyActorsInContext(uint contextId) {
|
|
// Collect actors to remove first, then delete them.
|
|
// This is necessary because calling erase on a hashmap invalidates
|
|
// the iterators, so collecting them all first makes more sense.
|
|
Common::Array<uint> actorsToRemove;
|
|
for (auto it = _actors.begin(); it != _actors.end(); ++it) {
|
|
uint actorContextId = it->_value->contextId();
|
|
if (actorContextId == contextId) {
|
|
actorsToRemove.push_back(it->_key);
|
|
}
|
|
}
|
|
|
|
// Now remove the collected actors.
|
|
for (uint actorId : actorsToRemove) {
|
|
destroyActor(actorId);
|
|
}
|
|
}
|
|
|
|
void MediaStationEngine::readUnrecognizedFromStream(Chunk &chunk, uint sectionType) {
|
|
bool paramHandled = false;
|
|
for (ParameterClient *client : g_engine->_parameterClients) {
|
|
if (client->attemptToReadFromStream(chunk, sectionType)) {
|
|
paramHandled = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!paramHandled) {
|
|
warning("%s: Parameter %d not handled", __func__, sectionType);
|
|
}
|
|
}
|
|
|
|
void MediaStationEngine::readChunk(Chunk &chunk) {
|
|
StreamType streamType = static_cast<StreamType>(chunk.readTypedUint16());
|
|
switch (streamType) {
|
|
case kDocumentDefStream:
|
|
readDocumentDef(chunk);
|
|
break;
|
|
|
|
case kControlCommandsStream:
|
|
readControlCommands(chunk);
|
|
break;
|
|
|
|
default:
|
|
error("%s: Unhandled section type 0x%x", __func__, static_cast<uint>(streamType));
|
|
}
|
|
}
|
|
|
|
} // End of namespace MediaStation
|