#include "th01/math/clamp.hpp" #include "th01/hardware/egc.h" #include "th01/hardware/frmdelay.h" #include "th01/hardware/grp2xscs.hpp" #include "th01/v_colors.hpp" #include "th01/score.h" #include "th01/hiscore/regist.hpp" // Null-terminated version of scoredat_name_t, used internally. typedef ShiftJISKanjiBuffer scoredat_name_z_t; // Byte-wise access to [scoredat_routes]. inline int8_t& scoredat_route_byte(int place, int byte) { if(byte == 0) { return scoredat_routes[place * SCOREDAT_ROUTE_LEN]; } return scoredat_routes[(place * SCOREDAT_ROUTE_LEN) + byte]; } #define TITLE_LEFT 48 #define TITLE_TOP 0 static const screen_x_t TITLE_BACK_LEFT = 0; static const screen_y_t TITLE_BACK_TOP = (RES_Y - GLYPH_H); static const pixel_t TITLE_BACK_W = 288; static const pixel_t TITLE_BACK_H = GLYPH_H; static const screen_x_t TITLE_BACK_RIGHT = (TITLE_BACK_LEFT + TITLE_BACK_W); static const screen_y_t TITLE_BACK_BOTTOM = (TITLE_BACK_TOP + TITLE_BACK_H); /// Table /// ----- #define TABLE_TOP 48 #define TABLE_ROW_H GLYPH_H #define TABLE_BODY_TOP (TABLE_TOP + TABLE_ROW_H) #define table_row_top(place) (TABLE_BODY_TOP + (place * TABLE_ROW_H)) // Recursively defining column positions \o/ inline screen_x_t table_left(int x_kanji) { return 32 + (x_kanji * GLYPH_FULL_W); } inline screen_x_t table_place_left(int x_kanji) { return table_left(x_kanji); } inline screen_x_t table_name_left(int x_kanji) { return table_place_left(7 + x_kanji); } inline screen_x_t table_score_left(int x_kanji) { return table_name_left(SCOREDAT_NAME_KANJI + 5 + x_kanji); } inline screen_x_t table_stage_route_left(int x_kanji) { return table_score_left(SCORE_DIGITS + 3 + x_kanji); } inline screen_x_t table_stage_left(int x_kanji) { return table_stage_route_left(1 + x_kanji); } // Code generation actually prohibits this from being a Point! static screen_x_t entered_name_left; static screen_y_t entered_name_top; /// ----- /// Alphabet /// -------- #define ALPHABET_TOP 240 #define MARGIN_W 32 #define KANJI_PADDING_X 16 #define KANJI_PADDING_Y 8 #define KANJI_PADDED_W (GLYPH_FULL_W + KANJI_PADDING_X) #define KANJI_PADDED_H (GLYPH_H + KANJI_PADDING_Y) #define KANJI_PER_ROW \ ((RES_X - (MARGIN_W * 2)) / KANJI_PADDED_W) #define relative_top(row) \ ((row) * KANJI_PADDED_H) inline pixel_t relative_top_for(int kanji_id) { return relative_top(kanji_id / KANJI_PER_ROW); } inline int row_ceil(int count) { return ((count + (KANJI_PER_ROW - 1)) / KANJI_PER_ROW); } inline pixel_t left_for(int kanji_id) { return (((kanji_id % KANJI_PER_ROW) * KANJI_PADDED_W) + MARGIN_W); } #define alphabet_putca_fx(top_for_0, i, fx, kanji) { \ graph_putkanji_fx( \ left_for(i), (top_for_0 + relative_top_for(i)), fx, kanji \ ); \ } #define alphabet_putsa_fx(top_for_0, i, fx, str) { \ graph_printf_fx(left_for(i), (top_for_0 + relative_top_for(i)), fx, str); \ } #define A_TO_Z_COUNT 26 #define NUM_COUNT 10 static const int SYM_COUNT = (sizeof(ALPHABET_SYMS) / sizeof(shiftjis_kanji_t)); // Rows #define LOWER_TOP (ALPHABET_TOP) #define LOWER2_TOP (LOWER_TOP + relative_top(1)) #define UPPER_TOP (LOWER_TOP + relative_top(row_ceil(A_TO_Z_COUNT))) #define UPPER2_TOP (UPPER_TOP + relative_top(1)) #define SYM_TOP (UPPER_TOP + relative_top(row_ceil(A_TO_Z_COUNT))) #define NUM_TOP (SYM_TOP + relative_top(row_ceil(SYM_COUNT))) // Columns #define A_TO_Z_2_END_LEFT left_for(A_TO_Z_COUNT - KANJI_PER_ROW) #define SPACE_LEFT left_for(NUM_COUNT) #define LEFT_COLUMN (KANJI_PER_ROW - 3) #define LEFT_LEFT left_for(LEFT_COLUMN + 0) #define RIGHT_LEFT left_for(LEFT_COLUMN + 1) #define ENTER_LEFT left_for(LEFT_COLUMN + 2) inline uint8_t kanji_hi(sshiftjis_kanji_swapped_t kanji) { return (kanji >> 8); } inline uint8_t kanji_lo(sshiftjis_kanji_swapped_t kanji) { return (kanji & 0xFF); } /// -------- void alphabet_put_initial() { int16_t col_and_fx = (COL_REGIST_REGULAR | FX_WEIGHT_BOLD); shiftjis_kanji_swapped_t kanji; int i; graph_putkanji_fx_declare(); kanji = KANJI_b; for(i = 1; i < A_TO_Z_COUNT; i++) { alphabet_putca_fx(LOWER_TOP, i, col_and_fx, kanji); kanji++; } graph_putsa_fx( MARGIN_W, LOWER_TOP, (COL_REGIST_SELECTED | FX_WEIGHT_BOLD | FX_REVERSE), ALPHABET_A ); kanji = KANJI_A; for(i = 0; i < A_TO_Z_COUNT; i++) { alphabet_putca_fx(UPPER_TOP, i, col_and_fx, kanji); kanji++; } for(i = 0; i < SYM_COUNT; i++) { kanji = ALPHABET_SYMS[i]; alphabet_putca_fx(SYM_TOP, i, col_and_fx, kanji); } kanji = KANJI_0; for(i = 0; i < NUM_COUNT; i++) { alphabet_putca_fx(NUM_TOP, i, col_and_fx, kanji); kanji++; } alphabet_putsa_fx(NUM_TOP, i, col_and_fx, ALPHABET_SPACE); i = LEFT_COLUMN; alphabet_putsa_fx(NUM_TOP, i, col_and_fx, ALPHABET_LEFT); i++; alphabet_putsa_fx(NUM_TOP, i, col_and_fx, ALPHABET_RIGHT); i++; alphabet_putsa_fx(NUM_TOP, i, col_and_fx, ALPHABET_ENTER); i++; } inline void header_cell_put(screen_x_t left, const char str[]) { graph_putsa_fx( left, TABLE_TOP, (COL_REGIST_SELECTED | FX_WEIGHT_BLACK), str ); } #define place_cell_put(top, col_and_fx, str) \ graph_putsa_fx(table_place_left(0), top, col_and_fx, str) void regist_put_initial( int entered_place, long entered_score, int entered_stage, const sshiftjis_t entered_route[SCOREDAT_ROUTE_LEN + 1], const scoredat_name_z_t names_z[SCOREDAT_PLACES] ) { const unsigned char name_blank[SCOREDAT_NAME_BYTES + 1] = REGIST_NAME_BLANK; graph_accesspage_func(0); header_cell_put(table_place_left(0), REGIST_HEADER_PLACE); header_cell_put(table_name_left(0), REGIST_HEADER_NAME); header_cell_put(table_score_left(0), REGIST_HEADER_SCORE); header_cell_put(table_stage_route_left(0), REGIST_HEADER_STAGE_ROUTE); for(int i = 0; i < SCOREDAT_PLACES; i++) { #define stage_expr(i, entered_place, expr) \ (i != entered_place && scoredat_stages[i] expr) || \ (i == entered_place && entered_stage expr) #define col_and_fx_text (place_col | FX_WEIGHT_BOLD) #if (BINARY == 'E') #define place_col ((i == entered_place) \ ? COL_REGIST_SELECTED \ : COL_REGIST_REGULAR \ ) #define top table_row_top(i) #else int place_col = ( (i == entered_place) ? COL_REGIST_SELECTED : COL_REGIST_REGULAR ); vram_y_t top = table_row_top(i); #endif switch(i) { case 0: place_cell_put(top, col_and_fx_text, REGIST_PLACE_0); break; case 1: place_cell_put(top, col_and_fx_text, REGIST_PLACE_1); break; case 2: place_cell_put(top, col_and_fx_text, REGIST_PLACE_2); break; case 3: place_cell_put(top, col_and_fx_text, REGIST_PLACE_3); break; case 4: place_cell_put(top, col_and_fx_text, REGIST_PLACE_4); break; case 5: place_cell_put(top, col_and_fx_text, REGIST_PLACE_5); break; case 6: place_cell_put(top, col_and_fx_text, REGIST_PLACE_6); break; case 7: place_cell_put(top, col_and_fx_text, REGIST_PLACE_7); break; case 8: place_cell_put(top, col_and_fx_text, REGIST_PLACE_8); break; case 9: place_cell_put(top, col_and_fx_text, REGIST_PLACE_9); break; } graph_putsa_fx( table_name_left(0), top, col_and_fx_text, (i == entered_place) ? name_blank : names_z[i].byte ); graph_putfwnum_fx( table_score_left(0), top, (place_col | FX_WEIGHT_BLACK), SCORE_DIGITS, (entered_place == i) ? entered_score : scoredat_score[i], 0, false ); if(stage_expr(i, entered_place, < SCOREDAT_CLEARED)) { graph_putfwnum_fx( table_stage_left(0), top, col_and_fx_text, 2, (i == entered_place) ? entered_stage : scoredat_stages[i], 0, false ); } else if(stage_expr(i, entered_place, == SCOREDAT_CLEARED_MAKAI)) { graph_putsa_fx( table_stage_left(0), top, col_and_fx_text, REGIST_STAGE_MAKAI ); } else if(stage_expr(i, entered_place, == SCOREDAT_CLEARED_JIGOKU)) { graph_putsa_fx( table_stage_left(0), top, col_and_fx_text, REGIST_STAGE_JIGOKU ); } graph_putsa_fx( table_stage_left(2), top, col_and_fx_text, REGIST_STAGE_ROUTE_DASH ); regist_route_put( table_stage_left(3), top, col_and_fx_text, (i == entered_place) ? entered_route[0] : scoredat_route_byte(i, 0), (i == entered_place) ? entered_route[1] : scoredat_route_byte(i, 1) ); if(entered_place == i) { entered_name_left = table_name_left(0); entered_name_top = table_row_top(i); } #undef top #undef place_col #undef col_and_fx_text #undef stage_expr } } #define alphabet_left_to_column(left) \ ((left - left_for(0)) / KANJI_PADDED_W) #define alphabet_if(kanji, left, top, on_space, on_left, on_right, on_enter) \ if(top == LOWER_TOP) { \ kanji = (KANJI_a + alphabet_left_to_column(left)); \ } else if(top == LOWER2_TOP) { \ kanji = (KANJI_a + alphabet_left_to_column(left)) + KANJI_PER_ROW; \ } else if(top == UPPER_TOP) { \ kanji = (KANJI_A + alphabet_left_to_column(left)); \ } else if(top == UPPER2_TOP) { \ kanji = (KANJI_A + alphabet_left_to_column(left)) + KANJI_PER_ROW; \ } else if(top == SYM_TOP) { \ kanji = ALPHABET_SYMS[alphabet_left_to_column(left)]; \ } else if(top == NUM_TOP && left < SPACE_LEFT) { \ kanji = (KANJI_0 + alphabet_left_to_column(left)); \ } else if((top == NUM_TOP) && (left == SPACE_LEFT)) { \ on_space \ } else if((top == NUM_TOP) && (left == LEFT_LEFT)) { \ on_left \ } else if((top == NUM_TOP) && (left == RIGHT_LEFT)) { \ on_right \ } else if((top == NUM_TOP) && (left == ENTER_LEFT)) { \ on_enter \ } void alphabet_put_at(screen_x_t left, screen_y_t top, bool16 is_selected) { sshiftjis_kanji_swapped_t kanji = '\0'; egc_copy_rect_1_to_0_16(left, top, KANJI_PADDED_W, GLYPH_H); int16_t col_and_fx = (FX_WEIGHT_BOLD | ( !is_selected ? COL_REGIST_REGULAR : (FX_REVERSE | COL_REGIST_SELECTED) )); alphabet_if(kanji, left, top, { graph_printf_fx(left, top, col_and_fx, ALPHABET_SPACE); }, { graph_printf_fx(left, top, col_and_fx, ALPHABET_LEFT); }, { graph_printf_fx(left, top, col_and_fx, ALPHABET_RIGHT); }, { graph_printf_fx(left, top, col_and_fx, ALPHABET_ENTER); } ); if(kanji != '\0') { graph_putkanji_fx_declare(); graph_putkanji_fx(left, top, col_and_fx, kanji); } } #if (BINARY == 'M') bool regist_jump_to_enter = false; #endif #define set_kanji_at(name, pos, kanji) { \ name[(pos * 2) + 0] = kanji_hi(kanji); \ name[(pos * 2) + 1] = kanji_lo(kanji); \ } int regist_on_shot( screen_x_t left, screen_y_t top, scoredat_name_z_t &entered_name, int &entered_name_cursor ) { shiftjis_kanji_swapped_t kanji = '\0'; shiftjis_t cursor_str[SCOREDAT_NAME_BYTES + 1] = REGIST_NAME_SPACES; alphabet_if(kanji, left, top, { kanji = KANJI_SP; }, { clamp_dec(entered_name_cursor, 0); }, { clamp_inc(entered_name_cursor, (SCOREDAT_NAME_KANJI - 1)); }, { return 1; } ); if(kanji != '\0') { set_kanji_at(entered_name.ubyte, entered_name_cursor, kanji); #if (BINARY == 'M') if(entered_name_cursor == (SCOREDAT_NAME_KANJI - 1)) { regist_jump_to_enter = true; } #endif if(entered_name_cursor < (SCOREDAT_NAME_KANJI - 1)) { entered_name_cursor++; } } egc_copy_rect_1_to_0_16( entered_name_left, entered_name_top, (SCOREDAT_NAME_KANJI * GLYPH_FULL_W), GLYPH_H ); graph_printf_s_fx( entered_name_left, entered_name_top, (COL_REGIST_SELECTED | FX_WEIGHT_BOLD), entered_name.ubyte ); set_kanji_at(cursor_str, entered_name_cursor, KANJI_UNDERSCORE); graph_printf_s_fx( entered_name_left, entered_name_top, COL_REGIST_SELECTED, cursor_str ); return 0; } enum regist_input_ret_t { RI_REGULAR = 0, RI_ENTER = 1, RI_NONE = 2, _regist_input_ret_t_FORCE_INT16 = 0x7FFF }; regist_input_ret_t regist_on_input( screen_x_t &left, screen_y_t &top, scoredat_name_z_t& entered_name, int& entered_name_cursor ) { if(input_up == true) { alphabet_put_at(left, top, false); top -= KANJI_PADDED_H; if(top < LOWER_TOP) { top = NUM_TOP; } if(top == LOWER2_TOP && left >= A_TO_Z_2_END_LEFT) { top = LOWER_TOP; } if(top == UPPER2_TOP && left >= A_TO_Z_2_END_LEFT) { top = UPPER_TOP; } if(top == NUM_TOP && left > SPACE_LEFT && left < LEFT_LEFT) { top = SYM_TOP; } alphabet_put_at(left, top, true); input_up = false; } else if(input_down == true) { alphabet_put_at(left, top, false); top += KANJI_PADDED_H; if(top > NUM_TOP) { top = LOWER_TOP; } if(top == LOWER2_TOP && left >= A_TO_Z_2_END_LEFT) { top = UPPER_TOP; } if(top == UPPER2_TOP && left >= A_TO_Z_2_END_LEFT) { top = SYM_TOP; } if(top == NUM_TOP && left > SPACE_LEFT && left < LEFT_LEFT) { top = LOWER_TOP; } alphabet_put_at(left, top, true); input_down = false; } else if(input_lr == INPUT_LEFT) { alphabet_put_at(left, top, false); left -= KANJI_PADDED_W; if(left < left_for(0)) { left = left_for(KANJI_PER_ROW - 1); } if(top == LOWER2_TOP && left >= A_TO_Z_2_END_LEFT) { left = left_for(A_TO_Z_COUNT - 1); } if(top == UPPER2_TOP && left >= A_TO_Z_2_END_LEFT) { left = left_for(A_TO_Z_COUNT - 1); } if(top == NUM_TOP && left > SPACE_LEFT && left < LEFT_LEFT) { left = SPACE_LEFT; } alphabet_put_at(left, top, true); input_lr = 0; } else if(input_lr == INPUT_RIGHT) { alphabet_put_at(left, top, false); left += KANJI_PADDED_W; if(left > left_for(KANJI_PER_ROW - 1)) { left = left_for(0); } if(top == LOWER2_TOP && left >= A_TO_Z_2_END_LEFT) { left = left_for(0); } if(top == UPPER2_TOP && left >= A_TO_Z_2_END_LEFT) { left = left_for(0); } if(top == NUM_TOP && left > SPACE_LEFT && left < LEFT_LEFT) { left = LEFT_LEFT; } alphabet_put_at(left, top, true); input_lr = 0; } else if(input_shot == true) { if(regist_on_shot(left, top, entered_name, entered_name_cursor)) { return RI_ENTER; } #if (BINARY == 'M') if(regist_jump_to_enter) { alphabet_put_at(left, top, false); left = ENTER_LEFT; top = NUM_TOP; alphabet_put_at(left, top, true); regist_jump_to_enter = false; } #endif input_shot = false; } else if(input_strike == true) { regist_on_shot(LEFT_LEFT, NUM_TOP, entered_name, entered_name_cursor); input_strike = false; } else { return RI_NONE; } return RI_REGULAR; } #if (BINARY == 'E') inline void scoredat_free(void) { #else void scoredat_free(void) { #endif delete[] scoredat_names; delete[] scoredat_stages; delete[] scoredat_routes; delete[] scoredat_score; } void scoredat_save(void) { FILE* fp; const char magic[sizeof(SCOREDAT_MAGIC)] = SCOREDAT_MAGIC; // ZUN bloat char fn[16]; scoredat_fn(fn); if( (fp = fopen(fn, "wb")) == nullptr) { return; } write(fileno(fp), magic, sizeof(SCOREDAT_MAGIC) - 1); for(int i = 0; i < SCOREDAT_NAMES_SIZE; i++) { scoredat_names[i] = scoredat_name_byte_encode(scoredat_names[i]); } write(fileno(fp), scoredat_names, SCOREDAT_NAMES_SIZE); write(fileno(fp), scoredat_score, (sizeof(uint32_t) * SCOREDAT_PLACES)); write(fileno(fp), scoredat_stages, (sizeof(int16_t) * SCOREDAT_PLACES)); write( fileno(fp), scoredat_routes, (sizeof(shiftjis_kanji_t) * SCOREDAT_PLACES) ); fclose(fp); } void regist_name_enter(int entered_place) { scoredat_name_z_t entered_name; regist_input_timeout_declare(); screen_x_t left; screen_y_t top; int entered_name_cursor; int i; entered_name_cursor = 0; regist_input_timeout_reset(); left = left_for(0); top = LOWER_TOP; for(i = 0; i < SCOREDAT_NAME_BYTES; i++) { entered_name.ubyte[i] = ' '; } entered_name.ubyte[SCOREDAT_NAME_BYTES] = '\0'; input_reset_sense(); while(1) { input_sense(false); int input_ret = regist_on_input( left, top, entered_name, entered_name_cursor ); // Code generation for FUUIN.EXE forces these to be in separate // statements... if(input_ret == RI_ENTER) { break; } if(input_ok) { break; } regist_input_timeout_if_reached({ break; }); if(input_ret == RI_NONE) { continue; } frame_delay(4); regist_input_timeout_inc(); } input_ok = false; for(i = 0; i < SCOREDAT_NAME_BYTES; i++) { scoredat_names[ (entered_place * SCOREDAT_NAME_BYTES) + i ] = entered_name.ubyte[i]; } scoredat_save(); } static const int PLACE_NONE = (SCOREDAT_PLACES + 20); static const int SCOREDAT_NOT_CLEARED = (SCOREDAT_CLEARED - 10); void regist( int32_t score, int16_t stage_num_or_scoredat_constant, const sshiftjis_t route[SCOREDAT_ROUTE_LEN + 1] ) { scoredat_name_z_t names[SCOREDAT_PLACES]; const shiftjis_t* RANKS[RANK_COUNT] = REGIST_TITLE_RANKS; long place; regist_bg_put(stage_num_or_scoredat_constant); graph_accesspage_func(1); regist_title_put( TITLE_BACK_LEFT, stage_num_or_scoredat_constant, RANKS, (COL_REGIST_REGULAR | FX_WEIGHT_BOLD | FX_CLEAR_BG) ); // On page 1, the title should now at (TITLE_BACK_LEFT, TITLE_BACK_TOP) if // not cleared, or at (TITLE_BACK_LEFT, TITLE_TOP) if cleared. graph_accesspage_func(0); if(stage_num_or_scoredat_constant < SCOREDAT_NOT_CLEARED) { graph_2xscale_byterect_1_to_0_slow( TITLE_LEFT, TITLE_TOP, TITLE_BACK_LEFT, TITLE_BACK_TOP, TITLE_BACK_W, TITLE_BACK_H ); graph_move_byterect_interpage( TITLE_BACK_LEFT, TITLE_BACK_TOP, TITLE_BACK_RIGHT, TITLE_BACK_BOTTOM, TITLE_BACK_LEFT, TITLE_BACK_TOP, 0, 1 ); } else { graph_2xscale_byterect_1_to_0_slow( TITLE_LEFT, TITLE_TOP, TITLE_BACK_LEFT, TITLE_TOP, TITLE_BACK_W, TITLE_BACK_H ); } if(scoredat_load()) { return; } for(place = 0; place < SCOREDAT_PLACES; place++) { scoredat_name_get(place, names[place].ubyte); } for(place = 0; place < SCOREDAT_PLACES; place++) { if(score >= scoredat_score[place]) { break; } } input_reset_sense(); if(place < SCOREDAT_PLACES) { for(long shift = (SCOREDAT_PLACES - 1); shift > place; shift--) { strcpy(names[shift].byte, names[shift - 1].byte); scoredat_score[shift] = scoredat_score[shift - 1]; scoredat_stages[shift] = scoredat_stages[shift - 1]; scoredat_route_byte(shift, 0) = scoredat_route_byte(shift, -2); scoredat_route_byte(shift, 1) = scoredat_route_byte(shift, -1); } long p = (SCOREDAT_NAMES_SIZE - 1); while(((place * SCOREDAT_NAME_BYTES) + SCOREDAT_NAME_BYTES) <= p) { scoredat_names[p] = scoredat_names[p - SCOREDAT_NAME_BYTES]; p--; } regist_put_initial( place, score, stage_num_or_scoredat_constant, route, names ); alphabet_put_initial(); scoredat_score[place] = score; scoredat_stages[place] = stage_num_or_scoredat_constant; scoredat_route_byte(place, 0) = route[0]; scoredat_route_byte(place, 1) = route[1]; // Writes the new name to scoredat_names[] and calls scoredat_save() regist_name_enter(place); _ES = FP_SEG(graph_accesspage_func); // Yes, no point to this at all. scoredat_free(); return; } regist_put_initial( PLACE_NONE, score, stage_num_or_scoredat_constant, route, names ); input_ok = true; input_shot = true; while(1) { input_sense(false); if(!input_ok || !input_shot) { break; } } _ES = FP_SEG(graph_accesspage_func); // Yes, no point to this at all. scoredat_free(); } // Global state that is defined here for some reason // ------------------------------------------------- int32_t* scoredat_score; // -------------------------------------------------