Source: summary/counters.js

var g_TeamHelperData = [];

function calculatePercent(divident, divisor)
{
	return { "percent": divisor ? Math.floor(100 * divident / divisor) : 0 };
}

function calculateRatio(divident, divisor)
{
	return divident ? +((divident / divisor).toFixed(2)) : 0;
}

function formatSummaryValue(values)
{
	if (typeof values != "object")
		return values === Infinity ? g_InfinitySymbol : values;

	let ret = "";
	for (let type in values)
		if (!g_SummaryTypes[type].hideInSummary)
			ret += (g_SummaryTypes[type].color ?
				coloredText(values[type], g_SummaryTypes[type].color) :
				values[type]) + g_SummaryTypes[type].postfix;
	return ret;
}

function getPlayerValuesPerTeam(team, index, type, counters, headings)
{
	let fn = counters[headings.map(heading => heading.identifier).indexOf(type) - 1].fn;
	return g_Teams[team].map(player => fn(g_GameData.sim.playerStates[player], index, type));
}

function updateCountersPlayer(playerState, allCounters, allHeadings, idGUI, index)
{
	let counters = allCounters.filter(counter => !counter.hideInSummary);
	let headings = allHeadings.filter(heading => !heading.hideInSummary);
	for (let n in counters)
	{
		let fn = counters[n].fn;
		Engine.GetGUIObjectByName(idGUI + "[" + n + "]").caption =
			formatSummaryValue(fn && fn(playerState, index, headings[+n + 1].identifier));
	}
}

function updateCountersTeam(teamFn, allCounters, allHeadings, index)
{
	let counters = allCounters.filter(counter => !counter.hideInSummary);
	let headings = allHeadings.filter(heading => !heading.hideInSummary);
	for (let team in g_Teams)
	{
		if (team == -1)
			continue;

		for (let n in counters)
			Engine.GetGUIObjectByName("valueDataTeam[" + team + "][" + n + "]").caption =
				formatSummaryValue(teamFn(team, index, headings[+n + 1].identifier, counters, headings));
	}
}

/**
 * Add two objects property-wise and writes the result to obj1.
 * So summaryAddObject([1, 2], [7, 42]) will result in [8, 44] and
 * summaryAddObject({ "f": 3, "o", 5 }, { "f": -1, "o", 42 }) will result in { "f": 2, "o", 47 }.
 *
 * @param {Object} obj1 - First summand object. This will be set to the sum of both.
 * @param {Object} obj2 - Second summand object.
 */
function summaryAddObject(obj1, obj2)
{
	for (let p in obj1)
		obj1[p] += obj2[p];
}

/**
 * The sum of all elements of an array. So summaryArraySum([1, 2]) will be 3 and
 * summaryArraySum([{ "f": 3, "o", 5 }, { "f": -1, "o", 42 }]) will be { "f": 2, "o", 47 }.
 *
 * @param {Array} array - The array to sum up.
 * @returns the sum of all elements.
 */
function summaryArraySum(array)
{
	return array.reduce((sum, val) => {
		if (typeof sum != "object")
			return sum + val;
		summaryAddObject(sum, val);
		return sum;
	});
}

function calculateTeamCounterDataHelper()
{
	for (let i = 0; i < g_PlayerCount; ++i)
	{
		let playerState = g_GameData.sim.playerStates[i + 1];

		if (!g_TeamHelperData[playerState.team])
		{
			g_TeamHelperData[playerState.team] = {};
			for (let value of ["food", "vegetarianFood", "femaleCitizen", "worker", "enemyUnitsKilled",
			                   "unitsLost", "mapControl", "mapControlPeak",
			                   "mapExploration", "totalBought", "totalSold"])
				g_TeamHelperData[playerState.team][value] = new Array(playerState.sequences.time.length).fill(0);
		}

		summaryAddObject(g_TeamHelperData[playerState.team].food, playerState.sequences.resourcesGathered.food);
		summaryAddObject(g_TeamHelperData[playerState.team].vegetarianFood, playerState.sequences.resourcesGathered.vegetarianFood);

		summaryAddObject(g_TeamHelperData[playerState.team].femaleCitizen, playerState.sequences.unitsTrained.FemaleCitizen);
		summaryAddObject(g_TeamHelperData[playerState.team].worker, playerState.sequences.unitsTrained.Worker);

		summaryAddObject(g_TeamHelperData[playerState.team].enemyUnitsKilled, playerState.sequences.enemyUnitsKilled.total);
		summaryAddObject(g_TeamHelperData[playerState.team].unitsLost, playerState.sequences.unitsLost.total);

		g_TeamHelperData[playerState.team].mapControl = playerState.sequences.teamPercentMapControlled;
		g_TeamHelperData[playerState.team].mapControlPeak = playerState.sequences.teamPeakPercentMapControlled;

		g_TeamHelperData[playerState.team].mapExploration = playerState.sequences.teamPercentMapExplored;

		for (let type in playerState.sequences.resourcesBought)
			summaryAddObject(g_TeamHelperData[playerState.team].totalBought, playerState.sequences.resourcesBought[type]);

		for (let type in playerState.sequences.resourcesSold)
			summaryAddObject(g_TeamHelperData[playerState.team].totalSold, playerState.sequences.resourcesSold[type]);
	}
}

