Ticket #4554: resource_graph.patch

File resource_graph.patch, 62.6 KB (added by Toonijn, 3 years ago)

A first implementation of the resource graph and unit count

  • binaries/data/mods/public/gui/credits/texts/programming.json

     
    11{
    22    "Title": "Programming",
    33    "Content": [
    44        {
    55            "Title": "Programming managers",
    66            "List": [
    77                { "nick": "Acumen", "name": "Stuart Walpole" },
    88                { "nick": "Dak Lozar", "name": "Dave Loeser" },
    99                { "nick": "h20", "name": "Daniel Wilhelm" },
    1010                { "nick": "Janwas", "name": "Jan Wassenberg" },
    1111                { "nick": "Raj", "name": "Raj Sharma" }
    1212            ]
    1313        },
    1414        {
    1515            "Subtitle": "Special thanks to",
    1616            "List": [
    1717                { "nick": "leper", "name": "Georg Kilzer" },
    1818                { "nick": "Ykkrosh", "name": "Philip Taylor" }
    1919            ]
    2020        },
    2121        {
    2222            "List": [
    2323                { "nick": "01d55" },
    2424                { "nick": "aBothe", "name": "Alexander Bothe" },
    2525                { "nick": "Acumen", "name": "Stuart Walpole" },
    2626                { "nick": "adrian", "name": "Adrian Boguszewszki" },
    2727                { "name": "Adrian Fatol" },
    2828                { "nick": "AI-Amsterdam" },
    2929                { "nick": "Alan", "name": "Alan Kemp" },
    3030                { "nick": "Alex", "name": "Alexander Yakobovich" },
    3131                { "nick": "alpha123", "name": "Peter P. Cannici" },
    3232                { "nick": "Ampaex", "name": "Antonio Vazquez" },
    3333                { "name": "André Puel" },
    3434                { "nick": "andy5995", "name": "Andy Alt" },
    3535                { "nick": "Angen" },
    3636                { "nick": "Arfrever", "name": "Arfrever Frehtes Taifersar Arahesis" },
    3737                { "nick": "ArnH", "name": "Arno Hemelhof" },
    3838                { "nick": "Aurium", "name": "Aurélio Heckert" },
    3939                { "nick": "badmadblacksad", "name": "Martin F" },
    4040                { "nick": "badosu", "name": "Amadeus Folego" },
    4141                { "nick": "bb", "name": "Bouke Jansen" },
    4242                { "nick": "Ben", "name": "Ben Vinegar" },
    4343                { "nick": "Bird" },
    4444                { "nick": "Blue", "name": "Richard Welsh" },
    4545                { "nick": "bmwiedemann" },
    4646                { "nick": "boeseRaupe", "name": "Michael Kluge" },
    4747                { "nick": "bog_dan_ro", "name": "BogDan Vatra" },
    4848                { "nick": "Bonk", "name": "Christopher Ebbert" },
    4949                { "nick": "Boudica" },
    5050                { "nick": "Caius", "name": "Lars Kemmann" },
    5151                { "nick": "Calefaction", "name": "Matt Holmes" },
    5252                { "nick": "Calvinh", "name": "Carl-Johan Höiby" },
    5353                { "nick": "causative", "name": "Bart Parkis" },
    5454                { "name": "Cédric Houbart" },
    5555                { "nick": "Chakakhan", "name": "Kenny Long" },
    5656                { "nick": "Clockwork-Muse", "name": "Stephen A. Imhoff" },
    5757                { "nick": "Cracker78", "name": "Chad Heim" },
    5858                { "nick": "Crynux", "name": "Stephen J. Fewer" },
    5959                { "nick": "cwprogger" },
    6060                { "nick": "cygal", "name": "Quentin Pradet" },
    6161                { "nick": "Dak Lozar", "name": "Dave Loeser" },
    6262                { "nick": "dalerank", "name": "Sergey Kushnirenko" },
    6363                { "nick": "dan", "name": "Dan Strandberg" },
    6464                { "nick": "DanCar", "name": "Daniel Cardenas" },
    6565                { "nick": "danger89", "name": "Melroy van den Berg" },
    6666                { "name": "Daniel Trevitz" },
    6767                { "nick": "Dariost", "name": "Dario Ostuni" },
    6868                { "nick": "Dave", "name": "David Protasowski" },
    6969                { "nick": "dax", "name": "Dacian Fiordean" },
    7070                { "nick": "deebee", "name": "Deepak Anthony" },
    7171                { "nick": "Deiz" },
    7272                { "nick": "Dietger", "name": "Dietger van Antwerpen" },
    7373                { "nick": "DigitalSeraphim", "name": "Nick Owens" },
    7474                { "nick": "dp304" },
    7575                { "nick": "dpiquet", "name": "Damien Piquet" },
    7676                { "nick": "dumbo" },
    7777                { "nick": "Dunedan", "name": "Daniel Roschka" },
    7878                { "nick": "dvangennip", "name": "Doménique" },
    7979                { "nick": "Echelon9", "name": "Rhys Kidd" },
    8080                { "nick": "echotangoecho" },
    8181                { "nick": "eihrul", "name": "Lee Salzman" },
    8282                { "nick": "elexis", "name": "Alexander Heinsius" },
    8383                { "nick": "EmjeR", "name": "Matthijs de Rijk" },
    8484                { "nick": "EMontana" },
    8585                { "nick": "ericb" },
    8686                { "nick": "evanssthomas", "name": "Evans Thomas" },
    8787                { "nick": "Evulant", "name": "Alexander S." },
    8888                { "nick": "fabio", "name": "Fabio Pedretti" },
    8989                { "nick": "falsevision", "name": "Mahdi Khodadadifard" },
    9090                { "nick": "fatherbushido", "name": "Nicolas Tisserand" },
    9191                { "nick": "fcxSanya", "name": "Alexander Olkhovskiy" },
    9292                { "nick": "FeXoR", "name": "Florian Finke" },
    9393                { "nick": "Fire Giant", "name": "Malte Schwarzkopf" },
    9494                { "name": "Fork AD" },
    9595                { "nick": "fpre", "name": "Frederick Stallmeyer" },
    9696                { "nick": "Freagarach" },
    9797                { "nick": "freenity", "name": "Anton Galitch" },
    9898                { "nick": "Gallaecio", "name": "Adrián Chaves" },
    9999                { "nick": "gbish (aka Iny)", "name": "Grant Bishop" },
    100100                { "nick": "Gee", "name": "Gustav Larsson" },
    101101                { "nick": "Gentz", "name": "Hal Gentz" },
    102102                { "nick": "gerbilOFdoom" },
    103103                { "nick": "godlikeldh" },
    104104                { "nick": "greybeard", "name": "Joe Cocovich" },
    105105                { "nick": "grillaz" },
    106106                { "nick": "Grugnas", "name": "Giuseppe Tranchese" },
    107107                { "nick": "gudo" },
    108108                { "nick": "Guuts", "name": "Matthew Guttag" },
    109109                { "nick": "h20", "name": "Daniel Wilhelm" },
    110110                { "nick": "Hannibal_Barca", "name": "Clive Juhász S." },
    111111                { "nick": "Haommin" },
    112112                { "nick": "happyconcepts", "name": "Ben Bird" },
    113113                { "nick": "historic_bruno", "name": "Ben Brian" },
    114114                { "nick": "idanwin" },
    115115                { "nick": "Imarok", "name": "J. S." },
    116116                { "nick": "Inari" },
    117117                { "nick": "infyquest", "name": "Vijay Kiran Kamuju" },
    118118                { "nick": "irishninja", "name": "Brian Broll" },
    119119                { "nick": "IronNerd", "name": "Matthew McMullan" },
    120120                { "nick": "Itms", "name": "Nicolas Auvray" },
    121121                { "nick": "Jaison", "name": "Marco tom Suden" },
    122122                { "nick": "jammus", "name": "James Scott" },
    123123                { "nick": "Janwas", "name": "Jan Wassenberg" },
    124124                { "nick": "javiergodas", "name": "Javier Godas Vieitez" },
    125125                { "nick": "Jgwman" },
    126126                { "nick": "JonBaer", "name": "Jon Baer" },
    127127                { "nick": "Josh", "name": "Joshua J. Bakita" },
    128128                { "nick": "joskar", "name": "Johnny Oskarsson" },
    129129                { "nick": "jP_wanN", "name": "Jonas Platte" },
    130130                { "nick": "Jubalbarca", "name": "James Baillie" },
    131131                { "nick": "JubJub", "name": "Sebastian Vetter" },
    132132                { "nick": "jurgemaister" },
    133133                { "nick": "kabzerek", "name": "Grzegorz Kabza" },
    134134                { "nick": "Kai", "name": "Kai Chen" },
    135135                { "name": "Kareem Ergawy" },
    136136                { "nick": "kevmo", "name": "Kevin Caffrey" },
    137137                { "nick": "kezz", "name": "Graeme Kerry" },
    138138                { "nick": "kingadami", "name": "Adam Winsor" },
    139139                { "nick": "kingbasil", "name": "Giannis Fafalios" },
    140140                { "nick": "Krinkle", "name": "Timo Tijhof" },
    141141                { "nick": "lafferjm", "name": "Justin Lafferty" },
    142142                { "nick": "LeanderH", "name": "Leander Hemelhof" },
    143143                { "nick": "leper", "name": "Georg Kilzer" },
    144144                { "nick": "Link Mauve", "name": "Emmanuel Gil Peyrot" },
    145145                { "nick": "LittleDev" },
    146146                { "nick": "livingaftermidnight", "name": "Will Dull" },
    147147                { "nick": "lonehawk", "name": "Vignesh Krishnan" },
    148148                { "nick": "Louhike" },
    149149                { "nick": "lsdh" },
    150150                { "nick": "Ludovic", "name": "Ludovic Rousseau" },
    151151                { "nick": "luiko", "name": "Luis Carlos Garcia Barajas" },
    152152                { "nick": "m0l0t0ph", "name": "Christoph Gielisch" },
    153153                { "nick": "madmax", "name": "Abhijit Nandy" },
    154154                { "nick": "madpilot", "name": "Guido Falsi" },
    155155                { "nick": "mammadori", "name": "Marco Amadori" },
    156156                { "nick": "markcho" },
    157157                { "nick": "MarkT", "name": "Mark Thompson" },
    158158                { "nick": "Markus" },
    159159                { "nick": "Mate-86", "name": "Mate Kovacs" },
    160160                { "nick": "Matei", "name": "Matei Zaharia" },
    161161                { "nick": "MattDoerksen", "name": "Matt Doerksen" },
    162162                { "nick": "mattlott", "name": "Matt Lott" },
    163163                { "nick": "maveric", "name": "Anton Protko" },
    164164                { "nick": "Micnasty", "name": "Travis Gorkin" },
    165165                { "name": "Mikołaj \"Bajter\" Korcz" },
    166166                { "nick": "mimo" },
    167167                { "nick": "mk12", "name": "Mitchell Kember" },
    168168                { "nick": "mmayfield45", "name": "Michael Mayfield" },
    169169                { "nick": "mmoanis", "name": "Mohamed Moanis" },
    170170                { "nick": "Molotov", "name": "Dario Alvarez" },
    171171                { "nick": "mpmoreti", "name": "Marcos Paulo Moreti" },
    172172                { "nick": "mreiland", "name": "Michael Reiland" },
    173173                { "nick": "myconid" },
    174174                { "nick": "nani", "name": "S. N." },
    175175                { "nick": "nd3c3nt", "name": "Gavin Fowler" },
    176176                { "nick": "nephele" },
    177177                { "nick": "Nescio" },
    178178                { "nick": "niektb", "name": "Niek ten Brinke" },
    179179                { "nick": "nikagra", "name": "Mikita Hradovich" },
    180180                { "nick": "njm" },
    181181                { "nick": "NoMonkey", "name": "John Mena" },
    182182                { "nick": "norsnor" },
    183183                { "nick": "notpete", "name": "Rich Cross" },
    184184                { "nick": "nwtour" },
    185185                { "nick": "odoaker", "name": "Ágoston Sipos" },
    186186                { "nick": "Offensive ePeen", "name": "Jared Ryan Bills" },
    187187                { "nick": "Ols", "name": "Oliver Whiteman" },
    188188                { "nick": "olsner", "name": "Simon Brenner" },
    189189                { "nick": "OptimusShepard", "name": "Pirmin Stanglmeier" },
    190190                { "nick": "otero" },
    191191                { "nick": "Palaxin", "name": "David A. Freitag" },
    192192                { "name": "Paul Withers" },
    193193                { "nick": "paulobezerr", "name": "Paulo George Gomes Bezerra" },
    194194                { "nick": "pcpa", "name": "Paulo Andrade" },
    195195                { "nick": "Pendingchaos" },
    196196                { "nick": "PeteVasi", "name": "Pete Vasiliauskas" },
    197197                { "nick": "pilino1234" },
    198198                { "nick": "PingvinBetyar", "name": "Schronk Tamás" },
    199199                { "nick": "plugwash", "name": "Peter Michael Green" },
    200200                { "nick": "Polakrity" },
    201201                { "nick": "Poya", "name": "Poya Manouchehri" },
    202202                { "nick": "prefect", "name": "Nicolai Hähnle" },
    203203                { "nick": "Prodigal Son" },
    204204                { "nick": "pstumpf", "name": "Pascal Stumpf" },
    205205                { "nick": "pyrolink", "name": "Andrew Decker" },
    206206                { "nick": "quantumstate", "name": "Jonathan Waller" },
    207207                { "nick": "QuickShot", "name": "Walter Krawec" },
    208208                { "nick": "quonter" },
    209209                { "nick": "qwertz" },
    210210                { "nick": "Radagast" },
    211211                { "nick": "Raj", "name": "Raj Sharma" },
    212212                { "nick": "ramtzok1", "name": "Ram" },
    213213                { "nick": "rapidelectron", "name": "Christian Weihsbach" },
    214214                { "nick": "r-a-sattarov", "name": "Ramil Sattarov" },
    215215                { "nick": "RedFox", "name": "Jorma Rebane" },
    216216                { "nick": "RefinedCode" },
    217217                { "nick": "Riemer" },
    218218                { "name": "Rolf Sievers" },
    219219                { "nick": "s0600204", "name": "Matthew Norwood" },
    220220                { "nick": "sacha_vrand", "name": "Sacha Vrand" },
    221221                { "nick": "SafaAlfulaij" },
    222222                { "name": "Samuel Guarnieri" },
    223223                { "nick": "Samulis", "name": "Sam Gossner" },
    224224                { "nick": "Sandarac" },
    225225                { "nick": "sanderd17", "name": "Sander Deryckere" },
    226226                { "nick": "sathyam", "name": "Sathyam Vellal" },
    227227                { "nick": "sbirmi", "name": "Sharad Birmiwal" },
    228228                { "nick": "sbte", "name": "Sven Baars" },
    229229                { "nick": "scroogie", "name": "André Gemünd" },
    230230                { "nick": "scythetwirler", "name": "Casey X." },
    231231                { "nick": "serveurix" },
    232232                { "nick": "Shane", "name": "Shane Grant" },
    233233                { "nick": "shh" },
    234234                { "nick": "Silk", "name": "Josh Godsiff" },
    235235                { "nick": "silure" },
    236236                { "nick": "Simikolon", "name": "Yannick & Simon" },
    237237                { "nick": "smiley", "name": "M. L." },
    238238                { "nick": "Spahbod", "name": "Omid Davoodi" },
    239239                { "nick": "Stan", "name": "Stanislas Dolcini" },
    240240                { "nick": "Stefan" },
    241241                { "nick": "StefanBruens", "name": "Stefan Brüns" },
    242242                { "nick": "stilz", "name": "Sławomir Zborowski" },
    243243                { "nick": "stwf", "name": "Steven Fuchs" },
    244244                { "nick": "svott", "name": "Sven Ott" },
    245245                { "nick": "t4nk004" },
    246246                { "nick": "tau" },
    247247                { "nick": "tbm", "name": "Martin Michlmayr" },
    248248                { "nick": "Teiresias" },
    249249                { "nick": "temple" },
    250250                { "nick": "texane" },
    251251                { "nick": "thamlett", "name": "Timothy Hamlett" },
    252252                { "nick": "thedrunkyak", "name": "Dan Fuhr" },
    253253                { "nick": "Tobbi" },
     254                { "nick": "Toonijn", "name": "Toon Baeyens" },
    254255                { "nick": "TrinityDeath", "name": "Jethro Lu" },
    255256                { "nick": "triumvir", "name": "Corin Schedler" },
    256257                { "nick": "trompetin17", "name": "Juan Guillermo" },
    257258                { "nick": "user1", "name": "A. C." },
    258259                { "nick": "usey11" },
    259260                { "nick": "vincent_c", "name": "Vincent Cheng" },
    260261                { "nick": "vinhig", "name": "Vincent Higginson" },
    261262                { "nick": "vladislavbelov", "name": "Vladislav Belov" },
    262263                { "nick": "voroskoi" },
    263264                { "nick": "vts", "name": "Jeroen DR" },
    264265                { "nick": "wacko", "name": "Andrew Spiering" },
    265266                { "nick": "WhiteTreePaladin", "name": "Brian Ashley" },
    266267                { "nick": "wraitii", "name": "Lancelot de Ferrière le Vayer" },
    267268                { "nick": "Xentelian", "name": "Mark Strawson" },
    268269                { "nick": "Xienen", "name": "Dayle Flowers" },
    269270                { "nick": "xtizer", "name": "Matt Green" },
    270271                { "nick": "yashi", "name": "Yasushi Shoji" },
    271272                { "nick": "Ykkrosh", "name": "Philip Taylor" },
    272273                { "nick": "Yves" },
    273274                { "nick": "Zeusthor", "name": "Jeffrey Tavares" },
    274275                { "nick": "zoot" },
    275276                { "nick": "zsol", "name": "Zsolt Dollenstein" },
    276277                { "nick": "ztamas", "name": "Tamas Zolnai" },
    277278                { "nick": "Zyi", "name": "Charles De Meulenaer" }
    278279            ]
    279280        }
    280281    ]
    281282}
  • binaries/data/mods/public/gui/summary/counters.js

     
    11var g_TeamHelperData = [];
    22
    33function calculatePercent(divident, divisor)
    44{
    55    return { "percent": divisor ? Math.floor(100 * divident / divisor) : 0 };
    66}
    77
    88function calculateRatio(divident, divisor)
    99{
    1010    return divident ? +((divident / divisor).toFixed(2)) : 0;
    1111}
    1212
    1313function formatSummaryValue(values)
    1414{
    1515    if (typeof values != "object")
    1616        return values === Infinity ? g_InfinitySymbol : values;
    1717
    1818    let ret = "";
    1919    for (let type in values)
    20         ret += (g_SummaryTypes[type].color ?
    21             coloredText(values[type], g_SummaryTypes[type].color) :
    22             values[type]) + g_SummaryTypes[type].postfix;
     20        if(g_SummaryTypes[type].hide !== true)
     21            ret += (g_SummaryTypes[type].color ?
     22                coloredText(values[type], g_SummaryTypes[type].color) :
     23                values[type]) + g_SummaryTypes[type].postfix;
    2324    return ret;
    2425}
    2526
    2627function getPlayerValuesPerTeam(team, index, type, counters, headings)
    2728{
    2829    let fn = counters[headings.map(heading => heading.identifier).indexOf(type) - 1].fn;
    2930    return g_Teams[team].map(player => fn(g_GameData.sim.playerStates[player], index, type));
    3031}
    3132
    3233function updateCountersPlayer(playerState, counters, headings, idGUI, index)
    3334{
    3435    for (let n in counters)
    3536    {
    3637        let fn = counters[n].fn;
    3738        Engine.GetGUIObjectByName(idGUI + "[" + n + "]").caption =
    3839            formatSummaryValue(fn && fn(playerState, index, headings[+n + 1].identifier));
    3940    }
    4041}
    4142
    4243function updateCountersTeam(teamFn, counters, headings, index)
    4344{
    4445    for (let team in g_Teams)
    4546    {
    4647        if (team == -1)
    4748            continue;
    4849
    4950        for (let n in counters)
    5051            Engine.GetGUIObjectByName("valueDataTeam[" + team + "][" + n + "]").caption =
    5152                formatSummaryValue(teamFn(team, index, headings[+n + 1].identifier, counters, headings));
    5253    }
    5354}
    5455
    5556/**
    5657 * Add two objects property-wise and writes the result to obj1.
    5758 * So summaryAddObject([1, 2], [7, 42]) will result in [8, 44] and
    5859 * summaryAddObject({ "f": 3, "o", 5 }, { "f": -1, "o", 42 }) will result in { "f": 2, "o", 47 }.
    5960 *
    6061 * @param {Object} obj1 - First summand object. This will be set to the sum of both.
    6162 * @param {Object} obj2 - Second summand object.
    6263 */
    6364function summaryAddObject(obj1, obj2)
    6465{
    6566    for (let p in obj1)
    6667        obj1[p] += obj2[p];
    6768}
    6869
    6970/**
    7071 * The sum of all elements of an array. So summaryArraySum([1, 2]) will be 3 and
    7172 * summaryArraySum([{ "f": 3, "o", 5 }, { "f": -1, "o", 42 }]) will be { "f": 2, "o", 47 }.
    7273 *
    7374 * @param {Array} array - The array to sum up.
    7475 * @returns the sum of all elements.
    7576 */
    7677function summaryArraySum(array)
    7778{
    7879    return array.reduce((sum, val) => {
    7980        if (typeof sum != "object")
    8081            return sum + val;
    8182        summaryAddObject(sum, val);
    8283        return sum;
    8384    });
    8485}
    8586
    8687function calculateTeamCounterDataHelper()
    8788{
    8889    for (let i = 0; i < g_PlayerCount; ++i)
    8990    {
    9091        let playerState = g_GameData.sim.playerStates[i + 1];
    9192
    9293        if (!g_TeamHelperData[playerState.team])
    9394        {
    9495            g_TeamHelperData[playerState.team] = {};
    9596            for (let value of ["food", "vegetarianFood", "femaleCitizen", "worker", "enemyUnitsKilled",
    9697                               "unitsLost", "mapControl", "mapControlPeak",
    9798                               "mapExploration", "totalBought", "totalSold"])
    9899                g_TeamHelperData[playerState.team][value] = new Array(playerState.sequences.time.length).fill(0);
    99100        }
    100101
    101102        summaryAddObject(g_TeamHelperData[playerState.team].food, playerState.sequences.resourcesGathered.food);
    102103        summaryAddObject(g_TeamHelperData[playerState.team].vegetarianFood, playerState.sequences.resourcesGathered.vegetarianFood);
    103104
    104105        summaryAddObject(g_TeamHelperData[playerState.team].femaleCitizen, playerState.sequences.unitsTrained.FemaleCitizen);
    105106        summaryAddObject(g_TeamHelperData[playerState.team].worker, playerState.sequences.unitsTrained.Worker);
    106107
    107108        summaryAddObject(g_TeamHelperData[playerState.team].enemyUnitsKilled, playerState.sequences.enemyUnitsKilled.total);
    108109        summaryAddObject(g_TeamHelperData[playerState.team].unitsLost, playerState.sequences.unitsLost.total);
    109110
    110111        g_TeamHelperData[playerState.team].mapControl = playerState.sequences.teamPercentMapControlled;
    111112        g_TeamHelperData[playerState.team].mapControlPeak = playerState.sequences.teamPeakPercentMapControlled;
    112113
    113114        g_TeamHelperData[playerState.team].mapExploration = playerState.sequences.teamPercentMapExplored;
    114115
    115116        for (let type in playerState.sequences.resourcesBought)
    116117            summaryAddObject(g_TeamHelperData[playerState.team].totalBought, playerState.sequences.resourcesBought[type]);
    117118
    118119        for (let type in playerState.sequences.resourcesSold)
    119120            summaryAddObject(g_TeamHelperData[playerState.team].totalSold, playerState.sequences.resourcesSold[type]);
    120121    }
    121122}
    122123
    123124/**
    124125 * Keep this in sync with the score computation in session/ for the lobby rating reports!
    125126 */
    126127function calculateEconomyScore(playerState, index)
    127128{
    128129    let total = 0;
    129130
    130131    // Notice that this skips the vegetarianFood property of resourcesGathered
    131132    for (let type of g_ResourceData.GetCodes())
    132133        total += playerState.sequences.resourcesGathered[type][index];
    133134
    134135    total += playerState.sequences.tradeIncome[index];
    135136    return Math.round(total / 10);
    136137}
    137138
    138139/**
    139140 * Keep this in sync with the score computation in session/ for the lobby rating reports!
    140141 */
    141142function calculateMilitaryScore(playerState, index)
    142143{
    143144    return Math.round((playerState.sequences.enemyUnitsKilledValue[index] +
    144145        playerState.sequences.unitsCapturedValue[index] +
    145146        playerState.sequences.enemyBuildingsDestroyedValue[index] +
    146147        playerState.sequences.buildingsCapturedValue[index]) / 10);
    147148}
    148149
    149150/**
    150151 * Keep this in sync with the score computation in session/ for the lobby rating reports!
    151152 */
    152153function calculateExplorationScore(playerState, index)
    153154{
    154155    return playerState.sequences.percentMapExplored[index] * 10;
    155156}
    156157
    157158/**
    158159 * Keep this in sync with the score computation in session/ for the lobby rating reports!
    159160 */
    160161 function calculateScoreTotal(playerState, index)
    161162{
    162163    return calculateEconomyScore(playerState, index) +
    163164        calculateMilitaryScore(playerState, index) +
    164165        calculateExplorationScore(playerState, index);
    165166}
    166167
    167168function calculateScoreTeam(team, index, type, counters, headings)
    168169{
    169170    if (type == "explorationScore")
    170171        return g_TeamHelperData[team].mapExploration[index] * 10;
    171172    if (type == "totalScore")
    172173        return ["economyScore", "militaryScore", "explorationScore"].map(
    173174            t => calculateScoreTeam(team, index, t, counters, headings)).reduce(
    174175            (sum, value) => sum + value);
    175176
    176177    return summaryArraySum(getPlayerValuesPerTeam(team, index, type, counters, headings));
    177178}
    178179
    179180function calculateBuildings(playerState, index, type)
    180181{
    181182    return {
    182183        "constructed": playerState.sequences.buildingsConstructed[type][index],
    183184        "destroyed": playerState.sequences.enemyBuildingsDestroyed[type][index],
    184185        "captured": playerState.sequences.buildingsCaptured[type][index],
    185186        "lost": playerState.sequences.buildingsLost[type][index]
    186187    };
    187188}
    188189
    189190function calculateBuildingsTeam(team, index, type, counters, headings)
    190191{
    191192    return summaryArraySum(getPlayerValuesPerTeam(team, index, type, counters, headings));
    192193}
    193194
    194195function calculateUnitsTeam(team, index, type, counters, headings)
    195196{
    196197    return summaryArraySum(getPlayerValuesPerTeam(team, index, type, counters, headings));
    197198}
    198199
    199200function calculateUnitsWithCaptured(playerState, index, type)
    200201{
    201202    return {
     203        "alive": playerState.sequences.unitsAlive[type][index],
    202204        "trained": playerState.sequences.unitsTrained[type][index],
    203205        "killed": playerState.sequences.enemyUnitsKilled[type][index],
    204206        "captured": playerState.sequences.unitsCaptured[type][index],
    205207        "lost": playerState.sequences.unitsLost[type][index]
    206208    };
    207209}
    208210
    209211function calculateUnits(playerState, index, type)
    210212{
    211213    return {
     214        "alive": playerState.sequences.unitsAlive[type][index],
    212215        "trained": playerState.sequences.unitsTrained[type][index],
    213216        "killed": playerState.sequences.enemyUnitsKilled[type][index],
    214217        "lost": playerState.sequences.unitsLost[type][index]
    215218    };
    216219}
    217220
    218221function calculateResources(playerState, index, type)
    219222{
    220223    return {
     224        "count": playerState.sequences.resourcesCount[type][index],
    221225        "gathered": playerState.sequences.resourcesGathered[type][index],
    222226        "used": playerState.sequences.resourcesUsed[type][index] - playerState.sequences.resourcesSold[type][index]
    223227    };
    224228}
    225229
    226230function calculateTotalResources(playerState, index)
    227231{
    228232    let totalGathered = 0;
    229233    let totalUsed = 0;
     234    let totalCount = 0;
    230235
    231236    for (let type of g_ResourceData.GetCodes())
    232237    {
     238        totalCount += playerState.sequences.resourcesCount[type][index];
    233239        totalGathered += playerState.sequences.resourcesGathered[type][index];
    234240        totalUsed += playerState.sequences.resourcesUsed[type][index] - playerState.sequences.resourcesSold[type][index];
    235241    }
    236242
    237     return { "gathered": totalGathered, "used": totalUsed };
     243    return { "count": totalCount, "gathered": totalGathered, "used": totalUsed };
    238244}
    239245
    240246function calculateTreasureCollected(playerState, index)
    241247{
    242248    return playerState.sequences.treasuresCollected[index];
    243249}
    244250
    245251function calculateLootCollected(playerState, index)
    246252{
    247253    return playerState.sequences.lootCollected[index];
    248254}
    249255
    250256function calculateTributeSent(playerState, index)
    251257{
    252258    return {
    253259        "sent": playerState.sequences.tributesSent[index],
    254260        "received": playerState.sequences.tributesReceived[index]
    255261    };
    256262}
    257263
    258264function calculateLivestockTrained(playerState, index)
    259265{
    260266    return playerState.sequences.unitsTrained.Domestic[index];
    261267}
    262268
    263269function calculateResourcesTeam(team, index, type, counters, headings)
    264270{
    265271    return summaryArraySum(getPlayerValuesPerTeam(team, index, type, counters, headings));
    266272}
    267273
    268274function calculateResourceExchanged(playerState, index, type)
    269275{
    270276    return {
    271277        "bought": playerState.sequences.resourcesBought[type][index],
    272278        "sold": playerState.sequences.resourcesSold[type][index]
    273279    };
    274280}
    275281
    276282function calculateBarterEfficiency(playerState, index)
    277283{
    278284    let totalBought = 0;
    279285    let totalSold = 0;
    280286
    281287    for (let type in playerState.sequences.resourcesBought)
    282288        totalBought += playerState.sequences.resourcesBought[type][index];
    283289
    284290    for (let type in playerState.sequences.resourcesSold)
    285291        totalSold += playerState.sequences.resourcesSold[type][index];
    286292
    287293    return calculatePercent(totalBought, totalSold);
    288294}
    289295
    290296function calculateTradeIncome(playerState, index)
    291297{
    292298    return playerState.sequences.tradeIncome[index];
    293299}
    294300
    295301function calculateMarketTeam(team, index, type, counters, headings)
    296302{
    297303    if (type == "barterEfficency")
    298304        return calculatePercent(g_TeamHelperData[team].totalBought[index], g_TeamHelperData[team].totalSold[index]);
    299305
    300306    return summaryArraySum(getPlayerValuesPerTeam(team, index, type, counters, headings));
    301307}
    302308
    303309function calculateVegetarianRatio(playerState, index)
    304310{
    305311    return calculatePercent(
    306312        playerState.sequences.resourcesGathered.vegetarianFood[index],
    307313        playerState.sequences.resourcesGathered.food[index]);
    308314}
    309315
    310316function calculateFeminization(playerState, index)
    311317{
    312318    return calculatePercent(
    313319        playerState.sequences.unitsTrained.FemaleCitizen[index],
    314320        playerState.sequences.unitsTrained.Worker[index]);
    315321}
    316322
    317323function calculateKillDeathRatio(playerState, index)
    318324{
    319325    return calculateRatio(
    320326        playerState.sequences.enemyUnitsKilled.total[index],
    321327        playerState.sequences.unitsLost.total[index]);
    322328}
    323329
    324330function calculateMapExploration(playerState, index)
    325331{
    326332    return { "percent": playerState.sequences.percentMapExplored[index] };
    327333}
    328334
    329335function calculateMapFinalControl(playerState, index)
    330336{
    331337    return { "percent": playerState.sequences.percentMapControlled[index] };
    332338}
    333339
    334340function calculateMapPeakControl(playerState, index)
    335341{
    336342    return { "percent": playerState.sequences.peakPercentMapControlled[index] };
    337343}
    338344
    339345function calculateMiscellaneousTeam(team, index, type, counters, headings)
    340346{
    341347    if (type == "vegetarianRatio")
    342348        return calculatePercent(g_TeamHelperData[team].vegetarianFood[index], g_TeamHelperData[team].food[index]);
    343349
    344350    if (type == "feminization")
    345351        return calculatePercent(g_TeamHelperData[team].femaleCitizen[index], g_TeamHelperData[team].worker[index]);
    346352
    347353    if (type == "killDeath")
    348354        return calculateRatio(g_TeamHelperData[team].enemyUnitsKilled[index], g_TeamHelperData[team].unitsLost[index]);
    349355
    350356    if (type == "bribes")
    351357        return summaryArraySum(getPlayerValuesPerTeam(team, index, type, counters, headings));
    352358
    353359    return { "percent": g_TeamHelperData[team][type][index] };
    354360}
    355361
    356362function calculateBribes(playerState, index, type)
    357363{
    358364    return {
    359365        "succeeded": playerState.sequences.successfulBribes[index],
    360366        "failed": playerState.sequences.failedBribes[index]
    361367    };
    362368}
  • binaries/data/mods/public/gui/summary/summary.js

     
    11const g_CivData = loadCivData(false, false);
    22
    33var g_ScorePanelsData;
    44
    55var g_MaxHeadingTitle = 9;
    66var g_LongHeadingWidth = 250;
    77var g_PlayerBoxYSize = 40;
    88var g_PlayerBoxGap = 2;
    99var g_PlayerBoxAlpha = 50;
    1010var g_TeamsBoxYStart = 40;
    1111
    1212var g_TypeColors = {
    1313    "blue": "196 198 255",
    1414    "green": "201 255 200",
    1515    "red": "255 213 213",
    1616    "yellow": "255 255 157"
    1717};
    1818
    1919/**
    2020 * Colors, captions and format used for units, structures, etc. types
    2121 */
    2222var g_SummaryTypes = {
    2323    "percent": {
    2424        "color": "",
    2525        "caption": "%",
    2626        "postfix": "%"
    2727    },
    2828    "trained": {
    2929        "color": g_TypeColors.green,
    3030        "caption": translate("Trained"),
    3131        "postfix": " / "
    3232    },
    3333    "constructed": {
    3434        "color": g_TypeColors.green,
    3535        "caption": translate("Constructed"),
    3636        "postfix": " / "
    3737    },
    3838    "gathered": {
    3939        "color": g_TypeColors.green,
    4040        "caption": translate("Gathered"),
    4141        "postfix": " / "
    4242    },
     43    "alive": {
     44        "caption": translate("Alive"),
     45        "hide": true
     46    },
     47    "count": {
     48        "caption": translate("Count"),
     49        "hide": true
     50    },
    4351    "sent": {
    4452        "color": g_TypeColors.green,
    4553        "caption": translate("Sent"),
    4654        "postfix": " / "
    4755    },
    4856    "bought": {
    4957        "color": g_TypeColors.green,
    5058        "caption": translate("Bought"),
    5159        "postfix": " / "
    5260    },
    5361    "income": {
    5462        "color": g_TypeColors.green,
    5563        "caption": translate("Income"),
    5664        "postfix": " / "
    5765    },
    5866    "captured": {
    5967        "color": g_TypeColors.yellow,
    6068        "caption": translate("Captured"),
    6169        "postfix": " / "
    6270    },
    6371    "succeeded": {
    6472        "color": g_TypeColors.green,
    6573        "caption": translate("Succeeded"),
    6674        "postfix": " / "
    6775    },
    6876    "destroyed": {
    6977        "color": g_TypeColors.blue,
    7078        "caption": translate("Destroyed"),
    7179        "postfix": "\n"
    7280    },
    7381    "killed": {
    7482        "color": g_TypeColors.blue,
    7583        "caption": translate("Killed"),
    7684        "postfix": "\n"
    7785    },
    7886    "lost": {
    7987        "color": g_TypeColors.red,
    8088        "caption": translate("Lost"),
    8189        "postfix": ""
    8290    },
    8391    "used": {
    8492        "color": g_TypeColors.red,
    8593        "caption": translate("Used"),
    8694        "postfix": ""
    8795    },
    8896    "received": {
    8997        "color": g_TypeColors.red,
    9098        "caption": translate("Received"),
    9199        "postfix": ""
    92100    },
    93101    "sold": {
    94102        "color": g_TypeColors.red,
    95103        "caption": translate("Sold"),
    96104        "postfix": ""
    97105    },
    98106    "outcome": {
    99107        "color": g_TypeColors.red,
    100108        "caption": translate("Outcome"),
    101109        "postfix": ""
    102110    },
    103111    "failed": {
    104112        "color": g_TypeColors.red,
    105113        "caption": translate("Failed"),
    106114        "postfix": ""
    107115    }
    108116};
    109117
    110118// Translation: Unicode encoded infinity symbol indicating a division by zero in the summary screen.
    111119var g_InfinitySymbol = translate("\u221E");
    112120
    113121var g_Teams = [];
    114122
    115123var g_PlayerCount;
    116124
    117125var g_GameData;
    118126var g_ResourceData = new Resources();
    119127
    120128/**
    121129 * Selected chart indexes.
    122130 */
    123131var g_SelectedChart = {
    124132    "category": [0, 0],
    125133    "value": [0, 1],
    126134    "type": [0, 0]
    127135};
    128136
    129137function init(data)
    130138{
    131139    initSummaryData(data);
    132140    initGUISummary();
    133141}
    134142
    135143function initSummaryData(data)
    136144{
    137145    g_GameData = data;
    138146    g_ScorePanelsData = getScorePanelsData();
    139147
    140148    let teamCharts = false;
    141149    if (data && data.gui && data.gui.summarySelection)
    142150    {
    143151        g_TabCategorySelected = data.gui.summarySelection.panel;
    144152        g_SelectedChart = data.gui.summarySelection.charts;
    145153        teamCharts = data.gui.summarySelection.teamCharts;
    146154    }
    147155    Engine.GetGUIObjectByName("toggleTeamBox").checked = g_Teams && teamCharts;
    148156
    149157    initTeamData();
    150158    calculateTeamCounterDataHelper();
    151159}
    152160
    153161function initGUISummary()
    154162{
    155163    initGUIWindow();
    156164    initPlayerBoxPositions();
    157165    initGUICharts();
    158166    initGUILabels();
    159167    initGUIButtons();
    160168}
    161169
    162170/**
    163171 * Sets the style and title of the page.
    164172 */
    165173function initGUIWindow()
    166174{
    167175    let summaryWindow = Engine.GetGUIObjectByName("summaryWindow");
    168176    summaryWindow.sprite = g_GameData.gui.dialog ? "ModernDialog" : "ModernWindow";
    169177    summaryWindow.size = g_GameData.gui.dialog ? "16 24 100%-16 100%-24" : "0 0 100% 100%";
    170178    Engine.GetGUIObjectByName("summaryWindowTitle").size = g_GameData.gui.dialog ? "50%-128 -16 50%+128 16" : "50%-128 4 50%+128 36";
    171179}
    172180
    173181function selectPanelGUI(panel)
    174182{
    175183    adjustTabDividers(Engine.GetGUIObjectByName("tabButton[" + panel + "]").size);
    176184
    177185    let generalPanel = Engine.GetGUIObjectByName("generalPanel");
    178186    let chartsPanel = Engine.GetGUIObjectByName("chartsPanel");
    179187
    180188    // We assume all scorePanels come before the charts.
    181189    let chartsHidden = panel < g_ScorePanelsData.length;
    182190    generalPanel.hidden = !chartsHidden;
    183191    chartsPanel.hidden = chartsHidden;
    184192    if (chartsHidden)
    185193        updatePanelData(g_ScorePanelsData[panel]);
    186194    else
    187195        [0, 1].forEach(updateCategoryDropdown);
    188196}
    189197
    190198function constructPlayersWithColor(color, playerListing)
    191199{
    192200    return sprintf(translateWithContext("Player listing with color indicator",
    193201        "%(colorIndicator)s %(playerListing)s"),
    194202    {
    195203        "colorIndicator": setStringTags(translateWithContext(
    196204            "Charts player color indicator", "■"), { "color": color }),
    197205        "playerListing": playerListing
    198206    });
    199207}
    200208
    201209function updateChartColorAndLegend()
    202210{
    203211    let playerColors = [];
    204212    for (let i = 1; i <= g_PlayerCount; ++i)
    205213    {
    206214        let playerState = g_GameData.sim.playerStates[i];
    207215        playerColors.push(
    208216            Math.floor(playerState.color.r * 255) + " " +
    209217            Math.floor(playerState.color.g * 255) + " " +
    210218            Math.floor(playerState.color.b * 255)
    211219        );
    212220    }
    213221
    214222    for (let i = 0; i < 2; ++i)
    215223        Engine.GetGUIObjectByName("chart[" + i + "]").series_color =
    216224            Engine.GetGUIObjectByName("toggleTeamBox").checked ?
    217225                g_Teams.filter(el => el !== null).map(players => playerColors[players[0] - 1]) :
    218226                playerColors;
    219227
    220228    let chartLegend = Engine.GetGUIObjectByName("chartLegend");
    221229    chartLegend.caption = (Engine.GetGUIObjectByName("toggleTeamBox").checked ?
    222230        g_Teams.filter(el => el !== null).map(players =>
    223231            constructPlayersWithColor(playerColors[players[0] - 1], players.map(player =>
    224232                g_GameData.sim.playerStates[player].name
    225233            ).join(translateWithContext("Player listing", ", ")))
    226234        ) :
    227235        g_GameData.sim.playerStates.slice(1).map((state, index) =>
    228236            constructPlayersWithColor(playerColors[index], state.name))
    229237    ).join("   ");
    230238}
    231239
    232240function initGUICharts()
    233241{
    234242    updateChartColorAndLegend();
    235243    let chart1Part = Engine.GetGUIObjectByName("chart[1]Part");
    236244    let chart1PartSize = chart1Part.size;
    237245    chart1PartSize.rright += 50;
    238246    chart1PartSize.rleft += 50;
    239247    chart1PartSize.right -= 5;
    240248    chart1PartSize.left -= 5;
    241249    chart1Part.size = chart1PartSize;
    242250    Engine.GetGUIObjectByName("toggleTeam").hidden = !g_Teams;
    243251}
    244252
    245253function resizeDropdown(dropdown)
    246254{
    247255    let size = dropdown.size;
    248256    size.bottom = dropdown.size.top +
    249257        (Engine.GetTextWidth(dropdown.font, dropdown.list[dropdown.selected]) >
    250258            dropdown.size.right - dropdown.size.left - 32 ? 42 : 27);
    251259    dropdown.size = size;
    252260}
    253261
    254262function updateCategoryDropdown(number)
    255263{
    256264    let chartCategory = Engine.GetGUIObjectByName("chart[" + number + "]CategorySelection");
    257265    chartCategory.list_data = g_ScorePanelsData.map((panel, idx) => idx);
    258266    chartCategory.list = g_ScorePanelsData.map(panel => panel.label);
    259267    chartCategory.onSelectionChange = function() {
    260268        if (!this.list_data[this.selected])
    261269            return;
    262270        if (g_SelectedChart.category[number] != this.selected)
    263271        {
    264272            g_SelectedChart.category[number] = this.selected;
    265273            g_SelectedChart.value[number] = 0;
    266274            g_SelectedChart.type[number] = 0;
    267275        }
    268276
    269277        resizeDropdown(this);
    270278        updateValueDropdown(number, this.list_data[this.selected]);
    271279    };
    272280    chartCategory.selected = g_SelectedChart.category[number];
    273281}
    274282
    275283function updateValueDropdown(number, category)
    276284{
    277285    let chartValue = Engine.GetGUIObjectByName("chart[" + number + "]ValueSelection");
    278286    let list = g_ScorePanelsData[category].headings.map(heading => heading.caption);
    279287    list.shift();
    280288    chartValue.list = list;
    281289    let list_data = g_ScorePanelsData[category].headings.map(heading => heading.identifier);
    282290    list_data.shift();
    283291    chartValue.list_data = list_data;
    284292    chartValue.onSelectionChange = function() {
    285293        if (!this.list_data[this.selected])
    286294            return;
    287295        if (g_SelectedChart.value[number] != this.selected)
    288296        {
    289297            g_SelectedChart.value[number] = this.selected;
    290298            g_SelectedChart.type[number] = 0;
    291299        }
    292300
    293301        resizeDropdown(this);
    294302        updateTypeDropdown(number, category, this.list_data[this.selected], this.selected);
    295303    };
    296304    chartValue.selected = g_SelectedChart.value[number];
    297305}
    298306
    299307function updateTypeDropdown(number, category, item, itemNumber)
    300308{
    301309    let testValue = g_ScorePanelsData[category].counters[itemNumber].fn(g_GameData.sim.playerStates[1], 0, item);
    302310    let hide = !g_ScorePanelsData[category].counters[itemNumber].fn ||
    303311        typeof testValue != "object" || Object.keys(testValue).length < 2;
    304312    Engine.GetGUIObjectByName("chart[" + number + "]TypeLabel").hidden = hide;
    305313    let chartType = Engine.GetGUIObjectByName("chart[" + number + "]TypeSelection");
    306314    chartType.hidden = hide;
    307315    if (hide)
    308316    {
    309317        updateChart(number, category, item, itemNumber, Object.keys(testValue)[0] || undefined);
    310318        return;
    311319    }
    312320
    313321    chartType.list = Object.keys(testValue).map(type => g_SummaryTypes[type].caption);
    314322    chartType.list_data = Object.keys(testValue);
    315323    chartType.onSelectionChange = function() {
    316324        if (!this.list_data[this.selected])
    317325            return;
    318326        g_SelectedChart.type[number] = this.selected;
    319327        resizeDropdown(this);
    320328        updateChart(number, category, item, itemNumber, this.list_data[this.selected]);
    321329    };
    322330    chartType.selected = g_SelectedChart.type[number];
    323331}
    324332
    325333function updateChart(number, category, item, itemNumber, type)
    326334{
    327335    if (!g_ScorePanelsData[category].counters[itemNumber].fn)
    328336        return;
    329337    let chart = Engine.GetGUIObjectByName("chart[" + number + "]");
    330338    chart.format_y = g_ScorePanelsData[category].headings[itemNumber + 1].format || "INTEGER";
    331339    Engine.GetGUIObjectByName("chart[" + number + "]XAxisLabel").caption = translate("Time elapsed");
    332340
    333341    let series = [];
    334342    if (Engine.GetGUIObjectByName("toggleTeamBox").checked)
    335343        for (let team in g_Teams)
    336344        {
    337345            let data = [];
    338346            for (let index in g_GameData.sim.playerStates[1].sequences.time)
    339347            {
    340348                let value = g_ScorePanelsData[category].teamCounterFn(team, index, item,
    341349                    g_ScorePanelsData[category].counters, g_ScorePanelsData[category].headings);
    342350                if (type)
    343351                    value = value[type];
    344352                data.push([g_GameData.sim.playerStates[1].sequences.time[index], value]);
    345353            }
    346354            series.push(data);
    347355        }
    348356    else
    349357        for (let j = 1; j <= g_PlayerCount; ++j)
    350358        {
    351359            let playerState = g_GameData.sim.playerStates[j];
    352360            let data = [];
    353361            for (let index in playerState.sequences.time)
    354362            {
    355363                let value = g_ScorePanelsData[category].counters[itemNumber].fn(playerState, index, item);
    356364                if (type)
    357365                    value = value[type];
    358366                data.push([playerState.sequences.time[index], value]);
    359367            }
    360368            series.push(data);
    361369        }
    362370
    363371    chart.series = series;
    364372}
    365373
    366374function adjustTabDividers(tabSize)
    367375{
    368376    let tabButtonsLeft = Engine.GetGUIObjectByName("tabButtonsFrame").size.left;
    369377
    370378    let leftSpacer = Engine.GetGUIObjectByName("tabDividerLeft");
    371379    let leftSpacerSize = leftSpacer.size;
    372380    leftSpacerSize.right = tabSize.left + tabButtonsLeft + 2;
    373381    leftSpacer.size = leftSpacerSize;
    374382
    375383    let rightSpacer = Engine.GetGUIObjectByName("tabDividerRight");
    376384    let rightSpacerSize = rightSpacer.size;
    377385    rightSpacerSize.left = tabSize.right + tabButtonsLeft - 2;
    378386    rightSpacer.size = rightSpacerSize;
    379387}
    380388
    381389function updatePanelData(panelInfo)
    382390{
    383391    resetGeneralPanel();
    384392    updateGeneralPanelHeadings(panelInfo.headings);
    385393    updateGeneralPanelTitles(panelInfo.titleHeadings);
    386394    let rowPlayerObjectWidth = updateGeneralPanelCounter(panelInfo.counters);
    387395    updateGeneralPanelTeams();
    388396
    389397    let index = g_GameData.sim.playerStates[1].sequences.time.length - 1;
    390398    let playerBoxesCounts = [];
    391399    for (let i = 0; i < g_PlayerCount; ++i)
    392400    {
    393401        let playerState = g_GameData.sim.playerStates[i + 1];
    394402
    395403        if (!playerBoxesCounts[playerState.team + 1])
    396404            playerBoxesCounts[playerState.team + 1] = 1;
    397405        else
    398406            playerBoxesCounts[playerState.team + 1] += 1;
    399407
    400408        let positionObject = playerBoxesCounts[playerState.team + 1] - 1;
    401409        let rowPlayer = "playerBox[" + positionObject + "]";
    402410        let playerOutcome = "playerOutcome[" + positionObject + "]";
    403411        let playerNameColumn = "playerName[" + positionObject + "]";
    404412        let playerCivicBoxColumn = "civIcon[" + positionObject + "]";
    405413        let playerCounterValue = "valueData[" + positionObject + "]";
    406414
    407415        if (playerState.team != -1)
    408416        {
    409417            rowPlayer = "playerBoxt[" + playerState.team + "][" + positionObject + "]";
    410418            playerOutcome = "playerOutcomet[" + playerState.team + "][" + positionObject + "]";
    411419            playerNameColumn = "playerNamet[" + playerState.team + "][" + positionObject + "]";
    412420            playerCivicBoxColumn = "civIcont[" + playerState.team + "][" + positionObject + "]";
    413421            playerCounterValue = "valueDataTeam[" + playerState.team + "][" + positionObject + "]";
    414422        }
    415423
    416424        let colorString = "color: " +
    417425            Math.floor(playerState.color.r * 255) + " " +
    418426            Math.floor(playerState.color.g * 255) + " " +
    419427            Math.floor(playerState.color.b * 255);
    420428
    421429        let rowPlayerObject = Engine.GetGUIObjectByName(rowPlayer);
    422430        rowPlayerObject.hidden = false;
    423431        rowPlayerObject.sprite = colorString + " " + g_PlayerBoxAlpha;
    424432
    425433        let boxSize = rowPlayerObject.size;
    426434        boxSize.right = rowPlayerObjectWidth;
    427435        rowPlayerObject.size = boxSize;
    428436
    429437        setOutcomeIcon(playerState.state, Engine.GetGUIObjectByName(playerOutcome));
    430438
    431439        playerNameColumn = Engine.GetGUIObjectByName(playerNameColumn);
    432440        playerNameColumn.caption = g_GameData.sim.playerStates[i + 1].name;
    433441        playerNameColumn.tooltip = translateAISettings(g_GameData.sim.mapSettings.PlayerData[i + 1]);
    434442
    435443        let civIcon = Engine.GetGUIObjectByName(playerCivicBoxColumn);
    436444        civIcon.sprite = "stretched:" + g_CivData[playerState.civ].Emblem;
    437445        civIcon.tooltip = g_CivData[playerState.civ].Name;
    438446
    439447        updateCountersPlayer(playerState, panelInfo.counters, panelInfo.headings, playerCounterValue, index);
    440448    }
    441449
    442450    let teamCounterFn = panelInfo.teamCounterFn;
    443451    if (g_Teams && teamCounterFn)
    444452        updateCountersTeam(teamCounterFn, panelInfo.counters, panelInfo.headings, index);
    445453}
    446454
    447455function continueButton()
    448456{
    449457    let summarySelection = {
    450458        "panel": g_TabCategorySelected,
    451459        "charts": g_SelectedChart,
    452460        "teamCharts": Engine.GetGUIObjectByName("toggleTeamBox").checked
    453461    };
    454462    if (g_GameData.gui.isInGame)
    455463        Engine.PopGuiPage({
    456464            "summarySelection": summarySelection
    457465        });
    458466    else if (g_GameData.gui.dialog)
    459467        Engine.PopGuiPage();
    460468    else if (Engine.HasXmppClient())
    461469        Engine.SwitchGuiPage("page_lobby.xml", { "dialog": false });
    462470    else if (g_GameData.gui.isReplay)
    463471        Engine.SwitchGuiPage("page_replaymenu.xml", {
    464472            "replaySelectionData": g_GameData.gui.replaySelectionData,
    465473            "summarySelection": summarySelection
    466474        });
    467475    else
    468476        Engine.SwitchGuiPage("page_pregame.xml");
    469477}
    470478
    471479function startReplay()
    472480{
    473481    if (!Engine.StartVisualReplay(g_GameData.gui.replayDirectory))
    474482    {
    475483        warn("Replay file not found!");
    476484        return;
    477485    }
    478486
    479487    Engine.SwitchGuiPage("page_loading.xml", {
    480488        "attribs": Engine.GetReplayAttributes(g_GameData.gui.replayDirectory),
    481489        "playerAssignments": {
    482490            "local": {
    483491                "name": singleplayerName(),
    484492                "player": -1
    485493            }
    486494        },
    487495        "savedGUIData": "",
    488496        "isReplay": true,
    489497        "replaySelectionData": g_GameData.gui.replaySelectionData
    490498    });
    491499}
    492500
    493501function initGUILabels()
    494502{
    495503    let assignedState = g_GameData.sim.playerStates[g_GameData.gui.assignedPlayer || -1];
    496504
    497505    Engine.GetGUIObjectByName("summaryText").caption =
    498506        g_GameData.gui.isInGame ?
    499507            translate("Current Scores") :
    500508        g_GameData.gui.isReplay ?
    501509            translate("Scores at the end of the game.") :
    502510        g_GameData.gui.disconnected ?
    503511            translate("You have been disconnected.") :
    504512        !assignedState ?
    505513            translate("You have left the game.") :
    506514        assignedState.state == "won" ?
    507515            translate("You have won the battle!") :
    508516        assignedState.state == "defeated" ?
    509517            translate("You have been defeated…") :
    510518            translate("You have abandoned the game.");
    511519
    512520    Engine.GetGUIObjectByName("timeElapsed").caption = sprintf(
    513521        translate("Game time elapsed: %(time)s"), {
    514522            "time": timeToString(g_GameData.sim.timeElapsed)
    515523    });
    516524
    517525    let mapType = g_Settings.MapTypes.find(type => type.Name == g_GameData.sim.mapSettings.mapType);
    518526    let mapSize = g_Settings.MapSizes.find(size => size.Tiles == g_GameData.sim.mapSettings.Size || 0);
    519527
    520528    Engine.GetGUIObjectByName("mapName").caption = sprintf(
    521529        translate("%(mapName)s - %(mapType)s"), {
    522530            "mapName": translate(g_GameData.sim.mapSettings.Name),
    523531            "mapType": mapSize ? mapSize.Name : (mapType ? mapType.Title : "")
    524532        });
    525533}
    526534
    527535function initGUIButtons()
    528536{
    529537    let replayButton = Engine.GetGUIObjectByName("replayButton");
    530538    replayButton.hidden = g_GameData.gui.isInGame || !g_GameData.gui.replayDirectory;
    531539
    532540    let lobbyButton = Engine.GetGUIObjectByName("lobbyButton");
    533541    lobbyButton.tooltip = colorizeHotkey(translate("%(hotkey)s: Toggle the multiplayer lobby in a dialog window."), "lobby");
    534542    lobbyButton.hidden = g_GameData.gui.isInGame || !Engine.HasXmppClient();
    535543
    536544    // Right-align lobby button
    537545    let lobbyButtonSize = lobbyButton.size;
    538546    let lobbyButtonWidth = lobbyButtonSize.right - lobbyButtonSize.left;
    539547    lobbyButtonSize.right = (replayButton.hidden ? Engine.GetGUIObjectByName("continueButton").size.left : replayButton.size.left) - 10;
    540548    lobbyButtonSize.left = lobbyButtonSize.right - lobbyButtonWidth;
    541549    lobbyButton.size = lobbyButtonSize;
    542550
    543551    let allPanelsData = g_ScorePanelsData.concat(g_ChartPanelsData);
    544552    for (let tab in allPanelsData)
    545553        allPanelsData[tab].tooltip =
    546554            sprintf(translate("Toggle the %(name)s summary tab."), { "name": allPanelsData[tab].label }) +
    547555            colorizeHotkey("\n" + translate("Use %(hotkey)s to move a summary tab right."), "tab.next") +
    548556            colorizeHotkey("\n" + translate("Use %(hotkey)s to move a summary tab left."), "tab.prev");
    549557
    550558    placeTabButtons(
    551559        allPanelsData,
    552560        true,
    553561        g_TabButtonWidth,
    554562        g_TabButtonDist,
    555563        selectPanel,
    556564        selectPanelGUI);
    557565}
    558566
    559567function initTeamData()
    560568{
    561569    // Panels
    562570    g_PlayerCount = g_GameData.sim.playerStates.length - 1;
    563571
    564572    if (g_GameData.sim.mapSettings.LockTeams)
    565573    {
    566574        // Count teams
    567575        for (let player = 1; player <= g_PlayerCount; ++player)
    568576        {
    569577            let playerTeam = g_GameData.sim.playerStates[player].team;
    570578            if (!g_Teams[playerTeam])
    571579                g_Teams[playerTeam] = [];
    572580            g_Teams[playerTeam].push(player);
    573581        }
    574582
    575583        if (g_Teams.every(team => team && team.length < 2))
    576584            g_Teams = false;    // Each player has his own team. Displaying teams makes no sense.
    577585    }
    578586    else
    579587        g_Teams = false;
    580588
    581589    // Erase teams data if teams are not displayed
    582590    if (!g_Teams)
    583591        for (let p = 0; p < g_PlayerCount; ++p)
    584592            g_GameData.sim.playerStates[p+1].team = -1;
    585593}
  • binaries/data/mods/public/simulation/components/StatisticsTracker.js

     
    11function StatisticsTracker() {}
    22
    33StatisticsTracker.prototype.Schema =
    44    "<a:help>This component records statistics over the course of the match, such as the number of trained, lost, captured and destroyed units and buildings The statistics are consumed by the summary screen and lobby rankings.</a:help>" +
    55    "<a:example>" +
    66        "<UnitClasses>Infantry FemaleCitizen</UnitClasses>" +
    77        "<StructureClasses>House Wonder</StructureClasses>" +
    88    "</a:example>" +
    99    "<element name='UnitClasses' a:help='The tracker records trained, lost, killed and captured units of entities that match any of these Identity classes.'>" +
    1010        "<attribute name='datatype'>" +
    1111            "<value>tokens</value>" +
    1212        "</attribute>" +
    1313        "<text/>" +
    1414    "</element>" +
    1515    "<element name='StructureClasses' a:help='The tracker records constructed, lost, destroyed and captured structures of entities that match any of these Identity classes.'>" +
    1616        "<attribute name='datatype'>" +
    1717            "<value>tokens</value>" +
    1818        "</attribute>" +
    1919        "<text/>" +
    2020    "</element>";
    2121
    2222/**
    2323 * This number specifies the time in milliseconds between consecutive statistics snapshots recorded.
    2424 */
    2525StatisticsTracker.prototype.UpdateSequenceInterval = 30 * 1000;
    2626
    2727StatisticsTracker.prototype.Init = function()
    2828{
    2929    this.unitsClasses = this.template.UnitClasses._string.split(/\s+/);
    3030    this.buildingsClasses = this.template.StructureClasses._string.split(/\s+/);
    3131
     32    this.unitsAlive = {};
    3233    this.unitsTrained = {};
    3334    this.unitsLost = {};
    3435    this.enemyUnitsKilled = {};
    3536    this.unitsCaptured = {};
    3637
    3738    this.unitsLostValue = 0;
    3839    this.enemyUnitsKilledValue = 0;
    3940    this.unitsCapturedValue = 0;
    4041
    41     for (let counterName of ["unitsTrained", "unitsLost", "enemyUnitsKilled", "unitsCaptured"])
     42    for (let counterName of ["unitsAlive", "unitsTrained", "unitsLost", "enemyUnitsKilled", "unitsCaptured"])
    4243    {
    4344        this[counterName].total = 0;
    4445        for (let unitClass of this.unitsClasses)
    4546            // Domestic units are only counted for training
    4647            if (unitClass != "Domestic" || counterName == "unitsTrained")
    4748                this[counterName][unitClass] = 0;
    4849    }
    4950
    5051    this.buildingsConstructed = {};
    5152    this.buildingsLost = {};
    5253    this.enemyBuildingsDestroyed = {};
    5354    this.buildingsCaptured = {};
    5455
    5556    this.buildingsLostValue = 0;
    5657    this.enemyBuildingsDestroyedValue = 0;
    5758    this.buildingsCapturedValue = 0;
    5859
    5960    for (let counterName of ["buildingsConstructed", "buildingsLost", "enemyBuildingsDestroyed", "buildingsCaptured"])
    6061    {
    6162        this[counterName].total = 0;
    6263        for (let unitClass of this.buildingsClasses)
    6364            this[counterName][unitClass] = 0;
    6465    }
    6566
    6667    this.resourcesGathered = {
    6768        "vegetarianFood": 0
    6869    };
    6970    this.resourcesUsed = {};
    7071    this.resourcesSold = {};
    7172    this.resourcesBought = {};
    7273    for (let res of Resources.GetCodes())
    7374    {
    7475        this.resourcesGathered[res] = 0;
    7576        this.resourcesUsed[res] = 0;
    7677        this.resourcesSold[res] = 0;
    7778        this.resourcesBought[res] = 0;
    7879    }
    7980
    8081    this.tributesSent = 0;
    8182    this.tributesReceived = 0;
    8283    this.tradeIncome = 0;
    8384    this.treasuresCollected = 0;
    8485    this.lootCollected = 0;
    8586    this.peakPercentMapControlled = 0;
    8687    this.teamPeakPercentMapControlled = 0;
    8788    this.successfulBribes = 0;
    8889    this.failedBribes = 0;
    8990
    9091    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    9192    this.updateTimer = cmpTimer.SetInterval(
    9293        this.entity, IID_StatisticsTracker, "UpdateSequences", 0, this.UpdateSequenceInterval);
    9394};
    9495
    9596StatisticsTracker.prototype.OnGlobalInitGame = function()
    9697{
    9798    this.sequences = clone(this.GetStatistics());
    9899    this.sequences.time = [];
     100    this.unitsAlive = this.GetUnitsAlive();
    99101};
    100102
    101103/**
    102104 * Returns a subset of statistics that will be added to the simulation state,
    103105 * thus called each turn. Basic statistics should not contain data that would
    104106 * be expensive to compute.
    105107 *
    106108 * Note: as of now, nothing in the game needs that, but some AIs developed by
    107109 * modders need it in the API.
    108110 */
    109111StatisticsTracker.prototype.GetBasicStatistics = function()
    110112{
    111113    return {
    112114        "resourcesGathered": this.resourcesGathered,
    113115        "percentMapExplored": this.GetPercentMapExplored()
    114116    };
    115117};
    116118
    117119StatisticsTracker.prototype.GetStatistics = function()
    118120{
    119121    return {
     122        "unitsAlive": this.unitsAlive,
    120123        "unitsTrained": this.unitsTrained,
    121124        "unitsLost": this.unitsLost,
    122125        "unitsLostValue": this.unitsLostValue,
    123126        "enemyUnitsKilled": this.enemyUnitsKilled,
    124127        "enemyUnitsKilledValue": this.enemyUnitsKilledValue,
    125128        "unitsCaptured": this.unitsCaptured,
    126129        "unitsCapturedValue": this.unitsCapturedValue,
    127130        "buildingsConstructed": this.buildingsConstructed,
    128131        "buildingsLost": this.buildingsLost,
    129132        "buildingsLostValue": this.buildingsLostValue,
    130133        "enemyBuildingsDestroyed": this.enemyBuildingsDestroyed,
    131134        "enemyBuildingsDestroyedValue": this.enemyBuildingsDestroyedValue,
    132135        "buildingsCaptured": this.buildingsCaptured,
    133136        "buildingsCapturedValue": this.buildingsCapturedValue,
     137        "resourcesCount": this.GetResourceCounts(),
    134138        "resourcesGathered": this.resourcesGathered,
    135139        "resourcesUsed": this.resourcesUsed,
    136140        "resourcesSold": this.resourcesSold,
    137141        "resourcesBought": this.resourcesBought,
    138142        "tributesSent": this.tributesSent,
    139143        "tributesReceived": this.tributesReceived,
    140144        "tradeIncome": this.tradeIncome,
    141145        "treasuresCollected": this.treasuresCollected,
    142146        "lootCollected": this.lootCollected,
    143147        "percentMapExplored": this.GetPercentMapExplored(),
    144148        "teamPercentMapExplored": this.GetTeamPercentMapExplored(),
    145149        "percentMapControlled": this.GetPercentMapControlled(),
    146150        "teamPercentMapControlled": this.GetTeamPercentMapControlled(),
    147151        "peakPercentMapControlled": this.peakPercentMapControlled,
    148152        "teamPeakPercentMapControlled": this.teamPeakPercentMapControlled,
    149153        "successfulBribes": this.successfulBribes,
    150154        "failedBribes": this.failedBribes
    151155    };
    152156};
    153157
    154158StatisticsTracker.prototype.GetSequences = function()
    155159{
    156160    let ret = clone(this.sequences);
    157161    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    158162
    159163    ret.time.push(cmpTimer.GetTime() / 1000);
    160164    this.PushValue(this.GetStatistics(), ret);
    161165    return ret;
    162166};
    163167
    164168/**
    165169 * Used to print statistics for non-visual autostart games.
    166170 * @return The player's statistics as a JSON string beautified with some indentations.
    167171 */
    168172StatisticsTracker.prototype.GetStatisticsJSON = function()
    169173{
    170174    let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
    171175
    172176    let playerStatistics = {
    173177        "playerID": cmpPlayer.GetPlayerID(),
    174178        "playerState": cmpPlayer.GetState(),
    175179        "statistics": this.GetStatistics()
    176180    };
    177181
    178182    return JSON.stringify(playerStatistics, null, "\t");
    179183};
    180184
    181185/**
     186 * @return Breakdown of the living units
     187 */
     188StatisticsTracker.prototype.GetUnitsAlive = function() {
     189    let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
     190
     191    let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     192    let entities = cmpRangeManager.GetEntitiesByPlayer(cmpPlayer.playerID);
     193
     194    let unitsAlive = { total: 0 };
     195    for (let unitClass of this.unitsClasses)
     196        unitsAlive[unitClass] = 0;
     197
     198    for (let entity of entities)
     199    {
     200        let cmpEntityIdentity = Engine.QueryInterface(entity, IID_Identity);
     201        if (!cmpEntityIdentity)
     202            break;
     203
     204        if (cmpEntityIdentity.HasClass("Unit"))
     205        {
     206            let classes = cmpEntityIdentity.GetClassesList();
     207            if (!classes)
     208                return;
     209
     210            for(let type in classes)
     211                if (unitsAlive[type] !== undefined)
     212                {
     213                    ++unitsAlive[type];
     214                }
     215
     216            if (!cmpEntityIdentity.HasClass("Domestic"))
     217                ++unitsAlive.total;
     218        }
     219
     220    }
     221
     222    return unitsAlive;
     223}
     224
     225/**
    182226 * Increments counter associated with certain entity/counter and type of given entity.
    183227 * @param cmpIdentity - the entity identity component.
    184228 * @param counter - the name of the counter to increment (e.g. "unitsTrained").
    185229 * @param type - the type of the counter (e.g. "workers").
     230 * @param value - the value to increment the counter with (default: 1) (e.g. -1 to decrement the counter)
    186231 */
    187 StatisticsTracker.prototype.CounterIncrement = function(cmpIdentity, counter, type)
     232StatisticsTracker.prototype.CounterIncrement = function(cmpIdentity, counter, type, value=1)
    188233{
    189234    var classes = cmpIdentity.GetClassesList();
    190235    if (!classes)
    191236        return;
    192237
    193238    if (classes.indexOf(type) != -1)
    194         ++this[counter][type];
     239        this[counter][type] += value;
    195240};
    196241
    197242/**
    198243 * Counts the total number of units trained as well as an individual count for
    199244 * each unit type. Based on templates.
    200245 */
    201246StatisticsTracker.prototype.IncreaseTrainedUnitsCounter = function(trainedUnit)
    202247{
    203248    let cmpUnitEntityIdentity = Engine.QueryInterface(trainedUnit, IID_Identity);
    204249
    205250    if (!cmpUnitEntityIdentity)
    206251        return;
    207252
    208253    let cmpCost = Engine.QueryInterface(trainedUnit, IID_Cost);
    209254    let costs = cmpCost && cmpCost.GetResourceCosts();
    210255
    211256    for (let type of this.unitsClasses)
     257    {
    212258        this.CounterIncrement(cmpUnitEntityIdentity, "unitsTrained", type);
     259        this.CounterIncrement(cmpUnitEntityIdentity, "unitsAlive", type);
     260    }
    213261
    214262    if (!cmpUnitEntityIdentity.HasClass("Domestic"))
     263    {
    215264        ++this.unitsTrained.total;
     265        ++this.unitsAlive.total;
     266    }
    216267
    217268    if (cmpUnitEntityIdentity.HasClass("Domestic") && costs)
    218269    {
    219270        // Subtract costs for sheep/goats/pigs to get the net food gain/use for corralling
    220271        this.resourcesUsed.food -= costs.food;
    221272        this.resourcesGathered.food -= costs.food;
    222273    }
    223274
    224275};
    225276
    226277/**
    227278 * Counts the total number of buildings constructed as well as an individual count for
    228279 * each building type. Based on templates.
    229280 */
    230281StatisticsTracker.prototype.IncreaseConstructedBuildingsCounter = function(constructedBuilding)
    231282{
    232283    var cmpBuildingEntityIdentity = Engine.QueryInterface(constructedBuilding, IID_Identity);
    233284
    234285    if (!cmpBuildingEntityIdentity)
    235286        return;
    236287
    237288    for (let type of this.buildingsClasses)
    238289        this.CounterIncrement(cmpBuildingEntityIdentity, "buildingsConstructed", type);
    239290
    240291    ++this.buildingsConstructed.total;
    241292};
    242293
    243294StatisticsTracker.prototype.KilledEntity = function(targetEntity)
    244295{
    245296    var cmpTargetEntityIdentity = Engine.QueryInterface(targetEntity, IID_Identity);
    246297    if (!cmpTargetEntityIdentity)
    247298        return;
    248299
    249300    var cmpCost = Engine.QueryInterface(targetEntity, IID_Cost);
    250301    var costs = cmpCost && cmpCost.GetResourceCosts();
    251302
    252303    // Exclude gaia animals but not gaia soldiers.
    253304    if (cmpTargetEntityIdentity.HasClass("Unit") && !cmpTargetEntityIdentity.HasClass("Animal"))
    254305    {
    255306        for (let type of this.unitsClasses)
    256307            this.CounterIncrement(cmpTargetEntityIdentity, "enemyUnitsKilled", type);
    257308
    258309        ++this.enemyUnitsKilled.total;
    259310
    260311        if (costs)
    261312            for (let type in costs)
    262313                this.enemyUnitsKilledValue += costs[type];
    263314    }
    264315
    265316    let cmpFoundation = Engine.QueryInterface(targetEntity, IID_Foundation);
    266317    if (cmpTargetEntityIdentity.HasClass("Structure") && !cmpFoundation)
    267318    {
    268319        for (let type of this.buildingsClasses)
    269320            this.CounterIncrement(cmpTargetEntityIdentity, "enemyBuildingsDestroyed", type);
    270321
    271322        ++this.enemyBuildingsDestroyed.total;
    272323
    273324        if (costs)
    274325            for (let type in costs)
    275326                this.enemyBuildingsDestroyedValue += costs[type];
    276327    }
    277328};
    278329
    279330StatisticsTracker.prototype.LostEntity = function(lostEntity)
    280331{
    281332    var cmpLostEntityIdentity = Engine.QueryInterface(lostEntity, IID_Identity);
    282333    if (!cmpLostEntityIdentity)
    283334        return;
    284335
    285336    var cmpCost = Engine.QueryInterface(lostEntity, IID_Cost);
    286337    var costs = cmpCost && cmpCost.GetResourceCosts();
    287338
    288339    if (cmpLostEntityIdentity.HasClass("Unit") && !cmpLostEntityIdentity.HasClass("Domestic"))
    289340    {
    290341        for (let type of this.unitsClasses)
     342        {
    291343            this.CounterIncrement(cmpLostEntityIdentity, "unitsLost", type);
     344            this.CounterIncrement(cmpLostEntityIdentity, "unitsAlive", type, -1);
     345        }
    292346
    293347        ++this.unitsLost.total;
     348        --this.unitsAlive.total;
    294349
    295350        if (costs)
    296351            for (let type in costs)
    297352                this.unitsLostValue += costs[type];
    298353    }
    299354
    300355    let cmpFoundation = Engine.QueryInterface(lostEntity, IID_Foundation);
    301356    if (cmpLostEntityIdentity.HasClass("Structure") && !cmpFoundation)
    302357    {
    303358        for (let type of this.buildingsClasses)
    304359            this.CounterIncrement(cmpLostEntityIdentity, "buildingsLost", type);
    305360
    306361        ++this.buildingsLost.total;
    307362
    308363        if (costs)
    309364            for (let type in costs)
    310365                this.buildingsLostValue += costs[type];
    311366    }
    312367};
    313368
    314369StatisticsTracker.prototype.CapturedEntity = function(capturedEntity)
    315370{
    316371    let cmpCapturedEntityIdentity = Engine.QueryInterface(capturedEntity, IID_Identity);
    317372    if (!cmpCapturedEntityIdentity)
    318373        return;
    319374
    320375    let cmpCost = Engine.QueryInterface(capturedEntity, IID_Cost);
    321376    let costs = cmpCost && cmpCost.GetResourceCosts();
    322377
    323378    if (cmpCapturedEntityIdentity.HasClass("Unit"))
    324379    {
    325380        for (let type of this.unitsClasses)
     381        {
    326382            this.CounterIncrement(cmpCapturedEntityIdentity, "unitsCaptured", type);
     383            this.CounterIncrement(cmpCapturedEntityIdentity, "unitsAlive", type);
     384        }
    327385
    328386        ++this.unitsCaptured.total;
     387        ++this.unitsAlive.total;
    329388
    330389        if (costs)
    331390            for (let type in costs)
    332391                this.unitsCapturedValue += costs[type];
    333392    }
    334393
    335394    if (cmpCapturedEntityIdentity.HasClass("Structure"))
    336395    {
    337396        for (let type of this.buildingsClasses)
    338397            this.CounterIncrement(cmpCapturedEntityIdentity, "buildingsCaptured", type);
    339398
    340399        ++this.buildingsCaptured.total;
    341400
    342401        if (costs)
    343402            for (let type in costs)
    344403                this.buildingsCapturedValue += costs[type];
    345404    }
    346405};
    347406
     407StatisticsTracker.prototype.GetResourceCounts = function() {
     408    let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
     409    if (!cmpPlayer) {
     410        let empty = {};
     411        for (let res of Resources.GetCodes())
     412            empty[res] = 0;
     413        return empty;
     414    }
     415    return cmpPlayer.GetResourceCounts();
     416}
     417
    348418/**
    349419 * @param {string} type - generic type of resource.
    350420 * @param {number} amount - amount of resource, whick should be added.
    351421 * @param {string} specificType - specific type of resource.
    352422 */
    353423StatisticsTracker.prototype.IncreaseResourceGatheredCounter = function(type, amount, specificType)
    354424{
    355425    this.resourcesGathered[type] += amount;
    356426
    357427    if (type == "food" && (specificType == "fruit" || specificType == "grain"))
    358428        this.resourcesGathered.vegetarianFood += amount;
    359429};
    360430
    361431/**
    362432 * @param {string} type - generic type of resource.
    363433 * @param {number} amount - amount of resource, which should be added.
    364434 */
    365435StatisticsTracker.prototype.IncreaseResourceUsedCounter = function(type, amount)
    366436{
    367437    this.resourcesUsed[type] += amount;
    368438};
    369439
    370440StatisticsTracker.prototype.IncreaseTreasuresCollectedCounter = function()
    371441{
    372442    ++this.treasuresCollected;
    373443};
    374444
    375445StatisticsTracker.prototype.IncreaseLootCollectedCounter = function(amount)
    376446{
    377447    for (let type in amount)
    378448        this.lootCollected += amount[type];
    379449};
    380450
    381451StatisticsTracker.prototype.IncreaseResourcesSoldCounter = function(type, amount)
    382452{
    383453    this.resourcesSold[type] += amount;
    384454};
    385455
    386456StatisticsTracker.prototype.IncreaseResourcesBoughtCounter = function(type, amount)
    387457{
    388458    this.resourcesBought[type] += amount;
    389459};
    390460
    391461StatisticsTracker.prototype.IncreaseTributesSentCounter = function(amount)
    392462{
    393463    this.tributesSent += amount;
    394464};
    395465
    396466StatisticsTracker.prototype.IncreaseTributesReceivedCounter = function(amount)
    397467{
    398468    this.tributesReceived += amount;
    399469};
    400470
    401471StatisticsTracker.prototype.IncreaseTradeIncomeCounter = function(amount)
    402472{
    403473    this.tradeIncome += amount;
    404474};
    405475
    406476StatisticsTracker.prototype.IncreaseSuccessfulBribesCounter = function()
    407477{
    408478    ++this.successfulBribes;
    409479};
    410480
    411481StatisticsTracker.prototype.IncreaseFailedBribesCounter = function()
    412482{
    413483    ++this.failedBribes;
    414484};
    415485
    416486StatisticsTracker.prototype.GetPercentMapExplored = function()
    417487{
    418488    let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
    419489    if (!cmpPlayer)
    420490        return 0;
    421491
    422492    return Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetPercentMapExplored(cmpPlayer.GetPlayerID());
    423493};
    424494
    425495/**
    426496 * Note: cmpRangeManager.GetUnionPercentMapExplored computes statistics from scratch!
    427497 * As a consequence, this function should not be called too often.
    428498 */
    429499StatisticsTracker.prototype.GetTeamPercentMapExplored = function()
    430500{
    431501    let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
    432502    if (!cmpPlayer)
    433503        return 0;
    434504
    435505    let team = cmpPlayer.GetTeam();
    436506    let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    437507    // If teams are not locked, this statistic won't be displayed, so don't bother computing
    438508    if (team == -1 || !cmpPlayer.GetLockTeams())
    439509        return cmpRangeManager.GetPercentMapExplored(cmpPlayer.GetPlayerID());
    440510
    441511    let teamPlayers = [];
    442512    let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
    443513    for (let i = 1; i < numPlayers; ++i)
    444514    {
    445515        let cmpOtherPlayer = QueryPlayerIDInterface(i);
    446516        if (cmpOtherPlayer && cmpOtherPlayer.GetTeam() == team)
    447517            teamPlayers.push(i);
    448518    }
    449519
    450520    return cmpRangeManager.GetUnionPercentMapExplored(teamPlayers);
    451521};
    452522
    453523StatisticsTracker.prototype.GetPercentMapControlled = function()
    454524{
    455525    let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
    456526    if (!cmpPlayer)
    457527        return 0;
    458528
    459529    return Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager).GetTerritoryPercentage(cmpPlayer.GetPlayerID());
    460530};
    461531
    462532StatisticsTracker.prototype.GetTeamPercentMapControlled = function()
    463533{
    464534    let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
    465535    if (!cmpPlayer)
    466536        return 0;
    467537
    468538    let team = cmpPlayer.GetTeam();
    469539    let cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
    470540    if (team == -1 || !cmpPlayer.GetLockTeams())
    471541        return cmpTerritoryManager.GetTerritoryPercentage(cmpPlayer.GetPlayerID());
    472542
    473543    let teamPercent = 0;
    474544    let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
    475545    for (let i = 1; i < numPlayers; ++i)
    476546    {
    477547        let cmpOtherPlayer = QueryPlayerIDInterface(i);
    478548        if (cmpOtherPlayer && cmpOtherPlayer.GetTeam() == team)
    479549            teamPercent += cmpTerritoryManager.GetTerritoryPercentage(i);
    480550    }
    481551
    482552    return teamPercent;
    483553};
    484554
    485555StatisticsTracker.prototype.OnTerritoriesChanged = function(msg)
    486556{
    487557    this.UpdatePeakPercentages();
    488558};
    489559
    490560StatisticsTracker.prototype.OnGlobalPlayerDefeated = function(msg)
    491561{
    492562    this.UpdatePeakPercentages();
    493563};
    494564
    495565StatisticsTracker.prototype.OnGlobalPlayerWon = function(msg)
    496566{
    497567    this.UpdatePeakPercentages();
    498568};
    499569
    500570StatisticsTracker.prototype.UpdatePeakPercentages = function()
    501571{
    502572    this.peakPercentMapControlled = Math.max(this.peakPercentMapControlled, this.GetPercentMapControlled());
    503573    this.teamPeakPercentMapControlled = Math.max(this.teamPeakPercentMapControlled, this.GetTeamPercentMapControlled());
    504574};
    505575
    506576/**
    507577 * Adds the values of fromData to the end of the arrays of toData.
    508578 * If toData misses the needed array, one will be created.
    509579 *
    510580 * @param fromData - an object of values or a value.
    511581 * @param toData - an object of arrays or an array.
    512582**/
    513583StatisticsTracker.prototype.PushValue = function(fromData, toData)
    514584{
    515585    if (typeof fromData == "object")
    516586        for (let prop in fromData)
    517587        {
    518588            if (typeof toData[prop] != "object")
    519589                toData[prop] = [fromData[prop]];
    520590            else
    521591                this.PushValue(fromData[prop], toData[prop]);
    522592        }
    523593    else
    524594        toData.push(fromData);
    525595};
    526596
    527597StatisticsTracker.prototype.UpdateSequences = function()
    528598{
    529599    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    530600    this.sequences.time.push(cmpTimer.GetTime() / 1000);
    531601    this.PushValue(this.GetStatistics(), this.sequences);
    532602};
    533603
    534604Engine.RegisterComponentType(IID_StatisticsTracker, "StatisticsTracker", StatisticsTracker);