Index: binaries/data/mods/public/gui/session/input.js
===================================================================
--- binaries/data/mods/public/gui/session/input.js	(révision 14296)
+++ binaries/data/mods/public/gui/session/input.js	(copie de travail)
@@ -213,16 +213,14 @@
 	// Look at the first targeted entity
 	// (TODO: maybe we eventually want to look at more, and be more context-sensitive?
 	// e.g. prefer to attack an enemy unit, even if some friendly units are closer to the mouse)
-	var targetState = GetEntityState(target);
+	var targetState = GetExtendedEntityState(target);
 
 	var gaiaOwned = (targetState.player == 0);
 
 	// Look to see what type of command units going to the rally point should use
 	if (action == "set-rallypoint")
 	{
-		// We assume that all entities are owned by the same player.
-		var entState = GetEntityState(selection[0]);
-
+		// We assume that all entities are owned by the same player (given par entState of selection[0]).
 		var playerState = simState.players[entState.player];
 		var playerOwned = (targetState.player == entState.player);
 		var allyOwned = playerState.isAlly[targetState.player];
@@ -315,7 +313,7 @@
 	// can return to the dropsite, can build the foundation, or can attack the enemy
 	for each (var entityID in selection)
 	{
-		var entState = GetEntityState(entityID);
+		var entState = GetExtendedEntityState(entityID);
 		if (!entState)
 			continue;
 
@@ -485,7 +483,6 @@
 
 	var targets = [];
 	var target = undefined;
-	var targetState = undefined;
 	if (!fromMinimap)
 		targets = Engine.PickEntitiesAtPoint(x, y);
 
@@ -1852,7 +1849,7 @@
 {
 	if (entity)
 	{
-		var entState = GetEntityState(entity);
+		var entState = GetExtendedEntityState(entity);
 		var playerID = Engine.GetPlayerID();
 
 		if (entState.player == playerID || g_DevSettings.controlAll)
Index: binaries/data/mods/public/gui/session/selection_details.js
===================================================================
--- binaries/data/mods/public/gui/session/selection_details.js	(révision 14296)
+++ binaries/data/mods/public/gui/session/selection_details.js	(copie de travail)
@@ -325,7 +325,7 @@
 	/* If the unit has no data (e.g. it was killed), don't try displaying any
 	 data for it. (TODO: it should probably be removed from the selection too;
 	 also need to handle multi-unit selections) */
-	var entState = GetEntityState(selection[0]);
+	var entState = GetExtendedEntityState(selection[0]);
 	if (!entState)
 		return;
 
Index: binaries/data/mods/public/gui/session/session.js
===================================================================
--- binaries/data/mods/public/gui/session/session.js	(révision 14296)
+++ binaries/data/mods/public/gui/session/session.js	(copie de travail)
@@ -33,6 +33,7 @@
 
 // Cache EntityStates
 var g_EntityStates = {}; // {id:entState}
+var g_ExtendedEntityStates = {}; // {id:entState}
 
 // Whether the player has lost/won and reached the end of their game
 var g_GameEnded = false;
@@ -72,6 +73,27 @@
 	return g_EntityStates[entId];
 }
 
+function GetExtendedEntityState(entId)
+{
+	if (!(entId in g_ExtendedEntityStates))
+	{
+		if (!(entId in g_EntityStates))
+		{
+			var entState = Engine.GuiInterfaceCall("GetEntityState", entId);
+			g_EntityStates[entId] = entState;
+		}
+		else
+			var entState = g_EntityStates[entId];
+
+		var extendedEntState = Engine.GuiInterfaceCall("GetExtendedEntityState", entId);
+		for (var prop in entState)
+			extendedEntState[prop] = entState[prop];
+		g_ExtendedEntityStates[entId] = extendedEntState;
+	}
+
+	return g_ExtendedEntityStates[entId];
+}
+
 // Cache TemplateData
 var g_TemplateData = {}; // {id:template}
 
@@ -435,6 +457,7 @@
 function onSimulationUpdate()
 {
 	g_EntityStates = {};
+	g_ExtendedEntityStates = {};
 	g_TemplateData = {};
 	g_TechnologyData = {};
 
@@ -480,7 +503,7 @@
 	}
 
 	var heroImage = getGUIObjectByName("unitHeroImage");
-	var heroState = GetEntityState(playerState.heroes[0]);
+	var heroState = GetExtendedEntityState(playerState.heroes[0]);
 	var template = GetTemplateData(heroState.template);
 	heroImage.sprite = "stretched:session/portraits/" + template.icon;
 	var hero = playerState.heroes[0];
@@ -555,7 +578,7 @@
 	var selection = g_Selection.toList();
 	if (selection.length)
 	{
-		var entState = GetEntityState(selection[0]);
+		var entState = GetExtendedEntityState(selection[0]);
 		if (entState)
 		{
 			var template = GetTemplateData(entState.template);
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js	(révision 14296)
+++ binaries/data/mods/public/simulation/components/GuiInterface.js	(copie de travail)
@@ -145,6 +145,9 @@
 	this.renamedEntities = [];
 };
 
+/**
+ * Get common entity info, often used in the gui
+ */
 GuiInterface.prototype.GetEntityState = function(player, ent)
 {
 	var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
@@ -185,52 +188,6 @@
 		ret.needsHeal = !cmpHealth.IsUnhealable();
 	}
 
-	var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
-	var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
-	var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
-
-	if (cmpAttack)
-	{
-		var type = cmpAttack.GetBestAttack(); // TODO: how should we decide which attack to show? show all?
-		ret.attack = cmpAttack.GetAttackStrengths(type);
-		var range = cmpAttack.GetRange(type);
-		ret.attack.type = type;
-		ret.attack.minRange = range.min;
-		ret.attack.maxRange = range.max;
-		if (type == "Ranged")
-		{
-			ret.attack.elevationBonus = range.elevationBonus;
-			if (cmpUnitAI && cmpPosition && cmpPosition.IsInWorld())
-			{
-				// For units, take the rage in front of it, no spread. So angle = 0
-				ret.attack.elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(ret.position, ret.rotation, range.max, range.elevationBonus, 0);
-			}
-			else if(cmpPosition && cmpPosition.IsInWorld())
-			{
-				// For buildings, take the average elevation around it. So angle = 2*pi
-				ret.attack.elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(ret.position, ret.rotation, range.max, range.elevationBonus, 2*Math.PI);
-			}
-			else
-			{
-				// not in world, set a default?
-				ret.attack.elevationAdaptedRange = ret.attack.maxRange;
-			}
-			
-		}
-		else
-		{
-			// not a ranged attack, set some defaults
-			ret.attack.elevationBonus = 0;
-			ret.attack.elevationAdaptedRange = ret.attack.maxRange;
-		}
-	}
-
-	var cmpArmour = Engine.QueryInterface(ent, IID_DamageReceiver);
-	if (cmpArmour)
-	{
-		ret.armour = cmpArmour.GetArmourStrengths();
-	}
-
 	var cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
 	if (cmpBuilder)
 	{
@@ -274,50 +231,12 @@
 		};
 	}
 
-	var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
-	if (cmpObstruction)
-	{
-		ret.obstruction = {
-			"controlGroup": cmpObstruction.GetControlGroup(),
-			"controlGroup2": cmpObstruction.GetControlGroup2(),
-		};
-	}
-
 	var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
 	if (cmpOwnership)
 	{
 		ret.player = cmpOwnership.GetOwner();
 	}
 
-	var cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
-	if (cmpResourceSupply)
-	{
-		ret.resourceSupply = {
-			"isInfinite": cmpResourceSupply.IsInfinite(),
-			"max": cmpResourceSupply.GetMaxAmount(),
-			"amount": cmpResourceSupply.GetCurrentAmount(),
-			"type": cmpResourceSupply.GetType(),
-			"killBeforeGather": cmpResourceSupply.GetKillBeforeGather(),
-			"maxGatherers": cmpResourceSupply.GetMaxGatherers(),
-			"gatherers": cmpResourceSupply.GetGatherers()
-		};
-	}
-
-	var cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer);
-	if (cmpResourceGatherer)
-	{
-		ret.resourceGatherRates = cmpResourceGatherer.GetGatherRates();
-		ret.resourceCarrying = cmpResourceGatherer.GetCarryingStatus();
-	}
-
-	var cmpResourceDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite);
-	if (cmpResourceDropsite)
-	{
-		ret.resourceDropsite = {
-			"types": cmpResourceDropsite.GetTypes()
-		};
-	}
-
 	var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
 	if (cmpRallyPoint)
 	{
@@ -335,15 +254,7 @@
 		};
 	}
 	
