Index: binaries/data/mods/public/gui/session/selection_details.js
===================================================================
--- binaries/data/mods/public/gui/session/selection_details.js	(révision 16942)
+++ binaries/data/mods/public/gui/session/selection_details.js	(copie de travail)
@@ -201,6 +201,14 @@
 		else
 			Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Number of builders.");
 	}
+	else if (entState.repairable && entState.repairable.numBuilders > 0 && entState.visibility == "visible")
+	{
+		Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
+		Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
+		Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
+		Engine.GetGUIObjectByName("resourceCarryingText").caption = entState.repairable.numBuilders + "    ";
+		Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Number of builders.");
+	}
 	else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints) && entState.visibility == "visible")
 	{
 		Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Index: binaries/data/mods/public/simulation/components/Builder.js
===================================================================
--- binaries/data/mods/public/simulation/components/Builder.js	(révision 16942)
+++ binaries/data/mods/public/simulation/components/Builder.js	(copie de travail)
@@ -64,10 +64,10 @@
  */
 Builder.prototype.PerformBuilding = function(target)
 {
-	var rate = ApplyValueModificationsToEntity("Builder/Rate", +this.template.Rate, this.entity);
+	let rate = ApplyValueModificationsToEntity("Builder/Rate", +this.template.Rate, this.entity);
 
 	// If it's a foundation, then build it
-	var cmpFoundation = Engine.QueryInterface(target, IID_Foundation);
+	let cmpFoundation = Engine.QueryInterface(target, IID_Foundation);
 	if (cmpFoundation)
 	{
 		cmpFoundation.Build(this.entity, rate);
@@ -75,10 +75,10 @@
 	}
 
 	// Otherwise try to repair it
-	var cmpHealth = Engine.QueryInterface(target, IID_Health);
-	if (cmpHealth)
+	let cmpRepairable = Engine.QueryInterface(target, IID_Repairable);
+	if (cmpRepairable)
 	{
-		cmpHealth.Repair(this.entity, rate);
+		cmpRepairable.Repair(this.entity, rate);
 		return;
 	}
 };
Index: binaries/data/mods/public/simulation/components/Foundation.js
===================================================================
--- binaries/data/mods/public/simulation/components/Foundation.js	(révision 16942)
+++ binaries/data/mods/public/simulation/components/Foundation.js	(copie de travail)
@@ -142,8 +142,8 @@
  */
 Foundation.prototype.SetBuildMultiplier = function()
 {
-	var numBuilders = this.builders.length;
-	if (numBuilders == 0)
+	let numBuilders = this.builders.length;
+	if (numBuilders < 2)
 		this.buildMultiplier = 1;
 	else
 		this.buildMultiplier = Math.pow(numBuilders, 0.7) / numBuilders;
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js	(révision 16942)
+++ binaries/data/mods/public/simulation/components/GuiInterface.js	(copie de travail)
@@ -333,6 +333,14 @@
 		};
 	}
 
+	var cmpRepairable = QueryMiragedInterface(ent, IID_Repairable);
+	if (cmpRepairable)
+	{
+		ret.repairable = {
+			"numBuilders": cmpRepairable.GetNumBuilders()
+		};
+	}
+    
 	var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
 	if (cmpOwnership)
 	{
Index: binaries/data/mods/public/simulation/components/Health.js
===================================================================
--- binaries/data/mods/public/simulation/components/Health.js	(révision 16942)
+++ binaries/data/mods/public/simulation/components/Health.js	(copie de travail)
@@ -32,9 +32,6 @@
 	"</element>" +
 	"<element name='Unhealable' a:help='Indicates that the entity can not be healed by healer units'>" +
 		"<data type='boolean'/>" +
-	"</element>" +
-	"<element name='Repairable' a:help='Indicates that the entity can be repaired by builder units'>" +
-		"<data type='boolean'/>" +
 	"</element>";
 
 Health.prototype.Init = function()
@@ -92,7 +89,7 @@
 
 Health.prototype.IsRepairable = function()
 {
-	return (this.template.Repairable == "true");
+	return (Engine.QueryInterface(this.entity, IID_Repairable) != null);
 };
 
 Health.prototype.IsUnhealable = function()
@@ -315,33 +312,6 @@
 	return spawnedEntity;
 };
 
