/* 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 . * */ #include "audio/mixer.h" #include "backends/keymapper/keymap.h" #include "backends/keymapper/keymapper.h" #include "common/config-manager.h" #include "common/debug.h" #include "common/debug-channels.h" #include "common/error.h" #include "common/file.h" #include "common/scummsys.h" #include "common/str.h" #include "common/system.h" #include "common/textconsole.h" #include "common/timer.h" #include "common/translation.h" #include "engines/util.h" #include "gui/message.h" #include "zvision/zvision.h" #include "zvision/core/console.h" #include "zvision/file/file_manager.h" #include "zvision/file/save_manager.h" #include "zvision/file/zfs_archive.h" #include "zvision/graphics/render_manager.h" #include "zvision/graphics/cursors/cursor_manager.h" #include "zvision/scripting/menu.h" #include "zvision/scripting/script_manager.h" #include "zvision/sound/midi.h" #include "zvision/sound/volume_manager.h" #include "zvision/text/string_manager.h" #include "zvision/text/subtitle_manager.h" #include "zvision/text/text.h" #include "zvision/text/truetype_font.h" namespace ZVision { #define ZVISION_SETTINGS_KEYS_COUNT 15 struct ZvisionIniSettings { const char *name; int16 slot; int16 defaultValue; // -1: use the bool value bool defaultBoolValue; bool allowEditing; } settingsKeys[ZVISION_SETTINGS_KEYS_COUNT] = { // Hardcoded settings {"countrycode", StateKey_CountryCode, 0, false, false}, // always 0 = US, subtitles are shown for codes 0 - 4, unused {"lineskipvideo", StateKey_VideoLineSkip, 0, false, false}, // video line skip, 0 = default, 1 = always, 2 = pixel double when possible, unused {"installlevel", StateKey_InstallLevel, 0, false, false}, // 0 = full, checked by universe.scr {"debugcheats", StateKey_DebugCheats, -1, true, false}, // always start with the GOxxxx cheat enabled {"Pentium", StateKey_CPU, 1, true, false}, // !1 = 486, 1 = i586/Pentium {"LowMemory", StateKey_WIN958, 0, false, false}, // 0 = high system RAM, !0 = low system RAM (<8MB) {"DOS", StateKey_Platform, 0, false, false}, // 0 = Windows, !0 = DOS // Editable settings {"qsoundenabled", StateKey_Qsound, -1, true, true}, // 1 = enable generic directional audio and non-linear volume scaling. Genuine Qsound is copyright & unlikely to be implemented. {"keyboardturnspeed", StateKey_KbdRotateSpeed, 5, false, true}, {"panarotatespeed", StateKey_RotateSpeed, 540, false, true}, // checked by universe.scr {"noanimwhileturning", StateKey_NoTurnAnim, -1, false, true}, // toggle playing animations during pana rotation {"highquality", StateKey_HighQuality, -1, true, false}, // high panorama quality; enables bilinear filtering in RenderTable {"venusenabled", StateKey_VenusEnable, -1, true, true}, {"subtitles", StateKey_Subtitles, -1, true, true}, {"mpegmovies", StateKey_MPEGMovies, -1, true, true} // Zork: Grand Inquisitor DVD hi-res MPEG movies (0 = normal, 1 = hires, 2 = disable option) }; const char *mainKeymapId = "zvision"; const char *gameKeymapId = "zvision-game"; const char *cutscenesKeymapId = "zvision-cutscenes"; ZVision::ZVision(OSystem *syst, const ZVisionGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc), _resourcePixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0), /* RGB 555 */ _screenPixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0), /* RGB 565 */ _clock(_system), _scriptManager(nullptr), _renderManager(nullptr), _fileManager(nullptr), _saveManager(nullptr), _stringManager(nullptr), _cursorManager(nullptr), _midiManager(nullptr), _rnd(nullptr), _menu(nullptr), _subtitleManager(nullptr), _volumeManager(nullptr), _textRenderer(nullptr), _doubleFPS(false), _widescreen(false), _audioId(0), _frameRenderDelay(2), _keyboardVelocity(0), _mouseVelocity(0), _videoIsPlaying(false), _renderedFrameCount(0), _fps(0) { debugC(1, kDebugLoop, "ZVision::ZVision"); memset(_cheatBuffer, 0, sizeof(_cheatBuffer)); } ZVision::~ZVision() { debugC(1, kDebugLoop, "ZVision::~ZVision"); // Dispose of resources delete _cursorManager; delete _stringManager; delete _fileManager; delete _saveManager; delete _scriptManager; delete _renderManager; // should be deleted after the script manager delete _subtitleManager; delete _rnd; delete _midiManager; delete _volumeManager; getTimerManager()->removeTimerProc(&fpsTimerCallback); } void ZVision::registerDefaultSettings() { for (int i = 0; i < ZVISION_SETTINGS_KEYS_COUNT; i++) { if (settingsKeys[i].allowEditing) { if (settingsKeys[i].defaultValue >= 0) ConfMan.registerDefault(settingsKeys[i].name, settingsKeys[i].defaultValue); else ConfMan.registerDefault(settingsKeys[i].name, settingsKeys[i].defaultBoolValue); } } } void ZVision::loadSettings() { int16 value = 0; bool boolValue = false; for (int i = 0; i < ZVISION_SETTINGS_KEYS_COUNT; i++) { if (settingsKeys[i].defaultValue >= 0) { value = (settingsKeys[i].allowEditing) ? ConfMan.getInt(settingsKeys[i].name) : settingsKeys[i].defaultValue; } else { boolValue = (settingsKeys[i].allowEditing) ? ConfMan.getBool(settingsKeys[i].name) : settingsKeys[i].defaultBoolValue; value = (boolValue) ? 1 : 0; } _scriptManager->setStateValue(settingsKeys[i].slot, value); } if (getGameId() == GID_NEMESIS) _scriptManager->setStateValue(StateKey_ExecScopeStyle, 1); else _scriptManager->setStateValue(StateKey_ExecScopeStyle, 0); } void ZVision::saveSettings() { for (int i = 0; i < ZVISION_SETTINGS_KEYS_COUNT; i++) { if (settingsKeys[i].allowEditing) { if (settingsKeys[i].defaultValue >= 0) ConfMan.setInt(settingsKeys[i].name, _scriptManager->getStateValue(settingsKeys[i].slot)); else ConfMan.setBool(settingsKeys[i].name, (_scriptManager->getStateValue(settingsKeys[i].slot) == 1)); } } ConfMan.flushToDisk(); } void ZVision::initialize() { // Graphics _widescreen = ConfMan.getBool("widescreen"); _doubleFPS = ConfMan.getBool("doublefps"); // Keymaps Common::Keymapper *keymapper = _system->getEventManager()->getKeymapper(); _gameKeymap = keymapper->getKeymap(gameKeymapId); _gameKeymap->setEnabled(true); _cutscenesKeymap = keymapper->getKeymap(cutscenesKeymapId); _cutscenesKeymap->setEnabled(false); // Register random source _rnd = new Common::RandomSource("zvision"); // Create managers switch (getGameId()) { case GID_NEMESIS: _renderManager = new RenderManager(this, nemesisLayout, _resourcePixelFormat, _doubleFPS, _widescreen); _menu = new MenuNemesis(this, _renderManager->getMenuArea()); _subtitleManager = new SubtitleManager(this, nemesisLayout, _resourcePixelFormat, _doubleFPS); _volumeManager = new VolumeManager(this, kVolumePowerLaw); break; case GID_GRANDINQUISITOR: _renderManager = new RenderManager(this, zgiLayout, _resourcePixelFormat, _doubleFPS, _widescreen); _menu = new MenuZGI(this, _renderManager->getMenuArea()); _subtitleManager = new SubtitleManager(this, zgiLayout, _resourcePixelFormat, _doubleFPS); _volumeManager = new VolumeManager(this, kVolumeLogAmplitude); break; case GID_NONE: default: error("Unknown/unspecified GameId"); break; } _scriptManager = new ScriptManager(this); _fileManager = new FileManager(this); _saveManager = new SaveManager(this); _stringManager = new StringManager(this); _cursorManager = new CursorManager(this, _resourcePixelFormat); _textRenderer = new TextRenderer(this); _midiManager = new MidiManager(); // Initialize the managers _renderManager->initialize(); _cursorManager->initialize(); _scriptManager->initialize(); _stringManager->initialize(getGameId()); registerDefaultSettings(); loadSettings(); #if !defined(USE_MPEG2) || !defined(USE_A52) // libmpeg2 or liba52 not loaded, disable the MPEG2 movies option _scriptManager->setStateValue(StateKey_MPEGMovies, 2); #endif // Create debugger console. It requires GFX to be initialized setDebugger(new Console(this)); // Initialize FPS timer callback getTimerManager()->installTimerProc(&fpsTimerCallback, 1000000, this, "zvisionFPS"); // Ensure a new game is launched with correct panorama quality setting _scriptManager->setStateValue(StateKey_HighQuality, ConfMan.getBool("highquality")); } extern const FontStyle getSystemFont(int fontIndex); Common::Error ZVision::run() { initialize(); // Check if a saved game is to be loaded from the launcher if (ConfMan.hasKey("save_slot")) _saveManager->loadGame(ConfMan.getInt("save_slot")); bool foundAllFonts = true; // Before starting, make absolutely sure that the user has copied the needed fonts for (int i = 0; i < FONT_COUNT; i++) { FontStyle curFont = getSystemFont(i); const char *fontSuffixes[4] = { "", "bd", "i", "bi" }; const char *liberationFontSuffixes[4] = { "-Regular", "-Bold", "-Italic", "-BoldItalic" }; for (int j = 0; j < 4; j++) { Common::String fontName = curFont.fontBase; if (fontName == "censcbk" && j > 0) fontName = "schlbk"; fontName += fontSuffixes[j]; fontName += ".ttf"; if (fontName == "schlbkbd.ttf") fontName = "schlbkb.ttf"; if (fontName == "garabi.ttf") continue; if (fontName == "garai.ttf") fontName = "garait.ttf"; Common::String liberationFontName = curFont.liberationFontBase; liberationFontName += liberationFontSuffixes[j]; liberationFontName += ".ttf"; if (!Common::File::exists(Common::Path(fontName)) && !SearchMan.hasFile(Common::Path(fontName)) && !Common::File::exists(Common::Path(liberationFontName)) && !SearchMan.hasFile(Common::Path(liberationFontName)) && !Common::File::exists("fonts.dat") && !SearchMan.hasFile("fonts.dat")) { foundAllFonts = false; break; } } if (!foundAllFonts) break; } if (!foundAllFonts) { GUI::MessageDialog dialog(_( "Before playing this game, you'll need to copy the required " "fonts into ScummVM's extras directory, or into the game directory. " "On Windows, you'll need the following font files from the Windows " "font directory: Times New Roman, Century Schoolbook, Garamond, " "Courier New and Arial. Alternatively, you can download the " "Liberation Fonts package. You'll need all the fonts from the " "font package you choose, i.e., LiberationMono, LiberationSans " "and LiberationSerif." )); dialog.runModal(); quitGame(); return Common::kUnknownError; } if (getGameId() == GID_NEMESIS && !_midiManager->isAvailable()) { GUI::MessageDialog MIDIdialog(_( "MIDI playback is not available, or else improperly configured. " "Zork Nemesis contains several music puzzles which require " "MIDI audio in order to be solved. These puzzles may alternatively " "be solved using subtitles, if supported. Continue launching game?" ), _("Yes"), _("No") ); if (MIDIdialog.runModal() != GUI::kMessageOK) quitGame(); } // Main loop while (!shouldQuit() && !_breakMainLoop) { debugC(1, kDebugLoop, "\nInitiating new game cycle"); debugC(5, kDebugLoop, "Timers"); // Timers _clock.update(); uint32 deltaTime = _clock.getDeltaTime(); debugC(5, kDebugLoop, "Logic"); // Process game logic & update backbuffers as necessary debugC(5, kDebugLoop, "Cursor"); _cursorManager->setItemID(_scriptManager->getStateValue(StateKey_InventoryItem)); debugC(5, kDebugLoop, "Events"); processEvents(); // NB rotateTo or playVideo event will pause clock & call renderSceneToScreen() directly. debugC(5, kDebugLoop, "Rotation"); _renderManager->updateRotation(); debugC(5, kDebugLoop, "Scripts"); _scriptManager->process(deltaTime); debugC(5, kDebugLoop, "Menu"); _menu->process(deltaTime); debugC(5, kDebugLoop, "Subtitles"); _subtitleManager->process(deltaTime); debugC(5, kDebugLoop, "Render"); // Render the backBuffer to the screen _renderManager->prepareBackground(); if (_renderManager->renderSceneToScreen()) { _renderedFrameCount++; } else { _frameRenderDelay--; } } return Common::kNoError; } void ZVision::pauseEngineIntern(bool pause) { _mixer->pauseAll(pause); if (pause) { _clock.stop(); } else { _clock.start(); } } void ZVision::setRenderDelay(uint delay) { debugC(2, kDebugGraphics, "Setting framerenderdelay to %d", delay); _frameRenderDelay = delay; } bool ZVision::canRender() { return _frameRenderDelay <= 0; } void ZVision::syncSoundSettings() { Engine::syncSoundSettings(); _scriptManager->setStateValue(StateKey_Subtitles, ConfMan.getBool("subtitles") ? 1 : 0); } void ZVision::fpsTimerCallback(void *refCon) { ((ZVision *)refCon)->fpsTimer(); } void ZVision::fpsTimer() { _fps = _renderedFrameCount; _renderedFrameCount = 0; } void ZVision::initializePath(const Common::FSNode &gamePath) { // File Paths const Common::FSNode gameDataDir(gamePath); SearchMan.setIgnoreClashes(true); SearchMan.addDirectory(gamePath, 0, 1, true); // Ensure addons (game patches) take search priority over files listed in .zix files SearchMan.addSubDirectoryMatching(gameDataDir, "addon"); Common::ArchiveMemberList listAddon; SearchMan.listMatchingMembers(listAddon,"*.zfs"); for (auto &member : listAddon) { Common::Path path(member->getPathInArchive()); ZfsArchive *archive = new ZfsArchive(path); SearchMan.add(path.toString(), archive); } switch (getGameId()) { case GID_GRANDINQUISITOR: break; case GID_NEMESIS: SearchMan.addSubDirectoriesMatching(gameDataDir, "znemscr", true); // Add directory that may contain .zix file in some versions of the game break; case GID_NONE: default: break; } SearchMan.addSubDirectoryMatching(gameDataDir, "FONTS"); // Ensure extras take first search priority if (ConfMan.hasKey("extrapath")) { Common::Path gameExtraPath = ConfMan.getPath("extrapath"); const Common::FSNode gameExtraDir(gameExtraPath); SearchMan.addSubDirectoryMatching(gameExtraDir, "auxvid"); SearchMan.addSubDirectoryMatching(gameExtraDir, "auxscr"); } switch (getGameId()) { case GID_GRANDINQUISITOR: if (!_fileManager->loadZix("INQUIS.ZIX", gameDataDir)) error("Unable to load file INQUIS.ZIX"); break; case GID_NEMESIS: if (!_fileManager->loadZix("NEMESIS.ZIX", gameDataDir)) // GOG version or used original game installer if (!_fileManager->loadZix("MEDIUM.ZIX", gameDataDir)) // Manual installation from CD or ZGI DVD according to wiki.scummvm.org error("Unable to load file NEMESIS.ZIX or MEDIUM.ZIX"); break; case GID_NONE: default: error("Unknown/unspecified GameId"); break; } } } // End of namespace ZVision