Ticket #3205: t3205_enlighten_too_dark_chat_colors_v2.1.patch

File t3205_enlighten_too_dark_chat_colors_v2.1.patch, 16.0 KB (added by elexis, 9 years ago)

ensureMinimumLightness was incorrectly applied in gamesetup.js.

  • binaries/data/mods/public/gui/common/color.js

     
     1//Some names are special and should always appear in certain colors.
     2var g_FixedPlayerColors = {
     3    "system": repeatString(7, "255.0.0."),
     4    "@WFGbot": repeatString(7, "255.24.24."),
     5    "pyrogenesis": repeatString(2, "97.0.0.") + repeatString(2, "124.0.0.") + "138.0.0." +
     6                    repeatString(2, "174.0.0.") + repeatString(2, "229.40.0.") + repeatString(2, "243.125.15.")
     7};
     8
     9function colorizePlayername(playername)
     10{
     11    let color = g_FixedPlayerColors[playername];
     12    if (color) {
     13        color = color.split(".");
     14        return ('[color="' + playername.split("").map(function (c, i) color.slice(i * 3, i * 3 + 3).join(" ") + '"]' + c + '[/color][color="')
     15                .join("") + '"]').slice(0, -10);
     16    }
     17    return '[color="' + getPlayerColor(playername.replace(g_modPrefix, "")) + '"]' + playername + '[/color]';
     18}
     19
     20// Generate a (mostly) unique color for this player based on their name.
     21// See http://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-jquery-javascript
     22function getPlayerColor(playername)
     23{
     24    // Generate a probably-unique hash for the player name and use that to create a color.
     25    let hash = 0;
     26    for (let i = 0; i < playername.length; i++)
     27        hash = playername.charCodeAt(i) + ((hash << 5) - hash);
     28
     29    // First create the color in RGB then HSL, clamp the lightness so it's not too dark to read, and then convert back to RGB to display.
     30    // The reason for this roundabout method is this algorithm can generate values from 0 to 255 for RGB but only 0 to 100 for HSL; this gives
     31    // us much more variety if we generate in RGB. Unfortunately, enforcing that RGB values are a certain lightness is very difficult, so
     32    // we convert to HSL to do the computation. Since our GUI code only displays RGB colors, we have to convert back.
     33    let [h, s, l] = ensureMinimumLightness(rgbToHsl(hash >> 24 & 0xFF, hash >> 16 & 0xFF, hash >> 8 & 0xFF));
     34    return hslToRgb(h, s, l).join(" ");
     35}
     36
     37function ensureMinimumLightness(hsl)
     38{
     39    let [h, s, l] = hsl;
     40    return [h, s, Math.max(0.7, l)];
     41}
     42
     43//Ensure `value` is between 0 and 1.
     44function clampColorValue(value)
     45{
     46    return Math.abs(1 - Math.abs(value - 1));
     47}
     48
     49// See http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
     50function rgbToHsl(r, g, b)
     51{
     52    r /= 255;
     53    g /= 255;
     54    b /= 255;
     55    let max = Math.max(r, g, b), min = Math.min(r, g, b);
     56    let h, s, l = (max + min) / 2;
     57
     58    if (max == min)
     59        h = s = 0; // achromatic
     60    else
     61    {
     62        let d = max - min;
     63        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
     64        switch (max)
     65        {
     66            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
     67            case g: h = (b - r) / d + 2; break;
     68            case b: h = (r - g) / d + 4; break;
     69        }
     70        h /= 6;
     71    }
     72
     73    return [h, s, l];
     74}
     75
     76function hslToRgb(h, s, l)
     77{
     78    function hue2rgb(p, q, t)
     79    {
     80        if (t < 0) t += 1;
     81        if (t > 1) t -= 1;
     82        if (t < 1/6) return p + (q - p) * 6 * t;
     83        if (t < 1/2) return q;
     84        if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
     85        return p;
     86    }
     87
     88    [h, s, l] = [h, s, l].map(clampColorValue);
     89    let r, g, b;
     90
     91    if (s == 0)
     92        r = g = b = l; // achromatic
     93    else {
     94        let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
     95        let p = 2 * l - q;
     96        r = hue2rgb(p, q, h + 1/3);
     97        g = hue2rgb(p, q, h);
     98        b = hue2rgb(p, q, h - 1/3);
     99    }
     100
     101    return [r, g, b].map(function (n) Math.round(n * 255));
     102}
     103
     104function repeatString(times, string) {
     105    return Array(times + 1).join(string);
     106}
  • binaries/data/mods/public/gui/gamesetup/gamesetup.js

     
    17121712        var mapData = loadMapData(mapName);
    17131713        var mapSettings = (mapData && mapData.settings ? mapData.settings : {});
    17141714        var pData = mapSettings.PlayerData ? mapSettings.PlayerData[player] : {};
    17151715        var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[player] : {};
    17161716
    1717         color = rgbToGuiColor(getSetting(pData, pDefs, "Color"));
     1717        // Enlighten player color to improve readability
     1718        color = getSetting(pData, pDefs, "Color");
     1719        let [h, s, l] = ensureMinimumLightness(rgbToHsl(color.r, color.g, color.b));
     1720        let [r, g, b] = hslToRgb(h, s, l);
     1721        color = rgbToGuiColor({"r": r, "g": g, "b": b});
    17181722    }
    17191723
    17201724    var formatted;
    17211725    switch (msg.type)
    17221726    {
  • binaries/data/mods/public/gui/gamesetup/gamesetup.xml

     
    11<?xml version="1.0" encoding="utf-8"?>
    22
    33<objects>
    44
     5    <script file="gui/common/color.js"/>
    56    <script file="gui/common/network.js"/>
    67    <script file="gui/common/functions_civinfo.js"/>
    78    <script file="gui/common/functions_global_object.js"/>
    89    <script file="gui/common/functions_utility.js"/>
    910    <script file="gui/gamesetup/gamesetup.js"/>
  • binaries/data/mods/public/gui/lobby/lobby.js

     
    1616var g_modPrefix = "@";
    1717var g_joined = false;
    1818// Block spammers for 30 seconds.
    1919var SPAM_BLOCK_LENGTH = 30;
    2020
     21var g_ColorPresence = {
     22    "playing": "125 0 0",
     23    "gone": "229 76 13",
     24    "away": "229 76 13",
     25    "available": "0 219 0",
     26    "offline": "0 0 0",
     27    "unknown": "178 178 178"
     28};
     29//'waiting' games are highlighted in orange, 'running' in red, and 'init' in green.
     30var g_ColorGame = {
     31    "init": "0 219 0",
     32    "waiting": "255 127 0",
     33    "running": "219 0 0"
     34};
     35var g_ColorModerator = "0 219 0";
     36var g_ColorSystemMessage = "150 0 0";
     37
    2138////////////////////////////////////////////////////////////////////////////////////////////////
    2239
    2340function init(attribs)
    2441{
    2542    // Play menu music
     
    273290    if (role && caller == "lobbylist")
    274291    {
    275292        // Make the role uppercase.
    276293        role = role.charAt(0).toUpperCase() + role.slice(1);
    277294        if (role == "Moderator")
    278             role = '[color="0 125 0"]' + translate(role) + '[/color]';
     295            role = '[color="' + g_ColorModerator + '"]' + translate(role) + '[/color]';
    279296    }
    280297    else
    281298        role = "";
    282299
    283300    Engine.GetGUIObjectByName("usernameText").caption = user;
     
    457474    var c = 0;
    458475    for (var g of gameList)
    459476    {
    460477        if (!filterGame(g))
    461478        {
    462             // 'waiting' games are highlighted in orange, 'running' in red, and 'init' in green.
    463             let name = escapeText(g.name);
    464             if (g.state == 'init')
    465                 name = '[color="0 125 0"]' + name + '[/color]';
    466             else if (g.state == 'waiting')
    467                 name = '[color="255 127 0"]' + name + '[/color]';
    468             else
    469                 name = '[color="255 0 0"]' + name + '[/color]';
     479            let name = '[color="' + g_ColorGame[g.state] + '"]' + escapeText(g.name) + '[/color]';
    470480            list_name.push(name);
    471481            list_ip.push(g.ip);
    472482            list_mapName.push(translate(g.niceMapName));
    473483            list_mapSize.push(translatedMapSize(g.mapSize));
    474484            let idx = g_mapTypes.indexOf(g.mapType);
     
    504514 * @return Colorized versions of name, status, and rating.
    505515 */
    506516function formatPlayerListEntry(nickname, presence, rating)
    507517{
    508518    // Set colors based on player status
    509     var color;
    510     var status;
     519    let color = presence in g_ColorPresence ? g_ColorPresence[presence] : g_ColorPresence["unknown"];
     520    let status;
     521   
    511522    switch (presence)
    512523    {
    513524    case "playing":
    514         color = "125 0 0";
    515525        status = translate("Busy");
    516526        break;
    517527    case "gone":
    518528    case "away":
    519         color = "229 76 13";
    520529        status = translate("Away");
    521530        break;
    522531    case "available":
    523         color = "0 125 0";
    524532        status = translate("Online");
    525533        break;
    526534    case "offline":
    527         color = "0 0 0";
    528535        status = translate("Offline");
    529536        break;
    530537    default:
    531538        warn(sprintf("Unknown presence '%(presence)s'", { presence: presence }));
    532         color = "178 178 178";
    533539        status = translateWithContext("lobby presence", "Unknown");
    534540        break;
    535541    }
    536542    // Center the unrated symbol.
    537543    if (rating == "-")
     
    539545    var formattedStatus = '[color="' + color + '"]' + status + "[/color]";
    540546    var formattedRating = '[color="' + color + '"]' + rating + "[/color]";
    541547    var role = Engine.LobbyGetPlayerRole(nickname);
    542548    if (role == "moderator")
    543549        nickname = g_modPrefix + nickname;
    544     var formattedName = colorPlayerName(nickname);
     550    let formattedName = colorizePlayername(nickname);
    545551
    546552    // Push this player's name and status onto the list
    547553    return [formattedName, formattedStatus, formattedRating];
    548554}
    549555
     
    768774            break;
    769775        case "system":
    770776            switch (message.level)
    771777            {
    772778            case "standard":
    773                 addChatMessage({ "from": "system", "text": text, "color": "150 0 0" });
     779                addChatMessage({ "from": "system", "text": text, "color": g_ColorSystemMessage });
    774780                if (message.text == "disconnected")
    775781                {
    776782                    // Clear the list of games and the list of players
    777783                    updateGameList();
    778784                    updateLeaderboard();
     
    784790                {
    785791                    Engine.GetGUIObjectByName("hostButton").enabled = true;
    786792                }
    787793                break;
    788794            case "error":
    789                 addChatMessage({ "from": "system", "text": text, "color": "150 0 0" });
     795                addChatMessage({ "from": "system", "text": text, "color": g_ColorSystemMessage });
    790796                break;
    791797            case "internal":
    792798                switch (message.text)
    793799                {
    794800                case "gamelist updated":
     
    897903    if (!msg.datetime)
    898904        msg.datetime = null;
    899905
    900906    // Highlight local user's nick
    901907    if (msg.text.indexOf(g_Name) != -1 && g_Name != msg.from)
    902         msg.text = msg.text.replace(new RegExp('\\b' + '\\' + g_Name + '\\b', "g"), colorPlayerName(g_Name));
     908        msg.text = msg.text.replace(new RegExp('\\b' + '\\' + g_Name + '\\b', "g"), colorizePlayername(g_Name));
    903909
    904910    // Run spam test if it's not a historical message
    905911    if (!msg.datetime)
    906912        updateSpamMonitor(msg.from);
    907913    if (isSpam(msg.text, msg.from))
     
    938944 */
    939945function ircFormat(text, from, color, key, datetime)
    940946{
    941947    // Generate and apply color to uncolored names,
    942948    if (!color && from)
    943         var coloredFrom = colorPlayerName(from);
     949        var coloredFrom = colorizePlayername(from);
    944950    else if (color && from)
    945951        var coloredFrom = '[color="' + color + '"]' + from + "[/color]";
    946952
    947953    // Handle commands allowed past handleSpecialCommand.
    948954    if (text[0] == '/')
     
    10871093            stats[0] = 0;
    10881094        }
    10891095    }
    10901096
    10911097}
    1092 
    1093 /* Utilities */
    1094 // Generate a (mostly) unique color for this player based on their name.
    1095 // See http://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-jquery-javascript
    1096 function getPlayerColor(playername)
    1097 {
    1098     // Generate a probably-unique hash for the player name and use that to create a color.
    1099     var hash = 0;
    1100     for (var i = 0; i < playername.length; i++)
    1101         hash = playername.charCodeAt(i) + ((hash << 5) - hash);
    1102 
    1103     // First create the color in RGB then HSL, clamp the lightness so it's not too dark to read, and then convert back to RGB to display.
    1104     // The reason for this roundabout method is this algorithm can generate values from 0 to 255 for RGB but only 0 to 100 for HSL; this gives
    1105     // us much more variety if we generate in RGB. Unfortunately, enforcing that RGB values are a certain lightness is very difficult, so
    1106     // we convert to HSL to do the computation. Since our GUI code only displays RGB colors, we have to convert back.
    1107     var [h, s, l] = rgbToHsl(hash >> 24 & 0xFF, hash >> 16 & 0xFF, hash >> 8 & 0xFF);
    1108     return hslToRgb(h, s, Math.max(0.4, l)).join(" ");
    1109 }
    1110 
    1111 function repeatString(times, string) {
    1112     return Array(times + 1).join(string);
    1113 }
    1114 
    1115 // Some names are special and should always appear in certain colors.
    1116 var fixedColors = { "system": repeatString(7, "255.0.0."), "@WFGbot": repeatString(7, "255.24.24."),
    1117                     "pyrogenesis": repeatString(2, "97.0.0.") + repeatString(2, "124.0.0.") + "138.0.0." +
    1118                         repeatString(2, "174.0.0.") + repeatString(2, "229.40.0.") + repeatString(2, "243.125.15.") };
    1119 function colorPlayerName(playername)
    1120 {
    1121     var color = fixedColors[playername];
    1122     if (color) {
    1123     color = color.split(".");
    1124     return ('[color="' + playername.split("").map(function (c, i) color.slice(i * 3, i * 3 + 3).join(" ") + '"]' + c + '[/color][color="')
    1125                 .join("") + '"]').slice(0, -10);
    1126     }
    1127     return '[color="' + getPlayerColor(playername.replace(g_modPrefix, "")) + '"]' + playername + '[/color]';
    1128 }
    1129 
    1130 // Ensure `value` is between 0 and 1.
    1131 function clampColorValue(value)
    1132 {
    1133     return Math.abs(1 - Math.abs(value - 1));
    1134 }
    1135 
    1136 // See http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
    1137 function rgbToHsl(r, g, b)
    1138 {
    1139     r /= 255;
    1140     g /= 255;
    1141     b /= 255;
    1142     var max = Math.max(r, g, b), min = Math.min(r, g, b);
    1143     var h, s, l = (max + min) / 2;
    1144 
    1145     if (max == min)
    1146         h = s = 0; // achromatic
    1147     else
    1148     {
    1149         var d = max - min;
    1150         s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    1151         switch (max)
    1152         {
    1153             case r: h = (g - b) / d + (g < b ? 6 : 0); break;
    1154             case g: h = (b - r) / d + 2; break;
    1155             case b: h = (r - g) / d + 4; break;
    1156         }
    1157         h /= 6;
    1158     }
    1159 
    1160     return [h, s, l];
    1161 }
    1162 
    1163 function hslToRgb(h, s, l)
    1164 {
    1165     function hue2rgb(p, q, t)
    1166     {
    1167         if (t < 0) t += 1;
    1168         if (t > 1) t -= 1;
    1169         if (t < 1/6) return p + (q - p) * 6 * t;
    1170         if (t < 1/2) return q;
    1171         if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
    1172         return p;
    1173     }
    1174 
    1175     [h, s, l] = [h, s, l].map(clampColorValue);
    1176     var r, g, b;
    1177 
    1178     if (s == 0)
    1179         r = g = b = l; // achromatic
    1180     else {
    1181         var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
    1182         var p = 2 * l - q;
    1183         r = hue2rgb(p, q, h + 1/3);
    1184         g = hue2rgb(p, q, h);
    1185         b = hue2rgb(p, q, h - 1/3);
    1186     }
    1187 
    1188     return [r, g, b].map(function (n) Math.round(n * 255));
    1189 }
    1190 
    1191 (function () {
    1192 function hexToRgb(hex) {
    1193     return parseInt(hex.slice(0, 2), 16) + "." + parseInt(hex.slice(2, 4), 16) + "." + parseInt(hex.slice(4, 6), 16) + ".";
    1194 }
    1195 function r(times, hex) {
    1196     return repeatString(times, hexToRgb(hex));
    1197 }
    1198 
    1199 fixedColors["Twilight_Sparkle"] = r(2, "d19fe3") + r(2, "b689c8") + r(2, "a76bc2") +
    1200     r(4, "263773") + r(2, "131f46") + r(2, "662d8a") + r(2, "ed438a");
    1201 fixedColors["Applejack"] = r(3, "ffc261") + r(3, "efb05d") + r(3, "f26f31");
    1202 fixedColors["Rarity"] = r(1, "ebeff1") + r(1, "dee3e4") + r(1, "bec2c3") +
    1203     r(1, "83509f") + r(1, "4b2568") + r(1, "4917d6");
    1204 fixedColors["Rainbow_Dash"] = r(2, "ee4144") + r(1, "f37033") + r(1, "fdf6af") +
    1205     r(1, "62bc4d") + r(1, "1e98d3") + r(2, "672f89") + r(1, "9edbf9") +
    1206     r(1, "88c4eb") + r(1, "77b0e0") + r(1, "1e98d3");
    1207 fixedColors["Pinkie_Pie"] = r(2, "f3b6cf") + r(2, "ec9dc4") + r(4, "eb81b4") +
    1208     r(1, "ed458b") + r(1, "be1d77");
    1209 fixedColors["Fluttershy"] = r(2, "fdf6af") + r(2, "fee78f") + r(2, "ead463") +
    1210     r(2, "f3b6cf") + r(2, "eb81b4");
    1211 fixedColors["Sweetie_Belle"] = r(2, "efedee") + r(3, "e2dee3") + r(3, "cfc8d1") +
    1212     r(2, "b28dc0") + r(2, "f6b8d2") + r(1, "795b8a");
    1213 fixedColors["Apple_Bloom"] = r(2, "f4f49b") + r(2, "e7e793") + r(2, "dac582") +
    1214     r(2, "f46091") + r(2, "f8415f") + r(1, "c52451");
    1215 fixedColors["Scootaloo"] = r(2, "fbba64") + r(2, "f2ab56") + r(2, "f37003") +
    1216     r(2, "bf5d95") + r(1, "bf1f79");
    1217 fixedColors["Luna"] = r(1, "7ca7fa") + r(1, "5d6fc1") + r(1, "656cb9") + r(1, "393993");
    1218 fixedColors["Celestia"] = r(1, "fdfafc") + r(1, "f7eaf2") + r(1, "d99ec5") +
    1219     r(1, "00aec5") + r(1, "f7c6dc") + r(1, "98d9ef") + r(1, "ced7ed") + r(1, "fed17b");
    1220 })();
  • binaries/data/mods/public/gui/lobby/lobby.xml

     
    11<?xml version="1.0" encoding="utf-8"?>
    22
    33<objects>
    44    <script file="gui/common/functions_global_object.js"/>
    55    <script file="gui/common/functions_utility.js"/>
     6    <script file="gui/common/color.js"/>
    67    <script file="gui/common/timer.js"/>
    78    <script file="gui/common/music.js"/>
    8 
    99    <script file="gui/lobby/lobby.js"/>
    1010
    1111    <object type="image" style="ModernWindow" size="0 0 100% 100%" name="lobbyWindow">
    1212
    1313        <object style="ModernLabelText" type="text" size="50%-128 0%+4 50%+128 36">