-Health.prototype.Repair = function(builderEnt, work)
-{
-	var damage = this.GetMaxHitpoints() - this.GetHitpoints();
-
-	// Do nothing if we're already at full hitpoints
-	if (damage <= 0)
-		return;
-
-	// Calculate the amount of hitpoints that will be added
-	// TODO: what computation should we use?
-	// TODO: should we do some diminishing returns thing? (see Foundation.Build)
-	var amount = Math.min(damage, work);
-
-	// TODO: resource costs?
-
-	// Add hitpoints
-	this.Increase(amount);
-
-	// If we repaired all the damage, send a message to entities
-	// to stop repairing this building
-	if (amount >= damage)
-	{
-		Engine.PostMessage(this.entity, MT_ConstructionFinished,
-			{ "entity": this.entity, "newentity": this.entity });
-	}
-};
-
 Health.prototype.OnValueModification = function(msg)
 {
 	if (msg.component != "Health")
Index: binaries/data/mods/public/simulation/components/Repairable.js
===================================================================
--- binaries/data/mods/public/simulation/components/Repairable.js	(révision 0)
+++ binaries/data/mods/public/simulation/components/Repairable.js	(copie de travail)
@@ -0,0 +1,75 @@
+function Repairable() {}
+
+Repairable.prototype.Schema =
+	"<a:help>Deals with repairable structures and units.</a:help>" +
+	"<a:example>" +
+		"<RepairTimeRatio>2.0</RepairTimeRatio>" +
+	"</a:example>" +
+	"<element name='RepairTimeRatio' a:help='repair time ratio relative to building (or production) time.'>" +
+		"<ref name='positiveDecimal'/>" +
+	"</element>";
+
+Repairable.prototype.Init = function()
+{
+	this.builders = []; // builder entities
+	this.buildMultiplier = 1; // Multiplier for the amount of work builders do.
+	this.repairTimeRatio = +this.template.RepairTimeRatio;
+};
+
+Repairable.prototype.GetNumBuilders = function()
+{
+	return this.builders.length;
+};
+
+Repairable.prototype.AddBuilder = function(builderEnt)
+{
+	if (this.builders.indexOf(builderEnt) !== -1)
+		return;
+	this.builders.push(builderEnt);
+	this.SetBuildMultiplier();
+};
+
+Repairable.prototype.RemoveBuilder = function(builderEnt)
+{
+	if (this.builders.indexOf(builderEnt) === -1)
+		return;
+	this.builders.splice(this.builders.indexOf(builderEnt), 1);
+ 	this.SetBuildMultiplier();
+ };
+
+/**
+ * Sets the build rate multiplier, which is applied to all builders.
+ * Yields a total rate of construction equal to numBuilders^0.7
+ */
+Repairable.prototype.SetBuildMultiplier = function()
+{
+	let numBuilders = this.builders.length;
+	if (numBuilders < 2)
+		this.buildMultiplier = 1;
+	else
+		this.buildMultiplier = Math.pow(numBuilders, 0.7) / numBuilders;
+};
+
+// TODO: should we have resource costs?
+Repairable.prototype.Repair = function(builderEnt, rate)
+{
+	let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
+	let cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
+	if (!cmpHealth || !cmpCost)
+		return;
+	let damage = cmpHealth.GetMaxHitpoints() - cmpHealth.GetHitpoints();
+	if (damage <= 0)
+		return;
+
+	// Calculate the amount of hitpoints that will be added (using diminushing rate when several builders)
+	let repairTime = this.repairTimeRatio * cmpCost.GetBuildTime();
+	let work = repairTime ? rate * this.buildMultiplier * cmpHealth.GetMaxHitpoints() / repairTime : rate * this.buildMultiplier;
+	let amount = Math.min(damage, work);
+	cmpHealth.Increase(amount);
+
+	// If we repaired all the damage, send a message to entities to stop repairing this building
+	if (amount >= damage)
+		Engine.PostMessage(this.entity, MT_ConstructionFinished, { "entity": this.entity, "newentity": this.entity });
+};
+
+Engine.RegisterComponentType(IID_Repairable, "Repairable", Repairable);
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js	(révision 16942)
+++ binaries/data/mods/public/simulation/components/UnitAI.js	(copie de travail)
@@ -2615,9 +2615,9 @@
 						return true;
 					}
 
