Skip to content

Commit

Permalink
Floating Info Box
Browse files Browse the repository at this point in the history
  • Loading branch information
kphoenix137 committed Feb 16, 2023
1 parent b46482f commit bbdb970
Show file tree
Hide file tree
Showing 9 changed files with 525 additions and 16 deletions.
1 change: 1 addition & 0 deletions Source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ set(libdevilutionx_SRCS

qol/autopickup.cpp
qol/chatlog.cpp
qol/floatinginfobox.cpp
qol/floatingnumbers.cpp
qol/itemlabels.cpp
qol/monhealthbar.cpp
Expand Down
37 changes: 37 additions & 0 deletions Source/engine/render/text_render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,43 @@ void UnloadFonts()
FontKerns.clear();
}

int GetMaxLineWidth(string_view text, GameFontTables size, int spacing, int *charactersInLine)
{
int lineWidth = 0;
int maxLineWidth = 0;

uint32_t codepoints = 0;
uint32_t currentUnicodeRow = 0;
std::array<uint8_t, 256> *kerning = nullptr;
char32_t next;
while (!text.empty()) {
next = ConsumeFirstUtf8CodePoint(&text);
if (next == Utf8DecodeError)
break;

if (next == '\n' || next == ZWSP) {
maxLineWidth = std::max(lineWidth, maxLineWidth);
lineWidth = 0;
continue;
}

uint8_t frame = next & 0xFF;
const uint32_t unicodeRow = GetUnicodeRow(next);
if (unicodeRow != currentUnicodeRow || kerning == nullptr) {
kerning = LoadFontKerning(size, unicodeRow);
currentUnicodeRow = unicodeRow;
}
lineWidth += (*kerning)[frame] + spacing;
codepoints++;
}
maxLineWidth = std::max(lineWidth, maxLineWidth);

if (charactersInLine != nullptr)
*charactersInLine = codepoints;

return maxLineWidth != 0 ? (maxLineWidth - spacing) : 0;
}

