Index: binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml	(révision 13808)
+++ binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml	(copie de travail)
@@ -45,6 +45,7 @@
     <List datatype="tokens">Support Infantry Cavalry Siege</List>
     <BuffHeal>1</BuffHeal>
     <LoadingRange>10</LoadingRange>
+    <Pickup>true</Pickup>
   </GarrisonHolder>
   <Health>
     <Max>2000</Max>
Index: binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml	(révision 13808)
+++ binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml	(copie de travail)
@@ -37,6 +37,7 @@
     <List datatype="tokens">Support Infantry Cavalry</List>
     <BuffHeal>1</BuffHeal>
     <LoadingRange>10</LoadingRange>
+    <Pickup>true</Pickup>
   </GarrisonHolder>
   <Health>
     <Max>800</Max>
Index: binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml	(révision 13808)
+++ binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml	(copie de travail)
@@ -37,6 +37,7 @@
     <List datatype="tokens">Support Infantry Cavalry Siege</List>
     <BuffHeal>1</BuffHeal>
     <LoadingRange>10</LoadingRange>
+    <Pickup>true</Pickup>
   </GarrisonHolder>
   <Health>
     <Max>1400</Max>
Index: binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fishing.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fishing.xml	(révision 13808)
+++ binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fishing.xml	(copie de travail)
@@ -26,6 +26,7 @@
     <List datatype="tokens">Support Infantry</List>
     <BuffHeal>1</BuffHeal>
     <LoadingRange>10</LoadingRange>
+    <Pickup>true</Pickup>
   </GarrisonHolder>
   <Identity>
     <GenericName>Fishing Boat</GenericName>
Index: binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_merchant.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_merchant.xml	(révision 13808)
+++ binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_merchant.xml	(copie de travail)
@@ -18,6 +18,7 @@
     <List datatype="tokens">Support Infantry Cavalry</List>
     <BuffHeal>1</BuffHeal>
     <LoadingRange>10</LoadingRange>
+    <Pickup>true</Pickup>
   </GarrisonHolder>
   <Health>
     <Max>400</Max>
Index: binaries/data/mods/public/simulation/components/GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/GarrisonHolder.js	(révision 13808)
+++ binaries/data/mods/public/simulation/components/GarrisonHolder.js	(copie de travail)
@@ -21,7 +21,12 @@
 	"</element>" +
 	"<element name='LoadingRange' a:help='The maximum distance from this holder at which entities are allowed to garrison. Should be about 2.0 for land entities and preferably greater for ships'>" +
 		"<ref name='nonNegativeDecimal'/>" +
-	"</element>";
+	"</element>" +
+	"<optional>" +
+		"<element name='Pickup' a:help='This garrisonHolder will move to pick up units to be garrisoned'>" +
+			"<data type='boolean'/>" +
+		"</element>" +
+	"</optional>";
 
 /**
  * Initialize GarrisonHolder Component
@@ -45,6 +50,21 @@
 };
 
 /**
+ * Return true if this garrisonHolder can pickup ent
+ */
+GarrisonHolder.prototype.CanPickup = function(ent)
+{
+	if (!this.template.Pickup || this.IsFull())
+		return false;
+	var cmpOwner = Engine.QueryInterface(this.entity, IID_Ownership);
+	if (!cmpOwner)
+		return false;
+	var player = cmpOwner.GetOwner();
+	return IsOwnedByPlayer(player, ent);
+};
+
+
+/**
  * Return the list of entities garrisoned inside
  */
 GarrisonHolder.prototype.GetEntities = function()
@@ -71,6 +91,14 @@
 };
 
 /**
+ * Return true if this garrisonHolder is full
+ */
+GarrisonHolder.prototype.IsFull = function()
+{
+	return this.entities.length >= this.GetCapacity();
+};
+
+/**
  * Get the heal rate with which garrisoned units will be healed
  */
 GarrisonHolder.prototype.GetHealRate = function()
Index: binaries/data/mods/public/simulation/components/interfaces/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/UnitAI.js	(révision 13808)
+++ binaries/data/mods/public/simulation/components/interfaces/UnitAI.js	(copie de travail)
@@ -11,3 +11,11 @@
 // Message of the form { "to": orderData }.
 // sent whenever the unit changes state
 Engine.RegisterMessageType("UnitAIOrderDataChanged");
+
+// Message of the form { "entity": entity },
+// sent whenever a pickup is requested
+Engine.RegisterMessageType("PickupRequested");
+
+// Message of the form { "entity": entity },
+// sent whenever a pickup is no more needed
+Engine.RegisterMessageType("PickupCanceled");
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js	(révision 13808)
+++ binaries/data/mods/public/simulation/components/UnitAI.js	(copie de travail)
@@ -160,6 +160,10 @@
 		// ignore
 	},
 
+	"PickupCanceled": function(msg) {
+		// ignore
+	},
+
 	// Formation handlers:
 
 	"FormationLeave": function(msg) {
@@ -328,6 +332,31 @@
 		}
 	},
 
