Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg	(revision 18113)
+++ binaries/data/config/default.cfg	(working copy)
@@ -274,11 +274,17 @@
 [hotkey.session]
 kill = Delete                ; Destroy selected units
 stop = "H"                   ; Stop the current action
-attack = Ctrl                ; Modifier to attack instead of another action (eg capture)
-attackmove = Ctrl            ; Modifier to attackmove when clicking on a point
-attackmoveUnit = "Ctrl+Q"    ; Modifier to attackmove targeting only units when clicking on a point (should contain the attackmove keys)
+attack = Ctrl                ; Modifier to primary attack instead of another action (eg capture)
+attackmove = Ctrl            ; Modifier to primary attackmove when clicking on a point
+attackmoveUnit = "Ctrl+Q"    ; Modifier to primary attackmove targeting only units when clicking on a point (should contain the attackmove keys)
 garrison = Ctrl              ; Modifier to garrison when clicking on building
 autorallypoint = Ctrl        ; Modifier to set the rally point on the building itself
+meleeattack = Alt            ; Modifier to melee attack instead of another action
+meleeattackmove = Alt        ; Modifier to melee attackmove when clicking on a point
+meleeattackmoveUnit = "Alt+Q"; Modifier to melee attackmove targeting only units when clicking on a point
+rangedattack = space         ; Modifier to ranged attack instead of another action
+rangedattackmove = space     ; Modifier to ranged attackmove when clicking on a point
+rangedattackmoveUnit = "space+Q"; Modifier to ranged attackmove targeting only units when clicking on a point
 guard = "G"                  ; Modifier to escort/guard when clicking on unit/building
 queue = Shift                ; Modifier to queue unit orders instead of replacing
 batchtrain = Shift           ; Modifier to train units in batches
Index: binaries/data/mods/public/art/actors/units/persians/champion_unit_1.xml
===================================================================
--- binaries/data/mods/public/art/actors/units/persians/champion_unit_1.xml	(revision 18113)
+++ binaries/data/mods/public/art/actors/units/persians/champion_unit_1.xml	(working copy)
@@ -42,6 +42,12 @@
   <group>
     <variant frequency="1" name="Idle"/>
     <variant file="biped/attack_capture.xml"/>
+    <variant file="biped/attack_ranged_archer.xml">
+      <props>
+        <prop attachpoint="r_hand"/>
+        <prop actor="props/units/weapons/bow_recurve.xml" attachpoint="l_hand"/>
+      </props>
+    </variant>
   </group>
   <material>player_trans.xml</material>
 </actor>
