Browse Source

Floating Item Info Box (#8000)

pull/7877/head
Eric Robinson 7 months ago committed by GitHub
parent
commit
e303a82b1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 239
      Source/control.cpp
  2. 6
      Source/control.h
  3. 29
      Source/engine/render/primitive_render.cpp
  4. 3
      Source/engine/render/primitive_render.hpp
  5. 2
      Source/engine/render/scrollrt.cpp
  6. 2
      Source/inv.cpp
  7. 146
      Source/items.cpp
  8. 2
      Source/options.cpp
  9. 2
      Source/options.h
  10. 5
      Source/qol/stash.cpp
  11. 4
      Source/qol/stash.h

239
Source/control.cpp

@ -26,6 +26,7 @@
#include "engine/clx_sprite.hpp"
#include "engine/load_cel.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/primitive_render.hpp"
#include "engine/render/text_render.hpp"
#include "engine/trn.hpp"
#include "gamemenu.h"
@ -62,6 +63,7 @@
#include "utils/status_macros.hpp"
#include "utils/str_case.hpp"
#include "utils/str_cat.hpp"
#include "utils/str_split.hpp"
#include "utils/string_or_view.hpp"
#include "utils/utf8.hpp"
@ -88,6 +90,7 @@ bool ChatFlag;
bool SpellbookFlag;
bool CharFlag;
StringOrView InfoString;
StringOrView FloatingInfoString;
bool MainPanelFlag;
bool MainPanelButtonDown;
bool SpellSelectFlag;
@ -340,6 +343,209 @@ void PrintInfo(const Surface &out)
});
}
Rectangle GetFloatingInfoRect(const int lineHeight, const int textSpacing)
{
// Calculate the width and height of the floating info box
std::string txt = std::string(FloatingInfoString);
auto lines = SplitByChar(txt, '\n');
const GameFontTables font = GameFont12;
int maxW = 0;
for (const auto &line : lines) {
int w = GetLineWidth(line, font, textSpacing, nullptr);
maxW = std::max(maxW, w);
}
const auto lineCount = 1 + static_cast<int>(c_count(FloatingInfoString.str(), '\n'));
int totalH = lineCount * lineHeight;
Player &player = *InspectPlayer;
// 1) Equipment (Rect position)
if (pcursinvitem >= INVITEM_HEAD && pcursinvitem < INVITEM_INV_FIRST) {
int slot = pcursinvitem - INVITEM_HEAD;
static constexpr Point equipLocal[] = {
{ 133, 59 },
{ 48, 205 },
{ 249, 205 },
{ 205, 60 },
{ 17, 160 },
{ 248, 160 },
{ 133, 160 },
};
Point itemPosition = equipLocal[slot];
auto &item = player.InvBody[slot];
Size frame = GetInvItemSize(item._iCurs + CURSOR_FIRSTITEM);
if (slot == INVLOC_HAND_LEFT) {
itemPosition.x += frame.width == InventorySlotSizeInPixels.width
? InventorySlotSizeInPixels.width
: 0;
itemPosition.y += frame.height == 3 * InventorySlotSizeInPixels.height
? 0
: -InventorySlotSizeInPixels.height;
} else if (slot == INVLOC_HAND_RIGHT) {
itemPosition.x += frame.width == InventorySlotSizeInPixels.width
? (InventorySlotSizeInPixels.width - 1)
: 1;
itemPosition.y += frame.height == 3 * InventorySlotSizeInPixels.height
? 0
: -InventorySlotSizeInPixels.height;
}
itemPosition.y++; // Align position to bottom left of the item graphic
itemPosition.x += frame.width / 2; // Align position to center of the item graphic
itemPosition.x -= maxW / 2; // Align position to the center of the floating item info box
Point screen = GetPanelPosition(UiPanels::Inventory, itemPosition);
return { { screen.x, screen.y }, { maxW, totalH } };
}
// 2) Inventory grid (Rect position)
if (pcursinvitem >= INVITEM_INV_FIRST && pcursinvitem < INVITEM_INV_FIRST + InventoryGridCells) {
int itemIdx = pcursinvitem - INVITEM_INV_FIRST;
for (int j = 0; j < InventoryGridCells; ++j) {
if (player.InvGrid[j] > 0 && player.InvGrid[j] - 1 == itemIdx) {
Item &it = player.InvList[itemIdx];
Point itemPosition = InvRect[j + SLOTXY_INV_FIRST].position;
itemPosition.x += GetInventorySize(it).width * InventorySlotSizeInPixels.width / 2; // Align position to center of the item graphic
itemPosition.x -= maxW / 2; // Align position to the center of the floating item info box
Point screen = GetPanelPosition(UiPanels::Inventory, itemPosition);
return { { screen.x, screen.y }, { maxW, totalH } };
}
}
}
// 3) Belt (Rect position)
if (pcursinvitem >= INVITEM_BELT_FIRST && pcursinvitem < INVITEM_BELT_FIRST + MaxBeltItems) {
int itemIdx = pcursinvitem - INVITEM_BELT_FIRST;
for (int i = 0; i < MaxBeltItems; ++i) {
if (player.SpdList[i].isEmpty())
continue;
if (i != itemIdx)
continue;
Item &item = player.SpdList[i];
Point itemPosition = InvRect[i + SLOTXY_BELT_FIRST].position;
itemPosition.x += GetInventorySize(item).width * InventorySlotSizeInPixels.width / 2; // Align position to center of the item graphic
itemPosition.x -= maxW / 2; // Align position to the center of the floating item info box
Point screen = GetMainPanel().position + Displacement { itemPosition.x, itemPosition.y };
return { { screen.x, screen.y }, { maxW, totalH } };
}
}
// 4) Stash (Rect position)
if (pcursstashitem != StashStruct::EmptyCell) {
for (auto slot : StashGridRange) {
auto itemId = Stash.GetItemIdAtPosition(slot);
if (itemId == StashStruct::EmptyCell)
continue;
if (itemId != pcursstashitem)
continue;
Item &item = Stash.stashList[itemId];
Point itemPosition = GetStashSlotCoord(slot);
Size itemGridSize = GetInventorySize(item);
itemPosition.y += itemGridSize.height * (InventorySlotSizeInPixels.height + 1) - 1; // Align position to bottom left of the item graphic
itemPosition.x += itemGridSize.width * InventorySlotSizeInPixels.width / 2; // Align position to center of the item graphic
itemPosition.x -= maxW / 2; // Align position to the center of the floating item info box
return { { itemPosition.x, itemPosition.y }, { maxW, totalH } };
}
}
return { { 0, 0 }, { 0, 0 } };
}
int GetHoverSpriteHeight()
{
if (pcursinvitem >= INVITEM_HEAD && pcursinvitem < INVITEM_INV_FIRST) {
auto &it = (*InspectPlayer).InvBody[pcursinvitem - INVITEM_HEAD];
return GetInvItemSize(it._iCurs + CURSOR_FIRSTITEM).height + 1;
}
if (pcursinvitem >= INVITEM_INV_FIRST
&& pcursinvitem < INVITEM_INV_FIRST + InventoryGridCells) {
int idx = pcursinvitem - INVITEM_INV_FIRST;
auto &it = (*InspectPlayer).InvList[idx];
return GetInventorySize(it).height * (InventorySlotSizeInPixels.height + 1)
- InventorySlotSizeInPixels.height;
}
if (pcursinvitem >= INVITEM_BELT_FIRST
&& pcursinvitem < INVITEM_BELT_FIRST + MaxBeltItems) {
int idx = pcursinvitem - INVITEM_BELT_FIRST;
auto &it = (*InspectPlayer).SpdList[idx];
return GetInventorySize(it).height * (InventorySlotSizeInPixels.height + 1)
- InventorySlotSizeInPixels.height - 1;
}
if (pcursstashitem != StashStruct::EmptyCell) {
auto &it = Stash.stashList[pcursstashitem];
return GetInventorySize(it).height * (InventorySlotSizeInPixels.height + 1);
}
return InventorySlotSizeInPixels.height;
}
int ClampAboveOrBelow(int anchorY, int spriteH, int boxH, int pad, int linePad)
{
int yAbove = anchorY - spriteH - boxH - pad;
int yBelow = anchorY + linePad / 2 + pad;
return (yAbove >= 0) ? yAbove : yBelow;
}
void PrintFloatingInfo(const Surface &out)
{
if (ChatFlag)
return;
if (FloatingInfoString.empty())
return;
const int verticalSpacing = 3;
const int lineHeight = 12 + verticalSpacing;
const int textSpacing = 2;
const int hPadding = 5;
const int vPadding = 4;
Rectangle floatingInfoBox = GetFloatingInfoRect(lineHeight, textSpacing);
// Prevent the floating info box from going off-screen horizontally
floatingInfoBox.position.x = std::clamp(floatingInfoBox.position.x, hPadding, GetScreenWidth() - (floatingInfoBox.size.width + hPadding));
int spriteH = GetHoverSpriteHeight();
int anchorY = floatingInfoBox.position.y;
int boxH = floatingInfoBox.size.height;
int yAbove = anchorY - spriteH - boxH - vPadding;
int yBelow = anchorY + verticalSpacing / 2 + vPadding;
// Prevent the floating info box from going off-screen vertically
floatingInfoBox.position.y = ClampAboveOrBelow(anchorY, spriteH, floatingInfoBox.size.height, vPadding, verticalSpacing);
SpeakText(FloatingInfoString);
for (int i = 0; i < 3; i++)
DrawHalfTransparentRectTo(out, floatingInfoBox.position.x - hPadding, floatingInfoBox.position.y - vPadding, floatingInfoBox.size.width + hPadding * 2, floatingInfoBox.size.height + vPadding * 2);
DrawHalfTransparentVerticalLine(out, { floatingInfoBox.position.x - hPadding - 1, floatingInfoBox.position.y - vPadding - 1 }, floatingInfoBox.size.height + (vPadding * 2) + 2, PAL16_GRAY + 10);
DrawHalfTransparentVerticalLine(out, { floatingInfoBox.position.x + hPadding + floatingInfoBox.size.width, floatingInfoBox.position.y - vPadding - 1 }, floatingInfoBox.size.height + (vPadding * 2) + 2, PAL16_GRAY + 10);
DrawHalfTransparentHorizontalLine(out, { floatingInfoBox.position.x - hPadding, floatingInfoBox.position.y - vPadding - 1 }, floatingInfoBox.size.width + (hPadding * 2), PAL16_GRAY + 10);
DrawHalfTransparentHorizontalLine(out, { floatingInfoBox.position.x - hPadding, floatingInfoBox.position.y + vPadding + floatingInfoBox.size.height }, floatingInfoBox.size.width + (hPadding * 2), PAL16_GRAY + 10);
DrawString(out, FloatingInfoString, floatingInfoBox,
{
.flags = InfoColor | UiFlags::AlignCenter | UiFlags::VerticalCenter,
.spacing = textSpacing,
.lineHeight = lineHeight,
});
}
int CapStatPointsToAdd(int remainingStatPoints, const Player &player, CharacterAttribute attribute)
{
int pointsToReachCap = player.GetMaximumAttributeValue(attribute) - player.GetBaseAttributeValue(attribute);
@ -802,20 +1008,24 @@ void ToggleCharPanel()
OpenCharPanel();
}
void AddInfoBoxString(std::string_view str)
void AddInfoBoxString(std::string_view str, bool floatingBox /*= false*/)
{
if (InfoString.empty())
InfoString = str;
StringOrView &infoString = floatingBox ? FloatingInfoString : InfoString;
if (infoString.empty())
infoString = str;
else
InfoString = StrCat(InfoString, "\n", str);
infoString = StrCat(infoString, "\n", str);
}
void AddInfoBoxString(std::string &&str)
void AddInfoBoxString(std::string &&str, bool floatingBox /*= false*/)
{
if (InfoString.empty())
InfoString = std::move(str);
StringOrView &infoString = floatingBox ? FloatingInfoString : InfoString;
if (infoString.empty())
infoString = std::move(str);
else
InfoString = StrCat(InfoString, "\n", str);
infoString = StrCat(infoString, "\n", str);
}
Point GetPanelPosition(UiPanels panel, Point offset)
@ -943,6 +1153,7 @@ tl::expected<void, std::string> InitMainPanel()
buttonEnabled = false;
CharPanelButtonActive = false;
InfoString = StringOrView {};
FloatingInfoString = StringOrView {};
RedrawComponent(PanelDrawComponent::Health);
RedrawComponent(PanelDrawComponent::Mana);
CloseCharPanel();
@ -1080,6 +1291,7 @@ void CheckPanelInfo()
{
MainPanelFlag = false;
InfoString = StringOrView {};
FloatingInfoString = StringOrView {};
int totalButtons = IsChatAvailable() ? TotalMpMainPanelButtons : TotalSpMainPanelButtons;
@ -1307,6 +1519,17 @@ void DrawInfoBox(const Surface &out)
PrintInfo(out);
}
void DrawFloatingInfoBox(const Surface &out)
{
if (pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell) {
FloatingInfoString = StringOrView {};
InfoColor = UiFlags::ColorWhite;
}
if (!FloatingInfoString.empty())
PrintFloatingInfo(out);
}
void CheckLevelButton()
{
if (!IsLevelUpButtonVisible()) {

6
Source/control.h

@ -51,6 +51,7 @@ extern bool ChatFlag;
extern bool SpellbookFlag;
extern bool CharFlag;
extern StringOrView InfoString;
extern StringOrView FloatingInfoString;
extern bool MainPanelFlag;
extern bool MainPanelButtonDown;
extern bool SpellSelectFlag;
@ -83,8 +84,8 @@ inline bool CanPanelsCoverView()
return GetScreenWidth() <= mainPanel.size.width && GetScreenHeight() <= SidePanelSize.height + mainPanel.size.height;
}
void AddInfoBoxString(std::string_view str);
void AddInfoBoxString(std::string &&str);
void AddInfoBoxString(std::string_view str, bool floatingBox = false);
void AddInfoBoxString(std::string &&str, bool floatingBox = false);
void DrawPanelBox(const Surface &out, SDL_Rect srcRect, Point targetPosition);
Point GetPanelPosition(UiPanels panel, Point offset = { 0, 0 });
@ -168,6 +169,7 @@ void FreeControlPan();
* Sets a string to be drawn in the info box and then draws it.
*/
void DrawInfoBox(const Surface &out);
void DrawFloatingInfoBox(const Surface &out);
void CheckLevelButton();
void CheckLevelButtonUp();
void DrawLevelButton(const Surface &out);

29
Source/engine/render/primitive_render.cpp

@ -127,6 +127,35 @@ void UnsafeDrawVerticalLine(const Surface &out, Point from, int height, std::uin
}
}
void DrawHalfTransparentHorizontalLine(const Surface &out, Point from, int width, uint8_t colorIndex)
{
// completely off-bounds?
if (from.y < 0 || from.y >= out.h() || width <= 0 || from.x >= out.w() || from.x + width <= 0)
return;
int x0 = std::max(0, from.x);
int x1 = std::min(out.w(), from.x + width);
for (int x = x0; x < x1; ++x) {
SetHalfTransparentPixel(out, { x, from.y }, colorIndex);
}
}
// Draw a half-transparent vertical line of `height` pixels starting at `from`.
void DrawHalfTransparentVerticalLine(const Surface &out, Point from, int height, uint8_t colorIndex)
{
// completely off-bounds?
if (from.x < 0 || from.x >= out.w() || height <= 0 || from.y >= out.h() || from.y + height <= 0)
return;
int y0 = std::max(0, from.y);
int y1 = std::min(out.h(), from.y + height);
for (int y = y0; y < y1; ++y) {
SetHalfTransparentPixel(out, { from.x, y }, colorIndex);
}
}
void DrawHalfTransparentRectTo(const Surface &out, int sx, int sy, int width, int height)
{
if (sx + width < 0)

3
Source/engine/render/primitive_render.hpp

@ -38,6 +38,9 @@ void DrawVerticalLine(const Surface &out, Point from, int height, std::uint8_t c
/** Same as DrawVerticalLine but without bounds clipping. */
void UnsafeDrawVerticalLine(const Surface &out, Point from, int height, std::uint8_t colorIndex);
void DrawHalfTransparentHorizontalLine(const Surface &out, Point from, int width, uint8_t colorIndex);
void DrawHalfTransparentVerticalLine(const Surface &out, Point from, int width, uint8_t colorIndex);
/**
* Draws a half-transparent rectangle by palette blending with black.
*

2
Source/engine/render/scrollrt.cpp

@ -1782,6 +1782,8 @@ void DrawAndBlit()
DrawFlaskValues(out, { mainPanel.position.x + mainPanel.size.width - 138, mainPanel.position.y + 28 },
(HasAnyOf(InspectPlayer->_pIFlags, ItemSpecialEffect::NoMana) || (MyPlayer->_pMana >> 6) <= 0) ? 0 : MyPlayer->_pMana >> 6,
HasAnyOf(InspectPlayer->_pIFlags, ItemSpecialEffect::NoMana) ? 0 : MyPlayer->_pMaxMana >> 6);
if (*GetOptions().Gameplay.floatingInfoBox)
DrawFloatingInfoBox(out);
DrawCursor(out);

2
Source/inv.cpp

@ -1984,9 +1984,11 @@ int8_t CheckInvHLight()
if (pi->_itype == ItemType::Gold) {
int nGold = pi->_ivalue;
InfoString = fmt::format(fmt::runtime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold)), FormatInteger(nGold));
FloatingInfoString = fmt::format(fmt::runtime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold)), FormatInteger(nGold));
} else {
InfoColor = pi->getTextColor();
InfoString = pi->getName();
FloatingInfoString = pi->getName();
if (pi->_iIdentified) {
PrintItemDetails(*pi);
} else {

146
Source/items.cpp

@ -1597,90 +1597,90 @@ void PrintItemOil(char iDidx)
{
switch (iDidx) {
case IMISC_OILACC:
AddInfoBoxString(_("increases a weapon's"));
AddInfoBoxString(_("chance to hit"));
AddInfoBoxString(_("increases a weapon's"), true);
AddInfoBoxString(_("chance to hit"), true);
break;
case IMISC_OILMAST:
AddInfoBoxString(_("greatly increases a"));
AddInfoBoxString(_("weapon's chance to hit"));
AddInfoBoxString(_("greatly increases a"), true);
AddInfoBoxString(_("weapon's chance to hit"), true);
break;
case IMISC_OILSHARP:
AddInfoBoxString(_("increases a weapon's"));
AddInfoBoxString(_("damage potential"));
AddInfoBoxString(_("increases a weapon's"), true);
AddInfoBoxString(_("damage potential"), true);
break;
case IMISC_OILDEATH:
AddInfoBoxString(_("greatly increases a weapon's"));
AddInfoBoxString(_("damage potential - not bows"));
AddInfoBoxString(_("greatly increases a weapon's"), true);
AddInfoBoxString(_("damage potential - not bows"), true);
break;
case IMISC_OILSKILL:
AddInfoBoxString(_("reduces attributes needed"));
AddInfoBoxString(_("to use armor or weapons"));
AddInfoBoxString(_("reduces attributes needed"), true);
AddInfoBoxString(_("to use armor or weapons"), true);
break;
case IMISC_OILBSMTH:
AddInfoBoxString(/*xgettext:no-c-format*/ _("restores 20% of an"));
AddInfoBoxString(_("item's durability"));
AddInfoBoxString(/*xgettext:no-c-format*/ _("restores 20% of an"), true);
AddInfoBoxString(_("item's durability"), true);
break;
case IMISC_OILFORT:
AddInfoBoxString(_("increases an item's"));
AddInfoBoxString(_("current and max durability"));
AddInfoBoxString(_("increases an item's"), true);
AddInfoBoxString(_("current and max durability"), true);
break;
case IMISC_OILPERM:
AddInfoBoxString(_("makes an item indestructible"));
AddInfoBoxString(_("makes an item indestructible"), true);
break;
case IMISC_OILHARD:
AddInfoBoxString(_("increases the armor class"));
AddInfoBoxString(_("of armor and shields"));
AddInfoBoxString(_("increases the armor class"), true);
AddInfoBoxString(_("of armor and shields"), true);
break;
case IMISC_OILIMP:
AddInfoBoxString(_("greatly increases the armor"));
AddInfoBoxString(_("class of armor and shields"));
AddInfoBoxString(_("greatly increases the armor"), true);
AddInfoBoxString(_("class of armor and shields"), true);
break;
case IMISC_RUNEF:
AddInfoBoxString(_("sets fire trap"));
AddInfoBoxString(_("sets fire trap"), true);
break;
case IMISC_RUNEL:
case IMISC_GR_RUNEL:
AddInfoBoxString(_("sets lightning trap"));
AddInfoBoxString(_("sets lightning trap"), true);
break;
case IMISC_GR_RUNEF:
AddInfoBoxString(_("sets fire trap"));
AddInfoBoxString(_("sets fire trap"), true);
break;
case IMISC_RUNES:
AddInfoBoxString(_("sets petrification trap"));
AddInfoBoxString(_("sets petrification trap"), true);
break;
case IMISC_FULLHEAL:
AddInfoBoxString(_("restore all life"));
AddInfoBoxString(_("restore all life"), true);
break;
case IMISC_HEAL:
AddInfoBoxString(_("restore some life"));
AddInfoBoxString(_("restore some life"), true);
break;
case IMISC_MANA:
AddInfoBoxString(_("restore some mana"));
AddInfoBoxString(_("restore some mana"), true);
break;
case IMISC_FULLMANA:
AddInfoBoxString(_("restore all mana"));
AddInfoBoxString(_("restore all mana"), true);
break;
case IMISC_ELIXSTR:
AddInfoBoxString(_("increase strength"));
AddInfoBoxString(_("increase strength"), true);
break;
case IMISC_ELIXMAG:
AddInfoBoxString(_("increase magic"));
AddInfoBoxString(_("increase magic"), true);
break;
case IMISC_ELIXDEX:
AddInfoBoxString(_("increase dexterity"));
AddInfoBoxString(_("increase dexterity"), true);
break;
case IMISC_ELIXVIT:
AddInfoBoxString(_("increase vitality"));
AddInfoBoxString(_("increase vitality"), true);
break;
case IMISC_REJUV:
AddInfoBoxString(_("restore some life and mana"));
AddInfoBoxString(_("restore some life and mana"), true);
break;
case IMISC_FULLREJUV:
AddInfoBoxString(_("restore all life and mana"));
AddInfoBoxString(_("restore all life and mana"), true);
break;
case IMISC_ARENAPOT:
AddInfoBoxString(_("restore all life and mana"));
AddInfoBoxString(_("(works only in arenas)"));
AddInfoBoxString(_("restore all life and mana"), true);
AddInfoBoxString(_("(works only in arenas)"), true);
break;
}
}
@ -1715,32 +1715,32 @@ Point DrawUniqueInfoWindow(const Surface &out)
void printItemMiscKBM(const Item &item, const bool isOil, const bool isCastOnTarget)
{
if (item._iMiscId == IMISC_MAPOFDOOM) {
AddInfoBoxString(_("Right-click to view"));
AddInfoBoxString(_("Right-click to view"), true);
} else if (isOil) {
PrintItemOil(item._iMiscId);
AddInfoBoxString(_("Right-click to use"));
AddInfoBoxString(_("Right-click to use"), true);
} else if (isCastOnTarget) {
AddInfoBoxString(_("Right-click to read, then\nleft-click to target"));
AddInfoBoxString(_("Right-click to read, then\nleft-click to target"), true);
} else if (IsAnyOf(item._iMiscId, IMISC_BOOK, IMISC_NOTE, IMISC_SCROLL, IMISC_SCROLLT)) {
AddInfoBoxString(_("Right-click to read"));
AddInfoBoxString(_("Right-click to read"), true);
}
}
void printItemMiscGenericGamepad(const Item &item, const bool isOil, bool isCastOnTarget)
{
if (item._iMiscId == IMISC_MAPOFDOOM) {
AddInfoBoxString(_("Activate to view"));
AddInfoBoxString(_("Activate to view"), true);
} else if (isOil) {
PrintItemOil(item._iMiscId);
if (!invflag) {
AddInfoBoxString(_("Open inventory to use"));
AddInfoBoxString(_("Open inventory to use"), true);
} else {
AddInfoBoxString(_("Activate to use"));
AddInfoBoxString(_("Activate to use"), true);
}
} else if (isCastOnTarget) {
AddInfoBoxString(_("Select from spell book, then\ncast spell to read"));
AddInfoBoxString(_("Select from spell book, then\ncast spell to read"), true);
} else if (IsAnyOf(item._iMiscId, IMISC_BOOK, IMISC_NOTE, IMISC_SCROLL, IMISC_SCROLLT)) {
AddInfoBoxString(_("Activate to read"));
AddInfoBoxString(_("Activate to read"), true);
}
}
@ -1754,29 +1754,29 @@ void printItemMiscGamepad(const Item &item, bool isOil, bool isCastOnTarget)
const std::string_view castButton = GetOptions().Padmapper.InputNameForAction("SpellAction");
if (item._iMiscId == IMISC_MAPOFDOOM) {
AddInfoBoxString(fmt::format(fmt::runtime(_("{} to view")), activateButton));
AddInfoBoxString(fmt::format(fmt::runtime(_("{} to view")), activateButton), true);
} else if (isOil) {
PrintItemOil(item._iMiscId);
if (!invflag) {
AddInfoBoxString(_("Open inventory to use"));
AddInfoBoxString(_("Open inventory to use"), true);
} else {
AddInfoBoxString(fmt::format(fmt::runtime(_("{} to use")), activateButton));
AddInfoBoxString(fmt::format(fmt::runtime(_("{} to use")), activateButton), true);
}
} else if (isCastOnTarget) {
AddInfoBoxString(fmt::format(fmt::runtime(_("Select from spell book,\nthen {} to read")), castButton));
AddInfoBoxString(fmt::format(fmt::runtime(_("Select from spell book,\nthen {} to read")), castButton), true);
} else if (IsAnyOf(item._iMiscId, IMISC_BOOK, IMISC_NOTE, IMISC_SCROLL, IMISC_SCROLLT)) {
AddInfoBoxString(fmt::format(fmt::runtime(_("{} to read")), activateButton));
AddInfoBoxString(fmt::format(fmt::runtime(_("{} to read")), activateButton), true);
}
}
void PrintItemMisc(const Item &item)
{
if (item._iMiscId == IMISC_EAR) {
AddInfoBoxString(fmt::format(fmt::runtime(pgettext("player", "Level: {:d}")), item._ivalue));
AddInfoBoxString(fmt::format(fmt::runtime(pgettext("player", "Level: {:d}")), item._ivalue), true);
return;
}
if (item._iMiscId == IMISC_AURIC) {
AddInfoBoxString(_("Doubles gold capacity"));
AddInfoBoxString(_("Doubles gold capacity"), true);
return;
}
const bool isOil = (item._iMiscId >= IMISC_USEFIRST && item._iMiscId <= IMISC_USELAST)
@ -1816,7 +1816,7 @@ void PrintItemInfo(const Item &item)
text.append(fmt::format(fmt::runtime(_(" {:d} Mag")), mag));
if (dex != 0)
text.append(fmt::format(fmt::runtime(_(" {:d} Dex")), dex));
AddInfoBoxString(text);
AddInfoBoxString(text, true);
}
}
@ -4059,33 +4059,33 @@ void PrintItemDetails(const Item &item)
if (item._iClass == ICLASS_WEAPON) {
if (item._iMinDam == item._iMaxDam) {
if (item._iMaxDur == DUR_INDESTRUCTIBLE)
AddInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d} Indestructible")), item._iMinDam));
AddInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d} Indestructible")), item._iMinDam), true);
else
AddInfoBoxString(fmt::format(fmt::runtime(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d} Dur: {:d}/{:d}")), item._iMinDam, item._iDurability, item._iMaxDur));
AddInfoBoxString(fmt::format(fmt::runtime(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d} Dur: {:d}/{:d}")), item._iMinDam, item._iDurability, item._iMaxDur), true);
} else {
if (item._iMaxDur == DUR_INDESTRUCTIBLE)
AddInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d}-{:d} Indestructible")), item._iMinDam, item._iMaxDam));
AddInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d}-{:d} Indestructible")), item._iMinDam, item._iMaxDam), true);
else
AddInfoBoxString(fmt::format(fmt::runtime(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d}-{:d} Dur: {:d}/{:d}")), item._iMinDam, item._iMaxDam, item._iDurability, item._iMaxDur));
AddInfoBoxString(fmt::format(fmt::runtime(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d}-{:d} Dur: {:d}/{:d}")), item._iMinDam, item._iMaxDam, item._iDurability, item._iMaxDur), true);
}
}
if (item._iClass == ICLASS_ARMOR) {
if (item._iMaxDur == DUR_INDESTRUCTIBLE)
AddInfoBoxString(fmt::format(fmt::runtime(_("armor: {:d} Indestructible")), item._iAC));
AddInfoBoxString(fmt::format(fmt::runtime(_("armor: {:d} Indestructible")), item._iAC), true);
else
AddInfoBoxString(fmt::format(fmt::runtime(_(/* TRANSLATORS: Dur: is durability */ "armor: {:d} Dur: {:d}/{:d}")), item._iAC, item._iDurability, item._iMaxDur));
AddInfoBoxString(fmt::format(fmt::runtime(_(/* TRANSLATORS: Dur: is durability */ "armor: {:d} Dur: {:d}/{:d}")), item._iAC, item._iDurability, item._iMaxDur), true);
}
if (item._iMiscId == IMISC_STAFF && item._iMaxCharges != 0) {
AddInfoBoxString(fmt::format(fmt::runtime(_("Charges: {:d}/{:d}")), item._iCharges, item._iMaxCharges));
AddInfoBoxString(fmt::format(fmt::runtime(_("Charges: {:d}/{:d}")), item._iCharges, item._iMaxCharges), true);
}
if (item._iPrePower != -1) {
AddInfoBoxString(PrintItemPower(item._iPrePower, item));
AddInfoBoxString(PrintItemPower(item._iPrePower, item), true);
}
if (item._iSufPower != -1) {
AddInfoBoxString(PrintItemPower(item._iSufPower, item));
AddInfoBoxString(PrintItemPower(item._iSufPower, item), true);
}
if (item._iMagical == ITEM_QUALITY_UNIQUE) {
AddInfoBoxString(_("unique item"));
AddInfoBoxString(_("unique item"), true);
ShowUniqueItemInfoBox = true;
curruitem = item;
}
@ -4100,34 +4100,34 @@ void PrintItemDur(const Item &item)
if (item._iClass == ICLASS_WEAPON) {
if (item._iMinDam == item._iMaxDam) {
if (item._iMaxDur == DUR_INDESTRUCTIBLE)
AddInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d} Indestructible")), item._iMinDam));
AddInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d} Indestructible")), item._iMinDam), true);
else
AddInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d} Dur: {:d}/{:d}")), item._iMinDam, item._iDurability, item._iMaxDur));
AddInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d} Dur: {:d}/{:d}")), item._iMinDam, item._iDurability, item._iMaxDur), true);
} else {
if (item._iMaxDur == DUR_INDESTRUCTIBLE)
AddInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d}-{:d} Indestructible")), item._iMinDam, item._iMaxDam));
AddInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d}-{:d} Indestructible")), item._iMinDam, item._iMaxDam), true);
else
AddInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d}-{:d} Dur: {:d}/{:d}")), item._iMinDam, item._iMaxDam, item._iDurability, item._iMaxDur));
AddInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d}-{:d} Dur: {:d}/{:d}")), item._iMinDam, item._iMaxDam, item._iDurability, item._iMaxDur), true);
}
if (item._iMiscId == IMISC_STAFF && item._iMaxCharges > 0) {
AddInfoBoxString(fmt::format(fmt::runtime(_("Charges: {:d}/{:d}")), item._iCharges, item._iMaxCharges));
AddInfoBoxString(fmt::format(fmt::runtime(_("Charges: {:d}/{:d}")), item._iCharges, item._iMaxCharges), true);
}
if (item._iMagical != ITEM_QUALITY_NORMAL)
AddInfoBoxString(_("Not Identified"));
AddInfoBoxString(_("Not Identified"), true);
}
if (item._iClass == ICLASS_ARMOR) {
if (item._iMaxDur == DUR_INDESTRUCTIBLE)
AddInfoBoxString(fmt::format(fmt::runtime(_("armor: {:d} Indestructible")), item._iAC));
AddInfoBoxString(fmt::format(fmt::runtime(_("armor: {:d} Indestructible")), item._iAC), true);
else
AddInfoBoxString(fmt::format(fmt::runtime(_("armor: {:d} Dur: {:d}/{:d}")), item._iAC, item._iDurability, item._iMaxDur));
AddInfoBoxString(fmt::format(fmt::runtime(_("armor: {:d} Dur: {:d}/{:d}")), item._iAC, item._iDurability, item._iMaxDur), true);
if (item._iMagical != ITEM_QUALITY_NORMAL)
AddInfoBoxString(_("Not Identified"));
AddInfoBoxString(_("Not Identified"), true);
if (item._iMiscId == IMISC_STAFF && item._iMaxCharges > 0) {
AddInfoBoxString(fmt::format(fmt::runtime(_("Charges: {:d}/{:d}")), item._iCharges, item._iMaxCharges));
AddInfoBoxString(fmt::format(fmt::runtime(_("Charges: {:d}/{:d}")), item._iCharges, item._iMaxCharges), true);
}
}
if (IsAnyOf(item._itype, ItemType::Ring, ItemType::Amulet))
AddInfoBoxString(_("Not Identified"));
AddInfoBoxString(_("Not Identified"), true);
PrintItemInfo(item);
}

