Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg	(revision 18481)
+++ binaries/data/config/default.cfg	(working copy)
@@ -276,15 +276,21 @@ offscreen = Alt              ; Include o
 9 = 9
 
 [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
 massbarter = Shift           ; Modifier to barter bunch of resources
 masstribute = Shift          ; Modifier to tribute bunch of resources
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 18481)
+++ binaries/data/mods/public/art/actors/units/persians/champion_unit_1.xml	(working copy)
@@ -40,8 +40,14 @@
     </variant>
   </group>
   <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/globalscripts/Templates.js
===================================================================
--- binaries/data/mods/public/globalscripts/Templates.js	(revision 18481)
+++ binaries/data/mods/public/globalscripts/Templates.js	(working copy)
@@ -64,10 +64,28 @@ function MatchesClassList(classes, match
 
 	return false;
 }
 
 /**
+ * Check if entity has all needed attack types.
+ */
+function HasNeededAttackTypes(attack, neededAttackTypes)
+{
+	if (!neededAttackTypes)
+		return true;
+	// transform the string to an array
+	if (typeof neededAttackTypes == "string")
+		neededAttackTypes = neededAttackTypes.split(/\s+/);
+
+	for (let type of neededAttackTypes)
+		if (!attack || attack.constructor === Object && !attack[type] || 
+		    attack.constructor === Array && attack.indexOf(type) == -1)
+			return false;
+	return true; 
+}
+
+/**
  * Get information about a template with or without technology modifications.
  * @param template A valid template as returned by the template loader.
  * @param player An optional player id to get the technology modifications
  *               of properties.
  * @param auraTemplates An object in the form of {key: {auraName: "", auraDescription: ""}}
Index: binaries/data/mods/public/gui/common/tooltips.js
===================================================================
--- binaries/data/mods/public/gui/common/tooltips.js	(revision 18481)
+++ binaries/data/mods/public/gui/common/tooltips.js	(working copy)
@@ -144,10 +144,12 @@ function getAttackTooltip(template)
 		return "";
 
 	let attacks = [];
 	for (let type in template.attack)
 	{
+		if (type == "ChangeDistance")
+			continue; // not an attack type
 		if (type == "Slaughter")
 			continue; // Slaughter is used to kill animals, so do not show it.
 		if (type == "Charge")
 			continue; // Charging isn't implemented yet and shouldn't be displayed.
 
Index: binaries/data/mods/public/gui/session/input.js
===================================================================
--- binaries/data/mods/public/gui/session/input.js	(revision 18481)
+++ binaries/data/mods/public/gui/session/input.js	(working copy)
@@ -203,10 +203,25 @@ function getActionInfo(action, target)
 			{
 				data.command = "attack-walk";
 				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 };
 		}
 
 		return { "possible": (action == "move" || action == "attack-move" || action == "remove-guard") };
 	}
@@ -217,20 +232,20 @@ function getActionInfo(action, target)
 	var targetState = GetExtendedEntityState(target);
 
 	// 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;
 
 		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;
 		}
 	}
 	return { "possible": false };
 }
Index: binaries/data/mods/public/gui/session/unit_actions.js
===================================================================
--- binaries/data/mods/public/gui/session/unit_actions.js	(revision 18481)
+++ binaries/data/mods/public/gui/session/unit_actions.js	(working copy)
@@ -56,11 +56,11 @@ var unitActions =
 			if (!someUnitAI(selection) || !getActionInfo("move", target).possible)
 				return false;
 
 			return { "type": "move" };
 		},
-		"specificness": 12,
+		"specificness": 13,
 	},
 
 	"attack-move":
 	{
 		"execute": function(target, action, selection, queued)
@@ -75,11 +75,12 @@ var unitActions =
 				"type": "attack-walk",
 				"entities": selection,
 				"x": target.x,
 				"z": target.z,
 				"targetClasses": targetClasses,
-				"queued": queued
+				"queued": queued,
+				"prefAttackType": "primary"
 			});
 
 			Engine.GuiInterfaceCall("PlaySound", {
 				"name": "order_walk",
 				"entity": selection[0]
@@ -104,20 +105,109 @@ var unitActions =
 			};
 		},
 		"specificness": 30,
 	},
 
+	"melee-attack-move":
+	{
+		"execute": function(target, action, selection, queued)
+		{
+			let targetClasses;
+			if (Engine.HotkeyIsPressed("session.meleeattackmoveUnit"))
+				targetClasses = { "meleeattack": ["Unit"] };
+			else
+				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)
+		{
+			if (someUnitAI(selection) && 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)
+		{
+			let targetClasses;
+			if (Engine.HotkeyIsPressed("session.rangedattackmoveUnit"))
+				targetClasses = { "rangedattack": ["Unit"] };
+			else
+				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)
+		{
+			if (someUnitAI(selection) && 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,
 				"allowCapture": true,
-				"queued": queued
+				"queued": queued,
+				"prefAttackType": "Capture"
 			});
 
 			Engine.GuiInterfaceCall("PlaySound", {
 				"name": "order_attack",
 				"entity": selection[0]
@@ -146,11 +236,11 @@ var unitActions =
 				"type": "capture",
 				"cursor": "action-capture",
 				"target": target
 			};
 		},
-		"specificness": 9,
+		"specificness": 7,
 	},
 
 	"attack":
 	{
 		"execute": function(target, action, selection, queued)
@@ -158,11 +248,12 @@ var unitActions =
 			Engine.PostNetworkCommand({
 				"type": "attack",
 				"entities": selection,
 				"target": action.target,
 				"queued": queued,
-				"allowCapture": false
+				"allowCapture": false,
+				"prefAttackType": "primary"
 			});
 
 			Engine.GuiInterfaceCall("PlaySound", {
 				"name": "order_attack",
 				"entity": selection[0]
@@ -203,10 +294,113 @@ var unitActions =
 				"type": "attack",
 				"cursor": "action-attack",
 				"target": target
 			};
 		},
+		"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)
+				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":
 	{
@@ -253,11 +447,11 @@ var unitActions =
 				"type": "heal",
 				"cursor": "action-heal",
 				"target": target
 			};
 		},
-		"specificness": 7,
+		"specificness": 6,
 	},
 
 	"build":
 	{
 		"execute": function(target, action, selection, queued)
@@ -353,11 +547,11 @@ var unitActions =
 				"type": "build",
 				"cursor": "action-repair",
 				"target": target
 			};
 		},
-		"specificness": 11,
+		"specificness": 12,
 	},
 
 	"gather":
 	{
 		"execute": function(target, action, selection, queued)
@@ -593,17 +787,18 @@ var unitActions =
 				extraCount += entState.garrisonHolder.garrisonedEntitiesCount;
 
 			if (targetState.garrisonHolder.garrisonedEntitiesCount + extraCount >= targetState.garrisonHolder.capacity)
 				tooltip = "[color=\"orange\"]" + tooltip + "[/color]";
 
-			if (!MatchesClassList(entState.identity.classes, targetState.garrisonHolder.allowedClasses))
-				return false;
+			if (MatchesClassList(entState.identity.classes, targetState.garrisonHolder.allowedClasses) &&
+			    HasNeededAttackTypes(entState.attack, targetState.garrisonHolder.neededAttackTypes))
+				return {
+					"possible": true,
+					"tooltip": tooltip
+				};
 
-			return {
-				"possible": true,
-				"tooltip": tooltip
-			};
+			return { "possible": false };
 		},
 		"preSelectedActionCheck": function(target)
 		{
 			if (preSelectedAction != ACTION_GARRISON)
 				return false;
@@ -781,10 +976,34 @@ var unitActions =
 				data.command = "attack-walk";
 				data.targetClasses = targetClasses;
 				cursor = "action-attack-move";
 			}
 
+			if (Engine.HotkeyIsPressed("session.meleeattackmove"))
+			{
+				let targetClasses;
+				if (Engine.HotkeyIsPressed("session.meleeattackmoveUnit"))
+					targetClasses = { "melee-attack": ["Unit"] };
+				else
+					targetClasses = { "melee-attack": ["Unit", "Structure"] };
+				data.command = "melee-attack-walk";
+				data.targetClasses = targetClasses;
+				cursor = "action-melee-attack";
+			}
+
+			if (Engine.HotkeyIsPressed("session.rangedattackmove"))
+			{
+				let targetClasses;
+				if (Engine.HotkeyIsPressed("session.rangedattackmoveUnit"))
+					targetClasses = { "ranged-attack": ["Unit"] };
+				else
+					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";
 				data.target = targetState.id;
@@ -899,11 +1118,11 @@ var unitActions =
 				"data": actionInfo.data,
 				"tooltip": actionInfo.tooltip,
 				"position": actionInfo.position
 			};
 		},
-		"specificness": 6,
+		"specificness": 5,
 	},
 
 	"unset-rallypoint":
 	{
 		"execute": function(target, action, selection, queued)
Index: binaries/data/mods/public/simulation/ai/common-api/entity.js
===================================================================
--- binaries/data/mods/public/simulation/ai/common-api/entity.js	(revision 18481)
+++ binaries/data/mods/public/simulation/ai/common-api/entity.js	(working copy)
@@ -744,26 +744,27 @@ m.Entity = m.Class({
 			if (r.split('.')[0] === type)
 				return true;
 		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(); },
 
 	move: function(x, z, queued = false) {
 		Engine.PostCommand(PlayerID,{"type": "walk", "entities": [this.id()], "x": x, "z": z, "queued": queued });
 		return this;
 	},
 
 	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 = "undefined", queued = false) { // hack
+		Engine.PostCommand(PlayerID,{"type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "targetClasses": targetClasses, "prefAttackType": prefAttackType, "queued": queued});
 		return this;
 	},
 
 	// violent, aggressive, defensive, passive, standground
 	setStance: function(stance, queued = false) {
@@ -793,12 +794,12 @@ m.Entity = m.Class({
 	garrison: function(target, queued = false) {
 		Engine.PostCommand(PlayerID,{"type": "garrison", "entities": [this.id()], "target": target.id(),"queued": queued});
 		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 = undefined, queued = false) {
+		Engine.PostCommand(PlayerID,{"type": "attack", "entities": [this.id()], "target": unitId, "prefAttackType": prefAttackType, "queued": queued});
 		return this;
 	},
 
 	// moveApart from a point in the opposite direction with a distance dist
 	moveApart: function(point, dist) {
Index: binaries/data/mods/public/simulation/ai/common-api/entitycollection.js
===================================================================
--- binaries/data/mods/public/simulation/ai/common-api/entitycollection.js	(revision 18481)
+++ binaries/data/mods/public/simulation/ai/common-api/entitycollection.js	(working copy)
@@ -154,11 +154,11 @@ m.EntityCollection.prototype.move = func
 };
 
 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": undefined }); // hack
 	return this;
 };
 
 m.EntityCollection.prototype.moveIndiv = function(x, z, queued)
 {
@@ -180,11 +180,11 @@ m.EntityCollection.prototype.destroy = f
 	return this;
 };
 
 m.EntityCollection.prototype.attack = function(unitId)
 {
-	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": undefined});
 	return this;
 };
 
 /** violent, aggressive, defensive, passive, standground */
 m.EntityCollection.prototype.setStance = function(stance)
