#include #include "sm64.h" #include "gfx_dimensions.h" #include "game_init.h" #include "mario.h" #include "memory.h" #include "save_file.h" #include "main.h" #include "engine/surface_collision.h" #include "geo_misc.h" #include "segment2.h" #include "print.h" /** * This file handles printing and formatting the colorful text that * appears when printing things such as "PRESS START". */ struct TextLabel { u32 x; u32 y; s16 length; char buffer[50]; }; /** * Stores the text to be rendered on screen * and how they are to be rendered. */ struct TextLabel *sTextLabels[256]; s16 sTextLabelsCount = 0; /** * Returns n to the exponent power, only for non-negative powers. */ s32 int_pow(s32 n, s32 exponent) { s32 result = 1; s32 i; for (i = 0; i < exponent; i++) { result = n * result; } return result; } /** * Formats an integer n for print by fitting it to width, prefixing with a negative, * and converting the base. */ void format_integer(s32 n, s32 base, char *dest, s32 *totalLength, u8 width, s8 zeroPad) { u32 powBase; s32 numDigits = 0; s32 i; s32 len = 0; s8 digit; s8 negative = FALSE; char pad; if (zeroPad == TRUE) { pad = '0'; } else { pad = -1; } if (n != 0) { // Formats a negative number for negative prefix. if (n < 0) { n = -n; negative = TRUE; } // Increments the number of digits until length is long enough. while (1) { powBase = int_pow(base, numDigits); if (powBase > (u32) n) { break; } numDigits++; } // Add leading pad to fit width. if (width > numDigits) { for (len = 0; len < width - numDigits; len++) dest[len] = pad; // Needs 1 length to print negative prefix. if (negative == TRUE) { len--; } } // Use 'M' prefix to indicate negative numbers. if (negative == TRUE) { dest[len] = 'M'; len++; } // Transfer the digits into the proper base. for (i = numDigits - 1; i >= 0; i--) { powBase = int_pow(base, i); digit = n / powBase; // FIXME: Why doesn't [] match? if (digit < 10) { *(dest + len + numDigits - 1 - i) = digit + '0'; } else { *(dest + len + numDigits - 1 - i) = digit + '7'; } n -= digit * powBase; } } else // n is zero. { numDigits = 1; if (width > numDigits) { for (len = 0; len < width - numDigits; len++) dest[len] = pad; } dest[len] = '0'; } *totalLength += numDigits + len; } /** * Determines the width of the number for printing, writing to 'width'. * Additionally, this determines if a number should be zero-padded, * writing to 'zeroPad'. */ void parse_width_field(const char *str, s32 *srcIndex, u8 *width, s8 *zeroPad) { s8 digits[12]; // unknown length s8 digitsLen = 0; s16 i; // If first character is 0, then the string should be zero padded. if (str[*srcIndex] == '0') { *zeroPad = TRUE; } // Read width digits up until the 'd' or 'x' format specifier. while (str[*srcIndex] != 'd' && str[*srcIndex] != 'x') { digits[digitsLen] = str[*srcIndex] - '0'; if (digits[digitsLen] < 0 || digits[digitsLen] >= 10) // not a valid digit { *width = 0; return; } digitsLen++; (*srcIndex)++; } // No digits if (digitsLen == 0) { return; } // Sum the digits to calculate the total width. for (i = 0; i < digitsLen - 1; i++) { *width = *width + digits[i] * ((digitsLen - i - 1) * 10); } *width = *width + digits[digitsLen - 1]; } /** * Takes a number, finds the intended base, formats the number, and prints it * at the given X & Y coordinates. * * Warning: this fails on too large numbers, because format_integer has bugs * related to overflow. For romhacks, prefer sprintf + print_text. */ void print_text_fmt_int(s32 x, s32 y, const char *str, s32 n) { char c = 0; s8 zeroPad = FALSE; u8 width = 0; s32 base = 0; s32 len = 0; s32 srcIndex = 0; // Don't continue if there is no memory to do so. if ((sTextLabels[sTextLabelsCount] = mem_pool_alloc(gEffectsMemoryPool, sizeof(struct TextLabel))) == NULL) { return; } sTextLabels[sTextLabelsCount]->x = x; sTextLabels[sTextLabelsCount]->y = y; c = str[srcIndex]; while (c != 0) { if (c == '%') { srcIndex++; parse_width_field(str, &srcIndex, &width, &zeroPad); if (str[srcIndex] != 'd' && str[srcIndex] != 'x') { break; } if (str[srcIndex] == 'd') { base = 10; } if (str[srcIndex] == 'x') { base = 16; } srcIndex++; format_integer(n, base, sTextLabels[sTextLabelsCount]->buffer + len, &len, width, zeroPad); } else // straight copy { sTextLabels[sTextLabelsCount]->buffer[len] = c; len++; srcIndex++; } c = str[srcIndex]; } sTextLabels[sTextLabelsCount]->length = len; sTextLabelsCount++; } /** * Prints text in the colorful lettering at given X, Y coordinates. */ void print_text(s32 x, s32 y, const char *str) { char c = 0; s32 length = 0; s32 srcIndex = 0; // Don't continue if there is no memory to do so. if ((sTextLabels[sTextLabelsCount] = mem_pool_alloc(gEffectsMemoryPool, sizeof(struct TextLabel))) == NULL) { return; } sTextLabels[sTextLabelsCount]->x = x; sTextLabels[sTextLabelsCount]->y = y; c = str[srcIndex]; // Set the array with the text to print while finding length. while (c != 0) { sTextLabels[sTextLabelsCount]->buffer[length] = c; length++; srcIndex++; c = str[srcIndex]; } sTextLabels[sTextLabelsCount]->length = length; sTextLabelsCount++; } /** * Prints text in the colorful lettering centered * at given X, Y coordinates. */ void print_text_centered(s32 x, s32 y, const char *str) { char c = 0; UNUSED s8 unused1 = 0; UNUSED s32 unused2 = 0; s32 length = 0; s32 srcIndex = 0; // Don't continue if there is no memory to do so. if ((sTextLabels[sTextLabelsCount] = mem_pool_alloc(gEffectsMemoryPool, sizeof(struct TextLabel))) == NULL) { return; } c = str[srcIndex]; // Set the array with the text to print while finding length. while (c != 0) { sTextLabels[sTextLabelsCount]->buffer[length] = c; length++; srcIndex++; c = str[srcIndex]; } sTextLabels[sTextLabelsCount]->length = length; sTextLabels[sTextLabelsCount]->x = x - length * 12 / 2; sTextLabels[sTextLabelsCount]->y = y; sTextLabelsCount++; } /** * Converts a char into the proper colorful glyph for the char. */ s8 char_to_glyph_index(char c) { if (c >= 'A' && c <= 'Z') { return c - 55; } if (c >= 'a' && c <= 'z') { return c - 87; } if (c >= '0' && c <= '9') { return c - 48; } if (c == ' ') { return GLYPH_SPACE; } if (c == '!') { return GLYPH_EXCLAMATION_PNT; // !, JP only } if (c == '#') { return GLYPH_TWO_EXCLAMATION; // !!, JP only } if (c == '?') { return GLYPH_QUESTION_MARK; // ?, JP only } if (c == '&') { return GLYPH_AMPERSAND; // &, JP only } if (c == '%') { return GLYPH_PERCENT; // %, JP only } if (c == '*') { return GLYPH_MULTIPLY; // x } if (c == '+') { return GLYPH_COIN; // coin } if (c == ',') { return GLYPH_MARIO_HEAD; // Imagine I drew Mario's head } if (c == '-') { return GLYPH_STAR; // star } if (c == '.') { return GLYPH_PERIOD; // large shaded dot, JP only } if (c == '/') { return GLYPH_BETA_KEY; // beta key, JP only. Reused for Ü in EU. } return GLYPH_SPACE; } /** * Adds an individual glyph to be rendered. */ void add_glyph_texture(s8 glyphIndex) { const u8 *const *glyphs = segmented_to_virtual(main_hud_lut); gDPPipeSync(gDisplayListHead++); gDPSetTextureImage(gDisplayListHead++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 1, glyphs[glyphIndex]); gSPDisplayList(gDisplayListHead++, dl_hud_img_load_tex_block); } #ifdef TARGET_N64 /** * Clips textrect into the boundaries defined. */ void clip_to_bounds(s32 *x, s32 *y) { if (*x < TEXRECT_MIN_X) { *x = TEXRECT_MIN_X; } if (*x > TEXRECT_MAX_X) { *x = TEXRECT_MAX_X; } if (*y < TEXRECT_MIN_Y) { *y = TEXRECT_MIN_Y; } if (*y > TEXRECT_MAX_Y) { *y = TEXRECT_MAX_Y; } } #endif /** * Renders the glyph that's set at the given position. */ void render_textrect(s32 x, s32 y, s32 pos) { s32 rectBaseX = x + pos * 12; s32 rectBaseY = 224 - y; s32 rectX; s32 rectY; #ifdef TARGET_N64 clip_to_bounds(&rectBaseX, &rectBaseY); #endif rectX = rectBaseX; rectY = rectBaseY; gSPTextureRectangle(gDisplayListHead++, rectX << 2, rectY << 2, (rectX + 15) << 2, (rectY + 15) << 2, G_TX_RENDERTILE, 0, 0, 4 << 10, 1 << 10); } /** * Renders the text in sTextLabels on screen at the proper locations by iterating * a for loop. */ void render_text_labels(void) { s32 i; s32 j; s8 glyphIndex; Mtx *mtx; if (sTextLabelsCount == 0) { return; } mtx = alloc_display_list(sizeof(*mtx)); if (mtx == NULL) { sTextLabelsCount = 0; return; } guOrtho(mtx, 0.0f, SCREEN_WIDTH, 0.0f, SCREEN_HEIGHT, -10.0f, 10.0f, 1.0f); gSPPerspNormalize((Gfx *) (gDisplayListHead++), 0xFFFF); gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); gSPDisplayList(gDisplayListHead++, dl_hud_img_begin); for (i = 0; i < sTextLabelsCount; i++) { for (j = 0; j < sTextLabels[i]->length; j++) { glyphIndex = char_to_glyph_index(sTextLabels[i]->buffer[j]); if (glyphIndex != GLYPH_SPACE) { #ifdef VERSION_EU // Beta Key was removed by EU, so glyph slot reused. // This produces a colorful Ü. if (glyphIndex == GLYPH_BETA_KEY) { add_glyph_texture(GLYPH_U); render_textrect(sTextLabels[i]->x, sTextLabels[i]->y, j); add_glyph_texture(GLYPH_UMLAUT); render_textrect(sTextLabels[i]->x, sTextLabels[i]->y + 3, j); } else { add_glyph_texture(glyphIndex); render_textrect(sTextLabels[i]->x, sTextLabels[i]->y, j); } #else add_glyph_texture(glyphIndex); render_textrect(sTextLabels[i]->x, sTextLabels[i]->y, j); #endif } } mem_pool_free(gEffectsMemoryPool, sTextLabels[i]); } gSPDisplayList(gDisplayListHead++, dl_hud_img_end); sTextLabelsCount = 0; }