diff --git a/port/src/main.c b/port/src/main.c index b17b40afe..6611fca83 100644 --- a/port/src/main.c +++ b/port/src/main.c @@ -191,5 +191,6 @@ PD_CONSTRUCTOR static void gameConfigInit(void) configRegisterInt(strFmt("Game.Player%d.ExtendedControls", i), &g_PlayerExtCfg[j].extcontrols, 0, 1); configRegisterUInt(strFmt("Game.Player%d.CrosshairColour", i), &g_PlayerExtCfg[j].crosshaircolour, 0, 0xFFFFFFFF); configRegisterUInt(strFmt("Game.Player%d.CrosshairSize", i), &g_PlayerExtCfg[j].crosshairsize, 0, 4); + configRegisterInt(strFmt("Game.Player%d.CrosshairHealth", i), &g_PlayerExtCfg[j].crosshairhealth, 0, CROSSHAIR_HEALTH_ON_WHITE); } } diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index dd999bee4..e8085b564 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -1040,6 +1040,30 @@ static MenuItemHandlerResult menuhandlerCrosshairSize(s32 operation, struct menu return 0; } +static MenuItemHandlerResult menuhandlerCrosshairHealth(s32 operation, struct menuitem *item, union handlerdata *data) +{ + static const char *opts[] = { + "Off", + "On (Green)", + "On (White)" + }; + + switch (operation) { + case MENUOP_GETOPTIONCOUNT: + data->dropdown.value = ARRAYCOUNT(opts); + break; + case MENUOP_GETOPTIONTEXT: + return (intptr_t)opts[data->dropdown.value]; + case MENUOP_SET: + g_PlayerExtCfg[g_ExtMenuPlayer].crosshairhealth = data->dropdown.value; + break; + case MENUOP_GETSELECTEDINDEX: + data->dropdown.value = g_PlayerExtCfg[g_ExtMenuPlayer].crosshairhealth; + } + + return 0; +} + struct menuitem g_ExtendedGameCrosshairColourMenuItems[] = { { MENUITEMTYPE_SLIDER, @@ -1158,6 +1182,14 @@ struct menuitem g_ExtendedGameMenuItems[] = { 0, (void*)&g_ExtendedGameCrosshairColourMenuDialog, }, + { + MENUITEMTYPE_DROPDOWN, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Crosshair Colour by Health", + 0, + menuhandlerCrosshairHealth, + }, { MENUITEMTYPE_SEPARATOR, 0, diff --git a/src/game/mplayer/mplayer.c b/src/game/mplayer/mplayer.c index 871560210..7f27e0165 100644 --- a/src/game/mplayer/mplayer.c +++ b/src/game/mplayer/mplayer.c @@ -127,6 +127,7 @@ struct mpweapon g_MpWeapons[NUM_MPWEAPONS] = { .extcontrols = true, \ .crosshaircolour = 0x00ff0028, \ .crosshairsize = 2, \ + .crosshairhealth = CROSSHAIR_HEALTH_OFF, \ } struct extplayerconfig g_PlayerExtCfg[MAX_LOCAL_PLAYERS] = { diff --git a/src/game/sight.c b/src/game/sight.c index cf8278054..4b0872e62 100644 --- a/src/game/sight.c +++ b/src/game/sight.c @@ -23,9 +23,55 @@ #include #include "video.h" -#define SIGHT_COLOUR PLAYER_EXTCFG().crosshaircolour +#define SIGHT_COLOUR ((PLAYER_EXTCFG().crosshairhealth >= CROSSHAIR_HEALTH_ON_GREEN) ? sightGetCrosshairHealthColor(g_Vars.currentplayer->bondhealth, g_Vars.currentplayer->prop->chr->cshield * 0.125f) : PLAYER_EXTCFG().crosshaircolour) #define SIGHT_SCALE PLAYER_EXTCFG().crosshairsize +static u32 sightGetCrosshairHealthColor(float health, float shield) +{ + const float ratio = MAX(0.0f, MIN(health + shield, 2.0f)); + + int red = 0; + int green = 0; + int blue = 0; + if (ratio < 0.2f) { + // Red (critical health level) + red = 255; + green = 0; + blue = 0; + } else if (ratio < 0.6f) { + // Red-yellow + red = 255; + green = 255 * ((ratio - 0.2f) / 0.4f); + blue = 0; + } else if (ratio < 1.0f) { + if (PLAYER_EXTCFG().crosshairhealth == CROSSHAIR_HEALTH_ON_GREEN) { + // Yellow-green + red = 255 * ((ratio - 0.6f) / 0.4f); + green = 255; + blue = 0; + } else { + // Yellow-white + red = 255; + green = 255; + blue = 255 * ((ratio - 0.6f) / 0.4f); + } + } else { + if (PLAYER_EXTCFG().crosshairhealth == CROSSHAIR_HEALTH_ON_GREEN) { + // Green-cyan (overheal via shield) + red = 0; + green = 255; + blue = 255 * (ratio - 1.0f); + } else { + // White-green (overheal via shield) + red = 255 * (2.0f - ratio); + green = 255; + blue = 255 * (2.0f - ratio); + } + } + + return (red << 24) + (green << 16) + (blue << 8) + (PLAYER_EXTCFG().crosshaircolour & 0xff); +} + static inline f32 sightGetScaleX(void) { return (videoGetAspect() / SCREEN_ASPECT); @@ -1536,6 +1582,13 @@ Gfx *sightDraw(Gfx *gdl, bool sighton, s32 sight) sight = SIGHT_DEFAULT; } +#ifndef PLATFORM_N64 + if (g_Vars.currentplayer->bondhealth <= 0.0f) { + // Hide crosshair during death animation + sight = SIGHT_NONE; + } +#endif + sightTick(sighton); switch (sight) { diff --git a/src/include/constants.h b/src/include/constants.h index 66ba29c9a..163812868 100644 --- a/src/include/constants.h +++ b/src/include/constants.h @@ -4725,6 +4725,10 @@ enum weaponnum { #define CROUCHMODE_TOGGLE 2 // press the crouch buttons to toggle stance #define CROUCHMODE_TOGGLE_ANALOG (CROUCHMODE_ANALOG | CROUCHMODE_TOGGLE) +#define CROSSHAIR_HEALTH_OFF 0 +#define CROSSHAIR_HEALTH_ON_GREEN 1 +#define CROSSHAIR_HEALTH_ON_WHITE 2 + #define NETMODE_NONE 0 #define NETMODE_SERVER 1 #define NETMODE_CLIENT 2 diff --git a/src/include/types.h b/src/include/types.h index 2365ca55f..04b34a8cd 100644 --- a/src/include/types.h +++ b/src/include/types.h @@ -6156,6 +6156,7 @@ struct extplayerconfig { s32 extcontrols; u32 crosshaircolour; u32 crosshairsize; + s32 crosshairhealth; }; #endif