Index: binaries/data/mods/public/simulation/ai/petra/attackPlan.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/attackPlan.js	(revision 18481)
+++ binaries/data/mods/public/simulation/ai/petra/attackPlan.js	(working copy)
@@ -1235,17 +1235,17 @@ m.AttackPlan.prototype.update = function
 				let collec = this.unitCollection.filter(API3.Filters.not(API3.Filters.byClass("Siege"))).filterNearest(ourUnit.position(), 5);
 				for (let ent of collec.values())
 				{
 					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);
 				}
 			}
 			else
 			{
@@ -1258,11 +1258,11 @@ m.AttackPlan.prototype.update = function
 				else if (this.isSiegeUnit(gameState, attacker))
 				{	// if our unit is attacked by a siege unit, we'll send some melee units to help it.
 					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);
 					}
 				}
 				else
 				{	// if units are attacked, abandon their target (if it was a structure or a support) and retaliate
@@ -1277,11 +1277,11 @@ m.AttackPlan.prototype.update = function
 						{
 							if (!target.hasClass("Ranged") || !attacker.hasClass("Melee"))
 								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);
 				}
 			}
 		}
 
@@ -1457,16 +1457,16 @@ m.AttackPlan.prototype.update = function
 						else if (structb.hasClass("ConquestCritical"))
 							valb += 200;
 						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 (!ent.hasClass("Ranged"))
