63b6584706
* initial additions * Move current_blind to game vars * tmp * tmp * tmp * tmp * tmp * tmp * tmp * Compile but is broken * temp fix for token location * tmp * Working, lets clean up * remove todo * Remove odd reroll blind variable * fix skipping blind screen * fix reroll * remove magic for blind token locations * clang format * Cleanup button highlight in blind menu * some cleanup * Add layout file for global UI rects * remove dead code * Clang format * clang format it up * Move reset top left funct to gfx utils * make note of soon-to-be removed vars * Cleanup gamevars declaration * Cleanup layout file * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Add include for stdbool.h in common_ui.h * Cleanup rect declaration * Start moving state machine stuff to new file * quick tmp * oops, add new files * working call list * fixed pointer stuff * working as per norm * working replacement * move defines to static const * fix compile issue * Add some missing "void"s * Working new substates * add statemachine to blind select * Fix game restart statemachine leak * clang-format * Clang format * Rework shop exit logic * Use new state machine in shop * Remove substates entirely * Remove debug stuff * Clang format * document state machine better * update list tests * clang-format * Update for PR * Claaaaang format * another clang-format * Remove extra state in round_end * cleanup docs for removals in lists * clang-format... again * update doxygen format * Clang format * tmp * Update for Pr * clang-format * Cleanup for only supporting losing * bug fix, thanks logs! * Fix dangling pointer bug The control of removing statemachines may call issues if starting the function is done from another state machine call. To remedy this, add a flag to determine if a state machine is registered and return early instead of blindly removing it to re-add. * Fix bug with audio iterations possibly going in reverse * Remove extra includes * Clang-format * Add sound effects for game win/lose * adjust audio steps * tmp * Nice sounding and cleaned up * Fix and cleanup * clang-format * clang-format * clang format again --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
473 lines
15 KiB
C
473 lines
15 KiB
C
#include "round_end.h"
|
|
|
|
#include "affine_background.h"
|
|
#include "affine_background_gfx.h"
|
|
#include "game.h"
|
|
#include "game_variables.h"
|
|
#include "layout.h"
|
|
#include "state_machine.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
|
|
enum GameRoundEndStates
|
|
{
|
|
ROUND_END_START,
|
|
START_EXPAND_POPUP,
|
|
DISPLAY_FINISHED_BLIND,
|
|
DISPLAY_SCORE_MIN,
|
|
UPDATE_BLIND_REWARD,
|
|
BLIND_PANEL_EXIT,
|
|
DISPLAY_REWARDS,
|
|
DISPLAY_CASHOUT,
|
|
DISMISS_ROUND_END_PANEL,
|
|
ROUND_END_STATES_MAX
|
|
};
|
|
|
|
static const u32 TM_RESET_STATIC_VARS = 30;
|
|
static const u32 TM_END_POP_MENU_ANIM = 13;
|
|
static const u32 TM_START_ROUND_END_REWARDS_ANIM = 1;
|
|
static const u32 TM_END_DISPLAY_FIN_BLIND = 30;
|
|
static const u32 TM_END_DISPLAY_SCORE_MIN = 4;
|
|
static const u32 TM_DISMISS_ROUND_END_TM = 20;
|
|
|
|
static const u32 REWARD_PANEL_BORDER_PID = 19;
|
|
|
|
static const u32 ROUND_END_BLACK_PANEL_INIT_BOTTOM_SE = 12;
|
|
static const u32 ROUND_END_REWARD_TEXT_X = 88;
|
|
static const u32 ROUND_END_REWARD_AMOUNT_X = 168;
|
|
|
|
// clang-format off
|
|
static const Rect ROUND_END_BLIND_REQ_RECT = {104, 96, 136, UNDEFINED};
|
|
static const Rect ROUND_END_BLIND_REWARD_RECT = {168, 96, UNDEFINED, UNDEFINED};
|
|
static const Rect CASHOUT_DEST_RECT = {10, 8, 23, 10 };
|
|
static const Rect CASHOUT_TEXT_RECT = {88, 72, UNDEFINED, UNDEFINED};
|
|
static const Rect BLIND_TOKEN_TEXT_RECT = {80, 72, 200, 160 };
|
|
static const Rect ROUND_END_MENU_RECT = {9, 7, 24, 20 };
|
|
|
|
static const BG_POINT CASHOUT_SRC_3X3_RECT_POS = {5, 29};
|
|
// clang-format on
|
|
|
|
static int blind_reward = 0;
|
|
static int hand_reward = 0;
|
|
static int interest_reward = 0;
|
|
static int interest_to_count = 0;
|
|
static int interest_start_time = UNDEFINED;
|
|
|
|
static int calculate_interest_reward(void);
|
|
|
|
static void game_round_end_start(void);
|
|
static void game_round_end_start_expand_popup(void);
|
|
static void game_round_end_display_finished_blind(void);
|
|
static void game_round_end_display_score_min(void);
|
|
static void game_round_end_update_blind_reward(void);
|
|
static void game_round_end_panel_exit(void);
|
|
static void game_round_end_display_rewards(void);
|
|
static void game_round_end_display_cashout(void);
|
|
static void game_round_end_dismiss_round_end_panel(void);
|
|
|
|
static void game_round_end_extend_black_panel_down(int black_panel_bottom);
|
|
|
|
static StateInfo state_info[] = {
|
|
STATE_INFO_UPDATE_FN_ONLY(game_round_end_start),
|
|
STATE_INFO_UPDATE_FN_ONLY(game_round_end_start_expand_popup),
|
|
STATE_INFO_UPDATE_FN_ONLY(game_round_end_display_finished_blind),
|
|
STATE_INFO_UPDATE_FN_ONLY(game_round_end_display_score_min),
|
|
STATE_INFO_UPDATE_FN_ONLY(game_round_end_update_blind_reward),
|
|
STATE_INFO_UPDATE_FN_ONLY(game_round_end_panel_exit),
|
|
STATE_INFO_UPDATE_FN_ONLY(game_round_end_display_rewards),
|
|
STATE_INFO_UPDATE_FN_ONLY(game_round_end_display_cashout),
|
|
STATE_INFO_UPDATE_FN_ONLY(game_round_end_dismiss_round_end_panel),
|
|
};
|
|
|
|
static StateMachine round_end_sm = STATE_MACHINE_DEFINE(state_info, ROUND_END_STATES_MAX);
|
|
|
|
static int calculate_interest_reward(void)
|
|
{
|
|
int reward = (g_game_vars.money / 5) * INTEREST_PER_5;
|
|
if (reward > MAX_INTEREST)
|
|
reward = MAX_INTEREST;
|
|
return reward;
|
|
}
|
|
|
|
static void game_round_end_start(void)
|
|
{
|
|
// Reset static variables to default values upon re-entering the round end state
|
|
if (g_game_vars.timer == TM_RESET_STATIC_VARS)
|
|
{
|
|
change_background(BG_ROUND_END, false); // Change the background to the round end background
|
|
state_machine_change_state(&round_end_sm, START_EXPAND_POPUP);
|
|
g_game_vars.timer = TM_ZERO; // Reset the timer
|
|
blind_reward = blind_get_reward(g_game_vars.current_blind);
|
|
hand_reward = g_game_vars.hands;
|
|
interest_reward = calculate_interest_reward();
|
|
interest_to_count = interest_reward;
|
|
interest_start_time = UNDEFINED;
|
|
}
|
|
}
|
|
|
|
static void game_round_end_start_expand_popup(void)
|
|
{
|
|
main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SCREEN_UP);
|
|
|
|
if (g_game_vars.timer == TM_END_POP_MENU_ANIM)
|
|
{
|
|
state_machine_change_state(&round_end_sm, DISPLAY_FINISHED_BLIND);
|
|
g_game_vars.timer = TM_ZERO;
|
|
}
|
|
}
|
|
|
|
static void game_round_end_display_finished_blind(void)
|
|
{
|
|
obj_unhide(g_game_vars.round_end_blind_token->obj, 0);
|
|
|
|
int current_ante = g_game_vars.ante;
|
|
|
|
// Beating the boss blind increases the ante, so we need to display the previous ante value
|
|
if (g_game_vars.current_blind > BLIND_TYPE_BIG)
|
|
current_ante--;
|
|
|
|
Rect blind_req_rect = ROUND_END_BLIND_REQ_RECT;
|
|
u32 blind_req = blind_get_requirement(g_game_vars.current_blind, current_ante);
|
|
|
|
/* Not bothering to truncate here because there are 8 tiles
|
|
* and the blind requirement will not increase past ante 8
|
|
* so there's enough room for sure.
|
|
*/
|
|
char blind_req_str_buff[UINT_MAX_DIGITS + 1];
|
|
snprintf(blind_req_str_buff, sizeof(blind_req_str_buff), "%lu", blind_req);
|
|
|
|
update_text_rect_to_right_align_str(&blind_req_rect, blind_req_str_buff, OVERFLOW_RIGHT);
|
|
|
|
tte_printf(
|
|
"#{P:%d,%d; cx:0x%X000}%s",
|
|
blind_req_rect.left,
|
|
blind_req_rect.top,
|
|
TTE_RED_PB,
|
|
blind_req_str_buff
|
|
);
|
|
|
|
if (g_game_vars.timer == TM_START_ROUND_END_REWARDS_ANIM)
|
|
{
|
|
game_round_end_extend_black_panel_down(ROUND_END_BLACK_PANEL_INIT_BOTTOM_SE);
|
|
}
|
|
|
|
if (g_game_vars.timer >= TM_END_DISPLAY_FIN_BLIND)
|
|
{
|
|
state_machine_change_state(&round_end_sm, DISPLAY_SCORE_MIN);
|
|
g_game_vars.timer = TM_ZERO;
|
|
}
|
|
}
|
|
|
|
static void game_round_end_display_score_min(void)
|
|
{
|
|
const int timer_offset = g_game_vars.timer - 1;
|
|
const int x_from = 0;
|
|
const int y_from = 29;
|
|
const int x_to = 13;
|
|
const int y_to = 11;
|
|
|
|
memcpy16(
|
|
&se_mem[MAIN_BG_SBB][x_to + timer_offset + 32 * y_to],
|
|
&se_mem[MAIN_BG_SBB][x_from + timer_offset + 32 * y_from],
|
|
1
|
|
);
|
|
|
|
if (g_game_vars.timer >= TM_END_DISPLAY_SCORE_MIN)
|
|
{
|
|
state_machine_change_state(&round_end_sm, UPDATE_BLIND_REWARD);
|
|
g_game_vars.timer = TM_ZERO;
|
|
}
|
|
}
|
|
|
|
static void game_round_end_update_blind_reward(void)
|
|
{
|
|
if (g_game_vars.timer % FRAMES(20) != 0)
|
|
return;
|
|
|
|
// TODO: Add sound effect here
|
|
|
|
if (blind_reward > 0)
|
|
{
|
|
blind_reward--;
|
|
tte_printf(
|
|
"#{P:%d,%d; cx:0x%X000}$%d",
|
|
BLIND_REWARD_RECT.left,
|
|
BLIND_REWARD_RECT.top,
|
|
TTE_YELLOW_PB,
|
|
blind_reward
|
|
);
|
|
tte_printf(
|
|
"#{P:%d,%d; cx:0x%X000}$%d",
|
|
ROUND_END_BLIND_REWARD_RECT.left,
|
|
ROUND_END_BLIND_REWARD_RECT.top,
|
|
TTE_YELLOW_PB,
|
|
blind_get_reward(g_game_vars.current_blind) - blind_reward
|
|
);
|
|
}
|
|
else if (g_game_vars.timer > FRAMES(20))
|
|
{
|
|
tte_erase_rect_wrapper(BLIND_REWARD_RECT);
|
|
tte_erase_rect_wrapper(BLIND_REQ_TEXT_RECT);
|
|
obj_hide(g_game_vars.playing_blind_token->obj);
|
|
affine_background_load_palette(affine_background_gfxPal);
|
|
state_machine_change_state(&round_end_sm, BLIND_PANEL_EXIT);
|
|
g_game_vars.timer = TM_ZERO;
|
|
}
|
|
}
|
|
|
|
static void game_round_end_panel_exit(void)
|
|
{
|
|
// TODO: make heads or tails of what's going on here and replace
|
|
// magic numbers.
|
|
if (g_game_vars.timer < 8)
|
|
{
|
|
main_bg_se_copy_rect_1_tile_vert(TOP_LEFT_PANEL_ANIM_RECT, SCREEN_UP);
|
|
|
|
if (g_game_vars.timer == 1)
|
|
{
|
|
reset_top_left_panel_bottom_row();
|
|
}
|
|
else if (g_game_vars.timer == 2)
|
|
{
|
|
int y = 5;
|
|
memset16(&se_mem[MAIN_BG_SBB][32 * (y - 1)], 0x0001, 1);
|
|
memset16(&se_mem[MAIN_BG_SBB][1 + 32 * (y - 1)], 0x0002, 7);
|
|
memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0401, 1);
|
|
}
|
|
}
|
|
else if (g_game_vars.timer > FRAMES(20))
|
|
{
|
|
memset16(&pal_bg_mem[REWARD_PANEL_BORDER_PID], 0x1483, 1);
|
|
state_machine_change_state(&round_end_sm, DISPLAY_REWARDS);
|
|
g_game_vars.timer = TM_ZERO;
|
|
}
|
|
}
|
|
|
|
static inline void game_round_end_print_separator_ellipsis(void)
|
|
{
|
|
int x =
|
|
(ROUND_END_REWARDS_ELLIPSIS_POS.x + g_game_vars.timer - TM_REWARDS_ELLIPSIS_PRINT_START) *
|
|
TILE_SIZE;
|
|
int y = (ROUND_END_REWARDS_ELLIPSIS_POS.y) * TILE_SIZE;
|
|
|
|
tte_printf("#{P:%d,%d; cx:0x%X000}.", x, y, TTE_WHITE_PB);
|
|
}
|
|
|
|
// TODO: Allow for more generic rewards and consolidate with game_round_end_print_interest_reward()
|
|
static inline void game_round_end_print_hand_reward(int hand_y_offset)
|
|
{
|
|
int hand_y = ROUND_END_REWARDS_ELLIPSIS_POS.y + hand_y_offset;
|
|
if (g_game_vars.timer == TM_DISPLAY_REWARDS_CONT_WAIT)
|
|
{
|
|
game_round_end_extend_black_panel_down(hand_y);
|
|
|
|
tte_printf(
|
|
"#{P:%lu,%d; cx:0x%X000}%d #{cx:0x%X000}Hands",
|
|
ROUND_END_REWARD_TEXT_X,
|
|
hand_y * TILE_SIZE,
|
|
TTE_BLUE_PB,
|
|
hand_reward,
|
|
TTE_WHITE_PB
|
|
);
|
|
}
|
|
// Increment the hand reward text until the hand reward variable is depleted
|
|
else if (g_game_vars.timer > TM_HAND_REWARD_INCR_WAIT &&
|
|
g_game_vars.timer % FRAMES(TM_REWARD_INCREMENT_INTERVAL) == 0)
|
|
{
|
|
hand_reward--;
|
|
tte_printf(
|
|
"#{P:%lu, %d; cx:0x%X000}$%ld",
|
|
ROUND_END_REWARD_AMOUNT_X,
|
|
hand_y * TILE_SIZE,
|
|
TTE_YELLOW_PB,
|
|
g_game_vars.hands - hand_reward
|
|
);
|
|
if (hand_reward == 0)
|
|
{
|
|
interest_start_time = g_game_vars.timer + TM_REWARD_DISPLAY_INTERVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void game_round_end_print_interest_reward(int interest_y_offset)
|
|
{
|
|
int interest_y = ROUND_END_REWARDS_ELLIPSIS_POS.y + interest_y_offset;
|
|
|
|
if (g_game_vars.timer == interest_start_time)
|
|
{
|
|
game_round_end_extend_black_panel_down(interest_y);
|
|
|
|
tte_printf(
|
|
"#{P:%lu,%d; cx:0x%X000}%d #{cx:0x%X000}Interest",
|
|
ROUND_END_REWARD_TEXT_X,
|
|
interest_y * TILE_SIZE,
|
|
TTE_YELLOW_PB,
|
|
interest_reward,
|
|
TTE_WHITE_PB
|
|
);
|
|
}
|
|
// Increment the interest reward text until the interest reward variable is depleted
|
|
else if (g_game_vars.timer > interest_start_time + TM_REWARD_DISPLAY_INTERVAL &&
|
|
g_game_vars.timer % FRAMES(TM_REWARD_INCREMENT_INTERVAL) == 0)
|
|
{
|
|
interest_to_count--;
|
|
tte_printf(
|
|
"#{P:%lu, %d; cx:0x%X000}$%d",
|
|
ROUND_END_REWARD_AMOUNT_X,
|
|
interest_y * TILE_SIZE,
|
|
TTE_YELLOW_PB,
|
|
interest_reward - interest_to_count
|
|
);
|
|
}
|
|
}
|
|
|
|
static void game_round_end_display_rewards(void)
|
|
{
|
|
int hand_y_offset = 0;
|
|
int interest_y_offset = 0;
|
|
|
|
if (g_game_vars.hands > 0)
|
|
{
|
|
hand_y_offset = 1;
|
|
}
|
|
else
|
|
{
|
|
interest_start_time = TM_DISPLAY_REWARDS_CONT_WAIT;
|
|
}
|
|
|
|
if (interest_reward > 0)
|
|
{
|
|
interest_y_offset = hand_y_offset + 1;
|
|
}
|
|
|
|
// Once all rewards are accounted for go to the next state
|
|
if (hand_reward <= 0 && interest_to_count <= 0)
|
|
{
|
|
g_game_vars.timer = TM_ZERO;
|
|
state_machine_change_state(&round_end_sm, DISPLAY_CASHOUT);
|
|
}
|
|
else if (g_game_vars.timer == TM_START_ROUND_END_REWARDS_ANIM)
|
|
{
|
|
game_round_end_extend_black_panel_down(ROUND_END_REWARDS_ELLIPSIS_POS.y);
|
|
}
|
|
else if (g_game_vars.timer < TM_REWARDS_ELLIPSIS_PRINT_END)
|
|
{
|
|
game_round_end_print_separator_ellipsis();
|
|
}
|
|
else if (g_game_vars.timer >= TM_DISPLAY_REWARDS_CONT_WAIT && hand_reward > 0)
|
|
{
|
|
game_round_end_print_hand_reward(hand_y_offset);
|
|
}
|
|
else if (interest_start_time != UNDEFINED && g_game_vars.timer >= interest_start_time &&
|
|
interest_to_count > 0)
|
|
{
|
|
game_round_end_print_interest_reward(interest_y_offset);
|
|
}
|
|
}
|
|
|
|
static inline void game_round_end_cashout(void)
|
|
{
|
|
// Reward the player
|
|
g_game_vars.money += g_game_vars.hands + blind_get_reward(g_game_vars.current_blind) +
|
|
calculate_interest_reward();
|
|
display_money();
|
|
|
|
g_game_vars.hands = MAX_HANDS; // Reset the hands to the maximum
|
|
g_game_vars.discards = MAX_DISCARDS; // Reset the discards to the maximum
|
|
// TODO: these can just be in one spot, passing global to global
|
|
display_hands(); // Set the hands display
|
|
display_discards(); // Set the discards display
|
|
|
|
g_game_vars.score = 0;
|
|
display_score(g_game_vars.score); // Set the score display
|
|
}
|
|
|
|
static void game_round_end_display_cashout()
|
|
{
|
|
if (g_game_vars.timer == FRAMES(40))
|
|
{
|
|
// Put the "cash out" button onto the round end panel
|
|
main_bg_se_copy_expand_3x3_rect(CASHOUT_DEST_RECT, CASHOUT_SRC_3X3_RECT_POS);
|
|
|
|
int cashout_amount = g_game_vars.hands + blind_get_reward(g_game_vars.current_blind) +
|
|
calculate_interest_reward();
|
|
|
|
bool omit_space = cashout_amount >= 10;
|
|
tte_printf(
|
|
"#{P:%d, %d; cx:0x%X000}Cash Out:%s$%d",
|
|
CASHOUT_TEXT_RECT.left,
|
|
CASHOUT_TEXT_RECT.top,
|
|
TTE_WHITE_PB,
|
|
omit_space ? "" : " ",
|
|
cashout_amount
|
|
);
|
|
}
|
|
|
|
// Wait until the player presses A to cash out
|
|
else if (g_game_vars.timer > FRAMES(40) && key_hit(SELECT_CARD))
|
|
{
|
|
game_round_end_cashout();
|
|
|
|
state_machine_change_state(&round_end_sm, DISMISS_ROUND_END_PANEL);
|
|
g_game_vars.timer = TM_ZERO;
|
|
|
|
obj_hide(g_game_vars.round_end_blind_token->obj); // Hide the blind token object
|
|
tte_erase_rect_wrapper(BLIND_TOKEN_TEXT_RECT); // Erase the blind token text
|
|
}
|
|
}
|
|
|
|
static void game_round_end_dismiss_round_end_panel(void)
|
|
{
|
|
Rect round_end_down = ROUND_END_MENU_RECT;
|
|
round_end_down.top--;
|
|
main_bg_se_copy_rect_1_tile_vert(round_end_down, SCREEN_DOWN);
|
|
|
|
if (g_game_vars.timer >= TM_DISMISS_ROUND_END_TM)
|
|
{
|
|
g_game_vars.timer = TM_ZERO;
|
|
game_change_state(GAME_STATE_SHOP);
|
|
}
|
|
}
|
|
|
|
static void game_round_end_extend_black_panel_down(int black_panel_bottom)
|
|
{
|
|
Rect single_line_rect = ROUND_END_MENU_RECT;
|
|
single_line_rect.bottom = black_panel_bottom;
|
|
single_line_rect.top = single_line_rect.bottom - 1;
|
|
main_bg_se_copy_rect_1_tile_vert(single_line_rect, SCREEN_DOWN);
|
|
}
|
|
|
|
void game_round_end_change_background(void)
|
|
{
|
|
// Disable window 0 so it doesn't make the cashout menu transparent
|
|
toggle_windows(false, true);
|
|
|
|
main_bg_se_clear_rect(ROUND_END_MENU_RECT);
|
|
tte_erase_rect_wrapper(HAND_SIZE_RECT);
|
|
}
|
|
|
|
void game_round_end_on_init(void)
|
|
{
|
|
g_game_vars.timer = 0;
|
|
state_machine_register(&round_end_sm);
|
|
state_machine_change_state(&round_end_sm, ROUND_END_START);
|
|
}
|
|
|
|
void game_round_end_on_update(void)
|
|
{
|
|
// Substate logic only
|
|
}
|
|
|
|
void game_round_end_on_exit(void)
|
|
{
|
|
// Cleanup blind tokens from this round to avoid accumulating
|
|
// allocated blind sprites each round
|
|
blind_reward = 0;
|
|
hand_reward = 0;
|
|
interest_reward = 0;
|
|
sprite_destroy(&g_game_vars.playing_blind_token);
|
|
sprite_destroy(&g_game_vars.round_end_blind_token);
|
|
state_machine_remove(&round_end_sm);
|
|
// TODO: Reuse sprites for blind selection?
|
|
}
|