2
Source/options.cpp

@ -787,6 +787,7 @@ GameplayOptions::GameplayOptions()
, showHealthValues("Show health values", OptionEntryFlags::None, N_("Show health values"), N_("Displays current / max health value on health globe."), false)
, showManaValues("Show mana values", OptionEntryFlags::None, N_("Show mana values"), N_("Displays current / max mana value on mana globe."), false)
, enemyHealthBar("Enemy Health Bar", OptionEntryFlags::None, N_("Enemy Health Bar"), N_("Enemy Health Bar is displayed at the top of the screen."), false)
, floatingInfoBox("Floating Item Info Box", OptionEntryFlags::None, N_("Floating Item Info Box"), N_("Displays item info in a floating box when hovering over an item."), false)
, autoGoldPickup("Auto Gold Pickup", OptionEntryFlags::None, N_("Auto Gold Pickup"), N_("Gold is automatically collected when in close proximity to the player."), false)
, autoElixirPickup("Auto Elixir Pickup", OptionEntryFlags::None, N_("Auto Elixir Pickup"), N_("Elixirs are automatically collected when in close proximity to the player."), false)
, autoOilPickup("Auto Oil Pickup", OptionEntryFlags::OnlyHellfire, N_("Auto Oil Pickup"), N_("Oils are automatically collected when in close proximity to the player."), false)
@ -837,6 +838,7 @@ std::vector<OptionEntryBase *> GameplayOptions::GetEntries()
&showHealthValues,
&showManaValues,
&enemyHealthBar,
&floatingInfoBox,
&showMonsterType,
&showItemLabels,
&enableFloatingNumbers,

