aboutsummaryrefslogtreecommitdiff
path: root/engines/sword2/sword2.cpp
diff options
context:
space:
mode:
authorMax Horn2006-02-11 22:45:04 +0000
committerMax Horn2006-02-11 22:45:04 +0000
commit26ee630756ebdd7c96bccede0881a8c8b98e8f2b (patch)
tree26e378d5cf990a2b81c2c96e9e683a7f333b62e8 /engines/sword2/sword2.cpp
parent2a9a0d4211b1ea5723f1409d91cb95de8984429e (diff)
downloadscummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.gz
scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.bz2
scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.zip
Moved engines to the new engines/ directory
svn-id: r20582
Diffstat (limited to 'engines/sword2/sword2.cpp')
-rw-r--r--engines/sword2/sword2.cpp689
1 files changed, 689 insertions, 0 deletions
diff --git a/engines/sword2/sword2.cpp b/engines/sword2/sword2.cpp
new file mode 100644
index 0000000000..6056d636b7
--- /dev/null
+++ b/engines/sword2/sword2.cpp
@@ -0,0 +1,689 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * 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.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+
+#include "backends/fs/fs.h"
+
+#include "base/gameDetector.h"
+#include "base/plugins.h"
+
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/system.h"
+
+#include "sword2/sword2.h"
+#include "sword2/console.h"
+#include "sword2/controls.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/maketext.h"
+#include "sword2/memory.h"
+#include "sword2/mouse.h"
+#include "sword2/resman.h"
+#include "sword2/router.h"
+#include "sword2/sound.h"
+
+#ifdef _WIN32_WCE
+extern bool isSmartphone();
+#endif
+
+struct Sword2GameSettings {
+ const char *gameid;
+ const char *description;
+ uint32 features;
+ const char *detectname;
+ GameSettings toGameSettings() const {
+ GameSettings dummy = { gameid, description, features };
+ return dummy;
+ }
+};
+
+static const Sword2GameSettings sword2_settings[] = {
+ /* Broken Sword 2 */
+ {"sword2", "Broken Sword 2: The Smoking Mirror", GF_DEFAULT_TO_1X_SCALER, "players.clu" },
+ {"sword2alt", "Broken Sword 2: The Smoking Mirror (alt)", GF_DEFAULT_TO_1X_SCALER, "r2ctlns.ocx" },
+ {"sword2demo", "Broken Sword 2: The Smoking Mirror (Demo)", GF_DEFAULT_TO_1X_SCALER | Sword2::GF_DEMO, "players.clu" },
+ {NULL, NULL, 0, NULL}
+};
+
+GameList Engine_SWORD2_gameList() {
+ const Sword2GameSettings *g = sword2_settings;
+ GameList games;
+ while (g->gameid) {
+ games.push_back(g->toGameSettings());
+ g++;
+ }
+ return games;
+}
+
+DetectedGameList Engine_SWORD2_detectGames(const FSList &fslist) {
+ DetectedGameList detectedGames;
+ const Sword2GameSettings *g;
+
+ // TODO: It would be nice if we had code here which distinguishes
+ // between the 'sword2' and 'sword2demo' targets. The current code
+ // can't do that since they use the same detectname.
+
+ for (g = sword2_settings; g->gameid; ++g) {
+ // Iterate over all files in the given directory
+ for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
+ if (!file->isDirectory()) {
+ const char *gameName = file->displayName().c_str();
+
+ if (0 == scumm_stricmp(g->detectname, gameName)) {
+ // Match found, add to list of candidates, then abort inner loop.
+ detectedGames.push_back(g->toGameSettings());
+ break;
+ }
+ }
+ }
+ }
+ return detectedGames;
+}
+
+Engine *Engine_SWORD2_create(GameDetector *detector, OSystem *syst) {
+ return new Sword2::Sword2Engine(detector, syst);
+}
+
+REGISTER_PLUGIN(SWORD2, "Broken Sword 2")
+
+namespace Sword2 {
+
+Sword2Engine::Sword2Engine(GameDetector *detector, OSystem *syst) : Engine(syst) {
+ // Add default file directories
+ Common::File::addDefaultDirectory(_gameDataPath + "CLUSTERS/");
+ Common::File::addDefaultDirectory(_gameDataPath + "SWORD2/");
+ Common::File::addDefaultDirectory(_gameDataPath + "VIDEO/");
+ Common::File::addDefaultDirectory(_gameDataPath + "clusters/");
+ Common::File::addDefaultDirectory(_gameDataPath + "sword2/");
+ Common::File::addDefaultDirectory(_gameDataPath + "video/");
+
+ _features = detector->_game.features;
+ _targetName = detector->_targetName;
+
+ _bootParam = ConfMan.getInt("boot_param");
+ _saveSlot = ConfMan.getInt("save_slot");
+
+ _memory = NULL;
+ _resman = NULL;
+ _sound = NULL;
+ _screen = NULL;
+ _mouse = NULL;
+ _logic = NULL;
+ _fontRenderer = NULL;
+ _debugger = NULL;
+
+ _keyboardEvent.pending = false;
+ _keyboardEvent.repeat = 0;
+ _mouseEvent.pending = false;
+
+ _wantSfxDebug = false;
+
+#ifdef SWORD2_DEBUG
+ _stepOneCycle = false;
+ _renderSkip = false;
+#endif
+
+ _gamePaused = false;
+ _graphicsLevelFudged = false;
+
+ _gameCycle = 0;
+
+ _quit = false;
+}
+
+Sword2Engine::~Sword2Engine() {
+ delete _debugger;
+ delete _sound;
+ delete _fontRenderer;
+ delete _screen;
+ delete _mouse;
+ delete _logic;
+ delete _resman;
+ delete _memory;
+}
+
+void Sword2Engine::errorString(const char *buf1, char *buf2) {
+ strcpy(buf2, buf1);
+
+#ifdef _WIN32_WCE
+ if (isSmartphone())
+ return;
+#endif
+
+ // Unless an error -originated- within the debugger, spawn the
+ // debugger. Otherwise exit out normally.
+ if (_debugger && !_debugger->isAttached()) {
+ // (Print it again in case debugger segfaults)
+ printf("%s\n", buf2);
+ _debugger->attach(buf2);
+ _debugger->onFrame();
+ }
+}
+
+void Sword2Engine::registerDefaultSettings() {
+ ConfMan.registerDefault("music_mute", false);
+ ConfMan.registerDefault("speech_mute", false);
+ ConfMan.registerDefault("sfx_mute", false);
+ ConfMan.registerDefault("gfx_details", 2);
+ ConfMan.registerDefault("subtitles", false);
+ ConfMan.registerDefault("reverse_stereo", false);
+}
+
+void Sword2Engine::readSettings() {
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume"));
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume"));
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume"));
+ setSubtitles(ConfMan.getBool("subtitles"));
+ _sound->muteMusic(ConfMan.getBool("music_mute"));
+ _sound->muteSpeech(ConfMan.getBool("speech_mute"));
+ _sound->muteFx(ConfMan.getBool("sfx_mute"));
+ _sound->setReverseStereo(ConfMan.getBool("reverse_stereo"));
+ _mouse->setObjectLabels(ConfMan.getBool("object_labels"));
+ _screen->setRenderLevel(ConfMan.getInt("gfx_details"));
+}
+
+void Sword2Engine::writeSettings() {
+ ConfMan.set("music_volume", _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType));
+ ConfMan.set("speech_volume", _mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType));
+ ConfMan.set("sfx_volume", _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType));
+ ConfMan.set("music_mute", _sound->isMusicMute());
+ ConfMan.set("speech_mute", _sound->isSpeechMute());
+ ConfMan.set("sfx_mute", _sound->isFxMute());
+ ConfMan.set("gfx_details", _screen->getRenderLevel());
+ ConfMan.set("subtitles", getSubtitles());
+ ConfMan.set("object_labels", _mouse->getObjectLabels());
+ ConfMan.set("reverse_stereo", _sound->isReverseStereo());
+
+ ConfMan.flushToDisk();
+}
+
+/**
+ * The global script variables and player object should be kept open throughout
+ * the game, so that they are never expelled by the resource manager.
+ */
+
+void Sword2Engine::setupPersistentResources() {
+ _logic->_scriptVars = _resman->openResource(1) + ResHeader::size();
+ _resman->openResource(CUR_PLAYER_ID);
+}
+
+int Sword2Engine::init(GameDetector &detector) {
+ // Get some falling RAM and put it in your pocket, never let it slip
+ // away
+
+ _system->beginGFXTransaction();
+ initCommonGFX(detector);
+ _screen = new Screen(this, 640, 480);
+ _system->endGFXTransaction();
+
+ // Create the debugger as early as possible (but not before the
+ // screen object!) so that errors can be displayed in it. In
+ // particular, we want errors about missing files to be clearly
+ // visible to the user.
+
+ _debugger = new Debugger(this);
+
+ _memory = new MemoryManager(this);
+ _resman = new ResourceManager(this);
+ _logic = new Logic(this);
+ _fontRenderer = new FontRenderer(this);
+ _sound = new Sound(this);
+ _mouse = new Mouse(this);
+
+ // Setup mixer
+ if (!_mixer->isReady())
+ warning("Sound initialization failed");
+
+ registerDefaultSettings();
+ readSettings();
+
+ initStartMenu();
+
+ // During normal gameplay, we care neither about mouse button releases
+ // nor the scroll wheel.
+ setInputEventFilter(RD_LEFTBUTTONUP | RD_RIGHTBUTTONUP | RD_WHEELUP | RD_WHEELDOWN);
+
+ setupPersistentResources();
+ initialiseFontResourceFlags();
+
+ if (_features & GF_DEMO)
+ _logic->writeVar(DEMO, 1);
+ else
+ _logic->writeVar(DEMO, 0);
+
+ if (_saveSlot != -1) {
+ if (saveExists(_saveSlot))
+ restoreGame(_saveSlot);
+ else {
+ RestoreDialog dialog(this);
+ if (!dialog.runModal())
+ startGame();
+ }
+ } else if (!_bootParam && saveExists()) {
+ int32 pars[2] = { 221, FX_LOOP };
+ bool result;
+
+ _mouse->setMouse(NORMAL_MOUSE_ID);
+ _logic->fnPlayMusic(pars);
+
+ StartDialog dialog(this);
+
+ result = (dialog.runModal() != 0);
+
+ // If the game is started from the beginning, the cutscene
+ // player will kill the music for us. Otherwise, the restore
+ // will either have killed the music, or done a crossfade.
+
+ if (_quit)
+ return 0;
+
+ if (result)
+ startGame();
+ } else
+ startGame();
+
+ _screen->initialiseRenderCycle();
+
+ return 0;
+}
+
+int Sword2Engine::go() {
+ while (1) {
+ if (_debugger->isAttached())
+ _debugger->onFrame();
+
+#ifdef SWORD2_DEBUG
+ if (_stepOneCycle) {
+ pauseGame();
+ _stepOneCycle = false;
+ }
+#endif
+
+ KeyboardEvent *ke = keyboardEvent();
+
+ if (ke) {
+ if ((ke->modifiers == OSystem::KBD_CTRL && ke->keycode == 'd') || ke->ascii == '#' || ke->ascii == '~') {
+ _debugger->attach();
+ } else if (ke->modifiers == 0 || ke->modifiers == OSystem::KBD_SHIFT) {
+ switch (ke->keycode) {
+ case 'p':
+ if (_gamePaused)
+ unpauseGame();
+ else
+ pauseGame();
+ break;
+ case 'c':
+ if (!_logic->readVar(DEMO) && !_mouse->isChoosing()) {
+ ScreenInfo *screenInfo = _screen->getScreenInfo();
+ _logic->fnPlayCredits(NULL);
+ screenInfo->new_palette = 99;
+ }
+ break;
+#ifdef SWORD2_DEBUG
+ case ' ':
+ if (_gamePaused) {
+ _stepOneCycle = true;
+ unpauseGame();
+ }
+ break;
+ case 's':
+ _renderSkip = !_renderSkip;
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ }
+
+ // skip GameCycle if we're paused
+ if (!_gamePaused) {
+ _gameCycle++;
+ gameCycle();
+ }
+
+ // We can't use this as termination condition for the loop,
+ // because we want the break to happen before updating the
+ // screen again.
+
+ if (_quit)
+ break;
+
+ // creates the debug text blocks
+ _debugger->buildDebugText();
+
+#ifdef SWORD2_DEBUG
+ // if not in console & '_renderSkip' is set, only render
+ // display once every 4 game-cycles
+
+ if (!_renderSkip || (_gameCycle % 4) == 0)
+ _screen->buildDisplay();
+#else
+ _screen->buildDisplay();
+#endif
+ }
+
+ return 0;
+}
+
+void Sword2Engine::closeGame() {
+ _quit = true;
+}
+
+void Sword2Engine::restartGame() {
+ ScreenInfo *screenInfo = _screen->getScreenInfo();
+ uint32 temp_demo_flag;
+
+ _mouse->closeMenuImmediately();
+
+ // Restart the game. To do this, we must...
+
+ // Stop music instantly!
+ _sound->stopMusic(true);
+
+ // In case we were dead - well we're not anymore!
+ _logic->writeVar(DEAD, 0);
+
+ // Restart the game. Clear all memory and reset the globals
+ temp_demo_flag = _logic->readVar(DEMO);
+
+ // Remove all resources from memory, including player object and
+ // global variables
+ _resman->removeAll();
+
+ // Reopen global variables resource and player object
+ setupPersistentResources();
+
+ _logic->writeVar(DEMO, temp_demo_flag);
+
+ // Free all the route memory blocks from previous game
+ _logic->_router->freeAllRouteMem();
+
+ // Call the same function that first started us up
+ startGame();
+
+ // Prime system with a game cycle
+
+ // Reset the graphic 'BuildUnit' list before a new logic list
+ // (see fnRegisterFrame)
+ _screen->resetRenderLists();
+
+ // Reset the mouse hot-spot list (see fnRegisterMouse and
+ // fnRegisterFrame)
+ _mouse->resetMouseList();
+
+ _mouse->closeMenuImmediately();
+
+ // FOR THE DEMO - FORCE THE SCROLLING TO BE RESET!
+ // - this is taken from fnInitBackground
+ // switch on scrolling (2 means first time on screen)
+ screenInfo->scroll_flag = 2;
+
+ if (_logic->processSession())
+ error("restart 1st cycle failed??");
+
+ // So palette not restored immediately after control panel - we want
+ // to fade up instead!
+ screenInfo->new_palette = 99;
+}
+
+bool Sword2Engine::checkForMouseEvents() {
+ return _mouseEvent.pending;
+}
+
+MouseEvent *Sword2Engine::mouseEvent() {
+ if (!_mouseEvent.pending)
+ return NULL;
+
+ _mouseEvent.pending = false;
+ return &_mouseEvent;
+}
+
+KeyboardEvent *Sword2Engine::keyboardEvent() {
+ if (!_keyboardEvent.pending)
+ return NULL;
+
+ _keyboardEvent.pending = false;
+ return &_keyboardEvent;
+}
+
+uint32 Sword2Engine::setInputEventFilter(uint32 filter) {
+ uint32 oldFilter = _inputEventFilter;
+
+ _inputEventFilter = filter;
+ return oldFilter;
+}
+
+/**
+ * Clear the input events. This is so that we won't get any keyboard repeat
+ * right after using the debugging console.
+ */
+
+void Sword2Engine::clearInputEvents() {
+ _keyboardEvent.pending = false;
+ _keyboardEvent.repeat = 0;
+ _mouseEvent.pending = false;
+}
+
+/**
+ * OSystem Event Handler. Full of cross platform goodness and 99% fat free!
+ */
+
+void Sword2Engine::parseInputEvents() {
+ OSystem::Event event;
+
+ uint32 now = _system->getMillis();
+
+ while (_system->pollEvent(event)) {
+ switch (event.type) {
+ case OSystem::EVENT_KEYDOWN:
+ if (!(_inputEventFilter & RD_KEYDOWN)) {
+ _keyboardEvent.pending = true;
+ _keyboardEvent.repeat = now + 400;
+ _keyboardEvent.ascii = event.kbd.ascii;
+ _keyboardEvent.keycode = event.kbd.keycode;
+ _keyboardEvent.modifiers = event.kbd.flags;
+ }
+ break;
+ case OSystem::EVENT_KEYUP:
+ _keyboardEvent.repeat = 0;
+ break;
+ case OSystem::EVENT_MOUSEMOVE:
+ if (!(_inputEventFilter & RD_KEYDOWN)) {
+ _mouse->setPos(event.mouse.x, event.mouse.y - MENUDEEP);
+ }
+ break;
+ case OSystem::EVENT_LBUTTONDOWN:
+ if (!(_inputEventFilter & RD_LEFTBUTTONDOWN)) {
+ _mouseEvent.pending = true;
+ _mouseEvent.buttons = RD_LEFTBUTTONDOWN;
+ }
+ break;
+ case OSystem::EVENT_RBUTTONDOWN:
+ if (!(_inputEventFilter & RD_RIGHTBUTTONDOWN)) {
+ _mouseEvent.pending = true;
+ _mouseEvent.buttons = RD_RIGHTBUTTONDOWN;
+ }
+ break;
+ case OSystem::EVENT_LBUTTONUP:
+ if (!(_inputEventFilter & RD_LEFTBUTTONUP)) {
+ _mouseEvent.pending = true;
+ _mouseEvent.buttons = RD_LEFTBUTTONUP;
+ }
+ break;
+ case OSystem::EVENT_RBUTTONUP:
+ if (!(_inputEventFilter & RD_RIGHTBUTTONUP)) {
+ _mouseEvent.pending = true;
+ _mouseEvent.buttons = RD_RIGHTBUTTONUP;
+ }
+ break;
+ case OSystem::EVENT_WHEELUP:
+ if (!(_inputEventFilter & RD_WHEELUP)) {
+ _mouseEvent.pending = true;
+ _mouseEvent.buttons = RD_WHEELUP;
+ }
+ break;
+ case OSystem::EVENT_WHEELDOWN:
+ if (!(_inputEventFilter & RD_WHEELDOWN)) {
+ _mouseEvent.pending = true;
+ _mouseEvent.buttons = RD_WHEELDOWN;
+ }
+ break;
+ case OSystem::EVENT_QUIT:
+ closeGame();
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Handle keyboard auto-repeat
+ if (!_keyboardEvent.pending && _keyboardEvent.repeat && now >= _keyboardEvent.repeat) {
+ _keyboardEvent.pending = true;
+ _keyboardEvent.repeat = now + 100;
+ }
+}
+
+void Sword2Engine::gameCycle() {
+ // Do one game cycle, that is run the logic session until a full loop
+ // has been performed.
+
+ if (_logic->getRunList()) {
+ do {
+ // Reset the 'BuildUnit' and mouse hot-spot lists
+ // before each new logic list. The service scripts
+ // will fill thrm through fnRegisterFrame() and
+ // fnRegisterMouse().
+
+ _screen->resetRenderLists();
+ _mouse->resetMouseList();
+
+ // Keep going as long as new lists keep getting put in
+ // - i.e. screen changes.
+ } while (_logic->processSession());
+ } else {
+ // Start the console and print the start options perhaps?
+ _debugger->attach("AWAITING START COMMAND: (Enter 's 1' then 'q' to start from beginning)");
+ }
+
+ // If this screen is wide, recompute the scroll offsets every cycle
+ ScreenInfo *screenInfo = _screen->getScreenInfo();
+
+ if (screenInfo->scroll_flag)
+ _screen->setScrolling();
+
+ _mouse->mouseEngine();
+ _sound->processFxQueue();
+}
+
+void Sword2Engine::startGame() {
+ // Boot the game straight into a start script. It's always George's
+ // script #1, but with different ScreenManager objects depending on
+ // if it's the demo or the full game, or if we're using a boot param.
+
+ int screen_manager_id = 0;
+
+ debug(5, "startGame() STARTING:");
+
+ if (!_bootParam) {
+ if (_logic->readVar(DEMO))
+ screen_manager_id = 19; // DOCKS SECTION START
+ else
+ screen_manager_id = 949; // INTRO & PARIS START
+ } else {
+ // FIXME this could be validated against startup.inf for valid
+ // numbers to stop people shooting themselves in the foot
+
+ if (_bootParam != 0)
+ screen_manager_id = _bootParam;
+ }
+
+ _logic->runResObjScript(screen_manager_id, CUR_PLAYER_ID, 1);
+}
+
+// FIXME: Move this to some better place?
+
+void Sword2Engine::sleepUntil(uint32 time) {
+ while (getMillis() < time) {
+ // Make sure menu animations and fades don't suffer, but don't
+ // redraw the entire scene.
+ _mouse->processMenu();
+ _screen->updateDisplay(false);
+ _system->delayMillis(10);
+ }
+}
+
+void Sword2Engine::pauseGame() {
+ // Don't allow Pause while screen fading or while black
+ if (_screen->getFadeStatus() != RDFADE_NONE)
+ return;
+
+ _sound->pauseAllSound();
+ _mouse->pauseGame();
+
+ // If render level is at max, turn it down because palette-matching
+ // won't work when the palette is dimmed.
+
+ if (_screen->getRenderLevel() == 3) {
+ _screen->setRenderLevel(2);
+ _graphicsLevelFudged = true;
+ }
+
+#ifdef SWORD2_DEBUG
+ // Don't dim it if we're single-stepping through frames
+ // dim the palette during the pause
+
+ if (!_stepOneCycle)
+ _screen->dimPalette();
+#else
+ _screen->dimPalette();
+#endif
+
+ _gamePaused = true;
+}
+
+void Sword2Engine::unpauseGame() {
+ _mouse->unpauseGame();
+ _sound->unpauseAllSound();
+
+ // Put back game screen palette; see build_display.cpp
+ _screen->setFullPalette(-1);
+
+ // If graphics level at max, turn up again
+ if (_graphicsLevelFudged) {
+ _screen->setRenderLevel(3);
+ _graphicsLevelFudged = false;
+ }
+
+ _gamePaused = false;
+
+ // If mouse is about or we're in a chooser menu
+ if (!_mouse->getMouseStatus() || _mouse->isChoosing())
+ _mouse->setMouse(NORMAL_MOUSE_ID);
+}
+
+uint32 Sword2Engine::getMillis() {
+ return _system->getMillis();
+}
+
+} // End of namespace Sword2