// wraitii's code for SetupStat
function SetupStat(panel, index, icon, text, tooltip, textColor) {
	const panelItem = Engine.GetGUIObjectByName(`${panel}[${index}]`);
	const panelIcon = Engine.GetGUIObjectByName(`${panel}Icon[${index}]`);
	const panelText = Engine.GetGUIObjectByName(`${panel}Text[${index}]`);
	if (textColor)
		panelText.textcolor = textColor;
	if (!text) {
		panelItem.hidden = true;
		return;
	}
	panelItem.hidden = false;
	panelIcon.sprite = `stretched:color:0 0 0 20:textureAsMask:${icon}`;
	const size = panelItem.size;
	size.top = 35 * index;
	size.bottom = 35 * index + 24;
	panelItem.size = size;
	panelText.tooltip = tooltip;
	panelIcon.tooltip = tooltip;
	panelText.caption = text;
}
/**
 * Calculates color intensity using exponential decay.
 * @param {number} bonusPercent - The bonus percentage (can be positive or negative).
 * @param {number} [decayRate=0.05] - Controls how quickly intensity drops (higher = faster decay).
 * @returns {string} - Color string in "R G B A" format.
 */
function getTextGreenColorFromBonus(bonusPercent, decayRate = 0.05) {
    const absBonus = Math.abs(bonusPercent);
    const colorIntensity = Math.min(Math.round(255 * Math.exp(-decayRate * absBonus)), 255);
    
    // For negative bonuses, use red
    if (bonusPercent < 0) {
        return `255 65 55 255`;
    }
    // For positive bonuses, use green
    return `${colorIntensity} 255 ${colorIntensity} 255`;
}
/**
 * Calculates color intensity using exponential decay.
 * @param {number} bonusPercent - The bonus percentage (can be positive or negative).
 * @param {number} [decayRate=0.05] - Controls how quickly intensity drops (higher = faster decay).
 * @returns {string} - Color string in "R G B A" format.
 */
function getTextBlueColorFromBonus(bonusPercent, decayRate = 0.1) {
    const absBonus = Math.abs(bonusPercent);
    const colorIntensityRed = Math.min(Math.round(255 * Math.exp(-decayRate * absBonus)), 165);
    const colorIntensityGreen = Math.min(Math.round(255 * Math.exp(-decayRate/5 * absBonus)), 205);
    
    // For negative bonuses, use red
    if (bonusPercent < 0) {
        return `255 65 55 255`;
    }
    // For positive bonuses, use green
    return `${colorIntensityRed} ${colorIntensityGreen} 255 255`;
}

