Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg	(revision 18037)
+++ binaries/data/config/default.cfg	(working copy)
@@ -274,11 +274,14 @@
 [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
+secondattack = Alt           ; Modifier to secondary attack instead of another action
+secondattackmove = Alt       ; Modifier to secondary attackmove when clicking on a point
+secondattackmoveUnit = "Alt+Q"; Modifier to  secondary 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 18037)
+++ binaries/data/mods/public/art/actors/units/persians/champion_unit_1.xml	(working copy)
@@ -14,6 +14,7 @@
         <animation event="0.5" file="infantry/sword/attack/isw_s_def_06.psa" name="attack_melee" speed="80"/>
         <animation event="0.5" file="infantry/sword/attack/isw_s_def_06.psa" name="attack_melee" speed="80"/>
         <animation event="0.5" file="infantry/sword/attack/isw_s_off_05.psa" name="attack_melee" speed="80"/>
+        <animation event="0.84" file="biped/inf_arch_atk_a.psa" load="0.16" name="attack_ranged" speed="90"/>
         <animation file="infantry/sword/move/run/isw_s_off_01.psa" name="Run" speed="25"/>
         <animation file="infantry/sword/move/run/isw_s_def_02.psa" name="Run" speed="30"/>
         <animation file="infantry/sword/move/run/isw_s_em_03.psa" name="Run" speed="30"/>
@@ -28,8 +29,6 @@
       <props>
         <prop actor="props/units/heads/head_pers_tiara.xml" attachpoint="head"/>
         <prop actor="props/units/heads/pers_kidaris_tied.xml" attachpoint="helmet"/>
-        <prop actor="props/units/shields/gerron_b.xml" attachpoint="shield"/>
-        <prop actor="props/units/weapons/spear_ball.xml" attachpoint="r_hand"/>
         <prop actor="props/units/pers_quiver_back.xml" attachpoint="back"/>
       </props>
     </variant>
@@ -39,5 +38,20 @@
       <textures><texture file="skeletal/pers_su1_anusiya_iron.dds" name="baseTex"/></textures>
     </variant>
   </group>
+  <group>
+    <variant name="attack_melee">
+      <props>
+        <prop actor="props/units/shields/gerron_b.xml" attachpoint="shield"/>
+        <prop actor="props/units/weapons/spear_ball.xml" attachpoint="r_hand"/>
+      </props>
+    </variant>
+    <variant name="attack_ranged">
+      <props>
+        <prop actor="props/units/weapons/bow_recurve.xml" attachpoint="l_hand"/>
+        <prop actor="props/units/weapons/arrow_back.xml" attachpoint="loaded-r_hand"/>
+        <prop actor="props/units/weapons/arrow_front.xml" attachpoint="projectile"/>
+      </props>
+    </variant>
+  </group>
   <material>player_trans.xml</material>
 </actor>
Index: binaries/data/mods/public/art/textures/cursors/action-second-attack-move.txt
===================================================================
--- binaries/data/mods/public/art/textures/cursors/action-second-attack-move.txt	(nonexistent)
+++ binaries/data/mods/public/art/textures/cursors/action-second-attack-move.txt	(working copy)
@@ -0,0 +1 @@
+1 1
Index: binaries/data/mods/public/art/textures/cursors/action-second-attack.txt
===================================================================
--- binaries/data/mods/public/art/textures/cursors/action-second-attack.txt	(nonexistent)
+++ binaries/data/mods/public/art/textures/cursors/action-second-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 18037)
+++ 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 18037)
+++ binaries/data/mods/public/gui/session/input.js	(working copy)
@@ -205,6 +205,13 @@
 				data.targetClasses = Engine.HotkeyIsPressed("session.attackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] };
 				cursor = "action-attack-move";
 			}
+
+			if (Engine.HotkeyIsPressed("session.secondattackmove"))
+			{
+				data.command = "attack-walk";
+				data.targetClasses = Engine.HotkeyIsPressed("session.secondattackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] };
+				cursor = "action-second-attack-move";
+			}
 			return { "possible": true, "data": data, "cursor": cursor };
 		}
 
Index: binaries/data/mods/public/gui/session/unit_actions.js
===================================================================
--- binaries/data/mods/public/gui/session/unit_actions.js	(revision 18037)
+++ binaries/data/mods/public/gui/session/unit_actions.js	(working copy)
@@ -65,7 +65,7 @@
 			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, "prefType": "primary" });
 			Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
 			return true;
 		},