Index: binaries/data/mods/public/art/textures/cursors/action-melee-attack.txt
===================================================================
--- binaries/data/mods/public/art/textures/cursors/action-melee-attack.txt	(nonexistent)
+++ binaries/data/mods/public/art/textures/cursors/action-melee-attack.txt	(working copy)
@@ -0,0 +1 @@
+1 1
Index: binaries/data/mods/public/art/textures/cursors/action-ranged-attack.txt
===================================================================
--- binaries/data/mods/public/art/textures/cursors/action-ranged-attack.txt	(nonexistent)
+++ binaries/data/mods/public/art/textures/cursors/action-ranged-attack.txt	(working copy)
@@ -0,0 +1 @@
+1 1
Index: binaries/data/mods/public/gui/common/tooltips.js
===================================================================
--- binaries/data/mods/public/gui/common/tooltips.js	(revision 18113)
+++ binaries/data/mods/public/gui/common/tooltips.js	(working copy)
@@ -166,6 +166,8 @@
 
 	for (let type in template.attack)
 	{
+		if (type == "ChangeDistance")
+			continue; // not an attack type
 		if (type == "Slaughter")
 			continue; // Slaughter is not a real attack, so do not show it.
 		if (type == "Charge")
Index: binaries/data/mods/public/gui/session/input.js
===================================================================
--- binaries/data/mods/public/gui/session/input.js	(revision 18113)
+++ binaries/data/mods/public/gui/session/input.js	(working copy)
@@ -205,6 +205,21 @@
 				data.targetClasses = Engine.HotkeyIsPressed("session.attackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] };
 				cursor = "action-attack-move";
 			}
+
+			if (Engine.HotkeyIsPressed("session.meleeattackmove"))
+			{
+				data.command = "attack-walk";
+				data.targetClasses = Engine.HotkeyIsPressed("session.meleeattackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] };
+				cursor = "action-melee-attack-move";
+			}
+
+			if (Engine.HotkeyIsPressed("session.rangedattackmove"))
+			{
+				data.command = "attack-walk";
+				data.targetClasses = Engine.HotkeyIsPressed("session.rangedattackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] };
+				cursor = "action-ranged-attack-move";
+			}
+
 			return { "possible": true, "data": data, "cursor": cursor };
 		}
 
@@ -219,9 +234,9 @@
 	// Check if the target entity is a resource, dropsite, foundation, or enemy unit.
 	// Check if any entities in the selection can gather the requested resource,
 	// can return to the dropsite, can build the foundation, or can attack the enemy
-	for each (var entityID in selection)
+	for (let entityID of selection)
 	{
-		var entState = GetExtendedEntityState(entityID);
+		let entState = GetExtendedEntityState(entityID);
 		if (!entState)
 			continue;
 
@@ -228,7 +243,7 @@
 		if (unitActions[action] && unitActions[action].getActionInfo)
 		{
 			var r = unitActions[action].getActionInfo(entState, targetState, simState);
-			if (r) // return true if it's possible for one of the entities
+			if (r && r.possible) // return true if it's possible for one of the entities
 				return r;
 		}
 	}
Index: binaries/data/mods/public/gui/session/unit_actions.js
===================================================================
--- binaries/data/mods/public/gui/session/unit_actions.js	(revision 18113)
+++ binaries/data/mods/public/gui/session/unit_actions.js	(working copy)
@@ -1,12 +1,12 @@
 /**
- * List of different actions units can execute, 
+ * List of different actions units can execute,
  * this is mostly used to determine which actions can be executed
  *
  * "execute" is meant to send the command to the engine
  *
- * The next functions will always return false 
+ * The next functions will always return false
  * in case you have to continue to seek
- * (i.e. look at the next entity for getActionInfo, the next 
+ * (i.e. look at the next entity for getActionInfo, the next
  * possible action for the actionCheck ...)
  * They will return an object when the searching is finished
  *
@@ -24,10 +24,10 @@
  *
  * "specificness" is used to determine how specific an action is,
  * The lower the number, the more specific an action is, and the bigger
- * the chance of selecting that action when multiple actions are possible 
+ * the chance of selecting that action when multiple actions are possible
  */
 
-var unitActions = 
+var unitActions =
 {
 	"move":
 	{
@@ -53,10 +53,10 @@
 				return {"type": "move"};
 			return false;
 		},
-		"specificness": 12,
+		"specificness": 13,
 	},
 
-	"attack-move": 
+	"attack-move":
 	{
 		"execute": function(target, action, selection, queued)
 		{
@@ -65,13 +65,13 @@
 			else
 				var targetClasses = { "attack": ["Unit", "Structure"] };
 
-			Engine.PostNetworkCommand({"type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "targetClasses": targetClasses, "queued": queued});
+			Engine.PostNetworkCommand({ "type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "targetClasses": targetClasses, "queued": queued, "prefAttackType": "primary" });
 			Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
 			return true;
 		},
 		"getActionInfo": function(entState, targetState)
 		{
-			return {"possible": true};
+			return { "possible": true };
 		},
 		"hotkeyActionCheck": function(target, selection)
 		{
@@ -81,17 +81,81 @@
 				return entState && entState.unitAI;
 			});
 			if (haveUnitAI && Engine.HotkeyIsPressed("session.attackmove") && getActionInfo("attack-move", target).possible)
-				return {"type": "attack-move", "cursor": "action-attack-move"};
+				return { "type": "attack-move", "cursor": "action-attack-move" };
 			return false;
 		},
 		"specificness": 30,
 	},
 
-	"capture": 
+	"melee-attack-move":
 	{
 		"execute": function(target, action, selection, queued)
 		{
-			Engine.PostNetworkCommand({"type": "attack", "entities": selection, "target": action.target, "allowCapture": true, "queued": queued});
+			if (Engine.HotkeyIsPressed("session.meleeattackmoveUnit"))
+				var targetClasses = { "meleeattack": ["Unit"] };
+			else
+				var targetClasses = { "meleeattack": ["Unit", "Structure"] };
+
+			Engine.PostNetworkCommand({ "type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "targetClasses": targetClasses, "queued": queued, "prefAttackType": "Melee" });
+			Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
+			return true;
+		},
+		"getActionInfo": function(entState, targetState)
+		{
+			return { "possible": true };
+		},
+		"hotkeyActionCheck": function(target, selection)
+		{
+			// Work out whether at least part of the selection have UnitAI
+			let haveUnitAI = selection.some(function(ent) {
+				let entState = GetEntityState(ent);
+				return entState && entState.unitAI && getActionInfo("melee-attack-move", target).possible;
+			});
+
+			if (haveUnitAI && Engine.HotkeyIsPressed("session.meleeattackmove") && getActionInfo("melee-attack-move", target).possible)
+				return { "type": "melee-attack-move", "cursor": "action-melee-attack" };
+			return false;
+		},
+		"specificness": 31,
+	},
+
+	"ranged-attack-move":
+	{
+		"execute": function(target, action, selection, queued)
+		{
+			if (Engine.HotkeyIsPressed("session.rangedattackmoveUnit"))
+				var targetClasses = { "rangedattack": ["Unit"] };
+			else
+				var targetClasses = { "rangedattack": ["Unit", "Structure"] };
+
+			Engine.PostNetworkCommand({ "type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "targetClasses": targetClasses, "queued": queued, "prefAttackType": "Ranged" });
+			Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
+			return true;
+		},
+		"getActionInfo": function(entState, targetState)
+		{
+			return { "possible": true };
+		},
+		"hotkeyActionCheck": function(target, selection)
+		{
+			// Work out whether at least part of the selection have UnitAI
+			let haveUnitAI = selection.some(function(ent) {
+				let entState = GetEntityState(ent);
+				return entState && entState.unitAI && getActionInfo("ranged-attack-move", target).possible;
+			});
+
+			if (haveUnitAI && Engine.HotkeyIsPressed("session.rangedattackmove") && getActionInfo("ranged-attack-move", target).possible)
+				return { "type": "ranged-attack-move", "cursor": "action-ranged-attack" };
+			return false;
+		},
+		"specificness": 32,
+	},
+
+	"capture":
+	{
+		"execute": function(target, action, selection, queued)
+		{
+			Engine.PostNetworkCommand({ "type": "attack", "entities": selection, "target": action.target, "queued": queued, "prefAttackType": "Capture" });
 			Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
 			return true;
 		},
@@ -99,15 +163,15 @@
 		{
 			if (!entState.attack || !targetState.hitpoints)
 				return false;
-			return {"possible": Engine.GuiInterfaceCall("CanCapture", {"entity": entState.id, "target": targetState.id})};
+			return { "possible": Engine.GuiInterfaceCall("CanCapture", { "entity": entState.id, "target": targetState.id }) };
 		},
 		"actionCheck": function(target)
 		{
 			if (getActionInfo("capture", target).possible)
-				return {"type": "capture", "cursor": "action-capture", "target": target};
+				return { "type": "capture", "cursor": "action-capture", "target": target };
 			return false;
 		},
-		"specificness": 9,
+		"specificness": 7,
 	},
 
 	"attack":
@@ -114,7 +178,7 @@
 	{
 		"execute": function(target, action, selection, queued)
 		{
-			Engine.PostNetworkCommand({"type": "attack", "entities": selection, "target": action.target, "queued": queued, "allowCapture": false});
+			Engine.PostNetworkCommand({ "type": "attack", "entities": selection, "target": action.target, "queued": queued, "prefAttackType": "primary" });
 			Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
 			return true;
 		},
@@ -122,28 +186,86 @@
 		{
 			if (!entState.attack || !targetState.hitpoints)
 				return false;
-			return {"possible": Engine.GuiInterfaceCall("CanAttack", {"entity": entState.id, "target": targetState.id})};
+			return { "possible": Engine.GuiInterfaceCall("CanAttack", { "entity": entState.id, "target": targetState.id }) };
 		},
 		"hotkeyActionCheck": function(target)
 		{
 			if (Engine.HotkeyIsPressed("session.attack") && getActionInfo("attack", target).possible)
-				return {"type": "attack", "cursor": "action-attack", "target": target};
+				return { "type": "attack", "cursor": "action-attack", "target": target };
 			return false;
 		},
 		"actionCheck": function(target)
 		{
 			if (getActionInfo("attack", target).possible)
-				return {"type": "attack", "cursor": "action-attack", "target": target};
+				return { "type": "attack", "cursor": "action-attack", "target": target };
 			return false;
 		},
+		"specificness": 8,
+	},
+
+	"melee-attack":
+	{
+		"execute": function(target, action, selection, queued)
+		{
+			Engine.PostNetworkCommand({ "type": "attack", "entities": selection, "target": action.target, "queued": queued, "prefAttackType": "Melee" });
+			Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
+			return true;
+		},
+		"getActionInfo": function(entState, targetState)
+		{
+			if (!entState.attack || !targetState.hitpoints) // hack
+				return false;
+			return { "possible": Engine.GuiInterfaceCall("CanAttackWithType", { "entity": entState.id, "target": targetState.id, "type": "Melee" }) };
+		},
+		"hotkeyActionCheck": function(target)
+		{
+			if (Engine.HotkeyIsPressed("session.meleeattack") && getActionInfo("melee-attack", target).possible)
+				return { "type": "melee-attack", "cursor": "action-melee-attack", "target": target };
+			return false;
+		},
+		"actionCheck": function(target)
+		{
+			if (getActionInfo("melee-attack", target).possible)
+					return { "type": "melee-attack", "cursor": "action-melee-attack", "target": target };
+			return false;
+		},
+		"specificness": 9,
+	},
+
+	"ranged-attack":
+	{
+		"execute": function(target, action, selection, queued)
+		{
+			Engine.PostNetworkCommand({ "type": "attack", "entities": selection, "target": action.target, "queued": queued, "prefAttackType": "Ranged" });
+			Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
+			return true;
+		},
+		"getActionInfo": function(entState, targetState)
+		{
+			if (!entState.attack || !targetState.hitpoints) // hack
+				return false;
+			return { "possible": Engine.GuiInterfaceCall("CanAttackWithType", { "entity": entState.id, "target": targetState.id, "type": "Ranged" }) };
+		},
+		"hotkeyActionCheck": function(target, selection)
+		{
+			if (Engine.HotkeyIsPressed("session.rangedattack") && getActionInfo("ranged-attack", target).possible)
+				return { "type": "ranged-attack", "cursor": "action-ranged-attack", "target": target };
+			return false;
+		},
+		"actionCheck": function(target, selection)
+		{
+			if (getActionInfo("ranged-attack", target).possible)
+					return { "type": "ranged-attack", "cursor": "action-ranged-attack", "target": target };
+			return false;
+		},
 		"specificness": 10,
 	},
 
-	"heal": 
+	"heal":
 	{
 		"execute": function(target, action, selection, queued)
 		{
-			Engine.PostNetworkCommand({"type": "heal", "entities": selection, "target": action.target, "queued": queued});
+			Engine.PostNetworkCommand({ "type": "heal", "entities": selection, "target": action.target, "queued": queued });
 			Engine.GuiInterfaceCall("PlaySound", { "name": "order_heal", "entity": selection[0] });
 			return true;
 		},
@@ -176,10 +298,10 @@
 				return {"type": "heal", "cursor": "action-heal", "target": target};
 			return false;
 		},
-		"specificness": 7,
+		"specificness": 6,
 	},
 
-	"build": 
+	"build":
 	{
 		"execute": function(target, action, selection, queued)
 		{
@@ -202,7 +324,7 @@
 		"specificness": 3,
 	},
 
-	"repair": 
+	"repair":
 	{
 		"execute": function(target, action, selection, queued)
 		{
@@ -230,10 +352,10 @@
 				return {"type": "build", "cursor": "action-repair", "target": target};
 			return false;
 		},
-		"specificness": 11,
+		"specificness": 12,
 	},
 
-	"gather": 
+	"gather":
 	{
 		"execute": function(target, action, selection, queued)
 		{
@@ -260,7 +382,7 @@
 		"specificness": 1,
 	},
 
-	"returnresource": 
+	"returnresource":
 	{
 		"execute": function(target, action, selection, queued)
 		{
@@ -297,7 +419,7 @@
 		"specificness": 2,
 	},
 
-	"setup-trade-route": 
+	"setup-trade-route":
 	{
 		"execute": function(target, action, selection, queued)
 		{
@@ -362,7 +484,7 @@
 		"specificness": 0,
 	},
 
-	"garrison": 
+	"garrison":
 	{
 		"execute": function(target, action, selection, queued)
 		{
@@ -409,7 +531,7 @@
 		"specificness": 20,
 	},
 
-	"guard": 
+	"guard":
 	{
 		"execute": function(target, action, selection, queued)
 		{
@@ -446,7 +568,7 @@
 		"specificness": 40,
 	},
 
-	"remove-guard": 
+	"remove-guard":
 	{
 		"execute": function(target, action, selection, queued)
 		{
@@ -471,11 +593,11 @@
 		"specificness": 41,
 	},
 
-	"set-rallypoint": 
+	"set-rallypoint":
 	{
 		"execute": function(target, action, selection, queued)
 		{
-			// if there is a position set in the action then use this so that when setting a 
+			// if there is a position set in the action then use this so that when setting a
 			// rally point on an entity it is centered on that entity
 			if (action.position)
 				target = action.position;
@@ -507,6 +629,28 @@
 				cursor = "action-attack-move";
 			}
 
+			if (Engine.HotkeyIsPressed("session.meleeattackmove"))
+			{
+				if (Engine.HotkeyIsPressed("session.meleeattackmoveUnit"))
+					var targetClasses = { "melee-attack": ["Unit"] };
+				else
+					var targetClasses = { "melee-attack": ["Unit", "Structure"] };
+				data.command = "melee-attack-walk";
+				data.targetClasses = targetClasses;
+				cursor = "action-melee-attack";
+			}
+
+			if (Engine.HotkeyIsPressed("session.rangedattackmove"))
+			{
+				if (Engine.HotkeyIsPressed("session.rangedattackmoveUnit"))
+					var targetClasses = { "ranged-attack": ["Unit"] };
+				else
+					var targetClasses = { "ranged-attack": ["Unit", "Structure"] };
+				data.command = "ranged-attack-walk";
+				data.targetClasses = targetClasses;
+				cursor = "action-ranged-attack";
+			}
+
 			if (targetState.garrisonHolder && playerCheck(entState, targetState, ["Player", "MutualAlly"]))
 			{
 				data.command = "garrison";
@@ -610,10 +754,10 @@
 				return false;
 			return {"type": "set-rallypoint", "cursor": actionInfo.cursor, "data": actionInfo.data, "tooltip": actionInfo.tooltip, "position": actionInfo.position};
 		},
-		"specificness": 6,
+		"specificness": 5,
 	},
 
-	"unset-rallypoint": 
+	"unset-rallypoint":
 	{
 		"execute": function(target, action, selection, queued)
 		{
@@ -654,7 +798,7 @@
 		"specificness": 11,
 	},
 
-	"none": 
+	"none":
 	{
 		"execute": function(target, action, selection, queued)
 		{
@@ -668,7 +812,7 @@
  * Info and actions for the entity commands
  * Currently displayed in the bottom of the central panel
  */
-var g_EntityCommands = 
+var g_EntityCommands =
 {
 	// Unload
 	"unload-all": {
Index: binaries/data/mods/public/simulation/ai/common-api/entity.js
===================================================================
--- binaries/data/mods/public/simulation/ai/common-api/entity.js	(revision 18113)
+++ binaries/data/mods/public/simulation/ai/common-api/entity.js	(working copy)
@@ -743,6 +743,7 @@
 		return false;
 	},
 
+	isCapturable: function() { return this.get("Capturable") !== undefined; },
 	isGarrisonHolder: function() { return this.get("GarrisonHolder") !== undefined; },
 	garrisoned: function() { return this._entity.garrisoned; },
 	canGarrisonInside: function() { return this._entity.garrisoned.length < this.garrisonMax(); },
@@ -753,12 +754,12 @@
 	},
 
 	moveToRange: function(x, z, min, max, queued = false) {
-		Engine.PostCommand(PlayerID,{"type": "walk-to-range", "entities": [this.id()], "x": x, "z": z, "min": min, "max": max, "queued": queued });
+		Engine.PostCommand(PlayerID,{ "type": "walk-to-range", "entities": [this.id()], "x": x, "z": z, "min": min, "max": max, "queued": queued });
 		return this;
 	},
 
-	attackMove: function(x, z, targetClasses, queued = false) {
-		Engine.PostCommand(PlayerID,{"type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "targetClasses": targetClasses, "queued": queued });
+	attackMove: function(x, z, targetClasses, prefAttackType = "primary", queued = false) { // hack
+		Engine.PostCommand(PlayerID,{"type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "targetClasses": targetClasses, "prefAttackType": prefAttackType, "queued": queued});
 		return this;
 	},
 
@@ -792,8 +793,8 @@
 		return this;
 	},
 
-	attack: function(unitId, allowCapture = true, queued = false) {
-		Engine.PostCommand(PlayerID,{"type": "attack", "entities": [this.id()], "target": unitId, "allowCapture": allowCapture, "queued": queued});
+	attack: function(unitId, prefAttackType, queued = false) {
+		Engine.PostCommand(PlayerID,{"type": "attack", "entities": [this.id()], "target": unitId, "prefAttackType": prefAttackType, "queued": queued});
 		return this;
 	},
 
Index: binaries/data/mods/public/simulation/ai/common-api/entitycollection.js
===================================================================
--- binaries/data/mods/public/simulation/ai/common-api/entitycollection.js	(revision 18113)
+++ binaries/data/mods/public/simulation/ai/common-api/entitycollection.js	(working copy)
@@ -153,7 +153,7 @@
 m.EntityCollection.prototype.attackMove = function(x, z, targetClasses, queued)
 {
 	queued = queued || false;
-	Engine.PostCommand(PlayerID,{"type": "attack-walk", "entities": this.toIdArray(), "x": x, "z": z, "targetClasses": targetClasses, "queued": queued});
+	Engine.PostCommand(PlayerID, { "type": "attack-walk", "entities": this.toIdArray(), "x": x, "z": z, "targetClasses": targetClasses, "queued": queued, "prefAttackType": "primary" }); // hack
 	return this;
 };
 
@@ -180,7 +180,7 @@
 m.EntityCollection.prototype.attack = function(unit)
 {
 	var unitId = unit;
-	Engine.PostCommand(PlayerID,{"type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false});
+	Engine.PostCommand(PlayerID, { "type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false, "prefAttackType": "Capture" });
 	return this;
 };
 
Index: binaries/data/mods/public/simulation/ai/petra/attackPlan.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/attackPlan.js	(revision 18113)
+++ binaries/data/mods/public/simulation/ai/petra/attackPlan.js	(working copy)
@@ -1078,7 +1078,7 @@
 						continue;
 					if (!ent.isIdle())
 						continue;
-					ent.attack(attacker.id(), !this.noCapture.has(attacker.id()));
+					ent.attack(attacker.id(), m.getPrefAttackType(ent, attacker, this.noCapture));
 				}
 				break;
 			}
@@ -1314,13 +1314,13 @@
 				{
 					if (this.isSiegeUnit(gameState, ent))	// needed as mauryan elephants are not filtered out
 						continue;
-					ent.attack(attacker.id(), !this.noCapture.has(attacker.id()));
+					ent.attack(attacker.id(), m.getPrefAttackType(ent, attacker, this.noCapture));
 					ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
 				}
 				// And if this attacker is a non-ranged siege unit and our unit also, attack it
 				if (this.isSiegeUnit(gameState, attacker) && attacker.hasClass("Melee") && ourUnit.hasClass("Melee"))
 				{
-					ourUnit.attack(attacker.id(), false);
+					ourUnit.attack(attacker.id(), "Melee");
 					ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
 				}
 			}
@@ -1331,7 +1331,7 @@
 					let collec = this.unitCollection.filter(API3.Filters.byClass("Melee")).filterNearest(ourUnit.position(), 5);
 					for (let ent of collec.values())
 					{
-						ent.attack(attacker.id(), false);
+						ent.attack(attacker.id(), m.getPrefAttackType(ent, attacker, this.noCapture));
 						ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
 					}
 				}
@@ -1350,7 +1350,7 @@
 								continue;
 						}
 					}
-					ourUnit.attack(attacker.id(), !this.noCapture.has(attacker.id()));
+					ourUnit.attack(attacker.id(), m.getPrefAttackType(ourUnit, attacker, this.noCapture));
 					ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
 				}
 			}
@@ -1521,12 +1521,12 @@
 						return (valb - vala);
 					});
 					if (mStruct[0].hasClass("Gates"))
-						ent.attack(mStruct[0].id(), false);
+						ent.attack(mStruct[0].id(), m.getPrefAttackType(ent, mStruct[0], this.noCapture));
 					else
 					{
 						let rand = Math.floor(Math.random() * mStruct.length * 0.2);
-						let newTargetId = mStruct[rand].id();
-						ent.attack(newTargetId, !this.noCapture.has(newTargetId));
+						let newTarget = mStruct[rand];
+						ent.attack(newTarget.id(), m.getPrefAttackType(ent, newTarget, this.noCapture));
 					}
 				}
 				else
@@ -1584,8 +1584,8 @@
 						return valb - vala;
 					});
 					let rand = Math.floor(Math.random() * mUnit.length * 0.1);
-					let newTargetId = mUnit[rand].id();
-					ent.attack(newTargetId, !this.noCapture.has(newTargetId));
+					let newTarget = mUnit[rand];
+					ent.attack(newTarget.id(), m.getPrefAttackType(ent, newTarget, this.noCapture));
 				}
 				else if (API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500 )
 				{
@@ -1628,12 +1628,12 @@
 							return (valb - vala);
 						});
 						if (mStruct[0].hasClass("Gates"))
-							ent.attack(mStruct[0].id(), false);
+							ent.attack(mStruct[0].id(), m.getPrefAttackType(ent, mStruct[0], this.noCapture));
 						else
 						{
 							let rand = Math.floor(Math.random() * mStruct.length * 0.2);
-							let newTargetId = mStruct[rand].id();
-							ent.attack(newTargetId, !this.noCapture.has(newTargetId));
+							let newTarget = mStruct[rand];
+							ent.attack(newTarget.id(), m.getPrefAttackType(ent, newTarget, this.noCapture));
 						}
 					}
 					else if (needsUpdate)  // really nothing   let's try to help our nearest unit
@@ -1654,7 +1654,7 @@
 
 						});
 						if (attackerId)
-							ent.attack(attackerId, !this.noCapture.has(attackerId));
+							ent.attack(attackerId, m.getPrefAttackType(ent, attacker, this.noCapture));
 					}
 				}
 			}
@@ -1828,7 +1828,7 @@
 
 	if (this.noCapture.has(targetId))
 	{
-		ent.attack(targetId, false);
+		ent.attack(targetId, m.getPrefAttackType(ent, target, this.noCapture));
 		return true;
 	}
 
@@ -1840,7 +1840,7 @@
 	if (target.hasClass("Siege") && target.hasClass("Melee"))
 	{
 		this.noCapture.add(targetId);
-		ent.attack(targetId, false);
+		ent.attack(targetId, m.getPrefAttackType(ent, target, this.noCapture));
 		return true;
 	}
 
@@ -1857,7 +1857,7 @@
 	if (antiCapture >= this.captureStrength)
 	{
 		this.noCapture.add(targetId);
-		ent.attack(targetId, false);
+		ent.attack(targetId, "primary"); // hack
 		return true;
 	}
 
@@ -1866,7 +1866,7 @@
 		this.unitCollection.length < 2*target.garrisoned().length)
 	{
 		this.noCapture.add(targetId);
-		ent.attack(targetId, false);
+		ent.attack(targetId, "primary"); // hack
 		return true;
 	}
 
Index: binaries/data/mods/public/simulation/ai/petra/defenseArmy.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/defenseArmy.js	(revision 18113)
+++ binaries/data/mods/public/simulation/ai/petra/defenseArmy.js	(working copy)
@@ -77,7 +77,7 @@
 	{
 		this.assignedTo[entID] = idFoe;
 		this.assignedAgainst[idFoe].push(entID);
-		ent.attack(idFoe, m.allowCapture(ent, foeEnt), queued);
+		ent.attack(idFoe, m.getPrefAttackType(ent, foeEnt), queued);
 	}
 	else
 		gameState.ai.HQ.navalManager.requireTransport(gameState, ent, ownIndex, foeIndex, foePosition);
@@ -116,8 +116,12 @@
 		else if (orderData.length && orderData[0].target && orderData[0].attackType && orderData[0].attackType === "Capture")
 		{
 			let target = gameState.getEntityById(orderData[0].target);
-			if (target && !m.allowCapture(ent, target))
-				ent.attack(orderData[0].target, false);
+			if (target)
+			{
+				let prefAttackType = m.getPrefAttackType(ent, target);
+				if (prefAttackType !== "Capture")
+					ent.attack(orderData[0].target, prefAttackType);
+			}
 		}
 	}
 
Index: binaries/data/mods/public/simulation/ai/petra/entityExtend.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/entityExtend.js	(revision 18113)
+++ binaries/data/mods/public/simulation/ai/petra/entityExtend.js	(working copy)
@@ -81,11 +81,15 @@
 	return strength * ent.maxHitpoints() / 100.0;
 };
 
-// Decide if we should try to capture or destroy
-m.allowCapture = function(ent, target)
+// Decide if we should try to capture, melee or range attack
+// TODO make this function less hacky
+m.getPrefAttackType = function(ent, target, noCapture)
 {
-	return !target.hasClass("Siege") || !ent.hasClass("Melee") ||
-		!target.isGarrisonHolder() || !target.garrisoned().length;
+	if (target.hasClass("Siege") || (noCapture && noCapture.has(target.id())))
+		return ent.hasClass("Melee") ? "Melee" : "Range";// Don't capture for now
+	if (target.isCapturable() && (!target.isGarrisonHolder() || !target.garrisoned().length))
+		return "Capture";
+	return "primary";
 };
 
 // Makes the worker deposit the currently carried resources at the closest accessible dropsite
Index: binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/Attack.js	(revision 18113)
+++ binaries/data/mods/public/simulation/components/Attack.js	(working copy)
@@ -1,6 +1,6 @@
 function Attack() {}
 
-Attack.prototype.bonusesSchema = 
+Attack.prototype.bonusesSchema =
 	"<optional>" +
 		"<element name='Bonuses'>" +
 			"<zeroOrMore>" +
@@ -41,7 +41,9 @@
 Attack.prototype.Schema =
 	"<a:help>Controls the attack abilities and strengths of the unit.</a:help>" +
 	"<a:example>" +
+		"<ChangeDistance>20</ChangeDistance>" +
 		"<Melee>" +
+			"<AttackOrder>primary</AttackOrder>" +
 			"<Hack>10.0</Hack>" +
 			"<Pierce>0.0</Pierce>" +
 			"<Crush>5.0</Crush>" +
@@ -62,6 +64,7 @@
 			"<PreferredClasses datatype=\"tokens\">Cavalry Infantry</PreferredClasses>" +
 		"</Melee>" +
 		"<Ranged>" +
+			"<AttackOrder>secondary</AttackOrder>" +
 			"<Hack>0.0</Hack>" +
 			"<Pierce>10.0</Pierce>" +
 			"<Crush>0.0</Crush>" +
@@ -103,7 +106,20 @@
 		"</Slaughter>" +
 	"</a:example>" +
 	"<optional>" +
+		"<element name='ChangeDistance' a:help='Distance to change between Melee and Ranged attack'>" +
+			"<ref name='nonNegativeDecimal'/>" +
+		"</element>" +
+	"</optional>" +
+	"<optional>" +
 		"<element name='Melee'>" +
+			"<optional>" +
+				"<element name='AttackOrder'>" +
+					"<choice>" +
+						"<value>primary</value>" +
+						"<value>secondary</value>" +
+					"</choice>" +
+				"</element>" +
+			"</optional>" +
 			"<interleave>" +
 				"<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
 				"<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
@@ -120,6 +136,14 @@
 	"</optional>" +
 	"<optional>" +
 		"<element name='Ranged'>" +
+			"<optional>" +
+				"<element name='AttackOrder'>" +
+					"<choice>" +
+						"<value>primary</value>" +
+						"<value>secondary</value>" +
+					"</choice>" +
+				"</element>" +
+			"</optional>" +
 			"<interleave>" +
 				"<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
 				"<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
@@ -220,9 +244,7 @@
 {
 	if (this.template[type] && this.template[type].PreferredClasses &&
 	    this.template[type].PreferredClasses._string)
-	{
 		return this.template[type].PreferredClasses._string.split(/\s+/);
-	}
 	return [];
 };
 
@@ -230,39 +252,59 @@
 {
 	if (this.template[type] && this.template[type].RestrictedClasses &&
 	    this.template[type].RestrictedClasses._string)
-	{
 		return this.template[type].RestrictedClasses._string.split(/\s+/);
-	}
 	return [];
 };
 
-Attack.prototype.CanAttack = function(target)
+Attack.prototype.GetChangeDistance = function()
 {
+	if (this.template.ChangeDistance)
+		return +this.template.ChangeDistance;
+	return 0; // Template is broken, should never happen.
+};
+
+Attack.prototype.CanAttack = function(target, wantedType)
+{
 	let cmpFormation = Engine.QueryInterface(target, IID_Formation);
 	if (cmpFormation)
 		return true;
 
+	if (wantedType && !this.template[wantedType])
+		return false;
+
 	let cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position);
 	let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
 	if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld())
 		return false;
 
+	const cmpIdentity = Engine.QueryInterface(target, IID_Identity);
+	if (!cmpIdentity)
+		return undefined;
+
+	let cmpEntityPlayer = QueryOwnerInterface(this.entity);
+	let cmpTargetPlayer = QueryOwnerInterface(target);
+	if (!cmpTargetPlayer || !cmpEntityPlayer)
+		return false;
+
+	const targetClasses = cmpIdentity.GetClassesList();
+	let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
+	if (targetClasses.indexOf("Domestic") == -1 && !cmpEntityPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID()) &&
+	   (!cmpCapturable || !cmpCapturable.CanCapture(cmpEntityPlayer.GetPlayerID())))
+		return false;
+
 	// Check if the relative height difference is larger than the attack range
 	// If the relative height is bigger, it means they will never be able to
 	// reach each other, no matter how close they come.
 	let heightDiff = Math.abs(cmpThisPosition.GetHeightOffset() - cmpTargetPosition.GetHeightOffset());
 
