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

757 lines
16 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/>.
*
*/
#ifndef LASTEXPRESS_LASTEXPRESS_H
#define LASTEXPRESS_LASTEXPRESS_H
#include "lastexpress/debug.h"
#include "lastexpress/game/beetle.h"
#include "lastexpress/game/vcr.h"
#include "lastexpress/game/savegame.h"
#include "lastexpress/sound/slot.h"
#include "lastexpress/sound/sound.h"
#include "lastexpress/sound/subtitle.h"
#include "lastexpress/game/logic.h"
#include "lastexpress/data/sprites.h"
#include "lastexpress/game/nis.h"
#include "lastexpress/game/otis.h"
#include "lastexpress/game/events.h"
#include "lastexpress/fight/fight.h"
#include "lastexpress/menu/menu.h"
#include "lastexpress/graphics.h"
#include "lastexpress/memory.h"
#include "common/random.h"
#include "common/file.h"
#include "engines/engine.h"
#include "graphics/pixelformat.h"
struct ADGameDescription;
/**
* This is the namespace of the LastExpress engine.
*
* Status of this engine:
* The engine is completely implemented, the game is fully playable
* and the code is as faithful to the original disassembly as it reasonably can be.
*
* Notable features:
* - Original savegames are supported and swappable from ScummVM to the original
* (and viceversa).
* - The debugger uses DearImGui and provides a real-time visualization of all the
* characters movements.
*
* Original maintainers:
* littleboy, jvprat, clone2727
*
* Engine rewrite/additional reverse engineering:
* Bosca
*
* Supported games:
* - The Last Express
*/
namespace LastExpress {
class ArchiveManager;
class GoldArchiveManager;
class CBeetle;
class CFight;
class Clock;
class GraphicsManager;
class LogicManager;
class MemoryManager;
class Menu;
class MessageManager;
class NISManager;
class OtisManager;
class ResourceManager;
class SaveManager;
class Slot;
class SoundManager;
class SpriteManager;
class SubtitleManager;
class VCR;
struct Extent;
enum {
GF_COMPRESSED = 1 << 0
};
typedef struct Item {
uint8 mnum;
uint16 closeUp;
int8 useable;
int8 haveIt;
int8 inPocket;
int8 floating;
Item() {
clear();
}
void clear() {
mnum = 0;
closeUp = 0;
useable = 0;
haveIt = 0;
inPocket = 0;
floating = 0;
}
} Item;
typedef struct Door {
int8 who;
int8 status;
uint8 windowCursor;
uint8 handleCursor;
int8 model;
Door() {
clear();
}
void clear() {
who = 0;
status = 0;
windowCursor = 0;
handleCursor = 0;
model = 0;
}
} Door;
typedef struct Position {
uint16 position;
uint16 location;
uint16 car;
Position() {
clear();
}
void clear() {
position = 0;
location = 0;
car = 0;
}
} Position;
typedef struct Sprite {
byte *compData;
byte *eraseMask;
uint16 *colorPalette;
Extent rect;
uint16 hotspotX1;
uint16 hotspotX2;
uint16 hotspotY1;
uint16 hotspotY2;
byte compBits;
byte compType;
byte copyScreenAndRedrawFlag;
byte spritesUnk3;
byte ticksToWaitUntilCycleRestart;
byte soundDelay;
byte soundAction;
byte flags;
byte position;
byte spritesUnk9;
byte spritesUnk10;
byte spritesUnk11;
int spritesUnk8;
uint16 visibilityDist;
uint16 hotspotPriority;
Sprite *nextSprite;
Sprite() {
compData = nullptr;
eraseMask = nullptr;
colorPalette = nullptr;
hotspotX1 = 0;
hotspotX2 = 0;
hotspotY1 = 0;
hotspotY2 = 0;
compBits = 0;
compType = 0;
copyScreenAndRedrawFlag = 0;
spritesUnk3 = 0;
ticksToWaitUntilCycleRestart = 0;
soundDelay = 0;
soundAction = 0;
flags = 0;
position = 0;
spritesUnk9 = 0;
spritesUnk10 = 0;
spritesUnk11 = 0;
spritesUnk8 = 0;
visibilityDist = 0;
hotspotPriority = 0;
nextSprite = nullptr;
}
} Sprite;
typedef struct Seq {
int numFrames;
Sprite *sprites;
Sprite spriteArrayStart;
byte gap[18];
byte *rawSeqData;
Seq() {
numFrames = 0;
sprites = nullptr;
memset(gap, 0, sizeof(gap));
rawSeqData = nullptr;
}
~Seq() {
delete[] sprites;
sprites = nullptr;
}
} Seq;
typedef struct CharacterCallParams {
int parameters[32];
CharacterCallParams() {
clear();
}
void clear() {
for (int i = 0; i < 32; i++) {
parameters[i] = 0;
}
}
} CharacterCallParams;
typedef struct Character {
CharacterCallParams callParams[9];
uint8 callbacks[16];
uint8 currentCall;
Position characterPosition;
uint8 walkCounter;
int8 attachedConductor;
uint8 inventoryItem;
int8 direction;
int16 waitedTicksUntilCycleRestart;
int16 currentFrameSeq1;
int16 currentFrameSeq2;
int16 elapsedFrames;
int16 walkStepSize;
int8 clothes;
int8 position2;
int8 car2;
int8 doProcessEntity;
int8 needsPosFudge;
int8 needsSecondaryPosFudge;
int8 directionSwitch;
char sequenceName[13];
char sequenceName2[13];
char sequenceNamePrefix[7];
char sequenceNameCopy[13];
Sprite *frame1;
Sprite *frame2;
Seq *sequence1;
Seq *sequence2;
Seq *sequence3;
Character() {
reset();
}
void reset() {
memset(callbacks, 0, sizeof(callbacks));
currentCall = 0;
walkCounter = 0;
attachedConductor = 0;
inventoryItem = 0;
direction = 0;
waitedTicksUntilCycleRestart = 0;
currentFrameSeq1 = 0;
currentFrameSeq2 = 0;
elapsedFrames = 0;
walkStepSize = 0;
clothes = 0;
position2 = 0;
car2 = 0;
doProcessEntity = 0;
needsPosFudge = 0;
needsSecondaryPosFudge = 0;
directionSwitch = 0;
memset(sequenceName, 0, sizeof(sequenceName));
memset(sequenceName2, 0, sizeof(sequenceName2));
memset(sequenceNamePrefix, 0, sizeof(sequenceNamePrefix));
memset(sequenceNameCopy, 0, sizeof(sequenceNameCopy));
frame1 = nullptr;
frame2 = nullptr;
sequence1 = nullptr;
sequence2 = nullptr;
sequence3 = nullptr;
}
} Character;
typedef struct Characters {
Character characters[40];
} Characters;
typedef struct Line7 {
int slope;
int intercept;
char lineType;
Line7 *next;
Line7() {
slope = 0;
intercept = 0;
lineType = 0;
next = nullptr;
}
~Line7() {
if (next) {
delete next;
next = nullptr;
}
}
} Line7;
typedef struct Link {
uint16 left;
uint16 right;
uint16 top;
uint16 bottom;
Line7 *lineList;
uint16 scene;
uint8 location;
uint8 action;
uint8 param1;
uint8 param2;
uint8 param3;
uint8 cursor;
Link *next;
Link() {
left = 0;
right = 0;
top = 0;
bottom = 0;
lineList = nullptr;
scene = 0;
location = 0;
action = 0;
param1 = 0;
param2 = 0;
param3 = 0;
cursor = 0;
next = nullptr;
}
~Link() {
if (lineList) {
delete lineList;
lineList = nullptr;
}
if (next) {
delete next;
next = nullptr;
}
}
void copyFrom(Link *otherLink) {
left = otherLink->left;
right = otherLink->right;
top = otherLink->top;
bottom = otherLink->bottom;
lineList = nullptr;
scene = otherLink->scene;
location = otherLink->location;
action = otherLink->action;
param1 = otherLink->param1;
param2 = otherLink->param2;
param3 = otherLink->param3;
cursor = otherLink->cursor;
next = nullptr;
}
} Link;
typedef struct Node {
char sceneFilename[9];
Position nodePosition;
uint8 cathDir;
uint8 property;
uint8 parameter1;
uint8 parameter2;
uint8 parameter3;
Link *link;
Node() {
memset(sceneFilename, 0, sizeof(sceneFilename));
cathDir = 0;
property = 0;
parameter1 = 0;
parameter2 = 0;
parameter3 = 0;
link = nullptr;
}
~Node() {
if (link) {
delete link;
link = nullptr;
}
}
} Node;
#include "common/pack-start.h"
typedef struct TGAHeader {
uint8 idLength;
uint8 colorMapType;
uint8 imageType;
uint16 colorMapFirstEntryIndex;
uint16 colorMapLength;
uint8 colorMapEntrySize;
uint16 xOrigin;
uint16 yOrigin;
uint16 width;
uint16 height;
uint8 bitsPerPixel;
uint8 imageDescriptor;
byte *rawDataPtr; // Not in the original, but we need it to avoid pointer fix-ups...
TGAHeader() {
idLength = 0;
colorMapType = 0;
imageType = 0;
colorMapFirstEntryIndex = 0;
colorMapLength = 0;
colorMapEntrySize = 0;
xOrigin = 0;
yOrigin = 0;
width = 0;
height = 0;
bitsPerPixel = 0;
imageDescriptor = 0;
rawDataPtr = nullptr;
}
} TGAHeader;
#include "common/pack-end.h"
typedef struct FontData {
uint16 palette[16];
uint8 charMap[256];
uint8 charKerning[256];
byte *fontData;
FontData() {
memset(palette, 0, sizeof(palette));
memset(charMap, 0, sizeof(charMap));
memset(charKerning, 0, sizeof(charKerning));
fontData = nullptr;
}
~FontData() {
memset(palette, 0, sizeof(palette));
memset(charMap, 0, sizeof(charMap));
memset(charKerning, 0, sizeof(charKerning));
if (fontData) {
free(fontData);
fontData = nullptr;
}
}
} FontData;
typedef struct NisSprite {
byte *compData;
byte *eraseMask;
uint16 *colorPalette;
Extent rect;
uint16 gammaPalette[128];
byte compBits;
NisSprite() {
compData = nullptr;
eraseMask = nullptr;
colorPalette = nullptr;
memset(gammaPalette, 0, sizeof(gammaPalette));
compBits = 0;
}
} NisSprite;
typedef struct NisEvents {
int16 eventType;
int16 eventTime;
int32 eventSize;
NisEvents() {
eventType = 0;
eventTime = 0;
eventSize = 0;
}
} NisEvents;
#define MEM_PAGE_SIZE 0x800
#define DEMO_TIMEOUT 2700
typedef uint16 PixMap;
class LastExpressEngine : public Engine {
// AARGH! Remove!
friend class VCR;
friend class SubtitleManager;
protected:
// Engine APIs
Common::Error run() override;
bool hasFeature(EngineFeature f) const override;
public:
LastExpressEngine(OSystem *syst, const ADGameDescription *gd);
~LastExpressEngine() override;
// Misc
Common::RandomSource& getRandom() {return _random; }
// Managers
ArchiveManager *getArchiveManager() const { return _archiveMan; }
GraphicsManager *getGraphicsManager() const { return _graphicsMan; }
SubtitleManager *getSubtitleManager() const { return _subtitleMan; }
MemoryManager *getMemoryManager() const { return _memMan; }
MessageManager *getMessageManager() const { return _msgMan; }
NISManager *getNISManager() const { return _nisMan; }
SoundManager *getSoundManager() const { return _soundMan; }
LogicManager *getLogicManager() const { return _logicMan; }
OtisManager *getOtisManager() const { return _otisMan; }
SpriteManager *getSpriteManager() const { return _spriteMan; }
SaveManager *getSaveManager() const { return _saveMan; }
VCR *getVCR() const { return _vcr; }
Menu *getMenu() const { return _menu; }
Clock *getClock() const { return _clock; }
// Event handling
bool handleEvents();
bool isDemo() const;
bool isGoldEdition() const;
bool isCompressed() const;
Common::String getTargetName() const;
// NEW STUFF
static void soundTimerHandler(void *refCon);
int32 _soundFrameCounter = 0;
Common::Mutex *_soundMutex;
const char *_savegameFilename = nullptr;
const char *_savegameTempNames[7] = {
"blue.tmp",
"red.tmp",
"green.tmp",
"purple.tmp",
"teal.tmp",
"gold.tmp",
"temp.tmp"
};
const char *_savegameNames[6] = {
"blue.egg",
"red.egg",
"green.egg",
"purple.egg",
"teal.egg",
"gold.egg"
};
CVCRFile *_savegame = nullptr;
int32 _currentGameFileColorId = -1;
int32 _gracePeriodTimer = 0;
bool _eventTickInternal = false;
bool _doShowCredits = false;
int32 _currentSavePoint = 0;
int32 _lastSavePointIdInFile = 0;
int32 _gameTimeOfLastSavePointInFile = 0;
int32 _gracePeriodIndex = 0;
int32 _fightSkipCounter = 0;
bool _navigationEngineIsRunning = false;
bool _stopUpdatingCharacters = false;
bool _charactersUpdateRecursionFlag = false;
bool _mouseHasLeftClicked = false;
bool _mouseHasRightClicked = false;
bool _fastWalkJustDeactivated = false;
int32 _cursorX = 320;
int32 _cursorY = 240;
int32 _cursorXOffscreenOffset = 0;
int32 _cursorYOffscreenOffset = 0;
int _cursorType = 0;
bool _systemEventLeftMouseDown = false;
bool _systemEventRightMouseDown = false;
Common::Point _systemEventLastMouseCoords;
bool _pendingExitEvent = false;
bool _exitFromMenuButton = false;
int32 _lastForcedScreenUpdateTicks = 0; // Not in the original
Seq *_doorSeqs[8] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
Seq *_clockHandsSeqs[2] = { nullptr, nullptr };
int32 _clockHandsValues[2] = { 0, 0 };
int32 _fudgePosition[41] = {
0, 851, 1430, 2110, 0, 2410, 2980, 3450,
3760, 4100, 4680, 5140, 5440, 5810, 6410, 6850,
7160, 7510, 8514, 0, 0, 0, 2086, 2690,
0, 3110, 3390, 3890, 4460, 4770, 5090, 5610,
6160, 6460, 6800, 7320, 7870, 8160, 8500, 9020,
9269
};
// DYNAMIC ALLOC
byte *_globalMemoryPool = nullptr;
byte *_soundMemoryPool = nullptr;
byte *_cursorsMemoryPool = nullptr;
Characters *_characters = nullptr;
SVCRSavePointHeader *_savePointHeaders = nullptr;
// GENERAL
void startUp();
void shutDown();
void waitForTimer(int millis);
void initGameData();
void startNewGame();
void engineEventHandler(Event *event);
int32 getSoundFrameCounter();
bool getEventTickInternal();
void setEventTickInternal(bool flag);
void mouseSetLeftClicked(bool hasClicked);
void mouseSetRightClicked(bool hasClicked);
bool mouseHasLeftClicked();
bool mouseHasRightClicked();
// BEETLE
CBeetle *_beetle = nullptr;
void doBeetle();
void endBeetle();
void positionSprite(Sprite *sprite, Common::Point coord);
// CREDITS
void doCredits();
void abortCredits();
void creditsMouse(Event *event);
void creditsTimer(Event *event);
int32 readTGAIntoMemory(const char *filename, TGAHeader *tgaHeader);
void constructPalette(TGAHeader *tgaHeader, uint16 *palette);
// DEMO CREDITS
bool demoEnding(bool wonGame);
void demoEndingMouse(Event *event);
void demoEndingTimer(Event *event);
// EVENT HANDLERS
void engineEventHandlerWrapper(Event *event);
void nodeStepMouseWrapper(Event *event);
void nodeStepTimerWrapper(Event *event);
void eggMouseWrapper(Event *event);
void eggTimerWrapper(Event *event);
void nisMouseWrapper(Event *event);
void nisTimerWrapper(Event *event);
void creditsMouseWrapper(Event *event);
void creditsTimerWrapper(Event *event);
void demoEndingMouseWrapper(Event *event);
void demoEndingTimerWrapper(Event *event);
void fightMouseWrapper(Event *event);
void fightTimerWrapper(Event *event);
void emptyHandler(Event *event);
int _savedFrameCounter = 0;
int32 _savedFrameInterval = 0;
int _doCredits = 0;
void (LastExpressEngine::*_savedTimerEventHandle)(Event *) = nullptr;
void (LastExpressEngine::*_savedMouseEventHandle)(Event *) = nullptr;
// FIGHT
int doFight(int fightId);
void abortFight();
CFight *_fight = nullptr;
// DEBUGGER
void showEngineInfo();
bool _lockGracePeriod = false;
private:
const ADGameDescription *_gameDescription;
Graphics::PixelFormat _pixelFormat;
// Misc
Debugger *_debugger;
Common::RandomSource _random;
// Game
Menu *_menu = nullptr;
Clock *_clock = nullptr;
VCR *_vcr = nullptr;
// Managers
ArchiveManager *_archiveMan = nullptr;
GraphicsManager *_graphicsMan = nullptr;
SoundManager *_soundMan = nullptr;
LogicManager *_logicMan = nullptr;
OtisManager *_otisMan = nullptr;
SpriteManager *_spriteMan = nullptr;
MemoryManager *_memMan = nullptr;
MessageManager *_msgMan = nullptr;
NISManager *_nisMan = nullptr;
SaveManager *_saveMan = nullptr;
SubtitleManager *_subtitleMan = nullptr;
};
} // End of namespace LastExpress
#endif // LASTEXPRESS_LASTEXPRESS_H