Index: binaries/data/mods/public/gui/session/input.js
===================================================================
--- binaries/data/mods/public/gui/session/input.js	(révision 13197)
+++ binaries/data/mods/public/gui/session/input.js	(copie de travail)
@@ -187,7 +187,7 @@
 	{
 		if (action == "set-rallypoint" && haveRallyPoints)
 			return {"possible": true};
-		else if (action == "move")
+		else if (action == "move" || action == "attack-move")
 			return {"possible": true};
 		else
 			return {"possible": false};
@@ -404,7 +404,7 @@
 			break;
 		}
 	}
-	if (action == "move")
+	if (action == "move" || action == "attack-move")
 		return {"possible": true};
 	else
 		return {"possible": false};
@@ -480,6 +480,10 @@
 	{
 		return {"type": "attack", "cursor": "action-attack", "target": target};
 	}
+	else if (Engine.HotkeyIsPressed("session.attackmove") && getActionInfo("attack-move", target).possible)
+	{
+			return {"type": "attack-move", "cursor": "action-attack"};
+	}
 	else if (Engine.HotkeyIsPressed("session.garrison"))
 	{
 		if (getActionInfo("garrison", target).possible)
@@ -1320,6 +1324,12 @@
 		Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
 		return true;
 
+	case "attack-move":
+		var target = Engine.GetTerrainAtScreenPoint(ev.x, ev.y);
+		Engine.PostNetworkCommand({"type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "queued": queued});
+		Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
+		return true;
+
 	case "attack":
 		Engine.PostNetworkCommand({"type": "attack", "entities": selection, "target": action.target, "queued": queued});
 		Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
@@ -1419,6 +1429,11 @@
 			Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
 			return true;
 
+		case "attack-move":
+			Engine.PostNetworkCommand({"type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "queued": queued});
+			Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
+			return true;
+
 		case "set-rallypoint":
 			Engine.PostNetworkCommand({"type": "set-rallypoint", "entities": selection, "x": target.x, "z": target.z});
 			// Display rally point at the new coordinates, to avoid display lag
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js	(révision 13197)
+++ binaries/data/mods/public/simulation/helpers/Commands.js	(copie de travail)
@@ -88,6 +88,13 @@
 		});
 		break;
 
+	case "attack-walk":
+		var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
+		GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
+			cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.queued);
+		});
+		break;
+
 	case "attack":
 		if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target)))
 		{
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js	(révision 13197)
+++ binaries/data/mods/public/simulation/components/UnitAI.js	(copie de travail)
@@ -270,6 +270,33 @@
 			this.SetNextState("INDIVIDUAL.WALKING");
 	},
 
+	"Order.WalkAndFight": function(msg) {
+		// Let players move captured domestic animals around
+		if (this.IsAnimal() && !this.IsDomestic())
+		{
+			this.FinishOrder();
+			return;
+		}
+
+		// For packable units:
+		// 1. If packed, we can move.
+		// 2. If unpacked, we first need to pack, then follow case 1.
+		if (this.CanPack())
+		{
+			// Case 2: pack
+			this.PushOrderFront("Pack", { "force": true });
+			return;
+		}
+
+		this.SetHeldPosition(this.order.data.x, this.order.data.z);
+		this.MoveToPoint(this.order.data.x, this.order.data.z);
+		if (this.IsAnimal())
+			this.SetNextState("ANIMAL.WALKING");   // WalkAndFight not applicable for animals
+		else
+			this.SetNextState("INDIVIDUAL.WALKINGANDFIGHTING");
+	},
+
+
 	"Order.WalkToTarget": function(msg) {
 		// Let players move captured domestic animals around
 		if (this.IsAnimal() && !this.IsDomestic())
@@ -353,8 +380,10 @@
 			if (this.CanUnpack())
 			{
 				// Ignore unforced attacks
+				// this would prevent attacks from AttackVisibleEntity or AttackEntityInZone ?
+				// so we accept attacks against targets for which we have a bonus
 				// TODO: use special stances instead?
-				if (!this.order.data.force)
+				if (!this.order.data.force && this.GetAttackBonus(type, this.order.data.target) < 1.5)
 				{
 					this.FinishOrder();
 					return;
@@ -628,6 +657,14 @@
 			this.MoveToPoint(this.order.data.x, this.order.data.z);
 			this.SetNextState("WALKING");
 		},
+
+		"Order.WalkAndFight": function(msg) {
+			var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
+			cmpFormation.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]);
+
+			this.MoveToPoint(this.order.data.x, this.order.data.z);
+			this.SetNextState("WALKINGANDFIGHTING");
+		},
 		
 		"Order.MoveIntoFormation": function(msg) {
 			var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
@@ -885,6 +922,54 @@
 			},
 		},
 
