Files
scummvm-cursorfix/engines/lastexpress/lastexpress.cpp
2026-02-02 04:50:13 +01:00

649 lines
18 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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