scummvm/engines/hugo/hugo.cpp
Colin Snover 432fd522d2 ENGINES: Remove default1x scaler flag
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.
2017-10-07 12:30:29 -05:00

754 lines
21 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/system.h"
#include "common/random.h"
#include "common/error.h"
#include "common/events.h"
#include "common/debug-channels.h"
#include "common/config-manager.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "hugo/hugo.h"
#include "hugo/console.h"
#include "hugo/dialogs.h"
#include "hugo/file.h"
#include "hugo/game.h"
#include "hugo/schedule.h"
#include "hugo/display.h"
#include "hugo/mouse.h"
#include "hugo/inventory.h"
#include "hugo/parser.h"
#include "hugo/route.h"
#include "hugo/util.h"
#include "hugo/sound.h"
#include "hugo/intro.h"
#include "hugo/object.h"
#include "hugo/text.h"
#include "engines/util.h"
namespace Hugo {
HugoEngine *HugoEngine::s_Engine = nullptr;
HugoEngine::HugoEngine(OSystem *syst, const HugoGameDescription *gd) : Engine(syst), _gameDescription(gd),
_hero(nullptr), _heroImage(0), _defltTunes(nullptr), _numScreens(0), _tunesNbr(0), _soundSilence(0), _soundTest(0),
_screenStates(nullptr), _numStates(0), _score(0), _maxscore(0), _lastTime(0), _curTime(0), _episode(nullptr)
{
_system = syst;
DebugMan.addDebugChannel(kDebugSchedule, "Schedule", "Script Schedule debug level");
DebugMan.addDebugChannel(kDebugEngine, "Engine", "Engine debug level");
DebugMan.addDebugChannel(kDebugDisplay, "Display", "Display debug level");
DebugMan.addDebugChannel(kDebugMouse, "Mouse", "Mouse debug level");
DebugMan.addDebugChannel(kDebugParser, "Parser", "Parser debug level");
DebugMan.addDebugChannel(kDebugFile, "File", "File IO debug level");
DebugMan.addDebugChannel(kDebugRoute, "Route", "Route debug level");
DebugMan.addDebugChannel(kDebugInventory, "Inventory", "Inventory debug level");
DebugMan.addDebugChannel(kDebugObject, "Object", "Object debug level");
DebugMan.addDebugChannel(kDebugMusic, "Music", "Music debug level");
_console = new HugoConsole(this);
_rnd = 0;
_screen = nullptr;
_mouse = nullptr;
_inventory = nullptr;
_parser = nullptr;
_route = nullptr;
_sound = nullptr;
_intro = nullptr;
_object = nullptr;
_text = nullptr;
_topMenu = nullptr;
_status._storyModeFl = false;
_status._gameOverFl = false;
_status._lookFl = false;
_status._recallFl = false;
_status._newScreenFl = false;
_status._godModeFl = false;
_status._showBoundariesFl = false;
_status._doQuitFl = false;
_status._skipIntroFl = false;
_status._helpFl = false;
_status._tick = 0;
_status._viewState = kViewIntroInit;
_status._song = 0;
_gameType = kGameTypeNone;
_platform = Common::kPlatformUnknown;
_packedFl = false;
_numVariant = 0;
_gameVariant = kGameVariantNone;
_normalTPS = 0;
_screenPtr = nullptr;
_config._musicFl = true;
_config._soundFl = true;
_config._turboFl = false;
_look = 0;
_take = 0;
_drop = 0;
_maze._enabledFl = false;
_maze._size = 0;
_maze._x1 = _maze._y1 = _maze._x2 = _maze._y2 = _maze._x3 = _maze._x4 = 0;
_maze._firstScreenIndex = 0;
_boot._checksum = 0;
_boot._registered = kRegShareware;
_boot._exitLen = 0;
_file = nullptr;
_scheduler = nullptr;
}
HugoEngine::~HugoEngine() {
_file->closeDatabaseFiles();
_intro->freeIntroData();
_inventory->freeInvent();
_mouse->freeHotspots();
_object->freeObjects();
_parser->freeParser();
_scheduler->freeScheduler();
_screen->freeScreen();
_text->freeAllTexts();
free(_defltTunes);
free(_screenStates);
delete _topMenu;
delete _object;
delete _sound;
delete _route;
delete _parser;
delete _inventory;
delete _mouse;
delete _screen;
delete _intro;
delete _scheduler;
delete _file;
delete _text;
DebugMan.clearAllDebugChannels();
delete _console;
delete _rnd;
}
GUI::Debugger *HugoEngine::getDebugger() {
return _console;
}
Status &HugoEngine::getGameStatus() {
return _status;
}
int HugoEngine::getScore() const {
return _score;
}
void HugoEngine::setScore(const int newScore) {
_score = newScore;
}
void HugoEngine::adjustScore(const int adjustment) {
_score += adjustment;
}
int HugoEngine::getMaxScore() const {
return _maxscore;
}
void HugoEngine::setMaxScore(const int newScore) {
_maxscore = newScore;
}
Common::Error HugoEngine::saveGameState(int slot, const Common::String &desc) {
return (_file->saveGame(slot, desc) ? Common::kWritingFailed : Common::kNoError);
}
Common::Error HugoEngine::loadGameState(int slot) {
return (_file->restoreGame(slot) ? Common::kReadingFailed : Common::kNoError);
}
bool HugoEngine::hasFeature(EngineFeature f) const {
return (f == kSupportsRTL) || (f == kSupportsLoadingDuringRuntime) || (f == kSupportsSavingDuringRuntime);
}
const char *HugoEngine::getCopyrightString() const {
return "Copyright 1989-1997 David P Gray, All Rights Reserved.";
}
GameType HugoEngine::getGameType() const {
return _gameType;
}
Common::Platform HugoEngine::getPlatform() const {
return _platform;
}
bool HugoEngine::isPacked() const {
return _packedFl;
}
/**
* Print options for user when dead
*/
void HugoEngine::gameOverMsg() {
Utils::notifyBox(_text->getTextUtil(kGameOver));
}
Common::Error HugoEngine::run() {
s_Engine = this;
initGraphics(320, 200);
_mouse = new MouseHandler(this);
_inventory = new InventoryHandler(this);
_route = new Route(this);
_sound = new SoundHandler(this);
// Setup mixer
syncSoundSettings();
_text = new TextHandler(this);
_topMenu = new TopMenu(this);
switch (_gameVariant) {
case kGameVariantH1Win: // H1 Win
_file = new FileManager_v1w(this);
_scheduler = new Scheduler_v1w(this);
_intro = new intro_v1w(this);
_screen = new Screen_v1w(this);
_parser = new Parser_v1w(this);
_object = new ObjectHandler_v1w(this);
_normalTPS = 9;
break;
case kGameVariantH2Win:
_file = new FileManager_v2w(this);
_scheduler = new Scheduler_v1w(this);
_intro = new intro_v2w(this);
_screen = new Screen_v1w(this);
_parser = new Parser_v1w(this);
_object = new ObjectHandler_v1w(this);
_normalTPS = 9;
break;
case kGameVariantH3Win:
_file = new FileManager_v2w(this);
_scheduler = new Scheduler_v1w(this);
_intro = new intro_v3w(this);
_screen = new Screen_v1w(this);
_parser = new Parser_v1w(this);
_object = new ObjectHandler_v1w(this);
_normalTPS = 9;
break;
case kGameVariantH1Dos: // H1 DOS
_file = new FileManager_v1d(this);
_scheduler = new Scheduler_v1d(this);
_intro = new intro_v1d(this);
_screen = new Screen_v1d(this);
_parser = new Parser_v1d(this);
_object = new ObjectHandler_v1d(this);
_normalTPS = 8;
break;
case kGameVariantH2Dos:
_file = new FileManager_v2d(this);
_scheduler = new Scheduler_v2d(this);
_intro = new intro_v2d(this);
_screen = new Screen_v1d(this);
_parser = new Parser_v2d(this);
_object = new ObjectHandler_v2d(this);
_normalTPS = 8;
break;
case kGameVariantH3Dos:
_file = new FileManager_v3d(this);
_scheduler = new Scheduler_v3d(this);
_intro = new intro_v3d(this);
_screen = new Screen_v1d(this);
_parser = new Parser_v3d(this);
_object = new ObjectHandler_v3d(this);
_normalTPS = 9;
break;
}
if (!loadHugoDat())
return Common::kUnknownError;
// Use Windows-looking mouse cursor
_screen->setCursorPal();
_screen->resetInventoryObjId();
_scheduler->initCypher();
initStatus(); // Initialize game status
initConfig(); // Initialize user's config
if (!_status._doQuitFl) {
initialize();
resetConfig(); // Reset user's config
initMachine();
// Start the state machine
_status._viewState = kViewIntroInit;
int16 loadSlot = Common::ConfigManager::instance().getInt("save_slot");
if (loadSlot >= 0) {
_status._skipIntroFl = true;
_file->restoreGame(loadSlot);
} else {
_file->saveGame(0, "New Game");
}
}
while (!_status._doQuitFl) {
_screen->drawBoundaries();
g_system->updateScreen();
runMachine();
// Handle input
Common::Event event;
while (_eventMan->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_KEYDOWN:
_parser->keyHandler(event);
break;
case Common::EVENT_MOUSEMOVE:
_mouse->setMouseX(event.mouse.x);
_mouse->setMouseY(event.mouse.y);
break;
case Common::EVENT_LBUTTONUP:
_mouse->setLeftButton();
break;
case Common::EVENT_RBUTTONUP:
_mouse->setRightButton();
break;
case Common::EVENT_QUIT:
_status._doQuitFl = true;
break;
default:
break;
}
}
if (_status._helpFl) {
_status._helpFl = false;
_file->instructions();
}
_mouse->mouseHandler(); // Mouse activity - adds to display list
_screen->displayList(kDisplayDisplay); // Blit the display list to screen
_status._doQuitFl |= shouldQuit(); // update game quit flag
}
return Common::kNoError;
}
void HugoEngine::initMachine() {
if (_gameVariant == kGameVariantH1Dos)
readScreenFiles(0);
else
_file->readBackground(_numScreens - 1); // Splash screen
_object->readObjectImages(); // Read all object images
if (_platform == Common::kPlatformWindows)
_file->readUIFImages(); // Read all uif images (only in Win versions)
_sound->initPcspkrPlayer();
}
/**
* Hugo game state machine - called during onIdle
*/
void HugoEngine::runMachine() {
Status &gameStatus = getGameStatus();
// Don't process if gameover
if (gameStatus._gameOverFl)
return;
_curTime = g_system->getMillis();
// Process machine once every tick
while (_curTime - _lastTime < (uint32)(1000 / getTPS())) {
g_system->delayMillis(5);
_curTime = g_system->getMillis();
}
_lastTime = _curTime;
switch (gameStatus._viewState) {
case kViewIdle: // Not processing state machine
_screen->hideCursor();
_intro->preNewGame(); // Any processing before New Game selected
break;
case kViewIntroInit: // Initialization before intro begins
_intro->introInit();
gameStatus._viewState = kViewIntro;
break;
case kViewIntro: // Do any game-dependant preamble
if (_intro->introPlay()) { // Process intro screen
_scheduler->newScreen(0); // Initialize first screen
gameStatus._viewState = kViewPlay;
}
break;
case kViewPlay: // Playing game
_screen->showCursor();
_parser->charHandler(); // Process user cmd input
_object->moveObjects(); // Process object movement
_scheduler->runScheduler(); // Process any actions
_screen->displayList(kDisplayRestore); // Restore previous background
_object->updateImages(); // Draw into _frontBuffer, compile display list
_screen->drawStatusText();
_screen->displayList(kDisplayDisplay); // Blit the display list to screen
_sound->checkMusic();
break;
case kViewInvent: // Accessing inventory
_inventory->runInventory(); // Process Inventory state machine
break;
case kViewExit: // Game over or user exited
gameStatus._viewState = kViewIdle;
_status._doQuitFl = true;
break;
}
}
/**
* Loads Hugo.dat file, which contains all the hardcoded data in the original executables
*/
bool HugoEngine::loadHugoDat() {
Common::File in;
Common::String filename = "hugo.dat";
in.open(filename.c_str());
if (!in.isOpen()) {
Common::String errorMessage = Common::String::format(_("Unable to locate the '%s' engine data file."), filename.c_str());
GUIErrorMessage(errorMessage);
warning("%s", errorMessage.c_str());
return false;
}
// Read header
char buf[4];
in.read(buf, 4);
if (memcmp(buf, "HUGO", 4)) {
Common::String errorMessage = Common::String::format(_("The '%s' engine data file is corrupt."), filename.c_str());
GUIErrorMessage(errorMessage);
return false;
}
int majVer = in.readByte();
int minVer = in.readByte();
if ((majVer != HUGO_DAT_VER_MAJ) || (minVer != HUGO_DAT_VER_MIN)) {
Common::String errorMessage = Common::String::format(
_("Incorrect version of the '%s' engine data file found. Expected %d.%d but got %d.%d."),
filename.c_str(),HUGO_DAT_VER_MAJ, HUGO_DAT_VER_MIN, majVer, minVer);
GUIErrorMessage(errorMessage);
return false;
}
_numVariant = in.readUint16BE();
_screen->loadPalette(in);
_screen->loadFontArr(in);
_text->loadAllTexts(in);
_intro->loadIntroData(in);
_parser->loadArrayReqs(in);
_parser->loadCatchallList(in);
_parser->loadBackgroundObjects(in);
_parser->loadCmdList(in);
_mouse->loadHotspots(in);
_inventory->loadInvent(in);
_object->loadObjectUses(in);
_object->loadObjectArr(in);
_object->loadNumObj(in);
_scheduler->loadPoints(in);
_scheduler->loadScreenAct(in);
_scheduler->loadActListArr(in);
_scheduler->loadAlNewscrIndex(in);
_hero = &_object->_objects[kHeroIndex]; // This always points to hero
_screenPtr = &(_object->_objects[kHeroIndex]._screenIndex); // Current screen is hero's
_heroImage = kHeroIndex; // Current in use hero image
for (int varnt = 0; varnt < _numVariant; varnt++) {
if (varnt == _gameVariant) {
_tunesNbr = in.readSByte();
_soundSilence = in.readSByte();
_soundTest = in.readSByte();
} else {
in.readSByte();
in.readSByte();
in.readSByte();
}
}
int numElem;
//Read _defltTunes
for (int varnt = 0; varnt < _numVariant; varnt++) {
numElem = in.readUint16BE();
if (varnt == _gameVariant) {
_defltTunes = (int16 *)malloc(sizeof(int16) * numElem);
for (int i = 0; i < numElem; i++)
_defltTunes[i] = in.readSint16BE();
} else {
for (int i = 0; i < numElem; i++)
in.readSint16BE();
}
}
//Read _screenStates size
for (int varnt = 0; varnt < _numVariant; varnt++) {
numElem = in.readUint16BE();
if (varnt == _gameVariant) {
_numStates = numElem;
_screenStates = (byte *)malloc(sizeof(byte) * numElem);
memset(_screenStates, 0, sizeof(byte) * numElem);
}
}
//Read look, take and drop special verbs indexes
for (int varnt = 0; varnt < _numVariant; varnt++) {
if (varnt == _gameVariant) {
_look = in.readUint16BE();
_take = in.readUint16BE();
_drop = in.readUint16BE();
} else {
in.readUint16BE();
in.readUint16BE();
in.readUint16BE();
}
}
_sound->loadIntroSong(in);
_topMenu->loadBmpArr(in);
return true;
}
uint16 **HugoEngine::loadLongArray(Common::SeekableReadStream &in) {
uint16 **resArray = 0;
for (int varnt = 0; varnt < _numVariant; varnt++) {
uint16 numRows = in.readUint16BE();
if (varnt == _gameVariant) {
resArray = (uint16 **)malloc(sizeof(uint16 *) * (numRows + 1));
resArray[numRows] = 0;
}
for (int i = 0; i < numRows; i++) {
uint16 numElems = in.readUint16BE();
if (varnt == _gameVariant) {
uint16 *resRow = (uint16 *)malloc(sizeof(uint16) * numElems);
for (int j = 0; j < numElems; j++)
resRow[j] = in.readUint16BE();
resArray[i] = resRow;
} else {
in.skip(numElems * sizeof(uint16));
}
}
}
return resArray;
}
/**
* Sets the playlist to be the default tune selection
*/
void HugoEngine::initPlaylist(bool playlist[kMaxTunes]) {
debugC(1, kDebugEngine, "initPlaylist");
for (int16 i = 0; i < kMaxTunes; i++)
playlist[i] = false;
for (int16 i = 0; _defltTunes[i] != -1; i++)
playlist[_defltTunes[i]] = true;
}
/**
* Initialize the dynamic game status
*/
void HugoEngine::initStatus() {
debugC(1, kDebugEngine, "initStatus");
_status._storyModeFl = false; // Not in story mode
_status._gameOverFl = false; // Hero not knobbled yet
_status._lookFl = false; // Toolbar "look" button
_status._recallFl = false; // Toolbar "recall" button
_status._newScreenFl = false; // Screen not just loaded
_status._godModeFl = false; // No special cheats allowed
_status._showBoundariesFl = false; // Boundaries hidden by default
_status._doQuitFl = false;
_status._skipIntroFl = false;
_status._helpFl = false;
// Initialize every start of new game
_status._tick = 0; // Tick count
_status._viewState = kViewIdle; // View state
}
/**
* Initialize default config values. Must be done before Initialize().
*/
void HugoEngine::initConfig() {
debugC(1, kDebugEngine, "initConfig()");
_config._musicFl = true; // Music state initially on
_config._soundFl = true; // Sound state initially on
_config._turboFl = false; // Turbo state initially off
initPlaylist(_config._playlist); // Initialize default tune playlist
_file->readBootFile(); // Read startup structure
}
/**
* Reset config parts. Currently only reset music played based on playlist
*/
void HugoEngine::resetConfig() {
debugC(1, kDebugEngine, "resetConfig()");
// Find first tune and play it
for (int16 i = 0; i < kMaxTunes; i++) {
if (_config._playlist[i]) {
_sound->playMusic(i);
break;
}
}
}
void HugoEngine::initialize() {
debugC(1, kDebugEngine, "initialize");
_maze._enabledFl = false;
_line[0] = '\0';
_sound->initSound();
_scheduler->initEventQueue(); // Init scheduler stuff
_screen->initDisplay(); // Create Dibs and palette
_file->openDatabaseFiles(); // Open database files
calcMaxScore(); // Initialize maxscore
_rnd = new Common::RandomSource("hugo");
_rnd->setSeed(42); // Kick random number generator
switch (_gameVariant) {
case kGameVariantH1Dos:
_episode = "\"Hugo's House of Horrors\"";
_picDir = "";
break;
case kGameVariantH2Dos:
_episode = "\"Hugo II: Whodunit?\"";
_picDir = "";
break;
case kGameVariantH3Dos:
_episode = "\"Hugo III: Jungle of Doom\"";
_picDir = "pictures/";
break;
case kGameVariantH1Win:
_episode = "\"Hugo's Horrific Adventure\"";
_picDir = "hugo1/";
break;
case kGameVariantH2Win:
_episode = "\"Hugo's Mystery Adventure\"";
_picDir = "hugo2/";
break;
case kGameVariantH3Win:
_episode = "\"Hugo's Amazon Adventure\"";
_picDir = "hugo3/";
break;
default:
error("Unknown game");
}
}
/**
* Read scenery, overlay files for given screen number
*/
void HugoEngine::readScreenFiles(const int screenNum) {
debugC(1, kDebugEngine, "readScreenFiles(%d)", screenNum);
_file->readBackground(screenNum); // Scenery file
memcpy(_screen->getBackBuffer(), _screen->getFrontBuffer(), sizeof(_screen->getFrontBuffer())); // Make a copy
// Workaround for graphic glitches in DOS versions. Cleaning the overlays fix the problem
memset(_object->_objBound, '\0', sizeof(Overlay));
memset(_object->_boundary, '\0', sizeof(Overlay));
memset(_object->_overlay, '\0', sizeof(Overlay));
memset(_object->_ovlBase, '\0', sizeof(Overlay));
_file->readOverlay(screenNum, _object->_boundary, kOvlBoundary); // Boundary file
_file->readOverlay(screenNum, _object->_overlay, kOvlOverlay); // Overlay file
_file->readOverlay(screenNum, _object->_ovlBase, kOvlBase); // Overlay base file
// Suppress a boundary used in H3 DOS in 'Crash' screen, which blocks
// pathfinding and is useless.
if ((screenNum == 0) && (_gameVariant == kGameVariantH3Dos))
_object->clearScreenBoundary(50, 311, 152);
}
/**
* Set the new screen number into the hero object and any carried objects
*/
void HugoEngine::setNewScreen(const int screenNum) {
debugC(1, kDebugEngine, "setNewScreen(%d)", screenNum);
*_screenPtr = screenNum; // HERO object
_object->setCarriedScreen(screenNum); // Carried objects
}
/**
* Add up all the object values and all the bonus points
*/
void HugoEngine::calcMaxScore() {
debugC(1, kDebugEngine, "calcMaxScore");
_maxscore = _object->calcMaxScore() + _scheduler->calcMaxPoints();
}
/**
* Exit game, advertise trilogy, show copyright
*/
void HugoEngine::endGame() {
debugC(1, kDebugEngine, "endGame");
if (_boot._registered != kRegRegistered)
Utils::notifyBox(_text->getTextEngine(kEsAdvertise));
Utils::notifyBox(Common::String::format("%s\n%s", _episode, getCopyrightString()));
_status._viewState = kViewExit;
}
bool HugoEngine::canLoadGameStateCurrently() {
return true;
}
bool HugoEngine::canSaveGameStateCurrently() {
return (_status._viewState == kViewPlay);
}
int8 HugoEngine::getTPS() const {
return ((_config._turboFl) ? kTurboTps : _normalTPS);
}
void HugoEngine::syncSoundSettings() {
Engine::syncSoundSettings();
_sound->syncVolume();
}
Common::String HugoEngine::getSavegameFilename(int slot) {
return _targetName + Common::String::format("-%02d.SAV", slot);
}
} // End of namespace Hugo