Index: binaries/data/mods/public/simulation/components/interfaces/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/UnitAI.js	(révision 13474)
+++ binaries/data/mods/public/simulation/components/interfaces/UnitAI.js	(copie de travail)
@@ -11,3 +11,8 @@
 // Message of the form { "to": orderData }.
 // sent whenever the unit changes state
 Engine.RegisterMessageType("UnitAIOrderDataChanged");
+
+// Message of the form { "attacker": attacker-entity, "target": attacked-entity }.
+// sent whenever a nearby unit is attacked
+Engine.RegisterMessageType("NearbyUnitAttacked");
+
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js	(révision 13474)
+++ binaries/data/mods/public/simulation/components/UnitAI.js	(copie de travail)
@@ -152,6 +152,10 @@
 		// ignore attacker
 	},
 
+	"NearbyAttacked": function(msg) {
+		// ignore nearby attack
+	},
+
 	"HealthChanged": function(msg) {
 		// ignore
 	},
@@ -329,9 +333,7 @@
 	},
 
 	"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);
-		var ok = this.MoveToTargetRangeExplicit(this.order.data.target, distance, -1);
+		var ok = this.PerformFlee(this.order.data);
 		if (ok)
 		{
 			// We've started fleeing from the given target
@@ -341,11 +343,7 @@
 				this.SetNextState("INDIVIDUAL.FLEEING");
 		}
 		else
-		{
-			// We are already at the target, or can't move at all
-			this.StopMoving();
 			this.FinishOrder();
-		}
 	},
 
 	"Order.Attack": function(msg) {
@@ -1149,6 +1147,10 @@
 		},
 
 		"IDLE": {
+			"NearbyAttacked": function(msg) {
+				this.RespondToTargetedEntities([msg.data.attacker]);
+			},
+
 			"enter": function() {
 				// Switch back to idle animation to guarantee we won't
 				// get stuck with an incorrect animation
@@ -1310,6 +1312,8 @@
 						// Return to our original position
 						if (this.GetStance().respondHoldGround)
 							this.WalkToHeldPosition();
+						else if (this.IsAnimal())
+							this.SetNextState("ROAMING");
 					}
 				},
 
@@ -1498,6 +1502,8 @@
 						// Return to our original position
 						if (this.GetStance().respondHoldGround)
 							this.WalkToHeldPosition();
+						else if (this.IsAnimal())
+							this.SetNextState("ROAMING");
 					}
 				},
 
@@ -2336,6 +2342,26 @@
 			}
 		},
 
+		"NearbyAttacked": function(msg) {
+			// All wild animals in a given distance respond to the attack :
+			// If skittish behaviour, they flee
+			// If defensive behaviour, they attack if it is a member of their species which is involved
+			if (this.template.NaturalBehaviour == "skittish" ||
+				this.template.NaturalBehaviour == "passive")
+			{
+				this.Flee(msg.data.attacker, false);
+			}
+			else if (this.IsDangerousAnimal() || this.template.NaturalBehaviour == "defensive")
+			{
+				var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
+				if (cmpIdentity && cmpIdentity.IsSameSpecy(msg.data.target))
+				{
+					if (this.CanAttack(msg.data.attacker))
+						this.Attack(msg.data.attacker, false);
+				}
+			}
+		},
+
 		"Order.LeaveFoundation": function(msg) {
 			// Run away from the foundation
 			this.MoveToTargetRangeExplicit(msg.data.target, +this.template.FleeDistance, +this.template.FleeDistance);
@@ -2406,6 +2432,14 @@
 
 			"leave": function() {
 				this.StopTimer();
+				// If not yet done, setup heldPosition for wild animals
+				// (initialize here as animals start in feeding state)
+				if (!this.IsDomestic() && !this.heldPosition)
+				{
+					var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+					if (cmpPosition && cmpPosition.IsInWorld())
+						this.heldPosition = cmpPosition.GetPosition();
+				}
 			},
 
 			"LosRangeUpdate": function(msg) {
@@ -2418,7 +2452,7 @@
 					}
 				}
 				// Start attacking one of the newly-seen enemy (if any)
-				else if (this.template.NaturalBehaviour == "violent")
+				else if (this.IsDangerousAnimal())
 				{
 					this.AttackVisibleEntity(msg.data.added);
 				}
@@ -2981,8 +3015,15 @@
 UnitAI.prototype.OnAttacked = function(msg)
 {
 	UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg});
+	// warn nearby units that we are attacked
+	this.WarnNearbyUnitsOfAttack(msg.attacker);
 };
 
