// The mouse (PLAN.md decision 4). Sierra's DOS interpreters had none; its // Amiga, ST, IIgs, and Macintosh ones did, and this follows them as // ScummVM describes them: // // - A pointer is drawn over the screen at every present while the machine // has a mouse and the game has not hidden it (hide.mouse), kept to the // box fence.mouse gives. // - A left click on the menu bar opens the menus (as menu.input would); // in the picture, with the player in control, ego walks to it: its // baseline's middle to the click (objMouseWalk). A direction key, or in // AGI 3 a new room, stops the walk. // - In the menus the pointer picks the menu and the item, and in a message // window a click is Enter (agiText.c, agiInput.c). #include "agi.h" #include "joey/core.h" #include "joey/input.h" #include "joey/sprite.h" #include "joey/tile.h" // The pointer: a 16x16 arrow, its point the hot spot. '.' is // transparent, 'k' its outline (dark grey: color 0, black, is the // sprite's transparent one), 'w' its white. #define POINTER_TILES 2u #define POINTER_PX (POINTER_TILES * JL_TILE_PIXELS_PER_SIDE) #define POINTER_OUTLINE 8u #define POINTER_FILL 15u #define PIXELS_PER_BYTE 2u #define NIBBLE_BITS 4u #define FENCE_X1 0u #define FENCE_Y1 1u #define FENCE_X2 2u #define FENCE_Y2 3u // ----- Prototypes ----- static void pointerAt(int16_t *x, int16_t *y); static bool pointerShown(void); // ----- Tables ----- static const char *const kPointerArt[POINTER_PX] = { "k...............", "kk..............", "kwk.............", "kwwk............", "kwwwk...........", "kwwwwk..........", "kwwwwwk.........", "kwwwwwwk........", "kwwwwwwwk.......", "kwwwwwkkkk......", "kwwkwwk.........", "kwk.kwwk........", "kk..kwwk........", "k....kwwk.......", ".....kwwk.......", "......kk........" }; // ----- Module state ----- static uint8_t gPointerTiles[POINTER_TILES * POINTER_TILES * JL_TILE_BYTES]; static jlSpriteT *gPointer; static uint16_t gPointerUnder[jlSpriteBackupLength(POINTER_TILES, POINTER_TILES)]; static jlSpriteBackupT gPointerSaved; static bool gPointerDrawn; // ----- Internal helpers (alphabetical) ----- // Where the pointer is on the screen, kept inside fence.mouse's box (in // picture coordinates; all zero is no fence). static void pointerAt(int16_t *x, int16_t *y) { const uint8_t *f; int16_t left; int16_t top; int16_t right; int16_t bottom; *x = jlMouseX(); *y = jlMouseY(); f = gAgi->mouseFence; if (f[FENCE_X1] == 0u && f[FENCE_Y1] == 0u && f[FENCE_X2] == 0u && f[FENCE_Y2] == 0u) { return; } left = (int16_t)(f[FENCE_X1] * AGI_SCREEN_PX_PER_AGI); right = (int16_t)(f[FENCE_X2] * AGI_SCREEN_PX_PER_AGI); top = (int16_t)(f[FENCE_Y1] + gAgi->picRow * AGI_CELL_PX); bottom = (int16_t)(f[FENCE_Y2] + gAgi->picRow * AGI_CELL_PX); if (*x < left) { *x = left; } else if (*x > right) { *x = right; } if (*y < top) { *y = top; } else if (*y > bottom) { *y = bottom; } } static bool pointerShown(void) { return gPointer != NULL && jlMousePresent() && !gAgi->mouseHidden; } // ----- Public API (alphabetical) ----- // A left click in a cycle (inputProcess): the menus on the menu bar, else // a walk for ego. void mouseClick(void) { int16_t x; int16_t y; uint8_t ax; uint8_t ay; if (!pointerShown()) { return; } pointerAt(&x, &y); if (y < (int16_t)AGI_CELL_PX) { if (gAgi->flags[FLAG_MENU] != 0u) { gAgi->menuRequested = true; } return; } if (gAgi->playerControl && !gAgi->textMode) { mousePosition(&ax, &ay); objMouseWalk(ax, ay); } } // The pointer onto the stage just before a present, what it covers kept. void mouseDrawPointer(void) { int16_t x; int16_t y; if (!pointerShown()) { return; } pointerAt(&x, &y); jlSpriteSaveAndDraw(jlStageGet(), gPointer, x, y, &gPointerSaved); gPointerDrawn = true; } // And off again just after, so nothing else ever draws under it. void mouseErasePointer(void) { if (!gPointerDrawn) { return; } jlSpriteRestoreUnder(jlStageGet(), &gPointerSaved); gPointerDrawn = false; } void mouseInit(void) { uint8_t tx; uint8_t ty; uint8_t r; uint8_t b; uint8_t pixel[PIXELS_PER_BYTE]; uint8_t p; char c; for (ty = 0u; ty < POINTER_TILES; ty++) { for (tx = 0u; tx < POINTER_TILES; tx++) { uint8_t *tile = &gPointerTiles[(ty * POINTER_TILES + tx) * JL_TILE_BYTES]; for (r = 0u; r < JL_TILE_PIXELS_PER_SIDE; r++) { for (b = 0u; b < JL_TILE_BYTES_PER_ROW; b++) { for (p = 0u; p < PIXELS_PER_BYTE; p++) { c = kPointerArt[ty * JL_TILE_PIXELS_PER_SIDE + r][tx * JL_TILE_PIXELS_PER_SIDE + b * PIXELS_PER_BYTE + p]; pixel[p] = (c == 'k') ? POINTER_OUTLINE : ((c == 'w') ? POINTER_FILL : 0u); } tile[r * JL_TILE_BYTES_PER_ROW + b] = (uint8_t)((pixel[0] << NIBBLE_BITS) | pixel[1]); } } } } gPointerSaved.bytes = (uint8_t *)gPointerUnder; gPointer = jlSpriteCreate(gPointerTiles, POINTER_TILES, POINTER_TILES); if (gPointer != NULL) { (void)jlSpriteCompile(gPointer); } } // The text cell under the pointer, when one is shown. bool mouseOver(int16_t *col, int16_t *row) { int16_t x; int16_t y; if (!pointerShown()) { return false; } pointerAt(&x, &y); *col = (int16_t)(x / AGI_CELL_PX); *row = (int16_t)(y / AGI_CELL_PX); return true; } // get.mse.posn: the pointer in picture coordinates, kept to the picture. void mousePosition(uint8_t *x, uint8_t *y) { int16_t sx; int16_t sy; int16_t mx; int16_t my; pointerAt(&sx, &sy); mx = (int16_t)(sx / AGI_SCREEN_PX_PER_AGI); my = (int16_t)(sy - (int16_t)gAgi->picRow * AGI_CELL_PX); *x = (uint8_t)(mx < 0 ? 0 : (mx > AGI_WIDTH - 1 ? AGI_WIDTH - 1 : mx)); *y = (uint8_t)(my < 0 ? 0 : (my > AGI_MAX_Y ? AGI_MAX_Y : my)); } void mouseShutdown(void) { if (gPointer != NULL) { jlSpriteDestroy(gPointer); gPointer = NULL; } }