Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg (revision 18044)
+++ 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 18044)
+++ binaries/data/mods/public/art/actors/units/persians/champion_unit_1.xml (working copy)
@@ -14,6 +14,7 @@
+
@@ -28,8 +29,6 @@
-
-
@@ -39,5 +38,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
player_trans.xml
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 18044)
+++ 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 18044)
+++ 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 18044)
+++ 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" };
+ 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-second-attack-move";
+ }
if (targetState.garrisonHolder && playerCheck(entState, targetState, ["Player", "MutualAlly"]))
{
Index: binaries/data/mods/public/simulation/ai/common-api/entity.js
===================================================================
--- binaries/data/mods/public/simulation/ai/common-api/entity.js (revision 18044)
+++ binaries/data/mods/public/simulation/ai/common-api/entity.js (working copy)
@@ -792,8 +792,8 @@
return this;
},
- attack: function(unitId, allowCapture = true, queued = false) {
- Engine.PostCommand(PlayerID,{"type": "attack", "entities": [this.id()], "target": unitId, "allowCapture": allowCapture, "queued": queued});
+ attack: function(unitId, prefType = "Capture", queued = false) {
+ Engine.PostCommand(PlayerID,{"type": "attack", "entities": [this.id()], "target": unitId, "prefType": prefType, "queued": queued});
return this;
},
Index: binaries/data/mods/public/simulation/ai/petra/defenseArmy.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/defenseArmy.js (revision 18044)
+++ binaries/data/mods/public/simulation/ai/petra/defenseArmy.js (working copy)
@@ -77,7 +77,7 @@
{
this.assignedTo[entID] = idFoe;
this.assignedAgainst[idFoe].push(entID);
- ent.attack(idFoe, m.allowCapture(ent, foeEnt), queued);
+ ent.attack(idFoe, m.prefType(ent, foeEnt), queued);
}
else
gameState.ai.HQ.navalManager.requireTransport(gameState, ent, ownIndex, foeIndex, foePosition);
@@ -116,7 +116,7 @@
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))
+ if (target && !m.prefType(ent, target))
ent.attack(orderData[0].target, false);
}
}
Index: binaries/data/mods/public/simulation/ai/petra/entityExtend.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/entityExtend.js (revision 18044)
+++ binaries/data/mods/public/simulation/ai/petra/entityExtend.js (working copy)
@@ -82,10 +82,11 @@
};
// Decide if we should try to capture or destroy
-m.allowCapture = function(ent, target)
+// TODO make this function less hacky
+m.prefType = function(ent, target)
{
return !target.hasClass("Siege") || !ent.hasClass("Melee") ||
- !target.isGarrisonHolder() || !target.garrisoned().length;
+ !target.isGarrisonHolder() || !target.garrisoned().length ? "Capture": "primary";
};
// Makes the worker deposit the currently carried resources at the closest accessible dropsite
Index: binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/Attack.js (revision 18044)
+++ binaries/data/mods/public/simulation/components/Attack.js (working copy)
@@ -1,6 +1,6 @@
function Attack() {}
-Attack.prototype.bonusesSchema =
+Attack.prototype.bonusesSchema =
"" +
"" +
"" +
@@ -41,7 +41,9 @@
Attack.prototype.Schema =
"Controls the attack abilities and strengths of the unit." +
"" +
+ "20" +
"" +
+ "primary" +
"10.0" +
"0.0" +
"5.0" +
@@ -62,6 +64,7 @@
"Cavalry Infantry" +
"" +
"" +
+ "secondary" +
"0.0" +
"10.0" +
"0.0" +
@@ -103,7 +106,20 @@
"" +
"" +
"" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
"" +
+ "" +
+ "" +
+ "" +
+ "primary" +
+ "secondary" +
+ "" +
+ "" +
+ "" +
"" +
"" +
"" +
@@ -120,6 +136,14 @@
"" +
"" +
"" +
+ "" +
+ "" +
+ "" +
+ "primary" +
+ "secondary" +
+ "" +
+ "" +
+ "" +
"" +
"" +
"" +
@@ -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,11 +273,19 @@
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 cmpEntityPlayer = QueryOwnerInterface(this.entity);
+ let cmpTargetPlayer = QueryOwnerInterface(target);
+ if (!cmpTargetPlayer || !cmpEntityPlayer)
+ return false;
+
+ if (targetClasses.indexOf("Domestic") == -1 && !cmpEntityPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID()))
+ return false;
+
for (let type of this.GetAttackTypes())
{
if (type == "Capture" && !QueryMiragedInterface(target, IID_Capturable))
@@ -288,6 +316,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 +329,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 +362,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 +383,7 @@
}
let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
- if (!cmpIdentity)
+ if (!cmpIdentity)
return undefined;
let targetClasses = cmpIdentity.GetClassesList();
@@ -357,7 +390,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 +398,55 @@
let types = this.GetAttackTypes().filter(isAllowed);
- // check if the target is capturable
- let captureIndex = types.indexOf("Capture");
- if (captureIndex != -1)
+ if (prefType && prefType != "Capture" && types.indexOf(prefType) != -1)
+ return prefType;
+ else if (!prefType || prefType == "Capture")
{
- let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
+ // check if the target is capturable
+ let captureIndex = types.indexOf("Capture");
+ if (captureIndex != -1)
+ {
+ let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
+ let cmpPlayer = QueryOwnerInterface(this.entity);
+ if (cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID()))
+ return "Capture";
+ // not captureable, so remove this attack
+ types.splice(captureIndex, 1);
+ }
+ }
- let cmpPlayer = QueryOwnerInterface(this.entity);
- if (allowCapture && 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);
+
+ // 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"
- 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();
};
Attack.prototype.CompareEntitiesByPreference = function(a, b)
@@ -478,6 +543,25 @@
return attackBonus;
};
+Attack.prototype.GetAttackTypeFromOrder = function(prefType)
+{
+ let types = this.GetAttackTypes();
+ for (let type of types)
+ if ((this.template[type].AttackOrder && this.template[type].AttackOrder == prefType) || prefType == type)
+ return type;
+
+ if (types.indexOf("Melee") != -1 && types.indexOf("Ranged") != -1)
+ return undefined; // we have a problem, should never happen
+ return types.indexOf("Melee") != -1 ? "Melee": "Ranged";
+};
+
+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 +570,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 +587,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 +619,9 @@
let horizDistance = targetPosition.horizDistanceTo(selfPosition);
- // This is an approximation of the time ot the target, it assumes that the target has a constant radial
- // velocity, but since units move in straight lines this is not true. The exact value would be more
- // difficult to calculate and I think this is sufficiently accurate. (I tested and for cavalry it was
+ // This is an approximation of the time to the target, it assumes that the target has a constant radial
+ // velocity, but since units move in straight lines this is not true. The exact value would be more
+ // difficult to calculate and I think this is sufficiently accurate. (I tested and for cavalry it was
// about 5% of the units radius out in the worst case)
let timeToTarget = horizDistance / (horizSpeed - radialSpeed);
@@ -620,7 +704,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 +742,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 +824,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 18044)
+++ binaries/data/mods/public/simulation/components/GuiInterface.js (working copy)
@@ -1748,16 +1748,25 @@
if (!cmpAttack)
return false;
- let cmpEntityPlayer = QueryOwnerInterface(data.entity, IID_Player);
- let cmpTargetPlayer = QueryOwnerInterface(data.target, IID_Player);
- if (!cmpEntityPlayer || !cmpTargetPlayer)
+ return cmpAttack.CanAttack(data.target);
+};
+
+GuiInterface.prototype.CanSecondAttack = function(player, data)
+{
+ let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
+ if (!cmpAttack)
return false;
- // if the owner is an enemy, it's up to the attack component to decide
- if (cmpEntityPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID()))
- return cmpAttack.CanAttack(data.target);
+ return cmpAttack.CanSecondAttack(data.target);
+};
- return false;
+GuiInterface.prototype.GetAttackGetAttackTypeFromOrder = function(player, data)
+{
+ let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
+ if (!cmpAttack)
+ return undefined;
+
+ return cmpAttack.GetAttackGetAttackTypeFromOrder(data.attackOrder);
};
/*
@@ -1903,6 +1912,8 @@
"GetTradingDetails": 1,
"CanCapture": 1,
"CanAttack": 1,
+ "CanSecondAttack": 1,
+ "GetAttackGetAttackTypeFromOrder": 1,
"GetBatchTime": 1,
"IsMapRevealed": 1,
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js (revision 18044)
+++ 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 18044)
+++ binaries/data/mods/public/simulation/helpers/Commands.js (working copy)
@@ -157,9 +157,13 @@
"attack-walk": function(player, cmd, data)
{
- GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
- cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.queued);
- });
+ for (let ent of data.entities)
+ {
+ let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
+ let cmpAttack = Engine.QueryInterface(ent, IID_Attack);
+ if (cmpUnitAI && cmpAttack)
+ cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.queued, cmpAttack.GetAttackTypeFromOrder(cmd.prefType));
+ }
},
"attack": function(player, cmd, data)
@@ -167,11 +171,14 @@
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);
- });
+ for (let ent of data.entities)
+ {
+ let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
+ let cmpAttack = Engine.QueryInterface(ent, IID_Attack);
+ if (cmpUnitAI && cmpAttack)
+ cmpUnitAI.Attack(cmd.target, cmd.queued, cmpAttack.GetAttackTypeFromOrder(cmd.prefType));
+ }
},
"heal": function(player, cmd, data)
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 18044)
+++ binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_spearman.xml (working copy)
@@ -5,7 +5,9 @@
3
+ 20
+ primary
6.0
5.0
0.0
@@ -18,6 +20,18 @@
+
+ secondary
+ 0
+ 6.0
+ 0
+ 72.0
+ 0.0
+ 120.0
+ 1000
+ 1000
+ 2.0
+
15.0
40.0