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 }