+UnitAI.prototype.OnNearbyUnitAttacked = function(msg)
+{
+	UnitFsm.ProcessMessage(this, {"type": "NearbyAttacked", "data": msg});
+};
+
 UnitAI.prototype.OnHealthChanged = function(msg)
 {
 	UnitFsm.ProcessMessage(this, {"type": "HealthChanged", "from": msg.from, "to": msg.to});
@@ -3973,8 +4014,7 @@
 };
 
 /**
- * Adds flee order to the queue, not forced, so it can be
- * interrupted by attacks.
+ * Adds flee order to the queue, not forced, so it can be interrupted by attacks.
  */
 UnitAI.prototype.Flee = function(target, queued)
 {
@@ -3982,6 +4022,67 @@
 };
 
 /**
+ * Internal function to abstract the details of the runaway
+ * data.target is the attacker
+ */
+UnitAI.prototype.PerformFlee = function(data)
+{
+	var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+	if (!cmpPosition || !cmpPosition.IsInWorld())
+		return false;
+	var cmpAttackerPosition = Engine.QueryInterface(data.target, IID_Position);
+	if (!cmpAttackerPosition || !cmpAttackerPosition.IsInWorld())
+		return false;
+
+	var pos = cmpPosition.GetPosition();
+	var posAttacker = cmpAttackerPosition.GetPosition();
+	var dx = posAttacker.x - pos.x;
+	var dz = posAttacker.z - pos.z;
+	var dist = Math.sqrt(dx*dx+dz*dz);
+	if (Engine.QueryInterface(data.target, IID_UnitAI))
+	{
+		// The attacker is (in principle) facing its target,
+		// so if we are not the target, we must have an angle compared to this direction
+		// and we will flee in a direction which increase this angle
+		var cosa = -dx/dist;                   // direction from attacker to this unit
+		var sina = -dz/dist;
+		var rotAttacker = cmpAttackerPosition.GetRotation();
+		var cosb = Math.sin(rotAttacker.y);    // direction the attacker is pointing at
+		var sinb = Math.cos(rotAttacker.y);
+		var cosc = cosa*cosb + sina*sinb;      // angle between these directions
+		var sinc = sina*cosb - cosa*sinb;
+		// We now compute from it a new angle which tends to move away from the attacker's direction
+		if (cosc > 0)
+		{
+			cosc = cosc*cosc;
+			if (sinc < 0)
+				sinc = -Math.sqrt(1 - cosc*cosc);
+			else
+				sinc =  Math.sqrt(1 - cosc*cosc);
+		}
+	}
+	else
+	{
+		var cosc = 1;
+		var sinc = 0;
+	}
+	// add a small additionnal random flee direction :
+	// take a distribution flat in sin between about -pi/6 and pi/6
+	// and approximate cos by 1-sin*sin/2 for these small angles
+	var sinb = -0.5 + Math.random();
+	var cosb = 1 - sinb*sinb/2;
+	var cosa = cosc*cosb - sinc*sinb;
+	var sina = sinc*cosb + cosc*sinb;
+	var tx = cosa*dx - sina*dz + pos.x;
+	var tz = sina*dx + cosa*dz + pos.z;
+	// We add the distance between the unit and the attacker to account for ranged attacks
+       	var distance = (+this.template.FleeDistance) + dist;
+       	var ok = this.MoveToPointRange(tx, tz, distance, -1);
+       	if (!ok) this.StopMoving();
+       	return ok;
+};
+
+/**
  * Adds cheer order to the queue. Forced so it won't be interrupted by attacks.
  */
 UnitAI.prototype.Cheer = function()
@@ -4171,6 +4272,29 @@
 	return false;
 };
 
+/**
+ * Warn units inside a given range that we are attacked
+ */
+UnitAI.prototype.WarnNearbyUnitsOfAttack = function(attacker)
+{
+	var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+	if (!cmpOwnership) return;
+	var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+	// Get units from same owner in a given range
+	// TODO what range ? and what about allied units ?
+	// For range, ideally we should take the vision range of the nearby unit,
+	// but would be time expensive to do range check for each unit, so take the attack unit vision range
+	// with a minimal value
+	var player = [ cmpOwnership.GetOwner() ]; 
+	var range = 20;
+	var cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
+	if (cmpVision)
+		range = Math.max(range, cmpVision.GetRange());
+	var nearby = cmpRangeManager.ExecuteQuery(this.entity, 0, range, player, IID_UnitAI);
+	for each (var ent in nearby)
+		Engine.PostMessage(ent, MT_NearbyUnitAttacked, { "attacker": attacker, "target": this.entity });
+};
+
 //// Helper functions ////
 
 UnitAI.prototype.CanAttack = function(target, forceResponse)