@@ -1520,12 +1520,12 @@ m.AttackPlan.prototype.update = function
 						if (veto[unitB.id()])
 							valb -= 20000;
 						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 (this.isBlocked)
 					ent.attack(this.target.id(), false);
 				else if (API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500 )
 				{
@@ -1568,36 +1568,40 @@ m.AttackPlan.prototype.update = function
 							else if (structb.hasClass("ConquestCritical"))
 								valb += 100;
 							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
 					{
 						let distmin = Math.min();
 						let attackerId;
+						let attacker;
 						this.unitCollection.forEach( function (unit) {
 							if (!unit.position())
 								return;
 							if (unit.unitAIState().split(".")[1] !== "COMBAT" || !unit.unitAIOrderData().length ||
 								!unit.unitAIOrderData()[0].target)
 								return;
 							let dist = API3.SquareVectorDistance(unit.position(), ent.position());
 							if (dist > distmin)
 								return;
+							if (!gameState.getEntityById(unit.unitAIOrderData()[0].target))
+								return;
 							distmin = dist;
 							attackerId = unit.unitAIOrderData()[0].target;
+							attacker = gameState.getEntityById(attackerId);
 						});
 						if (attackerId)
-							ent.attack(attackerId, !this.noCapture.has(attackerId));
+							ent.attack(attackerId, m.getPrefAttackType(ent, attacker, this.noCapture));
 					}
 				}
 			}
 		}
 		this.unitCollUpdateArray.splice(0, lgth);
@@ -1646,11 +1650,11 @@ m.AttackPlan.prototype.UpdateTransportin
 		{
 			if (ent.getMetadata(PlayerID, "transport") !== undefined)
 				continue;
 			if (!ent.isIdle())
 				continue;
-			ent.attack(attacker.id(), !this.noCapture.has(attacker.id()));
+			ent.attack(attacker.id(), !this.noCapture.has(attacker.id(), this.noCapture));
 		}
 		break;
 	}
 };
 
