Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg (revision 17749)
+++ binaries/data/config/default.cfg (working copy)
@@ -272,11 +272,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 17749)
+++ binaries/data/mods/public/art/actors/units/persians/champion_unit_1.xml (working copy)
@@ -14,6 +14,7 @@
+
@@ -32,6 +33,7 @@
+
@@ -38,6 +40,13 @@
+
+
+
+
+
+
+
player_trans.xml
Index: binaries/data/mods/public/gui/session/input.js
===================================================================
--- binaries/data/mods/public/gui/session/input.js (revision 17749)
+++ 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-attack-move"; //TODO another cursor
+ }
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 17749)
+++ binaries/data/mods/public/gui/session/unit_actions.js (working copy)
@@ -87,6 +87,39 @@
"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": "second-attack-walk", "entities": selection, "x": target.x, "z": target.z, "targetClasses": targetClasses, "queued": queued});
+ 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-attack-move"}; // TODO another cursor
+ return false;
+ },
+ "specificness": 32,
+ },
+
"capture":
{
"execute": function(target, action, selection, queued)
@@ -139,6 +172,35 @@
"specificness": 10,
},
+ "second-attack":
+ {
+ "execute": function(target, action, selection, queued)
+ {
+ Engine.PostNetworkCommand({"type": "second-attack", "entities": selection, "target": action.target, "queued": queued, "allowCapture": false});
+ Engine.GuiInterfaceCall("PlaySound", { "name": "order_secondattack", "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-attack", "target": target}; // TODO another cursor
+ return false;
+ },
+ "actionCheck": function(target)
+ {
+ if (getActionInfo("second-attack", target).possible)
+ return {"type": "second-attack", "cursor": "action-attack", "target": target}; // TODO another cursor
+ return false;
+ },
+ "specificness": 15,
+ },
+
"heal":
{
"execute": function(target, action, selection, queued)
@@ -499,6 +561,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", "Ally"]))
{
Index: binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/Attack.js (revision 17749)
+++ binaries/data/mods/public/simulation/components/Attack.js (working copy)
@@ -42,6 +42,7 @@
"Controls the attack abilities and strengths of the unit." +
"" +
"" +
+ "primary" +
"10.0" +
"0.0" +
"5.0" +
@@ -62,6 +63,7 @@
"Cavalry Infantry" +
"" +
"" +
+ "secondary" +
"0.0" +
"10.0" +
"0.0" +
@@ -104,6 +106,9 @@
"" +
"" +
"" +
+ "" +
+ "" +
+ "" +
"" +
"" +
"" +
@@ -120,6 +125,9 @@
"" +
"" +
"" +
+ "" +
+ "" +
+ "" +
"" +
"" +
"" +
@@ -218,21 +226,15 @@
Attack.prototype.GetPreferredClasses = function(type)
{
- if (this.template[type] && this.template[type].PreferredClasses &&
- this.template[type].PreferredClasses._string)
- {
+ if (this.template[type] && this.template[type].PreferredClasses && this.template[type].PreferredClasses._string)
return this.template[type].PreferredClasses._string.split(/\s+/);
- }
return [];
};
Attack.prototype.GetRestrictedClasses = function(type)
{
- if (this.template[type] && this.template[type].RestrictedClasses &&
- this.template[type].RestrictedClasses._string)
- {
+ if (this.template[type] && this.template[type].RestrictedClasses && this.template[type].RestrictedClasses._string)
return this.template[type].RestrictedClasses._string.split(/\s+/);
- }
return [];
};
@@ -288,6 +290,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.
*/
@@ -327,15 +336,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, allowCapture, prefType)
{
let cmpFormation = Engine.QueryInterface(target, IID_Formation);
if (cmpFormation)
@@ -365,6 +372,15 @@
let types = this.GetAttackTypes().filter(isAllowed);
+ if (prefType)
+ {
+ if (types[prefType])
+ return preftype
+ prefType = this.GetPrefAttack(types, prefType);
+ if (types.indexOf(prefType))
+ return prefType;
+ }
+
// check if the target is capturable
let captureIndex = types.indexOf("Capture");
if (captureIndex != -1)
@@ -378,10 +394,33 @@
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) ); };
+ // 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];
- return types.sort(byPreference).pop();
+ 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 <= 2 * this.GetRange("Melee").max)
+ return "Melee";
+ return "Ranged"
+
};
Attack.prototype.CompareEntitiesByPreference = function(a, b)
@@ -478,6 +517,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 types[0];
+};
+
+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 +541,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 +558,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
@@ -620,7 +675,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 +713,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 +795,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 17749)
+++ binaries/data/mods/public/simulation/components/GuiInterface.js (working copy)
@@ -1726,6 +1726,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.
*/
@@ -1869,6 +1878,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 17749)
+++ 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.allowCapture, 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, "allowCapture": false, "prefType": undefined });
return;
}
@@ -842,9 +842,10 @@
},
"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 allowCapture = msg.data.allowCapture;
+ let prefType = msg.data.prefType;
+ let cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI);
if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember())
target = cmpTargetUnitAI.GetFormationController();
@@ -863,7 +864,7 @@
this.FinishOrder();
return;
}
- this.CallMemberFunction("Attack", [target, false, allowCapture]);
+ this.CallMemberFunction("Attack", [target, false, allowCapture, prefType]);
if (cmpAttack.CanAttackAsFormation())
this.SetNextState("COMBAT.ATTACKING");
else
@@ -918,7 +919,7 @@
return;
}
- this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "allowCapture": false });
+ this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "allowCapture": false, "prefType": undefined });
return;
}
@@ -1155,7 +1156,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.allowCapture, this.order.data.prefType]);
if (cmpAttack.CanAttackAsFormation())
this.SetNextState("COMBAT.ATTACKING");
else
@@ -1166,8 +1167,9 @@
"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 allowCapture = this.order.data.allowCapture;
+ let prefType = this.order.data.prefType;
// Check if we are already in range, otherwise walk there
if (!this.CheckTargetAttackRange(target, target))
{
@@ -1174,7 +1176,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, "allowCapture": allowCapture, "prefType": prefType });
return true;
}
this.FinishOrder();
@@ -1190,8 +1192,9 @@
},
"Timer": function(msg) {
- var target = this.order.data.target;
- var allowCapture = this.order.data.allowCapture;
+ let target = this.order.data.target;
+ let allowCapture = this.order.data.allowCapture;
+ let prefType = this.order.data.prefType;
// Check if we are already in range, otherwise walk there
if (!this.CheckTargetAttackRange(target, target))
{
@@ -1198,7 +1201,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, "allowCapture": allowCapture, "prefType": prefType });
return;
}
this.FinishOrder();
@@ -1410,7 +1413,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, "allowCapture": true, "prefType": undefined });
else
{
var cmpPosition = Engine.QueryInterface(msg.data.attacker, IID_Position);
@@ -4523,12 +4526,12 @@
return distance < range;
};
-UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture)
+UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture, prefType)
{
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
if (!cmpAttack)
return undefined;
- return cmpAttack.GetBestAttackAgainst(target, allowCapture);
+ return cmpAttack.GetBestAttackAgainst(target, allowCapture, prefType);
};
UnitAI.prototype.GetAttackBonus = function(type, target)
@@ -4550,7 +4553,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, "allowCapture": true, "prefType": undefined });
return true;
};
@@ -4563,13 +4566,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, "allowCapture": true, "prefType": undefined });
return true;
};
@@ -4989,7 +4992,7 @@
/**
* Adds attack order to the queue, forced by the player.
*/
-UnitAI.prototype.Attack = function(target, queued, allowCapture)
+UnitAI.prototype.Attack = function(target, queued, allowCapture, prefType)
{
if (!this.CanAttack(target))
{
@@ -5001,7 +5004,7 @@
this.WalkToTarget(target, queued);
return;
}
- this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture}, queued);
+ this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture, "prefType": prefType }, queued);
};
/**
@@ -5411,7 +5414,7 @@
if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
continue;
}
- this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });
+ this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true, "prefType": undefined });
return true;
}
}
@@ -5437,7 +5440,7 @@
if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
continue;
}
- this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });
+ this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": 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 17749)
+++ binaries/data/mods/public/simulation/helpers/Commands.js (working copy)
@@ -141,6 +141,13 @@
});
},
+ "second-attack-walk": function(player, cmd, data)
+ {
+ GetFormationUnitAIs(data.entities, player).forEach(function(cmpUnitAI) {
+ cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.queued);
+ });
+ },
+
"attack": function(player, cmd, data)
{
if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target)))
@@ -149,13 +156,32 @@
warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd));
}
+ let prefType = "primary"
let allowCapture = cmd.allowCapture || cmd.allowCapture == null;
+ if (allowCapture)
+ prefType = "Capture";
// See UnitAI.CanAttack for target checks
GetFormationUnitAIs(data.entities, player).forEach(function(cmpUnitAI) {
- cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture);
+ cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture, prefType);
});
},
+ "second-attack": function(player, cmd, data)
+ {
+ if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target)))
+ {
+ // This check is for debugging only!
+ warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd));
+ }
+
+ let prefType = "secondary"
+ let allowCapture = cmd.allowCapture || cmd.allowCapture == null;
+ // See UnitAI.CanAttack for target checks
+ GetFormationUnitAIs(data.entities, player).forEach(function(cmpUnitAI) {
+ cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture, prefType);
+ });
+ },
+
"heal": function(player, cmd, data)
{
if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByAllyOfPlayer(player, cmd.target)))
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 17749)
+++ binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_spearman.xml (working copy)
@@ -6,6 +6,7 @@
+ primary
6.0
5.0
0.0
@@ -18,6 +19,18 @@
+
+ secondary
+ 0
+ 6.0
+ 0
+ 72.0
+ 0.0
+ 120.0
+ 1000
+ 1000
+ 2.0
+
15.0
40.0