@@ -81,17 +81,50 @@
 				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,
 	},
 
+	"second-attack-move": // TODO is this one needed?
+	{
+		"execute": function(target, action, selection, queued)
+		{
+			if (Engine.HotkeyIsPressed("session.secondattackmoveUnit"))
+				var targetClasses = { "secondattack": ["Unit"] };
+			else
+				var targetClasses = { "secondattack": ["Unit", "Structure"] };
+
+			Engine.PostNetworkCommand({ "type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "targetClasses": targetClasses, "queued": queued, "prefType": "secondary" });
+			Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
+			return true;
+		},
+		"getActionInfo": function(entState, targetState)
+		{
+			if (!entState.attack || !targetState.hitpoints)
+				return false;
+			return { "possible": Engine.GuiInterfaceCall("CanSecondAttack", { "entity": entState.id, "target": targetState.id }) };
+		},
+		"hotkeyActionCheck": function(target, selection)
+		{
+			// Work out whether at least part of the selection have UnitAI
+			var haveUnitAI = selection.some(function(ent) {
+				var entState = GetEntityState(ent);
+				return entState && entState.unitAI;
+			});
+			if (haveUnitAI && Engine.HotkeyIsPressed("session.secondattackmove") && getActionInfo("second-attack-move", target).possible)
+				return { "type": "second-attack-move", "cursor": "action-second-attack-move" }; // TODO another cursor
+			return false;
+		},
+		"specificness": 32,
+	},
+
 	"capture": 
 	{
 		"execute": function(target, action, selection, queued)
 		{
-			Engine.PostNetworkCommand({"type": "attack", "entities": selection, "target": action.target, "allowCapture": true, "queued": queued});
+			Engine.PostNetworkCommand({ "type": "attack", "entities": selection, "target": action.target, "prefType": "Capture", "queued": queued });
 			Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
 			return true;
 		},
@@ -99,12 +132,12 @@
 		{
 			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,
@@ -114,7 +147,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, "prefType": "primary" });
 			Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
 			return true;
 		},
@@ -122,28 +155,57 @@
 		{
 			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": 10,
 	},
 
+	"second-attack":
+	{
+		"execute": function(target, action, selection, queued)
+		{
+			Engine.PostNetworkCommand({ "type": "attack", "entities": selection, "target": action.target, "queued": queued, "prefType": "secondary" });
+			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("CanSecondAttack", {"entity": entState.id, "target": targetState.id }) };
+		},
+		"hotkeyActionCheck": function(target)
+		{
+			if (Engine.HotkeyIsPressed("session.secondattack") && getActionInfo("second-attack", target).possible)
+				return { "type": "second-attack", "cursor": "action-second-attack", "target": target };
+			return false;
+		},
+		"actionCheck": function(target)
+		{
+			if (getActionInfo("second-attack", target).possible)
+				return { "type": "second-attack", "cursor": "action-second-attack", "target": target };
+			return false;
+		},
+		"specificness": 15,
+	},
+
 	"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;
 		},
@@ -506,6 +568,16 @@
 				data.targetClasses = targetClasses;
 				cursor = "action-attack-move";
 			}
+			if (Engine.HotkeyIsPressed("session.secondattackmove"))
+			{
+				if (Engine.HotkeyIsPressed("session.secondattackmoveUnit"))
+					var targetClasses = { "second-attack": ["Unit"] };
+				else
+					var targetClasses = { "second-attack": ["Unit", "Structure"] };
+				data.command = "second-attack-walk";
+				data.targetClasses = targetClasses;
+				cursor = "action-attack-move"; //TODO another cursor
+			}
 
 			if (targetState.garrisonHolder && playerCheck(entState, targetState, ["Player", "MutualAlly"]))
 			{
Index: binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/Attack.js	(revision 18037)
+++ 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>" +
@@ -219,10 +243,8 @@
 Attack.prototype.GetPreferredClasses = function(type)
 {
 	if (this.template[type] && this.template[type].PreferredClasses &&
-	    this.template[type].PreferredClasses._string)
-	{
+		this.template[type].PreferredClasses._string)
 		return this.template[type].PreferredClasses._string.split(/\s+/);
-	}
 	return [];
 };
 
@@ -229,10 +251,8 @@
 Attack.prototype.GetRestrictedClasses = function(type)
 {
 	if (this.template[type] && this.template[type].RestrictedClasses &&
-	    this.template[type].RestrictedClasses._string)
-	{
+		this.template[type].RestrictedClasses._string)
 		return this.template[type].RestrictedClasses._string.split(/\s+/);
-	}
 	return [];
 };
 