@@ -4403,39 +4527,7 @@
 	return (cmpPack && cmpPack.IsPacking());
 };
 
-//// Animal specific functions ////
 
-UnitAI.prototype.MoveRandomly = function(distance)
-{
-	// We want to walk in a random direction, but avoid getting stuck
-	// in obstacles or narrow spaces.
-	// So pick a circular range from approximately our current position,
-	// and move outwards to the nearest point on that circle, which will
-	// lead to us avoiding obstacles and moving towards free space.
-
-	// TODO: we probably ought to have a 'home' point, and drift towards
-	// that, so we don't spread out all across the whole map
-
-	var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
-	if (!cmpPosition)
-		return;
-
-	if (!cmpPosition.IsInWorld())
-		return;
-
-	var pos = cmpPosition.GetPosition();
-
-	var jitter = 0.5;
-
-	// Randomly adjust the range's center a bit, so we tend to prefer
-	// moving in random directions (if there's nothing in the way)
-	var tx = pos.x + (2*Math.random()-1)*jitter;
-	var tz = pos.z + (2*Math.random()-1)*jitter;
-
-	var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
-	cmpMotion.MoveToPointRange(tx, tz, distance, distance);
-};
-
 UnitAI.prototype.AttackEntitiesByPreference = function(ents)
 {
 	var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
@@ -4465,4 +4557,48 @@
 	);
 };
 
+//// Animal specific functions ////
+
+UnitAI.prototype.MoveRandomly = function(distance)
+{
+	var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+	if (!cmpPosition || !cmpPosition.IsInWorld())
+		return;
+	var pos = cmpPosition.GetPosition();
+
+	// If we are far away from our starting position, we try to move closer
+	// with a probability increasing with the distance
+	if (this.heldPosition)
+	{
+		var dx = this.heldPosition.x - pos.x;
+		var dz = this.heldPosition.z - pos.z;
+		var dist = Math.sqrt(dx*dx + dz*dz);
+		var proba = RandomInt(0, 100);
+		if (20*dist/distance > Math.max(proba,30))
+		{
+			dist -= distance;
+			var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+			if (!cmpMotion.MoveToPointRange(this.heldPosition.x, this.heldPosition.z, dist, dist))
+				this.heldPosition = pos;
+			return;
+		}
+	}
+
+	// Otherwise we want to walk in a random direction, but avoid getting stuck
+	// in obstacles or narrow spaces.
+	// So pick a circular range from approximately our current position,
+	// and move outwards to the nearest point on that circle, which will
+	// lead to us avoiding obstacles and moving towards free space.
+
+	var jitter = 0.5;
+
+	// Randomly adjust the range's center a bit, so we tend to prefer
+	// moving in random directions (if there's nothing in the way)
+	var tx = pos.x + (2*Math.random()-1)*jitter;
+	var tz = pos.z + (2*Math.random()-1)*jitter;
+
+	var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+	cmpMotion.MoveToPointRange(tx, tz, distance, distance);
+};
+
 Engine.RegisterComponentType(IID_UnitAI, "UnitAI", UnitAI);
Index: binaries/data/mods/public/simulation/components/Identity.js
===================================================================
--- binaries/data/mods/public/simulation/components/Identity.js	(révision 13474)
+++ binaries/data/mods/public/simulation/components/Identity.js	(copie de travail)
@@ -15,6 +15,11 @@
 		"<text/>" +
 	"</element>" +
 	"<optional>" +
+		"<element name='Species' a:help='Species this unit belongs to'>" +
+			"<text/>" +
+		"</element>" +
+	"</optional>" +
+	"<optional>" +
 		"<element name='SpecificName' a:help='Specific native-language name for this unit type'>" +
 			"<text/>" +
 		"</element>" +
@@ -138,4 +143,13 @@
 	return this.template.GenericName;
 };
 
+Identity.prototype.IsSameSpecy = function(entity)
+{
+	var cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
+	if (this.template.Species)
+		return (cmpIdentity && this.template.Species == cmpIdentity.template.Species);
+	else
+		return (cmpIdentity && this.template.SpecificName == cmpIdentity.template.SpecificName);
+};
+
 Engine.RegisterComponentType(IID_Identity, "Identity", Identity);
