e7f550ce22
* background * Implement Options button * Update main menu artwork * add options menu tiles * prepare options menu handling * incorporate main menu refactor * toto * add all menu interactions * do not reset selection_x in main_menu * options struct * fix rebase from ricfehr3's repo * Implement high contrast cards * Save/Load options values to SRAM * load options on game_init and validate data within the load_options function * Get changes from ricfehr3's PR * recover fix for glitched ace * clang format * clang format * clang format * Save 500B of image data by using only minimal set of tiles instead of keeping final window in source file * clang format * clang format * Implement Options button * Update main menu artwork * progress, leaving it alone for now until discussion * make game_vars an extern * Move options vars into their own struct * rollback previous commit * Implement save validation. Version check to come * implement data validity + version check for game saves * clang format * Save position in rng sequence for consistent run after load * fix rebase issues * fix rebase issue * remove unused/duplicate define * Oops, made a mistake when testing PR #424 * encode high contrast palette into an image, and load it at game_start * document save.c/h and correct typos * correct alignment * game speed and high contrast buttons working * small cleanup * sliders also work again now * clang format * Moved balatro_version extern declaration to version.h file * removed unsused variable * make showing ROM version a toggle * move ante, round and money to the GameVariables struct * Rename SaveCheckInfo struct to SaveHeader for clarity * Add SaveHeader struct explanation * clang format * Revert unnecessary tileset optimization * SFX now follows the sound volume slider * clang format * Music now follows volume slider * Changed volume slider increment from 5 to 20 to avoid having the music sound too rough under 20% volume * refactor main_menu.c to use SelectionGrid and Button structs * clang format * make a "new" save button so we don't save during the state transition * clang format * Reduced state transition flicker by only changing affine background to a different one * Fix loading game too late in game init sequence * First batch of suggestions from @MeirGavish * Remove unnecessary intermediary buffers for high contrast palette swapping * comment row warp hack for button input * Add some of Copilot's suggestions * rename balatro_version into gbalatro_version for rebranding reasons + version display erase screen fix * remove unnecessary NULL check on `on_selection_changed` method * implement small game speed arrow buttons animation * factorize some code * remove unnecessary data validation in load_game(), since we already checked the header * Implement @MeirGavish 's suggestion regarding splitting the huge `on_update` function * clang format * implement @ricfehr3 's suggestion about keeping last selected button instead of using an obscure `on_boot` boolean * Apply @ricfehr3's suggestions * fix clang-format flags --------- Co-authored-by: MathisMartin31 <mathis.martin31@gmail.com>
244 lines
7.9 KiB
C
244 lines
7.9 KiB
C
#include "blind.h"
|
|
|
|
#include "blind_gfx.h"
|
|
#include "graphic_utils.h"
|
|
#include "list.h"
|
|
#include "stdbool.h"
|
|
#include "util.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <tonc.h>
|
|
|
|
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 unbeaten_boss_blinds;
|
|
static List unbeaten_showdown_blinds;
|
|
|
|
// 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
|
|
static Blind _blind_type_map[BLIND_TYPE_MAX] = {
|
|
{BLIND_TYPE_SMALL, FIX_ONE, 3},
|
|
{BLIND_TYPE_BIG, (FIX_ONE * 3) / 2, 4},
|
|
{BLIND_TYPE_HOOK, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_OX, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_HOUSE, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_WALL, FIX_ONE * 2, 5}, // x4 score requirement will be part of the effect
|
|
{BLIND_TYPE_WHEEL, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_ARM, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_CLUB, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_FISH, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_PSYCHIC, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_GOAD, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_WATER, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_WINDOW, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_MANACLE, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_EYE, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_MOUTH, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_PLANT, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_SERPENT, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_PILLAR, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_NEEDLE, FIX_ONE * 2, 5}, // Same as the Wall with normal requirement
|
|
{BLIND_TYPE_HEAD, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_TOOTH, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_FLINT, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_MARK, FIX_ONE * 2, 5},
|
|
{BLIND_TYPE_ACORN, FIX_ONE * 2, 8},
|
|
{BLIND_TYPE_LEAF, FIX_ONE * 2, 8},
|
|
{BLIND_TYPE_VESSEL, FIX_ONE * 2, 8}, // Same as the Wall with x6 requirement
|
|
{BLIND_TYPE_HEART, FIX_ONE * 2, 8},
|
|
{BLIND_TYPE_BELL, FIX_ONE * 2, 8}
|
|
};
|
|
// 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(_blind_type_map[type].score_req_multipler * ante_lut[ante]);
|
|
}
|
|
|
|
int blind_get_reward(enum BlindType type)
|
|
{
|
|
return _blind_type_map[type].reward;
|
|
}
|
|
|
|
// Fills the unbeaten boss blinds lists
|
|
// This must be called at the beginning of a run
|
|
void init_unbeaten_blinds_list(bool showdown)
|
|
{
|
|
// create the lists when calling for the first time
|
|
static bool init = false;
|
|
if (!init)
|
|
{
|
|
init = true;
|
|
unbeaten_showdown_blinds = list_create();
|
|
unbeaten_boss_blinds = list_create();
|
|
}
|
|
|
|
List* p_unbeaten_blinds = showdown ? &unbeaten_showdown_blinds : &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, &_blind_type_map[i]);
|
|
}
|
|
}
|
|
|
|
enum BlindType roll_blind_type(bool showdown)
|
|
{
|
|
List* p_unbeaten_blinds = showdown ? &unbeaten_showdown_blinds : &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 = get_rand() % 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 ? &unbeaten_showdown_blinds : &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)
|
|
);
|
|
}
|
|
|
|
static u32 get_layer_tile_index(int 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, int 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, int 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;
|
|
}
|