-	const cmpIdentity = Engine.QueryInterface(target, IID_Identity);
-	if (!cmpIdentity) 
-		return undefined;
-
-	const targetClasses = cmpIdentity.GetClassesList();
-
 	for (let type of this.GetAttackTypes())
 	{
-		if (type == "Capture" && !QueryMiragedInterface(target, IID_Capturable))
+		if (wantedType && type != wantedType) 
 			continue;
 
+		if (type == "Capture" && !cmpCapturable)
+			continue;
+
 		if (type == "Slaughter" && targetClasses.indexOf("Domestic") == -1)
 			continue;
 
@@ -294,7 +336,7 @@
 Attack.prototype.GetPreference = function(target)
 {
 	const cmpIdentity = Engine.QueryInterface(target, IID_Identity);
-	if (!cmpIdentity) 
+	if (!cmpIdentity)
 		return undefined;
 
 	const targetClasses = cmpIdentity.GetClassesList();
@@ -327,15 +369,13 @@
 		if (type == "Slaughter")
 			continue;
 		let range = this.GetRange(type);
-		if (range.min < ret.min)
-			ret.min = range.min;
-		if (range.max > ret.max)
-			ret.max = range.max;
+		ret.min = Math.min(ret.min, range.min);
+		ret.max = Math.max(ret.max, range.max);
 	}
 	return ret;
 };
 