@@ -2004,11 +2008,11 @@ m.AttackPlan.prototype.CheckCapture = fu
 	if (!target)
 		return false;
 
 	if (this.noCapture.has(targetId))
 	{
-		ent.attack(targetId, false);
+		ent.attack(targetId, m.getPrefAttackType(ent, target, this.noCapture));
 		return true;
 	}
 
 	// Do not try to (re)capture an allied decaying structuring
 	if (gameState.isPlayerAlly(target.owner()))
@@ -2016,11 +2020,11 @@ m.AttackPlan.prototype.CheckCapture = fu
 
 	// For the time being, do not try to capture rams
 	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;
 	}
 
 	// TODO need to know how many units are currently capturing this target
 	// For the time being, we check on our full army
@@ -2033,20 +2037,20 @@ m.AttackPlan.prototype.CheckCapture = fu
 	if (target.decaying())
 		antiCapture -= target.territoryDecayRate();
 	if (antiCapture >= this.captureStrength)
 	{
 		this.noCapture.add(targetId);
-		ent.attack(targetId, false);
+		ent.attack(targetId, "primary"); // hack
 		return true;
 	}
 
 	// If the structure has defensive fire, require a minimal army size
 	if (target.hasDefensiveFire() && target.isGarrisonHolder() && target.garrisoned() &&
 		this.unitCollection.length < 2*target.garrisoned().length)
 	{
 		this.noCapture.add(targetId);
-		ent.attack(targetId, false);
+		ent.attack(targetId, "primary"); // hack
 		return true;
 	}
 
 	return true;
 };
Index: binaries/data/mods/public/simulation/ai/petra/defenseArmy.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/defenseArmy.js	(revision 18481)
+++ binaries/data/mods/public/simulation/ai/petra/defenseArmy.js	(working copy)
@@ -75,11 +75,11 @@ m.DefenseArmy.prototype.assignUnit = fun
 	let foeIndex = gameState.ai.accessibility.getAccessValue(foePosition);
 	if (ownIndex == foeIndex || ent.hasClass("Ship"))
 	{
 		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);
 	return true;
 };
@@ -113,12 +113,16 @@ m.DefenseArmy.prototype.update = functio
 		if (!orderData.length && !ent.getMetadata(PlayerID, "transport"))
 			this.assignUnit(gameState, entId);
 		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);
+			}
 		}
 	}
 
 	return this.onUpdate(gameState);
 };
Index: binaries/data/mods/public/simulation/ai/petra/entityExtend.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/entityExtend.js	(revision 18481)
+++ binaries/data/mods/public/simulation/ai/petra/entityExtend.js	(working copy)
@@ -79,15 +79,20 @@ m.getMaxStrength = function(ent, against
 	}
 
 	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 */
 m.returnResources = function(gameState, ent)
 {
Index: binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/Attack.js	(revision 18481)
+++ binaries/data/mods/public/simulation/components/Attack.js	(working copy)
@@ -39,11 +39,13 @@ Attack.prototype.restrictedClassesSchema
 	"</optional>";
 
 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>" +
 			"<MaxRange>4.0</MaxRange>" +
 			"<RepeatTime>1000</RepeatTime>" +
@@ -60,10 +62,11 @@ Attack.prototype.Schema =
 			"</Bonuses>" +
 			"<RestrictedClasses datatype=\"tokens\">Champion</RestrictedClasses>" +
 			"<PreferredClasses datatype=\"tokens\">Cavalry Infantry</PreferredClasses>" +
 		"</Melee>" +
 		"<Ranged>" +
+			"<AttackOrder>secondary</AttackOrder>" +
 			"<Hack>0.0</Hack>" +
 			"<Pierce>10.0</Pierce>" +
 			"<Crush>0.0</Crush>" +
 			"<MaxRange>44.0</MaxRange>" +
 			"<MinRange>20.0</MinRange>" +
@@ -101,11 +104,24 @@ Attack.prototype.Schema =
 			"<Crush>0.0</Crush>" +
 			"<MaxRange>4.0</MaxRange>" +
 		"</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>" +
 				"<element name='Crush' a:help='Crush damage strength'><ref name='nonNegativeDecimal'/></element>" +
 				"<element name='MaxRange' a:help='Maximum attack range (in metres)'><ref name='nonNegativeDecimal'/></element>" +
@@ -118,10 +134,18 @@ Attack.prototype.Schema =
 			"</interleave>" +
 		"</element>" +
 	"</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>" +
 				"<element name='Crush' a:help='Crush damage strength'><ref name='nonNegativeDecimal'/></element>" +
 				"<element name='MaxRange' a:help='Maximum attack range (in metres)'><ref name='nonNegativeDecimal'/></element>" +
@@ -227,35 +251,56 @@ Attack.prototype.GetRestrictedClasses =
 		return this.template[type].RestrictedClasses._string.split(/\s+/);
 
 	return [];
 };
 