@@ -253,17 +273,22 @@
 	let heightDiff = Math.abs(cmpThisPosition.GetHeightOffset() - cmpTargetPosition.GetHeightOffset());
 
 	const cmpIdentity = Engine.QueryInterface(target, IID_Identity);
-	if (!cmpIdentity) 
+	if (!cmpIdentity)
 		return undefined;
 
 	const targetClasses = cmpIdentity.GetClassesList();
 
+	let cmpPlayer = QueryOwnerInterface(this.entity);
+	let cmpTargetPlayer =QueryOwnerInterface(target);
+
 	for (let type of this.GetAttackTypes())
 	{
 		if (type == "Capture" && !QueryMiragedInterface(target, IID_Capturable))
 			continue;
 
-		if (type == "Slaughter" && targetClasses.indexOf("Domestic") == -1)
+		if (type != "Slaughter" && cmpPlayer == cmpTargetPlayer)
+
+		if (type == "Slaughter", targetClasses.indexOf("Domestic") == -1)
 			continue;
 
 		if (heightDiff > this.GetRange(type).max)
@@ -288,6 +313,13 @@
 	return false;
 };
 
+Attack.prototype.CanSecondAttack = function(target)
+{
+	for (let type of this.GetAttackTypes())
+		if (this.template[type].AttackOrder && this.template[type].AttackOrder == "secondary")
+			return this.CanAttack(target);
+	return false;
+};
 /**
  * Returns null if we have no preference or the lowest index of a preferred class.
  */
@@ -294,7 +326,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 +359,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, prefType)
 {
 	let cmpFormation = Engine.QueryInterface(target, IID_Formation);
 	if (cmpFormation)
@@ -350,7 +380,7 @@
 	}
 
 	let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
-	if (!cmpIdentity) 
+	if (!cmpIdentity)
 		return undefined;
 
 	let targetClasses = cmpIdentity.GetClassesList();
@@ -357,7 +387,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;
@@ -365,23 +395,62 @@
 
 	let types = this.GetAttackTypes().filter(isAllowed);
 
-	// check if the target is capturable
-	let captureIndex = types.indexOf("Capture");
-	if (captureIndex != -1)
+	if (prefType)
 	{
-		let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
+		if (types.indexOf(prefType) != -1)
+			return prefType
+		prefType = this.GetPrefAttack(types, prefType);
+		if (prefType && types.indexOf(prefType) != -1)
+			return prefType;
+	}
+	else
+	{
+		// 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 (allowCapture && cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID()))
-			return "Capture";
-		// not captureable, so remove this attack
-		types.splice(captureIndex, 1);
+			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();
+	// only ranged and/or melee attack left
+	// if one attacktype left choose this one
+	if (types.indexOf("Melee") == -1 || types.indexOf("Ranged") == -1)
+		return types[0];
+
+	if (this.HasPreferredClasses(types))
+	{
+		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();
+	}
+		// assume ranged and melee attack
+		// TODO stop assuming that?
+	let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+	if (!cmpPosition || !cmpPosition.IsInWorld())
+		return undefined;
+	let selfPosition = cmpPosition.GetPosition();
+	let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
+	if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
+		return undefined;
+	let targetPosition = cmpTargetPosition.GetPosition();
+	let horizDistance = targetPosition.horizDistanceTo(selfPosition);
+	if (horizDistance <= this.template.ChangeDistance)
+		return "Melee";
+	return "Ranged"
+
 };
 
 Attack.prototype.CompareEntitiesByPreference = function(a, b)
@@ -478,6 +547,22 @@
 	return attackBonus;
 };
 
+// Returns preferred attack type if exists
+Attack.prototype.GetPrefAttack = function(types, pref)
+{
+	for (let type of types)
+		if (this.template[type].AttackOrder && pref == this.template[type].AttackOrder)
+			return type;
+	return undefined;
+};
+
+Attack.prototype.HasPreferredClasses = function(types)
+{
+	for (let type of types)
+		if (this.template[type].PreferredClasses)
+			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 +571,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 +588,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 +620,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 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
 		// about 5% of the units radius out in the worst case)
 		let timeToTarget = horizDistance / (horizSpeed - radialSpeed);
 
@@ -620,7 +705,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 +743,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 +825,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 18037)
+++ binaries/data/mods/public/simulation/components/GuiInterface.js	(working copy)
@@ -1760,6 +1760,15 @@
 	return false;
 };
 