-					var cmpFoundation = Engine.QueryInterface(this.repairTarget, IID_Foundation);
-					if (cmpFoundation)
-						cmpFoundation.AddBuilder(this.entity);
+					let cmpBuilderList = QueryBuilderListInterface(this.repairTarget);
+					if (cmpBuilderList)
+						cmpBuilderList.AddBuilder(this.entity);
 
 					this.SelectAnimation("build", false, 1.0, "build");
 					this.StartTimer(1000, 1000);
@@ -2625,9 +2625,9 @@
 				},
 
 				"leave": function() {
-					var cmpFoundation = Engine.QueryInterface(this.repairTarget, IID_Foundation);
-					if (cmpFoundation)
-						cmpFoundation.RemoveBuilder(this.entity);
+					let cmpBuilderList = QueryBuilderListInterface(this.repairTarget);
+					if (cmpBuilderList)
+						cmpBuilderList.RemoveBuilder(this.entity);
 					delete this.repairTarget;
 					this.StopTimer();
 				},
Index: binaries/data/mods/public/simulation/components/interfaces/Repairable.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/Repairable.js	(révision 0)
+++ binaries/data/mods/public/simulation/components/interfaces/Repairable.js	(copie de travail)
@@ -0,0 +1 @@
+Engine.RegisterInterface("Repairable");
Index: binaries/data/mods/public/simulation/helpers/Player.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Player.js	(révision 16942)
+++ binaries/data/mods/public/simulation/helpers/Player.js	(copie de travail)
@@ -243,6 +243,15 @@
 }
 
 /**
+ * Similar to Engine.QueryInterface, but checks for all interfaces
+ * implementing a builder list (currently Foundation and Repairable)
+ */
+function QueryBuilderListInterface(ent)
+{
+	return Engine.QueryInterface(ent, IID_Foundation) || Engine.QueryInterface(ent, IID_Repairable);
+}
+
+/**
  * Returns true if the entity 'target' is owned by an ally of
  * the owner of 'entity'.
  */
@@ -400,6 +409,7 @@
 Engine.RegisterGlobal("QueryOwnerInterface", QueryOwnerInterface);
 Engine.RegisterGlobal("QueryPlayerIDInterface", QueryPlayerIDInterface);
 Engine.RegisterGlobal("QueryMiragedInterface", QueryMiragedInterface);
+Engine.RegisterGlobal("QueryBuilderListInterface", QueryBuilderListInterface);
 Engine.RegisterGlobal("IsOwnedByAllyOfEntity", IsOwnedByAllyOfEntity);
 Engine.RegisterGlobal("IsOwnedByMutualAllyOfEntity", IsOwnedByMutualAllyOfEntity);
 Engine.RegisterGlobal("IsOwnedByPlayer", IsOwnedByPlayer);