-Attack.prototype.CanAttack = function(target)
+Attack.prototype.GetChangeDistance = function()
+{
+	return +(this.template.ChangeDistance || 0);
+};
+
+Attack.prototype.CanAttack = function(target, wantedType)
 {
+	if (wantedType && !this.template[wantedType])
+		return false;
+
 	let cmpFormation = Engine.QueryInterface(target, IID_Formation);
 	if (cmpFormation)
 		return true;
 
 	let cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position);
 	let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
 	if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld())
 		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;
 
+	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());
+
 	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;
 
@@ -315,52 +360,109 @@ Attack.prototype.GetFullAttackRange = fu
 		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)
 	{
 		// TODO: Formation against formation needs review
 		let types = this.GetAttackTypes();
-		return ["Ranged", "Melee", "Capture"].find(attack => types.indexOf(attack) != -1);
+		return ["Ranged", "Melee", "Capture"].find(attack =>
+			types.indexOf(attack) != -1 && (!prefAttackType || "no" + attack != prefAttackType));
 	}
 
 	let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
 	if (!cmpIdentity)
 		return undefined;
 
+	// If we are visisble garrisoned always do ranged attack if we can.
+	let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+	if (cmpPosition && cmpPosition.GetTurretParent() != INVALID_ENTITY)
+		return this.template.Ranged && "Ranged";
+
 	let targetClasses = cmpIdentity.GetClassesList();
 	let isTargetClass = className => targetClasses.indexOf(className) != -1;
 
 	// Always slaughter domestic animals instead of using a normal attack
 	if (isTargetClass("Domestic") && this.template.Slaughter)
 		return "Slaughter";
 
 	let attack = this;
-	let types = this.GetAttackTypes().filter(type => attack.GetRestrictedClasses(type).every(isTargetClass));
+	let types = this.GetAttackTypes().filter(type =>
+		attack.GetRestrictedClasses(type).every(isTargetClass) &&
+		(!prefAttackType || "no" + type != prefAttackType));
 
-	// check if the target is capturable
-	let captureIndex = types.indexOf("Capture");
-	if (captureIndex != -1)
+	if (!types.length)
+		return undefined;
+
+	prefAttackType = this.GetAttackTypeFromOrder(prefAttackType);
+
+	if (prefAttackType && prefAttackType != "Capture" && types.indexOf(prefAttackType) != -1)
+		return prefAttackType;
+
+	if (!prefAttackType || prefAttackType == "Capture")
+	{
+		// 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);
+		}
+	}
+
+	// ignore charges for now: TODO implement these
+	let chargeIndex = types.indexOf("Charge");
+	if (chargeIndex != -1)
+		types.splice(chargeIndex, 1);
+
+	if (!types.length)
+		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)
 	{
-		let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
+		if (this.HasTargetPreferredClass(types, targetClasses))
+		{
+			let isPreferred = className => attack.GetPreferredClasses(className).some(isTargetClass);
+
+			return types.sort((a, b) =>
+				(types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) -
+				(types.indexOf(b) + (isPreferred(b) ? types.length : 0))).pop();
+		}
 
-		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);
+		if (!cmpPosition.IsInWorld())
+			return undefined;
+
+		let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
+		if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
+			return undefined;
+
+		let selfPosition = cmpPosition.GetPosition2D();
+		let targetPosition = cmpTargetPosition.GetPosition2D();
+		if (targetPosition.distanceToSquared(selfPosition) <= Math.pow(this.GetChangeDistance(), 2))
+			return "Melee";
+
+		return "Ranged";
 	}
 
-	let isPreferred = className => attack.GetPreferredClasses(className).some(isTargetClass);
+	if (meleeIndex != -1)
+		return "Melee";
+
+	if (rangedIndex != -1)
+		return "Ranged";
 
