From 5a0c1a315220db6e5927e0afd88678c3b714e350 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 6 Oct 2026 16:45:33 -0600 Subject: [PATCH] Item names: limit visible characters per line (56), up to 72 in total TrimItemText limited a whole item name to 56 visible characters, so a multi-line name (unique name, base, a line of tags) was cut from the end of its top line while its widest line was much shorter. Each line now keeps the 56 limit (MAX_ITEM_NAME_LINE_SIZE), so labels are no wider than before, and a whole name may hold up to 72 (MAX_ITEM_NAME_TOTAL_SIZE). Names that fit today are unchanged, one-line names are cut as before, and shop items keep 512. --- BH/Constants.h | 3 +- BH/Modules/Item/ItemDisplay.cpp | 64 ++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/BH/Constants.h b/BH/Constants.h index 2e2fd78..06f3bcc 100644 --- a/BH/Constants.h +++ b/BH/Constants.h @@ -10,7 +10,8 @@ #define CODE_PAGE CP_UTF8 // 65001 - UTF-8: supports all Unicode languages -#define MAX_ITEM_NAME_SIZE 56 +#define MAX_ITEM_NAME_LINE_SIZE 56 // Visible characters on one line of an item name (color codes not counted) +#define MAX_ITEM_NAME_TOTAL_SIZE 72 // Visible characters in a whole item name, line breaks included #define MAX_ITEM_TEXT_SIZE 512 // Half of the maximum in-game limit #define BOOK_NAME_SIZE_LIMIT 127 // TP & ID Tomes have a smaller limit for some reason #define ITEM_TEXT_SIZE_LIMIT 1023 diff --git a/BH/Modules/Item/ItemDisplay.cpp b/BH/Modules/Item/ItemDisplay.cpp index ec79cc7..8505d48 100644 --- a/BH/Modules/Item/ItemDisplay.cpp +++ b/BH/Modules/Item/ItemDisplay.cpp @@ -3023,6 +3023,14 @@ bool IsWhitespaceEquivalent(wchar_t ch) return iswspace(ch) || ch == L'\b'; } +// A color code at position i: \xFF, 'c', then one of 0-9 ; : or \x01-\x1F (3 wchar_t) +bool IsColorCode(const wstring& text, size_t i) +{ + if (i + 2 >= text.size() || text[i] != L'\xFF' || text[i + 1] != L'c') { return false; } + const wchar_t c = text[i + 2]; + return (c >= L'0' && c <= L'9') || c == L';' || c == L':' || (c >= L'\x01' && c <= L'\x1F'); +} + void TrimItemText(UnitItemInfo* uInfo, wstring& name, BOOL bLimit) @@ -3065,39 +3073,47 @@ void TrimItemText(UnitItemInfo* uInfo, } name.resize(offset); - int nColorCodesSize = 0; int lengthLimit = 0; if (bLimit) { - // Calc the extra size from colors - // In wide strings: color code is \xFF followed by 'c' followed by digit = 3 wchar_t - std::wregex color_reg(L"\\xFFc[0-9;:\\x01-\\x1F]", std::regex_constants::ECMAScript); - auto color_matches = std::wsregex_iterator(name.begin(), name.end(), color_reg); - auto color_end = std::wsregex_iterator(); - auto match_count = std::distance(color_matches, color_end); - nColorCodesSize += 3 * match_count; - bool inShop = (uInfo->item->pItemData->pOwnerInventory != 0 && // Skip on ground items uInfo->item->pItemData->pOwnerInventory->pOwner != 0 && find(begin(ShopNPCs), end(ShopNPCs), uInfo->item->pItemData->pOwnerInventory->pOwner->dwTxtFileNo) != end(ShopNPCs)); - // Increase limit for shop items - lengthLimit = inShop ? MAX_ITEM_TEXT_SIZE : MAX_ITEM_NAME_SIZE; - - int nColorsToKeep = 0; - for (std::wsregex_iterator k = color_matches; k != color_end; ++k) + // Count visible characters only (color codes take no room). One line may hold up to + // MAX_ITEM_NAME_LINE_SIZE, so a label is never wider than before; a name with several + // lines may hold up to MAX_ITEM_NAME_TOTAL_SIZE in all. Shop items keep their larger limit. + const int lineLimit = inShop ? MAX_ITEM_TEXT_SIZE : MAX_ITEM_NAME_LINE_SIZE; + const int totalLimit = inShop ? MAX_ITEM_TEXT_SIZE : MAX_ITEM_NAME_TOTAL_SIZE; + + wstring trimmed; + trimmed.reserve(name.size()); + int lineChars = 0; + int totalChars = 0; + size_t keepSize = 0; // trimmed size up to the last visible character kept + bool cut = false; + for (size_t i = 0; i < name.size(); ++i) { - std::wsmatch match = *k; - auto pos = match.position(); - if (pos - (nColorsToKeep) > lengthLimit) { break; } - nColorsToKeep += 3; - } - - // Truncate if too long - if (name.size() - nColorCodesSize > lengthLimit) + if (IsColorCode(name, i)) + { + // Keep color codes, even inside a cut part of a line: they color the lines after it + trimmed.append(name, i, 3); + i += 2; + continue; + } + if (totalChars >= totalLimit) { cut = true; break; } + const wchar_t ch = name[i]; + if (ch == L'\n') { lineChars = 0; } + else if (lineChars >= lineLimit) { cut = true; continue; } // rest of this line: drop up to the next line break + else { ++lineChars; } + trimmed += ch; + ++totalChars; + keepSize = trimmed.size(); + } + if (cut) { - int max_size = lengthLimit + nColorsToKeep; - name.resize(max_size); + trimmed.resize(keepSize); // drop color codes left after the last visible character + name = trimmed; } }