/* 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/events.h" #include "common/random.h" #include "common/keyboard.h" #include "graphics/cursorman.h" #include "graphics/surface.h" #include "graphics/pixelformat.h" #include "graphics/wincursor.h" #include "engines/util.h" #include "composer/composer.h" #include "composer/graphics.h" #include "composer/resource.h" #include "composer/console.h" namespace Composer { ComposerEngine::ComposerEngine(OSystem *syst, const ComposerGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc) { _rnd = new Common::RandomSource("composer"); _audioStream = NULL; _currSoundPriority = 0; _currentTime = 0; _lastTime = 0; _needsUpdate = true; _directoriesToStrip = 1; _mouseVisible = true; _mouseEnabled = false; _mouseSpriteId = 0; _lastButton = NULL; _console = NULL; } ComposerEngine::~ComposerEngine() { DebugMan.clearAllDebugChannels(); stopPipes(); for (Common::List::iterator i = _oldScripts.begin(); i != _oldScripts.end(); i++) delete *i; for (Common::List::iterator i = _anims.begin(); i != _anims.end(); i++) delete *i; for (Common::List::iterator i = _libraries.begin(); i != _libraries.end(); i++) delete i->_archive; for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) i->_surface.free(); delete _rnd; delete _console; } Common::Error ComposerEngine::run() { Common::Event event; _vars.resize(1000); for (uint i = 0; i < _vars.size(); i++) _vars[i] = 0; _queuedScripts.resize(10); for (uint i = 0; i < _queuedScripts.size(); i++) { _queuedScripts[i]._count = 0; _queuedScripts[i]._scriptId = 0; } if (!loadDetectedConfigFile(_bookIni)) { // Config files for Darby the Dragon are located in subdirectory _directoriesToStrip = 0; if (!_bookIni.loadFromFile("programs/book.ini")) { error("failed to find book.ini"); } } uint width = 640; if (_bookIni.hasKey("Width", "Common")) width = atoi(getStringFromConfig("Common", "Width").c_str()); uint height = 480; if (_bookIni.hasKey("Height", "Common")) height = atoi(getStringFromConfig("Common", "Height").c_str()); initGraphics(width, height); _screen.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); Graphics::Cursor *cursor = Graphics::makeDefaultWinCursor(); CursorMan.replaceCursor(cursor); delete cursor; _console = new Console(this); loadLibrary(0); uint fps; if (_bookIni.hasKey("FPS", "Common")) fps = atoi(getStringFromConfig("Common", "FPS").c_str()); else { // On Macintosh version there is no FPS key if (getPlatform() != Common::kPlatformMacintosh) warning("there is no FPS key in book.ini. Defaulting to 8..."); fps = 8; } uint frameTime = 125; // Default to 125ms (1000/8) if (fps != 0) frameTime = 1000 / fps; else warning("FPS in book.ini is zero. Defaulting to 8..."); uint32 lastDrawTime = 0; _lastSaveTime = _system->getMillis(); bool loadFromLauncher = ConfMan.hasKey("save_slot"); while (!shouldQuit()) { for (uint i = 0; i < _pendingPageChanges.size(); i++) { if (_pendingPageChanges[i]._remove) unloadLibrary(_pendingPageChanges[i]._pageId); else loadLibrary(_pendingPageChanges[i]._pageId); lastDrawTime = 0; } _pendingPageChanges.clear(); uint32 thisTime = _system->getMillis(); // maintain our own internal timing, since otherwise we get // confused when starved of CPU (for example when the user // is dragging the scummvm window around) if (thisTime > _lastTime + frameTime) _currentTime += frameTime; else _currentTime += thisTime - _lastTime; _lastTime = thisTime; for (uint i = 0; i < _queuedScripts.size(); i++) { QueuedScript &script = _queuedScripts[i]; if (!script._count) continue; if (script._baseTime + script._duration > _currentTime) continue; if (script._count != 0xffffffff) script._count--; script._baseTime = _currentTime; runScript(script._scriptId, i, 0, 0); } if (lastDrawTime + frameTime <= thisTime) { // catch up if we're more than 2 frames behind if (lastDrawTime + (frameTime * 2) <= thisTime) lastDrawTime = thisTime; else lastDrawTime += frameTime; tickOldScripts(); redraw(); processAnimFrame(); } else if (_needsUpdate) { redraw(); } if (loadFromLauncher) { loadGameState(ConfMan.getInt("save_slot")); loadFromLauncher = false; } if (shouldPerformAutoSave(_lastSaveTime)) saveGameState(0, "Autosave"); while (_eventMan->pollEvent(event)) { switch (event.type) { case Common::EVENT_LBUTTONDOWN: onMouseDown(event.mouse); break; case Common::EVENT_LBUTTONUP: break; case Common::EVENT_RBUTTONDOWN: break; case Common::EVENT_MOUSEMOVE: onMouseMove(event.mouse); break; case Common::EVENT_KEYDOWN: switch (event.kbd.keycode) { case Common::KEYCODE_d: if (event.kbd.hasFlags(Common::KBD_CTRL)) { // Start the debugger getDebugger()->attach(); getDebugger()->onFrame(); } break; case Common::KEYCODE_q: if (event.kbd.hasFlags(Common::KBD_CTRL)) quitGame(); break; default: break; } onKeyDown(event.kbd.keycode); break; default: break; } } _system->delayMillis(20); } _screen.free(); return Common::kNoError; } void ComposerEngine::onMouseDown(const Common::Point &pos) { if (!_mouseEnabled || !_mouseVisible) return; const Sprite *sprite = getSpriteAtPos(pos); const Button *button = getButtonFor(sprite, pos); if (!button) return; debug(3, "mouseDown on button id %d", button->_id); // TODO: other buttons? uint16 buttonsDown = 1; // MK_LBUTTON uint16 spriteId = sprite ? sprite->_id : 0; runScript(button->_scriptId, (getGameType() == GType_ComposerV1) ? 0 : button->_id, buttonsDown, spriteId); } void ComposerEngine::onMouseMove(const Common::Point &pos) { _lastMousePos = pos; if (!_mouseEnabled || !_mouseVisible) return; // TODO: do we need to keep track of this? uint buttonsDown = 0; const Sprite *sprite = getSpriteAtPos(pos); const Button *button = getButtonFor(sprite, pos); if (_lastButton != button) { if (_lastButton && _lastButton->_scriptIdRollOff) runScript(_lastButton->_scriptIdRollOff, (getGameType() == GType_ComposerV1) ? 0 : _lastButton->_id, buttonsDown, 0); _lastButton = button; if (_lastButton && _lastButton->_scriptIdRollOn) runScript(_lastButton->_scriptIdRollOn, (getGameType() == GType_ComposerV1) ? 0 : _lastButton->_id, buttonsDown, 0); } if (_mouseSpriteId) { addSprite(_mouseSpriteId, 0, 0, _lastMousePos - _mouseOffset); } _needsUpdate = true; } void ComposerEngine::onKeyDown(uint16 keyCode) { runEvent(kEventKeyDown, keyCode, 0, 0); runEvent(kEventChar, keyCode, 0, 0); for (Common::List::iterator i = _libraries.begin(); i != _libraries.end(); i++) { for (Common::List::iterator j = i->_keyboardHandlers.begin(); j != i->_keyboardHandlers.end(); j++) { const KeyboardHandler &handler = *j; if (keyCode != handler.keyId) continue; int modifiers = g_system->getEventManager()->getModifierState(); switch (handler.modifierId) { case 0x10: // shift if (!(modifiers & Common::KBD_SHIFT)) continue; break; case 0x11: // control if (!(modifiers & Common::KBD_CTRL)) continue; break; case 0: break; default: warning("unknown keyb modifier %d", handler.modifierId); continue; } runScript(handler.scriptId); } } } void ComposerEngine::setCursor(uint16 id, const Common::Point &offset) { _mouseOffset = offset; if (_mouseSpriteId == id) return; if (_mouseSpriteId && _mouseVisible) { removeSprite(_mouseSpriteId, 0); } _mouseSpriteId = id; if (_mouseSpriteId && _mouseVisible) { addSprite(_mouseSpriteId, 0, 0, _lastMousePos - _mouseOffset); } } void ComposerEngine::setCursorVisible(bool visible) { if (visible && !_mouseVisible) { _mouseVisible = true; if (_mouseSpriteId) addSprite(_mouseSpriteId, 0, 0, _lastMousePos - _mouseOffset); else CursorMan.showMouse(true); onMouseMove(_lastMousePos); } else if (!visible && _mouseVisible) { _mouseVisible = false; if (_mouseSpriteId) removeSprite(_mouseSpriteId, 0); else CursorMan.showMouse(false); } } Common::String ComposerEngine::getStringFromConfig(const Common::String §ion, const Common::String &key) { Common::String value; if (!_bookIni.getKey(key, section, value)) error("failed to find key '%s' in section '%s' of book config", key.c_str(), section.c_str()); return value; } Common::String ComposerEngine::getFilename(const Common::String §ion, uint id) { Common::String key = Common::String::format("%d", id); Common::String filename = getStringFromConfig(section, key); return mangleFilename(filename); } Common::String ComposerEngine::mangleFilename(Common::String filename) { while (filename.size() && (filename[0] == '~' || filename[0] == ':' || filename[0] == '\\')) filename = filename.c_str() + 1; uint slashesToStrip = _directoriesToStrip; if (filename.hasPrefix("..")) slashesToStrip = 1; while (slashesToStrip--) { for (uint i = 0; i < filename.size(); i++) { if (filename[i] != '\\' && filename[i] != ':') continue; filename = filename.c_str() + i + 1; break; } } Common::String outFilename; for (uint i = 0; i < filename.size(); i++) { if (filename[i] == '\\' || filename[i] == ':') outFilename += '/'; else outFilename += filename[i]; } return outFilename; } void ComposerEngine::loadLibrary(uint id) { if (getGameType() == GType_ComposerV1 && !_libraries.empty()) { // kill the previous page, starting with any scripts running on it for (Common::List::iterator i = _oldScripts.begin(); i != _oldScripts.end(); i++) delete *i; _oldScripts.clear(); Library *library = &_libraries.front(); unloadLibrary(library->_id); } Common::String filename; Common::String oldGroup = _bookGroup; if (getGameType() == GType_ComposerV1) { if (getPlatform() == Common::kPlatformMacintosh) { if (!id || _bookGroup.empty()) filename = getStringFromConfig("splash.rsc", "100"); else filename = getStringFromConfig(_bookGroup + ".rsc", Common::String::format("%d", id)); } else { if (!id || _bookGroup.empty()) filename = getStringFromConfig("Common", "StartPage"); else filename = getStringFromConfig(_bookGroup, Common::String::format("%d", id)); } filename = mangleFilename(filename); // bookGroup is the basename of the path. // TODO: tidy this up. _bookGroup.clear(); for (uint i = 0; i < filename.size(); i++) { if (filename[i] == '~' || filename[i] == '/' || filename[i] == ':') continue; for (uint j = 0; j < filename.size(); j++) { if (filename[j] == '/') { _bookGroup.clear(); continue; } if (filename[j] == '.') break; _bookGroup += filename[j]; } break; } } else { if (!id) id = atoi(getStringFromConfig("Common", "StartUp").c_str()); filename = getFilename("Libs", id); } Library library; library._id = id; library._group = oldGroup; library._archive = new ComposerArchive(); if (!library._archive->openFile(filename)) error("failed to open '%s'", filename.c_str()); _libraries.push_front(library); Library &newLib = _libraries.front(); Common::Array buttonResources = library._archive->getResourceIDList(ID_BUTN); for (uint i = 0; i < buttonResources.size(); i++) { uint16 buttonId = buttonResources[i]; Common::SeekableReadStream *stream = library._archive->getResource(ID_BUTN, buttonId); Button button(stream, buttonId, getGameType()); bool inserted = false; for (Common::List