-Attack.prototype.GetBestAttackAgainst = function(target, allowCapture)
+Attack.prototype.GetBestAttackAgainst = function(target, prefAttackType)
 {
 	let cmpFormation = Engine.QueryInterface(target, IID_Formation);
 	if (cmpFormation)
@@ -350,7 +390,7 @@
 	}
 
 	let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
-	if (!cmpIdentity) 
+	if (!cmpIdentity)
 		return undefined;
 
 	let targetClasses = cmpIdentity.GetClassesList();
@@ -357,7 +397,7 @@
 	let isTargetClass = function (className) { return targetClasses.indexOf(className) != -1; };
 
 	// Always slaughter domestic animals instead of using a normal attack
-	if (isTargetClass("Domestic") && this.template.Slaughter) 
+	if (isTargetClass("Domestic") && this.template.Slaughter)
 		return "Slaughter";
 
 	let attack = this;
@@ -364,24 +404,66 @@
 	let isAllowed = function (type) { return !attack.GetRestrictedClasses(type).some(isTargetClass); };
 
 	let types = this.GetAttackTypes().filter(isAllowed);
+	if (types.length === 0)
+		return undefined;
 
-	// check if the target is capturable
-	let captureIndex = types.indexOf("Capture");
-	if (captureIndex != -1)
+	prefAttackType = this.GetAttackTypeFromOrder(prefAttackType);
+	if (prefAttackType && prefAttackType != "Capture" && types.indexOf(prefAttackType) != -1)
+		return prefAttackType;
+	else if (!prefAttackType || prefAttackType == "Capture")
 	{
-		let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
-
-		let cmpPlayer = QueryOwnerInterface(this.entity);
-		if (allowCapture && cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID()))
-			return "Capture";
-		// not captureable, so remove this attack
-		types.splice(captureIndex, 1);
+		// check if the target is capturable
+		let captureIndex = types.indexOf("Capture");
+		if (captureIndex != -1)
+		{
+			let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
+			let cmpPlayer = QueryOwnerInterface(this.entity);
+			if (cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID()))
+				return "Capture";
+			// not captureable, so remove this attack
+			types.splice(captureIndex, 1);
+		}
 	}
 
