diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index c0f992160..ab0ae3784 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -97,7 +97,6 @@ set(libdevilutionx_SRCS mpq/mpq_writer.cpp qol/autopickup.cpp qol/chatlog.cpp - qol/common.cpp qol/monhealthbar.cpp qol/stash.cpp qol/xpbar.cpp diff --git a/Source/control.cpp b/Source/control.cpp index 219ce70af..abd3a2398 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -67,7 +67,6 @@ bool drawmanaflag; bool chrbtnactive; int pnumlines; UiFlags InfoColor; -char tempstr[256]; int sbooktab; int8_t initialDropGoldIndex; bool talkflag; @@ -703,8 +702,7 @@ void CheckPanelInfo() strcpy(infostr, _("Player attack")); } if (PanBtnHotKey[i] != nullptr) { - strcpy(tempstr, fmt::format(_("Hotkey: {:s}"), _(PanBtnHotKey[i])).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("Hotkey: {:s}"), _(PanBtnHotKey[i]))); } InfoColor = UiFlags::ColorWhite; panelflag = true; @@ -714,41 +712,30 @@ void CheckPanelInfo() strcpy(infostr, _("Select current spell button")); InfoColor = UiFlags::ColorWhite; panelflag = true; - strcpy(tempstr, _("Hotkey: 's'")); - AddPanelString(tempstr); + AddPanelString(_("Hotkey: 's'")); auto &myPlayer = Players[MyPlayerId]; const spell_id spellId = myPlayer._pRSpell; if (spellId != SPL_INVALID && spellId != SPL_NULL) { switch (myPlayer._pRSplType) { case RSPLTYPE_SKILL: - strcpy(tempstr, fmt::format(_("{:s} Skill"), pgettext("spell", spelldata[spellId].sSkillText)).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("{:s} Skill"), pgettext("spell", spelldata[spellId].sSkillText))); break; case RSPLTYPE_SPELL: { - strcpy(tempstr, fmt::format(_("{:s} Spell"), pgettext("spell", spelldata[spellId].sNameText)).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("{:s} Spell"), pgettext("spell", spelldata[spellId].sNameText))); int c = std::max(myPlayer._pISplLvlAdd + myPlayer._pSplLvl[spellId], 0); - if (c == 0) - strcpy(tempstr, _("Spell Level 0 - Unusable")); - else - strcpy(tempstr, fmt::format(_("Spell Level {:d}"), c).c_str()); - AddPanelString(tempstr); + AddPanelString(c == 0 ? _("Spell Level 0 - Unusable") : fmt::format(_("Spell Level {:d}"), c)); } break; case RSPLTYPE_SCROLL: { - strcpy(tempstr, fmt::format(_("Scroll of {:s}"), pgettext("spell", spelldata[spellId].sNameText)).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("Scroll of {:s}"), pgettext("spell", spelldata[spellId].sNameText))); const InventoryAndBeltPlayerItemsRange items { myPlayer }; const int scrollCount = std::count_if(items.begin(), items.end(), [spellId](const Item &item) { return item.IsScrollOf(spellId); }); - strcpy(tempstr, fmt::format(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount), scrollCount).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount), scrollCount)); } break; case RSPLTYPE_CHARGES: - strcpy(tempstr, fmt::format(_("Staff of {:s}"), pgettext("spell", spelldata[spellId].sNameText)).c_str()); - AddPanelString(tempstr); - strcpy(tempstr, fmt::format(ngettext("{:d} Charge", "{:d} Charges", myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges), myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("Staff of {:s}"), pgettext("spell", spelldata[spellId].sNameText))); + AddPanelString(fmt::format(ngettext("{:d} Charge", "{:d} Charges", myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges), myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges)); break; case RSPLTYPE_INVALID: break; @@ -910,10 +897,8 @@ void DrawInfoBox(const Surface &out) auto &target = Players[pcursplr]; strcpy(infostr, target._pName); ClearPanel(); - strcpy(tempstr, fmt::format(_("{:s}, Level: {:d}"), _(ClassStrTbl[static_cast(target._pClass)]), target._pLevel).c_str()); - AddPanelString(tempstr); - strcpy(tempstr, fmt::format(_("Hit Points {:d} of {:d}"), target._pHitPoints >> 6, target._pMaxHP >> 6).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("{:s}, Level: {:d}"), _(ClassStrTbl[static_cast(target._pClass)]), target._pLevel)); + AddPanelString(fmt::format(_("Hit Points {:d} of {:d}"), target._pHitPoints >> 6, target._pMaxHP >> 6)); } } if (infostr[0] != '\0' || pnumlines != 0) @@ -1051,33 +1036,29 @@ void DrawGoldSplit(const Surface &out, int amount) CelDrawTo(out, GetPanelPosition(UiPanels::Inventory, { dialogX, 178 }), *pGBoxBuff, 1); - constexpr auto BufferSize = sizeof(tempstr) / sizeof(*tempstr); - - CopyUtf8( - tempstr, - fmt::format(ngettext( - /* TRANSLATORS: {:d} is a number. Dialog is shown when splitting a stash of Gold.*/ "You have {:d} gold piece. How many do you want to remove?", - "You have {:d} gold pieces. How many do you want to remove?", - initialDropGoldValue), + const std::string description = fmt::format( + /* TRANSLATORS: {:d} is a number. Dialog is shown when splitting a stash of Gold.*/ + ngettext( + "You have {:d} gold piece. How many do you want to remove?", + "You have {:d} gold pieces. How many do you want to remove?", initialDropGoldValue), - BufferSize); + initialDropGoldValue); // Pre-wrap the string at spaces, otherwise DrawString would hard wrap in the middle of words - const std::string wrapped = WordWrapString(tempstr, 200); + const std::string wrapped = WordWrapString(description, 200); // The split gold dialog is roughly 4 lines high, but we need at least one line for the player to input an amount. // Using a clipping region 50 units high (approx 3 lines with a lineheight of 17) to ensure there is enough room left // for the text entered by the player. DrawString(out, wrapped, { GetPanelPosition(UiPanels::Inventory, { dialogX + 31, 75 }), { 200, 50 } }, UiFlags::ColorWhitegold | UiFlags::AlignCenter, 1, 17); - tempstr[0] = '\0'; + std::string value = ""; if (amount > 0) { - // snprintf ensures that the destination buffer ends in a null character. - snprintf(tempstr, BufferSize, "%u", amount); + value = fmt::format("{:d}", amount); } // Even a ten digit amount of gold only takes up about half a line. There's no need to wrap or clip text here so we // use the Point form of DrawString. - DrawString(out, tempstr, GetPanelPosition(UiPanels::Inventory, { dialogX + 37, 128 }), UiFlags::ColorWhite | UiFlags::PentaCursor); + DrawString(out, value, GetPanelPosition(UiPanels::Inventory, { dialogX + 37, 128 }), UiFlags::ColorWhite | UiFlags::PentaCursor); } void control_drop_gold(char vkey) diff --git a/Source/control.h b/Source/control.h index 3913bac8c..e7821651e 100644 --- a/Source/control.h +++ b/Source/control.h @@ -41,7 +41,6 @@ extern bool drawmanaflag; extern bool chrbtnactive; extern DVL_API_FOR_TEST int pnumlines; extern UiFlags InfoColor; -extern char tempstr[256]; extern int sbooktab; extern int8_t initialDropGoldIndex; extern bool talkflag; diff --git a/Source/cursor.cpp b/Source/cursor.cpp index 0ac90ad42..acac8b92d 100644 --- a/Source/cursor.cpp +++ b/Source/cursor.cpp @@ -217,8 +217,7 @@ void CheckTown() trigflag = true; ClearPanel(); strcpy(infostr, _("Town Portal")); - strcpy(tempstr, fmt::format(_("from {:s}"), Players[missile._misource]._pName).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("from {:s}"), Players[missile._misource]._pName)); cursPosition = missile.position.tile; } } @@ -233,11 +232,7 @@ void CheckRportal() trigflag = true; ClearPanel(); strcpy(infostr, _("Portal to")); - if (!setlevel) - strcpy(tempstr, _("The Unholy Altar")); - else - strcpy(tempstr, _("level 15")); - AddPanelString(tempstr); + AddPanelString(!setlevel ? _("The Unholy Altar") : _("level 15")); cursPosition = missile.position.tile; } } diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 57aa3191a..a29bd6f47 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -12,7 +12,6 @@ #include "DiabloUI/selstart.h" #include "automap.h" #include "capture.h" -#include "control.h" #include "cursor.h" #include "dead.h" #ifdef _DEBUG @@ -58,9 +57,10 @@ #include "pfile.h" #include "plrmsg.h" #include "qol/chatlog.h" -#include "qol/common.h" #include "qol/itemlabels.h" +#include "qol/monhealthbar.h" #include "qol/stash.h" +#include "qol/xpbar.h" #include "restrict.h" #include "setmaps.h" #include "sound.h" @@ -159,7 +159,8 @@ void StartGame(interface_mode uMsg) #endif assert(ghMainWnd); music_stop(); - InitQol(); + InitMonsterHealthBar(); + InitXPBar(); ShowProgress(uMsg); gmenu_init_menu(); InitLevelCursor(); @@ -170,7 +171,8 @@ void StartGame(interface_mode uMsg) void FreeGame() { - FreeQol(); + FreeMonsterHealthBar(); + FreeXPBar(); FreeControlPan(); FreeInvGFX(); FreeStashGFX(); diff --git a/Source/error.cpp b/Source/error.cpp index b66cc3658..e4b1e1ed0 100644 --- a/Source/error.cpp +++ b/Source/error.cpp @@ -9,7 +9,6 @@ #include "error.h" #include "DiabloUI/ui_flags.hpp" -#include "control.h" #include "engine/render/cel_render.hpp" #include "engine/render/text_render.hpp" #include "panels/info_box.hpp" @@ -34,10 +33,7 @@ void InitNextLines() TextLines.clear(); - char tempstr[1536]; // Longest test is about 768 chars * 2 for unicode - strcpy(tempstr, message.data()); - - const std::string paragraphs = WordWrapString(tempstr, LineWidth, GameFont12, 1); + const std::string paragraphs = WordWrapString(message, LineWidth, GameFont12, 1); size_t previous = 0; while (true) { diff --git a/Source/help.cpp b/Source/help.cpp index 053d7a0a0..e468f6e95 100644 --- a/Source/help.cpp +++ b/Source/help.cpp @@ -7,7 +7,6 @@ #include #include "DiabloUI/ui_flags.hpp" -#include "control.h" #include "engine/render/text_render.hpp" #include "init.h" #include "minitext.h" @@ -148,12 +147,9 @@ void InitHelp() return; HelpFlag = false; - char tempString[1024]; for (const auto *text : HelpText) { - strcpy(tempString, _(text)); - - const std::string paragraph = WordWrapString(tempString, 577); + const std::string paragraph = WordWrapString(_(text), 577); size_t previous = 0; while (true) { diff --git a/Source/inv.cpp b/Source/inv.cpp index 5c0c52662..a984bb04a 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -1365,8 +1365,7 @@ void DrawInvBelt(const Surface &out) if (AllItemsList[myPlayer.SpdList[i].IDidx].iUsable && myPlayer.SpdList[i]._itype != ItemType::Gold) { - snprintf(tempstr, sizeof(tempstr) / sizeof(*tempstr), "%i", i + 1); - DrawString(out, tempstr, { position - Displacement { 0, 12 }, InventorySlotSizeInPixels }, UiFlags::ColorWhite | UiFlags::AlignRight); + DrawString(out, fmt::format("{:d}", i + 1), { position - Displacement { 0, 12 }, InventorySlotSizeInPixels }, UiFlags::ColorWhite | UiFlags::AlignRight); } } } diff --git a/Source/items.cpp b/Source/items.cpp index a447ba98f..2e00af4d6 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -1845,140 +1845,102 @@ void PrintItemOil(char iDidx) { switch (iDidx) { case IMISC_OILACC: - strcpy(tempstr, _("increases a weapon's")); - AddPanelString(tempstr); - strcpy(tempstr, _("chance to hit")); - AddPanelString(tempstr); + AddPanelString(_("increases a weapon's")); + AddPanelString(_("chance to hit")); break; case IMISC_OILMAST: - strcpy(tempstr, _("greatly increases a")); - AddPanelString(tempstr); - strcpy(tempstr, _("weapon's chance to hit")); - AddPanelString(tempstr); + AddPanelString(_("greatly increases a")); + AddPanelString(_("weapon's chance to hit")); break; case IMISC_OILSHARP: - strcpy(tempstr, _("increases a weapon's")); - AddPanelString(tempstr); - strcpy(tempstr, _("damage potential")); - AddPanelString(tempstr); + AddPanelString(_("increases a weapon's")); + AddPanelString(_("damage potential")); break; case IMISC_OILDEATH: - strcpy(tempstr, _("greatly increases a weapon's")); - AddPanelString(tempstr); - strcpy(tempstr, _("damage potential - not bows")); - AddPanelString(tempstr); + AddPanelString(_("greatly increases a weapon's")); + AddPanelString(_("damage potential - not bows")); break; case IMISC_OILSKILL: - strcpy(tempstr, _("reduces attributes needed")); - AddPanelString(tempstr); - strcpy(tempstr, _("to use armor or weapons")); - AddPanelString(tempstr); + AddPanelString(_("reduces attributes needed")); + AddPanelString(_("to use armor or weapons")); break; case IMISC_OILBSMTH: - /*xgettext:no-c-format*/ strcpy(tempstr, _("restores 20% of an")); - AddPanelString(tempstr); - strcpy(tempstr, _("item's durability")); - AddPanelString(tempstr); + AddPanelString(/*xgettext:no-c-format*/ _("restores 20% of an")); + AddPanelString(_("item's durability")); break; case IMISC_OILFORT: - strcpy(tempstr, _("increases an item's")); - AddPanelString(tempstr); - strcpy(tempstr, _("current and max durability")); - AddPanelString(tempstr); + AddPanelString(_("increases an item's")); + AddPanelString(_("current and max durability")); break; case IMISC_OILPERM: - strcpy(tempstr, _("makes an item indestructible")); - AddPanelString(tempstr); + AddPanelString(_("makes an item indestructible")); break; case IMISC_OILHARD: - strcpy(tempstr, _("increases the armor class")); - AddPanelString(tempstr); - strcpy(tempstr, _("of armor and shields")); - AddPanelString(tempstr); + AddPanelString(_("increases the armor class")); + AddPanelString(_("of armor and shields")); break; case IMISC_OILIMP: - strcpy(tempstr, _("greatly increases the armor")); - AddPanelString(tempstr); - strcpy(tempstr, _("class of armor and shields")); - AddPanelString(tempstr); + AddPanelString(_("greatly increases the armor")); + AddPanelString(_("class of armor and shields")); break; case IMISC_RUNEF: - strcpy(tempstr, _("sets fire trap")); - AddPanelString(tempstr); + AddPanelString(_("sets fire trap")); break; case IMISC_RUNEL: case IMISC_GR_RUNEL: - strcpy(tempstr, _("sets lightning trap")); - AddPanelString(tempstr); + AddPanelString(_("sets lightning trap")); break; case IMISC_GR_RUNEF: - strcpy(tempstr, _("sets fire trap")); - AddPanelString(tempstr); + AddPanelString(_("sets fire trap")); break; case IMISC_RUNES: - strcpy(tempstr, _("sets petrification trap")); - AddPanelString(tempstr); + AddPanelString(_("sets petrification trap")); break; case IMISC_FULLHEAL: - strcpy(tempstr, _("restore all life")); - AddPanelString(tempstr); + AddPanelString(_("restore all life")); break; case IMISC_HEAL: - strcpy(tempstr, _("restore some life")); - AddPanelString(tempstr); + AddPanelString(_("restore some life")); break; case IMISC_OLDHEAL: - strcpy(tempstr, _("recover life")); - AddPanelString(tempstr); + AddPanelString(_("recover life")); break; case IMISC_DEADHEAL: - strcpy(tempstr, _("deadly heal")); - AddPanelString(tempstr); + AddPanelString(_("deadly heal")); break; case IMISC_MANA: - strcpy(tempstr, _("restore some mana")); - AddPanelString(tempstr); + AddPanelString(_("restore some mana")); break; case IMISC_FULLMANA: - strcpy(tempstr, _("restore all mana")); - AddPanelString(tempstr); + AddPanelString(_("restore all mana")); break; case IMISC_ELIXSTR: - strcpy(tempstr, _("increase strength")); - AddPanelString(tempstr); + AddPanelString(_("increase strength")); break; case IMISC_ELIXMAG: - strcpy(tempstr, _("increase magic")); - AddPanelString(tempstr); + AddPanelString(_("increase magic")); break; case IMISC_ELIXDEX: - strcpy(tempstr, _("increase dexterity")); - AddPanelString(tempstr); + AddPanelString(_("increase dexterity")); break; case IMISC_ELIXVIT: - strcpy(tempstr, _("increase vitality")); - AddPanelString(tempstr); + AddPanelString(_("increase vitality")); break; case IMISC_ELIXWEAK: case IMISC_ELIXDIS: - strcpy(tempstr, _("decrease strength")); - AddPanelString(tempstr); + AddPanelString(_("decrease strength")); break; case IMISC_ELIXCLUM: - strcpy(tempstr, _("decrease dexterity")); - AddPanelString(tempstr); + AddPanelString(_("decrease dexterity")); break; case IMISC_ELIXSICK: - strcpy(tempstr, _("decrease vitality")); - AddPanelString(tempstr); + AddPanelString(_("decrease vitality")); break; case IMISC_REJUV: - strcpy(tempstr, _("restore some life and mana")); - AddPanelString(tempstr); + AddPanelString(_("restore some life and mana")); break; case IMISC_FULLREJUV: - strcpy(tempstr, _("restore all life and mana")); - AddPanelString(tempstr); + AddPanelString(_("restore all life and mana")); break; } } @@ -1992,49 +1954,38 @@ void DrawUniqueInfoWindow(const Surface &out) void PrintItemMisc(const Item &item) { if (item._iMiscId == IMISC_SCROLL) { - strcpy(tempstr, _("Right-click to read")); - AddPanelString(tempstr); + AddPanelString(_("Right-click to read")); } if (item._iMiscId == IMISC_SCROLLT) { - strcpy(tempstr, _("Right-click to read, then")); - AddPanelString(tempstr); - strcpy(tempstr, _("left-click to target")); - AddPanelString(tempstr); + AddPanelString(_("Right-click to read, then")); + AddPanelString(_("left-click to target")); } if (item._iMiscId >= IMISC_USEFIRST && item._iMiscId <= IMISC_USELAST) { PrintItemOil(item._iMiscId); - strcpy(tempstr, _("Right-click to use")); - AddPanelString(tempstr); + AddPanelString(_("Right-click to use")); } if (item._iMiscId > IMISC_OILFIRST && item._iMiscId < IMISC_OILLAST) { PrintItemOil(item._iMiscId); - strcpy(tempstr, _("Right click to use")); - AddPanelString(tempstr); + AddPanelString(_("Right click to use")); } if (item._iMiscId > IMISC_RUNEFIRST && item._iMiscId < IMISC_RUNELAST) { PrintItemOil(item._iMiscId); - strcpy(tempstr, _("Right click to use")); - AddPanelString(tempstr); + AddPanelString(_("Right click to use")); } if (item._iMiscId == IMISC_BOOK) { - strcpy(tempstr, _("Right-click to read")); - AddPanelString(tempstr); + AddPanelString(_("Right-click to read")); } if (item._iMiscId == IMISC_NOTE) { - strcpy(tempstr, _("Right click to read")); - AddPanelString(tempstr); + AddPanelString(_("Right click to read")); } if (item._iMiscId == IMISC_MAPOFDOOM) { - strcpy(tempstr, _("Right-click to view")); - AddPanelString(tempstr); + AddPanelString(_("Right-click to view")); } if (item._iMiscId == IMISC_EAR) { - strcpy(tempstr, fmt::format(_("Level: {:d}"), item._ivalue).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("Level: {:d}"), item._ivalue)); } if (item._iMiscId == IMISC_AURIC) { - strcpy(tempstr, _("Doubles gold capacity")); - AddPanelString(tempstr); + AddPanelString(_("Doubles gold capacity")); } } @@ -2045,14 +1996,14 @@ void PrintItemInfo(const Item &item) uint8_t dex = item._iMinDex; uint8_t mag = item._iMinMag; if (str != 0 || mag != 0 || dex != 0) { - strcpy(tempstr, _("Required:")); + std::string text = _("Required:"); if (str != 0) - strcpy(tempstr + strlen(tempstr), fmt::format(_(" {:d} Str"), str).c_str()); + text.append(fmt::format(_(" {:d} Str"), str)); if (mag != 0) - strcpy(tempstr + strlen(tempstr), fmt::format(_(" {:d} Mag"), mag).c_str()); + text.append(fmt::format(_(" {:d} Mag"), mag)); if (dex != 0) - strcpy(tempstr + strlen(tempstr), fmt::format(_(" {:d} Dex"), dex).c_str()); - AddPanelString(tempstr); + text.append(fmt::format(_(" {:d} Dex"), dex)); + AddPanelString(text); } } @@ -3414,24 +3365,30 @@ void RecreateItem(Item &item, int idx, uint16_t icreateinfo, int iseed, int ival void RecreateEar(Item &item, uint16_t ic, int iseed, int id, int dur, int mdur, int ch, int mch, int ivalue, int ibuff) { InitializeItem(item, IDI_EAR); - tempstr[0] = static_cast((ic >> 8) & 0x7F); - tempstr[1] = static_cast(ic & 0x7F); - tempstr[2] = static_cast((iseed >> 24) & 0x7F); - tempstr[3] = static_cast((iseed >> 16) & 0x7F); - tempstr[4] = static_cast((iseed >> 8) & 0x7F); - tempstr[5] = static_cast(iseed & 0x7F); - tempstr[6] = static_cast(id & 0x7F); - tempstr[7] = static_cast(dur & 0x7F); - tempstr[8] = static_cast(mdur & 0x7F); - tempstr[9] = static_cast(ch & 0x7F); - tempstr[10] = static_cast(mch & 0x7F); - tempstr[11] = static_cast((ivalue >> 8) & 0x7F); - tempstr[12] = static_cast((ibuff >> 24) & 0x7F); - tempstr[13] = static_cast((ibuff >> 16) & 0x7F); - tempstr[14] = static_cast((ibuff >> 8) & 0x7F); - tempstr[15] = static_cast(ibuff & 0x7F); - tempstr[16] = '\0'; - strcpy(item._iName, fmt::format(_(/* TRANSLATORS: {:s} will be a Character Name */ "Ear of {:s}"), tempstr).c_str()); + + char heroName[17]; + heroName[0] = static_cast((ic >> 8) & 0x7F); + heroName[1] = static_cast(ic & 0x7F); + heroName[2] = static_cast((iseed >> 24) & 0x7F); + heroName[3] = static_cast((iseed >> 16) & 0x7F); + heroName[4] = static_cast((iseed >> 8) & 0x7F); + heroName[5] = static_cast(iseed & 0x7F); + heroName[6] = static_cast(id & 0x7F); + heroName[7] = static_cast(dur & 0x7F); + heroName[8] = static_cast(mdur & 0x7F); + heroName[9] = static_cast(ch & 0x7F); + heroName[10] = static_cast(mch & 0x7F); + heroName[11] = static_cast((ivalue >> 8) & 0x7F); + heroName[12] = static_cast((ibuff >> 24) & 0x7F); + heroName[13] = static_cast((ibuff >> 16) & 0x7F); + heroName[14] = static_cast((ibuff >> 8) & 0x7F); + heroName[15] = static_cast(ibuff & 0x7F); + heroName[16] = '\0'; + + std::string itemName = fmt::format(_(/* TRANSLATORS: {:s} will be a Character Name */ "Ear of {:s}"), heroName); + + CopyUtf8(item._iName, itemName, sizeof(item._iName)); + item._iCurs = ((ivalue >> 6) & 3) + ICURS_EAR_SORCERER; item._ivalue = ivalue & 0x3F; item._iCreateInfo = ic; @@ -3985,31 +3942,24 @@ void PrintItemDetails(const Item &item) if (item._iClass == ICLASS_WEAPON) { if (item._iMinDam == item._iMaxDam) { if (item._iMaxDur == DUR_INDESTRUCTIBLE) - strcpy(tempstr, fmt::format(_("damage: {:d} Indestructible"), item._iMinDam).c_str()); + AddPanelString(fmt::format(_("damage: {:d} Indestructible"), item._iMinDam)); else - strcpy(tempstr, fmt::format(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d} Dur: {:d}/{:d}"), item._iMinDam, item._iDurability, item._iMaxDur).c_str()); + AddPanelString(fmt::format(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d} Dur: {:d}/{:d}"), item._iMinDam, item._iDurability, item._iMaxDur)); } else { if (item._iMaxDur == DUR_INDESTRUCTIBLE) - strcpy(tempstr, fmt::format(_("damage: {:d}-{:d} Indestructible"), item._iMinDam, item._iMaxDam).c_str()); + AddPanelString(fmt::format(_("damage: {:d}-{:d} Indestructible"), item._iMinDam, item._iMaxDam)); else - strcpy(tempstr, fmt::format(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d}-{:d} Dur: {:d}/{:d}"), item._iMinDam, item._iMaxDam, item._iDurability, item._iMaxDur).c_str()); + AddPanelString(fmt::format(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d}-{:d} Dur: {:d}/{:d}"), item._iMinDam, item._iMaxDam, item._iDurability, item._iMaxDur)); } - AddPanelString(tempstr); } if (item._iClass == ICLASS_ARMOR) { if (item._iMaxDur == DUR_INDESTRUCTIBLE) - strcpy(tempstr, fmt::format(_("armor: {:d} Indestructible"), item._iAC).c_str()); + AddPanelString(fmt::format(_("armor: {:d} Indestructible"), item._iAC)); else - strcpy(tempstr, fmt::format(_(/* TRANSLATORS: Dur: is durability */ "armor: {:d} Dur: {:d}/{:d}"), item._iAC, item._iDurability, item._iMaxDur).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_(/* TRANSLATORS: Dur: is durability */ "armor: {:d} Dur: {:d}/{:d}"), item._iAC, item._iDurability, item._iMaxDur)); } if (item._iMiscId == IMISC_STAFF && item._iMaxCharges != 0) { - if (item._iMinDam == item._iMaxDam) - strcpy(tempstr, fmt::format(_(/* TRANSLATORS: dam: is damage Dur: is durability */ "dam: {:d} Dur: {:d}/{:d}"), item._iMinDam, item._iDurability, item._iMaxDur).c_str()); - else - strcpy(tempstr, fmt::format(_(/* TRANSLATORS: dam: is damage Dur: is durability */ "dam: {:d}-{:d} Dur: {:d}/{:d}"), item._iMinDam, item._iMaxDam, item._iDurability, item._iMaxDur).c_str()); - strcpy(tempstr, fmt::format(_("Charges: {:d}/{:d}"), item._iCharges, item._iMaxCharges).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("Charges: {:d}/{:d}"), item._iCharges, item._iMaxCharges)); } if (item._iPrePower != -1) { AddPanelString(PrintItemPower(item._iPrePower, item)); @@ -4030,34 +3980,30 @@ void PrintItemDur(const Item &item) if (item._iClass == ICLASS_WEAPON) { if (item._iMinDam == item._iMaxDam) { if (item._iMaxDur == DUR_INDESTRUCTIBLE) - strcpy(tempstr, fmt::format(_("damage: {:d} Indestructible"), item._iMinDam).c_str()); + AddPanelString(fmt::format(_("damage: {:d} Indestructible"), item._iMinDam)); else - strcpy(tempstr, fmt::format(_("damage: {:d} Dur: {:d}/{:d}"), item._iMinDam, item._iDurability, item._iMaxDur).c_str()); + AddPanelString(fmt::format(_("damage: {:d} Dur: {:d}/{:d}"), item._iMinDam, item._iDurability, item._iMaxDur)); } else { if (item._iMaxDur == DUR_INDESTRUCTIBLE) - strcpy(tempstr, fmt::format(_("damage: {:d}-{:d} Indestructible"), item._iMinDam, item._iMaxDam).c_str()); + AddPanelString(fmt::format(_("damage: {:d}-{:d} Indestructible"), item._iMinDam, item._iMaxDam)); else - strcpy(tempstr, fmt::format(_("damage: {:d}-{:d} Dur: {:d}/{:d}"), item._iMinDam, item._iMaxDam, item._iDurability, item._iMaxDur).c_str()); + AddPanelString(fmt::format(_("damage: {:d}-{:d} Dur: {:d}/{:d}"), item._iMinDam, item._iMaxDam, item._iDurability, item._iMaxDur)); } - AddPanelString(tempstr); if (item._iMiscId == IMISC_STAFF && item._iMaxCharges > 0) { - strcpy(tempstr, fmt::format(_("Charges: {:d}/{:d}"), item._iCharges, item._iMaxCharges).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("Charges: {:d}/{:d}"), item._iCharges, item._iMaxCharges)); } if (item._iMagical != ITEM_QUALITY_NORMAL) AddPanelString(_("Not Identified")); } if (item._iClass == ICLASS_ARMOR) { if (item._iMaxDur == DUR_INDESTRUCTIBLE) - strcpy(tempstr, fmt::format(_("armor: {:d} Indestructible"), item._iAC).c_str()); + AddPanelString(fmt::format(_("armor: {:d} Indestructible"), item._iAC)); else - strcpy(tempstr, fmt::format(_("armor: {:d} Dur: {:d}/{:d}"), item._iAC, item._iDurability, item._iMaxDur).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("armor: {:d} Dur: {:d}/{:d}"), item._iAC, item._iDurability, item._iMaxDur)); if (item._iMagical != ITEM_QUALITY_NORMAL) AddPanelString(_("Not Identified")); if (item._iMiscId == IMISC_STAFF && item._iMaxCharges > 0) { - strcpy(tempstr, fmt::format(_("Charges: {:d}/{:d}"), item._iCharges, item._iMaxCharges).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("Charges: {:d}/{:d}"), item._iCharges, item._iMaxCharges)); } } if (IsAnyOf(item._itype, ItemType::Ring, ItemType::Amulet)) diff --git a/Source/minitext.cpp b/Source/minitext.cpp index ab1485dae..5a3abd306 100644 --- a/Source/minitext.cpp +++ b/Source/minitext.cpp @@ -42,10 +42,7 @@ void LoadText(const char *text) { TextLines.clear(); - char tempstr[2560]; - strcpy(tempstr, text); - - const std::string paragraphs = WordWrapString(tempstr, 543, GameFont30); + const std::string paragraphs = WordWrapString(text, 543, GameFont30); size_t previous = 0; while (true) { diff --git a/Source/monster.cpp b/Source/monster.cpp index 94481c37d..4c9313b1a 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -4644,12 +4644,11 @@ void M_FallenFear(Point position) void PrintMonstHistory(int mt) { if (*sgOptions.Gameplay.showMonsterType) { - strcpy(tempstr, fmt::format(_("Type: {:s} Kills: {:d}"), GetMonsterTypeText(MonstersData[mt]), MonsterKillCounts[mt]).c_str()); + AddPanelString(fmt::format(_("Type: {:s} Kills: {:d}"), GetMonsterTypeText(MonstersData[mt]), MonsterKillCounts[mt])); } else { - strcpy(tempstr, fmt::format(_("Total kills: {:d}"), MonsterKillCounts[mt]).c_str()); + AddPanelString(fmt::format(_("Total kills: {:d}"), MonsterKillCounts[mt])); } - AddPanelString(tempstr); if (MonsterKillCounts[mt] >= 30) { int minHP = MonstersData[mt].mMinHP; int maxHP = MonstersData[mt].mMaxHP; @@ -4679,38 +4678,32 @@ void PrintMonstHistory(int mt) minHP = 4 * minHP + hpBonusHell; maxHP = 4 * maxHP + hpBonusHell; } - strcpy(tempstr, fmt::format(_("Hit Points: {:d}-{:d}"), minHP, maxHP).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("Hit Points: {:d}-{:d}"), minHP, maxHP)); } if (MonsterKillCounts[mt] >= 15) { int res = (sgGameInitInfo.nDifficulty != DIFF_HELL) ? MonstersData[mt].mMagicRes : MonstersData[mt].mMagicRes2; if ((res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING | IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING)) == 0) { - strcpy(tempstr, _("No magic resistance")); - AddPanelString(tempstr); + AddPanelString(_("No magic resistance")); } else { if ((res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING)) != 0) { - strcpy(tempstr, _("Resists: ")); + std::string resists = _("Resists:"); if ((res & RESIST_MAGIC) != 0) - strcat(tempstr, _("Magic ")); + resists.append(_(" Magic")); if ((res & RESIST_FIRE) != 0) - strcat(tempstr, _("Fire ")); + resists.append(_(" Fire")); if ((res & RESIST_LIGHTNING) != 0) - strcat(tempstr, _("Lightning ")); - string_view str { tempstr }; - str.remove_suffix(str.size() - FindLastUtf8Symbols(str)); - AddPanelString(str); + resists.append(_(" Lightning")); + AddPanelString(resists); } if ((res & (IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING)) != 0) { - strcpy(tempstr, _("Immune: ")); + std::string immune = _("Immune:"); if ((res & IMMUNE_MAGIC) != 0) - strcat(tempstr, _("Magic ")); + immune.append(_(" Magic")); if ((res & IMMUNE_FIRE) != 0) - strcat(tempstr, _("Fire ")); + immune.append(_(" Fire")); if ((res & IMMUNE_LIGHTNING) != 0) - strcat(tempstr, _("Lightning ")); - string_view str { tempstr }; - str.remove_suffix(str.size() - FindLastUtf8Symbols(str)); - AddPanelString(str); + immune.append(_(" Lightning")); + AddPanelString(immune); } } } @@ -4720,28 +4713,24 @@ void PrintUniqueHistory() { auto &monster = Monsters[pcursmonst]; if (*sgOptions.Gameplay.showMonsterType) { - strcpy(tempstr, fmt::format(_("Type: {:s}"), GetMonsterTypeText(*monster.MData)).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("Type: {:s}"), GetMonsterTypeText(*monster.MData))); } int res = monster.mMagicRes & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING | IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING); if (res == 0) { - strcpy(tempstr, _("No resistances")); - AddPanelString(tempstr); - strcpy(tempstr, _("No Immunities")); + AddPanelString(_("No resistances")); + AddPanelString(_("No Immunities")); } else { if ((res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING)) != 0) - strcpy(tempstr, _("Some Magic Resistances")); + AddPanelString(_("Some Magic Resistances")); else - strcpy(tempstr, _("No resistances")); - AddPanelString(tempstr); + AddPanelString(_("No resistances")); if ((res & (IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING)) != 0) { - strcpy(tempstr, _("Some Magic Immunities")); + AddPanelString(_("Some Magic Immunities")); } else { - strcpy(tempstr, _("No Immunities")); + AddPanelString(_("No Immunities")); } } - AddPanelString(tempstr); } void PlayEffect(Monster &monster, int mode) diff --git a/Source/objects.cpp b/Source/objects.cpp index 6f6d9e178..fcb36942c 100644 --- a/Source/objects.cpp +++ b/Source/objects.cpp @@ -9,7 +9,6 @@ #include "DiabloUI/ui_flags.hpp" #include "automap.h" -#include "control.h" #include "cursor.h" #ifdef _DEBUG #include "debug.h" @@ -5457,8 +5456,7 @@ void GetObjectStr(const Object &object) break; case OBJ_SHRINEL: case OBJ_SHRINER: - strcpy(tempstr, fmt::format(_(/* TRANSLATORS: {:s} will be a name from the Shrine block above */ "{:s} Shrine"), _(ShrineNames[object._oVar1])).c_str()); - strcpy(infostr, tempstr); + strcpy(infostr, fmt::format(_(/* TRANSLATORS: {:s} will be a name from the Shrine block above */ "{:s} Shrine"), _(ShrineNames[object._oVar1])).c_str()); break; case OBJ_SKELBOOK: strcpy(infostr, _("Skeleton Tome")); @@ -5526,14 +5524,12 @@ void GetObjectStr(const Object &object) } if (Players[MyPlayerId]._pClass == HeroClass::Rogue) { if (object._oTrapFlag) { - strcpy(tempstr, fmt::format(_(/* TRANSLATORS: {:s} will either be a chest or a door */ "Trapped {:s}"), infostr).c_str()); - strcpy(infostr, tempstr); + strcpy(infostr, fmt::format(_(/* TRANSLATORS: {:s} will either be a chest or a door */ "Trapped {:s}"), infostr).c_str()); InfoColor = UiFlags::ColorRed; } } if (object.IsDisabled()) { - strcpy(tempstr, fmt::format(_(/* TRANSLATORS: If user enabled diablo.ini setting "Disable Crippling Shrines" is set to 1; also used for Na-Kruls leaver */ "{:s} (disabled)"), infostr).c_str()); - strcpy(infostr, tempstr); + strcpy(infostr, fmt::format(_(/* TRANSLATORS: If user enabled diablo.ini setting "Disable Crippling Shrines" is set to 1; also used for Na-Kruls leaver */ "{:s} (disabled)"), infostr).c_str()); InfoColor = UiFlags::ColorRed; } } diff --git a/Source/panels/spell_list.cpp b/Source/panels/spell_list.cpp index ca8074ed1..405b1e7c4 100644 --- a/Source/panels/spell_list.cpp +++ b/Source/panels/spell_list.cpp @@ -168,14 +168,12 @@ void DrawSpellList(const Surface &out) PrintSBookSpellType(out, spellListItem.location, _("Spell"), spellColor); strcpy(infostr, fmt::format(_("{:s} Spell"), pgettext("spell", spellDataItem.sNameText)).c_str()); if (spellId == SPL_HBOLT) { - strcpy(tempstr, _("Damages undead only")); - AddPanelString(tempstr); + AddPanelString(_("Damages undead only")); } if (spellLevel == 0) - strcpy(tempstr, _("Spell Level 0 - Unusable")); + AddPanelString(_("Spell Level 0 - Unusable")); else - strcpy(tempstr, fmt::format(_("Spell Level {:d}"), spellLevel).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("Spell Level {:d}"), spellLevel)); break; case RSPLTYPE_SCROLL: { if (myPlayer.plrlevel != 0) { @@ -187,8 +185,7 @@ void DrawSpellList(const Surface &out) const int scrollCount = std::count_if(items.begin(), items.end(), [spellId](const Item &item) { return item.IsScrollOf(spellId); }); - strcpy(tempstr, fmt::format(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount), scrollCount).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount), scrollCount)); } break; case RSPLTYPE_CHARGES: { if (myPlayer.plrlevel != 0) { @@ -197,15 +194,13 @@ void DrawSpellList(const Surface &out) PrintSBookSpellType(out, spellListItem.location, _("Staff"), spellColor); strcpy(infostr, fmt::format(_("Staff of {:s}"), pgettext("spell", spellDataItem.sNameText)).c_str()); int charges = myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges; - strcpy(tempstr, fmt::format(ngettext("{:d} Charge", "{:d} Charges", charges), charges).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(ngettext("{:d} Charge", "{:d} Charges", charges), charges)); } break; case RSPLTYPE_INVALID: break; } if (hotkeyName) { - strcpy(tempstr, fmt::format(_("Spell Hotkey {:s}"), *hotkeyName).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("Spell Hotkey {:s}"), *hotkeyName)); } } } diff --git a/Source/qol/common.cpp b/Source/qol/common.cpp deleted file mode 100644 index e05abd137..000000000 --- a/Source/qol/common.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @file common.h - * - * Common functions for QoL features - */ - -#include - -#include "common.h" -#include "engine.h" -#include "engine/render/text_render.hpp" -#include "qol/monhealthbar.h" -#include "qol/xpbar.h" -#include "utils/language.h" - -namespace devilution { - -char *PrintWithSeparator(char *out, int n) -{ - if (n < 1000) { - return out + sprintf(out, "%d", n); - } - - char *append = PrintWithSeparator(out, n / 1000); - return append + sprintf(append, _(/* TRANSLATORS: Decimal separator */ ",%03d"), n % 1000); -} - -void FreeQol() -{ - FreeMonsterHealthBar(); - FreeXPBar(); -} - -void InitQol() -{ - InitMonsterHealthBar(); - InitXPBar(); -} - -} // namespace devilution diff --git a/Source/qol/common.h b/Source/qol/common.h deleted file mode 100644 index 89957df61..000000000 --- a/Source/qol/common.h +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @file common.h - * - * Common functions for QoL features - */ -#pragma once - -#include - -namespace devilution { - -struct Surface; - -/** - * @brief Prints integer into buffer, using ',' as thousands separator. - * @param out Destination buffer - * @param n Number to print - * @return Address of first character after printed number - */ -char *PrintWithSeparator(char *out, int n); - -void FreeQol(); -void InitQol(); - -} // namespace devilution diff --git a/Source/qol/itemlabels.cpp b/Source/qol/itemlabels.cpp index 225495605..f16fa6e78 100644 --- a/Source/qol/itemlabels.cpp +++ b/Source/qol/itemlabels.cpp @@ -4,7 +4,8 @@ #include #include -#include "common.h" +#include + #include "control.h" #include "cursor.h" #include "engine/point.hpp" @@ -13,6 +14,7 @@ #include "inv.h" #include "itemlabels.h" #include "utils/language.h" +#include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -65,10 +67,9 @@ void AddItemToLabelQueue(int id, int x, int y) return; Item &item = Items[id]; - const char *textOnGround; + std::string textOnGround; if (item._itype == ItemType::Gold) { - std::sprintf(tempstr, _("%i gold"), item._ivalue); - textOnGround = tempstr; + textOnGround = fmt::format(_("{:d} gold"), item._ivalue); } else { textOnGround = item._iIdentified ? item._iIName : item._iName; } diff --git a/Source/qol/monhealthbar.cpp b/Source/qol/monhealthbar.cpp index 1a6ea071c..fbe3b3920 100644 --- a/Source/qol/monhealthbar.cpp +++ b/Source/qol/monhealthbar.cpp @@ -8,7 +8,6 @@ #include "control.h" #include "cursor.h" #include "options.h" -#include "qol/common.h" #include "utils/language.h" namespace devilution { diff --git a/Source/qol/stash.cpp b/Source/qol/stash.cpp index 533cf0208..2e63d22eb 100644 --- a/Source/qol/stash.cpp +++ b/Source/qol/stash.cpp @@ -564,26 +564,21 @@ void DrawGoldWithdraw(const Surface &out, int amount) CelDrawTo(out, GetPanelPosition(UiPanels::Stash, { dialogX, 178 }), *pGBoxBuff, 1); - constexpr auto BufferSize = sizeof(tempstr) / sizeof(*tempstr); - - CopyUtf8(tempstr, _("How many gold pieces do you want to withdraw? (MAX 5000)"), BufferSize); - // Pre-wrap the string at spaces, otherwise DrawString would hard wrap in the middle of words - const std::string wrapped = WordWrapString(tempstr, 200); + const std::string wrapped = WordWrapString(_("How many gold pieces do you want to withdraw? (MAX 5000)"), 200); // The split gold dialog is roughly 4 lines high, but we need at least one line for the player to input an amount. // Using a clipping region 50 units high (approx 3 lines with a lineheight of 17) to ensure there is enough room left // for the text entered by the player. DrawString(out, wrapped, { GetPanelPosition(UiPanels::Stash, { dialogX + 31, 75 }), { 200, 50 } }, UiFlags::ColorWhitegold | UiFlags::AlignCenter, 1, 17); - tempstr[0] = '\0'; + std::string value = ""; if (amount > 0) { - // snprintf ensures that the destination buffer ends in a null character. - snprintf(tempstr, BufferSize, "%u", amount); + value = fmt::format("{:d}", amount); } // Even a ten digit amount of gold only takes up about half a line. There's no need to wrap or clip text here so we // use the Point form of DrawString. - DrawString(out, tempstr, GetPanelPosition(UiPanels::Stash, { dialogX + 37, 128 }), UiFlags::ColorWhite | UiFlags::PentaCursor); + DrawString(out, value, GetPanelPosition(UiPanels::Stash, { dialogX + 37, 128 }), UiFlags::ColorWhite | UiFlags::PentaCursor); } void CloseGoldWithdraw() diff --git a/Source/qol/xpbar.cpp b/Source/qol/xpbar.cpp index ca787f75b..a95efe191 100644 --- a/Source/qol/xpbar.cpp +++ b/Source/qol/xpbar.cpp @@ -10,7 +10,6 @@ #include #include "DiabloUI/art_draw.h" -#include "common.h" #include "control.h" #include "engine/point.hpp" #include "options.h" @@ -45,6 +44,27 @@ void DrawEndCap(const Surface &out, Point point, int idx, const ColorGradient &g out.SetPixel({ point.x, point.y + 3 }, gradient[idx / 2]); } +/** + * @brief Prints integer with thousands separator. + */ +std::string PrintWithSeparator(int n) +{ + std::string number = fmt::format("{:d}", n); + std::string out = ""; + + int length = number.length(); + int mlength = length % 3; + if (mlength == 0) + mlength = 3; + out.append(number.substr(0, mlength)); + for (int i = mlength; i < length; i += 3) { + out.append(_(/* TRANSLATORS: Thousands separator */ ",")); + out.append(number.substr(i, 3)); + } + + return out; +} + } // namespace void InitXPBar() @@ -122,17 +142,13 @@ bool CheckXPBarInfo() const int8_t charLevel = player._pLevel; - strcpy(tempstr, fmt::format(_("Level {:d}"), charLevel).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format(_("Level {:d}"), charLevel)); if (charLevel == MAXCHARLEVEL) { // Show a maximum level indicator for max level players. InfoColor = UiFlags::ColorWhitegold; - strcpy(tempstr, _("Experience: ")); - PrintWithSeparator(tempstr + strlen(tempstr), ExpLvlsTbl[charLevel - 1]); - AddPanelString(tempstr); - + AddPanelString(fmt::format("Experience: {:s}", PrintWithSeparator(ExpLvlsTbl[charLevel - 1]))); AddPanelString(_("Maximum Level")); return true; @@ -140,16 +156,9 @@ bool CheckXPBarInfo() InfoColor = UiFlags::ColorWhite; - strcpy(tempstr, _("Experience: ")); - PrintWithSeparator(tempstr + strlen(tempstr), player._pExperience); - AddPanelString(tempstr); - - strcpy(tempstr, _("Next Level: ")); - PrintWithSeparator(tempstr + strlen(tempstr), ExpLvlsTbl[charLevel]); - AddPanelString(tempstr); - - strcpy(PrintWithSeparator(tempstr, ExpLvlsTbl[charLevel] - player._pExperience), fmt::format(_(" to Level {:d}"), charLevel + 1).c_str()); - AddPanelString(tempstr); + AddPanelString(fmt::format("Experience: {:s}", PrintWithSeparator(player._pExperience))); + AddPanelString(fmt::format("Next Level: {:s}", PrintWithSeparator(ExpLvlsTbl[charLevel]))); + AddPanelString(fmt::format(_("{:s} to Level {:d}"), PrintWithSeparator(ExpLvlsTbl[charLevel] - player._pExperience), charLevel + 1)); return true; } diff --git a/Source/stores.cpp b/Source/stores.cpp index e7097139b..f8385f730 100644 --- a/Source/stores.cpp +++ b/Source/stores.cpp @@ -22,6 +22,8 @@ #include "qol/stash.h" #include "towners.h" #include "utils/language.h" +#include "utils/stdcompat/string_view.hpp" +#include "utils/utf8.hpp" namespace devilution { @@ -201,11 +203,11 @@ void AddSTextVal(int y, int val) stext[y]._sval = val; } -void AddSText(int x, int y, const char *str, UiFlags flags, bool sel) +void AddSText(int x, int y, string_view text, UiFlags flags, bool sel) { stext[y]._sx = x; stext[y]._syoff = 0; - strcpy(stext[y]._sstr, str); + CopyUtf8(stext[y]._sstr, text, sizeof(stext[y]._sstr)); stext[y].flags = flags; stext[y]._sline = 0; stext[y]._ssel = sel; @@ -233,60 +235,58 @@ void AddItemListBackButton(bool selectable = false) void PrintStoreItem(const Item &item, int l, UiFlags flags) { - char sstr[128]; + std::string productLine = ""; - sstr[0] = '\0'; if (item._iIdentified) { if (item._iMagical != ITEM_QUALITY_UNIQUE) { if (item._iPrePower != -1) { - strcat(sstr, PrintItemPower(item._iPrePower, item).c_str()); + productLine.append(PrintItemPower(item._iPrePower, item)); } } if (item._iSufPower != -1) { - if (sstr[0] != '\0') - strcat(sstr, _(", ")); - strcat(sstr, PrintItemPower(item._iSufPower, item).c_str()); + if (!productLine.empty()) + productLine.append(_(", ")); + productLine.append(PrintItemPower(item._iSufPower, item)); } } if (item._iMiscId == IMISC_STAFF && item._iMaxCharges != 0) { - strcpy(tempstr, fmt::format(_("Charges: {:d}/{:d}"), item._iCharges, item._iMaxCharges).c_str()); - if (sstr[0] != '\0') - strcat(sstr, _(", ")); - strcat(sstr, tempstr); + if (!productLine.empty()) + productLine.append(_(", ")); + productLine.append(fmt::format(_("Charges: {:d}/{:d}"), item._iCharges, item._iMaxCharges)); } - if (sstr[0] != '\0') { - AddSText(40, l, sstr, flags, false); + if (!productLine.empty()) { + AddSText(40, l, productLine, flags, false); l++; + productLine = ""; } - sstr[0] = '\0'; - if (item._iClass == ICLASS_WEAPON) - strcpy(sstr, fmt::format(_("Damage: {:d}-{:d} "), item._iMinDam, item._iMaxDam).c_str()); - if (item._iClass == ICLASS_ARMOR) - strcpy(sstr, fmt::format(_("Armor: {:d} "), item._iAC).c_str()); - if (item._iMaxDur != DUR_INDESTRUCTIBLE && item._iMaxDur != 0) { - strcpy(tempstr, fmt::format(_("Dur: {:d}/{:d}, "), item._iDurability, item._iMaxDur).c_str()); - strcat(sstr, tempstr); - } else { - strcat(sstr, _("Indestructible, ")); + + if (item._itype != ItemType::Misc) { + if (item._iClass == ICLASS_WEAPON) + productLine = fmt::format(_("Damage: {:d}-{:d} "), item._iMinDam, item._iMaxDam); + else if (item._iClass == ICLASS_ARMOR) + productLine = fmt::format(_("Armor: {:d} "), item._iAC); + else if (item._iMaxDur != DUR_INDESTRUCTIBLE && item._iMaxDur != 0) + productLine = fmt::format(_("Dur: {:d}/{:d}, "), item._iDurability, item._iMaxDur); + else + productLine = _("Indestructible, "); } - if (item._itype == ItemType::Misc) - sstr[0] = '\0'; + int8_t str = item._iMinStr; uint8_t mag = item._iMinMag; int8_t dex = item._iMinDex; + if (str == 0 && mag == 0 && dex == 0) { - strcat(sstr, _("No required attributes")); + productLine.append(_("No required attributes")); } else { - strcpy(tempstr, _("Required:")); + productLine.append(_("Required:")); if (str != 0) - strcpy(tempstr + strlen(tempstr), fmt::format(_(" {:d} Str"), str).c_str()); + productLine.append(fmt::format(_(" {:d} Str"), str)); if (mag != 0) - strcpy(tempstr + strlen(tempstr), fmt::format(_(" {:d} Mag"), mag).c_str()); + productLine.append(fmt::format(_(" {:d} Mag"), mag)); if (dex != 0) - strcpy(tempstr + strlen(tempstr), fmt::format(_(" {:d} Dex"), dex).c_str()); - strcat(sstr, tempstr); + productLine.append(fmt::format(_(" {:d} Dex"), dex)); } - AddSText(40, l++, sstr, flags, false); + AddSText(40, l++, productLine, flags, false); } void StoreAutoPlace() @@ -354,9 +354,7 @@ void StartSmithBuy() stextsval = 0; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("I have these items for sale: Your gold: {:d}"), Players[MyPlayerId]._pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("I have these items for sale: Your gold: {:d}"), Players[MyPlayerId]._pGold).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); ScrollSmithBuy(stextsval); AddItemListBackButton(); @@ -421,9 +419,7 @@ bool StartSmithPremiumBuy() stextsval = 0; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("I have these premium items for sale: Your gold: {:d}"), Players[MyPlayerId]._pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("I have these premium items for sale: Your gold: {:d}"), Players[MyPlayerId]._pGold).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); AddItemListBackButton(); @@ -542,9 +538,7 @@ void StartSmithSell() stextscrl = false; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("You have nothing I want. Your gold: {:d}"), myPlayer._pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("You have nothing I want. Your gold: {:d}"), myPlayer._pGold).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); AddItemListBackButton(/*selectable=*/true); return; @@ -555,9 +549,7 @@ void StartSmithSell() stextsmax = myPlayer._pNumInv; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("Which item is for sale? Your gold: {:d}"), myPlayer._pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("Which item is for sale? Your gold: {:d}"), myPlayer._pGold).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); ScrollSmithSell(stextsval); AddItemListBackButton(); @@ -628,9 +620,7 @@ void StartSmithRepair() stextscrl = false; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("You have nothing to repair. Your gold: {:d}"), myPlayer._pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("You have nothing to repair. Your gold: {:d}"), myPlayer._pGold).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); AddItemListBackButton(/*selectable=*/true); return; @@ -641,9 +631,7 @@ void StartSmithRepair() stextsmax = myPlayer._pNumInv; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("Repair which item? Your gold: {:d}"), myPlayer._pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("Repair which item? Your gold: {:d}"), myPlayer._pGold).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); ScrollSmithSell(stextsval); @@ -731,9 +719,7 @@ void StartWitchBuy() stextsmax = 20; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("I have these items for sale: Your gold: {:d}"), Players[MyPlayerId]._pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("I have these items for sale: Your gold: {:d}"), Players[MyPlayerId]._pGold).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); ScrollWitchBuy(stextsval); AddItemListBackButton(); @@ -826,9 +812,7 @@ void StartWitchSell() stextscrl = false; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("You have nothing I want. Your gold: {:d}"), myPlayer._pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("You have nothing I want. Your gold: {:d}"), myPlayer._pGold).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); AddItemListBackButton(/*selectable=*/true); return; @@ -839,9 +823,7 @@ void StartWitchSell() stextsmax = myPlayer._pNumInv; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("Which item is for sale? Your gold: {:d}"), myPlayer._pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("Which item is for sale? Your gold: {:d}"), myPlayer._pGold).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); ScrollSmithSell(stextsval); AddItemListBackButton(); @@ -903,9 +885,7 @@ void StartWitchRecharge() stextscrl = false; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("You have nothing to recharge. Your gold: {:d}"), myPlayer._pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("You have nothing to recharge. Your gold: {:d}"), myPlayer._pGold).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); AddItemListBackButton(/*selectable=*/true); return; @@ -916,9 +896,7 @@ void StartWitchRecharge() stextsmax = myPlayer._pNumInv; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("Recharge which item? Your gold: {:d}"), myPlayer._pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("Recharge which item? Your gold: {:d}"), myPlayer._pGold).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); ScrollSmithSell(stextsval); AddItemListBackButton(); @@ -973,33 +951,35 @@ void StoreConfirm() AddSTextVal(8, item._iIvalue); PrintStoreItem(item, 9, itemColor); + string_view prompt; + switch (stextshold) { case STORE_BBOY: - strcpy(tempstr, _("Do we have a deal?")); + prompt = _("Do we have a deal?"); break; case STORE_SIDENTIFY: - strcpy(tempstr, _("Are you sure you want to identify this item?")); + prompt = _("Are you sure you want to identify this item?"); break; case STORE_HBUY: case STORE_SPBUY: case STORE_WBUY: case STORE_SBUY: - strcpy(tempstr, _("Are you sure you want to buy this item?")); + prompt = _("Are you sure you want to buy this item?"); break; case STORE_WRECHARGE: - strcpy(tempstr, _("Are you sure you want to recharge this item?")); + prompt = _("Are you sure you want to recharge this item?"); break; case STORE_SSELL: case STORE_WSELL: - strcpy(tempstr, _("Are you sure you want to sell this item?")); + prompt = _("Are you sure you want to sell this item?"); break; case STORE_SREPAIR: - strcpy(tempstr, _("Are you sure you want to repair this item?")); + prompt = _("Are you sure you want to repair this item?"); break; default: app_fatal("Unknown store dialog %i", stextshold); } - AddSText(0, 15, tempstr, UiFlags::ColorWhite | UiFlags::AlignCenter, false); + AddSText(0, 15, prompt, UiFlags::ColorWhite | UiFlags::AlignCenter, false); AddSText(0, 18, _("Yes"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); AddSText(0, 20, _("No"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); } @@ -1029,9 +1009,7 @@ void SStartBoyBuy() stextscrl = false; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("I have this item for sale: Your gold: {:d}"), MyPlayer->_pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("I have this item for sale: Your gold: {:d}"), MyPlayer->_pGold), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); UiFlags itemColor = boyitem.getTextColorWithStatCheck(); @@ -1112,9 +1090,7 @@ void StartHealerBuy() stextsval = 0; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("I have these items for sale: Your gold: {:d}"), Players[MyPlayerId]._pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("I have these items for sale: Your gold: {:d}"), Players[MyPlayerId]._pGold), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); ScrollHealerBuy(stextsval); @@ -1232,9 +1208,7 @@ void StartStorytellerIdentify() stextscrl = false; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("You have nothing to identify. Your gold: {:d}"), myPlayer._pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("You have nothing to identify. Your gold: {:d}"), myPlayer._pGold), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); AddItemListBackButton(/*selectable=*/true); return; @@ -1245,9 +1219,7 @@ void StartStorytellerIdentify() stextsmax = myPlayer._pNumInv; /* TRANSLATORS: This text is white space sensitive. Check for correct alignment! */ - strcpy(tempstr, fmt::format(_("Identify which item? Your gold: {:d}"), myPlayer._pGold).c_str()); - - AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 1, fmt::format(_("Identify which item? Your gold: {:d}"), myPlayer._pGold), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); ScrollSmithSell(stextsval); @@ -1276,12 +1248,10 @@ void StartTalk() stextsize = false; stextscrl = false; - strcpy(tempstr, fmt::format(_("Talk to {:s}"), _(TownerNames[talker])).c_str()); - AddSText(0, 2, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 2, fmt::format(_("Talk to {:s}"), _(TownerNames[talker])), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(5); if (gbIsSpawn) { - strcpy(tempstr, fmt::format(_("Talking to {:s}"), _(TownerNames[talker])).c_str()); - AddSText(0, 10, tempstr, UiFlags::ColorWhite | UiFlags::AlignCenter, false); + AddSText(0, 10, fmt::format(_("Talking to {:s}"), _(TownerNames[talker])), UiFlags::ColorWhite | UiFlags::AlignCenter, false); AddSText(0, 12, _("is not available"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); AddSText(0, 14, _("in the shareware"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); AddSText(0, 16, _("version"), UiFlags::ColorWhite | UiFlags::AlignCenter, false);