Index: binaries/data/mods/public/simulation/templates/gaia/fauna_shark.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/gaia/fauna_shark.xml	(révision 16942)
+++ binaries/data/mods/public/simulation/templates/gaia/fauna_shark.xml	(copie de travail)
@@ -1,58 +1,57 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Entity parent="template_unit_fauna_wild_passive">
-	<Footprint replace="">
-		<Square width="5" depth="8"/>
-		<Height>7.5</Height>
-	</Footprint>
-	<Health>
-		<Max>150</Max>
-		<Unhealable>true</Unhealable>
-		<Repairable>false</Repairable>
-	</Health>
-	<Identity>
-		<Civ>gaia</Civ>
-		<SpecificName>Great White</SpecificName>
-		<GenericName>Shark</GenericName>
-		<Classes datatype="tokens">SeaCreature</Classes>
-		<Icon>gaia/fauna_fish.png</Icon>
-	</Identity>
-	<Minimap disable=""/>
-	<Position>
-		<Altitude>-1</Altitude>
-		<Floating>true</Floating>
-		<TurnRate>5.0</TurnRate>
-	</Position>
-	<Selectable>
-		<Overlay>
-			<Texture>
-				<MainTexture>circle/128x128.png</MainTexture>
-				<MainTextureMask>circle/128x128_mask.png</MainTextureMask>
-			</Texture>
-		</Overlay>
-	</Selectable>
-	<Visibility>
-		<RetainInFog>false</RetainInFog>
-	</Visibility>
-	<Vision>
-		<Range>100</Range>
-	</Vision>
-	<VisualActor>
-		<Actor>fauna/shark.xml</Actor>
-	</VisualActor>
-	<UnitAI>
-		<NaturalBehaviour>passive</NaturalBehaviour>
-		<RoamDistance>100.0</RoamDistance>
-		<FleeDistance>60.0</FleeDistance>
-		<RoamTimeMin>100000</RoamTimeMin>
-		<RoamTimeMax>300000</RoamTimeMax>
-		<FeedTimeMin>1</FeedTimeMin>
-		<FeedTimeMax>2</FeedTimeMax>
-	</UnitAI>
-	<UnitMotion>
-		<PassabilityClass>ship-small</PassabilityClass>
-		<WalkSpeed>4.0</WalkSpeed>
-		<Run>
-			<Speed>35.0</Speed>
-		</Run>
-	</UnitMotion>
+  <Footprint replace="">
+    <Square width="5" depth="8"/>
+    <Height>7.5</Height>
+  </Footprint>
+  <Health>
+    <Max>150</Max>
+    <Unhealable>true</Unhealable>
+  </Health>
+  <Identity>
+    <Civ>gaia</Civ>
+    <SpecificName>Great White</SpecificName>
+    <GenericName>Shark</GenericName>
+    <Classes datatype="tokens">SeaCreature</Classes>
+    <Icon>gaia/fauna_fish.png</Icon>
+  </Identity>
+  <Minimap disable=""/>
+  <Position>
+    <Altitude>-1</Altitude>
+    <Floating>true</Floating>
+    <TurnRate>5.0</TurnRate>
+  </Position>
+  <Selectable>
+    <Overlay>
+      <Texture>
+        <MainTexture>circle/128x128.png</MainTexture>
+        <MainTextureMask>circle/128x128_mask.png</MainTextureMask>
+      </Texture>
+    </Overlay>
+  </Selectable>
+  <Visibility>
+    <RetainInFog>false</RetainInFog>
+  </Visibility>
+  <Vision>
+    <Range>100</Range>
+  </Vision>
+  <VisualActor>
+    <Actor>fauna/shark.xml</Actor>
+  </VisualActor>
+  <UnitAI>
+    <NaturalBehaviour>passive</NaturalBehaviour>
+    <RoamDistance>100.0</RoamDistance>
+    <FleeDistance>60.0</FleeDistance>
+    <RoamTimeMin>100000</RoamTimeMin>
+    <RoamTimeMax>300000</RoamTimeMax>
+    <FeedTimeMin>1</FeedTimeMin>
+    <FeedTimeMax>2</FeedTimeMax>
+  </UnitAI>
+  <UnitMotion>
+    <PassabilityClass>ship-small</PassabilityClass>
+    <WalkSpeed>4.0</WalkSpeed>
+    <Run>
+      <Speed>35.0</Speed>
+    </Run>
+  </UnitMotion>
 </Entity>
Index: binaries/data/mods/public/simulation/templates/other/plane.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/other/plane.xml	(révision 16942)
+++ binaries/data/mods/public/simulation/templates/other/plane.xml	(copie de travail)
@@ -41,10 +41,12 @@
     <Formations datatype="tokens" replace=""/>
   </Identity>
   <Health>
-	 <Max>100</Max>
+    <Max>100</Max>
     <Unhealable>true</Unhealable>
-    <Repairable>true</Repairable>
   </Health>
+  <Repairable>
+    <RepairTimeRatio>3.0</RepairTimeRatio>
+  </Repairable>
   <Obstruction disable=""/>
   <Position>
     <TurnRate>1.0</TurnRate>
Index: binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml	(révision 16942)
+++ binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml	(copie de travail)
@@ -51,7 +51,6 @@
   </GarrisonHolder>
   <Health>
     <Max>2500</Max>
-    <Repairable>true</Repairable>
     <SpawnEntityOnDeath>rubble/rubble_rome_sb</SpawnEntityOnDeath>
   </Health>
   <Identity>