-	let isPreferred = function (className) { return attack.GetPreferredClasses(className).some(isTargetClass); };
-	let byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); };
+	// ignore charges for now: TODO implement these
+	let chargeIndex = types.indexOf("Charge");
+	if (chargeIndex != -1)
+		types.splice(chargeIndex, 1);
 
-	return types.sort(byPreference).pop();
+	if (types.length === 0)
+		return undefined;
+
+	// assume ranged and/or melee attack left
+	// TODO stop assuming that?
+	let meleeIndex = types.indexOf("Melee");
+	let rangedIndex = types.indexOf("Ranged");
+	if (meleeIndex != -1 && rangedIndex != -1)
+	{
+		if (this.HasTargetPreferredClass(types, targetClasses))
+		{
+			let isPreferred = function (className) { return attack.GetPreferredClasses(className).some(isTargetClass); };
+			let byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); };
+
+			return types.sort(byPreference).pop();
+		}
+
+		let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+		if (!cmpPosition || !cmpPosition.IsInWorld())
+			return undefined;
+		let selfPosition = cmpPosition.GetPosition2D();
+		let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
+		if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
+			return undefined;
+		let targetPosition = cmpTargetPosition.GetPosition2D();
+		if (targetPosition.distanceToSquared(selfPosition) <= Math.pow(this.GetChangeDistance(), 2))
+			return "Melee";
+		return "Ranged";
+	}
+	else if (meleeIndex != -1)
+		return "Melee";
+	else if (rangedIndex != -1)
+		return "Ranged";
+	return types[0];
 };
 
 Attack.prototype.CompareEntitiesByPreference = function(a, b)
