From a08026097ae224099ad586af7213021b166344e4 Mon Sep 17 00:00:00 2001 From: KPhoenix <68359262+kphoenix137@users.noreply.github.com> Date: Sat, 25 Dec 2021 05:11:42 -0500 Subject: [PATCH] Chat Interface Revision (#3840) Co-authored-by: Anders Jenbo --- Source/debug.cpp | 30 +++---- Source/diablo.cpp | 13 ++- Source/engine/render/text_render.cpp | 22 ++--- Source/engine/render/text_render.hpp | 2 + Source/msg.cpp | 8 +- Source/multi.cpp | 6 +- Source/plrmsg.cpp | 126 +++++++++++++-------------- Source/plrmsg.h | 16 ++-- 8 files changed, 105 insertions(+), 118 deletions(-) diff --git a/Source/debug.cpp b/Source/debug.cpp index e66d34967..54e2ac8f8 100644 --- a/Source/debug.cpp +++ b/Source/debug.cpp @@ -22,6 +22,7 @@ #include "lighting.h" #include "monstdat.h" #include "monster.h" +#include "plrmsg.h" #include "quests.h" #include "setmaps.h" #include "spells.h" @@ -94,18 +95,19 @@ void SetSpellLevelCheat(spell_id spl, int spllvl) void PrintDebugMonster(int m) { - char dstr[MAX_SEND_STR_LEN]; - auto &monster = Monsters[m]; - sprintf(dstr, "Monster %i = %s", m, monster.mName); - NetSendCmdString(1 << MyPlayerId, dstr); - sprintf(dstr, "X = %i, Y = %i", monster.position.tile.x, monster.position.tile.y); - NetSendCmdString(1 << MyPlayerId, dstr); - sprintf(dstr, "Enemy = %i, HP = %i", monster._menemy, monster._mhitpoints); - NetSendCmdString(1 << MyPlayerId, dstr); - sprintf(dstr, "Mode = %i, Var1 = %i", static_cast(monster._mmode), monster._mVar1); - NetSendCmdString(1 << MyPlayerId, dstr); + EventPlrMsg(fmt::format( + "Monster {:i} = {:s}\nX = {:i}, Y = {:i}\nEnemy = {:i}, HP = {:i}\nMode = {:i}, Var1 = {:i}", + m, + monster.mName, + monster.position.tile.x, + monster.position.tile.y, + monster._menemy, + monster._mhitpoints, + static_cast(monster._mmode), + monster._mVar1), + UiFlags::ColorWhite); bool bActive = false; @@ -114,8 +116,7 @@ void PrintDebugMonster(int m) bActive = true; } - sprintf(dstr, "Active List = %i, Squelch = %i", bActive ? 1 : 0, monster._msquelch); - NetSendCmdString(1 << MyPlayerId, dstr); + EventPlrMsg(fmt::format("Active List = {:i}, Squelch = {:i}", bActive ? 1 : 0, monster._msquelch), UiFlags::ColorWhite); } void ProcessMessages() @@ -795,14 +796,11 @@ void GetDebugMonster() void NextDebugMonster() { - char dstr[MAX_SEND_STR_LEN]; - DebugMonsterId++; if (DebugMonsterId == MAXMONSTERS) DebugMonsterId = 0; - sprintf(dstr, "Current debug monster = %i", DebugMonsterId); - NetSendCmdString(1 << MyPlayerId, dstr); + EventPlrMsg(fmt::format("Current debug monster = {:i}", DebugMonsterId), UiFlags::ColorWhite); } void SetDebugLevelSeedInfos(uint32_t mid1Seed, uint32_t mid2Seed, uint32_t mid3Seed, uint32_t endSeed) diff --git a/Source/diablo.cpp b/Source/diablo.cpp index cb3d91152..119d4c8d3 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -1225,7 +1225,6 @@ void GameLogic() #endif sound_update(); - ClearPlrMsg(); CheckTriggers(); CheckQuests(); force_redraw |= 1; @@ -1486,17 +1485,17 @@ void InitKeymapActions() N_("Displays game infos."), 'V', [] { - char pszStr[MAX_SEND_STR_LEN]; const char *difficulties[3] = { _("Normal"), _("Nightmare"), _("Hell"), }; - CopyUtf8(pszStr, fmt::format(_(/* TRANSLATORS: {:s} means: Character Name, Game Version, Game Difficulty. */ - "{:s}, version = {:s}, mode = {:s}"), - PROJECT_NAME, PROJECT_VERSION, difficulties[sgGameInitInfo.nDifficulty]), - sizeof(pszStr)); - NetSendCmdString(1 << MyPlayerId, pszStr); + EventPlrMsg(fmt::format( + _(/* TRANSLATORS: {:s} means: Character Name, Game Version, Game Difficulty. */ "{:s} {:s}, Difficulty: {:s}"), + PROJECT_NAME, + PROJECT_VERSION, + difficulties[sgGameInitInfo.nDifficulty]), + UiFlags::ColorWhite); }, [&]() { return !IsPlayerDead(); }); for (int i = 0; i < 8; ++i) { diff --git a/Source/engine/render/text_render.cpp b/Source/engine/render/text_render.cpp index 89ac40386..0b5adaf7c 100644 --- a/Source/engine/render/text_render.cpp +++ b/Source/engine/render/text_render.cpp @@ -283,18 +283,10 @@ bool ContainsSmallFontTallCodepoints(string_view text) return false; } -int GetLineHeight(string_view text, unsigned fontIndex) -{ - if (fontIndex == 0 && IsSmallFontTall() && ContainsSmallFontTallCodepoints(text)) { - return SmallFontTallLineHeight; - } - return LineHeights[fontIndex]; -} - -int GetLineHeight(string_view fmt, DrawStringFormatArg *args, std::size_t argsLen, unsigned fontIndex) +int GetLineHeight(string_view fmt, DrawStringFormatArg *args, std::size_t argsLen, GameFontTables fontIndex) { constexpr std::array LineHeights = { 12, 26, 38, 42, 50, 22 }; - if (fontIndex == 0 && IsSmallFontTall()) { + if (fontIndex == GameFont12 && IsSmallFontTall()) { char32_t prev = U'\0'; char32_t next; FmtArgParser fmtArgParser { fmt, args, argsLen }; @@ -307,7 +299,7 @@ int GetLineHeight(string_view fmt, DrawStringFormatArg *args, std::size_t argsLe const std::optional fmtArgPos = fmtArgParser(rest); if (fmtArgPos) { if (ContainsSmallFontTallCodepoints(args[*fmtArgPos].GetFormatted())) - return true; + return SmallFontTallLineHeight; prev = U'\0'; continue; } @@ -484,6 +476,14 @@ int GetLineWidth(string_view fmt, DrawStringFormatArg *args, std::size_t argsLen return lineWidth != 0 ? (lineWidth - spacing) : 0; } +int GetLineHeight(string_view text, GameFontTables fontIndex) +{ + if (fontIndex == GameFont12 && IsSmallFontTall() && ContainsSmallFontTallCodepoints(text)) { + return SmallFontTallLineHeight; + } + return LineHeights[fontIndex]; +} + int AdjustSpacingToFitHorizontally(int &lineWidth, int maxSpacing, int charactersInLine, int availableWidth) { if (lineWidth <= availableWidth || charactersInLine < 2) diff --git a/Source/engine/render/text_render.hpp b/Source/engine/render/text_render.hpp index 3dc2649a6..920605fe2 100644 --- a/Source/engine/render/text_render.hpp +++ b/Source/engine/render/text_render.hpp @@ -143,6 +143,8 @@ int GetLineWidth(string_view text, GameFontTables size = GameFont12, int spacing */ int GetLineWidth(string_view fmt, DrawStringFormatArg *args, std::size_t argsLen, GameFontTables size, int spacing, int *charactersInLine = nullptr); +int GetLineHeight(string_view text, GameFontTables fontIndex); + [[nodiscard]] std::string WordWrapString(string_view text, size_t width, GameFontTables size = GameFont12, int spacing = 1); /** diff --git a/Source/msg.cpp b/Source/msg.cpp index 5d61c9d32..2839ea4d5 100644 --- a/Source/msg.cpp +++ b/Source/msg.cpp @@ -1669,7 +1669,7 @@ DWORD OnPlayerJoinLevel(const TCmd *pCmd, int pnum) ResetPlayerGFX(player); player.plractive = true; gbActivePlayers++; - EventPlrMsg(fmt::format(_("Player '{:s}' (level {:d}) just joined the game"), player._pName, player._pLevel).c_str()); + EventPlrMsg(fmt::format(_("Player '{:s}' (level {:d}) just joined the game"), player._pName, player._pLevel)); } if (player.plractive && MyPlayerId != pnum) { @@ -1811,13 +1811,13 @@ DWORD OnSetVitality(const TCmd *pCmd, int pnum) return sizeof(message); } -DWORD OnString(const TCmd *pCmd, int pnum) +DWORD OnString(const TCmd *pCmd, Player &player) { auto *p = (TCmdString *)pCmd; int len = strlen(p->str); if (gbBufferMsgs == 0) - SendPlrMsg(pnum, p->str); + SendPlrMsg(player, p->str); return len + 2; // length of string + nul terminator + sizeof(p->bCmd) } @@ -2826,7 +2826,7 @@ uint32_t ParseCmd(int pnum, const TCmd *pCmd) case CMD_SETVIT: return OnSetVitality(pCmd, pnum); case CMD_STRING: - return OnString(pCmd, pnum); + return OnString(pCmd, player); case CMD_SYNCQUEST: return OnSyncQuest(pCmd, pnum); case CMD_CHEAT_EXPERIENCE: diff --git a/Source/multi.cpp b/Source/multi.cpp index 9f1ba336c..0dec917fb 100644 --- a/Source/multi.cpp +++ b/Source/multi.cpp @@ -202,7 +202,7 @@ void PlayerLeftMsg(int pnum, bool left) pszFmt = _("Player '{:s}' dropped due to timeout"); break; } - EventPlrMsg(fmt::format(pszFmt, player._pName).c_str()); + EventPlrMsg(fmt::format(pszFmt, player._pName)); } player.plractive = false; player._pName[0] = '\0'; @@ -398,7 +398,7 @@ void HandleEvents(_SNETEVENT *pEvt) gbDeltaSender = MAX_PLRS; break; case EVENT_TYPE_PLAYER_MESSAGE: - ErrorPlrMsg((char *)pEvt->data); + EventPlrMsg((char *)pEvt->data); break; } } @@ -805,7 +805,7 @@ void recv_plrinfo(int pnum, const TCmdPlrInfoHdr &header, bool recv) } else { szEvent = _("Player '{:s}' (level {:d}) is already in the game"); } - EventPlrMsg(fmt::format(szEvent, player._pName, player._pLevel).c_str()); + EventPlrMsg(fmt::format(szEvent, player._pName, player._pLevel)); SyncInitPlr(pnum); diff --git a/Source/plrmsg.cpp b/Source/plrmsg.cpp index 5ae8863cc..ea9ede8ee 100644 --- a/Source/plrmsg.cpp +++ b/Source/plrmsg.cpp @@ -9,32 +9,41 @@ #include -#include "DiabloUI/ui_flags.hpp" #include "control.h" #include "engine/render/text_render.hpp" #include "inv.h" #include "utils/language.h" -#include "utils/stdcompat/string_view.hpp" #include "utils/utf8.hpp" namespace devilution { namespace { -#define PMSG_COUNT 8 - -uint8_t plr_msg_slot; -_plrmsg plr_msgs[PMSG_COUNT]; - -/** Maps from player_num to text color, as used in chat messages. */ -const UiFlags TextColorFromPlayerId[MAX_PLRS + 1] = { UiFlags::ColorWhite, UiFlags::ColorWhite, UiFlags::ColorWhite, UiFlags::ColorWhite, UiFlags::ColorWhitegold }; +struct PlayerMessage { + /** Time message was recived */ + Uint32 time; + /** The default text color */ + UiFlags style; + /** The text message to display on screen */ + std::string text; + /** First portion of text that should be rendered in gold */ + string_view from; + /** The line height of the text */ + int lineHeight; +}; + +std::array Messages; + +int CountLinesOfText(string_view text) +{ + return 1 + std::count(text.begin(), text.end(), '\n'); +} -void PrintChatMessage(const Surface &out, int x, int y, int width, char *textPtr, UiFlags style) +PlayerMessage &GetNextMessage() { - const size_t length = strlen(textPtr); - std::replace(textPtr, textPtr + length, '\n', ' '); - const string_view text { textPtr, length }; - DrawString(out, WordWrapString(text, width), { { x, y }, { width, 0 } }, style, 1, 18); + std::move_backward(Messages.begin(), Messages.end() - 1, Messages.end()); // Push back older messages + + return Messages.front(); } } // namespace @@ -49,87 +58,70 @@ void plrmsg_delay(bool delay) } plrmsgTicks += SDL_GetTicks(); - _plrmsg *pMsg = plr_msgs; - for (int i = 0; i < PMSG_COUNT; i++, pMsg++) - pMsg->time += plrmsgTicks; + for (PlayerMessage &message : Messages) + message.time += plrmsgTicks; } -void ErrorPlrMsg(const char *pszMsg) +void EventPlrMsg(string_view text, UiFlags style) { - _plrmsg *pMsg = &plr_msgs[plr_msg_slot]; - plr_msg_slot = (plr_msg_slot + 1) & (PMSG_COUNT - 1); - pMsg->player = MAX_PLRS; - pMsg->time = SDL_GetTicks(); - CopyUtf8(pMsg->str, pszMsg, sizeof(pMsg->str)); -} + PlayerMessage &message = GetNextMessage(); -size_t EventPlrMsg(const char *pszFmt, ...) -{ - _plrmsg *pMsg; - va_list va; - - va_start(va, pszFmt); - pMsg = &plr_msgs[plr_msg_slot]; - plr_msg_slot = (plr_msg_slot + 1) & (PMSG_COUNT - 1); - pMsg->player = MAX_PLRS; - pMsg->time = SDL_GetTicks(); - vsprintf(pMsg->str, pszFmt, va); - va_end(va); - return strlen(pMsg->str); + message.style = style; + message.time = SDL_GetTicks(); + message.text = std::string(text); + message.from = string_view(message.text.data(), 0); + message.lineHeight = GetLineHeight(message.text, GameFont12) + 3; } -void SendPlrMsg(int pnum, const char *pszStr) +void SendPlrMsg(Player &player, string_view text) { - _plrmsg *pMsg = &plr_msgs[plr_msg_slot]; - plr_msg_slot = (plr_msg_slot + 1) & (PMSG_COUNT - 1); - pMsg->player = pnum; - pMsg->time = SDL_GetTicks(); - auto &player = Players[pnum]; - assert(strlen(player._pName) < PLR_NAME_LEN); - assert(strlen(pszStr) < MAX_SEND_STR_LEN); - CopyUtf8(pMsg->str, fmt::format(_("{:s} (lvl {:d}): {:s}"), player._pName, player._pLevel, pszStr), sizeof(pMsg->str)); -} + PlayerMessage &message = GetNextMessage(); -void ClearPlrMsg() -{ - _plrmsg *pMsg = plr_msgs; - uint32_t tick = SDL_GetTicks(); + std::string from = fmt::format(_("{:s} (lvl {:d}): "), player._pName, player._pLevel); - for (int i = 0; i < PMSG_COUNT; i++, pMsg++) { - if ((int)(tick - pMsg->time) > 10000) - pMsg->str[0] = '\0'; - } + message.style = UiFlags::ColorWhite; + message.time = SDL_GetTicks(); + message.text = from + std::string(text); + message.from = string_view(message.text.data(), from.size()); + message.lineHeight = GetLineHeight(message.text, GameFont12) + 3; } void InitPlrMsg() { - memset(plr_msgs, 0, sizeof(plr_msgs)); - plr_msg_slot = 0; + Messages = {}; } void DrawPlrMsg(const Surface &out) { int x = 10; - int y = 58; + int y = PANEL_TOP - 13; int width = gnScreenWidth - 20; - _plrmsg *pMsg; - if (chrflag || QuestLogIsOpen) { + if (!talkflag && (chrflag || QuestLogIsOpen)) { x += GetLeftPanel().position.x + GetLeftPanel().size.width; width -= GetLeftPanel().size.width; } - if (invflag || sbookflag) + if (!talkflag && (invflag || sbookflag)) width -= gnScreenWidth - GetRightPanel().position.x; if (width < 300) return; - pMsg = plr_msgs; - for (int i = 0; i < PMSG_COUNT; i++) { - if (pMsg->str[0] != '\0') - PrintChatMessage(out, x, y, width, pMsg->str, TextColorFromPlayerId[pMsg->player]); - pMsg++; - y += 35; + width = std::min(540, width); + + for (PlayerMessage &message : Messages) { + if (message.text.empty()) + break; + if (!talkflag && SDL_GetTicks() - message.time >= 10000) + break; + + std::string text = WordWrapString(message.text, width); + int chatlines = CountLinesOfText(text); + y -= message.lineHeight * chatlines; + + DrawHalfTransparentRectTo(out, x - 3, y, width + 6, message.lineHeight * chatlines); + DrawString(out, text, { { x, y }, { width, 0 } }, UiFlags::ColorWhite, 1, message.lineHeight); + DrawString(out, message.from, { { x, y }, { width, 0 } }, UiFlags::ColorWhitegold, 1, message.lineHeight); } } diff --git a/Source/plrmsg.h b/Source/plrmsg.h index 3b6d563d2..a32f89226 100644 --- a/Source/plrmsg.h +++ b/Source/plrmsg.h @@ -7,22 +7,18 @@ #include "SDL.h" #include +#include +#include "DiabloUI/ui_flags.hpp" #include "engine.h" +#include "player.h" +#include "utils/stdcompat/string_view.hpp" namespace devilution { -struct _plrmsg { - Uint32 time; - uint8_t player; - char str[144]; -}; - void plrmsg_delay(bool delay); -void ErrorPlrMsg(const char *pszMsg); -size_t EventPlrMsg(const char *pszFmt, ...); -void SendPlrMsg(int pnum, const char *pszStr); -void ClearPlrMsg(); +void EventPlrMsg(string_view text, UiFlags style = UiFlags::ColorWhitegold); +void SendPlrMsg(Player &player, string_view text); void InitPlrMsg(); void DrawPlrMsg(const Surface &out);