Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg (revision 18614)
+++ binaries/data/config/default.cfg (working copy)
@@ -280,11 +280,13 @@
kill = Delete ; Destroy selected units
stop = "H" ; Stop the current action
unload = "U" ; Unload garrisoned units when a building/mechanical unit is selected
-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
+rangedattack = Space ; Modifier to ranged attack instead of another action
guard = "G" ; Modifier to escort/guard when clicking on unit/building
repair = "J" ; Modifier to repair when clicking on building/mechanical unit
queue = Shift ; Modifier to queue unit orders instead of replacing
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 18614)
+++ 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/actors/units/persians/infantry_archer_a.xml
===================================================================
--- binaries/data/mods/public/art/actors/units/persians/infantry_archer_a.xml (revision 18614)
+++ binaries/data/mods/public/art/actors/units/persians/infantry_archer_a.xml (working copy)
@@ -36,6 +36,7 @@
+
Index: binaries/data/mods/public/art/actors/units/persians/infantry_archer_b.xml
===================================================================
--- binaries/data/mods/public/art/actors/units/persians/infantry_archer_b.xml (revision 18614)
+++ binaries/data/mods/public/art/actors/units/persians/infantry_archer_b.xml (working copy)
@@ -24,6 +24,7 @@
+
Index: binaries/data/mods/public/art/actors/units/persians/infantry_archer_e.xml
===================================================================
--- binaries/data/mods/public/art/actors/units/persians/infantry_archer_e.xml (revision 18614)
+++ binaries/data/mods/public/art/actors/units/persians/infantry_archer_e.xml (working copy)
@@ -37,6 +37,7 @@
+
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/art/variants/biped/attack_melee_ranged.xml
===================================================================
--- binaries/data/mods/public/art/variants/biped/attack_melee_ranged.xml (nonexistent)
+++ binaries/data/mods/public/art/variants/biped/attack_melee_ranged.xml (working copy)
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
Index: binaries/data/mods/public/globalscripts/Templates.js
===================================================================
--- binaries/data/mods/public/globalscripts/Templates.js (revision 18614)
+++ binaries/data/mods/public/globalscripts/Templates.js (working copy)
@@ -66,6 +66,24 @@
}
/**
+ * Check if entity has all needed attack types.
+ */
+function HasNeededAttackTypes(attack, neededAttackTypes)
+{
+ if (!neededAttackTypes)
+ return true;
+ // transform the string to an array
+ if (typeof neededAttackTypes == "string")
+ neededAttackTypes = neededAttackTypes.split(/\s+/);
+
+ for (let type of neededAttackTypes)
+ if (!attack || attack.constructor === Object && !attack[type] ||
+ attack.constructor === Array && attack.indexOf(type) == -1)
+ return false;
+ return true;
+}
+
+/**
* Get information about a template with or without technology modifications.
*
* NOTICE: The data returned here should have the same structure as
Index: binaries/data/mods/public/gui/manual/intro.txt
===================================================================
--- binaries/data/mods/public/gui/manual/intro.txt (revision 18614)
+++ binaries/data/mods/public/gui/manual/intro.txt (working copy)
@@ -94,8 +94,10 @@
Right Click with a building/buildings selected: sets a rally point for units created/ungarrisoned from that building.
Ctrl + Right Click with units selected:
- If the cursor is over an allied structure: Garrison
- - If the cursor is over a non-allied unit or building: Attack (instead of capture or gather)
- - Otherwise: Attack move (by default all enemy units and structures along the way are targeted, use Ctrl + Q + Right Click to target only units).
+ - If the cursor is over a non-allied unit or building: Primary Attack (instead of capture, gather)
+ - Otherwise: Attack move (by default all enemy units and structures along the way are targeted, use Ctrl + Q + Right Click to target only units).
+Alt + Right Click on non-allied unit or building: Melee attack
+Space + Right Click on non-allied unit or building: Ranged attack
[font="sans-bold-14"]Overlays
[font="sans-14"]Alt + G: Hide/show the GUI
Index: binaries/data/mods/public/gui/session/input.js
===================================================================
--- binaries/data/mods/public/gui/session/input.js (revision 18614)
+++ binaries/data/mods/public/gui/session/input.js (working copy)
@@ -228,7 +228,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 18614)
+++ binaries/data/mods/public/gui/session/unit_actions.js (working copy)
@@ -58,7 +58,7 @@
return { "type": "move" };
},
- "specificness": 12,
+ "specificness": 13,
},
"attack-move":
@@ -77,7 +77,8 @@
"x": target.x,
"z": target.z,
"targetClasses": targetClasses,
- "queued": queued
+ "queued": queued,
+ "prefAttackType": "noCapture"
});
Engine.GuiInterfaceCall("PlaySound", {
@@ -114,8 +115,8 @@
"type": "attack",
"entities": selection,
"target": action.target,
- "allowCapture": true,
- "queued": queued
+ "queued": queued,
+ "prefAttackType": "Capture"
});
Engine.GuiInterfaceCall("PlaySound", {
@@ -130,8 +131,7 @@
if (!entState.attack || !targetState.hitpoints)
return false;
- return {
- "possible": Engine.GuiInterfaceCall("CanCapture", {
+ return { "possible": Engine.GuiInterfaceCall("CanCapture", {
"entity": entState.id,
"target": targetState.id
})
@@ -148,7 +148,7 @@
"target": target
};
},
- "specificness": 9,
+ "specificness": 7,
},
"attack":
@@ -160,7 +160,7 @@
"entities": selection,
"target": action.target,
"queued": queued,
- "allowCapture": false
+ "prefAttackType": "noCapture"
});
Engine.GuiInterfaceCall("PlaySound", {
@@ -205,6 +205,116 @@
"target": target
};
},
+ "specificness": 8,
+ },
+
+ "melee-attack":
+ {
+ "execute": function(target, action, selection, queued)
+ {
+ Engine.PostNetworkCommand({
+ "type": "attack",
+ "entities": selection,
+ "target": action.target,
+ "queued": queued,
+ "prefAttackType": "Melee"
+ });
+
+ Engine.GuiInterfaceCall("PlaySound", {
+ "name": "order_attack",
+ "entity": selection[0]
+ });
+
+ return true;
+ },
+ "getActionInfo": function(entState, targetState)
+ {
+ if (!entState.attack || !targetState.hitpoints)
+ return false;
+
+ return { "possible": Engine.GuiInterfaceCall("CanAttack", {
+ "entity": entState.id,
+ "target": targetState.id,
+ "type": "Melee"
+ }) };
+ },
+ "hotkeyActionCheck": function(target)
+ {
+ if (!Engine.HotkeyIsPressed("session.meleeattack") || !getActionInfo("melee-attack", target).possible)
+ return false;
+
+ return {
+ "type": "melee-attack",
+ "cursor": "action-melee-attack",
+ "target": target
+ };
+ },
+ "actionCheck": function(target)
+ {
+ if (!getActionInfo("melee-attack", target).possible)
+ return false;
+
+ return {
+ "type": "melee-attack",
+ "cursor": "action-melee-attack",
+ "target": target
+ };
+ },
+ "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)
+ return false;
+
+ return { "possible": Engine.GuiInterfaceCall("CanAttack", {
+ "entity": entState.id,
+ "target": targetState.id,
+ "type": "Ranged"
+ }) };
+ },
+ "hotkeyActionCheck": function(target, selection)
+ {
+ if (!Engine.HotkeyIsPressed("session.rangedattack") || !getActionInfo("ranged-attack", target).possible)
+ return false;
+
+ return {
+ "type": "ranged-attack",
+ "cursor": "action-ranged-attack",
+ "target": target
+ };
+ },
+ "actionCheck": function(target, selection)
+ {
+ if (!getActionInfo("ranged-attack", target).possible)
+ return false;
+
+ return {
+ "type": "ranged-attack",
+ "cursor": "action-ranged-attack",
+ "target": target
+ };
+ },
"specificness": 10,
},
@@ -255,7 +365,7 @@
"target": target
};
},
- "specificness": 7,
+ "specificness": 6,
},
"build":
@@ -368,7 +478,7 @@
"target": target
};
},
- "specificness": 11,
+ "specificness": 12,
},
"gather":
@@ -389,7 +499,7 @@
return true;
},
- "getActionInfo": function(entState, targetState)
+ "getActionInfo": function(entState, targetState)
{
if (!targetState.resourceSupply)
return false;
@@ -608,8 +718,9 @@
if (targetState.garrisonHolder.garrisonedEntitiesCount + extraCount >= targetState.garrisonHolder.capacity)
tooltip = "[color=\"orange\"]" + tooltip + "[/color]";
- if (!MatchesClassList(entState.identity.classes, targetState.garrisonHolder.allowedClasses))
- return false;
+ if (!MatchesClassList(entState.identity.classes, targetState.garrisonHolder.allowedClasses) ||
+ !HasNeededAttackTypes(entState.attack, targetState.garrisonHolder.neededAttackTypes))
+ return { "possible": false };
return {
"possible": true,
@@ -922,7 +1033,7 @@
"position": actionInfo.position
};
},
- "specificness": 6,
+ "specificness": 5,
},
"unset-rallypoint":
@@ -1005,6 +1116,7 @@
unloadAll();
},
},
+
"delete": {
"getInfo": function(entState)
{
@@ -1043,6 +1155,7 @@
openDeleteDialog(selection);
},
},
+
"stop": {
"getInfo": function(entState)
{
Index: binaries/data/mods/public/simulation/ai/common-api/entity.js
===================================================================
--- binaries/data/mods/public/simulation/ai/common-api/entity.js (revision 18614)
+++ binaries/data/mods/public/simulation/ai/common-api/entity.js (working copy)
@@ -742,6 +742,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(); },
@@ -756,8 +757,8 @@
return this;
},
- attackMove: function(x, z, targetClasses, queued = false) {
- Engine.PostCommand(PlayerID,{"type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "targetClasses": targetClasses, "queued": queued });
+ attackMove: function(x, z, targetClasses, prefAttackType = "undefined", queued = false) {
+ Engine.PostCommand(PlayerID,{ "type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "targetClasses": targetClasses, "prefAttackType": prefAttackType, "queued": queued });
return this;
},
@@ -791,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, prefAttackType = undefined, 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 18614)
+++ binaries/data/mods/public/simulation/ai/common-api/entitycollection.js (working copy)
@@ -156,7 +156,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": undefined });
return this;
};
@@ -182,7 +182,7 @@
m.EntityCollection.prototype.attack = function(unitId)
{
- Engine.PostCommand(PlayerID,{"type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false});
+ Engine.PostCommand(PlayerID,{ "type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false, "prefAttackType": undefined });
return this;
};
Index: binaries/data/mods/public/simulation/ai/petra/attackPlan.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/attackPlan.js (revision 18614)
+++ binaries/data/mods/public/simulation/ai/petra/attackPlan.js (working copy)
@@ -1237,13 +1237,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);
}
}
@@ -1260,7 +1260,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);
}
}
@@ -1279,7 +1279,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);
}
}
@@ -1459,12 +1459,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
@@ -1522,8 +1522,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 (this.isBlocked)
ent.attack(this.target.id(), false);
@@ -1570,12 +1570,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
@@ -1582,6 +1582,7 @@
{
let distmin = Math.min();
let attackerId;
+ let attacker;
this.unitCollection.forEach( function (unit) {
if (!unit.position())
return;
@@ -1591,11 +1592,14 @@
let dist = API3.SquareVectorDistance(unit.position(), ent.position());
if (dist > distmin)
return;
+ if (!gameState.getEntityById(unit.unitAIOrderData()[0].target))
+ return;
distmin = dist;
attackerId = unit.unitAIOrderData()[0].target;
+ attacker = gameState.getEntityById(attackerId);
});
if (attackerId)
- ent.attack(attackerId, !this.noCapture.has(attackerId));
+ ent.attack(attackerId, m.getPrefAttackType(ent, attacker, this.noCapture));
}
}
}
@@ -1648,7 +1652,7 @@
continue;
if (!ent.isIdle())
continue;
- ent.attack(attacker.id(), !this.noCapture.has(attacker.id()));
+ ent.attack(attacker.id(), !this.noCapture.has(attacker.id(), this.noCapture));
}
break;
}
@@ -2006,7 +2010,7 @@
if (this.noCapture.has(targetId))
{
- ent.attack(targetId, false);
+ ent.attack(targetId, m.getPrefAttackType(ent, target, this.noCapture));
return true;
}
@@ -2018,7 +2022,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;
}
@@ -2035,7 +2039,7 @@
if (antiCapture >= this.captureStrength)
{
this.noCapture.add(targetId);
- ent.attack(targetId, false);
+ ent.attack(targetId, "noCapture");
return true;
}
@@ -2044,7 +2048,7 @@
this.unitCollection.length < 2*target.garrisoned().length)
{
this.noCapture.add(targetId);
- ent.attack(targetId, false);
+ ent.attack(targetId, "noCapture");
return true;
}
Index: binaries/data/mods/public/simulation/ai/petra/defenseArmy.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/defenseArmy.js (revision 18614)
+++ 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);
@@ -115,8 +115,12 @@
else if (orderData.length && orderData[0].target && orderData[0].attackType && orderData[0].attackType === "Capture")
{
let target = gameState.getEntityById(orderData[0].target);
- if (target && !m.allowCapture(ent, target))
- ent.attack(orderData[0].target, false);
+ if (target)
+ {
+ let prefAttackType = m.getPrefAttackType(ent, target);
+ if (prefAttackType !== "Capture")
+ ent.attack(orderData[0].target, prefAttackType);
+ }
}
}
Index: binaries/data/mods/public/simulation/ai/petra/entityExtend.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/entityExtend.js (revision 18614)
+++ binaries/data/mods/public/simulation/ai/petra/entityExtend.js (working copy)
@@ -81,11 +81,16 @@
return strength * ent.maxHitpoints() / 100.0;
};
-/** Decide if we should try to capture or destroy */
-m.allowCapture = function(ent, target)
+// Decide if we should try to capture, melee or range attack
+// TODO make this function less hacky
+m.getPrefAttackType = function(ent, target, noCapture)
{
- return !target.hasClass("Siege") || !ent.hasClass("Melee") ||
- !target.isGarrisonHolder() || !target.garrisoned().length;
+ if (target.hasClass("Siege") || (noCapture && noCapture.has(target.id())))
+ return ent.hasClass("Melee") ? "Melee" : "Range"; // Don't capture for now
+
+ if (target.isCapturable() && (!target.isGarrisonHolder() || !target.garrisoned().length))
+ return "Capture";
+ return "noCapture";
};
/** Makes the worker deposit the currently carried resources at the closest accessible dropsite */
Index: binaries/data/mods/public/simulation/components/Armour.js
===================================================================
--- binaries/data/mods/public/simulation/components/Armour.js (revision 18614)
+++ binaries/data/mods/public/simulation/components/Armour.js (working copy)
@@ -51,26 +51,18 @@
if (this.invulnerable)
return { "killed": false, "change": 0 };
- // Adjust damage values based on armour; exponential armour: damage = attack * 0.9^armour
- var armourStrengths = this.GetArmourStrengths();
- var adjHack = hack * Math.pow(0.9, armourStrengths.hack);
- var adjPierce = pierce * Math.pow(0.9, armourStrengths.pierce);
- var adjCrush = crush * Math.pow(0.9, armourStrengths.crush);
+ let damage = this.GetDamage(hack, pierce, crush);
- // Total is sum of individual damages
- // Don't bother rounding, since HP is no longer integral.
- var total = adjHack + adjPierce + adjCrush;
-
// Reduce health
- var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
- return cmpHealth.Reduce(total);
+ let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
+ return cmpHealth.Reduce(damage);
};
Armour.prototype.GetArmourStrengths = function()
{
// Work out the armour values with technology effects
- var applyMods = (type, foundation) => {
- var strength;
+ let applyMods = (type, foundation) => {
+ let strength;
if (foundation)
{
strength = +this.template.Foundation[type];
@@ -82,7 +74,7 @@
return ApplyValueModificationsToEntity("Armour/" + type, strength, this.entity);
};
- var foundation = Engine.QueryInterface(this.entity, IID_Foundation) && this.template.Foundation;
+ let foundation = Engine.QueryInterface(this.entity, IID_Foundation) && this.template.Foundation;
return {
"hack": applyMods("Hack", foundation),
@@ -91,4 +83,17 @@
};
};
+Armour.prototype.GetDamage = function(hack, pierce, crush)
+{
+ // Adjust damage values based on armour; exponential armour: damage = attack * 0.9^armour
+ let armourStrengths = this.GetArmourStrengths();
+ let adjHack = hack * Math.pow(0.9, armourStrengths.hack);
+ let adjPierce = pierce * Math.pow(0.9, armourStrengths.pierce);
+ let adjCrush = crush * Math.pow(0.9, armourStrengths.crush);
+
+ // Total is sum of individual damages
+ // Don't bother rounding, since HP is no longer integral.
+ return adjHack + adjPierce + adjCrush;
+};
+
Engine.RegisterComponentType(IID_DamageReceiver, "Armour", Armour);
Index: binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/Attack.js (revision 18614)
+++ binaries/data/mods/public/simulation/components/Attack.js (working copy)
@@ -208,33 +208,48 @@
return [];
};
-Attack.prototype.CanAttack = function(target)
+Attack.prototype.CanAttack = function(target, wantedType)
{
let cmpFormation = Engine.QueryInterface(target, IID_Formation);
if (cmpFormation)
return true;
+ if (wantedType && !this.template[wantedType])
+ return false;
+
let cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position);
let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld())
return false;
- // Check if the relative height difference is larger than the attack range
- // If the relative height is bigger, it means they will never be able to
- // reach each other, no matter how close they come.
- let heightDiff = Math.abs(cmpThisPosition.GetHeightOffset() - cmpTargetPosition.GetHeightOffset());
-
const cmpIdentity = Engine.QueryInterface(target, IID_Identity);
if (!cmpIdentity)
return undefined;
+ let cmpEntityPlayer = QueryOwnerInterface(this.entity);
+ let cmpTargetPlayer = QueryOwnerInterface(target);
+ if (!cmpTargetPlayer || !cmpEntityPlayer)
+ return false;
+
const targetClasses = cmpIdentity.GetClassesList();
+ let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
+ if (targetClasses.indexOf("Domestic") == -1 && !cmpEntityPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID()) &&
+ (!cmpCapturable || !cmpCapturable.CanCapture(cmpEntityPlayer.GetPlayerID())))
+ return false;
+ // Check if the relative height difference is larger than the attack range
+ // If the relative height is bigger, it means they will never be able to
+ // reach each other, no matter how close they come.
+ let heightDiff = Math.abs(cmpThisPosition.GetHeightOffset() - cmpTargetPosition.GetHeightOffset());
+
for (let type of this.GetAttackTypes())
{
- if (type == "Capture" && !QueryMiragedInterface(target, IID_Capturable))
+ if (wantedType && type != wantedType)
continue;
+ if (type == "Capture" && !cmpCapturable)
+ continue;
+
if (heightDiff > this.GetRange(type).max)
continue;
@@ -291,7 +306,7 @@
return ret;
};
-Attack.prototype.GetBestAttackAgainst = function(target, allowCapture)
+Attack.prototype.GetBestAttackAgainst = function(target, prefAttackType)
{
let cmpFormation = Engine.QueryInterface(target, IID_Formation);
if (cmpFormation)
@@ -298,7 +313,8 @@
{
// TODO: Formation against formation needs review
let types = this.GetAttackTypes();
- return ["Ranged", "Melee", "Capture"].find(attack => types.indexOf(attack) != -1);
+ return ["Ranged", "Melee", "Capture"].find(attack => types.indexOf(attack) != -1
+ && (!prefAttackType || "no" + attack != prefAttackType));
}
let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
@@ -305,6 +321,14 @@
if (!cmpIdentity)
return undefined;
+ let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+ if (!cmpPosition || !cmpPosition.IsInWorld())
+ return undefined;
+
+ // If we are visisble garrisoned always do ranged attack if we can.
+ if (cmpPosition.GetTurretParent() != INVALID_ENTITY)
+ return this.template.Ranged && "Ranged";
+
let targetClasses = cmpIdentity.GetClassesList();
let isTargetClass = className => targetClasses.indexOf(className) != -1;
@@ -313,26 +337,89 @@
return "Slaughter";
let attack = this;
+ let isAllowed = function (type) { return !attack.GetRestrictedClasses(type).some(isTargetClass)
+ && (!prefAttackType || "no" + type != prefAttackType); };
+
let types = this.GetAttackTypes().filter(type => !attack.GetRestrictedClasses(type).some(isTargetClass));
+ if (!types.length)
+ return undefined;
- // 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 = className => attack.GetPreferredClasses(className).some(isTargetClass);
+ if (!types.length)
+ return undefined;
- return types.sort((a, b) =>
- (types.indexOf(a) + (isPreferred(a) ? types.length : 0)) -
- (types.indexOf(b) + (isPreferred(b) ? types.length : 0))).pop();
+ // assume ranged and/or melee attack left
+ // TODO stop assuming that?
+ let meleeIndex = types.indexOf("Melee");
+ let rangedIndex = types.indexOf("Ranged");
+ if (meleeIndex != -1 && rangedIndex != -1)
+ {
+ let selfPosition = cmpPosition.GetPosition2D();
+ let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
+ if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
+ return undefined;
+ let targetPosition = cmpTargetPosition.GetPosition2D();
+ let distanceToSquared = targetPosition.distanceToSquared(selfPosition);
+ if (distanceToSquared <= Math.pow(this.GetRange("Ranged").min, 2))
+ return "Melee";
+
+ let cmpDamageReceiver = Engine.QueryInterface(target, IID_DamageReceiver);
+ if (!cmpDamageReceiver)
+ return undefined;
+
+ let attackStrengthsMelee = this.GetAttackStrengths("Melee");
+ let attackBonusMelee = this.GetAttackBonus("Melee", target);
+ let DPSMelee = cmpDamageReceiver.GetDamage(
+ attackStrengthsMelee.hack * attackBonusMelee,
+ attackStrengthsMelee.pierce * attackBonusMelee,
+ attackStrengthsMelee.crush * attackBonusMelee
+ ) / this.GetTimers("Melee").repeat;
+
+ let attackStrengthsRanged = this.GetAttackStrengths("Ranged");
+ let attackBonusRanged = this.GetAttackBonus("Ranged", target);
+ let DPSRanged = cmpDamageReceiver.GetDamage(
+ attackStrengthsRanged.hack * attackBonusRanged,
+ attackStrengthsRanged.pierce * attackBonusRanged,
+ attackStrengthsRanged.crush * attackBonusRanged
+ ) / this.GetTimers("Ranged").repeat;
+
+ // When we are out of range do DPS only else take distance into account
+ let maxRange = this.GetFullAttackRange().max;
+ if (distanceToSquared > 1.2 * Math.pow(maxRange, 2))
+ {
+ if (DPSRanged > DPSMelee)
+ return "Ranged";
+ return "Melee";
+ }
+
+ let strenghtRanged = DPSRanged * distanceToSquared / Math.pow(maxRange, 2);
+ let strenghtMelee = DPSMelee * (1 - distanceToSquared / Math.pow(maxRange, 2));
+
+ if (strenghtRanged > strenghtMelee)
+ return "Ranged";
+ return "Melee";
+ }
+ else if (meleeIndex != -1)
+ return "Melee";
+ else if (rangedIndex != -1)
+ return "Ranged";
+ return types[0];
};
Attack.prototype.CompareEntitiesByPreference = function(a, b)
@@ -496,7 +583,7 @@
let horizDistance = targetPosition.horizDistanceTo(selfPosition);
- // This is an approximation of the time ot the target, it assumes that the target has a constant radial
+ // This is an approximation of the time to the target, it assumes that the target has a constant radial
// velocity, but since units move in straight lines this is not true. The exact value would be more
// difficult to calculate and I think this is sufficiently accurate. (I tested and for cavalry it was
// about 5% of the units radius out in the worst case)
@@ -572,7 +659,7 @@
"target": target,
"attacker": this.entity,
"multiplier": this.GetAttackBonus(type, target),
- "type":type
+ "type": type
});
}
};
Index: binaries/data/mods/public/simulation/components/GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/GarrisonHolder.js (revision 18614)
+++ binaries/data/mods/public/simulation/components/GarrisonHolder.js (working copy)
@@ -10,6 +10,14 @@
"" +
"" +
"" +
+ "" +
+ "" +
+ "" +
+ "tokens" +
+ "" +
+ "" +
+ "" +
+ "" +
"" +
"" +
"" +
@@ -119,6 +127,15 @@
};
/**
+ * Returns an array of attack types which the unit garrisoned inside this
+ * particualar entity needs to have. Obtained from the entity's template
+ */
+GarrisonHolder.prototype.GetNeededAttackTypes = function()
+{
+ return this.template.NeededAttackTypes ? this.template.NeededAttackTypes._string : undefined;
+};
+
+/**
* Get Maximum pop which can be garrisoned
*/
GarrisonHolder.prototype.GetCapacity = function()
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js (revision 18614)
+++ binaries/data/mods/public/simulation/components/GuiInterface.js (working copy)
@@ -359,6 +359,7 @@
"entities": cmpGarrisonHolder.GetEntities(),
"buffHeal": cmpGarrisonHolder.GetHealRate(),
"allowedClasses": cmpGarrisonHolder.GetAllowedClasses(),
+ "neededAttackTypes": cmpGarrisonHolder.GetNeededAttackTypes(),
"capacity": cmpGarrisonHolder.GetCapacity(),
"garrisonedEntitiesCount": cmpGarrisonHolder.GetGarrisonedEntitiesCount()
};
@@ -440,6 +441,7 @@
let types = cmpAttack.GetAttackTypes();
if (types.length)
ret.attack = {};
+
for (let type of types)
{
ret.attack[type] = cmpAttack.GetAttackStrengths(type);
@@ -1829,16 +1831,8 @@
if (!cmpAttack)
return false;
- let cmpEntityPlayer = QueryOwnerInterface(data.entity, IID_Player);
- let cmpTargetPlayer = QueryOwnerInterface(data.target, IID_Player);
- if (!cmpEntityPlayer || !cmpTargetPlayer)
- 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 false;
+ let attackType = data.type || undefined;
+ return cmpAttack.CanAttack(data.target, attackType);
};
/*
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js (revision 18614)
+++ binaries/data/mods/public/simulation/components/UnitAI.js (working copy)
@@ -420,7 +420,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
@@ -563,7 +563,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
@@ -587,7 +587,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;
}
@@ -842,9 +842,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();
@@ -863,7 +863,7 @@
this.FinishOrder();
return;
}
- this.CallMemberFunction("Attack", [target, false, allowCapture]);
+ this.CallMemberFunction("Attack", [target, false, prefAttackType]);
if (cmpAttack.CanAttackAsFormation())
this.SetNextState("COMBAT.ATTACKING");
else
@@ -918,7 +918,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;
}
@@ -1155,7 +1155,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
@@ -1166,8 +1166,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))
{
@@ -1174,7 +1174,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();
@@ -1190,8 +1190,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))
{
@@ -1198,7 +1198,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();
@@ -1410,7 +1410,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);
@@ -1805,6 +1805,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");
@@ -1916,6 +1918,12 @@
// Can't reach it - try to chase after it
if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
{
+ if (!this.order.data.force)
+ {
+ let type = this.GetBestAttackAgainst(target);
+ if (type)
+ this.order.data.attackType = type;
+ }
if (this.MoveToTargetRange(target, IID_Attack, this.order.data.attackType))
{
this.SetNextState("COMBAT.CHASING");
@@ -4549,12 +4557,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)
@@ -4572,11 +4580,12 @@
*/
UnitAI.prototype.AttackVisibleEntity = function(ents, forceResponse)
{
- var target = ents.find(target => this.CanAttack(target, forceResponse));
+ let target = ents.find(target => this.CanAttack(target, forceResponse));
if (!target)
return false;
- this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true });
+ let prefAttackType = this.GetStance().respondStandGround && "Ranged";
+ this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "prefAttackType": prefAttackType });
return true;
};
@@ -4589,13 +4598,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;
};
@@ -4986,9 +4995,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, prefAttackType)
{
- this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "force": true }, queued);
+ this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "force": true, "prefAttackType": prefAttackType }, queued);
};
/**
@@ -5009,7 +5018,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))
{
@@ -5021,7 +5030,7 @@
this.WalkToTarget(target, queued);
return;
}
- this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture}, queued);
+ this.AddOrder("Attack", { "target": target, "force": true, "prefAttackType": prefAttackType }, queued);
};
/**
@@ -5450,7 +5459,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;
}
}
@@ -5476,7 +5485,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;
}
@@ -5676,12 +5685,12 @@
if (this.IsFormationController())
return true;
- var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);
+ let cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);
if (!cmpGarrisonHolder)
return false;
// Verify that the target is owned by this entity's player or a mutual ally of this player
- var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership || !(IsOwnedByPlayer(cmpOwnership.GetOwner(), target) || IsOwnedByMutualAllyOfPlayer(cmpOwnership.GetOwner(), target)))
return false;
@@ -5691,6 +5700,15 @@
if (this.IsAnimal())
return false;
+ let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity)
+ if (!cmpIdentity)
+ return false;
+
+ let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack)
+ let attackTypes = cmpAttack ? cmpAttack.GetAttackTypes() : [];
+ if (!MatchesClassList(cmpIdentity.GetClassesList(), cmpGarrisonHolder.GetAllowedClasses()) ||
+ !HasNeededAttackTypes(attackTypes, cmpGarrisonHolder.GetNeededAttackTypes()))
+ return false;
return true;
};
Index: binaries/data/mods/public/simulation/components/tests/test_Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Attack.js (revision 18614)
+++ binaries/data/mods/public/simulation/components/tests/test_Attack.js (working copy)
@@ -22,7 +22,8 @@
});
AddMock(playerEnt1, IID_Player, {
- "GetPlayerID": () => 1
+ "GetPlayerID": () => 1,
+ "IsEnemy": () => true
});
}
@@ -30,6 +31,7 @@
AddMock(attacker, IID_Position, {
"IsInWorld": () => true,
+ "GetTurretParent": () => INVALID_ENTITY,
"GetHeightOffset": () => 5
});
@@ -87,8 +89,13 @@
"HasClass": className => className == defenderClass
});
+ AddMock(defender, IID_Ownership, {
+ "GetOwner": () => 1
+ });
+
AddMock(defender, IID_Position, {
"IsInWorld": () => true,
+ "GetTurretParent": () => INVALID_ENTITY,
"GetHeightOffset": () => 0
});
@@ -148,12 +155,11 @@
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), true);
- let allowCapturing = [true];
+ let prefAttackType = "Capture";
if (!isBuilding)
- allowCapturing.push(false);
+ prefAttackType = bestAttack;
- for (let allowCapturing of allowCapturing)
- TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, allowCapturing), bestAttack);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, prefAttackType), bestAttack);
});
}
Index: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js (revision 18614)
+++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js (working copy)
@@ -97,7 +97,7 @@
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; },
@@ -247,7 +247,7 @@
AddMock(unit + i, IID_Attack, {
GetRange: function() { return {"max":10, "min": 0}; },
GetFullAttackRange: function() { return { "max": 40, "min": 0}; },
- GetBestAttackAgainst: function(t) { return "melee"; },
+ GetBestAttackAgainst: function(t) { return "Melee"; },
GetTimers: function() { return { "prepare": 500, "repeat": 1000 }; },
CanAttack: function(v) { return true; },
CompareEntitiesByPreference: function(a, b) { return 0; },
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js (revision 18614)
+++ binaries/data/mods/public/simulation/helpers/Commands.js (working copy)
@@ -160,7 +160,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);
});
},
@@ -169,10 +169,8 @@
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;
-
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/template_structure_defense_wall_long.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_defense_wall_long.xml (revision 18614)
+++ binaries/data/mods/public/simulation/templates/template_structure_defense_wall_long.xml (working copy)
@@ -12,7 +12,8 @@
5
- Ranged+Infantry
+ Infantry
+ Ranged
0.1
Unit
0
Index: binaries/data/mods/public/simulation/templates/template_structure_defense_wall_medium.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_defense_wall_medium.xml (revision 18614)
+++ binaries/data/mods/public/simulation/templates/template_structure_defense_wall_medium.xml (working copy)
@@ -9,7 +9,8 @@
3
- Ranged+Infantry
+ Infantry
+ Ranged
0.1
Unit
0
Index: binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml (revision 18614)
+++ binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml (working copy)
@@ -6,6 +6,14 @@
10
+
+ 3
+ 0
+ 0
+ 4.0
+ 1000
+ Human
+
0
1.5
Index: binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_archer.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_archer.xml (revision 18614)
+++ binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_archer.xml (working copy)
@@ -10,7 +10,7 @@
6.0
0
72.0
- 0.0
+ 10.0
120.0
1000
1000
Index: binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml (revision 18614)
+++ binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml (working copy)
@@ -1,5 +1,18 @@
+
+
+ 0
+ 6.0
+ 0
+ 65.0
+ 10.0
+ 120.0
+ 1000
+ 1000
+ 3.0
+
+
pers
Persian Immortal