+		"WALKINGANDFIGHTING": {
+			"enter": function(msg) {
+				this.StartTimer(0, 1000);
+			},
+
+			"Timer": function(msg) {
+				// check if there are no ennemies to attack
+				var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
+				for each (var ent in cmpFormation.members)
+				{
+					var cmpUnitAI =  Engine.QueryInterface(ent, IID_UnitAI);
+	    				if (cmpUnitAI.FindNewTargets())
+					{
+						if (cmpUnitAI.orderQueue[0] && cmpUnitAI.orderQueue[0].type == "Attack")
+						{
+							var data = cmpUnitAI.orderQueue[0].data;
+							cmpUnitAI.FinishOrder();
+							this.PushOrderFront("Attack", { "target": data.target, "force": false, "forceResponse": data.forceResponse });
+							break;
+						}
+					}
+				}
+			},
+
+			"leave": function(msg) {
+				this.StopTimer();
+			},
+
+			"MoveStarted": function(msg) {
+				var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
+				cmpFormation.SetRearrange(true);
+				cmpFormation.MoveMembersIntoFormation(true, true);
+			},
+
+			"MoveCompleted": function(msg) {
+				var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
+
+				if (this.FinishOrder())
+				{
+					cmpFormation.CallMemberFunction("ResetFinishOrder", []);
+					return;
+				}
+
+				// No more orders left.
+				cmpFormation.Disband();
+			},
+		},
+
 		"FORMING": {
 			"MoveStarted": function(msg) {
 				var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
@@ -938,7 +1023,27 @@
 
 				// Execute the next order
 				if (this.FinishOrder())
+				{
+					// if WalkAndFight order, look for new target before moving again
+					if (this.orderQueue.length > 0 && this.orderQueue[0].type == "WalkAndFight")
+					{
+						for each (var ent in cmpFormation.members)
+						{
+							var cmpUnitAI =  Engine.QueryInterface(ent, IID_UnitAI);
+	    						if (cmpUnitAI.FindNewTargets())
+							{
+								if (cmpUnitAI.orderQueue[0] && cmpUnitAI.orderQueue[0].type == "Attack")
+								{
+									var data = cmpUnitAI.orderQueue[0].data;
+									cmpUnitAI.FinishOrder();
+									this.PushOrderFront("Attack", { "target": data.target, "force": false, "forceResponse": data.forceResponse });
+									break;
+								}
+							}
+						}
+					}
 					return;
+				}
 
 				// No more order left.
 				cmpFormation.Disband();
@@ -1146,6 +1251,25 @@
 			},
 		},
 
+		"WALKINGANDFIGHTING": {
+			"enter": function () {
+				this.StartTimer(0, 1000);
+				this.SelectAnimation("move");
+			},
+
+			"Timer": function(msg) {
+				this.FindNewTargets();
+			},
+
+			"leave": function(msg) {
+				this.StopTimer();
+			},
+
+			"MoveCompleted": function() {
+				this.FinishOrder();
+			},
+		},
+
 		"FLEEING": {
 			"enter": function() {
 				this.PlaySound("panic");
@@ -1335,8 +1459,13 @@
 					}
 
 					// Can't reach it, no longer owned by enemy, or it doesn't exist any more - give up
+					// Except if in WalkAndFight mode where we look for more ennemies around before moving again
 					if (this.FinishOrder())
+					{
+						if (this.orderQueue.length > 0 && this.orderQueue[0].type == "WalkAndFight")
+	    						this.FindNewTargets();
 						return;
+					}
 
 					// See if we can switch to a new nearby enemy
 					if (this.FindNewTargets())
@@ -3213,6 +3342,14 @@
 	return cmpAttack.GetBestAttackAgainst(target);
 };
 
+UnitAI.prototype.GetAttackBonus = function(type, target)
+{
+	var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
+	if (!cmpAttack)
+		return 1;
+	return cmpAttack.GetAttackBonus(type, target);
+};
+
 /**
  * Try to find one of the given entities which can be attacked,
  * and start attacking it.
@@ -3425,6 +3562,7 @@
 		switch (order.type)
 		{
 		case "Walk":
+		case "WalkAndFight":
 		case "WalkToPointRange":
 		case "MoveIntoFormation":
 		case "GatherNearPosition":
@@ -3511,6 +3649,15 @@
 };
 
 /**
+ * Adds walk-and-fight order to queue, this only occurs in response
+ * to a player order, and so is forced.
+ */
+UnitAI.prototype.WalkAndFight = function(x, z, queued)
+{
+	this.AddOrder("WalkAndFight", { "x": x, "z": z, "force": true }, queued);
+};
+
+/**
  * Adds leave foundation order to queue, treated as forced.
  */
 UnitAI.prototype.LeaveFoundation = function(target)
Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg	(révision 13197)
+++ binaries/data/config/default.cfg	(copie de travail)
@@ -245,6 +245,7 @@
 hotkey.session.kill = Delete                ; Destroy selected units
 hotkey.session.attack = Ctrl                ; Modifier to force attack instead of another action
 hotkey.session.garrison = Ctrl              ; Modifier to garrison when clicking on building
+hotkey.session.attackmove = AltGr           ; Modifier to attackmove when clicking on a point
 hotkey.session.queue = Shift                ; Modifier to queue unit orders instead of replacing
 hotkey.session.batchtrain = Shift           ; Modifier to train units in batches
 hotkey.session.massbarter = Shift           ; Modifier to barter bunch of resources