int GetLineWidth(string_view text, GameFontTables size, int spacing, int *charactersInLine)
{
int lineWidth = 0;
Expand Down
10 changes: 10 additions & 0 deletions Source/engine/render/text_render.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,16 @@ extern OptionalOwnedClxSpriteList pSPentSpn2Cels;

void LoadSmallSelectionSpinner();

/**
* @brief Calculate highest pixel width from all lines of text, respecting kerning
* @param text Text to check, will read until first eol or terminator
* @param size Font size to use
* @param spacing Extra spacing to add per character
* @param charactersInLine Receives characters read until newline or terminator
* @return Line width in pixels
*/
int GetMaxLineWidth(string_view text, GameFontTables size = GameFont12, int spacing = 1, int *charactersInLine = nullptr);

/**
* @brief Calculate pixel width of first line of text, respecting kerning
* @param text Text to check, will read until first eol or terminator
Expand Down
51 changes: 51 additions & 0 deletions Source/inv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "options.h"
#include "panels/ui_panels.hpp"
#include "plrmsg.h"
#include "qol/floatinginfobox.hpp"
#include "qol/stash.h"
#include "stores.h"
#include "towners.h"
Expand Down Expand Up @@ -1177,6 +1178,47 @@ void DrawInv(const Surface &out)
DrawItem(myPlayer.InvList[ii], out, position, sprite);
}
}

if (*sgOptions.Gameplay.enableFloatingInfoBox) {
// Draw Info Box
for (int slot = INVLOC_HEAD; slot < NUM_INVLOC; slot++) {
if (!myPlayer.InvBody[slot].isEmpty()) {
int screenX = slotPos[slot].x;
int screenY = slotPos[slot].y;

const int cursId = myPlayer.InvBody[slot]._iCurs + CURSOR_FIRSTITEM;

auto frameSize = GetInvItemSize(cursId);

// calc item offsets for weapons smaller than 2x3 slots
if (slot == INVLOC_HAND_LEFT) {
screenX += frameSize.width == InventorySlotSizeInPixels.width ? INV_SLOT_HALF_SIZE_PX : 0;
screenY += frameSize.height == (3 * InventorySlotSizeInPixels.height) ? 0 : -INV_SLOT_HALF_SIZE_PX;
} else if (slot == INVLOC_HAND_RIGHT) {
screenX += frameSize.width == InventorySlotSizeInPixels.width ? (INV_SLOT_HALF_SIZE_PX - 1) : 1;
screenY += frameSize.height == (3 * InventorySlotSizeInPixels.height) ? 0 : -INV_SLOT_HALF_SIZE_PX;
}

const Point position = GetPanelPosition(UiPanels::Inventory, { screenX, screenY });

if (pcursinvitem == slot) {
DrawFloatingInfoBox(out, position);
}
}
}

// Draw info box
for (int j = 0; j < InventoryGridCells; j++) {
if (myPlayer.InvGrid[j] > 0) { // first slot of an item
int ii = myPlayer.InvGrid[j] - 1;

const Point position = GetPanelPosition(UiPanels::Inventory, InvRect[j + SLOTXY_INV_FIRST]) + Displacement { 0, -1 };
if (pcursinvitem == ii + INVITEM_INV_FIRST) {
DrawFloatingInfoBox(out, position);
}
}
}
}
}

void DrawInvBelt(const Surface &out)
Expand Down Expand Up @@ -1210,6 +1252,15 @@ void DrawInvBelt(const Surface &out)

DrawItem(myPlayer.SpdList[i], out, position, sprite);

if (*sgOptions.Gameplay.enableFloatingInfoBox) {
// Draw info box
if (pcursinvitem == i + INVITEM_BELT_FIRST) {
if (ControlMode == ControlTypes::KeyboardAndMouse || invflag) {
DrawFloatingInfoBox(out, position);
}
}
}

if (myPlayer.SpdList[i].isUsable()
&& myPlayer.SpdList[i]._itype != ItemType::Gold) {
DrawString(out, StrCat(i + 1), { position - Displacement { 0, 12 }, InventorySlotSizeInPixels }, UiFlags::ColorWhite | UiFlags::AlignRight);
Expand Down
125 changes: 109 additions & 16 deletions Source/items.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "player.h"
#include "playerdat.hpp"
#include "qol/stash.h"
#include "spelldat.h"
#include "spells.h"
#include "stores.h"
#include "utils/format_int.hpp"
Expand Down Expand Up @@ -3687,40 +3688,132 @@ void PrintItemDetails(const Item &item)
if (HeadlessMode)
return;

// Base Item
if (strcmp(item._iIName, item._iName) != 0)
AddPanelString(_(item._iName));

// Modify damage string?

int modMinDam = item._iMinDam;
int modMaxDam = item._iMaxDam;
int modAC = item._iAC;

if (item._iMagical == ITEM_QUALITY_UNIQUE) {
const UniqueItem &uitem = UniqueItems[item._iUid];
assert(uitem.UINumPL <= sizeof(uitem.powers) / sizeof(*uitem.powers));
for (const auto &power : uitem.powers) {
switch (power.type) {
case IPL_ACP:
modAC = modAC * (100 + item._iPLAC) / 100;
break;
case IPL_ACP_CURSE:
modAC = modAC * (100 - item._iPLAC) / 100;
break;
case IPL_DAMP:
case IPL_TOHIT_DAMP:
modMinDam = modMinDam * (100 + item._iPLDam) / 100;
modMaxDam = modMaxDam * (100 + item._iPLDam) / 100;
break;
case IPL_DAMP_CURSE:
case IPL_TOHIT_DAMP_CURSE:
modMinDam = modMinDam * (100 - item._iPLDam) / 100;
modMaxDam = modMaxDam * (100 - item._iPLDam) / 100;
break;
case IPL_DAMMOD:
modMinDam += item._iPLDamMod;
modMaxDam += item._iPLDamMod;
break;
default:
break;
}
}
} else if (item._iMagical == ITEM_QUALITY_MAGIC) {
switch (item._iPrePower) {
case IPL_ACP:
modAC = modAC * (100 + item._iPLAC) / 100;
break;
case IPL_ACP_CURSE:
modAC = modAC * (100 - item._iPLAC) / 100;
break;
case IPL_DAMP:
case IPL_TOHIT_DAMP:
modMinDam = modMinDam * (100 + item._iPLDam) / 100;
modMaxDam = modMaxDam * (100 + item._iPLDam) / 100;
break;
case IPL_DAMP_CURSE:
case IPL_TOHIT_DAMP_CURSE:
modMinDam = modMinDam * (100 - item._iPLDam) / 100;
modMaxDam = modMaxDam * (100 - item._iPLDam) / 100;
break;
default:
break;
}

switch (item._iSufPower) {
case IPL_DAMMOD:
modMinDam += item._iPLDamMod;
modMaxDam += item._iPLDamMod;
break;
default:
break;
}
}


if (item._iClass == ICLASS_WEAPON) {
if (item._iMinDam == item._iMaxDam) {
if (item._iMaxDur == DUR_INDESTRUCTIBLE)
AddPanelString(fmt::format(fmt::runtime(_("damage: {:d} Indestructible")), item._iMinDam));
else
AddPanelString(fmt::format(fmt::runtime(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d} Dur: {:d}/{:d}")), item._iMinDam, item._iDurability, item._iMaxDur));
AddPanelString(fmt::format(fmt::runtime(_("Damage: {:d}")), modMinDam));
} else {
if (item._iMaxDur == DUR_INDESTRUCTIBLE)
AddPanelString(fmt::format(fmt::runtime(_("damage: {:d}-{:d} Indestructible")), item._iMinDam, item._iMaxDam));
else
AddPanelString(fmt::format(fmt::runtime(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d}-{:d} Dur: {:d}/{:d}")), item._iMinDam, item._iMaxDam, item._iDurability, item._iMaxDur));
AddPanelString(fmt::format(fmt::runtime(_("Damage: {:d} to {:d}")), modMinDam, modMaxDam));
}
}
if (item._iClass == ICLASS_ARMOR) {
if (item._iMaxDur == DUR_INDESTRUCTIBLE)
AddPanelString(fmt::format(fmt::runtime(_("armor: {:d} Indestructible")), item._iAC));
else
AddPanelString(fmt::format(fmt::runtime(_(/* TRANSLATORS: Dur: is durability */ "armor: {:d} Dur: {:d}/{:d}")), item._iAC, item._iDurability, item._iMaxDur));
AddPanelString(fmt::format(fmt::runtime(_("Armor: {:d}")), modAC));
}
if (item._iMaxDur != DUR_INDESTRUCTIBLE && (item._iClass == ICLASS_WEAPON || item._iClass == ICLASS_ARMOR)) {
AddPanelString(fmt::format(fmt::runtime(_("Durability: {:d} of {:d}")), item._iDurability, item._iMaxDur));
}
if (item._iMiscId == IMISC_STAFF && item._iMaxCharges != 0) {
AddPanelString(fmt::format(fmt::runtime(_("Charges: {:d}/{:d}")), item._iCharges, item._iMaxCharges));
const char *spellName = GetSpellData(item._iSpell).sNameText;
AddPanelString(fmt::format(fmt::runtime(_("{:s} ({:d}/{:d} Charges)")), spellName, item._iCharges, item._iMaxCharges));
}

