scummvm/engines/tony/tony.cpp
Paul Gilbert 9f175c4053 ENGINES: Cleanup of savegame filenames generation
This removes filename methods when it matched the Engine method.
Secondly, ensuring there was an overriden getSaveStateName method
for engines that didn't do the standard target.00x save filenames
2020-02-16 15:44:28 -08:00

808 lines
20 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 "common/scummsys.h"
#include "common/algorithm.h"
#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/events.h"
#include "common/file.h"
#include "common/installshield_cab.h"
#include "common/translation.h"
#include "tony/tony.h"
#include "tony/custom.h"
#include "tony/debugger.h"
#include "tony/game.h"
#include "tony/mpal/mpal.h"
namespace Tony {
TonyEngine *g_vm;
TonyEngine::TonyEngine(OSystem *syst, const TonyGameDescription *gameDesc) : Engine(syst),
_gameDescription(gameDesc), _randomSource("tony") {
g_vm = this;
_loadSlotNumber = -1;
// Set the up the debugger
setDebugger(new Debugger());
DebugMan.addDebugChannel(kTonyDebugAnimations, "animations", "Animations debugging");
DebugMan.addDebugChannel(kTonyDebugActions, "actions", "Actions debugging");
DebugMan.addDebugChannel(kTonyDebugSound, "sound", "Sound debugging");
DebugMan.addDebugChannel(kTonyDebugMusic, "music", "Music debugging");
// Add folders to the search directory list
const Common::FSNode gameDataDir(ConfMan.get("path"));
SearchMan.addSubDirectoryMatching(gameDataDir, "Voices");
SearchMan.addSubDirectoryMatching(gameDataDir, "Roasted");
SearchMan.addSubDirectoryMatching(gameDataDir, "Music");
SearchMan.addSubDirectoryMatching(gameDataDir, "Music/utilsfx");
SearchMan.addSubDirectoryMatching(gameDataDir, "Music/Layer");
// Set up load slot number
_initialLoadSlotNumber = -1;
if (ConfMan.hasKey("save_slot")) {
int slotNumber = ConfMan.getInt("save_slot");
if (slotNumber >= 0 && slotNumber <= 99)
_initialLoadSlotNumber = slotNumber;
}
// Load the ScummVM sound settings
syncSoundSettings();
_hEndOfFrame = 0;
for (int i = 0; i < 6; i++)
_stream[i] = NULL;
for (int i = 0; i < MAX_SFX_CHANNELS; i++) {
_sfx[i] = NULL;
_utilSfx[i] = NULL;
}
_bPaused = false;
_bDrawLocation = false;
_startTime = 0;
_curThumbnail = NULL;
_bQuitNow = false;
_bTimeFreezed = false;
_nTimeFreezed = 0;
_vdbCodec = FPCODEC_UNKNOWN;
memset(_funcList, 0, sizeof(_funcList));
}
TonyEngine::~TonyEngine() {
// Close the voice database
closeVoiceDatabase();
// Reset the coroutine scheduler
CoroScheduler.reset();
CoroScheduler.setResourceCallback(NULL);
}
/**
* Run the game
*/
Common::Error TonyEngine::run() {
Common::ErrorCode result = init();
if (result != Common::kNoError)
return result;
play();
close();
return Common::kNoError;
}
/**
* Initialize the game
*/
Common::ErrorCode TonyEngine::init() {
// Load DAT file (used by font manager)
if (!loadTonyDat())
return Common::kUnknownError;
if (isCompressed()) {
Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember("data1.cab");
if (!stream)
error("Failed to open data1.cab");
Common::Archive *cabinet = Common::makeInstallShieldArchive(stream);
if (!cabinet)
error("Failed to parse data1.cab");
SearchMan.add("data1.cab", cabinet);
}
_hEndOfFrame = CoroScheduler.createEvent(false, false);
_bPaused = false;
_bDrawLocation = true;
_startTime = g_system->getMillis();
// Init static class fields
RMText::initStatics();
RMTony::initStatics();
// Reset the scheduler
CoroScheduler.reset();
// Initialize the graphics window
_window.init();
// Initialize the function list
Common::fill(_funcList, _funcList + 300, (LPCUSTOMFUNCTION)NULL);
initCustomFunctionMap();
// Initializes MPAL system, passing the custom functions list
Common::File f;
if (!f.open("ROASTED.MPC"))
return Common::kReadingFailed;
f.close();
if (!mpalInit("ROASTED.MPC", "ROASTED.MPR", _funcList, _funcListStrings))
return Common::kUnknownError;
// Initialize the update resources
_resUpdate.init("ROASTED.MPU");
// Initialize the music
initMusic();
// Initialize the voices database
if (!openVoiceDatabase())
return Common::kReadingFailed;
// Initialize the boxes
_theBoxes.init();
// Link to the custom graphics engine
_theEngine.initCustomDll();
_theEngine.init();
// Allocate space for thumbnails when saving the game
_curThumbnail = new uint16[160 * 120];
_bQuitNow = false;
return Common::kNoError;
}
bool TonyEngine::loadTonyDat() {
Common::String msg;
Common::File in;
Common::String filename = "tony.dat";
in.open(filename.c_str());
if (!in.isOpen()) {
msg = Common::String::format(_("Unable to locate the '%s' engine data file."), filename.c_str());
GUIErrorMessage(msg);
warning("%s", msg.c_str());
return false;
}
// Read header
char buf[4+1];
in.read(buf, 4);
buf[4] = '\0';
if (strcmp(buf, "TONY")) {
msg = Common::String::format(_("The '%s' engine data file is corrupt."), filename.c_str());
GUIErrorMessage(msg);
warning("%s", msg.c_str());
return false;
}
int majVer = in.readByte();
int minVer = in.readByte();
if ((majVer != TONY_DAT_VER_MAJ) || (minVer != TONY_DAT_VER_MIN)) {
msg = Common::String::format(
_("Incorrect version of the '%s' engine data file found. Expected %d.%d but got %d.%d."),
filename.c_str(), TONY_DAT_VER_MAJ, TONY_DAT_VER_MIN, majVer, minVer);
GUIErrorMessage(msg);
warning("%s", msg.c_str());
return false;
}
int expectedLangVariant = -1;
switch (g_vm->getLanguage()) {
case Common::IT_ITA:
case Common::EN_ANY:
expectedLangVariant = 0;
break;
case Common::PL_POL:
expectedLangVariant = 1;
break;
case Common::RU_RUS:
expectedLangVariant = 2;
break;
case Common::CZ_CZE:
expectedLangVariant = 3;
break;
case Common::FR_FRA:
expectedLangVariant = 4;
break;
case Common::DE_DEU:
expectedLangVariant = 5;
break;
default:
warning("Unhandled language, falling back to English/Italian fonts.");
expectedLangVariant = 0;
break;
}
int numVariant = in.readUint16BE();
if (expectedLangVariant > numVariant - 1) {
msg = Common::String::format(_("Font variant not present in '%s' engine data file."), filename.c_str());
GUIErrorMessage(msg);
warning("%s", msg.c_str());
return false;
}
in.seek(in.pos() + (2 * 256 * 8 * expectedLangVariant));
for (int i = 0; i < 256; i++) {
_cTableDialog[i] = in.readSint16BE();
_lTableDialog[i] = in.readSint16BE();
_cTableMacc[i] = in.readSint16BE();
_lTableMacc[i] = in.readSint16BE();
_cTableCred[i] = in.readSint16BE();
_lTableCred[i] = in.readSint16BE();
_cTableObj[i] = in.readSint16BE();
_lTableObj[i] = in.readSint16BE();
}
return true;
}
void TonyEngine::initCustomFunctionMap() {
INIT_CUSTOM_FUNCTION(_funcList, _funcListStrings);
}
void TonyEngine::playMusic(int nChannel, const Common::String &fname, int nFX, bool bLoop, int nSync) {
if (nChannel < 4) {
if (GLOBALS._flipflop)
nChannel = nChannel + 1;
}
switch (nFX) {
case 0:
case 1:
case 2:
_stream[nChannel]->stop();
_stream[nChannel]->unloadFile();
break;
case 22:
default:
break;
}
if (nFX == 22) { // Sync a tempo
GLOBALS._curChannel = nChannel;
GLOBALS._nextLoop = bLoop;
GLOBALS._nextSync = nSync;
GLOBALS._nextMusic = fname;
if (GLOBALS._flipflop)
GLOBALS._nextChannel = nChannel - 1;
else
GLOBALS._nextChannel = nChannel + 1;
uint32 hThread = CoroScheduler.createProcess(doNextMusic, NULL, 0);
assert(hThread != CORO_INVALID_PID_VALUE);
} else if (nFX == 44) { // Change the channel and let the first finish
if (GLOBALS._flipflop)
GLOBALS._nextChannel = nChannel - 1;
else
GLOBALS._nextChannel = nChannel + 1;
_stream[GLOBALS._nextChannel]->stop();
_stream[GLOBALS._nextChannel]->unloadFile();
if (!getIsDemo()) {
if (!_stream[GLOBALS._nextChannel]->loadFile(fname, nSync))
error("failed to open music file '%s'", fname.c_str());
} else {
_stream[GLOBALS._nextChannel]->loadFile(fname, nSync);
}
_stream[GLOBALS._nextChannel]->setLoop(bLoop);
_stream[GLOBALS._nextChannel]->play();
GLOBALS._flipflop = 1 - GLOBALS._flipflop;
} else {
if (!getIsDemo()) {
if (!_stream[nChannel]->loadFile(fname, nSync))
error("failed to open music file '%s'", fname.c_str());
} else {
_stream[nChannel]->loadFile(fname, nSync);
}
_stream[nChannel]->setLoop(bLoop);
_stream[nChannel]->play();
}
}
void TonyEngine::doNextMusic(CORO_PARAM, const void *param) {
CORO_BEGIN_CONTEXT;
Common::String fn;
CORO_END_CONTEXT(_ctx);
FPStream **streams = g_vm->_stream;
CORO_BEGIN_CODE(_ctx);
if (!g_vm->getIsDemo()) {
if (!streams[GLOBALS._nextChannel]->loadFile(GLOBALS._nextMusic, GLOBALS._nextSync))
error("failed to open next music file '%s'", GLOBALS._nextMusic.c_str());
} else {
streams[GLOBALS._nextChannel]->loadFile(GLOBALS._nextMusic, GLOBALS._nextSync);
}
streams[GLOBALS._nextChannel]->setLoop(GLOBALS._nextLoop);
//streams[GLOBALS._nextChannel]->prefetch();
streams[GLOBALS._curChannel]->waitForSync(streams[GLOBALS._nextChannel]);
streams[GLOBALS._curChannel]->unloadFile();
GLOBALS._flipflop = 1 - GLOBALS._flipflop;
CORO_END_CODE;
}
void TonyEngine::playSFX(int nChannel, int nFX) {
if (_sfx[nChannel] == NULL)
return;
switch (nFX) {
case 0:
_sfx[nChannel]->setLoop(false);
break;
case 1:
_sfx[nChannel]->setLoop(true);
break;
default:
break;
}
_sfx[nChannel]->play();
}
void TonyEngine::stopMusic(int nChannel) {
if (nChannel < 4)
_stream[nChannel + GLOBALS._flipflop]->stop();
else
_stream[nChannel]->stop();
}
void TonyEngine::stopSFX(int nChannel) {
_sfx[nChannel]->stop();
}
void TonyEngine::playUtilSFX(int nChannel, int nFX) {
if (_utilSfx[nChannel] == NULL)
return;
switch (nFX) {
case 0:
_utilSfx[nChannel]->setLoop(false);
break;
case 1:
_utilSfx[nChannel]->setLoop(true);
break;
default:
break;
}
_utilSfx[nChannel]->setVolume(52);
_utilSfx[nChannel]->play();
}
void TonyEngine::stopUtilSFX(int nChannel) {
_utilSfx[nChannel]->stop();
}
void TonyEngine::preloadSFX(int nChannel, const char *fn) {
if (_sfx[nChannel] != NULL) {
_sfx[nChannel]->stop();
_sfx[nChannel]->release();
_sfx[nChannel] = NULL;
}
_theSound.createSfx(&_sfx[nChannel]);
_sfx[nChannel]->loadFile(fn);
}
FPSfx *TonyEngine::createSFX(Common::SeekableReadStream *stream) {
FPSfx *sfx;
_theSound.createSfx(&sfx);
sfx->loadWave(stream);
return sfx;
}
void TonyEngine::preloadUtilSFX(int nChannel, const char *fn) {
if (_utilSfx[nChannel] != NULL) {
_utilSfx[nChannel]->stop();
_utilSfx[nChannel]->release();
_utilSfx[nChannel] = NULL;
}
_theSound.createSfx(&_utilSfx[nChannel]);
_utilSfx[nChannel]->loadFile(fn);
_utilSfx[nChannel]->setVolume(63);
}
void TonyEngine::unloadAllSFX() {
for (int i = 0; i < MAX_SFX_CHANNELS; i++) {
if (_sfx[i] != NULL) {
_sfx[i]->stop();
_sfx[i]->release();
_sfx[i] = NULL;
}
}
}
void TonyEngine::unloadAllUtilSFX() {
for (int i = 0; i < MAX_SFX_CHANNELS; i++) {
if (_utilSfx[i] != NULL) {
_utilSfx[i]->stop();
_utilSfx[i]->release();
_utilSfx[i] = NULL;
}
}
}
void TonyEngine::initMusic() {
int i;
_theSound.init();
_theSound.setMasterVolume(63);
for (i = 0; i < 6; i++)
_theSound.createStream(&_stream[i]);
for (i = 0; i < MAX_SFX_CHANNELS; i++) {
_sfx[i] = _utilSfx[i] = NULL;
}
// Preload sound effects
preloadUtilSFX(0, "U01.ADP"); // Reversed!!
preloadUtilSFX(1, "U02.ADP");
// Start check processes for sound
CoroScheduler.createProcess(FPSfx::soundCheckProcess, NULL);
}
void TonyEngine::closeMusic() {
for (int i = 0; i < 6; i++) {
_stream[i]->stop();
_stream[i]->unloadFile();
_stream[i]->release();
}
unloadAllSFX();
unloadAllUtilSFX();
}
void TonyEngine::pauseSound(bool bPause) {
_theEngine.pauseSound(bPause);
for (uint i = 0; i < 6; i++)
if (_stream[i])
_stream[i]->setPause(bPause);
for (uint i = 0; i < MAX_SFX_CHANNELS; i++) {
if (_sfx[i])
_sfx[i]->setPause(bPause);
if (_utilSfx[i])
_utilSfx[i]->setPause(bPause);
}
}
void TonyEngine::setMusicVolume(int nChannel, int volume) {
_stream[nChannel + GLOBALS._flipflop]->setVolume(volume);
}
int TonyEngine::getMusicVolume(int nChannel) {
int volume;
_stream[nChannel + GLOBALS._flipflop]->getVolume(&volume);
return volume;
}
Common::String TonyEngine::getSaveStateFileName(int n) {
return Common::String::format("tony.%03d", n);
}
Common::String TonyEngine::getSaveStateName(int slot) const {
return getSaveStateFileName(slot);
}
void TonyEngine::autoSave(CORO_PARAM) {
CORO_BEGIN_CONTEXT;
Common::String buf;
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
grabThumbnail();
CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE);
CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE);
_ctx->buf = getSaveStateFileName(0);
_theEngine.saveState(_ctx->buf, (byte *)_curThumbnail, "Autosave");
CORO_END_CODE;
}
void TonyEngine::saveState(int n, const char *name) {
Common::String buf = getSaveStateFileName(n);
_theEngine.saveState(buf.c_str(), (byte *)_curThumbnail, name);
}
void TonyEngine::loadState(CORO_PARAM, int n) {
CORO_BEGIN_CONTEXT;
Common::String buf;
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
_ctx->buf = getSaveStateFileName(n);
CORO_INVOKE_1(_theEngine.loadState, _ctx->buf.c_str());
CORO_END_CODE;
}
bool TonyEngine::openVoiceDatabase() {
// Open the voices database
if (!_vdbFP.open("voices.vdb"))
if (!_vdbFP.open("voices.mdb"))
if (!_vdbFP.open("voices.odb"))
if (!_vdbFP.open("voices.fdb"))
return false;
_vdbFP.seek(-8, SEEK_END);
uint32 numfiles = _vdbFP.readUint32LE();
int32 id = _vdbFP.readUint32BE();
if (id == MKTAG('V', 'D', 'B', '1'))
_vdbCodec = FPCODEC_ADPCM;
else if (id == MKTAG('M', 'D', 'B', '1'))
_vdbCodec = FPCODEC_MP3;
else if (id == MKTAG('O', 'D', 'B', '1'))
_vdbCodec = FPCODEC_OGG;
else if (id == MKTAG('F', 'D', 'B', '1'))
_vdbCodec = FPCODEC_FLAC;
else {
_vdbFP.close();
return false;
}
// Read in the index
_vdbFP.seek(-8 - (numfiles * VOICE_HEADER_SIZE), SEEK_END);
for (uint32 i = 0; i < numfiles; ++i) {
VoiceHeader vh;
vh._offset = _vdbFP.readUint32LE();
vh._code = _vdbFP.readUint32LE();
vh._parts = _vdbFP.readUint32LE();
_voices.push_back(vh);
}
return true;
}
void TonyEngine::closeVoiceDatabase() {
if (_vdbFP.isOpen())
_vdbFP.close();
if (_voices.size() > 0)
_voices.clear();
}
void TonyEngine::grabThumbnail() {
_window.grabThumbnail(_curThumbnail);
}
uint16 *TonyEngine::getThumbnail() {
return _curThumbnail;
}
void TonyEngine::quitGame() {
_bQuitNow = true;
}
void TonyEngine::openInitLoadMenu(CORO_PARAM) {
_theEngine.openOptionScreen(coroParam, 1);
}
void TonyEngine::openInitOptions(CORO_PARAM) {
_theEngine.openOptionScreen(coroParam, 2);
}
/**
* Main process for playing the game.
*
* @remarks This needs to be in a separate process, since there are some things that can briefly
* block the execution of process. For now, all ScummVm event handling is dispatched to within the context of this
* process. If it ever proves a problem, we may have to look into whether it's feasible to have it still remain
* in the outer 'main' process.
*/
void TonyEngine::playProcess(CORO_PARAM, const void *param) {
CORO_BEGIN_CONTEXT;
Common::String fn;
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
// Game loop. We rely on the outer main process to detect if a shutdown is required,
// and kill the scheudler and all the processes, including this one
for (;;) {
// If a savegame needs to be loaded, then do so
if (g_vm->_loadSlotNumber != -1 && GLOBALS._gfxEngine != NULL) {
_ctx->fn = getSaveStateFileName(g_vm->_loadSlotNumber);
CORO_INVOKE_1(GLOBALS._gfxEngine->loadState, _ctx->fn);
g_vm->_loadSlotNumber = -1;
}
// Wait for the next frame
CORO_INVOKE_1(CoroScheduler.sleep, 50);
// Call the engine to handle the next frame
CORO_INVOKE_1(g_vm->_theEngine.doFrame, g_vm->_bDrawLocation);
// Warns that a frame is finished
CoroScheduler.pulseEvent(g_vm->_hEndOfFrame);
// Handle drawing the frame
if (!g_vm->_bPaused) {
if (!g_vm->_theEngine._bWiping)
g_vm->_window.getNewFrame(g_vm->_theEngine, NULL);
else
g_vm->_window.getNewFrame(g_vm->_theEngine, &g_vm->_theEngine._rcWipeEllipse);
}
// Paint the frame onto the screen
g_vm->_window.repaint();
}
CORO_END_CODE;
}
/**
* Play the game
*/
void TonyEngine::play() {
// Create the game player process
CoroScheduler.createProcess(playProcess, NULL);
// Loop through calling the scheduler until it's time for the game to quit
while (!shouldQuit() && !_bQuitNow) {
// Delay for a brief amount
g_system->delayMillis(10);
// Call any scheduled processes
CoroScheduler.schedule();
}
}
void TonyEngine::close() {
closeMusic();
CoroScheduler.closeEvent(_hEndOfFrame);
_theBoxes.close();
_theEngine.close();
_window.close();
mpalFree();
freeMpc();
delete[] _curThumbnail;
}
void TonyEngine::freezeTime() {
_bTimeFreezed = true;
_nTimeFreezed = getTime() - _startTime;
}
void TonyEngine::unfreezeTime() {
_bTimeFreezed = false;
}
/**
* Returns the millisecond timer
*/
uint32 TonyEngine::getTime() {
return g_system->getMillis();
}
bool TonyEngine::canLoadGameStateCurrently() {
return GLOBALS._gfxEngine != NULL && GLOBALS._gfxEngine->canLoadSave();
}
bool TonyEngine::canSaveGameStateCurrently() {
return GLOBALS._gfxEngine != NULL && GLOBALS._gfxEngine->canLoadSave();
}
Common::Error TonyEngine::loadGameState(int slot) {
_loadSlotNumber = slot;
return Common::kNoError;
}
Common::Error TonyEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
if (!GLOBALS._gfxEngine)
return Common::kUnknownError;
RMGfxTargetBuffer &bigBuf = *GLOBALS._gfxEngine;
RMSnapshot s;
s.grabScreenshot(bigBuf, 4, _curThumbnail);
GLOBALS._gfxEngine->saveState(getSaveStateFileName(slot), (byte *)_curThumbnail, desc);
return Common::kNoError;
}
void TonyEngine::syncSoundSettings() {
Engine::syncSoundSettings();
GLOBALS._bCfgDubbing = !ConfMan.getBool("mute") && !ConfMan.getBool("speech_mute");
GLOBALS._bCfgSFX = !ConfMan.getBool("mute") && !ConfMan.getBool("sfx_mute");
GLOBALS._bCfgMusic = !ConfMan.getBool("mute") && !ConfMan.getBool("music_mute");
GLOBALS._nCfgDubbingVolume = ConfMan.getInt("speech_volume") * 10 / 256;
GLOBALS._nCfgSFXVolume = ConfMan.getInt("sfx_volume") * 10 / 256;
GLOBALS._nCfgMusicVolume = ConfMan.getInt("music_volume") * 10 / 256;
GLOBALS._bShowSubtitles = ConfMan.getBool("subtitles");
GLOBALS._nCfgTextSpeed = ConfMan.getInt("talkspeed") * 10 / 256;
}
void TonyEngine::saveSoundSettings() {
ConfMan.setBool("speech_mute", !GLOBALS._bCfgDubbing);
ConfMan.setBool("sfx_mute", !GLOBALS._bCfgSFX);
ConfMan.setBool("music_mute", !GLOBALS._bCfgMusic);
ConfMan.setInt("speech_volume", GLOBALS._nCfgDubbingVolume * 256 / 10);
ConfMan.setInt("sfx_volume", GLOBALS._nCfgSFXVolume * 256 / 10);
ConfMan.setInt("music_volume", GLOBALS._nCfgMusicVolume * 256 / 10);
ConfMan.setBool("subtitles", GLOBALS._bShowSubtitles);
ConfMan.setInt("talkspeed", GLOBALS._nCfgTextSpeed * 256 / 10);
}
void TonyEngine::showLocation() {
_bDrawLocation = true;
}
void TonyEngine::hideLocation() {
_bDrawLocation = false;
}
} // End of namespace Tony