mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
This flag is removed for a few reasons: * Engines universally set this flag to true for widths > 320, which made it redundant everywhere; * This flag functioned primarily as a "force 1x scaler" flag, since its behaviour was almost completely undocumented and users would need to figure out that they'd need an explicit non-default scaler set to get a scaler to operate at widths > 320; * (Most importantly) engines should not be in the business of deciding how the backend may choose to render its virtual screen. The choice of rendering behaviour belongs to the user, and the backend, in that order. A nearby future commit restores the default1x scaler behaviour in the SDL backend code for the moment, but in the future it is my hope that there will be a better configuration UI to allow users to specify how they want scaling to work for high resolutions.
520 lines
12 KiB
C++
520 lines
12 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.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* This code is based on original Mortville Manor DOS source code
|
|
* Copyright (c) 1987-1989 Lankhor
|
|
*/
|
|
|
|
#include "mortevielle/mortevielle.h"
|
|
|
|
#include "mortevielle/dialogs.h"
|
|
#include "mortevielle/menu.h"
|
|
#include "mortevielle/mouse.h"
|
|
#include "mortevielle/outtext.h"
|
|
#include "mortevielle/saveload.h"
|
|
#include "mortevielle/outtext.h"
|
|
|
|
#include "common/system.h"
|
|
#include "common/config-manager.h"
|
|
#include "common/debug-channels.h"
|
|
#include "common/translation.h"
|
|
#include "engines/util.h"
|
|
#include "engines/engine.h"
|
|
#include "graphics/palette.h"
|
|
#include "graphics/pixelformat.h"
|
|
|
|
namespace Mortevielle {
|
|
|
|
MortevielleEngine *g_vm;
|
|
|
|
MortevielleEngine::MortevielleEngine(OSystem *system, const MortevielleGameDescription *gameDesc):
|
|
Engine(system), _gameDescription(gameDesc), _randomSource("mortevielle") {
|
|
// Set debug channels
|
|
DebugMan.addDebugChannel(kMortevielleCore, "core", "Core debugging");
|
|
DebugMan.addDebugChannel(kMortevielleGraphics, "graphics", "Graphics debugging");
|
|
|
|
g_vm = this;
|
|
_debugger = new Debugger(this);
|
|
_dialogManager = new DialogManager(this);
|
|
_screenSurface = new ScreenSurface(this);
|
|
_mouse = new MouseHandler(this);
|
|
_text = new TextHandler(this);
|
|
_soundManager = new SoundManager(this, _mixer);
|
|
_savegameManager = new SavegameManager(this);
|
|
_menu = new Menu(this);
|
|
|
|
_lastGameFrame = 0;
|
|
_mouseClick = false;
|
|
_inMainGameLoop = false;
|
|
_quitGame = false;
|
|
_pauseStartTime = -1;
|
|
|
|
_roomPresenceLuc = false;
|
|
_roomPresenceIda = false;
|
|
_purpleRoomPresenceLeo = false;
|
|
_roomPresenceGuy = false;
|
|
_roomPresenceEva = false;
|
|
_roomPresenceMax = false;
|
|
_roomPresenceBob = false;
|
|
_roomPresencePat = false;
|
|
_toiletsPresenceBobMax = false;
|
|
_bathRoomPresenceBobMax = false;
|
|
_juliaRoomPresenceLeo = false;
|
|
|
|
_soundOff = false;
|
|
_largestClearScreen = false;
|
|
_hiddenHero = false;
|
|
_heroSearching = false;
|
|
_keyPressedEsc = false;
|
|
_reloadCFIEC = false;
|
|
|
|
_outsideOnlyFl = true;
|
|
_col = false;
|
|
_syn = false;
|
|
_obpart = false;
|
|
_destinationOk = false;
|
|
_anyone = false;
|
|
_uptodatePresence = false;
|
|
|
|
_textColor = 0;
|
|
_place = -1;
|
|
|
|
_x26KeyCount = -1;
|
|
_caff = -1;
|
|
_day = 0;
|
|
|
|
_curPict = nullptr;
|
|
_curAnim = nullptr;
|
|
_rightFramePict = nullptr;
|
|
|
|
resetCoreVar();
|
|
|
|
_maff = 0;
|
|
_crep = 0;
|
|
|
|
_minute = 0;
|
|
_curSearchObjId = 0;
|
|
_controlMenu = 0;
|
|
_startTime = 0;
|
|
_endTime = 0;
|
|
_roomDoorId = OWN_ROOM;
|
|
_openObjCount = 0;
|
|
_takeObjCount = 0;
|
|
_num = 0;
|
|
_searchCount = 0;
|
|
_introSpeechPlayed = false;
|
|
_inGameHourDuration = 0;
|
|
_x = 0;
|
|
_y = 0;
|
|
_currentHourCount = 0;
|
|
_currentTime = 0;
|
|
_cfiecBuffer = nullptr;
|
|
_cfiecBufferSize = 0;
|
|
for (int i = 0; i < 601; i++) {
|
|
_dialogHintArray[i]._hintId = 0;
|
|
_dialogHintArray[i]._point = 0;
|
|
}
|
|
_currMenu = OPCODE_NONE;
|
|
_currAction = OPCODE_NONE;
|
|
_menuOpcode = OPCODE_NONE;
|
|
_addFix = 0;
|
|
_currBitIndex = 0;
|
|
_currDay = 0;
|
|
_currHour = 10;
|
|
_currHalfHour = 0;
|
|
_hour = 10;
|
|
_key = 0;
|
|
_manorDistance = 0;
|
|
_numpal = 0;
|
|
_savedBitIndex = 0;
|
|
_endGame = false;
|
|
_loseGame = false;
|
|
_txxFileFl = false;
|
|
_is = 0;
|
|
}
|
|
|
|
MortevielleEngine::~MortevielleEngine() {
|
|
delete _menu;
|
|
delete _savegameManager;
|
|
delete _soundManager;
|
|
delete _text;
|
|
delete _mouse;
|
|
delete _screenSurface;
|
|
delete _dialogManager;
|
|
delete _debugger;
|
|
|
|
free(_curPict);
|
|
free(_curAnim);
|
|
free(_rightFramePict);
|
|
}
|
|
|
|
/**
|
|
* Specifies whether the engine supports given features
|
|
*/
|
|
bool MortevielleEngine::hasFeature(EngineFeature f) const {
|
|
return
|
|
(f == kSupportsRTL) ||
|
|
(f == kSupportsLoadingDuringRuntime) ||
|
|
(f == kSupportsSavingDuringRuntime);
|
|
}
|
|
|
|
/**
|
|
* Return true if a game can currently be loaded
|
|
*/
|
|
bool MortevielleEngine::canLoadGameStateCurrently() {
|
|
// Saving is only allowed in the main game event loop
|
|
return _inMainGameLoop;
|
|
}
|
|
|
|
/**
|
|
* Return true if a game can currently be saved
|
|
*/
|
|
bool MortevielleEngine::canSaveGameStateCurrently() {
|
|
// Loading is only allowed in the main game event loop
|
|
return _inMainGameLoop;
|
|
}
|
|
|
|
/**
|
|
* Load in a savegame at the specified slot number
|
|
*/
|
|
Common::Error MortevielleEngine::loadGameState(int slot) {
|
|
return _savegameManager->loadGame(slot);
|
|
}
|
|
|
|
/**
|
|
* Save the current game
|
|
*/
|
|
Common::Error MortevielleEngine::saveGameState(int slot, const Common::String &desc) {
|
|
if (slot == 0)
|
|
return Common::kWritingFailed;
|
|
|
|
return _savegameManager->saveGame(slot, desc);
|
|
}
|
|
|
|
/**
|
|
* Support method that generates a savegame name
|
|
* @param slot Slot number
|
|
*/
|
|
Common::String MortevielleEngine::generateSaveFilename(const Common::String &target, int slot) {
|
|
if (slot == 0)
|
|
// Initial game state loaded when the game starts
|
|
return "sav0.mor";
|
|
|
|
return Common::String::format("%s.%03d", target.c_str(), slot);
|
|
}
|
|
|
|
/**
|
|
* Pause the game.
|
|
*/
|
|
void MortevielleEngine::pauseEngineIntern(bool pause) {
|
|
Engine::pauseEngineIntern(pause);
|
|
if (pause) {
|
|
if (_pauseStartTime == -1)
|
|
_pauseStartTime = readclock();
|
|
} else {
|
|
if (_pauseStartTime != -1) {
|
|
int pauseEndTime = readclock();
|
|
_currentTime += (pauseEndTime - _pauseStartTime);
|
|
if (_uptodatePresence)
|
|
_startTime += (pauseEndTime - _pauseStartTime);
|
|
}
|
|
_pauseStartTime = -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the game state
|
|
*/
|
|
Common::ErrorCode MortevielleEngine::initialize() {
|
|
// Initialize graphics mode
|
|
initGraphics(SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
|
|
// Set up an intermediate screen surface
|
|
_screenSurface->create(SCREEN_WIDTH, SCREEN_HEIGHT, Graphics::PixelFormat::createFormatCLUT8());
|
|
|
|
_txxFileFl = false;
|
|
// Load texts from TXX files
|
|
loadTexts();
|
|
|
|
// Load the mort.dat resource
|
|
Common::ErrorCode result = loadMortDat();
|
|
if (result != Common::kNoError) {
|
|
_screenSurface->free();
|
|
return result;
|
|
}
|
|
|
|
// Load some error messages (was previously in chartex())
|
|
_hintPctMessage = getString(580); // You should have noticed %d hints
|
|
|
|
// Set default EGA palette
|
|
_paletteManager.setDefaultPalette();
|
|
|
|
// Setup the mouse cursor
|
|
initMouse();
|
|
|
|
loadPalette();
|
|
loadCFIPH();
|
|
loadCFIEC();
|
|
decodeNumber(&_cfiecBuffer[161 * 16], (_cfiecBufferSize - (161 * 16)) / 64);
|
|
_x26KeyCount = 1;
|
|
initMaxAnswer();
|
|
initMouse();
|
|
|
|
loadPlaces();
|
|
_soundOff = false;
|
|
_largestClearScreen = false;
|
|
|
|
testKeyboard();
|
|
showConfigScreen();
|
|
testKeyboard();
|
|
clearScreen();
|
|
|
|
_soundManager->loadNoise();
|
|
_soundManager->loadAmbiantSounds();
|
|
|
|
return Common::kNoError;
|
|
}
|
|
|
|
/**
|
|
* Loads the contents of the mort.dat data file
|
|
*/
|
|
Common::ErrorCode MortevielleEngine::loadMortDat() {
|
|
Common::File f;
|
|
|
|
// Open the mort.dat file
|
|
if (!f.open(MORT_DAT)) {
|
|
Common::String msg = Common::String::format(_("Unable to locate the '%s' engine data file."), MORT_DAT);
|
|
GUIErrorMessage(msg);
|
|
return Common::kReadingFailed;
|
|
}
|
|
|
|
// Validate the data file header
|
|
char fileId[4];
|
|
f.read(fileId, 4);
|
|
if (strncmp(fileId, "MORT", 4) != 0) {
|
|
Common::String msg = Common::String::format(_("The '%s' engine data file is corrupt."), MORT_DAT);
|
|
GUIErrorMessage(msg);
|
|
return Common::kReadingFailed;
|
|
}
|
|
|
|
// Check the version
|
|
int majVer = f.readByte();
|
|
int minVer = f.readByte();
|
|
|
|
if (majVer < MORT_DAT_REQUIRED_VERSION) {
|
|
Common::String msg = Common::String::format(
|
|
_("Incorrect version of the '%s' engine data file found. Expected %d.%d but got %d.%d."),
|
|
MORT_DAT, MORT_DAT_REQUIRED_VERSION, 0, majVer, minVer);
|
|
GUIErrorMessage(msg);
|
|
return Common::kReadingFailed;
|
|
}
|
|
|
|
// Loop to load resources from the data file
|
|
while (f.pos() < f.size()) {
|
|
// Get the Id and size of the next resource
|
|
char dataType[4];
|
|
int dataSize;
|
|
f.read(dataType, 4);
|
|
dataSize = f.readUint16LE();
|
|
|
|
if (!strncmp(dataType, "FONT", 4)) {
|
|
// Font resource
|
|
_screenSurface->readFontData(f, dataSize);
|
|
} else if (!strncmp(dataType, "SSTR", 4)) {
|
|
readStaticStrings(f, dataSize, kStaticStrings);
|
|
} else if ((!strncmp(dataType, "GSTR", 4)) && (!_txxFileFl)) {
|
|
readStaticStrings(f, dataSize, kGameStrings);
|
|
} else if (!strncmp(dataType, "VERB", 4)) {
|
|
_menu->readVerbNums(f, dataSize);
|
|
} else {
|
|
// Unknown section
|
|
f.skip(dataSize);
|
|
}
|
|
}
|
|
|
|
// Close the file
|
|
f.close();
|
|
|
|
assert(_engineStrings.size() > 0);
|
|
return Common::kNoError;
|
|
}
|
|
|
|
|
|
/**
|
|
* Read in a static strings block, and if the language matches, load up the static strings
|
|
*/
|
|
void MortevielleEngine::readStaticStrings(Common::File &f, int dataSize, DataType dataType) {
|
|
// Figure out what language Id is needed
|
|
byte desiredLanguageId;
|
|
switch(getLanguage()) {
|
|
case Common::EN_ANY:
|
|
desiredLanguageId = MORTDAT_LANG_ENGLISH;
|
|
break;
|
|
case Common::FR_FRA:
|
|
desiredLanguageId = MORTDAT_LANG_FRENCH;
|
|
break;
|
|
case Common::DE_DEU:
|
|
desiredLanguageId = MORTDAT_LANG_GERMAN;
|
|
break;
|
|
default:
|
|
warning("Language not supported, switching to English");
|
|
desiredLanguageId = MORTDAT_LANG_ENGLISH;
|
|
break;
|
|
}
|
|
|
|
// Read in the language
|
|
byte languageId = f.readByte();
|
|
--dataSize;
|
|
|
|
// If the language isn't correct, then skip the entire block
|
|
if (languageId != desiredLanguageId) {
|
|
f.skip(dataSize);
|
|
return;
|
|
}
|
|
|
|
// Load in each of the strings
|
|
while (dataSize > 0) {
|
|
Common::String s;
|
|
char ch;
|
|
while ((ch = (char)f.readByte()) != '\0')
|
|
s += ch;
|
|
|
|
if (dataType == kStaticStrings)
|
|
_engineStrings.push_back(s);
|
|
else if (dataType == kGameStrings)
|
|
_gameStrings.push_back(s);
|
|
|
|
dataSize -= s.size() + 1;
|
|
}
|
|
assert(dataSize == 0);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
Common::Error MortevielleEngine::run() {
|
|
// Initialize the game
|
|
Common::ErrorCode err = initialize();
|
|
if (err != Common::kNoError)
|
|
return err;
|
|
|
|
// Check for a savegame
|
|
int loadSlot = 0;
|
|
if (ConfMan.hasKey("save_slot")) {
|
|
int gameToLoad = ConfMan.getInt("save_slot");
|
|
if ((gameToLoad >= 1) && (gameToLoad <= 999))
|
|
loadSlot = gameToLoad;
|
|
}
|
|
|
|
if (loadSlot == 0)
|
|
// Show the game introduction
|
|
showIntroduction();
|
|
else {
|
|
_caff = 51;
|
|
_text->taffich();
|
|
}
|
|
|
|
// Either load the initial game state savegame, or the specified savegame number
|
|
adzon();
|
|
resetVariables();
|
|
if (loadSlot != 0)
|
|
_savegameManager->loadSavegame(generateSaveFilename(loadSlot));
|
|
|
|
// Run the main game loop
|
|
mainGame();
|
|
|
|
// Cleanup (allocated in initialize())
|
|
_screenSurface->free();
|
|
free(_soundManager->_cfiphBuffer);
|
|
free(_cfiecBuffer);
|
|
|
|
return Common::kNoError;
|
|
}
|
|
|
|
/**
|
|
* Show the game introduction
|
|
*/
|
|
void MortevielleEngine::showIntroduction() {
|
|
_dialogManager->displayIntroScreen(false);
|
|
_dialogManager->checkForF8(142, false);
|
|
if (shouldQuit())
|
|
return;
|
|
|
|
_dialogManager->displayIntroFrame2();
|
|
_dialogManager->checkForF8(143, true);
|
|
if (shouldQuit())
|
|
return;
|
|
|
|
showTitleScreen();
|
|
music();
|
|
_mixer->stopAll();
|
|
}
|
|
|
|
/**
|
|
* Main game loop. Handles potentially playing the game multiple times, such as if the player
|
|
* loses, and chooses to start playing the game again.
|
|
*/
|
|
void MortevielleEngine::mainGame() {
|
|
if (_reloadCFIEC)
|
|
loadCFIEC();
|
|
|
|
for (_crep = 1; _crep <= _x26KeyCount; ++_crep)
|
|
decodeNumber(&_cfiecBuffer[161 * 16], (_cfiecBufferSize - (161 * 16)) / 64);
|
|
|
|
_menu->initMenu();
|
|
|
|
charToHour();
|
|
initGame();
|
|
clearScreen();
|
|
drawRightFrame();
|
|
_mouse->showMouse();
|
|
|
|
// Loop to play the game
|
|
do {
|
|
playGame();
|
|
if (shouldQuit())
|
|
return;
|
|
} while (!_quitGame);
|
|
}
|
|
|
|
/**
|
|
* This method handles playing a loaded game
|
|
* @remarks Originally called tjouer
|
|
*/
|
|
void MortevielleEngine::playGame() {
|
|
gameLoaded();
|
|
|
|
// Loop handling actions until the game has to be quit, or show the lose or end sequence
|
|
do {
|
|
handleAction();
|
|
if (shouldQuit())
|
|
return;
|
|
} while (!((_quitGame) || (_endGame) || (_loseGame)));
|
|
|
|
if (_endGame)
|
|
endGame();
|
|
else if (_loseGame)
|
|
askRestart();
|
|
}
|
|
|
|
} // End of namespace Mortevielle
|