-	return types.sort((a, b) =>
-		(types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) -
-		(types.indexOf(b) + (isPreferred(b) ? types.length : 0))).pop();
+	return types[0];
 };
 
 Attack.prototype.CompareEntitiesByPreference = function(a, b)
 {
 	let aPreference = this.GetPreference(a);
@@ -464,20 +566,38 @@ Attack.prototype.GetAttackBonus = functi
 	}
 
 	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(){
 
 	// Use the Box-Muller transform to get a gaussian distribution
 	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];
 };
 
 /**
@@ -489,11 +609,11 @@ Attack.prototype.PerformAttack = functio
 {
 	// If this is a ranged attack, then launch a projectile
 	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
 
 		// Get some data about the entity
@@ -521,11 +641,11 @@ Attack.prototype.PerformAttack = functio
 		// The component of the targets velocity radially away from the archer
 		let radialSpeed = relativePosition.dot(targetVelocity) / relativePosition.length();
 
 		let horizDistance = targetPosition.horizDistanceTo(selfPosition);
 
-		// This is an approximation of the time ot the target, it assumes that the target has a constant radial
+		// 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);
 
@@ -606,11 +726,11 @@ Attack.prototype.PerformAttack = functio
 };
 
 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;
 	let curPos = cmpTargetPosition.GetPosition();
 	let prevPos = cmpTargetPosition.GetPreviousPosition();
@@ -644,11 +764,11 @@ Attack.prototype.testCollision = functio
 		let angle = Engine.QueryInterface(ent, IID_Position).GetRotation().y;
 
 		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);
 	}
 };
 
 Attack.prototype.MissileHit = function(data, lateness)
 {
Index: binaries/data/mods/public/simulation/components/GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/GarrisonHolder.js	(revision 18481)
+++ binaries/data/mods/public/simulation/components/GarrisonHolder.js	(working copy)
@@ -8,10 +8,18 @@ GarrisonHolder.prototype.Schema =
 		"<attribute name='datatype'>" +
 			"<value>tokens</value>" +
 		"</attribute>" +
 		"<text/>" +
 	"</element>" +
+	"<optional>" +
+		"<element name='NeededAttackTypes' a:help='AttackType which are needed to garrison inside this holder (from Identity)'>" +
+			"<attribute name='datatype'>" +
+				"<value>tokens</value>" +
+			"</attribute>" +
+			"<text/>" +
+		"</element>" +
+	"</optional>" +
 	"<element name='EjectHealth' a:help='Percentage of maximum health below which this holder no longer allows garrisoning'>" +
 		"<ref name='nonNegativeDecimal'/>" +
 	"</element>" +
 	"<element name='EjectClassesOnDestroy' a:help='Classes of entities to be ejected on destroy. Others are killed'>" +
 		"<attribute name='datatype'>" +
@@ -117,10 +125,19 @@ GarrisonHolder.prototype.GetAllowedClass
 {
 	return this.template.List._string;
 };
 
 /**
+ * Returns an array of attack types which the unit garrisoned inside this
+ * particualar entity needs to have. Obtained from the entity's template 
+ */
+GarrisonHolder.prototype.GetNeededAttackTypes = function()
+{
+	return this.template.NeededAttackTypes ? this.template.NeededAttackTypes._string : undefined;
+};
+
+/**
  * Get Maximum pop which can be garrisoned
  */
 GarrisonHolder.prototype.GetCapacity = function()
 {
 	return ApplyValueModificationsToEntity("GarrisonHolder/Max", +this.template.Max, this.entity);
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js	(revision 18481)
+++ binaries/data/mods/public/simulation/components/GuiInterface.js	(working copy)
@@ -355,10 +355,11 @@ GuiInterface.prototype.GetEntityState =
 	let cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
 	if (cmpGarrisonHolder)
 		ret.garrisonHolder = {
 			"entities": cmpGarrisonHolder.GetEntities(),
 			"allowedClasses": cmpGarrisonHolder.GetAllowedClasses(),
+			"neededAttackTypes": cmpGarrisonHolder.GetNeededAttackTypes(),
 			"capacity": cmpGarrisonHolder.GetCapacity(),
 			"garrisonedEntitiesCount": cmpGarrisonHolder.GetGarrisonedEntitiesCount()
 		};
 
 	let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
@@ -436,10 +437,11 @@ GuiInterface.prototype.GetExtendedEntity
 	if (cmpAttack)
 	{
 		let types = cmpAttack.GetAttackTypes();
 		if (types.length)
 			ret.attack = {};
+
 		for (let type of types)
 		{
 			ret.attack[type] = cmpAttack.GetAttackStrengths(type);
 			ret.attack[type].splash = cmpAttack.GetSplashDamage(type);
 
@@ -1825,20 +1827,20 @@ GuiInterface.prototype.CanAttack = funct
 {
 	let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
 	if (!cmpAttack)
 		return false;
 
-	let cmpEntityPlayer = QueryOwnerInterface(data.entity, IID_Player);
-	let cmpTargetPlayer = QueryOwnerInterface(data.target, IID_Player);
-	if (!cmpEntityPlayer || !cmpTargetPlayer)
-		return false;
+	return cmpAttack.CanAttack(data.target);
+};
 
-	// 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);
+GuiInterface.prototype.CanAttackWithType = function(player, data)
+{
+	let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
+	if (!cmpAttack)
+		return false;
 
-	return false;
+	return cmpAttack.CanAttack(data.target, data.type);
 };
 
 /*
  * Returns batch build time.
  */
@@ -1981,10 +1983,11 @@ let exposedFunctions = {
 	"HasIdleUnits": 1,
 	"GetTradingRouteGain": 1,
 	"GetTradingDetails": 1,
 	"CanCapture": 1,
 	"CanAttack": 1,
+	"CanAttackWithType": 1,
 	"GetBatchTime": 1,
 
 	"IsMapRevealed": 1,
 	"SetPathfinderDebugOverlay": 1,
 	"SetPathfinderHierDebugOverlay": 1,
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js	(revision 18481)
+++ binaries/data/mods/public/simulation/components/UnitAI.js	(working copy)
@@ -418,11 +418,11 @@ UnitAI.prototype.UnitFsmSpec = {
 			this.FinishOrder();
 			return;
 		}
 
 		// 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
 			this.FinishOrder();
 			return;
@@ -561,11 +561,11 @@ UnitAI.prototype.UnitFsmSpec = {
 	"Order.Gather": function(msg) {
 		// If the target is still alive, we need to kill it first
 		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
 				this.FinishOrder();
 				return;
@@ -585,11 +585,11 @@ UnitAI.prototype.UnitFsmSpec = {
 					this.FinishOrder();
 				}
 				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;
 		}
 
 		// Try to move within range
 		if (this.MoveToTargetRange(this.order.data.target, IID_ResourceGatherer))
@@ -840,13 +840,13 @@ UnitAI.prototype.UnitFsmSpec = {
 				this.CallMemberFunction("Stop", [false]);
 			this.FinishOrder();
 		},
 
 		"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();
 
 			var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
 			// Check if we are already in range, otherwise walk there
@@ -861,11 +861,11 @@ UnitAI.prototype.UnitFsmSpec = {
 					}
 				}
 				this.FinishOrder();
 				return;
 			}
-			this.CallMemberFunction("Attack", [target, false, allowCapture]);
+			this.CallMemberFunction("Attack", [target, false, prefAttackType]);
 			if (cmpAttack.CanAttackAsFormation())
 				this.SetNextState("COMBAT.ATTACKING");
 			else
 				this.SetNextState("MEMBER");
 		},
@@ -916,11 +916,11 @@ UnitAI.prototype.UnitFsmSpec = {
 						this.FinishOrder();
 					}
 					return;
 				}
 
-				this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "allowCapture": false });
+				this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "prefAttackType": undefined });
 				return;
 			}
 
 			// TODO: on what should we base this range?
 			// Check if we are already in range, otherwise walk there