/**
 * Keep this in sync with the score computation in session/ for the lobby rating reports!
 */
function calculateEconomyScore(playerState, index)
{
	let total = 0;

	// Notice that this skips the vegetarianFood property of resourcesGathered
	for (let type of g_ResourceData.GetCodes())
		total += playerState.sequences.resourcesGathered[type][index];

	total += playerState.sequences.tradeIncome[index];
	return Math.round(total / 10);
}

/**
 * Keep this in sync with the score computation in session/ for the lobby rating reports!
 */
function calculateMilitaryScore(playerState, index)
{
	return Math.round((playerState.sequences.enemyUnitsKilledValue[index] +
		playerState.sequences.unitsCapturedValue[index] +
		playerState.sequences.enemyBuildingsDestroyedValue[index] +
		playerState.sequences.buildingsCapturedValue[index]) / 10);
}

/**
 * Keep this in sync with the score computation in session/ for the lobby rating reports!
 */
function calculateExplorationScore(playerState, index)
{
	return playerState.sequences.percentMapExplored[index] * 10;
}

/**
 * Keep this in sync with the score computation in session/ for the lobby rating reports!
 */
function calculateScoreTotal(playerState, index)
{
	return calculateEconomyScore(playerState, index) +
		calculateMilitaryScore(playerState, index) +
		calculateExplorationScore(playerState, index);
}

function calculateScoreTeam(team, index, type, counters, headings)
{
	if (type == "explorationScore")
		return g_TeamHelperData[team].mapExploration[index] * 10;
	if (type == "totalScore")
		return ["economyScore", "militaryScore", "explorationScore"].map(
			t => calculateScoreTeam(team, index, t, counters, headings)).reduce(
			(sum, value) => sum + value);

	return summaryArraySum(getPlayerValuesPerTeam(team, index, type, counters, headings));
}

function calculateBuildings(playerState, index, type)
{
	return {
		"constructed": playerState.sequences.buildingsConstructed[type][index],
		"destroyed": playerState.sequences.enemyBuildingsDestroyed[type][index],
		"captured": playerState.sequences.buildingsCaptured[type][index],
		"lost": playerState.sequences.buildingsLost[type][index]
	};
}

function calculateBuildingsTeam(team, index, type, counters, headings)
{
	return summaryArraySum(getPlayerValuesPerTeam(team, index, type, counters, headings));
}

function calculateUnitsTeam(team, index, type, counters, headings)
{
	return summaryArraySum(getPlayerValuesPerTeam(team, index, type, counters, headings));
}

function calculateUnitsWithCaptured(playerState, index, type)
{
	return {
		"trained": playerState.sequences.unitsTrained[type][index],
		"killed": playerState.sequences.enemyUnitsKilled[type][index],
		"captured": playerState.sequences.unitsCaptured[type][index],
		"lost": playerState.sequences.unitsLost[type][index]
	};
}

function calculateUnits(playerState, index, type)
{
	return {
		"trained": playerState.sequences.unitsTrained[type][index],
		"killed": playerState.sequences.enemyUnitsKilled[type][index],
		"lost": playerState.sequences.unitsLost[type][index]
	};
}

function calculateResources(playerState, index, type)
{
	return {
		"count": playerState.sequences.resourcesCount[type][index],
		"gathered": playerState.sequences.resourcesGathered[type][index],
		"used": playerState.sequences.resourcesUsed[type][index] - playerState.sequences.resourcesSold[type][index]
	};
}