2
Source/options.h

@ -580,6 +580,8 @@ struct GameplayOptions : OptionCategoryBase {
OptionEntryBoolean showManaValues;
/** @brief Show enemy health at the top of the screen. */
OptionEntryBoolean enemyHealthBar;
/** @brief Displays item info in a floating box when hovering over an ite. */
OptionEntryBoolean floatingInfoBox;
/** @brief Automatically pick up gold when walking over it. */
OptionEntryBoolean autoGoldPickup;
/** @brief Auto-pickup elixirs */

5
Source/qol/stash.cpp

@ -12,7 +12,6 @@
#include "cursor.h"
#include "engine/clx_sprite.hpp"
#include "engine/load_clx.hpp"
#include "engine/points_in_rectangle_range.hpp"
#include "engine/rectangle.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
@ -54,9 +53,6 @@ constexpr Rectangle StashButtonRect[] = {
// clang-format on
};
constexpr Size StashGridSize { 10, 10 };
constexpr PointsInRectangle<int> StashGridRange { { { 0, 0 }, StashGridSize } };
OptionalOwnedClxSpriteList StashPanelArt;
OptionalOwnedClxSpriteList StashNavButtonArt;
@ -448,6 +444,7 @@ uint16_t CheckStashHLight(Point mousePosition)
InfoColor = item.getTextColor();
InfoString = item.getName();
FloatingInfoString = item.getName();
if (item._iIdentified) {
PrintItemDetails(item);
} else {

4
Source/qol/stash.h

@ -11,6 +11,7 @@
#include <ankerl/unordered_dense.h>
#include "engine/point.hpp"
#include "engine/points_in_rectangle_range.hpp"
#include "items.h"
namespace devilution {
@ -73,6 +74,9 @@ extern StashStruct Stash;
extern bool IsWithdrawGoldOpen;
extern int WithdrawGoldValue;
inline constexpr Size StashGridSize { 10, 10 };
inline constexpr PointsInRectangle<int> StashGridRange { { { 0, 0 }, StashGridSize } };
Point GetStashSlotCoord(Point slot);
void InitStash();
void FreeStashGFX();

Loading…
Cancel
Save