diff --git a/doc/hosting/AutoratingServer.md b/doc/hosting/AutoratingServer.md index a6297bdfc67..43c50c77fe8 100644 --- a/doc/hosting/AutoratingServer.md +++ b/doc/hosting/AutoratingServer.md @@ -6,30 +6,36 @@ To activate autorating, you must set `autorating` to `true` in the host's config When autorating is not activated or the response is incorrect or empty, the default local rating is used. +Autorating is propagated to clients (when hosting) regardless of them having their own requesting enabled. +In case they do have it enabled, their version will override host-provided one. + ## Autorating request -The request is set to the url defined in the configuration file, with the following headers: +HTTP(s) request is sent to the url defined in the configuration file, with the following headers: -* `WZ-Player-Hash` the hash of the player's public key. -* `WZ-Player-Key` the player's public key. -* `WZ-Locale` the locale of the player to send back translated information. +* `WZ-Player-Hash` sha256 hash of the player's public key. +* `WZ-Player-Key` player's hex encoded public key. +* `WZ-Locale` locale of the requesting client (to send back translated information). +* `WZ-Version` game version, as in bottom right corner in menus. See `PlayerKeys.md` for more information about player keys. ## Autorating response -The server must return a response code 200 and some data json format. Otherwise the response is ignored. The json data are a json object with the following properties: +The server must return a response code 200 and some data in json format. Otherwise the response is ignored. The json data must be a single json object with the following properties: +* `autohoster` (boolean): if the player is a dedicated hoster. It will have an hoster icon instead of a medal and no stars. * `dummy` (boolean): if there aren't enough data to rate the player. It will show a pacifier medal and no stars. * `star` (array of three integers): the code of each star, top to bottom. `0`: none, `1`: gold, `2`: silver, `3`: bronze. * `medal` (integer): the code of the medal to display. `0`: none, `1`: gold, `2`: silver, `3`: bronze. -* `level` (integer): 0-10, the player's level to show beside the medal (even for dummy and autohoster). They are the same as unit experience level in-game. `0` for none, up to 10 for each experience level. -* `elo` (string): the text to display under the player's name. -* `autohoster` (boolean): if the player is a dedicated hoster. It will have an hoster icon instead of a medal and no stars. +* `level` (integer): 0-8, the player's level to show beside the medal (even for dummy and autohoster). They are the same as unit experience level in-game. `0` for none, up to 8 for each experience level. * `details` (string): notes to display in the player's tooltip. -* `name` (string): provides an alternative name of the player in lobby (optional) (ignored if empty). -* `nameTextColorOverride` (array of three integers): overrides alt name color in lobby (rgb 0-255) ([255,255,255] will result in default coloring) (optional). -* `eloTextColorOverride` (array of three integers): overrides elo text color in lobby (rgb 0-255) (optional). +* `elo` (string): the text to display under the player's name. +* `name` (string): provides leaderboard name of the player in lobby (optional) (ignored if empty) (will be replacing player's name). +* `tag` (string): provides an tag of the player in lobby (optional) (ignored if empty) (will be displayed in top right corner). +* `nameTextColorOverride` (array of three integers): overrides name color in lobby (rgb 0-255) ([255,255,255] will result in default coloring) (optional). +* `eloTextColorOverride` (array of three integers): overrides elo text color in lobby (rgb 0-255) ([255,255,255] will result in default coloring) (optional). +* `tagTextColorOverride` (array of three integers): overrides tag text color in lobby (rgb 0-255) ([255,255,255] will result in default coloring) (optional). ### Response sample @@ -43,8 +49,10 @@ The server must return a response code 200 and some data json format. Otherwise "autohoster": false, "details": "Played 264 games, win rate: 53%", "name": "Flex seal", - "nameTextColorOverride": [51,255,51], - "eloTextColorOverride": [255,255,255] + "tag": "Admin", + "nameTextColorOverride": [255,255,255], + "tagTextColorOverride": [51,255,51], + "eloTextColorOverride": [51,255,51] } ``` diff --git a/src/multiint.cpp b/src/multiint.cpp index 86258b9b6ea..2a8c3d87a3b 100644 --- a/src/multiint.cpp +++ b/src/multiint.cpp @@ -232,8 +232,10 @@ struct DisplayPlayerCache { std::string fullMainText; // the “full” main text (used for storing the full player name when displaying a player) WzText wzMainText; // the main text - std::string fullAltNameText; - WzText wzAltNameText; + std::string fullTagText; + WzText wzTagText; + std::string fullNameText; + WzText wzNameText; WzText wzSubText; // the sub text (used for players) WzText wzEloText; // the elo text (used for players) @@ -4177,42 +4179,60 @@ class WzPlayerRow : public WIDGET std::string autoratingTooltipText; if (stats.autorating.valid) { - if (!stats.autorating.altName.empty()) + if (!stats.autorating.name.empty()) { if (!autoratingTooltipText.empty()) { autoratingTooltipText += "\n"; } - std::string altnameStr = stats.autorating.altName; - if (altnameStr.size() > 128) + std::string nameStr = stats.autorating.name; + if (nameStr.size() > 128) { - altnameStr = altnameStr.substr(0, 128); + nameStr = nameStr.substr(0, 128); } - size_t maxLinePos = nthOccurrenceOfChar(altnameStr, '\n', 1); + size_t maxLinePos = nthOccurrenceOfChar(nameStr, '\n', 1); if (maxLinePos != std::string::npos) { - altnameStr = altnameStr.substr(0, maxLinePos); + nameStr = nameStr.substr(0, maxLinePos); } - autoratingTooltipText += std::string(_("Alt Name:")) + " " + altnameStr; + autoratingTooltipText += std::string(_("Leaderboard name:")) + " " + nameStr + "\n"; + autoratingTooltipText += std::string(_("Current name:")) + " " + NetPlay.players[playerIdx].name; } - if (!stats.autorating.details.empty() - && stats.autoratingFrom == RATING_SOURCE_LOCAL) // do not display host-provided details (for now) + if (!stats.autorating.tag.empty()) { if (!autoratingTooltipText.empty()) { autoratingTooltipText += "\n"; } - std::string detailsstr = stats.autorating.details; - if (detailsstr.size() > 512) + std::string tagStr = stats.autorating.tag; + if (tagStr.size() > 128) { - detailsstr = detailsstr.substr(0, 512); + tagStr = tagStr.substr(0, 128); } - size_t maxLinePos = nthOccurrenceOfChar(detailsstr, '\n', 10); + size_t maxLinePos = nthOccurrenceOfChar(tagStr, '\n', 1); if (maxLinePos != std::string::npos) { - detailsstr = detailsstr.substr(0, maxLinePos); + tagStr = tagStr.substr(0, maxLinePos); } - autoratingTooltipText += std::string(_("Player rating:")) + "\n" + detailsstr; + autoratingTooltipText += std::string(_("Tag:")) + " " + tagStr; + } + if (!stats.autorating.details.empty()) + { + if (!autoratingTooltipText.empty()) + { + autoratingTooltipText += "\n"; + } + std::string detailsStr = stats.autorating.details; + if (detailsStr.size() > 512) + { + detailsStr = detailsStr.substr(0, 512); + } + size_t maxLinePos = nthOccurrenceOfChar(detailsStr, '\n', 10); + if (maxLinePos != std::string::npos) + { + detailsStr = detailsStr.substr(0, maxLinePos); + } + autoratingTooltipText += std::string(_("Player rating:")) + "\n" + detailsStr; } } if (!autoratingTooltipText.empty()) @@ -7987,26 +8007,26 @@ static bool isKnownPlayer(std::map const &knownPlayers, return i != knownPlayers.end() && key.toBytes(EcKey::Public) == i->second; } -static void displayAltNameBox(int x, int y, WIDGET *psWidget, DisplayPlayerCache& cache, const PLAYERSTATS::Autorating& ar, bool isHighlight) +static void displayPlayerTagBox(int x, int y, WIDGET *psWidget, DisplayPlayerCache& cache, const PLAYERSTATS::Autorating& ar, bool isHighlight) { - int altNameBoxWidth = cache.wzAltNameText.width() + 4; - int altNameBoxHeight = cache.wzAltNameText.lineSize() + 2; - int altNameBoxX0 = (x + psWidget->width()) - altNameBoxWidth; - PIELIGHT altNameBoxColor = WZCOL_MENU_BORDER; - altNameBoxColor.byte.a = static_cast(static_cast(altNameBoxColor.byte.a) * (isHighlight ? 0.3f : 0.75f)); - pie_UniTransBoxFill(altNameBoxX0, y, altNameBoxX0 + altNameBoxWidth, y + altNameBoxHeight, altNameBoxColor); + int tagBoxWidth = cache.wzTagText.width() + 4; + int tagBoxHeight = cache.wzTagText.lineSize() + 2; + int tagBoxX0 = (x + psWidget->width()) - tagBoxWidth; + PIELIGHT tagBoxColor = WZCOL_MENU_BORDER; + tagBoxColor.byte.a = static_cast(static_cast(tagBoxColor.byte.a) * (isHighlight ? 0.3f : 0.75f)); + pie_UniTransBoxFill(tagBoxX0, y, tagBoxX0 + tagBoxWidth, y + tagBoxHeight, tagBoxColor); - int altNameTextY0 = y + (altNameBoxHeight - cache.wzAltNameText.lineSize()) / 2 - cache.wzAltNameText.aboveBase(); - PIELIGHT altNameTextColor = WZCOL_TEXT_MEDIUM; - if (ar.altNameTextColorOverride[0] != 255 || ar.altNameTextColorOverride[1] != 255 || ar.altNameTextColorOverride[2] != 255) + int tagTextY0 = y + (tagBoxHeight - cache.wzTagText.lineSize()) / 2 - cache.wzTagText.aboveBase(); + PIELIGHT tagTextColor = WZCOL_TEXT_MEDIUM; + if (ar.tagTextColorOverride[0] != 255 || ar.tagTextColorOverride[1] != 255 || ar.tagTextColorOverride[2] != 255) { - altNameTextColor = pal_Colour(ar.altNameTextColorOverride[0], ar.altNameTextColorOverride[1], ar.altNameTextColorOverride[2]); + tagTextColor = pal_Colour(ar.tagTextColorOverride[0], ar.tagTextColorOverride[1], ar.tagTextColorOverride[2]); } if (isHighlight) { - altNameTextColor.byte.a = static_cast(static_cast(altNameTextColor.byte.a) * 0.3f); + tagTextColor.byte.a = static_cast(static_cast(tagTextColor.byte.a) * 0.3f); } - cache.wzAltNameText.render(altNameBoxX0 + 2, altNameTextY0, altNameTextColor); + cache.wzTagText.render(tagBoxX0 + 2, tagTextY0, tagTextColor); } // //////////////////////////////////////////////////////////////////////////// @@ -8041,11 +8061,21 @@ void displayPlayer(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset) auto ar = stat.autorating; std::string name = NetPlay.players[j].name; + // if you hover it shows the actual over the wire name provided by the player itself (can be a lie) + // it is also present in tooltip + if (ar.valid && !isHighlight && !ar.name.empty()) + { + name = ar.name; + } std::map serverPlayers; // TODO Fill this with players known to the server (needs implementing on the server, too). Currently useless. PIELIGHT colour; - if (ingame.PingTimes[j] >= PING_LIMIT) + if (ar.valid && (ar.nameTextColorOverride[0] != 255 || ar.nameTextColorOverride[1] != 255 || ar.nameTextColorOverride[2] != 255)) + { + colour = pal_Colour(ar.nameTextColorOverride[0], ar.nameTextColorOverride[1], ar.nameTextColorOverride[2]); + } + else if (ingame.PingTimes[j] >= PING_LIMIT) { colour = WZCOL_FORM_PLAYER_NOPING; } @@ -8119,28 +8149,28 @@ void displayPlayer(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset) ar.elo.clear(); } - if (cache.fullAltNameText != ar.altName) + if (cache.fullTagText != ar.tag) { - std::string altName = ar.altName; - int maxAltNameWidth = static_cast(static_cast(psWidget->width() - nameX) * 0.65f); + std::string tagStr = ar.tag; + int maxTagStrWidth = static_cast(static_cast(psWidget->width() - nameX) * 0.65f); iV_fonts fontID = font_small; - cache.wzAltNameText.setText(WzString::fromUtf8(altName), fontID); - cache.fullAltNameText = altName; - if (cache.wzAltNameText.width() > maxAltNameWidth) + cache.wzTagText.setText(WzString::fromUtf8(tagStr), fontID); + cache.fullTagText = tagStr; + if (cache.wzTagText.width() > maxTagStrWidth) { - while (!altName.empty() && ((int)iV_GetTextWidth(altName.c_str(), cache.wzAltNameText.getFontID()) + iV_GetEllipsisWidth(cache.wzAltNameText.getFontID())) > maxAltNameWidth) + while (!tagStr.empty() && ((int)iV_GetTextWidth(tagStr.c_str(), cache.wzTagText.getFontID()) + iV_GetEllipsisWidth(cache.wzTagText.getFontID())) > maxTagStrWidth) { - altName.resize(altName.size() - 1); // Clip alt name. + tagStr.resize(tagStr.size() - 1); // Clip alt name. } - altName += "\u2026"; - cache.wzAltNameText.setText(WzString::fromUtf8(altName), fontID); + tagStr += "\u2026"; + cache.wzTagText.setText(WzString::fromUtf8(tagStr), fontID); } } - if (!ar.altName.empty() && isHighlight) + if (!ar.tag.empty() && isHighlight) { // display first, behind everything - displayAltNameBox(x, y, psWidget, cache, ar, isHighlight); + displayPlayerTagBox(x, y, psWidget, cache, ar, isHighlight); } int H = 5; @@ -8197,10 +8227,10 @@ void displayPlayer(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset) cache.wzEloText.render(x + nameX, y + 28 + H*!subText.isEmpty(), eloColour); } - if (!ar.altName.empty() && !isHighlight) + if (!ar.tag.empty() && !isHighlight) { // display last, over top of everything - displayAltNameBox(x, y, psWidget, cache, ar, isHighlight); + displayPlayerTagBox(x, y, psWidget, cache, ar, isHighlight); } } else // AI diff --git a/src/multistat.cpp b/src/multistat.cpp index d0b172304f4..35994e1d2b4 100644 --- a/src/multistat.cpp +++ b/src/multistat.cpp @@ -40,6 +40,7 @@ #include "multistat.h" #include "urlrequest.h" #include "stdinreader.h" +#include "version.h" #include #include @@ -71,8 +72,10 @@ static void NETauto(PLAYERSTATS::Autorating &ar) NETauto(ar.elo); NETauto(ar.autohoster); NETauto(ar.details); - NETauto(ar.altName); - NETauto(ar.altNameTextColorOverride); + NETauto(ar.tag); + NETauto(ar.name); + NETauto(ar.tagTextColorOverride); + NETauto(ar.nameTextColorOverride); NETauto(ar.eloTextColorOverride); } } @@ -91,13 +94,23 @@ PLAYERSTATS::Autorating::Autorating(nlohmann::json const &json) details = json["details"].get(); if (json.contains("name")) { - altName = json["name"].get(); + name = json["name"].get(); + } + if (json.contains("tag")) + { + tag = json["tag"].get(); } if (json.contains("nameTextColorOverride")) { - altNameTextColorOverride[0] = json["nameTextColorOverride"][0].get(); - altNameTextColorOverride[1] = json["nameTextColorOverride"][1].get(); - altNameTextColorOverride[2] = json["nameTextColorOverride"][2].get(); + nameTextColorOverride[0] = json["nameTextColorOverride"][0].get(); + nameTextColorOverride[1] = json["nameTextColorOverride"][1].get(); + nameTextColorOverride[2] = json["nameTextColorOverride"][2].get(); + } + if (json.contains("tagTextColorOverride")) + { + tagTextColorOverride[0] = json["tagTextColorOverride"][0].get(); + tagTextColorOverride[1] = json["tagTextColorOverride"][1].get(); + tagTextColorOverride[2] = json["tagTextColorOverride"][2].get(); } if (json.contains("eloTextColorOverride")) { @@ -142,6 +155,7 @@ void lookupRatingAsync(uint32_t playerIndex) req.setRequestHeader("WZ-Player-Hash", hash); req.setRequestHeader("WZ-Player-Key", key); req.setRequestHeader("WZ-Locale", getLanguage()); + req.setRequestHeader("WZ-Version", version_getVersionString()); debug(LOG_INFO, "Requesting \"%s\" for player %d (%.32s) (%s)", req.url.c_str(), playerIndex, NetPlay.players[playerIndex].name, hash.c_str()); req.onSuccess = [playerIndex, hash](std::string const &url, HTTPResponseDetails const &response, std::shared_ptr const &data) { long httpStatusCode = response.httpStatusCode(); diff --git a/src/multistat.h b/src/multistat.h index b2d0eb024f5..7211353ea9b 100644 --- a/src/multistat.h +++ b/src/multistat.h @@ -75,11 +75,13 @@ struct PLAYERSTATS uint8_t star[3] = {0, 0, 0}; uint8_t medal = 0; uint8_t level = 0; - uint8_t altNameTextColorOverride[3] = {255, 255, 255}; // rgb + uint8_t nameTextColorOverride[3] = {255, 255, 255}; // rgb + uint8_t tagTextColorOverride[3] = {255, 255, 255}; // rgb uint8_t eloTextColorOverride[3] = {255, 255, 255}; // rgb std::string elo; std::string details; - std::string altName; + std::string tag; + std::string name; }; Autorating autorating; RATING_SOURCE autoratingFrom = RATING_SOURCE_HOST;