scummvm/engines/asylum/asylum.cpp
D G Turner d84d002212 ASYLUM: Fix for Correct MetaEngine Feature API
This was previously showing the Launcher Load button as active, but
with no saves present as most of the API was not implemented.

This corrects that so that the various Advanced Engine / MetaEngine
APIs for loading / saving from launcher and command line may be
implemented in future.
2021-05-19 22:46:45 +01:00

607 lines
17 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.
*
*/
#include "backends/keymapper/keymapper.h"
#include "common/debug-channels.h"
#include "common/rect.h"
#include "engines/util.h"
#include "asylum/asylum.h"
#include "asylum/resources/actor.h"
#include "asylum/resources/encounters.h"
#include "asylum/resources/reaction.h"
#include "asylum/resources/script.h"
#include "asylum/resources/special.h"
#include "asylum/resources/worldstats.h"
#include "asylum/puzzles/puzzles.h"
#include "asylum/system/cursor.h"
#include "asylum/system/savegame.h"
#include "asylum/system/screen.h"
#include "asylum/system/speech.h"
#include "asylum/system/text.h"
#include "asylum/views/scene.h"
#include "asylum/views/menu.h"
#include "asylum/views/video.h"
#include "asylum/respack.h"
namespace Asylum {
// inventory ring is a circle of radius 80 centered at (-20, 20)
static const int16 inventoryRingPoints[36][2] = {
{ -20, 100},
{ -20, 100}, { -20, -60},
{ -20, 100}, { -89, -20}, { 49, -20},
{ -20, 100}, {-100, 20}, { -20, -60}, { 60, 20},
{ -20, 100}, { -96, 45}, { -67, -45}, { 27, -45}, { 56, 45},
{ -20, 100}, { -89, 60}, { -89, -20}, { -20, -60}, { 49, -20}, { 49, 60},
{ -20, 100}, { -82, 70}, { -98, 3}, { -56, -51}, { 13, -53}, { 57, -1}, { 45, 67},
{ -20, 100}, { -77, 77}, {-100, 20}, { -77, -37}, { -20, -60}, { 37, -37}, { 60, 20}, { 37, 77}
};
// first 8 triangular numbers
static const uint32 inventoryRingOffsets[8] = {0, 1, 3, 6, 10, 15, 21, 28};
AsylumEngine::AsylumEngine(OSystem *system, const ADGameDescription *gd) : Engine(system), _gameDescription(gd),
_console(NULL), _cursor(NULL), _encounter(NULL), _menu(NULL), _reaction(NULL), _resource(NULL), _savegame(NULL),
_scene(NULL), _screen(NULL), _script(NULL), _special(NULL), _speech(NULL), _sound(NULL), _text(NULL),
_video(NULL), _handler(NULL), _puzzles(NULL) {
// Init data
memset(&_gameFlags, 0, sizeof(_gameFlags));
_introPlayed = false;
_tickOffset = 0;
screenUpdateCount = 0;
lastScreenUpdate = 0;
// Debug
_delayedSceneIndex = kResourcePackInvalid;
_delayedVideoIndex = -1;
_previousScene = NULL;
// Add default search directories
const Common::FSNode gameDataDir(ConfMan.get("path"));
SearchMan.addSubDirectoryMatching(gameDataDir, "data");
SearchMan.addSubDirectoryMatching(gameDataDir, "data/vids");
SearchMan.addSubDirectoryMatching(gameDataDir, "data/music");
// Initialize custom debug levels
DebugMan.addDebugChannel(kDebugLevelMain, "Main", "Generic debug level");
DebugMan.addDebugChannel(kDebugLevelResources, "Resources", "Resources debugging");
DebugMan.addDebugChannel(kDebugLevelSprites, "Sprites", "Sprites debugging");
DebugMan.addDebugChannel(kDebugLevelInput, "Input", "Input events debugging");
DebugMan.addDebugChannel(kDebugLevelMenu, "Menu", "Menu debugging");
DebugMan.addDebugChannel(kDebugLevelScripts, "Scripts", "Scripts debugging");
DebugMan.addDebugChannel(kDebugLevelSound, "Sound", "Sound debugging");
DebugMan.addDebugChannel(kDebugLevelSavegame, "Savegame", "Saving & restoring game debugging");
DebugMan.addDebugChannel(kDebugLevelScene, "Scene", "Scene process and draw debugging");
DebugMan.addDebugChannel(kDebugLevelObjects, "Objects", "Debug Object objects");
DebugMan.addDebugChannel(kDebugLevelActor, "Actor", "Debug Actor");
DebugMan.addDebugChannel(kDebugLevelEncounter, "Encounter", "Debug Encounter");
DebugMan.addDebugChannel(kDebugLevelVideo, "Video", "Debug Video");
DebugMan.addDebugChannel(kDebugLevelCommands, "Commands", "Script commands debugging");
// Initialize random number source
_rnd = new Common::RandomSource("asylum");
}
AsylumEngine::~AsylumEngine() {
_handler = NULL;
delete _cursor;
delete _scene;
delete _encounter;
delete _puzzles;
delete _reaction;
delete _savegame;
delete _screen;
delete _script;
delete _special;
delete _speech;
delete _sound;
delete _text;
delete _video;
delete _menu;
delete _resource;
_previousScene = NULL;
delete _rnd;
// Zero passed pointers
_gameDescription = NULL;
}
bool AsylumEngine::hasFeature(EngineFeature f) const {
return (f == kSupportsReturnToLauncher);
}
Common::Error AsylumEngine::run() {
// Initialize the graphics
initGraphics(640, 480);
// Create debugger. It requires GFX to be initialized
_console = new Console(this);
setDebugger(_console);
// Create resource manager
_resource = new ResourceManager();
_resource->setCdNumber(1);
// Create all game classes
_encounter = new Encounter(this);
_cursor = new Cursor(this);
_puzzles = new Puzzles(this);
_reaction = new Reaction(this);
_savegame = new Savegame(this);
_screen = new Screen(this);
_script = new ScriptManager(this);
_sound = new Sound(this, _mixer);
_special = new Special(this);
_speech = new Speech(this);
_text = new Text(this);
_video = new VideoPlayer(this, _mixer);
// Create main menu
_menu = new Menu(this);
_handler = _menu;
// Load config
Config.read();
// Setup mixer
syncSoundSettings();
// Send init event to our default event handler
AsylumEvent initEvt(EVENT_ASYLUM_INIT);
if (_handler)
_handler->handleEvent(initEvt);
// Start running event loop
while (!shouldQuit()) {
handleEvents();
_system->delayMillis(10);
_system->updateScreen();
}
return Common::kNoError;
}
void AsylumEngine::startGame(ResourcePackId sceneId, StartGameType type) {
if (!_cursor || !_screen || !_savegame)
error("[AsylumEngine::startGame] Subsystems not initialized properly!");
// Load the default mouse cursor
_cursor->set(MAKE_RESOURCE(kResourcePackSound, 14), 0, kCursorAnimationNone);
_cursor->hide();
// Clear the graphic list
_screen->clearGraphicsInQueue();
// Reset scene (this ensures the current resource pack is closed as in the original)
delete _scene;
_scene = new Scene(this);
_handler = _scene;
// Set the current cd number (necessary for proper SharedSound resource pack initialization)
int32 cdNumber = _resource->getCdNumber();
switch (sceneId) {
default:
_resource->setCdNumber(-1);
break;
case kResourcePackTowerCells:
case kResourcePackInnocentAbandoned:
case kResourcePackCourtyardAndChapel:
_resource->setCdNumber(1);
break;
case kResourcePackCircusOfFools:
case kResourcePackCave:
case kResourcePackMansion:
case kResourcePackLaboratory:
case kResourcePackHive:
_resource->setCdNumber(2);
break;
case kResourcePackMorgueAndCemetery:
case kResourcePackLostVillage:
case kResourcePackMaze:
case kResourcePackGauntlet:
case kResourcePackMorgansLastGame:
_resource->setCdNumber(3);
break;
}
if (_resource->getCdNumber() != cdNumber)
_resource->clearSharedSoundCache();
_resource->clearMusicCache();
switch (type) {
default:
error("[AsylumEngine::startGame] Invalid start game type!");
case kStartGamePlayIntro:
_scene->enter(sceneId);
playIntro();
break;
case kStartGameLoad:
if (_savegame->load()) {
_scene->enterLoad();
updateReverseStereo();
switchEventHandler(_scene);
}
break;
case kStartGameScene:
_scene->enter(sceneId);
break;
}
_cursor->show();
}
void AsylumEngine::restart() {
if (!_cursor || !_script)
error("[AsylumEngine::restart] Subsystems not initialized properly!");
_cursor->hide();
// Cleanup
memset(&_gameFlags, 0, sizeof(_gameFlags));
delete _scene;
_scene = NULL;
delete _encounter;
_encounter = new Encounter(this);
_script->resetQueue();
_data.setGlobalPoint(Common::Point(-1, -1));
reset();
_introPlayed = false;
_screen->clear();
_sound->playMusic(kResourceNone, 0);
startGame(kResourcePackTowerCells, kStartGamePlayIntro);
}
void AsylumEngine::reset() {
if (!_menu || !_special || !_puzzles)
error("[AsylumEngine::reset] Subsystems not initialized properly!");
// Set game as started
_menu->setGameStarted();
// Reset puzzles
_puzzles->reset();
// Reset shared data
_data.reset();
// Reset special palette info
_special->reset(true);
}
void AsylumEngine::playIntro() {
if (!_video || !_screen)
error("[AsylumEngine::playIntro] Subsystems not initialized properly!");
updateReverseStereo();
if (!_introPlayed) {
_cursor->hide();
_cursor->setForceHide(true);
if (!Config.showIntro) {
if (_scene->worldstats()->chapter == kChapter1)
_sound->playMusic(MAKE_RESOURCE(kResourcePackMusic, _scene->worldstats()->musicCurrentResourceIndex));
} else {
_sound->playMusic(kResourceNone, 0);
_video->play(1, _menu);
if (_scene->worldstats()->musicCurrentResourceIndex != kMusicStopped)
_sound->playMusic(MAKE_RESOURCE(kResourcePackMusic, _scene->worldstats()->musicCurrentResourceIndex));
_screen->clear();
setGameFlag(kGameFlag4);
setGameFlag(kGameFlag12);
// Play the intro speech: it is played after the intro video over a black background,
// and the game is "locked" until the speech is completed.
ResourceId introSpeech = MAKE_RESOURCE(kResourcePackSound, 7);
_sound->playSound(introSpeech);
int8 skip = 0;
do {
// Poll events (this ensures we don't freeze the screen)
Common::Event ev;
while (_eventMan->pollEvent(ev)) {
switch (ev.type) {
case Common::EVENT_LBUTTONDOWN:
case Common::EVENT_KEYDOWN:
skip = true;
break;
default:
break;
}
}
_system->updateScreen();
_system->delayMillis(100);
} while (_sound->isPlaying(introSpeech) && !skip);
if (_sound->isPlaying(introSpeech)) {
_sound->stop(introSpeech);
}
}
_cursor->setForceHide(false);
_introPlayed = true;
}
_cursor->show();
_savegame->loadMoviesViewed();
// Switch to scene event handling
switchEventHandler(_scene);
}
void AsylumEngine::handleEvents() {
if (!_console || !_video || !_screen || !_sound || !_menu || !_cursor)
error("[AsylumEngine::handleEvents] Subsystems not initialized properly!");
// Show the debugger if required
_console->onFrame();
AsylumEvent ev;
Common::Keymapper *const keymapper = _eventMan->getKeymapper();
while (_eventMan->pollEvent(ev)) {
keymapper->setEnabled(_handler == _scene);
switch (ev.type) {
default:
break;
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
// Handle custom actions
if (_handler)
_handler->handleEvent(ev);
break;
case Common::EVENT_KEYDOWN:
if ((ev.kbd.flags & Common::KBD_CTRL) && ev.kbd.keycode == Common::KEYCODE_d) {
_console->attach();
break;
}
// Handle key events
if (_handler)
_handler->handleEvent(ev);
break;
case Common::EVENT_KEYUP:
// Handle key events
if (_handler)
_handler->handleEvent(ev);
break;
case Common::EVENT_MOUSEMOVE:
case Common::EVENT_LBUTTONDOWN:
case Common::EVENT_LBUTTONUP:
case Common::EVENT_RBUTTONDOWN:
case Common::EVENT_RBUTTONUP:
case Common::EVENT_MBUTTONUP:
case Common::EVENT_MBUTTONDOWN:
// Handle mouse events
_cursor->setState(ev);
if (_handler)
_handler->handleEvent(ev);
break;
case Common::EVENT_QUIT:
quitGame();
break;
// TODO handle cases where we receive a midi or music event
}
}
// Animate cursor
_cursor->animate();
// Send update event to our event handler
AsylumEvent updateEvt = AsylumEvent(EVENT_ASYLUM_UPDATE);
if (_handler)
_handler->handleEvent(updateEvt);
// Handle debug events
processDelayedEvents();
}
void AsylumEngine::processDelayedEvents() {
if (!_video || !_sound || !_menu || !_script || !_screen)
error("[AsylumEngine::processDelayedEvents] Subsystems not initialized properly!");
// check for a delayed scene change
if (_delayedSceneIndex != kResourcePackInvalid && isGameFlagNotSet(kGameFlagScriptProcessing)) {
ResourcePackId sceneIndex = _delayedSceneIndex;
_delayedSceneIndex = kResourcePackInvalid;
// Reset actor and script queue
_script->resetQueue();
_script->reset();
if (_scene)
_scene->getActor(0)->updateStatus(kActorStatusDisabled);
// Fade screen to black
_screen->paletteFade(0, 75, 8);
_screen->clear();
// Stop all sounds & music
_sound->stopMusic();
_sound->stopAll();
// Switch the scene
switchScene(sceneIndex);
}
// Check for delayed video
if (_delayedVideoIndex != -1 && isGameFlagNotSet(kGameFlagScriptProcessing)) {
uint32 index = (uint32)_delayedVideoIndex;
_delayedVideoIndex = -1;
_video->play(index, _handler);
}
}
//////////////////////////////////////////////////////////////////////////
// Message handlers
//////////////////////////////////////////////////////////////////////////
void AsylumEngine::switchEventHandler(EventHandler *handler) {
if (handler == NULL)
warning("[AsylumEngine::switchMessageHandler] NULL handler parameter (shouldn't happen outside of debug commands)!");
// De-init previous handler
if (_handler != NULL) {
AsylumEvent deinit(EVENT_ASYLUM_DEINIT);
_handler->handleEvent(deinit);
}
//////////////////////////////////////////////////////////////////////////
// DEBUG - If a previous scene is found, replace the current scene by this one
if (handler == _scene) {
if (_previousScene) {
delete _scene;
_scene = _previousScene;
handler = _scene;
_previousScene = NULL;
}
}
//////////////////////////////////////////////////////////////////////////
// replace message handler
_handler = handler;
// Init new handler
AsylumEvent init(EVENT_ASYLUM_INIT);
if (_handler)
_handler->handleEvent(init);
}
void AsylumEngine::notify(AsylumEventType type, int32 param1, int32 param2) {
if (_handler == NULL)
error("[AsylumEngine::notify] Invalid handler parameter (cannot be NULL)!");
AsylumEvent evt(type, param1, param2);
_handler->handleEvent(evt);
}
Common::Point AsylumEngine::getInventoryRingPoint(uint32 nPoints, uint32 index) const {
if (!_scene)
error("[AsylumEngine::getInventoryRingPoint] Subsystems not initialized properly!");
const int16 (*pointPtr)[2];
if (_scene->worldstats()->chapter == kChapter11)
pointPtr = &inventoryRingPoints[inventoryRingOffsets[7] + index + 3];
else
pointPtr = &inventoryRingPoints[inventoryRingOffsets[nPoints - 1] + index];
return Common::Point((*pointPtr)[0], (*pointPtr)[1]);
}
void AsylumEngine::updateReverseStereo() {
if (_scene && _scene->worldstats())
_scene->worldstats()->reverseStereo = Config.reverseStereo;
}
void AsylumEngine::saveLoadWithSerializer(Common::Serializer &s) {
if (!_script)
error("[AsylumEngine::saveLoadWithSerializer] Subsystems not initialized properly!");
// Game flags
for (uint32 i = 0; i < ARRAYSIZE(_gameFlags); i++)
s.syncAsUint32LE(_gameFlags[i]);
// The original has the script data in the middle of other shared data,
// so to be compatible with original savegames, we want to save it in
// the proper order
_data.saveLoadAmbientSoundData(s);
// Original skips two elements
// (original has one unused, one used for debugging screen update counts)
s.skip(8);
// Script queue
_script->saveQueue(s);
// Shared data (the rest of it)
_data.saveLoadWithSerializer(s);
}
//////////////////////////////////////////////////////////////////////////
// Game flags
//////////////////////////////////////////////////////////////////////////
void AsylumEngine::setGameFlag(GameFlag flag) {
_gameFlags[flag / 32] |= 1 << (flag % 32);
}
void AsylumEngine::clearGameFlag(GameFlag flag) {
_gameFlags[flag / 32] &= ~(1 << (flag % 32));
}
void AsylumEngine::toggleGameFlag(GameFlag flag) {
_gameFlags[flag / 32] ^= 1 << (flag % 32);
}
bool AsylumEngine::isGameFlagSet(GameFlag flag) const {
return ((1 << (flag % 32)) & _gameFlags[flag / 32]) >> (flag % 32) != 0;
}
bool AsylumEngine::isGameFlagNotSet(GameFlag flag) const {
return ((1 << (flag % 32)) & _gameFlags[flag / 32]) >> (flag % 32) == 0;
}
//////////////////////////////////////////////////////////////////////////
// Misc
//////////////////////////////////////////////////////////////////////////
bool AsylumEngine::rectContains(const int16 (*rectPtr)[4], const Common::Point &p) const {
return ((*rectPtr)[0] <= p.x) && (p.x < (*rectPtr)[2]) && ((*rectPtr)[1] <= p.y) && (p.y < (*rectPtr)[3]);
}
} // namespace Asylum