@@ -1153,30 +1153,30 @@ UnitAI.prototype.UnitFsmSpec = {
 					cmpFormation.MoveMembersIntoFormation(true, true);
 				},
 
 				"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
 						this.SetNextState("MEMBER");
 				},
 			},
 
 			"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))
 					{
 						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();
 						return true;
 					}
@@ -1188,19 +1188,19 @@ UnitAI.prototype.UnitFsmSpec = {
 					this.StartTimer(200, 200);
 					return false;
 				},
 
 				"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))
 					{
 						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();
 						return;
 					}
@@ -1408,11 +1408,11 @@ UnitAI.prototype.UnitFsmSpec = {
 				return;
 			}
 
 			// 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);
 				if (!cmpPosition || !cmpPosition.IsInWorld())
 					return;
@@ -1803,10 +1803,12 @@ UnitAI.prototype.UnitFsmSpec = {
 					    !this.CheckTargetAttackRange(target, this.order.data.attackType))
 					{
 						// 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");
 								return;
 							}
@@ -1914,10 +1916,16 @@ UnitAI.prototype.UnitFsmSpec = {
 						}
 
 						// 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");
 								return;
 							}
@@ -4547,16 +4555,16 @@ UnitAI.prototype.CheckTargetIsInVisionRa
 	var distance = DistanceBetweenEntities(this.entity, target);
 
 	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)
 {
 	var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
@@ -4574,11 +4582,11 @@ UnitAI.prototype.AttackVisibleEntity = f
 {
 	var target = ents.find(target => this.CanAttack(target, forceResponse));
 	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;
 };
 
 /**
  * Try to find one of the given entities which can be attacked
@@ -4587,17 +4595,17 @@ UnitAI.prototype.AttackVisibleEntity = f
  */
 UnitAI.prototype.AttackEntityInZone = function(ents, forceResponse)
 {
 	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;
 };
 
 /**
  * Try to respond appropriately given our current stance,
@@ -4984,13 +4992,13 @@ UnitAI.prototype.WalkToTarget = function
 /**
  * Adds walk-and-fight order to queue, this only occurs in response
  * 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);
 };
 
 /**
  * Adds leave foundation order to queue, treated as forced.
  */
@@ -5007,11 +5015,11 @@ UnitAI.prototype.LeaveFoundation = funct
 };
 
 /**
  * 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))
 	{
 		// We don't want to let healers walk to the target unit so they can be easily killed.
 		// Instead we just let them get into healing range.
@@ -5019,11 +5027,11 @@ UnitAI.prototype.Attack = function(targe
 			this.MoveToTargetRange(target, IID_Heal);
 		else
 			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);
 };
 
 /**
  * Adds garrison order to the queue, forced by the player.
  */
