GBAlatro
A Demake of Balatro for the GBA
Loading...
Searching...
No Matches
blind_select.c
1#include "blind_select.h"
2
3#include "affine_background.h"
4#include "audio_utils.h"
5#include "background_blind_select_gfx.h"
6#include "blind.h"
7#include "button.h"
8#include "game.h"
9#include "game_variables.h"
10#include "graphic_utils.h"
11#include "layout.h"
12#include "selection_grid.h"
13#include "skip_tag.h"
14#include "soundbank.h"
15#include "sprite.h"
16#include "state_machine.h"
17#include "timer.h"
18#include "util.h"
19
20#include <maxmod.h>
21
22// Palette indices for colors that change at runtime start at 32
23// while indices 0 to 31 are for static colors.
24// The indexing is determined by background_blind_select_gfx.png
25static const u8 BLIND_SELECT_BTN_MAIN_COLOR_PAL_IDX = 22;
26static const u8 BLIND_SELECT_BTN_OUTLINE_COLOR_PAL_IDX = 34;
27
28static const u8 BLIND_SKIP_BTN_MAIN_COLOR_PAL_IDX = 19;
29static const u8 BLIND_SKIP_BTN_OUTLINE_COLOR_PAL_IDX = 35;
30
31static const u8 BOSS_BLIND_PANEL_OUTLINE_PAL_IDX = 32;
32static const u8 BOSS_BLIND_PANEL_SHADOW_PAL_IDX = 33;
33
34static const u32 TM_DISP_BLIND_PANEL_FINISH = 7;
35static const u32 TM_DISP_BLIND_PANEL_START = 1;
36static const u32 TM_BOSS_BLIND_REROLL_DURATION = 13;
37
38static int s_timer;
39
40static void blind_select_start_anim_seq(void);
41static void blind_select_start_anim_seq_reposition_tag_sprites(void);
42static void blind_select_handle_input(void);
43static void blind_select_handle_immediate_tags_on_init(void);
44static void blind_select_handle_immediate_tags_on_update(void);
45static void blind_select_reroll_boss_anim_seq_on_init(void);
46static void blind_select_reroll_boss_anim_seq_on_update(void);
47static void blind_select_reroll_boss_anim_seq_on_exit(void);
48static void blind_select_selected_anim_seq_on_init(void);
49static void blind_select_selected_anim_seq_on_update(void);
50static void blind_select_selected_anim_seq_on_exit(void);
51static void blind_select_display_blind_panel(void);
52static void blind_select_exit(void);
53static Rect blind_select_get_req_score_rect(enum BlindTokens blind);
54static void blind_select_print_blinds_reqs_and_rewards(void);
55static enum BlindType get_blind_type_from_token(enum BlindTokens blind);
56static void blind_tokens_init(void);
57static void blind_skip_tags_init(void);
58
59enum BlindSelectState
60{
61 START_ANIM_SEQ,
62 BLIND_SELECT,
63 APPLY_BLIND_TAGS,
64 REROLL_BOSS_ANIM_SEQ,
65 BLIND_SELECTED_ANIM_SEQ,
66 DISPLAY_BLIND_PANEL,
67 BLIND_SELECT_EXIT,
68 BLIND_SELECT_MAX,
69};
70
71// clang-format off
72static StateInfo state_info[] = {
73 [START_ANIM_SEQ] = STATE_INFO_UPDATE_FN_ONLY(blind_select_start_anim_seq),
74 [BLIND_SELECT] = STATE_INFO_UPDATE_FN_ONLY(blind_select_handle_input),
75 [APPLY_BLIND_TAGS] = STATE_INFO_INIT_UPDATE_FN(
76 blind_select_handle_immediate_tags_on_init,
77 blind_select_handle_immediate_tags_on_update),
78 [REROLL_BOSS_ANIM_SEQ] = STATE_INFO_ALL_FN(
79 blind_select_reroll_boss_anim_seq_on_init,
80 blind_select_reroll_boss_anim_seq_on_update,
81 blind_select_reroll_boss_anim_seq_on_exit),
82 [BLIND_SELECTED_ANIM_SEQ] = STATE_INFO_ALL_FN(
83 blind_select_selected_anim_seq_on_init,
84 blind_select_selected_anim_seq_on_update,
85 blind_select_selected_anim_seq_on_exit),
86 [DISPLAY_BLIND_PANEL] = STATE_INFO_UPDATE_FN_ONLY(blind_select_display_blind_panel),
87 [BLIND_SELECT_EXIT] = STATE_INFO_UPDATE_FN_ONLY(blind_select_exit),
88};
89// clang-format on
90
91static StateMachine blind_select_sm = STATE_MACHINE_DEFINE(state_info, BLIND_SELECT_MAX);
92
93// clang-format off
94// Points x y
95static const BG_POINT TOP_LEFT_PANEL_EMPTY_3W_ROW_POS = {29, 31};
96static const BG_POINT SMALL_BLIND_SKIP_TAG_INIT_POS = {75, 170};
97static const BG_POINT BIG_BLIND_SKIP_TAG_INIT_POS = {115, 170};
98static const BG_POINT SMALL_BLIND_SKIP_TAG_HIGH_POS = {75, 132};
99static const BG_POINT SMALL_BLIND_SKIP_TAG_LOW_POS = {75, 140};
100static const BG_POINT BIG_BLIND_SKIP_TAG_HIGH_POS = {115, 132};
101static const BG_POINT BIG_BLIND_SKIP_TAG_LOW_POS = {115, 140};
102// Rects left top right bottom
103static const Rect BLIND_SKIP_BTN_GRAY_RECT = {0, 24, 4, 27};
104static const Rect BLIND_SKIP_BTN_PREANIM_DEST_RECT = {9, 29, 19, 31};
105static const Rect SINGLE_BLIND_SEL_REQ_SCORE_RECT = {80, 120, 104, 128};
106static const Rect SINGLE_BLIND_SELECT_RECT = {9, 7, 13, 31};
107static const Rect BOSS_BLIND_PANEL_MOVE_DOWN_RECT = {19, 6, 24, 31};
108static const Rect BOSS_BLIND_PANEL_MOVE_UP_RECT = {19, 7, 24, 31};
109static const Rect BOSS_BLIND_REROLL_DUP_LINE_RECT = {19, 30, 24, 30};
110// clang-format on
111
112static const u32 TM_END_ANIM_SEQ = 12;
113static const u32 TM_SKIP_TAGS_ANIM_SEQ = 6;
114
115static const u32 BLIND_LEFT_X = 80;
116static const u32 BLIND_CENTER_X = 120;
117static const u32 BLIND_RIGHT_X = 160;
118
119enum BlindSelectRow
120{
121 BLIND_ROW = 0,
122 SKIP_ROW,
123 MAX_ROW
124};
125
126static int blind_select_get_selection_row_size(void);
127static void blind_select_row_on_key_transit(SelectionGrid* selection_grid, Selection* selection);
128static bool blind_select_row_on_selection_changed(
129 SelectionGrid* selection_grid,
130 int row_idx,
131 const Selection* prev_selection,
132 const Selection* new_selection
133);
134
135// clang-format off
136static SelectionGridRow blind_select_selection_rows[] = {
137 {
138 BLIND_ROW,
139 blind_select_get_selection_row_size,
140 blind_select_row_on_selection_changed,
141 blind_select_row_on_key_transit,
142 {.wrap = false}
143 },
144 {
145 SKIP_ROW,
146 blind_select_get_selection_row_size,
147 blind_select_row_on_selection_changed,
148 blind_select_row_on_key_transit,
149 {.wrap = false}
150 }
151};
152// clang-format on
153
154static const Selection BLIND_SELECT_INIT_SEL = {0, BLIND_ROW};
155static SelectionGrid blind_select_selection_grid = {
156 blind_select_selection_rows,
157 MAX_ROW,
158 BLIND_SELECT_INIT_SEL
159};
160
161static void select_on_pressed(void);
162static void skip_on_pressed(void);
163
164static Button blind_select_buttons[] = {
165 {BLIND_SELECT_BTN_OUTLINE_COLOR_PAL_IDX, BLIND_SELECT_BTN_MAIN_COLOR_PAL_IDX, select_on_pressed, NULL},
166 {BLIND_SKIP_BTN_OUTLINE_COLOR_PAL_IDX, BLIND_SKIP_BTN_MAIN_COLOR_PAL_IDX, skip_on_pressed, NULL}
167};
168
169static Sprite* blind_select_tokens[NUM_BLINDS_PER_ANTE] = {NULL};
170static SkipTag* blind_skip_tags[NB_SKIPPABLE_BLINDS] = {NULL};
171
172static void blind_select_start_anim_seq()
173{
174 main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SCREEN_UP);
175
176 for (int i = 0; i < NUM_BLINDS_PER_ANTE; i++)
177 {
179 blind_select_tokens[i],
180 blind_select_tokens[i]->pos.x,
181 blind_select_tokens[i]->pos.y - TILE_SIZE
182 );
183 }
184
185 if (s_timer == TM_SKIP_TAGS_ANIM_SEQ)
186 {
187 blind_select_start_anim_seq_reposition_tag_sprites();
188 }
189
190 if (s_timer == TM_END_ANIM_SEQ)
191 {
192 blind_select_print_blinds_reqs_and_rewards();
193 state_machine_change_state(&blind_select_sm, BLIND_SELECT);
194 s_timer = TM_ZERO;
195 }
196}
197
198static void blind_select_start_anim_seq_reposition_tag_sprites(void)
199{
200 // first index is the Small/Big Blind Tag, the second one is for whether the current blind
201 // corresponds to the Tag
202 static const BG_POINT tag_sprites_target_positions[NB_SKIPPABLE_BLINDS][NB_SKIPPABLE_BLINDS] = {
203 {SMALL_BLIND_SKIP_TAG_LOW_POS, SMALL_BLIND_SKIP_TAG_HIGH_POS},
204 {BIG_BLIND_SKIP_TAG_LOW_POS, BIG_BLIND_SKIP_TAG_HIGH_POS }
205 };
206
207 for (int i = 0; i < NB_SKIPPABLE_BLINDS; i++)
208 {
209 if (blind_skip_tags[i] != NULL)
210 {
212 (SpriteObject*)blind_skip_tags[i],
213 tag_sprites_target_positions[i][g_game_vars.current_blind == (enum BlindType)i]
214 );
215 }
216 }
217}
218
219static inline void blind_select_erase_blind_req_and_reward(enum BlindTokens blind)
220{
221 Rect blind_req_and_reward_rect = SINGLE_BLIND_SEL_REQ_SCORE_RECT;
222
223 // To account for both raised blind and reward
224 blind_req_and_reward_rect.top -= TILE_SIZE;
225 blind_req_and_reward_rect.bottom += TILE_SIZE;
226
227 // To account for overflow
228 blind_req_and_reward_rect.right += TILE_SIZE;
229
230 // Position the rect over the right Blind
231 int score_rect_x_offset = blind * rect_width(&SINGLE_BLIND_SELECT_RECT) * TILE_SIZE;
232 blind_req_and_reward_rect.left += score_rect_x_offset;
233 blind_req_and_reward_rect.right += score_rect_x_offset;
234
235 tte_erase_rect_wrapper(blind_req_and_reward_rect);
236}
237
238static inline void blind_select_erase_all_blind_reqs_and_rewards(void)
239{
240 for (enum BlindTokens curr_blind = SMALL_BLIND; curr_blind < NUM_BLINDS_PER_ANTE; curr_blind++)
241 {
242 blind_select_erase_blind_req_and_reward(curr_blind);
243 }
244}
245
246void increment_blind(enum BlindState increment_reason)
247{
248 switch (g_game_vars.current_blind)
249 {
250 // defeated small blind: go to big and raise the big blind's skip tag sprite by one tile
251 case BLIND_TYPE_SMALL:
252 g_game_vars.current_blind = BLIND_TYPE_BIG;
253 g_game_vars.blinds_states[SMALL_BLIND] = increment_reason;
254 g_game_vars.blinds_states[BIG_BLIND] = BLIND_STATE_CURRENT;
255 break;
256 // defeated big blind: go to next boss
257 case BLIND_TYPE_BIG:
258 g_game_vars.current_blind = g_game_vars.next_boss_blind;
259 g_game_vars.blinds_states[BIG_BLIND] = increment_reason;
260 g_game_vars.blinds_states[BOSS_BLIND] = BLIND_STATE_CURRENT;
261 break;
262 // defeated a boss: reset everything
263 default:
264 g_game_vars.current_blind = BLIND_TYPE_SMALL;
265 g_game_vars.blinds_states[SMALL_BLIND] = BLIND_STATE_CURRENT;
266 g_game_vars.blinds_states[BIG_BLIND] = BLIND_STATE_UPCOMING;
267 g_game_vars.blinds_states[BOSS_BLIND] = BLIND_STATE_UPCOMING;
268 break;
269 }
270}
271
272static void blind_select_handle_input(void)
273{
274 selection_grid_process_input(&blind_select_selection_grid);
275}
276
277static int blind_select_get_selection_row_size(void)
278{
279 return 1;
280}
281
282static void blind_select_row_on_key_transit(SelectionGrid* selection_grid, Selection* selection)
283{
284 if (key_hit(SELECT_CARD))
285 {
286 button_press(&blind_select_buttons[selection->y]);
287 }
288}
289
290static bool blind_select_row_on_selection_changed(
291 SelectionGrid* selection_grid,
292 int row_idx,
293 const Selection* prev_selection,
294 const Selection* new_selection
295)
296{
297 if (g_game_vars.current_blind >= BLIND_TYPE_BOSS && new_selection->y == SKIP_ROW)
298 return false;
299
300 if (prev_selection->y == row_idx)
301 button_set_highlight(&blind_select_buttons[row_idx], false);
302
303 if (new_selection->y == row_idx)
304 button_set_highlight(&blind_select_buttons[row_idx], true);
305
306 return true;
307}
308
309static void select_on_pressed(void)
310{
311 blind_select_erase_all_blind_reqs_and_rewards();
312
313 s_timer = TM_ZERO;
314 ++g_game_vars.round;
315 display_round();
316
317 state_machine_change_state(&blind_select_sm, BLIND_SELECTED_ANIM_SEQ);
318}
319
320static void skip_on_pressed(void)
321{
322 blind_select_erase_all_blind_reqs_and_rewards();
323
324 g_game_vars.nb_skipped_rounds++;
325 add_skip_tag(&blind_skip_tags[g_game_vars.current_blind]);
326 if (blind_skip_tags[BLIND_TYPE_BIG])
327 blind_skip_tags[BLIND_TYPE_BIG]->ty -= int2fx(TILE_SIZE);
328
329 increment_blind(BLIND_STATE_SKIPPED);
330
331 change_background(BG_BLIND_SELECT, true);
332
333 // TODO: Create a generic vertical move by any number of tiles to avoid for
334 // loops?
335 for (int i = 0; i < 12; i++)
336 {
337 main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SCREEN_UP);
338 }
339
340 for (int i = 0; i < NUM_BLINDS_PER_ANTE; i++)
341 {
343 blind_select_tokens[i],
344 blind_select_tokens[i]->pos.x,
345 blind_select_tokens[i]->pos.y - (TILE_SIZE * 12)
346 );
347 }
348
349 blind_select_print_blinds_reqs_and_rewards();
350
351 selection_grid_move_selection_vert(&blind_select_selection_grid, SCREEN_UP);
352
353 s_timer = TM_ZERO;
354 state_machine_change_state(&blind_select_sm, APPLY_BLIND_TAGS);
355}
356
357static void blind_select_handle_immediate_tags_on_init(void)
358{
359 skip_tag_process_start(SKIP_TAG_EVENT_IMMEDIATE);
360}
361
362static void blind_select_handle_immediate_tags_on_update(void)
363{
364 if (skip_tag_process_get_proc_stage() == SKIP_TAG_PROCESS_STAGE_END)
365 {
366 s_timer = TM_ZERO;
367 state_machine_change_state(&blind_select_sm, BLIND_SELECT);
368 }
369}
370
372{
373 s_timer = TM_ZERO;
374 state_machine_change_state(&blind_select_sm, REROLL_BOSS_ANIM_SEQ);
375}
376
381static inline void reroll_boss_blind(void)
382{
383 // Showdown blinds only show up on ante 8, 16, etc...
384 g_game_vars.next_boss_blind =
385 roll_blind_type((g_game_vars.ante % MAX_ANTE == 0) && (g_game_vars.ante > 0));
386 // Apply new blind immediately if Boss Blind is selected, as increment_blind will not be called
387 if (g_game_vars.current_blind >= BLIND_TYPE_BOSS)
388 {
389 g_game_vars.current_blind = g_game_vars.next_boss_blind;
390 }
391}
392
393static void blind_select_reroll_boss_anim_seq_on_init(void)
394{
395 // We need to pause the Tags processing to let the reroll animation play and finish.
396 // Otherwise, if chaining Boss Tags, the next animation would start to play before the previous
397 // one finishes, causing glitches
399 blind_select_erase_blind_req_and_reward(BOSS_BLIND);
400}
401
402static inline void blind_select_move_boss_blind_panel(enum ScreenVertDir dir)
403{
405 dir == SCREEN_DOWN ? BOSS_BLIND_PANEL_MOVE_DOWN_RECT : BOSS_BLIND_PANEL_MOVE_UP_RECT,
406 dir
407 );
409 blind_select_tokens[BOSS_BLIND],
410 blind_select_tokens[BOSS_BLIND]->pos.x,
411 blind_select_tokens[BOSS_BLIND]->pos.y + dir * TILE_SIZE
412 );
413}
414
415static inline void blind_select_reroll_boss_anim_do_reroll(void)
416{
417 reroll_boss_blind();
418 apply_blind_tiles(g_game_vars.next_boss_blind, BOSS_BLIND_TOKEN_LAYER);
419 pal_bg_mem[BOSS_BLIND_PANEL_OUTLINE_PAL_IDX] =
420 blind_get_color(g_game_vars.next_boss_blind, BLIND_BACKGROUND_MAIN_COLOR_INDEX);
421 pal_bg_mem[BOSS_BLIND_PANEL_SHADOW_PAL_IDX] =
422 blind_get_color(g_game_vars.next_boss_blind, BLIND_BACKGROUND_SHADOW_COLOR_INDEX);
423}
424
425static void blind_select_reroll_boss_anim_seq_on_update(void)
426{
427 // Move Boss Blind panel 1 tile more up and down if needed
428 bool is_boss_selected = g_game_vars.current_blind >= BLIND_TYPE_BOSS;
429 int panel_move_duration = TM_BOSS_BLIND_REROLL_DURATION + (int)is_boss_selected;
430
431 if (s_timer < panel_move_duration)
432 blind_select_move_boss_blind_panel(SCREEN_DOWN);
433
434 // Reroll Boss Blind
435 else if (s_timer == MENU_POP_OUT_ANIM_FRAMES)
436 blind_select_reroll_boss_anim_do_reroll();
437
438 // Move whole panel up after a short pause
439 else if (s_timer > MENU_POP_OUT_ANIM_FRAMES &&
440 s_timer < (MENU_POP_OUT_ANIM_FRAMES + panel_move_duration))
441 {
442 blind_select_move_boss_blind_panel(SCREEN_UP);
443
444 // Just once if Boss Blind panel is raised, to compensate for the fact that the panel will
445 // lack one row of tiles, copy a line on the first frame to fill that gap
446 if (s_timer == (MENU_POP_OUT_ANIM_FRAMES + 1) && is_boss_selected)
447 {
448 main_bg_se_copy_rect_1_tile_vert(BOSS_BLIND_REROLL_DUP_LINE_RECT, SCREEN_DOWN);
449 }
450 }
451
452 // Go back to applying skip tags, even if we rerolled through a Voucher because there will be no
453 // Tags to activate and the state will immediately go back to blind selection
454 else if (s_timer >= (MENU_POP_OUT_ANIM_FRAMES + panel_move_duration))
455 {
456 s_timer = TM_ZERO;
457 state_machine_change_state(&blind_select_sm, APPLY_BLIND_TAGS);
458 }
459}
460
461static void blind_select_reroll_boss_anim_seq_on_exit(void)
462{
464 blind_select_print_blinds_reqs_and_rewards();
465}
466
467static void blind_select_selected_anim_seq_on_init(void)
468{
469 if (blind_skip_tags[0] != NULL)
470 {
471 sprite_object_set_target((SpriteObject*)blind_skip_tags[0], SMALL_BLIND_SKIP_TAG_INIT_POS);
472 }
473 if (blind_skip_tags[1] != NULL)
474 {
475 sprite_object_set_target((SpriteObject*)blind_skip_tags[1], BIG_BLIND_SKIP_TAG_INIT_POS);
476 }
477}
478
479static void blind_select_selected_anim_seq_on_update(void)
480{
481 if (s_timer < 15)
482 {
483 Rect blinds_rect = POP_MENU_ANIM_RECT;
484 blinds_rect.top -= 1; // Because of the raised blind
485 main_bg_se_move_rect_1_tile_vert(blinds_rect, SCREEN_DOWN);
486
487 for (int i = 0; i < NUM_BLINDS_PER_ANTE; i++)
488 {
490 blind_select_tokens[i],
491 blind_select_tokens[i]->pos.x,
492 blind_select_tokens[i]->pos.y + TILE_SIZE
493 );
494 }
495 }
496 else if (s_timer >= MENU_POP_OUT_ANIM_FRAMES)
497 {
498 for (int i = 0; i < NUM_BLINDS_PER_ANTE; i++)
499 {
500 sprite_hide(blind_select_tokens[i]);
501 }
502
503 s_timer = TM_ZERO;
504 state_machine_change_state(&blind_select_sm, DISPLAY_BLIND_PANEL);
505 }
506}
507
508static void blind_select_selected_anim_seq_on_exit(void)
509{
510 for (int i = 0; i < NUM_BLINDS_PER_ANTE; i++)
511 {
512 sprite_hide(blind_select_tokens[i]);
513 }
514
515 // Destroy the current blind's skip tag if we are starting Small or Big blind.
516 // This way, both SkipTag pointers will be NULL by the end of the Ante
517 if (g_game_vars.current_blind <= BLIND_TYPE_BIG)
518 {
519 skip_tag_destroy(&blind_skip_tags[g_game_vars.current_blind]);
520 }
521}
522
523static void blind_select_display_blind_panel()
524{
525 if (s_timer >= TM_DISP_BLIND_PANEL_FINISH)
526 {
527 state_machine_change_state(&blind_select_sm, BLIND_SELECT_EXIT);
528 return;
529 }
530
531 // Switches to the selecting background and clears the blind panel area
532 if (s_timer == TM_DISP_BLIND_PANEL_START)
533 {
534 change_background(BG_CARD_SELECTING, false);
535
536 // Need to clear the top left panel as a side effect of change_background()
537 main_bg_se_copy_expand_3w_row(TOP_LEFT_PANEL_ANIM_RECT, TOP_LEFT_PANEL_EMPTY_3W_ROW_POS);
538
540
541 if (g_game_vars.current_blind >= BLIND_TYPE_BOSS)
542 {
544 blind_get_color(g_game_vars.current_blind, BLIND_SHADOW_COLOR_INDEX)
545 );
546 }
547 }
548
549 // Shift the blind panel down onto screen
550 for (int y = 0; y < s_timer; y++)
551 {
552 int y_from = 26 + y - s_timer;
553 int y_to = 0 + y;
554
555 Rect from = {0, y_from, 8, y_from};
556 BG_POINT to = {0, y_to};
557
558 main_bg_se_copy_rect(from, to);
559 }
560}
561
562static void blind_select_exit(void)
563{
564 change_background(BG_NONE, false);
565 game_change_state(GAME_STATE_ROUND);
566}
567
568static Rect blind_select_get_req_score_rect(enum BlindTokens blind)
569{
570 Rect blind_req_score_rect = SINGLE_BLIND_SEL_REQ_SCORE_RECT;
571
572 int score_rect_x_offset = blind * rect_width(&SINGLE_BLIND_SELECT_RECT) * TILE_SIZE;
573 blind_req_score_rect.left += score_rect_x_offset;
574 blind_req_score_rect.right += score_rect_x_offset;
575
576 if (g_game_vars.blinds_states[blind] == BLIND_STATE_CURRENT)
577 {
578 // Current blind is raised
579 blind_req_score_rect.top -= TILE_SIZE;
580 blind_req_score_rect.bottom -= TILE_SIZE;
581 }
582
583 return blind_req_score_rect;
584}
585
586static enum BlindType get_blind_type_from_token(enum BlindTokens blind)
587{
588 enum BlindType blind_type;
589 switch (blind)
590 {
591 case SMALL_BLIND:
592 blind_type = BLIND_TYPE_SMALL;
593 break;
594 case BIG_BLIND:
595 blind_type = BLIND_TYPE_BIG;
596 break;
597 default:
598 blind_type = g_game_vars.next_boss_blind;
599 break;
600 }
601 return blind_type;
602}
603
604static inline void blind_select_print_blind_req(enum BlindTokens blind)
605{
606 Rect blind_req_score_rect = blind_select_get_req_score_rect(blind);
607
608 u32 blind_req = blind_get_requirement(get_blind_type_from_token(blind), g_game_vars.ante);
609
610 char blind_req_str_buff[UINT_MAX_DIGITS + 1];
612 blind_req,
613 rect_width(&blind_req_score_rect) / TTE_CHAR_SIZE,
614 blind_req_str_buff
615 );
616
617 update_text_rect_to_right_align_str(&blind_req_score_rect, blind_req_str_buff, OVERFLOW_RIGHT);
618
619 tte_printf(
620 "#{P:%d,%d; cx:0x%X000}%s",
621 blind_req_score_rect.left,
622 blind_req_score_rect.top,
623 TTE_RED_PB,
624 blind_req_str_buff
625 );
626}
627
628static inline void blind_select_print_blind_reward(enum BlindTokens blind)
629{
630 int blind_reward = blind_get_reward(get_blind_type_from_token(blind));
631 Rect blind_reward_rect = blind_select_get_req_score_rect(blind);
632
633 // The reward is right below the score.
634 blind_reward_rect.top += TILE_SIZE;
635 blind_reward_rect.bottom += TILE_SIZE;
636
637 char blind_reward_str_buff[UINT_MAX_DIGITS + 2]; // +2 for null terminator and "$"
638 snprintf(blind_reward_str_buff, sizeof(blind_reward_str_buff), "$%d", blind_reward);
639
640 update_text_rect_to_right_align_str(&blind_reward_rect, blind_reward_str_buff, OVERFLOW_RIGHT);
641
642 tte_printf(
643 "#{P:%d,%d; cx:0x%X000}%s",
644 blind_reward_rect.left,
645 blind_reward_rect.top,
646 TTE_YELLOW_PB,
647 blind_reward_str_buff
648 );
649}
650
651static void blind_select_print_blinds_reqs_and_rewards(void)
652{
653 for (enum BlindTokens curr_blind = 0; curr_blind < NUM_BLINDS_PER_ANTE; curr_blind++)
654 {
655 blind_select_print_blind_req(curr_blind);
656 blind_select_print_blind_reward(curr_blind);
657 }
658}
659
660static void blind_tokens_init()
661{
662 if (g_game_vars.current_blind == BLIND_TYPE_SMALL)
663 reroll_boss_blind();
664
665 sprite_destroy(&blind_select_tokens[SMALL_BLIND]);
666 sprite_destroy(&blind_select_tokens[BIG_BLIND]);
667 sprite_destroy(&blind_select_tokens[BOSS_BLIND]);
668
669 blind_select_tokens[SMALL_BLIND] = blind_token_new(
670 BLIND_TYPE_SMALL,
671 CUR_BLIND_TOKEN_POS.x,
672 CUR_BLIND_TOKEN_POS.y,
673 SMALL_BLIND_TOKEN_LAYER
674 );
675 blind_select_tokens[BIG_BLIND] = blind_token_new(
676 BLIND_TYPE_BIG,
677 CUR_BLIND_TOKEN_POS.x,
678 CUR_BLIND_TOKEN_POS.y,
679 BIG_BLIND_TOKEN_LAYER
680 );
681 blind_select_tokens[BOSS_BLIND] = blind_token_new(
682 g_game_vars.next_boss_blind,
683 CUR_BLIND_TOKEN_POS.x,
684 CUR_BLIND_TOKEN_POS.y,
685 BOSS_BLIND_TOKEN_LAYER
686 );
687
688 for (int i = 0; i < NUM_BLINDS_PER_ANTE; i++)
689 {
690 sprite_hide(blind_select_tokens[i]);
691 }
692}
693
694static void blind_skip_tags_init(void)
695{
696 if (g_game_vars.current_blind != BLIND_TYPE_SMALL)
697 return;
698
699 skip_tag_destroy(&blind_skip_tags[0]);
700 skip_tag_destroy(&blind_skip_tags[1]);
701
702 blind_skip_tags[0] = roll_skip_tag();
703 blind_skip_tags[1] = roll_skip_tag();
704
705 skip_tag_set_sprite(blind_skip_tags[0], SMALL_BLIND_SKIP_TAG_LAYER);
706 skip_tag_set_sprite(blind_skip_tags[1], BIG_BLIND_SKIP_TAG_LAYER);
707
709 (SpriteObject*)blind_skip_tags[0],
710 SMALL_BLIND_SKIP_TAG_INIT_POS.x,
711 SMALL_BLIND_SKIP_TAG_INIT_POS.y
712 );
714 (SpriteObject*)blind_skip_tags[1],
715 BIG_BLIND_SKIP_TAG_INIT_POS.x,
716 BIG_BLIND_SKIP_TAG_INIT_POS.y
717 );
718}
719
721{
722 s_timer = TM_ZERO;
723 state_machine_register(&blind_select_sm);
724 state_machine_change_state(&blind_select_sm, START_ANIM_SEQ);
725
726 blind_select_selection_grid.selection = BLIND_SELECT_INIT_SEL;
727
728 blind_tokens_init();
729
730 if (g_game_vars.current_blind == BLIND_TYPE_SMALL)
731 blind_skip_tags_init();
732
733 // TODO: silly bug rn, the sprite tokens are unhidden on a background change.
734 // this probably shouldn't be here. also need to force redraw or the callback
735 // doesn't run that moves the tokens. that should probably happen here.
736 change_background(BG_BLIND_SELECT, true);
737
738 // "SELECT" button will always be selected by default
739 button_set_highlight(&blind_select_buttons[BLIND_ROW], true);
740 button_set_highlight(&blind_select_buttons[SKIP_ROW], false);
741
743}
744
746{
747 s_timer++;
748}
749
751{
752 // For some reason that I haven't figured out yet,
753 // if I don't destroy the blind tokens they won't
754 // show up on the next run.
755 sprite_destroy(&blind_select_tokens[SMALL_BLIND]);
756 sprite_destroy(&blind_select_tokens[BIG_BLIND]);
757 sprite_destroy(&blind_select_tokens[BOSS_BLIND]);
758
759 change_background(BG_NONE, false);
760
761 state_machine_remove(&blind_select_sm);
762}
763
765{
766 for (int i = 0; i < NUM_BLINDS_PER_ANTE; i++)
767 {
768 sprite_unhide(blind_select_tokens[i]);
769 }
770
771 // Default y position for the blind select tokens. 12 is the amount of tiles the background
772 // is shifted down by
773 const int default_y = 89 + (TILE_SIZE * 12);
774 // TODO refactor magic numbers '80/120/160' into a map to loop with
775 sprite_position(blind_select_tokens[SMALL_BLIND], BLIND_LEFT_X, default_y);
776 sprite_position(blind_select_tokens[BIG_BLIND], BLIND_CENTER_X, default_y);
777 sprite_position(blind_select_tokens[BOSS_BLIND], BLIND_RIGHT_X, default_y);
778
779 toggle_windows(false, true);
780
781 GRIT_CPY(pal_bg_mem, background_blind_select_gfxPal);
782 GRIT_CPY(&tile_mem[MAIN_BG_CBB], background_blind_select_gfxTiles);
783 GRIT_CPY(&se_mem[MAIN_BG_SBB], background_blind_select_gfxMap);
784
785 // Copy boss blind colors to blind select palette
786 pal_bg_mem[BOSS_BLIND_PANEL_OUTLINE_PAL_IDX] =
787 blind_get_color(g_game_vars.next_boss_blind, BLIND_BACKGROUND_MAIN_COLOR_INDEX);
788 pal_bg_mem[BOSS_BLIND_PANEL_SHADOW_PAL_IDX] =
789 blind_get_color(g_game_vars.next_boss_blind, BLIND_BACKGROUND_SHADOW_COLOR_INDEX);
790
791 for (int i = 0; i < NUM_BLINDS_PER_ANTE; i++)
792 {
793 Rect curr_blind_rect = SINGLE_BLIND_SELECT_RECT;
794
795 // There's no gap between them
796 curr_blind_rect.left += i * rect_width(&SINGLE_BLIND_SELECT_RECT);
797 curr_blind_rect.right += i * rect_width(&SINGLE_BLIND_SELECT_RECT);
798
799 if (g_game_vars.blinds_states[i] != BLIND_STATE_CURRENT &&
800 (i == BLIND_TYPE_SMALL || i == BLIND_TYPE_BIG)) // Make the skip button gray
801 {
802 BG_POINT skip_blind_btn_pos_dest = {
803 BLIND_SKIP_BTN_PREANIM_DEST_RECT.left,
804 BLIND_SKIP_BTN_PREANIM_DEST_RECT.top
805 };
806 skip_blind_btn_pos_dest.x = curr_blind_rect.left;
807
808 Rect skip_blind_btn_rect_src = BLIND_SKIP_BTN_GRAY_RECT;
809 skip_blind_btn_rect_src.top += i * rect_height(&BLIND_SKIP_BTN_GRAY_RECT);
810 skip_blind_btn_rect_src.bottom += i * rect_height(&BLIND_SKIP_BTN_GRAY_RECT);
811
812 main_bg_se_copy_rect(skip_blind_btn_rect_src, skip_blind_btn_pos_dest);
813 }
814
815 switch (g_game_vars.blinds_states[i])
816 {
817 case BLIND_STATE_CURRENT: // Raise the blind panel up a bit
818 {
819 // TODO: Replace copies with main_bg_se_copy_rect() of named rects
820 int x_from = 0;
821 int y_from = 27;
822
823 main_bg_se_copy_rect_1_tile_vert(curr_blind_rect, SCREEN_UP);
824
825 int x_to = curr_blind_rect.left;
826 int y_to = 31;
827
828 if (i == BLIND_TYPE_BIG)
829 {
830 y_from = 31;
831 }
832 else if (i > BLIND_TYPE_BIG)
833 {
834 x_from = x_to;
835 y_from = 30;
836 }
837
838 // Copy plain tiles onto the bottom of the raised blind panel to fill the gap
839 // created by the raise
840 Rect gap_fill_rect =
841 {x_from, y_from, x_from + rect_width(&SINGLE_BLIND_SELECT_RECT) - 1, y_from};
842 BG_POINT gap_fill_point = {x_to, y_to};
843 main_bg_se_copy_rect(gap_fill_rect, gap_fill_point);
844
845 // Move token up by a tile
847 blind_select_tokens[i],
848 blind_select_tokens[i]->pos.x,
849 blind_select_tokens[i]->pos.y - TILE_SIZE
850 );
851 break;
852 }
853 case BLIND_STATE_UPCOMING: // Change the select icon to "NEXT"
854 {
855 int x_from = 0;
856 int y_from = 20;
857
858 int x_to = 10 + (i * rect_width(&SINGLE_BLIND_SELECT_RECT));
859 int y_to = 20;
860
861 memcpy16(
862 &se_mem[MAIN_BG_SBB][x_to + 32 * y_to],
863 &se_mem[MAIN_BG_SBB][x_from + 32 * y_from],
864 3
865 );
866 break;
867 }
868 case BLIND_STATE_SKIPPED: // Change the select icon to "SKIP"
869 {
870 int x_from = 3;
871 int y_from = 20;
872
873 int x_to = 10 + (i * 5);
874 int y_to = 20;
875
876 memcpy16(
877 &se_mem[MAIN_BG_SBB][x_to + 32 * y_to],
878 &se_mem[MAIN_BG_SBB][x_from + 32 * y_from],
879 3
880 );
881 break;
882 }
883 case BLIND_STATE_DEFEATED: // Change the select icon to "DEFEATED"
884 {
885 int x_from = 6;
886 int y_from = 20;
887
888 int x_to = 10 + (i * 5);
889 int y_to = 20;
890
891 memcpy16(
892 &se_mem[MAIN_BG_SBB][x_to + 32 * y_to],
893 &se_mem[MAIN_BG_SBB][x_from + 32 * y_from],
894 3
895 );
896 break;
897 }
898 default:
899 break;
900 }
901 }
902}
Utilities for affine background on GBA.
void affine_background_set_color(COLOR color)
Update the affine background color.
Utilities for using maxmod to play sound effects.
#define MM_BASE_PITCH_RATE
The default pitch rate for the sound effect played.
Definition audio_utils.h:37
void play_sfx(mm_word id, mm_word rate, mm_byte volume)
Play a sound effect, wrapper for mmEffectEx() See https://maxmod.org/ref/functions/mm_sound_effect....
#define SFX_DEFAULT_VOLUME
Default volume for sound effects.
Definition audio_utils.h:43
Data structures and functions relative to the behaviour and graphics of Blinds.
void apply_blind_tiles(enum BlindType type, enum BlindTokenLayers layer)
Change the tiles of the BlindToken Sprite at a given layer to that of the given BlindType.
Definition blind.c:249
enum BlindType roll_blind_type(bool showdown)
Choose a random Blind among the ones that haven't been beaten yet.
Definition blind.c:157
BlindType
All Blind types in the game are listed here.
Definition blind.h:69
BlindState
All the possible states an Ante's Blinds can be in when viewed in the "Blind Select" screen.
Definition blind.h:121
u16 blind_get_color(enum BlindType type, enum BlindColorIndex index)
Get the color associated with a given Blind.
Definition blind.c:213
u32 blind_get_requirement(enum BlindType type, int ante)
Get the score required to beat a certain blind (either the Small, Big, or any Boss/Showdown blind) at...
Definition blind.c:101
BlindTokens
The sprites that display the blinds when in "GAME_BLIND_SELECT" state. There are only 3 blinds per An...
Definition blind.h:21
Sprite * blind_token_new(enum BlindType type, int x, int y, enum BlindTokenLayers sprite_index)
Create a new BlindToken sprite.
Definition blind.c:263
int blind_get_reward(enum BlindType type)
Get the amount of money gained from beating a certain Blind.
Definition blind.c:110
Blind select state functions.
void blind_select_on_update(void)
Blind select state update.
void blind_select_reroll_boss_from_menu(void)
Reroll the Boss Blind from inside the menu. This function is made to be used for both Vouchers and th...
void blind_select_on_exit(void)
Blind select cleanup.
void blind_select_on_init(void)
Blind select state initialization.
void blind_select_change_background(void)
Change to the blind select background.
A button structure containing the common button functionalities Including highlight and pressing func...
void button_press(Button *button)
Execute a button press, by calling the button's on_pressed function. This checks the button's can_be_...
Definition button.c:18
void button_set_highlight(Button *button, bool highlight)
Set the button highlight on or off.
Definition button.c:8
void change_background(enum BackgroundId id, bool force_redraw)
Definition common_ui.c:35
void reset_top_left_panel_bottom_row(void)
Restores the bottom row of the top-left panel from the background map.
Definition common_ui.c:48
Game global game variables struct definition.
Graphic utility functions.
void main_bg_se_move_rect_1_tile_vert(Rect se_rect, enum ScreenVertDir direction)
Moves a rect in the main background vertically in direction by a single tile.
INLINE int rect_height(const Rect *rect)
Get the height of a rectangle.
void main_bg_se_copy_rect_1_tile_vert(Rect se_rect, enum ScreenVertDir direction)
Copies a rect in the main background vertically in direction by a single tile.
void main_bg_se_copy_rect(Rect se_rect, BG_POINT dest_pos)
Copies a rect in the main background from se_rect to the position (x, y).
void toggle_windows(bool win0, bool win1)
Toggles the visibility of the window layers.
void update_text_rect_to_right_align_str(Rect *rect, const char *str, enum OverflowDir overflow_direction)
Changes rect->left so it fits the digits of num exactly when right aligned to rect->right....
#define TILE_SIZE
Tile size in pixels, both height and width as tiles are square.
#define TTE_CHAR_SIZE
By default TTE characters occupy a single tile.
void tte_erase_rect_wrapper(Rect rect)
A wrapper for tte_erase_rect that would use the rect struct.
void main_bg_se_copy_expand_3w_row(Rect se_dest_rect, BG_POINT src_row_left_pnt)
Copies a 3-width SE rect and expands it to fit a passed rect.
INLINE int rect_width(const Rect *rect)
Get the width of a rectangle.
Header of shared rects and points needed for ui.
An implementation for a selection grid that handles directional selection.
void selection_grid_move_selection_vert(SelectionGrid *selection_grid, int direction_tribool)
Moves the selection vertically within the selection grid.
void selection_grid_process_input(SelectionGrid *selection_grid)
Processes user input for the selection grid.
Data structures and functions related to the handling of Skip Tags.
void skip_tag_process_pause(void)
Suspend the processing of Skip Tags.
Definition skip_tag.c:357
void skip_tag_set_sprite(SkipTag *tag, s16 layer)
Set the sprite for the given SkipTag, according to its internal ID.
Definition skip_tag.c:94
void skip_tag_destroy(SkipTag **tag)
Destroy the given SkipTag, invalidate it in the corresponding memory pool and set the pointer provide...
Definition skip_tag.c:128
SkipTag * roll_skip_tag(void)
Create a new SkipTag with a random ID, picked by taking the Ante into account, as some Tags only appe...
Definition skip_tag.c:154
void skip_tag_process_start(enum SkipTagEvent checked_tag_event)
Start Skip Tag processing for the specified event.
Definition skip_tag.c:344
enum SkipTagProcStage skip_tag_process_get_proc_stage(void)
Recover the current stage of the Tag processing.
Definition skip_tag.c:339
void skip_tag_process_resume(void)
Resume the processing of Skip Tags.
Definition skip_tag.c:366
void add_skip_tag(SkipTag **blind_tag)
Adds the SkipTag to the owned list, snaps it into position and sets the original pointer to NULL.
Definition skip_tag.c:230
Sprite system for Gbalatro.
INLINE void sprite_position(Sprite *sprite, int x, int y)
Set sprite position. Inlined for efficiency.
Definition sprite.h:466
void sprite_object_set_target(SpriteObject *sprite_object, BG_POINT to)
Set a SpriteObject's target position so that is slides across the screen between from the current pos...
Definition sprite.c:429
INLINE void sprite_object_position(SpriteObject *sprite_object, int x, int y)
Set sprite_object position. Inlined for efficiency.
Definition sprite.h:483
void sprite_destroy(Sprite **sprite)
Destroy Sprite.
Definition sprite.c:134
void sprite_hide(Sprite *sprite)
Hides the sprite by manipulating ATTR0_HIDE in OAM.
Definition sprite.c:222
void sprite_unhide(Sprite *sprite)
Unhides the sprite by manipulating ATTR0_HIDE in OAM. The sprite's ATTR0_MODE is maintained from the ...
Definition sprite.c:229
State Machine.
void state_machine_remove(StateMachine *state_machine)
Remove a statemachine's update function.
void state_machine_register(StateMachine *state_machine)
Register a statemachine to run it's update function once per frame.
void state_machine_change_state(StateMachine *state_machine, int new_state)
Calls the current state's on_exit, the new state's on_init, and sets the active update fn.
A button representation.
Definition button.h:31
The core selection grid struct, represents the grid itself and its state.
A sprite object is a sprite that is focusable and movable in animation.
Definition sprite.h:86
Sprite struct for GBA hardware specifics.
Definition sprite.h:54
POINT pos
Sprite position on screen in pixels.
Definition sprite.h:68
State machine callbacks.
State machine instance.
Timer constants.
Utilities relating around number string representation and protected arithmatic helper functions.
void truncate_uint_to_suffixed_str(uint32_t num, int num_req_chars, char out_str_buff[UINT_MAX_DIGITS+1])
Truncate an unsigned number into a suffixed string representation e.g. 12000 -> "12K" The least signi...
Definition util.c:116