@@ -478,6 +560,24 @@
 	return attackBonus;
 };
 
+Attack.prototype.GetAttackTypeFromOrder = function(prefAttackType)
+{
+	let types = this.GetAttackTypes();
+	for (let type of types)
+		if (this.template[type].AttackOrder && this.template[type].AttackOrder == prefAttackType)
+			return type;
+	return prefAttackType;
+};
+
+Attack.prototype.HasTargetPreferredClass = function(types, targetClasses)
+{
+	let isTargetClass = function (className) { return targetClasses.indexOf(className) != -1; };
+	for (let type of types)
+		if (this.template[type].PreferredClasses && this.GetPreferredClasses(type).some(isTargetClass))
+			return true;
+	return false
+};
+
 // Returns a 2d random distribution scaled for a spread of scale 1.
 // The current implementation is a 2d gaussian with sigma = 1
 Attack.prototype.GetNormalDistribution = function(){
@@ -486,8 +586,8 @@
 	let a = Math.random();
 	let b = Math.random();
 
-	let c = Math.sqrt(-2*Math.log(a)) * Math.cos(2*Math.PI*b);
-	let d = Math.sqrt(-2*Math.log(a)) * Math.sin(2*Math.PI*b);
+	let c = Math.sqrt(-2 * Math.log(a)) * Math.cos(2 * Math.PI * b);
+	let d = Math.sqrt(-2 * Math.log(a)) * Math.sin(2 * Math.PI * b);
 
 	return [c, d];
 };