function calculateTotalResources(playerState, index)
{
	let totalGathered = 0;
	let totalUsed = 0;
	let totalCount = 0;

	for (let type of g_ResourceData.GetCodes())
	{
		totalCount += playerState.sequences.resourcesCount[type][index];
		totalGathered += playerState.sequences.resourcesGathered[type][index];
		totalUsed += playerState.sequences.resourcesUsed[type][index] - playerState.sequences.resourcesSold[type][index];
	}

	return { "count": totalCount, "gathered": totalGathered, "used": totalUsed };
}

function calculateTreasureCollected(playerState, index)
{
	return playerState.sequences.treasuresCollected[index];
}

function calculateLootCollected(playerState, index)
{
	return playerState.sequences.lootCollected[index];
}

function calculateTributeSent(playerState, index)
{
	return {
		"sent": playerState.sequences.tributesSent[index],
		"received": playerState.sequences.tributesReceived[index]
	};
}

function calculateLivestockTrained(playerState, index)
{
	return playerState.sequences.unitsTrained.Domestic[index];
}

function calculateResourcesTeam(team, index, type, counters, headings)
{
	return summaryArraySum(getPlayerValuesPerTeam(team, index, type, counters, headings));
}

function calculateResourceExchanged(playerState, index, type)
{
	return {
		"bought": playerState.sequences.resourcesBought[type][index],
		"sold": playerState.sequences.resourcesSold[type][index]
	};
}

function calculateBarterEfficiency(playerState, index)
{
	let totalBought = 0;
	let totalSold = 0;

	for (let type in playerState.sequences.resourcesBought)
		totalBought += playerState.sequences.resourcesBought[type][index];

	for (let type in playerState.sequences.resourcesSold)
		totalSold += playerState.sequences.resourcesSold[type][index];

	return calculatePercent(totalBought, totalSold);
}

function calculateTradeIncome(playerState, index)
{
	return playerState.sequences.tradeIncome[index];
}

function calculateMarketTeam(team, index, type, counters, headings)
{
	if (type == "barterEfficency")
		return calculatePercent(g_TeamHelperData[team].totalBought[index], g_TeamHelperData[team].totalSold[index]);

	return summaryArraySum(getPlayerValuesPerTeam(team, index, type, counters, headings));
}

function calculateVegetarianRatio(playerState, index)
{
	return calculatePercent(
		playerState.sequences.resourcesGathered.vegetarianFood[index],
		playerState.sequences.resourcesGathered.food[index]);
}

function calculateFeminization(playerState, index)
{
	return calculatePercent(
		playerState.sequences.unitsTrained.FemaleCitizen[index],
		playerState.sequences.unitsTrained.Worker[index]);
}

function calculateKillDeathRatio(playerState, index)
{
	return calculateRatio(
		playerState.sequences.enemyUnitsKilled.total[index],
		playerState.sequences.unitsLost.total[index]);
}

function calculatePopulationCount(playerState, index)
{
	return { "population": playerState.sequences.populationCount[index] };
}

function calculateMapExploration(playerState, index)
{
	return { "percent": playerState.sequences.percentMapExplored[index] };
}

function calculateMapFinalControl(playerState, index)
{
	return { "percent": playerState.sequences.percentMapControlled[index] };
}

function calculateMapPeakControl(playerState, index)
{
	return { "percent": playerState.sequences.peakPercentMapControlled[index] };
}

function calculateMiscellaneousTeam(team, index, type, counters, headings)
{
	if (type == "vegetarianRatio")
		return calculatePercent(g_TeamHelperData[team].vegetarianFood[index], g_TeamHelperData[team].food[index]);

	if (type == "feminization")
		return calculatePercent(g_TeamHelperData[team].femaleCitizen[index], g_TeamHelperData[team].worker[index]);

	if (type == "killDeath")
		return calculateRatio(g_TeamHelperData[team].enemyUnitsKilled[index], g_TeamHelperData[team].unitsLost[index]);

	if (type == "bribes" || type == "population")
		return summaryArraySum(getPlayerValuesPerTeam(team, index, type, counters, headings));

	return { "percent": g_TeamHelperData[team][type][index] };
}

function calculateBribes(playerState, index, type)
{
	return {
		"succeeded": playerState.sequences.successfulBribes[index],
		"failed": playerState.sequences.failedBribes[index]
	};
}