Files
balatro-gba/source/blind.c
Geralt 0eb7042468
Some checks failed
Build and Deploy Doxygen Docs / docs (push) Has been cancelled
Deploy Nightly / Run Build ROM Workflow (push) Has been cancelled
Deploy Nightly / Deploy nightly (push) Has been cancelled
[Bug] Implement independent RNG sequences to avoid interferences (#599)
* Implement our own Xorshift32 RNG algo to manage independent number sequences

* Fix issues with seed 0

* adress @MeirGavish's review

* Apply suggestions from @MeirGavish's code review

Co-authored-by: MeirGavish <meir.gavish@gmail.com>

* adress the rest of @MeirGavish's review

* adress @MeirGavish's review

* Fix last issues

---------

Co-authored-by: MathisMartin31 <mathis.martin31@gmail.com>
Co-authored-by: MeirGavish <meir.gavish@gmail.com>
Co-authored-by: MonteCrysto <monte.crysto31@gmail.com>
2026-08-05 12:37:06 +03:00

296 lines
9.5 KiB
C

/**
* @file blind.c
* @brief Implementation of functions relative to the behaviour and graphics of Blinds.
*/
#include "blind.h"
#include "blind_gfx.h"
#include "game.h"
#include "graphic_utils.h"
#include "hand.h"
#include "list.h"
#include "random.h"
#include "stdbool.h"
#include "util.h"
#include <stdlib.h>
#include <tonc.h>
#define NORMAL_BLIND_PB 2
#define BOSS_BLIND_PB 3
#define BLIND_BASE_LAYER (MAX_HAND_SIZE + MAX_SELECTION_SIZE)
#define BLIND_SPRITE_OFFSET 16
#define BLIND_SPRITE_COPY_SIZE (BLIND_SPRITE_OFFSET * TILE_SIZE)
#define BLIND_TOKENS_PER_SPRITESHEET 2
#define BLIND_TOKEN_PALETTE_SIZE 8
#define SMALL_BLIND_REWARD 3
#define BIG_BLIND_REWARD 4
#define BOSS_BLIND_REWARD 5
#define SHOWDOWN_BLIND_REWARD 8
static const unsigned int* blind_gfxTiles[] = {
#define DEF_BLIND_GFX(idx) blind_gfx##idx##Tiles,
#include "../include/def_blind_gfx_table.h"
#undef DEF_BLIND_GFX
};
static const unsigned short* blind_gfxPal[] = {
#define DEF_BLIND_GFX(idx) blind_gfx##idx##Pal,
#include "../include/def_blind_gfx_table.h"
#undef DEF_BLIND_GFX
};
// Bitfields storing blinds we have yet to beat during the current run
static List s_unbeaten_boss_blinds = LIST_DEFAULT;
static List s_unbeaten_showdown_blinds = LIST_DEFAULT;
// Maps the ante number to the base blind requirement for that ante.
// The game starts at ante 1 which is at index 1 for base requirement 300.
// Ante 0 is also there in case it is ever reached.
static const u32 ANTE_LUT[] = {100, 300, 800, 2000, 5000, 11000, 20000, 35000, 50000};
// clang-format off
/**
* @brief Stores an instance of the Blind struct for each BlindType value, ordered in the same way.
* Acts the same way the Joker registry does, and may need to go in its own file depending
* on how Blind effects are implemented.
*/
static Blind s_blind_type_map[BLIND_TYPE_MAX] = {
{BLIND_TYPE_SMALL, FIX_ONE },
{BLIND_TYPE_BIG, (FIX_ONE * 3) / 2},
{BLIND_TYPE_HOOK, FIX_ONE * 2 },
{BLIND_TYPE_OX, FIX_ONE * 2 },
{BLIND_TYPE_HOUSE, FIX_ONE * 2 },
{BLIND_TYPE_WHEEL, FIX_ONE * 2 },
{BLIND_TYPE_WALL, FIX_ONE * 2 }, // x4 score requirement will be part of the effect
{BLIND_TYPE_ARM, FIX_ONE * 2 },
{BLIND_TYPE_CLUB, FIX_ONE * 2 },
{BLIND_TYPE_FISH, FIX_ONE * 2 },
{BLIND_TYPE_PSYCHIC, FIX_ONE * 2 },
{BLIND_TYPE_GOAD, FIX_ONE * 2 },
{BLIND_TYPE_WATER, FIX_ONE * 2 },
{BLIND_TYPE_WINDOW, FIX_ONE * 2 },
{BLIND_TYPE_MANACLE, FIX_ONE * 2 },
{BLIND_TYPE_EYE, FIX_ONE * 2 },
{BLIND_TYPE_MOUTH, FIX_ONE * 2 },
{BLIND_TYPE_PLANT, FIX_ONE * 2 },
{BLIND_TYPE_SERPENT, FIX_ONE * 2 },
{BLIND_TYPE_PILLAR, FIX_ONE * 2 },
{BLIND_TYPE_NEEDLE, FIX_ONE * 2 }, // Same as the Wall with normal requirement
{BLIND_TYPE_HEAD, FIX_ONE * 2 },
{BLIND_TYPE_TOOTH, FIX_ONE * 2 },
{BLIND_TYPE_FLINT, FIX_ONE * 2 },
{BLIND_TYPE_MARK, FIX_ONE * 2 },
{BLIND_TYPE_ACORN, FIX_ONE * 2 },
{BLIND_TYPE_LEAF, FIX_ONE * 2 },
{BLIND_TYPE_VESSEL, FIX_ONE * 2 }, // Same as the Wall with x6 requirement
{BLIND_TYPE_HEART, FIX_ONE * 2 },
{BLIND_TYPE_BELL, FIX_ONE * 2 }
};
// clang-format on
void blind_init()
{
// Set up the palette used by normal blind tokens.
// We will never need to edit it ever again.
memcpy16(&pal_obj_bank[NORMAL_BLIND_PB], blind_gfxPal[0], NUM_ELEM_IN_ARR(blind_gfx0Pal));
return;
}
u32 blind_get_requirement(enum BlindType type, int ante)
{
// Ensure ante is within valid range
if (ante < 0 || ante > MAX_ANTE)
ante = 0;
return fx2int(s_blind_type_map[type].score_req_multiplier * ANTE_LUT[ante]);
}
int blind_get_reward(enum BlindType type)
{
switch (type)
{
case BLIND_TYPE_SMALL:
return SMALL_BLIND_REWARD;
case BLIND_TYPE_BIG:
return BIG_BLIND_REWARD;
case BLIND_TYPE_BOSS ...(BLIND_TYPE_SHOWDOWN - 1):
return BOSS_BLIND_REWARD;
default:
return SHOWDOWN_BLIND_REWARD;
}
}
/**
* @brief Fill Lists of unbeaten Boss and Showdown Blinds
*
* By keeping track of what Blind we have beaten or not, we can ensure that until we've
* beaten all the Blinds in a single Run, we won't encounter the same one twice.
*
* This must be called at the beginning of a run.
*
* @param showdown toggle between the List for Showdown and regular Boss Blinds
*/
static void init_unbeaten_blinds_list(bool showdown)
{
List* p_unbeaten_blinds = showdown ? &s_unbeaten_showdown_blinds : &s_unbeaten_boss_blinds;
// empty the list just to be sure
list_clear(p_unbeaten_blinds);
int lower_blind = showdown ? BLIND_TYPE_SHOWDOWN : BLIND_TYPE_BOSS;
int upper_blind = showdown ? BLIND_TYPE_MAX - 1 : BLIND_TYPE_SHOWDOWN - 1;
for (int i = lower_blind; i <= upper_blind; i++)
{
list_push_back(p_unbeaten_blinds, &s_blind_type_map[i]);
}
}
void init_unbeaten_blinds_lists(void)
{
init_unbeaten_blinds_list(false);
init_unbeaten_blinds_list(true);
}
enum BlindType roll_blind_type(bool showdown)
{
List* p_unbeaten_blinds = showdown ? &s_unbeaten_showdown_blinds : &s_unbeaten_boss_blinds;
// Fill the list with all blinds if it is empty
// (happens on startup or if we have beaten all blinds)
if (list_is_empty(p_unbeaten_blinds))
{
init_unbeaten_blinds_list(showdown);
}
// roll a random blind among the unbeaten ones
int random_blind_idx = rng_get_u32(RNG_SEQ_BLIND) % list_get_len(p_unbeaten_blinds);
Blind* random_blind = list_get_at_idx(p_unbeaten_blinds, random_blind_idx);
return random_blind->type;
}
void set_blind_beaten(enum BlindType type)
{
bool showdown = (type >= BLIND_TYPE_SHOWDOWN);
List* p_unbeaten_blinds = showdown ? &s_unbeaten_showdown_blinds : &s_unbeaten_boss_blinds;
// find the beaten blind idx in the list
int beaten_idx = 0;
Blind* beaten_blind = NULL;
ListItr itr = list_itr_create(p_unbeaten_blinds);
while ((beaten_blind = list_itr_next(&itr)))
{
if (beaten_blind->type == type)
{
break;
}
beaten_idx++;
}
if (beaten_idx < list_get_len(p_unbeaten_blinds))
{
list_remove_at_idx(p_unbeaten_blinds, beaten_idx);
}
}
static u32 get_blind_pb(enum BlindType type)
{
return (type <= BLIND_TYPE_BIG) ? NORMAL_BLIND_PB : BOSS_BLIND_PB;
}
static u32 get_blind_spritesheet_idx(enum BlindType type)
{
// See comment below in blind_get_color why we differenciate before/after the Mark
return (type < BLIND_TYPE_MARK)
? type / BLIND_TOKENS_PER_SPRITESHEET
: BLIND_TYPE_MARK / BLIND_TOKENS_PER_SPRITESHEET + type - BLIND_TYPE_MARK;
}
u16 blind_get_color(enum BlindType type, enum BlindColorIndex index)
{
// Do a little translation of palette idx -> custom array idx
// All blinds before the Mark are arranged in pairs with their palettte split in two
// | XX | 1 | 2 | 3 | 4 | 5 | 6 | 7 | -> first sprite
// | XX | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -> second sprite
// so we need to offset the color indices by 8 for blinds with an odd type
// From the Mark onwards, all sprites are alone in their spritesheet, so no need to offset
u32 color_idx = index + ((type < BLIND_TYPE_MARK)
? BLIND_TOKEN_PALETTE_SIZE * (type % BLIND_TOKENS_PER_SPRITESHEET)
: 0);
return blind_gfxPal[get_blind_spritesheet_idx(type)][color_idx];
}
void apply_blind_colors(enum BlindType type)
{
// keep track of active boss blind spritesheet to copy colors only when changing
static u32 active_boss_spritesheet = BLIND_TYPE_MAX;
u32 new_spritesheet = get_blind_spritesheet_idx(type);
// No need to update normal blind palette
if (type < BLIND_TYPE_BOSS || active_boss_spritesheet == new_spritesheet)
{
return;
}
active_boss_spritesheet = new_spritesheet;
// Just copy the palette as is to the sprite palette bank
memcpy16(
&pal_obj_bank[BOSS_BLIND_PB],
blind_gfxPal[active_boss_spritesheet],
NUM_ELEM_IN_ARR(blind_gfx0Pal)
);
}
/**
* @brief Get the starting tile index in tiles memory for the given BlindToken sprite layer
*
* @param layer of the BlindToken, as an offset relative to `BLIND_BASE_LAYER`
*
* @return the starting tile index of the BlindToken sprite at requested layer
* @sa BLIND_BASE_LAYER
*/
static u32 get_layer_tile_index(enum BlindTokenLayers layer)
{
// All Blind sprites are stored sequentially and correspond to their IDs
return (BLIND_BASE_LAYER + layer) * BLIND_SPRITE_OFFSET;
}
void apply_blind_tiles(enum BlindType type, enum BlindTokenLayers layer)
{
u32 spritesheet_idx = get_blind_spritesheet_idx(type);
u32 sprite_idx = (type < BLIND_TYPE_MARK) ? type % BLIND_TOKENS_PER_SPRITESHEET : 0;
memcpy32(
&tile_mem[TILE_MEM_OBJ_CHARBLOCK0_IDX][get_layer_tile_index(layer)],
&blind_gfxTiles[spritesheet_idx][sprite_idx * BLIND_SPRITE_COPY_SIZE],
BLIND_SPRITE_COPY_SIZE
);
apply_blind_colors(type);
}
Sprite* blind_token_new(enum BlindType type, int x, int y, enum BlindTokenLayers layer)
{
apply_blind_tiles(type, layer);
Sprite* sprite = sprite_new(
ATTR0_SQUARE | ATTR0_4BPP,
ATTR1_SIZE_32x32,
get_layer_tile_index(layer),
get_blind_pb(type),
BLIND_BASE_LAYER + layer
);
sprite_position(sprite, x, y);
return sprite;
}