@@ -503,7 +603,7 @@
 	if (type == "Ranged")
 	{
 		let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
-		let turnLength = cmpTimer.GetLatestTurnLength()/1000;
+		let turnLength = cmpTimer.GetLatestTurnLength() / 1000;
 		// In the future this could be extended:
 		//  * Obstacles like trees could reduce the probability of the target being hit
 		//  * Obstacles like walls should block projectiles entirely
@@ -535,9 +635,9 @@
 
 		let horizDistance = targetPosition.horizDistanceTo(selfPosition);
 
-		// This is an approximation of the time ot the target, it assumes that the target has a constant radial 
-		// velocity, but since units move in straight lines this is not true.  The exact value would be more 
-		// difficult to calculate and I think this is sufficiently accurate.  (I tested and for cavalry it was 
+		// This is an approximation of the time to the target, it assumes that the target has a constant radial
+		// velocity, but since units move in straight lines this is not true. The exact value would be more
+		// difficult to calculate and I think this is sufficiently accurate. (I tested and for cavalry it was
 		// about 5% of the units radius out in the worst case)
 		let timeToTarget = horizDistance / (horizSpeed - radialSpeed);
 
@@ -620,7 +720,7 @@
 Attack.prototype.InterpolatedLocation = function(ent, lateness)
 {
 	let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
-	let turnLength = cmpTimer.GetLatestTurnLength()/1000;
+	let turnLength = cmpTimer.GetLatestTurnLength() / 1000;
 	let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position);
 	if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly
 		return undefined;
@@ -658,7 +758,7 @@
 		let d = Vector3D.sub(point, targetPosition);
 		d = Vector2D.from3D(d).rotate(-angle);
 
-		return d.x < Math.abs(targetShape.width/2) && d.y < Math.abs(targetShape.depth/2);
+		return d.x < Math.abs(targetShape.width / 2) && d.y < Math.abs(targetShape.depth / 2);
 	}
 };
 
@@ -740,7 +840,7 @@
 		return;
 
 	for (let type of this.GetAttackTypes())
-		if (msg.valueNames.indexOf("Attack/"+type+"/MaxRange") !== -1)
+		if (msg.valueNames.indexOf("Attack/" + type + "/MaxRange") !== -1)
 		{
 			cmpUnitAI.UpdateRangeQueries();
 			return;
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js	(revision 18113)
+++ binaries/data/mods/public/simulation/components/GuiInterface.js	(working copy)
@@ -1748,16 +1748,16 @@
 	if (!cmpAttack)
 		return false;
 
-	let cmpEntityPlayer = QueryOwnerInterface(data.entity, IID_Player);
-	let cmpTargetPlayer = QueryOwnerInterface(data.target, IID_Player);
-	if (!cmpEntityPlayer || !cmpTargetPlayer)
+	return cmpAttack.CanAttack(data.target);
+};
+
+GuiInterface.prototype.CanAttackWithType = function(player, data)
+{
+	let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
+	if (!cmpAttack)
 		return false;
 
-	// if the owner is an enemy, it's up to the attack component to decide
-	if (cmpEntityPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID()))
-		return cmpAttack.CanAttack(data.target);
-
-	return false;
+	return cmpAttack.CanAttack(data.target, data.type);
 };
 
 /*
@@ -1903,6 +1903,7 @@
 	"GetTradingDetails": 1,
 	"CanCapture": 1,
 	"CanAttack": 1,
+	"CanAttackWithType": 1,
 	"GetBatchTime": 1,
 
 	"IsMapRevealed": 1,
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js	(revision 18113)
+++ binaries/data/mods/public/simulation/components/UnitAI.js	(working copy)
@@ -416,7 +416,7 @@
 		}
 
 		// Work out how to attack the given target
-		var type = this.GetBestAttackAgainst(this.order.data.target, this.order.data.allowCapture);
+		let type = this.GetBestAttackAgainst(this.order.data.target, this.order.data.prefAttackType);
 		if (!type)
 		{
 			// Oops, we can't attack at all
@@ -559,7 +559,7 @@
 		if (this.MustKillGatherTarget(this.order.data.target))
 		{
 			// Make sure we can attack the target, else we'll get very stuck
-			if (!this.GetBestAttackAgainst(this.order.data.target, false))
+			if (!this.GetBestAttackAgainst(this.order.data.target))
 			{
 				// Oops, we can't attack at all - give up
 				// TODO: should do something so the player knows why this failed
@@ -583,7 +583,7 @@
 				return;
 			}
 
-			this.PushOrderFront("Attack", { "target": this.order.data.target, "force": false, "hunting": true, "allowCapture": false });
+			this.PushOrderFront("Attack", { "target": this.order.data.target, "force": false, "hunting": true, "prefAttackType": undefined });
 			return;
 		}
 
@@ -838,9 +838,9 @@
 		},
 
 		"Order.Attack": function(msg) {
-			var target = msg.data.target;
-			var allowCapture = msg.data.allowCapture;
-			var cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI);
+			let target = msg.data.target;
+			let prefAttackType = msg.data.prefAttackType;
+			let cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI);
 			if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember())
 				target = cmpTargetUnitAI.GetFormationController();
 
@@ -859,7 +859,7 @@
 				this.FinishOrder();
 				return;
 			}
-			this.CallMemberFunction("Attack", [target, false, allowCapture]);
+			this.CallMemberFunction("Attack", [target, false, prefAttackType]);
 			if (cmpAttack.CanAttackAsFormation())
 				this.SetNextState("COMBAT.ATTACKING");
 			else
@@ -914,7 +914,7 @@
 					return;
 				}
 
-				this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "allowCapture": false });
+				this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "prefAttackType": undefined });
 				return;
 			}
 
@@ -1151,7 +1151,7 @@
 
 				"MoveCompleted": function(msg) {
 					var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
-					this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data.allowCapture]);
+					this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data.prefAttackType]);
 					if (cmpAttack.CanAttackAsFormation())
 						this.SetNextState("COMBAT.ATTACKING");
 					else
@@ -1162,8 +1162,8 @@
 			"ATTACKING": {
 				// Wait for individual members to finish
 				"enter": function(msg) {
-					var target = this.order.data.target;
-					var allowCapture = this.order.data.allowCapture;
+					let target = this.order.data.target;
+					let prefAttackType = this.order.data.prefAttackType;
 					// Check if we are already in range, otherwise walk there
 					if (!this.CheckTargetAttackRange(target, target))
 					{
@@ -1170,7 +1170,7 @@
 						if (this.TargetIsAlive(target) && this.CheckTargetVisible(target))
 						{
 							this.FinishOrder();
-							this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture });
+							this.PushOrderFront("Attack", { "target": target, "force": false, "prefAttackType": prefAttackType });
 							return true;
 						}
 						this.FinishOrder();
@@ -1186,8 +1186,8 @@
 				},
 
 				"Timer": function(msg) {
-					var target = this.order.data.target;
-					var allowCapture = this.order.data.allowCapture;
+					let target = this.order.data.target;
+					let prefAttackType = this.order.data.prefAttackType;
 					// Check if we are already in range, otherwise walk there
 					if (!this.CheckTargetAttackRange(target, target))
 					{
@@ -1194,7 +1194,7 @@
 						if (this.TargetIsAlive(target) && this.CheckTargetVisible(target))
 						{
 							this.FinishOrder();
-							this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture });
+							this.PushOrderFront("Attack", { "target": target, "force": false, "prefAttackType": prefAttackType });
 							return;
 						}
 						this.FinishOrder();
@@ -1406,7 +1406,7 @@
 
 			// target the unit
 			if (this.CheckTargetVisible(msg.data.attacker))
-				this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "allowCapture": true });
+				this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "prefAttackType": undefined });
 			else
 			{
 				var cmpPosition = Engine.QueryInterface(msg.data.attacker, IID_Position);
@@ -1801,6 +1801,8 @@
 						// Can't reach it - try to chase after it
 						if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
 						{
+							if (!this.order.data.force)
+								this.order.data.attackType = this.GetBestAttackAgainst(target);
 							if (this.MoveToTargetAttackRange(target, this.order.data.attackType))
 							{
 								this.SetNextState("COMBAT.CHASING");
@@ -1912,6 +1914,12 @@
 						// Can't reach it - try to chase after it
 						if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
 						{
+							if (!this.order.data.force)
+							{
+								let type = this.GetBestAttackAgainst(target);
+								if (type)
+									this.order.data.attackType = type;
+							}
 							if (this.MoveToTargetRange(target, IID_Attack, this.order.data.attackType))
 							{
 								this.SetNextState("COMBAT.CHASING");
@@ -4535,12 +4543,12 @@
 	return distance < range;
 };
 
-UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture)
+UnitAI.prototype.GetBestAttackAgainst = function(target, prefAttackType)
 {
 	var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
 	if (!cmpAttack)
 		return undefined;
-	return cmpAttack.GetBestAttackAgainst(target, allowCapture);
+	return cmpAttack.GetBestAttackAgainst(target, prefAttackType);
 };
 
 UnitAI.prototype.GetAttackBonus = function(type, target)
@@ -4562,7 +4570,7 @@
 	if (!target)
 		return false;
 
-	this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true });
+	this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "prefAttackType": undefined });
 	return true;
 };
 
@@ -4575,13 +4583,13 @@
 {
 	var target = ents.find(target =>
 		this.CanAttack(target, forceResponse)
-		&& this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true))
+		&& this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target))
 		&& (this.GetStance().respondChaseBeyondVision || this.CheckTargetIsInVisionRange(target))
 	);
 	if (!target)
 		return false;
 
-	this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true });
+	this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "prefAttackType": undefined });
 	return true;
 };
 
@@ -4978,9 +4986,9 @@
  * to a player order, and so is forced.
  * If targetClasses is given, only entities matching the targetClasses can be attacked.
  */
