Gakuen KING: Draw the cursor on the I command grid

The original leaves the selection feedback to the mouse pointer,
which may be missing or impossible to move in system3-sdl2. Draw the
mouse cursor image extracted from GAKUEN.COM on the grid while the
keyboard drives the selection.
This commit is contained in:
kichikuou
2026-08-24 16:29:15 +09:00
parent 6bd5baf7c2
commit a82638c142
2 changed files with 110 additions and 12 deletions
+2 -2
View File
@@ -87,8 +87,6 @@ private:
void draw_char_antialias(int dest, int dest_x, int dest_y, uint16 code, TTF_Font* font, uint8 color, uint8 cache[]); void draw_char_antialias(int dest, int dest_x, int dest_y, uint16 code, TTF_Font* font, uint8 color, uint8 cache[]);
void draw_gaiji(int dest, int dest_x, int dest_y, const uint8_t bitmap[32], int size, uint8 color); void draw_gaiji(int dest, int dest_x, int dest_y, const uint8_t bitmap[32], int size, uint8 color);
void draw_screen(int sx, int sy, int width, int heignt);
uint8_t palR(uint8_t col) const { return screen_palette->colors[col].r; } uint8_t palR(uint8_t col) const { return screen_palette->colors[col].r; }
uint8_t palG(uint8_t col) const { return screen_palette->colors[col].g; } uint8_t palG(uint8_t col) const { return screen_palette->colors[col].g; }
uint8_t palB(uint8_t col) const { return screen_palette->colors[col].b; } uint8_t palB(uint8_t col) const { return screen_palette->colors[col].b; }
@@ -122,6 +120,8 @@ public:
std::vector<uint32_t> get_screen_palette() const; std::vector<uint32_t> get_screen_palette() const;
uint8 get_pixel(int dest, int x, int y) const { return vram[dest][y][x]; } uint8 get_pixel(int dest, int x, int y) const { return vram[dest][y][x]; }
void set_pixel(int dest, int x, int y, uint8 color) { vram[dest][y][x] = color; } void set_pixel(int dest, int x, int y, uint8 color) { vram[dest][y][x] = color; }
// Marks a rectangle of the display screen for the next update_screen().
void draw_screen(int sx, int sy, int width, int height);
void fade_out(int duration_ms, bool white); void fade_out(int duration_ms, bool white);
void fade_in(int duration_ms); void fade_in(int duration_ms);
+108 -10
View File
@@ -93,6 +93,7 @@ private:
exec_y(14, param); exec_y(14, param);
break; break;
case 25: case 25:
fixed_cursor_shape = (param == 1);
if (0 <= param && param <= 4) { if (0 <= param && param <= 4) {
ags->menu.back_color = ags->text.back_color = ags->menu.back_color = ags->text.back_color =
(param == 1 || param == 2) ? 0 : 1; (param == 1 || param == 2) ? 0 : 1;
@@ -212,6 +213,7 @@ private:
return; return;
} }
load_digit_font(exe); load_digit_font(exe);
load_mouse_cursors(exe);
load_credit_text(exe); load_credit_text(exe);
} }
@@ -1225,6 +1227,53 @@ private:
VAR_TROOP_COUNT = VAR_TROOP_COUNT_FULL; VAR_TROOP_COUNT = VAR_TROOP_COUNT_FULL;
} }
// --- mouse cursor ------------------------------------------------------
// The shapes GAKUEN.COM hands to the mouse driver: six 16x16 patterns of
// two 32-byte planes each, the interior first and the silhouette second.
// Pattern 0 is blank and unused.
static constexpr int MOUSE_CURSOR_OFFSET = 0x2a92;
static constexpr int NR_MOUSE_CURSORS = 6;
static constexpr int MOUSE_CURSOR_SIZE = 16;
struct MouseCursor {
uint16_t body[MOUSE_CURSOR_SIZE];
uint16_t silhouette[MOUSE_CURSOR_SIZE]; // body plus its outline
};
MouseCursor mouse_cursor[NR_MOUSE_CURSORS] = {};
bool fixed_cursor_shape = false; // set by Y 25,1
void load_mouse_cursors(const std::vector<uint8_t>& exe) {
const uint8_t* p = &exe[MOUSE_CURSOR_OFFSET];
for (MouseCursor& cursor : mouse_cursor) {
for (uint16_t* plane : {cursor.body, cursor.silhouette}) {
for (int row = 0; row < MOUSE_CURSOR_SIZE; row++, p += 2)
plane[row] = p[0] | p[1] << 8;
}
}
}
int pick_cursor_shape() {
if (fixed_cursor_shape)
return 5;
int r = random(100);
return r < 5 ? r : 1;
}
void draw_mouse_cursor(int shape, int x, int y) {
const MouseCursor& cursor = mouse_cursor[shape];
for (int row = 0; row < MOUSE_CURSOR_SIZE; row++) {
for (int col = 0; col < MOUSE_CURSOR_SIZE; col++) {
uint16_t bit = 0x8000 >> col;
if (!(cursor.silhouette[row] & bit))
continue;
ags->set_pixel(0, x + col, y + row, (cursor.body[row] & bit) ? 15 : 0);
}
}
ags->draw_screen(x, y, MOUSE_CURSOR_SIZE, MOUSE_CURSOR_SIZE);
}
// --- I grid selection ------------------------------------------------- // --- I grid selection -------------------------------------------------
// I 2 composes its overlays over the background CG on the work screen and // I 2 composes its overlays over the background CG on the work screen and
@@ -1293,42 +1342,91 @@ private:
int& cx = grid_cursor_x[wide ? 1 : 0]; int& cx = grid_cursor_x[wide ? 1 : 0];
int& cy = grid_cursor_y[wide ? 1 : 0]; int& cy = grid_cursor_y[wide ? 1 : 0];
// The original relies on the mouse pointer movement for visual feedback
// on player input. In system3-sdl2 the pointer may be absent or
// unmovable, so we draw the cursor shape onto the grid instead. It
// disappears once the player moves the pointer, and comes back on the
// next direction key.
const int shape = pick_cursor_shape();
bool cursor_drawn = false;
int seen_x, seen_y;
get_cursor(&seen_x, &seen_y);
// Hides the system pointer and draws the cursor at (cx, cy) instead.
auto show_cursor = [&] {
SDL_ShowCursor(SDL_DISABLE);
draw_mouse_cursor(shape, cx, cy);
cursor_drawn = true;
};
// Erases the drawn cursor and restores the system pointer. Must be
// called before (cx, cy) moves, which is where the cursor is drawn.
auto hide_cursor = [&] {
if (!cursor_drawn)
return;
SDL_ShowCursor(SDL_ENABLE);
ags->copy_screen(WORK_SCREEN, 0, cx, cy, cx + MOUSE_CURSOR_SIZE - 1,
cy + MOUSE_CURSOR_SIZE - 1, cx, cy);
cursor_drawn = false;
};
set_cursor(cx, cy); set_cursor(cx, cy);
show_cursor();
while (!terminate) { while (!terminate) {
uint8_t key = get_key(); uint8_t key = get_key();
int mx, my;
get_cursor(&mx, &my);
// A pointer position that is neither where it was nor where
// set_cursor() put it is a move by the player. The pointer comes
// back through a scaling that rounds, so allow it one pixel.
if ((mx != seen_x || my != seen_y) &&
(std::abs(mx - cx) > 1 || std::abs(my - cy) > 1))
hide_cursor();
seen_x = mx;
seen_y = my;
if (!key) { if (!key) {
sys_sleep(16); sys_sleep(16);
continue; continue;
} }
if (!(key & 0xb0)) { if (!(key & 0xb0)) {
hide_cursor();
if (key & 1 && cy >= (wide ? 54 : 55)) cy -= 32; if (key & 1 && cy >= (wide ? 54 : 55)) cy -= 32;
if (key & 2 && cy < (wide ? 247 : 248)) cy += 32; if (key & 2 && cy < (wide ? 247 : 248)) cy += 32;
if (key & 4 && cx >= (wide ? 544 : 424)) cx -= cell_w; if (key & 4 && cx >= (wide ? 544 : 424)) cx -= cell_w;
if (key & 8 && cx < (wide ? 433 : 553)) cx += cell_w; if (key & 8 && cx < (wide ? 433 : 553)) cx += cell_w;
set_cursor(cx, cy); set_cursor(cx, cy);
show_cursor();
wait_key_release(0x0f); wait_key_release(0x0f);
continue; continue;
} }
if (key & 0xa0) { if (key & 0xa0) {
hide_cursor();
wait_key_release(); wait_key_release();
return GRID_SELECT_CANCELED; return GRID_SELECT_CANCELED;
} }
// The item comes from where the pointer actually is. The original int col, row;
// confines it to the grid with the mouse driver; here the pointer if (cursor_drawn) {
// can leave the grid and the selection is clamped instead. col = (cx - (wide ? 432 : 392)) / cell_w;
int mx, my; row = (cy - (wide ? 22 : 23)) / 32;
get_cursor(&mx, &my); } else {
int col = std::clamp((mx - GRID_X) / cell_w, 0, cols - 1); if (mx < GRID_X || mx >= GRID_X + cols * cell_w ||
int row = std::clamp((my - 7 - (wide ? 1 : 0)) / 32, 0, rows - 1); my < 7 || my >= 7 + rows * 32) {
cx = col * cell_w + (wide ? 432 : 392); // outside the grid.
cy = row * 32 + (wide ? 22 : 23); wait_key_release();
set_cursor(cx, cy); continue;
}
col = (mx - GRID_X) / cell_w;
row = (my - 7 - (wide ? 1 : 0)) / 32;
cx = col * cell_w + (wide ? 432 : 392);
cy = row * 32 + (wide ? 22 : 23);
set_cursor(cx, cy);
}
hide_cursor();
wait_key_release(); wait_key_release();
// The state table is not consulted: state 0 returns its number // The state table is not consulted: state 0 returns its number
// too, and the scenario rejects it. // too, and the scenario rejects it.
return static_cast<uint16_t>(row * cols + col + 1); return static_cast<uint16_t>(row * cols + col + 1);
} }
hide_cursor();
return GRID_SELECT_CANCELED; return GRID_SELECT_CANCELED;
} }