+	"Order.PickupUnit": function(msg) {
+		var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
+		if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull())
+		{
+			this.FinishOrder();
+			return;
+		}
+
+		// TODO improve these movements in case of ship : the MoveToTarget of the target brings
+		// it to the nearest point-on-land from the ship, while the MoveToTarget of the ship
+		// brings it to the nearest point-on-water from the target, and these can be quite
+		// different in some cases leading to weird movements. MoveToTarget should update its path
+		// according to the target movement more often.
+		if (this.MoveToTarget(this.order.data.target))
+		{
+			this.SetNextState("INDIVIDUAL.PICKUP.APPROACHING");
+		}
+		else
+		{
+		// We are already at the target, or can't move at all
+			this.StopMoving();
+			this.SetNextState("INDIVIDUAL.PICKUP.LOADING");
+		}
+	},
+
 	"Order.Flee": function(msg) {
 		// We use the distance between the enities to account for ranged attacks
 		var distance = DistanceBetweenEntities(this.entity, this.order.data.target) + (+this.template.FleeDistance);
@@ -608,7 +637,7 @@
 			return;
 		}
 
-		if (this.MoveToTarget(this.order.data.target))
+		if (this.MoveToGarrisonRange(this.order.data.target))
 		{
 			this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING");
 		}
@@ -727,26 +756,31 @@
 		},
 
 		"Order.Garrison": function(msg) {
-			// TODO: on what should we base this range?
+			if (!Engine.QueryInterface(msg.data.target, IID_GarrisonHolder))
+			{
+				this.FinishOrder();
+				return;
+			}
 			// Check if we are already in range, otherwise walk there
-			if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10))
+			if (!this.CheckGarrisonRange(msg.data.target))
 			{
-				if (!this.TargetIsAlive(msg.data.target) || !this.CheckTargetVisible(msg.data.target))
-					// The target was destroyed
+				if (!this.CheckTargetVisible(msg.data.target))
+				{
 					this.FinishOrder();
+					return;
+				}
 				else
+				{
 					// Out of range; move there in formation
-					this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 });
-				return;
+					if (this.MoveToGarrisonRange(msg.data.target))
+					{
+						this.SetNextState("GARRISON.APPROACHING");
+						return;
+					}
+				}
 			}
 
-			var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
-			// We don't want to rearrange the formation if the individual units are carrying
-			// out a task and one of the members dies/leaves the formation.
-			cmpFormation.SetRearrange(false);
-			cmpFormation.CallMemberFunction("Garrison", [msg.data.target, false]);
-
-			this.SetNextStateAlwaysEntering("MEMBER");
+			this.SetNextState("GARRISON.GARRISONING");
 		},
 
 		"Order.Gather": function(msg) {
@@ -977,6 +1011,57 @@
 			},
 		},
 
+		"GARRISON":{
+			"enter": function() {
+				// If the garrisonholder should pickup, warn it so it can take needed action
+				var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder);
+				if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity))
+				{
+					this.pickup = this.order.data.target;       // temporary, deleted in "leave"
+					Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity });
+				}
+			},
+
+			"leave": function() {
+				// If a pickup has been requested and not yet canceled, cancel it
+				if (this.pickup)
+				{
+					Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity });
+					delete this.pickup;
+				}
+			},
+
+
+			"APPROACHING": {
+				"MoveStarted": function(msg) {
+					var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
+					cmpFormation.SetRearrange(true);
+					cmpFormation.MoveMembersIntoFormation(true, true);
+				},
+
+				"MoveCompleted": function(msg) {
+					this.SetNextState("GARRISONING");
+				},
+			},
+
+			"GARRISONING": {
+				"enter": function() {
+					// If a pickup has been requested, cancel it as it will be requested by members
+					if (this.pickup)
+					{
+						Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity });
+						delete this.pickup;
+					}
+					var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
+					// We don't want to rearrange the formation if the individual units are carrying
+					// out a task and one of the members dies/leaves the formation.
+					cmpFormation.SetRearrange(false);
+					cmpFormation.CallMemberFunction("Garrison", [this.order.data.target, false]);
+					this.SetNextStateAlwaysEntering("MEMBER");
+				},
+			},
+		},
+
 		"FORMING": {
 			"MoveStarted": function(msg) {
 				var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
@@ -2257,6 +2342,26 @@
 		},
 
 		"GARRISON": {
+			"enter": function() {
+				// If the garrisonholder should pickup, warn it so it can take needed action
+				var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder);
+				if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity))
+				{
+					this.pickup = this.order.data.target;       // temporary, deleted in "leave"
+					Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity });
+				}
+			},
+
+			"leave": function() {
+				// If a pickup has been requested and not yet canceled, cancel it
+				if (this.pickup)
+				{
+					Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity });
+					delete this.pickup;
+				}
+
+			},
+
 			"APPROACHING": {
 				"enter": function() {
 					this.SelectAnimation("move");
@@ -2265,10 +2370,6 @@
 				"MoveCompleted": function() {
 					this.SetNextState("GARRISONED");
 				},
-				
-				"leave": function() {
-					this.StopTimer();
-				}
 			},
 
 			"GARRISONED": {
@@ -2300,14 +2401,32 @@
 										this.SetGathererAnimationOverride();
 									}
 								}
