diff --git a/graphics/joker_gfx25.png b/graphics/joker_gfx25.png index 104deee..507a60e 100644 Binary files a/graphics/joker_gfx25.png and b/graphics/joker_gfx25.png differ diff --git a/include/game.h b/include/game.h index 6e1268c..3dffa41 100644 --- a/include/game.h +++ b/include/game.h @@ -1,6 +1,8 @@ #ifndef GAME_H #define GAME_H +#include + #define MAX_HAND_SIZE 16 #define MAX_DECK_SIZE 52 #define MAX_JOKERS_HELD_SIZE 5 // This doesn't account for negatives right now. @@ -56,6 +58,7 @@ enum PlayState PLAY_SCORING_CARD_JOKERS, PLAY_SCORING_HELD_CARDS, PLAY_SCORING_INDEPENDENT_JOKERS, + PLAY_SCORING_HAND_SCORED_END, PLAY_ENDING, PLAY_ENDED }; @@ -109,11 +112,23 @@ int get_scored_card_index(void); bool is_joker_owned(int joker_id); bool card_is_face(Card *card); List* get_jokers_list(void); +List* get_expired_jokers_list(void); int get_deck_top(void); int get_num_discards_remaining(void); int get_num_hands_remaining(void); -int get_money(void); + +u32 get_chips(void); +void set_chips(u32 new_chips); +void display_chips(); +u32 get_mult(void); +void set_mult(u32 new_mult); +void display_mult(); +int get_money(void); +void set_money(int new_money); +void display_money(); +void set_retrigger(bool new_retrigger); + int get_game_speed(void); void set_game_speed(int new_game_speed); diff --git a/include/joker.h b/include/joker.h index 525f6e9..7abd765 100644 --- a/include/joker.h +++ b/include/joker.h @@ -139,8 +139,12 @@ int joker_get_sell_value(const Joker* joker); JokerObject *joker_object_new(Joker *joker); void joker_object_destroy(JokerObject **joker_object); void joker_object_update(JokerObject *joker_object); -void joker_object_shake(JokerObject *joker_object, mm_word sound_id); // This doesn't actually score anything, it just performs an animation and plays a sound effect -bool joker_object_score(JokerObject *joker_object, CardObject* card_object, enum JokerEvent joker_event, u32 *chips, u32 *mult, int *money, bool *retrigger); // This scores the joker and returns true if it was scored successfully (Card = NULL means the joker is independent and not scored by a card) +// This doesn't actually score anything, it just performs an animation and plays a sound effect +void joker_object_shake(JokerObject *joker_object, mm_word sound_id); +// This scores the joker and returns true if it was scored successfully +// card_object = NULL means the joker_event does not concern a particular Card, i.e. Independend or On_Blind_Selected +// as opposed to events that concern a particular card, i.e. On_Card_Scored or On_Card_Held +bool joker_object_score(JokerObject *joker_object, CardObject* card_object, enum JokerEvent joker_event); void joker_object_set_selected(JokerObject* joker_object, bool selected); bool joker_object_is_selected(JokerObject* joker_object); diff --git a/source/game.c b/source/game.c index f645b35..f32ef24 100644 --- a/source/game.c +++ b/source/game.c @@ -2,7 +2,6 @@ #include #include -#include #include #include "bitset.h" @@ -238,6 +237,7 @@ static bool sort_by_suit = false; static List _owned_jokers_list; static List _discarded_jokers_list; +static List _expired_jokers_list; BITSET_DEFINE(_avail_jokers_bitset, MAX_DEFINABLE_JOKERS) static List _shop_jokers_list; @@ -392,6 +392,11 @@ List* get_jokers_list(void) return &_owned_jokers_list; } +List* get_expired_jokers_list(void) +{ + return &_expired_jokers_list; +} + bool is_joker_owned(int joker_id) { ListItr itr = list_itr_create(&_owned_jokers_list); @@ -478,11 +483,42 @@ void set_game_speed(int new_game_speed) game_speed = new_game_speed; } + +u32 get_chips(void) +{ + return chips; +} + +void set_chips(u32 new_chips) +{ + chips = new_chips; +} + +u32 get_mult(void) +{ + return mult; +} + +void set_mult(u32 new_mult) +{ + mult = new_mult; +} + int get_money(void) { return money; } +void set_money(int new_money) +{ + money = new_money; +} + +void set_retrigger(bool new_retrigger) +{ + retrigger = new_retrigger; +} + // Consts @@ -1127,11 +1163,11 @@ void display_score(u32 value) tte_printf("#{P:%d,48; cx:0x%X000}%lu%c", x_offset, TTE_WHITE_PB, display_value, score_suffix); } -void display_money(int value) +void display_money() { - int x_offset = 32 - get_digits_odd(value) * TILE_SIZE; + int x_offset = 32 - get_digits_odd(money) * TILE_SIZE; tte_erase_rect_wrapper(MONEY_TEXT_RECT); - tte_printf("#{P:%d,%d; cx:0x%X000}$%d", x_offset, MONEY_TEXT_RECT.top, TTE_YELLOW_PB, value); + tte_printf("#{P:%d,%d; cx:0x%X000}$%d", x_offset, MONEY_TEXT_RECT.top, TTE_YELLOW_PB, money); } // Show/Hide flaming score effect if we will score @@ -1474,6 +1510,7 @@ void game_init() // Initialize all jokers list once _owned_jokers_list = list_create(); _discarded_jokers_list = list_create(); + _expired_jokers_list = list_create(); _shop_jokers_list = list_create(); // TODO: Move this to an initialization of the play scoring states _joker_scored_itr = list_itr_create(&_owned_jokers_list); @@ -1538,7 +1575,7 @@ void game_start() display_hands(hands); // Hand display_discards(discards); // Discard - display_money(money); // Set the money display + display_money(); // Set the money display tte_printf("#{P:%d,%d; cx:0x%X000}%d#{cx:0x%X000}/%d", ANTE_TEXT_RECT.left, ANTE_TEXT_RECT.top, TTE_YELLOW_PB, ante, TTE_WHITE_PB, MAX_ANTE); // Ante @@ -2191,12 +2228,8 @@ static bool check_and_score_joker_for_event(ListItr* starting_joker_itr, CardObj while((joker = list_itr_next(starting_joker_itr))) { - if (joker_object_score(joker, card_object, joker_event, &chips, &mult, &money, &retrigger)) + if (joker_object_score(joker, card_object, joker_event)) { - display_chips(); - display_mult(); - display_money(money); - return true; } } @@ -2241,7 +2274,7 @@ static bool play_before_scoring_cards_update() } // returns true if the scoring loop has returned early -static bool play_scoring_cards_update() +static bool play_scoring_cards_update(void) { if (timer % FRAMES(30) == 0 && timer > FRAMES(40)) { @@ -2261,6 +2294,7 @@ static bool play_scoring_cards_update() scored_card_index = hand_top; play_state = PLAY_SCORING_HELD_CARDS; + return false; } @@ -2331,7 +2365,6 @@ static bool play_scoring_card_jokers_update() // increment index to start seeking the next scoring card from the next card scored_card_index++; play_state = PLAY_SCORING_CARDS; - return false; } return false; @@ -2356,7 +2389,7 @@ static bool play_scoring_held_cards_update(int played_idx) } scored_card_index = 0; - + _joker_round_end_itr = list_itr_create(&_owned_jokers_list); play_state = PLAY_SCORING_INDEPENDENT_JOKERS; } @@ -2377,15 +2410,29 @@ static bool play_scoring_independent_jokers_update(int played_idx) return true; } - // Trigger hand end effect for all jokers once they are done scoring + scored_card_index = played_top + 1; // Reset the scored card index to the top of the played stack + + play_state = PLAY_SCORING_HAND_SCORED_END; + } + + return false; +} + +// Trigger hand end effect for all jokers once they are done scoring +static bool play_scoring_hand_scored_end_update(int played_idx) +{ + if (played_idx == 0 && (timer % FRAMES(30) == 0) && timer > FRAMES(40)) + { + + tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT); + if (check_and_score_joker_for_event(&_joker_round_end_itr, NULL, JOKER_EVENT_ON_HAND_SCORED_END)) { return true; } - play_state = PLAY_ENDING; timer = TM_ZERO; - scored_card_index = played_top + 1; // Reset the scored card index to the top of the played stack + play_state = PLAY_ENDING; } return false; @@ -2528,6 +2575,14 @@ static void played_cards_update_loop() return; } break; + + case PLAY_SCORING_HAND_SCORED_END: + + if (play_scoring_hand_scored_end_update(played_idx)) + { + return; + } + break; case PLAY_ENDING: @@ -2641,7 +2696,7 @@ static int calculate_interest_reward() static void game_round_end_cashout() { money += hands + blind_get_reward(current_blind) + calculate_interest_reward(); // Reward the player - display_money(money); + display_money(); hands = max_hands; // Reset the hands to the maximum discards = max_discards; // Reset the discards to the maximum @@ -3094,7 +3149,7 @@ static void game_shop_intro() static void game_shop_reroll(int *reroll_cost) { money -= *reroll_cost; - display_money(money); // Update the money display + display_money(); // Update the money display ListItr itr = list_itr_create(&_shop_jokers_list); JokerObject* joker_object; @@ -3173,7 +3228,7 @@ void game_sell_joker(int joker_idx) JokerObject* joker_object = (JokerObject*)list_get_at_idx(&_owned_jokers_list, joker_idx); money += joker_get_sell_value(joker_object->joker); - display_money(money); + display_money(); erase_price_under_sprite_object(joker_object->sprite_object); remove_owned_joker(joker_idx); @@ -3208,7 +3263,7 @@ static void game_shop_buy_joker(int shop_joker_idx) JokerObject *joker_object = (JokerObject*)list_get_at_idx(&_shop_jokers_list, shop_joker_idx); money -= joker_object->joker->value; // Deduct the money spent on the joker - display_money(money); // Update the money display + display_money(); // Update the money display erase_price_under_sprite_object(joker_object->sprite_object); sprite_object_set_focus(joker_object->sprite_object, false); add_to_held_jokers(joker_object); @@ -3669,6 +3724,43 @@ static void game_main_menu_on_update() } } +#define EXPIRE_ANIMATION_FRAME_COUNT 3 +static void expired_jokers_update_loop() +{ + if(list_is_empty(&_expired_jokers_list)) { + return; + } + + ListItr itr = list_itr_create(&_expired_jokers_list); + JokerObject* joker_object; + + while((joker_object = list_itr_next(&itr))) + { + joker_object_update(joker_object); + + // let just enough frames pass that we see it rotating and shrinking + if (timer % FRAMES(EXPIRE_ANIMATION_FRAME_COUNT) == 0) + { + // get joker idx + int expired_joker_idx = 0; + ListItr joker_itr = list_itr_create(&_owned_jokers_list); + JokerObject* expired_joker; + while ((expired_joker = list_itr_next(&joker_itr)) && expired_joker != joker_object) + { + expired_joker_idx++; + } + + // Removing expired Jokers here, instead of immediately like ones we + // sell or discard allow us to have a small shrink animation without + // the other owned Jokers rearranging themselves to fill the newly + // freed space, therefore obscuring the animation + remove_owned_joker(expired_joker_idx); + list_itr_remove_current_node(&itr); + joker_object_destroy(&joker_object); + } + } +} + static void discarded_jokers_update_loop() { if(list_is_empty(&_discarded_jokers_list)) { @@ -3720,6 +3812,7 @@ static void jokers_update_loop() { held_jokers_update_loop(); discarded_jokers_update_loop(); + expired_jokers_update_loop(); } static void game_over_anim_frame() @@ -3767,6 +3860,7 @@ static void game_over_on_exit() list_clear(&_owned_jokers_list); list_clear(&_discarded_jokers_list); + list_clear(&_expired_jokers_list); list_clear(&_shop_jokers_list); game_init(); @@ -3777,7 +3871,7 @@ static void game_over_on_exit() display_mult(); display_hands(hands); display_discards(discards); - display_money(money); + display_money(); tte_printf( "#{P:%d,%d; cx:0x%X000}%d#{cx:0x%X000}/%d", ANTE_TEXT_RECT.left, diff --git a/source/joker.c b/source/joker.c index 513c043..28213c9 100644 --- a/source/joker.c +++ b/source/joker.c @@ -265,7 +265,7 @@ void set_and_shift_text(char* str, int* cursor_pos_x, int* cursor_pos_y, int col *cursor_pos_x += joker_score_display_offset_px; } -bool joker_object_score(JokerObject *joker_object, CardObject* card_object, enum JokerEvent joker_event, u32 *chips, u32 *mult, int *money, bool *retrigger) +bool joker_object_score(JokerObject *joker_object, CardObject* card_object, enum JokerEvent joker_event) { if (joker_object == NULL) { @@ -280,11 +280,17 @@ bool joker_object_score(JokerObject *joker_object, CardObject* card_object, enum return false; } + u32 chips = get_chips(); + u32 mult = get_mult(); + int money = get_money(); + if (effect_flags_ret & JOKER_EFFECT_FLAG_RETRIGGER) { - *retrigger = joker_effect->retrigger; + set_retrigger(joker_effect->retrigger); } + // joker_effect.message will have been set if the Joker had anything custom to say + int cursorPosX = TILE_SIZE; // Offset of one tile to better center the text on the card int cursorPosY = 0; if (joker_event == JOKER_EVENT_ON_CARD_HELD) @@ -303,7 +309,7 @@ bool joker_object_score(JokerObject *joker_object, CardObject* card_object, enum mm_word sfx_id; if (effect_flags_ret & JOKER_EFFECT_FLAG_CHIPS) { - *chips = u32_protected_add(*chips, joker_effect->chips); + chips = u32_protected_add(chips, joker_effect->chips); char score_buffer[INT_MAX_DIGITS + 2]; // For '+' and null terminator snprintf(score_buffer, sizeof(score_buffer), "+%lu", joker_effect->chips); set_and_shift_text(score_buffer, &cursorPosX, &cursorPosY, TTE_BLUE_PB); @@ -311,7 +317,7 @@ bool joker_object_score(JokerObject *joker_object, CardObject* card_object, enum } if (effect_flags_ret & JOKER_EFFECT_FLAG_MULT) { - *mult = u32_protected_add(*mult, joker_effect->mult); + mult = u32_protected_add(mult, joker_effect->mult); char score_buffer[INT_MAX_DIGITS + 2]; snprintf(score_buffer, sizeof(score_buffer), "+%lu", joker_effect->mult); set_and_shift_text(score_buffer, &cursorPosX, &cursorPosY, TTE_RED_PB); @@ -320,7 +326,7 @@ bool joker_object_score(JokerObject *joker_object, CardObject* card_object, enum // if xmult is zero, DO NOT multiply by it if (effect_flags_ret & JOKER_EFFECT_FLAG_XMULT && joker_effect->xmult > 0) { - *mult = u32_protected_mult(*mult, joker_effect->xmult); + mult = u32_protected_mult(mult, joker_effect->xmult); char score_buffer[INT_MAX_DIGITS + 2]; snprintf(score_buffer, sizeof(score_buffer), "X%lu", joker_effect->xmult); set_and_shift_text(score_buffer, &cursorPosX, &cursorPosY, TTE_RED_PB); @@ -328,7 +334,7 @@ bool joker_object_score(JokerObject *joker_object, CardObject* card_object, enum } if (effect_flags_ret & JOKER_EFFECT_FLAG_MONEY) { - *money += joker_effect->money; + money += joker_effect->money; char score_buffer[INT_MAX_DIGITS + 2]; snprintf(score_buffer, sizeof(score_buffer), "%d$", joker_effect->money); set_and_shift_text(score_buffer, &cursorPosX, &cursorPosY, TTE_YELLOW_PB); @@ -340,11 +346,23 @@ bool joker_object_score(JokerObject *joker_object, CardObject* card_object, enum { set_and_shift_text(joker_effect->message, &cursorPosX, &cursorPosY, TTE_WHITE_PB); } + // this will start the Joker expire animation if (effect_flags_ret & JOKER_EFFECT_FLAG_EXPIRE && joker_effect->expire) { - // TODO make Jokers expire + joker_object_shake(joker_object, UNDEFINED); + list_push_back(get_expired_jokers_list(), joker_object); } + // Update values + set_chips(chips); + set_mult(mult); + set_money(money); + + // Update displays + display_chips(); + display_mult(); + display_money(); + joker_object_shake(joker_object, sfx_id); return true; diff --git a/source/joker_effects.c b/source/joker_effects.c index 313d8a3..504adc4 100644 --- a/source/joker_effects.c +++ b/source/joker_effects.c @@ -976,7 +976,9 @@ static u32 blueprint_brainstorm_joker_effect(Joker *joker, Card *scored_card, en // No need for this kind of init since these Jokers // will have their data copied when needed - if (joker_event == JOKER_EVENT_ON_JOKER_CREATED || joker_event == JOKER_EVENT_ON_ROUND_END) + if (joker_event == JOKER_EVENT_ON_JOKER_CREATED || + joker_event == JOKER_EVENT_ON_HAND_SCORED_END || + joker_event == JOKER_EVENT_ON_ROUND_END) { return effect_flags_ret; } @@ -1090,8 +1092,6 @@ static u32 hack_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent jo } -// Note: Joker expiration is not yet implemented so Seltzer cannot be made active before it does. -GBLA_UNUSED static u32 seltzer_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event, JokerEffect **joker_effect) { u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; @@ -1110,28 +1110,33 @@ static u32 seltzer_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent break; case JOKER_EVENT_ON_CARD_SCORED_END: - // Works the same way as Dusk, but checks if it can still trigger - if (*p_hands_left_until_exp > 0) + // Works the same way as Dusk + // No need to check for p_hands_left_until_exp because the Joker + // will be destroyed the moment we hit 0 + *joker_effect = &shared_joker_effect; + + (*joker_effect)->retrigger = ((*p_last_retriggered_idx) < get_scored_card_index()); + if ((*joker_effect)->retrigger) { - *joker_effect = &shared_joker_effect; - - (*joker_effect)->retrigger = (*p_last_retriggered_idx < get_scored_card_index()); - if ((*joker_effect)->retrigger) - { - *p_last_retriggered_idx = get_scored_card_index(); - (*joker_effect)->message = "Again!"; - effect_flags_ret = JOKER_EFFECT_FLAG_RETRIGGER | JOKER_EFFECT_FLAG_MESSAGE; - } + *p_last_retriggered_idx = get_scored_card_index(); + (*joker_effect)->message = "Again!"; + effect_flags_ret = JOKER_EFFECT_FLAG_RETRIGGER | JOKER_EFFECT_FLAG_MESSAGE; } break; case JOKER_EVENT_ON_HAND_SCORED_END: *joker_effect = &shared_joker_effect; effect_flags_ret = JOKER_EFFECT_FLAG_MESSAGE; + + (*p_hands_left_until_exp)--; if (*p_hands_left_until_exp > 0) { - *p_hands_left_until_exp -= 1; - (*joker_effect)->message = "-1"; + // Need to do this for now because the message's memory can't really be allocated + // So we can't use snprintf to craft a message depending on the number of hands left + static const char* seltzer_messages[] = { + "1", "2", "3", "4", "5", "6", "7", "8", "9" + }; + (*joker_effect)->message = (char*)seltzer_messages[(*p_hands_left_until_exp) - 1]; } else { @@ -1244,12 +1249,12 @@ const JokerInfo joker_registry[] = { UNCOMMON_JOKER, 7, joker_effect_noop, }, // 48 Four Fingers { COMMON_JOKER, 4, scholar_joker_effect }, // 49 { UNCOMMON_JOKER, 8, fibonnaci_joker_effect }, // 50 + { UNCOMMON_JOKER, 6, seltzer_joker_effect, }, // 51 // The following jokers don't have sprites yet, // uncomment them when their sprites are added. #if 0 - { UNCOMMON_JOKER, 6, seltzer_joker_effect, }, { COMMON_JOKER, 5, shoot_the_moon_joker_effect, }, #endif };