You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
426 lines
16 KiB
426 lines
16 KiB
|
3 months ago
|
#include "control.hpp"
|
||
|
|
#include "control_panel.hpp"
|
||
|
|
|
||
|
|
#include "engine/render/primitive_render.hpp"
|
||
|
|
#include "inv.h"
|
||
|
|
#include "levels/trigs.h"
|
||
|
|
#include "panels/partypanel.hpp"
|
||
|
|
#include "qol/stash.h"
|
||
|
|
#include "qol/xpbar.h"
|
||
|
|
#include "towners.h"
|
||
|
|
#include "utils/algorithm/container.hpp"
|
||
|
|
#include "utils/format_int.hpp"
|
||
|
|
#include "utils/log.hpp"
|
||
|
|
#include "utils/screen_reader.hpp"
|
||
|
|
#include "utils/str_cat.hpp"
|
||
|
|
#include "utils/str_split.hpp"
|
||
|
|
|
||
|
|
namespace devilution {
|
||
|
|
|
||
|
|
StringOrView InfoString;
|
||
|
|
StringOrView FloatingInfoString;
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
void PrintInfo(const Surface &out)
|
||
|
|
{
|
||
|
|
if (ChatFlag)
|
||
|
|
return;
|
||
|
|
|
||
|
|
const int space[] = { 18, 12, 6, 3, 0 };
|
||
|
|
Rectangle infoBox = InfoBoxRect;
|
||
|
|
|
||
|
|
SetPanelObjectPosition(UiPanels::Main, infoBox);
|
||
|
|
|
||
|
|
const auto newLineCount = static_cast<int>(c_count(InfoString.str(), '\n'));
|
||
|
|
const int spaceIndex = std::min(4, newLineCount);
|
||
|
|
const int spacing = space[spaceIndex];
|
||
|
|
const int lineHeight = 12 + spacing;
|
||
|
|
|
||
|
|
// Adjusting the line height to add spacing between lines
|
||
|
|
// will also add additional space beneath the last line
|
||
|
|
// which throws off the vertical centering
|
||
|
|
infoBox.position.y += spacing / 2;
|
||
|
|
|
||
|
|
SpeakText(InfoString);
|
||
|
|
|
||
|
|
DrawString(out, InfoString, infoBox,
|
||
|
|
{
|
||
|
|
.flags = InfoColor | UiFlags::AlignCenter | UiFlags::VerticalCenter | UiFlags::KerningFitSpacing,
|
||
|
|
.spacing = 2,
|
||
|
|
.lineHeight = lineHeight,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
Rectangle GetFloatingInfoRect(const int lineHeight, const int textSpacing)
|
||
|
|
{
|
||
|
|
// Calculate the width and height of the floating info box
|
||
|
|
const std::string txt = std::string(FloatingInfoString);
|
||
|
|
|
||
|
|
auto lines = SplitByChar(txt, '\n');
|
||
|
|
const GameFontTables font = GameFont12;
|
||
|
|
int maxW = 0;
|
||
|
|
|
||
|
|
for (const auto &line : lines) {
|
||
|
|
const int w = GetLineWidth(line, font, textSpacing, nullptr);
|
||
|
|
maxW = std::max(maxW, w);
|
||
|
|
}
|
||
|
|
|
||
|
|
const auto lineCount = 1 + static_cast<int>(c_count(FloatingInfoString.str(), '\n'));
|
||
|
|
const int totalH = lineCount * lineHeight;
|
||
|
|
|
||
|
|
const Player &player = *InspectPlayer;
|
||
|
|
|
||
|
|
// 1) Equipment (Rect position)
|
||
|
|
if (pcursinvitem >= INVITEM_HEAD && pcursinvitem < INVITEM_INV_FIRST) {
|
||
|
|
const 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];
|
||
|
|
const 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
|
||
|
|
|
||
|
|
const 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) {
|
||
|
|
const int itemIdx = pcursinvitem - INVITEM_INV_FIRST;
|
||
|
|
|
||
|
|
for (int j = 0; j < InventoryGridCells; ++j) {
|
||
|
|
if (player.InvGrid[j] > 0 && player.InvGrid[j] - 1 == itemIdx) {
|
||
|
|
const 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
|
||
|
|
|
||
|
|
const 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) {
|
||
|
|
const int itemIdx = pcursinvitem - INVITEM_BELT_FIRST;
|
||
|
|
for (int i = 0; i < MaxBeltItems; ++i) {
|
||
|
|
if (player.SpdList[i].isEmpty())
|
||
|
|
continue;
|
||
|
|
if (i != itemIdx)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
const 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
|
||
|
|
|
||
|
|
const 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;
|
||
|
|
|
||
|
|
const Item &item = Stash.stashList[itemId];
|
||
|
|
Point itemPosition = GetStashSlotCoord(slot);
|
||
|
|
const 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) {
|
||
|
|
const 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) {
|
||
|
|
const 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)
|
||
|
|
{
|
||
|
|
const int yAbove = anchorY - spriteH - boxH - pad;
|
||
|
|
const 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));
|
||
|
|
|
||
|
|
const int spriteH = GetHoverSpriteHeight();
|
||
|
|
const int anchorY = floatingInfoBox.position.y;
|
||
|
|
|
||
|
|
// 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,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
void AddInfoBoxString(std::string_view str, bool floatingBox /*= false*/)
|
||
|
|
{
|
||
|
|
StringOrView &infoString = floatingBox ? FloatingInfoString : InfoString;
|
||
|
|
|
||
|
|
if (infoString.empty())
|
||
|
|
infoString = str;
|
||
|
|
else
|
||
|
|
infoString = StrCat(infoString, "\n", str);
|
||
|
|
}
|
||
|
|
|
||
|
|
void AddInfoBoxString(std::string &&str, bool floatingBox /*= false*/)
|
||
|
|
{
|
||
|
|
StringOrView &infoString = floatingBox ? FloatingInfoString : InfoString;
|
||
|
|
|
||
|
|
if (infoString.empty())
|
||
|
|
infoString = std::move(str);
|
||
|
|
else
|
||
|
|
infoString = StrCat(infoString, "\n", str);
|
||
|
|
}
|
||
|
|
|
||
|
|
void CheckPanelInfo()
|
||
|
|
{
|
||
|
|
MainPanelFlag = false;
|
||
|
|
InfoString = StringOrView {};
|
||
|
|
FloatingInfoString = StringOrView {};
|
||
|
|
|
||
|
|
const int totalButtons = IsChatAvailable() ? TotalMpMainPanelButtons : TotalSpMainPanelButtons;
|
||
|
|
|
||
|
|
for (int i = 0; i < totalButtons; i++) {
|
||
|
|
Rectangle button = MainPanelButtonRect[i];
|
||
|
|
|
||
|
|
SetPanelObjectPosition(UiPanels::Main, button);
|
||
|
|
|
||
|
|
if (button.contains(MousePosition)) {
|
||
|
|
if (i != 7) {
|
||
|
|
InfoString = _(PanBtnStr[i]);
|
||
|
|
} else {
|
||
|
|
if (MyPlayer->friendlyMode)
|
||
|
|
InfoString = _("Player friendly");
|
||
|
|
else
|
||
|
|
InfoString = _("Player attack");
|
||
|
|
}
|
||
|
|
if (PanBtnHotKey[i] != nullptr) {
|
||
|
|
AddInfoBoxString(fmt::format(fmt::runtime(_("Hotkey: {:s}")), _(PanBtnHotKey[i])));
|
||
|
|
}
|
||
|
|
InfoColor = UiFlags::ColorWhite;
|
||
|
|
MainPanelFlag = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Rectangle spellSelectButton = SpellButtonRect;
|
||
|
|
|
||
|
|
SetPanelObjectPosition(UiPanels::Main, spellSelectButton);
|
||
|
|
|
||
|
|
if (!SpellSelectFlag && spellSelectButton.contains(MousePosition)) {
|
||
|
|
InfoString = _("Select current spell button");
|
||
|
|
InfoColor = UiFlags::ColorWhite;
|
||
|
|
MainPanelFlag = true;
|
||
|
|
AddInfoBoxString(_("Hotkey: 's'"));
|
||
|
|
const Player &myPlayer = *MyPlayer;
|
||
|
|
const SpellID spellId = myPlayer._pRSpell;
|
||
|
|
if (IsValidSpell(spellId)) {
|
||
|
|
switch (myPlayer._pRSplType) {
|
||
|
|
case SpellType::Skill:
|
||
|
|
AddInfoBoxString(fmt::format(fmt::runtime(_("{:s} Skill")), pgettext("spell", GetSpellData(spellId).sNameText)));
|
||
|
|
break;
|
||
|
|
case SpellType::Spell: {
|
||
|
|
AddInfoBoxString(fmt::format(fmt::runtime(_("{:s} Spell")), pgettext("spell", GetSpellData(spellId).sNameText)));
|
||
|
|
const int spellLevel = myPlayer.GetSpellLevel(spellId);
|
||
|
|
AddInfoBoxString(spellLevel == 0 ? _("Spell Level 0 - Unusable") : fmt::format(fmt::runtime(_("Spell Level {:d}")), spellLevel));
|
||
|
|
} break;
|
||
|
|
case SpellType::Scroll: {
|
||
|
|
AddInfoBoxString(fmt::format(fmt::runtime(_("Scroll of {:s}")), pgettext("spell", GetSpellData(spellId).sNameText)));
|
||
|
|
const int scrollCount = c_count_if(InventoryAndBeltPlayerItemsRange { myPlayer }, [spellId](const Item &item) {
|
||
|
|
return item.isScrollOf(spellId);
|
||
|
|
});
|
||
|
|
AddInfoBoxString(fmt::format(fmt::runtime(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount)), scrollCount));
|
||
|
|
} break;
|
||
|
|
case SpellType::Charges:
|
||
|
|
AddInfoBoxString(fmt::format(fmt::runtime(_("Staff of {:s}")), pgettext("spell", GetSpellData(spellId).sNameText)));
|
||
|
|
AddInfoBoxString(fmt::format(fmt::runtime(ngettext("{:d} Charge", "{:d} Charges", myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges)), myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges));
|
||
|
|
break;
|
||
|
|
case SpellType::Invalid:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Rectangle belt = BeltRect;
|
||
|
|
|
||
|
|
SetPanelObjectPosition(UiPanels::Main, belt);
|
||
|
|
|
||
|
|
if (belt.contains(MousePosition))
|
||
|
|
pcursinvitem = CheckInvHLight();
|
||
|
|
|
||
|
|
if (CheckXPBarInfo())
|
||
|
|
MainPanelFlag = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void DrawInfoBox(const Surface &out)
|
||
|
|
{
|
||
|
|
DrawPanelBox(out, MakeSdlRect(InfoBoxRect.position.x, InfoBoxRect.position.y + PanelPaddingHeight, InfoBoxRect.size.width, InfoBoxRect.size.height), GetMainPanel().position + Displacement { InfoBoxRect.position.x, InfoBoxRect.position.y });
|
||
|
|
if (!MainPanelFlag && !trigflag && pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell && !SpellSelectFlag && pcurs != CURSOR_HOURGLASS) {
|
||
|
|
InfoString = StringOrView {};
|
||
|
|
InfoColor = UiFlags::ColorWhite;
|
||
|
|
}
|
||
|
|
const Player &myPlayer = *MyPlayer;
|
||
|
|
if (SpellSelectFlag || trigflag || pcurs == CURSOR_HOURGLASS) {
|
||
|
|
InfoColor = UiFlags::ColorWhite;
|
||
|
|
} else if (!myPlayer.HoldItem.isEmpty()) {
|
||
|
|
if (myPlayer.HoldItem._itype == ItemType::Gold) {
|
||
|
|
const int nGold = myPlayer.HoldItem._ivalue;
|
||
|
|
InfoString = fmt::format(fmt::runtime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold)), FormatInteger(nGold));
|
||
|
|
} else if (!myPlayer.CanUseItem(myPlayer.HoldItem)) {
|
||
|
|
InfoString = _("Requirements not met");
|
||
|
|
} else {
|
||
|
|
InfoString = myPlayer.HoldItem.getName();
|
||
|
|
InfoColor = myPlayer.HoldItem.getTextColor();
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (pcursitem != -1)
|
||
|
|
GetItemStr(Items[pcursitem]);
|
||
|
|
else if (ObjectUnderCursor != nullptr)
|
||
|
|
GetObjectStr(*ObjectUnderCursor);
|
||
|
|
if (pcursmonst != -1) {
|
||
|
|
if (leveltype != DTYPE_TOWN) {
|
||
|
|
const Monster &monster = Monsters[pcursmonst];
|
||
|
|
InfoColor = UiFlags::ColorWhite;
|
||
|
|
InfoString = monster.name();
|
||
|
|
if (monster.isUnique()) {
|
||
|
|
InfoColor = UiFlags::ColorWhitegold;
|
||
|
|
PrintUniqueHistory();
|
||
|
|
} else {
|
||
|
|
PrintMonstHistory(monster.type().type);
|
||
|
|
}
|
||
|
|
} else if (pcursitem == -1) {
|
||
|
|
InfoString = std::string_view(Towners[pcursmonst].name);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (PlayerUnderCursor != nullptr) {
|
||
|
|
InfoColor = UiFlags::ColorWhitegold;
|
||
|
|
const auto &target = *PlayerUnderCursor;
|
||
|
|
InfoString = std::string_view(target._pName);
|
||
|
|
AddInfoBoxString(fmt::format(fmt::runtime(_("{:s}, Level: {:d}")), target.getClassName(), target.getCharacterLevel()));
|
||
|
|
AddInfoBoxString(fmt::format(fmt::runtime(_("Hit Points {:d} of {:d}")), target._pHitPoints >> 6, target._pMaxHP >> 6));
|
||
|
|
}
|
||
|
|
if (PortraitIdUnderCursor != -1) {
|
||
|
|
InfoColor = UiFlags::ColorWhitegold;
|
||
|
|
auto &target = Players[PortraitIdUnderCursor];
|
||
|
|
InfoString = std::string_view(target._pName);
|
||
|
|
AddInfoBoxString(_("Right click to inspect"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!InfoString.empty())
|
||
|
|
PrintInfo(out);
|
||
|
|
}
|
||
|
|
|
||
|
|
void DrawFloatingInfoBox(const Surface &out)
|
||
|
|
{
|
||
|
|
if (pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell) {
|
||
|
|
FloatingInfoString = StringOrView {};
|
||
|
|
InfoColor = UiFlags::ColorWhite;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!FloatingInfoString.empty())
|
||
|
|
PrintFloatingInfo(out);
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace devilution
|