+
+								// If a pickup has been requested, remove it
+								if (this.pickup)
+								{
+									Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity });
+									delete this.pickup;
+								}
 								
 								return false;
 							}
 						}
 						else
 						{
-							// Unable to reach the target, try again
-							// (or follow if it's a moving target)
+							// Unable to reach the target, try again (or follow if it is a moving target)
+							// except if the garrisonholder should pickup does not exits anymore or its orders have changed 
+							if (this.pickup)
+							{
+								var cmpUnitAI = Engine.QueryInterface(this.pickup, IID_UnitAI);
+								if (!cmpUnitAI || !cmpUnitAI.HasPickupOrder(this.entity))
+								{
+									delete this.pickup;
+									this.FinishOrder();
+									return true;
+								}
+
+							}
 							if (this.MoveToTarget(target))
 							{
 								this.SetNextState("APPROACHING");
@@ -2327,7 +2446,7 @@
 
 				"leave": function() {
 					this.isGarrisoned = false;
-				}
+				},
 			},
 		},
 
@@ -2387,6 +2506,40 @@
 				// Ignore attacks while unpacking
 			},
 		},
+
+		"PICKUP": {
+			"APPROACHING": {
+				"enter": function() {
+					this.SelectAnimation("move");
+				},
+
+				"MoveCompleted": function() {
+					this.SetNextState("LOADING");
+				},
+
+				"PickupCanceled": function() {
+					this.StopMoving();
+					this.FinishOrder();
+				},
+			},
+
+			"LOADING": {
+				"enter": function() {
+					this.SelectAnimation("idle");
+					var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
+					if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull())
+					{
+						this.FinishOrder();
+						return true;
+					}
+					return false;
+				},
+
+				"PickupCanceled": function() {
+					this.FinishOrder();
+				},
+			},
+		},
 	},
 
 	"ANIMAL": {
@@ -2656,6 +2809,39 @@
 		this.SetupRangeQueries();
 };
 
+UnitAI.prototype.HasPickupOrder = function(entity)
+{
+	for each (var order in this.orderQueue)
+		if (order.type == "PickupUnit" && order.data.target == entity)
+			return true;
+	return false;
+};
+
+UnitAI.prototype.OnPickupRequested = function(msg)
+{
+	// First check if we already have such a request
+	if (this.HasPickupOrder(msg.entity))
+		return;
+	// Otherwise, insert the PickUp order after the last forced order
+	this.PushOrderAfterForced("PickupUnit", { "target": msg.entity });
+};
+
+UnitAI.prototype.OnPickupCanceled = function(msg)
+{
+	var cmpUnitAI = Engine.QueryInterface(msg.entity, IID_UnitAI);
+	for (var i = 0; i < this.orderQueue.length; ++i)
+	{
+		if (this.orderQueue[i].type == "PickupUnit" && this.orderQueue[i].data.target == msg.entity)
+		{
+			if (i == 0)
+				UnitFsm.ProcessMessage(this, {"type": "PickupCanceled", "data": msg});
+			else
+				this.orderQueue.splice(i, 1);
+			break;
+		}
+	}
+};
+
 // Wrapper function that sets up the normal, healer, and Gaia range queries.
 UnitAI.prototype.SetupRangeQueries = function()
 {
@@ -2921,6 +3107,31 @@
 	}
 };
 
+/**
+ * Insert an order after the last forced order onto the queue
+ * and after the other orders of the same type
+ */
+UnitAI.prototype.PushOrderAfterForced = function(type, data)
+{
+	if (!this.order || ((!this.order.data || !this.order.data.force) && this.order.type != type))
+	{
+		this.PushOrderFront(type, data);
+	}
+	else
+	{
+		for (var i = 1; i < this.orderQueue.length; ++i)
+		{
+			if (this.orderQueue[i].data && this.orderQueue[i].data.force)
+				continue;
+			if (this.orderQueue[i].type == type)
+				continue;
+			this.orderQueue.splice(i, 0, {"type": type, "data": data});
+			return;
+		}
+		this.PushOrder(type, data);
+	}
+};
+
 UnitAI.prototype.ReplaceOrder = function(type, data)
 {
 	// Special cases of orders that shouldn't be replaced:
@@ -3406,6 +3617,20 @@
 	return cmpUnitMotion.MoveToTargetRange(target, min, max);
 };
 
+UnitAI.prototype.MoveToGarrisonRange = function(target)
+{
+	if (!this.CheckTargetVisible(target))
+		return false;
+
+	var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);
+	if (!cmpGarrisonHolder)
+		return false;
+	var range = cmpGarrisonHolder.GetLoadingRange();
+
+	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+	return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
+};
+
 UnitAI.prototype.CheckPointRangeExplicit = function(x, z, min, max)
 {
 	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
@@ -3467,6 +3692,8 @@
 UnitAI.prototype.CheckGarrisonRange = function(target)
 {
 	var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);
+	if (!cmpGarrisonHolder)
+		return false;
 	var range = cmpGarrisonHolder.GetLoadingRange();
 
 	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
