diff --git a/Makefile b/Makefile index 55f4d21..9bee64f 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ GRAPHICS := graphics #--------------------------------------------------------------------------------- ARCH := -mthumb -mthumb-interwork -CFLAGS := -O3 -Wall -Werror\ +CFLAGS := -g -O3 -Wall -Werror\ -mcpu=arm7tdmi -mtune=arm7tdmi \ -ffast-math -fomit-frame-pointer -funroll-loops \ $(ARCH) diff --git a/graphics/joker_gfx8.grit b/graphics/joker_gfx8.grit new file mode 100644 index 0000000..a8ac3f6 --- /dev/null +++ b/graphics/joker_gfx8.grit @@ -0,0 +1 @@ +-gB4 -Mw4 -Mh4 -m! -pn 16 \ No newline at end of file diff --git a/graphics/joker_gfx8.png b/graphics/joker_gfx8.png new file mode 100644 index 0000000..01dbbdd Binary files /dev/null and b/graphics/joker_gfx8.png differ diff --git a/include/game.h b/include/game.h index ed77467..6fdaec1 100644 --- a/include/game.h +++ b/include/game.h @@ -74,9 +74,12 @@ void game_update(); // Utility functions for other files typedef struct CardObject CardObject; // forward declaration, actually declared in card.h -CardObject **get_hand_array(void); -CardObject **get_played_array(void); -int get_hand_top(void); -int get_played_top(void); +typedef struct JokerObject JokerObject; +CardObject** get_hand_array(void); +int get_hand_top(void); +CardObject** get_played_array(void); +int get_played_top(void); +JokerObject** get_jokers(void); +int get_jokers_top(void); #endif // GAME_H \ No newline at end of file diff --git a/include/joker.h b/include/joker.h index a869ed5..c959883 100644 --- a/include/joker.h +++ b/include/joker.h @@ -6,10 +6,16 @@ #include "sprite.h" #include "card.h" #include "game.h" +#include "graphic_utils.h" #define JOKER_TID (MAX_HAND_SIZE + MAX_SELECTION_SIZE) * JOKER_SPRITE_OFFSET // Tile ID for the starting index in the tile memory #define JOKER_SPRITE_OFFSET 16 // Offset for the joker sprites -#define JOKER_BASE_PB 4 // Palette bank for the default jokers +#define JOKER_BASE_PB 4 // The starting palette index for the jokers +#define JOKER_LAST_PB (NUM_PALETTES - 1) +// Currently allocating the rest of the palettes for the jokers. +// This number needs to be decreased once we need to allocated palettes for other sprites +// such as planet cards etc. + #define JOKER_STARTING_LAYER 27 #define BASE_EDITION 0 @@ -29,6 +35,7 @@ #define DEFAULT_JOKER_ID 0 #define GREEDY_JOKER_ID 1 // This is just an example to show the patern of making joker IDs +#define JOKER_STENCIL_ID 16 typedef struct { @@ -39,7 +46,7 @@ typedef struct bool processed; } Joker; -typedef struct // copy of CardObject in card.h +typedef struct JokerObject { Joker *joker; SpriteObject* sprite_object; diff --git a/include/joker_gfx.h b/include/joker_gfx.h index c549405..92c3edc 100644 --- a/include/joker_gfx.h +++ b/include/joker_gfx.h @@ -9,5 +9,6 @@ #include "joker_gfx5.h" #include "joker_gfx6.h" #include "joker_gfx7.h" +#include "joker_gfx8.h" #endif \ No newline at end of file diff --git a/include/sprite.h b/include/sprite.h index 0cd9840..b4d6bca 100644 --- a/include/sprite.h +++ b/include/sprite.h @@ -4,11 +4,12 @@ #include #include +#define CARD_SPRITE_SIZE 32 + typedef struct { OBJ_ATTR *obj; OBJ_AFFINE *aff; - u32 tid, pb; POINT pos; } Sprite; @@ -44,6 +45,7 @@ INLINE void sprite_position(Sprite *sprite, int x, int y) // Sprite functions void sprite_init(); void sprite_draw(); +int sprite_get_pb(const Sprite* sprite); // SpriteObject methods SpriteObject *sprite_object_new(); diff --git a/source/game.c b/source/game.c index 68720f3..119c930 100644 --- a/source/game.c +++ b/source/game.c @@ -30,7 +30,7 @@ static uint timer = 0; // This might already exist in libtonc but idk so i'm jus static int game_speed = 1; static int background = 0; -static enum GameState game_state = GAME_BLIND_SELECT; // The current game state, this is used to determine what the game is doing at any given time +static enum GameState game_state = GAME_SHOP; // The current game state, this is used to determine what the game is doing at any given time static enum HandState hand_state = HAND_DRAW; static enum PlayState play_state = PLAY_PLAYING; static int state = 0; // General state variable, used for switch statements in each game state related function @@ -140,6 +140,31 @@ static inline Card *discard_pop() return discard_pile[discard_top--]; } +// get-functions, for other files to view game state (mainly for jokers) +CardObject **get_hand_array(void) { + return hand; +} + +int get_hand_top(void) { + return hand_top; +} + +CardObject **get_played_array(void) { + return played; +} + +int get_played_top(void) { + return played_top; +} + +JokerObject **get_jokers(void) { + return jokers; +} + +int get_jokers_top(void) { + return jokers_top; +} + // Consts // Rects left top right bottom @@ -291,22 +316,6 @@ void sort_cards() } } -CardObject **get_hand_array(void) { - return hand; -} - -int get_hand_top(void) { - return hand_top; -} - -CardObject **get_played_array(void) { - return played; -} - -int get_played_top(void) { - return played_top; -} - enum HandType hand_get_type() { enum HandType res_hand_type = NONE; @@ -1831,6 +1840,8 @@ static void game_shop_create_items(JokerObject *shop_jokers[], bool first_time) } u8 joker_id = random() % get_joker_registry_size(); + // TODO: don't pick a joker that we already own + // TODO: weight the random choice by joker rarity shop_jokers[i] = joker_object_new(joker_new(joker_id)); shop_jokers[i]->sprite_object->x = int2fx(120 + i * 32); @@ -1839,7 +1850,7 @@ static void game_shop_create_items(JokerObject *shop_jokers[], bool first_time) shop_jokers[i]->sprite_object->ty = int2fx(71); int x = fx2int(shop_jokers[i]->sprite_object->tx) + TILE_SIZE - (get_digits_even(shop_jokers[i]->joker->value) - 1) * TILE_SIZE; - int y = fx2int(shop_jokers[i]->sprite_object->ty); + int y = fx2int(shop_jokers[i]->sprite_object->ty) + CARD_SPRITE_SIZE + TILE_SIZE; tte_printf("#{P:%d,%d; cx:0xC000}$%d", x, y, shop_jokers[i]->joker->value); if (first_time == false) @@ -2153,6 +2164,39 @@ void game_round_end() } } +void game_shop_intro(JokerObject* shop_jokers[]) +{ + main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_UP); + + if (timer == 1) + { + game_shop_create_items(shop_jokers, true); + } + + if (timer >= 7) // Shift the shop icon + { + int timer_offset = timer - 6; + + // TODO: Extract to generic function? + for (int y = 0; y < timer_offset; y++) + { + int y_from = 26 + y - timer_offset; + int y_to = 0 + y; + + Rect from = { 0, y_from, 8, y_from }; + BG_POINT to = { 0, y_to }; + + main_bg_se_copy_rect(from, to); + } + } + + if (timer == 12) + { + state = 1; + timer = 0; // Reset the timer + } +} + void game_shop() { change_background(BG_ID_SHOP); @@ -2201,36 +2245,7 @@ void game_shop() { case 0: // Intro sequence (menu and shop icon coming into frame) { - main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_UP); - - if (timer == 1) - { - game_shop_create_items(shop_jokers, true); - } - - if (timer >= 7) // Shift the shop icon - { - int timer_offset = timer - 6; - - // TODO: Extract to generic function? - for (int y = 0; y < timer_offset; y++) - { - int y_from = 26 + y - timer_offset; - int y_to = 0 + y; - - Rect from = {0, y_from, 8, y_from}; - BG_POINT to = {0, y_to}; - - main_bg_se_copy_rect(from, to); - } - } - - if (timer == 12) - { - state = 1; - timer = 0; // Reset the timer - } - + game_shop_intro(shop_jokers); break; } case 1: // Shop menu input and selection @@ -2332,7 +2347,8 @@ void game_shop() Rect joker_price_rect; joker_price_rect.left = fx2int(shop_jokers[i]->sprite_object->tx); - joker_price_rect.top = fx2int(shop_jokers[i]->sprite_object->ty) + TILE_SIZE; + // 2*TILE_SIZE because the item is highlighted and raised by 1 additional tile + joker_price_rect.top = fx2int(shop_jokers[i]->sprite_object->ty) + CARD_SPRITE_SIZE + 2*TILE_SIZE; joker_price_rect.right = joker_price_rect.left + TILE_SIZE * 3; joker_price_rect.bottom = joker_price_rect.top + TILE_SIZE; diff --git a/source/joker.c b/source/joker.c index 02febc5..de376b0 100644 --- a/source/joker.c +++ b/source/joker.c @@ -39,16 +39,52 @@ static bool used_layers[MAX_JOKER_OBJECTS] = {false}; // Track used layers for j // Maps the spritesheet index to the palette bank index allocated to it. // Spritesheets that were not allocated are static int* joker_spritesheet_pb_map; +static int joker_pb_num_sprite_users[JOKER_LAST_PB - JOKER_BASE_PB + 1] = { 0 }; static int get_num_spritesheets() { return (get_joker_registry_size() + NUM_JOKERS_PER_SPRITESHEET - 1) / NUM_JOKERS_PER_SPRITESHEET; } +static int joker_get_spritesheet_idx(u8 joker_id) + +{ + return joker_id / NUM_JOKERS_PER_SPRITESHEET; +} + // TODO: This should be generalized so any sprite can have dynamic swapping +static void joker_pb_add_sprite_user(int pb) +{ + joker_pb_num_sprite_users[pb - JOKER_BASE_PB]++; +} + +static void joker_pb_remove_sprite_user(int pb) +{ + int num_sprite_users = joker_pb_num_sprite_users[pb - JOKER_BASE_PB]; + joker_pb_num_sprite_users[pb - JOKER_BASE_PB] = max(0, num_sprite_users - 1); +} + +static int joker_pb_get_num_sprite_users(int joker_pb) +{ + return joker_pb_num_sprite_users[joker_pb - JOKER_BASE_PB]; +} + +static int get_unused_joker_pb() +{ + for (int i = 0; i < NUM_ELEM_IN_ARR(joker_pb_num_sprite_users); i++) + { + if (joker_pb_num_sprite_users[i] == 0) + { + return (i + JOKER_BASE_PB); + } + } + + return UNDEFINED; +} + static int allocate_pb_if_needed(u8 joker_id) { - int joker_spritesheet_idx = joker_id / NUM_JOKERS_PER_SPRITESHEET; + int joker_spritesheet_idx = joker_get_spritesheet_idx(joker_id); int joker_pb = joker_spritesheet_pb_map[joker_spritesheet_idx]; if (joker_pb != UNDEFINED) { @@ -57,23 +93,9 @@ static int allocate_pb_if_needed(u8 joker_id) } // Allocate a new palette - joker_pb = int_arr_max(joker_spritesheet_pb_map, get_num_spritesheets()); + joker_pb = get_unused_joker_pb(); if (joker_pb == UNDEFINED) - { - // This is the first palette loaded - joker_pb = JOKER_BASE_PB; - } - else - { - // The new palette bank is a 1 increment of the last allocated one - /* TODO: This doesn't account for removal, we need to address that, - * track sprite usage and remove palettes for unused sprites - */ - joker_pb = joker_pb + 1; - } - - if (joker_pb == NUM_PALETTES) { // Ran out of palettes, default to base and pray joker_pb = JOKER_BASE_PB; @@ -104,6 +126,7 @@ void joker_init() joker_gfxTiles[5] = joker_gfx5Tiles; joker_gfxTiles[6] = joker_gfx6Tiles; joker_gfxTiles[7] = joker_gfx7Tiles; + joker_gfxTiles[8] = joker_gfx8Tiles; joker_gfxPal[0] = joker_gfx0Pal; joker_gfxPal[1] = joker_gfx1Pal; @@ -113,6 +136,7 @@ void joker_init() joker_gfxPal[5] = joker_gfx5Pal; joker_gfxPal[6] = joker_gfx6Pal; joker_gfxPal[7] = joker_gfx7Pal; + joker_gfxPal[8] = joker_gfx8Pal; for (int i = 0; i < num_spritesheets; i++) { @@ -129,7 +153,7 @@ Joker *joker_new(u8 id) joker->id = id; joker->modifier = BASE_EDITION; // TODO: Make this a parameter - joker->value = jinfo->base_value + edition_price_lut[joker->modifier]; // Base value + edition price + joker->value = jinfo->base_value + edition_price_lut[joker->modifier]; joker->rarity = jinfo->rarity; joker->processed = false; @@ -172,12 +196,13 @@ JokerObject *joker_object_new(Joker *joker) int tile_index = JOKER_TID + (layer * JOKER_SPRITE_OFFSET); - int joker_spritesheet_idx = joker->id / NUM_JOKERS_PER_SPRITESHEET; + int joker_spritesheet_idx = joker_get_spritesheet_idx(joker->id); int joker_idx = joker->id % NUM_JOKERS_PER_SPRITESHEET; int joker_pb = allocate_pb_if_needed(joker->id); + joker_pb_add_sprite_user(joker_pb); memcpy32(&tile_mem[4][tile_index], &joker_gfxTiles[joker_spritesheet_idx][joker_idx * TILE_SIZE * JOKER_SPRITE_OFFSET], TILE_SIZE * JOKER_SPRITE_OFFSET); - + sprite_object_set_sprite ( joker_object->sprite_object, @@ -198,10 +223,16 @@ JokerObject *joker_object_new(Joker *joker) void joker_object_destroy(JokerObject **joker_object) { - if (*joker_object == NULL) return; + if (joker_object == NULL || *joker_object == NULL) return; int layer = sprite_get_layer(joker_object_get_sprite(*joker_object)) - JOKER_STARTING_LAYER; used_layers[layer] = false; + joker_pb_remove_sprite_user(sprite_get_pb(joker_object_get_sprite(*joker_object))); + if (joker_pb_get_num_sprite_users((sprite_get_pb(joker_object_get_sprite(*joker_object)))) == 0) + { + joker_spritesheet_pb_map[joker_get_spritesheet_idx((*joker_object)->joker->id)] = UNDEFINED; + } + sprite_object_destroy(&(*joker_object)->sprite_object); // Destroy the sprite joker_destroy(&(*joker_object)->joker); // Destroy the joker free(*joker_object); @@ -229,7 +260,7 @@ bool joker_object_score(JokerObject *joker_object, Card* scored_card, int *chips { *chips += joker_effect.chips; *mult += joker_effect.mult; - // TODO: XMult + *mult *= joker_effect.xmult > 0 ? joker_effect.xmult : 1; // if xmult is zero, DO NOT multiply by it *money += joker_effect.money; // TODO: Retrigger @@ -247,6 +278,11 @@ bool joker_object_score(JokerObject *joker_object, Card* scored_card, int *chips tte_set_special(0xE000); // Red snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.mult); } + else if (joker_effect.xmult > 0) + { + tte_set_special(0xE000); // Red + snprintf(score_buffer, sizeof(score_buffer), "X%d", joker_effect.xmult); + } else if (joker_effect.money > 0) { tte_set_special(0xC000); // Yellow diff --git a/source/joker_effects.c b/source/joker_effects.c index 792970c..5e934ee 100644 --- a/source/joker_effects.c +++ b/source/joker_effects.c @@ -1,7 +1,7 @@ #include "joker.h" #include "util.h" #include "hand_analysis.h" - +#include static JokerEffect default_joker_effect(Joker *joker, Card *scored_card) { JokerEffect effect = {0}; @@ -184,6 +184,37 @@ static JokerEffect half_joker_effect(Joker *joker, Card *scored_card) { return effect; } +static JokerEffect joker_stencil_effect(Joker *joker, Card *scored_card) { + JokerEffect effect = {0}; + if (scored_card != NULL) + return effect; // if card != null, we are not at the end-phase of scoring yet + + // +1 xmult per empty joker slot... + int jokers_top = get_jokers_top(); + effect.xmult = (MAX_JOKERS_HELD_SIZE-1) - jokers_top; + + // ...and also each stencil_joker adds +1 xmult + JokerObject** jokers = get_jokers(); + for (int i = 0; i < jokers_top; i++ ) + { + if (jokers[i]->joker->id == JOKER_STENCIL_ID) + effect.xmult++; + } + + return effect; +} + +#define MISPRINT_MAX_MULT 23 +static JokerEffect misprint_joker_effect(Joker *joker, Card *scored_card) { + JokerEffect effect = {0}; + if (scored_card != NULL) + return effect; // if card != null, we are not at the end-phase of scoring yet + + effect.mult = random() % (MISPRINT_MAX_MULT + 1); + + return effect; +} + const JokerInfo joker_registry[] = { { COMMON_JOKER, 2, default_joker_effect }, // DEFAULT_JOKER_ID = 0 { COMMON_JOKER, 5, greedy_joker_effect }, // GREEDY_JOKER_ID = 1 @@ -201,6 +232,8 @@ const JokerInfo joker_registry[] = { { COMMON_JOKER, 4, devious_joker_effect }, { COMMON_JOKER, 4, crafty_joker_effect }, { COMMON_JOKER, 5, half_joker_effect }, + { UNCOMMON_JOKER, 8, joker_stencil_effect }, + { COMMON_JOKER, 4, misprint_joker_effect }, }; static const size_t joker_registry_size = NUM_ELEM_IN_ARR(joker_registry); diff --git a/source/sprite.c b/source/sprite.c index 5e3edf6..a93b953 100644 --- a/source/sprite.c +++ b/source/sprite.c @@ -1,5 +1,6 @@ #include "sprite.h" #include "util.h" +#include "audio_utils.h" #include #include @@ -100,6 +101,15 @@ void sprite_draw() oam_copy(oam_mem, obj_buffer, MAX_SPRITES); } +int sprite_get_pb(const Sprite *sprite) +{ + if (sprite == NULL) + { + return UNDEFINED; + } + return (sprite->obj->attr2 & ATTR2_PALBANK_MASK) >> ATTR2_PALBANK_SHIFT; +} + // SpriteObject methods SpriteObject* sprite_object_new() { @@ -206,8 +216,7 @@ void sprite_object_shake(SpriteObject* sprite_object, mm_word sound_id) if (sound_id == UNDEFINED) return; // If no sound ID is provided, do nothing - mm_sound_effect sfx_select = { {sound_id}, 1024, 0, 255, 128, }; - mmEffectEx(&sfx_select); + play_sfx(sound_id, MM_BASE_PITCH_RATE); } void sprite_object_set_selected(SpriteObject* sprite_object, bool selected)