From 7c62cfc9501cd745743b7ae168e4289dcb45e7c2 Mon Sep 17 00:00:00 2001 From: wuxu Date: Sun, 19 Jul 2026 11:36:38 +0800 Subject: [PATCH] Add a main-menu Joker gallery Expose every registered Joker from a paged review screen entered with Start or Select. Reuse the gameplay card art and description renderers, support controller navigation, and clean up the preview object when returning to the menu. --- include/def_state_info_table.h | 6 ++ include/game/joker_gallery.h | 24 ++++++ source/game.c | 1 + source/game/joker_gallery.c | 146 +++++++++++++++++++++++++++++++++ source/game/main_menu.c | 16 ++++ 5 files changed, 193 insertions(+) create mode 100644 include/game/joker_gallery.h create mode 100644 source/game/joker_gallery.c diff --git a/include/def_state_info_table.h b/include/def_state_info_table.h index c733251..bb2cff0 100644 --- a/include/def_state_info_table.h +++ b/include/def_state_info_table.h @@ -2,6 +2,12 @@ // (stateEnum, on_init, on_update, on_exit) DEF_STATE_INFO(GAME_STATE_SPLASH_SCREEN, splash_screen_on_init, splash_screen_on_update, splash_screen_on_exit ) DEF_STATE_INFO(GAME_STATE_MAIN_MENU, game_main_menu_on_init, game_main_menu_on_update, game_main_menu_on_exit ) +DEF_STATE_INFO( + GAME_STATE_JOKER_GALLERY, + game_joker_gallery_on_init, + game_joker_gallery_on_update, + game_joker_gallery_on_exit +) DEF_STATE_INFO(GAME_STATE_OPTIONS_MENU, game_options_menu_on_init, game_options_menu_on_update, game_options_menu_on_exit) DEF_STATE_INFO(GAME_STATE_RUN_SETUP , game_run_setup_on_init, game_run_setup_on_update, game_run_setup_on_exit ) DEF_STATE_INFO(GAME_STATE_GAME_START, noop, game_start, noop ) diff --git a/include/game/joker_gallery.h b/include/game/joker_gallery.h new file mode 100644 index 0000000..e4d4e21 --- /dev/null +++ b/include/game/joker_gallery.h @@ -0,0 +1,24 @@ +/** + * @file joker_gallery.h + * + * @brief Main-menu Joker description gallery state functions. + */ +#ifndef GAME_JOKER_GALLERY_H +#define GAME_JOKER_GALLERY_H + +/** + * @brief Initialize the Joker gallery and display the selected Joker. + */ +void game_joker_gallery_on_init(void); + +/** + * @brief Process Joker gallery navigation input. + */ +void game_joker_gallery_on_update(void); + +/** + * @brief Destroy gallery resources before leaving the state. + */ +void game_joker_gallery_on_exit(void); + +#endif // GAME_JOKER_GALLERY_H diff --git a/source/game.c b/source/game.c index 0505d3a..3880456 100644 --- a/source/game.c +++ b/source/game.c @@ -9,6 +9,7 @@ #include "game/blind_select.h" #include "game/common_ui.h" #include "game/game_over.h" +#include "game/joker_gallery.h" #include "game/joker_row.h" #include "game/main_menu.h" #include "game/options_menu.h" diff --git a/source/game/joker_gallery.c b/source/game/joker_gallery.c new file mode 100644 index 0000000..08a3d5b --- /dev/null +++ b/source/game/joker_gallery.c @@ -0,0 +1,146 @@ +#include "game/joker_gallery.h" + +#include "affine_background.h" +#include "game.h" +#include "game/common_ui.h" +#include "graphic_utils.h" +#include "joker.h" + +#include +#include + +#define SHOP_DESC_RARITY_MAIN_COLOR_PAL_IDX 27 +#define SHOP_DESC_RARITY_SHADOW_COLOR_PAL_IDX 28 + +// clang-format off +static const Rect VISIBLE_SCREEN_TILE_RECT = { 0, 0, 29, 19}; +static const BG_POINT CLEAR_TILE_SOURCE = {30, 1}; +static const Rect CARD_DESC_PANEL_RECT = { 9, 6, 28, 18}; +static const NinePatchRect CARD_DESC_PANEL_SOURCE = { + .patch_rect = {27, 25, 31, 31}, + .margins = { 2, 3, 2, 3} +}; +static const int CARD_DESC_MAX_TEXT_HEIGHT = CARD_DESC_PANEL_RECT.bottom - + CARD_DESC_PANEL_RECT.top + 1 - + CARD_DESC_PANEL_SOURCE.margins.top - + CARD_DESC_PANEL_SOURCE.margins.bottom; +static const Rect CARD_DESC_TEXT_RECT = {11, 9, 26, 18}; +static const Rect CARD_NAME_TEXT_RECT = {10, 7, 27, 7}; +static const BG_POINT JOKER_SPRITE_POS = {135, 9}; +// clang-format on + +static JokerObject* s_gallery_joker = NULL; +static size_t s_joker_index = 0; + +static void game_joker_gallery_render(void) +{ + size_t joker_count = get_joker_registry_size(); + const JokerInfo* info = get_joker_registry_entry(s_joker_index); + + tte_erase_screen(); + main_bg_se_copy_expand_tile(VISIBLE_SCREEN_TILE_RECT, CLEAR_TILE_SOURCE); + + if (joker_count == 0 || info == NULL) + { + return; + } + + s_gallery_joker = joker_object_new(joker_new(s_joker_index)); + if (s_gallery_joker == NULL) + { + return; + } + + sprite_object_position((SpriteObject*)s_gallery_joker, JOKER_SPRITE_POS.x, JOKER_SPRITE_POS.y); + + int desc_bottom_offset = CARD_DESC_MAX_TEXT_HEIGHT - + info->joker_print_desc(s_gallery_joker->joker, CARD_DESC_TEXT_RECT); + desc_bottom_offset = max(0, desc_bottom_offset); + + pal_bg_mem[SHOP_DESC_RARITY_MAIN_COLOR_PAL_IDX] = joker_get_rarity_color(info->rarity, true); + pal_bg_mem[SHOP_DESC_RARITY_SHADOW_COLOR_PAL_IDX] = joker_get_rarity_color(info->rarity, false); + + Rect panel_rect = CARD_DESC_PANEL_RECT; + panel_rect.bottom -= desc_bottom_offset; + main_bg_se_copy_expand_9_patch(panel_rect, &CARD_DESC_PANEL_SOURCE); + + tte_printf( + TTE_WHITE_TAG "#{P:%d,%d}%*s%s", + CARD_NAME_TEXT_RECT.left * TILE_SIZE, + CARD_NAME_TEXT_RECT.top * TILE_SIZE, + (rect_width(&CARD_NAME_TEXT_RECT) - strlen(info->name)) / 2, + "", + info->name + ); + + const char* rarity = joker_get_rarity_string(info->rarity); + tte_printf( + TTE_WHITE_TAG "#{P:%d,%d}%*s%s", + CARD_DESC_TEXT_RECT.left * TILE_SIZE, + (CARD_DESC_TEXT_RECT.bottom - desc_bottom_offset - 1) * TILE_SIZE, + (rect_width(&CARD_DESC_TEXT_RECT) - strlen(rarity)) / 2, + "", + rarity + ); + + tte_printf(TTE_WHITE_TAG "#{P:8,16}JOKER\n#{P:8,24}GALLERY"); + tte_printf(TTE_YELLOW_TAG "#{P:8,48}%02d/%02d", (int)s_joker_index + 1, (int)joker_count); + tte_printf(TTE_WHITE_TAG "#{P:8,80}L PREV\n#{P:8,96}R NEXT\n#{P:8,120}B BACK"); +} + +static void game_joker_gallery_change_page(int direction) +{ + size_t joker_count = get_joker_registry_size(); + if (joker_count == 0) + { + return; + } + + joker_object_destroy(&s_gallery_joker); + if (direction < 0) + { + s_joker_index = (s_joker_index + joker_count - 1) % joker_count; + } + else + { + s_joker_index = (s_joker_index + 1) % joker_count; + } + game_joker_gallery_render(); +} + +void game_joker_gallery_on_init(void) +{ + affine_background_change_background(AFFINE_BG_MAIN_MENU); + change_background(BG_SHOP, true); + toggle_windows(false, false); + tte_colors_setup(); + + if (s_joker_index >= get_joker_registry_size()) + { + s_joker_index = 0; + } + + game_joker_gallery_render(); +} + +void game_joker_gallery_on_update(void) +{ + if (key_hit(KEY_LEFT | KEY_L)) + { + game_joker_gallery_change_page(-1); + } + else if (key_hit(KEY_RIGHT | KEY_R | KEY_A)) + { + game_joker_gallery_change_page(1); + } + else if (key_hit(KEY_B)) + { + game_change_state(GAME_STATE_MAIN_MENU); + } +} + +void game_joker_gallery_on_exit(void) +{ + joker_object_destroy(&s_gallery_joker); + tte_erase_screen(); +} diff --git a/source/game/main_menu.c b/source/game/main_menu.c index b2165ad..75d882b 100644 --- a/source/game/main_menu.c +++ b/source/game/main_menu.c @@ -82,6 +82,7 @@ static CardObject* s_main_menu_ace = NULL; // Keep track of last highlighted button static enum MainButtons s_last_highlighted_button = PLAY_BTN_IDX; +static bool s_gallery_prompt_drawn = false; void game_main_menu_change_background(void) { @@ -103,6 +104,7 @@ void game_main_menu_on_init(void) { affine_background_change_background(AFFINE_BG_MAIN_MENU); change_background(BG_MAIN_MENU, true); + s_gallery_prompt_drawn = false; s_main_menu_ace = card_object_new(card_new(SPADES, ACE)); card_object_set_sprite(s_main_menu_ace, 0); // TODO: NULL-check sprite @@ -123,6 +125,13 @@ void game_main_menu_on_update(void) { s_main_menu_ace->trotation = lu_sin((g_game_vars.timer << 8) / 2) / 3; + // The splash state clears TTE after transitioning, so draw this on the first menu update. + if (!s_gallery_prompt_drawn) + { + tte_printf(TTE_WHITE_TAG "#{P:128,152}START: JOKERS"); + s_gallery_prompt_drawn = true; + } + selection_grid_process_input(&main_menu_selection_grid); } @@ -186,6 +195,7 @@ static void main_menu_on_key_transit(SelectionGrid* selection_grid, Selection* s else { tte_erase_rect_wrapper(GBALATRO_VERSION_TEXT_RECT); + s_gallery_prompt_drawn = false; } } @@ -196,6 +206,12 @@ static void main_menu_on_key_transit(SelectionGrid* selection_grid, Selection* s combo_pressed = false; } + if ((key_hit(KEY_START) || key_hit(KEY_SELECT)) && !key_is_down(KEY_L) && !key_is_down(KEY_R)) + { + game_change_state(GAME_STATE_JOKER_GALLERY); + return; + } + if (key_hit(SELECT_CARD)) { button_press(&main_menu_buttons[selection->x]);