From 779ccaca1775d550429c910ad5daf1964cb1bac2 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sun, 8 May 2022 12:43:47 +0100 Subject: [PATCH] Overhaul translation fetching 1. Do not modify the map after loading. Instead, return string views (guaranteed to be null-terminated) from look up functions and return the key directly if not found. 2. Use an `unorded_map` instead of `map` where available (C++20). Saves a bit of RAM (~50 KiB) and improves lookup performance. --- Source/DiabloUI/dialogs.cpp | 16 +++-- Source/DiabloUI/dialogs.h | 5 +- Source/DiabloUI/mainmenu.cpp | 2 +- Source/DiabloUI/selconn.cpp | 8 +-- Source/DiabloUI/selgame.cpp | 48 ++++++------- Source/DiabloUI/selhero.cpp | 40 +++++------ Source/DiabloUI/title.cpp | 2 +- Source/appfat.cpp | 27 +++----- Source/appfat.h | 7 +- Source/automap.cpp | 6 +- Source/control.cpp | 17 ++--- Source/control.h | 3 +- Source/controls/modifier_hints.cpp | 7 +- Source/controls/plrctrls.cpp | 2 +- Source/discord/discord.cpp | 8 +-- Source/dvlnet/tcp_client.cpp | 4 +- Source/error.cpp | 4 +- Source/error.h | 3 +- Source/gamemenu.cpp | 10 +-- Source/init.cpp | 4 +- Source/inv.cpp | 4 +- Source/items.cpp | 26 +++---- Source/items.h | 3 +- Source/loadsave.cpp | 10 +-- Source/menu.cpp | 2 +- Source/monster.cpp | 24 +++---- Source/msg.cpp | 21 +++--- Source/panels/charpanel.cpp | 6 +- Source/panels/spell_list.cpp | 4 +- Source/pfile.cpp | 14 ++-- Source/plrmsg.cpp | 4 +- Source/qol/chatlog.cpp | 6 +- Source/qol/chatlog.h | 2 +- Source/qol/monhealthbar.cpp | 7 +- Source/qol/stash.cpp | 4 +- Source/qol/xpbar.cpp | 9 ++- Source/stores.cpp | 18 ++--- Source/utils/language.cpp | 64 +++++++++++++----- Source/utils/language.h | 26 ++++++- Source/utils/log.hpp | 40 +++++------ Source/utils/stdcompat/string_view.hpp | 11 +++ Source/utils/string_or_view.hpp | 94 ++++++++++++++++++++++++++ Source/utils/ui_fwd.h | 2 +- 43 files changed, 389 insertions(+), 235 deletions(-) create mode 100644 Source/utils/string_or_view.hpp diff --git a/Source/DiabloUI/dialogs.cpp b/Source/DiabloUI/dialogs.cpp index e7f06fd72..294051b07 100644 --- a/Source/DiabloUI/dialogs.cpp +++ b/Source/DiabloUI/dialogs.cpp @@ -277,11 +277,11 @@ void DialogLoop(const std::vector> &items, const std } while (!dialogEnd); } -void UiOkDialog(const char *caption, const char *text, bool error, const std::vector> &renderBehind) +void UiOkDialog(string_view caption, string_view text, bool error, const std::vector> &renderBehind) { static bool inDialog = false; - if (caption != nullptr) { + if (!caption.empty()) { LogError("{}\n{}", caption, text); } else { LogError("{}", text); @@ -291,7 +291,9 @@ void UiOkDialog(const char *caption, const char *text, bool error, const std::ve if (!gbQuietMode) { if (SDL_ShowCursor(SDL_ENABLE) <= -1) Log("{}", SDL_GetError()); - if (SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, caption, text, nullptr) <= -1) { + std::string captionStr = std::string(caption); + std::string textStr = std::string(caption); + if (SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, captionStr.c_str(), textStr.c_str(), nullptr) <= -1) { Log("{}", SDL_GetError()); } } @@ -313,19 +315,19 @@ void UiOkDialog(const char *caption, const char *text, bool error, const std::ve } // namespace -void UiErrorOkDialog(const char *caption, const char *text, const std::vector> &renderBehind) +void UiErrorOkDialog(string_view caption, string_view text, const std::vector> &renderBehind) { UiOkDialog(caption, text, /*error=*/true, renderBehind); } -void UiErrorOkDialog(const char *caption, const char *text, bool error) +void UiErrorOkDialog(string_view caption, string_view text, bool error) { UiOkDialog(caption, text, error, vecNULL); } -void UiErrorOkDialog(const char *text, const std::vector> &renderBehind) +void UiErrorOkDialog(string_view text, const std::vector> &renderBehind) { - UiErrorOkDialog(nullptr, text, renderBehind); + UiErrorOkDialog({}, text, renderBehind); } } // namespace devilution diff --git a/Source/DiabloUI/dialogs.h b/Source/DiabloUI/dialogs.h index 7d2852f1f..6f3d4cec1 100644 --- a/Source/DiabloUI/dialogs.h +++ b/Source/DiabloUI/dialogs.h @@ -3,10 +3,11 @@ #include #include "DiabloUI/ui_item.h" +#include "utils/stdcompat/string_view.hpp" namespace devilution { -void UiErrorOkDialog(const char *text, const std::vector> &renderBehind); -void UiErrorOkDialog(const char *caption, const char *text, const std::vector> &renderBehind); +void UiErrorOkDialog(string_view text, const std::vector> &renderBehind); +void UiErrorOkDialog(string_view caption, string_view text, const std::vector> &renderBehind); } // namespace devilution diff --git a/Source/DiabloUI/mainmenu.cpp b/Source/DiabloUI/mainmenu.cpp index f762409a8..28c32d8f1 100644 --- a/Source/DiabloUI/mainmenu.cpp +++ b/Source/DiabloUI/mainmenu.cpp @@ -60,7 +60,7 @@ void MainmenuLoad(const char *name, void (*fnSound)(const char *file)) if (gbIsSpawn && gbIsHellfire) { SDL_Rect rect1 = { (Sint16)(uiPosition.x), (Sint16)(uiPosition.y + 145), 640, 30 }; - vecMainMenuDialog.push_back(std::make_unique(_("Shareware").c_str(), rect1, UiFlags::FontSize30 | UiFlags::ColorUiSilver | UiFlags::AlignCenter, 8)); + vecMainMenuDialog.push_back(std::make_unique(_("Shareware").data(), rect1, UiFlags::FontSize30 | UiFlags::ColorUiSilver | UiFlags::AlignCenter, 8)); } vecMainMenuDialog.push_back(std::make_unique(vecMenuItems, vecMenuItems.size(), uiPosition.x + 64, (uiPosition.y + 192), 510, 43, UiFlags::FontSize42 | UiFlags::ColorUiGold | UiFlags::AlignCenter, 5)); diff --git a/Source/DiabloUI/selconn.cpp b/Source/DiabloUI/selconn.cpp index 60f3c0197..616f19ef1 100644 --- a/Source/DiabloUI/selconn.cpp +++ b/Source/DiabloUI/selconn.cpp @@ -53,25 +53,25 @@ void SelconnLoad() const Point uiPosition = GetUIRectangle().position; SDL_Rect rect1 = { (Sint16)(uiPosition.x + 24), (Sint16)(Sint16)(uiPosition.y + 161), 590, 35 }; - vecSelConnDlg.push_back(std::make_unique(_("Multi Player Game").c_str(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); + vecSelConnDlg.push_back(std::make_unique(_("Multi Player Game").data(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); SDL_Rect rect2 = { (Sint16)(uiPosition.x + 35), (Sint16)(uiPosition.y + 218), DESCRIPTION_WIDTH, 21 }; vecSelConnDlg.push_back(std::make_unique(selconn_MaxPlayers, rect2, UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); SDL_Rect rect3 = { (Sint16)(uiPosition.x + 35), (Sint16)(uiPosition.y + 256), DESCRIPTION_WIDTH, 21 }; - vecSelConnDlg.push_back(std::make_unique(_("Requirements:").c_str(), rect3, UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); + vecSelConnDlg.push_back(std::make_unique(_("Requirements:").data(), rect3, UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); SDL_Rect rect4 = { (Sint16)(uiPosition.x + 35), (Sint16)(uiPosition.y + 275), DESCRIPTION_WIDTH, 66 }; vecSelConnDlg.push_back(std::make_unique(selconn_Description, rect4, UiFlags::FontSize12 | UiFlags::ColorUiSilverDark, 1, 16)); SDL_Rect rect5 = { (Sint16)(uiPosition.x + 30), (Sint16)(uiPosition.y + 356), 220, 31 }; - vecSelConnDlg.push_back(std::make_unique(_("no gateway needed").c_str(), rect5, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiSilver, 0)); + vecSelConnDlg.push_back(std::make_unique(_("no gateway needed").data(), rect5, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiSilver, 0)); SDL_Rect rect6 = { (Sint16)(uiPosition.x + 35), (Sint16)(uiPosition.y + 393), DESCRIPTION_WIDTH, 21 }; vecSelConnDlg.push_back(std::make_unique(selconn_Gateway, rect6, UiFlags::AlignCenter | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); SDL_Rect rect7 = { (Sint16)(uiPosition.x + 300), (Sint16)(uiPosition.y + 211), 295, 33 }; - vecSelConnDlg.push_back(std::make_unique(_("Select Connection").c_str(), rect7, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); + vecSelConnDlg.push_back(std::make_unique(_("Select Connection").data(), rect7, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); SDL_Rect rect8 = { (Sint16)(uiPosition.x + 16), (Sint16)(uiPosition.y + 427), 250, 35 }; vecSelConnDlg.push_back(std::make_unique(_("Change Gateway"), nullptr, rect8, UiFlags::AlignCenter | UiFlags::VerticalCenter | UiFlags::FontSize30 | UiFlags::ColorUiGold | UiFlags::ElementHidden)); diff --git a/Source/DiabloUI/selgame.cpp b/Source/DiabloUI/selgame.cpp index 58299a467..1892b8310 100644 --- a/Source/DiabloUI/selgame.cpp +++ b/Source/DiabloUI/selgame.cpp @@ -77,7 +77,7 @@ bool IsGameCompatible(const GameData &data) static std::string GetErrorMessageIncompatibility(const GameData &data) { if (data.programid != GAME_ID) { - std::string gameMode; + string_view gameMode; switch (data.programid) { case GameIdDiabloFull: gameMode = _("Diablo"); @@ -92,7 +92,7 @@ static std::string GetErrorMessageIncompatibility(const GameData &data) gameMode = _("Hellfire Shareware"); break; default: - return _("The host is running a different game than you."); + return std::string(_("The host is running a different game than you.")); } return fmt::format(_("The host is running a different game mode ({:s}) than you."), gameMode); } else { @@ -128,16 +128,16 @@ void UiInitGameSelectionList(string_view search) vecSelGameDialog.push_back(std::make_unique(PcxSprite { *ArtScrollBarBackground }, PcxSprite { *ArtScrollBarThumb }, PcxSpriteSheet { *ArtScrollBarArrow }, rectScrollbar)); SDL_Rect rect1 = { (Sint16)(uiPosition.x + 24), (Sint16)(uiPosition.y + 161), 590, 35 }; - vecSelGameDialog.push_back(std::make_unique(_(ConnectionNames[provider]).c_str(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); + vecSelGameDialog.push_back(std::make_unique(_(ConnectionNames[provider]).data(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); SDL_Rect rect2 = { (Sint16)(uiPosition.x + 35), (Sint16)(uiPosition.y + 211), 205, 192 }; - vecSelGameDialog.push_back(std::make_unique(_("Description:").c_str(), rect2, UiFlags::FontSize24 | UiFlags::ColorUiSilver)); + vecSelGameDialog.push_back(std::make_unique(_("Description:").data(), rect2, UiFlags::FontSize24 | UiFlags::ColorUiSilver)); SDL_Rect rect3 = { (Sint16)(uiPosition.x + 35), (Sint16)(uiPosition.y + 256), DESCRIPTION_WIDTH, 192 }; vecSelGameDialog.push_back(std::make_unique(selgame_Description, rect3, UiFlags::FontSize12 | UiFlags::ColorUiSilverDark, 1, 16)); SDL_Rect rect4 = { (Sint16)(uiPosition.x + 300), (Sint16)(uiPosition.y + 211), 295, 33 }; - vecSelGameDialog.push_back(std::make_unique(_("Select Action").c_str(), rect4, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); + vecSelGameDialog.push_back(std::make_unique(_("Select Action").data(), rect4, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); #ifdef PACKET_ENCRYPTION vecSelGameDlgItems.push_back(std::make_unique(_("Create Game"), 0, UiFlags::ColorUiGold)); @@ -220,7 +220,7 @@ void selgame_GameSelection_Focus(int value) break; default: const auto &gameInfo = Gamelist[vecSelGameDlgItems[value]->m_value - 3]; - std::string infoString = _("Join the public game already in progress."); + std::string infoString = std::string(_("Join the public game already in progress.")); infoString.append("\n\n"); if (IsGameCompatible(gameInfo.gameData)) { string_view difficulty; @@ -239,16 +239,16 @@ void selgame_GameSelection_Focus(int value) infoString.append("\n"); switch (gameInfo.gameData.nTickRate) { case 20: - infoString.append(_("Speed: Normal")); + AppendStrView(infoString, _("Speed: Normal")); break; case 30: - infoString.append(_("Speed: Fast")); + AppendStrView(infoString, _("Speed: Fast")); break; case 40: - infoString.append(_("Speed: Faster")); + AppendStrView(infoString, _("Speed: Faster")); break; case 50: - infoString.append(_("Speed: Fastest")); + AppendStrView(infoString, _("Speed: Fastest")); break; default: // This should not occure, so no translations is needed @@ -256,7 +256,7 @@ void selgame_GameSelection_Focus(int value) break; } infoString.append("\n"); - infoString.append(_("Players: ")); + AppendStrView(infoString, _("Players: ")); for (auto &playerName : gameInfo.players) { infoString.append(playerName); infoString.append(" "); @@ -315,10 +315,10 @@ void selgame_GameSelection_Select(int value) switch (value) { case 0: case 1: { - title = _("Create Game").c_str(); + title = _("Create Game").data(); SDL_Rect rect4 = { (Sint16)(uiPosition.x + 299), (Sint16)(uiPosition.y + 211), 295, 35 }; - vecSelGameDialog.push_back(std::make_unique(_("Select Difficulty").c_str(), rect4, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); + vecSelGameDialog.push_back(std::make_unique(_("Select Difficulty").data(), rect4, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); vecSelGameDlgItems.push_back(std::make_unique(_("Normal"), DIFF_NORMAL)); vecSelGameDlgItems.push_back(std::make_unique(_("Nightmare"), DIFF_NIGHTMARE)); @@ -341,9 +341,9 @@ void selgame_GameSelection_Select(int value) const char *inputHint; if (provider == SELCONN_ZT) { - inputHint = _("Enter Game ID").c_str(); + inputHint = _("Enter Game ID").data(); } else { - inputHint = _("Enter address").c_str(); + inputHint = _("Enter address").data(); } SDL_Rect rect4 = { (Sint16)(uiPosition.x + 305), (Sint16)(uiPosition.y + 211), 285, 33 }; @@ -405,9 +405,9 @@ bool IsDifficultyAllowed(int value) selgame_Free(); if (value == 1) - UiSelOkDialog(title, _("Your character must reach level 20 before you can enter a multiplayer game of Nightmare difficulty.").c_str(), false); + UiSelOkDialog(title, _("Your character must reach level 20 before you can enter a multiplayer game of Nightmare difficulty.").data(), false); if (value == 2) - UiSelOkDialog(title, _("Your character must reach level 30 before you can enter a multiplayer game of Hell difficulty.").c_str(), false); + UiSelOkDialog(title, _("Your character must reach level 30 before you can enter a multiplayer game of Hell difficulty.").data(), false); selgame_Init(); @@ -476,7 +476,7 @@ void selgame_GameSpeedSelection() const Point uiPosition = GetUIRectangle().position; SDL_Rect rect1 = { (Sint16)(uiPosition.x + 24), (Sint16)(uiPosition.y + 161), 590, 35 }; - vecSelGameDialog.push_back(std::make_unique(_("Create Game").c_str(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); + vecSelGameDialog.push_back(std::make_unique(_("Create Game").data(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); SDL_Rect rect2 = { (Sint16)(uiPosition.x + 34), (Sint16)(uiPosition.y + 211), 205, 33 }; vecSelGameDialog.push_back(std::make_unique(selgame_Label, rect2, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); @@ -485,7 +485,7 @@ void selgame_GameSpeedSelection() vecSelGameDialog.push_back(std::make_unique(selgame_Description, rect3, UiFlags::FontSize12 | UiFlags::ColorUiSilverDark, 1, 16)); SDL_Rect rect4 = { (Sint16)(uiPosition.x + 299), (Sint16)(uiPosition.y + 211), 295, 35 }; - vecSelGameDialog.push_back(std::make_unique(_("Select Game Speed").c_str(), rect4, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); + vecSelGameDialog.push_back(std::make_unique(_("Select Game Speed").data(), rect4, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); vecSelGameDlgItems.push_back(std::make_unique(_("Normal"), 20)); vecSelGameDlgItems.push_back(std::make_unique(_("Fast"), 30)); @@ -555,16 +555,16 @@ void selgame_Password_Init(int /*value*/) const Point uiPosition = GetUIRectangle().position; SDL_Rect rect1 = { (Sint16)(uiPosition.x + 24), (Sint16)(uiPosition.y + 161), 590, 35 }; - vecSelGameDialog.push_back(std::make_unique(_(ConnectionNames[provider]).c_str(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); + vecSelGameDialog.push_back(std::make_unique(_(ConnectionNames[provider]).data(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); SDL_Rect rect2 = { (Sint16)(uiPosition.x + 35), (Sint16)(uiPosition.y + 211), 205, 192 }; - vecSelGameDialog.push_back(std::make_unique(_("Description:").c_str(), rect2, UiFlags::FontSize24 | UiFlags::ColorUiSilver)); + vecSelGameDialog.push_back(std::make_unique(_("Description:").data(), rect2, UiFlags::FontSize24 | UiFlags::ColorUiSilver)); SDL_Rect rect3 = { (Sint16)(uiPosition.x + 35), (Sint16)(uiPosition.y + 256), DESCRIPTION_WIDTH, 192 }; vecSelGameDialog.push_back(std::make_unique(selgame_Description, rect3, UiFlags::FontSize12 | UiFlags::ColorUiSilverDark, 1, 16)); SDL_Rect rect4 = { (Sint16)(uiPosition.x + 305), (Sint16)(uiPosition.y + 211), 285, 33 }; - vecSelGameDialog.push_back(std::make_unique(_("Enter Password").c_str(), rect4, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); + vecSelGameDialog.push_back(std::make_unique(_("Enter Password").data(), rect4, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); // Allow password to be empty only when joining games bool allowEmpty = selgame_selectedGame == 2; @@ -629,7 +629,7 @@ void selgame_Password_Select(int /*value*/) std::string error = SDL_GetError(); if (error.empty()) error = "Unknown network error"; - UiSelOkDialog(_("Multi Player Game").c_str(), error.c_str(), false); + UiSelOkDialog(_("Multi Player Game").data(), error.c_str(), false); selgame_Init(); selgame_Password_Init(selgame_selectedGame); } @@ -650,7 +650,7 @@ void selgame_Password_Select(int /*value*/) std::string error = SDL_GetError(); if (error.empty()) error = "Unknown network error"; - UiSelOkDialog(_("Multi Player Game").c_str(), error.c_str(), false); + UiSelOkDialog(_("Multi Player Game").data(), error.c_str(), false); selgame_Init(); selgame_Password_Init(0); } diff --git a/Source/DiabloUI/selhero.cpp b/Source/DiabloUI/selhero.cpp index c1e6574ec..cd1107f99 100644 --- a/Source/DiabloUI/selhero.cpp +++ b/Source/DiabloUI/selhero.cpp @@ -131,7 +131,7 @@ void SelheroListSelect(int value) vecSelDlgItems.clear(); SDL_Rect rect1 = { (Sint16)(uiPosition.x + 264), (Sint16)(uiPosition.y + 211), 320, 33 }; - vecSelDlgItems.push_back(std::make_unique(_("Choose Class").c_str(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); + vecSelDlgItems.push_back(std::make_unique(_("Choose Class").data(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); vecSelHeroDlgItems.clear(); int itemH = 33; @@ -162,7 +162,7 @@ void SelheroListSelect(int value) memset(&selhero_heroInfo.name, 0, sizeof(selhero_heroInfo.name)); selhero_heroInfo.saveNumber = pfile_ui_get_first_unused_save_num(); SelheroSetStats(); - title = selhero_isMultiPlayer ? _("New Multi Player Hero").c_str() : _("New Single Player Hero").c_str(); + title = selhero_isMultiPlayer ? _("New Multi Player Hero").data() : _("New Single Player Hero").data(); return; } @@ -170,7 +170,7 @@ void SelheroListSelect(int value) vecSelDlgItems.clear(); SDL_Rect rect1 = { (Sint16)(uiPosition.x + 264), (Sint16)(uiPosition.y + 211), 320, 33 }; - vecSelDlgItems.push_back(std::make_unique(_("Save File Exists").c_str(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); + vecSelDlgItems.push_back(std::make_unique(_("Save File Exists").data(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); vecSelHeroDlgItems.clear(); vecSelHeroDlgItems.push_back(std::make_unique(_("Load Game"), 0)); @@ -184,7 +184,7 @@ void SelheroListSelect(int value) vecSelDlgItems.push_back(std::make_unique(_("Cancel"), &UiFocusNavigationEsc, rect3, UiFlags::AlignCenter | UiFlags::VerticalCenter | UiFlags::FontSize30 | UiFlags::ColorUiGold)); UiInitList(SelheroLoadFocus, SelheroLoadSelect, selhero_List_Init, vecSelDlgItems, true); - title = _("Single Player Characters").c_str(); + title = _("Single Player Characters").data(); return; } @@ -230,7 +230,7 @@ void SelheroClassSelectorSelect(int value) auto hClass = static_cast(vecSelHeroDlgItems[value]->m_value); if (gbIsSpawn && (hClass == HeroClass::Rogue || hClass == HeroClass::Sorcerer || (hClass == HeroClass::Bard && !hfbard_mpq))) { ArtBackground = std::nullopt; - UiSelOkDialog(nullptr, _("The Rogue and Sorcerer are only available in the full retail version of Diablo. Visit https://www.gog.com/game/diablo to purchase.").c_str(), false); + UiSelOkDialog(nullptr, _("The Rogue and Sorcerer are only available in the full retail version of Diablo. Visit https://www.gog.com/game/diablo to purchase.").data(), false); LoadBackgroundArt("ui_art\\selhero.pcx"); SelheroListSelect(selhero_SaveCount); return; @@ -238,13 +238,13 @@ void SelheroClassSelectorSelect(int value) const Point uiPosition = GetUIRectangle().position; - title = selhero_isMultiPlayer ? _("New Multi Player Hero").c_str() : _("New Single Player Hero").c_str(); + title = selhero_isMultiPlayer ? _("New Multi Player Hero").data() : _("New Single Player Hero").data(); memset(selhero_heroInfo.name, '\0', sizeof(selhero_heroInfo.name)); if (ShouldPrefillHeroName()) strcpy(selhero_heroInfo.name, SelheroGenerateName(selhero_heroInfo.heroclass)); vecSelDlgItems.clear(); SDL_Rect rect1 = { (Sint16)(uiPosition.x + 264), (Sint16)(uiPosition.y + 211), 320, 33 }; - vecSelDlgItems.push_back(std::make_unique(_("Enter Name").c_str(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); + vecSelDlgItems.push_back(std::make_unique(_("Enter Name").data(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); SDL_Rect rect2 = { (Sint16)(uiPosition.x + 265), (Sint16)(uiPosition.y + 317), 320, 33 }; vecSelDlgItems.push_back(std::make_unique(_("Enter Name"), selhero_heroInfo.name, 15, false, rect2, UiFlags::FontSize24 | UiFlags::ColorUiGold)); @@ -276,14 +276,14 @@ void SelheroNameSelect(int /*value*/) // only check names in multiplayer, we don't care about them in single if (selhero_isMultiPlayer && !UiValidPlayerName(selhero_heroInfo.name)) { ArtBackground = std::nullopt; - UiSelOkDialog(title, _("Invalid name. A name cannot contain spaces, reserved characters, or reserved words.\n").c_str(), false); + UiSelOkDialog(title, _("Invalid name. A name cannot contain spaces, reserved characters, or reserved words.\n").data(), false); LoadBackgroundArt("ui_art\\selhero.pcx"); } else { if (gfnHeroCreate(&selhero_heroInfo)) { SelheroLoadSelect(1); return; } - UiErrorOkDialog(_(/* TRANSLATORS: Error Message */ "Unable to create character.").c_str(), vecSelDlgItems); + UiErrorOkDialog(_(/* TRANSLATORS: Error Message */ "Unable to create character."), vecSelDlgItems); } memset(selhero_heroInfo.name, '\0', sizeof(selhero_heroInfo.name)); @@ -439,36 +439,36 @@ void selhero_Init() vecSelHeroDialog.push_back(std::move(heroImg)); SDL_Rect rect3 = { (Sint16)(uiPosition.x + 39), (Sint16)(uiPosition.y + 323), 110, 21 }; - vecSelHeroDialog.push_back(std::make_unique(_("Level:").c_str(), rect3, UiFlags::AlignRight | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); + vecSelHeroDialog.push_back(std::make_unique(_("Level:").data(), rect3, UiFlags::AlignRight | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); SDL_Rect rect4 = { (Sint16)(uiPosition.x + 39), (Sint16)(uiPosition.y + 323), 110, 21 }; - vecSelHeroDialog.push_back(std::make_unique(_("Level:").c_str(), rect4, UiFlags::AlignRight | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); + vecSelHeroDialog.push_back(std::make_unique(_("Level:").data(), rect4, UiFlags::AlignRight | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); SDL_Rect rect5 = { (Sint16)(uiPosition.x + 159), (Sint16)(uiPosition.y + 323), 40, 21 }; vecSelHeroDialog.push_back(std::make_unique(textStats[0], rect5, UiFlags::AlignCenter | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); SDL_Rect rect6 = { (Sint16)(uiPosition.x + 39), (Sint16)(uiPosition.y + 358), 110, 21 }; - vecSelHeroDialog.push_back(std::make_unique(_("Strength:").c_str(), rect6, UiFlags::AlignRight | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); + vecSelHeroDialog.push_back(std::make_unique(_("Strength:").data(), rect6, UiFlags::AlignRight | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); SDL_Rect rect7 = { (Sint16)(uiPosition.x + 159), (Sint16)(uiPosition.y + 358), 40, 21 }; vecSelHeroDialog.push_back(std::make_unique(textStats[1], rect7, UiFlags::AlignCenter | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); SDL_Rect rect8 = { (Sint16)(uiPosition.x + 39), (Sint16)(uiPosition.y + 380), 110, 21 }; - vecSelHeroDialog.push_back(std::make_unique(_("Magic:").c_str(), rect8, UiFlags::AlignRight | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); + vecSelHeroDialog.push_back(std::make_unique(_("Magic:").data(), rect8, UiFlags::AlignRight | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); SDL_Rect rect9 = { (Sint16)(uiPosition.x + 159), (Sint16)(uiPosition.y + 380), 40, 21 }; vecSelHeroDialog.push_back(std::make_unique(textStats[2], rect9, UiFlags::AlignCenter | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); SDL_Rect rect10 = { (Sint16)(uiPosition.x + 39), (Sint16)(uiPosition.y + 401), 110, 21 }; - vecSelHeroDialog.push_back(std::make_unique(_("Dexterity:").c_str(), rect10, UiFlags::AlignRight | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); + vecSelHeroDialog.push_back(std::make_unique(_("Dexterity:").data(), rect10, UiFlags::AlignRight | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); SDL_Rect rect11 = { (Sint16)(uiPosition.x + 159), (Sint16)(uiPosition.y + 401), 40, 21 }; vecSelHeroDialog.push_back(std::make_unique(textStats[3], rect11, UiFlags::AlignCenter | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); SDL_Rect rect12 = { (Sint16)(uiPosition.x + 39), (Sint16)(uiPosition.y + 422), 110, 21 }; - vecSelHeroDialog.push_back(std::make_unique(_("Vitality:").c_str(), rect12, UiFlags::AlignRight | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); + vecSelHeroDialog.push_back(std::make_unique(_("Vitality:").data(), rect12, UiFlags::AlignRight | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); SDL_Rect rect13 = { (Sint16)(uiPosition.x + 159), (Sint16)(uiPosition.y + 422), 40, 21 }; vecSelHeroDialog.push_back(std::make_unique(textStats[4], rect13, UiFlags::AlignCenter | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); #ifdef _DEBUG SDL_Rect rect14 = { (Sint16)(uiPosition.x + 39), (Sint16)(uiPosition.y + 443), 110, 21 }; - vecSelHeroDialog.push_back(std::make_unique(_("Savegame:").c_str(), rect14, UiFlags::AlignRight | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); + vecSelHeroDialog.push_back(std::make_unique(_("Savegame:").data(), rect14, UiFlags::AlignRight | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); SDL_Rect rect15 = { (Sint16)(uiPosition.x + 159), (Sint16)(uiPosition.y + 443), 40, 21 }; vecSelHeroDialog.push_back(std::make_unique(textStats[5], rect15, UiFlags::AlignCenter | UiFlags::FontSize12 | UiFlags::ColorUiSilverDark)); #endif @@ -482,7 +482,7 @@ void selhero_List_Init() vecSelDlgItems.clear(); SDL_Rect rect1 = { (Sint16)(uiPosition.x + 264), (Sint16)(uiPosition.y + 211), 320, 33 }; - vecSelDlgItems.push_back(std::make_unique(_("Select Hero").c_str(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); + vecSelDlgItems.push_back(std::make_unique(_("Select Hero").data(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); vecSelHeroDlgItems.clear(); for (std::size_t i = 0; i < selhero_SaveCount; i++) { @@ -490,7 +490,7 @@ void selhero_List_Init() if (selhero_heros[i].saveNumber == selhero_heroInfo.saveNumber) selectedItem = i; } - vecSelHeroDlgItems.push_back(std::make_unique(_("New Hero").c_str(), static_cast(selhero_SaveCount))); + vecSelHeroDlgItems.push_back(std::make_unique(_("New Hero").data(), static_cast(selhero_SaveCount))); vecSelDlgItems.push_back(std::make_unique(vecSelHeroDlgItems, 6, uiPosition.x + 265, (uiPosition.y + 256), 320, 26, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiGold)); @@ -510,9 +510,9 @@ void selhero_List_Init() UiInitList(SelheroListFocus, SelheroListSelect, SelheroListEsc, vecSelDlgItems, false, nullptr, SelheroListDeleteYesNo, selectedItem); if (selhero_isMultiPlayer) { - title = _("Multi Player Characters").c_str(); + title = _("Multi Player Characters").data(); } else { - title = _("Single Player Characters").c_str(); + title = _("Single Player Characters").data(); } } diff --git a/Source/DiabloUI/title.cpp b/Source/DiabloUI/title.cpp index 41e683883..34582a4dd 100644 --- a/Source/DiabloUI/title.cpp +++ b/Source/DiabloUI/title.cpp @@ -48,7 +48,7 @@ void UiTitleDialog() UiAddLogo(&vecTitleScreen, LOGO_BIG, 182); SDL_Rect rect = { (Sint16)(uiPosition.x), (Sint16)(uiPosition.y + 410), 640, 26 }; - vecTitleScreen.push_back(std::make_unique(_("Copyright © 1996-2001 Blizzard Entertainment").c_str(), rect, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiSilver)); + vecTitleScreen.push_back(std::make_unique(_("Copyright © 1996-2001 Blizzard Entertainment").data(), rect, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiSilver)); } bool endMenu = false; diff --git a/Source/appfat.cpp b/Source/appfat.cpp index b2c608199..a1c029ea3 100644 --- a/Source/appfat.cpp +++ b/Source/appfat.cpp @@ -35,7 +35,7 @@ void MsgBox(const char *pszFmt, va_list va) vsnprintf(text, sizeof(text), pszFmt, va); - UiErrorOkDialog(_("Error").c_str(), text); + UiErrorOkDialog(_("Error"), text); } /** @@ -59,6 +59,13 @@ void FreeDlg() } // namespace +void app_fatal(string_view str) +{ + FreeDlg(); + UiErrorOkDialog(_("Error"), str); + diablo_quit(1); +} + void app_fatal(const char *pszFmt, ...) { va_list va; @@ -74,18 +81,6 @@ void app_fatal(const char *pszFmt, ...) diablo_quit(1); } -void DrawDlg(const char *pszFmt, ...) -{ - char text[256]; - va_list va; - - va_start(va, pszFmt); - vsnprintf(text, sizeof(text), pszFmt, va); - va_end(va); - - UiErrorOkDialog(PROJECT_NAME, text, false); -} - #ifdef _DEBUG void assert_fail(int nLineNo, const char *pszFile, const char *pszFail) { @@ -99,7 +94,7 @@ void ErrDlg(const char *title, string_view error, string_view logFilePath, int l std::string text = fmt::format(_(/* TRANSLATORS: Error message that displays relevant information for bug report */ "{:s}\n\nThe error occurred at: {:s} line {:d}"), error, logFilePath, logLineNr); - UiErrorOkDialog(title, text.c_str()); + UiErrorOkDialog(title, text); app_fatal(nullptr); } @@ -111,7 +106,7 @@ void InsertCDDlg(string_view archiveName) "Make sure that it is in the game folder."), archiveName); - UiErrorOkDialog(_("Data File Error").c_str(), text.c_str()); + UiErrorOkDialog(_("Data File Error"), text); app_fatal(nullptr); } @@ -119,7 +114,7 @@ void DirErrorDlg(string_view error) { std::string text = fmt::format(_(/* TRANSLATORS: Error when Program is not allowed to write data */ "Unable to write to location:\n{:s}"), error); - UiErrorOkDialog(_("Read-Only Directory Error").c_str(), text.c_str()); + UiErrorOkDialog(_("Read-Only Directory Error"), text); app_fatal(nullptr); } diff --git a/Source/appfat.h b/Source/appfat.h index dda125ea2..0b51b1f2a 100644 --- a/Source/appfat.h +++ b/Source/appfat.h @@ -31,13 +31,8 @@ namespace devilution { * @param ... (see printf) */ [[noreturn]] void app_fatal(const char *pszFmt, ...) DVL_PRINTF_ATTRIBUTE(1, 2); +[[noreturn]] void app_fatal(string_view str); -/** - * @brief Displays a warning message box based on the given formatted error message. - * @param pszFmt Error message format - * @param ... Additional parameters for message format - */ -void DrawDlg(const char *pszFmt, ...) DVL_PRINTF_ATTRIBUTE(1, 2); #ifdef _DEBUG /** * @brief Show an error and exit the application. diff --git a/Source/automap.cpp b/Source/automap.cpp index c8603367a..19060c4ab 100644 --- a/Source/automap.cpp +++ b/Source/automap.cpp @@ -480,7 +480,7 @@ void DrawAutomapText(const Surface &out) if (gbIsMultiplayer) { if (strcasecmp("0.0.0.0", szPlayerName) != 0) { - std::string description = _("Game: "); + std::string description = std::string(_("Game: ")); description.append(szPlayerName); DrawString(out, description, linePosition); linePosition.y += 15; @@ -488,10 +488,10 @@ void DrawAutomapText(const Surface &out) std::string description; if (!PublicGame) { - description = _("Password: "); + description = std::string(_("Password: ")); description.append(szPlayerDescript); } else { - description = _("Public Game"); + description = std::string(_("Public Game")); } DrawString(out, description, linePosition); linePosition.y += 15; diff --git a/Source/control.cpp b/Source/control.cpp index 2a9a2d5a3..8025eb50b 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -45,6 +45,7 @@ #include "utils/language.h" #include "utils/sdl_geometry.h" #include "utils/stdcompat/optional.hpp" +#include "utils/string_or_view.hpp" #include "utils/utf8.hpp" #ifdef _DEBUG @@ -74,7 +75,7 @@ bool talkflag; bool sbookflag; bool chrflag; bool drawbtnflag; -std::string InfoString; +StringOrView InfoString; bool panelflag; int initialDropGoldValue; bool panbtndown; @@ -579,7 +580,7 @@ void InitControlPan() buttonEnabled = false; chrbtnactive = false; pDurIcons = LoadCel("Items\\DurIcons.CEL", 32); - InfoString.clear(); + InfoString = {}; ClearPanel(); drawhpflag = true; drawmanaflag = true; @@ -867,7 +868,7 @@ void DrawInfoBox(const Surface &out) { DrawPanelBox(out, { 177, 62, 288, 63 }, GetMainPanel().position + Displacement { 177, 46 }); if (!panelflag && !trigflag && pcursinvitem == -1 && pcursstashitem == uint16_t(-1) && !spselflag) { - InfoString.clear(); + InfoString = {}; InfoColor = UiFlags::ColorWhite; ClearPanel(); } @@ -882,9 +883,9 @@ void DrawInfoBox(const Surface &out) InfoString = _("Requirements not met"); } else { if (myPlayer.HoldItem._iIdentified) - InfoString = myPlayer.HoldItem._iIName; + InfoString = string_view(myPlayer.HoldItem._iIName); else - InfoString = myPlayer.HoldItem._iName; + InfoString = string_view(myPlayer.HoldItem._iName); InfoColor = myPlayer.HoldItem.getTextColor(); } } else { @@ -896,7 +897,7 @@ void DrawInfoBox(const Surface &out) if (leveltype != DTYPE_TOWN) { const auto &monster = Monsters[pcursmonst]; InfoColor = UiFlags::ColorWhite; - InfoString = monster.mName; + InfoString = string_view(monster.mName); ClearPanel(); if (monster._uniqtype != 0) { InfoColor = UiFlags::ColorWhitegold; @@ -905,13 +906,13 @@ void DrawInfoBox(const Surface &out) PrintMonstHistory(monster.MType->mtype); } } else if (pcursitem == -1) { - InfoString = std::string(Towners[pcursmonst].name); + InfoString = string_view(Towners[pcursmonst].name); } } if (pcursplr != -1) { InfoColor = UiFlags::ColorWhitegold; auto &target = Players[pcursplr]; - InfoString = target._pName; + InfoString = string_view(target._pName); ClearPanel(); 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)); diff --git a/Source/control.h b/Source/control.h index cab19ab49..15acdeb58 100644 --- a/Source/control.h +++ b/Source/control.h @@ -19,6 +19,7 @@ #include "utils/attributes.h" #include "utils/stdcompat/optional.hpp" #include "utils/stdcompat/string_view.hpp" +#include "utils/string_or_view.hpp" #include "utils/ui_fwd.h" namespace devilution { @@ -41,7 +42,7 @@ extern bool talkflag; extern bool sbookflag; extern bool chrflag; extern bool drawbtnflag; -extern std::string InfoString; +extern StringOrView InfoString; extern bool panelflag; extern int initialDropGoldValue; extern bool panbtndown; diff --git a/Source/controls/modifier_hints.cpp b/Source/controls/modifier_hints.cpp index 4ec4142cd..0e6eda2f6 100644 --- a/Source/controls/modifier_hints.cpp +++ b/Source/controls/modifier_hints.cpp @@ -176,10 +176,9 @@ void InitModifierHints() LoadMaskedArt("data\\hinticons.pcx", &hintIcons, 6, 1); if (hintBox.surface == nullptr || hintBoxBackground.surface == nullptr) { - app_fatal("%s", _("Failed to load UI resources.\n" - "\n" - "Make sure devilutionx.mpq is in the game folder and that it is up to date.") - .c_str()); + app_fatal(_("Failed to load UI resources.\n" + "\n" + "Make sure devilutionx.mpq is in the game folder and that it is up to date.")); } } diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index 147210b0e..1b65adcd4 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -1645,7 +1645,7 @@ void plrctrls_after_check_curs_move() return; } if (!invflag) { - InfoString.clear(); + InfoString = {}; ClearPanel(); FindActor(); FindItemOrObject(); diff --git a/Source/discord/discord.cpp b/Source/discord/discord.cpp index 3eaddc4f2..027623fa4 100644 --- a/Source/discord/discord.cpp +++ b/Source/discord/discord.cpp @@ -59,7 +59,7 @@ std::string GetLocationString() { // Quest Level Name if (setlevel) { - return _(QuestLevelNames[setlvlnum]); + return std::string(_(QuestLevelNames[setlvlnum])); } // Dungeon Name @@ -86,7 +86,7 @@ std::string GetLocationString() std::string GetCharacterString() { - const std::string &charClassStr = _(ClassStrTbl[static_cast(MyPlayer->_pClass)]); + const string_view charClassStr = _(ClassStrTbl[static_cast(MyPlayer->_pClass)]); return fmt::format(_(/* TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" */ "Lv {} {}"), tracked_data.playerLevel, charClassStr); } @@ -98,7 +98,7 @@ std::string GetDetailString() std::string GetStateString() { constexpr std::array DifficultyStrs = { N_("Normal"), N_("Nightmare"), N_("Hell") }; - std::string difficultyStr = _(DifficultyStrs[sgGameInitInfo.nDifficulty]); + const string_view difficultyStr = _(DifficultyStrs[sgGameInitInfo.nDifficulty]); return fmt::format(_(/* TRANSLATORS: Discord state i.e. "Nightmare difficulty" */ "{} difficulty"), difficultyStr); } @@ -179,7 +179,7 @@ void UpdateMenu(bool forced) discord::Activity activity = {}; activity.SetName(PROJECT_NAME); - activity.SetState(_(/* TRANSLATORS: Discord activity, not in game */ "In Menu").c_str()); + activity.SetState(_(/* TRANSLATORS: Discord activity, not in game */ "In Menu").data()); activity.GetTimestamps().SetStart(start_time); diff --git a/Source/dvlnet/tcp_client.cpp b/Source/dvlnet/tcp_client.cpp index 6675cef58..6f02b8d47 100644 --- a/Source/dvlnet/tcp_client.cpp +++ b/Source/dvlnet/tcp_client.cpp @@ -62,7 +62,7 @@ int tcp_client::join(std::string addrstr) } } if (plr_self == PLR_BROADCAST) { - SDL_SetError("%s", _("Unable to connect").c_str()); + SDL_SetError("%s", _("Unable to connect").data()); return -1; } @@ -88,7 +88,7 @@ void tcp_client::HandleReceive(const asio::error_code &error, size_t bytesRead) return; } if (bytesRead == 0) { - throw std::runtime_error(_("error: read 0 bytes from server")); + throw std::runtime_error(_("error: read 0 bytes from server").data()); } recv_buffer.resize(bytesRead); recv_queue.Write(std::move(recv_buffer)); diff --git a/Source/error.cpp b/Source/error.cpp index 679a6006f..a96f10fe0 100644 --- a/Source/error.cpp +++ b/Source/error.cpp @@ -111,7 +111,7 @@ void InitDiabloMsg(diablo_message e) InitDiabloMsg(LanguageTranslate(MsgStrings[e])); } -void InitDiabloMsg(const std::string &msg) +void InitDiabloMsg(string_view msg) { if (DiabloMessages.size() >= MAX_SEND_STR_LEN) return; @@ -119,7 +119,7 @@ void InitDiabloMsg(const std::string &msg) if (std::find(DiabloMessages.begin(), DiabloMessages.end(), msg) != DiabloMessages.end()) return; - DiabloMessages.push_back(msg); + DiabloMessages.push_back(std::string(msg)); if (DiabloMessages.size() == 1) InitNextLines(); } diff --git a/Source/error.h b/Source/error.h index a0a5259ad..57b66b936 100644 --- a/Source/error.h +++ b/Source/error.h @@ -9,6 +9,7 @@ #include #include "engine.h" +#include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -71,7 +72,7 @@ enum diablo_message : uint8_t { }; void InitDiabloMsg(diablo_message e); -void InitDiabloMsg(const std::string &msg); +void InitDiabloMsg(string_view msg); bool IsDiabloMsgAvailable(); void CancelCurrentDiabloMsg(); void ClrDiabloMsg(); diff --git a/Source/gamemenu.cpp b/Source/gamemenu.cpp index 02b8ea356..bd455a349 100644 --- a/Source/gamemenu.cpp +++ b/Source/gamemenu.cpp @@ -153,19 +153,19 @@ void GamemenuGetSpeed() if (gbIsMultiplayer) { sgOptionsMenu[3].dwFlags &= ~(GMENU_ENABLED | GMENU_SLIDER); if (sgGameInitInfo.nTickRate >= 50) - sgOptionsMenu[3].pszStr = _("Speed: Fastest").c_str(); + sgOptionsMenu[3].pszStr = _("Speed: Fastest").data(); else if (sgGameInitInfo.nTickRate >= 40) - sgOptionsMenu[3].pszStr = _("Speed: Faster").c_str(); + sgOptionsMenu[3].pszStr = _("Speed: Faster").data(); else if (sgGameInitInfo.nTickRate >= 30) - sgOptionsMenu[3].pszStr = _("Speed: Fast").c_str(); + sgOptionsMenu[3].pszStr = _("Speed: Fast").data(); else if (sgGameInitInfo.nTickRate == 20) - sgOptionsMenu[3].pszStr = _("Speed: Normal").c_str(); + sgOptionsMenu[3].pszStr = _("Speed: Normal").data(); return; } sgOptionsMenu[3].dwFlags |= GMENU_ENABLED | GMENU_SLIDER; - sgOptionsMenu[3].pszStr = _("Speed").c_str(); + sgOptionsMenu[3].pszStr = _("Speed").data(); gmenu_slider_steps(&sgOptionsMenu[3], 46); gmenu_slider_set(&sgOptionsMenu[3], 20, 50, sgGameInitInfo.nTickRate); } diff --git a/Source/init.cpp b/Source/init.cpp index 3c3b7afcd..11f8ff9f8 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -212,7 +212,7 @@ void LoadGameArchives() hfvoice_mpq = LoadMPQ(paths, "hfvoice.mpq"); if (gbIsHellfire && (!hfmonk_mpq || !hfmusic_mpq || !hfvoice_mpq)) { - UiErrorOkDialog(_("Some Hellfire MPQs are missing").c_str(), _("Not all Hellfire MPQs were found.\nPlease copy all the hf*.mpq files.").c_str()); + UiErrorOkDialog(_("Some Hellfire MPQs are missing"), _("Not all Hellfire MPQs were found.\nPlease copy all the hf*.mpq files.")); app_fatal(nullptr); } } @@ -220,7 +220,7 @@ void LoadGameArchives() void init_create_window() { if (!SpawnWindow(PROJECT_NAME)) - app_fatal("%s", _("Unable to create main window").c_str()); + app_fatal(_("Unable to create main window")); dx_init(); gbActive = true; #ifndef USE_SDL1 diff --git a/Source/inv.cpp b/Source/inv.cpp index d628f0032..bb72debbd 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -1932,10 +1932,10 @@ int8_t CheckInvHLight() } else { InfoColor = pi->getTextColor(); if (pi->_iIdentified) { - InfoString = pi->_iIName; + InfoString = string_view(pi->_iIName); PrintItemDetails(*pi); } else { - InfoString = pi->_iName; + InfoString = string_view(pi->_iName); PrintItemDur(*pi); } } diff --git a/Source/items.cpp b/Source/items.cpp index 67c0ad7c9..13761a7f4 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -631,9 +631,11 @@ void GetBookSpell(Item &item, int lvl) if (s == maxSpells) s = 1; } - std::string spellName = pgettext("spell", spelldata[bs].sNameText); - CopyUtf8(item._iName, std::string(item._iName + spellName), sizeof(item._iIName)); - CopyUtf8(item._iIName, std::string(item._iIName + spellName), sizeof(item._iIName)); + const string_view spellName = pgettext("spell", spelldata[bs].sNameText); + const size_t iNameLen = string_view(item._iName).size(); + const size_t iINameLen = string_view(item._iIName).size(); + CopyUtf8(item._iName + iNameLen, spellName, sizeof(item._iName) - iNameLen); + CopyUtf8(item._iIName + iINameLen, spellName, sizeof(item._iIName) - iINameLen); item._iSpell = bs; item._iMinMag = spelldata[bs].sMinInt; item._ivalue += spelldata[bs].sBookCost; @@ -1847,7 +1849,7 @@ void PrintItemInfo(const Item &item) uint8_t dex = item._iMinDex; uint8_t mag = item._iMinMag; if (str != 0 || mag != 0 || dex != 0) { - std::string text = _("Required:"); + std::string text = std::string(_("Required:")); if (str != 0) text.append(fmt::format(_(" {:d} Str"), str)); if (mag != 0) @@ -3443,9 +3445,9 @@ void GetItemStr(Item &item) { if (item._itype != ItemType::Gold) { if (item._iIdentified) - InfoString = item._iIName; + InfoString = string_view(item._iIName); else - InfoString = item._iName; + InfoString = string_view(item._iName); InfoColor = item.getTextColor(); } else { @@ -3511,7 +3513,7 @@ bool DoOil(Player &player, int cii) return true; } -[[nodiscard]] std::string PrintItemPower(char plidx, const Item &item) +[[nodiscard]] StringOrView PrintItemPower(char plidx, const Item &item) { switch (plidx) { case IPL_TOHIT: @@ -3646,13 +3648,13 @@ bool DoOil(Player &player, int cii) return _(/*xgettext:no-c-format*/ "hit steals 3% mana"); if (HasAnyOf(item._iFlags, ItemSpecialEffect::StealMana5)) return _(/*xgettext:no-c-format*/ "hit steals 5% mana"); - return ""; + return {}; case IPL_STEALLIFE: if (HasAnyOf(item._iFlags, ItemSpecialEffect::StealLife3)) return _(/*xgettext:no-c-format*/ "hit steals 3% life"); if (HasAnyOf(item._iFlags, ItemSpecialEffect::StealLife5)) return _(/*xgettext:no-c-format*/ "hit steals 5% life"); - return ""; + return {}; case IPL_TARGAC: return _("penetrates target's armor"); case IPL_FASTATTACK: @@ -3696,7 +3698,7 @@ bool DoOil(Player &player, int cii) case IPL_INFRAVISION: return _("see with infravision"); case IPL_INVCURS: - return " "; + return { string_view(" ") }; case IPL_ADDACLIFE: if (item._iFMinDam == item._iFMaxDam) return fmt::format(_("lightning damage: {:d}"), item._iFMinDam); @@ -3708,7 +3710,7 @@ bool DoOil(Player &player, int cii) if (item._iPLFR > 0) return fmt::format(_("Resist Fire: {:+d}%"), item._iPLFR); else - return " "; + return { string_view(" ") }; case IPL_DEVASTATION: return _("occasional triple damage"); case IPL_DECAY: @@ -3716,7 +3718,7 @@ bool DoOil(Player &player, int cii) case IPL_PERIL: return _("2x dmg to monst, 1x to you"); case IPL_JESTERS: - return _(/*xgettext:no-c-format*/ "Random 0 - 500% damage"); + return std::string(_(/*xgettext:no-c-format*/ "Random 0 - 500% damage")); case IPL_CRYSTALLINE: return fmt::format(_(/*xgettext:no-c-format*/ "low dur, {:+d}% damage"), item._iPLDam); case IPL_DOPPELGANGER: diff --git a/Source/items.h b/Source/items.h index 0b3e06dd6..980715443 100644 --- a/Source/items.h +++ b/Source/items.h @@ -14,6 +14,7 @@ #include "itemdat.h" #include "monster.h" #include "utils/stdcompat/optional.hpp" +#include "utils/string_or_view.hpp" namespace devilution { @@ -495,7 +496,7 @@ void CheckIdentify(Player &player, int cii); void DoRepair(Player &player, int cii); void DoRecharge(Player &player, int cii); bool DoOil(Player &player, int cii); -[[nodiscard]] std::string PrintItemPower(char plidx, const Item &item); +[[nodiscard]] StringOrView PrintItemPower(char plidx, const Item &item); void DrawUniqueInfo(const Surface &out); void PrintItemDetails(const Item &item); void PrintItemDur(const Item &item); diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 194b44293..508a37a87 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -1958,10 +1958,10 @@ void LoadGame(bool firstflag) LoadHelper file(OpenSaveArchive(gSaveNumber), "game"); if (!file.IsValid()) - app_fatal("%s", _("Unable to open save file archive").c_str()); + app_fatal(_("Unable to open save file archive")); if (!IsHeaderValid(file.NextLE())) - app_fatal("%s", _("Invalid save file").c_str()); + app_fatal(_("Invalid save file")); if (gbIsHellfireSaveGame) { giNumberOfLevels = 25; @@ -1992,7 +1992,7 @@ void LoadGame(bool firstflag) int tmpNobjects = file.NextBE(); if (!gbIsHellfire && IsAnyOf(leveltype, DTYPE_NEST, DTYPE_CRYPT)) - app_fatal("%s", _("Player is on a Hellfire only level").c_str()); + app_fatal(_("Player is on a Hellfire only level")); for (uint8_t i = 0; i < giNumberOfLevels; i++) { glSeedTbl[i] = file.NextBE(); @@ -2238,7 +2238,7 @@ void SaveGameData() else if (!gbIsSpawn && !gbIsHellfire) file.WriteLE(LoadLE32("RETL")); else - app_fatal("%s", _("Invalid game state").c_str()); + app_fatal(_("Invalid game state")); if (gbIsHellfire) { giNumberOfLevels = 25; @@ -2478,7 +2478,7 @@ void LoadLevel() GetPermLevelNames(szName); LoadHelper file(OpenSaveArchive(gSaveNumber), szName); if (!file.IsValid()) - app_fatal("%s", _("Unable to open save file archive").c_str()); + app_fatal(_("Unable to open save file archive")); if (leveltype != DTYPE_TOWN) { for (int j = 0; j < MAXDUNY; j++) { diff --git a/Source/menu.cpp b/Source/menu.cpp index 3cfba0769..bc2a750b0 100644 --- a/Source/menu.cpp +++ b/Source/menu.cpp @@ -151,7 +151,7 @@ void mainmenu_loop() if (demo::IsRunning()) menu = MAINMENU_SINGLE_PLAYER; else if (!UiMainMenuDialog(gszProductName, &menu, effects_play_sound, 30)) - app_fatal("%s", _("Unable to display mainmenu").c_str()); + app_fatal(_("Unable to display mainmenu")); switch (menu) { case MAINMENU_NONE: diff --git a/Source/monster.cpp b/Source/monster.cpp index 470afc10f..812100343 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -195,7 +195,7 @@ void InitMonster(Monster &monster, Direction rd, int mtype, Point position) monster._mmode = MonsterMode::Stand; monster.MType = &LevelMonsterTypes[mtype]; monster.MData = monster.MType->MData; - monster.mName = pgettext("monster", monster.MData->mName).c_str(); + monster.mName = pgettext("monster", monster.MData->mName).data(); monster.AnimInfo = {}; monster.ChangeAnimationData(MonsterGraphic::Stand); monster.AnimInfo.TickCounterOfCurrentFrame = GenerateRnd(monster.AnimInfo.TicksPerFrame - 1); @@ -3490,7 +3490,7 @@ void PrepareUniqueMonst(Monster &monster, int uniqindex, int miniontype, int bos } monster.mExp *= 2; - monster.mName = pgettext("monster", uniqueMonsterData.mName).c_str(); + monster.mName = pgettext("monster", uniqueMonsterData.mName).data(); monster._mmaxhp = uniqueMonsterData.mmaxhp << 6; if (!gbIsMultiplayer) @@ -4523,9 +4523,9 @@ void SyncMonsterAnim(Monster &monster) #endif monster.MData = LevelMonsterTypes[monster._mMTidx].MData; if (monster._uniqtype != 0) - monster.mName = pgettext("monster", UniqueMonstersData[monster._uniqtype - 1].mName).c_str(); + monster.mName = pgettext("monster", UniqueMonstersData[monster._uniqtype - 1].mName).data(); else - monster.mName = pgettext("monster", monster.MData->mName).c_str(); + monster.mName = pgettext("monster", monster.MData->mName).data(); if (monster._uniqtype != 0) InitTRNForUniqueMonster(monster); @@ -4657,23 +4657,23 @@ void PrintMonstHistory(int mt) AddPanelString(_("No magic resistance")); } else { if ((res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING)) != 0) { - std::string resists = _("Resists:"); + std::string resists = std::string(_("Resists:")); if ((res & RESIST_MAGIC) != 0) - resists.append(_(" Magic")); + AppendStrView(resists, _(" Magic")); if ((res & RESIST_FIRE) != 0) - resists.append(_(" Fire")); + AppendStrView(resists, _(" Fire")); if ((res & RESIST_LIGHTNING) != 0) - resists.append(_(" Lightning")); + AppendStrView(resists, _(" Lightning")); AddPanelString(resists); } if ((res & (IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING)) != 0) { - std::string immune = _("Immune:"); + std::string immune = std::string(_("Immune:")); if ((res & IMMUNE_MAGIC) != 0) - immune.append(_(" Magic")); + AppendStrView(immune, _(" Magic")); if ((res & IMMUNE_FIRE) != 0) - immune.append(_(" Fire")); + AppendStrView(immune, _(" Fire")); if ((res & IMMUNE_LIGHTNING) != 0) - immune.append(_(" Lightning")); + AppendStrView(immune, _(" Lightning")); AddPanelString(immune); } } diff --git a/Source/msg.cpp b/Source/msg.cpp index 8c297d26c..8e4faf3bb 100644 --- a/Source/msg.cpp +++ b/Source/msg.cpp @@ -11,6 +11,7 @@ #include "DiabloUI/diabloui.h" #include "automap.h" +#include "config.h" #include "control.h" #include "dead.h" #include "drlg_l1.h" @@ -534,7 +535,7 @@ void DeltaPutItem(const TCmdPItem &message, Point position, uint8_t bLevel) && item.dwSeed == message.dwSeed) { if (item.bCmd == TCmdPItem::DroppedItem) return; - app_fatal("%s", _("Trying to drop a floor item?").c_str()); + app_fatal(_("Trying to drop a floor item?")); } } @@ -1036,7 +1037,7 @@ DWORD OnSpellWall(const TCmd *pCmd, Player &player) auto spell = static_cast(message.wParam1); if (leveltype == DTYPE_TOWN && !spelldata[spell].sTownSpell) { - LogError(_("{:s} has cast an illegal spell.").c_str(), player._pName); + LogError(_("{:s} has cast an illegal spell."), player._pName); return sizeof(message); } @@ -1071,7 +1072,7 @@ DWORD OnSpellTile(const TCmd *pCmd, Player &player) auto spell = static_cast(message.wParam1); if (leveltype == DTYPE_TOWN && !spelldata[spell].sTownSpell) { - LogError(_("{:s} has cast an illegal spell.").c_str(), player._pName); + LogError(_("{:s} has cast an illegal spell."), player._pName); return sizeof(message); } @@ -1102,7 +1103,7 @@ DWORD OnTargetSpellTile(const TCmd *pCmd, Player &player) auto spell = static_cast(message.wParam1); if (leveltype == DTYPE_TOWN && !spelldata[spell].sTownSpell) { - LogError(_("{:s} has cast an illegal spell.").c_str(), player._pName); + LogError(_("{:s} has cast an illegal spell."), player._pName); return sizeof(message); } @@ -1229,7 +1230,7 @@ DWORD OnSpellMonster(const TCmd *pCmd, Player &player) auto spell = static_cast(message.wParam2); if (leveltype == DTYPE_TOWN && !spelldata[spell].sTownSpell) { - LogError(_("{:s} has cast an illegal spell.").c_str(), player._pName); + LogError(_("{:s} has cast an illegal spell."), player._pName); return sizeof(message); } @@ -1261,7 +1262,7 @@ DWORD OnSpellPlayer(const TCmd *pCmd, Player &player) auto spell = static_cast(message.wParam2); if (leveltype == DTYPE_TOWN && !spelldata[spell].sTownSpell) { - LogError(_("{:s} has cast an illegal spell.").c_str(), player._pName); + LogError(_("{:s} has cast an illegal spell."), player._pName); return sizeof(message); } @@ -1293,7 +1294,7 @@ DWORD OnTargetSpellMonster(const TCmd *pCmd, Player &player) auto spell = static_cast(message.wParam2); if (leveltype == DTYPE_TOWN && !spelldata[spell].sTownSpell) { - LogError(_("{:s} has cast an illegal spell.").c_str(), player._pName); + LogError(_("{:s} has cast an illegal spell."), player._pName); return sizeof(message); } @@ -1323,7 +1324,7 @@ DWORD OnTargetSpellPlayer(const TCmd *pCmd, Player &player) auto spell = static_cast(message.wParam2); if (leveltype == DTYPE_TOWN && !spelldata[spell].sTownSpell) { - LogError(_("{:s} has cast an illegal spell.").c_str(), player._pName); + LogError(_("{:s} has cast an illegal spell."), player._pName); return sizeof(message); } @@ -2053,13 +2054,13 @@ bool msg_wait_resync() } if (gbGameDestroyed) { - DrawDlg("%s", _("The game ended").c_str()); + UiErrorOkDialog(PROJECT_NAME, _("The game ended"), /*error=*/false); FreePackets(); return false; } if (sgbDeltaChunks != MAX_CHUNKS) { - DrawDlg("%s", _("Unable to get level data").c_str()); + UiErrorOkDialog(PROJECT_NAME, _("Unable to get level data"), /*error=*/false); FreePackets(); return false; } diff --git a/Source/panels/charpanel.cpp b/Source/panels/charpanel.cpp index 5213d755f..9f94f889b 100644 --- a/Source/panels/charpanel.cpp +++ b/Source/panels/charpanel.cpp @@ -125,7 +125,7 @@ PanelEntry panelEntries[] = { { "", { 9, 14 }, 150, 0, []() { return StyledText { UiFlags::ColorWhite, MyPlayer->_pName }; } }, { "", { 161, 14 }, 149, 0, - []() { return StyledText { UiFlags::ColorWhite, _(ClassStrTbl[static_cast(MyPlayer->_pClass)]) }; } }, + []() { return StyledText { UiFlags::ColorWhite, std::string(_(ClassStrTbl[static_cast(MyPlayer->_pClass)])) }; } }, { N_("Level"), { 57, 52 }, 57, 45, []() { return StyledText { UiFlags::ColorWhite, fmt::format("{:d}", MyPlayer->_pLevel) }; } }, @@ -134,7 +134,7 @@ PanelEntry panelEntries[] = { { N_("Next level"), { TopRightLabelX, 80 }, 99, 198, []() { if (MyPlayer->_pLevel == MAXCHARLEVEL) { - return StyledText { UiFlags::ColorWhitegold, _("None") }; + return StyledText { UiFlags::ColorWhitegold, std::string(_("None")) }; } return StyledText { UiFlags::ColorWhite, fmt::format("{:d}", MyPlayer->_pNextExper) }; } }, @@ -219,7 +219,7 @@ void DrawShadowString(const Surface &out, const PanelEntry &entry) return; constexpr int Spacing = 0; - const std::string &textStr = LanguageTranslate(entry.label.c_str()); + const string_view textStr = LanguageTranslate(entry.label); string_view text; std::string wrapped; if (entry.labelLength > 0) { diff --git a/Source/panels/spell_list.cpp b/Source/panels/spell_list.cpp index 8494bb1a7..ddbddc6bc 100644 --- a/Source/panels/spell_list.cpp +++ b/Source/panels/spell_list.cpp @@ -20,7 +20,7 @@ namespace devilution { namespace { -void PrintSBookSpellType(const Surface &out, Point position, const std::string &text, uint8_t rectColorIndex) +void PrintSBookSpellType(const Surface &out, Point position, string_view text, uint8_t rectColorIndex) { Point rect { position }; rect += Displacement { 0, -SPLICONLENGTH + 1 }; @@ -124,7 +124,7 @@ void DrawSpell(const Surface &out) void DrawSpellList(const Surface &out) { - InfoString.clear(); + InfoString = {}; ClearPanel(); Player &myPlayer = *MyPlayer; diff --git a/Source/pfile.cpp b/Source/pfile.cpp index 0a77c3b2a..446cf0836 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -303,7 +303,7 @@ PFileScopedArchiveWriter::PFileScopedArchiveWriter(bool clearTables) , clear_tables_(clearTables) { if (!OpenArchive(save_num_)) - app_fatal("%s", _("Failed to open player archive for writing.").c_str()); + app_fatal(_("Failed to open player archive for writing.")); } PFileScopedArchiveWriter::~PFileScopedArchiveWriter() @@ -370,7 +370,7 @@ void sfile_write_stash() return; if (!StashWriter.Open(GetStashSavePath().c_str())) - app_fatal("%s", _("Failed to open stash archive for writing.").c_str()); + app_fatal(_("Failed to open stash archive for writing.")); SaveStash(); @@ -481,9 +481,9 @@ void pfile_read_player_from_save(uint32_t saveNum, Player &player) { std::optional archive = OpenSaveArchive(saveNum); if (!archive) - app_fatal("%s", _("Unable to open archive").c_str()); + app_fatal(_("Unable to open archive")); if (!ReadHero(*archive, &pkplr)) - app_fatal("%s", _("Unable to load character").c_str()); + app_fatal(_("Unable to load character")); gbValidSaveFile = ArchiveContainsGame(*archive); if (gbValidSaveFile) @@ -507,7 +507,7 @@ bool LevelFileExists() uint32_t saveNum = gSaveNumber; if (!OpenArchive(saveNum)) - app_fatal("%s", _("Unable to read to save file archive").c_str()); + app_fatal(_("Unable to read to save file archive")); bool hasFile = SaveWriter.HasFile(szName); SaveWriter.Close(); @@ -527,7 +527,7 @@ void GetPermLevelNames(char *szPerm) uint32_t saveNum = gSaveNumber; GetTempLevelNames(szPerm); if (!OpenArchive(saveNum)) - app_fatal("%s", _("Unable to read to save file archive").c_str()); + app_fatal(_("Unable to read to save file archive")); bool hasFile = SaveWriter.HasFile(szPerm); SaveWriter.Close(); @@ -546,7 +546,7 @@ void pfile_remove_temp_files() uint32_t saveNum = gSaveNumber; if (!OpenArchive(saveNum)) - app_fatal("%s", _("Unable to write to save file archive").c_str()); + app_fatal(_("Unable to write to save file archive")); SaveWriter.RemoveHashEntries(GetTempSaveNames); SaveWriter.Close(); } diff --git a/Source/plrmsg.cpp b/Source/plrmsg.cpp index 916f29e64..3faaf3cb2 100644 --- a/Source/plrmsg.cpp +++ b/Source/plrmsg.cpp @@ -73,7 +73,7 @@ void EventPlrMsg(string_view text, UiFlags style) message.text = std::string(text); message.from = string_view(message.text.data(), 0); message.lineHeight = GetLineHeight(message.text, GameFont12) + 3; - AddMessageToChatLog(std::string(text)); + AddMessageToChatLog(text); } void SendPlrMsg(Player &player, string_view text) @@ -87,7 +87,7 @@ void SendPlrMsg(Player &player, string_view text) message.text = from + std::string(text); message.from = string_view(message.text.data(), from.size()); message.lineHeight = GetLineHeight(message.text, GameFont12) + 3; - AddMessageToChatLog(std::string(text), &player); + AddMessageToChatLog(text, &player); } void InitPlrMsg() diff --git a/Source/qol/chatlog.cpp b/Source/qol/chatlog.cpp index bcb712234..8d16ecc1f 100644 --- a/Source/qol/chatlog.cpp +++ b/Source/qol/chatlog.cpp @@ -113,7 +113,7 @@ void ToggleChatLog() } } -void AddMessageToChatLog(const std::string &message, Player *player, UiFlags flags) +void AddMessageToChatLog(string_view message, Player *player, UiFlags flags) { MessageCounter++; time_t tm = time(nullptr); @@ -121,10 +121,10 @@ void AddMessageToChatLog(const std::string &message, Player *player, UiFlags fla int oldSize = ChatLogLines.size(); ChatLogLines.emplace_back(MultiColoredText { "", { {} } }); if (player == nullptr) { - ChatLogLines.emplace_back(MultiColoredText { "{0} {1}", { { timestamp, UiFlags::ColorRed }, { message, flags } } }); + ChatLogLines.emplace_back(MultiColoredText { "{0} {1}", { { timestamp, UiFlags::ColorRed }, { std::string(message), flags } } }); } else { std::string playerInfo = fmt::format(_("{:s} (lvl {:d}): "), player->_pName, player->_pLevel); - ChatLogLines.emplace_back(MultiColoredText { message, { {} }, 20 }); + ChatLogLines.emplace_back(MultiColoredText { std::string(message), { {} }, 20 }); UiFlags nameColor = player == MyPlayer ? UiFlags::ColorWhitegold : UiFlags::ColorBlue; ChatLogLines.emplace_back(MultiColoredText { "{0} - {1}", { { timestamp, UiFlags::ColorRed }, { playerInfo, nameColor } } }); } diff --git a/Source/qol/chatlog.h b/Source/qol/chatlog.h index 1fe92e4a2..dd48db35d 100644 --- a/Source/qol/chatlog.h +++ b/Source/qol/chatlog.h @@ -13,7 +13,7 @@ namespace devilution { extern bool ChatLogFlag; void ToggleChatLog(); -void AddMessageToChatLog(const std::string &message, Player *player = nullptr, UiFlags flags = UiFlags::ColorWhite); +void AddMessageToChatLog(string_view message, Player *player = nullptr, UiFlags flags = UiFlags::ColorWhite); void DrawChatLog(const Surface &out); void ChatLogScrollUp(); void ChatLogScrollDown(); diff --git a/Source/qol/monhealthbar.cpp b/Source/qol/monhealthbar.cpp index 9e72c6ae1..7027f35e3 100644 --- a/Source/qol/monhealthbar.cpp +++ b/Source/qol/monhealthbar.cpp @@ -41,10 +41,9 @@ void InitMonsterHealthBar() if ((healthBox.surface == nullptr) || (health.surface == nullptr) || (resistance.surface == nullptr)) { - app_fatal("%s", _("Failed to load UI resources.\n" - "\n" - "Make sure devilutionx.mpq is in the game folder and that it is up to date.") - .c_str()); + app_fatal(_("Failed to load UI resources.\n" + "\n" + "Make sure devilutionx.mpq is in the game folder and that it is up to date.")); } } diff --git a/Source/qol/stash.cpp b/Source/qol/stash.cpp index 8f0c91617..663715596 100644 --- a/Source/qol/stash.cpp +++ b/Source/qol/stash.cpp @@ -425,10 +425,10 @@ uint16_t CheckStashHLight(Point mousePosition) InfoColor = item.getTextColor(); if (item._iIdentified) { - InfoString = item._iIName; + InfoString = string_view(item._iIName); PrintItemDetails(item); } else { - InfoString = item._iName; + InfoString = string_view(item._iName); PrintItemDur(item); } diff --git a/Source/qol/xpbar.cpp b/Source/qol/xpbar.cpp index 5de697059..6da77c837 100644 --- a/Source/qol/xpbar.cpp +++ b/Source/qol/xpbar.cpp @@ -58,7 +58,7 @@ std::string PrintWithSeparator(int n) mlength = 3; out.append(number.substr(0, mlength)); for (int i = mlength; i < length; i += 3) { - out.append(_(/* TRANSLATORS: Thousands separator */ ",")); + AppendStrView(out, _(/* TRANSLATORS: Thousands separator */ ",")); out.append(number.substr(i, 3)); } @@ -73,10 +73,9 @@ void InitXPBar() LoadMaskedArt("data\\xpbar.pcx", &xpbarArt, 1, 1); if (xpbarArt.surface == nullptr) { - app_fatal("%s", _("Failed to load UI resources.\n" - "\n" - "Make sure devilutionx.mpq is in the game folder and that it is up to date.") - .c_str()); + app_fatal(_("Failed to load UI resources.\n" + "\n" + "Make sure devilutionx.mpq is in the game folder and that it is up to date.")); } } } diff --git a/Source/stores.cpp b/Source/stores.cpp index 1536afb47..3baf495d5 100644 --- a/Source/stores.cpp +++ b/Source/stores.cpp @@ -241,29 +241,29 @@ void AddItemListBackButton(bool selectable = false) void PrintStoreItem(const Item &item, int l, UiFlags flags) { - std::string productLine = ""; + std::string productLine; if (item._iIdentified) { if (item._iMagical != ITEM_QUALITY_UNIQUE) { if (item._iPrePower != -1) { - productLine.append(PrintItemPower(item._iPrePower, item)); + AppendStrView(productLine, PrintItemPower(item._iPrePower, item)); } } if (item._iSufPower != -1) { if (!productLine.empty()) - productLine.append(_(", ")); - productLine.append(PrintItemPower(item._iSufPower, item)); + AppendStrView(productLine, _(", ")); + AppendStrView(productLine, PrintItemPower(item._iSufPower, item)); } } if (item._iMiscId == IMISC_STAFF && item._iMaxCharges != 0) { if (!productLine.empty()) - productLine.append(_(", ")); + AppendStrView(productLine, _(", ")); productLine.append(fmt::format(_("Charges: {:d}/{:d}"), item._iCharges, item._iMaxCharges)); } if (!productLine.empty()) { AddSText(40, l, productLine, flags, false); l++; - productLine = ""; + productLine.clear(); } if (item._itype != ItemType::Misc) { @@ -274,7 +274,7 @@ void PrintStoreItem(const Item &item, int l, UiFlags flags) if (item._iMaxDur != DUR_INDESTRUCTIBLE && item._iMaxDur != 0) productLine += fmt::format(_("Dur: {:d}/{:d}, "), item._iDurability, item._iMaxDur); else - productLine += _("Indestructible, "); + AppendStrView(productLine, _("Indestructible, ")); } int8_t str = item._iMinStr; @@ -282,9 +282,9 @@ void PrintStoreItem(const Item &item, int l, UiFlags flags) int8_t dex = item._iMinDex; if (str == 0 && mag == 0 && dex == 0) { - productLine.append(_("No required attributes")); + AppendStrView(productLine, _("No required attributes")); } else { - productLine.append(_("Required:")); + AppendStrView(productLine, _("Required:")); if (str != 0) productLine.append(fmt::format(_(" {:d} Str"), str)); if (mag != 0) diff --git a/Source/utils/language.cpp b/Source/utils/language.cpp index 09956ea4c..ac2a05668 100644 --- a/Source/utils/language.cpp +++ b/Source/utils/language.cpp @@ -1,10 +1,13 @@ #include "utils/language.h" #include -#include #include #include +#if __cplusplus >= 202002L +#include +#endif + #include "engine/assets.hpp" #include "options.h" #include "utils/file_util.h" @@ -12,19 +15,46 @@ #include "utils/paths.h" #include "utils/stdcompat/string_view.hpp" -using namespace devilution; #define MO_MAGIC 0x950412de +#if defined(__cpp_lib_generic_unordered_lookup) +#include + namespace { -struct CStringCmp { - bool operator()(const char *s1, const char *s2) const +using namespace devilution; + +struct StringHash { + using is_transparent = void; + + auto operator()(const char *str) const noexcept { - return strcmp(s1, s2) < 0; + return std::hash {}(str); + } + + auto operator()(string_view str) const noexcept + { + return std::hash {}(str); + } + + auto operator()(const std::string &str) const noexcept + { + return std::hash {}(str); } }; +std::vector>> translation = { {}, {} }; +} // namespace +#else +#include +namespace { +using namespace devilution; + std::vector>> translation = { {}, {} }; +} // namespace +#endif + +namespace { struct MoHead { uint32_t magic; @@ -254,42 +284,42 @@ bool ReadEntry(SDL_RWops *rw, MoEntry *e, std::vector &result) } // namespace -const std::string &LanguageParticularTranslate(const char *context, const char *message) +string_view LanguageParticularTranslate(string_view context, string_view message) { - constexpr const char *glue = "\004"; + constexpr const char Glue = '\004'; - std::string key = context; - key += glue; - key += message; + std::string key = std::string(context); + key.reserve(key.size() + 1 + message.size()); + key += Glue; + AppendStrView(key, message); auto it = translation[0].find(key); if (it == translation[0].end()) { - it = translation[0].insert({ key, message }).first; + return message; } return it->second; } -const std::string &LanguagePluralTranslate(const char *singular, const char *plural, int count) +string_view LanguagePluralTranslate(string_view singular, string_view plural, int count) { int n = GetLocalPluralId(count); auto it = translation[n].find(singular); if (it == translation[n].end()) { if (count != 1) - it = translation[1].insert({ singular, plural }).first; - else - it = translation[0].insert({ singular, singular }).first; + return plural; + return singular; } return it->second; } -const std::string &LanguageTranslate(const char *key) +string_view LanguageTranslate(string_view key) { auto it = translation[0].find(key); if (it == translation[0].end()) { - it = translation[0].insert({ key, key }).first; + return key; } return it->second; diff --git a/Source/utils/language.h b/Source/utils/language.h index 676fc323d..1c433009e 100644 --- a/Source/utils/language.h +++ b/Source/utils/language.h @@ -2,6 +2,8 @@ #include +#include "utils/stdcompat/string_view.hpp" + #define _(x) LanguageTranslate(x) #define ngettext(x, y, z) LanguagePluralTranslate(x, y, z) #define pgettext(context, x) LanguageParticularTranslate(context, x) @@ -10,9 +12,27 @@ bool HasTranslation(const std::string &locale); void LanguageInitialize(); -const std::string &LanguageParticularTranslate(const char *context, const char *message); -const std::string &LanguagePluralTranslate(const char *singular, const char *plural, int count); -const std::string &LanguageTranslate(const char *key); + +/** + * @brief Returns the translation for the given key. + * + * @return guaranteed to be null-terminated if `key` is. + */ +devilution::string_view LanguageTranslate(devilution::string_view key); + +/** + * @brief Returns a singular or plural translation for the given keys and count. + * + * @return guaranteed to be null-terminated if `key` is. + */ +devilution::string_view LanguagePluralTranslate(devilution::string_view singular, devilution::string_view plural, int count); + +/** + * @brief Returns the translation for the given key and context identifier. + * + * @return guaranteed to be null-terminated if `key` is. + */ +devilution::string_view LanguageParticularTranslate(devilution::string_view context, devilution::string_view message); // Chinese and Japanese, and Korean small font is 16px instead of a 12px one for readability. bool IsSmallFontTall(); diff --git a/Source/utils/log.hpp b/Source/utils/log.hpp index f904bad4d..e741d51d1 100644 --- a/Source/utils/log.hpp +++ b/Source/utils/log.hpp @@ -4,6 +4,8 @@ #include #include +#include "utils/stdcompat/string_view.hpp" + #ifdef USE_SDL1 #include "sdl2_to_1_2_backports.h" #endif @@ -11,7 +13,7 @@ namespace devilution { // Local definition to fix compilation issue due to header conflict. -[[noreturn]] void app_fatal(const char *pszFmt, ...); +[[noreturn]] void app_fatal(string_view); enum class LogCategory { Application = SDL_LOG_CATEGORY_APPLICATION, @@ -39,7 +41,7 @@ enum class LogPriority { namespace detail { template -std::string format(const char *fmt, Args &&...args) +std::string format(string_view fmt, Args &&...args) { FMT_TRY { @@ -47,108 +49,108 @@ std::string format(const char *fmt, Args &&...args) } FMT_CATCH(const fmt::format_error &e) { - auto error = fmt::format("Format error, fmt: {}, error: {}", fmt ? fmt : "nullptr", e.what()); + std::string error = fmt::format("Format error, fmt: {}, error: {}", fmt, e.what()); SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "%s", error.c_str()); - app_fatal("%s", error.c_str()); + app_fatal(error); } } } // namespace detail template -void Log(const char *fmt, Args &&...args) +void Log(string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_Log("%s", str.c_str()); } template -void LogVerbose(LogCategory category, const char *fmt, Args &&...args) +void LogVerbose(LogCategory category, string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_LogVerbose(static_cast(category), "%s", str.c_str()); } template -void LogVerbose(const char *fmt, Args &&...args) +void LogVerbose(string_view fmt, Args &&...args) { LogVerbose(defaultCategory, fmt, std::forward(args)...); } template -void LogDebug(LogCategory category, const char *fmt, Args &&...args) +void LogDebug(LogCategory category, string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_LogDebug(static_cast(category), "%s", str.c_str()); } template -void LogDebug(const char *fmt, Args &&...args) +void LogDebug(string_view fmt, Args &&...args) { LogDebug(defaultCategory, fmt, std::forward(args)...); } template -void LogInfo(LogCategory category, const char *fmt, Args &&...args) +void LogInfo(LogCategory category, string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_LogInfo(static_cast(category), "%s", str.c_str()); } template -void LogInfo(const char *fmt, Args &&...args) +void LogInfo(string_view fmt, Args &&...args) { LogInfo(defaultCategory, fmt, std::forward(args)...); } template -void LogWarn(LogCategory category, const char *fmt, Args &&...args) +void LogWarn(LogCategory category, string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_LogWarn(static_cast(category), "%s", str.c_str()); } template -void LogWarn(const char *fmt, Args &&...args) +void LogWarn(string_view fmt, Args &&...args) { LogWarn(defaultCategory, fmt, std::forward(args)...); } template -void LogError(LogCategory category, const char *fmt, Args &&...args) +void LogError(LogCategory category, string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_LogError(static_cast(category), "%s", str.c_str()); } template -void LogError(const char *fmt, Args &&...args) +void LogError(string_view fmt, Args &&...args) { LogError(defaultCategory, fmt, std::forward(args)...); } template -void LogCritical(LogCategory category, const char *fmt, Args &&...args) +void LogCritical(LogCategory category, string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_LogCritical(static_cast(category), "%s", str.c_str()); } template -void LogCritical(const char *fmt, Args &&...args) +void LogCritical(string_view fmt, Args &&...args) { LogCritical(defaultCategory, fmt, std::forward(args)...); } template -void LogMessageV(LogCategory category, LogPriority priority, const char *fmt, Args &&...args) +void LogMessageV(LogCategory category, LogPriority priority, string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_LogMessageV(static_cast(category), static_cast(priority), "%s", str.c_str()); } template -void LogMessageV(const char *fmt, Args &&...args) +void LogMessageV(string_view fmt, Args &&...args) { LogMessageV(defaultCategory, fmt, std::forward(args)...); } diff --git a/Source/utils/stdcompat/string_view.hpp b/Source/utils/stdcompat/string_view.hpp index 90fc183a2..b6e901a84 100644 --- a/Source/utils/stdcompat/string_view.hpp +++ b/Source/utils/stdcompat/string_view.hpp @@ -3,16 +3,27 @@ #ifdef __has_include #if defined(__cplusplus) && (__cplusplus >= 201703L || _MSC_VER >= 1930) && __has_include() // should be 201606L, but STL headers disagree +#include #include // IWYU pragma: export namespace devilution { using string_view = std::string_view; + +inline void AppendStrView(std::string &out, string_view str) +{ + out.append(str); } +} // namespace devilution #elif __has_include() #include // IWYU pragma: export namespace devilution { using string_view = std::experimental::string_view; + +inline void AppendStrView(std::string &out, string_view str) +{ + out.append(str.data(), str.size()); } +} // namespace devilution #else #error "Missing support for or " diff --git a/Source/utils/string_or_view.hpp b/Source/utils/string_or_view.hpp new file mode 100644 index 000000000..97bcf8e5b --- /dev/null +++ b/Source/utils/string_or_view.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include +#include + +#include "utils/stdcompat/string_view.hpp" + +namespace devilution { + +class StringOrView { +public: + StringOrView() + : owned_(false) + , view_() + { + } + + StringOrView(std::string &&str) + : owned_(true) + , str_(std::move(str)) + { + } + + StringOrView(string_view str) + : owned_(false) + , view_(str) + { + } + + StringOrView(StringOrView &&other) noexcept + : owned_(other.owned_) + { + if (other.owned_) { + new (&str_) std::string(std::move(other.str_)); + } else { + new (&view_) string_view(other.view_); + } + } + + StringOrView &operator=(StringOrView &&other) noexcept + { + if (owned_) { + if (other.owned_) { + str_ = std::move(other.str_); + } else { + str_.~basic_string(); + owned_ = false; + new (&view_) string_view(other.view_); + } + } else { + if (other.owned_) { + view_.~string_view(); + owned_ = true; + new (&str_) std::string(std::move(other.str_)); + } else { + view_ = other.view_; + } + } + return *this; + } + + ~StringOrView() + { + if (owned_) { + str_.~basic_string(); + } else { + view_.~string_view(); + } + } + + bool empty() const + { + return owned_ ? str_.empty() : view_.empty(); + } + + string_view str() const + { + return owned_ ? str_ : view_; + } + + operator string_view() const + { + return str(); + } + +private: + bool owned_; + union { + std::string str_; + string_view view_; + }; +}; + +} // namespace devilution diff --git a/Source/utils/ui_fwd.h b/Source/utils/ui_fwd.h index f085e2bda..6e6d1243e 100644 --- a/Source/utils/ui_fwd.h +++ b/Source/utils/ui_fwd.h @@ -30,6 +30,6 @@ void ReinitializeIntegerScale(); #endif void ReinitializeRenderer(); void ResizeWindow(); -void UiErrorOkDialog(const char *caption, const char *text, bool error = true); +void UiErrorOkDialog(string_view caption, string_view text, bool error = true); } // namespace devilution