@@ -59,8 +58,8 @@
     <GenericName>Entrenched Army Camp</GenericName>
     <SpecificName>Castrum Vallum</SpecificName>
     <Classes datatype="tokens">
-	  ArmyCamp
-	</Classes>
+      ArmyCamp
+    </Classes>
     <Icon>structures/roman_camp.png</Icon>
     <Tooltip>Build anywhere on the map, even in enemy territory. Construct siege weapons and train citizen-soldiers. Heal garrisoned units slowly.</Tooltip>
     <History>Sometimes it was a temporary camp built facing the route by which the army is to march, other times a defensive or offensive (for sieges) structure. Within this gate the tents of the first centuries or cohorts are pitched, and the dragons (ensigns of cohorts) and other ensigns planted. The Decumane gate is directly opposite to the Praetorian in the rear of the camp, and through this the soldiers are conducted to the place appointed for punishment or execution.</History>
Index: binaries/data/mods/public/simulation/templates/template_structure.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure.xml	(révision 16942)
+++ binaries/data/mods/public/simulation/templates/template_structure.xml	(copie de travail)
@@ -48,8 +48,10 @@
     <DeathType>corpse</DeathType>
     <RegenRate>0</RegenRate>
     <Unhealable>true</Unhealable>
-    <Repairable>true</Repairable>
   </Health>
+  <Repairable>
+    <RepairTimeRatio>2.0</RepairTimeRatio>
+  </Repairable>
   <Identity>
     <GenericName>Structure</GenericName>
     <Classes datatype="tokens">Structure ConquestCritical</Classes>
Index: binaries/data/mods/public/simulation/templates/template_unit.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit.xml	(révision 16942)
+++ binaries/data/mods/public/simulation/templates/template_unit.xml	(copie de travail)
@@ -32,14 +32,13 @@
     <Max>100</Max>
     <RegenRate>0</RegenRate>
     <Unhealable>false</Unhealable>
-    <Repairable>false</Repairable>
   </Health>
   <Identity>
     <GenericName>Unit</GenericName>
     <Classes datatype="tokens">Unit ConquestCritical</Classes>
-		<Formations datatype="tokens">
-			formations/null
-		</Formations>
+    <Formations datatype="tokens">
+      formations/null
+    </Formations>
   </Identity>
   <Looter/>
   <Minimap>
Index: binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_whale.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_whale.xml	(révision 16942)
+++ binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_whale.xml	(copie de travail)
@@ -8,23 +8,22 @@
     <Max>200</Max>
     <DeathType>remain</DeathType>
     <Unhealable>true</Unhealable>
-    <Repairable>false</Repairable>
   </Health>
   <Identity>
     <Classes datatype="tokens">SeaCreature</Classes>
     <Civ>gaia</Civ>
     <GenericName>Cetacean</GenericName>
-	<Tooltip>Kill, then collect food from this bountiful oceanic resource.</Tooltip>
+    <Tooltip>Kill, then collect food from this bountiful oceanic resource.</Tooltip>
     <Icon>gaia/fauna_fish.png</Icon>
   </Identity>
   <Position>
-	<Floating>true</Floating>
+    <Floating>true</Floating>
   </Position>
   <ResourceSupply>
     <KillBeforeGather>true</KillBeforeGather>
     <Amount>2000</Amount>
     <Type>food.fish</Type>
-	<MaxGatherers>5</MaxGatherers>
+    <MaxGatherers>5</MaxGatherers>
   </ResourceSupply>
   <Selectable>
     <Overlay>
Index: binaries/data/mods/public/simulation/templates/template_unit_mechanical.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_mechanical.xml	(révision 16942)
+++ binaries/data/mods/public/simulation/templates/template_unit_mechanical.xml	(copie de travail)
@@ -7,8 +7,10 @@
   </Decay>
   <Health>
     <Unhealable>true</Unhealable>
-    <Repairable>true</Repairable>
   </Health>
+  <Repairable>
+    <RepairTimeRatio>4.0</RepairTimeRatio>
+  </Repairable>
   <Identity>
     <GenericName>Mechanical</GenericName>
     <VisibleClasses datatype="tokens">Mechanical</VisibleClasses>