// Fills out information that most entities have
function displaySingle(entState) {
	const template = GetTemplateData(entState.template);
	const templateData = Engine.GuiInterfaceCall("GetTemplateData", {
		"templateName": entState?.template,
		"player": 0
	});

	const primaryName = g_SpecificNamesPrimary ? template.name.specific : template.name.generic;
	let secondaryName;
	if (g_ShowSecondaryNames)
		secondaryName = g_SpecificNamesPrimary ? template.name.generic : template.name.specific;

	// If packed, add that to the generic name (reduces template clutter).
	if (template.pack && template.pack.state == "packed") {
		if (secondaryName && g_ShowSecondaryNames)
			secondaryName = sprintf(translate("%(secondaryName)s — Packed"), { "secondaryName": secondaryName });
		else
			secondaryName = sprintf(translate("Packed"));
	}
	const playerState = g_Players[entState.player];

	const civName = g_CivData[playerState.civ].Name;
	const civEmblem = g_CivData[playerState.civ].Emblem;

	let playerName = playerState.name;

	// Indicate disconnected players by prefixing their name
	if (g_Players[entState.player].offline)
		playerName = sprintf(translate("\\[OFFLINE] %(player)s"), { "player": playerName });

	// Rank
	if (entState.identity && entState.identity.rank && entState.identity.classes) {
		const rankObj = GetTechnologyData(entState.identity.rankTechName, playerState.civ);
		Engine.GetGUIObjectByName("rankIcon").tooltip = sprintf(translate("%(rank)s Rank"), {
			"rank": translateWithContext("Rank", entState.identity.rank)
		}) + (rankObj ? `\n${rankObj.tooltip}` : "");
		Engine.GetGUIObjectByName("rankIcon").sprite = `stretched:session/icons/ranks/${entState.identity.rank}.png`;
		Engine.GetGUIObjectByName("rankIcon").hidden = false;
	}
	else {
		Engine.GetGUIObjectByName("rankIcon").hidden = true;
		Engine.GetGUIObjectByName("rankIcon").tooltip = "";
	}

	if (entState.statusEffects) {
		const statusEffectsSection = Engine.GetGUIObjectByName("statusEffectsIcons");
		statusEffectsSection.hidden = false;
		const statusIcons = statusEffectsSection.children;
		let i = 0;
		for (const effectCode in entState.statusEffects) {
			const effect = entState.statusEffects[effectCode];
			statusIcons[i].hidden = false;
			statusIcons[i].sprite = `stretched:session/icons/status_effects/${g_StatusEffectsMetadata.getIcon(effect.baseCode)}.png`;
			statusIcons[i].tooltip = getStatusEffectsTooltip(effect.baseCode, effect, false);
			const size = statusIcons[i].size;
			size.top = i * 18;
			size.bottom = i * 18 + 16;
			statusIcons[i].size = size;

			if (++i >= statusIcons.length)
				break;
		}
		for (; i < statusIcons.length; ++i)
			statusIcons[i].hidden = true;
	}
	else
		Engine.GetGUIObjectByName("statusEffectsIcons").hidden = true;

	const showHealth = entState.hitpoints;
	const showResource = entState.resourceSupply;
	const showCapture = entState.capturePoints;

	const healthSection = Engine.GetGUIObjectByName("healthSection");
	const captureSection = Engine.GetGUIObjectByName("captureSection");
	const resourceSection = Engine.GetGUIObjectByName("resourceSection");
	const sectionPosTop = Engine.GetGUIObjectByName("sectionPosTop");
	const sectionPosMiddle = Engine.GetGUIObjectByName("sectionPosMiddle");
	const sectionPosBottom = Engine.GetGUIObjectByName("sectionPosBottom");

	// Hitpoints
	healthSection.hidden = !showHealth;
	if (showHealth) {
		const unitHealthBar = Engine.GetGUIObjectByName("healthBar");
		const healthSize = unitHealthBar.size;
		healthSize.rright = 100 * Math.max(0, Math.min(1, entState.hitpoints / entState.maxHitpoints));
		unitHealthBar.size = healthSize;
		Engine.GetGUIObjectByName("healthStats").caption = sprintf(translate("%(hitpoints)s / %(maxHitpoints)s"), {
			"hitpoints": Math.ceil(entState.hitpoints),
			"maxHitpoints": Math.ceil(entState.maxHitpoints)
		});
	}

	// CapturePoints
	captureSection.hidden = !entState.capturePoints;
	if (entState.capturePoints) {
		const setCaptureBarPart = function (playerID, startSize) {
			const unitCaptureBar = Engine.GetGUIObjectByName(`captureBar[${playerID}]`);
			const sizeObj = unitCaptureBar.size;
			sizeObj.rleft = startSize;

			const size = 100 * Math.max(0, Math.min(1, entState.capturePoints[playerID] / entState.maxCapturePoints));
			sizeObj.rright = startSize + size;
			unitCaptureBar.size = sizeObj;
			unitCaptureBar.sprite = `color:${g_DiplomacyColors.getPlayerColor(playerID, 200)}`;
			unitCaptureBar.hidden = false;
			return startSize + size;
		};

		// first handle the owner's points, to keep those points on the left for clarity
		let size = setCaptureBarPart(entState.player, 0);

		for (const i in entState.capturePoints)
			if (i != entState.player)
				size = setCaptureBarPart(i, size);

		const captureText = sprintf(translate("%(capturePoints)s / %(maxCapturePoints)s"), {
			"capturePoints": Math.ceil(entState.capturePoints[entState.player]),
			"maxCapturePoints": Math.ceil(entState.maxCapturePoints)
		});

		Engine.GetGUIObjectByName("captureStats").caption = captureText;
	}

	// Experience
	Engine.GetGUIObjectByName("experience").hidden = !entState.promotion;
	if (entState.promotion) {
		const experienceBar = Engine.GetGUIObjectByName("experienceBar");
		const experienceSize = experienceBar.size;
		experienceSize.rtop = 100 - (100 * Math.max(0, Math.min(1, 1.0 * +entState.promotion.curr / (+entState.promotion.req || 1))));
		experienceBar.size = experienceSize;

		if (entState.promotion.curr < entState.promotion.req)
			Engine.GetGUIObjectByName("experience").tooltip = sprintf(translate("%(experience)s %(current)s / %(required)s"), {
				"experience": `[font="sans-bold-13"]${translate("Experience:")}[/font]`,
				"current": Math.floor(entState.promotion.curr),
				"required": Math.ceil(entState.promotion.req)
			});
		else
			Engine.GetGUIObjectByName("experience").tooltip = sprintf(translate("%(experience)s %(current)s"), {
				"experience": `[font="sans-bold-13"]${translate("Experience:")}[/font]`,
				"current": Math.floor(entState.promotion.curr)
			});
	}

	// Resource stats
	resourceSection.hidden = !showResource;
	if (entState.resourceSupply) {
		const resources = entState.resourceSupply.isInfinite ? translate("∞") :  // Infinity symbol
			sprintf(translate("%(amount)s / %(max)s"), {
				"amount": Math.ceil(+entState.resourceSupply.amount),
				"max": entState.resourceSupply.max
			});

		const unitResourceBar = Engine.GetGUIObjectByName("resourceBar");
		const resourceSize = unitResourceBar.size;

		resourceSize.rright = entState.resourceSupply.isInfinite ? 100 :
			100 * Math.max(0, Math.min(1, +entState.resourceSupply.amount / +entState.resourceSupply.max));
		unitResourceBar.size = resourceSize;

		Engine.GetGUIObjectByName("resourceStats").caption = resources;
		Engine.GetGUIObjectByName("resourceStats").tooltip = resourceNameFirstWord(entState.resourceSupply.type.generic);
	}

	const resourceCarryingIcon = Engine.GetGUIObjectByName("resourceCarryingIcon");
	const resourceCarryingText = Engine.GetGUIObjectByName("resourceCarryingText");
	resourceCarryingIcon.hidden = false;
	resourceCarryingText.hidden = false;

	// Resource carrying
	if (entState.resourceCarrying && entState.resourceCarrying.length) {
		// Carrying one resource type at once, so just display the first
		const carried = entState.resourceCarrying[0];
		resourceCarryingIcon.sprite = `stretched:session/icons/resources/${carried.type}.png`;
		resourceCarryingText.caption = sprintf(translate("%(amount)s / %(max)s"), { "amount": carried.amount, "max": carried.max });
		resourceCarryingIcon.tooltip = "";
	}
	// Use the same indicators for traders
	else if (entState.trader && entState.trader.goods.amount) {
		resourceCarryingIcon.sprite = `stretched:session/icons/resources/${entState.trader.goods.type}.png`;
		let totalGain = entState.trader.goods.amount.traderGain;
		if (entState.trader.goods.amount.market1Gain)
			totalGain += entState.trader.goods.amount.market1Gain;
		if (entState.trader.goods.amount.market2Gain)
			totalGain += entState.trader.goods.amount.market2Gain;
		resourceCarryingText.caption = totalGain;
		resourceCarryingIcon.tooltip = sprintf(translate("Gain: %(gain)s"), {
			"gain": getTradingTooltip(entState.trader.goods.amount)
		});
	}
	// And for number of workers
	else if (entState.foundation) {
		resourceCarryingIcon.sprite = "stretched:session/icons/repair.png";
		resourceCarryingIcon.tooltip = getBuildTimeTooltip(entState);
		resourceCarryingText.caption = entState.foundation.numBuilders ? sprintf(translate("(%(number)s)\n%(time)s"), {
			"number": entState.foundation.numBuilders,
			"time": Engine.FormatMillisecondsIntoDateStringGMT(entState.foundation.buildTime.timeRemaining * 1000, translateWithContext("countdown format", "m:ss"))
		}) : "";
	}
	else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints)) {
		resourceCarryingIcon.sprite = "stretched:session/icons/repair.png";
		resourceCarryingText.caption = sprintf(translate("%(amount)s / %(max)s"), {
			"amount": entState.resourceSupply.numGatherers,
			"max": entState.resourceSupply.maxGatherers
		});
		Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Current/max gatherers");
	}
	else if (entState.repairable && entState.needsRepair) {
		resourceCarryingIcon.sprite = "stretched:session/icons/repair.png";
		resourceCarryingIcon.tooltip = getRepairTimeTooltip(entState);
		resourceCarryingText.caption = entState.repairable.numBuilders ? sprintf(translate("(%(number)s)\n%(time)s"), {
			"number": entState.repairable.numBuilders,
			"time": Engine.FormatMillisecondsIntoDateStringGMT(entState.repairable.buildTime.timeRemaining * 1000, translateWithContext("countdown format", "m:ss"))
		}) : "";
	}
	else {
		resourceCarryingIcon.hidden = true;
		resourceCarryingText.hidden = true;
	}

	Engine.GetGUIObjectByName("player").caption = playerName;

	Engine.GetGUIObjectByName("playerColorBackground").sprite =
		"color:" + g_DiplomacyColors.getPlayerColor(entState.player, 128);


	const hideSecondary = !secondaryName || primaryName == secondaryName;

	const primaryObject = Engine.GetGUIObjectByName("primary");
	primaryObject.caption = primaryName;
	const primaryObjectSize = primaryObject.size;
	primaryObjectSize.rbottom = hideSecondary ? 100 : 50;
	primaryObject.size = primaryObjectSize;

	const secondaryObject = Engine.GetGUIObjectByName("secondary");
	secondaryObject.caption = hideSecondary ? "" :
		sprintf(translate("(%(secondaryName)s)"), {
			"secondaryName": secondaryName
		});
	secondaryObject.hidden = hideSecondary;

	const isGaia = playerState.civ == "gaia";
	Engine.GetGUIObjectByName("playerCivIcon").sprite = isGaia ? "" : `cropped:1.0, 0.15625 center:grayscale:${civEmblem}`;
	Engine.GetGUIObjectByName("player").tooltip = isGaia ? "" : civName;

	if (isGaia) {
		Engine.GetGUIObjectByName("phaseEmblems").sprite = "";
		Engine.GetGUIObjectByName("player").tooltip = "";
	}
	else {
		let civilizationTooltip = civName;
		let civPhaseEmblems = "session/panel_phase_emblems_hidden.png";

		// Reveal phases to mutual allies and observers
		if (g_ViewedPlayer == -1 || playerState.isMutualAlly[g_ViewedPlayer]) {
			const civPhase = g_SimState.players[entState.player].phase
			civPhaseEmblems = "session/panel_phase_emblems_" + civPhase + ".png";
			const civPhaseData = GetTechnologyData("phase_" + civPhase + "_" + playerState.civ, playerState.civ) ||
				GetTechnologyData("phase_" + civPhase, playerState.civ);
			civilizationTooltip += " — " + getEntityNames(civPhaseData);
		}
		Engine.GetGUIObjectByName("phaseEmblems").sprite = "cropped:1.0, 1.0 center:" + civPhaseEmblems;
		Engine.GetGUIObjectByName("player").tooltip = civilizationTooltip;
	}

	// TODO: we should require all entities to have icons
	Engine.GetGUIObjectByName("icon").sprite = template.icon ? (`stretched:session/portraits/${template.icon}`) : "BackgroundBlack";
	if (template.icon) {
		Engine.GetGUIObjectByName("iconBorder").onPressRight = () => { showTemplateDetails(entState.template, playerState.civ); };
	}
	// Left-hand side of the stats panel
	// Attack per second
	let projectiles = 1;
	let projectilesDefaultTemplate = 1;
	let projectilesDefaultEmpty = 1;
	let garrisonProjectiles = 0;
	if (entState.buildingAI) {
		projectiles = entState.buildingAI.arrowCount || entState.buildingAI.defaultArrowCount;
		projectilesDefaultTemplate = templateData.buildingAI.defaultArrowCount;
		projectilesDefaultEmpty = entState.buildingAI.defaultArrowCount;
		garrisonProjectiles = Math.max(0, projectiles - projectilesDefaultEmpty);
	}
	if (!!entState?.attack?.Melee || !!entState?.attack?.Ranged) {
		// Final stats
		const attackType = entState?.attack?.Melee || entState?.attack?.Ranged;
		if (!attackType)
			warn(`The attackType for ${entState?.template} is undefined."`);
		const attackPower = (attackType?.Damage?.Hack || 0) + (attackType?.Damage?.Pierce || 0) + (attackType?.Damage?.Crush || 0);
		const currentDPS = attackPower * projectiles / attackType.repeatTime * 1000;

		const attackTypeBase = templateData?.attack?.Melee || templateData?.attack?.Ranged;
		if (!attackTypeBase)
			warn(`The attackType for ${templateData?.template} is undefined."`);
		const baseAttackPower = (attackTypeBase?.Damage?.Hack || 0) + (attackTypeBase?.Damage?.Pierce || 0) + (attackTypeBase?.Damage?.Crush || 0);
		const baseDPS = baseAttackPower * projectiles / attackTypeBase.repeatTime * 1000;

		// Calculate bonus percentage
		const bonusPercent = baseDPS > 0 ? Math.min(100, Math.round(((currentDPS - baseDPS) / baseDPS * 100))) : 0;

		// Color Intensity of the stat based on upgrade level
		const colorSteepness = 0.01; //Steepness of the color change

		const textcolor = garrisonProjectiles > 0 ? getTextBlueColorFromBonus(garrisonProjectiles) : getTextGreenColorFromBonus(bonusPercent, colorSteepness); //If garrison is adding arrows, display in blue, if not get the color scale.

		SetupStat(
			"LHS",
			0,
			"session/icons/attackPower.png",
			limitNumber(currentDPS),
			setupStatHUDAttackTooltip(entState, templateData, projectiles, projectilesDefaultEmpty, projectilesDefaultTemplate, colorSteepness),
			textcolor
		);
		SetupStat("FullSpace", 0, "", "");
	}
	else if (!!template?.treasure) {
		const treasureInfo = setupStatHUDTreasureInfo(template);
		SetupStat("LHS", 0, `session/icons/resources/${treasureInfo.resourceName}_small.png`, `${treasureInfo.resourceAmount}`, getTreasureTooltip(template));
		SetupStat("FullSpace", 0, "", "");
	}

	else if (template?.visibleIdentityClasses.includes("Relic")) {
		let text = [];
		for (const nameOfAuras in template.auras) {
			// we take the aura description and make an array of sentences
			const auraDescriptionCutInSentences = template.auras[nameOfAuras].description.match(/[^!.?]+[!.?]+/g);
			// the last sentence contains the important stuff we would like to display
			const auraSnippet = auraDescriptionCutInSentences.pop();
			// some of the description contains line breaks, we get rid of it here.
			text += `${coloredText("●", "orange") + auraSnippet.replace(/(\r\n|\n|\r)/gm, " ")}\n`;

			const radius = +template.auras[nameOfAuras].radius;
			if (radius)
				text += `${sprintf("%(label)s %(val)s %(unit)s", {
					"label": "Range:",
					"val": radius,
					"unit": unitFont("m")
				})}\n`;

		}
		const font = text.length < 280 ? "sans-13" : "sans-12";
		SetupStat("LHS", 0, "", "");
		SetupStat("FullSpace", 0, "", setStringTags(text, { font }), "");
	}

	else {
		SetupStat("LHS", 0, "", "");
		SetupStat("FullSpace", 0, "", "");
	}

	// Movement Speed
	if (!!entState?.speed && !template?.visibleIdentityClasses.includes("Relic")) {
		const walkSpeed = entState?.speed?.walk || 0;
		const baseWalkSpeed = templateData?.speed?.walk || walkSpeed;

		const bonusPercent = baseWalkSpeed > 0 ? Math.round(((walkSpeed - baseWalkSpeed) / baseWalkSpeed * 100)) : 0;
		const colorSteepness = 0.03;
		const textcolor = getTextGreenColorFromBonus(bonusPercent, colorSteepness);

		SetupStat(
			"LHS",
			1,
			"session/icons/walk.png",
			limitNumber(walkSpeed),
			setupStatHUDSpeedTooltip(entState, templateData, colorSteepness),
			textcolor
		);
	} else {
		SetupStat("LHS", 1, "", "");
	}

	// Range
	if (entState?.attack?.Ranged) {
		const rangeWithElevation = Math.round(entState.attack.Ranged.elevationAdaptedRange);
		const baseRange = templateData?.attack?.Ranged?.maxRange || entState.attack.Ranged.maxRange;

		const bonusPercent = baseRange > 0 ? Math.round(((rangeWithElevation - baseRange) / baseRange * 100)) : 0;
		const colorSteepness = 0.03;
		const textcolor = bonusPercent >= 0 ? getTextGreenColorFromBonus(bonusPercent, colorSteepness) : "255 230 230 255";

		SetupStat(
			"LHS",
			2,
			"session/icons/range.png",
			rangeWithElevation || 0,
			setupStatHUDRangeTooltip(entState.attack.Ranged, templateData, colorSteepness),
			textcolor
		);
	}
	else
		SetupStat("LHS", 2, "", "");

	// Right-hand side -> resistances
	if (entState?.resistance?.Damage) {
		const colorSteepness = 0.012;
		// Hack Resistance
		const hackResistance = entState.resistance.Damage?.Hack || 0;
		const baseHackResistance = templateData?.resistance?.Damage?.Hack || hackResistance;
		const hackBonusPercent = baseHackResistance > 0 ?
			Math.round(((hackResistance - baseHackResistance) * 10)) : 0;
		const hackColor = getTextGreenColorFromBonus(hackBonusPercent, colorSteepness);

		// Pierce Resistance
		const pierceResistance = entState.resistance.Damage?.Pierce || 0;
		const basePierceResistance = templateData?.resistance?.Damage?.Pierce || pierceResistance;
		const pierceBonusPercent = basePierceResistance > 0 ?
			Math.round(((pierceResistance - basePierceResistance) * 10)) : 0;
		const pierceColor = getTextGreenColorFromBonus(pierceBonusPercent, colorSteepness);

		// Crush Resistance
		const crushResistance = entState.resistance.Damage?.Crush || 0;
		const baseCrushResistance = templateData?.resistance?.Damage?.Crush || crushResistance;
		const crushBonusPercent = baseCrushResistance > 0 ?
			Math.round(((crushResistance - baseCrushResistance) * 10)) : 0;
		const crushColor = getTextGreenColorFromBonus(crushBonusPercent, colorSteepness);

		SetupStat(
			"RHS",
			0,
			"session/icons/res_hack.png",
			hackResistance,
			setupStatHUDHackResistanceTooltip(entState, templateData, hackColor),
			hackColor
		);
		SetupStat(
			"RHS",
			1,
			"session/icons/res_pierce.png",
			pierceResistance,
			setupStatHUDPierceResistanceTooltip(entState, templateData, pierceColor),
			pierceColor
		);
		SetupStat(
			"RHS",
			2,
			"session/icons/res_crush.png",
			crushResistance,
			setupStatHUDCrushResistanceTooltip(entState, templateData, crushColor),
			crushColor
		);
	}
	else {
		SetupStat("RHS", 0, "", "");
		SetupStat("RHS", 1, "", "");
		SetupStat("RHS", 2, "", "");
	}

	const detailedTooltip = [
		getAttackTooltip,
		getHealerTooltip,
		getResistanceTooltip,
		getGatherTooltip,
		getSpeedTooltip,
		getGarrisonTooltip,
		getTurretsTooltip,
		getPopulationBonusTooltip,
		getProjectilesTooltip,
		getResourceTrickleTooltip,
		getUpkeepTooltip,
		getLootTooltip
	].map(func => func(entState)).filter(Boolean).join("\n");
	if (detailedTooltip) {
		// for the relic we need the space to display text in the HUD and therefore it should be hidden.
		Engine.GetGUIObjectByName("attackAndResistanceStats").hidden = !!template?.visibleIdentityClasses.includes("Relic");
		Engine.GetGUIObjectByName("attackAndResistanceStats").tooltip = detailedTooltip;
	}
	else
		Engine.GetGUIObjectByName("attackAndResistanceStats").hidden = true;

	let iconTooltips = [];

	iconTooltips.push(setStringTags(primaryName, g_TooltipTextFormats.namePrimaryBig));
	iconTooltips = iconTooltips.concat([
		getVisibleEntityClassesFormatted,
		getAurasTooltip,
		getEntityTooltip,
		getTreasureTooltip,
		showTemplateViewerOnRightClickTooltip
	].map(func => func(template)));

	Engine.GetGUIObjectByName("iconBorder").tooltip = iconTooltips.filter(Boolean).join("\n");

	Engine.GetGUIObjectByName("detailsAreaSingle").hidden = false;
	Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
}