Index: binaries/data/mods/public/simulation/templates/gaia/fauna_elephant.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/gaia/fauna_elephant.xml	(révision 13474)
+++ binaries/data/mods/public/simulation/templates/gaia/fauna_elephant.xml	(copie de travail)
@@ -11,6 +11,7 @@
   <Identity>
     <Civ>gaia</Civ>
     <Classes datatype="tokens">Elephant</Classes>
+    <Species>Elephant</Species>
     <SpecificName>Elephant</SpecificName>
     <Icon>gaia/fauna_elephant.png</Icon>
   </Identity>
Index: binaries/data/mods/public/simulation/templates/gaia/fauna_lioness.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/gaia/fauna_lioness.xml	(révision 13474)
+++ binaries/data/mods/public/simulation/templates/gaia/fauna_lioness.xml	(copie de travail)
@@ -15,6 +15,7 @@
   </Footprint>
   <Identity>
     <Civ>gaia</Civ>
+    <Species>Lion</Species>
     <SpecificName>Lion</SpecificName>
     <Icon>gaia/fauna_lion.png</Icon>
   </Identity>
Index: binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_african_bush.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_african_bush.xml	(révision 13474)
+++ binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_african_bush.xml	(copie de travail)
@@ -44,6 +44,7 @@
   <Identity>
     <Civ>gaia</Civ>
     <Classes datatype="tokens">Elephant</Classes>
+    <Species>Elephant</Species>
     <SpecificName>African Bush Elephant</SpecificName>
     <Icon>gaia/fauna_elephant_african_bush.png</Icon>
   </Identity>
Index: binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_african_infant.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_african_infant.xml	(révision 13474)
+++ binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_african_infant.xml	(copie de travail)
@@ -12,6 +12,7 @@
   <Identity>
     <Civ>gaia</Civ>
     <Classes datatype="tokens">Elephant</Classes>
+    <Species>Elephant</Species>
     <SpecificName>African Elephant (Infant)</SpecificName>
     <Icon>gaia/fauna_elephant_african_infant.png</Icon>
   </Identity>
Index: binaries/data/mods/public/simulation/templates/gaia/fauna_lion.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/gaia/fauna_lion.xml	(révision 13474)
+++ binaries/data/mods/public/simulation/templates/gaia/fauna_lion.xml	(copie de travail)
@@ -15,6 +15,7 @@
   </Footprint>
   <Identity>
     <Civ>gaia</Civ>
+    <Species>Lion</Species>
     <SpecificName>Lion</SpecificName>
     <Icon>gaia/fauna_lion.png</Icon>
   </Identity>
Index: binaries/data/mods/public/simulation/templates/gaia/fauna_wolf_snow.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/gaia/fauna_wolf_snow.xml	(révision 13474)
+++ binaries/data/mods/public/simulation/templates/gaia/fauna_wolf_snow.xml	(copie de travail)
@@ -15,6 +15,7 @@
   </Footprint>
   <Identity>
     <Civ>gaia</Civ>
+    <Species>Wolf</Species>
     <SpecificName>Snow Wolf</SpecificName>
     <Icon>gaia/fauna_wolf_snow.png</Icon>
   </Identity>
Index: binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_north_african.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_north_african.xml	(révision 13474)
+++ binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_north_african.xml	(copie de travail)
@@ -31,6 +31,7 @@
   <Identity>
     <Civ>gaia</Civ>
     <Classes datatype="tokens">Elephant</Classes>
+    <Species>Elephant</Species>
     <SpecificName>North African Elephant</SpecificName>
     <Icon>gaia/fauna_elephant_north_african.png</Icon>
   </Identity>
Index: binaries/data/mods/public/simulation/templates/gaia/fauna_wolf.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/gaia/fauna_wolf.xml	(révision 13474)
+++ binaries/data/mods/public/simulation/templates/gaia/fauna_wolf.xml	(copie de travail)
@@ -15,6 +15,7 @@
   </Footprint>
   <Identity>
     <Civ>gaia</Civ>
+    <Species>Wolf</Species>
     <SpecificName>Wolf</SpecificName>
     <Icon>gaia/fauna_wolf.png</Icon>
   </Identity>
Index: binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_asian.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_asian.xml	(révision 13474)
+++ binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_asian.xml	(copie de travail)
@@ -44,6 +44,7 @@
   <Identity>
     <Civ>gaia</Civ>
     <Classes datatype="tokens">Elephant</Classes>
+    <Species>Elephant</Species>
     <SpecificName>Asian Elephant</SpecificName>
     <Icon>gaia/fauna_elephant_african_bush.png</Icon>
   </Identity>
