diff --git a/CMakeLists.txt b/CMakeLists.txt index 649be09e0..94e9b3cbc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -294,6 +294,8 @@ set(devilutionx_SRCS Source/controls/modifier_hints.cpp Source/controls/plrctrls.cpp Source/controls/touch.cpp + Source/qol/common.cpp + Source/qol/xpbar.cpp Source/utils/console.cpp Source/utils/display.cpp Source/utils/file_util.cpp diff --git a/Packaging/resources/devilutionx.mpq b/Packaging/resources/devilutionx.mpq index 1a52d468d..d62e01e30 100644 Binary files a/Packaging/resources/devilutionx.mpq and b/Packaging/resources/devilutionx.mpq differ diff --git a/Source/control.cpp b/Source/control.cpp index 46f07179e..4dc48d3af 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -17,6 +17,7 @@ #include "lighting.h" #include "minitext.h" #include "missiles.h" +#include "qol/xpbar.h" #include "stores.h" #include "towners.h" #include "trigs.h" @@ -1116,6 +1117,11 @@ void CheckPanelInfo() } if (MouseX > 190 + PANEL_LEFT && MouseX < 437 + PANEL_LEFT && MouseY > 4 + PANEL_TOP && MouseY < 33 + PANEL_TOP) pcursinvitem = CheckInvHLight(); + + if (CheckXPBarInfo()) { + panelflag = true; + pinfoflag = true; + } } /** diff --git a/Source/qol.cpp b/Source/qol.cpp index c7f2aa4a1..61392a225 100644 --- a/Source/qol.cpp +++ b/Source/qol.cpp @@ -8,6 +8,8 @@ #include "cursor.h" #include "DiabloUI/art_draw.h" #include "options.h" +#include "qol/common.h" +#include "qol/xpbar.h" namespace devilution { namespace { @@ -29,11 +31,6 @@ int GetTextWidth(const char *s) return l; } -void FastDrawHorizLine(CelOutputBuffer out, int x, int y, int width, BYTE col) -{ - memset(out.at(x, y), col, width); -} - void FastDrawVertLine(CelOutputBuffer out, int x, int y, int height, BYTE col) { BYTE *p = out.at(x, y); @@ -43,19 +40,14 @@ void FastDrawVertLine(CelOutputBuffer out, int x, int y, int height, BYTE col) } } -void FillRect(CelOutputBuffer out, int x, int y, int width, int height, BYTE col) -{ - for (int j = 0; j < height; j++) { - FastDrawHorizLine(out, x, y + j, width, col); - } -} - } // namespace void FreeQol() { delete qolArt; qolArt = nullptr; + + FreeXPBar(); } void InitQol() @@ -72,6 +64,8 @@ void InitQol() app_fatal("Failed to load UI resources. Is devilutionx.mpq accessible and up to date?"); } } + + InitXPBar(); } void DrawMonsterHealthBar(CelOutputBuffer out) @@ -152,45 +146,7 @@ void DrawMonsterHealthBar(CelOutputBuffer out) } } -void DrawXPBar(CelOutputBuffer out) -{ - if (!sgOptions.Gameplay.bExperienceBar) - return; - int barWidth = 306; - int barHeight = 5; - int yPos = gnScreenHeight - 9; // y position of xp bar - int xPos = (gnScreenWidth - barWidth) / 2 + 5; // x position of xp bar - int dividerHeight = 3; - int numDividers = 10; - int barColor = 198; - int emptyBarColor = 0; - int frameColor = 196; - bool space = true; // add 1 pixel separator on top/bottom of the bar - - PrintGameStr(out, xPos - 22, yPos + 6, "XP", COL_WHITE); - int charLevel = plr[myplr]._pLevel; - if (charLevel == MAXCHARLEVEL - 1) - return; - - int prevXp = ExpLvlsTbl[charLevel - 1]; - if (plr[myplr]._pExperience < prevXp) - return; - - Uint64 prevXpDelta_1 = plr[myplr]._pExperience - prevXp; - int prevXpDelta = ExpLvlsTbl[charLevel] - prevXp; - int visibleBar = barWidth * prevXpDelta_1 / prevXpDelta; - - FillRect(out, xPos, yPos, barWidth, barHeight, emptyBarColor); - FastDrawHorizLine(out, xPos - 1, yPos - 1, barWidth + 2, frameColor); - FastDrawHorizLine(out, xPos - 1, yPos + barHeight, barWidth + 2, frameColor); - FastDrawVertLine(out, xPos - 1, yPos - 1, barHeight + 2, frameColor); - FastDrawVertLine(out, xPos + barWidth, yPos - 1, barHeight + 2, frameColor); - for (int i = 1; i < numDividers; i++) - FastDrawVertLine(out, xPos - 1 + (barWidth * i / numDividers), yPos - dividerHeight + 3, barHeight, 245); - - FillRect(out, xPos, yPos + (space ? 1 : 0), visibleBar, barHeight - (space ? 2 : 0), barColor); -} bool HasRoomForGold() { diff --git a/Source/qol.h b/Source/qol.h index c10eb528d..64736bd42 100644 --- a/Source/qol.h +++ b/Source/qol.h @@ -12,7 +12,6 @@ namespace devilution { void FreeQol(); void InitQol(); void DrawMonsterHealthBar(CelOutputBuffer out); -void DrawXPBar(CelOutputBuffer out); void AutoGoldPickup(int pnum); } // namespace devilution diff --git a/Source/qol/common.cpp b/Source/qol/common.cpp new file mode 100644 index 000000000..62e35cf39 --- /dev/null +++ b/Source/qol/common.cpp @@ -0,0 +1,27 @@ +/** +* @file common.h +* +* Common functions for QoL features +*/ + +#include "common.h" +#include "engine.h" + +namespace devilution { + +void FastDrawHorizLine(const CelOutputBuffer &out, int x, int y, int width, BYTE col) +{ + memset(out.at(x, y), col, width); +} + +char *PrintWithSeparator(char *out, long long n) +{ + if (n < 1000) { + return out + sprintf(out, "%lld", n); + } + + char *append = PrintWithSeparator(out, n / 1000); + return append + sprintf(append, ",%03lld", n % 1000); +} + +} // namespace devilution diff --git a/Source/qol/common.h b/Source/qol/common.h new file mode 100644 index 000000000..4e4dde4d2 --- /dev/null +++ b/Source/qol/common.h @@ -0,0 +1,23 @@ +/** +* @file common.h +* +* Common functions for QoL features +*/ +#pragma once + +#include "SDL_stdinc.h" // for Uint8 + +namespace devilution { + +struct CelOutputBuffer; + +void FastDrawHorizLine(const CelOutputBuffer &out, int x, int y, int width, Uint8 col); +/** + * @brief Prints integer into buffer, using ',' as thousands separator. + * @param out Destination buffer + * @param n Number to print + * @return Address of first character after printed number +*/ +char *PrintWithSeparator(char *out, long long n); + +} // namespace devilution diff --git a/Source/qol/xpbar.cpp b/Source/qol/xpbar.cpp new file mode 100644 index 000000000..20b8f237c --- /dev/null +++ b/Source/qol/xpbar.cpp @@ -0,0 +1,154 @@ +/** +* @file xpbar.cpp +* +* Adds XP bar QoL feature +*/ + +#include "common.h" +#include "control.h" +#include "DiabloUI/art_draw.h" +#include "options.h" + +#include + +namespace devilution { + +namespace { + +constexpr int BAR_WIDTH = 307; +constexpr int BAR_HEIGHT = 5; + +using ColorGradient = std::array; +constexpr ColorGradient GOLD_GRADIENT = { 0xCF, 0xCE, 0xCD, 0xCC, 0xCB, 0xCA, 0xC9, 0xC8, 0xC7, 0xC6, 0xC5, 0xC4 }; +constexpr ColorGradient SILVER_GRADIENT = { 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3 }; + +constexpr int BACK_WIDTH = 313; +constexpr int BACK_HEIGHT = 9; + +Art xpbarArt; + +void DrawBar(const CelOutputBuffer &out, int x, int y, int width, const ColorGradient &gradient) +{ + FastDrawHorizLine(out, x, y + 1, width, gradient[gradient.size() * 3 / 4 - 1]); + FastDrawHorizLine(out, x, y + 2, width, gradient[gradient.size() - 1]); + FastDrawHorizLine(out, x, y + 3, width, gradient[gradient.size() / 2 - 1]); +} + +void DrawEndCap(const CelOutputBuffer &out, int x, int y, int idx, const ColorGradient &gradient) +{ + SetPixel(out, x, y + 1, gradient[idx * 3 / 4]); + SetPixel(out, x, y + 2, gradient[idx]); + SetPixel(out, x, y + 3, gradient[idx / 2]); +} + +} // namespace + +void InitXPBar() +{ + if (sgOptions.Gameplay.bExperienceBar) { + LoadMaskedArt("data\\xpbar.pcx", &xpbarArt, 1, 1); + + if (xpbarArt.surface == nullptr) { + app_fatal("Failed to load UI resources. Is devilutionx.mpq accessible and up to date?"); + } + } +} + +void FreeXPBar() +{ + xpbarArt.Unload(); +} + +void DrawXPBar(const CelOutputBuffer &out) +{ + if (!sgOptions.Gameplay.bExperienceBar) + return; + + const PlayerStruct &player = plr[myplr]; + + const int backX = PANEL_LEFT + PANEL_WIDTH / 2 - 155; + const int backY = PANEL_TOP + PANEL_HEIGHT - 11; + + const int xPos = backX + 3; + const int yPos = backY + 2; + + DrawArt(out, backX, backY, &xpbarArt); + + const int charLevel = player._pLevel; + + if (charLevel == MAXCHARLEVEL - 1) { + // Draw a nice golden bar for max level characters. + DrawBar(out, xPos, yPos, BAR_WIDTH, GOLD_GRADIENT); + + return; + } + + const int prevXp = ExpLvlsTbl[charLevel - 1]; + if (player._pExperience < prevXp) + return; + + Uint64 prevXpDelta_1 = player._pExperience - prevXp; + Uint64 prevXpDelta = ExpLvlsTbl[charLevel] - prevXp; + Uint64 fullBar = BAR_WIDTH * prevXpDelta_1 / prevXpDelta; + + // Figure out how much to fill the last pixel of the XP bar, to make it gradually appear with gained XP + Uint64 onePx = prevXpDelta / BAR_WIDTH; + Uint64 lastFullPx = fullBar * prevXpDelta / BAR_WIDTH; + + const Uint64 fade = (prevXpDelta_1 - lastFullPx) * (SILVER_GRADIENT.size() - 1) / onePx; + + // Draw beginning of bar full brightness + DrawBar(out, xPos, yPos, fullBar, SILVER_GRADIENT); + + // End pixels appear gradually + DrawEndCap(out, xPos + fullBar, yPos, fade, SILVER_GRADIENT); +} + +bool CheckXPBarInfo() +{ + if (!sgOptions.Gameplay.bExperienceBar) + return false; + + const int backX = PANEL_LEFT + PANEL_WIDTH / 2 - 155; + const int backY = PANEL_TOP + PANEL_HEIGHT - 11; + + if (MouseX < backX || MouseX >= backX + BACK_WIDTH || MouseY < backY || MouseY >= backY + BACK_HEIGHT) + return false; + + const PlayerStruct &player = plr[myplr]; + + const int charLevel = player._pLevel; + + sprintf(tempstr, "Level %d", charLevel); + AddPanelString(tempstr, true); + + if (charLevel == MAXCHARLEVEL - 1) { + // Show a maximum level indicator for max level players. + infoclr = COL_GOLD; + + sprintf(tempstr, "Experience: "); + PrintWithSeparator(tempstr + SDL_arraysize("Experience: ") - 1, ExpLvlsTbl[charLevel - 1]); + AddPanelString(tempstr, true); + + AddPanelString("Maximum Level", true); + + return true; + } + + infoclr = COL_WHITE; + + sprintf(tempstr, "Experience: "); + PrintWithSeparator(tempstr + SDL_arraysize("Experience: ") - 1, player._pExperience); + AddPanelString(tempstr, true); + + sprintf(tempstr, "Next Level: "); + PrintWithSeparator(tempstr + SDL_arraysize("Next Level: ") - 1, ExpLvlsTbl[charLevel]); + AddPanelString(tempstr, true); + + sprintf(PrintWithSeparator(tempstr, ExpLvlsTbl[charLevel] - player._pExperience), " to Level %d", charLevel + 1); + AddPanelString(tempstr, true); + + return true; +} + +} // namespace devilution diff --git a/Source/qol/xpbar.h b/Source/qol/xpbar.h new file mode 100644 index 000000000..56e45d75e --- /dev/null +++ b/Source/qol/xpbar.h @@ -0,0 +1,18 @@ +/** +* @file xpbar.h +* +* Adds XP bar QoL feature +*/ +#pragma once + +namespace devilution { + +struct CelOutputBuffer; + +void InitXPBar(); +void FreeXPBar(); + +void DrawXPBar(const CelOutputBuffer &out); +bool CheckXPBarInfo(); + +} // namespace devilution diff --git a/Source/scrollrt.cpp b/Source/scrollrt.cpp index c82ca66e1..76fe8275d 100644 --- a/Source/scrollrt.cpp +++ b/Source/scrollrt.cpp @@ -24,6 +24,7 @@ #include "nthread.h" #include "plrmsg.h" #include "qol.h" +#include "qol/xpbar.h" #include "render.h" #include "stores.h" #include "towners.h"