aboutsummaryrefslogtreecommitdiff
path: root/engines/mads/game.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/mads/game.cpp')
-rw-r--r--engines/mads/game.cpp600
1 files changed, 600 insertions, 0 deletions
diff --git a/engines/mads/game.cpp b/engines/mads/game.cpp
new file mode 100644
index 0000000000..91f6cd5630
--- /dev/null
+++ b/engines/mads/game.cpp
@@ -0,0 +1,600 @@
+/* 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/config-manager.h"
+#include "common/memstream.h"
+#include "common/serializer.h"
+#include "graphics/palette.h"
+#include "graphics/scaler.h"
+#include "graphics/thumbnail.h"
+#include "mads/mads.h"
+#include "mads/compression.h"
+#include "mads/game.h"
+#include "mads/game_data.h"
+#include "mads/events.h"
+#include "mads/screen.h"
+#include "mads/msurface.h"
+#include "mads/resources.h"
+#include "mads/dragonsphere/game_dragonsphere.h"
+#include "mads/nebular/game_nebular.h"
+#include "mads/phantom/game_phantom.h"
+
+namespace MADS {
+
+Game *Game::init(MADSEngine *vm) {
+ switch (vm->getGameID()) {
+ case GType_RexNebular:
+ return new Nebular::GameNebular(vm);
+ case GType_Dragonsphere:
+ return new Dragonsphere::GameDragonsphere(vm);
+ case GType_Phantom:
+ return new Phantom::GamePhantom(vm);
+ default:
+ error("Game: Unknown game");
+ }
+
+ return nullptr;
+}
+
+Game::Game(MADSEngine *vm)
+ : _vm(vm), _surface(nullptr), _objects(vm), _scene(vm),
+ _screenObjects(vm), _player(vm) {
+ _sectionNumber = 1;
+ _priorSectionNumber = 0;
+ _loadGameSlot = -1;
+ _lastSave = -1;
+ _saveFile = nullptr;
+ _saveThumb = nullptr;
+ _statusFlag = 0;
+ _sectionHandler = nullptr;
+ _sectionNumber = 1;
+ _priorSectionNumber = 0;
+ _currentSectionNumber = -1;
+ _kernelMode = KERNEL_GAME_LOAD;
+ _quoteEmergency = false;
+ _vocabEmergency = false;
+ _aaName = "*I0.AA";
+ _priorFrameTimer = 0;
+ _anyEmergency = false;
+ _triggerMode = SEQUENCE_TRIGGER_PARSER;
+ _triggerSetupMode = SEQUENCE_TRIGGER_DAEMON;
+ _trigger = 0;
+ _winStatus = 0;
+ _widepipeCtr = 0;
+ _fx = kTransitionNone;
+
+ // Load the inventory object list
+ _objects.load();
+ if (_objects._inventoryList.size() > 0)
+ // At least one item in default inventory, so select first item for display
+ _scene._userInterface._selectedInvIndex = 0;
+
+ // Load the quotes
+ loadQuotes();
+}
+
+Game::~Game() {
+ if (_saveThumb) {
+ _saveThumb->free();
+ delete _saveThumb;
+ }
+
+ delete _saveFile;
+ _surface->free();
+ delete _surface;
+ delete _sectionHandler;
+}
+
+void Game::run() {
+ // If requested, load a savegame instead of showing the intro
+ if (ConfMan.hasKey("save_slot")) {
+ int saveSlot = ConfMan.getInt("save_slot");
+ if (saveSlot >= 0 && saveSlot <= 999)
+ _loadGameSlot = saveSlot;
+ }
+
+ _statusFlag = true;
+
+ while (!_vm->shouldQuit()) {
+ if (_loadGameSlot == -1) {
+ startGame();
+ }
+
+ // Get the initial starting time for the first scene
+ _scene._frameStartTime = _vm->_events->getFrameCounter();
+
+ if (!_vm->shouldQuit())
+ gameLoop();
+ }
+}
+
+void Game::splitQuote(const Common::String &source, Common::String &line1, Common::String &line2) {
+ // Make the first line up the end of the word at the half-way point
+ const char *strP = source.c_str() + source.size() / 2;
+ while (*strP != ' ') ++strP;
+
+ line1 = Common::String(source.c_str(), strP);
+
+ // The rest of the string goes in the second line
+ while (*strP == ' ') ++strP;
+ line2 = Common::String(strP);
+}
+
+void Game::gameLoop() {
+ while (!_vm->shouldQuit() && _statusFlag && !_winStatus) {
+ if (_loadGameSlot != -1) {
+ loadGame(_loadGameSlot);
+ _loadGameSlot = -1;
+ }
+
+ setSectionHandler();
+ _sectionHandler->preLoadSection();
+ initSection(_sectionNumber);
+ _vm->_sound->init(_sectionNumber);
+ _sectionHandler->postLoadSection();
+
+ _scene._spriteSlots.reset();
+
+ if (_sectionNumber == _currentSectionNumber)
+ sectionLoop();
+
+ _player.releasePlayerSprites();
+ assert(_scene._sprites.size() == 0);
+
+ _vm->_palette->unlock();
+ _vm->_events->waitCursor();
+ _vm->_events->freeCursors();
+ _vm->_sound->closeDriver();
+ }
+}
+
+void Game::sectionLoop() {
+ while (!_vm->shouldQuit() && _statusFlag && !_winStatus &&
+ (_sectionNumber == _currentSectionNumber)) {
+ _kernelMode = KERNEL_ROOM_PRELOAD;
+ _player._spritesChanged = true;
+ _quoteEmergency = false;
+ _vocabEmergency = false;
+ _vm->_events->waitCursor();
+
+ _scene.clearVocab();
+ _scene._dynamicHotspots.clear();
+ _scene.loadSceneLogic();
+
+ _player._walkAnywhere = false;
+ _player._stepEnabled = true;
+ _player._visible = true;
+ _vm->_dialogs->_defaultPosition = Common::Point(-1, -1);
+ _visitedScenes.add(_scene._nextSceneId);
+
+ // Reset the user interface
+ _screenObjects._forceRescan = true;
+ _screenObjects._inputMode = kInputBuildingSentences;
+ _scene._userInterface._scrollbarActive = SCROLLBAR_NONE;
+
+ _player._loadsFirst = true;
+
+ _scene._sceneLogic->setup();
+ if (_player._spritesChanged || _player._loadsFirst) {
+ if (_player._spritesLoaded)
+ _player.releasePlayerSprites();
+ _vm->_palette->resetGamePalette(18, 10);
+ _scene._spriteSlots.reset();
+ } else {
+ _vm->_palette->initPalette();
+ }
+
+ // Set up scene palette usage
+ _scene._scenePaletteUsage.clear();
+ _scene._scenePaletteUsage.push_back(PaletteUsage::UsageEntry(0xF0));
+ _scene._scenePaletteUsage.push_back(PaletteUsage::UsageEntry(0xF1));
+ _scene._scenePaletteUsage.push_back(PaletteUsage::UsageEntry(0xF2));
+ _vm->_palette->_paletteUsage.load(&_scene._scenePaletteUsage);
+
+ if (!_player._spritesLoaded && _player._loadsFirst) {
+ if (_player.loadSprites(""))
+ _vm->quitGame();
+ _player._loadedFirst = true;
+ }
+
+ _scene.loadScene(_scene._nextSceneId, _aaName, 0);
+ _vm->_sound->pauseNewCommands();
+
+ if (!_player._spritesLoaded) {
+ if (_player.loadSprites(""))
+ _vm->quitGame();
+ _player._loadedFirst = false;
+ }
+
+ _vm->_events->initVars();
+ _scene._userInterface._highlightedCommandIndex = -1;
+ _scene._userInterface._highlightedInvIndex = -1;
+ _scene._userInterface._highlightedItemVocabIndex = -1;
+
+ _scene._action.clear();
+ _player.setFinalFacing();
+ _player._facing = _player._turnToFacing;
+ _player.cancelCommand();
+ _kernelMode = KERNEL_ROOM_INIT;
+
+ switch (_vm->_screenFade) {
+ case SCREEN_FADE_SMOOTH:
+ _fx = kTransitionFadeOutIn;
+ break;
+ case SCREEN_FADE_FAST:
+ _fx = kNullPaletteCopy;
+ break;
+ default:
+ _fx = kTransitionNone;
+ break;
+ }
+
+ _trigger = 0;
+ _priorFrameTimer = _scene._frameStartTime;
+
+ // If in the middle of restoring a game, handle the rest of the loading
+ if (_saveFile != nullptr) {
+ Common::Serializer s(_saveFile, nullptr);
+ synchronize(s, false);
+ delete _saveFile;
+ _saveFile = nullptr;
+ }
+
+ // Call the scene logic for entering the given scene
+ _triggerSetupMode = SEQUENCE_TRIGGER_DAEMON;
+ _scene._sceneLogic->enter();
+
+ // Set player data
+ _player._targetPos = _player._playerPos;
+ _player._turnToFacing = _player._facing;
+ _player._targetFacing = _player._facing;
+ _player.selectSeries();
+ _player.updateFrame();
+
+ _player._beenVisible = _player._visible;
+ _player._special = _scene.getDepthHighBits(_player._playerPos);
+ _player._priorTimer = _scene._frameStartTime - _player._ticksAmount;
+ _player.idle();
+
+ if (_scene._userInterface._selectedInvIndex >= 0) {
+ _scene._userInterface.loadInventoryAnim(
+ _objects._inventoryList[_scene._userInterface._selectedInvIndex]);
+ } else {
+ _scene._userInterface.noInventoryAnim();
+ }
+
+ _kernelMode = KERNEL_ACTIVE_CODE;
+ _scene._roomChanged = false;
+
+ if ((_quoteEmergency || _vocabEmergency) && !_anyEmergency) {
+ _scene._currentSceneId = _scene._priorSceneId;
+ _anyEmergency = true;
+ } else {
+ _anyEmergency = false;
+ _scene.loop();
+ }
+
+ _vm->_events->waitCursor();
+ _kernelMode = KERNEL_ROOM_PRELOAD;
+
+ delete _scene._activeAnimation;
+ _scene._activeAnimation = nullptr;
+
+ _scene._reloadSceneFlag = false;
+
+ _scene._userInterface.noInventoryAnim();
+ _scene.removeSprites();
+
+ if (!_player._loadedFirst) {
+ _player._spritesLoaded = false;
+ _player._spritesChanged = true;
+ }
+
+ // Clear the scene
+ _scene.freeCurrentScene();
+ _sectionNumber = _scene._nextSceneId / 100;
+
+ // Check whether to show a dialog
+ checkShowDialog();
+ }
+}
+
+void Game::initSection(int sectionNumber) {
+ _priorSectionNumber = _currentSectionNumber;
+ _currentSectionNumber = sectionNumber;
+
+ _vm->_palette->resetGamePalette(18, 10);
+ _vm->_palette->setLowRange();
+
+ if (_scene._mode == SCREENMODE_VGA)
+ _vm->_palette->setPalette(_vm->_palette->_mainPalette, 0, 4);
+
+ _vm->_events->loadCursors("*CURSOR.SS");
+
+ assert(_vm->_events->_cursorSprites);
+ _vm->_events->setCursor2((_vm->_events->_cursorSprites->getCount() <= 1) ?
+ CURSOR_ARROW : CURSOR_WAIT);
+}
+
+void Game::loadQuotes() {
+ File f("*QUOTES.DAT");
+
+ Common::String msg;
+ while (true) {
+ uint8 b = f.readByte();
+
+ msg += b;
+ if (f.eos() || b == '\0') {
+ // end of string, add it to the strings list
+ _quotes.push_back(msg);
+ msg = "";
+ }
+
+ if (f.eos()) break;
+ }
+
+ f.close();
+}
+
+Common::StringArray Game::getMessage(uint32 id) {
+ File f("*MESSAGES.DAT");
+ int count = f.readUint16LE();
+
+ for (int idx = 0; idx < count; ++idx) {
+ uint32 itemId = f.readUint32LE();
+ uint32 offset = f.readUint32LE();
+ uint16 size = f.readUint16LE();
+
+ if (itemId == id) {
+ // Get the source buffer size
+ uint16 sizeIn;
+ if (idx == (count - 1)) {
+ sizeIn = f.size() - offset;
+ } else {
+ f.skip(4);
+ uint32 nextOffset = f.readUint32LE();
+ sizeIn = nextOffset - offset;
+ }
+
+ // Get the compressed data
+ f.seek(offset);
+ byte *bufferIn = new byte[sizeIn];
+ f.read(bufferIn, sizeIn);
+
+ // Decompress it
+ char *bufferOut = new char[size];
+ FabDecompressor fab;
+ fab.decompress(bufferIn, sizeIn, (byte *)bufferOut, size);
+
+ // Form the output string list
+ Common::StringArray result;
+ const char *p = bufferOut;
+ while (p < (bufferOut + size)) {
+ result.push_back(p);
+ p += strlen(p) + 1;
+ }
+
+ delete[] bufferIn;
+ delete[] bufferOut;
+ return result;
+ }
+ }
+
+ error("Invalid message Id specified");
+}
+
+static const char *const DEBUG_STRING = "WIDEPIPE";
+
+void Game::handleKeypress(const Common::KeyState &kbd) {
+ if (kbd.flags & Common::KBD_CTRL) {
+ if (_widepipeCtr == 8) {
+ // Implement original game cheating keys here someday
+ } else {
+ if (kbd.keycode == (Common::KEYCODE_a +
+ (DEBUG_STRING[_widepipeCtr] - 'a'))) {
+ if (++_widepipeCtr == 8) {
+ MessageDialog *dlg = new MessageDialog(_vm, 2,
+ "CHEATING ENABLED", "(for your convenience).");
+ dlg->show();
+ delete dlg;
+ }
+ }
+ }
+ }
+
+ Scene &scene = _vm->_game->_scene;
+ switch (kbd.keycode) {
+ case Common::KEYCODE_F1:
+ _vm->_dialogs->_pendingDialog = DIALOG_GAME_MENU;
+ break;
+ case Common::KEYCODE_F5:
+ _vm->_dialogs->_pendingDialog = DIALOG_SAVE;
+ break;
+ case Common::KEYCODE_F7:
+ _vm->_dialogs->_pendingDialog = DIALOG_RESTORE;
+ break;
+ case Common::KEYCODE_PAGEUP:
+ scene._userInterface._scrollbarStrokeType = SCROLLBAR_UP;
+ scene._userInterface.changeScrollBar();
+ break;
+ case Common::KEYCODE_PAGEDOWN:
+ scene._userInterface._scrollbarStrokeType = SCROLLBAR_DOWN;
+ scene._userInterface.changeScrollBar();
+ break;
+
+
+ default:
+ break;
+ }
+}
+
+void Game::synchronize(Common::Serializer &s, bool phase1) {
+ if (phase1) {
+ s.syncAsSint16LE(_fx);
+ s.syncAsSint16LE(_trigger);
+ s.syncAsUint16LE(_triggerSetupMode);
+ s.syncAsUint16LE(_triggerMode);
+ s.syncString(_aaName);
+ s.syncAsSint16LE(_lastSave);
+
+ _scene.synchronize(s);
+ _objects.synchronize(s);
+ _visitedScenes.synchronize(s, _scene._nextSceneId);
+ _player.synchronize(s);
+ _screenObjects.synchronize(s);
+ } else {
+ // Load scene specific data for the loaded scene
+ _scene._sceneLogic->synchronize(s);
+ }
+}
+
+void Game::loadGame(int slotNumber) {
+ _saveFile = g_system->getSavefileManager()->openForLoading(
+ _vm->generateSaveName(slotNumber));
+
+ Common::Serializer s(_saveFile, nullptr);
+
+ // Load the savaegame header
+ MADSSavegameHeader header;
+ if (!readSavegameHeader(_saveFile, header))
+ error("Invalid savegame");
+
+ if (header._thumbnail) {
+ header._thumbnail->free();
+ delete header._thumbnail;
+ }
+
+ // Load most of the savegame data with the exception of scene specific info
+ synchronize(s, true);
+
+ // Set up section/scene and other initial states for post-load
+ _currentSectionNumber = -2;
+ _scene._currentSceneId = -2;
+ _sectionNumber = _scene._nextSceneId / 100;
+ _scene._frameStartTime = _vm->_events->getFrameCounter();
+ _vm->_screen._shakeCountdown = -1;
+
+ // Default the selected inventory item to the first one, if the player has any
+ _scene._userInterface._selectedInvIndex = _objects._inventoryList.size() > 0 ? 0 : -1;
+
+ // Set player sprites sets flags
+ _player._spritesLoaded = false;
+ _player._spritesChanged = true;
+}
+
+void Game::saveGame(int slotNumber, const Common::String &saveName) {
+ Common::OutSaveFile *out = g_system->getSavefileManager()->openForSaving(
+ _vm->generateSaveName(slotNumber));
+
+ MADSSavegameHeader header;
+ header._saveName = saveName;
+ writeSavegameHeader(out, header);
+
+ Common::Serializer s(nullptr, out);
+ synchronize(s, true);
+ synchronize(s, false);
+
+ out->finalize();
+ delete out;
+}
+
+const char *const SAVEGAME_STR = "MADS";
+#define SAVEGAME_STR_SIZE 4
+
+bool Game::readSavegameHeader(Common::InSaveFile *in, MADSSavegameHeader &header) {
+ char saveIdentBuffer[SAVEGAME_STR_SIZE + 1];
+ header._thumbnail = nullptr;
+
+ // Validate the header Id
+ in->read(saveIdentBuffer, SAVEGAME_STR_SIZE + 1);
+ if (strncmp(saveIdentBuffer, SAVEGAME_STR, SAVEGAME_STR_SIZE))
+ return false;
+
+ header._version = in->readByte();
+ if (header._version > MADS_SAVEGAME_VERSION)
+ return false;
+
+ // Read in the string
+ header._saveName.clear();
+ char ch;
+ while ((ch = (char)in->readByte()) != '\0') header._saveName += ch;
+
+ // Get the thumbnail
+ header._thumbnail = Graphics::loadThumbnail(*in);
+ if (!header._thumbnail)
+ return false;
+
+ // Read in save date/time
+ header._year = in->readSint16LE();
+ header._month = in->readSint16LE();
+ header._day = in->readSint16LE();
+ header._hour = in->readSint16LE();
+ header._minute = in->readSint16LE();
+ header._totalFrames = in->readUint32LE();
+
+ return true;
+}
+
+void Game::writeSavegameHeader(Common::OutSaveFile *out, MADSSavegameHeader &header) {
+ // Write out a savegame header
+ out->write(SAVEGAME_STR, SAVEGAME_STR_SIZE + 1);
+
+ out->writeByte(MADS_SAVEGAME_VERSION);
+
+ // Write savegame name
+ out->write(header._saveName.c_str(), header._saveName.size());
+ out->writeByte('\0');
+
+ // Handle the thumbnail. If there's already one set by the game, create one
+ if (!_saveThumb)
+ createThumbnail();
+ Graphics::saveThumbnail(*out, *_saveThumb);
+
+ _saveThumb->free();
+ delete _saveThumb;
+ _saveThumb = nullptr;
+
+ // Write out the save date/time
+ TimeDate td;
+ g_system->getTimeAndDate(td);
+ out->writeSint16LE(td.tm_year + 1900);
+ out->writeSint16LE(td.tm_mon + 1);
+ out->writeSint16LE(td.tm_mday);
+ out->writeSint16LE(td.tm_hour);
+ out->writeSint16LE(td.tm_min);
+ out->writeUint32LE(_vm->_events->getFrameCounter());
+}
+
+void Game::createThumbnail() {
+ if (_saveThumb) {
+ _saveThumb->free();
+ delete _saveThumb;
+ }
+
+ uint8 thumbPalette[PALETTE_SIZE];
+ _vm->_palette->grabPalette(thumbPalette, 0, PALETTE_COUNT);
+ _saveThumb = new Graphics::Surface();
+ ::createThumbnail(_saveThumb, _vm->_screen.getData(), MADS_SCREEN_WIDTH, MADS_SCREEN_HEIGHT, thumbPalette);
+}
+
+} // End of namespace MADS