Index: binaries/data/mods/public/gui/common/functions_utility.js =================================================================== --- binaries/data/mods/public/gui/common/functions_utility.js +++ binaries/data/mods/public/gui/common/functions_utility.js @@ -3,6 +3,14 @@ */ var g_LastNickNotification = -1; +/** + * Used to track autocompleted names to try next autocompletion if found multiples + */ +var g_LastAutoCompleteBufferPosition = 0; +var g_LastAutoCompleteText = ""; +var g_SameAutoCompleteTries = 0; +var g_LastAutoCompleteNewText = ""; + // Get list of XML files in pathname with recursion, excepting those starting with _ function getXMLFileList(pathname) { @@ -140,7 +148,7 @@ return Engine.ConfigDB_GetValue("user", "playername.multiplayer") || Engine.GetSystemUsername(); } -function tryAutoComplete(text, autoCompleteList) +function tryAutoComplete(text, autoCompleteList, tries) { if (!text.length) return text; @@ -153,19 +161,35 @@ if (!lastWord.length) return text; + let firstFound = ""; for (var word of autoCompleteList) { if (word.toLowerCase().indexOf(lastWord.toLowerCase()) != 0) continue; + else + { + --tries; + if (firstFound == "") + firstFound = word; + } + if (tries < 0) break; + } - text = wordSplit.join(" "); - if (text.length > 0) - text += " "; + if (firstFound == "") + return text; - text += word; - break; + // wrap search to start, cause tries could not complete to 0, means there are no more matches as tries in list + if (tries >= 0) + { + g_SameAutoCompleteTries = 1; + word = firstFound; } - return text; + + text = wordSplit.join(" "); + if (text.length > 0) + text += " "; + + return text + word; } function autoCompleteNick(guiObject, playernames) @@ -175,11 +199,23 @@ return; let bufferPosition = guiObject.buffer_position; - let textTillBufferPosition = text.substring(0, bufferPosition); - let newText = tryAutoComplete(textTillBufferPosition, playernames); - - guiObject.caption = newText + text.substring(bufferPosition); - guiObject.buffer_position = bufferPosition + (newText.length - textTillBufferPosition.length); + let sameTry = g_LastAutoCompleteNewText == text; + if (!sameTry) + { + g_LastAutoCompleteBufferPosition = bufferPosition; + g_LastAutoCompleteText = text; + g_LastAutoCompleteNewText = ""; + g_SameAutoCompleteTries = 0; + } + + let textTillBufferPosition = sameTry ? g_LastAutoCompleteText.substring(0, g_LastAutoCompleteBufferPosition) : text.substring(0, bufferPosition); + let newText = tryAutoComplete(textTillBufferPosition, playernames, g_SameAutoCompleteTries++); + + guiObject.caption = newText + (sameTry ? g_LastAutoCompleteText.substring(g_LastAutoCompleteBufferPosition) : text.substring(bufferPosition)); + if (g_LastAutoCompleteNewText == "" || sameTry) + g_LastAutoCompleteNewText = guiObject.caption; + guiObject.buffer_position = (sameTry ? g_LastAutoCompleteBufferPosition : bufferPosition) + (newText.length - textTillBufferPosition.length); + return; } function clearChatMessages() Index: binaries/data/mods/public/gui/lobby/lobby.js =================================================================== --- binaries/data/mods/public/gui/lobby/lobby.js +++ binaries/data/mods/public/gui/lobby/lobby.js @@ -135,7 +135,7 @@ addChatMessage({ "from": "system", "time": msg.time, - "text": translate("Disconnected.") + " " + msg.text + "text": translate("Disconnected.") + " " + msg.reason }); return true; }, @@ -150,13 +150,13 @@ }, "chat": { "subject": msg => { - updateSubject(msg.text); + updateSubject(msg.subject); return false; }, "join": msg => { addChatMessage({ "text": "/special " + sprintf(translate("%(nick)s has joined."), { - "nick": msg.text + "nick": msg.nick }), "time": msg.time, "isSpecial": true @@ -181,8 +181,8 @@ "role": msg => { Engine.GetGUIObjectByName("chatInput").hidden = Engine.LobbyGetPlayerRole(g_Username) == "visitor"; - let me = g_Username == msg.text; - let role = Engine.LobbyGetPlayerRole(msg.text); + let me = g_Username == msg.nick; + let role = Engine.LobbyGetPlayerRole(msg.role); let txt = role == "visitor" ? me ? @@ -201,12 +201,12 @@ translate("%(nick)s is not a moderator anymore."); addChatMessage({ - "text": "/special " + sprintf(txt, { "nick": msg.text }), + "text": "/special " + sprintf(txt, { "nick": msg.nick }), "time": msg.time, "isSpecial": true }); - if (g_SelectedPlayer == msg.text) + if (g_SelectedPlayer == msg.nick) updateUserRoleText(g_SelectedPlayer); return false; @@ -214,8 +214,8 @@ "nick": msg => { addChatMessage({ "text": "/special " + sprintf(translate("%(oldnick)s is now known as %(newnick)s."), { - "oldnick": msg.text, - "newnick": msg.data + "oldnick": msg.oldnick, + "newnick": msg.newnick }), "time": msg.time, "isSpecial": true Index: binaries/data/mods/public/gui/lobby/prelobby.js =================================================================== --- binaries/data/mods/public/gui/lobby/prelobby.js +++ binaries/data/mods/public/gui/lobby/prelobby.js @@ -162,11 +162,18 @@ g_LobbyIsConnecting = false; - switch(message.level) { + switch (message.level) + { case "error": + { + Engine.GetGUIObjectByName("feedback").caption = message.text; + g_DisplayingSystemMessage = true; + Engine.StopXmppClient(); + break; + } case "disconnected": { - Engine.GetGUIObjectByName("feedback").caption = message.text || + Engine.GetGUIObjectByName("feedback").caption = message.reason || translate("Unknown error. This usually occurs because the same IP address is not allowed to register more than one account within one hour."); g_DisplayingSystemMessage = true; Engine.StopXmppClient(); Index: source/lobby/XmppClient.h =================================================================== --- source/lobby/XmppClient.h +++ source/lobby/XmppClient.h @@ -85,6 +85,7 @@ void SendStunEndpointToHost(StunClient::StunEndpoint* stunEndpoint, const std::string& hostJID); protected: + shared_ptr GetScriptInterface(); /* Xmpp handlers */ /* MUC handlers */ virtual void handleMUCParticipantPresence(glooxwrapper::MUCRoom*, const glooxwrapper::MUCRoomParticipant, const glooxwrapper::Presence&); @@ -132,24 +133,19 @@ std::time_t ComputeTimestamp(const glooxwrapper::Message& msg) const; public: - /* Messages */ - struct GUIMessage - { - std::wstring type; - std::wstring level; - std::wstring text; - std::wstring data; - std::wstring from; - std::wstring message; - std::time_t time; - }; void GuiPollMessage(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret); void SendMUCMessage(const std::string& message); void ClearPresenceUpdates(); -protected: - void PushGuiMessage(XmppClient::GUIMessage message); - void CreateGUIMessage(const std::string& type, const std::string& level, const std::string& text = "", const std::string& data = ""); +protected: + void CreateGUIMessage( + std::string type, + std::string level, + std::string prop1, + std::string val1, + std::string prop2, + std::string val2, + std::time_t time); private: /// Map of players std::map > m_PlayerMap; @@ -160,7 +156,7 @@ /// Profile data std::vector m_Profile; /// Queue of messages for the GUI - std::deque m_GuiMessageQueue; + std::deque> m_GuiMessageQueue; /// Current room subject/topic. std::string m_Subject; }; Index: source/lobby/XmppClient.cpp =================================================================== --- source/lobby/XmppClient.cpp +++ source/lobby/XmppClient.cpp @@ -25,6 +25,8 @@ #include "i18n/L10n.h" +#include "gui/GUI.h" +#include "gui/GUIManager.h" #include "lib/external_libraries/enet.h" #include "lib/utf8.h" #include "network/NetServer.h" @@ -165,6 +167,11 @@ glooxwrapper::Tag::free(t); } +shared_ptr XmppClient::GetScriptInterface() +{ + return g_GUI->GetScriptInterface(); +} + /// Network void XmppClient::connect() { @@ -190,6 +197,46 @@ std::cout << "log: level: " << level << ", area: " << area << ", message: " << message << std::endl; } +void XmppClient::CreateGUIMessage( + std::string type, + std::string level, + std::string prop1 = "", + std::string val1 = "", + std::string prop2 = "", + std::string val2 = "", + std::time_t time = std::time(nullptr)) +{ + JSContext* cx = GetScriptInterface()->GetContext(); + JSAutoRequest rq(cx); + JS::RootedValue message(cx); + GetScriptInterface()->Eval("({})", &message); + GetScriptInterface()->SetProperty(message, "time", (double)time); + GetScriptInterface()->SetProperty(message, "type", type); + GetScriptInterface()->SetProperty(message, "level", level); + if (!prop1.empty()) + GetScriptInterface()->SetProperty(message, prop1.c_str(), val1); + if (!prop2.empty()) + GetScriptInterface()->SetProperty(message, prop2.c_str(), val2); + m_GuiMessageQueue.push_back(JS::Heap(message)); +} + +#define CREATE_GUI_MESSAGE_SYSTEM(level) \ + CreateGUIMessage("system", level); + +#define CREATE_GUI_MESSAGE_ERROR(text) \ + CreateGUIMessage("system", "error", "text", text); + +#define CREATE_GUI_MESSAGE_GAME(level) \ + CreateGUIMessage("game", level); + +#define CREATE_GUI_MESSAGE_CHAT(msg, priv) \ + CreateGUIMessage( \ + "chat", \ + priv ? "private-message" : "room-message", \ + "from", msg.from().resource().to_string(), \ + "text", msg.body().to_string(), \ + ComputeTimestamp(msg)); + /***************************************************** * Connection handlers * *****************************************************/ @@ -201,7 +248,7 @@ { if (m_mucRoom) { - CreateGUIMessage("system", "connected"); + CREATE_GUI_MESSAGE_SYSTEM("connected"); m_mucRoom->join(); } @@ -231,7 +278,7 @@ m_PlayerMap.clear(); m_Profile.clear(); - CreateGUIMessage("system", "disconnected", ConnectionErrorToString(error)); + CreateGUIMessage("system", "disconnected", "reason", ConnectionErrorToString(error)); } /** @@ -257,7 +304,7 @@ */ void XmppClient::handleMUCError(glooxwrapper::MUCRoom*, gloox::StanzaError err) { - CreateGUIMessage("system", "error", StanzaErrorToString(err)); + CREATE_GUI_MESSAGE_ERROR(StanzaErrorToString(err)); } /***************************************************** @@ -423,9 +470,10 @@ void XmppClient::handleRegistrationResult(const glooxwrapper::JID&, gloox::RegistrationResult result) { if (result == gloox::RegistrationSuccess) - CreateGUIMessage("system", "registered"); + CREATE_GUI_MESSAGE_SYSTEM("registered") else - CreateGUIMessage("system", "error", RegistrationResultToString(result)); + CREATE_GUI_MESSAGE_ERROR(RegistrationResultToString(result)) + disconnect(); } @@ -560,23 +608,11 @@ return; } - GUIMessage message = m_GuiMessageQueue.front(); - JSContext* cx = scriptInterface.GetContext(); + JSContext* cx = GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); + JS::RootedValue retVal(cx, m_GuiMessageQueue.front()); - scriptInterface.Eval("({})", ret); - scriptInterface.SetProperty(ret, "type", message.type); - if (!message.from.empty()) - scriptInterface.SetProperty(ret, "from", message.from); - if (!message.text.empty()) - scriptInterface.SetProperty(ret, "text", message.text); - if (!message.level.empty()) - scriptInterface.SetProperty(ret, "level", message.level); - if (!message.data.empty()) - scriptInterface.SetProperty(ret, "data", message.data); - - scriptInterface.SetProperty(ret, "time", (double)message.time); - + ret.set(scriptInterface.CloneValueFromOtherContext(*GetScriptInterface(), retVal)); m_GuiMessageQueue.pop_front(); } @@ -589,28 +625,28 @@ } /** - * Push a message onto the GUI queue. - * - * @param message Message to add to the queue - */ -void XmppClient::PushGuiMessage(XmppClient::GUIMessage message) -{ - m_GuiMessageQueue.push_back(std::move(message)); -} - -/** * Clears all presence updates from the message queue. * Used when rejoining the lobby, since we don't need to handle past presence changes. */ void XmppClient::ClearPresenceUpdates() { - m_GuiMessageQueue.erase( - std::remove_if(m_GuiMessageQueue.begin(), m_GuiMessageQueue.end(), - [](XmppClient::GUIMessage& message) - { - return message.type == L"chat" && message.level == L"presence"; - } - ), m_GuiMessageQueue.end()); + JSContext* cx = GetScriptInterface()->GetContext(); + JSAutoRequest rq(cx); + + for (std::deque>::iterator message = m_GuiMessageQueue.begin(); message != m_GuiMessageQueue.end(); ) + { + JS::RootedValue messageVal(cx, message->get()); + std::string type, level; + + if (GetScriptInterface()->HasProperty(messageVal, "type") && + GetScriptInterface()->HasProperty(messageVal, "level") && + GetScriptInterface()->GetProperty(messageVal, "type", type) && + GetScriptInterface()->GetProperty(messageVal, "level", level) && + type == "chat" && level == "presence") + message = m_GuiMessageQueue.erase(message); + else + ++message; + } } /** @@ -619,14 +655,7 @@ void XmppClient::handleMUCMessage(glooxwrapper::MUCRoom*, const glooxwrapper::Message& msg, bool priv) { DbgXMPP(msg.from().resource() << " said " << msg.body()); - - GUIMessage message; - message.type = L"chat"; - message.level = priv ? L"private-message" : L"room-message"; - message.from = wstring_from_utf8(msg.from().resource().to_string()); - message.text = wstring_from_utf8(msg.body().to_string()); - message.time = ComputeTimestamp(msg); - PushGuiMessage(message); + CREATE_GUI_MESSAGE_CHAT(msg, priv); } /** @@ -636,14 +665,7 @@ { DbgXMPP("type " << msg.subtype() << ", subject " << msg.subject() << ", message " << msg.body() << ", thread id " << msg.thread()); - - GUIMessage message; - message.type = L"chat"; - message.level = L"private-message"; - message.from = wstring_from_utf8(msg.from().username().to_string()); - message.text = wstring_from_utf8(msg.body().to_string()); - message.time = ComputeTimestamp(msg); - PushGuiMessage(message); + CREATE_GUI_MESSAGE_CHAT(msg, true); } /** @@ -667,7 +689,7 @@ for (const glooxwrapper::Tag* const& t : gq->m_GameList) m_GameList.emplace_back(t->clone()); - CreateGUIMessage("game", "gamelist"); + CREATE_GUI_MESSAGE_GAME("gamelist"); } if (bq) { @@ -680,7 +702,7 @@ for (const glooxwrapper::Tag* const& t : bq->m_StanzaBoardList) m_BoardList.emplace_back(t->clone()); - CreateGUIMessage("game", "leaderboard"); + CREATE_GUI_MESSAGE_GAME("leaderboard"); } else if (bq->m_Command == "ratinglist") { @@ -691,7 +713,7 @@ m_PlayerMap[name][1] = t->findAttribute("rating").to_string(); } - CreateGUIMessage("game", "ratinglist"); + CREATE_GUI_MESSAGE_GAME("ratinglist"); } } if (pq) @@ -703,42 +725,21 @@ for (const glooxwrapper::Tag* const& t : pq->m_StanzaProfile) m_Profile.emplace_back(t->clone()); - CreateGUIMessage("game", "profile"); + CREATE_GUI_MESSAGE_GAME("profile"); } } else if (iq.subtype() == gloox::IQ::Error) { - gloox::StanzaError err = iq.error_error(); - CreateGUIMessage("system", "error", StanzaErrorToString(err)); + CREATE_GUI_MESSAGE_ERROR(StanzaErrorToString(iq.error_error())); } else { - CreateGUIMessage("system", "error", g_L10n.Translate("unknown subtype (see logs)")); - std::string tag = tag_name(iq); - LOGMESSAGE("unknown subtype '%s'", tag.c_str()); + CREATE_GUI_MESSAGE_ERROR(g_L10n.Translate("unknown subtype (see logs)")); + LOGMESSAGE("unknown subtype '%s'", tag_name(iq).c_str()); } return true; } -/** - * Create a new detail message for the GUI. - * - * @param type General message type - * @param level Detailed message type - * @param text Body of the message - * @param data Optional field, used for auxiliary data - */ -void XmppClient::CreateGUIMessage(const std::string& type, const std::string& level, const std::string& text, const std::string& data) -{ - GUIMessage message; - message.type = wstring_from_utf8(type); - message.level = wstring_from_utf8(level); - message.text = wstring_from_utf8(text); - message.data = wstring_from_utf8(data); - message.time = std::time(nullptr); - PushGuiMessage(message); -} - /***************************************************** * Presence, nickname, and subject * *****************************************************/ @@ -763,25 +764,25 @@ m_PlayerMap[newNick].resize(3); m_PlayerMap[newNick][0] = presenceString; m_PlayerMap[newNick][2] = roleString; - CreateGUIMessage("chat", "nick", nick, participant.newNick.to_string()); + DbgXMPP(nick << " is now known as " << participant.newNick.to_string()); + CreateGUIMessage("chat", "nick", "oldnick", nick, "newnick", participant.newNick.to_string()); } else if (participant.flags & gloox::UserKicked) { DbgXMPP(nick << " was kicked. Reason: " << participant.reason.to_string()); - CreateGUIMessage("chat", "kicked", nick, participant.reason.to_string()); + CreateGUIMessage("chat", "kicked", "nick", nick, "reason", participant.reason.to_string()); } else if (participant.flags & gloox::UserBanned) { DbgXMPP(nick << " was banned. Reason: " << participant.reason.to_string()); - CreateGUIMessage("chat", "banned", nick, participant.reason.to_string()); + CreateGUIMessage("chat", "banned", "nick", nick, "reason", participant.reason.to_string()); } else { DbgXMPP(nick << " left the room (flags " << participant.flags << ")"); - CreateGUIMessage("chat", "leave", nick); + CreateGUIMessage("chat", "leave", "nick", nick); } - m_PlayerMap.erase(nick); } else @@ -796,11 +797,11 @@ m_initialLoadComplete = true; } else if (m_PlayerMap.find(nick) == m_PlayerMap.end()) - CreateGUIMessage("chat", "join", nick); + CreateGUIMessage("chat", "join", "nick", nick); else if (m_PlayerMap[nick][2] != roleString) - CreateGUIMessage("chat", "role", nick, m_PlayerMap[nick][2]); + CreateGUIMessage("chat", "role", "nick", nick, "role", m_PlayerMap[nick][2]); else - CreateGUIMessage("chat", "presence", nick); + CreateGUIMessage("chat", "presence", "nick", nick); DbgXMPP(nick << " is in the room, presence : " << (int)presenceType); m_PlayerMap[nick].resize(3); @@ -815,7 +816,7 @@ void XmppClient::handleMUCSubject(glooxwrapper::MUCRoom*, const glooxwrapper::string& UNUSED(nick), const glooxwrapper::string& subject) { m_Subject = subject.c_str(); - CreateGUIMessage("chat", "subject", m_Subject); + CreateGUIMessage("chat", "subject", "subject", m_Subject); } /** @@ -1131,3 +1132,8 @@ g_NetServer->SendHolePunchingMessage(candidate.ip.to_string(), candidate.port); } + +#undef CREATE_GUI_MESSAGE_SYSTEM +#undef CREATE_GUI_MESSAGE_ERROR +#undef CREATE_GUI_MESSAGE_GAME +#undef CREATE_GUI_MESSAGE_CHAT