Merge pull request #44 from cellos51/jokers

Basic joker implementation
This commit is contained in:
Elliptical
2025-07-22 20:08:20 -04:00
committed by GitHub
18 changed files with 724 additions and 152 deletions
+3 -2
View File
@@ -38,8 +38,9 @@ GRAPHICS := graphics
#---------------------------------------------------------------------------------
ARCH := -mthumb -mthumb-interwork
CFLAGS := -g -Wall -O2\
-mcpu=arm7tdmi -mtune=arm7tdmi\
CFLAGS := -O3 -Wall \
-mcpu=arm7tdmi -mtune=arm7tdmi \
-ffast-math -fomit-frame-pointer -funroll-loops \
$(ARCH)
CFLAGS += $(INCLUDE)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

+1
View File
@@ -0,0 +1 @@
-gB4 -Mw4 -Mh4 -m!
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

+1 -1
View File
@@ -38,7 +38,7 @@ enum BlindState
BLIND_SKIPPED
};
void blinds_init();
void blind_init();
int blind_get_requirement(int type, int ante);
int blind_get_reward(int type);
+7 -2
View File
@@ -2,13 +2,14 @@
#define CARD_H
#include <tonc.h>
#include <maxmod.h>
#include "sprite.h"
// Card suits
#define HEARTS 0
#define DIAMONDS 1
#define CLUBS 2
#define CLUBS 1
#define DIAMONDS 2
#define SPADES 3
#define NUM_SUITS 4
@@ -52,6 +53,9 @@ typedef struct
bool selected;
} CardObject;
// Card functions
void card_init();
// Card methods
Card *card_new(u8 suit, u8 rank);
void card_destroy(Card **card);
@@ -62,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_shake(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
+9
View File
@@ -3,6 +3,8 @@
#define MAX_HAND_SIZE 16
#define MAX_DECK_SIZE 52
#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 FRAMES(x) (((x) + game_speed - 1) / game_speed)
@@ -13,6 +15,13 @@
#define BG_ID_SHOP 4
#define BG_ID_BLIND_SELECT 5
// Input bindings
#define SELECT_CARD KEY_A
#define DESELECT_CARDS KEY_B
#define PEEK_DECK KEY_L // Not implemented
#define SORT_HAND KEY_R
#define PAUSE_GAME KEY_START // Not implemented
enum GameState
{
GAME_PLAYING,
+1 -1
View File
@@ -15,7 +15,7 @@
* Screen Base Block is the base for the screenblock entries i.e. tilemap
* Character Base Block is the base for the tiles themselves
*/
#define MAIN_BG_SBB 29
#define MAIN_BG_SBB 31
#define MAIN_BG_CBB 1
#define TTE_SBB 30
#define TTE_CBB 0
+82
View File
@@ -0,0 +1,82 @@
#ifndef JOKER_H
#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 <maxmod.h>
#include "sprite.h"
#include "card.h"
#define BASE_EDITION 0
#define FOIL_EDITION 1
#define HOLO_EDITION 2
#define POLY_EDITION 3
#define NEGATIVE_EDITION 4
#define MAX_EDITIONS 5
#define COMMON_JOKER 0
#define UNCOMMON_JOKER 1
#define RARE_JOKER 2
#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
typedef struct
{
u8 id; // Unique ID for the joker, used to identify different jokers
u8 modifier; // base, foil, holo, poly, negative
u8 value;
u8 rarity;
bool processed;
} Joker;
typedef struct // copy of CardObject in card.h
{
Joker *joker;
Sprite *sprite;
FIXED tx, ty;
FIXED x, y;
FIXED vx, vy;
FIXED tscale;
FIXED scale;
FIXED vscale;
FIXED trotation;
FIXED rotation;
FIXED vrotation;
bool selected;
} JokerObject;
typedef struct // These jokers are triggered after the played hand has finished scoring.
{
int chips;
int mult;
int xmult;
int money;
bool retrigger; // Retrigger played hand (e.g. "Dusk" joker, even though on the wiki it says "On Scored" it makes more sense to have it here)
} JokerEffect;
void joker_init();
Joker *joker_new(u8 id);
void joker_destroy(Joker **joker);
// Unique effects like "Four Fingers" or "Credit Card" will be hard coded into game.c with a conditional check for the joker ID from the players owned jokers
// game.c should probably be restructured so most of the variables in it are moved to some sort of global variable header file so they can be easily accessed and modified for the jokers
JokerEffect joker_get_score_effect(Joker *joker, Card *scored_card);
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, int *chips, int *mult, int *xmult, 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)
#endif // JOKER_H
+1
View File
@@ -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;
+5 -9
View File
@@ -4,24 +4,20 @@
#include "blinds_gfx.h"
#include "graphic_utils.h"
// Palettes for the blinds (Transparency, Text Color, Shadow, Highlight, Main Color) Use this: http://www.budmelvin.com/dev/15bitconverter.html
static const u16 small_blind_token_palette[PAL_ROW_LEN] = {0x0000, 0x7FFF, 0x34A1, 0x5DCB, 0x5104, 0x55A0, 0x2D01, 0x34E0};
static const u16 big_blind_token_palette[PAL_ROW_LEN] = {0x0000, 0x2527, 0x15F5, 0x36FC, 0x1E9C, 0x01B4, 0x0D0A, 0x010E};
static const u16 boss_blind_token_palette[PAL_ROW_LEN] = {0x0000, 0x2CC9, 0x3D0D, 0x5E14, 0x5171, 0x4D0F, 0x2CC8, 0x3089}; // This variable is temporary, each boss blind will have its own unique palette
void blinds_init()
void blind_init()
{
// Blind graphics (fighting grit every step of the way as usual)
GRIT_CPY(&tile_mem[4][SMALL_BLIND_TID], blinds_gfxTiles);
// Palettes for the blinds (Transparency, Text Color, Shadow, Highlight, Main Color) Use this: http://www.budmelvin.com/dev/15bitconverter.html
// Small Blind
memcpy16(&pal_obj_mem[16 * SMALL_BLIND_PB], &small_blind_token_palette, sizeof(small_blind_token_palette) / 2);
// Big Blind
memcpy16(&pal_obj_mem[16 * BIG_BLIND_PB], &big_blind_token_palette, sizeof(big_blind_token_palette) / 2);
memcpy16(&pal_obj_mem[PAL_ROW_LEN * SMALL_BLIND_PB], &small_blind_token_palette, sizeof(small_blind_token_palette) / 2);
memcpy16(&pal_obj_mem[PAL_ROW_LEN * BIG_BLIND_PB], &big_blind_token_palette, sizeof(big_blind_token_palette) / 2);
// Boss Blind (This is temporary. Each boss blind is unique and will have to have its own graphics and palette which will probably be stored in some huge array)
memcpy16(&pal_obj_mem[16 * BOSS_BLIND_PB], &boss_blind_token_palette, sizeof(boss_blind_token_palette) / 2);
memcpy16(&pal_obj_mem[PAL_ROW_LEN * BOSS_BLIND_PB], &boss_blind_token_palette, sizeof(boss_blind_token_palette) / 2);
}
int blind_get_requirement(int type, int ante)
+19
View File
@@ -3,6 +3,8 @@
#include <maxmod.h>
#include <stdlib.h>
#include "deck_gfx.h"
// Audio
#include "soundbank.h"
@@ -14,6 +16,12 @@ const static u16 card_sprite_lut[NUM_SUITS][NUM_RANKS] = {
{624, 640, 656, 672, 688, 704, 720, 736, 752, 768, 784, 800, 816}
};
void card_init()
{
GRIT_CPY(&tile_mem[4], deck_gfxTiles);
GRIT_CPY(pal_obj_mem, deck_gfxPal);
}
// Card methods
Card *card_new(u8 suit, u8 rank)
{
@@ -144,3 +152,14 @@ 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);
}
void card_object_shake(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);
}
+371 -114
View File
@@ -9,6 +9,7 @@
#include "sprite.h"
#include "card.h"
#include "blind.h"
#include "joker.h"
#include "graphic_utils.h"
#include "tonc_video.h"
@@ -29,6 +30,7 @@ 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 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
static enum HandType hand_type = NONE;
@@ -62,11 +64,15 @@ static int hand_size = 8; // Default hand size is 8
static int cards_drawn = 0;
static int hand_selections = 0;
static int card_focused = 0;
static int selection_x = 0;
static int selection_y = 0;
static bool sort_by_suit = false;
// Stacks
static JokerObject *jokers[MAX_JOKERS_HELD_SIZE] = {NULL};
static int jokers_top = -1;
static CardObject *played[MAX_SELECTION_SIZE] = {NULL};
static int played_top = -1;
@@ -79,6 +85,19 @@ static int deck_top = -1;
static Card *discard_pile[MAX_DECK_SIZE] = {NULL};
static int discard_top = -1;
// Joker stack
static inline void joker_push(JokerObject *joker)
{
if (jokers_top >= MAX_JOKERS_HELD_SIZE - 1) return;
jokers[++jokers_top] = joker;
}
static inline JokerObject *joker_pop()
{
if (jokers_top < 0) return NULL;
return jokers[jokers_top--];
}
// Played stack
static inline void played_push(CardObject *card_object)
{
@@ -133,7 +152,7 @@ static const Rect POP_MENU_ANIM_RECT = {9, 7, 24, 31 };
static const Rect SINGLE_BLIND_SELECT_RECT = { 9, 7, 13, 32 };
static const Rect HAND_BG_RECT_SELECTING = {9, 11, 24, 15 };
static const Rect HAND_BG_RECT_SELECTING = {9, 11, 24, 17 };
// TODO: Currently unused, remove?
//static const Rect HAND_BG_RECT_PLAYING = {9, 14, 24, 18 };
@@ -162,11 +181,12 @@ static const Rect SCORE_RECT = {32, 48, 64, 56 };
static const Rect PLAYED_CARDS_SCORES_RECT = {72, 48, 240, 56 };
static const Rect BLIND_TOKEN_TEXT_RECT = {80, 72, 200, 160 };
static const Rect MONEY_TEXT_RECT = {8, 120, 64, 120 };
static const Rect MONEY_TEXT_RECT = {8, 120, 64, 128 };
static const Rect CHIPS_TEXT_RECT = {8, 80, 32, 88 };
static const Rect MULT_TEXT_RECT = {40, 80, 64, 88 };
static const Rect BLIND_REWARD_RECT = {40, 32, 64, 40 };
static const Rect BLIND_REQ_TEXT_RECT = {32, 24, 64, 32 };
static const Rect SHOP_PRICES_TEXT_RECT = {72, 56, 192, 160 };
// Rects with UNDEFINED are only used in tte_printf, they need to be fully defined
// to be used with tte_erase_rect_wrapper()
@@ -180,6 +200,7 @@ static const Rect ROUND_END_BLIND_REWARD_RECT = { 168, 96, UNDEFINED, UNDEF
static const Rect ROUND_END_NUM_HANDS_RECT = {88, 116, UNDEFINED, UNDEFINED };
static const Rect HAND_REWARD_RECT = {168, UNDEFINED, UNDEFINED, UNDEFINED };
static const Rect CASHOUT_RECT = {88, 72, UNDEFINED, UNDEFINED };
static const Rect SHOP_REROLL_RECT = {88, 96, UNDEFINED, UNDEFINED };
//TODO: Properly define and use
#define MENU_POP_OUT_ANIM_FRAMES 20
@@ -400,6 +421,16 @@ void change_background(int id)
return;
}
else if (id == BG_ID_CARD_SELECTING)
{
tte_erase_rect_wrapper(HAND_SIZE_RECT_PLAYING);
REG_WIN0V = (REG_WIN0V << 8) | 0x80; // Set window 0 top to 128
if (background == BG_ID_CARD_PLAYING)
{
int offset = 11;
memcpy16(&se_mem[MAIN_BG_SBB][SE_ROW_LEN * offset], &background_gfxMap[SE_ROW_LEN * offset], SE_ROW_LEN * 8);
}
else
{
REG_DISPCNT |= DCNT_WIN0; // Enable window 0 to make hand background transparent
// Load the tiles and palette
@@ -408,8 +439,6 @@ void change_background(int id)
GRIT_CPY(&tile8_mem[MAIN_BG_CBB], background_gfxTiles); // Deadass i have no clue how any of these memory things work but I just messed with them until stuff worked
GRIT_CPY(&se_mem[MAIN_BG_SBB], background_gfxMap);
tte_erase_rect_wrapper(HAND_SIZE_RECT_PLAYING);
if (current_blind == BIG_BLIND) // Change text and palette depending on blind type
{
main_bg_se_copy_rect(BIG_BLIND_TITLE_SRC_RECT, TOP_LEFT_BLIND_TITLE_POINT);
@@ -422,9 +451,14 @@ void change_background(int id)
bg_copy_current_item_to_top_left_panel();
// This would change the palette of the background to match the blind, but the backgroun doesn't use the blind token's exact colors so a different approach is required
memset16(&pal_bg_mem[17], blind_get_color(current_blind, BLIND_BACKGROUND_MAIN_COLOR_INDEX), 1);
memset16(&pal_bg_mem[16], blind_get_color(current_blind, BLIND_BACKGROUND_SECONDARY_COLOR_INDEX), 1);
memset16(&pal_bg_mem[5], blind_get_color(current_blind, BLIND_BACKGROUND_SHADOW_COLOR_INDEX), 1);
memset16(&pal_bg_mem[19], blind_get_color(current_blind, BLIND_BACKGROUND_MAIN_COLOR_INDEX), 1);
memset16(&pal_bg_mem[5], blind_get_color(current_blind, BLIND_BACKGROUND_SECONDARY_COLOR_INDEX), 1);
memset16(&pal_bg_mem[2], blind_get_color(current_blind, BLIND_BACKGROUND_SHADOW_COLOR_INDEX), 1);
// Copy the Play Hand and Discard button colors to their selection highlights
memcpy16(&pal_bg_mem[1], &pal_bg_mem[7], 1);
memcpy16(&pal_bg_mem[9], &pal_bg_mem[12], 1);
}
}
else if (id == BG_ID_CARD_PLAYING)
{
@@ -434,19 +468,18 @@ void change_background(int id)
background = BG_ID_CARD_PLAYING;
}
Rect curr_hand_bg_rect = HAND_BG_RECT_SELECTING;
REG_WIN0V = (REG_WIN0V << 8) | 0xA0; // Set window 0 bottom to 160
for (int i = 0; i <= 2; i++)
{
main_bg_se_move_rect_1_tile_vert(curr_hand_bg_rect, SE_DOWN);
curr_hand_bg_rect.top++;
curr_hand_bg_rect.bottom++;
main_bg_se_move_rect_1_tile_vert(HAND_BG_RECT_SELECTING, SE_DOWN);
}
tte_erase_rect_wrapper(HAND_SIZE_RECT_SELECT);
}
else if (id == BG_ID_ROUND_END)
{
if (background != BG_ID_CARD_SELECTING)
if (background != BG_ID_CARD_SELECTING && background != BG_ID_CARD_PLAYING)
{
change_background(BG_ID_CARD_SELECTING);
background = BG_ID_ROUND_END;
@@ -663,7 +696,7 @@ static void print_hand_type(const char* hand_type_str)
{
if (hand_type_str == NULL)
return; // NULL-checking paranoia
tte_printf("#{P:%d,%d;}%s", HAND_TYPE_RECT.left, HAND_TYPE_RECT.top, hand_type_str);
tte_printf("#{P:%d,%d; cx:0xF000}%s", HAND_TYPE_RECT.left, HAND_TYPE_RECT.top, hand_type_str);
}
void set_hand()
@@ -772,24 +805,19 @@ void card_draw()
void hand_set_focus(int index)
{
if (index < 0 || index > hand_top || hand_state != HAND_SELECT) return;
card_focused = index;
selection_x = index;
mm_sound_effect sfx_focus = {{SFX_CARD_FOCUS}, 1024 + rand() % 512, 0, 255, 128,};
mmEffectEx(&sfx_focus);
}
int hand_get_focus()
{
return card_focused;
}
void hand_select()
{
if (hand_state != HAND_SELECT || hand[card_focused] == NULL) return;
if (hand_state != HAND_SELECT || hand[selection_x] == NULL) return;
if (hand[card_focused]->selected)
if (hand[selection_x]->selected)
{
hand[card_focused]->selected = false;
hand[selection_x]->selected = false;
hand_selections--;
mm_sound_effect sfx_select = {{SFX_CARD_SELECT}, 1024, 0, 255, 128,};
@@ -797,7 +825,7 @@ void hand_select()
}
else if (hand_selections < MAX_SELECTION_SIZE)
{
hand[card_focused]->selected = true;
hand[selection_x]->selected = true;
hand_selections++;
mm_sound_effect sfx_deselect = {{SFX_CARD_DESELECT}, 1024, 0, 255, 128,};
@@ -824,16 +852,12 @@ int hand_get_max_size()
bool hand_discard()
{
if (hand_state != HAND_SELECT || hand_selections == 0) return false;
hand_state = HAND_DISCARD;
card_focused = 0;
return true;
}
bool hand_play()
{
if (hand_state != HAND_SELECT || hand_selections == 0) return false;
hand_state = HAND_PLAY;
card_focused = 0;
return true;
}
@@ -884,13 +908,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)
{
@@ -941,9 +965,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);
@@ -982,38 +1006,96 @@ static void game_playing_process_input_and_state()
{
if (hand_state == HAND_SELECT)
{
static bool discard_button_highlighted = false; // true = play button highlighted, false = discard button highlighted
if (key_hit(KEY_LEFT))
{
hand_set_focus(hand_get_focus() + 1); // The reason why this adds 1 is because the hand is drawn from right to left. There is no particular reason for this, it's just how I did it.
if (selection_y == 0)
{
hand_set_focus(selection_x + 1); // The reason why this adds 1 is because the hand is drawn from right to left. There is no particular reason for this, it's just how I did it.
}
else
{
discard_button_highlighted = false; // Play button
}
}
else if (key_hit(KEY_RIGHT))
{
hand_set_focus(hand_get_focus() - 1);
if (selection_y == 0)
{
hand_set_focus(selection_x - 1);
}
else
{
discard_button_highlighted = true; // Discard button
}
}
else if (key_hit(KEY_UP) && selection_y != 0)
{
selection_y = 0;
}
else if (key_hit(KEY_DOWN) && selection_y != 1)
{
selection_y = 1;
if (selection_x > hand_top / 2)
{
discard_button_highlighted = false; // Play button
}
else
{
discard_button_highlighted = true; // Discard button
}
}
if (key_hit(KEY_A))
if (selection_y == 1) // On row of play/discard buttons
{
if (discard_button_highlighted == false) // Play button logic
{
memset16(&pal_bg_mem[1], 0xFFFF, 1);
memcpy16(&pal_bg_mem[9], &pal_bg_mem[12], 1);
if (key_hit(SELECT_CARD) && hands > 0 && hand_play())
{
hand_state = HAND_PLAY;
selection_x = 0;
selection_y = 0;
set_hands(--hands);
}
}
else // Discard button logic
{
memcpy16(&pal_bg_mem[1], &pal_bg_mem[7], 1);
memset16(&pal_bg_mem[9], 0xFFFF, 1);
if (key_hit(SELECT_CARD) && discards > 0 && hand_discard())
{
hand_state = HAND_DISCARD;
selection_x = 0;
selection_y = 0;
set_hands(--discards);
set_hand();
tte_printf("#{P:%d,%d; cx:0xE000}%d", DISCARDS_TEXT_RECT.left, DISCARDS_TEXT_RECT.top, discards);
}
}
}
if (selection_y == 0) // On row of cards
{
memcpy16(&pal_bg_mem[1], &pal_bg_mem[7], 1); // Play button highlight color
memcpy16(&pal_bg_mem[9], &pal_bg_mem[12], 1); // Discard button highlight color
}
if (key_hit(SELECT_CARD))
{
hand_select();
set_hand();
}
if (key_hit(KEY_B))
if (key_hit(SORT_HAND))
{
hand_change_sort();
}
if (key_hit(KEY_SELECT) && discards > 0 && hand_discard())
{
set_hand();
discards--;
tte_printf("#{P:%d,%d; cx:0xE000}%d", DISCARDS_TEXT_RECT.left, DISCARDS_TEXT_RECT.top, discards);
}
if (key_hit(KEY_START) && hands > 0 && hand_play())
{
hands--;
tte_printf("#{P:%d,%d; cx:0xD000}%d", HANDS_TEXT_RECT.left, HANDS_TEXT_RECT.top, hands);
}
}
else if (play_state == PLAY_ENDING)
{
@@ -1147,20 +1229,22 @@ static void cards_in_hand_update_loop(bool* discarded_card, int* played_selectio
hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -spacing_lut[hand_top];
break;
case HAND_SELECT:
if (i == card_focused && !hand[i]->selected)
bool is_focused = (i == selection_x && selection_y == 0);
if (is_focused && !hand[i]->selected)
{
hand_y -= (10 << FIX_SHIFT);
hand_y -= int2fx(10);
}
else if (i != card_focused && hand[i]->selected)
else if (!is_focused && hand[i]->selected)
{
hand_y -= (15 << FIX_SHIFT);
hand_y -= int2fx(15);
}
else if (i == card_focused && hand[i]->selected)
else if (is_focused && hand[i]->selected)
{
hand_y -= (20 << FIX_SHIFT);
hand_y -= int2fx(20);
}
if (i != card_focused && hand[i]->y > hand_y)
if (i != selection_x && hand[i]->y > hand_y)
{
hand[i]->y = hand_y;
hand[i]->vy = 0;
@@ -1207,11 +1291,11 @@ static void cards_in_hand_update_loop(bool* discarded_card, int* played_selectio
{
if (hand_state == HAND_DISCARD)
{
hand_y -= (15 << FIX_SHIFT); // Don't raise the card if we're mass discarding, it looks stupid.
hand_y -= int2fx(15); // Don't raise the card if we're mass discarding, it looks stupid.
}
else
{
hand_y += (24 << FIX_SHIFT);
hand_y += int2fx(24);
}
hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -spacing_lut[hand_top];
}
@@ -1234,7 +1318,7 @@ static void cards_in_hand_update_loop(bool* discarded_card, int* played_selectio
break;
case HAND_PLAY:
hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -spacing_lut[hand_top];
hand_y += (24 << FIX_SHIFT);
hand_y += int2fx(24);
if (hand[i]->selected && *discarded_card == false && timer % FRAMES(10) == 0)
{
@@ -1410,7 +1494,7 @@ static void cards_in_hand_update_loop(bool* discarded_card, int* played_selectio
break;
case HAND_PLAYING: // Don't need to do anything here, just wait for the player to select cards
hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -spacing_lut[hand_top];
hand_y += (24 << FIX_SHIFT);
hand_y += int2fx(24);
break;
}
@@ -1457,7 +1541,7 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
if (played[i]->selected && played_top - i >= *played_selections)
{
played_y -= (10 << FIX_SHIFT);
played_y -= int2fx(10);
}
break;
case PLAY_SCORING:
@@ -1465,40 +1549,81 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
{
// So pretend "played_selections" is now called "scored_cards" and it counts the number of cards that have been scored
int scored_cards = 0;
for (int j = played_top; j >= 0; j--)
{
if (played[played_top - j]->selected)
{
scored_cards++;
if (scored_cards > *played_selections)
for (int j = 0; j <= played_top; j++)
{
tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT);
tte_set_pos(fx2int(played[played_top - j]->x) + 8, 48); // Offset of 16 pixels to center the text on the card
if (*played_selections > 0)
{
for (int k = 0; k <= jokers_top; k++)
{
if (joker_object_score(jokers[k], played[*played_selections - 1]->card, &chips, &mult, NULL, &money, NULL)) // NULLs aren't implemented yet
{
set_chips(chips);
set_mult(mult);
set_money(money);
return;
}
}
}
if (played[j]->selected)
{
scored_cards = j + 1; // Count the number of cards that have been scored
if (scored_cards > *played_selections)
{
for (int k = 0; k <= jokers_top; k++)
{
if (jokers[k] != NULL)
{
jokers[k]->joker->processed = false; // Reset the joker's processed state for the next score
}
}
tte_set_pos(fx2int(played[j]->x) + 8, 48); // Offset of 16 pixels to center the text on the card
tte_set_special(0xD000); // Set text color to blue from background memory
// Write the score to a character buffer variable
char score_buffer[5]; // Assuming the maximum score is 99, we need 4 characters (2 digits + null terminator)
snprintf(score_buffer, sizeof(score_buffer), "+%d", card_get_value(played[played_top - j]->card));
snprintf(score_buffer, sizeof(score_buffer), "+%d", card_get_value(played[j]->card));
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_shake(played[j], SFX_CARD_SELECT);
// Relocated card scoring logic here
chips += card_get_value(played[played_top - j]->card);
chips += card_get_value(played[j]->card);
set_chips(chips);
break;
}
}
if (j == 0 && scored_cards == *played_selections) // Check if it's the last card
if (j == played_top && scored_cards == *played_selections) // Check if it's the last card
{
tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT);
for (int k = 0; k <= jokers_top; k++) // Independent joker scoring loop
{
if (joker_object_score(jokers[k], NULL, &chips, &mult, NULL, &money, NULL)) // NULLs aren't implemented yet
{
set_chips(chips);
set_mult(mult);
set_money(money);
return; // Returning was just the easiest way to break out of the loop
}
}
for (int k = 0; k <= jokers_top; k++)
{
if (jokers[k] != NULL)
{
jokers[k]->joker->processed = false; // Reset the joker's processed state for the next round
}
}
play_state = PLAY_ENDING;
timer = 0;
*played_selections = played_top + 1; // Reset the played selections to the top of the played stack
@@ -1509,7 +1634,7 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
if (played[i]->selected)
{
played_y -= (10 << FIX_SHIFT);
played_y -= int2fx(10);
}
break;
case PLAY_ENDING: // This is the reverse of PLAY_PLAYING. The cards get reset back to their neutral position sequentially
@@ -1526,7 +1651,7 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
if (played[i]->selected && played_top - i <= *played_selections - 1)
{
played_y -= (10 << FIX_SHIFT);
played_y -= int2fx(10);
}
break;
case PLAY_ENDED: // Basically a copy of HAND_DISCARD
@@ -1592,6 +1717,34 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
}
}
static void held_jokers_update_loop()
{
const int spacing_lut[MAX_JOKERS_HELD_SIZE][MAX_JOKERS_HELD_SIZE] =
{
{0, 0, 0, 0, 0},
{13, -13, 0, 0, 0},
{26, 0, -26, 0, 0},
{39, 13, -13, -39, 0},
{40, 20, 0, -20, -40}
};
FIXED hand_x = int2fx(108);
FIXED hand_y = int2fx(10);
for (int i = jokers_top; i >= 0; i--)
{
jokers[i]->tx = hand_x - int2fx(spacing_lut[jokers_top][i]);
jokers[i]->ty = hand_y;
if (jokers[i]->selected)
{
jokers[i]->ty -= int2fx(10);
}
joker_object_update(jokers[i]);
}
}
static void game_playing_ui_text_update()
{
static int last_hand_size = 0;
@@ -1629,6 +1782,39 @@ 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)
{
tte_erase_rect_wrapper(SHOP_PRICES_TEXT_RECT);
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
}
u8 joker_id = random() % MAX_JOKERS;
shop_jokers[i] = joker_object_new(joker_new(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);
int x = fx2int(shop_jokers[i]->tx) + TILE_SIZE - (get_digits_even(shop_jokers[i]->joker->value) - 1) * TILE_SIZE;
int y = fx2int(shop_jokers[i]->ty);
tte_printf("#{P:%d,%d; cx:0xC000}$%d", x, y, shop_jokers[i]->joker->value);
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_shake(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...
@@ -1677,8 +1863,6 @@ void game_round_end_cleanup()
void game_round_end()
{
static int state = 0;
static int blind_reward = 0;
static int hand_reward = 0;
static int interest_reward = 0;
@@ -1803,7 +1987,7 @@ void game_round_end()
}
else if (timer > FRAMES(20))
{
memset16(&pal_bg_mem[17], 0x1483, 1);
memset16(&pal_bg_mem[19], 0x1483, 1);
state = 6;
timer = 0;
}
@@ -1892,14 +2076,13 @@ void game_round_end()
tte_printf("#{P:%d, %d; cx:0xF000}Cash Out: $%d", CASHOUT_RECT.left, CASHOUT_RECT.top, hands + blind_get_reward(current_blind)); // Print the cash out amount
}
else if (timer > FRAMES(40) && key_hit(KEY_A)) // Wait until the player presses A to cash out
else if (timer > FRAMES(40) && key_hit(SELECT_CARD)) // Wait until the player presses A to cash out
{
game_round_end_cashout();
state = 8; // Go to the next state
timer = 0; // Reset the timer
memset16(&pal_bg_mem[6], 0x0174, 1);
obj_hide(round_end_blind_token->obj); // Hide the blind token object
tte_erase_rect_wrapper(BLIND_TOKEN_TEXT_RECT); // Erase the blind token text
}
@@ -1935,15 +2118,21 @@ void game_shop()
{
change_background(BG_ID_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};
// these are for controlling the shop menu
static bool top_row = true;
static ushort selection_x = 0;
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
{
if (shop_jokers[i] != NULL)
{
joker_object_update(shop_jokers[i]);
}
}
const int reroll_base_cost = 5; // Base cost for rerolling the shop items
static int reroll_cost = reroll_base_cost;
// temp variables for future implementation
const ushort max_items_top = 0;
const ushort max_items_top = MAX_SHOP_JOKERS;
const ushort max_items_bottom = 0;
if (timer % 20 == 0) // Shift palette around the border of the shop icon
@@ -1975,6 +2164,11 @@ void game_shop()
{
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;
@@ -2002,10 +2196,15 @@ void game_shop()
}
case 1: // Shop menu input and selection
{
if (timer == 1)
{
tte_printf("#{P:%d,%d; cx:0xF000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, reroll_cost);
}
// Shop input logic
if (key_hit(KEY_UP))
{
top_row = true;
selection_y = 0;
if (selection_x > max_items_top)
{
@@ -2014,7 +2213,7 @@ void game_shop()
}
else if (key_hit(KEY_DOWN))
{
top_row = false;
selection_y = 1;
if (selection_x > max_items_bottom)
{
@@ -2030,23 +2229,25 @@ void game_shop()
}
else if (key_hit(KEY_RIGHT))
{
if (top_row && selection_x < max_items_top)
if (selection_y == 0 && selection_x < max_items_top)
{
selection_x++;
}
else if (!top_row && selection_x < max_items_bottom)
else if (selection_y == 1 && selection_x < max_items_bottom)
{
selection_x++;
}
}
memcpy16(&pal_bg_mem[7], &pal_bg_mem[3], 1);
memcpy16(&pal_bg_mem[5], &pal_bg_mem[16], 1);
// Shop selection logic
if (selection_x == 0 && top_row)
if (selection_x == 0 && selection_y == 0)
{
memcpy16(&pal_bg_mem[7], &pal_bg_mem[3], 1);
memset16(&pal_bg_mem[5], 0xFFFF, 1);
if (key_hit(KEY_A))
if (key_hit(SELECT_CARD))
{
// Go to next blind selection game state
state = 2; // Go to the outro sequence state
@@ -2060,14 +2261,51 @@ void game_shop()
// And I don't care enough to fix it right now.
}
}
else if (selection_x == 0 && !top_row)
else if (selection_x == 0 && selection_y == 1)
{
memset16(&pal_bg_mem[7], 0xFFFF, 1);
memcpy16(&pal_bg_mem[5], &pal_bg_mem[16], 1);
if (key_hit(KEY_A))
if (key_hit(SELECT_CARD) && money >= reroll_cost)
{
// Do shop re-roll for money
money -= reroll_cost;
set_money(money); // Update the money display
game_shop_create_items(shop_jokers, false);
reroll_cost++;
tte_printf("#{P:%d,%d; cx:0xF000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, reroll_cost);
}
}
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
{
if (shop_jokers[i] != NULL)
{
if (i == selection_x - 1 && selection_y == 0)
{
shop_jokers[i]->ty = int2fx(61);
if (key_hit(SELECT_CARD) && jokers_top < MAX_JOKERS_HELD_SIZE - 1 && money >= shop_jokers[i]->joker->value)
{
joker_push(shop_jokers[i]);
money -= shop_jokers[i]->joker->value; // Deduct the money spent on the joker
set_money(money); // Update the money display
Rect joker_price_rect;
joker_price_rect.left = fx2int(shop_jokers[i]->tx);
joker_price_rect.top = fx2int(shop_jokers[i]->ty) + TILE_SIZE;
joker_price_rect.right = joker_price_rect.left + TILE_SIZE * 3;
joker_price_rect.bottom = joker_price_rect.top + TILE_SIZE;
tte_erase_rect_wrapper(joker_price_rect);
shop_jokers[i] = NULL; // Remove the joker from the shop
}
}
else
{
shop_jokers[i]->ty = int2fx(71);
}
}
}
@@ -2082,6 +2320,16 @@ void game_shop()
if (timer == 1)
{
tte_erase_rect_wrapper(SHOP_PRICES_TEXT_RECT); // Erase the shop prices text
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);
@@ -2108,9 +2356,15 @@ void game_shop()
}
default:
state = 0; // Reset the state
reroll_cost = reroll_base_cost;
selection_x = 0; // Reset the selection
top_row = true; // Reset the top row selection
selection_y = 0; // Reset the 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
@@ -2121,10 +2375,6 @@ void game_shop()
void game_blind_select()
{
static int state = 0;
static bool top_row = true; // There's only one row in this game state, but this is here for consistency with the shop state if we make these variables global or something
switch (state) // I'm only using magic numbers here for the sake of simplicity since it's just sequential, but you can replace them with named constants or enums if it makes it clearer
{
case 0: // Intro sequence (menu coming into frame)
@@ -2147,18 +2397,23 @@ void game_blind_select()
}
case 1: // Blind select input and selection
{
if (timer == 1 && current_blind == BOSS_BLIND)
{
selection_y = 0;
}
// Blind select input logic
if (key_hit(KEY_UP))
{
top_row = true;
selection_y = 0;
}
else if (key_hit(KEY_DOWN))
else if (key_hit(KEY_DOWN) && current_blind != BOSS_BLIND)
{
top_row = false;
selection_y = 1;
}
else if (key_hit(KEY_A))
else if (key_hit(SELECT_CARD))
{
if (top_row) // Blind selected
if (selection_y == 0) // Blind selected
{
state++;
timer = 0;
@@ -2186,7 +2441,7 @@ void game_blind_select()
}
}
if (top_row)
if (selection_y == 0)
{
memset16(&pal_bg_mem[18], 0xFFFF, 1);
memcpy16(&pal_bg_mem[10], &pal_bg_mem[5], 1);
@@ -2275,7 +2530,7 @@ void game_blind_select()
default:
state = 0;
timer = 0;
top_row = true;
selection_y = 0;
background = UNDEFINED;
game_set_state(GAME_PLAYING);
break;
@@ -2295,6 +2550,8 @@ void game_update()
}
}
held_jokers_update_loop();
switch (game_state)
{
case GAME_PLAYING:
+197
View File
@@ -0,0 +1,197 @@
#include <tonc.h>
#include "joker.h"
#include "joker_gfx.h"
#include "graphic_utils.h"
#include "card.h"
#include "soundbank.h"
#include <maxmod.h>
#include <stdlib.h>
#include <string.h>
const static u8 joker_data_lut[MAX_JOKERS][2] = // Rarity, Value
{
{COMMON_JOKER, 2}, // Default Joker
{COMMON_JOKER, 5}, // Greedy Joker
};
const static u8 edition_price_lut[MAX_EDITIONS] =
{
0, // BASE_EDITION
2, // FOIL_EDITION
3, // HOLO_EDITION
5, // POLY_EDITION
5, // NEGATIVE_EDITION
};
/* 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);
memcpy16(&pal_obj_mem[PAL_ROW_LEN * JOKER_PB], joker_gfxPal, sizeof(joker_gfxPal) / 2);
}
Joker *joker_new(u8 id)
{
Joker *joker = malloc(sizeof(Joker));
joker->id = id; // TODO: Make this random later
joker->modifier = BASE_EDITION; // TODO: Make this random later
joker->value = joker_data_lut[id][1] + edition_price_lut[joker->modifier]; // Base value + edition price
joker->rarity = joker_data_lut[id][0];
joker->processed = false;
return joker;
}
void joker_destroy(Joker **joker)
{
if (*joker == NULL) return;
free(*joker);
*joker = NULL;
}
JokerEffect joker_get_score_effect(Joker *joker, Card *scored_card)
{
JokerEffect effect = {0};
switch (joker->id)
{
case DEFAULT_JOKER_ID: // Default Joker
if (scored_card != NULL) break; // Joker is independent, no effect
effect.mult = 4;
break;
case GREEDY_JOKER_ID: // Greedy Joker
if (scored_card != NULL && scored_card->suit == DIAMONDS) // If the scored card is a diamond
{
effect.mult = 3;
}
break;
default:
break;
}
return effect;
}
// 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);
joker_object->selected = false;
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_shake(JokerObject *joker_object, mm_word sound_id)
{
CardObject *card_object = (CardObject *)joker_object;
card_object_shake(card_object, sound_id);
}
bool joker_object_score(JokerObject *joker_object, Card* scored_card, int *chips, int *mult, int *xmult, int *money, bool *retrigger)
{
if (joker_object->joker->processed == true) return false; // If the joker has already been processed, return false
JokerEffect joker_effect = joker_get_score_effect(joker_object->joker, scored_card);
if (memcmp(&joker_effect, &(JokerEffect){0}, sizeof(JokerEffect)) != 0)
{
*chips += joker_effect.chips;
*mult += joker_effect.mult;
// TODO: XMult
*money += joker_effect.money;
// TODO: Retrigger
tte_set_pos(fx2int(joker_object->x) + 8, 48); // Offset of 16 pixels to center the text on the card
char score_buffer[12];
if (joker_effect.chips > 0)
{
tte_set_special(0xD000); // Blue
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.chips);
}
else if (joker_effect.mult > 0)
{
tte_set_special(0xE000); // Red
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.mult);
}
else if (joker_effect.money > 0)
{
tte_set_special(0xC000); // Yellow
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.money);
}
tte_write(score_buffer);
joker_object->joker->processed = true; // Mark the joker as processed
joker_object_shake(joker_object, SFX_CARD_SELECT); // TODO: Add a sound effect for scoring the joker
return true;
}
return false;
}
+5 -7
View File
@@ -6,10 +6,10 @@
#include "card.h"
#include "game.h"
#include "blind.h"
#include "joker.h"
#include "graphic_utils.h"
// Graphics
#include "deck_gfx.h"
#include "background_gfx.h"
#include "affine_background_gfx.h"
@@ -60,10 +60,6 @@ void init()
pal_bg_bank[14][15] = 0x213F; // Red
pal_bg_bank[15][15] = CLR_WHITE;
// Deck graphics
GRIT_CPY(&tile_mem[4], deck_gfxTiles);
GRIT_CPY(pal_obj_mem, deck_gfxPal);
// Set up the video mode
// BG0 is the TTE text layer
REG_BG0CNT = BG_PRIO(0) | BG_CBB(TTE_CBB) | BG_SBB(TTE_SBB) | BG_4BPP;
@@ -75,7 +71,7 @@ void init()
int win1_left = 72;
int win1_top = 44;
int win1_right = 200;
int win1_bottom = 160;
int win1_bottom = 128;
int win2_left = 72;
int win2_top = 0;
@@ -101,7 +97,9 @@ void init()
// Initialize subsystems
sprite_init();
blinds_init();
card_init();
blind_init();
joker_init();
game_init();
}
+6
View File
@@ -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()
{