/* 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. * * $URL$ * $Id$ * */ #ifndef AGI_H #define AGI_H #include "common/stdafx.h" #include "common/scummsys.h" #include "common/endian.h" #include "common/util.h" #include "common/file.h" #include "common/savefile.h" #include "common/system.h" #include "common/hash-str.h" #include "engines/engine.h" namespace Agi { typedef signed int Err; /* * Version and other definitions */ #define TITLE "AGI engine" #define DIR_ "dir" #define LOGDIR "logdir" #define PICDIR "picdir" #define VIEWDIR "viewdir" #define SNDDIR "snddir" #define OBJECTS "object" #define WORDS "words.tok" #define MAX_DIRS 256 #define MAX_VARS 256 #define MAX_FLAGS (256 >> 3) #define MAX_VIEWTABLE 255 /* KQ3 uses o255! */ #define MAX_WORDS 20 #define MAX_STRINGS 24 /* MAX_STRINGS + 1 used for get.num */ #define MAX_STRINGLEN 40 #ifndef MAX_PATH #define MAX_PATH 260 #endif #define _EMPTY 0xfffff #define EGO_OWNED 0xff #define CRYPT_KEY_SIERRA "Avis Durgan" #define CRYPT_KEY_AGDS "Alex Simkin" #ifndef INLINE #define INLINE #endif #define MSG_BOX_COLOUR 0x0f /* White */ #define MSG_BOX_TEXT 0x00 /* Black */ #define MSG_BOX_LINE 0x04 /* Red */ #define BUTTON_BORDER 0x00 /* Black */ #define STATUS_FG 0x00 /* Black */ #define STATUS_BG 0x0f /* White */ #define ADD_PIC 1 #define ADD_VIEW 2 enum AgiGameID { GID_AGIDEMO, GID_BC, GID_DDP, GID_GOLDRUSH, GID_KQ1, GID_KQ2, GID_KQ3, GID_KQ4, GID_LSL1, GID_MH1, GID_MH2, GID_MIXEDUP, GID_PQ1, GID_SQ1, GID_SQ2, GID_XMASCARD, GID_FANMADE // TODO: Should this be extended to include all fanmade games? }; } // End of namespace Agi /* AGI resources */ #include "agi/console.h" #include "agi/view.h" #include "agi/picture.h" #include "agi/logic.h" #include "agi/sound.h" namespace Agi { enum AgiGameType { GType_V2 = 1, GType_V3 }; enum AgiGameFeatures { GF_AGIMOUSE = (1 << 0), GF_AGDS = (1 << 1), GF_AGI256 = (1 << 2), GF_AGI256_2 = (1 << 3), GF_AGIPAL = (1 << 4), GF_MACGOLDRUSH = (1 << 5), GF_FANMADE = (1 << 6), GF_MENUS = (1 << 7), GF_ESCPAUSE = (1 << 8) }; struct AGIGameDescription; enum { NO_GAMEDIR = 0, GAMEDIR }; enum AGIErrors { errOK = 0, errDoNothing, errBadCLISwitch, errInvalidAGIFile, errBadFileOpen, errNotEnoughMemory, errBadResource, errUnknownAGIVersion, errRestartGame, errNoLoopsInView, errViewDataError, errNoGameList, errIOError, errUnk = 127 }; enum kDebugLevels { kDebugLevelMain = 1 << 0, kDebugLevelResources = 1 << 1, kDebugLevelSprites = 1 << 2, kDebugLevelInventory = 1 << 3, kDebugLevelInput = 1 << 4, kDebugLevelMenu = 1 << 5, kDebugLevelScripts = 1 << 6, kDebugLevelSound = 1 << 7, kDebugLevelText = 1 << 8, kDebugLevelSavegame = 1 << 9 }; /** * AGI resources. */ enum { rLOGIC = 1, rSOUND, rVIEW, rPICTURE }; enum { RES_LOADED = 1, RES_COMPRESSED = 0x40 }; enum { lCOMMAND_MODE = 1, lTEST_MODE }; struct gameIdList { gameIdList *next; uint32 version; uint32 crc; char *gName; char *switches; }; struct Mouse { int button; unsigned int x; unsigned int y; }; #define report printf enum GameId { GID_AGI = 1 }; #define WIN_TO_PIC_X(x) ((x) / 2) #define WIN_TO_PIC_Y(y) ((y) < 8 ? 999 : (y) >= (8 + _HEIGHT) ? 999 : (y) - 8) /** * AGI variables. */ enum { vCurRoom = 0, /* 0 */ vPrevRoom, vBorderTouchEgo, vScore, vBorderCode, vBorderTouchObj, /* 5 */ vEgoDir, vMaxScore, vFreePages, vWordNotFound, vTimeDelay, /* 10 */ vSeconds, vMinutes, vHours, vDays, vJoystickSensitivity, /* 15 */ vEgoViewResource, vAgiErrCode, vAgiErrCodeInfo, vKey, vComputer, /* 20 */ vWindowReset, vSoundgen, vVolume, vMaxInputChars, vSelItem, /* 25 */ vMonitor }; /** * Different monitor types. * Used with AGI variable 26 i.e. vMonitor. */ enum AgiMonitorType { kAgiMonitorCga = 0, // kAgiMonitorTandy = 1, // Not sure about this kAgiMonitorHercules = 2, kAgiMonitorEga = 3 // kAgiMonitorVga = 4 // Not sure about this }; /** * AGI flags */ enum { fEgoWater = 0, /* 0 */ fEgoInvisible, fEnteredCli, fEgoTouchedP2, fSaidAcceptedInput, fNewRoomExec, /* 5 */ fRestartGame, fScriptBlocked, fJoySensitivity, fSoundOn, fDebuggerOn, /* 10 */ fLogicZeroFirsttime, fRestoreJustRan, fStatusSelectsItems, fMenusWork, fOutputMode, /* 15 */ fAutoRestart }; struct AgiEvent { uint16 data; uint8 occured; }; struct AgiObject { int location; char *name; }; struct AgiWord { int id; char *word; }; struct AgiDir { uint8 volume; uint32 offset; uint32 len; uint32 clen; uint8 flags; /* 0 = not in mem, can be freed * 1 = in mem, can be released * 2 = not in mem, cant be released * 3 = in mem, cant be released * 0x40 = was compressed */ }; struct AgiBlock { int active; int x1, y1; int x2, y2; uint8 *buffer; /* used for window background */ }; /** AGI text color (Background and foreground color). */ struct AgiTextColor { /** Creates an AGI text color. Uses black text on white background by default. */ AgiTextColor(int fgColor = 0x00, int bgColor = 0x0F) : fg(fgColor), bg(bgColor) {} /** Get an AGI text color with swapped foreground and background color. */ AgiTextColor swap() const { return AgiTextColor(bg, fg); } int fg; ///< Foreground color (Used for text). int bg; ///< Background color (Used for text's background). }; /** * AGI button style (Amiga or PC). * * Supports positive and negative button types (Used with Amiga-style only): * Positive buttons do what the dialog was opened for. * Negative buttons cancel what the dialog was opened for. * Restart-dialog example: Restart-button is positive, Cancel-button negative. * Paused-dialog example: Continue-button is positive. */ struct AgiButtonStyle { // Public constants etc public: static const int // Amiga colors (Indexes into the Amiga-ish palette) amigaBlack = 0x00, ///< Accurate, is #000000 (24-bit RGB) amigaWhite = 0x0F, ///< Practically accurate, is close to #FFFFFF (24-bit RGB) amigaGreen = 0x02, ///< Quite accurate, should be #008A00 (24-bit RGB) amigaOrange = 0x0C, ///< Inaccurate, too much blue, should be #FF7500 (24-bit RGB) amigaPurple = 0x0D, ///< Inaccurate, too much green, should be #FF00FF (24-bit RGB) amigaRed = 0x04, ///< Quite accurate, should be #BD0000 (24-bit RGB) amigaCyan = 0x0B, ///< Inaccurate, too much red, should be #00FFDE (24-bit RGB) // PC colors (Indexes into the EGA-palette) pcBlack = 0x00, pcWhite = 0x0F; // Public methods public: /** * Get the color of the button with the given state and type using current style. * * @param hasFocus True if button has focus, false otherwise. * @param pressed True if button is being pressed, false otherwise. * @param positive True if button is positive, false if button is negative. Only matters for Amiga-style buttons. */ AgiTextColor getColor(bool hasFocus, bool pressed, bool positive = true) const; /** * Get the color of a button with the given base color and state ignoring current style. * Swaps foreground and background color when the button has focus or is being pressed. * * @param hasFocus True if button has focus, false otherwise. * @param pressed True if button is being pressed, false otherwise. * @param baseFgColor Foreground color of the button when it has no focus and is not being pressed. * @param baseBgColor Background color of the button when it has no focus and is not being pressed. */ AgiTextColor getColor(bool hasFocus, bool pressed, int baseFgColor, int baseBgColor) const; /** * Get the color of a button with the given base color and state ignoring current style. * Swaps foreground and background color when the button has focus or is being pressed. * * @param hasFocus True if button has focus, false otherwise. * @param pressed True if button is being pressed, false otherwise. * @param baseColor Color of the button when it has no focus and is not being pressed. */ AgiTextColor getColor(bool hasFocus, bool pressed, const AgiTextColor &baseColor) const; /** * How many pixels to offset the shown text diagonally down and to the right. * Currently only used for pressed PC-style buttons. */ int getTextOffset(bool hasFocus, bool pressed) const; /** * Show border around the button? * Currently border is only used for in focus or pressed Amiga-style buttons * when in inauthentic Amiga-style mode. */ bool getBorder(bool hasFocus, bool pressed) const; /** * Set Amiga-button style. * * @param amigaStyle Set Amiga-button style if true, otherwise set PC-button style. * @param olderAgi If true then use older AGI style in Amiga-mode, otherwise use newer. * @param authenticAmiga If true then don't use a border around buttons in Amiga-mode, otherwise use. */ void setAmigaStyle(bool amigaStyle = true, bool olderAgi = false, bool authenticAmiga = false); /** * Set PC-button style. * @param pcStyle Set PC-button style if true, otherwise set default Amiga-button style. */ void setPcStyle(bool pcStyle = true); // Public constructors public: /** * Create a button style based on the given rendering mode. * @param renderMode If Common::kRenderAmiga then creates default Amiga-button style, otherwise PC-style. */ AgiButtonStyle(Common::RenderMode renderMode = Common::kRenderDefault); // Private member variables private: bool _amigaStyle; ///< Use Amiga-style buttons if true, otherwise use PC-style buttons. bool _olderAgi; ///< Use older AGI style in Amiga-style mode. bool _authenticAmiga; ///< Don't use border around buttons in Amiga-style mode. }; #define EGO_VIEW_TABLE 0 #define HORIZON 36 #define _WIDTH 160 #define _HEIGHT 168 /** * AGI game structure. * This structure contains all global data of an AGI game executed * by the interpreter. */ struct AgiGame { #define STATE_INIT 0x00 #define STATE_LOADED 0x01 #define STATE_RUNNING 0x02 int state; /**< state of the interpreter */ char name[8]; /**< lead in id (e.g. `GR' for goldrush) */ char id[8]; /**< game id */ uint32 crc; /**< game CRC */ /* game flags and variables */ uint8 flags[MAX_FLAGS]; /**< 256 1-bit flags */ uint8 vars[MAX_VARS]; /**< 256 variables */ /* internal variables */ int horizon; /**< horizon y coordinate */ int lineStatus; /**< line number to put status on */ int lineUserInput; /**< line to put user input on */ int lineMinPrint; /**< num lines to print on */ int cursorPos; /**< column where the input cursor is */ uint8 inputBuffer[40]; /**< buffer for user input */ uint8 echoBuffer[40]; /**< buffer for echo.line */ int keypress; #define INPUT_NORMAL 0x01 #define INPUT_GETSTRING 0x02 #define INPUT_MENU 0x03 #define INPUT_NONE 0x04 int inputMode; /**< keyboard input mode */ int inputEnabled; /**< keyboard input enabled */ int lognum; /**< current logic number */ /* internal flags */ int playerControl; /**< player is in control */ int quitProgNow; /**< quit now */ int statusLine; /**< status line on/off */ int clockEnabled; /**< clock is on/off */ int exitAllLogics; /**< break cycle after new.room */ int pictureShown; /**< show.pic has been issued */ int hasPrompt; /**< input prompt has been printed */ #define ID_AGDS 0x00000001 #define ID_AMIGA 0x00000002 int gameFlags; /**< agi options flags */ uint8 priTable[_HEIGHT];/**< priority table */ /* windows */ uint32 msgBoxTicks; /**< timed message box tick counter */ AgiBlock block; AgiBlock window; int hasWindow; /* graphics & text */ int gfxMode; char cursorChar; unsigned int colorFg; unsigned int colorBg; #define SBUF16_OFFSET 0 #define SBUF256_OFFSET ((_WIDTH) * (_HEIGHT)) #define FROM_SBUF16_TO_SBUF256_OFFSET ((SBUF256_OFFSET) - (SBUF16_OFFSET)) #define FROM_SBUF256_TO_SBUF16_OFFSET ((SBUF16_OFFSET) - (SBUF256_OFFSET)) uint8 *sbufOrig; /**< Pointer to the 160x336 AGI screen buffer that contains vertically two 160x168 screens (16 color and 256 color). */ uint8 *sbuf16c; /**< 160x168 16 color (+control line & priority information) AGI screen buffer. Points at sbufOrig + SBUF16_OFFSET. */ uint8 *sbuf256c; /**< 160x168 256 color AGI screen buffer (For AGI256 and AGI256-2 support). Points at sbufOrig + SBUF256_OFFSET. */ uint8 *sbuf; /**< Currently chosen AGI screen buffer (sbuf256c if AGI256 or AGI256-2 is used, otherwise sbuf16c). */ /* player command line */ AgiWord egoWords[MAX_WORDS]; int numEgoWords; unsigned int numObjects; AgiEvent evKeyp[MAX_DIRS]; /**< keyboard keypress events */ char strings[MAX_STRINGS + 1][MAX_STRINGLEN]; /**< strings */ /* directory entries for resources */ AgiDir dirLogic[MAX_DIRS]; AgiDir dirPic[MAX_DIRS]; AgiDir dirView[MAX_DIRS]; AgiDir dirSound[MAX_DIRS]; /* resources */ AgiPicture pictures[MAX_DIRS]; /**< AGI picture resources */ AgiLogic logics[MAX_DIRS]; /**< AGI logic resources */ AgiView views[MAX_DIRS]; /**< AGI view resources */ AgiSound *sounds[MAX_DIRS]; /**< Pointers to AGI sound resources */ /* view table */ VtEntry viewTable[MAX_VIEWTABLE]; int32 ver; /**< detected game version */ int simpleSave; /**< select simple savegames */ }; class AgiLoader { private: int intVersion; AgiEngine *_vm; public: AgiLoader() {} virtual ~AgiLoader() {} virtual int init() = 0; virtual int deinit() = 0; virtual int detectGame() = 0; virtual int loadResource(int, int) = 0; virtual int unloadResource(int, int) = 0; virtual int loadObjects(const char *) = 0; virtual int loadWords(const char *) = 0; virtual int version() = 0; virtual void setIntVersion(int) = 0; virtual int getIntVersion() = 0; }; class AgiLoader_v2 : public AgiLoader { private: int _intVersion; AgiEngine *_vm; int loadDir(AgiDir *agid, const char *fname); uint8 *loadVolRes(AgiDir *agid); public: AgiLoader_v2(AgiEngine *vm) { _vm = vm; _intVersion = 0; } virtual int init(); virtual int deinit(); virtual int detectGame(); virtual int loadResource(int, int); virtual int unloadResource(int, int); virtual int loadObjects(const char *); virtual int loadWords(const char *); virtual int version(); virtual void setIntVersion(int); virtual int getIntVersion(); }; class AgiLoader_v3 : public AgiLoader { private: int _intVersion; AgiEngine *_vm; int loadDir(AgiDir *agid, Common::File *fp, uint32 offs, uint32 len); uint8 *loadVolRes(AgiDir *agid); public: AgiLoader_v3(AgiEngine *vm) { _vm = vm; _intVersion = 0; } virtual int init(); virtual int deinit(); virtual int detectGame(); virtual int loadResource(int, int); virtual int unloadResource(int, int); virtual int loadObjects(const char *); virtual int loadWords(const char *); virtual int version(); virtual void setIntVersion(int); virtual int getIntVersion(); }; class GfxMgr; class SpritesMgr; class Menu; class SearchTree; extern struct Mouse g_mouse; /* Image stack support */ struct ImageStackElement { uint8 type; uint8 pad; int16 parm1; int16 parm2; int16 parm3; int16 parm4; int16 parm5; int16 parm6; int16 parm7; }; struct StringData { int x; int y; int len; int str; }; #define TICK_SECONDS 20 #define KEY_QUEUE_SIZE 16 class AgiEngine : public ::Engine { int _gameId; protected: int init(); int go(); void shutdown(); void initialize(); bool initGame(); public: AgiEngine(OSystem *syst); virtual ~AgiEngine(); int getGameId() { return _gameId; } const AGIGameDescription *_gameDescription; uint32 getGameID() const; uint32 getFeatures() const; uint16 getVersion() const; Common::Platform getPlatform() const; private: int _keyQueue[KEY_QUEUE_SIZE]; int _keyQueueStart; int _keyQueueEnd; bool _allowSynthetic; int checkPriority(VtEntry *v); int checkCollision(VtEntry *v); int checkPosition(VtEntry *v); uint32 matchVersion(uint32 crc); int _firstSlot; public: AgiGame _game; AgiObject *_objects; /* objects in the game */ StringData _stringdata; AgiLoader *_loader; /* loader */ Common::RandomSource *_rnd; const char *getSavegameFilename(int num); void getSavegameDescription(int num, char *buf, bool showEmpty = true); int selectSlot(); int saveGame(const char *fileName, const char *saveName); int saveGameDialog(); int saveGameSimple(); int loadGame(const char *fileName, bool checkId = true); int loadGameDialog(); int loadGameSimple(); volatile uint32 _clockCount; uint8 *_intobj; int _oldMode; Menu* _menu; AgiButtonStyle _buttonStyle; AgiButtonStyle _defaultButtonStyle; char _lastSentence[40]; SpritesMgr *_sprites; GfxMgr *_gfx; SoundMgr *_sound; PictureMgr *_picture; #define INITIAL_IMAGE_STACK_SIZE 32 int _stackSize; ImageStackElement *_imageStack; int _imageStackPointer; void clearImageStack(); void recordImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3, int16 p4, int16 p5, int16 p6, int16 p7); void replayImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3, int16 p4, int16 p5, int16 p6, int16 p7); void releaseImageStack(); AgiDebug _debug; Common::RenderMode _renderMode; int _soundemu; int _keyControl; int _keyAlt; Console *_console; int agiInit(); int agiDeinit(); int agiVersion(); int agiGetRelease(); void agiSetRelease(int); int agiDetectGame(); int agiLoadResource(int, int); int agiUnloadResource(int, int); void agiUnloadResources(); void agiTimerLow(); int agiGetKeypressLow(); int agiIsKeypressLow(); static void agiTimerFunctionLow(void *refCon); void initPriTable(); void newInputMode(int); void oldInputMode(); int getflag(int); void setflag(int, int); void flipflag(int); int getvar(int); void setvar(int, int); void decrypt(uint8 * mem, int len); void releaseSprites(); int mainCycle(); int viewPictures(); int parseCli(int, char **); int runGame(); void inventory(); void listGames(); uint32 matchCrc(uint32, char *, int); int v2IdGame(); int v3IdGame(); int v4IdGame(uint32 ver); void updateTimer(); int getAppDir(char *appDir, unsigned int size); int setupV2Game(int ver, uint32 crc); int setupV3Game(int ver, uint32 crc); void newRoom(int n); void resetControllers(); void interpretCycle(); int playGame(); void printItem(int n, int fg, int bg); int findItem(); int showItems(); void selectItems(int n); void allowSynthetic(bool); void processEvents(); // Objects int showObjects(); int decodeObjects(uint8 *mem, uint32 flen); int loadObjects(const char *fname); int allocObjects(int); void unloadObjects(); const char *objectName(unsigned int); int objectGetLocation(unsigned int); void objectSetLocation(unsigned int, int); // Logic int decodeLogic(int); void unloadLogic(int); int runLogic(int); void debugConsole(int, int, const char *); int testIfCode(int); void executeAgiCommand(uint8, uint8 *); // View private: void lSetCel(VtEntry *v, int n); void lSetLoop(VtEntry *v, int n); void updateView(VtEntry *v); public: void setCel(VtEntry *, int); void setLoop(VtEntry *, int); void setView(VtEntry *, int); void startUpdate(VtEntry *); void stopUpdate(VtEntry *); void updateViewtable(); void unloadView(int); int decodeView(int); void addToPic(int, int, int, int, int, int, int); void drawObj(int); bool isEgoView(const VtEntry *v); // Words int showWords(); int loadWords(const char *); void unloadWords(); int findWord(char *word, int *flen); void dictionaryWords(char *); // Motion private: int checkStep(int delta, int step); int checkBlock(int x, int y); void changePos(VtEntry *v); void motionWander(VtEntry *v); void motionFollowEgo(VtEntry *v); void motionMoveObj(VtEntry *v); void checkMotion(VtEntry *v); public: void checkAllMotions(); void moveObj(VtEntry *); void inDestination(VtEntry *); void fixPosition(int); void updatePosition(); int getDirection(int x0, int y0, int x, int y, int s); // Keyboard void initWords(); void cleanInput(); int doPollKeyboard(); void cleanKeyboard(); void handleKeys(int); void handleGetstring(int); int handleController(int); void getString(int, int, int, int); uint16 agiGetKeypress(); int waitKey(); int waitAnyKey(); // Text public: #define MAXWORDLEN 24 typedef Common::String String; int messageBox(const char *); int selectionBox(const char *, const char **); void closeWindow(void); void drawWindow(int, int, int, int); void printText(const char *, int, int, int, int, int, int, bool checkerboard = false); void printTextConsole(const char *, int, int, int, int, int); int print(const char *, int, int, int); char *wordWrapString(char *, int *); char *agiSprintf(const char *); void writeStatus(void); void writePrompt(void); void clearLines(int, int, int); void flushLines(int, int); bool predictiveDialog(void); private: void printStatus(const char *message, ...); void printText2(int l, const char *msg, int foff, int xoff, int yoff, int len, int fg, int bg, bool checkerboard = false); void blitTextbox(const char *p, int y, int x, int len); void eraseTextbox(); char *safeStrcat(char *s, const char *t); void loadDict(void); bool matchWord(void); // Predictive dialog // TODO: Move this to a separate class char *_predictiveDictText; char **_predictiveDictLine; int32 _predictiveDictLineCount; char *_predictiveDictActLine; String _currentCode; String _currentWord; int _wordNumber; bool _predictiveDialogRunning; public: char _predictiveResult[40]; }; } // End of namespace Agi #endif /* AGI_H */