Merge branch 'main' of https://github.com/cellos51/balatro-gba into hblank_affine_background_effects and changed parameters on affine source to be updated from experimental branch

This commit is contained in:
cellos51
2025-08-28 22:39:48 -04:00
17 changed files with 273 additions and 57 deletions
+1 -1
View File
@@ -1 +1 @@
-gt -gB8 -mLa -mRa
-gt -gB8 -mLa -mRa -pe 16
@@ -0,0 +1 @@
-gt -gB8 -mLa -mRa -pe 16
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

+1 -1
View File
@@ -1 +1 @@
-gB8 -mR8 -mLs -mRtf
-gB8 -mR8 -mLs -mRtf -pe 160
+1 -1
View File
@@ -1 +1 @@
-gB8 -mR8 -mLs -mRtf
-gB8 -mR8 -mLs -mRtf -pe 160
+1
View File
@@ -0,0 +1 @@
-gB8 -mR8 -mLs -mRtf -pe 160
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

+1 -1
View File
@@ -1 +1 @@
-gB8 -mR8 -mLs -mRtf
-gB8 -mR8 -mLs -mRtf -pe 160
+9 -2
View File
@@ -5,9 +5,15 @@
#include "graphic_utils.h"
#define AFFINE_BG_PAL_LEN 5
#define AFFINE_BG_PB (PAL_ROW_LEN * 15) // This isn't really a palette bank, just the starting index of the palette
#define AFFINE_BG_IDX 2 // The index of the affine background BGCNT register etc.
#define AFFINE_BG_PAL_LEN 16
#define AFFINE_BG_PB (PAL_ROW_LEN * 10) // This isn't really a palette bank, just the starting index of the palette
enum AffineBackgroundID
{
AFFINE_BG_MAIN_MENU,
AFFINE_BG_GAME,
};
void affine_background_init();
IWRAM_CODE void affine_background_hblank();
@@ -15,5 +21,6 @@ IWRAM_CODE void affine_background_update();
void affine_background_set_color(COLOR color);
// Must be called with an array of size at least AFFINE_BG_PAL_LEN
void affine_background_load_palette(const u16 *src);
void affine_background_change_background(enum AffineBackgroundID new_bg);
#endif // AFFINE_BACKGROUND_H
+4
View File
@@ -16,6 +16,7 @@
#define BG_ID_ROUND_END 3
#define BG_ID_SHOP 4
#define BG_ID_BLIND_SELECT 5
#define BG_ID_MAIN_MENU 6
// Input bindings
#define SELECT_CARD KEY_A
@@ -27,6 +28,8 @@
enum GameState
{
GAME_SPLASH_SCREEN,
GAME_MAIN_MENU,
GAME_PLAYING,
GAME_ROUND_END,
GAME_SHOP,
@@ -75,6 +78,7 @@ enum HandType
// Game functions
void game_init();
void game_update();
void game_set_state(enum GameState new_game_state);
// Forward declaration
struct List;
+6
View File
@@ -173,4 +173,10 @@ void memcpy16_tile8_with_palette_offset(u16* dst, const u16* src, uint hwcount,
*/
void memcpy32_tile8_with_palette_offset(u32* dst, const u32* src, uint wcount, u8 palette_offset);
/* Toggles the visibility of the window layers.
* win0 and win1 are the visibility states for the two windows.
* These windows are primarily used for the shadows on held jokers, consumables and cards.
*/
void toggle_windows(bool win0, bool win1);
#endif //GRAPHIC_UTILS_H
+13
View File
@@ -0,0 +1,13 @@
#ifndef SPLASH_SCREEN_H
#define SPLASH_SCREEN_H
#include <tonc.h>
#define SPLASH_FPS 60
#define SPLASH_DURATION_SECONDS 10
#define SPLASH_DURATION_FRAMES (SPLASH_FPS * SPLASH_DURATION_SECONDS)
void splash_screen_init();
void splash_screen_update(uint timer);
#endif // SPLASH_SCREEN_H
+40 -12
View File
@@ -1,20 +1,23 @@
#include "affine_background.h"
#include "affine_background_gfx.h"
#include "affine_main_menu_background_gfx.h"
#include "graphic_utils.h"
#define ANIMATION_SPEED_DIVISOR 16
BG_AFFINE bgaff_arr[SCREEN_HEIGHT + 1];
BG_AFFINE bgaff;
AFF_SRC_EX asx = {0};
enum AffineBackgroundID background = AFFINE_BG_MAIN_MENU;
static uint timer = 0;
#define ANIMATION_SPEED_DIVISOR 16
void affine_background_init()
{
memcpy32_tile8_with_palette_offset((u32*)&tile8_mem[AFFINE_BG_CBB], (const u32*)affine_background_gfxTiles, affine_background_gfxTilesLen/4, AFFINE_BG_PB);
GRIT_CPY(&se_mem[AFFINE_BG_SBB], affine_background_gfxMap);
affine_background_load_palette(affine_background_gfxPal);
{
affine_background_update();
REG_BG_AFFINE[AFFINE_BG_IDX] = bg_aff_default;
}
@@ -35,10 +38,10 @@ IWRAM_CODE void affine_background_prep_bgaff_arr()
asx.scr_x = (SCREEN_WIDTH / 2); // 128 on x and y is an offset used to center the rotation
asx.scr_y = vcount_s16 - (SCREEN_HEIGHT / 2); // scr_y must equal vcount otherwise the background will have no vertical difference
asx.tex_x = vcount_sine;
asx.tex_y = vcount_sine / 64;
asx.sx = vcount_sine / 32;
asx.sy = vcount_sine / 16;
asx.tex_x = (1000 * 1000) + (vcount_sine);
asx.tex_y = (1000 * 1000);
asx.sx = 128;
asx.sy = 128;
asx.alpha = vcount_sine + (timer_s32 / ANIMATION_SPEED_DIVISOR);
bg_rotscale_ex(&bgaff_arr[vcount], &asx);
@@ -72,7 +75,7 @@ void affine_background_update()
void affine_background_set_color(COLOR color)
{
memcpy16(&pal_bg_mem[AFFINE_BG_PB], affine_background_gfxPal, AFFINE_BG_PAL_LEN);
affine_background_change_background(background); // Reload the palette to reset any previous color scaling
for (int i = 0; i < AFFINE_BG_PAL_LEN; i++)
{
clr_rgbscale(&pal_bg_mem[AFFINE_BG_PB] + i, &pal_bg_mem[AFFINE_BG_PB] + i, 1, color);
@@ -82,4 +85,29 @@ void affine_background_set_color(COLOR color)
void affine_background_load_palette(const u16 *src)
{
memcpy16(&pal_bg_mem[AFFINE_BG_PB], src, AFFINE_BG_PAL_LEN);
}
void affine_background_change_background(enum AffineBackgroundID new_bg)
{
background = new_bg;
switch (background)
{
case AFFINE_BG_MAIN_MENU:
REG_BG2CNT &= ~BG_AFF_32x32;
REG_BG2CNT |= BG_AFF_16x16;
memcpy32_tile8_with_palette_offset((u32*)&tile8_mem[AFFINE_BG_CBB], (const u32*)affine_main_menu_background_gfxTiles, affine_main_menu_background_gfxTilesLen/4, AFFINE_BG_PB);
GRIT_CPY(&se_mem[AFFINE_BG_SBB], affine_main_menu_background_gfxMap);
affine_background_load_palette(affine_main_menu_background_gfxPal);
break;
case AFFINE_BG_GAME:
REG_BG2CNT &= ~BG_AFF_16x16;
REG_BG2CNT |= BG_AFF_32x32;
memcpy32_tile8_with_palette_offset((u32*)&tile8_mem[AFFINE_BG_CBB], (const u32*)affine_background_gfxTiles, affine_background_gfxTilesLen/4, AFFINE_BG_PB);
GRIT_CPY(&se_mem[AFFINE_BG_SBB], affine_background_gfxMap);
affine_background_load_palette(affine_background_gfxPal);
break;
}
}
+130 -34
View File
@@ -16,31 +16,34 @@
#include "tonc_video.h"
#include "audio_utils.h"
#include "selection_grid.h"
#include "splash_screen.h"
#include "background_gfx.h"
#include "background_shop_gfx.h"
#include "background_blind_select_gfx.h"
#include "affine_background_gfx.h"
#include "background_main_menu_gfx.h"
#include "affine_main_menu_background_gfx.h"
#include "soundbank.h"
#include "list.h"
// This seed system is temporary.
static uint rng_seed = 2048;
static bool seeded = false;
static uint rng_seed = 0;
static uint timer = 0; // This might already exist in libtonc but idk so i'm just making my own
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_SPLASH_SCREEN; // 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;
static CardObject *main_menu_ace = NULL;
static Sprite *playing_blind_token = NULL; // The sprite that displays the blind when in "GAME_PLAYING/GAME_ROUND_END" state
static Sprite *round_end_blind_token = NULL; // The sprite that displays the blind when in "GAME_ROUND_END" state
@@ -249,6 +252,11 @@ static const BG_POINT JOKER_DISCARD_TARGET = {240, 30};
#define ITEM_SHOP_Y 71 // TODO: Needs to be a rect?
#define MAIN_MENU_BUTTONS 2
#define MAIN_MENU_IMPLEMENTED_BUTTONS 1 // Remove this once all buttons are implemented
#define MAIN_MENU_PLAY_BUTTON_MAIN_COLOR_PID 5
#define MAIN_MENU_PLAY_BUTTON_OUTLINE_COLOR_PID 2
//TODO: Properly define and use
#define MENU_POP_OUT_ANIM_FRAMES 20
#define GAME_OVER_ANIM_FRAMES 15
@@ -265,12 +273,8 @@ static const BG_POINT JOKER_DISCARD_TARGET = {240, 30};
// General functions
void set_seed(int seed)
{
if (!seeded)
{
seeded = true;
rng_seed = seed;
srand(rng_seed);
}
rng_seed = seed;
srand(rng_seed);
}
void sort_hand_by_suit()
@@ -437,11 +441,12 @@ void change_background(int id)
}
else
{
REG_DISPCNT |= DCNT_WIN0; // Enable window 0 to make hand background transparent
toggle_windows(true, true); // Enable window 0 for the hand shadow
// Load the tiles and palette
// Background
memcpy(pal_bg_mem, background_gfxPal, 64); // This '64" isn't a specific number, I'm just using it to prevent the text colors from being overridden
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(pal_bg_mem, background_gfxPal);
GRIT_CPY(&tile8_mem[MAIN_BG_CBB], background_gfxTiles);
GRIT_CPY(&se_mem[MAIN_BG_SBB], background_gfxMap);
if (current_blind == BIG_BLIND) // Change text and palette depending on blind type
@@ -476,6 +481,7 @@ void change_background(int id)
}
REG_WIN0V = (REG_WIN0V << 8) | 0xA0; // Set window 0 bottom to 160
toggle_windows(true, true);
for (int i = 0; i <= 2; i++)
{
@@ -492,16 +498,16 @@ void change_background(int id)
background = BG_ID_ROUND_END;
}
REG_DISPCNT &= ~DCNT_WIN0; // Disable window 0 so it doesn't make the cashout menu transparent
toggle_windows(false, true); // Disable window 0 so it doesn't make the cashout menu transparent
main_bg_se_clear_rect(ROUND_END_MENU_RECT);
tte_erase_rect_wrapper(HAND_SIZE_RECT);
}
else if (id == BG_ID_SHOP)
{
REG_DISPCNT &= ~DCNT_WIN0;
toggle_windows(false, true);
memcpy(pal_bg_mem, background_shop_gfxPal, 64);
GRIT_CPY(pal_bg_mem, background_shop_gfxPal);
GRIT_CPY(&tile_mem[MAIN_BG_CBB], background_shop_gfxTiles);
GRIT_CPY(&se_mem[MAIN_BG_SBB], background_shop_gfxMap);
@@ -528,9 +534,9 @@ void change_background(int id)
sprite_position(blind_select_tokens[BIG_BLIND], 120, default_y);
sprite_position(blind_select_tokens[BOSS_BLIND], 160, default_y);
REG_DISPCNT &= ~DCNT_WIN0;
toggle_windows(false, true);
memcpy16(pal_bg_mem, background_blind_select_gfxPal, 64);
GRIT_CPY(pal_bg_mem, background_blind_select_gfxPal);
GRIT_CPY(&tile_mem[MAIN_BG_CBB], background_blind_select_gfxTiles);
GRIT_CPY(&se_mem[MAIN_BG_SBB], background_blind_select_gfxMap);
@@ -542,7 +548,7 @@ void change_background(int id)
// Select button PID is 15 and the outline is 18
memcpy16(&pal_bg_mem[18], &pal_bg_mem[15], 1);
// Skip button PID is 10 and the outline is 5
memcpy16(&pal_bg_mem[10 ], &pal_bg_mem[5], 1);
memcpy16(&pal_bg_mem[10], &pal_bg_mem[5], 1);
for (int i = 0; i < MAX_BLINDS; i++)
{
@@ -627,6 +633,18 @@ void change_background(int id)
}
}
}
else if (id == BG_ID_MAIN_MENU)
{
toggle_windows(false, false);
tte_erase_screen();
GRIT_CPY(pal_bg_mem, background_main_menu_gfxPal);
GRIT_CPY(&tile_mem[MAIN_BG_CBB], background_main_menu_gfxTiles);
GRIT_CPY(&se_mem[MAIN_BG_SBB], background_main_menu_gfxMap);
// Disable the button highlight colors
memcpy16(&pal_bg_mem[MAIN_MENU_PLAY_BUTTON_OUTLINE_COLOR_PID], &pal_bg_mem[MAIN_MENU_PLAY_BUTTON_MAIN_COLOR_PID], 1);
}
else
{
return; // Invalid background ID
@@ -931,9 +949,6 @@ void increment_blind(enum BlindState increment_reason)
static void game_round_init()
{
// once we have a main menu, move the seed set there. this is only here now because there isn't a way to set the seed to a psudorandom value unless there is something to wait on
set_seed(rng_seed);
hand_state = HAND_DRAW;
cards_drawn = 0;
hand_selections = 0;
@@ -981,6 +996,20 @@ static void game_round_init()
deck_shuffle(); // Shuffle the deck at the start of the round
}
static void game_main_menu_init()
{
affine_background_change_background(AFFINE_BG_MAIN_MENU);
change_background(BG_ID_MAIN_MENU);
main_menu_ace = card_object_new(card_new(SPADES, ACE));
card_object_set_sprite(main_menu_ace, 0); // Set the sprite for the ace of spades
main_menu_ace->sprite_object->sprite->obj->attr0 |= ATTR0_AFF_DBL; // Make the sprite double sized
main_menu_ace->sprite_object->tx = int2fx(88);
main_menu_ace->sprite_object->x = main_menu_ace->sprite_object->tx;
main_menu_ace->sprite_object->ty = int2fx(26);
main_menu_ace->sprite_object->y = main_menu_ace->sprite_object->ty;
main_menu_ace->sprite_object->tscale = float2fx(0.8f);
}
static void game_over_init()
{
// Clears the round end menu
@@ -1008,6 +1037,12 @@ static void init_game_state(enum GameState game_state_to_init)
// Switch written out, add init for states as needed
switch (game_state_to_init)
{
case GAME_SPLASH_SCREEN:
splash_screen_init();
break;
case GAME_MAIN_MENU:
game_main_menu_init();
break;
case GAME_PLAYING:
game_round_init();
break;
@@ -1029,7 +1064,7 @@ static void init_game_state(enum GameState game_state_to_init)
}
// Game functions
static void game_set_state(enum GameState new_game_state)
void game_set_state(enum GameState new_game_state)
{
timer = 0; // Reset the timer
init_game_state(new_game_state);
@@ -1069,6 +1104,22 @@ void game_init()
obj_hide(blind_select_tokens[BIG_BLIND]->obj);
obj_hide(blind_select_tokens[BOSS_BLIND]->obj);
game_set_state(game_state);
}
void game_start()
{
set_seed(rng_seed);
affine_background_change_background(AFFINE_BG_GAME);
// Normally I would just cache these and hide/unhide but I didn't feel like dealing with defining a layer for it
card_destroy(&main_menu_ace->card);
card_object_destroy(&main_menu_ace);
hands = max_hands;
discards = max_discards;
// Fill the deck with all the cards. Later on this can be replaced with a more dynamic system that allows for different decks and card types.
for (int suit = 0; suit < NUM_SUITS; suit++)
{
@@ -1095,6 +1146,8 @@ void game_init()
display_money(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
game_set_state(GAME_BLIND_SELECT);
}
static void game_playing_process_hand_select_input()
@@ -2613,7 +2666,6 @@ void game_shop()
}
}
}
if (timer % 20 == 0)
{
@@ -2824,6 +2876,54 @@ void game_blind_select()
}
}
void game_main_menu()
{
change_background(BG_ID_MAIN_MENU);
card_object_update(main_menu_ace);
main_menu_ace->sprite_object->trotation = lu_sin((timer << 8) / 2) / 3;
main_menu_ace->sprite_object->rotation = main_menu_ace->sprite_object->trotation;
// Seed randomization
rng_seed++;
if (key_curr_state() != key_prev_state()) // If the keys have changed, make it more pseudo-random
{
rng_seed *= 2;
}
if (key_hit(KEY_LEFT))
{
if (selection_x > 0)
{
selection_x--;
}
}
else if (key_hit(KEY_RIGHT))
{
if (selection_x < MAIN_MENU_IMPLEMENTED_BUTTONS - 1)
{
selection_x++;
}
}
if (selection_x == 0) // Play button
{
// Select button PID is 5 and the outline is 3
memset16(&pal_bg_mem[MAIN_MENU_PLAY_BUTTON_OUTLINE_COLOR_PID], HIGHLIGHT_COLOR, 1);
if (key_hit(KEY_A))
{
// Start the game
game_start();
}
}
else
{
// Select button PID is 5 and the outline is 3
memcpy16(&pal_bg_mem[MAIN_MENU_PLAY_BUTTON_OUTLINE_COLOR_PID], &pal_bg_mem[MAIN_MENU_PLAY_BUTTON_MAIN_COLOR_PID], 1);
}
}
static void discarded_jokers_update_loop()
{
if (discarded_jokers == NULL)
@@ -2841,7 +2941,6 @@ static void discarded_jokers_update_loop()
joker_object_destroy(&joker_object);
}
}
}
static void held_jokers_update_loop()
@@ -2906,19 +3005,16 @@ void game_update()
{
timer++;
if (seeded == false)
{
rng_seed++;
if (key_curr_state() != key_prev_state()) // If the keys have changed, make it more pseudo-random
{
rng_seed *= 2;
}
}
jokers_update_loop();
switch (game_state)
{
case GAME_SPLASH_SCREEN:
splash_screen_update(timer);
break;
case GAME_MAIN_MENU:
game_main_menu();
break;
case GAME_PLAYING:
game_playing();
break;
+30
View File
@@ -269,4 +269,34 @@ void memcpy32_tile8_with_palette_offset(u32* dst, const u32* src, uint wcount, u
// Copying u8 data 4 times across u32 data
dst[i] = src[i] + offset;
}
}
void toggle_windows(bool win0, bool win1)
{
if (win0)
{
REG_DISPCNT |= DCNT_WIN0;
}
else
{
REG_DISPCNT &= ~DCNT_WIN0;
}
if (win1)
{
REG_DISPCNT |= DCNT_WIN1;
}
else
{
REG_DISPCNT &= ~DCNT_WIN1;
}
if (win0 || win1)
{
REG_BLDCNT = BLD_BUILD(BLD_BG1, BLD_BG2, 1);
}
else
{
REG_BLDCNT = 0;
}
}
+3 -5
View File
@@ -24,10 +24,6 @@ void init()
irq_add(II_VBLANK, mmVBlank);
irq_add(II_HBLANK, affine_background_hblank);
// Initialize maxmod
mmInitDefault((mm_addr)soundbank_bin, 12);
mmStart(MOD_MAIN_THEME, MM_PLAY_LOOP);
// Initialize text engine
tte_init_se(0, BG_CBB(TTE_CBB) | BG_SBB(TTE_SBB), 0, CLR_WHITE, TTE_BIT_UNPACK_OFFSET, NULL, NULL);
tte_erase_screen();
@@ -45,7 +41,7 @@ void init()
// BG1 is the main background layer
REG_BG1CNT = BG_PRIO(1) | BG_CBB(MAIN_BG_CBB) | BG_SBB(MAIN_BG_SBB) | BG_8BPP;
// BG2 is the affine background layer
REG_BG2CNT = BG_PRIO(2) | BG_CBB(AFFINE_BG_CBB) | BG_SBB(AFFINE_BG_SBB) | BG_8BPP | BG_WRAP | BG_AFF_32x32;
REG_BG2CNT = BG_PRIO(2) | BG_CBB(AFFINE_BG_CBB) | BG_SBB(AFFINE_BG_SBB) | BG_8BPP | BG_WRAP;
int win1_left = 72;
int win1_top = 44;
@@ -73,6 +69,8 @@ void init()
REG_DISPCNT = DCNT_MODE1 | DCNT_OBJ_1D | DCNT_BG0 | DCNT_BG1 | DCNT_BG2 | DCNT_OBJ | DCNT_WIN0 | DCNT_WIN1;
// Initialize subsystems
mmInitDefault((mm_addr)soundbank_bin, 12);
mmStart(MOD_MAIN_THEME, MM_PLAY_LOOP);
affine_background_init();
sprite_init();
card_init();
+32
View File
@@ -0,0 +1,32 @@
#include "splash_screen.h"
#include <tonc.h>
#include "graphic_utils.h"
#include "game.h"
static const Rect COUNTDOWN_TIMER_RECT = {208, 144, 240, 152};
void splash_screen_init()
{
tte_printf("#{P:72,8; cx:0xF000}DISCLAIMER");
tte_printf("#{P:8,24; cx:0xF000}This project is NOT endorsed \n by or affiliated with \n Playstack or LocalThunk.\n\n If you have paid for this, \n you have been scammed \n and should request a refund \n IMMEDIATELY. \n\n The only official place \n to obtain this is from: \n\n 'github.com/\n cellos51/balatro-gba'");
tte_printf("#{P:8,144; cx:0xF000}(Press any key to skip)");
}
void splash_screen_update(uint timer)
{
if (timer < SPLASH_DURATION_FRAMES)
{
tte_erase_rect_wrapper(COUNTDOWN_TIMER_RECT);
tte_printf("#{P:%d,%d; cx:0xF000}%d", COUNTDOWN_TIMER_RECT.left, COUNTDOWN_TIMER_RECT.top, 1 + (SPLASH_DURATION_FRAMES - timer) / SPLASH_FPS);
if (!key_hit(KEY_ANY))
{
return;
}
}
game_set_state(GAME_MAIN_MENU);
tte_erase_screen();
}