joeyagi/agiMouse.c
2026-10-20 17:47:19 -05:00

232 lines
6.4 KiB
C

// 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;
}
}