/* 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 "lastexpress/lastexpress.h" #include "lastexpress/game/logic.h" #include "lastexpress/data/gold_archive.h" #include "lastexpress/menu/menu.h" #include "lastexpress/menu/clock.h" #include "lastexpress/sound/sound.h" #include "lastexpress/sound/subtitle.h" #include "lastexpress/graphics.h" #include "lastexpress/helpers.h" #include "common/config-manager.h" #include "common/debug-channels.h" #include "common/error.h" #include "common/fs.h" #include "common/timer.h" #include "common/compression/installshield_cab.h" #include "engines/util.h" #include "engines/advancedDetector.h" namespace LastExpress { LastExpressEngine::LastExpressEngine(OSystem *syst, const ADGameDescription *gd) : Engine(syst), _gameDescription(gd), _debugger(nullptr), _random("lastexpress") { // Setup mixer Engine::syncSoundSettings(); // Adding the default directories const Common::FSNode gameDataDir(ConfMan.getPath("path")); if (isGoldEdition()) { SearchMan.addSubDirectoryMatching(gameDataDir, "roms"); } else { SearchMan.addSubDirectoryMatching(gameDataDir, "data"); } _soundMutex = new Common::Mutex(); } LastExpressEngine::~LastExpressEngine() { SAFE_DELETE(_graphicsMan); SAFE_DELETE(_spriteMan); SAFE_DELETE(_otisMan); SAFE_DELETE(_subtitleMan); SAFE_DELETE(_archiveMan); SAFE_DELETE(_msgMan); SAFE_DELETE(_nisMan); SAFE_DELETE(_soundMan); SAFE_DELETE(_logicMan); SAFE_DELETE(_menu); SAFE_DELETE(_saveMan); SAFE_DELETE(_clock); SAFE_DELETE(_vcr); SAFE_DELETE(_soundMutex); SAFE_DELETE(_savegame); SAFE_DELETE(_memMan); //_debugger is deleted by Engine // Zero passed pointers _gameDescription = nullptr; } void LastExpressEngine::startUp() { getArchiveManager()->initHPFS(); getMemoryManager()->initMem(); getGraphicsManager()->clear(getGraphicsManager()->_screenSurface, 0, 0, 640, 480); getGraphicsManager()->clear(getGraphicsManager()->_backBuffer, 0, 0, 640, 480); getGraphicsManager()->clear(getGraphicsManager()->_frontBuffer, 0, 0, 640, 480); getVCR()->shuffleGames(); getArchiveManager()->loadMice(); getGraphicsManager()->setMouseDrawable(false); initGameData(); getSubtitleManager()->initSubtitles(); getLogicManager()->loadTrain(isDemo() ? 2 : 1); } void LastExpressEngine::shutDown() { getMemoryManager()->releaseMemory(); getArchiveManager()->shutDownHPFS(); } Common::String LastExpressEngine::getTargetName() const { return _targetName; } void LastExpressEngine::soundTimerHandler(void *refCon) { LastExpressEngine *engine = (LastExpressEngine *)refCon; if (!engine) return; Common::StackLock lock(*engine->_soundMutex); if ((engine->_soundFrameCounter & 1) != 0) { engine->getSoundManager()->soundDriverCopyBuffersToDevice(); } if ((engine->_soundFrameCounter & 3) == 0) { if (engine->getEventTickInternal()) { engine->_soundFrameCounter++; return; } engine->getMessageManager()->addEvent(kEventChannelTimer, 0, 0, 0); } engine->_soundFrameCounter++; } Common::Error LastExpressEngine::run() { // Allow HD.HPF to be read directly from the InstallShield archive if (isCompressed()) { Common::Archive *cabinet = Common::makeInstallShieldArchive("data"); if (cabinet) { SearchMan.add("data1.cab", cabinet); } } // Initialize the graphics const Graphics::PixelFormat dataPixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0); initGraphics(640, 480, &dataPixelFormat); // We do not support color conversion if (_system->getScreenFormat() != dataPixelFormat) return Common::kUnsupportedColorMode; // Create debugger. It requires GFX to be initialized _debugger = new Debugger(this); setDebugger(_debugger); if (gDebugLevel >= 3) DebugMan.enableDebugChannel(kDebugConsole); // Graphics manager _graphicsMan = new GraphicsManager(this); // Animation system _spriteMan = new SpriteManager(this); _otisMan = new OtisManager(this); // Archive manager if (isGoldEdition()) { _archiveMan = new GoldArchiveManager(this); } else { _archiveMan = new ArchiveManager(this); } // Memory manager _memMan = new MemoryManager(this); // Message manager _msgMan = new MessageManager(this); // NIS manager _nisMan = new NISManager(this); // Sound&subtitles manager _soundMan = new SoundManager(this); _subtitleMan = new SubtitleManager(this); // Logic manager _logicMan = new LogicManager(this); // Menu _menu = new Menu(this); // Save manager _saveMan = new SaveManager(this); // Clock _clock = new Clock(this); // VCR _vcr = new VCR(this); startUp(); getGraphicsManager()->initLuminosityValues(dataPixelFormat.rMax() << 11, dataPixelFormat.gMax() << 5, dataPixelFormat.bMax() << 0); #ifdef USE_IMGUI ImGuiCallbacks callbacks; callbacks.init = onImGuiInit; callbacks.render = onImGuiRender; callbacks.cleanup = onImGuiCleanup; _system->setImGuiCallbacks(callbacks); #endif getMessageManager()->setEventHandle(kEventChannelEngine, &LastExpressEngine::engineEventHandlerWrapper); getTimerManager()->installTimerProc(soundTimerHandler, 17000, this, "LastExpressEngine"); getGraphicsManager()->newMouseLoc(); getArchiveManager()->loadMice(); getVCR()->loadSettings(); getMenu()->doEgg(false, kSavegameTypeIndex, 0); while (!shouldQuit()) { do { getSoundManager()->soundThread(); getSubtitleManager()->subThread(); } while (getMessageManager()->process()); // Originally the game waited for at most four frames (17 * 4 ms). // Since the game relies on the sound timer for actual engine pacing, // we can reduce the time to 5 milliseconds of wait time, fetching // input in the meanwhile, making the cursor a lot smoother. waitForTimer(5); } bool haveEvent = true; while (_pendingExitEvent && haveEvent) { haveEvent = getMessageManager()->process(); } getSoundManager()->destroyAllSound(); getTimerManager()->removeTimerProc(soundTimerHandler); shutDown(); #ifdef USE_IMGUI _system->setImGuiCallbacks(ImGuiCallbacks()); #endif return Common::kNoError; } void LastExpressEngine::initGameData() { Sprite eraser; eraser.rect.top = 0; eraser.rect.left = 80; eraser.rect.right = 559; eraser.rect.bottom = 479; getSpriteManager()->queueErase(&eraser); for (int i = 0; i < 1000; i++) { getLogicManager()->_blockedViews[i] = 0; } for (int i = 0; i < 16; i++) { getLogicManager()->_blockedX[i] = 0; getLogicManager()->_softBlockedX[i] = 0; } for (int i = 0; i < 512; i++) { getLogicManager()->_doneNIS[i] = 0; } for (int i = 0; i < 128; i++) { getLogicManager()->_globals[i] = 0; } for (int i = 0; i < 128; i++) { getMessageManager()->_autoMessages[i].receiver = 0; getMessageManager()->_autoMessages[i].action = 0; getMessageManager()->_autoMessages[i].sender = 0; getMessageManager()->_autoMessages[i].param = 0; } for (int i = 0; i < 40; i++) { (_characters->characters[i]).reset(); (_characters->characters[i]).walkStepSize = 30; } for (int i = 0; i < 128; i++) { getLogicManager()->_doors[i].status = 0; getLogicManager()->_doors[i].who = 0; getLogicManager()->_doors[i].windowCursor = 10; getLogicManager()->_doors[i].handleCursor = 9; getLogicManager()->_doors[i].model = 0; } for (int i = 0; i < 32; i++) { getLogicManager()->_items[i].mnum = 0; getLogicManager()->_items[i].haveIt = 0; getLogicManager()->_items[i].floating = 0; getLogicManager()->_items[i].inPocket = 1; } getLogicManager()->_items[kItemMatchBox].mnum = 16; getLogicManager()->_items[kItemTelegram].mnum = 17; getLogicManager()->_items[kItemPassengerList].mnum = 18; getLogicManager()->_items[kItemArticle].mnum = 19; getLogicManager()->_items[kItemScarf].mnum = 20; getLogicManager()->_items[kItemPaper].mnum = 21; getLogicManager()->_items[kItemParchemin].mnum = 22; getLogicManager()->_items[kItemMatch].mnum = 23; getLogicManager()->_items[kItemWhistle].mnum = 24; getLogicManager()->_items[kItemKey].mnum = 25; getLogicManager()->_items[kItemBomb].mnum = 26; getLogicManager()->_items[kItemFirebird].mnum = 27; getLogicManager()->_items[kItemBriefcase].mnum = 28; getLogicManager()->_items[kItemCorpse].mnum = 29; getLogicManager()->_items[kItemMatchBox].useable = 1; getLogicManager()->_items[kItemMatch].useable = 1; getLogicManager()->_items[kItemTelegram].useable = 1; getLogicManager()->_items[kItemWhistle].useable = 1; getLogicManager()->_items[kItemKey].useable = 1; getLogicManager()->_items[kItemFirebird].useable = 1; getLogicManager()->_items[kItemBriefcase].useable = 1; getLogicManager()->_items[kItemCorpse].useable = 1; getLogicManager()->_items[kItemPassengerList].useable = 1; getLogicManager()->_items[kItem2].inPocket = 0; getLogicManager()->_items[kItem3].inPocket = 0; getLogicManager()->_items[kItem5].inPocket = 0; getLogicManager()->_items[kItem7].inPocket = 0; getLogicManager()->_items[kItem9].inPocket = 0; getLogicManager()->_items[kItemBeetle].inPocket = 0; getLogicManager()->_items[kItem11].inPocket = 0; getLogicManager()->_items[kItem17].inPocket = 0; getLogicManager()->_items[kItemFirebird].inPocket = 0; getLogicManager()->_items[kItemBriefcase].inPocket = 0; getLogicManager()->_items[kItemCorpse].inPocket = 0; getLogicManager()->_items[kItemGreenJacket].inPocket = 0; getLogicManager()->_items[kItem22].inPocket = 0; getLogicManager()->_items[kItemMatchBox].closeUp = 31; getLogicManager()->_items[kItemTelegram].closeUp = 32; getLogicManager()->_items[kItemPassengerList].closeUp = 33; getLogicManager()->_items[kItemScarf].closeUp = 34; getLogicManager()->_items[kItemParchemin].closeUp = 35; getLogicManager()->_items[kItemFirebird].closeUp = 38; getLogicManager()->_items[kItemBriefcase].closeUp = 39; getLogicManager()->_items[kItemPaper].closeUp = 37; getLogicManager()->_items[kItemArticle].closeUp = 36; getLogicManager()->_items[kItemTelegram].haveIt = 1; getLogicManager()->_items[kItemArticle].haveIt = 1; getLogicManager()->_globals[kGlobalCathIcon] = isDemo() ? 34: 32; getLogicManager()->_globals[kGlobalChapter] = isDemo() ? 3 : 1; getLogicManager()->_lastSavegameSessionTicks = 0; getLogicManager()->_realTime = 0; getLogicManager()->_closeUp = 0; getLogicManager()->_nodeReturn = 0; getLogicManager()->_nodeReturn2 = 0; getLogicManager()->_activeItem = 0; getLogicManager()->_gameTime = isDemo() ? 2241000 : 1037700; getLogicManager()->_timeSpeed = 3; getLogicManager()->_activeNode = 40; } void LastExpressEngine::startNewGame() { initGameData(); getLogicManager()->CONS_All(true, kCharacterCath); } void LastExpressEngine::engineEventHandler(Event *event) { switch (event->flags) { case 1: // Quit signal request getNISManager()->abortNIS(); abortFight(); abortCredits(); if (_pendingExitEvent) { if (getMenu()->isShowingMenu() || (getMessageManager()->getEventHandle(1) == &LastExpressEngine::nodeStepMouseWrapper && !getVCR()->isVirgin(_currentGameFileColorId))) { getMenu()->doEgg(true, 0, 0); // Save! _pendingExitEvent = false; // We're done, we can quit } else { getMessageManager()->addEvent(kEventChannelEngine, 0, 0, 2); // Give the engine the actual chance to abort NIS, fights and credits by running process() _pendingExitEvent = true; } } break; case 2: // Quit signal handler getNISManager()->abortNIS(); abortFight(); abortCredits(); if (_pendingExitEvent && !getMenu()->isShowingMenu()) { if (getMessageManager()->getEventHandle(1) != &LastExpressEngine::nodeStepMouseWrapper || getVCR()->isVirgin(_currentGameFileColorId)) { _pendingExitEvent = true; } else { getMenu()->doEgg(true, 0, 0); // Save! _pendingExitEvent = false; // We're done, we can quit } } break; case 3: // Originally handled WM_PAINT events and triggered burstBox on the screen rectangle break; case 4: { bool oldCanDrawMouse = getGraphicsManager()->canDrawMouse(); getGraphicsManager()->setMouseDrawable(false); getGraphicsManager()->burstMouseArea(); getGraphicsManager()->setMouseDrawable(oldCanDrawMouse); break; } default: break; } } int32 LastExpressEngine::getSoundFrameCounter() { Common::StackLock lock(*_soundMutex); return _soundFrameCounter; } bool LastExpressEngine::getEventTickInternal() { Common::StackLock lock(*_soundMutex); return _eventTickInternal; } void LastExpressEngine::setEventTickInternal(bool flag) { Common::StackLock lock(*_soundMutex); _eventTickInternal = flag; } void LastExpressEngine::mouseSetLeftClicked(bool hasClicked) { Common::StackLock lock(*_soundMutex); _mouseHasLeftClicked = hasClicked; } void LastExpressEngine::mouseSetRightClicked(bool hasClicked) { Common::StackLock lock(*_soundMutex); _mouseHasRightClicked = hasClicked; } bool LastExpressEngine::mouseHasLeftClicked() { Common::StackLock lock(*_soundMutex); return _mouseHasLeftClicked; } bool LastExpressEngine::mouseHasRightClicked() { Common::StackLock lock(*_soundMutex); return _mouseHasRightClicked; } void LastExpressEngine::waitForTimer(int millis) { handleEvents(); _system->delayMillis(millis); } bool LastExpressEngine::handleEvents() { // Handle input Common::Event ev; int32 curFlags = 0; bool eventWillUpdateScreen = false; while (_eventMan->pollEvent(ev)) { switch (ev.type) { case Common::EVENT_KEYDOWN: switch (ev.kbd.keycode) { case Common::KEYCODE_F4: if (_navigationEngineIsRunning && gDebugLevel >= 3) getLogicManager()->doF4(); break; default: break; } break; case Common::EVENT_LBUTTONDOWN: _systemEventLeftMouseDown = true; curFlags |= kMouseFlagLeftButton; if (_systemEventRightMouseDown) curFlags |= kMouseFlagRightButton; getMessageManager()->addEvent(kEventChannelMouse, ev.mouse.x, ev.mouse.y, curFlags); eventWillUpdateScreen = true; break; case Common::EVENT_LBUTTONUP: _systemEventLeftMouseDown = false; if (_systemEventRightMouseDown) curFlags |= kMouseFlagRightButton; getMessageManager()->addEvent(kEventChannelMouse, ev.mouse.x, ev.mouse.y, curFlags); eventWillUpdateScreen = true; break; case Common::EVENT_RBUTTONDOWN: _systemEventRightMouseDown = true; curFlags |= kMouseFlagRightButton; if (_systemEventLeftMouseDown) curFlags |= kMouseFlagLeftButton; getMessageManager()->addEvent(kEventChannelMouse, ev.mouse.x, ev.mouse.y, curFlags); eventWillUpdateScreen = true; break; case Common::EVENT_RBUTTONUP: _systemEventRightMouseDown = false; if (_systemEventLeftMouseDown) curFlags |= kMouseFlagLeftButton; getMessageManager()->addEvent(kEventChannelMouse, ev.mouse.x, ev.mouse.y, curFlags); eventWillUpdateScreen = true; break; case Common::EVENT_MOUSEMOVE: // The reason I'm adding this check is because we don't want to // stall the "fast-movement" mode when dragging a mouse with // pressed buttons. The original doesn't do this, because the handler // for input events and the engine lived on separate threads, so it // works out nicely for them... if (!getLogicManager()->_doubleClickFlag) { if (_systemEventLeftMouseDown) curFlags |= kMouseFlagLeftButton; if (_systemEventRightMouseDown) curFlags |= kMouseFlagRightButton; } _systemEventLastMouseCoords.x = ev.mouse.x; _systemEventLastMouseCoords.y = ev.mouse.y; getMessageManager()->addEvent(kEventChannelMouse, ev.mouse.x, ev.mouse.y, curFlags); eventWillUpdateScreen = true; break; case Common::EVENT_QUIT: case Common::EVENT_RETURN_TO_LAUNCHER: if (!_exitFromMenuButton) { getMessageManager()->addEvent(kEventChannelEngine, 0, 0, 1); _pendingExitEvent = true; } break; default: break; } } if (_exitFromMenuButton) _exitFromMenuButton = false; #ifdef USE_IMGUI // Allow the debugger to pick up the changes... if (gDebugLevel >= 3) { _system->updateScreen(); } else { #endif // Force the update only if it hasn't been already triggered by an event... if (!eventWillUpdateScreen && (_system->getMillis() - _lastForcedScreenUpdateTicks >= 17)) { _lastForcedScreenUpdateTicks = _system->getMillis(); _system->updateScreen(); } #ifdef USE_IMGUI } #endif return true; } void LastExpressEngine::eggMouseWrapper(Event *event) { getMenu()->eggMouse(event); } void LastExpressEngine::eggTimerWrapper(Event *event) { getMenu()->eggTimer(event); } void LastExpressEngine::engineEventHandlerWrapper(Event *event) { engineEventHandler(event); } void LastExpressEngine::nodeStepMouseWrapper(Event *event) { getLogicManager()->nodeStepMouse(event); } void LastExpressEngine::nodeStepTimerWrapper(Event *event) { getLogicManager()->nodeStepTimer(event); } void LastExpressEngine::nisMouseWrapper(Event *event) { getNISManager()->nisMouse(event); } void LastExpressEngine::nisTimerWrapper(Event *event) { getNISManager()->nisTimer(event); } void LastExpressEngine::creditsMouseWrapper(Event *event) { creditsMouse(event); } void LastExpressEngine::creditsTimerWrapper(Event *event) { creditsTimer(event); } void LastExpressEngine::demoEndingMouseWrapper(Event *event) { demoEndingMouse(event); } void LastExpressEngine::demoEndingTimerWrapper(Event *event) { demoEndingTimer(event); } void LastExpressEngine::fightMouseWrapper(Event *event) { _fight->mouse(event); } void LastExpressEngine::fightTimerWrapper(Event *event) { _fight->timer(event, true); } void LastExpressEngine::emptyHandler(Event *event) { // No-op } /////////////////////////////////////////////////////////////////////////////////// /// Misc Engine /////////////////////////////////////////////////////////////////////////////////// bool LastExpressEngine::hasFeature(EngineFeature f) const { return (f == kSupportsReturnToLauncher); } } // End of namespace LastExpress