function displayMultiple(entStates) {
	let averageHealth = 0;
	let maxHealth = 0;
	let maxCapturePoints = 0;
	let capturePoints = (new Array(g_MaxPlayers + 1)).fill(0);
	let playerID = 0;
	let totalCarrying = {};
	let totalLoot = {};
	let garrisonSize = 0;

	for (let entState of entStates) {
		playerID = entState.player; // trust that all selected entities have the same owner
		if (entState.hitpoints) {
			averageHealth += entState.hitpoints;
			maxHealth += entState.maxHitpoints;
		}
		if (entState.capturePoints) {
			maxCapturePoints += entState.maxCapturePoints;
			capturePoints = entState.capturePoints.map((v, i) => v + capturePoints[i]);
		}

		let carrying = calculateCarriedResources(
			entState.resourceCarrying || null,
			entState.trader && entState.trader.goods
		);

		if (entState.loot)
			for (let type in entState.loot)
				totalLoot[type] = (totalLoot[type] || 0) + entState.loot[type];

		for (let type in carrying) {
			totalCarrying[type] = (totalCarrying[type] || 0) + carrying[type];
			totalLoot[type] = (totalLoot[type] || 0) + carrying[type];
		}

		if (entState.garrisonable)
			garrisonSize += entState.garrisonable.size;

		if (entState.garrisonHolder)
			garrisonSize += entState.garrisonHolder.occupiedSlots;
	}

	Engine.GetGUIObjectByName("healthMultiple").hidden = averageHealth <= 0;
	if (averageHealth > 0) {
		let unitHealthBar = Engine.GetGUIObjectByName("healthBarMultiple");
		let healthSize = unitHealthBar.size;
		healthSize.rtop = 100 - 100 * Math.max(0, Math.min(1, averageHealth / maxHealth));
		unitHealthBar.size = healthSize;

		Engine.GetGUIObjectByName("healthMultiple").tooltip = getCurrentHealthTooltip({
			"hitpoints": averageHealth,
			"maxHitpoints": maxHealth
		});
	}

	Engine.GetGUIObjectByName("captureMultiple").hidden = maxCapturePoints <= 0;
	if (maxCapturePoints > 0) {
		let setCaptureBarPart = function (pID, startSize) {
			let unitCaptureBar = Engine.GetGUIObjectByName("captureBarMultiple[" + pID + "]");
			let sizeObj = unitCaptureBar.size;
			sizeObj.rtop = startSize;

			let size = 100 * Math.max(0, Math.min(1, capturePoints[pID] / maxCapturePoints));
			sizeObj.rbottom = startSize + size;
			unitCaptureBar.size = sizeObj;
			unitCaptureBar.sprite = "color:" + g_DiplomacyColors.getPlayerColor(pID, 128);
			unitCaptureBar.hidden = false;
			return startSize + size;
		};

		let size = 0;
		for (let i in capturePoints)
			if (i != playerID)
				size = setCaptureBarPart(i, size);

		// last handle the owner's points, to keep those points on the bottom for clarity
		setCaptureBarPart(playerID, size);

		Engine.GetGUIObjectByName("captureMultiple").tooltip = getCurrentHealthTooltip(
			{
				"hitpoints": capturePoints[playerID],
				"maxHitpoints": maxCapturePoints
			},
			translate("Capture Points:"));
	}

	let numberOfUnits = Engine.GetGUIObjectByName("numberOfUnits");
	numberOfUnits.caption = entStates.length;
	numberOfUnits.tooltip = "";

	if (garrisonSize)
		numberOfUnits.tooltip = sprintf(translate("%(label)s: %(details)s\n"), {
			"label": headerFont(translate("Garrison Size")),
			"details": bodyFont(garrisonSize)
		});

	if (Object.keys(totalCarrying).length)
		numberOfUnits.tooltip = sprintf(translate("%(label)s %(details)s\n"), {
			"label": headerFont(translate("Carrying:")),
			"details": bodyFont(Object.keys(totalCarrying).filter(
				res => totalCarrying[res] != 0).map(
					res => sprintf(translate("%(type)s %(amount)s"),
						{ "type": resourceIcon(res), "amount": totalCarrying[res] })).join("  "))
		});

	if (Object.keys(totalLoot).length)
		numberOfUnits.tooltip += sprintf(translate("%(label)s %(details)s\n"), {
			"label": headerFont(translate("Loot:")),
			"details": bodyFont(Object.keys(totalLoot).filter(
				res => totalLoot[res] != 0).map(
					res => sprintf(translate("%(type)s %(amount)s"),
						{ "type": resourceIcon(res), "amount": totalLoot[res] })).join("  "))
		});
	// Unhide Details Area
	Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = false;
	Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
}