@@ -5448,11 +5456,11 @@ UnitAI.prototype.FindWalkAndFightTargets
 						continue;
 					// Only used by the AIs to prevent some choices of targets
 					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;
 	}
@@ -5474,11 +5482,11 @@ UnitAI.prototype.FindWalkAndFightTargets
 				continue;
 			// Only used by the AIs to prevent some choices of targets
 			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;
 	}
 
 	// healers on a walk-and-fight order should heal injured units
 	if (this.IsHealer())
@@ -5674,25 +5682,34 @@ UnitAI.prototype.CanGarrison = function(
 	// Formation controllers should always respond to commands
 	// (then the individual units can make up their own minds)
 	if (this.IsFormationController())
 		return true;
 
-	var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);
+	let cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);
 	if (!cmpGarrisonHolder)
 		return false;
 
 	// Verify that the target is owned by this entity's player or a mutual ally of this player
-	var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+	let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
 	if (!cmpOwnership || !(IsOwnedByPlayer(cmpOwnership.GetOwner(), target) || IsOwnedByMutualAllyOfPlayer(cmpOwnership.GetOwner(), target)))
 		return false;
 
 	// Don't let animals garrison for now
 	// (If we want to support that, we'll need to change Order.Garrison so it
 	// doesn't move the animal into an INVIDIDUAL.* state)
 	if (this.IsAnimal())
 		return false;
 
+	let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity)
+	if (!cmpIdentity)
+		return false;
+
+	let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack)
+	let attackTypes = cmpAttack ? cmpAttack.GetAttackTypes() : [];
+	if (!MatchesClassList(cmpIdentity.GetClassesList(), cmpGarrisonHolder.GetAllowedClasses()) || 
+	    !HasNeededAttackTypes(attackTypes, cmpGarrisonHolder.GetNeededAttackTypes()))
+		return false;
 	return true;
 };
 
 UnitAI.prototype.CanGather = function(target)
 {
Index: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js	(revision 18481)
+++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js	(working copy)
@@ -95,11 +95,11 @@ function TestFormationExiting(mode)
 	});
 
 	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; },
 		CompareEntitiesByPreference: function(a, b) { return 0; },
 	});
@@ -245,11 +245,11 @@ function TestMoveIntoFormationWhileAttac
 		});
 
 		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 18481)
+++ binaries/data/mods/public/simulation/helpers/Commands.js	(working copy)
@@ -158,23 +158,22 @@ var g_Commands = {
 	},
 
 	"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);
 		});
 	},
 
 	"attack": function(player, cmd, data)
 	{
 		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);
 		});
 	},
 
 	"heal": function(player, cmd, data)
 	{
Index: binaries/data/mods/public/simulation/templates/template_structure_defense_wall_long.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_defense_wall_long.xml	(revision 18481)
+++ binaries/data/mods/public/simulation/templates/template_structure_defense_wall_long.xml	(working copy)
@@ -10,11 +10,12 @@
   <Health>
     <SpawnEntityOnDeath>rubble/rubble_stone_wall_long</SpawnEntityOnDeath>
   </Health>
   <GarrisonHolder>
     <Max>5</Max>
-    <List datatype="tokens">Ranged+Infantry</List>
+    <List datatype="tokens">Infantry</List>
+    <NeededAttackTypes datatype="tokens">Ranged</NeededAttackTypes>
     <EjectHealth>0.1</EjectHealth>
     <EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
     <BuffHeal>0</BuffHeal>
     <LoadingRange>2</LoadingRange>
     <VisibleGarrisonPoints>
Index: binaries/data/mods/public/simulation/templates/template_structure_defense_wall_medium.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_defense_wall_medium.xml	(revision 18481)
+++ binaries/data/mods/public/simulation/templates/template_structure_defense_wall_medium.xml	(working copy)
@@ -7,11 +7,12 @@
       <stone>22</stone>
     </Resources>
   </Cost>
   <GarrisonHolder>
     <Max>3</Max>
-    <List datatype="tokens">Ranged+Infantry</List>
+    <List datatype="tokens">Infantry</List>
+    <NeededAttackTypes datatype="tokens">Ranged</NeededAttackTypes>
     <EjectHealth>0.1</EjectHealth>
     <EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
     <BuffHeal>0</BuffHeal>
     <LoadingRange>2</LoadingRange>
     <VisibleGarrisonPoints>
Index: binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml	(revision 18481)
+++ binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml	(working copy)
@@ -1,7 +1,26 @@
 <?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>
     <SpecificName>Anusiya</SpecificName>
     <Classes datatype="tokens">Immortal</Classes>