-UnitAI.prototype.WalkAndFight = function(x, z, targetClasses, queued)
+UnitAI.prototype.WalkAndFight = function(x, z, targetClasses, queued, prefAttackType)
 {
-	this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "force": true }, queued);
+	this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "force": true, "prefAttackType": prefAttackType }, queued);
 };
 
 /**
@@ -5001,7 +5009,7 @@
 /**
  * Adds attack order to the queue, forced by the player.
  */
-UnitAI.prototype.Attack = function(target, queued, allowCapture)
+UnitAI.prototype.Attack = function(target, queued, prefAttackType)
 {
 	if (!this.CanAttack(target))
 	{
@@ -5013,7 +5021,7 @@
 			this.WalkToTarget(target, queued);
 		return;
 	}
-	this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture}, queued);
+	this.AddOrder("Attack", { "target": target, "force": true, "prefAttackType": prefAttackType }, queued);
 };
 
 /**
@@ -5430,7 +5438,7 @@
 					if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
 						continue;
 				}
-				this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });
+				this.PushOrderFront("Attack", { "target": targ, "force": true, "prefAttackType": undefined });
 				return true;
 			}
 		}
@@ -5456,7 +5464,7 @@
 			if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
 				continue;
 		}
-		this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });
+		this.PushOrderFront("Attack", { "target": targ, "force": true, "prefAttackType": undefined });
 		return true;
 	}
 	return false;
Index: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js	(revision 18113)
+++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js	(working copy)
@@ -97,7 +97,7 @@
 	AddMock(unit, IID_Attack, {
 		GetRange: function() { return { "max": 10, "min": 0}; },
 		GetFullAttackRange: function() { return { "max": 40, "min": 0}; },
-		GetBestAttackAgainst: function(t) { return "melee"; },
+		GetBestAttackAgainst: function(t) { return "Melee"; },
 		GetPreference: function(t) { return 0; },
 		GetTimers: function() { return { "prepare": 500, "repeat": 1000 }; },
 		CanAttack: function(v) { return true; },
@@ -247,7 +247,7 @@
 		AddMock(unit + i, IID_Attack, {
 			GetRange: function() { return {"max":10, "min": 0}; },
 			GetFullAttackRange: function() { return { "max": 40, "min": 0}; },
-			GetBestAttackAgainst: function(t) { return "melee"; },
+			GetBestAttackAgainst: function(t) { return "Melee"; },
 			GetTimers: function() { return { "prepare": 500, "repeat": 1000 }; },
 			CanAttack: function(v) { return true; },
 			CompareEntitiesByPreference: function(a, b) { return 0; },
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js	(revision 18113)
+++ binaries/data/mods/public/simulation/helpers/Commands.js	(working copy)
@@ -158,7 +158,7 @@
 	"attack-walk": function(player, cmd, data)
 	{
 		GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
-			cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.queued);
+			cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.queued, cmd.prefAttackType);
 		});
 	},
 
@@ -167,10 +167,9 @@
 		if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target)))
 			warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd));
 
-		let allowCapture = cmd.allowCapture || cmd.allowCapture == null;
 		// See UnitAI.CanAttack for target checks
 		GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
-			cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture);
+			cmpUnitAI.Attack(cmd.target, cmd.queued, cmd.prefAttackType);
 		});
 	},
 
Index: binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml	(revision 18113)
+++ binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml	(working copy)
@@ -1,5 +1,24 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Entity parent="template_unit_champion_infantry_spearman">
+  <Attack>
+    <ChangeDistance>25</ChangeDistance>
+    <Melee>
+      <AttackOrder>primary</AttackOrder>
+      <PreferredClasses datatype="tokens">Siege</PreferredClasses>
+    </Melee>
+    <Ranged>
+      <AttackOrder>secondary</AttackOrder>
+      <Hack>0</Hack>
+      <Pierce>6.0</Pierce>
+      <Crush>0</Crush>
+      <MaxRange>65.0</MaxRange>
+      <MinRange>10.0</MinRange>
+      <ProjectileSpeed>120.0</ProjectileSpeed>
+      <PrepareTime>1000</PrepareTime>
+      <RepeatTime>1000</RepeatTime>
+      <Spread>3.0</Spread>
+    </Ranged>
+  </Attack>
   <Identity>
     <Civ>pers</Civ>
     <GenericName>Persian Immortal</GenericName>
