[Feature] Implement options menu and reading/writing values from/to the SRAM (#449)
* 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>
This commit is contained in:
@@ -572,7 +572,7 @@ EXTRACT_PACKAGE = NO
|
||||
# included in the documentation.
|
||||
# The default value is: NO.
|
||||
|
||||
EXTRACT_STATIC = NO
|
||||
EXTRACT_STATIC = YES
|
||||
|
||||
# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
|
||||
# locally in source files will be included in the documentation. If set to NO,
|
||||
@@ -588,7 +588,7 @@ EXTRACT_LOCAL_CLASSES = YES
|
||||
# included.
|
||||
# The default value is: NO.
|
||||
|
||||
EXTRACT_LOCAL_METHODS = NO
|
||||
EXTRACT_LOCAL_METHODS = YES
|
||||
|
||||
# If this flag is set to YES, the members of anonymous namespaces will be
|
||||
# extracted and appear in the documentation as a namespace called
|
||||
@@ -3023,3 +3023,4 @@ MSCFILE_DIRS =
|
||||
EXCLUDE = build tests graphics audio doc scripts
|
||||
|
||||
USE_MDFILE_AS_MAINPAGE = CONTRIBUTING.md
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ CFLAGS += $(INCLUDE)
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
|
||||
ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -g $(ARCH) -Wl,-Map,$(notdir $*.map),--undefined=balatro_version
|
||||
LDFLAGS = -g $(ARCH) -Wl,-Map,$(notdir $*.map),--undefined=gbalatro_version
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# any extra libraries we wish to link with the project
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1 @@
|
||||
-gB8 -mR8 -mLs -mRtf -pn 160
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
@@ -1 +1 @@
|
||||
-gB4 -Mw4 -Mh4 -m!
|
||||
-gB4 -Mw4 -Mh4 -m! -pn 16
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1 @@
|
||||
-gB4 -g! -m! -pn 16
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 568 B |
@@ -33,6 +33,13 @@
|
||||
*/
|
||||
enum AffineBackgroundID
|
||||
{
|
||||
/**
|
||||
* @brief Display background for the initial splash screen.
|
||||
*
|
||||
* Signifies that no background is currently in use.
|
||||
*/
|
||||
AFFINE_BG_NONE,
|
||||
|
||||
/**
|
||||
* @brief Display background for main menu.
|
||||
*
|
||||
|
||||
@@ -9,10 +9,16 @@
|
||||
#include <mm_types.h>
|
||||
|
||||
/**
|
||||
* @def MM_FULL_VOLUME
|
||||
* @def MM_MODULE_FULL_VOLUME
|
||||
* @brief The maximum volume for maxmod module volume (main theme)
|
||||
*/
|
||||
#define MM_MODULE_FULL_VOLUME 1024
|
||||
|
||||
/**
|
||||
* @def MM_SFX_FULL_VOLUME
|
||||
* @brief The maximum volume for maxmod mm_sound_effect.volume
|
||||
*/
|
||||
#define MM_FULL_VOLUME 255
|
||||
#define MM_SFX_FULL_VOLUME 255
|
||||
|
||||
/**
|
||||
* @def MM_PAN_CENTER
|
||||
@@ -32,7 +38,7 @@
|
||||
* @def SFX_DEFAULT_VOLUME
|
||||
* @brief Default volume for sound effects
|
||||
*/
|
||||
#define SFX_DEFAULT_VOLUME MM_FULL_VOLUME
|
||||
#define SFX_DEFAULT_VOLUME MM_SFX_FULL_VOLUME
|
||||
|
||||
/**
|
||||
* @def SFX_DEFAULT_PAN
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
#define BTN_HIGHLIGHT_COLOR 0xFFFF
|
||||
|
||||
#define BUTTON_SFX_VOLUME 154 // 60% of MM_FULL_VOLUME
|
||||
#define BUTTON_SFX_VOLUME 154 // 60% of MM_SFX_FULL_VOLUME
|
||||
|
||||
/**
|
||||
* @brief The function to be called when the button is pressed
|
||||
|
||||
@@ -56,6 +56,7 @@ typedef struct CardObject
|
||||
|
||||
// Card functions
|
||||
void card_init();
|
||||
void toggle_high_contrast_cards(bool enable);
|
||||
|
||||
// Card methods
|
||||
Card* card_new(u8 suit, u8 rank);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// clang-format off
|
||||
// (stateEnum, on_init, on_update, on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_SPLASH_SCREEN, splash_screen_on_init, splash_screen_on_update, splash_screen_on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_MAIN_MENU, game_main_menu_on_init, game_main_menu_on_update, game_main_menu_on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_GAME_START, noop, game_start, noop)
|
||||
DEF_STATE_INFO(GAME_STATE_PLAYING, game_round_on_init, game_playing_on_update, noop)
|
||||
DEF_STATE_INFO(GAME_STATE_ROUND_END, noop, game_round_end_on_update, game_round_end_on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_SHOP, noop, game_shop_on_update, game_shop_on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_BLIND_SELECT, game_blind_select_on_init, game_blind_select_on_update, game_blind_select_on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_LOSE, game_lose_on_init, game_lose_on_update, game_over_on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_WIN, game_win_on_init, game_win_on_update, game_over_on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_SPLASH_SCREEN, splash_screen_on_init, splash_screen_on_update, splash_screen_on_exit )
|
||||
DEF_STATE_INFO(GAME_STATE_MAIN_MENU, game_main_menu_on_init, game_main_menu_on_update, game_main_menu_on_exit )
|
||||
DEF_STATE_INFO(GAME_STATE_OPTIONS_MENU, game_options_menu_on_init, game_options_menu_on_update, game_options_menu_on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_GAME_START, noop, game_start, noop )
|
||||
DEF_STATE_INFO(GAME_STATE_PLAYING, game_round_on_init, game_playing_on_update, noop )
|
||||
DEF_STATE_INFO(GAME_STATE_ROUND_END, noop, game_round_end_on_update, game_round_end_on_exit )
|
||||
DEF_STATE_INFO(GAME_STATE_SHOP, noop, game_shop_on_update, game_shop_on_exit )
|
||||
DEF_STATE_INFO(GAME_STATE_BLIND_SELECT, game_blind_select_on_init, game_blind_select_on_update, game_blind_select_on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_LOSE, game_lose_on_init, game_lose_on_update, game_over_on_exit )
|
||||
DEF_STATE_INFO(GAME_STATE_WIN, game_win_on_init, game_win_on_update, game_over_on_exit )
|
||||
// clang-format on
|
||||
|
||||
+9
-9
@@ -1,6 +1,7 @@
|
||||
#ifndef GAME_H
|
||||
#define GAME_H
|
||||
|
||||
#include "blind.h"
|
||||
#include "game/common_ui.h"
|
||||
#include "game_variables.h"
|
||||
|
||||
@@ -13,7 +14,7 @@ extern GameVariables g_game_vars;
|
||||
#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)
|
||||
#define FRAMES(x) (((x) + (g_game_vars.game_speed) - 1) / (g_game_vars.game_speed))
|
||||
|
||||
// TODO: Can make these dynamic to support interest-related jokers and vouchers
|
||||
#define MAX_INTEREST 5
|
||||
@@ -123,7 +124,7 @@ typedef struct ContainedHandTypes
|
||||
} ContainedHandTypes;
|
||||
// clang-format on
|
||||
|
||||
typedef void (*GameStateCallback)();
|
||||
typedef void (*GameStateCallback)(void);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@@ -158,17 +159,16 @@ int get_num_hands_remaining(void);
|
||||
|
||||
u32 get_chips(void);
|
||||
void set_chips(u32 new_chips);
|
||||
void display_chips();
|
||||
void display_chips(void);
|
||||
u32 get_mult(void);
|
||||
void set_mult(u32 new_mult);
|
||||
void display_mult();
|
||||
int get_money(void);
|
||||
void set_money(int new_money);
|
||||
void display_money();
|
||||
void display_mult(void);
|
||||
void display_money(void);
|
||||
void set_retrigger(bool new_retrigger);
|
||||
enum BlindType get_current_blind(void);
|
||||
enum BlindType get_next_boss_blind(void);
|
||||
|
||||
int get_game_speed(void);
|
||||
void set_game_speed(int new_game_speed);
|
||||
u32 get_rand(void);
|
||||
|
||||
// joker specific functions
|
||||
bool is_shortcut_joker_active(void);
|
||||
|
||||
@@ -17,6 +17,7 @@ enum BackgroundId
|
||||
BG_ROUND_END,
|
||||
BG_SHOP,
|
||||
BG_BLIND_SELECT,
|
||||
BG_OPTIONS_MENU,
|
||||
BG_MAIN_MENU
|
||||
};
|
||||
|
||||
|
||||
@@ -15,23 +15,17 @@ void game_main_menu_change_background(void);
|
||||
|
||||
/**
|
||||
* @brief Main menu state initialization
|
||||
*
|
||||
* @param vars passed @ref GameVariables struct
|
||||
*/
|
||||
void game_main_menu_on_init(void);
|
||||
|
||||
/**
|
||||
* @brief Main menu state update
|
||||
*
|
||||
* @param vars passed @ref GameVariables struct
|
||||
*/
|
||||
void game_main_menu_on_update(void);
|
||||
|
||||
/**
|
||||
* @brief Main menu cleanup (called when transitioning to game start)
|
||||
*
|
||||
* @param vars passed @ref GameVariables struct
|
||||
*/
|
||||
void game_main_menu_on_exit(void);
|
||||
|
||||
#endif // GAME_MAIN_MENU_H
|
||||
#endif // GAME_MAIN_MENU_H
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @file options_menu.h
|
||||
*
|
||||
* @brief Options menu state functions.
|
||||
*/
|
||||
#ifndef GAME_OPTIONS_MENU_H
|
||||
#define GAME_OPTIONS_MENU_H
|
||||
|
||||
#include "game_variables.h"
|
||||
|
||||
/**
|
||||
* @brief Change the options menu background
|
||||
*/
|
||||
void game_options_menu_change_background(void);
|
||||
|
||||
/**
|
||||
* @brief Options menu state initialization
|
||||
*/
|
||||
void game_options_menu_on_init(void);
|
||||
|
||||
/**
|
||||
* @brief Options menu state update
|
||||
*/
|
||||
void game_options_menu_on_update(void);
|
||||
|
||||
/**
|
||||
* @brief Options menu cleanup (called when going back to main menu)
|
||||
*/
|
||||
void game_options_menu_on_exit(void);
|
||||
|
||||
#endif // GAME_OPTIONS_MENU_H
|
||||
@@ -8,6 +8,19 @@
|
||||
|
||||
#include <tonc.h>
|
||||
|
||||
#define GAME_SPEED_MIN 1
|
||||
#define GAME_SPEED_MAX 4
|
||||
|
||||
// Volume is stored from 0 to 5 but is an increment of 20 so 0 to 100 will be displayed
|
||||
#define VOLUME_OPTION_MIN 0
|
||||
#define VOLUME_OPTION_MAX 5
|
||||
#define VOLUME_OPTION_INCREMENT 20
|
||||
|
||||
#define DEFAULT_GAME_SPEED 1
|
||||
#define DEFAULT_HIGH_CONTRAST false
|
||||
#define DEFAULT_MUSIC_VOLUME VOLUME_OPTION_MAX
|
||||
#define DEFAULT_SOUND_VOLUME VOLUME_OPTION_MAX
|
||||
|
||||
/**
|
||||
* @brief A central location for all game variables.
|
||||
*
|
||||
@@ -16,8 +29,26 @@
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
s32 timer;
|
||||
// Internal variables
|
||||
|
||||
s32 timer; // This might already exist in libtonc but idk so i'm just making my own
|
||||
u32 rng_seed;
|
||||
u32 rng_step; // Position in the rng sequence.
|
||||
|
||||
// Variables visible by the player
|
||||
|
||||
int round;
|
||||
int ante;
|
||||
int money;
|
||||
|
||||
// Options variables
|
||||
|
||||
// BY DEFAULT IS SET TO 1, but if changed to 2 or more, should speed up all (or most) of the
|
||||
// game aspects that should be sped up by speed, as in the original game.
|
||||
u8 game_speed;
|
||||
bool high_contrast;
|
||||
u8 music_volume;
|
||||
u8 sound_volume;
|
||||
} GameVariables;
|
||||
|
||||
extern GameVariables g_game_vars;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @file save.h
|
||||
*
|
||||
* @brief Utils functions to save/load data structures from/to the SRAM.
|
||||
*/
|
||||
#ifndef SAVE_H
|
||||
#define SAVE_H
|
||||
|
||||
#include "game_variables.h"
|
||||
|
||||
#include <tonc.h>
|
||||
|
||||
/**
|
||||
* @brief Save game data to SRAM.
|
||||
*/
|
||||
void save_game();
|
||||
|
||||
/**
|
||||
* @brief Load game data from SRAM.
|
||||
*
|
||||
* @sa save_game
|
||||
*/
|
||||
void load_game();
|
||||
|
||||
#endif // SAVE_H
|
||||
+1
-1
@@ -9,4 +9,4 @@
|
||||
|
||||
#define TM_ZERO 0
|
||||
|
||||
#endif // TIMER_H
|
||||
#endif // TIMER_H
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @file version.h
|
||||
*
|
||||
* @brief Game ROM version global var declaration.
|
||||
*/
|
||||
#ifndef VERSION_H
|
||||
#define VERSION_H
|
||||
|
||||
extern const char gbalatro_version[];
|
||||
|
||||
#endif // VERSION_H
|
||||
+1
-1
@@ -20,7 +20,7 @@ if __name__ == "__main__":
|
||||
sys.exit(1)
|
||||
|
||||
file_path = sys.argv[1]
|
||||
pattern = "GBALATRO_VERSION"
|
||||
pattern = "GBALATRO[_,-]VERSION"
|
||||
|
||||
for offset, text in grep_binary_offsets(file_path, pattern):
|
||||
version = text.split(":")[1]
|
||||
|
||||
@@ -13,7 +13,7 @@ static IWRAM_CODE void s_affine_background_prep_bgaff_arr();
|
||||
|
||||
static BG_AFFINE _bgaff_arr[SCREEN_HEIGHT + 1];
|
||||
static AFF_SRC_EX _asx = {0};
|
||||
static enum AffineBackgroundID _background = AFFINE_BG_MAIN_MENU;
|
||||
static enum AffineBackgroundID _background = AFFINE_BG_NONE;
|
||||
static uint _timer = 0;
|
||||
|
||||
void affine_background_init()
|
||||
@@ -78,6 +78,11 @@ void affine_background_load_palette(const u16* src)
|
||||
|
||||
void affine_background_change_background(enum AffineBackgroundID new_bg)
|
||||
{
|
||||
if (_background == new_bg)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_background = new_bg;
|
||||
|
||||
switch (_background)
|
||||
@@ -110,6 +115,8 @@ void affine_background_change_background(enum AffineBackgroundID new_bg)
|
||||
GRIT_CPY(&se_mem[AFFINE_BG_SBB], affine_background_gfxMap);
|
||||
affine_background_load_palette(affine_background_gfxPal);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "audio_utils.h"
|
||||
|
||||
#include "game_variables.h"
|
||||
|
||||
#include <maxmod.h>
|
||||
|
||||
void play_sfx(mm_word id, mm_word rate, mm_byte volume)
|
||||
@@ -8,7 +10,7 @@ void play_sfx(mm_word id, mm_word rate, mm_byte volume)
|
||||
{id},
|
||||
rate,
|
||||
0,
|
||||
volume,
|
||||
(volume * g_game_vars.sound_volume) / VOLUME_OPTION_MAX,
|
||||
SFX_DEFAULT_PAN,
|
||||
};
|
||||
mmEffectEx(&sfx);
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ enum BlindType roll_blind_type(bool showdown)
|
||||
}
|
||||
|
||||
// roll a random blind among the unbeaten ones
|
||||
int random_blind_idx = rand() % list_get_len(p_unbeaten_blinds);
|
||||
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;
|
||||
|
||||
+14
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "deck_gfx.h"
|
||||
#include "graphic_utils.h"
|
||||
#include "high_contrast_deck_pal_gfx.h"
|
||||
|
||||
#include <maxmod.h>
|
||||
#include <stdlib.h>
|
||||
@@ -21,7 +22,19 @@ const static u16 _card_sprite_lut[NUM_SUITS][NUM_RANKS] = {
|
||||
|
||||
void card_init()
|
||||
{
|
||||
GRIT_CPY(&pal_obj_mem[CARD_PB], deck_gfxPal);
|
||||
toggle_high_contrast_cards(false);
|
||||
}
|
||||
|
||||
void toggle_high_contrast_cards(bool enable)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
GRIT_CPY(&pal_obj_mem[CARD_PB], high_contrast_deck_pal_gfxPal);
|
||||
}
|
||||
else
|
||||
{
|
||||
GRIT_CPY(&pal_obj_mem[CARD_PB], deck_gfxPal);
|
||||
}
|
||||
}
|
||||
|
||||
// Card methods
|
||||
|
||||
+85
-79
@@ -8,16 +8,17 @@
|
||||
#include "background_main_menu_gfx.h"
|
||||
#include "background_shop_gfx.h"
|
||||
#include "bitset.h"
|
||||
#include "blind.h"
|
||||
#include "button.h"
|
||||
#include "card.h"
|
||||
#include "game/common_ui.h"
|
||||
#include "game/main_menu.h"
|
||||
#include "game/options_menu.h"
|
||||
#include "game_variables.h"
|
||||
#include "graphic_utils.h"
|
||||
#include "hand_analysis.h"
|
||||
#include "joker.h"
|
||||
#include "list.h"
|
||||
#include "save.h"
|
||||
#include "selection_grid.h"
|
||||
#include "soundbank.h"
|
||||
#include "splash_screen.h"
|
||||
@@ -64,6 +65,7 @@
|
||||
#define CARD_UNFOCUSED_SEL_Y 15
|
||||
#define CARD_FOCUSED_SEL_Y 20
|
||||
|
||||
// Timer defs
|
||||
#define TM_RESET_STATIC_VARS 30
|
||||
#define TM_END_POP_MENU_ANIM 13
|
||||
#define TM_START_ROUND_END_REWARDS_ANIM 1
|
||||
@@ -87,6 +89,7 @@
|
||||
|
||||
// TODO: Rename "PID" to "PAL_IDX"
|
||||
// Palette IDs
|
||||
|
||||
#define BOSS_BLIND_PRIMARY_PID 1
|
||||
#define REROLL_BTN_PID 3
|
||||
#define BLIND_SKIP_BTN_PID 5
|
||||
@@ -428,9 +431,6 @@ static const BG_POINT HAND_PLAY_POS = {120, 70};
|
||||
|
||||
typedef void (*SubStateActionFn)(void);
|
||||
|
||||
// BY DEFAULT IS SET TO 1, but if changed to 2 or more, should speed up all (or most) of the game
|
||||
// aspects that should be sped up by speed, as in the original game.
|
||||
static int game_speed = 1;
|
||||
static enum BackgroundId background = BG_NONE;
|
||||
|
||||
static StateInfo state_info[] = {
|
||||
@@ -552,6 +552,18 @@ static enum PlayState play_state = PLAY_STARTING;
|
||||
static enum HandType hand_type = NONE;
|
||||
static ContainedHandTypes _contained_hands = {0};
|
||||
|
||||
// Initialization of the global var
|
||||
// clang-format off
|
||||
GameVariables g_game_vars = {
|
||||
0, 0, 0,
|
||||
0, 0, 0,
|
||||
DEFAULT_GAME_SPEED,
|
||||
DEFAULT_HIGH_CONTRAST,
|
||||
DEFAULT_MUSIC_VOLUME,
|
||||
DEFAULT_SOUND_VOLUME
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// The sprite that displays the blind when in "GAME_PLAYING/GAME_ROUND_END" state
|
||||
static Sprite* playing_blind_token = NULL;
|
||||
|
||||
@@ -586,9 +598,6 @@ static int max_discards = 4;
|
||||
static int hands = 0;
|
||||
static int discards = 0;
|
||||
|
||||
static int round = 0;
|
||||
static int ante = 0;
|
||||
static int money = 0;
|
||||
static u32 score = 0;
|
||||
static u32 temp_score = 0; // This is the score that shows in the same spot as the hand type.
|
||||
static bool score_flames_active = false;
|
||||
@@ -645,9 +654,6 @@ static int shortcut_joker_count = 0;
|
||||
|
||||
static int four_fingers_joker_count = 0;
|
||||
|
||||
GBAL_UNUSED
|
||||
GameVariables g_game_vars;
|
||||
|
||||
GBAL_UNUSED
|
||||
static inline bool is_shop_joker_avail(int joker_id)
|
||||
{
|
||||
@@ -729,7 +735,7 @@ static inline void jokers_available_to_shop_init(void)
|
||||
static void reroll_boss_blind(bool no_tiles)
|
||||
{
|
||||
// Showdown blinds only show up on ante 8, 16, etc...
|
||||
next_boss_blind = roll_blind_type((ante % 8 == 0) && (ante > 0));
|
||||
next_boss_blind = roll_blind_type((g_game_vars.ante % 8 == 0) && (g_game_vars.ante > 0));
|
||||
if (!no_tiles)
|
||||
{
|
||||
apply_blind_tiles(next_boss_blind, BOSS_BLIND_TOKEN_LAYER);
|
||||
@@ -781,6 +787,8 @@ void game_init()
|
||||
|
||||
jokers_available_to_shop_init();
|
||||
|
||||
load_game();
|
||||
|
||||
hands = max_hands;
|
||||
discards = max_discards;
|
||||
g_game_vars.timer = TM_ZERO;
|
||||
@@ -788,10 +796,10 @@ void game_init()
|
||||
blinds_states[0] = BLIND_STATE_CURRENT;
|
||||
blinds_states[1] = BLIND_STATE_UPCOMING;
|
||||
blinds_states[2] = BLIND_STATE_UPCOMING;
|
||||
ante = STARTING_ANTE;
|
||||
money = STARTING_MONEY;
|
||||
g_game_vars.ante = STARTING_ANTE;
|
||||
g_game_vars.money = STARTING_MONEY;
|
||||
score = STARTING_SCORE;
|
||||
round = 0;
|
||||
g_game_vars.round = 0;
|
||||
|
||||
// Initialize/reset unbeaten Boss/Showdown Blinds so they are all available
|
||||
init_unbeaten_blinds_list(false);
|
||||
@@ -918,6 +926,12 @@ void game_change_state(enum GameState new_game_state)
|
||||
}
|
||||
}
|
||||
|
||||
u32 get_rand()
|
||||
{
|
||||
g_game_vars.rng_step++;
|
||||
return rand();
|
||||
}
|
||||
|
||||
CardObject** get_hand_array(void)
|
||||
{
|
||||
return hand;
|
||||
@@ -1037,15 +1051,14 @@ int get_num_hands_remaining(void)
|
||||
return hands;
|
||||
}
|
||||
|
||||
int get_game_speed(void)
|
||||
enum BlindType get_current_blind()
|
||||
{
|
||||
return game_speed;
|
||||
return current_blind;
|
||||
}
|
||||
|
||||
// for the future when a menu actually lets this variable be changed.
|
||||
void set_game_speed(int new_game_speed)
|
||||
enum BlindType get_next_boss_blind()
|
||||
{
|
||||
game_speed = new_game_speed;
|
||||
return next_boss_blind;
|
||||
}
|
||||
|
||||
u32 get_chips(void)
|
||||
@@ -1068,16 +1081,6 @@ void set_mult(u32 new_mult)
|
||||
mult = new_mult;
|
||||
}
|
||||
|
||||
int get_money(void)
|
||||
{
|
||||
return money;
|
||||
}
|
||||
|
||||
void set_money(int new_money)
|
||||
{
|
||||
money = new_money;
|
||||
}
|
||||
|
||||
void set_retrigger(bool new_retrigger)
|
||||
{
|
||||
retrigger = new_retrigger;
|
||||
@@ -1089,7 +1092,7 @@ void display_money()
|
||||
tte_erase_rect_wrapper(MONEY_TEXT_RECT);
|
||||
|
||||
char money_str_buff[INT_MAX_DIGITS + 2]; // + 2 for null terminator and "$" sign
|
||||
snprintf(money_str_buff, sizeof(money_str_buff), "$%d", money);
|
||||
snprintf(money_str_buff, sizeof(money_str_buff), "$%d", g_game_vars.money);
|
||||
|
||||
// Bias left so the number is centered and the "$" sign is on the left
|
||||
update_text_rect_to_center_str(&money_text_rect, money_str_buff, SCREEN_LEFT);
|
||||
@@ -1806,7 +1809,7 @@ static void display_score(u32 value)
|
||||
static void check_flaming_score(void)
|
||||
{
|
||||
u32 curr_score = u32_protected_mult(chips, mult);
|
||||
u32 required_score = blind_get_requirement(current_blind, ante);
|
||||
u32 required_score = blind_get_requirement(current_blind, g_game_vars.ante);
|
||||
if (curr_score >= required_score && !score_flames_active)
|
||||
{
|
||||
// start flaming score
|
||||
@@ -1834,7 +1837,7 @@ static void display_round(void)
|
||||
ROUND_TEXT_RECT.left,
|
||||
ROUND_TEXT_RECT.top,
|
||||
TTE_YELLOW_PB,
|
||||
round
|
||||
g_game_vars.round
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1935,14 +1938,14 @@ static inline void deck_shuffle(void)
|
||||
{
|
||||
for (int i = deck_top; i > 0; i--)
|
||||
{
|
||||
int j = rand() % (i + 1);
|
||||
int j = get_rand() % (i + 1);
|
||||
Card* temp = deck[i];
|
||||
deck[i] = deck[j];
|
||||
deck[j] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
static void game_round_on_init()
|
||||
static void game_round_on_init(void)
|
||||
{
|
||||
hand_state = HAND_DRAW;
|
||||
cards_drawn = 0;
|
||||
@@ -1974,7 +1977,7 @@ static void game_round_on_init()
|
||||
}
|
||||
|
||||
Rect blind_req_text_rect = BLIND_REQ_TEXT_RECT;
|
||||
u32 blind_requirement = blind_get_requirement(current_blind, ante);
|
||||
u32 blind_requirement = blind_get_requirement(current_blind, g_game_vars.ante);
|
||||
|
||||
char blind_req_str_buff[UINT_MAX_DIGITS + 1];
|
||||
|
||||
@@ -2019,14 +2022,14 @@ static void game_over_init(void)
|
||||
main_bg_se_copy_rect(NEW_RUN_BTN_SRC_RECT, NEW_RUN_BTN_DEST_POS);
|
||||
}
|
||||
|
||||
static void game_lose_on_init()
|
||||
static void game_lose_on_init(void)
|
||||
{
|
||||
game_over_init();
|
||||
// Using the text color to match the "Game Over" text
|
||||
affine_background_set_color(TEXT_CLR_RED);
|
||||
}
|
||||
|
||||
static void game_win_on_init()
|
||||
static void game_win_on_init(void)
|
||||
{
|
||||
game_over_init();
|
||||
// Using the text color to match the "You Win" text
|
||||
@@ -2036,8 +2039,9 @@ static void game_win_on_init()
|
||||
// General functions
|
||||
static inline void set_seed(int seed)
|
||||
{
|
||||
srand(seed);
|
||||
g_game_vars.rng_seed = seed;
|
||||
srand(g_game_vars.rng_seed);
|
||||
g_game_vars.rng_step = 0;
|
||||
}
|
||||
|
||||
// Playing state functions
|
||||
@@ -2195,7 +2199,7 @@ static bool game_playing_hand_row_on_selection_changed(
|
||||
*/
|
||||
play_sfx(
|
||||
SFX_CARD_FOCUS,
|
||||
MM_BASE_PITCH_RATE + rand() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
|
||||
MM_BASE_PITCH_RATE + get_rand() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
|
||||
SFX_DEFAULT_VOLUME
|
||||
);
|
||||
}
|
||||
@@ -2215,7 +2219,7 @@ static bool game_playing_hand_row_on_selection_changed(
|
||||
*/
|
||||
play_sfx(
|
||||
SFX_CARD_FOCUS,
|
||||
MM_BASE_PITCH_RATE + rand() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
|
||||
MM_BASE_PITCH_RATE + get_rand() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
|
||||
SFX_DEFAULT_VOLUME
|
||||
);
|
||||
}
|
||||
@@ -2377,13 +2381,13 @@ static inline void game_playing_handle_round_over(void)
|
||||
{
|
||||
enum GameState next_state = GAME_STATE_ROUND_END;
|
||||
|
||||
if (score >= blind_get_requirement(current_blind, ante))
|
||||
if (score >= blind_get_requirement(current_blind, g_game_vars.ante))
|
||||
{
|
||||
if (current_blind > BLIND_TYPE_BIG)
|
||||
{
|
||||
if (ante < MAX_ANTE)
|
||||
if (g_game_vars.ante < MAX_ANTE)
|
||||
{
|
||||
display_ante(++ante);
|
||||
display_ante(++g_game_vars.ante);
|
||||
|
||||
// mark current boss blind as beaten and allow for reroll
|
||||
set_blind_beaten(next_boss_blind);
|
||||
@@ -2706,7 +2710,7 @@ static bool check_and_score_joker_for_event(
|
||||
|
||||
static inline bool game_round_is_over(void)
|
||||
{
|
||||
return hands == 0 || score >= blind_get_requirement(current_blind, ante);
|
||||
return hands == 0 || score >= blind_get_requirement(current_blind, g_game_vars.ante);
|
||||
}
|
||||
|
||||
// Basically a copy of HAND_DISCARD
|
||||
@@ -3156,7 +3160,7 @@ static inline void game_playing_process_input_and_state(void)
|
||||
display_chips();
|
||||
|
||||
static const int SCORE_CALC_SFX_PITCH_SHIFT = -102; // -10% OF MM_BASE_PITCH_RATE
|
||||
static const int SCORE_CALC_SFX_VOLUME = 204; // 80% MM_FULL_VOLUME
|
||||
static const int SCORE_CALC_SFX_VOLUME = 204; // 80% MM_SFX_FULL_VOLUME
|
||||
|
||||
// The chips calculation SFX is the same as button
|
||||
play_sfx(
|
||||
@@ -3171,10 +3175,10 @@ static inline void game_playing_process_input_and_state(void)
|
||||
/* Using fixed point in case the score is lower than NUM_SCORE_LERP_STEPS and then
|
||||
* then the division rounds it down to 0 and it's never added to the total.
|
||||
* The operation is equivalent to
|
||||
* fxdiv(int2fx(temp_score * get_game_speed()), int2fx(NUM_SCORE_LERP_STEPS))
|
||||
* fxdiv(int2fx(temp_score * g_game_vars.game_speed), int2fx(NUM_SCORE_LERP_STEPS))
|
||||
*/
|
||||
lerped_temp_score -= int2fx(temp_score * get_game_speed()) / NUM_SCORE_LERP_STEPS;
|
||||
lerped_score += int2fx(temp_score * get_game_speed()) / NUM_SCORE_LERP_STEPS;
|
||||
lerped_temp_score -= int2fx(temp_score * g_game_vars.game_speed) / NUM_SCORE_LERP_STEPS;
|
||||
lerped_score += int2fx(temp_score * g_game_vars.game_speed) / NUM_SCORE_LERP_STEPS;
|
||||
|
||||
if (lerped_temp_score > 0)
|
||||
{
|
||||
@@ -3504,7 +3508,7 @@ static inline void game_playing_process_flaming_score(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void game_playing_on_update()
|
||||
static void game_playing_on_update(void)
|
||||
{
|
||||
// Background logic (thissss might be moved to the card'ssss logic later. I'm a sssssnake)
|
||||
if (hand_state == HAND_DRAW || hand_state == HAND_DISCARD || hand_state == HAND_SELECT)
|
||||
@@ -3537,13 +3541,13 @@ static void game_playing_on_update()
|
||||
|
||||
static int calculate_interest_reward(void)
|
||||
{
|
||||
int reward = (money / 5) * INTEREST_PER_5;
|
||||
int reward = (g_game_vars.money / 5) * INTEREST_PER_5;
|
||||
if (reward > MAX_INTEREST)
|
||||
reward = MAX_INTEREST;
|
||||
return reward;
|
||||
}
|
||||
|
||||
static void game_round_end_on_exit()
|
||||
static void game_round_end_on_exit(void)
|
||||
{
|
||||
// Cleanup blind tokens from this round to avoid accumulating
|
||||
// allocated blind sprites each round
|
||||
@@ -3555,7 +3559,7 @@ static void game_round_end_on_exit()
|
||||
// TODO: Reuse sprites for blind selection?
|
||||
}
|
||||
|
||||
static void game_round_end_on_update()
|
||||
static void game_round_end_on_update(void)
|
||||
{
|
||||
if (state_info[game_state].substate == ROUND_END_EXIT)
|
||||
{
|
||||
@@ -3606,7 +3610,7 @@ static void game_round_end_display_finished_blind()
|
||||
{
|
||||
obj_unhide(round_end_blind_token->obj, 0);
|
||||
|
||||
int current_ante = ante;
|
||||
int current_ante = g_game_vars.ante;
|
||||
|
||||
// Beating the boss blind increases the ante, so we need to display the previous ante value
|
||||
if (current_blind > BLIND_TYPE_BIG)
|
||||
@@ -3854,7 +3858,7 @@ static void game_round_end_display_rewards()
|
||||
static inline void game_round_end_cashout(void)
|
||||
{
|
||||
// Reward the player
|
||||
money += hands + blind_get_reward(current_blind) + calculate_interest_reward();
|
||||
g_game_vars.money += hands + blind_get_reward(current_blind) + calculate_interest_reward();
|
||||
display_money();
|
||||
|
||||
hands = max_hands; // Reset the hands to the maximum
|
||||
@@ -3969,7 +3973,7 @@ static inline int game_shop_get_rand_available_joker_id(void)
|
||||
return UNDEFINED;
|
||||
|
||||
int matching_joker_ids[jokers_avail_size];
|
||||
int fallback_random_idx = random() % jokers_avail_size;
|
||||
int fallback_random_idx = get_rand() % jokers_avail_size;
|
||||
int fallback_random_joker_id = UNDEFINED;
|
||||
int match_count = 0;
|
||||
|
||||
@@ -3989,7 +3993,7 @@ static inline int game_shop_get_rand_available_joker_id(void)
|
||||
}
|
||||
|
||||
int selected_joker_id =
|
||||
(match_count > 0) ? matching_joker_ids[random() % match_count] : fallback_random_joker_id;
|
||||
(match_count > 0) ? matching_joker_ids[get_rand() % match_count] : fallback_random_joker_id;
|
||||
|
||||
return selected_joker_id;
|
||||
}
|
||||
@@ -4160,7 +4164,7 @@ static inline void game_sell_joker(int joker_idx)
|
||||
return;
|
||||
|
||||
JokerObject* joker_object = (JokerObject*)list_get_at_idx(&_owned_jokers_list, joker_idx);
|
||||
money += joker_get_sell_value(joker_object->joker);
|
||||
g_game_vars.money += joker_get_sell_value(joker_object->joker);
|
||||
display_money();
|
||||
erase_price_under_sprite_object(joker_object->sprite_object);
|
||||
|
||||
@@ -4216,8 +4220,8 @@ static inline void game_shop_buy_joker(int shop_joker_idx)
|
||||
{
|
||||
JokerObject* joker_object = (JokerObject*)list_get_at_idx(&_shop_jokers_list, shop_joker_idx);
|
||||
|
||||
money -= joker_object->joker->value; // Deduct the money spent on the joker
|
||||
display_money(); // Update the money display
|
||||
g_game_vars.money -= joker_object->joker->value; // Deduct the money spent on the joker
|
||||
display_money(); // Update the money display
|
||||
erase_price_under_sprite_object(joker_object->sprite_object);
|
||||
sprite_object_set_focus(joker_object->sprite_object, false);
|
||||
add_to_held_jokers(joker_object);
|
||||
@@ -4255,7 +4259,7 @@ static void shop_top_row_on_key_transit(SelectionGrid* selection_grid, Selection
|
||||
JokerObject* joker_object =
|
||||
(JokerObject*)list_get_at_idx(&_shop_jokers_list, shop_joker_idx);
|
||||
if (joker_object == NULL || list_get_len(&_owned_jokers_list) >= MAX_JOKERS_HELD_SIZE ||
|
||||
money < joker_object->joker->value)
|
||||
g_game_vars.money < joker_object->joker->value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -4354,7 +4358,7 @@ static bool shop_reroll_row_on_selection_changed(
|
||||
|
||||
static inline void game_shop_reroll(int* reroll_cost)
|
||||
{
|
||||
money -= *reroll_cost;
|
||||
g_game_vars.money -= *reroll_cost;
|
||||
display_money(); // Update the money display
|
||||
|
||||
ListItr itr = list_itr_create(&_shop_jokers_list);
|
||||
@@ -4405,7 +4409,7 @@ static void shop_reroll_row_on_key_transit(SelectionGrid* selection_grid, Select
|
||||
return;
|
||||
}
|
||||
|
||||
if (money >= reroll_cost)
|
||||
if (g_game_vars.money >= reroll_cost)
|
||||
{
|
||||
// TODO: Add money sound effect
|
||||
play_sfx(SFX_BUTTON, MM_BASE_PITCH_RATE, BUTTON_SFX_VOLUME);
|
||||
@@ -4503,7 +4507,7 @@ static inline void game_shop_lights_anim_frame(void)
|
||||
memcpy16(&pal_bg_mem[SHOP_LIGHTS_1_PID], &shifted_palette[3], 1);
|
||||
}
|
||||
|
||||
static void game_shop_on_update()
|
||||
static void game_shop_on_update(void)
|
||||
{
|
||||
change_background(BG_SHOP);
|
||||
|
||||
@@ -4536,7 +4540,7 @@ static void game_shop_on_update()
|
||||
shop_state_actions[substate]();
|
||||
}
|
||||
|
||||
static void game_shop_on_exit()
|
||||
static void game_shop_on_exit(void)
|
||||
{
|
||||
ListItr itr = list_itr_create(&_shop_jokers_list);
|
||||
JokerObject* joker_object;
|
||||
@@ -4554,9 +4558,11 @@ static void game_shop_on_exit()
|
||||
list_clear(&_shop_jokers_list);
|
||||
|
||||
increment_blind(BLIND_STATE_DEFEATED); // TODO: Move to game_round_end()?
|
||||
|
||||
save_game();
|
||||
}
|
||||
|
||||
static void game_blind_select_on_init()
|
||||
static void game_blind_select_on_init(void)
|
||||
{
|
||||
change_background(BG_BLIND_SELECT);
|
||||
selection_x = 0;
|
||||
@@ -4565,7 +4571,7 @@ static void game_blind_select_on_init()
|
||||
play_sfx(SFX_POP, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME);
|
||||
}
|
||||
|
||||
static void game_blind_select_on_update()
|
||||
static void game_blind_select_on_update(void)
|
||||
{
|
||||
if (state_info[game_state].substate == BLIND_SELECT_MAX)
|
||||
{
|
||||
@@ -4638,7 +4644,7 @@ static inline void game_blind_select_print_blind_req(enum BlindTokens blind)
|
||||
{
|
||||
Rect blind_req_score_rect = game_blind_select_get_req_score_rect(blind);
|
||||
|
||||
u32 blind_req = blind_get_requirement(get_blind_type_from_token(blind), ante);
|
||||
u32 blind_req = blind_get_requirement(get_blind_type_from_token(blind), g_game_vars.ante);
|
||||
|
||||
char blind_req_str_buff[UINT_MAX_DIGITS + 1];
|
||||
truncate_uint_to_suffixed_str(
|
||||
@@ -4736,7 +4742,7 @@ static void game_blind_select_handle_input()
|
||||
play_sfx(SFX_BUTTON, MM_BASE_PITCH_RATE, BUTTON_SFX_VOLUME);
|
||||
state_info[game_state].substate = BLIND_SELECTED_ANIM_SEQ;
|
||||
g_game_vars.timer = TM_ZERO;
|
||||
++round;
|
||||
++g_game_vars.round;
|
||||
display_round();
|
||||
}
|
||||
else if (current_blind <= BLIND_TYPE_BIG)
|
||||
@@ -4853,25 +4859,25 @@ static void game_blind_select_display_blind_panel()
|
||||
}
|
||||
}
|
||||
|
||||
static void game_blind_select_on_exit()
|
||||
static void game_blind_select_on_exit(void)
|
||||
{
|
||||
selection_y = 0;
|
||||
background = UNDEFINED;
|
||||
}
|
||||
|
||||
void game_start()
|
||||
void game_start(void)
|
||||
{
|
||||
set_seed(g_game_vars.rng_seed);
|
||||
// set_seed(9); // 9 is a full house
|
||||
set_seed(g_game_vars.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
|
||||
|
||||
hands = max_hands;
|
||||
discards = max_discards;
|
||||
|
||||
// Activate high contrast palette for cards if loaded settings tell us to
|
||||
toggle_high_contrast_cards(g_game_vars.high_contrast);
|
||||
|
||||
// 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++)
|
||||
@@ -4912,7 +4918,7 @@ void game_start()
|
||||
ANTE_TEXT_RECT.left,
|
||||
ANTE_TEXT_RECT.top,
|
||||
TTE_YELLOW_PB,
|
||||
ante,
|
||||
g_game_vars.ante,
|
||||
TTE_WHITE_PB,
|
||||
MAX_ANTE
|
||||
); // Ante
|
||||
@@ -4934,7 +4940,7 @@ static inline void game_over_process_user_input()
|
||||
}
|
||||
}
|
||||
|
||||
static void game_lose_on_update()
|
||||
static void game_lose_on_update(void)
|
||||
{
|
||||
if (g_game_vars.timer < GAME_OVER_ANIM_FRAMES)
|
||||
{
|
||||
@@ -4956,7 +4962,7 @@ static void game_lose_on_update()
|
||||
// This function isn't set in stone. This is just a placeholder
|
||||
// allowing the player to restart the game. Thought it would be nice to have
|
||||
// util we decide what we want to do after a game over.
|
||||
static void game_over_on_exit()
|
||||
static void game_over_on_exit(void)
|
||||
{
|
||||
while (list_get_len(&_owned_jokers_list) > 0)
|
||||
{
|
||||
@@ -4996,7 +5002,7 @@ static void game_over_on_exit()
|
||||
ANTE_TEXT_RECT.left,
|
||||
ANTE_TEXT_RECT.top,
|
||||
TTE_YELLOW_PB,
|
||||
ante,
|
||||
g_game_vars.ante,
|
||||
TTE_WHITE_PB,
|
||||
MAX_ANTE
|
||||
);
|
||||
@@ -5004,7 +5010,7 @@ static void game_over_on_exit()
|
||||
affine_background_load_palette(affine_background_gfxPal);
|
||||
}
|
||||
|
||||
static void game_win_on_update()
|
||||
static void game_win_on_update(void)
|
||||
{
|
||||
if (g_game_vars.timer < GAME_OVER_ANIM_FRAMES)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "game.h"
|
||||
#include "game/main_menu.h"
|
||||
#include "game/options_menu.h"
|
||||
|
||||
typedef void (*BackgroundRenderCallback)(void);
|
||||
|
||||
@@ -15,18 +16,18 @@ static const BackgroundRenderCallback bgCallbacks[] = {
|
||||
[BG_ROUND_END] = NULL,
|
||||
[BG_SHOP] = NULL,
|
||||
[BG_BLIND_SELECT] = NULL,
|
||||
[BG_OPTIONS_MENU] = game_options_menu_change_background,
|
||||
[BG_MAIN_MENU] = game_main_menu_change_background,
|
||||
};
|
||||
|
||||
void change_background(enum BackgroundId id)
|
||||
{
|
||||
if (id == BG_MAIN_MENU)
|
||||
if (id != background && bgCallbacks[id] != NULL)
|
||||
{
|
||||
if (id != background)
|
||||
{
|
||||
bgCallbacks[id]();
|
||||
}
|
||||
bgCallbacks[id]();
|
||||
}
|
||||
change_background_legacy(id);
|
||||
background = id;
|
||||
|
||||
// Can be removed once all states have their own "change_background" func
|
||||
change_background_legacy(id);
|
||||
}
|
||||
|
||||
+152
-41
@@ -9,29 +9,79 @@
|
||||
#include "game/common_ui.h"
|
||||
#include "game_variables.h"
|
||||
#include "graphic_utils.h"
|
||||
#include "selection_grid.h"
|
||||
#include "soundbank.h"
|
||||
#include "sprite.h"
|
||||
#include "version.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <tonc.h>
|
||||
#include <tonc_math.h>
|
||||
#include <tonc_memdef.h>
|
||||
|
||||
static const u32 PLAY_BUTTON_OUTLINE_PID = 2;
|
||||
static const u32 PLAY_BUTTON_MAIN_COLOR_PID = 5;
|
||||
#define PLAY_BUTTON_MAIN_COLOR_PID 5
|
||||
#define PLAY_BUTTON_OUTLINE_PID 6
|
||||
#define OPTIONS_BUTTON_MAIN_COLOR_PID 7
|
||||
#define OPTIONS_BUTTON_OUTLINE_PID 1
|
||||
|
||||
static const u32 IMPLEMENTED_BUTTONS = 1; // Remove this once all buttons are implemented
|
||||
static const u32 PLAY_BTN_IDX = 0;
|
||||
enum MainButtons
|
||||
{
|
||||
PLAY_BTN_IDX,
|
||||
OPTIONS_BTN_IDX,
|
||||
MAIN_MENU_NB_BTN
|
||||
};
|
||||
|
||||
// Pixel sizes
|
||||
#define MAIN_MENU_ACE_T_X 88
|
||||
#define MAIN_MENU_ACE_T_Y 26
|
||||
|
||||
// clang-format off
|
||||
static const BG_POINT GBALATRO_VERSION_TEXT_POS = {0, 152};
|
||||
static const Rect GBALATRO_VERSION_TEXT_RECT = {0, 152, 240, 160};
|
||||
// clang-format on
|
||||
|
||||
// Define SelectionGrid for the main menu buttons
|
||||
|
||||
static int main_menu_return_row_size(void);
|
||||
static bool main_menu_on_selection_changed(
|
||||
SelectionGrid* selection_grid,
|
||||
int row_idx,
|
||||
const Selection* prev_selection,
|
||||
const Selection* new_selection
|
||||
);
|
||||
static void main_menu_on_key_transit(SelectionGrid* selection_grid, Selection* selection);
|
||||
|
||||
static void play_on_pressed(void);
|
||||
static void options_on_pressed(void);
|
||||
|
||||
// clang-format off
|
||||
static SelectionGridRow main_menu_selection_rows[] = {
|
||||
{
|
||||
0,
|
||||
main_menu_return_row_size,
|
||||
main_menu_on_selection_changed,
|
||||
main_menu_on_key_transit,
|
||||
{.wrap = false}
|
||||
},
|
||||
};
|
||||
|
||||
static Button main_menu_buttons[] = {
|
||||
{PLAY_BUTTON_OUTLINE_PID, PLAY_BUTTON_MAIN_COLOR_PID, play_on_pressed, NULL},
|
||||
{OPTIONS_BUTTON_OUTLINE_PID, OPTIONS_BUTTON_MAIN_COLOR_PID, options_on_pressed, NULL},
|
||||
};
|
||||
|
||||
static SelectionGrid main_menu_selection_grid = {
|
||||
main_menu_selection_rows,
|
||||
1,
|
||||
{0, 0}
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// Main menu sprite - the ace of spades
|
||||
static CardObject* main_menu_ace = NULL;
|
||||
|
||||
// Current selected button index
|
||||
static int selection_x = 0;
|
||||
// Keep track of last highlighted button
|
||||
static enum MainButtons last_highlighted_button = PLAY_BTN_IDX;
|
||||
|
||||
void game_main_menu_change_background(void)
|
||||
{
|
||||
@@ -42,11 +92,14 @@ void game_main_menu_change_background(void)
|
||||
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[PLAY_BUTTON_OUTLINE_PID], &pal_bg_mem[PLAY_BUTTON_MAIN_COLOR_PID], 1);
|
||||
// Disable all button highlight colors
|
||||
for (int i = 0; i < MAIN_MENU_NB_BTN; i++)
|
||||
{
|
||||
button_set_highlight(&main_menu_buttons[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
void game_main_menu_on_init()
|
||||
void game_main_menu_on_init(void)
|
||||
{
|
||||
affine_background_change_background(AFFINE_BG_MAIN_MENU);
|
||||
change_background(BG_MAIN_MENU);
|
||||
@@ -58,10 +111,18 @@ void game_main_menu_on_init()
|
||||
main_menu_ace->sprite_object->ty = int2fx(MAIN_MENU_ACE_T_Y);
|
||||
main_menu_ace->sprite_object->y = main_menu_ace->sprite_object->ty;
|
||||
main_menu_ace->sprite_object->tscale = float2fx(0.8f);
|
||||
selection_x = 0;
|
||||
card_object_update(main_menu_ace);
|
||||
|
||||
// Select last highlighted button, Play button by default.
|
||||
// e.g. if we return from the options menu, we want the Options button to be highlighted.
|
||||
Selection sel_init = {last_highlighted_button, 0};
|
||||
main_menu_selection_grid.selection = sel_init;
|
||||
|
||||
// Highlight current button
|
||||
button_set_highlight(&main_menu_buttons[main_menu_selection_grid.selection.x], true);
|
||||
}
|
||||
|
||||
void game_main_menu_on_update()
|
||||
void game_main_menu_on_update(void)
|
||||
{
|
||||
card_object_update(main_menu_ace);
|
||||
main_menu_ace->sprite_object->trotation = lu_sin((g_game_vars.timer << 8) / 2) / 3;
|
||||
@@ -75,41 +136,91 @@ void game_main_menu_on_update()
|
||||
g_game_vars.rng_seed *= 2;
|
||||
}
|
||||
|
||||
if (key_hit(KEY_LEFT))
|
||||
{
|
||||
if (selection_x > 0)
|
||||
{
|
||||
selection_x--;
|
||||
}
|
||||
}
|
||||
else if (key_hit(KEY_RIGHT))
|
||||
{
|
||||
if (selection_x < IMPLEMENTED_BUTTONS - 1)
|
||||
{
|
||||
selection_x++;
|
||||
}
|
||||
}
|
||||
|
||||
if (selection_x == PLAY_BTN_IDX)
|
||||
{
|
||||
memset16(&pal_bg_mem[PLAY_BUTTON_OUTLINE_PID], BTN_HIGHLIGHT_COLOR, 1);
|
||||
|
||||
if (key_hit(SELECT_CARD))
|
||||
{
|
||||
play_sfx(SFX_BUTTON, MM_BASE_PITCH_RATE, BUTTON_SFX_VOLUME);
|
||||
game_change_state(GAME_STATE_GAME_START);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy16(&pal_bg_mem[PLAY_BUTTON_OUTLINE_PID], &pal_bg_mem[PLAY_BUTTON_MAIN_COLOR_PID], 1);
|
||||
}
|
||||
selection_grid_process_input(&main_menu_selection_grid);
|
||||
}
|
||||
|
||||
void game_main_menu_on_exit()
|
||||
void game_main_menu_on_exit(void)
|
||||
{
|
||||
// Save selected button
|
||||
last_highlighted_button = main_menu_selection_grid.selection.x;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Implement SelectionGrid handler functions
|
||||
|
||||
static int main_menu_return_row_size(void)
|
||||
{
|
||||
return MAIN_MENU_NB_BTN;
|
||||
}
|
||||
|
||||
static bool main_menu_on_selection_changed(
|
||||
SelectionGrid* selection_grid,
|
||||
int row_idx,
|
||||
const Selection* prev_selection,
|
||||
const Selection* new_selection
|
||||
)
|
||||
{
|
||||
if (prev_selection->x >= 0 && prev_selection->x < MAIN_MENU_NB_BTN)
|
||||
{
|
||||
button_set_highlight(&main_menu_buttons[prev_selection->x], false);
|
||||
}
|
||||
|
||||
play_sfx(SFX_BUTTON, MM_BASE_PITCH_RATE, BUTTON_SFX_VOLUME);
|
||||
button_set_highlight(&main_menu_buttons[new_selection->x], true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void main_menu_on_key_transit(SelectionGrid* selection_grid, Selection* selection)
|
||||
{
|
||||
// DEBUG: toggle git hash display by pressing L+R+START+SELECT
|
||||
static bool combo_pressed = false;
|
||||
static bool show_version = false;
|
||||
if (key_is_down(KEY_START) && key_is_down(KEY_SELECT) && key_is_down(KEY_L) &&
|
||||
key_is_down(KEY_R))
|
||||
{
|
||||
if (!combo_pressed)
|
||||
{
|
||||
show_version = !show_version;
|
||||
if (show_version)
|
||||
{
|
||||
tte_printf(
|
||||
"#{P:%d,%d; cx:0x%X000}%s",
|
||||
GBALATRO_VERSION_TEXT_POS.x,
|
||||
GBALATRO_VERSION_TEXT_POS.y,
|
||||
TTE_WHITE_PB,
|
||||
gbalatro_version
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
tte_erase_rect_wrapper(GBALATRO_VERSION_TEXT_RECT);
|
||||
}
|
||||
}
|
||||
|
||||
combo_pressed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
combo_pressed = false;
|
||||
}
|
||||
|
||||
if (key_hit(SELECT_CARD))
|
||||
{
|
||||
button_press(&main_menu_buttons[selection->x]);
|
||||
}
|
||||
}
|
||||
|
||||
static void play_on_pressed(void)
|
||||
{
|
||||
game_change_state(GAME_STATE_GAME_START);
|
||||
}
|
||||
|
||||
static void options_on_pressed(void)
|
||||
{
|
||||
game_change_state(GAME_STATE_OPTIONS_MENU);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,624 @@
|
||||
#include "game/options_menu.h"
|
||||
|
||||
#include "affine_background.h"
|
||||
#include "audio_utils.h"
|
||||
#include "background_options_menu_gfx.h"
|
||||
#include "button.h"
|
||||
#include "game.h"
|
||||
#include "game/common_ui.h"
|
||||
#include "game_variables.h"
|
||||
#include "graphic_utils.h"
|
||||
#include "save.h"
|
||||
#include "selection_grid.h"
|
||||
#include "soundbank.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <tonc.h>
|
||||
#include <tonc_math.h>
|
||||
#include <tonc_memdef.h>
|
||||
|
||||
// Button indices
|
||||
enum OptionButtons
|
||||
{
|
||||
GAME_SPEED_BTN_IDX,
|
||||
HIGH_CONTRAST_BTN_IDX,
|
||||
MUSIC_VOLUME_BTN_IDX,
|
||||
SOUND_VOLUME_BTN_IDX,
|
||||
BACK_BTN_IDX,
|
||||
NB_OPTIONS_BUTTONS
|
||||
};
|
||||
enum OptionSpeedButtons
|
||||
{
|
||||
GAME_SPEED_DOWN_BTN_IDX,
|
||||
GAME_SPEED_UP_BTN_IDX,
|
||||
NB_GAME_SPEED_BUTTONS
|
||||
};
|
||||
|
||||
// Color palette indices
|
||||
#define MENU_BUTTON_MAIN_COLOR_PAL_IDX 1
|
||||
#define BACK_BUTTON_MAIN_COLOR_PAL_IDX 3
|
||||
#define SPEED_DOWN_BUTTON_OUTLINE_COLOR_PAL_IDX 4
|
||||
#define SPEED_BUTTON_OUTLINE_COLOR_PAL_IDX 5
|
||||
#define SPEED_UP_BUTTON_OUTLINE_COLOR_PAL_IDX 6
|
||||
#define CONTRAST_BUTTON_OUTLINE_COLOR_PAL_IDX 7
|
||||
#define MUSIC_BUTTON_OUTLINE_COLOR_PAL_IDX 8
|
||||
#define SOUND_BUTTON_OUTLINE_COLOR_PAL_IDX 9
|
||||
#define BACK_BUTTON_OUTLINE_COLOR_PAL_IDX 10
|
||||
|
||||
// Define selection grid for the menu buttons
|
||||
|
||||
static void game_speed_down_on_pressed(void);
|
||||
static void game_speed_up_on_pressed(void);
|
||||
static void high_contrast_on_pressed(void);
|
||||
static void back_on_pressed(void);
|
||||
static int options_menu_return_row_size(void);
|
||||
static void options_menu_row_on_key_transit(SelectionGrid* selection_grid, Selection* selection);
|
||||
static bool game_speed_row_on_selection_changed(
|
||||
SelectionGrid* selection_grid,
|
||||
int row_idx,
|
||||
const Selection* prev_selection,
|
||||
const Selection* new_selection
|
||||
);
|
||||
static bool music_volume_row_on_selection_changed(
|
||||
SelectionGrid* selection_grid,
|
||||
int row_idx,
|
||||
const Selection* prev_selection,
|
||||
const Selection* new_selection
|
||||
);
|
||||
static bool sound_volume_row_on_selection_changed(
|
||||
SelectionGrid* selection_grid,
|
||||
int row_idx,
|
||||
const Selection* prev_selection,
|
||||
const Selection* new_selection
|
||||
);
|
||||
static bool regular_button_row_on_selection_changed(
|
||||
SelectionGrid* selection_grid,
|
||||
int row_idx,
|
||||
const Selection* prev_selection,
|
||||
const Selection* new_selection
|
||||
);
|
||||
|
||||
// clang-format off
|
||||
static SelectionGridRow options_menu_selection_rows[] = {
|
||||
{
|
||||
GAME_SPEED_BTN_IDX,
|
||||
options_menu_return_row_size,
|
||||
game_speed_row_on_selection_changed,
|
||||
NULL,
|
||||
{.wrap = true} // The wrapping is used to allow button activation with Left/Right arrows by
|
||||
// putting logic into the `game_speed_row_on_selection_changed` function
|
||||
},
|
||||
{
|
||||
HIGH_CONTRAST_BTN_IDX,
|
||||
options_menu_return_row_size,
|
||||
regular_button_row_on_selection_changed,
|
||||
options_menu_row_on_key_transit,
|
||||
{.wrap = false}
|
||||
},
|
||||
{
|
||||
MUSIC_VOLUME_BTN_IDX,
|
||||
options_menu_return_row_size,
|
||||
music_volume_row_on_selection_changed,
|
||||
NULL,
|
||||
{.wrap = true} // Same trick as Game Speed button
|
||||
},
|
||||
{
|
||||
SOUND_VOLUME_BTN_IDX,
|
||||
options_menu_return_row_size,
|
||||
sound_volume_row_on_selection_changed,
|
||||
NULL,
|
||||
{.wrap = true} // Same trick as Game Speed button
|
||||
},
|
||||
{
|
||||
BACK_BTN_IDX,
|
||||
options_menu_return_row_size,
|
||||
regular_button_row_on_selection_changed,
|
||||
options_menu_row_on_key_transit,
|
||||
{.wrap = false}
|
||||
}
|
||||
};
|
||||
|
||||
static Button options_menu_buttons[] = {
|
||||
{SPEED_BUTTON_OUTLINE_COLOR_PAL_IDX, MENU_BUTTON_MAIN_COLOR_PAL_IDX, NULL, NULL},
|
||||
{CONTRAST_BUTTON_OUTLINE_COLOR_PAL_IDX, MENU_BUTTON_MAIN_COLOR_PAL_IDX, high_contrast_on_pressed, NULL},
|
||||
{MUSIC_BUTTON_OUTLINE_COLOR_PAL_IDX, MENU_BUTTON_MAIN_COLOR_PAL_IDX, NULL, NULL},
|
||||
{SOUND_BUTTON_OUTLINE_COLOR_PAL_IDX, MENU_BUTTON_MAIN_COLOR_PAL_IDX, NULL, NULL},
|
||||
{BACK_BUTTON_OUTLINE_COLOR_PAL_IDX, BACK_BUTTON_MAIN_COLOR_PAL_IDX, back_on_pressed, NULL}
|
||||
};
|
||||
|
||||
static Button game_speed_buttons[] = {
|
||||
{SPEED_DOWN_BUTTON_OUTLINE_COLOR_PAL_IDX, MENU_BUTTON_MAIN_COLOR_PAL_IDX, game_speed_down_on_pressed, NULL},
|
||||
{SPEED_UP_BUTTON_OUTLINE_COLOR_PAL_IDX, MENU_BUTTON_MAIN_COLOR_PAL_IDX, game_speed_up_on_pressed, NULL},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
const Selection OPTIONS_MENU_INIT_SEL = {0, 0};
|
||||
|
||||
static SelectionGrid options_menu_selection_grid = {
|
||||
options_menu_selection_rows,
|
||||
NB_OPTIONS_BUTTONS,
|
||||
OPTIONS_MENU_INIT_SEL
|
||||
};
|
||||
|
||||
// Positions/Rects used to construct and update the menu
|
||||
// clang-format off
|
||||
|
||||
// Values in tiles
|
||||
static const Rect OPTIONS_SPEED_DOWN_ACTIVE_BTN_SRC_RECT = { 3, 20, 3, 21};
|
||||
static const Rect OPTIONS_SPEED_DOWN_DISABLED_BTN_SRC_RECT = { 9, 22, 9, 23};
|
||||
static const BG_POINT OPTIONS_SPEED_DOWN_BTN_DEST_POS = {11, 3};
|
||||
static const Rect OPTIONS_SPEED_UP_ACTIVE_BTN_SRC_RECT = {10, 20, 10, 21};
|
||||
static const Rect OPTIONS_SPEED_UP_DISABLED_BTN_SRC_RECT = {10, 22, 10, 23};
|
||||
static const BG_POINT OPTIONS_SPEED_UP_BTN_DEST_POS = {18, 3};
|
||||
static const Rect OPTIONS_SPEED_VALUES[GAME_SPEED_MAX] = { { 6, 20, 7, 21},
|
||||
{ 3, 22, 4, 23},
|
||||
{ 5, 22, 6, 23},
|
||||
{ 7, 22, 8, 23} };
|
||||
static const BG_POINT OPTIONS_SPEED_VALUE_DEST_POS = {14, 3};
|
||||
|
||||
static const Rect OPTIONS_CONTRAST_VALUE_YES_SRC_RECT = { 4, 24, 7, 25};
|
||||
static const Rect OPTIONS_CONTRAST_VALUE_NO_SRC_RECT = { 9, 24, 12, 25};
|
||||
static const BG_POINT OPTIONS_CONTRAST_VALUE_DEST_POS = {13, 7};
|
||||
|
||||
static const Rect OPTIONS_MUSIC_SLIDER_FULL_SRC = {12, 20, 12, 20};
|
||||
static const Rect OPTIONS_MUSIC_SLIDER_MID_SRC = {13, 20, 13, 20};
|
||||
static const Rect OPTIONS_MUSIC_SLIDER_EMPTY_SRC = {14, 20, 14, 20};
|
||||
static const Rect OPTIONS_SOUND_SLIDER_FULL_SRC = {12, 22, 12, 22};
|
||||
static const Rect OPTIONS_SOUND_SLIDER_MID_SRC = {13, 22, 13, 22};
|
||||
static const Rect OPTIONS_SOUND_SLIDER_EMPTY_SRC = {14, 22, 14, 22};
|
||||
static const BG_POINT OPTIONS_MUSIC_SLIDER_START_POS = { 5, 11};
|
||||
static const BG_POINT OPTIONS_SOUND_SLIDER_START_POS = { 5, 14};
|
||||
static const BG_POINT OPTIONS_MUSIC_SLIDER_END_POS = {24, 11};
|
||||
static const BG_POINT OPTIONS_SOUND_SLIDER_END_POS = {24, 14};
|
||||
static const u8 OPTIONS_MUSIC_SLIDER_SEGMENT_LENGTH = (OPTIONS_MUSIC_SLIDER_END_POS.x - OPTIONS_MUSIC_SLIDER_START_POS.x + 1)
|
||||
/ VOLUME_OPTION_MAX;
|
||||
static const u8 OPTIONS_SOUND_SLIDER_SEGMENT_LENGTH = (OPTIONS_SOUND_SLIDER_END_POS.x - OPTIONS_SOUND_SLIDER_START_POS.x + 1)
|
||||
/ VOLUME_OPTION_MAX;
|
||||
|
||||
// Values in pixels
|
||||
static const BG_POINT OPTIONS_GAME_SPEED_TEXT_POS = { 82, 16};
|
||||
static const BG_POINT OPTIONS_HIGH_CONTRAST_TEXT_POS = { 40, 48};
|
||||
static const BG_POINT OPTIONS_MUSIC_VOLUME_TEXT_POS = { 56, 80};
|
||||
static const BG_POINT OPTIONS_MUSIC_VALUE_TEXT_POS = {160, 80};
|
||||
static const BG_POINT OPTIONS_SOUND_VOLUME_TEXT_POS = { 56, 104};
|
||||
static const BG_POINT OPTIONS_SOUND_VALUE_TEXT_POS = {160, 104};
|
||||
static const BG_POINT OPTIONS_BACK_SAVE_TEXT_POS = { 72, 136};
|
||||
// clang-format on
|
||||
|
||||
#define GAME_SPEED_ARROW_HIGHLIGHT_DURATION 10
|
||||
enum OptionSpeedButtons game_speed_arrow_highlight_button = GAME_SPEED_UP_BTN_IDX;
|
||||
static s32 game_speed_arrow_highlight_start = UNDEFINED;
|
||||
|
||||
static bool any_value_changed = false;
|
||||
static bool back_btn_is_save_state = false;
|
||||
|
||||
static void disable_all_game_speed_outlines_except_self(enum OptionSpeedButtons highlighted_btn)
|
||||
{
|
||||
for (int i = 0; i < NB_GAME_SPEED_BUTTONS; i++)
|
||||
{
|
||||
button_set_highlight(&game_speed_buttons[i], i == highlighted_btn);
|
||||
}
|
||||
}
|
||||
|
||||
static void disable_all_outlines_except_self(enum OptionButtons highlighted_btn)
|
||||
{
|
||||
// These two get disabled no matter what here
|
||||
disable_all_game_speed_outlines_except_self(NB_GAME_SPEED_BUTTONS);
|
||||
|
||||
for (int i = 0; i < NB_OPTIONS_BUTTONS; i++)
|
||||
{
|
||||
button_set_highlight(&options_menu_buttons[i], i == highlighted_btn);
|
||||
}
|
||||
}
|
||||
|
||||
static void update_game_speed_button_graphics()
|
||||
{
|
||||
// check if need to disable game speed arrows
|
||||
if (g_game_vars.game_speed == GAME_SPEED_MIN)
|
||||
{
|
||||
main_bg_se_copy_rect(
|
||||
OPTIONS_SPEED_DOWN_DISABLED_BTN_SRC_RECT,
|
||||
OPTIONS_SPEED_DOWN_BTN_DEST_POS
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
main_bg_se_copy_rect(
|
||||
OPTIONS_SPEED_DOWN_ACTIVE_BTN_SRC_RECT,
|
||||
OPTIONS_SPEED_DOWN_BTN_DEST_POS
|
||||
);
|
||||
}
|
||||
|
||||
if (g_game_vars.game_speed == GAME_SPEED_MAX)
|
||||
{
|
||||
main_bg_se_copy_rect(OPTIONS_SPEED_UP_DISABLED_BTN_SRC_RECT, OPTIONS_SPEED_UP_BTN_DEST_POS);
|
||||
}
|
||||
else
|
||||
{
|
||||
main_bg_se_copy_rect(OPTIONS_SPEED_UP_ACTIVE_BTN_SRC_RECT, OPTIONS_SPEED_UP_BTN_DEST_POS);
|
||||
}
|
||||
|
||||
main_bg_se_copy_rect(
|
||||
OPTIONS_SPEED_VALUES[g_game_vars.game_speed - 1],
|
||||
OPTIONS_SPEED_VALUE_DEST_POS
|
||||
);
|
||||
|
||||
any_value_changed = true;
|
||||
}
|
||||
|
||||
static void update_high_contrast_button_graphics()
|
||||
{
|
||||
if (g_game_vars.high_contrast)
|
||||
{
|
||||
main_bg_se_copy_rect(OPTIONS_CONTRAST_VALUE_YES_SRC_RECT, OPTIONS_CONTRAST_VALUE_DEST_POS);
|
||||
}
|
||||
else
|
||||
{
|
||||
main_bg_se_copy_rect(OPTIONS_CONTRAST_VALUE_NO_SRC_RECT, OPTIONS_CONTRAST_VALUE_DEST_POS);
|
||||
}
|
||||
|
||||
any_value_changed = true;
|
||||
}
|
||||
|
||||
static void update_volume_slider_graphics(enum OptionButtons slider_idx)
|
||||
{
|
||||
u8 slider_value;
|
||||
BG_POINT slider_segment_dest;
|
||||
int slider_segment_length;
|
||||
Rect slider_segment_full;
|
||||
Rect slider_segment_mid;
|
||||
Rect slider_segment_empty;
|
||||
BG_POINT slider_text_pos;
|
||||
|
||||
switch (slider_idx)
|
||||
{
|
||||
case MUSIC_VOLUME_BTN_IDX:
|
||||
slider_value = g_game_vars.music_volume;
|
||||
slider_segment_dest = OPTIONS_MUSIC_SLIDER_START_POS;
|
||||
slider_segment_length = OPTIONS_MUSIC_SLIDER_SEGMENT_LENGTH;
|
||||
slider_segment_full = OPTIONS_MUSIC_SLIDER_FULL_SRC;
|
||||
slider_segment_mid = OPTIONS_MUSIC_SLIDER_MID_SRC;
|
||||
slider_segment_empty = OPTIONS_MUSIC_SLIDER_EMPTY_SRC;
|
||||
slider_text_pos = OPTIONS_MUSIC_VALUE_TEXT_POS;
|
||||
break;
|
||||
case SOUND_VOLUME_BTN_IDX:
|
||||
slider_value = g_game_vars.sound_volume;
|
||||
slider_segment_dest = OPTIONS_SOUND_SLIDER_START_POS;
|
||||
slider_segment_length = OPTIONS_SOUND_SLIDER_SEGMENT_LENGTH;
|
||||
slider_segment_full = OPTIONS_SOUND_SLIDER_FULL_SRC;
|
||||
slider_segment_mid = OPTIONS_SOUND_SLIDER_MID_SRC;
|
||||
slider_segment_empty = OPTIONS_SOUND_SLIDER_EMPTY_SRC;
|
||||
slider_text_pos = OPTIONS_SOUND_VALUE_TEXT_POS;
|
||||
break;
|
||||
// This function only takes in a volume slider
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
|
||||
// full part of the bar
|
||||
for (; i < slider_value * slider_segment_length - 1; i++)
|
||||
{
|
||||
main_bg_se_copy_rect(slider_segment_full, slider_segment_dest);
|
||||
slider_segment_dest.x++;
|
||||
}
|
||||
|
||||
// at exactly `slider_value` we either:
|
||||
// - are in the middle of the bar, then we draw the frontier between full and empty tiles
|
||||
// - are at the end, then draw a full segment
|
||||
if (slider_value == VOLUME_OPTION_MAX)
|
||||
{
|
||||
main_bg_se_copy_rect(slider_segment_full, slider_segment_dest);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (slider_value != VOLUME_OPTION_MIN)
|
||||
{
|
||||
// draw middle point
|
||||
main_bg_se_copy_rect(slider_segment_mid, slider_segment_dest);
|
||||
i++;
|
||||
slider_segment_dest.x++;
|
||||
}
|
||||
|
||||
// empty part of the bar
|
||||
for (; i < VOLUME_OPTION_MAX * slider_segment_length; i++)
|
||||
{
|
||||
main_bg_se_copy_rect(slider_segment_empty, slider_segment_dest);
|
||||
slider_segment_dest.x++;
|
||||
}
|
||||
}
|
||||
|
||||
tte_printf(
|
||||
"#{P:%d,%d; cx:0x%X000}%3d",
|
||||
slider_text_pos.x,
|
||||
slider_text_pos.y,
|
||||
TTE_WHITE_PB,
|
||||
(slider_value * VOLUME_OPTION_INCREMENT)
|
||||
);
|
||||
|
||||
any_value_changed = true;
|
||||
}
|
||||
|
||||
static void change_back_save_text(bool is_back)
|
||||
{
|
||||
char* btn_text = is_back ? " Back " : "Save Changes";
|
||||
tte_printf(
|
||||
"#{P:%d,%d; cx:0x%X000}%s",
|
||||
OPTIONS_BACK_SAVE_TEXT_POS.x,
|
||||
OPTIONS_BACK_SAVE_TEXT_POS.y,
|
||||
TTE_WHITE_PB,
|
||||
btn_text
|
||||
);
|
||||
}
|
||||
|
||||
void game_options_menu_change_background(void)
|
||||
{
|
||||
tte_erase_screen();
|
||||
CBB_CLEAR(MAIN_BG_CBB);
|
||||
|
||||
GRIT_CPY(pal_bg_mem, background_options_menu_gfxPal);
|
||||
GRIT_CPY(&tile_mem[MAIN_BG_CBB], background_options_menu_gfxTiles);
|
||||
GRIT_CPY(&se_mem[MAIN_BG_SBB], background_options_menu_gfxMap);
|
||||
|
||||
tte_printf(
|
||||
"#{P:%d,%d; cx:0x%X000}Game Speed",
|
||||
OPTIONS_GAME_SPEED_TEXT_POS.x,
|
||||
OPTIONS_GAME_SPEED_TEXT_POS.y,
|
||||
TTE_WHITE_PB
|
||||
);
|
||||
tte_printf(
|
||||
"#{P:%d,%d; cx:0x%X000}High Contrast Cards",
|
||||
OPTIONS_HIGH_CONTRAST_TEXT_POS.x,
|
||||
OPTIONS_HIGH_CONTRAST_TEXT_POS.y,
|
||||
TTE_WHITE_PB
|
||||
);
|
||||
tte_printf(
|
||||
"#{P:%d,%d; cx:0x%X000}Music Volume",
|
||||
OPTIONS_MUSIC_VOLUME_TEXT_POS.x,
|
||||
OPTIONS_MUSIC_VOLUME_TEXT_POS.y,
|
||||
TTE_WHITE_PB
|
||||
);
|
||||
tte_printf(
|
||||
"#{P:%d,%d; cx:0x%X000}Sound Volume",
|
||||
OPTIONS_SOUND_VOLUME_TEXT_POS.x,
|
||||
OPTIONS_SOUND_VOLUME_TEXT_POS.y,
|
||||
TTE_WHITE_PB
|
||||
);
|
||||
change_back_save_text(true);
|
||||
}
|
||||
|
||||
void game_options_menu_on_init(void)
|
||||
{
|
||||
change_background(BG_OPTIONS_MENU);
|
||||
|
||||
// Select game speed button by default
|
||||
options_menu_selection_grid.selection = OPTIONS_MENU_INIT_SEL;
|
||||
disable_all_outlines_except_self(GAME_SPEED_BTN_IDX);
|
||||
|
||||
// Do an update on the first frame
|
||||
update_game_speed_button_graphics();
|
||||
update_high_contrast_button_graphics();
|
||||
update_volume_slider_graphics(MUSIC_VOLUME_BTN_IDX);
|
||||
update_volume_slider_graphics(SOUND_VOLUME_BTN_IDX);
|
||||
|
||||
// Prevent "Back" button to become "Save Changes" when we first load the data,
|
||||
// the player hasn't technically changed anything so it would be confusing.
|
||||
any_value_changed = false;
|
||||
}
|
||||
|
||||
void game_options_menu_on_update(void)
|
||||
{
|
||||
selection_grid_process_input(&options_menu_selection_grid);
|
||||
|
||||
// game speed arrows small animation: they stay highlighted for a few frames
|
||||
if (game_speed_arrow_highlight_start != UNDEFINED &&
|
||||
(g_game_vars.timer - game_speed_arrow_highlight_start) >
|
||||
GAME_SPEED_ARROW_HIGHLIGHT_DURATION)
|
||||
{
|
||||
game_speed_arrow_highlight_start = UNDEFINED;
|
||||
button_set_highlight(&game_speed_buttons[game_speed_arrow_highlight_button], false);
|
||||
}
|
||||
|
||||
// Except on the first update where it is forced to display the loaded values
|
||||
// instead of the default ones, if any value was changed, edit the last button
|
||||
// text to read "Save Changes" instead of "Back"
|
||||
if (any_value_changed && !back_btn_is_save_state)
|
||||
{
|
||||
change_back_save_text(false);
|
||||
back_btn_is_save_state = true;
|
||||
}
|
||||
|
||||
any_value_changed = false;
|
||||
}
|
||||
|
||||
void game_options_menu_on_exit(void)
|
||||
{
|
||||
tte_erase_screen();
|
||||
}
|
||||
|
||||
static void game_speed_down_on_pressed(void)
|
||||
{
|
||||
g_game_vars.game_speed--;
|
||||
game_speed_arrow_highlight_start = g_game_vars.timer;
|
||||
game_speed_arrow_highlight_button = GAME_SPEED_DOWN_BTN_IDX;
|
||||
disable_all_game_speed_outlines_except_self(GAME_SPEED_DOWN_BTN_IDX);
|
||||
update_game_speed_button_graphics();
|
||||
}
|
||||
|
||||
static void game_speed_up_on_pressed(void)
|
||||
{
|
||||
g_game_vars.game_speed++;
|
||||
game_speed_arrow_highlight_start = g_game_vars.timer;
|
||||
game_speed_arrow_highlight_button = GAME_SPEED_UP_BTN_IDX;
|
||||
disable_all_game_speed_outlines_except_self(GAME_SPEED_UP_BTN_IDX);
|
||||
update_game_speed_button_graphics();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles input for the high contrast card toggle button and nothing more.
|
||||
*/
|
||||
static void high_contrast_on_pressed(void)
|
||||
{
|
||||
g_game_vars.high_contrast = (g_game_vars.high_contrast == 1) ? false : true;
|
||||
update_high_contrast_button_graphics();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles input for the Back/Save button.
|
||||
*/
|
||||
static void back_on_pressed(void)
|
||||
{
|
||||
if (back_btn_is_save_state)
|
||||
{
|
||||
save_game();
|
||||
change_back_save_text(true);
|
||||
back_btn_is_save_state = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
game_change_state(GAME_STATE_MAIN_MENU);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gives the width of options menu rows in selection grid.
|
||||
*
|
||||
* @returns Always 1, all rows contain only 1 button. Could have been a define
|
||||
* but I had to make this a function so it could be given to the
|
||||
* SelectionGrid constructor.
|
||||
*/
|
||||
static int options_menu_return_row_size(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void change_button_highlight(
|
||||
int row_idx,
|
||||
const Selection* prev_selection,
|
||||
const Selection* new_selection
|
||||
)
|
||||
{
|
||||
if (prev_selection->y == row_idx && prev_selection->x >= 0 &&
|
||||
prev_selection->x < NB_OPTIONS_BUTTONS)
|
||||
{
|
||||
button_set_highlight(&options_menu_buttons[row_idx], false);
|
||||
}
|
||||
|
||||
if (new_selection->y == row_idx)
|
||||
{
|
||||
play_sfx(SFX_BUTTON, MM_BASE_PITCH_RATE, BUTTON_SFX_VOLUME);
|
||||
button_set_highlight(&options_menu_buttons[row_idx], true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles left/Right inputs on the row of the Game Speed button.
|
||||
* Is used to increased/decrease the speed value, instead of relying on a
|
||||
* `on_key_transit` function because the latter does not take directional
|
||||
* inputs.
|
||||
*/
|
||||
static bool game_speed_row_on_selection_changed(
|
||||
SelectionGrid* selection_grid,
|
||||
int row_idx,
|
||||
const Selection* prev_selection,
|
||||
const Selection* new_selection
|
||||
)
|
||||
{
|
||||
change_button_highlight(row_idx, prev_selection, new_selection);
|
||||
disable_all_game_speed_outlines_except_self(NB_GAME_SPEED_BUTTONS);
|
||||
|
||||
// Can only change game speed by pressing left/right while staying on the button's row
|
||||
if (prev_selection->y != new_selection->y)
|
||||
return true;
|
||||
|
||||
if (key_hit(KEY_LEFT) && g_game_vars.game_speed > GAME_SPEED_MIN)
|
||||
{
|
||||
button_press(&game_speed_buttons[GAME_SPEED_DOWN_BTN_IDX]);
|
||||
}
|
||||
else if (key_hit(KEY_RIGHT) && g_game_vars.game_speed < GAME_SPEED_MAX)
|
||||
{
|
||||
button_press(&game_speed_buttons[GAME_SPEED_UP_BTN_IDX]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles input for all SelectionGridRows in options menu, only actually does
|
||||
* something on rows containing "normal" buttons (those for which the on_pressed
|
||||
* method in options_menu_buttons is not NULL).
|
||||
*/
|
||||
static void options_menu_row_on_key_transit(SelectionGrid* selection_grid, Selection* selection)
|
||||
{
|
||||
if (key_hit(SELECT_CARD))
|
||||
{
|
||||
button_press(&options_menu_buttons[selection->y]);
|
||||
}
|
||||
}
|
||||
|
||||
static bool regular_button_row_on_selection_changed(
|
||||
SelectionGrid* selection_grid,
|
||||
int row_idx,
|
||||
const Selection* prev_selection,
|
||||
const Selection* new_selection
|
||||
)
|
||||
{
|
||||
change_button_highlight(row_idx, prev_selection, new_selection);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool music_volume_row_on_selection_changed(
|
||||
SelectionGrid* selection_grid,
|
||||
int row_idx,
|
||||
const Selection* prev_selection,
|
||||
const Selection* new_selection
|
||||
)
|
||||
{
|
||||
change_button_highlight(row_idx, prev_selection, new_selection);
|
||||
|
||||
if (prev_selection->y != new_selection->y)
|
||||
return true;
|
||||
|
||||
if (key_hit(KEY_LEFT) && g_game_vars.music_volume > VOLUME_OPTION_MIN)
|
||||
{
|
||||
g_game_vars.music_volume--;
|
||||
}
|
||||
else if (key_hit(KEY_RIGHT) && g_game_vars.music_volume < VOLUME_OPTION_MAX)
|
||||
{
|
||||
g_game_vars.music_volume++;
|
||||
}
|
||||
|
||||
update_volume_slider_graphics(MUSIC_VOLUME_BTN_IDX);
|
||||
mmSetModuleVolume(MM_MODULE_FULL_VOLUME * g_game_vars.music_volume / VOLUME_OPTION_MAX);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sound_volume_row_on_selection_changed(
|
||||
SelectionGrid* selection_grid,
|
||||
int row_idx,
|
||||
const Selection* prev_selection,
|
||||
const Selection* new_selection
|
||||
)
|
||||
{
|
||||
change_button_highlight(row_idx, prev_selection, new_selection);
|
||||
|
||||
if (prev_selection->y != new_selection->y)
|
||||
return true;
|
||||
|
||||
if (key_hit(KEY_LEFT) && g_game_vars.sound_volume > VOLUME_OPTION_MIN)
|
||||
{
|
||||
g_game_vars.sound_volume--;
|
||||
}
|
||||
else if (key_hit(KEY_RIGHT) && g_game_vars.sound_volume < VOLUME_OPTION_MAX)
|
||||
{
|
||||
g_game_vars.sound_volume++;
|
||||
}
|
||||
|
||||
update_volume_slider_graphics(SOUND_VOLUME_BTN_IDX);
|
||||
|
||||
return true;
|
||||
}
|
||||
+4
-3
@@ -1,6 +1,7 @@
|
||||
#include "joker.h"
|
||||
|
||||
#include "card.h"
|
||||
#include "game_variables.h"
|
||||
#include "graphic_utils.h"
|
||||
#include "joker_gfx.h"
|
||||
#include "pool.h"
|
||||
@@ -271,7 +272,7 @@ bool joker_object_score(
|
||||
|
||||
u32 chips = get_chips();
|
||||
u32 mult = get_mult();
|
||||
int money = get_money();
|
||||
int money = g_game_vars.money;
|
||||
|
||||
if (effect_flags_ret & JOKER_EFFECT_FLAG_RETRIGGER)
|
||||
{
|
||||
@@ -345,7 +346,7 @@ bool joker_object_score(
|
||||
// Update values
|
||||
set_chips(chips);
|
||||
set_mult(mult);
|
||||
set_money(money);
|
||||
g_game_vars.money = money;
|
||||
|
||||
// Update displays
|
||||
display_chips();
|
||||
@@ -367,7 +368,7 @@ Sprite* joker_object_get_sprite(JokerObject* joker_object)
|
||||
int joker_get_random_rarity()
|
||||
{
|
||||
int joker_rarity = 0;
|
||||
int rarity_roll = random() % 100;
|
||||
int rarity_roll = get_rand() % 100;
|
||||
if (rarity_roll < COMMON_JOKER_CHANCE)
|
||||
{
|
||||
joker_rarity = COMMON_JOKER;
|
||||
|
||||
@@ -562,7 +562,7 @@ static u32 misprint_joker_effect(
|
||||
|
||||
*joker_effect = &shared_joker_effect;
|
||||
|
||||
(*joker_effect)->mult = random() % (MISPRINT_MAX_MULT + 1);
|
||||
(*joker_effect)->mult = get_rand() % (MISPRINT_MAX_MULT + 1);
|
||||
|
||||
return JOKER_EFFECT_FLAG_MULT;
|
||||
}
|
||||
@@ -775,7 +775,7 @@ static u32 reserved_parking_joker_effect(
|
||||
|
||||
u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE;
|
||||
|
||||
if ((random() % 2 == 0) && card_is_face(scored_card))
|
||||
if ((get_rand() % 2 == 0) && card_is_face(scored_card))
|
||||
{
|
||||
*joker_effect = &shared_joker_effect;
|
||||
|
||||
@@ -797,7 +797,7 @@ static u32 business_card_joker_effect(
|
||||
|
||||
u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE;
|
||||
|
||||
if ((random() % 2 == 0) && card_is_face(scored_card))
|
||||
if ((get_rand() % 2 == 0) && card_is_face(scored_card))
|
||||
{
|
||||
*joker_effect = &shared_joker_effect;
|
||||
|
||||
@@ -885,11 +885,11 @@ static u32 bull_joker_effect(
|
||||
|
||||
// The wiki says it does nothing if money is 0 or below
|
||||
// This allows us to avoid scoring negative Chips
|
||||
if (get_money() > 0)
|
||||
if (g_game_vars.money > 0)
|
||||
{
|
||||
*joker_effect = &shared_joker_effect;
|
||||
|
||||
(*joker_effect)->chips = get_money() * 2;
|
||||
(*joker_effect)->chips = g_game_vars.money * 2;
|
||||
effect_flags_ret = JOKER_EFFECT_FLAG_CHIPS;
|
||||
}
|
||||
|
||||
@@ -1154,11 +1154,11 @@ static u32 bootstraps_joker_effect(
|
||||
u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE;
|
||||
|
||||
// Same protection as the Bull Joker
|
||||
if (get_money() > 0)
|
||||
if (g_game_vars.money > 0)
|
||||
{
|
||||
*joker_effect = &shared_joker_effect;
|
||||
|
||||
(*joker_effect)->mult = (get_money() / 5) * 2;
|
||||
(*joker_effect)->mult = (g_game_vars.money / 5) * 2;
|
||||
effect_flags_ret = JOKER_EFFECT_FLAG_MULT;
|
||||
}
|
||||
|
||||
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* @file save.c
|
||||
*/
|
||||
#include "save.h"
|
||||
|
||||
#include "audio_utils.h"
|
||||
#include "game.h"
|
||||
#include "joker.h"
|
||||
#include "list.h"
|
||||
#include "util.h"
|
||||
#include "version.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// See https://gbadev.net/gbadoc/memory.html for more details on SRAM
|
||||
// A few important pieces of info:
|
||||
// - Read/Writes are limited to 8-bits words so they are done byte per byte
|
||||
// - Memory is filled with 1s by default
|
||||
// (at least in mgba, not sure about real HW)
|
||||
|
||||
#define CHECK_BASE 0x0000
|
||||
#define GAME_BASE 0x0010
|
||||
#define LISTS_BASE 0x0060
|
||||
|
||||
#define CHECK_MAGIC 0x4C414247 // Spells GBAL, used to determine if the save data is junk
|
||||
#define CHECK_HASH_SIZE 7
|
||||
#define GIT_HASH_START 17 // starts after "GBALATRO-VERSION:" in the gbalatro_version var
|
||||
|
||||
// clang-format off
|
||||
/**
|
||||
* @brief SaveHeader for validation checks
|
||||
*
|
||||
* Structure holding save data header info to be packed and written to SRAM for validation.
|
||||
* Defined in this discussion as follows: https://github.com/GBALATRO/balatro-gba/discussions/450
|
||||
* word | Byte 0 | Byte 1 | Byte 2 | Byte 3 | name | purpose
|
||||
* -----|--------|--------|--------|--------|--------------|------------------------------------------------------------------
|
||||
* 0 | 0x47 | 0x42 | 0x41 | 0x4C | MAGIC | Identify if proceeding data is valid and not junk, spells "GBAL"
|
||||
* 1 | Dirty | H[0] | H[1] | H[2] | GITHASH_LOW | Dirty flag, followed by the first 3 bytes of shortened git hash H
|
||||
* 2 | H[3] | H[4] | H[5] | H[6] | GITHASH_HIGH | Last 4 bytes of shortened git hash H, with a dirty flag
|
||||
*/
|
||||
// clang-format on
|
||||
typedef struct SaveHeader
|
||||
{
|
||||
u32 magic;
|
||||
bool dirty;
|
||||
char githash[CHECK_HASH_SIZE];
|
||||
} SaveHeader;
|
||||
|
||||
/**
|
||||
* @brief Write raw binary data to SRAM
|
||||
*
|
||||
* @param sram_base address written to in the SRAM
|
||||
* @param bytes pointer to the written data
|
||||
* @param size number of bytes written
|
||||
*/
|
||||
static inline void write_sram(u32 sram_base, const u8* bytes, u32 size)
|
||||
{
|
||||
if (sram_base + size > SRAM_SIZE)
|
||||
return;
|
||||
|
||||
for (u32 i = 0; i < size; i++)
|
||||
{
|
||||
sram_mem[sram_base + i] = bytes[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read raw binary data from SRAM
|
||||
*
|
||||
* @sa write_sram
|
||||
*/
|
||||
static inline void read_sram(u32 sram_base, u8* bytes, u32 size)
|
||||
{
|
||||
if (sram_base + size > SRAM_SIZE)
|
||||
return;
|
||||
|
||||
for (u32 i = 0; i < size; i++)
|
||||
{
|
||||
bytes[i] = sram_mem[sram_base + i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks the 7 chars of gbalatro_version after the "GBALATRO_VERSION" prefix
|
||||
* representing the git hash of the code the build is based on.
|
||||
*
|
||||
* @returns true if the git hash of the ROM is equal to the hash saved in SRAM.
|
||||
* false if they are different.
|
||||
*/
|
||||
static inline bool check_hash(const char* prefix)
|
||||
{
|
||||
for (u32 i = 0; i < CHECK_HASH_SIZE; i++)
|
||||
{
|
||||
if (gbalatro_version[GIT_HASH_START + i] != prefix[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determines if the current build is considered "dirty" aka has uncommitted changes.
|
||||
* This works because the gbalatro_version string has "-dirty" added at the end if it's
|
||||
* dirty.
|
||||
*
|
||||
* @returns true if version is dirty, false otherwise.
|
||||
*/
|
||||
static inline bool is_version_dirty()
|
||||
{
|
||||
return strlen(gbalatro_version) > GIT_HASH_START + CHECK_HASH_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes a magic number and ROM version info to SRAM to signal that the
|
||||
* save data exists and allow the game to determine if it is compatible.
|
||||
*/
|
||||
static inline void set_save_header()
|
||||
{
|
||||
SaveHeader check = {};
|
||||
check.magic = CHECK_MAGIC;
|
||||
check.dirty = is_version_dirty();
|
||||
memcpy(&(check.githash), gbalatro_version + GIT_HASH_START, CHECK_HASH_SIZE);
|
||||
|
||||
write_sram(CHECK_BASE, (const u8*)&check, sizeof(check));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads whether the save data exists and is valid.
|
||||
*
|
||||
* @sa set_save_valid
|
||||
*/
|
||||
static inline bool check_save_header()
|
||||
{
|
||||
SaveHeader check;
|
||||
read_sram(CHECK_BASE, (u8*)&check, sizeof(check));
|
||||
|
||||
bool is_valid = (check.magic == CHECK_MAGIC) && check.dirty == is_version_dirty() &&
|
||||
check_hash(check.githash);
|
||||
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
void save_game(void)
|
||||
{
|
||||
set_save_header();
|
||||
write_sram(GAME_BASE, (const u8*)&g_game_vars, sizeof(g_game_vars));
|
||||
}
|
||||
|
||||
void load_game(void)
|
||||
{
|
||||
if (!check_save_header())
|
||||
return;
|
||||
|
||||
read_sram(GAME_BASE, (u8*)&g_game_vars, sizeof(g_game_vars));
|
||||
|
||||
// return to where we were in the random sequence so that the run stays reproducible
|
||||
for (u32 i = 0; i < g_game_vars.rng_step; i++)
|
||||
{
|
||||
(void)rand();
|
||||
}
|
||||
|
||||
mmSetModuleVolume(MM_MODULE_FULL_VOLUME * g_game_vars.music_volume / VOLUME_OPTION_MAX);
|
||||
}
|
||||
@@ -125,6 +125,9 @@ void selection_grid_process_input(SelectionGrid* selection_grid)
|
||||
{
|
||||
// To make the next line shorter and more readable
|
||||
Selection* selection = &selection_grid->selection;
|
||||
selection_grid->rows[selection->y].on_key_transit(selection_grid, selection);
|
||||
if (selection_grid->rows[selection->y].on_key_transit != NULL)
|
||||
{
|
||||
selection_grid->rows[selection->y].on_key_transit(selection_grid, selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
static const Rect COUNTDOWN_TIMER_RECT = {208, 144, 240, 152};
|
||||
static uint timer = 0;
|
||||
|
||||
void splash_screen_on_init()
|
||||
void splash_screen_on_init(void)
|
||||
{
|
||||
timer = 0;
|
||||
|
||||
@@ -25,7 +25,7 @@ void splash_screen_on_init()
|
||||
tte_printf("#{P:8,144; cx:0xF000}(Press any key to skip)");
|
||||
}
|
||||
|
||||
void splash_screen_on_update()
|
||||
void splash_screen_on_update(void)
|
||||
{
|
||||
timer++;
|
||||
|
||||
@@ -49,7 +49,7 @@ void splash_screen_on_update()
|
||||
tte_erase_screen();
|
||||
}
|
||||
|
||||
void splash_screen_on_exit()
|
||||
void splash_screen_on_exit(void)
|
||||
{
|
||||
mmStart(MOD_MAIN_THEME, MM_PLAY_LOOP);
|
||||
}
|
||||
|
||||
+4
-3
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "audio_utils.h"
|
||||
#include "game.h"
|
||||
#include "game_variables.h"
|
||||
#include "pool.h"
|
||||
#include "soundbank.h"
|
||||
#include "util.h"
|
||||
@@ -209,8 +210,8 @@ void sprite_object_reset_transform(SpriteObject* sprite_object)
|
||||
|
||||
void sprite_object_update(SpriteObject* sprite_object)
|
||||
{
|
||||
sprite_object->vx += ((sprite_object->tx - sprite_object->x) * get_game_speed()) / 8;
|
||||
sprite_object->vy += ((sprite_object->ty - sprite_object->y) * get_game_speed()) / 8;
|
||||
sprite_object->vx += ((sprite_object->tx - sprite_object->x) * g_game_vars.game_speed) / 8;
|
||||
sprite_object->vy += ((sprite_object->ty - sprite_object->y) * g_game_vars.game_speed) / 8;
|
||||
|
||||
// Scale up the card when it's played
|
||||
sprite_object->vscale += (sprite_object->tscale - sprite_object->scale) / 8;
|
||||
@@ -310,7 +311,7 @@ void sprite_object_set_focus(SpriteObject* sprite_object, bool focus)
|
||||
|
||||
play_sfx(
|
||||
SFX_CARD_FOCUS,
|
||||
MM_BASE_PITCH_RATE + rand() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
|
||||
MM_BASE_PITCH_RATE + get_rand() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
|
||||
SFX_DEFAULT_VOLUME
|
||||
);
|
||||
sprite_object->ty = sprite_object->ty + int2fx((focus ? -1 : 1) * SPRITE_FOCUS_RAISE_PX);
|
||||
|
||||
+2
-2
@@ -6,5 +6,5 @@
|
||||
#define GIT_DIRTY "-dirty"
|
||||
#endif
|
||||
|
||||
__attribute__((section(".version"), used)) const char balatro_version[] =
|
||||
"GBALATRO_VERSION:" GIT_HASH GIT_DIRTY;
|
||||
__attribute__((section(".version"), used)) const char gbalatro_version[] =
|
||||
"GBALATRO-VERSION:" GIT_HASH GIT_DIRTY;
|
||||
|
||||
Reference in New Issue
Block a user