// JoeyAGI: an interpreter for Sierra's Adventure Game Interpreter (AGI) v2 // games, written from the public AGI specifications on JoeyLib. // // One game runs at a time, so the interpreter's state is a single AgiStateT // (gAgi), allocated at start-up with jlAlloc to keep it out of the IIgs's // bank-0 BSS. The modules: // agiRes.c resource directories, VOL files, WORDS.TOK, OBJECT // agiPic.c PICTURE decoding into the picture buffers // agiView.c VIEW parsing // agiGfx.c the picture buffers, screen objects' blitting, the stage // agiObj.c screen objects: cycling, motion, collisions // agiText.c text: display, windows, status and input lines, menus // agiInput.c the keyboard, the command line and the parser // agiSound.c PCjr sound resources on JoeyLib's tone and noise voices // agiSave.c save, restore and the picture script restore replays // agiVm.c the LOGIC interpreter // agi.c start-up and the interpreter cycle #ifndef AGI_H #define AGI_H #include "joey/types.h" #include "joey/surface.h" #include // ----- Limits and the game's geometry ----- // Resource numbers are one byte; directories may name all 256. #define AGI_MAX_RESOURCES 256u // VOL.x files: the directory's volume nibble. #define AGI_MAX_VOLUMES 16u // The picture is 160x168 AGI pixels; each is two screen pixels wide, so // it fills 320x168 of the 320x200 screen, below the status line. #define AGI_WIDTH 160 #define AGI_HEIGHT 168 #define AGI_PIXELS ((uint16_t)AGI_WIDTH * (uint16_t)AGI_HEIGHT) #define AGI_MAX_X (AGI_WIDTH - 1) #define AGI_MAX_Y (AGI_HEIGHT - 1) #define AGI_SCREEN_PX_PER_AGI 2 // The 40x25 text grid of 8x8 character cells. #define AGI_TEXT_COLS 40u #define AGI_TEXT_ROWS 25u #define AGI_CELL_PX 8 // Colours. The picture starts white with priority 4 (red) everywhere. #define AGI_COLOR_BLACK 0u #define AGI_COLOR_RED 4u #define AGI_COLOR_WHITE 15u #define AGI_COLOR_COUNT 16u #define AGI_COLOR_MASK 0x0Fu #define AGI_PIC_BG_COLOR AGI_COLOR_WHITE #define AGI_PIC_BG_PRIORITY 4u // Priority values below 4 are control lines drawn into the priority // screen: an unconditional barrier, a conditional barrier (blocks), a // signal (alarm) line and water. #define AGI_CTL_BARRIER 0u #define AGI_CTL_BLOCK 1u #define AGI_CTL_SIGNAL 2u #define AGI_CTL_WATER 3u #define AGI_PRIORITY_MIN 4u #define AGI_PRIORITY_MAX 15u // Priority bands: y below 48 is priority 4, then a band every 12 lines // from priority 5 at y 48 to 14 at y 156..167. #define AGI_PRI_BAND_TOP 48 #define AGI_PRI_BAND_HEIGHT 12 #define AGI_PRI_FIRST_BAND 5u // The horizon new.room restores. #define AGI_DEFAULT_HORIZON 36u // Animated (screen) objects: OBJECT's header says how many a game uses. #define AGI_MAX_OBJECTS 32u #define AGI_EGO 0u // Inventory items and the 40-character strings. #define AGI_MAX_ITEMS 256u #define AGI_ITEM_CARRIED 255u #define AGI_MAX_STRINGS 24u #define AGI_STRING_LEN 40u // Variables, flags, controllers (set.key and menu items share one space). #define AGI_NUM_VARS 256u #define AGI_NUM_FLAGS 256u #define AGI_NUM_CONTROLLERS 256u #define AGI_MAX_KEYMAPS 64u #define AGI_SCRIPT_MAX 200u #define AGI_SCRIPT_ARGS 7u // Menus. #define AGI_MAX_MENUS 10u #define AGI_MAX_MENU_ITEMS 48u #define AGI_MENU_NAME_MAX 20u #define AGI_MENU_ITEM_MAX 38u // The command line and the parser. #define AGI_INPUT_MAX 40u #define AGI_MAX_WORDS 12u #define AGI_WORD_MAX_LEN 40u #define AGI_WORD_IGNORE 0u #define AGI_WORD_ANY 1u #define AGI_WORD_REST 9999u #define AGI_WORD_NONE 0xFFFFu #define AGI_WORDS_HEADER_BYTES 52u #define AGI_WORDS_MAX 16384u // Key codes as AGI sees them: the character, or the PC scan code in the // high byte. Joystick buttons are 0x0101 and 0x0201. #define AGI_KEY_NONE 0u #define AGI_KEY_BACKSPACE 0x0008u #define AGI_KEY_ENTER 0x000Du #define AGI_KEY_ESCAPE 0x001Bu #define AGI_KEY_UP 0x4800u #define AGI_KEY_DOWN 0x5000u #define AGI_KEY_LEFT 0x4B00u #define AGI_KEY_RIGHT 0x4D00u #define AGI_KEY_JOY_BUTTON_1 0x0101u #define AGI_KEY_JOY_BUTTON_2 0x0201u // A menu.input or inventory answer meaning "nothing chosen". #define AGI_NO_CONTROLLER 0xFFu // inputGetString with no position: on the input line. #define AGI_NO_POSITION 0xFFu // No set.cursor.char yet: the input line shows no cursor. #define AGI_NO_CURSOR '\0' // The font (agiFont.c): characters 0x20..0x7E, 8 rows each. #define AGI_FONT_FIRST 0x20u #define AGI_FONT_GLYPHS 95u #define AGI_FONT_ROWS 8u extern const uint8_t kAgiFont[AGI_FONT_GLYPHS][AGI_FONT_ROWS]; // ----- Reserved variables and flags (AGI specification 3.3 and 3.4) ----- #define VAR_ROOM 0u #define VAR_PREV_ROOM 1u #define VAR_EGO_EDGE 2u #define VAR_SCORE 3u #define VAR_OBJ_EDGE_OBJ 4u #define VAR_OBJ_EDGE 5u #define VAR_EGO_DIR 6u #define VAR_MAX_SCORE 7u #define VAR_FREE_PAGES 8u #define VAR_BAD_WORD 9u #define VAR_CYCLE_DELAY 10u #define VAR_SECONDS 11u #define VAR_MINUTES 12u #define VAR_HOURS 13u #define VAR_DAYS 14u #define VAR_EGO_VIEW 16u #define VAR_KEY 19u #define VAR_COMPUTER 20u #define VAR_WINDOW_TIMER 21u #define VAR_SOUND_TYPE 22u #define VAR_INPUT_MAX 24u #define VAR_SELECTED_ITEM 25u #define VAR_MONITOR 26u #define FLAG_EGO_WATER 0u #define FLAG_ENTERED 2u #define FLAG_EGO_SIGNAL 3u #define FLAG_SAID_OK 4u #define FLAG_NEW_ROOM 5u #define FLAG_RESTART 6u #define FLAG_NO_SCRIPT 7u #define FLAG_SOUND 9u #define FLAG_FIRST_LOGIC0 11u #define FLAG_RESTORED 12u #define FLAG_ITEM_SELECT 13u #define FLAG_MENU 14u #define FLAG_WINDOW_STAYS 15u // ----- Interpreter versions ----- // The Sierra interpreter a game shipped with, as major * 1000 + minor // (2.435 is 2435), read from the game's AGIDATA.OVL (or AGI) when its // folder has one; otherwise the version the quirks were measured on. #define AGI_VERSION_MEASURED 2435u // Behaviour of particular Sierra interpreter versions that games may lean // on; agiQuirk says whether the game's version has one (the version ranges // are agiRes.c's kQuirkVersions). typedef enum { // A window's x is a byte and the screen one line of 160-pixel rows: // a box starting left of the screen starts 256 pixels on instead, // and every span runs on into the next row. AGI_QUIRK_WINDOW_BYTE_X = 0, // A typed character first steps back over the cursor, whether or not // one was drawn there. AGI_QUIRK_ECHO_STEPS_BACK = 1, // A picture line carries its error in a byte, losing steps once it // passes 255. AGI_QUIRK_LINE_BYTE_ERROR = 2, AGI_QUIRK_COUNT = 3 } AgiQuirkE; // ----- Resources (agiRes.c) ----- typedef enum { AGI_RES_LOGIC = 0, AGI_RES_PIC = 1, AGI_RES_VIEW = 2, AGI_RES_SOUND = 3, AGI_RES_COUNT = 4 } AgiResTypeE; typedef struct { uint8_t volume; uint8_t offsetHigh; uint16_t offsetLow; } AgiResEntryT; typedef struct { FILE *volFiles[AGI_MAX_VOLUMES]; uint16_t resCount[AGI_RES_COUNT]; AgiResEntryT resDir[AGI_RES_COUNT][AGI_MAX_RESOURCES]; uint8_t *words; uint16_t wordsLen; uint8_t *objectData; uint16_t itemCount; uint8_t maxObjects; uint16_t interpreterVersion; } AgiGameT; // ----- Pictures and views ----- typedef struct { uint8_t width; uint8_t height; uint8_t transparent; uint8_t mirrorFlags; const uint8_t *rle; } AgiCelT; typedef struct { uint8_t celCount; AgiCelT *cels; } AgiLoopT; typedef struct { uint8_t *raw; uint16_t rawLength; uint8_t loopCount; AgiLoopT *loops; const char *description; } AgiViewT; // ----- Screen objects ----- typedef enum { AGI_CYCLE_NORMAL = 0, AGI_CYCLE_END_LOOP = 1, AGI_CYCLE_REVERSE_LOOP = 2, AGI_CYCLE_REVERSE = 3 } AgiCycleE; typedef enum { AGI_MOTION_NORMAL = 0, AGI_MOTION_WANDER = 1, AGI_MOTION_FOLLOW = 2, AGI_MOTION_MOVE = 3 } AgiMotionE; // Directions: 0 is still, 1..8 run clockwise from north. #define AGI_DIR_NONE 0u #define AGI_DIR_N 1u #define AGI_DIR_NE 2u #define AGI_DIR_E 3u #define AGI_DIR_SE 4u #define AGI_DIR_S 5u #define AGI_DIR_SW 6u #define AGI_DIR_W 7u #define AGI_DIR_NW 8u #define AGI_DIR_COUNT 9u // Edges, as v2 and v5 report them. #define AGI_EDGE_NONE 0u #define AGI_EDGE_TOP 1u #define AGI_EDGE_RIGHT 2u #define AGI_EDGE_BOTTOM 3u #define AGI_EDGE_LEFT 4u #define OBJ_ANIMATED 0x0001u #define OBJ_DRAWN 0x0002u #define OBJ_UPDATE 0x0004u #define OBJ_CYCLING 0x0008u #define OBJ_FIX_LOOP 0x0010u #define OBJ_FIX_PRIORITY 0x0020u #define OBJ_IGNORE_HORIZON 0x0040u #define OBJ_IGNORE_BLOCKS 0x0080u #define OBJ_IGNORE_OBJS 0x0100u #define OBJ_ON_WATER 0x0200u #define OBJ_ON_LAND 0x0400u // Measured from Sierra's interpreter: an object's first step after the // game starts or the room changes is lost, as is the first cel change // after end.of.loop or reverse.loop on an object already drawn. #define OBJ_SKIP_STEP 0x0800u #define OBJ_SKIP_CYCLE 0x1000u typedef struct { const AgiViewT *view; uint8_t viewId; uint8_t loop; uint8_t cel; uint8_t width; uint8_t height; uint8_t priority; int16_t x; int16_t y; int16_t prevX; int16_t prevY; uint16_t flags; uint8_t direction; uint8_t stepSize; uint8_t stepTime; uint8_t stepCount; uint8_t cycleTime; uint8_t cycleCount; uint8_t cycleMode; uint8_t motion; uint8_t loopFlag; uint8_t motionFlag; uint8_t targetX; uint8_t targetY; uint8_t savedStepSize; uint8_t randomSteps; uint8_t followDistance; // Blit state: where the object was last drawn and the buffer bytes // its cel covered there, restored when it is erased. bool blitted; int16_t blitX; int16_t blitY; uint8_t blitW; uint8_t blitH; uint8_t *saveUnder; } AgiObjectT; // ----- Text, menus, the parser ----- typedef struct { uint16_t key; uint8_t controller; } AgiKeyMapT; // One recorded picture-building command (the restore script): an AGI // command number and its argument values (add.to.pic has seven). typedef struct { uint8_t op; uint8_t args[AGI_SCRIPT_ARGS]; } AgiScriptEntryT; typedef struct { char text[AGI_MENU_ITEM_MAX + 1u]; uint8_t controller; bool enabled; } AgiMenuItemT; typedef struct { char name[AGI_MENU_NAME_MAX + 1u]; uint8_t firstItem; uint8_t itemCount; uint8_t col; } AgiMenuT; // ----- The interpreter state ----- typedef struct { AgiGameT game; uint8_t vars[AGI_NUM_VARS]; uint8_t flags[AGI_NUM_FLAGS]; char strings[AGI_MAX_STRINGS][AGI_STRING_LEN + 1u]; uint8_t controllers[AGI_NUM_CONTROLLERS]; AgiKeyMapT keyMaps[AGI_MAX_KEYMAPS]; uint8_t keyMapCount; // The commands that built the current picture, replayed by restore. AgiScriptEntryT script[AGI_SCRIPT_MAX]; uint8_t scriptCount; // Logic resources (loaded on demand) and each one's scan start. uint8_t *logicRaw[AGI_MAX_RESOURCES]; uint16_t logicLength[AGI_MAX_RESOURCES]; uint16_t logicScanStart[AGI_MAX_RESOURCES]; bool logicLoaded[AGI_MAX_RESOURCES]; uint8_t currentLogic; bool abortLogic; bool newRoomPending; uint8_t newRoomNumber; // Views loaded with load.view or on demand. AgiViewT *views[AGI_MAX_RESOURCES]; // Screen objects. AgiObjectT objects[AGI_MAX_OBJECTS]; uint8_t objectCount; uint8_t horizon; bool blockActive; uint8_t blockX1; uint8_t blockY1; uint8_t blockX2; uint8_t blockY2; bool playerControl; // Inventory: the room each item is in. uint8_t itemRoom[AGI_MAX_ITEMS]; // Screen layout (configure.screen) and text state. uint8_t picRow; uint8_t inputRow; uint8_t statusRow; bool statusOn; bool inputOn; bool textMode; uint8_t textFg; uint8_t textBg; char cursorChar; bool windowOpen; // The command line. char inputLine[AGI_INPUT_MAX + 1u]; uint8_t inputLen; char lastLine[AGI_INPUT_MAX + 1u]; uint16_t wordIds[AGI_MAX_WORDS]; char wordText[AGI_MAX_WORDS][AGI_WORD_MAX_LEN + 1u]; uint8_t wordCount; // Menus. AgiMenuT menus[AGI_MAX_MENUS]; AgiMenuItemT menuItems[AGI_MAX_MENU_ITEMS]; uint8_t menuCount; uint8_t menuItemCount; bool menuSubmitted; bool menuRequested; uint8_t menuLastMenu; // Requests that end the cycle, and a restore that just did. bool quitRequested; bool restartRequested; bool restored; } AgiStateT; extern AgiStateT *gAgi; // ----- agiRes.c ----- void agiDecrypt(uint8_t *data, uint16_t length); const char *agiItemName(const AgiGameT *game, uint8_t item); uint8_t agiItemStartRoom(const AgiGameT *game, uint8_t item); bool agiQuirk(AgiQuirkE quirk); void agiResClose(AgiGameT *game); uint8_t *agiResLoad(const AgiGameT *game, AgiResTypeE type, uint16_t index, uint16_t *outLength); bool agiResOpen(AgiGameT *game); uint16_t agiWordMatch(const AgiGameT *game, const char *text, uint8_t *outLen); // ----- agiPic.c ----- bool agiPicDecode(uint8_t *visual, uint8_t *priority, const uint8_t *data, uint16_t length); uint8_t agiPriorityForY(int16_t y); int16_t agiYForPriority(uint8_t priority); // ----- agiView.c ----- const AgiCelT *agiViewCel(const AgiViewT *view, uint8_t loop, uint8_t cel, bool *outMirror); void agiViewDiscardUnused(void); const AgiViewT *agiViewGet(uint8_t viewId); // ----- agiGfx.c ----- void gfxAddToPic(uint8_t viewId, uint8_t loop, uint8_t cel, int16_t x, int16_t y, uint8_t priority, uint8_t margin); uint8_t gfxControlAt(int16_t x, int16_t y); void gfxDrawPic(uint8_t picId, bool clearFirst); void gfxDrawStatic(void); void gfxEraseAll(void); void gfxEraseUpdating(void); void gfxFree(void); bool gfxInit(void); void gfxPresentDirty(void); void gfxRedrawAll(void); void gfxRedrawUpdating(void); void gfxRestorePicture(void); void gfxShowObjectCel(uint8_t viewId); void gfxShowPic(void); void gfxShakeScreen(uint8_t times); void gfxShowPriority(void); // ----- agiObj.c ----- void objAnimate(uint8_t n); void objClearAll(void); uint8_t objDirectionTo(int16_t fromX, int16_t fromY, int16_t toX, int16_t toY, uint8_t slack); void objFixPosition(uint8_t n); void objMoveTo(uint8_t n, uint8_t x, uint8_t y, uint8_t step, uint8_t flag); void objPlanMotion(void); void objSetCel(uint8_t n, uint8_t cel); void objSetLoop(uint8_t n, uint8_t loop); bool objSetView(uint8_t n, uint8_t viewId); void objUnanimateAll(void); void objUpdateAll(void); // ----- agiText.c ----- void textClearLines(uint8_t top, uint8_t bottom, uint8_t color); void textClearRect(uint8_t top, uint8_t left, uint8_t bottom, uint8_t right, uint8_t color); void textCloseWindow(void); void textDisplay(uint8_t row, uint8_t col, const char *text); void textDrawInputLine(void); void textDrawStatus(void); void textEchoInput(uint8_t oldLen); void textExpand(const char *src, char *dst, uint16_t dstCap); void textGraphicsMode(void); void textInventory(void); void textMenuAdd(const char *name); void textMenuAddItem(const char *name, uint8_t controller); uint8_t textMenuInput(void); void textMessageBox(const char *text); void textPrint(const char *text, int16_t row, int16_t col, uint8_t width); void textSetAttribute(uint8_t fg, uint8_t bg); void textShowObject(uint8_t viewId); void textTextMode(void); void textUpdateStatus(void); // ----- agiInput.c ----- bool inputGetNumber(const char *prompt, uint8_t *outValue); bool inputGetString(char *dst, uint8_t maxLen, uint8_t row, uint8_t col, const char *prompt); bool inputHaveKey(void); void inputParse(const char *line); void inputPoll(void); void inputProcess(void); void inputReset(void); uint16_t inputWaitKey(void); bool inputWaitPrintClose(uint16_t timeoutHalfSeconds); // ----- agiSound.c ----- void soundInit(void); void soundPump(void); void soundShutdown(void); void soundStart(uint8_t soundId, uint8_t flag); void soundStop(void); // ----- agiSave.c ----- bool restoreGame(void); void saveGame(void); void saveScriptAdd(uint8_t op, uint8_t arg); void saveScriptAddArgs(uint8_t op, const uint8_t *args); void saveScriptClear(void); // ----- agiVm.c ----- void vmCallLogic(uint8_t logicId); void vmDiscardLogics(void); bool vmLoadLogic(uint8_t logicId); const char *vmMessage(uint8_t logicId, uint8_t msgId); void vmReplay(const AgiScriptEntryT *e); // ----- agi.c ----- void agiIdle(void); void agiNewRoom(uint8_t room); void agiObjectStatus(uint8_t n); void agiQuit(bool immediately); void agiResetState(void); void agiRestartGame(void); #endif