+GuiInterface.prototype.CanSecondAttack = function(player, data)
+{
+	let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
+	if (!cmpAttack)
+		return false;
+
+	return cmpAttack.CanSecondAttack(data.target)
+};
+
 /*
  * Returns batch build time.
  */
@@ -1903,6 +1912,7 @@
 	"GetTradingDetails": 1,
 	"CanCapture": 1,
 	"CanAttack": 1,
+	"CanSecondAttack": 1,
 	"GetBatchTime": 1,
 
 	"IsMapRevealed": 1,
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js	(revision 18037)
+++ 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.prefType);
 		if (!type)
 		{
 			// Oops, we can't attack at all
@@ -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, "prefType": 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 prefType = msg.data.prefType;
+			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, prefType]);
 			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, "prefType": 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.prefType]);
 					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 prefType = this.order.data.prefType;
 					// 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, "prefType": prefType });
 							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 prefType = this.order.data.prefType;
 					// 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, "prefType": prefType });
 							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, "prefType": undefined });
 			else
 			{
 				var cmpPosition = Engine.QueryInterface(msg.data.attacker, IID_Position);
@@ -4535,12 +4535,12 @@
 	return distance < range;
 };
 
-UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture)
+UnitAI.prototype.GetBestAttackAgainst = function(target, prefType)
 {
 	var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
 	if (!cmpAttack)
 		return undefined;
-	return cmpAttack.GetBestAttackAgainst(target, allowCapture);
+	return cmpAttack.GetBestAttackAgainst(target, prefType);
 };
 
 UnitAI.prototype.GetAttackBonus = function(type, target)
@@ -4562,7 +4562,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, "prefType": undefined });
 	return true;
 };
 
@@ -4575,13 +4575,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, true, undefined))
 		&& (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, "prefType": undefined });
 	return true;
 };
 
@@ -4978,9 +4978,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, prefType)
 {
-	this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "force": true }, queued);
+	this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "force": true, "prefType": prefType }, queued);
 };
 
 /**
@@ -5001,7 +5001,7 @@
 /**
  * Adds attack order to the queue, forced by the player.
  */
-UnitAI.prototype.Attack = function(target, queued, allowCapture)
+UnitAI.prototype.Attack = function(target, queued, prefType)
 {
 	if (!this.CanAttack(target))
 	{
@@ -5013,7 +5013,7 @@
 			this.WalkToTarget(target, queued);
 		return;
 	}
-	this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture}, queued);
+	this.AddOrder("Attack", { "target": target, "force": true, "prefType": prefType }, queued);
 };
 
 /**
@@ -5430,7 +5430,7 @@
 					if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
 						continue;
 				}
-				this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });
+				this.PushOrderFront("Attack", { "target": targ, "force": true, "prefType": undefined });
 				return true;
 			}
 		}
@@ -5456,7 +5456,7 @@
 			if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
 				continue;
 		}
-		this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });
+		this.PushOrderFront("Attack", { "target": targ, "force": true, "prefType": undefined });
 		return true;
 	}
 	return false;
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js	(revision 18037)
+++ 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.prefType);
 		});
 	},
 
@@ -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.prefType);
 		});
 	},
 
Index: binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_spearman.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_spearman.xml	(revision 18037)
+++ binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_spearman.xml	(working copy)
@@ -5,7 +5,9 @@
     <Pierce op="add">3</Pierce>
   </Armour>
   <Attack>
+    <ChangeDistance>20</ChangeDistance>
     <Melee>
+      <AttackOrder>primary</AttackOrder>
       <Hack>6.0</Hack>
       <Pierce>5.0</Pierce>
       <Crush>0.0</Crush>
@@ -18,6 +20,18 @@
           </BonusCavMelee>
       </Bonuses>
     </Melee>
+    <Ranged>
+      <AttackOrder>secondary</AttackOrder>
+      <Hack>0</Hack>
+      <Pierce>6.0</Pierce>
+      <Crush>0</Crush>
+      <MaxRange>72.0</MaxRange>
+      <MinRange>0.0</MinRange>
+      <ProjectileSpeed>120.0</ProjectileSpeed>
+      <PrepareTime>1000</PrepareTime>
+      <RepeatTime>1000</RepeatTime>
+      <Spread>2.0</Spread>
+    </Ranged>
     <Charge>
       <Hack>15.0</Hack>
       <Pierce>40.0</Pierce>
