diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e9ab2055..147a5b779 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -389,6 +389,7 @@ set(libdevilutionx_SRCS Source/qol/common.cpp Source/qol/monhealthbar.cpp Source/qol/xpbar.cpp + Source/qol/itemlabels.cpp Source/utils/console.cpp Source/utils/display.cpp Source/utils/file_util.cpp diff --git a/Source/cursor.cpp b/Source/cursor.cpp index a328a5cae..fd4670220 100644 --- a/Source/cursor.cpp +++ b/Source/cursor.cpp @@ -16,6 +16,7 @@ #include "track.h" #include "trigs.h" #include "utils/language.h" +#include "qol/itemlabels.h" namespace devilution { namespace { @@ -249,6 +250,9 @@ void CheckCursMove() int8_t bv; bool flipflag, flipx, flipy; + if (IsItemLabelHighlighted()) + return; + sx = MouseX; sy = MouseY; diff --git a/Source/diablo.cpp b/Source/diablo.cpp index d8b99c9b9..a43c5cd03 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -43,6 +43,7 @@ #include "pfile.h" #include "plrmsg.h" #include "qol/common.h" +#include "qol/itemlabels.h" #include "restrict.h" #include "setmaps.h" #include "stores.h" @@ -1070,6 +1071,10 @@ static void ReleaseKey(int vkey) { if (vkey == DVL_VK_SNAPSHOT) CaptureScreen(); + if (vkey == DVL_VK_MENU || vkey == DVL_VK_LMENU || vkey == DVL_VK_RMENU) + AltPressed(false); + if (vkey == DVL_VK_CONTROL || vkey == DVL_VK_LCONTROL || vkey == DVL_VK_RCONTROL) + ToggleItemLabelHighlight(); } static void ClosePanels() @@ -1146,6 +1151,9 @@ static void PressKey(int vkey) return; } + if (vkey == DVL_VK_MENU || vkey == DVL_VK_LMENU || vkey == DVL_VK_RMENU) + AltPressed(true); + if (deathflag) { if (sgnTimeoutCurs != CURSOR_NONE) { return; diff --git a/Source/engine.cpp b/Source/engine.cpp index 66ddb4277..10b19048f 100644 --- a/Source/engine.cpp +++ b/Source/engine.cpp @@ -55,7 +55,7 @@ void DrawHorizontalLine(const CelOutputBuffer &out, Point from, int width, std:: from.x = 0; } if (from.x + width > out.w()) - width = (from.x + width) - out.w(); + width = out.w() - from.x; return UnsafeDrawHorizontalLine(out, from, width, colorIndex); } @@ -116,6 +116,29 @@ static void DrawHalfTransparentStippledRectTo(const CelOutputBuffer &out, int sx void DrawHalfTransparentRectTo(const CelOutputBuffer &out, int sx, int sy, int width, int height) { + if (sx + width < 0) + return; + if (sy + height < 0) + return; + if (sx >= out.w()) + return; + if (sy >= out.h()) + return; + + if (sx < 0) { + width += sx; + sx = 0; + } else if (sx + width >= out.w()) { + width = out.w() - sx; + } + + if (sy < 0) { + height += sy; + sy = 0; + } else if (sy + height >= out.h()) { + height = out.h() - sy; + } + if (sgOptions.Graphics.bBlendedTransparancy) { DrawHalfTransparentBlendedRectTo(out, sx, sy, width, height); } else { diff --git a/Source/engine/render/cel_render.cpp b/Source/engine/render/cel_render.cpp index 61198b1ce..9fa49d989 100644 --- a/Source/engine/render/cel_render.cpp +++ b/Source/engine/render/cel_render.cpp @@ -713,7 +713,7 @@ void CelBlitOutlineTo(const CelOutputBuffer &out, uint8_t col, int sx, int sy, c std::pair MeasureSolidHorizontalBounds(const CelSprite &cel, int frame) { int nDataSize; - auto src = reinterpret_cast(CelGetFrame(cel.Data(), frame, &nDataSize)); + const byte *src = CelGetFrameClipped(cel.Data(), frame, &nDataSize); auto end = &src[nDataSize]; const int celWidth = cel.Width(frame); diff --git a/Source/items.cpp b/Source/items.cpp index 27ce3f89b..26fe3ae0d 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -23,8 +23,6 @@ #include "utils/language.h" #include "utils/math.h" -#define ITEMTYPES 43 - namespace devilution { namespace { std::optional itemanims[ITEMTYPES]; diff --git a/Source/items.h b/Source/items.h index 3293cd790..43c8735c5 100644 --- a/Source/items.h +++ b/Source/items.h @@ -15,6 +15,7 @@ namespace devilution { #define MAXITEMS 127 +#define ITEMTYPES 43 #define GOLD_SMALL_LIMIT 1000 #define GOLD_MEDIUM_LIMIT 2500 diff --git a/Source/qol/itemlabels.cpp b/Source/qol/itemlabels.cpp new file mode 100644 index 000000000..722d95dea --- /dev/null +++ b/Source/qol/itemlabels.cpp @@ -0,0 +1,157 @@ +#include +#include +#include + +#include "inv.h" +#include "gmenu.h" +#include "cursor.h" +#include "common.h" +#include "control.h" +#include "itemlabels.h" +#include "utils/language.h" +#include "engine/render/cel_render.hpp" + +namespace devilution { + +namespace { + +struct itemLabel { + int id, width; + Point pos; + std::string text; +}; + +std::vector labelQueue; + +bool altPressed = false; +bool isLabelHighlighted = false; +std::array, ITEMTYPES> labelCenterOffsets; +bool invertHighlightToggle = false; + +} // namespace + +void ToggleItemLabelHighlight() +{ + invertHighlightToggle = !invertHighlightToggle; +} + +void AltPressed(bool pressed) +{ + altPressed = pressed; +} + +bool IsItemLabelHighlighted() +{ + return isLabelHighlighted; +} + +bool IsHighlightingLabelsEnabled() +{ + return altPressed != invertHighlightToggle; +} + +void AddItemToLabelQueue(int id, int x, int y) +{ + if (!IsHighlightingLabelsEnabled()) + return; + ItemStruct *it = &items[id]; + + const char *textOnGround; + if (it->_itype == ITYPE_GOLD) { + std::sprintf(tempstr, _("%i gold"), it->_ivalue); + textOnGround = tempstr; + } else { + textOnGround = it->_iIdentified ? it->_iIName : it->_iName; + } + + int nameWidth = GetLineWidth(textOnGround); + int index = ItemCAnimTbl[it->_iCurs]; + if (!labelCenterOffsets[index]) { + std::pair itemBounds = MeasureSolidHorizontalBounds(*it->_iAnimData, it->_iAnimFrame); + labelCenterOffsets[index].emplace((itemBounds.first + itemBounds.second) / 2); + } + + x += *labelCenterOffsets[index]; + y -= TILE_HEIGHT; + if (!zoomflag) { + x *= 2; + y *= 2; + } + x -= nameWidth / 2; + labelQueue.push_back(itemLabel { id, nameWidth, { x, y }, textOnGround }); +} + +bool IsMouseOverGameArea() +{ + if ((invflag || sbookflag) && MouseX > RIGHT_PANEL && MouseY <= SPANEL_HEIGHT) + return false; + if ((chrflag || questlog) && MouseX < SPANEL_WIDTH && MouseY <= SPANEL_HEIGHT) + return false; + if (MouseY >= PANEL_TOP && MouseX >= PANEL_LEFT && MouseX <= PANEL_LEFT + PANEL_WIDTH) + return false; + + return true; +} + +void FillRect(const CelOutputBuffer &out, int x, int y, int width, int height, Uint8 col) +{ + for (int j = 0; j < height; j++) { + DrawHorizontalLine(out, { x, y + j }, width, col); + } +} + +void DrawItemNameLabels(const CelOutputBuffer &out) +{ + isLabelHighlighted = false; + const int borderX = 5; + const int borderY = 2; + const int height = 13; + for (unsigned int i = 0; i < labelQueue.size(); ++i) { + std::unordered_set backtrace; + + bool canShow; + do { + canShow = true; + for (unsigned int j = 0; j < i; ++j) { + itemLabel &a = labelQueue[i], &b = labelQueue[j]; + if (abs(b.pos.y - a.pos.y) < height + borderY) { + int newpos = b.pos.x; + if (b.pos.x >= a.pos.x && b.pos.x - a.pos.x < a.width + borderX) { + newpos -= a.width + borderX; + if (backtrace.find(newpos) != backtrace.end()) + newpos = b.pos.x + b.width + borderX; + } else if (b.pos.x < a.pos.x && a.pos.x - b.pos.x < b.width + borderX) { + newpos += b.width + borderX; + if (backtrace.find(newpos) != backtrace.end()) + newpos = b.pos.x - a.width - borderX; + } else + continue; + canShow = false; + a.pos.x = newpos; + backtrace.insert(newpos); + } + } + } while (!canShow); + } + + for (const itemLabel &label : labelQueue) { + ItemStruct &itm = items[label.id]; + + if (MouseX >= label.pos.x && MouseX <= label.pos.x + label.width && MouseY >= label.pos.y - height && MouseY <= label.pos.y) { + if (!gmenu_is_active() && PauseMode == 0 && !deathflag && IsMouseOverGameArea()) { + isLabelHighlighted = true; + cursmx = itm.position.x; + cursmy = itm.position.y; + pcursitem = label.id; + } + } + if (pcursitem == label.id) + FillRect(out, label.pos.x, label.pos.y - height, label.width + 1, height, PAL8_BLUE + 6); + else + DrawHalfTransparentRectTo(out, label.pos.x, label.pos.y - height, label.width, height); + DrawString(out, label.text.c_str(), { label.pos.x, label.pos.y, label.width, height }, itm.getTextColor()); + } + labelQueue.clear(); +} + +} // namespace devilution diff --git a/Source/qol/itemlabels.h b/Source/qol/itemlabels.h new file mode 100644 index 000000000..a63a57d4b --- /dev/null +++ b/Source/qol/itemlabels.h @@ -0,0 +1,17 @@ +/** +* @file itemlabels.h +* +* Adds item labels QoL feature +*/ +#pragma once + +namespace devilution { + +void ToggleItemLabelHighlight(); +void AltPressed(bool pressed); +bool IsItemLabelHighlighted(); +bool IsHighlightingLabelsEnabled(); +void AddItemToLabelQueue(int id, int x, int y); +void DrawItemNameLabels(const CelOutputBuffer &out); + +} // namespace devilution diff --git a/Source/scrollrt.cpp b/Source/scrollrt.cpp index 0e962ffdb..56d8d82ef 100644 --- a/Source/scrollrt.cpp +++ b/Source/scrollrt.cpp @@ -25,6 +25,7 @@ #include "plrmsg.h" #include "qol/monhealthbar.h" #include "qol/xpbar.h" +#include "qol/itemlabels.h" #include "stores.h" #include "towners.h" #include "utils/log.hpp" @@ -636,6 +637,8 @@ static void DrawItem(const CelOutputBuffer &out, int x, int y, int sx, int sy, b CelBlitOutlineTo(out, GetOutlineColor(*pItem, false), px, sy, *cel, nCel); } CelClippedDrawLightTo(out, px, sy, *cel, nCel); + if (pItem->_iAnimFrame == pItem->_iAnimLen) + AddItemToLabelQueue(bItem - 1, px, sy); } /** @@ -1247,6 +1250,7 @@ void DrawView(const CelOutputBuffer &out, int StartX, int StartY) DrawAutomap(out.subregionY(0, gnViewportHeight)); } DrawMonsterHealthBar(out); + DrawItemNameLabels(out); if (stextflag != STORE_NONE && !qtextflag) DrawSText(out); @@ -1544,7 +1548,7 @@ void DrawAndBlit() bool ddsdesc = false; bool ctrlPan = false; - if (gnScreenWidth > PANEL_WIDTH || force_redraw == 255) { + if (gnScreenWidth > PANEL_WIDTH || force_redraw == 255 || IsHighlightingLabelsEnabled()) { drawhpflag = true; drawmanaflag = true; drawbtnflag = true;