Merge branch 'main' into hblank_affine_background_effects

This commit is contained in:
MeirGavish
2025-08-12 17:24:14 +03:00
6 changed files with 357 additions and 191 deletions
+1
View File
@@ -3,3 +3,4 @@
/.vs
*.sln
*.vcxproj*
/balatro-gba/x64/**
+25
View File
@@ -0,0 +1,25 @@
#ifndef LIST_H
#define LIST_H
#include <stdbool.h>
/* Defining an int list for simplicity
* If another list is needed, a generic void*
* list should be defined....
*/
typedef struct IntList
{
int* _array;
int size;
int allocated_size;
} IntList;
IntList *int_list_new(int init_size);
void int_list_destroy(IntList **list);
bool int_list_append(IntList *list, int value);
bool int_list_remove_by_idx(IntList *list, int index);
int int_list_get(IntList *list, int index);
int int_list_get_size(IntList *list);
bool int_list_remove_by_value(IntList *list, int value);
#endif
+144 -88
View File
@@ -23,6 +23,8 @@
#include "soundbank.h"
#include "list.h"
// This seed system is temporary.
static uint rng_seed = 2048;
static bool seeded = false;
@@ -171,6 +173,8 @@ int get_jokers_top(void) {
return jokers_top;
}
IntList* jokers_available_to_shop; // List of joker IDs
// Consts
// Rects left top right bottom
@@ -998,8 +1002,21 @@ void game_set_state(enum GameState new_game_state)
game_state = new_game_state;
}
void jokers_avialable_to_shop_init()
{
int num_defined_jokers = get_joker_registry_size();
jokers_available_to_shop = int_list_new(num_defined_jokers);
for (int i = 0; i < num_defined_jokers; i++)
{
// Add all joker IDs sequentially
int_list_append(jokers_available_to_shop, i);
}
}
void game_init()
{
jokers_avialable_to_shop_init();
hands = max_hands;
discards = max_discards;
@@ -1559,7 +1576,7 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
FIXED played_x = int2fx(120);
FIXED played_y = int2fx(70);
FIXED played_scale = float2fx(1.0f);
FIXED played_scale = FIX_ONE;
played_x = played_x + (int2fx(played_top - i) - int2fx(played_top) / 2) * -27;
@@ -1818,41 +1835,6 @@ static void game_round_end_cashout()
display_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() % 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);
shop_jokers[i]->sprite_object->y = int2fx(160);
shop_jokers[i]->sprite_object->tx = shop_jokers[i]->sprite_object->x;
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) + CARD_SPRITE_SIZE + TILE_SIZE;
tte_printf("#{P:%d,%d; cx:0xC000}$%d", x, y, shop_jokers[i]->joker->value);
if (first_time == false)
{
shop_jokers[i]->sprite_object->y = shop_jokers[i]->sprite_object->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(joker_object_get_sprite(shop_jokers[i]), fx2int(shop_jokers[i]->sprite_object->x), fx2int(shop_jokers[i]->sprite_object->y));
}
}
void game_playing()
{
// TODO: Blind rect sliding into view animation...
@@ -2155,13 +2137,44 @@ void game_round_end()
}
}
void game_shop_intro(JokerObject* shop_jokers[])
static void game_shop_create_items(JokerObject *shop_jokers[])
{
tte_erase_rect_wrapper(SHOP_PRICES_TEXT_RECT);
if (int_list_get_size(jokers_available_to_shop) == 0)
{
// No jokers to create
return;
}
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
{
// TODO: weight the random choice by joker rarity
int joker_idx = random() % int_list_get_size(jokers_available_to_shop);
int joker_id = int_list_get(jokers_available_to_shop, joker_idx);
int_list_remove_by_idx(jokers_available_to_shop, joker_idx);
shop_jokers[i] = joker_object_new(joker_new(joker_id));
shop_jokers[i]->sprite_object->x = int2fx(120 + i * 32);
shop_jokers[i]->sprite_object->y = int2fx(160);
shop_jokers[i]->sprite_object->tx = shop_jokers[i]->sprite_object->x;
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) + CARD_SPRITE_SIZE + TILE_SIZE;
tte_printf("#{P:%d,%d; cx:0xC000}$%d", x, y, shop_jokers[i]->joker->value);
sprite_position(joker_object_get_sprite(shop_jokers[i]), fx2int(shop_jokers[i]->sprite_object->x), fx2int(shop_jokers[i]->sprite_object->y));
}
}
// Intro sequence (menu and shop icon coming into frame)
static 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);
game_shop_create_items(shop_jokers);
}
if (timer >= 7) // Shift the shop icon
@@ -2188,59 +2201,44 @@ void game_shop_intro(JokerObject* shop_jokers[])
}
}
void game_shop()
static void game_shop_reroll(JokerObject** shop_jokers, int *reroll_cost)
{
change_background(BG_ID_SHOP);
money -= *reroll_cost;
display_money(money); // Update the money display
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
{
if (shop_jokers[i] != NULL)
{
int_list_append(jokers_available_to_shop, shop_jokers[i]->joker->id);
joker_object_destroy(&shop_jokers[i]); // Destroy the joker object if it exists
}
}
static JokerObject *shop_jokers[MAX_SHOP_JOKERS] = {NULL};
game_shop_create_items(shop_jokers);
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
{
if (shop_jokers[i] != NULL)
{
joker_object_update(shop_jokers[i]);
shop_jokers[i]->sprite_object->y = shop_jokers[i]->sprite_object->ty; // Set the y position to the target position
joker_object_shake(shop_jokers[i], UNDEFINED); // Give the joker a little wiggle animation
}
}
const int reroll_base_cost = 5; // Base cost for rerolling the shop items
static int reroll_cost = reroll_base_cost;
(*reroll_cost)++;
tte_printf("#{P:%d,%d; cx:0xF000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, *reroll_cost);
}
// Shop menu input and selection
static void game_shop_process_user_input(JokerObject** shop_jokers)
{
static 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 = MAX_SHOP_JOKERS;
const ushort max_items_bottom = 0;
if (timer % 20 == 0) // Shift palette around the border of the shop icon
{
COLOR shifted_palette[4];
memcpy16(&shifted_palette[0], &pal_bg_mem[14], 1);
memcpy16(&shifted_palette[1], &pal_bg_mem[17], 1);
memcpy16(&shifted_palette[2], &pal_bg_mem[22], 1);
memcpy16(&shifted_palette[3], &pal_bg_mem[8], 1);
// Circularly shift the palette
int last = shifted_palette[3];
for (int i = 3; i > 0; --i) {
shifted_palette[i] = shifted_palette[i - 1];
}
shifted_palette[0] = last;
memcpy16(&pal_bg_mem[14], &shifted_palette[0], 1); // Copy the shifted palette to the next 4 slots
memcpy16(&pal_bg_mem[17], &shifted_palette[1], 1);
memcpy16(&pal_bg_mem[22], &shifted_palette[2], 1);
memcpy16(&pal_bg_mem[8], &shifted_palette[3], 1);
}
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 and shop icon coming into frame)
{
game_shop_intro(shop_jokers);
break;
}
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);
@@ -2274,6 +2272,7 @@ void game_shop()
}
else if (key_hit(KEY_RIGHT))
{
// TODO: Don't allow cursor on sold items
if (selection_y == 0 && selection_x < max_items_top)
{
selection_x++;
@@ -2288,7 +2287,7 @@ void game_shop()
memcpy16(&pal_bg_mem[5], &pal_bg_mem[16], 1);
// Shop selection logic
if (selection_x == 0 && selection_y == 0)
if (selection_x == 0 && selection_y == 0) // Next round button
{
memset16(&pal_bg_mem[5], 0xFFFF, 1);
@@ -2297,6 +2296,7 @@ void game_shop()
// Go to next blind selection game state
state = 2; // Go to the outro sequence state
timer = 0; // Reset the timer
reroll_cost = REROLL_BASE_COST;
memcpy16(&pal_bg_mem[5], &pal_bg_mem[6], 1);
@@ -2306,23 +2306,17 @@ void game_shop()
// And I don't care enough to fix it right now.
}
}
else if (selection_x == 0 && selection_y == 1)
else if (selection_x == 0 && selection_y == 1) // Reroll button
{
memset16(&pal_bg_mem[7], 0xFFFF, 1);
if (key_hit(SELECT_CARD) && money >= reroll_cost)
{
money -= reroll_cost;
display_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);
game_shop_reroll(shop_jokers, &reroll_cost);
}
}
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
for (int i = 0; i < MAX_SHOP_JOKERS; i++) // Item selection logic
{
if (shop_jokers[i] != NULL)
{
@@ -2354,7 +2348,63 @@ void game_shop()
}
}
}
}
static void game_shop_lights_anim_frame()
{
// Shift palette around the border of the shop icon
COLOR shifted_palette[4];
memcpy16(&shifted_palette[0], &pal_bg_mem[14], 1);
memcpy16(&shifted_palette[1], &pal_bg_mem[17], 1);
memcpy16(&shifted_palette[2], &pal_bg_mem[22], 1);
memcpy16(&shifted_palette[3], &pal_bg_mem[8], 1);
// Circularly shift the palette
int last = shifted_palette[3];
for (int i = 3; i > 0; --i)
{
shifted_palette[i] = shifted_palette[i - 1];
}
shifted_palette[0] = last;
memcpy16(&pal_bg_mem[14], &shifted_palette[0], 1); // Copy the shifted palette to the next 4 slots
memcpy16(&pal_bg_mem[17], &shifted_palette[1], 1);
memcpy16(&pal_bg_mem[22], &shifted_palette[2], 1);
memcpy16(&pal_bg_mem[8], &shifted_palette[3], 1);
}
void game_shop()
{
change_background(BG_ID_SHOP);
static JokerObject *shop_jokers[MAX_SHOP_JOKERS] = {NULL};
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
{
if (shop_jokers[i] != NULL)
{
joker_object_update(shop_jokers[i]);
}
}
if (timer % 20 == 0)
{
game_shop_lights_anim_frame();
}
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:
{
game_shop_intro(shop_jokers);
break;
}
case 1:
{
game_shop_process_user_input(shop_jokers);
break;
}
case 2: // Outro sequence (menu and shop icon going out of frame)
@@ -2402,23 +2452,29 @@ void game_shop()
}
default:
state = 0; // Reset the state
reroll_cost = reroll_base_cost;
selection_x = 0; // Reset the selection
selection_y = 0; // Reset the selection
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
{
if (shop_jokers[i] != NULL)
{
// Make the joker available back to shop
int_list_append(jokers_available_to_shop, shop_jokers[i]->joker->id);
}
joker_object_destroy(&shop_jokers[i]); // Destroy the joker objects
}
increment_blind(BLIND_DEFEATED);
increment_blind(BLIND_DEFEATED); // TODO: Move to game_round_end()?
game_set_state(GAME_BLIND_SELECT); // If we reach here, we should go to the blind select state
break;
}
}
void game_blind_select()
{
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
+84
View File
@@ -0,0 +1,84 @@
#include <stdlib.h>
#include <stdbool.h>
#include "list.h"
#include "util.h"
IntList *int_list_new(int init_size) {
IntList *list = (IntList *)malloc(sizeof(IntList));
if (list == NULL) return NULL;
list->_array = (int *)malloc(sizeof(int) * init_size);
if (!list->_array)
{
free(list);
return NULL;
}
list->size = 0;
list->allocated_size = init_size;
return list;
}
void int_list_destroy(IntList **list) {
if (list == NULL || *list == NULL)
return;
{
free((*list)->_array);
free(*list);
}
*list = NULL;
}
bool int_list_append(IntList *list, int value)
{
if (list->size >= list->allocated_size)
{
int new_size = list->allocated_size * 2;
int *new_arr = (int *)realloc(list->_array, sizeof(int) * new_size);
if (new_arr == NULL)
return false;
list->_array = new_arr;
list->allocated_size = new_size;
}
list->_array[list->size++] = value;
return true;
}
bool int_list_remove_by_idx(IntList *list, int index) {
if (index < 0 || index >= list->size)
return false;
for (int i = index; i < list->size - 1; ++i)
{
list->_array[i] = list->_array[i + 1];
}
list->size--;
return true;
}
bool int_list_remove_by_value(IntList *list, int value)
{
for (int i = 0; i < list->size; i++)
{
if (list->_array[i] == value)
{
return int_list_remove_by_idx(list, i);
}
}
return false;
}
int int_list_get(IntList *list, int index)
{
if (index < 0 || index >= list->size)
return 0;
return list->_array[index];
}
int int_list_get_size(IntList *list)
{
if (list == NULL)
{
return UNDEFINED;
}
return list->size;
}
+1 -1
View File
@@ -45,7 +45,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_CBB) | 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 | BG_AFF_32x32;
int win1_left = 72;
int win1_top = 44;
+6 -6
View File
@@ -145,9 +145,9 @@ void sprite_object_reset_transform(SpriteObject* sprite_object)
sprite_object->y = 0;
sprite_object->vx = 0;
sprite_object->vy = 0;
sprite_object->tscale = float2fx(1.0f); // Target scale
sprite_object->scale = float2fx(1.0f);
sprite_object->vscale = float2fx(0.0f);
sprite_object->tscale = FIX_ONE; // Target scale
sprite_object->scale = FIX_ONE;
sprite_object->vscale = 0;
sprite_object->trotation = 0; // Target rotation
sprite_object->rotation = 0;
sprite_object->vrotation = 0;
@@ -163,7 +163,7 @@ void sprite_object_update(SpriteObject* sprite_object)
sprite_object->vrotation += (sprite_object->trotation - sprite_object->rotation) / 8; // Rotate the card when it's played
// set velocity to 0 if it's close enough to the target
const float epsilon = float2fx(0.01f);
const FIXED epsilon = float2fx(0.01f);
if (sprite_object->vx < epsilon && sprite_object->vx > -epsilon && sprite_object->vy < epsilon && sprite_object->vy > -epsilon)
{
sprite_object->vx = 0;
@@ -211,8 +211,8 @@ void sprite_object_update(SpriteObject* sprite_object)
void sprite_object_shake(SpriteObject* sprite_object, mm_word sound_id)
{
sprite_object->vscale = float2fx(0.3f); // Scale down the card when it's scored
sprite_object->vrotation = float2fx(8.0f); // Rotate the card when it's scored
sprite_object->vscale = float2fx(0.3f);
sprite_object->vrotation = float2fx(8.0f); //Rotate the card when it's scored
if (sound_id == UNDEFINED) return; // If no sound ID is provided, do nothing