mgs_reversing/source/menu/life.c

581 lines
17 KiB
C

#include "menuman.h"
#include "common.h"
#include "libgcl/libgcl.h"
#include "libdg/libdg.h"
#include "game/game.h"
#include "linkvar.h"
#include "font/font.h"
#include "menu/menuman.h"
#include "menu/radio.h"
MENU_BAR_CONF gSnakeLifeBarConfig_8009E5F4 = {"LIFE", {0x10, 0x8F, 0x7F}, {0x1F, 0xDF, 0x3F}, 0};
MENU_BAR_CONF gSnakeO2BarConfig_8009E600 = {"O2", {0x1F, 0x3F, 0xC0}, {0x1F, 0x7F, 0xFF}, 1};
STATIC int gTakeDamageCounter_800AB5FC = 0;
STATIC RECT rect_800AB600 = {960, 256, 50, 80};
STATIC short coords_800AB600[2][2] = {{86, 72}, {44, 72}};
int SECTION(".sbss") dword_800ABAEC;
int SECTION(".sbss") gSnakeLifeYPos_800ABAF0;
int SECTION(".sbss") dword_800ABAF4;
/**
* @brief Updates the displayed health bar value to match the actual health of Snake.
*
* This function gradually updates the displayed health bar value (`displayedHp`) to match
* the actual health (`GM_SnakeCurrentHealth`). It uses a delay counter to control the speed
* of the update, making the health bar decrease more slowly when Snake takes damage.
*
* @param pBars Pointer to the MenuMan_MenuBars structure containing the health bar state.
* @return 1 if the displayed health was updated, 0 otherwise.
*/
int menu_life_update_helper_8003ECCC(MenuMan_MenuBars *pBars)
{
short displayedHp;
displayedHp = pBars->field_6_snake_hp;
if (displayedHp == GM_SnakeCurrentHealth)
{
pBars->health_delay_counter = 10;
return 0;
}
else
{
if (GM_SnakeCurrentHealth < displayedHp)
{
if (pBars->health_delay_counter == 0)
{
// try to decrease the displayed health faster
pBars->field_6_snake_hp = displayedHp - 64;
}
else
{
--pBars->health_delay_counter;
}
}
// if we overshhoot the displayed health, just match real health
if (pBars->field_6_snake_hp < GM_SnakeCurrentHealth)
{
pBars->field_6_snake_hp = GM_SnakeCurrentHealth;
}
return 1;
}
}
/**
* @brief Draws a status bar (such as a health or oxygen bar) on the screen.
*
* This function renders a status bar with a gradient fill, displaying the
* current value (`now`) relative to the maximum value (`max`).
* It also handles the visualization of the portion of the bar that will be
* removed (`rest`) in red if the character is taking damage.
* The function takes into account various game states and configurations to
* determine the appearance and behavior of the bar.
*
* @param prim Pointer to the structure containing the primitive.
* @param x The x-coordinate of the bar's position.
* @param y The y-coordinate of the bar's position.
* @param rest Value for portion of the bar that will be removed.
* @param now The current value to be displayed on the bar.
* @param max The maximum value of the bar.
* @param bconf Pointer to the bar's configuration (such as text and colors).
*/
void menu_draw_bar(MenuPrim *prim, long x, long y, long rest, long now, long max, MENU_BAR_CONF *bconf)
{
TextConfig text_config;
int scaled_max_val;
int sp2C;
char *pOt;
TILE *pTile;
TILE *pTile_2;
TILE *pTile_3;
POLY_G4 *pPoly;
DR_TPAGE *pTpage;
int y_with_offset;
int x_text;
int diff;
int width;
if (GM_GameStatus & (STATE_TAKING_PHOTO | STATE_LIFEBAR_OFF | STATE_DEMO))
{
return;
}
pOt = prim->mPrimBuf.mOt;
sp2C = 5 - bconf->height;
y_with_offset = y + 1;
scaled_max_val = max / 8;
// offset text by 4 pixels to the right
text_config.xpos = x + 4;
x_text = text_config.xpos;
text_config.flags = 0;
// offset text by 4 pixels down
text_config.ypos = y + 4;
if ( !((int)bconf & 0x80000000) )
{
// Red flashing text when taking smoking or O2 damage
text_config.colour = 0x643030FF;
}
else
{
// White color for everything else
text_config.colour = 0x64FFFFFF;
}
_menu_number_draw_string(prim, &text_config, bconf->name);
width = text_config.xpos - x_text + 2;
pTile = menu_render_rect_8003DB2C(prim, x_text - 1, text_config.ypos - 1, width, 7, 0);
setSemiTrans(pTile, 1);
if (rest > max)
{
rest = max;
}
// if snake is taking damage, draw in red the part of the health that will
// be removed
if (rest > now)
{
_NEW_PRIM(pTile_2, prim);
setTile(pTile_2);
diff = (rest - now) / 8;
pTile_2->x0 = x + now / 8;
pTile_2->y0 = y_with_offset;
pTile_2->w = diff;
pTile_2->h = sp2C;
pTile_2->r0 = 200;
pTile_2->g0 = 0;
pTile_2->b0 = 0;
addPrim(pOt, pTile_2);
}
_NEW_PRIM(pPoly, prim);
setXYWH(pPoly, x, y_with_offset, (now + 7) / 8, sp2C);
// Set the color of the bar as gradient fill. The color is interpolated
// between the left and right color.
pPoly->r0 = bconf->left[0];
pPoly->g0 = bconf->left[1];
pPoly->b0 = bconf->left[2];
pPoly->r1 = bconf->left[0] + ((bconf->right[0] - bconf->left[0]) * (pPoly->x1 - pPoly->x0)) / 128;
pPoly->g1 = bconf->left[1] + ((bconf->right[1] - bconf->left[1]) * (pPoly->x1 - pPoly->x0)) / 128;
pPoly->b1 = bconf->left[2] + ((bconf->right[2] - bconf->left[2]) * (pPoly->x1 - pPoly->x0)) / 128;
pPoly->r2 = bconf->left[0] + ((bconf->right[0] - bconf->left[0]) * (pPoly->x2 - pPoly->x0)) / 128;
pPoly->g2 = bconf->left[1] + ((bconf->right[1] - bconf->left[1]) * (pPoly->x2 - pPoly->x0)) / 128;
pPoly->b2 = bconf->left[2] + ((bconf->right[2] - bconf->left[2]) * (pPoly->x2 - pPoly->x0)) / 128;
pPoly->r3 = bconf->left[0] + ((bconf->right[0] - bconf->left[0]) * (pPoly->x3 - pPoly->x0)) / 128;
pPoly->g3 = bconf->left[1] + ((bconf->right[1] - bconf->left[1]) * (pPoly->x3 - pPoly->x0)) / 128;
pPoly->b3 = bconf->left[2] + ((bconf->right[2] - bconf->left[2]) * (pPoly->x3 - pPoly->x0)) / 128;
setPolyG4(pPoly);
addPrim(pOt, pPoly);
pTile_3 = menu_render_rect_8003DB2C(prim, x, y_with_offset, scaled_max_val, sp2C, 0x181800);
setSemiTrans(pTile_3, 1);
menu_render_rect_8003DB2C(prim, x - 1, y, 1, sp2C + 2, 0);
menu_render_rect_8003DB2C(prim, x, y, scaled_max_val, 1, 0);
menu_render_rect_8003DB2C(prim, x, y + sp2C + 1, scaled_max_val, 1, 0);
menu_render_rect_8003DB2C(prim, x + scaled_max_val, y, 1, sp2C + 2, 0);
_NEW_PRIM(pTpage, prim);
setDrawTPage(pTpage, 1, 1, getTPage(0, 0, 960, 256));
addPrim(prim->mPrimBuf.mOt, pTpage);
}
/**
* @brief Draws the life and oxygen bars for Snake.
*
* This function updates the position and state of the life bar and oxygen bar
* for Snake. It handles the visualization of damage taken by changing the color
* of the life bar text to red when Snake takes damage.
*
* @param prim Pointer to the MenuPrim structure containing the primitive.
* @param pBars Pointer to the MenuMan_MenuBars structure containing the bar states.
*/
void menu_life_update_helper2_8003F30C(MenuPrim *prim, MenuMan_MenuBars *pBars)
{
MENU_BAR_CONF *pBar;
pBar = &gSnakeLifeBarConfig_8009E5F4;
gSnakeLifeYPos_800ABAF0 = pBars->field_4_bar_y;
// Reset the flag and start the damage counter if we taken damage
if ((GM_GameStatus & STATE_DAMAGED) != 0)
{
gTakeDamageCounter_800AB5FC = 8;
GM_GameStatus &= ~STATE_DAMAGED;
}
// If the damage counter is active, decrement it and set the bar to render
// in red
if (gTakeDamageCounter_800AB5FC > 0)
{
--gTakeDamageCounter_800AB5FC;
pBar = UNTAG_PTR(MENU_BAR_CONF, pBar); // pointer flag to make it render in red
}
menu_draw_bar(prim,
pBars->field_2_bar_x,
pBars->field_4_bar_y,
pBars->field_6_snake_hp,
GM_SnakeCurrentHealth,
GM_SnakeMaxHealth,
pBar);
// If the oxygen bar is not full then draw it
if (pBars->field_1_O2_hp)
{
menu_draw_bar(prim,
pBars->field_2_bar_x,
pBars->field_4_bar_y + 12,
GM_O2,
GM_O2,
1024,
&gSnakeO2BarConfig_8009E600);
}
}
void draw_life_defaultX_8003F408(MenuPrim *prim, long y, long rest, long now, long max, MENU_BAR_CONF *bconf)
{
GM_GameStatus |= STATE_SHOW_LIFEBAR;
menu_draw_bar(prim,
16,
y + gSnakeLifeYPos_800ABAF0 - 16,
rest,
now,
max,
bconf);
}
void draw_life_8003F464(MenuPrim *prim, long x, long y, long rest, long now, long max, MENU_BAR_CONF *bconf)
{
GM_GameStatus |= STATE_SHOW_LIFEBAR;
menu_draw_bar(prim,
x,
y + gSnakeLifeYPos_800ABAF0 - 16,
rest,
now,
max,
bconf);
}
void draw_player_life_8003F4B8(MenuPrim *prim, long x, long y)
{
GM_GameStatus |= STATE_SHOW_LIFEBAR;
menu_draw_bar(prim,
x,
y,
GM_SnakeCurrentHealth,
GM_SnakeCurrentHealth,
GM_SnakeMaxHealth,
&gSnakeLifeBarConfig_8009E5F4);
}
void menu_font_kill_helper_8003F50C(void)
{
GM_GameStatus &= ~STATE_SHOW_LIFEBAR;
}
/**
* @brief Display the life bar and oxygen bar according to the bar state.
*
* This function updates the position and visibility of the life bar and oxygen
* bar based on the current state of the game and the player's health.
*
* @param work Pointer to the MenuWork structure containing the bar states.
* @param unused
*/
void menu_life_update_8003F530(MenuWork *work, unsigned char *unused)
{
int updated;
MenuMan_MenuBars *pBars;
int state;
updated = menu_life_update_helper_8003ECCC(&work->field_204_bars);
pBars = &work->field_204_bars;
state = work->field_2A_state;
// if the oxygen is not full
if (GM_O2 < 1024)
{
updated = 1;
pBars->field_1_O2_hp = -106;
}
if (state)
{
return;
}
if (GM_GameStatus & STATE_HIDE_LIFEBAR)
{
pBars->field_0_state = BAR_STATE_MOVING_UP;
}
if ((pBars->field_0_state == BAR_STATE_HIDDEN || pBars->field_0_state == BAR_STATE_MOVING_UP) &&
(updated || GM_GameStatus & STATE_SHOW_LIFEBAR || (GM_SnakeMaxHealth / 2) >= GM_SnakeCurrentHealth))
{
// hide the bar by moving it off screen
if (pBars->field_0_state == BAR_STATE_HIDDEN)
{
pBars->field_4_bar_y = -48;
}
pBars->field_0_state = BAR_STATE_MOVING_DOWN;
gTakeDamageCounter_800AB5FC = 0;
}
if (pBars->field_0_state == BAR_STATE_HIDDEN)
{
return;
}
switch (pBars->field_0_state)
{
case BAR_STATE_MOVING_DOWN:
// move the life bar down
pBars->field_4_bar_y += 8;
// if the bar is fully visible then move to state 2
if (pBars->field_4_bar_y >= 16)
{
// saturate the vertical position of the bar
pBars->field_4_bar_y = 16;
pBars->field_0_state = BAR_STATE_VISIBLE;
pBars->field_8_hide_bar_delay_counter = 150;
}
break;
case BAR_STATE_MOVING_UP:
// we need to slowly hide the bars
// reduce the vertical position of the bars by 8
pBars->field_4_bar_y -= 8;
// if the bars are off finally screen
if (pBars->field_4_bar_y < -47)
{
pBars->field_0_state = BAR_STATE_HIDDEN;
pBars->field_4_bar_y = -48;
if (GM_GameStatus & STATE_HIDE_LIFEBAR)
{
GM_GameStatus = (GM_GameStatus & ~STATE_HIDE_LIFEBAR) | STATE_LIFEBAR_OFF;
}
// if oxygen is full then hide the oxygen bar
if (GM_O2 == 1024)
{
pBars->field_1_O2_hp = 0;
}
return;
}
break;
case BAR_STATE_VISIBLE:
if (updated || (GM_SnakeMaxHealth / 2) >= GM_SnakeCurrentHealth || GM_GameStatus & STATE_SHOW_LIFEBAR)
{
pBars->field_8_hide_bar_delay_counter = 150;
// If the oxigen bar is not hidden then decrease the oxygen bar
if (pBars->field_1_O2_hp)
{
pBars->field_1_O2_hp--;
}
}
// Keep the bars visible until the delay is over
else if (--pBars->field_8_hide_bar_delay_counter <= 0)
{
pBars->field_0_state = BAR_STATE_MOVING_UP;
}
break;
case BAR_STATE_FORCE_HIDE:
pBars->field_0_state = BAR_STATE_HIDDEN;
pBars->field_4_bar_y = -48;
break;
}
menu_life_update_helper2_8003F30C(work->field_20_otBuf, pBars);
}
/**
* @brief Initialize the bars menu module.
* Set the update function for the bars module and initialize the default state.
*
* @param work Pointer to the Menu actor.
*/
void menu_life_init_8003F7E0(MenuWork *work)
{
MenuMan_MenuBars *pBar;
work->field_2C_modules[MENU_LIFE] = menu_life_update_8003F530;
// Set initialized flag
work->field_28_flags |= 1;
pBar = &work->field_204_bars;
pBar->field_6_snake_hp = GM_SnakeCurrentHealth;
pBar->health_delay_counter = 10;
pBar->field_2_bar_x = 16;
pBar->field_0_state = BAR_STATE_HIDDEN;
// start with the bars off screen
pBar->field_4_bar_y = -48;
pBar->field_1_O2_hp = 0;
gSnakeLifeYPos_800ABAF0 = -48;
}
/**
* @brief Deinitialize the bars menu module by clearing the initialized flag.
*
* @param work Pointer to the Menu actor.
*/
void menu_life_kill_8003F838(MenuWork *work)
{
// Clear the initialized flag
work->field_28_flags &= ~1u;
}
//below may be separate to life but draws it in one function
extern SPRT gMenuSprt_800bd998;
extern KCB font_800BD968;
int sub_8003F84C(int idx)
{
void *font_buffer;
setSprt(&gMenuSprt_800bd998);
gMenuSprt_800bd998.u0 = 0;
gMenuSprt_800bd998.v0 = rect_800AB600.y;
gMenuSprt_800bd998.r0 = 128;
gMenuSprt_800bd998.g0 = 128;
gMenuSprt_800bd998.b0 = 128;
gMenuSprt_800bd998.w = 200;
gMenuSprt_800bd998.h = 80;
// Callers to this function call it with idx = 0 or idx = 1
gMenuSprt_800bd998.x0 = coords_800AB600[idx][0];
gMenuSprt_800bd998.y0 = coords_800AB600[idx][1];
gMenuSprt_800bd998.clut = 0x7fbc;
ClearImage(&rect_800AB600, 0, 0, 0);
font_init_kcb(&font_800BD968, &rect_800AB600, 960, 510);
font_set_kcb(&font_800BD968, -1, -1, 0, 6, 2, 0);
font_buffer = GV_Malloc(font_get_buffer_size(&font_800BD968));
if (font_buffer == NULL)
{
return 0;
}
font_set_buffer(&font_800BD968, font_buffer);
font_set_color(&font_800BD968, 0, 0x6739, 0);
font_clut_update(&font_800BD968);
return 1;
}
void menu_printDescription_8003F97C(char *description)
{
font_print_string(&font_800BD968, description);
font_update(&font_800BD968);
}
// When scrolling items/weapons menu, draws the life bar, the text "EQUIP" or
// "WEAPON" and the background rectangle around the item/weapon description.
void menu_drawDescriptionPanel_8003F9B4(MenuWork *work, unsigned int *pOt, const char *str)
{
POLY_F4 *polyF4;
TILE *tile;
SPRT *sprt;
int i;
int w;
// Variables storing temporary X coordinate calculations
// It's required to get a match...
int x0, x1, x2, x3, x4;
work->field_2B |= 2;
NEW_PRIM(sprt, work);
*sprt = gMenuSprt_800bd998;
addPrim(pOt, sprt);
x0 = gMenuSprt_800bd998.x0;
x3 = x0 - 10;
w = gMenuSprt_800bd998.w + 10;
x1 = x0;
x4 = menu_number_draw_string(work, pOt, x1 - 8, gMenuSprt_800bd998.y0 - 7, str, 0);
draw_player_life_8003F4B8(work->field_20_otBuf, x3, 24);
i = 0;
x0 = 12;
x0 = x1 - x0;
x2 = x3 + (x4 - x0);
for (; i < 2; i++)
{
NEW_PRIM(polyF4, work);
LSTORE(0, &polyF4->r0);
polyF4->x0 = x3;
polyF4->y0 = gMenuSprt_800bd998.y0 - 9;
polyF4->x1 = x2;
polyF4->y1 = gMenuSprt_800bd998.y0 - 9;
polyF4->x2 = x3;
polyF4->y2 = gMenuSprt_800bd998.y0;
polyF4->x3 = x2;
polyF4->y3 = gMenuSprt_800bd998.y0;
polyF4->x3 += 10;
setPolyF4(polyF4);
setSemiTrans(polyF4, 1);
addPrim(pOt, polyF4);
NEW_PRIM(tile, work);
LSTORE(0, &tile->r0);
tile->x0 = x3;
tile->y0 = gMenuSprt_800bd998.y0;
tile->w = w;
tile->h = gMenuSprt_800bd998.h;
setTile(tile);
setSemiTrans(tile, 1);
addPrim(pOt, tile);
}
}
extern MenuWork gMenuWork_800BD360;
void menu_font_kill_8003FC0C(void)
{
void *ptr;
gMenuWork_800BD360.field_2B &= ~2;
menu_font_kill_helper_8003F50C();
ptr = font_get_buffer_ptr(&font_800BD968);
GV_Free(ptr);
}