Implement cards held in hand effects (#205)

* Adapt existing Jokers to the Held Card mechanic + effect display location

* Add suggestions from @ricfehr3

* Add suggestions from @MeirGavish

---------

Co-authored-by: MathisMartin31 <mathis.martin31@gmail.com>
This commit is contained in:
Geralt
2025-11-12 12:18:53 +01:00
committed by GitHub
parent e0cffab768
commit 4183f563ab
5 changed files with 200 additions and 156 deletions
+2 -2
View File
@@ -6,8 +6,6 @@
#define MAX_JOKERS_HELD_SIZE 5 // This doesn't account for negatives right now.
#define MAX_SHOP_JOKERS 2 // TODO: Make this dynamic and allow for other items besides jokers
#define MAX_SELECTION_SIZE 5
#define MAX_CARD_SCORE_DIGITS 2 // Current digit limit for score received from cards including mult etc. from jokers
#define MAX_CARD_SCORE_STR_LEN (MAX_CARD_SCORE_DIGITS + 1) // For the '+' or 'X'
#define FRAMES(x) (((x) + game_speed - 1) / game_speed)
// TODO: Can make these dynamic to support interest-related jokers and vouchers
@@ -53,7 +51,9 @@ enum HandState
enum PlayState
{
PLAY_PLAYING,
PLAY_BEFORE_SCORING,
PLAY_SCORING_CARDS,
PLAY_SCORING_HELD,
PLAY_SCORING_JOKERS,
PLAY_ENDING,
PLAY_ENDED
+4 -4
View File
@@ -49,9 +49,11 @@
// Joker per Joker basis (see if it's there, then do something, e.g. Pareidolia, Baseball Card)
enum JokerEvent
{
JOKER_EVENT_ON_HAND_PLAYED, // Triggers only once when the hand is played
JOKER_EVENT_ON_JOKER_CREATED, // Triggers only once when the Joker is created, mainly used for data initialization
JOKER_EVENT_ON_HAND_PLAYED, // Triggers only once when the hand is played and before the cards are scored
JOKER_EVENT_ON_CARD_SCORED, // Triggers when a played card scores (e.g. Walkie Talkie, Fibonnacci...)
JOKER_EVENT_ON_CARD_SCORED_END, // Triggers after the card has finishd scoring (e.g. retrigger Jokers)
JOKER_EVENT_ON_CARD_HELD, // Triggers when going through held cards
JOKER_EVENT_INDEPENDENT, // Joker will trigger normally, when Jokers are scored (e.g. base Joker)
JOKER_EVENT_ON_HAND_SCORED_END, // Triggers when entire hand has finished scoring (e.g. food Jokers)
JOKER_EVENT_ON_HAND_DISCARDED, // Triggers when discarding a hand
@@ -105,12 +107,10 @@ typedef struct // These jokers are triggered after the played hand has finished
} JokerEffect;
typedef JokerEffect (*JokerEffectFunc)(Joker *joker, Card *scored_card, enum JokerEvent joker_event);
typedef void (*JokerCallbackOnCreated)(Joker *joker);
typedef struct {
u8 rarity;
u8 base_value;
JokerEffectFunc joker_effect;
JokerCallbackOnCreated on_joker_created;
} JokerInfo;
const JokerInfo* get_joker_registry_entry(int joker_id);
size_t get_joker_registry_size(void);
@@ -129,7 +129,7 @@ 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, Card* scored_card, enum JokerEvent joker_event, int *chips, int *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)
bool joker_object_score(JokerObject *joker_object, CardObject* card_object, enum JokerEvent joker_event, int *chips, int *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)
void joker_object_set_selected(JokerObject* joker_object, bool selected);
bool joker_object_is_selected(JokerObject* joker_object);
+55 -11
View File
@@ -541,6 +541,7 @@ static const Rect TEMP_SCORE_RECT = {8, 64, 64, 72 };
static const Rect SCORE_RECT = {32, 48, 64, 56 };
static const Rect PLAYED_CARDS_SCORES_RECT = {72, 48, 240, 56 };
static const Rect HELD_CARDS_SCORES_RECT = {72, 108, 240, 116 };
static const Rect BLIND_TOKEN_TEXT_RECT = {80, 72, 200, 160 };
static const Rect MONEY_TEXT_RECT = {8, 120, 64, 128 };
static const Rect CHIPS_TEXT_RECT = {8, 80, 32, 88 };
@@ -2181,13 +2182,13 @@ static void cards_in_hand_update_loop(bool* discarded_card, int* played_selectio
}
// returns true if a joker was scored, false otherwise
static bool check_and_score_joker_for_event(ListItr* starting_joker_itr, Card* played_card, enum JokerEvent joker_event)
static bool check_and_score_joker_for_event(ListItr* starting_joker_itr, CardObject* card_object, enum JokerEvent joker_event)
{
JokerObject* joker;
while((joker = list_itr_next(starting_joker_itr)))
{
if (joker_object_score(joker, played_card, joker_event, &chips, &mult, &money, &retrigger))
if (joker_object_score(joker, card_object, joker_event, &chips, &mult, &money, &retrigger))
{
display_chips();
display_mult();
@@ -2231,8 +2232,9 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
if (*played_selections == 0)
{
play_state = PLAY_SCORING_CARDS;
_joker_scored_itr = list_itr_create(&_owned_jokers_list);
timer = TM_ZERO;
play_state = PLAY_BEFORE_SCORING;
}
}
@@ -2242,11 +2244,26 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
}
break;
case PLAY_BEFORE_SCORING:
// Activate Jokers with an effect just before the hand is scored
if (check_and_score_joker_for_event(&_joker_scored_itr, NULL, JOKER_EVENT_ON_HAND_PLAYED))
{
return;
}
if (card_object_is_selected(played[i]) && played_top - i >= *played_selections)
{
played_y -= int2fx(10);
}
play_state = PLAY_SCORING_CARDS;
break;
case PLAY_SCORING_CARDS:
if (i == 0 && (timer % FRAMES(30) == 0) && timer > FRAMES(40))
{
// We are about to score played Cards, then Jokers.
// If we need to retrigger, then we have scored a card previously
// and thus have incremented scored_card_index by 1.
@@ -2268,7 +2285,7 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
// Trigger all Jokers after each card scored
if (*played_selections > 0)
{
if (check_and_score_joker_for_event(&_joker_scored_itr, played[*played_selections - 1]->card, JOKER_EVENT_ON_CARD_SCORED))
if (check_and_score_joker_for_event(&_joker_scored_itr, played[*played_selections - 1], JOKER_EVENT_ON_CARD_SCORED))
{
return;
}
@@ -2276,7 +2293,7 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
// Trigger all Jokers that have an effect when a card finishes scoring
// (e.g. retriggers) after activating all the other scored_card Jokers normally
_joker_scored_itr = list_itr_create(&_owned_jokers_list);
if (check_and_score_joker_for_event(&_joker_scored_itr, played[*played_selections - 1]->card, JOKER_EVENT_ON_CARD_SCORED_END))
if (check_and_score_joker_for_event(&_joker_scored_itr, played[*played_selections - 1], JOKER_EVENT_ON_CARD_SCORED_END))
{
return;
}
@@ -2310,11 +2327,10 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
}
}
// advance state after going past the last card (exited the loop without returning)
play_state = PLAY_SCORING_JOKERS;
play_state = PLAY_SCORING_HELD;
// reuse these variables for held cards
_joker_scored_itr = list_itr_create(&_owned_jokers_list);
scored_card_index = 0; // reuse this variable for held cards
return;
scored_card_index = hand_top;
}
if (card_object_is_selected(played[i]))
@@ -2322,7 +2338,35 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
played_y -= int2fx(10);
}
break;
case PLAY_SCORING_HELD:
if (i == 0 && (timer % FRAMES(30) == 0) && timer > FRAMES(40))
{
tte_erase_rect_wrapper(HELD_CARDS_SCORES_RECT);
// Go through all held cards and see if they activate Jokers
for ( ; scored_card_index >= 0; scored_card_index--)
{
if (check_and_score_joker_for_event(&_joker_scored_itr, hand[scored_card_index], JOKER_EVENT_ON_CARD_HELD))
{
card_object_shake(hand[scored_card_index], SFX_CARD_SELECT);
return;
}
_joker_scored_itr = list_itr_create(&_owned_jokers_list);
}
scored_card_index = 0;
play_state = PLAY_SCORING_JOKERS;
}
if (card_object_is_selected(played[i]))
{
played_y -= int2fx(10);
}
break;
// Score Jokers normally
case PLAY_SCORING_JOKERS:
+33 -15
View File
@@ -14,6 +14,8 @@
#include "pool.h"
#define JOKER_SCORE_TEXT_Y 48
#define HELD_CARD_SCORE_TEXT_Y 108
#define MAX_CARD_SCORE_STR_LEN 2
#define NUM_JOKERS_PER_SPRITESHEET 2
static const unsigned int *joker_gfxTiles[] =
@@ -144,7 +146,9 @@ Joker *joker_new(u8 id)
joker->value = jinfo->base_value + edition_price_lut[joker->modifier];
joker->rarity = jinfo->rarity;
joker->data = 0;
jinfo->on_joker_created(joker);
// initialize persistent Joker data if needed
jinfo->joker_effect(joker, NULL, JOKER_EVENT_ON_JOKER_CREATED);
return joker;
}
@@ -158,7 +162,7 @@ void joker_destroy(Joker **joker)
JokerEffect joker_get_score_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event)
{
const JokerInfo *jinfo = get_joker_registry_entry(joker->id);
if (!jinfo || jinfo->joker_effect == NULL) return (JokerEffect){0};
if (!jinfo) return (JokerEffect){0};
return jinfo->joker_effect(joker, scored_card, joker_event);
}
@@ -248,25 +252,25 @@ void joker_object_shake(JokerObject *joker_object, mm_word sound_id)
sprite_object_shake(joker_object->sprite_object, sound_id);
}
void set_and_shift_text(char* str, int* cursor_pos_x, int color_pb)
void set_and_shift_text(char* str, int* cursor_pos_x, int* cursor_pos_y, int color_pb)
{
tte_set_pos(*cursor_pos_x, JOKER_SCORE_TEXT_Y);
tte_set_pos(*cursor_pos_x, *cursor_pos_y);
tte_set_special(color_pb * TTE_SPECIAL_PB_MULT_OFFSET);
tte_write(str);
// + 1 For space
const int joker_score_display_offset_px = (MAX_CARD_SCORE_STR_LEN + 1)*TTE_CHAR_SIZE;
const int joker_score_display_offset_px = (MAX_CARD_SCORE_STR_LEN + 1) * TTE_CHAR_SIZE;
*cursor_pos_x += joker_score_display_offset_px;
}
bool joker_object_score(JokerObject *joker_object, Card* scored_card, enum JokerEvent joker_event, int *chips, int *mult, int *money, bool *retrigger)
bool joker_object_score(JokerObject *joker_object, CardObject* card_object, enum JokerEvent joker_event, int *chips, int *mult, int *money, bool *retrigger)
{
if (joker_object == NULL)
{
return false;
}
JokerEffect joker_effect = joker_get_score_effect(joker_object->joker, scored_card, joker_event);
JokerEffect joker_effect = joker_get_score_effect(joker_object->joker, card_object->card, joker_event);
if (memcmp(&joker_effect, &(JokerEffect){0}, sizeof(JokerEffect)) == 0)
{
@@ -280,35 +284,49 @@ bool joker_object_score(JokerObject *joker_object, Card* scored_card, enum Joker
*retrigger = joker_effect.retrigger;
// joker_effect.message will have been set if the Joker had anything custom to say
int cursorPosX = fx2int(joker_object->sprite_object->x) + 8; // Offset of 16 pixels to center the text on the card
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)
{
// display the text on top of the card instead of below the Joker for Held Cards effects
// scored_card cannot be NULL here because of the joker event
cursorPosX += fx2int(card_object->sprite_object->x);
cursorPosY = HELD_CARD_SCORE_TEXT_Y;
}
else
{
cursorPosX += fx2int(joker_object->sprite_object->x);
cursorPosY = JOKER_SCORE_TEXT_Y;
}
if (joker_effect.chips > 0)
{
char score_buffer[INT_MAX_DIGITS + 2]; // For '+' and null terminator
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.chips);
set_and_shift_text(score_buffer, &cursorPosX, TTE_BLUE_PB);
set_and_shift_text(score_buffer, &cursorPosX, &cursorPosY, TTE_BLUE_PB);
}
if (joker_effect.mult > 0)
{
char score_buffer[INT_MAX_DIGITS + 2];
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.mult);
set_and_shift_text(score_buffer, &cursorPosX, TTE_RED_PB);
set_and_shift_text(score_buffer, &cursorPosX, &cursorPosY, TTE_RED_PB);
}
if (joker_effect.xmult > 0)
{
char score_buffer[INT_MAX_DIGITS + 2];
snprintf(score_buffer, sizeof(score_buffer), "X%d", joker_effect.xmult);
set_and_shift_text(score_buffer, &cursorPosX, TTE_RED_PB);
set_and_shift_text(score_buffer, &cursorPosX, &cursorPosY, TTE_RED_PB);
}
if (joker_effect.money > 0)
{
char score_buffer[INT_MAX_DIGITS + 2];
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.money);
set_and_shift_text(score_buffer, &cursorPosX, TTE_YELLOW_PB);
snprintf(score_buffer, sizeof(score_buffer), "%d$", joker_effect.money);
set_and_shift_text(score_buffer, &cursorPosX, &cursorPosY, TTE_YELLOW_PB);
}
// custom message for Jokers (including retriggers where Jokers will say "Again!")
if (joker_effect.message[0] != '\0') // Message is not empty
if (joker_effect.message != NULL && joker_effect.message[0] != '\0') // Message is not empty
{
set_and_shift_text(joker_effect.message, &cursorPosX, TTE_WHITE_PB);
set_and_shift_text(joker_effect.message, &cursorPosX, &cursorPosY, TTE_WHITE_PB);
}
if (joker_effect.expire)
{
+106 -124
View File
@@ -20,6 +20,11 @@ if (checked_event != restricted_event) \
// Joker Effect functions
static JokerEffect joker_effect_noop(Joker *joker, Card *scored_card, enum JokerEvent joker_event)
{
return (JokerEffect){0};
}
static JokerEffect default_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event)
{
JokerEffect effect = {0};
@@ -399,23 +404,41 @@ static JokerEffect blue_joker_effect(Joker *joker, Card *scored_card, enum Joker
static JokerEffect raised_fist_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event)
{
JokerEffect effect = {0};
SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect)
// Find the lowest rank card in hand
// Aces are always considered high value, even in an ace-low straight
u8 lowest_value = IMPOSSIBLY_HIGH_CARD_VALUE;
CardObject** hand = get_hand_array();
int hand_size = hand_get_size();
for (int i = 0; i < hand_size; i++ )
s32* p_lowest_value_index = &(joker->data);
switch (joker_event)
{
u8 value = card_get_value(hand[i]->card);
if (lowest_value > value)
lowest_value = value;
}
// Use this event to compute the index of the lowest value card only once.
// Aces are always considered high value, even in an ace-low straight
case JOKER_EVENT_ON_HAND_PLAYED:
// index initialized at 0 but accessed only if
// hand_size > 0 so we're never out of bounds
*p_lowest_value_index = 0;
u8 lowest_value = IMPOSSIBLY_HIGH_CARD_VALUE;
CardObject** hand = get_hand_array();
int hand_size = hand_get_size();
for (int i = 0; i < hand_size; i++ )
{
u8 value = card_get_value(hand[i]->card);
if (lowest_value > value)
{
*p_lowest_value_index = i;
lowest_value = value;
}
}
break;
if (lowest_value != IMPOSSIBLY_HIGH_CARD_VALUE)
effect.mult = lowest_value * 2;
case JOKER_EVENT_ON_CARD_HELD:
if (get_scored_card_index() == *p_lowest_value_index)
{
effect.mult = 2 * card_get_value(scored_card);
}
break;
default:
break;
}
return effect;
}
@@ -424,17 +447,11 @@ static JokerEffect reserved_parking_joker_effect(Joker *joker, Card *scored_card
{
JokerEffect effect = {0};
// TODO: switch from INDEPENDENT to CARD_HELD when it's implemented
SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect)
SCORE_ON_EVENT_ONLY(JOKER_EVENT_ON_CARD_HELD, joker_event, effect)
CardObject** hand = get_hand_array();
int hand_size = hand_get_size();
for (int i = 0; i < hand_size; i++ )
if ((random() % 2 == 0) && card_is_face(scored_card))
{
if ((random() % 2 == 0) && card_is_face(hand[i]->card))
{
effect.money += 1;
}
effect.money++;
}
return effect;
@@ -583,7 +600,7 @@ static JokerEffect hanging_chad_joker_effect(Joker *joker, Card *scored_card, en
switch (joker_event)
{
case JOKER_EVENT_ON_HAND_SCORED_END:
case JOKER_EVENT_ON_HAND_PLAYED:
*p_remaining_retriggers = 2;
break;
@@ -724,17 +741,11 @@ static JokerEffect shoot_the_moon_joker_effect(Joker *joker, Card *scored_card,
{
JokerEffect effect = {0};
// TODO: switch from CARD_SCORED to CARD_HELD when triggering held cards is implemented
SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect)
SCORE_ON_EVENT_ONLY(JOKER_EVENT_ON_CARD_HELD, joker_event, effect)
CardObject** hand = get_hand_array();
int hand_size = hand_get_size();
for (int i = 0; i < hand_size; i++ )
if (scored_card->rank == QUEEN)
{
if (hand[i]->card->rank == QUEEN)
{
effect.mult += 13;
}
effect.mult += 13;
}
return effect;
@@ -749,7 +760,7 @@ static JokerEffect photograph_joker_effect(Joker *joker, Card *scored_card, enum
switch (joker_event)
{
case JOKER_EVENT_ON_HAND_SCORED_END:
case JOKER_EVENT_ON_HAND_PLAYED:
*p_first_face_index = UNDEFINED;
break;
@@ -801,7 +812,7 @@ static JokerEffect dusk_joker_effect(Joker *joker, Card *scored_card, enum Joker
switch (joker_event)
{
case JOKER_EVENT_ON_HAND_SCORED_END:
case JOKER_EVENT_ON_HAND_PLAYED:
*p_last_retriggered_index = UNDEFINED;
break;
@@ -880,7 +891,7 @@ static JokerEffect hack_joker_effect(Joker *joker, Card *scored_card, enum Joker
switch (joker_event)
{
case JOKER_EVENT_ON_HAND_SCORED_END:
case JOKER_EVENT_ON_HAND_PLAYED:
*p_last_retriggered_index = UNDEFINED;
break;
@@ -920,8 +931,15 @@ static JokerEffect seltzer_joker_effect(Joker *joker, Card *scored_card, enum Jo
switch (joker_event)
{
case JOKER_EVENT_ON_HAND_SCORED_END:
case JOKER_EVENT_ON_JOKER_CREATED:
*p_hands_left_until_exp = 10; // remaining retriggered hands
break;
case JOKER_EVENT_ON_HAND_PLAYED:
*p_last_retriggered_idx = UNDEFINED;
break;
case JOKER_EVENT_ON_HAND_SCORED_END:
*p_hands_left_until_exp -= 1;
if (*p_hands_left_until_exp <= 0)
{
@@ -962,7 +980,7 @@ static JokerEffect sock_and_buskin_joker_effect(Joker *joker, Card *scored_card,
switch (joker_event)
{
case JOKER_EVENT_ON_HAND_SCORED_END:
case JOKER_EVENT_ON_HAND_PLAYED:
*p_last_retriggered_face_index = UNDEFINED;
break;
@@ -983,42 +1001,6 @@ static JokerEffect sock_and_buskin_joker_effect(Joker *joker, Card *scored_card,
return effect;
}
// ON JOKER CREATED Callbacks
// For Jokers that don't need to do anything when created
void on_joker_created_noop(Joker *joker) {}
void hanging_chad_on_joker_created(Joker *joker)
{
joker->data = 2; // retriggers left, reset to 2 at round end
}
void dusk_on_joker_created(Joker *joker)
{
joker->data = UNDEFINED; // previously retriggered card index
}
void hack_on_joker_created(Joker *joker)
{
joker->data = UNDEFINED; // previously retriggered card index
}
void photograph_on_joker_created(Joker *joker)
{
joker->data = UNDEFINED; // First scoring face card index
}
void sock_and_buskin_on_joker_created(Joker *joker)
{
joker->data = UNDEFINED; // previously retriggered face card index
}
void seltzer_on_joker_created(Joker *joker)
{
joker->halves.data0 = UNDEFINED; // previously retriggered card index
joker->halves.data1 = 10; // remaining retriggered hands
}
/* The index of a joker in the registry matches its ID.
* The joker sprites are matched by ID so the position in the registry
@@ -1030,57 +1012,57 @@ void seltzer_on_joker_created(Joker *joker)
* Otherwise the order is similar to the wiki.
*/
const JokerInfo joker_registry[] = {
{ COMMON_JOKER, 2, default_joker_effect, on_joker_created_noop }, // DEFAULT_JOKER_ID = 0
{ COMMON_JOKER, 5, greedy_joker_effect, on_joker_created_noop }, // GREEDY_JOKER_ID = 1
{ COMMON_JOKER, 5, lusty_joker_effect, on_joker_created_noop }, // etc... 2
{ COMMON_JOKER, 5, wrathful_joker_effect, on_joker_created_noop }, // 3
{ COMMON_JOKER, 5, gluttonous_joker_effect, on_joker_created_noop }, // 4
{ COMMON_JOKER, 3, jolly_joker_effect, on_joker_created_noop }, // 5
{ COMMON_JOKER, 4, zany_joker_effect, on_joker_created_noop }, // 6
{ COMMON_JOKER, 4, mad_joker_effect, on_joker_created_noop }, // 7
{ COMMON_JOKER, 4, crazy_joker_effect, on_joker_created_noop }, // 8
{ COMMON_JOKER, 4, droll_joker_effect, on_joker_created_noop }, // 9
{ COMMON_JOKER, 3, sly_joker_effect, on_joker_created_noop }, // 10
{ COMMON_JOKER, 4, wily_joker_effect, on_joker_created_noop }, // 11
{ COMMON_JOKER, 4, clever_joker_effect, on_joker_created_noop }, // 12
{ COMMON_JOKER, 4, devious_joker_effect, on_joker_created_noop }, // 13
{ COMMON_JOKER, 4, crafty_joker_effect, on_joker_created_noop }, // 14
{ COMMON_JOKER, 5, half_joker_effect, on_joker_created_noop }, // 15
{ UNCOMMON_JOKER, 8, joker_stencil_effect, on_joker_created_noop }, // 16
{ COMMON_JOKER, 5, banner_joker_effect, on_joker_created_noop }, // 17
{ COMMON_JOKER, 4, walkie_talkie_joker_effect, on_joker_created_noop }, // 18
{ UNCOMMON_JOKER, 8, fibonnaci_joker_effect, on_joker_created_noop }, // 19
{ UNCOMMON_JOKER, 6, blackboard_joker_effect, on_joker_created_noop }, // 20
{ COMMON_JOKER, 5, mystic_summit_joker_effect, on_joker_created_noop }, // 21
{ COMMON_JOKER, 4, misprint_joker_effect, on_joker_created_noop }, // 22
{ COMMON_JOKER, 4, even_steven_joker_effect, on_joker_created_noop }, // 23
{ COMMON_JOKER, 5, blue_joker_effect, on_joker_created_noop }, // 24
{ COMMON_JOKER, 4, odd_todd_joker_effect, on_joker_created_noop }, // 25
{ UNCOMMON_JOKER, 7, NULL, /* Shortcut */ on_joker_created_noop }, // 26
{ COMMON_JOKER, 4, business_card_joker_effect, on_joker_created_noop }, // 27
{ COMMON_JOKER, 2, default_joker_effect }, // DEFAULT_JOKER_ID = 0
{ COMMON_JOKER, 5, greedy_joker_effect }, // GREEDY_JOKER_ID = 1
{ COMMON_JOKER, 5, lusty_joker_effect }, // etc... 2
{ COMMON_JOKER, 5, wrathful_joker_effect }, // 3
{ COMMON_JOKER, 5, gluttonous_joker_effect }, // 4
{ COMMON_JOKER, 3, jolly_joker_effect }, // 5
{ COMMON_JOKER, 4, zany_joker_effect }, // 6
{ COMMON_JOKER, 4, mad_joker_effect }, // 7
{ COMMON_JOKER, 4, crazy_joker_effect }, // 8
{ COMMON_JOKER, 4, droll_joker_effect }, // 9
{ COMMON_JOKER, 3, sly_joker_effect }, // 10
{ COMMON_JOKER, 4, wily_joker_effect }, // 11
{ COMMON_JOKER, 4, clever_joker_effect }, // 12
{ COMMON_JOKER, 4, devious_joker_effect }, // 13
{ COMMON_JOKER, 4, crafty_joker_effect }, // 14
{ COMMON_JOKER, 5, half_joker_effect }, // 15
{ UNCOMMON_JOKER, 8, joker_stencil_effect }, // 16
{ COMMON_JOKER, 5, banner_joker_effect }, // 17
{ COMMON_JOKER, 4, walkie_talkie_joker_effect }, // 18
{ UNCOMMON_JOKER, 8, fibonnaci_joker_effect }, // 19
{ UNCOMMON_JOKER, 6, blackboard_joker_effect }, // 20
{ COMMON_JOKER, 5, mystic_summit_joker_effect }, // 21
{ COMMON_JOKER, 4, misprint_joker_effect }, // 22
{ COMMON_JOKER, 4, even_steven_joker_effect }, // 23
{ COMMON_JOKER, 5, blue_joker_effect }, // 24
{ COMMON_JOKER, 4, odd_todd_joker_effect }, // 25
{ UNCOMMON_JOKER, 7, joker_effect_noop, }, // 26 Shortcut
{ COMMON_JOKER, 4, business_card_joker_effect }, // 27
// Business card should be paired with Shortcut for palette optimization when it's added
{ COMMON_JOKER, 4, scary_face_joker_effect, on_joker_created_noop }, // 28
{ UNCOMMON_JOKER, 7, bootstraps_joker_effect, on_joker_created_noop }, // 29
{ UNCOMMON_JOKER, 5, NULL, on_joker_created_noop }, // 30 Pareidolia
{ COMMON_JOKER, 6, reserved_parking_joker_effect, on_joker_created_noop }, // 31
{ COMMON_JOKER, 4, abstract_joker_effect, on_joker_created_noop }, // 32
{ UNCOMMON_JOKER, 6, bull_joker_effect, on_joker_created_noop }, // 33
{ RARE_JOKER, 8, the_duo_joker_effect, on_joker_created_noop }, // 34
{ RARE_JOKER, 8, the_trio_joker_effect, on_joker_created_noop }, // 35
{ RARE_JOKER, 8, the_family_joker_effect, on_joker_created_noop }, // 36
{ RARE_JOKER, 8, the_order_joker_effect, on_joker_created_noop }, // 37
{ RARE_JOKER, 8, the_tribe_joker_effect, on_joker_created_noop }, // 38
{ RARE_JOKER, 10, blueprint_joker_effect, on_joker_created_noop }, // 39
{ RARE_JOKER, 10, brainstorm_joker_effect, on_joker_created_noop }, // 40
{ COMMON_JOKER, 5, raised_fist_joker_effect, on_joker_created_noop }, // 41
{ COMMON_JOKER, 4, smiley_face_joker_effect, on_joker_created_noop }, // 42
{ UNCOMMON_JOKER, 6, acrobat_joker_effect, on_joker_created_noop }, // 43
{ UNCOMMON_JOKER, 5, dusk_joker_effect, dusk_on_joker_created }, // 44
{ UNCOMMON_JOKER, 6, sock_and_buskin_joker_effect, sock_and_buskin_on_joker_created }, // 45
{ UNCOMMON_JOKER, 6, hack_joker_effect, hack_on_joker_created }, // 46
{ COMMON_JOKER, 4, hanging_chad_joker_effect, hanging_chad_on_joker_created }, // 47
{ UNCOMMON_JOKER, 7, NULL,/* Four Fingers */ on_joker_created_noop }, // 48
{ COMMON_JOKER, 4, scholar_joker_effect, on_joker_created_noop }, // 49
{ COMMON_JOKER, 4, scary_face_joker_effect }, // 28
{ UNCOMMON_JOKER, 7, bootstraps_joker_effect }, // 29
{ UNCOMMON_JOKER, 5, joker_effect_noop }, // 30 Pareidolia
{ COMMON_JOKER, 6, reserved_parking_joker_effect }, // 31
{ COMMON_JOKER, 4, abstract_joker_effect }, // 32
{ UNCOMMON_JOKER, 6, bull_joker_effect }, // 33
{ RARE_JOKER, 8, the_duo_joker_effect }, // 34
{ RARE_JOKER, 8, the_trio_joker_effect }, // 35
{ RARE_JOKER, 8, the_family_joker_effect }, // 36
{ RARE_JOKER, 8, the_order_joker_effect }, // 37
{ RARE_JOKER, 8, the_tribe_joker_effect }, // 38
{ RARE_JOKER, 10, blueprint_joker_effect }, // 39
{ RARE_JOKER, 10, brainstorm_joker_effect }, // 40
{ COMMON_JOKER, 5, raised_fist_joker_effect }, // 41
{ COMMON_JOKER, 4, smiley_face_joker_effect }, // 42
{ UNCOMMON_JOKER, 6, acrobat_joker_effect }, // 43
{ UNCOMMON_JOKER, 5, dusk_joker_effect }, // 44
{ UNCOMMON_JOKER, 6, sock_and_buskin_joker_effect }, // 45
{ UNCOMMON_JOKER, 6, hack_joker_effect }, // 46
{ COMMON_JOKER, 4, hanging_chad_joker_effect }, // 47
{ UNCOMMON_JOKER, 7, joker_effect_noop, }, // 48 Four Fingers
{ COMMON_JOKER, 4, scholar_joker_effect }, // 49
// The following jokers don't have sprites yet,
// uncomment them when their sprites are added.