PrintItemMisc(item);

uint8_t str = item._iMinStr;
uint8_t dex = item._iMinDex;
uint8_t mag = item._iMinMag;
if (str != 0 || mag != 0 || dex != 0) {
if (str != 0) {
std::string textStr = fmt::format(fmt::runtime(_("Required Strength: {:d}")), str);
AddPanelString(textStr);
}
if (mag != 0) {
std::string textMag = fmt::format(fmt::runtime(_("Required Magic: {:d}")), mag);
AddPanelString(textMag);
}
if (dex != 0) {
std::string textDex = fmt::format(fmt::runtime(_("Required Dexterity: {:d}")), dex);
AddPanelString(textDex);
}
}

if (item._iPrePower != -1) {
AddPanelString(PrintItemPower(item._iPrePower, item));
}
if (item._iSufPower != -1) {
AddPanelString(PrintItemPower(item._iSufPower, item));
}
if (item._iMagical == ITEM_QUALITY_UNIQUE) {
AddPanelString(_("unique item"));
ShowUniqueItemInfoBox = true;
curruitem = item;
const UniqueItem &uitem = UniqueItems[curruitem._iUid];
assert(uitem.UINumPL <= sizeof(uitem.powers) / sizeof(*uitem.powers));
for (const auto &power : uitem.powers) {
if (power.type == IPL_INVALID || power.type == IPL_INVCURS)
break;
AddPanelString(PrintItemPower(power.type, curruitem));
}
}
PrintItemInfo(item);
}

void PrintItemDur(const Item &item)
Expand Down
2 changes: 2 additions & 0 deletions Source/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,7 @@ GameplayOptions::GameplayOptions()
{ FloatingNumbers::Random, N_("Random Angles") },
{ FloatingNumbers::Vertical, N_("Vertical Only") },
})
, enableFloatingInfoBox("Enable Floating Info Box", OptionEntryFlags::None, N_("Enable Floating Info Box"), N_("Moves information from the control panel to a floating box."), false)
{
grabInput.SetValueChangedCallback(OptionGrabInputChanged);
experienceBar.SetValueChangedCallback(OptionExperienceBarChanged);
Expand Down Expand Up @@ -1129,6 +1130,7 @@ std::vector<OptionEntryBase *> GameplayOptions::GetEntries()
&numRejuPotionPickup,
&numFullRejuPotionPickup,
&enableFloatingNumbers,
&enableFloatingInfoBox,
};
}

Expand Down
2 changes: 2 additions & 0 deletions Source/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,8 @@ struct GameplayOptions : OptionCategoryBase {
OptionEntryInt<int> numFullRejuPotionPickup;
/** @brief Enable floating numbers. */
OptionEntryEnum<FloatingNumbers> enableFloatingNumbers;
/** @brief Enable floating info box. */
OptionEntryBoolean enableFloatingInfoBox;
};

struct ControllerOptions : OptionCategoryBase {
Expand Down
Loading

0 comments on commit bbdb970

Please sign in to comment.