sm64

A Super Mario 64 decompilation
Log | Files | Refs | README | LICENSE

hud.c (14678B)


      1 #include <PR/ultratypes.h>
      2 
      3 #include "sm64.h"
      4 #include "actors/common1.h"
      5 #include "gfx_dimensions.h"
      6 #include "game_init.h"
      7 #include "level_update.h"
      8 #include "camera.h"
      9 #include "print.h"
     10 #include "ingame_menu.h"
     11 #include "hud.h"
     12 #include "segment2.h"
     13 #include "area.h"
     14 #include "save_file.h"
     15 #include "print.h"
     16 
     17 /* @file hud.c
     18  * This file implements HUD rendering and power meter animations.
     19  * That includes stars, lives, coins, camera status, power meter, timer
     20  * cannon reticle, and the unused keys.
     21  **/
     22 
     23 struct PowerMeterHUD {
     24     s8 animation;
     25     s16 x;
     26     s16 y;
     27     f32 unused;
     28 };
     29 
     30 // Stores health segmented value defined by numHealthWedges
     31 // When the HUD is rendered this value is 8, full health.
     32 static s16 sPowerMeterStoredHealth;
     33 
     34 static struct PowerMeterHUD sPowerMeterHUD = {
     35     POWER_METER_HIDDEN,
     36     140,
     37     166,
     38     1.0,
     39 };
     40 
     41 // Power Meter timer that keeps counting when it's visible.
     42 // Gets reset when the health is filled and stops counting
     43 // when the power meter is hidden.
     44 s32 sPowerMeterVisibleTimer = 0;
     45 
     46 // TODO: fakediff?
     47 #ifndef VERSION_CN
     48 UNUSED static s32 sUnusedHUDValue1 = 0;
     49 UNUSED static s16 sUnusedHUDValue2 = 10;
     50 #else
     51 UNUSED static s32 sUnusedHUDValue2 = 10;
     52 #endif
     53 
     54 static s16 sCameraHUDStatus = CAM_STATUS_NONE;
     55 
     56 /**
     57  * Renders a rgba16 16x16 glyph texture from a table list.
     58  */
     59 void render_hud_tex_lut(s32 x, s32 y, u8 *texture) {
     60     gDPPipeSync(gDisplayListHead++);
     61     gDPSetTextureImage(gDisplayListHead++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 1, texture);
     62     gSPDisplayList(gDisplayListHead++, &dl_hud_img_load_tex_block);
     63     gSPTextureRectangle(gDisplayListHead++, x << 2, y << 2, (x + 15) << 2, (y + 15) << 2,
     64                         G_TX_RENDERTILE, 0, 0, 4 << 10, 1 << 10);
     65 }
     66 
     67 /**
     68  * Renders a rgba16 8x8 glyph texture from a table list.
     69  */
     70 void render_hud_small_tex_lut(s32 x, s32 y, u8 *texture) {
     71     gDPSetTile(gDisplayListHead++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 0, 0, G_TX_LOADTILE, 0,
     72                 G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK, G_TX_NOLOD, G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK, G_TX_NOLOD);
     73     gDPTileSync(gDisplayListHead++);
     74     gDPSetTile(gDisplayListHead++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 2, 0, G_TX_RENDERTILE, 0,
     75                 G_TX_CLAMP, 3, G_TX_NOLOD, G_TX_CLAMP, 3, G_TX_NOLOD);
     76     gDPSetTileSize(gDisplayListHead++, G_TX_RENDERTILE, 0, 0, (8 - 1) << G_TEXTURE_IMAGE_FRAC, (8 - 1) << G_TEXTURE_IMAGE_FRAC);
     77     gDPPipeSync(gDisplayListHead++);
     78     gDPSetTextureImage(gDisplayListHead++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 1, texture);
     79     gDPLoadSync(gDisplayListHead++);
     80     gDPLoadBlock(gDisplayListHead++, G_TX_LOADTILE, 0, 0, 8 * 8 - 1, CALC_DXT(8, G_IM_SIZ_16b_BYTES));
     81     gSPTextureRectangle(gDisplayListHead++, x << 2, y << 2, (x + 7) << 2, (y + 7) << 2, G_TX_RENDERTILE,
     82                         0, 0, 4 << 10, 1 << 10);
     83 }
     84 
     85 /**
     86  * Renders power meter health segment texture using a table list.
     87  */
     88 void render_power_meter_health_segment(s16 numHealthWedges) {
     89     u8 *(*healthLUT)[] = segmented_to_virtual(&power_meter_health_segments_lut);
     90 
     91     gDPPipeSync(gDisplayListHead++);
     92     gDPSetTextureImage(gDisplayListHead++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 1,
     93                        (*healthLUT)[numHealthWedges - 1]);
     94     gDPLoadSync(gDisplayListHead++);
     95     gDPLoadBlock(gDisplayListHead++, G_TX_LOADTILE, 0, 0, 32 * 32 - 1, CALC_DXT(32, G_IM_SIZ_16b_BYTES));
     96     gSP1Triangle(gDisplayListHead++, 0, 1, 2, 0);
     97     gSP1Triangle(gDisplayListHead++, 0, 2, 3, 0);
     98 }
     99 
    100 /**
    101  * Renders power meter display lists.
    102  * That includes the "POWER" base and the colored health segment textures.
    103  */
    104 void render_dl_power_meter(s16 numHealthWedges) {
    105     Mtx *mtx = alloc_display_list(sizeof(Mtx));
    106 
    107     if (mtx == NULL) {
    108         return;
    109     }
    110 
    111     guTranslate(mtx, (f32) sPowerMeterHUD.x, (f32) sPowerMeterHUD.y, 0);
    112 
    113     gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx++),
    114               G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH);
    115     gSPDisplayList(gDisplayListHead++, &dl_power_meter_base);
    116 
    117     if (numHealthWedges != 0) {
    118         gSPDisplayList(gDisplayListHead++, &dl_power_meter_health_segments_begin);
    119         render_power_meter_health_segment(numHealthWedges);
    120         gSPDisplayList(gDisplayListHead++, &dl_power_meter_health_segments_end);
    121     }
    122 
    123     gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
    124 }
    125 
    126 /**
    127  * Power meter animation called when there's less than 8 health segments
    128  * Checks its timer to later change into deemphasizing mode.
    129  */
    130 void animate_power_meter_emphasized(void) {
    131     s16 hudDisplayFlags = gHudDisplay.flags;
    132 
    133     if (!(hudDisplayFlags & HUD_DISPLAY_FLAG_EMPHASIZE_POWER)) {
    134         if (sPowerMeterVisibleTimer == 45.0) {
    135             sPowerMeterHUD.animation = POWER_METER_DEEMPHASIZING;
    136         }
    137     } else {
    138         sPowerMeterVisibleTimer = 0;
    139     }
    140 }
    141 
    142 /**
    143  * Power meter animation called after emphasized mode.
    144  * Moves power meter y pos speed until it's at 200 to be visible.
    145  */
    146 static void animate_power_meter_deemphasizing(void) {
    147     s16 speed = 5;
    148 
    149     if (sPowerMeterHUD.y > 180) {
    150         speed = 3;
    151     }
    152 
    153     if (sPowerMeterHUD.y > 190) {
    154         speed = 2;
    155     }
    156 
    157     if (sPowerMeterHUD.y > 195) {
    158         speed = 1;
    159     }
    160 
    161     sPowerMeterHUD.y += speed;
    162 
    163     if (sPowerMeterHUD.y > 200) {
    164         sPowerMeterHUD.y = 200;
    165         sPowerMeterHUD.animation = POWER_METER_VISIBLE;
    166     }
    167 }
    168 
    169 /**
    170  * Power meter animation called when there's 8 health segments.
    171  * Moves power meter y pos quickly until it's at 301 to be hidden.
    172  */
    173 static void animate_power_meter_hiding(void) {
    174     sPowerMeterHUD.y += 20;
    175     if (sPowerMeterHUD.y > 300) {
    176         sPowerMeterHUD.animation = POWER_METER_HIDDEN;
    177         sPowerMeterVisibleTimer = 0;
    178     }
    179 }
    180 
    181 /**
    182  * Handles power meter actions depending of the health segments values.
    183  */
    184 void handle_power_meter_actions(s16 numHealthWedges) {
    185     // Show power meter if health is not full, less than 8
    186     if (numHealthWedges < 8 && sPowerMeterStoredHealth == 8
    187         && sPowerMeterHUD.animation == POWER_METER_HIDDEN) {
    188         sPowerMeterHUD.animation = POWER_METER_EMPHASIZED;
    189         sPowerMeterHUD.y = 166;
    190     }
    191 
    192     // Show power meter if health is full, has 8
    193     if (numHealthWedges == 8 && sPowerMeterStoredHealth == 7) {
    194         sPowerMeterVisibleTimer = 0;
    195     }
    196 
    197     // After health is full, hide power meter
    198     if (numHealthWedges == 8 && sPowerMeterVisibleTimer > 45.0) {
    199         sPowerMeterHUD.animation = POWER_METER_HIDING;
    200     }
    201 
    202     // Update to match health value
    203     sPowerMeterStoredHealth = numHealthWedges;
    204 
    205     // If Mario is swimming, keep power meter visible
    206     if (gPlayerCameraState->action & ACT_FLAG_SWIMMING) {
    207         if (sPowerMeterHUD.animation == POWER_METER_HIDDEN
    208             || sPowerMeterHUD.animation == POWER_METER_EMPHASIZED) {
    209             sPowerMeterHUD.animation = POWER_METER_DEEMPHASIZING;
    210             sPowerMeterHUD.y = 166;
    211         }
    212         sPowerMeterVisibleTimer = 0;
    213     }
    214 }
    215 
    216 /**
    217  * Renders the power meter that shows when Mario is in underwater
    218  * or has taken damage and has less than 8 health segments.
    219  * And calls a power meter animation function depending of the value defined.
    220  */
    221 void render_hud_power_meter(void) {
    222     s16 shownHealthWedges = gHudDisplay.wedges;
    223 
    224     if (sPowerMeterHUD.animation != POWER_METER_HIDING) {
    225         handle_power_meter_actions(shownHealthWedges);
    226     }
    227 
    228     if (sPowerMeterHUD.animation == POWER_METER_HIDDEN) {
    229         return;
    230     }
    231 
    232     switch (sPowerMeterHUD.animation) {
    233         case POWER_METER_EMPHASIZED:
    234             animate_power_meter_emphasized();
    235             break;
    236         case POWER_METER_DEEMPHASIZING:
    237             animate_power_meter_deemphasizing();
    238             break;
    239         case POWER_METER_HIDING:
    240             animate_power_meter_hiding();
    241             break;
    242         default:
    243             break;
    244     }
    245 
    246     render_dl_power_meter(shownHealthWedges);
    247 
    248     sPowerMeterVisibleTimer++;
    249 }
    250 
    251 #ifdef VERSION_JP
    252 #define HUD_TOP_Y 210
    253 #else
    254 #define HUD_TOP_Y 209
    255 #endif
    256 
    257 /**
    258  * Renders the amount of lives Mario has.
    259  */
    260 void render_hud_mario_lives(void) {
    261     print_text(GFX_DIMENSIONS_RECT_FROM_LEFT_EDGE(22), HUD_TOP_Y, ","); // 'Mario Head' glyph
    262     print_text(GFX_DIMENSIONS_RECT_FROM_LEFT_EDGE(38), HUD_TOP_Y, "*"); // 'X' glyph
    263     print_text_fmt_int(GFX_DIMENSIONS_RECT_FROM_LEFT_EDGE(54), HUD_TOP_Y, "%d", gHudDisplay.lives);
    264 }
    265 
    266 /**
    267  * Renders the amount of coins collected.
    268  */
    269 void render_hud_coins(void) {
    270     print_text(168, HUD_TOP_Y, "+"); // 'Coin' glyph
    271     print_text(184, HUD_TOP_Y, "*"); // 'X' glyph
    272     print_text_fmt_int(198, HUD_TOP_Y, "%d", gHudDisplay.coins);
    273 }
    274 
    275 #ifdef VERSION_JP
    276 #define HUD_STARS_X 73
    277 #else
    278 #define HUD_STARS_X 78
    279 #endif
    280 
    281 /**
    282  * Renders the amount of stars collected.
    283  * Disables "X" glyph when Mario has 100 stars or more.
    284  */
    285 void render_hud_stars(void) {
    286     s8 showX = 0;
    287 
    288     if (gHudFlash == 1 && gGlobalTimer & 8) {
    289         return;
    290     }
    291 
    292     if (gHudDisplay.stars < 100) {
    293         showX = 1;
    294     }
    295 
    296     print_text(GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(HUD_STARS_X), HUD_TOP_Y, "-"); // 'Star' glyph
    297     if (showX == 1) {
    298         print_text(GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(HUD_STARS_X) + 16, HUD_TOP_Y, "*"); // 'X' glyph
    299     }
    300     print_text_fmt_int((showX * 14) + GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(HUD_STARS_X - 16),
    301                        HUD_TOP_Y, "%d", gHudDisplay.stars);
    302 }
    303 
    304 /**
    305  * Unused function that renders the amount of keys collected.
    306  * Leftover function from the beta version of the game.
    307  */
    308 void render_hud_keys(void) {
    309     s16 i;
    310 
    311     for (i = 0; i < gHudDisplay.keys; i++) {
    312         print_text((i * 16) + 220, 142, "/"); // unused glyph - beta key
    313     }
    314 }
    315 
    316 /**
    317  * Renders the timer when Mario start sliding in PSS.
    318  */
    319 void render_hud_timer(void) {
    320     u8 *(*hudLUT)[58] = segmented_to_virtual(&main_hud_lut);
    321     u16 timerValFrames = gHudDisplay.timer;
    322     u16 timerMins = timerValFrames / (30 * 60);
    323     u16 timerSecs = (timerValFrames - (timerMins * 1800)) / 30;
    324     u16 timerFracSecs = ((u16) (timerValFrames - (timerMins * 1800) - (timerSecs * 30))) / 3;
    325 #ifdef VERSION_CN
    326     u8 timeString[2];
    327 #endif
    328 
    329 #ifdef VERSION_EU
    330     switch (eu_get_language()) {
    331         case LANGUAGE_ENGLISH:
    332             print_text(GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(150), 185, "TIME");
    333             break;
    334         case LANGUAGE_FRENCH:
    335             print_text(GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(155), 185, "TEMPS");
    336             break;
    337         case LANGUAGE_GERMAN:
    338             print_text(GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(150), 185, "ZEIT");
    339             break;
    340     }
    341 #elif defined(VERSION_CN)
    342     timeString[0] = 0xC0; // TODO: iQue colorful text
    343     timeString[1] = 0x00;
    344     print_text_centered(GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(150), 185, (const char *) timeString);
    345 #else
    346     print_text(GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(150), 185, "TIME");
    347 #endif
    348 
    349     print_text_fmt_int(GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(91), 185, "%0d", timerMins);
    350     print_text_fmt_int(GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(71), 185, "%02d", timerSecs);
    351     print_text_fmt_int(GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(37), 185, "%d", timerFracSecs);
    352 
    353     gSPDisplayList(gDisplayListHead++, dl_hud_img_begin);
    354     render_hud_tex_lut(GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(81), 32, (*hudLUT)[GLYPH_APOSTROPHE]);
    355     render_hud_tex_lut(GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(46), 32, (*hudLUT)[GLYPH_DOUBLE_QUOTE]);
    356     gSPDisplayList(gDisplayListHead++, dl_hud_img_end);
    357 }
    358 
    359 /**
    360  * Sets HUD status camera value depending of the actions
    361  * defined in update_camera_status.
    362  */
    363 void set_hud_camera_status(s16 status) {
    364     sCameraHUDStatus = status;
    365 }
    366 
    367 /**
    368  * Renders camera HUD glyphs using a table list. Depending on
    369  * the camera status called, a defined glyph is rendered.
    370  */
    371 void render_hud_camera_status(void) {
    372     u8 *(*cameraLUT)[6] = segmented_to_virtual(&main_hud_camera_lut);
    373     s32 x = GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(54);
    374     s32 y = 205;
    375 
    376     if (sCameraHUDStatus == CAM_STATUS_NONE) {
    377         return;
    378     }
    379 
    380     gSPDisplayList(gDisplayListHead++, dl_hud_img_begin);
    381     render_hud_tex_lut(x, y, (*cameraLUT)[GLYPH_CAM_CAMERA]);
    382 
    383     switch (sCameraHUDStatus & CAM_STATUS_MODE_GROUP) {
    384         case CAM_STATUS_MARIO:
    385             render_hud_tex_lut(x + 16, y, (*cameraLUT)[GLYPH_CAM_MARIO_HEAD]);
    386             break;
    387         case CAM_STATUS_LAKITU:
    388             render_hud_tex_lut(x + 16, y, (*cameraLUT)[GLYPH_CAM_LAKITU_HEAD]);
    389             break;
    390         case CAM_STATUS_FIXED:
    391             render_hud_tex_lut(x + 16, y, (*cameraLUT)[GLYPH_CAM_FIXED]);
    392             break;
    393     }
    394 
    395     switch (sCameraHUDStatus & CAM_STATUS_C_MODE_GROUP) {
    396         case CAM_STATUS_C_DOWN:
    397             render_hud_small_tex_lut(x + 4, y + 16, (*cameraLUT)[GLYPH_CAM_ARROW_DOWN]);
    398             break;
    399         case CAM_STATUS_C_UP:
    400             render_hud_small_tex_lut(x + 4, y - 8, (*cameraLUT)[GLYPH_CAM_ARROW_UP]);
    401             break;
    402     }
    403 
    404     gSPDisplayList(gDisplayListHead++, dl_hud_img_end);
    405 }
    406 
    407 /**
    408  * Render HUD strings using hudDisplayFlags with it's render functions,
    409  * excluding the cannon reticle which detects a camera preset for it.
    410  */
    411 void render_hud(void) {
    412     s16 hudDisplayFlags = gHudDisplay.flags;
    413 
    414     if (hudDisplayFlags == HUD_DISPLAY_NONE) {
    415         sPowerMeterHUD.animation = POWER_METER_HIDDEN;
    416         sPowerMeterStoredHealth = 8;
    417         sPowerMeterVisibleTimer = 0;
    418     } else {
    419 #ifdef VERSION_EU
    420         // basically create_dl_ortho_matrix but guOrtho screen width is different
    421         Mtx *mtx = alloc_display_list(sizeof(*mtx));
    422 
    423         if (mtx == NULL) {
    424             return;
    425         }
    426 
    427         create_dl_identity_matrix();
    428         guOrtho(mtx, -16.0f, SCREEN_WIDTH + 16, 0, SCREEN_HEIGHT, -10.0f, 10.0f, 1.0f);
    429         gSPPerspNormalize(gDisplayListHead++, 0xFFFF);
    430         gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx),
    431                   G_MTX_PROJECTION | G_MTX_MUL | G_MTX_NOPUSH);
    432 #else
    433         create_dl_ortho_matrix();
    434 #endif
    435 
    436         if (gCurrentArea != NULL && gCurrentArea->camera->mode == CAMERA_MODE_INSIDE_CANNON) {
    437             render_hud_cannon_reticle();
    438         }
    439 
    440         if (hudDisplayFlags & HUD_DISPLAY_FLAG_LIVES) {
    441             render_hud_mario_lives();
    442         }
    443 
    444         if (hudDisplayFlags & HUD_DISPLAY_FLAG_COIN_COUNT) {
    445             render_hud_coins();
    446         }
    447 
    448         if (hudDisplayFlags & HUD_DISPLAY_FLAG_STAR_COUNT) {
    449             render_hud_stars();
    450         }
    451 
    452         if (hudDisplayFlags & HUD_DISPLAY_FLAG_KEYS) {
    453             render_hud_keys();
    454         }
    455 
    456         if (hudDisplayFlags & HUD_DISPLAY_FLAG_CAMERA_AND_POWER) {
    457             render_hud_power_meter();
    458             render_hud_camera_status();
    459         }
    460 
    461         if (hudDisplayFlags & HUD_DISPLAY_FLAG_TIMER) {
    462             render_hud_timer();
    463         }
    464     }
    465 }