diff --git a/Core/Config.cpp b/Core/Config.cpp index 5dca9c3f5d..2f7e0952f3 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1057,6 +1057,7 @@ static const ConfigSetting networkSettings[] = { ConfigSetting("EnableNetworkChat", SETTING(g_Config, bEnableNetworkChat), false, CfgFlag::PER_GAME), ConfigSetting("ChatButtonPosition", SETTING(g_Config, iChatButtonPosition), (int)ScreenEdgePosition::BOTTOM_LEFT, CfgFlag::PER_GAME), ConfigSetting("ChatScreenPosition", SETTING(g_Config, iChatScreenPosition), (int)ScreenEdgePosition::BOTTOM_LEFT, CfgFlag::PER_GAME), + ConfigSetting("ChatTimestamps", SETTING(g_Config, bChatTimestamps), true, CfgFlag::PER_GAME), ConfigSetting("EnableQuickChat", SETTING(g_Config, bEnableQuickChat), true, CfgFlag::PER_GAME), ConfigSetting("QuickChat1", SETTING(g_Config, sQuickChat[0]), "Quick Chat 1", CfgFlag::PER_GAME), ConfigSetting("QuickChat2", SETTING(g_Config, sQuickChat[1]), "Quick Chat 2", CfgFlag::PER_GAME), diff --git a/Core/Config.h b/Core/Config.h index c5b7ead9aa..cb4ebc95a6 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -611,6 +611,7 @@ public: bool bDontDownloadInfraJson; int iChatButtonPosition; int iChatScreenPosition; + bool bChatTimestamps; bool bEnableQuickChat; std::string sQuickChat[5]; diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 3c40adf583..9b55fb7d9a 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -120,7 +120,17 @@ sockaddr LocalIP; int defaultWlanChannel = PSP_SYSTEMPARAM_ADHOC_CHANNEL_11; // Don't put 0(Auto) here, it needed to be a valid/actual channel number static std::mutex chatLogLock; -static std::vector chatLog; +static std::vector chatLog; +// Enough to scroll back through a decent conversation without growing unbounded. +static const size_t MAX_CHAT_LOG_LINES = 250; + +// chatLogLock must be held. +static void AddChatLogEntry(std::string text) { + chatLog.push_back(ChatLogEntry{std::move(text), time(nullptr)}); + if (chatLog.size() > MAX_CHAT_LOG_LINES) { + chatLog.erase(chatLog.begin(), chatLog.begin() + (chatLog.size() - MAX_CHAT_LOG_LINES)); + } +} static int chatMessageGeneration = 0; static int chatMessageCount = 0; @@ -1322,25 +1332,21 @@ void sendChat(std::string_view chatString) { std::string name = g_Config.sNickName; std::lock_guard guard(chatLogLock); - chatLog.emplace_back(name.substr(0, 8) + ": " + chat.message); + AddChatLogEntry(name.substr(0, 8) + ": " + chat.message); chatMessageGeneration++; } } } else { std::lock_guard guard(chatLogLock); auto n = GetI18NCategory(I18NCat::NETWORKING); - chatLog.push_back(std::string(n->T("You're in Offline Mode, go to lobby or online hall"))); + AddChatLogEntry(std::string(n->T("You're in Offline Mode, go to lobby or online hall"))); INFO_LOG(Log::sceNet, "Offline. Would have sent: %.*s", STR_VIEW(chatString)); chatMessageGeneration++; } } -std::vector getChatLog() { +std::vector getChatLog() { std::lock_guard guard(chatLogLock); - // If the log gets large, trim it down. - if (chatLog.size() > 50) { - chatLog.erase(chatLog.begin(), chatLog.begin() + (chatLog.size() - 50)); - } return chatLog; } @@ -1555,7 +1561,7 @@ int friendFinder() { incoming.append((char*)packet->base.message); std::lock_guard guard(chatLogLock); - chatLog.push_back(incoming); + AddChatLogEntry(incoming); chatMessageGeneration++; chatMessageCount++; @@ -1626,7 +1632,7 @@ int friendFinder() { //joined.append((char *)packet->ip); std::lock_guard guard(chatLogLock); - chatLog.push_back(incoming); + AddChatLogEntry(incoming); chatMessageGeneration++; #ifdef LOCALHOST_AS_PEER diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index 4ca28ea360..969ba11afe 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -29,6 +29,7 @@ #include #include #include +#include #include "Common/Net/Resolve.h" #include "Common/Serialize/Serializer.h" @@ -884,7 +885,12 @@ void addFriend(SceNetAdhocctlConnectPacketS2C * packet); * @param std::string ChatString */ void sendChat(std::string_view chatString); -std::vector getChatLog(); + +struct ChatLogEntry { + std::string text; // "name: message", or an info line with no colon. + time_t timestamp; +}; +std::vector getChatLog(); int GetChatChangeID(); int GetChatMessageCount(); diff --git a/UI/ChatScreen.cpp b/UI/ChatScreen.cpp index 3bc2d0e10a..0fe79b99c2 100644 --- a/UI/ChatScreen.cpp +++ b/UI/ChatScreen.cpp @@ -10,6 +10,7 @@ #include "Common/UI/UI.h" #include "Common/Data/Text/I18n.h" +#include "Common/File/FileUtil.h" // For the localtime_r shim on Windows. #include "Common/Data/Encoding/Utf8.h" #include "Common/System/Request.h" #include "Core/Config.h" @@ -137,11 +138,13 @@ void ChatMenu::UpdateChat() { using namespace UI; if (chatVert_ != nullptr) { chatVert_->Clear(); //read Access violation is proadhoc.cpp use NULL_->Clear() pointer? - std::vector chatLog = getChatLog(); - for (auto i : chatLog) { + std::vector chatLog = getChatLog(); + for (const auto &entry : chatLog) { + const std::string &i = entry.text; uint32_t namecolor = 0x29B6F6; uint32_t textcolor = 0xFFFFFF; uint32_t infocolor = 0xFDD835; + uint32_t timecolor = 0x9E9E9E; std::string name = g_Config.sNickName; std::string displayname = i.substr(0, i.find(':')); @@ -150,12 +153,27 @@ void ChatMenu::UpdateChat() { namecolor = 0xE53935; } + std::string timestamp; + if (g_Config.bChatTimestamps) { + char buf[16]; + tm localTime; + localtime_r(&entry.timestamp, &localTime); + if (strftime(buf, sizeof(buf), "%H:%M ", &localTime)) { + timestamp = buf; + } + } + if (i.length() <= displayname.length() || i[displayname.length()] != ':') { - TextView *v = chatVert_->Add(new TextView(i, ALIGN_LEFT | FLAG_WRAP_TEXT, true, new LayoutParams(FILL_PARENT, WRAP_CONTENT))); + // Info line - no name to color separately, so the timestamp can just go in the text. + TextView *v = chatVert_->Add(new TextView(timestamp + i, ALIGN_LEFT | FLAG_WRAP_TEXT, true, new LayoutParams(FILL_PARENT, WRAP_CONTENT))); v->SetTextColor(0xFF000000 | infocolor); } else { LinearLayout *line = chatVert_->Add(new LinearLayout(ORIENT_HORIZONTAL, new LayoutParams(FILL_PARENT, FILL_PARENT))); line->SetSpacing(0.0f); + if (!timestamp.empty()) { + TextView *timeView = line->Add(new TextView(timestamp, ALIGN_LEFT, true, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 0.0f))); + timeView->SetTextColor(0xFF000000 | timecolor); + } TextView *nameView = line->Add(new TextView(displayname, ALIGN_LEFT, true, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 0.0f))); nameView->SetTextColor(0xFF000000 | namecolor); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index e5257a4ed3..9aed67a5c6 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1070,6 +1070,7 @@ void GameSettingsScreen::CreateNetworkingSettings(UI::ViewGroup *networkingSetti networkingSettings->Add(new PopupMultiChoice(&g_Config.iChatButtonPosition, n->T("Chat Button Position"), chatButtonPositions, 0, ARRAY_SIZE(chatButtonPositions), I18NCat::DIALOG, screenManager()))->SetEnabledPtr(&g_Config.bEnableNetworkChat); static const char *chatScreenPositions[] = { "Bottom Left", "Bottom Center", "Bottom Right", "Top Left", "Top Center", "Top Right" }; networkingSettings->Add(new PopupMultiChoice(&g_Config.iChatScreenPosition, n->T("Chat Screen Position"), chatScreenPositions, 0, ARRAY_SIZE(chatScreenPositions), I18NCat::DIALOG, screenManager()))->SetEnabledPtr(&g_Config.bEnableNetworkChat); + networkingSettings->Add(new CheckBox(&g_Config.bChatTimestamps, n->T("Show timestamps in chat")))->SetEnabledPtr(&g_Config.bEnableNetworkChat); networkingSettings->Add(new ItemHeader(n->T("Quick chat"))); CheckBox *qc = networkingSettings->Add(new CheckBox(&g_Config.bEnableQuickChat, n->T("Enable quick chat")));