-	var cmpPromotion = Engine.QueryInterface(ent, IID_Promotion);
-	if (cmpPromotion)
-	{
-		ret.promotion = {
-			"curr": cmpPromotion.GetCurrentXp(),
-			"req": cmpPromotion.GetRequiredXp()
-		};
-	}
-
+	var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
 	if (cmpUnitAI)
 	{
 		ret.unitAI = {
@@ -365,15 +276,131 @@
 			"entities": cmpGuard.GetEntities(),
 		};
 	}
-	
+
 	var cmpGate = Engine.QueryInterface(ent, IID_Gate);
 	if (cmpGate)
 	{
 		ret.gate = {
 			"locked": cmpGate.IsLocked(),
 		};
+	}
+
+	var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+	ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false);
+
+	return ret;
+};
+
+/**
+ * Get additionnal entity info, rarely used in the gui
+ */
+GuiInterface.prototype.GetExtendedEntityState = function(player, ent)
+{
+	var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+
+	// All units must have a template; if not then it's a nonexistent entity id
+	var template = cmpTemplateManager.GetCurrentTemplateName(ent);
+	if (!template)
+		return null;
+
+	var ret = { "id": ent };
+
+	var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
+	var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
+	var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+	var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
+
+	if (cmpAttack)
+	{
+		var type = cmpAttack.GetBestAttack(); // TODO: how should we decide which attack to show? show all?
+		ret.attack = cmpAttack.GetAttackStrengths(type);
+		var range = cmpAttack.GetRange(type);
+		ret.attack.type = type;
+		ret.attack.minRange = range.min;
+		ret.attack.maxRange = range.max;
+		if (type == "Ranged")
+		{
+			ret.attack.elevationBonus = range.elevationBonus;
+			var cmpPosition = Engine.QueryInterface(ent, IID_Position);
+			if (cmpUnitAI && cmpPosition && cmpPosition.IsInWorld())
+			{
+				// For units, take the rage in front of it, no spread. So angle = 0
+				ret.attack.elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(cmpPosition.GetPosition(), cmpPosition.GetRotation(), range.max, range.elevationBonus, 0);
+			}
+			else if(cmpPosition && cmpPosition.IsInWorld())
+			{
+				// For buildings, take the average elevation around it. So angle = 2*pi
+				ret.attack.elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(cmpPosition.GetPosition(), cmpPosition.GetRotation(), range.max, range.elevationBonus, 2*Math.PI);
+			}
+			else
+			{
+				// not in world, set a default?
+				ret.attack.elevationAdaptedRange = ret.attack.maxRange;
+			}
+			
+		}
+		else
+		{
+			// not a ranged attack, set some defaults
+			ret.attack.elevationBonus = 0;
+			ret.attack.elevationAdaptedRange = ret.attack.maxRange;
+		}
+	}
+
+	var cmpArmour = Engine.QueryInterface(ent, IID_DamageReceiver);
+	if (cmpArmour)
+	{
+		ret.armour = cmpArmour.GetArmourStrengths();
+	}
+
+	var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
+	if (cmpObstruction)
+	{
+		ret.obstruction = {
+			"controlGroup": cmpObstruction.GetControlGroup(),
+			"controlGroup2": cmpObstruction.GetControlGroup2(),
+		};
+	}
+
+	var cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
+	if (cmpResourceSupply)
+	{
+		ret.resourceSupply = {
+			"isInfinite": cmpResourceSupply.IsInfinite(),
+			"max": cmpResourceSupply.GetMaxAmount(),
+			"amount": cmpResourceSupply.GetCurrentAmount(),
+			"type": cmpResourceSupply.GetType(),
+			"killBeforeGather": cmpResourceSupply.GetKillBeforeGather(),
+			"maxGatherers": cmpResourceSupply.GetMaxGatherers(),
+			"gatherers": cmpResourceSupply.GetGatherers()
+		};
+	}
+
+	var cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer);
+	if (cmpResourceGatherer)
+	{
+		ret.resourceGatherRates = cmpResourceGatherer.GetGatherRates();
+		ret.resourceCarrying = cmpResourceGatherer.GetCarryingStatus();
+	}
+
+	var cmpResourceDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite);
+	if (cmpResourceDropsite)
+	{
+		ret.resourceDropsite = {
+			"types": cmpResourceDropsite.GetTypes()
+		};
+	}
+	
+	var cmpPromotion = Engine.QueryInterface(ent, IID_Promotion);
+	if (cmpPromotion)
+	{
+		ret.promotion = {
+			"curr": cmpPromotion.GetCurrentXp(),
+			"req": cmpPromotion.GetRequiredXp()
+		};
 	}	
 
+	var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
 	if (!cmpFoundation && cmpIdentity && cmpIdentity.HasClass("BarterMarket"))
 	{
 		var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
@@ -389,8 +416,6 @@
 		};
 	}
 
-	ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false);
-
 	return ret;
 };
 
@@ -407,8 +432,6 @@
 	return cmpRangeManager.GetElevationAdaptedRange(pos, rot, range, elevationBonus, 2*Math.PI);
 };
 
-
-
 GuiInterface.prototype.GetTemplateData = function(player, extendedName)
 {
 	var name = extendedName;
@@ -1799,6 +1822,7 @@
 	"GetRenamedEntities": 1,
 	"ClearRenamedEntities": 1,
 	"GetEntityState": 1,
+	"GetExtendedEntityState": 1,
 	"GetAverageRangeForBuildings": 1,
 	"GetTemplateData": 1,
 	"GetTechnologyData": 1,
