From 176910c48324624dcde3ee185938481a2f8df237 Mon Sep 17 00:00:00 2001 From: cellos51 Date: Fri, 18 Jul 2025 19:29:17 -0400 Subject: [PATCH] Jokers appear in the shop but don't do anything yet --- Makefile | 9 ++--- include/card.h | 2 ++ include/game.h | 2 ++ include/joker.h | 11 +++++- include/sprite.h | 1 + source/card.c | 13 ++++++- source/game.c | 92 ++++++++++++++++++++++++++++++++++++++++-------- source/joker.c | 82 +++++++++++++++++++++++++++++++++++++++++- source/sprite.c | 6 ++++ 9 files changed, 197 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index 596c0a9..d5c7ebc 100644 --- a/Makefile +++ b/Makefile @@ -38,16 +38,17 @@ GRAPHICS := graphics #--------------------------------------------------------------------------------- ARCH := -mthumb -mthumb-interwork -CFLAGS := -g -Wall -O2\ - -mcpu=arm7tdmi -mtune=arm7tdmi\ - $(ARCH) +CFLAGS := -O3 -Wall \ + -mcpu=arm7tdmi -mtune=arm7tdmi \ + -ffast-math -fomit-frame-pointer -funroll-loops -flto \ + $(ARCH) CFLAGS += $(INCLUDE) CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions ASFLAGS := -g $(ARCH) -LDFLAGS = -g $(ARCH) -Wl,-Map,$(notdir $*.map) +LDFLAGS = -g $(ARCH) -Wl,-Map,$(notdir $*.map) -flto #--------------------------------------------------------------------------------- # any extra libraries we wish to link with the project diff --git a/include/card.h b/include/card.h index 314866c..ecd724d 100644 --- a/include/card.h +++ b/include/card.h @@ -2,6 +2,7 @@ #define CARD_H #include +#include #include "sprite.h" @@ -65,5 +66,6 @@ CardObject *card_object_new(Card *card); void card_object_destroy(CardObject **card_object); void card_object_update(CardObject *card_object); // Update the card object position and scale void card_object_set_sprite(CardObject *card_object, int layer); +void card_object_score(CardObject *card_object, mm_word sound_id); // This doesn't actually score anything, it just performs an animation and plays a sound effect #endif // CARD_H \ No newline at end of file diff --git a/include/game.h b/include/game.h index f9fed6f..ab6be3b 100644 --- a/include/game.h +++ b/include/game.h @@ -3,6 +3,8 @@ #define MAX_HAND_SIZE 16 #define MAX_DECK_SIZE 52 +#define MAX_JOKERS_SIZE 5 +#define MAX_SHOP_JOKERS 2 // TODO: Make this dynamic and allow for other items besides jokers #define MAX_SELECTION_SIZE 5 #define FRAMES(x) (((x) + game_speed - 1) / game_speed) diff --git a/include/joker.h b/include/joker.h index 4488380..4f60e76 100644 --- a/include/joker.h +++ b/include/joker.h @@ -2,7 +2,11 @@ #define JOKER_H #define JOKER_TID 880 // Tile ID for the starting index in the tile memory +#define JOKER_SPRITE_OFFSET 16 // Offset for the joker sprites #define JOKER_PB 4 // Palette bank for the default jokers +#define JOKER_STARTING_LAYER 27 + +#include #include "sprite.h" #include "card.h" @@ -19,6 +23,7 @@ #define LEGENDARY_JOKER 3 #define MAX_JOKERS 2 // The current maximum jokers added +#define MAX_JOKER_OBJECTS 32 // The maximum number of joker objects that can be created at once #define DEFAULT_JOKER_ID 0 #define GREEDY_JOKER_ID 1 // This is just an example to show the patern of making joker IDs @@ -44,6 +49,7 @@ typedef struct // copy of CardObject in card.h FIXED trotation; FIXED rotation; FIXED vrotation; + bool selected; } JokerObject; typedef struct // These jokers are triggered after the played hand has finished scoring. @@ -74,6 +80,9 @@ void joker_destroy(Joker **joker); IndependentEffect joker_independent_effect(Joker *joker); OnScoredEffect joker_on_scored(Joker *joker, Card *scored_card); -void joker_object_score(JokerObject *joker_object); // This doesn't actually score anything, it just performs an animation and plays a sound effect +JokerObject *joker_object_new(Joker *joker); +void joker_object_destroy(JokerObject **joker_object); +void joker_object_update(JokerObject *joker_object); +void joker_object_score(JokerObject *joker_object, mm_word sound_id); // This doesn't actually score anything, it just performs an animation and plays a sound effect #endif // JOKER_H \ No newline at end of file diff --git a/include/sprite.h b/include/sprite.h index fdcf7f0..f2b6083 100644 --- a/include/sprite.h +++ b/include/sprite.h @@ -15,6 +15,7 @@ typedef struct Sprite *sprite_new(u16 a0, u16 a1, u32 tid, u32 pb, int sprite_index); Sprite *affine_sprite_new(u16 a0, u16 a1, u32 tid, u32 pb); void sprite_destroy(Sprite **sprite); +int sprite_get_layer(Sprite *sprite); INLINE void sprite_position(Sprite *sprite, int x, int y) { sprite->pos.x = x; diff --git a/source/card.c b/source/card.c index 4848c20..7b9dcfd 100644 --- a/source/card.c +++ b/source/card.c @@ -151,4 +151,15 @@ void card_object_set_sprite(CardObject *card_object, int layer) { sprite_destroy(&card_object->sprite); // Destroy the old sprite if it exists card_object->sprite = sprite_new(ATTR0_SQUARE | ATTR0_4BPP | ATTR0_AFF, ATTR1_SIZE_32, card_sprite_lut[card_object->card->suit][card_object->card->rank], 0, layer); -} \ No newline at end of file +} + +void card_object_score(CardObject *card_object, mm_word sound_id) +{ + card_object->vscale = float2fx(0.3f); // Scale down the card when it's scored + card_object->vrotation = float2fx(8.0f); // Rotate the card when it's scored + + if (sound_id == -1) return; // If no sound ID is provided, do nothing + + mm_sound_effect sfx_select = {{sound_id}, 1024, 0, 255, 128,}; + mmEffectEx(&sfx_select); +} diff --git a/source/game.c b/source/game.c index ccfb2f2..e26de39 100644 --- a/source/game.c +++ b/source/game.c @@ -9,6 +9,7 @@ #include "sprite.h" #include "card.h" #include "blind.h" +#include "joker.h" #include "graphic_utils.h" #include "tonc_video.h" @@ -79,6 +80,9 @@ static int deck_top = -1; static Card *discard_pile[MAX_DECK_SIZE] = {NULL}; static int discard_top = -1; +static JokerObject *jokers[MAX_JOKERS] = {NULL}; +static int jokers_top = -1; + // Played stack static inline void played_push(CardObject *card_object) { @@ -118,6 +122,18 @@ static inline Card *discard_pop() return discard_pile[discard_top--]; } +static inline void joker_push(JokerObject *joker) +{ + if (jokers_top >= MAX_JOKERS_SIZE - 1) return; + jokers[++jokers_top] = joker; +} + +static inline JokerObject *joker_pop() +{ + if (jokers_top < 0) return NULL; + return jokers[jokers_top--]; +} + // Consts // Rects left top right bottom @@ -877,13 +893,13 @@ void game_round_init() cards_drawn = 0; hand_selections = 0; - playing_blind_token = blind_token_new(current_blind, 8, 18, 33); // Create the blind token sprite at the top left corner + playing_blind_token = blind_token_new(current_blind, 8, 18, MAX_SELECTION_SIZE + MAX_HAND_SIZE + 1); // Create the blind token sprite at the top left corner // TODO: Hide blind token and display it after sliding blind rect animation //if (playing_blind_token != NULL) //{ // obj_hide(playing_blind_token->obj); // Hide the blind token sprite for now //} - round_end_blind_token = blind_token_new(current_blind, 82, 86, 34); // Create the blind token sprite for round end + round_end_blind_token = blind_token_new(current_blind, 82, 86, MAX_SELECTION_SIZE + MAX_HAND_SIZE + 2); // Create the blind token sprite for round end if (round_end_blind_token != NULL) { @@ -930,9 +946,9 @@ void game_init() hands = max_hands; discards = max_discards; - blind_select_tokens[SMALL_BLIND] = blind_token_new(SMALL_BLIND, 8, 18, 35); - blind_select_tokens[BIG_BLIND] = blind_token_new(BIG_BLIND, 8, 18, 36); - blind_select_tokens[BOSS_BLIND] = blind_token_new(BOSS_BLIND, 8, 18, 37); + blind_select_tokens[SMALL_BLIND] = blind_token_new(SMALL_BLIND, 8, 18, MAX_SELECTION_SIZE + MAX_HAND_SIZE + 3); + blind_select_tokens[BIG_BLIND] = blind_token_new(BIG_BLIND, 8, 18, MAX_SELECTION_SIZE + MAX_HAND_SIZE + 4); + blind_select_tokens[BOSS_BLIND] = blind_token_new(BOSS_BLIND, 8, 18, MAX_SELECTION_SIZE + MAX_HAND_SIZE + 5); obj_hide(blind_select_tokens[SMALL_BLIND]->obj); obj_hide(blind_select_tokens[BIG_BLIND]->obj); @@ -1471,12 +1487,7 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection tte_write(score_buffer); *played_selections = scored_cards; - //played[j]->vy += (3 << FIX_SHIFT); - played[played_top - j]->vscale = float2fx(0.3f); // Scale down the card when it's scored - played[played_top - j]->vrotation = float2fx(8.0f); // Rotate the card when it's scored - - mm_sound_effect sfx_select = {{SFX_CARD_SELECT}, 1024, 0, 255, 128,}; - mmEffectEx(&sfx_select); + card_object_score(played[played_top - j], SFX_CARD_SELECT); // Relocated card scoring logic here chips += card_get_value(played[played_top - j]->card); @@ -1618,6 +1629,31 @@ static void game_round_end_cashout() set_score(score); // Set the score display } +static void game_shop_create_items(JokerObject *shop_jokers[], bool first_time) +{ + for (int i = 0; i < MAX_SHOP_JOKERS; i++) + { + if (shop_jokers[i] != NULL) + { + joker_object_destroy(&shop_jokers[i]); // Destroy the joker object if it exists + } + + shop_jokers[i] = joker_object_new(joker_new(DEFAULT_JOKER_ID)); + shop_jokers[i]->x = int2fx(120 + i * 32); + shop_jokers[i]->y = int2fx(160); + shop_jokers[i]->tx = shop_jokers[i]->x; + shop_jokers[i]->ty = int2fx(71); + + if (first_time == false) + { + shop_jokers[i]->y = shop_jokers[i]->ty; // If it's not the first time, set the y position to the target position + joker_object_score(shop_jokers[i], UNDEFINED); // Give the joker a little wiggle animation + } + + sprite_position(shop_jokers[i]->sprite, fx2int(shop_jokers[i]->x), fx2int(shop_jokers[i]->y)); + } +} + void game_playing() { // TODO: Blind rect sliding into view animation... @@ -1923,6 +1959,16 @@ void game_shop() // TODO: Later move these static variables somewhere else so they can be reused for each game state static int state = 0; + static JokerObject *shop_jokers[MAX_SHOP_JOKERS] = {NULL}; + + for (int i = 0; i < MAX_SHOP_JOKERS; i++) + { + if (shop_jokers[i] != NULL) + { + joker_object_update(shop_jokers[i]); + } + } + // these are for controlling the shop menu static bool top_row = true; static ushort selection_x = 0; @@ -1960,6 +2006,11 @@ void game_shop() { main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT_UP, SE_UP); + if (timer == 1) + { + game_shop_create_items(shop_jokers, true); + } + if (timer >= 7) // Shift the shop icon { int timer_offset = timer - 6; @@ -2052,7 +2103,7 @@ void game_shop() if (key_hit(KEY_A)) { - // Do shop re-roll for money + game_shop_create_items(shop_jokers, false); } } @@ -2067,20 +2118,28 @@ void game_shop() if (timer == 1) { + for (int i = 0; i < MAX_SHOP_JOKERS; i++) + { + if (shop_jokers[i] != NULL) + { + shop_jokers[i]->ty = int2fx(160); + } + } + int y = 6; memset16(&se_mem[MAIN_BG_SBB][32 * (y - 1)], 0x0006, 1); memset16(&se_mem[MAIN_BG_SBB][1 + 32 * (y - 1)], 0x0007, 2); memset16(&se_mem[MAIN_BG_SBB][3 + 32 * (y - 1)], 0x0008, 1); memset16(&se_mem[MAIN_BG_SBB][4 + 32 * (y - 1)], 0x0009, 4); memset16(&se_mem[MAIN_BG_SBB][7 + 32 * (y - 1)], 0x000A, 1); - memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0406, 1); + memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0406, 1); } else if (timer == 2) { int y = 5; memset16(&se_mem[MAIN_BG_SBB][32 * (y - 1)], 0x0001, 1); memset16(&se_mem[MAIN_BG_SBB][1 + 32 * (y - 1)], 0x0002, 7); - memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0401, 1); + memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0401, 1); } if (timer >= MENU_POP_OUT_ANIM_FRAMES) @@ -2097,6 +2156,11 @@ void game_shop() selection_x = 0; // Reset the selection top_row = true; // Reset the top row selection + for (int i = 0; i < MAX_SHOP_JOKERS; i++) + { + joker_object_destroy(&shop_jokers[i]); // Destroy the joker objects + } + increment_blind(BLIND_DEFEATED); game_set_state(GAME_BLIND_SELECT); // If we reach here, we should go to the blind select state diff --git a/source/joker.c b/source/joker.c index 3f07e42..8e6f392 100644 --- a/source/joker.c +++ b/source/joker.c @@ -3,6 +3,10 @@ #include "joker.h" #include "joker_gfx.h" #include "graphic_utils.h" +#include "card.h" + +#include +#include const static u8 joker_data_lut[MAX_JOKERS][2] = // Rarity, Value { @@ -10,6 +14,15 @@ const static u8 joker_data_lut[MAX_JOKERS][2] = // Rarity, Value {COMMON_JOKER, 5}, // Greedy Joker }; +/* So for the card objects, I needed them to be properly sorted + which is why they let you specify the layer index when creating a new card object. + Since the cards would overlap a lot in your hand, If they weren't sorted properly, it would look like a mess. + The joker objects are functionally identical to card objects, so they use the same logic. + But I'm going to use a simpler approach for the joker objects + since I'm lazy and sorting them wouldn't look good enough to warrant the effort. +*/ +static bool used_layers[MAX_JOKER_OBJECTS] = {false}; // Track used layers for joker sprites + void joker_init() { GRIT_CPY(&tile_mem[4][JOKER_TID], joker_gfxTiles); @@ -33,4 +46,71 @@ void joker_destroy(Joker **joker) if (*joker == NULL) return; free(*joker); *joker = NULL; -} \ No newline at end of file +} + +// JokerObject methods +JokerObject *joker_object_new(Joker *joker) +{ + JokerObject *joker_object = malloc(sizeof(JokerObject)); + + int layer = 0; + for (int i = 0; i < MAX_JOKER_OBJECTS; i++) + { + if (!used_layers[i]) + { + layer = i; + used_layers[i] = true; // Mark this layer as used + break; + } + } + + joker_object->joker = joker; + joker_object->sprite = sprite_new + ( + ATTR0_SQUARE | ATTR0_4BPP | ATTR0_AFF, + ATTR1_SIZE_32, + JOKER_TID + (JOKER_SPRITE_OFFSET * joker->id), + JOKER_PB, + JOKER_STARTING_LAYER + layer + ); + joker_object->tx = 0; // Target position + joker_object->ty = 0; + joker_object->x = 0; + joker_object->y = 0; + joker_object->vx = 0; + joker_object->vy = 0; + joker_object->tscale = float2fx(1.0f); // Target scale + joker_object->scale = float2fx(1.0f); + joker_object->vscale = float2fx(0.0f); + joker_object->trotation = float2fx(0.0f); // Target rotation + joker_object->rotation = float2fx(0.0f); + joker_object->vrotation = float2fx(0.0f); + + return joker_object; +} + +void joker_object_destroy(JokerObject **joker_object) +{ + if (*joker_object == NULL) return; + + int layer = sprite_get_layer((*joker_object)->sprite) - JOKER_STARTING_LAYER; + used_layers[layer] = false; + sprite_destroy(&(*joker_object)->sprite); // Destroy the sprite + joker_destroy(&(*joker_object)->joker); // Destroy the joker + free(*joker_object); + *joker_object = NULL; +} + +void joker_object_update(JokerObject *joker_object) +{ + // Re-use the card object update logic by casting the JokerObject to a CardObject temporarily + // This works because card_object_update doesn't use any card data, which is why CardObject should've been generic. my bad + CardObject *card_object = (CardObject *)joker_object; + card_object_update(card_object); +} + +void joker_object_score(JokerObject *joker_object, mm_word sound_id) // Another derived function of card_object +{ + CardObject *card_object = (CardObject *)joker_object; + card_object_score(card_object, sound_id); +} diff --git a/source/sprite.c b/source/sprite.c index 57c98a9..ca2ace1 100644 --- a/source/sprite.c +++ b/source/sprite.c @@ -79,6 +79,12 @@ void sprite_destroy(Sprite **sprite) *sprite = NULL; } +int sprite_get_layer(Sprite *sprite) +{ + if (sprite == NULL || sprite->obj == NULL) return -1; + return sprite->obj - obj_buffer; +} + // Sprite functions void sprite_init() {