From c5153c4ab1e35fade8029b9ed3172985a21b39f3 Mon Sep 17 00:00:00 2001 From: pierow Date: Mon, 29 Apr 2024 17:58:32 -0400 Subject: [PATCH] health and ammo HUD scaling - Scale HUD with a modified version of @Toodles2You 's triapi HUD scaling - Add commands for position, scale, and minimum alpha of heath and ammo - Fixes sprite blurring - Fixed nearby pixels bleeding in to the drawn sprites - Fixed the health cross art being cut off at the edges --- main/source/cl_dll/ammo.cpp | 196 ++++++++++++-- main/source/cl_dll/ammo_secondary.cpp | 8 +- main/source/cl_dll/battery.cpp | 114 +++++++- main/source/cl_dll/chud.h | 83 +++++- main/source/cl_dll/chudmisc.h | 8 +- main/source/cl_dll/health.cpp | 163 +++++++++-- main/source/cl_dll/health.h | 7 + main/source/cl_dll/hud.cpp | 12 + main/source/cl_dll/hud_redraw.cpp | 372 ++++++++++++++++++++++++-- main/source/mod/AvHHudConstants.h | 2 +- main/sprites/640hud7.spr | Bin 66371 -> 66366 bytes main/sprites/640nlhud7.spr | Bin 66366 -> 66366 bytes main/sprites/hud.txt | 50 ++-- main/sprites/hudnl.txt | 56 ++-- 14 files changed, 918 insertions(+), 153 deletions(-) diff --git a/main/source/cl_dll/ammo.cpp b/main/source/cl_dll/ammo.cpp index 92a2ee1a..9a10ce6e 100644 --- a/main/source/cl_dll/ammo.cpp +++ b/main/source/cl_dll/ammo.cpp @@ -589,6 +589,10 @@ void WeaponsResource :: SelectSlot( int iSlot, int fAdvance, int iDirection ) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ int giBucketHeight, giBucketWidth, giABHeight, giABWidth; // Ammo Bar width and height +cvar_t* hud_ammo_x; +cvar_t* hud_ammo_y; +cvar_t* hud_ammo_scale; +cvar_t* hud_ammo_alphamin; AVHHSPRITE ghsprBuckets; // Sprite for top row of weapons menu @@ -651,6 +655,11 @@ int CHudAmmo::Init(void) CVAR_CREATE( "hud_drawhistory_time", HISTORY_DRAW_TIME, 0 ); CVAR_CREATE( "hud_fastswitch", "0", FCVAR_ARCHIVE ); // controls whether or not weapons can be selected in one keypress + hud_ammo_x = CVAR_CREATE("hud_ammo_x", "0", FCVAR_ARCHIVE); + hud_ammo_y = CVAR_CREATE("hud_ammo_y", "0", FCVAR_ARCHIVE); + hud_ammo_scale = CVAR_CREATE("hud_ammo_scale", "1", FCVAR_ARCHIVE); + hud_ammo_alphamin = CVAR_CREATE("hud_ammo_alphamin", "128", FCVAR_ARCHIVE); + m_iFlags |= HUD_ACTIVE; //!!! gWR.Init(); @@ -1161,6 +1170,125 @@ void CHudAmmo::SetCurrentClip(int inClip) } } +//// Old HUD drawing +//int CHudAmmo::Draw(float flTime) +//{ +// int a, x, y, r, g, b; +// int AmmoWidth; +// +// if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) +// return 1; +// +// if (/*!gHUD.GetIsAlive() ||*/ (gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) ) +// return 1; +// +// // Draw Weapon Menu +// DrawWList(flTime); +// +// // Draw ammo pickup history +// gHR.DrawAmmoHistory( flTime ); +// +// if (!(m_iFlags & HUD_ACTIVE)) +// return 0; +// +// if (!m_pWeapon) +// return 0; +// +// WEAPON *pw = m_pWeapon; // shorthand +// +// // SPR_Draw Ammo +// if ((pw->iAmmoType < 0) && (pw->iAmmo2Type < 0)) +// return 0; +// +// +// int iFlags = DHN_DRAWZERO; // draw 0 values +// +// AmmoWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; +// +// a = (int) max( MIN_ALPHA, m_fFade ); +// +// if (m_fFade > 0) +// m_fFade -= (gHUD.m_flTimeDelta * 20); +// +// gHUD.GetPrimaryHudColor(r, g, b); +// +// ScaleColors(r, g, b, a ); +// +// int theViewport[4]; +// gHUD.GetViewport(theViewport); +// +// // Does this weapon have a clip? +// y = theViewport[1] + theViewport[3] - gHUD.m_iFontHeight - gHUD.m_iFontHeight/2; +// +// // Does weapon have any ammo at all? +// if (m_pWeapon->iAmmoType > 0) +// { +// int iIconWidth = m_pWeapon->rcAmmo.right - m_pWeapon->rcAmmo.left; +// +// if (pw->iClip >= 0) +// { +// // room for the number and the '|' and the current ammo +// +// x = theViewport[0] + theViewport[2] - (8 * AmmoWidth) - iIconWidth; +// x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, pw->iClip, r, g, b); +// +// wrect_t rc; +// rc.top = 0; +// rc.left = 0; +// rc.right = AmmoWidth; +// rc.bottom = 100; +// +// int iBarWidth = AmmoWidth/10; +// +// x += AmmoWidth/2; +// +// gHUD.GetPrimaryHudColor(r, g, b); +// +// // draw the | bar +// FillRGBA(x, y, iBarWidth, gHUD.m_iFontHeight, r, g, b, a); +// +// x += iBarWidth + AmmoWidth/2;; +// +// // GL Seems to need this +// ScaleColors(r, g, b, a ); +// x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, gWR.CountAmmo(pw->iAmmoType), r, g, b); +// +// +// } +// else +// { +// // SPR_Draw a bullets only line +// x = theViewport[0] + theViewport[2] - 4 * AmmoWidth - iIconWidth; +// x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, gWR.CountAmmo(pw->iAmmoType), r, g, b); +// } +// +// // Draw the ammo Icon +// int iOffset = (m_pWeapon->rcAmmo.bottom - m_pWeapon->rcAmmo.top)/8; +// SPR_Set(m_pWeapon->hAmmo, r, g, b); +// SPR_DrawAdditive(0, x, y - iOffset, &m_pWeapon->rcAmmo); +// } +// +// // Does weapon have seconday ammo? +// if (pw->iAmmo2Type > 0) +// { +// int iIconWidth = m_pWeapon->rcAmmo2.right - m_pWeapon->rcAmmo2.left; +// +// // Do we have secondary ammo? +// if ((pw->iAmmo2Type != 0) && (gWR.CountAmmo(pw->iAmmo2Type) > 0)) +// { +// y -= gHUD.m_iFontHeight + gHUD.m_iFontHeight/4; +// x = theViewport[0] + theViewport[2] - 4 * AmmoWidth - iIconWidth; +// x = gHUD.DrawHudNumber(x, y, iFlags|DHN_3DIGITS, gWR.CountAmmo(pw->iAmmo2Type), r, g, b); +// +// // Draw the ammo Icon +// SPR_Set(m_pWeapon->hAmmo2, r, g, b); +// int iOffset = (m_pWeapon->rcAmmo2.bottom - m_pWeapon->rcAmmo2.top)/8; +// SPR_DrawAdditive(0, x, y - iOffset, &m_pWeapon->rcAmmo2); +// } +// } +// return 1; +//} + //------------------------------------------------------------------------- // Drawing code //------------------------------------------------------------------------- @@ -1197,9 +1325,21 @@ int CHudAmmo::Draw(float flTime) int iFlags = DHN_DRAWZERO; // draw 0 values - AmmoWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + float ammoScale = 1.0f; + if (hud_ammo_scale) + { + ammoScale = min(max(0.01f, hud_ammo_scale->value), 5.0f); + } - a = (int) max( MIN_ALPHA, m_fFade ); + AmmoWidth = (gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left) * ammoScale; + + int minAlpha; + if (hud_ammo_alphamin) + minAlpha = max(0, min(hud_ammo_alphamin->value, 255)); + else + minAlpha = MIN_ALPHA; + + a = (int)max(minAlpha, m_fFade); if (m_fFade > 0) m_fFade -= (gHUD.m_flTimeDelta * 20); @@ -1208,58 +1348,60 @@ int CHudAmmo::Draw(float flTime) ScaleColors(r, g, b, a ); - int theViewport[4]; - gHUD.GetViewport(theViewport); - // Does this weapon have a clip? - y = theViewport[1] + theViewport[3] - gHUD.m_iFontHeight - gHUD.m_iFontHeight/2; + int initialY = gHUD.GetHeight(); + if (hud_ammo_y && hud_ammo_y->value != 0.0f) + { + initialY *= min(max(0.0f, hud_ammo_y->value), 1.0f); + } + + y = initialY - gHUD.m_iFontHeight / 2; // Does weapon have any ammo at all? if (m_pWeapon->iAmmoType > 0) { - int iIconWidth = m_pWeapon->rcAmmo.right - m_pWeapon->rcAmmo.left; + int iIconWidth = (m_pWeapon->rcAmmo.right - m_pWeapon->rcAmmo.left) * ammoScale; if (pw->iClip >= 0) { // room for the number and the '|' and the current ammo - x = theViewport[0] + theViewport[2] - (8 * AmmoWidth) - iIconWidth; - x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, pw->iClip, r, g, b); + int initialX = gHUD.GetWidth(); + if (hud_ammo_x && hud_ammo_x->value != 0.0f) + { + initialX *= min(max(0.0f, hud_ammo_x->value), 1.0f); + } - wrect_t rc; - rc.top = 0; - rc.left = 0; - rc.right = AmmoWidth; - rc.bottom = 100; + x = initialX - (8 * AmmoWidth) - iIconWidth; + + x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, pw->iClip, r, g, b, 255, ammoScale, CHud::a_southwest); int iBarWidth = AmmoWidth/10; + int iBarHeight = gHUD.m_iFontHeight * -1; x += AmmoWidth/2; gHUD.GetPrimaryHudColor(r, g, b); // draw the | bar - FillRGBA(x, y, iBarWidth, gHUD.m_iFontHeight, r, g, b, a); + gHUD.DrawHudFill(x, y, iBarWidth, iBarHeight, r, g, b, a, ammoScale); - x += iBarWidth + AmmoWidth/2;; + x += iBarWidth + AmmoWidth/2; // GL Seems to need this ScaleColors(r, g, b, a ); - x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, gWR.CountAmmo(pw->iAmmoType), r, g, b); - + x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, gWR.CountAmmo(pw->iAmmoType), r, g, b, 255, ammoScale, CHud::a_southwest); } else { // SPR_Draw a bullets only line - x = theViewport[0] + theViewport[2] - 4 * AmmoWidth - iIconWidth; - x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, gWR.CountAmmo(pw->iAmmoType), r, g, b); + x = gHUD.GetWidth() - 4 * AmmoWidth - iIconWidth; + x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, gWR.CountAmmo(pw->iAmmoType), r, g, b, 255, ammoScale, CHud::a_southwest); } // Draw the ammo Icon - int iOffset = (m_pWeapon->rcAmmo.bottom - m_pWeapon->rcAmmo.top)/8; - SPR_Set(m_pWeapon->hAmmo, r, g, b); - SPR_DrawAdditive(0, x, y - iOffset, &m_pWeapon->rcAmmo); + gHUD.DrawHudSprite(m_pWeapon->hAmmo, 0, &m_pWeapon->rcAmmo, x, y, r, g, b, 255, ammoScale, CHud::a_southwest); } // Does weapon have seconday ammo? @@ -1271,13 +1413,11 @@ int CHudAmmo::Draw(float flTime) if ((pw->iAmmo2Type != 0) && (gWR.CountAmmo(pw->iAmmo2Type) > 0)) { y -= gHUD.m_iFontHeight + gHUD.m_iFontHeight/4; - x = theViewport[0] + theViewport[2] - 4 * AmmoWidth - iIconWidth; - x = gHUD.DrawHudNumber(x, y, iFlags|DHN_3DIGITS, gWR.CountAmmo(pw->iAmmo2Type), r, g, b); + x = gHUD.GetWidth() - 4 * AmmoWidth - iIconWidth; + x = gHUD.DrawHudNumber(x, y, iFlags|DHN_3DIGITS, gWR.CountAmmo(pw->iAmmo2Type), r, g, b, 255, ammoScale, CHud::a_southwest); // Draw the ammo Icon - SPR_Set(m_pWeapon->hAmmo2, r, g, b); - int iOffset = (m_pWeapon->rcAmmo2.bottom - m_pWeapon->rcAmmo2.top)/8; - SPR_DrawAdditive(0, x, y - iOffset, &m_pWeapon->rcAmmo2); + gHUD.DrawHudSprite(m_pWeapon->hAmmo2, 0, &m_pWeapon->rcAmmo2, x, y, r, b, g, 255, ammoScale, CHud::a_southwest); } } return 1; diff --git a/main/source/cl_dll/ammo_secondary.cpp b/main/source/cl_dll/ammo_secondary.cpp index ac554fa2..7f602eb3 100644 --- a/main/source/cl_dll/ammo_secondary.cpp +++ b/main/source/cl_dll/ammo_secondary.cpp @@ -70,8 +70,9 @@ int CHudAmmoSecondary :: Draw(float flTime) x -= (gHUD.GetSpriteRect(m_HUD_ammoicon).right - gHUD.GetSpriteRect(m_HUD_ammoicon).left); y -= (gHUD.GetSpriteRect(m_HUD_ammoicon).top - gHUD.GetSpriteRect(m_HUD_ammoicon).bottom); - SPR_Set( gHUD.GetSprite(m_HUD_ammoicon), r, g, b ); - SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_ammoicon) ); + //SPR_Set( gHUD.GetSprite(m_HUD_ammoicon), r, g, b ); + //SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_ammoicon) ); + gHUD.DrawHudSpriteIndex(m_HUD_ammoicon, x, y, r , g, b); } else { // move the cursor by the '0' char instead, since we don't have an icon to work with @@ -96,7 +97,8 @@ int CHudAmmoSecondary :: Draw(float flTime) { // draw the divider bar x -= (AmmoWidth / 2); - FillRGBA(x, y, (AmmoWidth/10), gHUD.m_iFontHeight, r, g, b, a); + //FillRGBA(x, y, (AmmoWidth/10), gHUD.m_iFontHeight, r, g, b, a); + gHUD.DrawHudFill(x, y, (AmmoWidth / 10), gHUD.m_iFontHeight, r, g ,b , MIN_ALPHA); } } diff --git a/main/source/cl_dll/battery.cpp b/main/source/cl_dll/battery.cpp index 03b40147..141771b5 100644 --- a/main/source/cl_dll/battery.cpp +++ b/main/source/cl_dll/battery.cpp @@ -95,7 +95,7 @@ int CHudBattery::Draw(float flTime) if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) return 1; - + // Has health changed? Flash the health # if (m_fFade) { @@ -105,23 +105,23 @@ int CHudBattery::Draw(float flTime) m_fFade -= (gHUD.m_flTimeDelta * 20); if (m_fFade <= 0) { - a = 128; + //a = 128; m_fFade = 0; } // Fade the health number back to dim - a = MIN_ALPHA + (m_fFade/FADE_TIME) * 128; + a = gHUD.m_Health.m_iMinAlpha + (m_fFade/FADE_TIME) * 128; } else - a = MIN_ALPHA; + a = gHUD.m_Health.m_iMinAlpha; ScaleColors(r, g, b, a ); - int iOffset = (m_prc1->bottom - m_prc1->top)/6; + //int iOffset = (m_prc1->bottom - m_prc1->top)/6; - int theInset = 0; + //int theInset = 0; //if(gHUD.GetIsAlien()) //{ // theInset = ScreenWidth()*kResourceEnergyBarWidth; @@ -130,8 +130,11 @@ int CHudBattery::Draw(float flTime) int theViewport[4]; gHUD.GetViewport(theViewport); - y = theViewport[1] + theViewport[3] - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; - x = theViewport[0] + theInset + kArmorLeftInset*ScreenWidth(); + //y = theViewport[1] + theViewport[3] - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; + //x = theViewport[0] + theInset + kArmorLeftInset*ScreenWidth(); + //y = m_iAnchorY - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; + y = m_iAnchorY - gHUD.m_iFontHeight / 2; + x = m_iAnchorX; // make sure we have the right sprite handles if ( !m_hSprite1 ) @@ -139,18 +142,101 @@ int CHudBattery::Draw(float flTime) if ( !m_hSprite2 ) m_hSprite2 = gHUD.GetSprite( gHUD.GetSpriteIndex( "suit_full" ) ); - SPR_Set(m_hSprite1, r, g, b ); - SPR_DrawAdditive( 0, x, y - iOffset, m_prc1); + //SPR_Set(m_hSprite1, r, g, b ); + //SPR_DrawAdditive( 0, x, y - iOffset, m_prc1); + gHUD.DrawHudSprite(m_hSprite1, 0, m_prc1, x, y, r, g, b, a, gHUD.m_Health.m_fHealthScale, CHud::a_southwest); if (rc.bottom > rc.top) { - SPR_Set(m_hSprite2, r, g, b ); - SPR_DrawAdditive( 0, x, y - iOffset + (rc.top - m_prc2->top), &rc); + //SPR_Set(m_hSprite2, r, g, b ); + //SPR_DrawAdditive( 0, x, y - iOffset + (rc.top - m_prc2->top), &rc); + gHUD.DrawHudSprite(m_hSprite2, 0, &rc, x, y, r, g, b, a, gHUD.m_Health.m_fHealthScale, CHud::a_southwest); } - x += (m_prc1->right - m_prc1->left); - x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iBat, r, g, b); + x += (m_prc1->right - m_prc1->left) * gHUD.m_Health.m_fHealthScale; + x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iBat, r, g, b, 255, gHUD.m_Health.m_fHealthScale, CHud::a_southwest); return 1; } + +//// Old HUD drawing. +//int CHudBattery::Draw(float flTime) +//{ +// +// if (gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH) +// return 1; +// +// int r, g, b, x, y, a; +// wrect_t rc; +// +// rc = *m_prc2; +// +// int theMaxArmor = gHUD.GetHUDMaxArmor(); +// float theScalar = 1.0f / theMaxArmor; +// +// rc.top += m_iHeight * ((float)(theMaxArmor - (min(theMaxArmor, m_iBat))) * theScalar); // battery can go from 0 to 100 so * 0.01 goes from 0 to 1 +// +// gHUD.GetPrimaryHudColor(r, g, b); +// +// if (!(gHUD.m_iWeaponBits & (1 << (WEAPON_SUIT)))) +// return 1; +// +// // Has health changed? Flash the health # +// if (m_fFade) +// { +// if (m_fFade > FADE_TIME) +// m_fFade = FADE_TIME; +// +// m_fFade -= (gHUD.m_flTimeDelta * 20); +// if (m_fFade <= 0) +// { +// a = 128; +// m_fFade = 0; +// } +// +// // Fade the health number back to dim +// +// a = MIN_ALPHA + (m_fFade / FADE_TIME) * 128; +// +// } +// else +// a = MIN_ALPHA; +// +// ScaleColors(r, g, b, a); +// +// int iOffset = (m_prc1->bottom - m_prc1->top) / 6; +// +// int theInset = 0; +// //if(gHUD.GetIsAlien()) +// //{ +// // theInset = ScreenWidth()*kResourceEnergyBarWidth; +// //} +// +// int theViewport[4]; +// gHUD.GetViewport(theViewport); +// +// y = theViewport[1] + theViewport[3] - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; +// x = theViewport[0] + theInset + kArmorLeftInset * ScreenWidth(); +// +// // make sure we have the right sprite handles +// if (!m_hSprite1) +// m_hSprite1 = gHUD.GetSprite(gHUD.GetSpriteIndex("suit_empty")); +// if (!m_hSprite2) +// m_hSprite2 = gHUD.GetSprite(gHUD.GetSpriteIndex("suit_full")); +// +// SPR_Set(m_hSprite1, r, g, b); +// SPR_DrawAdditive(0, x, y - iOffset, m_prc1); +// +// if (rc.bottom > rc.top) +// { +// SPR_Set(m_hSprite2, r, g, b); +// SPR_DrawAdditive(0, x, y - iOffset + (rc.top - m_prc2->top), &rc); +// } +// +// x += (m_prc1->right - m_prc1->left); +// x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iBat, r, g, b); +// +// return 1; +// +//} \ No newline at end of file diff --git a/main/source/cl_dll/chud.h b/main/source/cl_dll/chud.h index f2cab7a1..9cc9d848 100644 --- a/main/source/cl_dll/chud.h +++ b/main/source/cl_dll/chud.h @@ -19,6 +19,14 @@ private: float m_flMouseSensitivity; bool wstoggle; + float m_flScaleX; + float m_flScaleY; + unsigned int m_iConWidth; + unsigned int m_iConHeight; + float m_flOffsetX; + float m_flOffsetY; + byte m_bIsWidescreen; + public: AVHHSPRITE m_hsprCursor; @@ -35,17 +43,88 @@ public: cvar_t *m_pCvarStealMouse; cvar_t *m_pCvarDraw; bool m_bWindowed; + cvar_t* m_pCvarWidescreen; int m_iFontHeight; - int DrawHudNumber(int x, int y, int iFlags, int iNumber, int r, int g, int b ); + //int DrawHudNumber(int x, int y, int iFlags, int iNumber, int r, int g, int b ); int DrawHudStringCentered(int x, int y, int iMaxX, const char *szString, int r, int g, int b ); int DrawHudString(int x, int y, int iMaxX, const char *szString, int r, int g, int b ); int GetHudStringHeight(); int GetHudStringWidth(const char* szIt); int DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ); - int DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ); + //int DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ); int GetNumWidth(int iNumber, int iFlags); + // New stuff + typedef enum { + COLOR_DEFAULT = 0, + COLOR_PRIMARY, + COLOR_SECONDARY, + COLOR_WARNING, + COLOR_COUNT + } hudcolor_e; + + typedef enum { + a_northwest = 0, + a_north, + a_northeast, + a_west, + a_center, + a_east, + a_southwest, + a_south, + a_southeast, + } hudalign_e; + + void DrawHudSprite(AVHHSPRITE pic, int frame, wrect_t* rect, int x, int y, int r, int g, int b, int a = 255, float scale = 1.0f, hudalign_e alignment = a_northwest); + //void DrawHudSprite(AVHHSPRITE pic, int frame, wrect_t* rect, int x, int y, hudcolor_e color, int a = 255, hudalign_e alignment = a_northwest); + //void DrawHudSpriteIndex(int index, int x, int y, hudcolor_e color, int a = 255, hudalign_e alignment = a_northwest); + void DrawHudSpriteIndex(int index, int x, int y, int r, int g, int b, int a = 255, float scale = 1.0f, hudalign_e alignment = a_northwest); + void DrawHudFill(int x, int y, int w, int h, int r, int g, int b, int a, float scale = 1.0f); + //void DrawHudFill(int x, int y, int w, int h, hudcolor_e color, int a); + int DrawHudNumber(int x, int y, int iFlags, int iNumber, int r, int g, int b, int a = 255, float scale = 1.0f, hudalign_e alignment = a_northwest); + //int DrawHudNumber(int x, int y, int iFlags, int iNumber, hudcolor_e color, int a = 255, hudalign_e alignment = a_northwest); + + //int DrawHudStringReverse(int xpos, int ypos, int iMinX, const char* szString, int r, int g, int b); + int DrawHudNumberString(int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b); + + int GetHudNumberWidth(int number, int width, int flags, float scale = 1.0f); + int DrawHudNumberReverse(int x, int y, int number, int flags, int r, int g, int b, int a = 255, float scale = 1.0f, hudalign_e alignment = a_northwest); + //int DrawHudNumberReverse(int x, int y, int number, int flags, hudcolor_e color, int a = 255, hudalign_e alignment = a_northwest); + + //int DrawHudString(const char* string, int x, int y); + //void GetHudStringSize(const char* string, int& width, int& height); + //int HudStringLen(const char* string); + + //void GetChatInputPosition(int& x, int& y); + + inline unsigned int GetWidth() const + { + return m_iConWidth; + } + + inline unsigned int GetHeight() const + { + return m_iConHeight; + } + + inline float GetScaleX() const + { + return m_flScaleX; + } + + inline float GetScaleY() const + { + return m_flScaleY; + } + + //inline void GetColor(int& r, int& g, int& b, hudcolor_e color) const + //{ + // r = m_cColors[color].r; + // g = m_cColors[color].g; + // b = m_cColors[color].b; + //} + private: // the memory for these arrays are allocated in the first call to CHud::VidInit(), when the hud.txt and associated sprites are loaded. // freed in ~CHud() diff --git a/main/source/cl_dll/chudmisc.h b/main/source/cl_dll/chudmisc.h index 9456d87d..f4748cf0 100644 --- a/main/source/cl_dll/chudmisc.h +++ b/main/source/cl_dll/chudmisc.h @@ -23,7 +23,8 @@ #define DHN_DRAWZERO 1 #define DHN_2DIGITS 2 #define DHN_3DIGITS 4 -#define MIN_ALPHA 100 +//#define MIN_ALPHA 100 +#define MIN_ALPHA 128 #define HUDELEM_ACTIVE 1 @@ -342,7 +343,10 @@ public: int VidInit( void ); int Draw(float flTime); int MsgFunc_Battery(const char *pszName, int iSize, void *pbuf ); - + + int m_iAnchorX; // our x position, set by the health hud + int m_iAnchorY; // our y position, set by the health hud + private: AVHHSPRITE m_hSprite1; AVHHSPRITE m_hSprite2; diff --git a/main/source/cl_dll/health.cpp b/main/source/cl_dll/health.cpp index 45a6eb06..d26ed1c0 100644 --- a/main/source/cl_dll/health.cpp +++ b/main/source/cl_dll/health.cpp @@ -28,6 +28,7 @@ #include "mod/AvHHudConstants.h" #include "mod/AvHPlayerUpgrade.h" #include "mod/AvHNetworkMessages.h" +#include "chudmisc.h" DECLARE_MESSAGE(m_Health, Health ) DECLARE_MESSAGE(m_Health, Damage ) @@ -66,9 +67,14 @@ int CHudHealth::Init(void) m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; giDmgHeight = 0; giDmgWidth = 0; + m_fHealthScale = 1.0f; memset(m_dmg, 0, sizeof(DAMAGE_IMAGE) * NUM_DMG_TYPES); + hud_health_x = CVAR_CREATE("hud_health_x", "0", FCVAR_ARCHIVE); + hud_health_y = CVAR_CREATE("hud_health_y", "0", FCVAR_ARCHIVE); + hud_health_scale = CVAR_CREATE("hud_health_scale", "1", FCVAR_ARCHIVE); + hud_health_alphamin = CVAR_CREATE("hud_health_alphamin", "128", FCVAR_ARCHIVE); gHUD.AddHudElem(this); return 1; @@ -160,6 +166,91 @@ void CHudHealth::GetPainColor( int &r, int &g, int &b ) } } + +//// Old HUD drawing. +//int CHudHealth::Draw(float flTime) +//{ +// int r, g, b; +// int a = 0, x, y; +// int HealthWidth; +// +// if ( /*!gHUD.GetIsAlive() ||*/ (gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH) /*|| gEngfuncs.IsSpectateOnly()*/) +// return 1; +// +// if (!m_hSprite) +// m_hSprite = LoadSprite(PAIN_NAME); +// +// // Has health changed? Flash the health # +// if (m_fFade) +// { +// m_fFade -= (gHUD.m_flTimeDelta * 20); +// if (m_fFade <= 0) +// { +// a = MIN_ALPHA; +// m_fFade = 0; +// } +// +// // Fade the health number back to dim +// +// a = MIN_ALPHA + (m_fFade / FADE_TIME) * 128; +// +// } +// else +// a = MIN_ALPHA; +// +// // Potentially se upgrades and health of spectator target +// int theUpgrades = gHUD.GetHUDUpgrades(); +// +// // If health is getting low, make it bright red +// int theMaxHealth = gHUD.GetHUDMaxHealth(); +// if (m_iHealth <= theMaxHealth / 10) +// { +// a = 255; +// } +// +// GetPainColor(r, g, b); +// ScaleColors(r, g, b, a); +// +// int theViewport[4]; +// gHUD.GetViewport(theViewport); +// +// // Only draw health if we have the suit. +// if (gHUD.m_iWeaponBits & (1 << (WEAPON_SUIT))) +// { +// HealthWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; +// int CrossWidth = gHUD.GetSpriteRect(m_HUD_cross).right - gHUD.GetSpriteRect(m_HUD_cross).left; +// +// y = theViewport[1] + theViewport[3] - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; +// +// x = theViewport[0] + CrossWidth / 2; +// +// int theInset = 0; +// //if(gHUD.GetIsAlien() && !gHUD.GetIsCombatMode()) +// //{ +// // theInset = ScreenWidth()*kResourceEnergyBarWidth; +// //} +// x += theInset;// + kHealthLeftInset*ScreenWidth; +// +// SPR_Set(gHUD.GetSprite(m_HUD_cross), r, g, b); +// SPR_DrawAdditive(0, x, y, &gHUD.GetSpriteRect(m_HUD_cross)); +// +// x += CrossWidth + HealthWidth / 2; +// +// x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iHealth, r, g, b); +// +// x += HealthWidth / 2; +// +// gHUD.GetPrimaryHudColor(r, g, b); +// +// int iHeight = gHUD.m_iFontHeight; +// int iWidth = HealthWidth / 10; +// FillRGBA(x, y, iWidth, iHeight, r, g, b, a); +// } +// +// DrawDamage(flTime); +// return DrawPain(flTime); +//} + int CHudHealth::Draw(float flTime) { int r, g, b; @@ -172,23 +263,24 @@ int CHudHealth::Draw(float flTime) if ( !m_hSprite ) m_hSprite = LoadSprite(PAIN_NAME); + if (hud_health_alphamin) + m_iMinAlpha = max(0, min(hud_health_alphamin->value, 255)); + else + m_iMinAlpha = MIN_ALPHA; + // Has health changed? Flash the health # if (m_fFade) { m_fFade -= (gHUD.m_flTimeDelta * 20); if (m_fFade <= 0) { - a = MIN_ALPHA; m_fFade = 0; } - // Fade the health number back to dim - - a = MIN_ALPHA + (m_fFade/FADE_TIME) * 128; - + a = m_iMinAlpha + (m_fFade/FADE_TIME) * 128; } else - a = MIN_ALPHA; + a = m_iMinAlpha; // Potentially se upgrades and health of spectator target int theUpgrades = gHUD.GetHUDUpgrades(); @@ -203,40 +295,53 @@ int CHudHealth::Draw(float flTime) GetPainColor( r, g, b ); ScaleColors(r, g, b, a ); - int theViewport[4]; - gHUD.GetViewport(theViewport); - // Only draw health if we have the suit. if (gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT))) { - HealthWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; - int CrossWidth = gHUD.GetSpriteRect(m_HUD_cross).right - gHUD.GetSpriteRect(m_HUD_cross).left; + if (hud_health_scale) + { + m_fHealthScale = min(max(0.01f, hud_health_scale->value), 5.0f); + } - y = theViewport[1] + theViewport[3] - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; - - x = theViewport[0] + CrossWidth /2; + HealthWidth = (gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left) * m_fHealthScale; + int CrossWidth = (gHUD.GetSpriteRect(m_HUD_cross).right - gHUD.GetSpriteRect(m_HUD_cross).left) * m_fHealthScale; - int theInset = 0; - //if(gHUD.GetIsAlien() && !gHUD.GetIsCombatMode()) - //{ - // theInset = ScreenWidth()*kResourceEnergyBarWidth; - //} - x += theInset;// + kHealthLeftInset*ScreenWidth; - - SPR_Set(gHUD.GetSprite(m_HUD_cross), r, g, b); - SPR_DrawAdditive(0, x, y, &gHUD.GetSpriteRect(m_HUD_cross)); + if (hud_health_x && hud_health_x->value != 0.0f) + { + x = min(max(0.0f, hud_health_x->value), 1.0f) * gHUD.GetWidth(); + } + else + { + x = CrossWidth / 2; + } + int initialY = gHUD.GetHeight(); + if (hud_health_y && hud_health_y->value != 0.0f) + { + initialY *= min(max(0.0f, hud_health_y->value), 1.0f); + } + + y = initialY - gHUD.m_iFontHeight / 2; + + gHUD.DrawHudSpriteIndex(m_HUD_cross, x, y, r, g, b, 255, m_fHealthScale, CHud::a_southwest); x += CrossWidth + HealthWidth / 2; + + //Reserve space for 3 digits by default, but allow it to expand + x += gHUD.GetHudNumberWidth(m_iHealth, 3, DHN_DRAWZERO, m_fHealthScale); - x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iHealth, r, g, b); + gHUD.DrawHudNumberReverse(x, y, m_iHealth, DHN_DRAWZERO, r, g, b, 255, m_fHealthScale, CHud::a_southwest); - x += HealthWidth/2; + x += HealthWidth /2; gHUD.GetPrimaryHudColor(r, g, b); - - int iHeight = gHUD.m_iFontHeight; - int iWidth = HealthWidth/10; - FillRGBA(x, y, iWidth, iHeight, r, g, b, a); + + int iHeight = gHUD.m_iFontHeight * -1; + int iWidth = max(1, HealthWidth /10); + + gHUD.DrawHudFill(x, y, iWidth, iHeight, r ,g , b, a, m_fHealthScale); + + gHUD.m_Battery.m_iAnchorX = x + + iWidth + HealthWidth / 2; + gHUD.m_Battery.m_iAnchorY = initialY; } DrawDamage(flTime); diff --git a/main/source/cl_dll/health.h b/main/source/cl_dll/health.h index 97a8a4b1..961d9e09 100644 --- a/main/source/cl_dll/health.h +++ b/main/source/cl_dll/health.h @@ -101,6 +101,8 @@ public: float m_fAttackFront, m_fAttackRear, m_fAttackLeft, m_fAttackRight; void GetPainColor( int &r, int &g, int &b ); float m_fFade; + float m_fHealthScale; + int m_iMinAlpha; private: AVHHSPRITE m_hSprite; @@ -112,6 +114,11 @@ private: int DrawDamage(float fTime); void CalcDamageDirection(vec3_t vecFrom); void UpdateTiles(float fTime, long bits); + + cvar_t* hud_health_x; + cvar_t* hud_health_y; + cvar_t* hud_health_scale; + cvar_t* hud_health_alphamin; }; #endif diff --git a/main/source/cl_dll/hud.cpp b/main/source/cl_dll/hud.cpp index 8f94e9b3..7373d18f 100644 --- a/main/source/cl_dll/hud.cpp +++ b/main/source/cl_dll/hud.cpp @@ -230,6 +230,7 @@ void CHud :: Init( void ) CVAR_CREATE("hud_nameinfo", "1", FCVAR_ARCHIVE); CVAR_CREATE("hud_drawwaypoints", "2", FCVAR_ARCHIVE); CVAR_CREATE("hud_waypointblinking", "0", FCVAR_ARCHIVE); + m_pCvarWidescreen = gEngfuncs.pfnGetCvarPointer("gl_widescreen_yfov"); m_pSpriteList = NULL; @@ -367,6 +368,17 @@ void CHud :: VidInit( void ) else m_iRes = 640; + // HUD scaling + //m_iConHeight = m_iRes * 0.75F; + m_iConHeight = 600; // Replicate 800x600 scale since NS seems to have been designed for that. + + m_flScaleY = ScreenHeight() / (float)m_iConHeight; + m_flScaleX = m_flScaleY; + + m_flOffsetY = 0.0F; + + m_bIsWidescreen = m_pCvarWidescreen->value == 0.0F; + // Only load this once if ( !m_pSpriteList || gHUD.GetReInitHUD()) { diff --git a/main/source/cl_dll/hud_redraw.cpp b/main/source/cl_dll/hud_redraw.cpp index 93803944..589f7a0f 100644 --- a/main/source/cl_dll/hud_redraw.cpp +++ b/main/source/cl_dll/hud_redraw.cpp @@ -21,9 +21,13 @@ #include "mod/AvHFont.h" #include "vgui_TeamFortressViewport.h" +#include "common/com_model.h" +//#include "r_studioint.h" #define MAX_LOGO_FRAMES 56 +//extern engine_studio_api_t IEngineStudio; + int grgLogoFrame[MAX_LOGO_FRAMES] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 13, 13, 13, 13, 12, 11, 10, 9, 8, 14, 15, @@ -106,6 +110,22 @@ int CHud :: Redraw( float flTime, int intermission ) if ( m_flTimeDelta < 0 ) m_flTimeDelta = 0; + bool bWantWidescreen = m_pCvarWidescreen->value != 0.0F; + + if (bWantWidescreen != m_bIsWidescreen) + { + if (bWantWidescreen) + { + m_iConWidth = m_iConHeight * (ScreenWidth() / (float)ScreenHeight()); + } + else + { + m_iConWidth = m_iRes; + } + m_flOffsetX = (ScreenWidth() - m_iConWidth * m_flScaleX) / 2.0F; + m_bIsWidescreen = bWantWidescreen; + } + // Bring up the scoreboard during intermission if (gViewPort) { @@ -318,22 +338,253 @@ int CHud :: DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, } -int CHud :: DrawHudNumber( int x, int y, int iFlags, int iNumber, int r, int g, int b) +//// Old metod before scaling. +//int CHud :: DrawHudNumber( int x, int y, int iFlags, int iNumber, int r, int g, int b) +//{ +// int iWidth = GetSpriteRect(m_HUD_number_0).right - GetSpriteRect(m_HUD_number_0).left; +// int k; +// +// if (iNumber > 0) +// { +// // SPR_Draw 100's +// if (iNumber >= 100) +// { +// k = iNumber/100; +// SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); +// SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); +// x += iWidth; +// } +// else if (iFlags & (DHN_3DIGITS)) +// { +// //SPR_DrawAdditive( 0, x, y, &rc ); +// x += iWidth; +// } +// +// // SPR_Draw 10's +// if (iNumber >= 10) +// { +// k = (iNumber % 100)/10; +// SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); +// SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); +// x += iWidth; +// } +// else if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) +// { +// //SPR_DrawAdditive( 0, x, y, &rc ); +// x += iWidth; +// } +// +// // SPR_Draw ones +// k = iNumber % 10; +// SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); +// SPR_DrawAdditive(0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); +// x += iWidth; +// } +// else if (iFlags & DHN_DRAWZERO) +// { +// SPR_Set(GetSprite(m_HUD_number_0), r, g, b ); +// +// // SPR_Draw 100's +// if (iFlags & (DHN_3DIGITS)) +// { +// //SPR_DrawAdditive( 0, x, y, &rc ); +// x += iWidth; +// } +// +// if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) +// { +// //SPR_DrawAdditive( 0, x, y, &rc ); +// x += iWidth; +// } +// +// // SPR_Draw ones +// +// SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0)); +// x += iWidth; +// } +// +// return x; +//} + +// Toodles' sprite scaling with additional scaling option added. +void CHud::DrawHudSprite(AVHHSPRITE pic, int frame, wrect_t * rect, int x, int y, int r, int g, int b, int a, float scale, hudalign_e alignment) { - int iWidth = GetSpriteRect(m_HUD_number_0).right - GetSpriteRect(m_HUD_number_0).left; + auto sprw = gEngfuncs.pfnSPR_Width(pic, frame); + auto sprh = gEngfuncs.pfnSPR_Height(pic, frame); + + if (!rect) + { + static wrect_t rc; + rc.left = 0; + rc.right = sprw; + rc.top = 0; + rc.bottom = sprh; + rect = &rc; + } + + float xf = x; + float yf = y; + auto width = (rect->right - rect->left) * scale; + auto height = (rect->bottom - rect->top) * scale; + + switch (alignment) + { + case a_north: + case a_center: + case a_south: + xf -= width / 2.0F - 0.5F; + break; + case a_northeast: + case a_east: + case a_southeast: + xf -= width; + break; + } + + switch (alignment) + { + case a_west: + case a_center: + case a_east: + yf -= height / 2.0F - 0.5F; + break; + case a_southwest: + case a_south: + case a_southeast: + yf -= height; + break; + } + + //// No software mode in NS. + //if (!IEngineStudio.IsHardware()) + //{ + // x += m_flOffsetX; + // y += m_flOffsetY; + + // ScaleColors(r, g, b, a); + // gEngfuncs.pfnSPR_Set(pic, r, g, b); + + // // Toodles FIXME: Hack for the crosshair. + // if (alignment == a_center) + // { + // gEngfuncs.pfnSPR_DrawHoles(frame, x, y, rect); + // } + // else + // { + // gEngfuncs.pfnSPR_DrawAdditive(frame, x, y, rect); + // } + // return; + //} + + auto pSprite = const_cast(gEngfuncs.GetSpritePointer(pic)); + + auto x1 = roundf(m_flOffsetX + xf * m_flScaleX); + auto y1 = roundf(m_flOffsetY + yf * m_flScaleY); + auto x2 = roundf(m_flOffsetX + (xf + width) * m_flScaleX); + auto y2 = roundf(m_flOffsetY + (yf + height) * m_flScaleY); + + auto left = rect->left / (float)sprw; + auto right = rect->right / (float)sprw; + auto top = rect->top / (float)sprh; + auto bottom = rect->bottom / (float)sprh; + + gEngfuncs.pTriAPI->SpriteTexture(pSprite, frame); + + auto rendermode = kRenderTransAdd; + + // Toodles FIXME: Hack for the crosshair. + if (alignment == a_center) + { + rendermode = kRenderTransAlpha; + } + + gEngfuncs.pTriAPI->Color4fRendermode(r / 255.0F, g / 255.0F, b / 255.0F, a / 255.0F, rendermode); + gEngfuncs.pTriAPI->RenderMode(rendermode); + + gEngfuncs.pTriAPI->Begin(TRI_QUADS); + + gEngfuncs.pTriAPI->TexCoord2f(left, top); + gEngfuncs.pTriAPI->Vertex3f(x1, y1, 0); + + gEngfuncs.pTriAPI->TexCoord2f(right, top); + gEngfuncs.pTriAPI->Vertex3f(x2, y1, 0); + + gEngfuncs.pTriAPI->TexCoord2f(right, bottom); + gEngfuncs.pTriAPI->Vertex3f(x2, y2, 0); + + gEngfuncs.pTriAPI->TexCoord2f(left, bottom); + gEngfuncs.pTriAPI->Vertex3f(x1, y2, 0); + + gEngfuncs.pTriAPI->End(); + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); +} + +//void CHud::DrawHudSprite(AVHHSPRITE pic, int frame, wrect_t* rect, int x, int y, hudcolor_e color, int a, hudalign_e alignment) +//{ +// int r, g, b; +// GetColor(r, g, b, color); +// DrawHudSprite(pic, frame, rect, x, y, r, g, b, a, alignment); +//} + +void CHud::DrawHudSpriteIndex(int index, int x, int y, int r, int g, int b, int a, float scale, hudalign_e alignment) +{ + DrawHudSprite(GetSprite(index), 0, &GetSpriteRect(index), x, y, r, g, b, a, scale, alignment); +} + +//void CHud::DrawHudSpriteIndex(int index, int x, int y, hudcolor_e color, int a, hudalign_e alignment) +//{ +// int r, g, b; +// GetColor(r, g, b, color); +// DrawHudSprite(GetSprite(index), 0, &GetSpriteRect(index), x, y, r, g, b, a, alignment); +//} + +void CHud::DrawHudFill(int x, int y, int w, int h, int r, int g, int b, int a, float scale) +{ + x = roundf(m_flOffsetX + x * m_flScaleX); + y = roundf(m_flOffsetY + y * m_flScaleY); + w = roundf(w * m_flScaleX * scale); + h = roundf(h * m_flScaleY * scale); + + gEngfuncs.pfnFillRGBA(x, y, w, h, r, g, b, a); +} + +//void CHud::DrawHudFill(int x, int y, int w, int h, hudcolor_e color, int a) +//{ +// int r, g, b; +// GetColor(r, g, b, color); +// DrawHudFill(x, y, w, h, r, g, b, a); +//} + +//int CHud::DrawHudString(int xpos, int ypos, int iMaxX, const char* szIt, int r, int g, int b) +//{ +// auto x1 = roundf(m_flOffsetX + xpos * m_flScaleX); +// auto y1 = roundf(m_flOffsetY + ypos * m_flScaleY); +// return (gEngfuncs.pfnDrawString(x1, y1, szIt, r, g, b) - m_flOffsetX) / m_flScaleX; +//} + +//// draws a string from right to left (right-aligned) +//int CHud::DrawHudStringReverse(int xpos, int ypos, int iMinX, const char* szString, int r, int g, int b) +//{ +// auto x1 = roundf(m_flOffsetX + xpos * m_flScaleX); +// auto y1 = roundf(m_flOffsetY + ypos * m_flScaleY); +// return (gEngfuncs.pfnDrawStringReverse(x1, y1, szString, r, g, b) - m_flOffsetX) / m_flScaleX; +//} + +int CHud::DrawHudNumber(int x, int y, int iFlags, int iNumber, int r, int g, int b, int a, float scale, hudalign_e alignment) +{ + int iWidth = (GetSpriteRect(m_HUD_number_0).right - GetSpriteRect(m_HUD_number_0).left) * scale; int k; - + if (iNumber > 0) { // SPR_Draw 100's if (iNumber >= 100) { - k = iNumber/100; - SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); - SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + k = iNumber / 100; + DrawHudSpriteIndex(m_HUD_number_0 + k, x, y, r, g, b, a, scale, alignment); x += iWidth; } - else if (iFlags & (DHN_3DIGITS)) + else if ((iFlags & DHN_3DIGITS) != 0) { //SPR_DrawAdditive( 0, x, y, &rc ); x += iWidth; @@ -342,12 +593,11 @@ int CHud :: DrawHudNumber( int x, int y, int iFlags, int iNumber, int r, int g, // SPR_Draw 10's if (iNumber >= 10) { - k = (iNumber % 100)/10; - SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); - SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + k = (iNumber % 100) / 10; + DrawHudSpriteIndex(m_HUD_number_0 + k, x, y, r, g, b, a, scale, alignment); x += iWidth; } - else if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + else if ((iFlags & (DHN_3DIGITS | DHN_2DIGITS)) != 0) { //SPR_DrawAdditive( 0, x, y, &rc ); x += iWidth; @@ -355,36 +605,39 @@ int CHud :: DrawHudNumber( int x, int y, int iFlags, int iNumber, int r, int g, // SPR_Draw ones k = iNumber % 10; - SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); - SPR_DrawAdditive(0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + DrawHudSpriteIndex(m_HUD_number_0 + k, x, y, r, g, b, a, scale, alignment); x += iWidth; - } - else if (iFlags & DHN_DRAWZERO) + } + else if ((iFlags & DHN_DRAWZERO) != 0) { - SPR_Set(GetSprite(m_HUD_number_0), r, g, b ); - // SPR_Draw 100's - if (iFlags & (DHN_3DIGITS)) + if ((iFlags & DHN_3DIGITS) != 0) { //SPR_DrawAdditive( 0, x, y, &rc ); x += iWidth; } - if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + if ((iFlags & (DHN_3DIGITS | DHN_2DIGITS)) != 0) { //SPR_DrawAdditive( 0, x, y, &rc ); x += iWidth; } // SPR_Draw ones - - SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0)); + + DrawHudSpriteIndex(m_HUD_number_0, x, y, r, g, b, a, scale, alignment); x += iWidth; } return x; } +//int CHud::DrawHudNumber(int x, int y, int iFlags, int iNumber, hudcolor_e color, int a, hudalign_e alignment) +//{ +// int r, g, b; +// GetColor(r, g, b, color); +// return DrawHudNumber(x, y, iFlags, iNumber, r, g, b, a, alignment); +//} int CHud::GetNumWidth( int iNumber, int iFlags ) { @@ -412,4 +665,81 @@ int CHud::GetNumWidth( int iNumber, int iFlags ) } +int CHud::GetHudNumberWidth(int number, int width, int flags, float scale) +{ + const int digitWidth = (GetSpriteRect(m_HUD_number_0).right - GetSpriteRect(m_HUD_number_0).left) * scale; + int totalDigits = 0; + + if (number > 0) + { + totalDigits = static_cast(log10(number)) + 1; + } + else if ((flags & DHN_DRAWZERO) != 0) + { + totalDigits = 1; + } + + totalDigits = max(totalDigits, width); + + return totalDigits * digitWidth; +} + +int CHud::DrawHudNumberReverse(int x, int y, int number, int flags, int r, int g, int b, int a, float scale, hudalign_e alignment) +{ + if (number > 0 || (flags & DHN_DRAWZERO) != 0) + { + const int digitWidth = (GetSpriteRect(m_HUD_number_0).right - GetSpriteRect(m_HUD_number_0).left) * scale; + + int remainder = number; + + do + { + const int digit = remainder % 10; + const int digitSpriteIndex = m_HUD_number_0 + digit; + + //This has to happen *before* drawing because we're drawing in reverse + x -= digitWidth; + + DrawHudSpriteIndex(digitSpriteIndex, x, y, r, g, b, a, scale, alignment); + + remainder /= 10; + } while (remainder > 0); + } + + return x; +} + +//int CHud::DrawHudNumberReverse(int x, int y, int number, int flags, hudcolor_e color, int a, hudalign_e alignment) +//{ +// int r, g, b; +// GetColor(r, g, b, color); +// return DrawHudNumberReverse(x, y, number, flags, r, g, b, a, alignment); +//} + +//int CHud::DrawHudString(const char* string, int x, int y) +//{ +// auto x1 = roundf (m_flOffsetX + x * m_flScaleX); +// auto y1 = roundf (m_flOffsetY + y * m_flScaleY); +// return (gEngfuncs.pfnDrawConsoleString(x1, y1, (char*)string) - m_flOffsetX) / m_flScaleX; +//} +// +//void CHud::GetHudStringSize(const char* string, int& width, int& height) +//{ +// gEngfuncs.pfnDrawConsoleStringLen(string, &width, &height); +// width /= m_flScaleX; +// height /= m_flScaleY; +//} +// +//int CHud::HudStringLen(const char* string) +//{ +// int width, height; +// GetHudStringSize(string, width, height); +// return width; +//} +// +//void CHud::GetChatInputPosition(int& x, int& y) +//{ +// x = roundf (m_flOffsetX + m_SayText.m_iBaseX * m_flScaleX); +// y = roundf (m_flOffsetY + (m_SayText.m_iBaseY + m_SayText.m_iLineHeight) * m_flScaleY); +//} diff --git a/main/source/mod/AvHHudConstants.h b/main/source/mod/AvHHudConstants.h index 5c9137a9..3e903ca7 100644 --- a/main/source/mod/AvHHudConstants.h +++ b/main/source/mod/AvHHudConstants.h @@ -36,7 +36,7 @@ const float kPlayerStatusStatusSpacing = .02f; // health armor inset (it is inset by the alien energy indicator for aliens) const float kHealthLeftInset = .05f; -const float kArmorLeftInset = .1f; +const float kArmorLeftInset = .2f; // blip size const float kWorldBlipScale = 100; diff --git a/main/sprites/640hud7.spr b/main/sprites/640hud7.spr index a1e371977ab2602e750a3b8b2d506457e5291cf6..0a20fad37e02cbc7655ce43b2946830670326e2d 100644 GIT binary patch literal 66366 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 WSU|9VU;)7bf&~N%C~kp{DE%KCWdVQy diff --git a/main/sprites/640nlhud7.spr b/main/sprites/640nlhud7.spr index abff1df9608a3f33048816d67f3fa41e1ab12f24..9be7b3fbef91355c6d33e10c1f04ecccd859bdbd 100644 GIT binary patch delta 1095 zcmZvaO-~b17=;JhXo6Cx3`NEG1N;FEDTzj5CH-XhDARWMXlJ1G8;S;(nh=5&V~$%{ zXow3pCSABOEQm2C#JDguYK*#}DnG%M=iV8r5KeM4cg}g<_ukCB+pF&FRriabj(XJ9 z)>a?tcyzh`x!>E3sH+|O!z#Q%AI=6F_#ITy74pFwYT#E$MQ7NDu5bfC!zw%xA37rq zG$X1{Idk0_^dC~dRN#qzL^H}bU|u^E`u#GFF^;L{m7}#XrU`HC1DYdjU=19p_XjNY z*AiIPD)QP*O+BYrfnzW$fG#bL>gb{kj*5OQ@ckF5E!l5OM|I4>#tvcF6xw|&v2kMK z$71&JTQ)X9Y{J1N*v)JmdUsjqm5s%T#gE1IZ0*QHCy7lu*d)7UTHW^Uj8bt;ep8*8 z?qE8x^gpa<-%er)VhIOJ2(16$%k90}5qc=GB(bD}C5g?pu;(wO)+Cl9mU6I^zy|uM zz<;_TewRVaa4>_IH8VXkGn-ClG8xmfDAURin<6%KlRs-6)#+tEPdh{TiTiG6DDAyH zl#lT=Jt#SQFyCM|)5rIAF`R8;4xiwedDV8J(utLD&r-2w8Hfljrn?j&p72gyPiwR|s^jvIo{Nj^unK)2wydFkdWxL@?4R@AY-M7AK^!Xh?GJ*buR z6N-Xw#f#z^MVe)rW!99RcJ=kfUlDkGwg3PC delta 950 zcmZ9KyK7Tn7{)27i%~)RP6JiDxwz;gB@PZ+mp10)*4$IuG^x2?dZN@(p@t+zcPYWN;}(gM(1S$wBw5B~||bpYO|;^q3bO$ou1$a}F=h!S3wA?(By&&Bf>@ON407+6^EraKkKZ1lW8hNy8BE7b9N!Z<2CjQQ;mA9Op;s^l zu9=^4WO6sTn|k+g_i^{>-Ot_6-OoL6m(M(dMxcgafJ|k~ov?y+41+>s-8b}n|D|Tn zoyB}8rv7Y&D2412-pD!pDy3B|txij$2kt2;tfereun?Ka>z+2`>?G__ymJ*eK{|sl+aRw zQbLH0iS+)Xnx>SJT1rw%^2jPYvd0H2MV2B{AzND--E{4ttr+XD?l)}3_<>`O!D5U{ z;k6Y4c$>a$E5tqyH!s=>@fzZt8SU_vbKy!=yzfzDta4`uCa{{OG z6@Kq&v~pxQvOHNqGAG)37ZV?`VpVzV0$G8qNUtQB6P==qiD6hGE7z3QDUp@P%4C(g zV$aKS=#