Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg (revision 18101)
+++ binaries/data/config/default.cfg (working copy)
@@ -274,11 +274,17 @@
[hotkey.session]
kill = Delete ; Destroy selected units
stop = "H" ; Stop the current action
-attack = Ctrl ; Modifier to attack instead of another action (eg capture)
-attackmove = Ctrl ; Modifier to attackmove when clicking on a point
-attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point (should contain the attackmove keys)
+attack = Ctrl ; Modifier to primary attack instead of another action (eg capture)
+attackmove = Ctrl ; Modifier to primary attackmove when clicking on a point
+attackmoveUnit = "Ctrl+Q" ; Modifier to primary attackmove targeting only units when clicking on a point (should contain the attackmove keys)
garrison = Ctrl ; Modifier to garrison when clicking on building
autorallypoint = Ctrl ; Modifier to set the rally point on the building itself
+meleeattack = Alt ; Modifier to melee attack instead of another action
+meleeattackmove = Alt ; Modifier to melee attackmove when clicking on a point
+meleeattackmoveUnit = "Alt+Q"; Modifier to melee attackmove targeting only units when clicking on a point
+rangedattack = space ; Modifier to ranged attack instead of another action
+rangedattackmove = space ; Modifier to ranged attackmove when clicking on a point
+rangedattackmoveUnit = "space+Q"; Modifier to ranged attackmove targeting only units when clicking on a point
guard = "G" ; Modifier to escort/guard when clicking on unit/building
queue = Shift ; Modifier to queue unit orders instead of replacing
batchtrain = Shift ; Modifier to train units in batches
Index: binaries/data/mods/public/art/actors/units/persians/champion_unit_1.xml
===================================================================
--- binaries/data/mods/public/art/actors/units/persians/champion_unit_1.xml (revision 18101)
+++ binaries/data/mods/public/art/actors/units/persians/champion_unit_1.xml (working copy)
@@ -42,6 +42,12 @@
+
+
+
+
+
+
player_trans.xml
Index: binaries/data/mods/public/art/textures/cursors/action-melee-attack.txt
===================================================================
--- binaries/data/mods/public/art/textures/cursors/action-melee-attack.txt (nonexistent)
+++ binaries/data/mods/public/art/textures/cursors/action-melee-attack.txt (working copy)
@@ -0,0 +1 @@
+1 1
Index: binaries/data/mods/public/art/textures/cursors/action-ranged-attack.txt
===================================================================
--- binaries/data/mods/public/art/textures/cursors/action-ranged-attack.txt (nonexistent)
+++ binaries/data/mods/public/art/textures/cursors/action-ranged-attack.txt (working copy)
@@ -0,0 +1 @@
+1 1
Index: binaries/data/mods/public/gui/common/tooltips.js
===================================================================
--- binaries/data/mods/public/gui/common/tooltips.js (revision 18101)
+++ 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 18101)
+++ binaries/data/mods/public/gui/session/input.js (working copy)
@@ -205,6 +205,21 @@
data.targetClasses = Engine.HotkeyIsPressed("session.attackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] };
cursor = "action-attack-move";
}
+
+ if (Engine.HotkeyIsPressed("session.meleeattackmove"))
+ {
+ data.command = "attack-walk";
+ data.targetClasses = Engine.HotkeyIsPressed("session.meleeattackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] };
+ cursor = "action-melee-attack-move";
+ }
+
+ if (Engine.HotkeyIsPressed("session.rangedattackmove"))
+ {
+ data.command = "attack-walk";
+ data.targetClasses = Engine.HotkeyIsPressed("session.rangedattackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] };
+ cursor = "action-ranged-attack-move";
+ }
+
return { "possible": true, "data": data, "cursor": cursor };
}
@@ -219,9 +234,9 @@
// Check if the target entity is a resource, dropsite, foundation, or enemy unit.
// Check if any entities in the selection can gather the requested resource,
// can return to the dropsite, can build the foundation, or can attack the enemy
- for each (var entityID in selection)
+ for (let entityID of selection)
{
- var entState = GetExtendedEntityState(entityID);
+ let entState = GetExtendedEntityState(entityID);
if (!entState)
continue;
@@ -228,7 +243,7 @@
if (unitActions[action] && unitActions[action].getActionInfo)
{
var r = unitActions[action].getActionInfo(entState, targetState, simState);
- if (r) // return true if it's possible for one of the entities
+ if (r && r.possible) // return true if it's possible for one of the entities
return r;
}
}
Index: binaries/data/mods/public/gui/session/unit_actions.js
===================================================================
--- binaries/data/mods/public/gui/session/unit_actions.js (revision 18101)
+++ binaries/data/mods/public/gui/session/unit_actions.js (working copy)
@@ -1,12 +1,12 @@
/**
- * List of different actions units can execute,
+ * List of different actions units can execute,
* this is mostly used to determine which actions can be executed
*
* "execute" is meant to send the command to the engine
*
- * The next functions will always return false
+ * The next functions will always return false
* in case you have to continue to seek
- * (i.e. look at the next entity for getActionInfo, the next
+ * (i.e. look at the next entity for getActionInfo, the next
* possible action for the actionCheck ...)
* They will return an object when the searching is finished
*
@@ -24,10 +24,10 @@
*
* "specificness" is used to determine how specific an action is,
* The lower the number, the more specific an action is, and the bigger
- * the chance of selecting that action when multiple actions are possible
+ * the chance of selecting that action when multiple actions are possible
*/
-var unitActions =
+var unitActions =
{
"move":
{
@@ -53,10 +53,10 @@
return {"type": "move"};
return false;
},
- "specificness": 12,
+ "specificness": 13,
},
- "attack-move":
+ "attack-move":
{
"execute": function(target, action, selection, queued)
{
@@ -65,13 +65,13 @@
else
var targetClasses = { "attack": ["Unit", "Structure"] };
- Engine.PostNetworkCommand({"type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "targetClasses": targetClasses, "queued": queued});
+ Engine.PostNetworkCommand({ "type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "targetClasses": targetClasses, "queued": queued, "prefAttackType": "primary" });
Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
return true;
},
"getActionInfo": function(entState, targetState)
{
- return {"possible": true};
+ return { "possible": true };
},
"hotkeyActionCheck": function(target, selection)
{
@@ -81,17 +81,81 @@
return entState && entState.unitAI;
});
if (haveUnitAI && Engine.HotkeyIsPressed("session.attackmove") && getActionInfo("attack-move", target).possible)
- return {"type": "attack-move", "cursor": "action-attack-move"};
+ return { "type": "attack-move", "cursor": "action-attack-move" };
return false;
},
"specificness": 30,
},
- "capture":
+ "melee-attack-move":
{
"execute": function(target, action, selection, queued)
{
- Engine.PostNetworkCommand({"type": "attack", "entities": selection, "target": action.target, "allowCapture": true, "queued": queued});
+ if (Engine.HotkeyIsPressed("session.meleeattackmoveUnit"))
+ var targetClasses = { "meleeattack": ["Unit"] };
+ else
+ var targetClasses = { "meleeattack": ["Unit", "Structure"] };
+
+ Engine.PostNetworkCommand({ "type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "targetClasses": targetClasses, "queued": queued, "prefAttackType": "Melee" });
+ Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
+ return true;
+ },
+ "getActionInfo": function(entState, targetState)
+ {
+ return { "possible": true };
+ },
+ "hotkeyActionCheck": function(target, selection)
+ {
+ // Work out whether at least part of the selection have UnitAI
+ let haveUnitAI = selection.some(function(ent) {
+ let entState = GetEntityState(ent);
+ return entState && entState.unitAI && getActionInfo("melee-attack-move", target).possible;
+ });
+
+ if (haveUnitAI && Engine.HotkeyIsPressed("session.meleeattackmove") && getActionInfo("melee-attack-move", target).possible)
+ return { "type": "melee-attack-move", "cursor": "action-melee-attack" };
+ return false;
+ },
+ "specificness": 31,
+ },
+
+ "ranged-attack-move":
+ {
+ "execute": function(target, action, selection, queued)
+ {
+ if (Engine.HotkeyIsPressed("session.rangedattackmoveUnit"))
+ var targetClasses = { "rangedattack": ["Unit"] };
+ else
+ var targetClasses = { "rangedattack": ["Unit", "Structure"] };
+
+ Engine.PostNetworkCommand({ "type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "targetClasses": targetClasses, "queued": queued, "prefAttackType": "Ranged" });
+ Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
+ return true;
+ },
+ "getActionInfo": function(entState, targetState)
+ {
+ return { "possible": true };
+ },
+ "hotkeyActionCheck": function(target, selection)
+ {
+ // Work out whether at least part of the selection have UnitAI
+ let haveUnitAI = selection.some(function(ent) {
+ let entState = GetEntityState(ent);
+ return entState && entState.unitAI && getActionInfo("ranged-attack-move", target).possible;
+ });
+
+ if (haveUnitAI && Engine.HotkeyIsPressed("session.rangedattackmove") && getActionInfo("ranged-attack-move", target).possible)
+ return { "type": "ranged-attack-move", "cursor": "action-ranged-attack" };
+ return false;
+ },
+ "specificness": 32,
+ },
+
+ "capture":
+ {
+ "execute": function(target, action, selection, queued)
+ {
+ Engine.PostNetworkCommand({ "type": "attack", "entities": selection, "target": action.target, "queued": queued, "prefAttackType": "Capture" });
Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
return true;
},
@@ -99,15 +163,15 @@
{
if (!entState.attack || !targetState.hitpoints)
return false;
- return {"possible": Engine.GuiInterfaceCall("CanCapture", {"entity": entState.id, "target": targetState.id})};
+ return { "possible": Engine.GuiInterfaceCall("CanCapture", { "entity": entState.id, "target": targetState.id }) };
},
"actionCheck": function(target)
{
if (getActionInfo("capture", target).possible)
- return {"type": "capture", "cursor": "action-capture", "target": target};
+ return { "type": "capture", "cursor": "action-capture", "target": target };
return false;
},
- "specificness": 9,
+ "specificness": 7,
},
"attack":
@@ -114,7 +178,7 @@
{
"execute": function(target, action, selection, queued)
{
- Engine.PostNetworkCommand({"type": "attack", "entities": selection, "target": action.target, "queued": queued, "allowCapture": false});
+ Engine.PostNetworkCommand({ "type": "attack", "entities": selection, "target": action.target, "queued": queued, "prefAttackType": "primary" });
Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
return true;
},
@@ -122,28 +186,86 @@
{
if (!entState.attack || !targetState.hitpoints)
return false;
- return {"possible": Engine.GuiInterfaceCall("CanAttack", {"entity": entState.id, "target": targetState.id})};
+ return { "possible": Engine.GuiInterfaceCall("CanAttack", { "entity": entState.id, "target": targetState.id }) };
},
"hotkeyActionCheck": function(target)
{
if (Engine.HotkeyIsPressed("session.attack") && getActionInfo("attack", target).possible)
- return {"type": "attack", "cursor": "action-attack", "target": target};
+ return { "type": "attack", "cursor": "action-attack", "target": target };
return false;
},
"actionCheck": function(target)
{
if (getActionInfo("attack", target).possible)
- return {"type": "attack", "cursor": "action-attack", "target": target};
+ return { "type": "attack", "cursor": "action-attack", "target": target };
return false;
},
+ "specificness": 8,
+ },
+
+ "melee-attack":
+ {
+ "execute": function(target, action, selection, queued)
+ {
+ Engine.PostNetworkCommand({ "type": "attack", "entities": selection, "target": action.target, "queued": queued, "prefAttackType": "Melee" });
+ Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
+ return true;
+ },
+ "getActionInfo": function(entState, targetState)
+ {
+ if (!entState.attack || !targetState.hitpoints) // hack
+ return false;
+ return { "possible": Engine.GuiInterfaceCall("CanAttackWithType", { "entity": entState.id, "target": targetState.id, "type": "Melee" }) };
+ },
+ "hotkeyActionCheck": function(target)
+ {
+ if (Engine.HotkeyIsPressed("session.meleeattack") && getActionInfo("melee-attack", target).possible)
+ return { "type": "melee-attack", "cursor": "action-melee-attack", "target": target };
+ return false;
+ },
+ "actionCheck": function(target)
+ {
+ if (getActionInfo("melee-attack", target).possible)
+ return { "type": "melee-attack", "cursor": "action-melee-attack", "target": target };
+ return false;
+ },
+ "specificness": 9,
+ },
+
+ "ranged-attack":
+ {
+ "execute": function(target, action, selection, queued)
+ {
+ Engine.PostNetworkCommand({ "type": "attack", "entities": selection, "target": action.target, "queued": queued, "prefAttackType": "Ranged" });
+ Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
+ return true;
+ },
+ "getActionInfo": function(entState, targetState)
+ {
+ if (!entState.attack || !targetState.hitpoints) // hack
+ return false;
+ return { "possible": Engine.GuiInterfaceCall("CanAttackWithType", { "entity": entState.id, "target": targetState.id, "type": "Ranged" }) };
+ },
+ "hotkeyActionCheck": function(target, selection)
+ {
+ if (Engine.HotkeyIsPressed("session.rangedattack") && getActionInfo("ranged-attack", target).possible)
+ return { "type": "ranged-attack", "cursor": "action-ranged-attack", "target": target };
+ return false;
+ },
+ "actionCheck": function(target, selection)
+ {
+ if (getActionInfo("ranged-attack", target).possible)
+ return { "type": "ranged-attack", "cursor": "action-ranged-attack", "target": target };
+ return false;
+ },
"specificness": 10,
},
- "heal":
+ "heal":
{
"execute": function(target, action, selection, queued)
{
- Engine.PostNetworkCommand({"type": "heal", "entities": selection, "target": action.target, "queued": queued});
+ Engine.PostNetworkCommand({ "type": "heal", "entities": selection, "target": action.target, "queued": queued });
Engine.GuiInterfaceCall("PlaySound", { "name": "order_heal", "entity": selection[0] });
return true;
},
@@ -176,10 +298,10 @@
return {"type": "heal", "cursor": "action-heal", "target": target};
return false;
},
- "specificness": 7,
+ "specificness": 6,
},
- "build":
+ "build":
{
"execute": function(target, action, selection, queued)
{
@@ -202,7 +324,7 @@
"specificness": 3,
},
- "repair":
+ "repair":
{
"execute": function(target, action, selection, queued)
{
@@ -230,10 +352,10 @@
return {"type": "build", "cursor": "action-repair", "target": target};
return false;
},
- "specificness": 11,
+ "specificness": 12,
},
- "gather":
+ "gather":
{
"execute": function(target, action, selection, queued)
{
@@ -260,7 +382,7 @@
"specificness": 1,
},
- "returnresource":
+ "returnresource":
{
"execute": function(target, action, selection, queued)
{
@@ -297,7 +419,7 @@
"specificness": 2,
},
- "setup-trade-route":
+ "setup-trade-route":
{
"execute": function(target, action, selection, queued)
{
@@ -362,7 +484,7 @@
"specificness": 0,
},
- "garrison":
+ "garrison":
{
"execute": function(target, action, selection, queued)
{
@@ -409,7 +531,7 @@
"specificness": 20,
},
- "guard":
+ "guard":
{
"execute": function(target, action, selection, queued)
{
@@ -446,7 +568,7 @@
"specificness": 40,
},
- "remove-guard":
+ "remove-guard":
{
"execute": function(target, action, selection, queued)
{
@@ -471,11 +593,11 @@
"specificness": 41,
},
- "set-rallypoint":
+ "set-rallypoint":
{
"execute": function(target, action, selection, queued)
{
- // if there is a position set in the action then use this so that when setting a
+ // if there is a position set in the action then use this so that when setting a
// rally point on an entity it is centered on that entity
if (action.position)
target = action.position;
@@ -507,6 +629,28 @@
cursor = "action-attack-move";
}
+ if (Engine.HotkeyIsPressed("session.meleeattackmove"))
+ {
+ if (Engine.HotkeyIsPressed("session.meleeattackmoveUnit"))
+ var targetClasses = { "melee-attack": ["Unit"] };
+ else
+ var targetClasses = { "melee-attack": ["Unit", "Structure"] };
+ data.command = "melee-attack-walk";
+ data.targetClasses = targetClasses;
+ cursor = "action-melee-attack";
+ }
+
+ if (Engine.HotkeyIsPressed("session.rangedattackmove"))
+ {
+ if (Engine.HotkeyIsPressed("session.rangedattackmoveUnit"))
+ var targetClasses = { "ranged-attack": ["Unit"] };
+ else
+ var targetClasses = { "ranged-attack": ["Unit", "Structure"] };
+ data.command = "ranged-attack-walk";
+ data.targetClasses = targetClasses;
+ cursor = "action-ranged-attack";
+ }
+
if (targetState.garrisonHolder && playerCheck(entState, targetState, ["Player", "MutualAlly"]))
{
data.command = "garrison";
@@ -610,10 +754,10 @@
return false;
return {"type": "set-rallypoint", "cursor": actionInfo.cursor, "data": actionInfo.data, "tooltip": actionInfo.tooltip, "position": actionInfo.position};
},
- "specificness": 6,
+ "specificness": 5,
},
- "unset-rallypoint":
+ "unset-rallypoint":
{
"execute": function(target, action, selection, queued)
{
@@ -654,7 +798,7 @@
"specificness": 11,
},
- "none":
+ "none":
{
"execute": function(target, action, selection, queued)
{
@@ -668,7 +812,7 @@
* Info and actions for the entity commands
* Currently displayed in the bottom of the central panel
*/
-var g_EntityCommands =
+var g_EntityCommands =
{
// Unload
"unload-all": {
Index: binaries/data/mods/public/simulation/ai/common-api/entity.js
===================================================================
--- binaries/data/mods/public/simulation/ai/common-api/entity.js (revision 18101)
+++ binaries/data/mods/public/simulation/ai/common-api/entity.js (working copy)
@@ -743,6 +743,7 @@
return false;
},
+ isCapturable: function() { return this.get("Capturable") !== undefined; },
isGarrisonHolder: function() { return this.get("GarrisonHolder") !== undefined; },
garrisoned: function() { return this._entity.garrisoned; },
canGarrisonInside: function() { return this._entity.garrisoned.length < this.garrisonMax(); },
@@ -753,12 +754,12 @@
},
moveToRange: function(x, z, min, max, queued = false) {
- Engine.PostCommand(PlayerID,{"type": "walk-to-range", "entities": [this.id()], "x": x, "z": z, "min": min, "max": max, "queued": queued });
+ Engine.PostCommand(PlayerID,{ "type": "walk-to-range", "entities": [this.id()], "x": x, "z": z, "min": min, "max": max, "queued": queued });
return this;
},
- attackMove: function(x, z, targetClasses, queued = false) {
- Engine.PostCommand(PlayerID,{"type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "targetClasses": targetClasses, "queued": queued });
+ attackMove: function(x, z, targetClasses, queued = false, prefAttackType = "primary") { // hack
+ Engine.PostCommand(PlayerID,{"type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "targetClasses": targetClasses, "queued": queued, "prefAttackType": prefAttackType });
return this;
},
@@ -792,8 +793,8 @@
return this;
},
- attack: function(unitId, allowCapture = true, queued = false) {
- Engine.PostCommand(PlayerID,{"type": "attack", "entities": [this.id()], "target": unitId, "allowCapture": allowCapture, "queued": queued});
+ attack: function(unitId, prefAttackType, queued = false) {
+ Engine.PostCommand(PlayerID,{"type": "attack", "entities": [this.id()], "target": unitId, "prefAttackType": prefAttackType, "queued": queued});
return this;
},
Index: binaries/data/mods/public/simulation/ai/common-api/entitycollection.js
===================================================================
--- binaries/data/mods/public/simulation/ai/common-api/entitycollection.js (revision 18101)
+++ binaries/data/mods/public/simulation/ai/common-api/entitycollection.js (working copy)
@@ -148,7 +148,7 @@
m.EntityCollection.prototype.attackMove = function(x, z, targetClasses, queued)
{
queued = queued || false;
- Engine.PostCommand(PlayerID,{"type": "attack-walk", "entities": this.toIdArray(), "x": x, "z": z, "targetClasses": targetClasses, "queued": queued});
+ Engine.PostCommand(PlayerID, { "type": "attack-walk", "entities": this.toIdArray(), "x": x, "z": z, "targetClasses": targetClasses, "queued": queued, "prefAttackType": "primary" }); // hack
return this;
};
@@ -175,7 +175,7 @@
m.EntityCollection.prototype.attack = function(unit)
{
var unitId = unit;
- Engine.PostCommand(PlayerID,{"type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false});
+ Engine.PostCommand(PlayerID, { "type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false, "prefAttackType": "Capture" });
return this;
};
Index: binaries/data/mods/public/simulation/ai/petra/attackPlan.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/attackPlan.js (revision 18101)
+++ binaries/data/mods/public/simulation/ai/petra/attackPlan.js (working copy)
@@ -1078,7 +1078,7 @@
continue;
if (!ent.isIdle())
continue;
- ent.attack(attacker.id(), !this.noCapture.has(attacker.id()));
+ ent.attack(attacker.id(), m.getPrefAttackType(ent, attacker, this.noCapture));
}
break;
}
@@ -1314,13 +1314,13 @@
{
if (this.isSiegeUnit(gameState, ent)) // needed as mauryan elephants are not filtered out
continue;
- ent.attack(attacker.id(), !this.noCapture.has(attacker.id()));
+ ent.attack(attacker.id(), m.getPrefAttackType(ent, attacker, this.noCapture));
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
// And if this attacker is a non-ranged siege unit and our unit also, attack it
if (this.isSiegeUnit(gameState, attacker) && attacker.hasClass("Melee") && ourUnit.hasClass("Melee"))
{
- ourUnit.attack(attacker.id(), false);
+ ourUnit.attack(attacker.id(), "Melee");
ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
}
@@ -1331,7 +1331,7 @@
let collec = this.unitCollection.filter(API3.Filters.byClass("Melee")).filterNearest(ourUnit.position(), 5);
for (let ent of collec.values())
{
- ent.attack(attacker.id(), false);
+ ent.attack(attacker.id(), m.getPrefAttackType(ent, attacker, this.noCapture));
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
}
@@ -1350,7 +1350,7 @@
continue;
}
}
- ourUnit.attack(attacker.id(), !this.noCapture.has(attacker.id()));
+ ourUnit.attack(attacker.id(), m.getPrefAttackType(ourUnit, attacker, this.noCapture));
ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
}
@@ -1521,12 +1521,12 @@
return (valb - vala);
});
if (mStruct[0].hasClass("Gates"))
- ent.attack(mStruct[0].id(), false);
+ ent.attack(mStruct[0].id(), m.getPrefAttackType(ent, mStruct[0], this.noCapture));
else
{
let rand = Math.floor(Math.random() * mStruct.length * 0.2);
- let newTargetId = mStruct[rand].id();
- ent.attack(newTargetId, !this.noCapture.has(newTargetId));
+ let newTarget = mStruct[rand];
+ ent.attack(newTarget.id(), m.getPrefAttackType(ent, newTarget, this.noCapture));
}
}
else
@@ -1584,8 +1584,8 @@
return valb - vala;
});
let rand = Math.floor(Math.random() * mUnit.length * 0.1);
- let newTargetId = mUnit[rand].id();
- ent.attack(newTargetId, !this.noCapture.has(newTargetId));
+ let newTarget = mUnit[rand];
+ ent.attack(newTarget.id(), m.getPrefAttackType(ent, newTarget, this.noCapture));
}
else if (API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500 )
{
@@ -1628,12 +1628,12 @@
return (valb - vala);
});
if (mStruct[0].hasClass("Gates"))
- ent.attack(mStruct[0].id(), false);
+ ent.attack(mStruct[0].id(), m.getPrefAttackType(ent, mStruct[0], this.noCapture));
else
{
let rand = Math.floor(Math.random() * mStruct.length * 0.2);
- let newTargetId = mStruct[rand].id();
- ent.attack(newTargetId, !this.noCapture.has(newTargetId));
+ let newTarget = mStruct[rand];
+ ent.attack(newTarget.id(), m.getPrefAttackType(ent, newTarget, this.noCapture));
}
}
else if (needsUpdate) // really nothing let's try to help our nearest unit
@@ -1654,7 +1654,7 @@
});
if (attackerId)
- ent.attack(attackerId, !this.noCapture.has(attackerId));
+ ent.attack(attackerId, m.getPrefAttackType(ent, attackerId, this.noCapture));
}
}
}
@@ -1828,7 +1828,7 @@
if (this.noCapture.has(targetId))
{
- ent.attack(targetId, false);
+ ent.attack(targetId, m.getPrefAttackType(ent, target, this.noCapture));
return true;
}
@@ -1840,7 +1840,7 @@
if (target.hasClass("Siege") && target.hasClass("Melee"))
{
this.noCapture.add(targetId);
- ent.attack(targetId, false);
+ ent.attack(targetId, m.getPrefAttackType(ent, target, this.noCapture));
return true;
}
@@ -1857,7 +1857,7 @@
if (antiCapture >= this.captureStrength)
{
this.noCapture.add(targetId);
- ent.attack(targetId, false);
+ ent.attack(targetId, "primary"); // hack
return true;
}
@@ -1866,7 +1866,7 @@
this.unitCollection.length < 2*target.garrisoned().length)
{
this.noCapture.add(targetId);
- ent.attack(targetId, false);
+ ent.attack(targetId, "primary"); // hack
return true;
}
Index: binaries/data/mods/public/simulation/ai/petra/defenseArmy.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/defenseArmy.js (revision 18101)
+++ binaries/data/mods/public/simulation/ai/petra/defenseArmy.js (working copy)
@@ -77,7 +77,7 @@
{
this.assignedTo[entID] = idFoe;
this.assignedAgainst[idFoe].push(entID);
- ent.attack(idFoe, m.allowCapture(ent, foeEnt), queued);
+ ent.attack(idFoe, m.getPrefAttackType(ent, foeEnt), queued);
}
else
gameState.ai.HQ.navalManager.requireTransport(gameState, ent, ownIndex, foeIndex, foePosition);
@@ -116,8 +116,8 @@
else if (orderData.length && orderData[0].target && orderData[0].attackType && orderData[0].attackType === "Capture")
{
let target = gameState.getEntityById(orderData[0].target);
- if (target && !m.allowCapture(ent, target))
- ent.attack(orderData[0].target, false);
+ if (target)
+ ent.attack(orderData[0].target, m.getPrefAttackType(ent, target));
}
}
Index: binaries/data/mods/public/simulation/ai/petra/entityExtend.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/entityExtend.js (revision 18101)
+++ binaries/data/mods/public/simulation/ai/petra/entityExtend.js (working copy)
@@ -81,11 +81,15 @@
return strength * ent.maxHitpoints() / 100.0;
};
-// Decide if we should try to capture or destroy
-m.allowCapture = function(ent, target)
+// Decide if we should try to capture, melee or range attack
+// TODO make this function less hacky
+m.getPrefAttackType = function(ent, target, noCapture)
{
- return !target.hasClass("Siege") || !ent.hasClass("Melee") ||
- !target.isGarrisonHolder() || !target.garrisoned().length;
+ if (target.hasClass("Siege") || (noCapture && noCapture.has(target)))
+ return "Melee"; // Don't capture for now
+ if (target.isCapturable() && (!target.isGarrisonHolder() || !target.garrisoned().length))
+ return "Capture";
+ return "primary";
};
// Makes the worker deposit the currently carried resources at the closest accessible dropsite
Index: binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/Attack.js (revision 18101)
+++ 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,13 +251,16 @@
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 [];
};
+Attack.prototype.GetChangeDistance = function()
+{
+ return +this.template.ChangeDistance;
+};
+
Attack.prototype.CanAttack = function(target)
{
let cmpFormation = Engine.QueryInterface(target, IID_Formation);
@@ -253,14 +278,24 @@
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;
+
+ let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
+ if (targetClasses.indexOf("Domestic") == -1 && !cmpEntityPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID()) &&
+ (!cmpCapturable || !cmpCapturable.CanCapture(cmpEntityPlayer.GetPlayerID())))
+ return false;
+
for (let type of this.GetAttackTypes())
{
- if (type == "Capture" && !QueryMiragedInterface(target, IID_Capturable))
+ if (type == "Capture" && cmpCapturable)
continue;
if (type == "Slaughter" && targetClasses.indexOf("Domestic") == -1)
@@ -288,6 +323,13 @@
return false;
};
+Attack.prototype.CanAttackWithType = function(target, type)
+{
+ if (this.template[type])
+ return this.CanAttack(target);
+ return false;
+};
+
/**
* Returns null if we have no preference or the lowest index of a preferred class.
*/
@@ -294,7 +336,7 @@
Attack.prototype.GetPreference = function(target)
{
const cmpIdentity = Engine.QueryInterface(target, IID_Identity);
- if (!cmpIdentity)
+ if (!cmpIdentity)
return undefined;
const targetClasses = cmpIdentity.GetClassesList();
@@ -327,15 +369,13 @@
if (type == "Slaughter")
continue;
let range = this.GetRange(type);
- if (range.min < ret.min)
- ret.min = range.min;
- if (range.max > ret.max)
- ret.max = range.max;
+ ret.min = Math.min(ret.min, range.min);
+ ret.max = Math.max(ret.max, range.max);
}
return ret;
};
-Attack.prototype.GetBestAttackAgainst = function(target, allowCapture)
+Attack.prototype.GetBestAttackAgainst = function(target, prefAttackType)
{
let cmpFormation = Engine.QueryInterface(target, IID_Formation);
if (cmpFormation)
@@ -350,7 +390,7 @@
}
let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
- if (!cmpIdentity)
+ if (!cmpIdentity)
return undefined;
let targetClasses = cmpIdentity.GetClassesList();
@@ -357,7 +397,7 @@
let isTargetClass = function (className) { return targetClasses.indexOf(className) != -1; };
// Always slaughter domestic animals instead of using a normal attack
- if (isTargetClass("Domestic") && this.template.Slaughter)
+ if (isTargetClass("Domestic") && this.template.Slaughter)
return "Slaughter";
let attack = this;
@@ -365,23 +405,47 @@
let types = this.GetAttackTypes().filter(isAllowed);
- // check if the target is capturable
- let captureIndex = types.indexOf("Capture");
- if (captureIndex != -1)
+ if (prefAttackType && prefAttackType != "Capture" && types.indexOf(prefAttackType) != -1)
+ return prefAttackType;
+ else if (!prefAttackType || prefAttackType == "Capture")
{
- let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
-
- let cmpPlayer = QueryOwnerInterface(this.entity);
- if (allowCapture && cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID()))
- return "Capture";
- // not captureable, so remove this attack
- types.splice(captureIndex, 1);
+ // check if the target is capturable
+ let captureIndex = types.indexOf("Capture");
+ if (captureIndex != -1)
+ {
+ let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
+ let cmpPlayer = QueryOwnerInterface(this.entity);
+ if (cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID()))
+ return "Capture";
+ // not captureable, so remove this attack
+ types.splice(captureIndex, 1);
+ }
}
- let isPreferred = function (className) { return attack.GetPreferredClasses(className).some(isTargetClass); };
- let byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); };
+ // ignore charges for now: TODO implement these
+ let chargeIndex = types.indexOf("Charge");
+ if (chargeIndex != -1)
+ types.splice(chargeIndex, 1);
- return types.sort(byPreference).pop();
+ // 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];
+
+ // 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.GetPosition2D();
+ let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
+ if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
+ return undefined;
+ let targetPosition = cmpTargetPosition.GetPosition2D();
+ if (targetPosition.distanceToSquared(selfPosition) <= Math.pow(this.GetChangeDistance(), 2))
+ return "Melee";
+ return "Ranged"
+
};
Attack.prototype.CompareEntitiesByPreference = function(a, b)
@@ -478,6 +542,18 @@
return attackBonus;
};
+Attack.prototype.GetAttackTypeFromOrder = function(prefAttackType)
+{
+ let types = this.GetAttackTypes();
+ for (let type of types)
+ if ((this.template[type].AttackOrder && this.template[type].AttackOrder == prefAttackType) || prefAttackType == 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";
+};
+
// 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 +562,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 +579,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 +611,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 +696,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 +734,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 +816,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 18101)
+++ 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.CanAttackWithType = function(player, data)
+{
+ let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
+ if (!cmpAttack)
return false;
- // if the owner is an enemy, it's up to the attack component to decide
- if (cmpEntityPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID()))
- return cmpAttack.CanAttack(data.target);
+ return cmpAttack.CanAttackWithType(data.target, data.type);
+};
- 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,
+ "CanAttackWithType": 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 18101)
+++ binaries/data/mods/public/simulation/components/UnitAI.js (working copy)
@@ -416,7 +416,7 @@
}
// Work out how to attack the given target
- var type = this.GetBestAttackAgainst(this.order.data.target, this.order.data.allowCapture);
+ let type = this.GetBestAttackAgainst(this.order.data.target, this.order.data.prefAttackType);
if (!type)
{
// Oops, we can't attack at all
@@ -559,7 +559,7 @@
if (this.MustKillGatherTarget(this.order.data.target))
{
// Make sure we can attack the target, else we'll get very stuck
- if (!this.GetBestAttackAgainst(this.order.data.target, false))
+ if (!this.GetBestAttackAgainst(this.order.data.target))
{
// Oops, we can't attack at all - give up
// TODO: should do something so the player knows why this failed
@@ -583,7 +583,7 @@
return;
}
- this.PushOrderFront("Attack", { "target": this.order.data.target, "force": false, "hunting": true, "allowCapture": false });
+ this.PushOrderFront("Attack", { "target": this.order.data.target, "force": false, "hunting": true, "prefAttackType": undefined });
return;
}
@@ -838,9 +838,9 @@
},
"Order.Attack": function(msg) {
- var target = msg.data.target;
- var allowCapture = msg.data.allowCapture;
- var cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI);
+ let target = msg.data.target;
+ let prefAttackType = msg.data.prefAttackType;
+ let cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI);
if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember())
target = cmpTargetUnitAI.GetFormationController();
@@ -859,7 +859,7 @@
this.FinishOrder();
return;
}
- this.CallMemberFunction("Attack", [target, false, allowCapture]);
+ this.CallMemberFunction("Attack", [target, false, prefAttackType]);
if (cmpAttack.CanAttackAsFormation())
this.SetNextState("COMBAT.ATTACKING");
else
@@ -914,7 +914,7 @@
return;
}
- this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "allowCapture": false });
+ this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "prefAttackType": undefined });
return;
}
@@ -1151,7 +1151,7 @@
"MoveCompleted": function(msg) {
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
- this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data.allowCapture]);
+ this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data.prefAttackType]);
if (cmpAttack.CanAttackAsFormation())
this.SetNextState("COMBAT.ATTACKING");
else
@@ -1162,8 +1162,8 @@
"ATTACKING": {
// Wait for individual members to finish
"enter": function(msg) {
- var target = this.order.data.target;
- var allowCapture = this.order.data.allowCapture;
+ let target = this.order.data.target;
+ let prefAttackType = this.order.data.prefAttackType;
// Check if we are already in range, otherwise walk there
if (!this.CheckTargetAttackRange(target, target))
{
@@ -1170,7 +1170,7 @@
if (this.TargetIsAlive(target) && this.CheckTargetVisible(target))
{
this.FinishOrder();
- this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture });
+ this.PushOrderFront("Attack", { "target": target, "force": false, "prefAttackType": prefAttackType });
return true;
}
this.FinishOrder();
@@ -1186,8 +1186,8 @@
},
"Timer": function(msg) {
- var target = this.order.data.target;
- var allowCapture = this.order.data.allowCapture;
+ let target = this.order.data.target;
+ let prefAttackType = this.order.data.prefAttackType;
// Check if we are already in range, otherwise walk there
if (!this.CheckTargetAttackRange(target, target))
{
@@ -1194,7 +1194,7 @@
if (this.TargetIsAlive(target) && this.CheckTargetVisible(target))
{
this.FinishOrder();
- this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture });
+ this.PushOrderFront("Attack", { "target": target, "force": false, "prefAttackType": prefAttackType });
return;
}
this.FinishOrder();
@@ -1406,7 +1406,7 @@
// target the unit
if (this.CheckTargetVisible(msg.data.attacker))
- this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "allowCapture": true });
+ this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "prefAttackType": undefined });
else
{
var cmpPosition = Engine.QueryInterface(msg.data.attacker, IID_Position);
@@ -1801,6 +1801,8 @@
// Can't reach it - try to chase after it
if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
{
+ if (!this.order.data.force)
+ this.order.data.attackType = this.GetBestAttackAgainst(target);
if (this.MoveToTargetAttackRange(target, this.order.data.attackType))
{
this.SetNextState("COMBAT.CHASING");
@@ -1912,6 +1914,8 @@
// Can't reach it - try to chase after it
if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
{
+ if (!this.order.data.force)
+ this.order.data.attackType = this.GetBestAttackAgainst(target);
if (this.MoveToTargetRange(target, IID_Attack, this.order.data.attackType))
{
this.SetNextState("COMBAT.CHASING");
@@ -4535,12 +4539,12 @@
return distance < range;
};
-UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture)
+UnitAI.prototype.GetBestAttackAgainst = function(target, prefAttackType)
{
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
if (!cmpAttack)
return undefined;
- return cmpAttack.GetBestAttackAgainst(target, allowCapture);
+ return cmpAttack.GetBestAttackAgainst(target, prefAttackType);
};
UnitAI.prototype.GetAttackBonus = function(type, target)
@@ -4562,7 +4566,7 @@
if (!target)
return false;
- this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true });
+ this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "prefAttackType": undefined });
return true;
};
@@ -4575,13 +4579,13 @@
{
var target = ents.find(target =>
this.CanAttack(target, forceResponse)
- && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true))
+ && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target))
&& (this.GetStance().respondChaseBeyondVision || this.CheckTargetIsInVisionRange(target))
);
if (!target)
return false;
- this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true });
+ this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "prefAttackType": undefined });
return true;
};
@@ -4978,9 +4982,11 @@
* to a player order, and so is forced.
* If targetClasses is given, only entities matching the targetClasses can be attacked.
*/
-UnitAI.prototype.WalkAndFight = function(x, z, targetClasses, queued)
+UnitAI.prototype.WalkAndFight = function(x, z, targetClasses, queued, prefAttackType)
{
- this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "force": true }, queued);
+ let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
+ if (cmpAttack)
+ this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "force": true, "prefAttackType": cmpAttack.GetAttackTypeFromOrder(prefAttackType) }, queued);
};
/**
@@ -5001,7 +5007,7 @@
/**
* Adds attack order to the queue, forced by the player.
*/
-UnitAI.prototype.Attack = function(target, queued, allowCapture)
+UnitAI.prototype.Attack = function(target, queued, prefAttackType)
{
if (!this.CanAttack(target))
{
@@ -5013,7 +5019,10 @@
this.WalkToTarget(target, queued);
return;
}
- this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture}, queued);
+
+ let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
+ if (cmpAttack) // not needed as canAttack is true
+ this.AddOrder("Attack", { "target": target, "force": true, "prefAttackType": cmpAttack.GetAttackTypeFromOrder(prefAttackType) }, queued);
};
/**
@@ -5430,7 +5439,7 @@
if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
continue;
}
- this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });
+ this.PushOrderFront("Attack", { "target": targ, "force": true, "prefAttackType": undefined });
return true;
}
}
@@ -5456,7 +5465,7 @@
if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
continue;
}
- this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });
+ this.PushOrderFront("Attack", { "target": targ, "force": true, "prefAttackType": undefined });
return true;
}
return false;
Index: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js (revision 18101)
+++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js (working copy)
@@ -97,11 +97,12 @@
AddMock(unit, IID_Attack, {
GetRange: function() { return { "max": 10, "min": 0}; },
GetFullAttackRange: function() { return { "max": 40, "min": 0}; },
- GetBestAttackAgainst: function(t) { return "melee"; },
+ GetBestAttackAgainst: function(t) { return "Melee"; },
GetPreference: function(t) { return 0; },
GetTimers: function() { return { "prepare": 500, "repeat": 1000 }; },
CanAttack: function(v) { return true; },
CompareEntitiesByPreference: function(a, b) { return 0; },
+ GetAttackTypeFromOrder: function(t) { return "Melee"; },
});
unitAI.OnCreate();
@@ -247,10 +248,11 @@
AddMock(unit + i, IID_Attack, {
GetRange: function() { return {"max":10, "min": 0}; },
GetFullAttackRange: function() { return { "max": 40, "min": 0}; },
- GetBestAttackAgainst: function(t) { return "melee"; },
+ GetBestAttackAgainst: function(t) { return "Melee"; },
GetTimers: function() { return { "prepare": 500, "repeat": 1000 }; },
CanAttack: function(v) { return true; },
CompareEntitiesByPreference: function(a, b) { return 0; },
+ GetAttackTypeFromOrder: function(t) { return "Melee"; },
});
unitAI.OnCreate();
@@ -287,6 +289,7 @@
AddMock(controller, IID_Attack, {
GetRange: function() { return {"max":10, "min": 0}; },
CanAttackAsFormation: function() { return false },
+ GetAttackTypeFromOrder: function(t) { return "Melee"; },
});
controllerAI.OnCreate();
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js (revision 18101)
+++ binaries/data/mods/public/simulation/helpers/Commands.js (working copy)
@@ -158,7 +158,7 @@
"attack-walk": function(player, cmd, data)
{
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
- cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.queued);
+ cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.queued, cmd.prefAttackType);
});
},
@@ -167,10 +167,9 @@
if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target)))
warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd));
- let allowCapture = cmd.allowCapture || cmd.allowCapture == null;
// See UnitAI.CanAttack for target checks
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
- cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture);
+ cmpUnitAI.Attack(cmd.target, cmd.queued, cmd.prefAttackType);
});
},
Index: binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml (revision 18101)
+++ binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml (working copy)
@@ -1,5 +1,23 @@
+
+ 25
+
+ primary
+
+
+ secondary
+ 0
+ 6.0
+ 0
+ 65.0
+ 10.0
+ 120.0
+ 1000
+ 1000
+ 3.0
+
+
pers
Persian Immortal