Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg
+++ binaries/data/config/default.cfg
@@ -285,16 +285,19 @@
backtowork = "Y" ; The unit will go back to work
unload = "U" ; Unload garrisoned units when a building/mechanical unit is selected
move = unused ; Modifier to move to a point instead of another action (e.g. gather)
-attack = Ctrl ; Modifier to attack instead of another action (e.g. 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)
+move = unused ; Modifier to move to a point instead of another attack action (e.g. gather)
+attack = Ctrl ; Modifier to primary attack instead of another attack action (e.g. 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 attack action (e.g. ranged attack, capture)
+rangedattack = Space ; Modifier to ranged attack instead of another attack action (e.g. melee attack, capture)
guard = "G" ; Modifier to escort/guard when clicking on unit/building
patrol = "P" ; Modifier to patrol a unit
repair = "J" ; Modifier to repair when clicking on building/mechanical unit
queue = Shift ; Modifier to queue unit orders instead of replacing
-orderone = Alt ; Modifier to order only one entity in selection.
+orderone = "O" ; Modifier to order only one entity in selection.
batchtrain = Shift ; Modifier to train units in batches
massbarter = Shift ; Modifier to barter bunch of resources
masstribute = Shift ; Modifier to tribute bunch of resources
Index: binaries/data/mods/public/art/actors/units/persians/infantry_spearman_c2.xml
===================================================================
--- binaries/data/mods/public/art/actors/units/persians/infantry_spearman_c2.xml
+++ binaries/data/mods/public/art/actors/units/persians/infantry_spearman_c2.xml
@@ -77,6 +77,11 @@
+
+
+
+
+
player_trans_spec_helmet.xml
Index: binaries/data/mods/public/art/textures/cursors/action-attack-ranged.txt
===================================================================
--- /dev/null
+++ binaries/data/mods/public/art/textures/cursors/action-attack-ranged.txt
@@ -0,0 +1 @@
+1 1
Index: binaries/data/mods/public/art/textures/cursors/action-attack.txt
===================================================================
--- binaries/data/mods/public/art/textures/cursors/action-attack.txt
+++ binaries/data/mods/public/art/textures/cursors/action-attack.txt
@@ -1 +0,0 @@
-1 1
Index: binaries/data/mods/public/art/textures/cursors/action-capture.txt
===================================================================
--- binaries/data/mods/public/art/textures/cursors/action-capture.txt
+++ binaries/data/mods/public/art/textures/cursors/action-capture.txt
@@ -1 +0,0 @@
-1 1
Index: binaries/data/mods/public/globalscripts/AttackEffects.js
===================================================================
--- binaries/data/mods/public/globalscripts/AttackEffects.js
+++ binaries/data/mods/public/globalscripts/AttackEffects.js
@@ -3,14 +3,20 @@
const g_EffectReceiver = {
"Damage": {
"IID": "IID_Health",
- "method": "TakeDamage"
+ "method": "TakeDamage",
+ "canTakeEffectMethod": "CanAttack",
+ "getRelativeEffectMethod": "GetRelativeDamage"
},
"Capture": {
"IID": "IID_Capturable",
- "method": "Capture"
+ "method": "Capture",
+ "canTakeEffectMethod": "CanCapture",
+ "getRelativeEffectMethod": "GetRelativeCapture"
},
"ApplyStatus": {
"IID": "IID_StatusEffectsReceiver",
- "method": "ApplyStatus"
+ "method": "ApplyStatus",
+ "canTakeEffectMethod": "CanTakeEffect",
+ "getRelativeEffectMethod": "GetRelativeEffect"
}
};
Index: binaries/data/mods/public/globalscripts/Templates.js
===================================================================
--- binaries/data/mods/public/globalscripts/Templates.js
+++ binaries/data/mods/public/globalscripts/Templates.js
@@ -196,6 +196,7 @@
};
ret.attack[type] = {
+ "form": template.Attack[type].Form,
"minRange": getAttackStat("MinRange"),
"maxRange": getAttackStat("MaxRange"),
"elevationBonus": getAttackStat("ElevationBonus"),
Index: binaries/data/mods/public/gui/common/tooltips.js
===================================================================
--- binaries/data/mods/public/gui/common/tooltips.js
+++ binaries/data/mods/public/gui/common/tooltips.js
@@ -13,6 +13,13 @@
return g_ResourceData.GetCodes().concat(["population", "populationBonus", "time"]);
}
+var g_AttackForms = {
+ "Melee": translate("Melee Attack:"),
+ "Ranged": translate("Ranged Attack:"),
+ "Capture": translate("Capture Attack:"),
+ "Slaughter": translate("Slaughter Attack:")
+};
+
var g_DamageTypesMetadata = new DamageTypesMetadata();
var g_StatusEffectsMetadata = new StatusEffectsMetadata();
@@ -312,13 +319,8 @@
let tooltips = [];
for (let attackType in template.attack)
{
- // Slaughter is used to kill animals, so do not show it.
- if (attackType == "Slaughter")
- continue;
+ let attackLabel = g_AttackForms[template.attack[attackType].form];
- let attackLabel = sprintf(headerFont(translate("%(attackType)s Attack")), {
- "attackType": attackType
- });
let attackTypeTemplate = template.attack[attackType];
let projectiles;
@@ -337,7 +339,7 @@
}
statusEffectsDetails = statusEffectsDetails.join("");
- tooltips.push(sprintf(translate("%(attackLabel)s: %(effects)s, %(range)s, %(rate)s%(statusEffects)s"), {
+ tooltips.push(sprintf(translate("%(attackLabel)s %(effects)s, %(range)s, %(rate)s%(statusEffects)s"), {
"attackLabel": attackLabel,
"effects": attackEffectsDetails(attackTypeTemplate),
"range": rangeDetails(attackTypeTemplate),
Index: binaries/data/mods/public/gui/manual/intro.txt
===================================================================
--- binaries/data/mods/public/gui/manual/intro.txt
+++ binaries/data/mods/public/gui/manual/intro.txt
@@ -100,7 +100,7 @@
J + Right Click on structure – Repair
P + Right Click – Patrol
Shift + Right Click – Queue the move/build/gather/etc. order
- Alt + Right Click – Order one unit from the current selection to move/build/gather/etc. and unselect it. Used to quickly dispatch units with specific tasks
+ O + Right Click – Order one unit from the current selection to move/build/gather/etc and unselect it. Used to quickly dispatch units with specific tasks.
Shift + Left Click when training units – Add units in batches (the batch size is 5 by default and can be changed in the options)
Shift + Left Click or Left Drag over unit on map – Add unit to selection
Ctrl + Left Click or Left Drag over unit on map – Remove unit from selection
@@ -112,10 +112,12 @@
Right Click with a structure(s) selected – Set a rally point for units created/ungarrisoned from that structure
Ctrl + Right Click with unit(s) selected:
• If the cursor is over an own or allied structure – Garrison
- • If the cursor is over an enemy unit/structure – Attack (instead of capture or gather)
+ • 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)
Ctrl + Q + Right Click with unit(s) selected – Attack move, only units along the way are targeted
Ctrl + Mouse Move near structures – Align the new structure with an existing nearby structure
+ 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/unit_actions.js
===================================================================
--- binaries/data/mods/public/gui/session/unit_actions.js
+++ binaries/data/mods/public/gui/session/unit_actions.js
@@ -84,7 +84,7 @@
return { "type": "move" };
},
- "specificness": 12,
+ "specificness": 13,
},
"attack-move":
@@ -134,7 +134,7 @@
"specificness": 30,
},
- "capture":
+ "attack-capture":
{
"execute": function(target, action, selection, queued)
{
@@ -142,7 +142,7 @@
"type": "attack",
"entities": selection,
"target": action.target,
- "allowCapture": true,
+ "prefAttackForms": ["Capture"],
"queued": queued
});
@@ -155,7 +155,7 @@
},
"getActionInfo": function(entState, targetState)
{
- if (!entState.attack || !targetState.hitpoints)
+ if (!entState.attack)
return false;
return {
@@ -168,19 +168,19 @@
},
"actionCheck": function(target, selection)
{
- if (!getActionInfo("capture", target, selection).possible)
+ if (!getActionInfo("attack-capture", target, selection).possible)
return false;
return {
- "type": "capture",
- "cursor": "action-capture",
+ "type": "attack-capture",
+ "cursor": "action-attack-capture",
"target": target
};
},
- "specificness": 9,
+ "specificness": 7,
},
- "attack":
+ "attack-nocapture":
{
"execute": function(target, action, selection, queued)
{
@@ -188,8 +188,8 @@
"type": "attack",
"entities": selection,
"target": action.target,
- "queued": queued,
- "allowCapture": false
+ "prefAttackForms": ["!Capture"],
+ "queued": queued
});
Engine.GuiInterfaceCall("PlaySound", {
@@ -215,23 +215,137 @@
"hotkeyActionCheck": function(target, selection)
{
if (!Engine.HotkeyIsPressed("session.attack") ||
- !getActionInfo("attack", target, selection).possible)
+ !getActionInfo("attack-nocapture", target, selection).possible)
return false;
return {
- "type": "attack",
- "cursor": "action-attack",
+ "type": "attack-nocapture",
+ // TODO: new cursor?
+ "cursor": "action-attack-melee",
"target": target
};
},
"actionCheck": function(target, selection)
{
- if (!getActionInfo("attack", target, selection).possible)
+ if (!getActionInfo("attack-nocapture", target, selection).possible)
+ return false;
+
+ return {
+ "type": "attack-nocapture",
+ "cursor": "action-attack-melee",
+ "target": target
+ };
+ },
+ "specificness": 8,
+ },
+ "attack-melee":
+ {
+ "execute": function(target, action, selection, queued)
+ {
+ Engine.PostNetworkCommand({
+ "type": "attack",
+ "entities": selection,
+ "target": action.target,
+ "prefAttackForms": ["Melee"],
+ "queued": queued
+ });
+
+ 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,
+ "types": ["Melee"]
+ })
+ };
+ },
+ "hotkeyActionCheck": function(target, selection)
+ {
+ if (!Engine.HotkeyIsPressed("session.meleeattack") || !getActionInfo("attack-melee", target, selection).possible)
+ return false;
+
+ return {
+ "type": "attack-melee",
+ "cursor": "action-attack-melee",
+ "target": target
+ };
+ },
+ "actionCheck": function(target, selection)
+ {
+ if (!getActionInfo("attack-melee", target, selection).possible)
+ return false;
+
+ return {
+ "type": "attack-melee",
+ "cursor": "action-attack-melee",
+ "target": target
+ };
+ },
+ "specificness": 9,
+ },
+
+ "attack-ranged":
+ {
+ "execute": function(target, action, selection, queued)
+ {
+ Engine.PostNetworkCommand({
"type": "attack",
- "cursor": "action-attack",
+ "entities": selection,
+ "target": action.target,
+ "prefAttackForms": ["Ranged"],
+ "queued": queued
+ });
+
+ 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,
+ "types": ["Ranged"]
+ })
+ };
+ },
+ "hotkeyActionCheck": function(target, selection)
+ {
+ if (!Engine.HotkeyIsPressed("session.rangedattack") || !getActionInfo("attack-ranged", target, selection).possible)
+ return false;
+
+ return {
+ "type": "attack-ranged",
+ "cursor": "action-attack-ranged",
+ "target": target
+ };
+ },
+ "actionCheck": function(target, selection)
+ {
+ if (!getActionInfo("attack-ranged", target, selection).possible)
+ return false;
+
+ return {
+ "type": "attack-ranged",
+ "cursor": "action-attack-ranged",
"target": target
};
},
@@ -249,8 +363,8 @@
"z": target.z,
"target": action.target,
"targetClasses": { "attack": g_PatrolTargets },
- "queued": queued,
- "allowCapture": false
+ "prefAttackForms": ["!Capture"],
+ "queued": queued
});
DrawTargetMarker(target);
@@ -337,7 +451,7 @@
"target": target
};
},
- "specificness": 7,
+ "specificness": 6,
},
"repair":
@@ -721,6 +835,7 @@
if (targetState.garrisonHolder.garrisonedEntitiesCount + extraCount >= targetState.garrisonHolder.capacity)
tooltip = coloredText(tooltip, "orange");
+ //TODO check for attackTypes
if (!MatchesClassList(entState.identity.classes, targetState.garrisonHolder.allowedClasses))
return false;
@@ -1001,8 +1116,9 @@
else if (playerCheck(entState, targetState, ["Enemy"]))
{
data.target = targetState.id;
- data.command = "attack";
- cursor = "action-attack";
+ // TODO other attackTypes should be allowed too, maybe this needs a rewrite of the rallypoint handling
+ data.command = "attack-nocapture";
+ cursor = "action-attack-melee";
}
// Don't allow the rally point to be set on any of the currently selected entities (used for unset)
@@ -1050,7 +1166,7 @@
"position": actionInfo.position
};
},
- "specificness": 6,
+ "specificness": 5,
},
"unset-rallypoint":
Index: binaries/data/mods/public/maps/random/danubius_triggers.js
===================================================================
--- binaries/data/mods/public/maps/random/danubius_triggers.js
+++ binaries/data/mods/public/maps/random/danubius_triggers.js
@@ -436,8 +436,8 @@
"type": "attack",
"entities": attackers,
"target": closestTarget,
- "queued": true,
- "allowCapture": false
+ "prefAttackForms": ["!Capture"],
+ "queued": true
});
let patrolTargets = shuffleArray(this.GetTriggerPoints(triggerPointRef)).slice(0, patrolCount);
@@ -454,8 +454,8 @@
"targetClasses": {
"attack": targetClass
},
- "queued": true,
- "allowCapture": false
+ "prefAttackForms": ["!Capture"],
+ "queued": true
});
}
};
Index: binaries/data/mods/public/maps/random/jebel_barkal_triggers.js
===================================================================
--- binaries/data/mods/public/maps/random/jebel_barkal_triggers.js
+++ binaries/data/mods/public/maps/random/jebel_barkal_triggers.js
@@ -476,8 +476,8 @@
"targetClasses": {
"attack": jebelBarkal_cityPatrolGroup_balancing.targetClasses()
},
- "queued": true,
- "allowCapture": false
+ "prefAttackForms": ["!Capture"],
+ "queued": true
});
}
}
@@ -578,8 +578,8 @@
"targetClasses": {
"attack": spawnPointBalancing.targetClasses()
},
- "queued": true,
- "allowCapture": false
+ "prefAttackForms": ["!Capture"],
+ "queued": true
});
}
}
Index: binaries/data/mods/public/maps/random/polar_sea_triggers.js
===================================================================
--- binaries/data/mods/public/maps/random/polar_sea_triggers.js
+++ binaries/data/mods/public/maps/random/polar_sea_triggers.js
@@ -87,6 +87,7 @@
"type": "attack",
"entities": attackers[spawnPoint],
"target": target,
+ "prefAttackForms": ["!Capture"],
"queued": true
});
}
Index: binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
===================================================================
--- binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
+++ binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
@@ -247,7 +247,7 @@
"x": targetPos.x,
"z": targetPos.y,
"targetClasses": undefined,
- "allowCapture": false,
+ "prefAttackForms": ["!Capture"],
"queued": true
});
Index: binaries/data/mods/public/maps/tutorials/introductory_tutorial.js
===================================================================
--- binaries/data/mods/public/maps/tutorials/introductory_tutorial.js
+++ binaries/data/mods/public/maps/tutorials/introductory_tutorial.js
@@ -380,7 +380,7 @@
entities.find(e => {
let cmpIdentity = Engine.QueryInterface(e, IID_Identity);
return cmpIdentity && cmpIdentity.HasClass("DefenseTower") && Engine.QueryInterface(e, IID_Position);
- }) ||
+ }) ||
entities.find(e => {
let cmpIdentity = Engine.QueryInterface(e, IID_Identity);
return cmpIdentity && cmpIdentity.HasClass("CivilCentre") && Engine.QueryInterface(e, IID_Position);
@@ -399,7 +399,7 @@
"x": position.x,
"z": position.y,
"targetClasses": { "attack": ["Unit"] },
- "allowCapture": false,
+ "prefAttackForms": ["!Capture"],
"queued": false
});
};
Index: binaries/data/mods/public/simulation/ai/common-api/entity.js
===================================================================
--- binaries/data/mods/public/simulation/ai/common-api/entity.js
+++ binaries/data/mods/public/simulation/ai/common-api/entity.js
@@ -230,23 +230,44 @@
};
},
+ // TODO: maybe DPS
"attackStrengths": function(type) {
let attackDamageTypes = this.get("Attack/" + type + "/Damage");
if (!attackDamageTypes)
return undefined;
- let damage = {};
+ let strength = {};
for (let damageType in attackDamageTypes)
- damage[damageType] = +attackDamageTypes[damageType];
+ strength[damageType] = +attackDamageTypes[damageType];
- return damage;
+ strength.Capture = +this.get("Attack/" + type + "/Capture") || 0;
+
+ return strength;
},
- "captureStrength": function() {
- if (!this.get("Attack/Capture"))
- return undefined;
+ "captureStrength": function(target) {
+ let strength = 0;
+ for (let type in this.get("Attack"))
+ strength = Math.max(strength, +(this.get("Attack/"+ type + "/Capture")|| 0) * this.getAttackBonus(this, target, type));
- return +this.get("Attack/Capture/Capture") || 0;
+ return strength;
+ },
+
+ "getAttackBonus": function(target, type) {
+ let attackBonus = 1;
+ if (!this.get("Attack/" + type) || !this.get("Attack/" + type + "/Bonuses"))
+ return attackBonus;
+ let bonuses = this.get("Attack/" + type + "/Bonuses");
+ for (let key in bonuses)
+ {
+ let bonus = bonuses[key];
+ if (bonus.Civ && bonus.Civ !== target.civ())
+ continue;
+ if (bonus.Classes && bonus.Classes.split(/\s+/).some(cls => !target.hasClass(cls)))
+ continue;
+ attackBonus *= bonus.Multiplier;
+ }
+ return attackBonus;
},
"attackTimes": function(type) {
@@ -523,12 +544,12 @@
"isGarrisonHolder": function() { return this.get("GarrisonHolder") !== undefined; },
/**
- * returns true if the tempalte can capture the given target entity
+ * returns true if the template can capture the given target entity
* if no target is given, returns true if the template has the Capture attack
*/
"canCapture": function(target)
{
- if (!this.get("Attack/Capture"))
+ if (!this.get("Attack") || Object.keys(this.get("Attack")).every(type => !this.get("Attack/" + type + "/Capture")))
return false;
if (!target)
return true;
@@ -748,22 +769,16 @@
* Derived from Attack.js' similary named function.
* @return {boolean} - Whether an entity can attack a given target.
*/
- "canAttackTarget": function(target, allowCapture)
+ "canAttackTarget": function(target)
{
let attackTypes = this.get("Attack");
if (!attackTypes)
return false;
- let canCapture = allowCapture && this.canCapture(target);
- let armourStrengths = target.get("Armour");
- if (!armourStrengths)
- return canCapture;
+ // TODO there was some stuff from allowCapture here, which did not esily translate
for (let type in attackTypes)
{
- if (type == "Capture" ? !canCapture : target.isInvulnerable())
- continue;
-
let restrictedClasses = this.get("Attack/" + type + "/RestrictedClasses/_string");
if (!restrictedClasses || !MatchesClassList(target.classes(), restrictedClasses))
return true;
@@ -782,8 +797,8 @@
return this;
},
- "attackMove": function(x, z, targetClasses, allowCapture = true, queued = false) {
- Engine.PostCommand(PlayerID, { "type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "targetClasses": targetClasses, "allowCapture": allowCapture, "queued": queued });
+ "attackMove": function(x, z, targetClasses, prefAttackForms = ["Capture"], prefAttackTypes = undefined, queued = false) {
+ Engine.PostCommand(PlayerID, { "type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "targetClasses": targetClasses, "prefAttackForms": prefAttackForms, "prefAttackTypes": prefAttackTypes, "queued": queued });
return this;
},
@@ -819,8 +834,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, prefAttackForms = ["Capture"], prefAttackTypes = undefined, queued = false) {
+ Engine.PostCommand(PlayerID, { "type": "attack", "entities": [this.id()], "target": unitId, "prefAttackForms": prefAttackForms, "prefAttackTypes": prefAttackTypes, "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
+++ binaries/data/mods/public/simulation/ai/common-api/entitycollection.js
@@ -155,10 +155,10 @@
return this;
};
-m.EntityCollection.prototype.attackMove = function(x, z, targetClasses, allowCapture = true, queued = false)
+m.EntityCollection.prototype.attackMove = function(x, z, targetClasses, prefAttackForms = ["Capture"], prefAttackTypes = undefined, queued = false)
{
Engine.PostCommand(PlayerID, { "type": "attack-walk", "entities": this.toIdArray(), "x": x, "z": z,
- "targetClasses": targetClasses, "allowCapture": allowCapture, "queued": queued });
+ "targetClasses": targetClasses, "prefAttackForms": prefAttackForms, "prefAttackTypes": prefAttackTypes, "queued": queued });
return this;
};
@@ -181,9 +181,9 @@
return this;
};
-m.EntityCollection.prototype.attack = function(unitId)
+m.EntityCollection.prototype.attack = function(unitId, prefAttackForms = ["Capture"], prefAttackTypes = undefined, queued = false)
{
- Engine.PostCommand(PlayerID, { "type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false });
+ Engine.PostCommand(PlayerID, { "type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false, "prefAttackForms": prefAttackForms, "prefAttackTypes": prefAttackTypes, "queued": queued });
return this;
};
Index: binaries/data/mods/public/simulation/ai/petra/attackManager.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/attackManager.js
+++ binaries/data/mods/public/simulation/ai/petra/attackManager.js
@@ -180,7 +180,7 @@
let access = PETRA.getLandAccess(gameState, ent);
for (let struct of gameState.getEnemyStructures().values())
{
- if (!ent.canAttackTarget(struct, PETRA.allowCapture(gameState, ent, struct)))
+ if (!ent.canAttackTarget(struct))
continue;
let structPos = struct.position();
@@ -225,7 +225,7 @@
attackingUnits.add(ent.id());
if (dist > range)
ent.move(x, z);
- ent.attack(struct.id(), false, dist > range);
+ ent.attack(struct.id(), ["!Capture"], undefined, dist > range);
break;
}
}
Index: binaries/data/mods/public/simulation/ai/petra/attackPlan.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/attackPlan.js
+++ binaries/data/mods/public/simulation/ai/petra/attackPlan.js
@@ -1323,16 +1323,15 @@
if (PETRA.isSiegeUnit(ent)) // needed as mauryan elephants are not filtered out
continue;
- let allowCapture = PETRA.allowCapture(gameState, ent, attacker);
- if (!ent.canAttackTarget(attacker, allowCapture))
+ if (!ent.canAttackTarget(attacker))
continue;
- ent.attack(attacker.id(), allowCapture);
+ ent.attack(attacker.id(), PETRA.getPrefAttackForms(gameState, ent, attacker));
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
// And if this attacker is a non-ranged siege unit and our unit also, attack it
- if (PETRA.isSiegeUnit(attacker) && attacker.hasClass("Melee") && ourUnit.hasClass("Melee") && ourUnit.canAttackTarget(attacker, PETRA.allowCapture(gameState, ourUnit, attacker)))
+ if (PETRA.isSiegeUnit(attacker) && attacker.hasClass("Melee") && ourUnit.hasClass("Melee") && ourUnit.canAttackTarget(attacker))
{
- ourUnit.attack(attacker.id(), PETRA.allowCapture(gameState, ourUnit, attacker));
+ ourUnit.attack(attacker.id(), PETRA.getPrefAttackForms(gameState, ourUnit, attacker));
ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
}
@@ -1349,10 +1348,9 @@
let collec = this.unitCollection.filter(API3.Filters.byClass("Melee")).filterNearest(ourUnit.position(), 5);
for (let ent of collec.values())
{
- let allowCapture = PETRA.allowCapture(gameState, ent, attacker);
- if (!ent.canAttackTarget(attacker, allowCapture))
+ if (!ent.canAttackTarget(attacker))
continue;
- ent.attack(attacker.id(), allowCapture);
+ ent.attack(attacker.id(), PETRA.getPrefAttackForms(gameState, ent, attacker));
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
}
@@ -1362,8 +1360,7 @@
let collec = this.unitCollection.filterNearest(ourUnit.position(), 2);
for (let ent of collec.values())
{
- let allowCapture = PETRA.allowCapture(gameState, ent, attacker);
- if (PETRA.isSiegeUnit(ent) || !ent.canAttackTarget(attacker, allowCapture))
+ if (PETRA.isSiegeUnit(ent) || !ent.canAttackTarget(attacker))
continue;
let orderData = ent.unitAIOrderData();
if (orderData && orderData.length && orderData[0].target)
@@ -1374,7 +1371,7 @@
if (target && !target.hasClass("Structure") && !target.hasClass("Support"))
continue;
}
- ent.attack(attacker.id(), allowCapture);
+ ent.attack(attacker.id(), PETRA.getPrefAttackForms(gameState, ent, attacker));
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
// Then the unit under attack: abandon its target (if it was a structure or a support) and retaliate
@@ -1391,10 +1388,9 @@
continue;
}
}
- let allowCapture = PETRA.allowCapture(gameState, ourUnit, attacker);
- if (ourUnit.canAttackTarget(attacker, allowCapture))
+ if (ourUnit.canAttackTarget(attacker))
{
- ourUnit.attack(attacker.id(), allowCapture);
+ ourUnit.attack(attacker.id(), PETRA.getPrefAttackForms(gameState, ourUnit, attacker));
ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
}
@@ -1547,7 +1543,7 @@
if (siegeUnit)
{
let mStruct = enemyStructures.filter(enemy => {
- if (!enemy.position() || !ent.canAttackTarget(enemy, PETRA.allowCapture(gameState, ent, enemy)))
+ if (!enemy.position() || !ent.canAttackTarget(enemy))
return false;
if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range)
return false;
@@ -1577,11 +1573,11 @@
return valb - vala;
});
if (mStruct[0].hasClass("Gates"))
- ent.attack(mStruct[0].id(), PETRA.allowCapture(gameState, ent, mStruct[0]));
+ ent.attack(mStruct[0].id(), PETRA.getPrefAttackForms(gameState, ent, mStruct[0]));
else
{
let rand = randIntExclusive(0, mStruct.length * 0.2);
- ent.attack(mStruct[rand].id(), PETRA.allowCapture(gameState, ent, mStruct[rand]));
+ ent.attack(mStruct[rand].id(), PETRA.getPrefAttackForms(gameState, ent, mStruct[rand]));
}
}
else
@@ -1599,7 +1595,7 @@
{
let nearby = !ent.hasClass("Cavalry") && !ent.hasClass("Ranged");
let mUnit = enemyUnits.filter(enemy => {
- if (!enemy.position() || !ent.canAttackTarget(enemy, PETRA.allowCapture(gameState, ent, enemy)))
+ if (!enemy.position() || !ent.canAttackTarget(enemy))
return false;
if (enemy.hasClass("Animal"))
return false;
@@ -1639,12 +1635,12 @@
return valb - vala;
});
let rand = randIntExclusive(0, mUnit.length * 0.1);
- ent.attack(mUnit[rand].id(), PETRA.allowCapture(gameState, ent, mUnit[rand]));
+ ent.attack(mUnit[rand].id(), PETRA.getPrefAttackForms(gameState, ent, mUnit[rand]));
}
// This may prove dangerous as we may be blocked by something we
// cannot attack. See similar behaviour at #5741.
- else if (this.isBlocked && ent.canAttackTarget(this.target, false))
- ent.attack(this.target.id(), false);
+ else if (this.isBlocked && ent.canAttackTarget(this.target))
+ ent.attack(this.target.id(), ["!Capture"]);
else if (API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500)
{
let targetClasses = targetClassesUnit;
@@ -1664,7 +1660,7 @@
let mStruct = enemyStructures.filter(enemy => {
if (this.isBlocked && enemy.id() != this.target.id())
return false;
- if (!enemy.position() || !ent.canAttackTarget(enemy, PETRA.allowCapture(gameState, ent, enemy)))
+ if (!enemy.position() || !ent.canAttackTarget(enemy))
return false;
if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range)
return false;
@@ -1688,11 +1684,11 @@
return valb - vala;
});
if (mStruct[0].hasClass("Gates"))
- ent.attack(mStruct[0].id(), false);
+ ent.attack(mStruct[0].id(), ["!Capture"]);
else
{
let rand = randIntExclusive(0, mStruct.length * 0.2);
- ent.attack(mStruct[rand].id(), PETRA.allowCapture(gameState, ent, mStruct[rand]));
+ ent.attack(mStruct[rand].id(), PETRA.getPrefAttackForms(gameState, ent, mStruct[rand]));
}
}
else if (needsUpdate) // really nothing let's try to help our nearest unit
@@ -1712,12 +1708,12 @@
if (dist > distmin)
return;
distmin = dist;
- if (!ent.canAttackTarget(target, PETRA.allowCapture(gameState, ent, target)))
+ if (!ent.canAttackTarget(target))
return;
attacker = target;
});
if (attacker)
- ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker));
+ ent.attack(attacker.id(), PETRA.getPrefAttackForms(gameState, ent, attacker));
}
}
}
@@ -1769,10 +1765,9 @@
if (ent.getMetadata(PlayerID, "transport") !== undefined)
continue;
- let allowCapture = PETRA.allowCapture(gameState, ent, attacker);
- if (!ent.isIdle() || !ent.canAttackTarget(attacker, allowCapture))
+ if (!ent.isIdle() || !ent.canAttackTarget(attacker))
continue;
- ent.attack(attacker.id(), allowCapture);
+ ent.attack(attacker.id(), PETRA.getPrefAttackForms(gameState, ent, attacker));
}
break;
}
Index: binaries/data/mods/public/simulation/ai/petra/defenseArmy.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/defenseArmy.js
+++ binaries/data/mods/public/simulation/ai/petra/defenseArmy.js
@@ -312,7 +312,7 @@
if (!eEnt || !eEnt.position()) // probably can't happen.
continue;
- if (!ent.canAttackTarget(eEnt, PETRA.allowCapture(gameState, ent, eEnt)))
+ if (!ent.canAttackTarget(eEnt))
continue;
if (eEnt.hasClass("Unit") && eEnt.unitAIOrderData() && eEnt.unitAIOrderData().length &&
@@ -358,7 +358,7 @@
{
this.assignedTo[entID] = idFoe;
this.assignedAgainst[idFoe].push(entID);
- ent.attack(idFoe, PETRA.allowCapture(gameState, ent, foeEnt), queued);
+ ent.attack(idFoe, PETRA.getPrefAttackForms(gameState, ent, foeEnt), undefined, queued);
}
else
gameState.ai.HQ.navalManager.requireTransport(gameState, ent, ownIndex, foeIndex, foePosition);
@@ -574,8 +574,8 @@
else if (orderData.length && orderData[0].target && orderData[0].attackType && orderData[0].attackType === "Capture")
{
let target = gameState.getEntityById(orderData[0].target);
- if (target && !PETRA.allowCapture(gameState, ent, target))
- ent.attack(orderData[0].target, false);
+ if (target)
+ ent.attack(orderData[0].target, PETRA.getPrefAttackForms(gameState, ent, target));
}
}
Index: binaries/data/mods/public/simulation/ai/petra/defenseManager.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/defenseManager.js
+++ binaries/data/mods/public/simulation/ai/petra/defenseManager.js
@@ -468,7 +468,7 @@
// Do not assign defender if it cannot attack at least part of the attacking army.
if (!armiesNeeding[a].army.foeEntities.some(eEnt => {
let eEntID = gameState.getEntityById(eEnt);
- return ent.canAttackTarget(eEntID, PETRA.allowCapture(gameState, ent, eEntID));
+ return ent.canAttackTarget(eEntID);
}))
continue;
@@ -713,7 +713,7 @@
if (allAttacked[entId])
continue;
let ent = gameState.getEntityById(entId);
- if (!ent || !ent.position() || !ent.canAttackTarget(attacker, PETRA.allowCapture(gameState, ent, attacker)))
+ if (!ent || !ent.position() || !ent.canAttackTarget(attacker))
continue;
// Check that the unit is still attacking the structure (since the last played turn).
let state = ent.unitAIState();
@@ -732,13 +732,12 @@
if (minEnt)
{
capturableTarget.ents.delete(minEnt.id());
- minEnt.attack(attacker.id(), PETRA.allowCapture(gameState, minEnt, attacker));
+ minEnt.attack(attacker.id(), PETRA.getPrefAttackForms(gameState, minEnt, attacker));
}
}
}
- let allowCapture = PETRA.allowCapture(gameState, target, attacker);
- if (target.canAttackTarget(attacker, allowCapture))
- target.attack(attacker.id(), allowCapture);
+ if (target.canAttackTarget(attacker))
+ target.attack(attacker.id(), PETRA.getPrefAttackForms(gameState, target, attacker));
}
}
};
Index: binaries/data/mods/public/simulation/ai/petra/entityExtend.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/entityExtend.js
+++ binaries/data/mods/public/simulation/ai/petra/entityExtend.js
@@ -132,16 +132,19 @@
PETRA.getSeaAccess(gameState, ent);
};
-/** Decide if we should try to capture (returns true) or destroy (return false) */
-PETRA.allowCapture = function(gameState, ent, target)
+// TODO: probably use some DPS to choose some specific type.
+/**
+ * Decide if we should try to capture, melee or ranged attack.
+*/
+PETRA.getPrefAttackForms = function(gameState, ent, target)
{
if (!target.isCapturable() || !ent.canCapture(target))
- return false;
+ return ["!Capture"];
if (target.isInvulnerable())
- return true;
+ return ["Capture"];
// always try to recapture cp from an allied, except if it's decaying
if (gameState.isPlayerAlly(target.owner()))
- return !target.decaying();
+ return target.decaying() ? ["!Capture"] : ["Capture"];
let antiCapture = target.defaultRegenRate();
if (target.isGarrisonHolder() && target.garrisoned())
@@ -153,7 +156,7 @@
let capturableTargets = gameState.ai.HQ.capturableTargets;
if (!capturableTargets.has(target.id()))
{
- capture = ent.captureStrength() * PETRA.getAttackBonus(ent, target, "Capture");
+ capture = ent.captureStrength(target);
capturableTargets.set(target.id(), { "strength": capture, "ents": new Set([ent.id()]) });
}
else
@@ -161,7 +164,7 @@
let capturable = capturableTargets.get(target.id());
if (!capturable.ents.has(ent.id()))
{
- capturable.strength += ent.captureStrength() * PETRA.getAttackBonus(ent, target, "Capture");
+ capturable.strength += ent.captureStrength(target);
capturable.ents.add(ent.id());
}
capture = capturable.strength;
@@ -169,26 +172,8 @@
capture *= 1 / (0.1 + 0.9*target.healthLevel());
let sumCapturePoints = target.capturePoints().reduce((a, b) => a + b);
if (target.hasDefensiveFire() && target.isGarrisonHolder() && target.garrisoned())
- return capture > antiCapture + sumCapturePoints/50;
- return capture > antiCapture + sumCapturePoints/80;
-};
-
-PETRA.getAttackBonus = function(ent, target, type)
-{
- let attackBonus = 1;
- if (!ent.get("Attack/" + type) || !ent.get("Attack/" + type + "/Bonuses"))
- return attackBonus;
- let bonuses = ent.get("Attack/" + type + "/Bonuses");
- for (let key in bonuses)
- {
- let bonus = bonuses[key];
- if (bonus.Civ && bonus.Civ !== target.civ())
- continue;
- if (bonus.Classes && bonus.Classes.split(/\s+/).some(cls => !target.hasClass(cls)))
- continue;
- attackBonus *= bonus.Multiplier;
- }
- return attackBonus;
+ return capture > antiCapture + sumCapturePoints/50 ? ["Capture"] : ["!Capture"];
+ return capture > antiCapture + sumCapturePoints/80 ? ["Capture"] : ["!Capture"];
};
/** Makes the worker deposit the currently carried resources at the closest accessible dropsite */
Index: binaries/data/mods/public/simulation/ai/petra/headquarters.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/headquarters.js
+++ binaries/data/mods/public/simulation/ai/petra/headquarters.js
@@ -2501,13 +2501,13 @@
continue;
if (!this.capturableTargets.has(targetId))
this.capturableTargets.set(targetId, {
- "strength": ent.captureStrength() * PETRA.getAttackBonus(ent, target, "Capture"),
+ "strength": ent.captureStrength(target),
"ents": new Set([ent.id()])
});
else
{
let capturableTarget = this.capturableTargets.get(target.id());
- capturableTarget.strength += ent.captureStrength() * PETRA.getAttackBonus(ent, target, "Capture");
+ capturableTarget.strength += ent.captureStrength(target);
capturableTarget.ents.add(ent.id());
}
}
@@ -2515,17 +2515,16 @@
for (let [targetId, capturableTarget] of this.capturableTargets)
{
let target = gameState.getEntityById(targetId);
- let allowCapture;
+ let prefAttackForms;
for (let entId of capturableTarget.ents)
{
let ent = gameState.getEntityById(entId);
- if (allowCapture === undefined)
- allowCapture = PETRA.allowCapture(gameState, ent, target);
+ if (prefAttackForms === undefined)
+ prefAttackForms = PETRA.getPrefAttackForms(gameState, ent, target);
let orderData = ent.unitAIOrderData();
if (!orderData || !orderData.length || !orderData[0].attackType)
continue;
- if ((orderData[0].attackType == "Capture") !== allowCapture)
- ent.attack(targetId, allowCapture);
+ ent.attack(targetId, prefAttackForms);
}
}
Index: binaries/data/mods/public/simulation/ai/petra/victoryManager.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/victoryManager.js
+++ binaries/data/mods/public/simulation/ai/petra/victoryManager.js
@@ -659,7 +659,7 @@
if (!attack)
continue;
for (let ent of attack.unitCollection.values())
- capture += ent.captureStrength() * PETRA.getAttackBonus(ent, relic, "Capture");
+ capture += ent.captureStrength(relic);
}
// No need to make a new attack if already enough units
if (capture > sumCapturePoints / 50)
@@ -689,7 +689,7 @@
let expedition = [];
for (let ent of units.values())
{
- capture += ent.captureStrength() * PETRA.getAttackBonus(ent, relic, "Capture");
+ capture += ent.captureStrength(relic);
expedition.push(ent);
if (capture > sumCapturePoints / 25)
break;
Index: binaries/data/mods/public/simulation/ai/petra/worker.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/worker.js
+++ binaries/data/mods/public/simulation/ai/petra/worker.js
@@ -180,10 +180,10 @@
if (orderData && orderData.target && orderData.attackType && orderData.attackType == "Capture")
{
// If we are here, an enemy structure must have targeted one of our workers
- // and UnitAI sent it fight back with allowCapture=true
+ // and UnitAI sent it fight back with prefAttackForms === undefined
let target = gameState.getEntityById(orderData.target);
if (target && target.owner() > 0 && !gameState.isPlayerAlly(target.owner()))
- ent.attack(orderData.target, PETRA.allowCapture(gameState, ent, target));
+ ent.attack(orderData.target, PETRA.getPrefAttackForms(gameState, ent, target));
}
}
return;
Index: binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/Attack.js
+++ binaries/data/mods/public/simulation/components/Attack.js
@@ -1,7 +1,5 @@
function Attack() {}
-var g_AttackTypes = ["Melee", "Ranged", "Capture"];
-
Attack.prototype.preferredClassesSchema =
"" +
"" +
@@ -94,117 +92,92 @@
"4.0" +
"" +
"" +
- "" +
- "" +
- "" +
- Attacking.BuildAttackEffectsSchema() +
- "" +
- "" +
- "" +
- "" +
- "" + // TODO: it shouldn't be stretched
- "" +
- "" +
- Attack.prototype.preferredClassesSchema +
- Attack.prototype.restrictedClassesSchema +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- Attacking.BuildAttackEffectsSchema() +
- "" +
- "" +
- ""+
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
"" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- Attacking.BuildAttackEffectsSchema() +
- "" +
+ Attacking.BuildAttackEffectsSchema() +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
"" +
- "" +
- "" +
- "" +
- "" +
- "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ Attacking.BuildAttackEffectsSchema() +
+ "" +
"" +
- "" +
- "" +
- "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
"" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- Attack.prototype.preferredClassesSchema +
- Attack.prototype.restrictedClassesSchema +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- Attacking.BuildAttackEffectsSchema() +
- "" +
- "" + // TODO: it shouldn't be stretched
- "" +
- "" +
- Attack.prototype.preferredClassesSchema +
- Attack.prototype.restrictedClassesSchema +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- Attacking.BuildAttackEffectsSchema() +
- "" + // TODO: how do these work?
- Attack.prototype.preferredClassesSchema +
- Attack.prototype.restrictedClassesSchema +
- "" +
- "" +
- "";
+ "" +
+ Attack.prototype.preferredClassesSchema +
+ Attack.prototype.restrictedClassesSchema +
+ "" +
+ "" +
+ "" +
+ "";
Attack.prototype.Init = function()
{
@@ -214,7 +187,7 @@
Attack.prototype.GetAttackTypes = function(wantedTypes)
{
- let types = g_AttackTypes.filter(type => !!this.template[type]);
+ let types = Object.keys(this.template);
if (!wantedTypes)
return types;
@@ -223,6 +196,17 @@
(!wantedTypesReal || !wantedTypesReal.length || wantedTypesReal.indexOf(type) != -1));
};
+Attack.prototype.GetAttackTypesFromForms = function(forms)
+{
+ let types = Object.keys(this.template);
+ if (!forms)
+ return types;
+
+ let wantedForms = forms.filter(form => form.indexOf("!") != 0);
+ return types.filter(type => forms.indexOf("!" + this.template[type].Form) == -1 &&
+ (!wantedForms || !wantedForms.length || wantedForms.indexOf(this.template[type].Form) != -1));
+};
+
Attack.prototype.GetPreferredClasses = function(type)
{
if (this.template[type] && this.template[type].PreferredClasses &&
@@ -243,50 +227,61 @@
Attack.prototype.CanAttack = function(target, wantedTypes)
{
+ return this.GetAllowedAttackTypes(target, wantedTypes).length > 0
+};
+
+Attack.prototype.GetAllowedAttackTypes = function(target, wantedTypes)
+{
+ let types = this.GetAttackTypes(wantedTypes);
let cmpFormation = Engine.QueryInterface(target, IID_Formation);
if (cmpFormation)
- return true;
+ return types;
let cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position);
let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld())
- return false;
+ return [];
- let cmpIdentity = QueryMiragedInterface(target, IID_Identity);
- if (!cmpIdentity)
- return false;
+ let isTurret = cmpThisPosition.GetTurretParent() != INVALID_ENTITY
- let cmpHealth = QueryMiragedInterface(target, IID_Health);
- let targetClasses = cmpIdentity.GetClassesList();
- if (targetClasses.indexOf("Domestic") != -1 && this.template.Slaughter && cmpHealth && cmpHealth.GetHitpoints() &&
- (!wantedTypes || !wantedTypes.filter(wType => wType.indexOf("!") != 0).length))
- return true;
+ let cmpTargetIdentity = QueryMiragedInterface(target, IID_Identity);
+ if (!cmpTargetIdentity)
+ return [];
+ let targetClasses = cmpTargetIdentity.GetClassesList();
let cmpEntityPlayer = QueryOwnerInterface(this.entity);
- let cmpTargetPlayer = QueryOwnerInterface(target);
- if (!cmpTargetPlayer || !cmpEntityPlayer)
- return false;
+ if (!cmpEntityPlayer)
+ return [];
- let types = this.GetAttackTypes(wantedTypes);
let entityOwner = cmpEntityPlayer.GetPlayerID();
- let targetOwner = cmpTargetPlayer.GetPlayerID();
- let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
+
+ let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
// 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 types)
- {
- if (type != "Capture" && (!cmpEntityPlayer.IsEnemy(targetOwner) || !cmpHealth || !cmpHealth.GetHitpoints()))
- continue;
-
- if (type == "Capture" && (!cmpCapturable || !cmpCapturable.CanCapture(entityOwner)))
- continue;
+ return types.filter(type => {
+ let attackEffects = this.GetAttackEffectsData(type, false);
+ let attackEffectsSplash = this.GetAttackEffectsData(type, true);
+ // We can't use the type if we can't cause any effect.
+ if (g_EffectTypes.every(effectType => {
+ if (!attackEffects[effectType] && !attackEffectsSplash[effectType])
+ return true;
+
+ let receiver = g_EffectReceiver[effectType];
+ let cmpReceiver = QueryMiragedInterface(target, global[receiver.IID]);
+ return !cmpReceiver || !cmpReceiver[receiver.canTakeEffectMethod](entityOwner);
+ }))
+ return false;
+
+ // If we are visisble garrisoned always do attacks with projectiles.
+ if (isTurret && this.GetRange(type).max < cmpObstructionManager.DistanceToTarget(this.entity, target))
+ return false;
if (heightDiff > this.GetRange(type).max)
- continue;
+ return false;
let restrictedClasses = this.GetRestrictedClasses(type);
if (!restrictedClasses.length)
@@ -294,9 +289,7 @@
if (!MatchesClassList(targetClasses, restrictedClasses))
return true;
- }
-
- return false;
+ });
};
/**
@@ -344,52 +337,111 @@
Attack.prototype.GetAttackEffectsData = function(type, splash)
{
let template = this.template[type];
- if (splash)
+ if (splash && template.Splash)
template = template.Splash;
+
return Attacking.GetAttackEffectsData("Attack/" + type + (splash ? "/Splash" : ""), template, this.entity);
};
/**
* Find the best attack against a target.
* @param {number} target - The entity-ID of the target.
- * @param {boolean} allowCapture - Whether capturing is allowed.
+ * @param {Array} prefAttackForms - List of (negated) attack forms which are prefered above others.
+ * @param {Array} prefAttackTypes - List of (negated) attack types which are prefered above others.
* @return {string} - The preferred attack type.
*/
-Attack.prototype.GetBestAttackAgainst = function(target, allowCapture)
+Attack.prototype.GetBestAttackAgainst = function(target, prefAttackForms = [], prefAttackTypes = [])
{
+ let prefFormTypes = this.GetAttackTypesFromForms(prefAttackForms);
let cmpFormation = Engine.QueryInterface(target, IID_Formation);
if (cmpFormation)
{
// TODO: Formation against formation needs review
- let types = this.GetAttackTypes();
- return g_AttackTypes.find(attack => types.indexOf(attack) != -1);
+ let types = this.GetAttackTypes(prefAttackTypes.concat(prefFormTypes));
+ if (types.length)
+ return types[0];
+ types = this.GetAttackTypes(prefFormTypes);
+ if (types.length)
+ return types[0];
+ types = this.GetAttackTypes(prefAttackTypes);
+ if (types.length)
+ return types[0];
+ types = this.GetAttackTypes()
+ return types.length ? types[0] : 0;
+
}
- let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
- if (!cmpIdentity)
+ let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+ if (!cmpPosition || !cmpPosition.IsInWorld())
+ return undefined;
+
+ let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership)
+ if (!cmpOwnership)
return undefined;
+ let attackerOwner = cmpOwnership.GetOwner();
- // Always slaughter domestic animals instead of using a normal attack
- if (this.template.Slaughter && cmpIdentity.HasClass("Domestic"))
- return "Slaughter";
-
- let types = this.GetAttackTypes().filter(type => this.CanAttack(target, [type]));
-
- // Check whether the target is capturable and prefer that when it is allowed.
- let captureIndex = types.indexOf("Capture");
- if (captureIndex != -1)
+ // Try find an allowed attack preferred by prefAttackForms and prefAttackTypes.
+ // If none exists, try all prefered by prefAttackForms or prefAttackTypes respectively.
+ // If still none exists, try all all types in the template
+ let types = this.GetAllowedAttackTypes(target, prefAttackTypes.concat(prefFormTypes));
+ if (!types.length)
{
- if (allowCapture)
- return "Capture";
- types.splice(captureIndex, 1);
+ types = this.GetAllowedAttackTypes(target, prefFormTypes);
+ if (!types.length)
+ {
+ types = this.GetAllowedAttackTypes(target, prefAttackTypes);
+ if (!types.length)
+ {
+ types = this.GetAllowedAttackTypes(target);
+ if (!types.length)
+ return undefined;
+ }
+ }
}
- let targetClasses = cmpIdentity.GetClassesList();
- let isPreferred = attackType => MatchesClassList(targetClasses, this.GetPreferredClasses(attackType));
+ // For performance: don't worry calculating the best when there is only one
+ if (types.length == 1)
+ return types[0];
+
+ let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
+ let distance = cmpObstructionManager.DistanceToTarget(this.entity, target);
+ let fullRange = this.GetFullAttackRange();
+ // Choose the best attack on a DPS/Range
+ let bestType;
+ let bestDPSRange = -Infinity;
+ for (let type of types)
+ {
+ let DPSRange = 0;
+ let attackEffects = this.GetAttackEffectsData(type, false);
+ let multiplier = GetAttackBonus(this.entity, target, type, attackEffects.Bonuses || {});
+ for (let effectType of g_EffectTypes)
+ {
+ if (!attackEffects[effectType])
+ continue;
+
+ let receiver = g_EffectReceiver[effectType];
+ let cmpReceiver = QueryMiragedInterface(target, global[receiver.IID]);
+ if (!cmpReceiver)
+ continue;
+
+ DPSRange += cmpReceiver[receiver.getRelativeEffectMethod](attackEffects[effectType], effectType, attackerOwner) * multiplier;
+ }
+ DPSRange /= this.GetRepeatTime(type);
+
+ // TODO elevation?
+ let range = this.GetRange(type);
+ if (distance < range.min)
+ DPSRange *= Math.pow(0.2, (range.min - distance) / (fullRange.min || 1));
+ else if (distance > range.max)
+ DPSRange *= Math.pow(0.2, (distance - range.max) / (fullRange.max || 1));
- return types.sort((a, b) =>
- (types.indexOf(a) + (isPreferred(a) ? types.length : 0)) -
- (types.indexOf(b) + (isPreferred(b) ? types.length : 0))).pop();
+ if (DPSRange > bestDPSRange)
+ {
+ bestType = type;
+ bestDPSRange = DPSRange;
+ }
+ }
+ return bestType
};
Attack.prototype.CompareEntitiesByPreference = function(a, b)
@@ -403,22 +455,22 @@
return aPreference - bPreference;
};
+// TODO what if !this.template[type]
Attack.prototype.GetRepeatTime = function(type)
{
- let repeatTime = 1000;
-
- if (this.template[type] && this.template[type].RepeatTime)
- repeatTime = +this.template[type].RepeatTime;
-
- return ApplyValueModificationsToEntity("Attack/" + type + "/RepeatTime", repeatTime, this.entity);
+ if (this.template[type])
+ return ApplyValueModificationsToEntity("Attack/" + type + "/RepeatTime", +this.template[type].RepeatTime, this.entity);
+ return 0;
};
Attack.prototype.GetTimers = function(type)
{
- return {
- "prepare": ApplyValueModificationsToEntity("Attack/" + type + "/PrepareTime", +(this.template[type].PrepareTime || 0), this.entity),
- "repeat": this.GetRepeatTime(type)
- };
+ if (this.template[type])
+ return {
+ "prepare": ApplyValueModificationsToEntity("Attack/" + type + "/PrepareTime", +(this.template[type].PrepareTime || 0), this.entity),
+ "repeat": this.GetRepeatTime(type)
+ };
+ return {};
};
Attack.prototype.GetSplashData = function(type)
@@ -451,6 +503,17 @@
return { "max": max, "min": min, "elevationBonus": elevationBonus };
};
+Attack.prototype.GetForm = function(type)
+{
+ return this.template[type] ? this.template[type].Form : "";
+};
+
+Attack.prototype.HasProjectile = function(type)
+{
+ return !!(this.template[type] && this.template[type].Projectile);
+};
+
+
/**
* Attack the target entity. This should only be called after a successful range check,
* and should only be called after GetTimers().repeat msec has passed since the last
@@ -458,12 +521,38 @@
*/
Attack.prototype.PerformAttack = function(type, target)
{
- let attackerOwner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
+ // Safety check, TODO find out if it is required
+ if (!this.CanAttack(target, [type]))
+ return;
+
+ let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+ if (!cmpPosition || !cmpPosition.IsInWorld())
+ return;
+ let selfPosition = cmpPosition.GetPosition();
+
+ let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
+ if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
+ return;
+ let targetPosition = cmpTargetPosition.GetPosition();
+
+ let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership)
+ if (!cmpOwnership)
+ return;
+ let attackerOwner = cmpOwnership.GetOwner();
+
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
- // If this is a ranged attack, then launch a projectile
- if (type == "Ranged")
+ let data = {
+ "type": type,
+ "attackData": this.GetAttackEffectsData(type),
+ "attacker": this.entity,
+ "target": target,
+ "attackerOwner": attackerOwner,
+ "splash": this.GetSplashData(type)
+ };
+ // When we have a projectile, launch it.
+ if (this.template[type].Projectile)
{
- let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
let turnLength = cmpTimer.GetLatestTurnLength()/1000;
// In the future this could be extended:
// * Obstacles like trees could reduce the probability of the target being hit
@@ -473,15 +562,6 @@
let gravity = +this.template[type].Projectile.Gravity;
// horizSpeed /= 2; gravity /= 2; // slow it down for testing
- let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
- if (!cmpPosition || !cmpPosition.IsInWorld())
- return;
- let selfPosition = cmpPosition.GetPosition();
- let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
- if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
- return;
- let targetPosition = cmpTargetPosition.GetPosition();
-
let previousTargetPosition = Engine.QueryInterface(target, IID_Position).GetPreviousPosition();
let targetVelocity = Vector3D.sub(targetPosition, previousTargetPosition).div(turnLength);
@@ -489,7 +569,7 @@
let predictedPosition = (timeToTarget !== false) ? Vector3D.mult(targetVelocity, timeToTarget).add(targetPosition) : targetPosition;
// Add inaccuracy based on spread.
- let distanceModifiedSpread = ApplyValueModificationsToEntity("Attack/Ranged/Spread", +this.template[type].Projectile.Spread, this.entity) *
+ let distanceModifiedSpread = ApplyValueModificationsToEntity("Attack/" + type + "/Spread", +this.template[type].Projectile.Spread, this.entity) *
predictedPosition.horizDistanceTo(selfPosition) / 100;
let randNorm = randomNormal2D();
@@ -497,12 +577,14 @@
let offsetZ = randNorm[1] * distanceModifiedSpread;
let realTargetPosition = new Vector3D(predictedPosition.x + offsetX, targetPosition.y, predictedPosition.z + offsetZ);
+ data.position = realTargetPosition;
// Recalculate when the missile will hit the target position.
let realHorizDistance = realTargetPosition.horizDistanceTo(selfPosition);
timeToTarget = realHorizDistance / horizSpeed;
let missileDirection = Vector3D.sub(realTargetPosition, selfPosition).div(realHorizDistance);
+ data.direction = Vector3D.sub(realTargetPosition, selfPosition).div(realHorizDistance);
// Launch the graphical projectile.
let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
@@ -532,31 +614,22 @@
launchPoint = visualActorLaunchPoint;
}
- let id = cmpProjectileManager.LaunchProjectileAtPoint(launchPoint, realTargetPosition, horizSpeed, gravity, actorName, impactActorName, impactAnimationLifetime);
+ data.projectileId = cmpProjectileManager.LaunchProjectileAtPoint(launchPoint, realTargetPosition, horizSpeed, gravity, actorName, impactActorName, impactAnimationLifetime)
- let attackImpactSound = "";
let cmpSound = Engine.QueryInterface(this.entity, IID_Sound);
- if (cmpSound)
- attackImpactSound = cmpSound.GetSoundGroup("attack_impact_" + type.toLowerCase());
+ data.attackImpactSound = cmpSound ? cmpSound.GetSoundGroup("attack_impact_" + type.toLowerCase()) : "";
- let data = {
- "type": type,
- "attackData": this.GetAttackEffectsData(type),
- "target": target,
- "attacker": this.entity,
- "attackerOwner": attackerOwner,
- "position": realTargetPosition,
- "direction": missileDirection,
- "projectileId": id,
- "attackImpactSound": attackImpactSound,
- "splash": this.GetSplashData(type),
- "friendlyFire": this.template[type].Projectile.FriendlyFire == "true",
- };
+ data.friendlyFire = this.template[type].Projectile.FriendlyFire == "true";
- cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_DelayedDamage, "MissileHit", +this.template[type].Delay + timeToTarget * 1000, data);
+ cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_DelayedDamage, "MissileHit", timeToTarget * 1000 + +(this.template[type].Delay || 0), data);
}
+ // Close attack, hurt the target immediately after this.template.Delay
else
- Attacking.HandleAttackEffects(type, this.GetAttackEffectsData(type), target, this.entity, attackerOwner);
+ {
+ data.position = targetPosition;
+ data.direction = new Vector3D.sub(targetPosition, selfPosition);
+ cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_DelayedDamage, "CauseAttackEffects", +(this.template[type].Delay || 0), data);
+ }
};
/**
Index: binaries/data/mods/public/simulation/components/Capturable.js
===================================================================
--- binaries/data/mods/public/simulation/components/Capturable.js
+++ binaries/data/mods/public/simulation/components/Capturable.js
@@ -40,6 +40,16 @@
return this.garrisonRegenRate;
};
+Capturable.prototype.GetRelativeCapture = function(effectData, effectType, attackerOwner)
+{
+ let cmpHealth = QueryMiragedInterface(this.entity, IID_Health);
+ let hitpoints = cmpHealth && cmpHealth.GetHitpoints();
+ if (attackerOwner == INVALID_PLAYER || !this.CanCapture(attackerOwner) || hitpoints)
+ return 0;
+
+ return Attacking.GetTotalAttackEffects({ "Capture": effectData }, effectType) * maxHitpoints / (0.1 * maxHitpoints + 0.9 * hitpoints) / this.maxCp;
+};
+
/**
* Set the new capture points, used for cloning entities
* The caller should assure that the sum of capture points
@@ -53,12 +63,12 @@
Capturable.prototype.Capture = function(effectData, attacker, attackerOwner, bonusMultiplier)
{
let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
-
- if (attackerOwner == INVALID_PLAYER || !this.CanCapture(attackerOwner) ||
- !cmpHealth || cmpHealth.GetHitpoints() == 0)
+ let hitpoints = cmpHealth && cmpHealth.GetHitpoints();
+ if (attackerOwner == INVALID_PLAYER || !this.CanCapture(attackerOwner) || !hitpoints)
return {};
- bonusMultiplier *= cmpHealth.GetMaxHitpoints() / (0.1 * cmpHealth.GetMaxHitpoints() + 0.9 * cmpHealth.GetHitpoints());
+ let maxHitpoints = cmpHealth.GetMaxHitpoints();
+ bonusMultiplier *= maxHitpoints / (0.1 * maxHitpoints + 0.9 * hitpoints);
let total = Attacking.GetTotalAttackEffects({ "Capture": effectData }, "Capture") * bonusMultiplier;
Index: binaries/data/mods/public/simulation/components/DelayedDamage.js
===================================================================
--- binaries/data/mods/public/simulation/components/DelayedDamage.js
+++ binaries/data/mods/public/simulation/components/DelayedDamage.js
@@ -22,7 +22,6 @@
* @param {number} data.target - The entity id of the target.
* @param {number} data.attacker - The entity id of the attacker.
* @param {number} data.attackerOwner - The player id of the owner of the attacker.
- * @param {Vector2D} data.origin - The origin of the projectile hit.
* @param {Vector3D} data.position - The expected position of the target.
* @param {number} data.projectileId - The id of the projectile.
* @param {Vector3D} data.direction - The unit vector defining the direction.
@@ -91,4 +90,45 @@
}
};
+/**
+ * Handles damage caused by immediate attack.
+ * @param {Object} data - The data sent by the caller.
+ * @param {string} data.type - The type of damage.
+ * @param {Object} data.attackData - Data of the form { 'effectType': { ...opaque effect data... }, 'Bonuses': {...} }.
+ * @param {number} data.target - The entity id of the target.
+ * @param {number} data.attacker - The entity id of the attacker.
+ * @param {number} data.attackerOwner - The player id of the owner of the attacker.
+ * @param {Vector3D} data.position - The expected position of the target.
+ * @param {Vector3D} data.direction - The unit vector defining the direction.
+ * @param {boolean} data.friendlyFire - A flag indicating whether allied entities can also be damaged.
+ * ***When splash damage***
+ * @param {boolean} data.splash.friendlyFire - A flag indicating if allied entities are also damaged.
+ * @param {number} data.splash.radius - The radius of the splash damage.
+ * @param {string} data.splash.shape - The shape of the splash range.
+ * @param {Object} data.splash.attackData - same as attackData, for splash.
+ */
+DelayedDamage.prototype.CauseAttackEffects = function(data, lateness)
+{
+ if (!data.position)
+ return;
+
+ // Do this first in case the direct hit kills the target
+ if (data.splash)
+ {
+ Attacking.CauseDamageOverArea({
+ "type": data.type,
+ "attackData": data.splash.attackData,
+ "attacker": data.attacker,
+ "attackerOwner": data.attackerOwner,
+ "origin": Vector2D.from3D(data.position),
+ "radius": data.splash.radius,
+ "shape": data.splash.shape,
+ "direction": data.direction,
+ "friendlyFire": data.splash.friendlyFire
+ });
+ }
+
+ Attacking.HandleAttackEffects(data.type, data.attackData, data.target, data.attacker, data.attackerOwner);
+};
+
Engine.RegisterSystemComponentType(IID_DelayedDamage, "DelayedDamage", DelayedDamage);
Index: binaries/data/mods/public/simulation/components/Fogging.js
===================================================================
--- binaries/data/mods/public/simulation/components/Fogging.js
+++ binaries/data/mods/public/simulation/components/Fogging.js
@@ -132,6 +132,14 @@
if (cmpCapturable)
cmpMirage.CopyCapturable(cmpCapturable);
+ var cmpStatusEffectsReceiver = Engine.QueryInterface(this.entity, IID_StatusEffectsReceiver);
+ if (cmpStatusEffectsReceiver)
+ cmpMirage.CopyStatusEffectsReceiver(cmpStatusEffectsReceiver);
+
+ var cmpResistance = Engine.QueryInterface(this.entity, IID_Resistance);
+ if (cmpCapturable)
+ cmpMirage.CopyResistance(cmpResistance);
+
var cmpResourceSupply = Engine.QueryInterface(this.entity, IID_ResourceSupply);
if (cmpResourceSupply)
cmpMirage.CopyResourceSupply(cmpResourceSupply);
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js
+++ binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -411,6 +411,7 @@
ret.attack[type] = {};
Object.assign(ret.attack[type], cmpAttack.GetAttackEffectsData(type));
+ ret.attack[type].form = cmpAttack.GetForm(type);
ret.attack[type].splash = cmpAttack.GetSplashData(type);
if (ret.attack[type].splash)
@@ -419,21 +420,7 @@
let range = cmpAttack.GetRange(type);
ret.attack[type].minRange = range.min;
ret.attack[type].maxRange = range.max;
-
- let timers = cmpAttack.GetTimers(type);
- ret.attack[type].prepareTime = timers.prepare;
- ret.attack[type].repeatTime = timers.repeat;
-
- if (type != "Ranged")
- {
- // Not a ranged attack, set some defaults.
- ret.attack[type].elevationBonus = 0;
- ret.attack[type].elevationAdaptedRange = ret.attack.maxRange;
- continue;
- }
-
ret.attack[type].elevationBonus = range.elevationBonus;
-
if (cmpPosition && cmpPosition.IsInWorld())
// For units, take the range in front of it, no spread, so angle = 0,
// else, take the average elevation around it: angle = 2 * pi.
@@ -441,6 +428,12 @@
else
// Not in world, set a default?
ret.attack[type].elevationAdaptedRange = ret.attack.maxRange;
+
+ let timers = cmpAttack.GetTimers(type);
+ ret.attack[type].prepareTime = timers.prepare;
+ ret.attack[type].repeatTime = timers.repeat;
+
+ ret.attack[type].projectile = cmpAttack.HasProjectile(type);
}
}
Index: binaries/data/mods/public/simulation/components/Health.js
===================================================================
--- binaries/data/mods/public/simulation/components/Health.js
+++ binaries/data/mods/public/simulation/components/Health.js
@@ -79,6 +79,18 @@
};
/**
+ * Get damage according to the entity's resistance.
+ * @return {number} -
+ */
+Health.prototype.GetRelativeDamage = function(effectData, effectType, attackerOwner)
+{
+ let cmpResistance = QueryMiragedInterface(this.entity, IID_Resistance);
+ if (cmpResistance && cmpResistance.IsInvulnerable())
+ return 0;
+
+ return Attacking.GetTotalAttackEffects(effectData, effectType, cmpResistance) / this.maxHitpoints;
+};
+/**
* @return {boolean} Whether the units are injured. Dead units are not considered injured.
*/
Health.prototype.IsInjured = function()
@@ -107,6 +119,18 @@
this.RegisterHealthChanged(old);
};
+// TODO: Invulnerabe targets should maybe be excluded.
+Health.prototype.CanAttack = function(playerID)
+{
+ let cmpIdentity = QueryMiragedInterface(this.entity, IID_Identity);
+ let cmpPlayerEntity = QueryOwnerInterface(this.entity);
+ let cmpPlayerSource = QueryPlayerIDInterface(playerID);
+ if (!cmpIdentity || !cmpPlayerEntity || !cmpPlayerSource)
+ return false;
+
+ return this.hitpoints > 0 && (cmpPlayerSource.IsEnemy(cmpPlayerEntity.GetPlayerID()) || cmpIdentity.GetClassesList().indexOf("Domestic") != -1);
+};
+
Health.prototype.IsRepairable = function()
{
return Engine.QueryInterface(this.entity, IID_Repairable) != null;
Index: binaries/data/mods/public/simulation/components/Mirage.js
===================================================================
--- binaries/data/mods/public/simulation/components/Mirage.js
+++ binaries/data/mods/public/simulation/components/Mirage.js
@@ -26,6 +26,9 @@
this.unhealable = null;
this.injured = null;
+ this.armourStrengths = {};
+ this.invulnerable = null;
+
this.capturePoints = [];
this.maxCapturePoints = 0;
@@ -115,6 +118,20 @@
Mirage.prototype.IsInjured = function() { return this.injured; };
Mirage.prototype.IsUnhealable = function() { return this.unhealable; };
+Mirage.prototype.CanAttack = Health.prototype.CanAttack;
+Mirage.prototype.GetRelativeDamage = Health.prototype.GetRelativeDamage;
+
+// Armour data
+
+Mirage.prototype.CopyResistance = function(cmpResistance)
+{
+ this.miragedIids.add(IID_Resistance);
+ this.armourStrengths = cmpResistance.GetArmourStrengths("Damage");
+ this.invulnerable = cmpResistance.IsInvulnerable();
+};
+Mirage.prototype.IsInvulnerable = function() { return this.invulnerable; }
+Mirage.prototype.GetArmourStrengths = function(effectType) { return this.armourStrengths; };
+
// Capture data
Mirage.prototype.CopyCapturable = function(cmpCapturable)
@@ -128,6 +145,17 @@
Mirage.prototype.GetCapturePoints = function() { return this.capturePoints; };
Mirage.prototype.CanCapture = Capturable.prototype.CanCapture;
+Mirage.prototype.GetRelativeCapture = Capturable.prototype.GetRelativeCapture;
+
+// StatusEffects data
+
+Mirage.prototype.CopyStatusEffectsReceiver = function(cmpStatusEffectsReceiver)
+{
+ this.miragedIids.add(IID_StatusEffectsReceiver);
+};
+
+Mirage.prototype.CanTakeEffect = Capturable.prototype.CanTakeEffect;
+Mirage.prototype.GetRelativeEffects = Capturable.prototype.GetRelativeEffects;
// ResourceSupply data
Index: binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js
===================================================================
--- binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js
+++ binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js
@@ -21,6 +21,23 @@
};
/**
+ * Quantify how much the effect does, relative to the maximum amount.
+ * For now return 0.
+ */
+StatusEffectsReceiver.prototype.GetRelativeEffect = function(effectData, effectType, attackerOwner)
+{
+ return 0;
+};
+
+/**
+ * Return whether an effect can be applied. Since effects can always be applied, return true.
+ */
+StatusEffectsReceiver.prototype.CanTakeEffect = function(playerID)
+{
+ return true;
+};
+
+/**
* Called by Attacking effects. Adds status effects for each entry in the effectData.
*
* @param {Object} effectData - An object containing the status effects to give to the entity.
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js
+++ binaries/data/mods/public/simulation/components/UnitAI.js
@@ -413,7 +413,7 @@
}
// Work out how to attack the given target
- var type = this.GetBestAttackAgainst(this.order.data.target, this.order.data.allowCapture);
+ var type = this.GetBestAttackAgainst(this.order.data.target, this.order.data.prefAttackForms, this.order.data.prefAttackTypes);
if (!type)
{
// Oops, we can't attack at all
@@ -533,7 +533,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, ["!Capture"]))
{
// Oops, we can't attack at all - give up
// TODO: should do something so the player knows why this failed
@@ -564,7 +564,7 @@
return;
}
- this.PushOrderFront("Attack", { "target": this.order.data.target, "force": !!this.order.data.force, "hunting": true, "allowCapture": false });
+ this.PushOrderFront("Attack", { "target": this.order.data.target, "force": !!this.order.data.force, "hunting": true, "prefAttackForms": ["!Capture"], "prefAttackTypes": [] });
return;
}
@@ -753,7 +753,6 @@
"Order.Attack": function(msg) {
let target = msg.data.target;
- let allowCapture = msg.data.allowCapture;
let cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI);
if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember())
target = cmpTargetUnitAI.GetFormationController();
@@ -769,7 +768,7 @@
this.FinishOrder();
return;
}
- this.CallMemberFunction("Attack", [target, allowCapture, false]);
+ this.CallMemberFunction("Attack", [target, msg.data.prefAttackForms, msg.data.prefAttackTypes, false]);
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
if (cmpAttack && cmpAttack.CanAttackAsFormation())
this.SetNextState("COMBAT.ATTACKING");
@@ -827,7 +826,7 @@
}
return;
}
- this.PushOrderFront("Attack", { "target": msg.data.target, "force": !!msg.data.force, "hunting": true, "allowCapture": false, "min": 0, "max": 10 });
+ this.PushOrderFront("Attack", { "target": msg.data.target, "force": !!msg.data.force, "hunting": true, "prefAttackForms": ["!Capture"], "prefAttackTypes": [], "min": 0, "max": 10 });
return;
}
@@ -1009,7 +1008,8 @@
{
this.patrolStartPosOrder = cmpPosition.GetPosition();
this.patrolStartPosOrder.targetClasses = this.order.data.targetClasses;
- this.patrolStartPosOrder.allowCapture = this.order.data.allowCapture;
+ this.patrolStartPosOrder.prefAttackForms = this.order.data.prefAttackForms;
+ this.patrolStartPosOrder.prefAttackTypes = this.order.data.prefAttackTypes;
}
if (!this.MoveTo(this.order.data))
@@ -1154,7 +1154,7 @@
if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember())
target = cmpTargetUnitAI.GetFormationController();
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
- this.CallMemberFunction("Attack", [target, this.order.data.allowCapture, false]);
+ this.CallMemberFunction("Attack", [target, this.order.data.prefAttackForms, this.order.data.prefAttackTypes, false]);
if (cmpAttack.CanAttackAsFormation())
this.SetNextState("COMBAT.ATTACKING");
else
@@ -1166,14 +1166,13 @@
// Wait for individual members to finish
"enter": function(msg) {
let target = this.order.data.target;
- let allowCapture = this.order.data.allowCapture;
// Check if we are already in range, otherwise walk there
if (!this.CheckFormationTargetAttackRange(target))
{
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, "prefAttackForms": this.order.data.prefAttackForms, "prefAttackTypes": this.order.data.prefAttackTypes });
return true;
}
this.FinishOrder();
@@ -1190,14 +1189,13 @@
"Timer": function(msg) {
let target = this.order.data.target;
- let allowCapture = this.order.data.allowCapture;
// Check if we are already in range, otherwise walk there
if (!this.CheckFormationTargetAttackRange(target))
{
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, "prefAttackForms": this.order.data.prefAttackForms, "prefAttackTypes": this.order.data.prefAttackTypes });
return;
}
this.FinishOrder();
@@ -1437,7 +1435,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, "prefAttackForms": ["Capture"], "prefAttackTypes": [] });
else
{
var cmpPosition = Engine.QueryInterface(msg.data.attacker, IID_Position);
@@ -1615,7 +1613,8 @@
{
this.patrolStartPosOrder = cmpPosition.GetPosition();
this.patrolStartPosOrder.targetClasses = this.order.data.targetClasses;
- this.patrolStartPosOrder.allowCapture = this.order.data.allowCapture;
+ this.patrolStartPosOrder.prefAttackForms = this.order.data.prefAttackForms;
+ this.patrolStartPosOrder.prefAttackTypes = this.order.data.prefAttackTypes;
}
this.StartTimer(0, 1000);
@@ -2027,8 +2026,9 @@
"Attacked": function(msg) {
// If we are capturing and are attacked by something that we would not capture, attack that entity instead
+ // TODO find out why we want this
if (this.order.data.attackType == "Capture" && (this.GetStance().targetAttackersAlways || !this.order.data.force)
- && this.order.data.target != msg.data.attacker && this.GetBestAttackAgainst(msg.data.attacker, true) != "Capture")
+ && this.order.data.target != msg.data.attacker && this.GetBestAttackAgainst(msg.data.attacker, ["Capture"]) != "Capture")
this.RespondToTargetedEntities([msg.data.attacker]);
},
},
@@ -4894,12 +4894,12 @@
return distance < range;
};
-UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture)
+UnitAI.prototype.GetBestAttackAgainst = function(target, prefAttackForms, prefAttackTypes)
{
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
if (!cmpAttack)
return undefined;
- return cmpAttack.GetBestAttackAgainst(target, allowCapture);
+ return cmpAttack.GetBestAttackAgainst(target, prefAttackForms, prefAttackTypes);
};
/**
@@ -4913,7 +4913,7 @@
if (!target)
return false;
- this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": true });
+ this.PushOrderFront("Attack", { "target": target, "force": false, "prefAttackForms": ["!Capture"], "prefAttackTypes": [] });
return true;
};
@@ -4926,13 +4926,13 @@
{
var target = ents.find(target =>
this.CanAttack(target)
- && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true))
+ && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, ["Capture"]))
&& (this.GetStance().respondChaseBeyondVision || this.CheckTargetIsInVisionRange(target))
);
if (!target)
return false;
- this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": true });
+ this.PushOrderFront("Attack", { "target": target, "force": false, "prefAttackForms": ["!Capture"], "prefAttackTypes": [] });
return true;
};
@@ -5328,12 +5328,12 @@
* 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, allowCapture = true, queued = false)
+UnitAI.prototype.WalkAndFight = function(x, z, targetClasses, prefAttackForms = ["Capture"], prefAttackTypes = [], queued = false)
{
- this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "allowCapture": allowCapture, "force": true }, queued);
+ this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "prefAttackForms": prefAttackForms, "prefAttackTypes": prefAttackTypes, "force": true }, queued);
};
-UnitAI.prototype.Patrol = function(x, z, targetClasses, allowCapture = true, queued = false)
+UnitAI.prototype.Patrol = function(x, z, targetClasses, prefAttackForms = ["Capture"], prefAttackTypes = [], queued = false)
{
if (!this.CanPatrol())
{
@@ -5341,7 +5341,7 @@
return;
}
- this.AddOrder("Patrol", { "x": x, "z": z, "targetClasses": targetClasses, "allowCapture": allowCapture, "force": true }, queued);
+ this.AddOrder("Patrol", { "x": x, "z": z, "targetClasses": targetClasses, "prefAttackForms": prefAttackForms, "prefAttackTypes": prefAttackTypes, "force": true }, queued);
};
/**
@@ -5372,7 +5372,7 @@
/**
* Adds attack order to the queue, forced by the player.
*/
-UnitAI.prototype.Attack = function(target, allowCapture = true, queued = false)
+UnitAI.prototype.Attack = function(target, prefAttackForms = ["Capture"], prefAttackTypes = [], queued = false)
{
if (!this.CanAttack(target))
{
@@ -5388,7 +5388,8 @@
let order = {
"target": target,
"force": true,
- "allowCapture": allowCapture,
+ "prefAttackForms": prefAttackForms,
+ "prefAttackTypes": prefAttackTypes
};
this.RememberTargetPosition(order);
@@ -5849,7 +5850,7 @@
if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
continue;
}
- this.PushOrderFront("Attack", { "target": targ, "force": false, "allowCapture": this.order.data.allowCapture });
+ this.PushOrderFront("Attack", { "target": targ, "force": false, "prefAttackForms": this.order.data.prefAttackForms, "prefAttackTypes": this.order.data.prefAttackTypes });
return true;
}
}
@@ -5875,7 +5876,7 @@
if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
continue;
}
- this.PushOrderFront("Attack", { "target": targ, "force": false, "allowCapture": this.order.data.allowCapture });
+ this.PushOrderFront("Attack", { "target": targ, "force": false, "prefAttackForms": this.order.data.prefAttackForms, "prefAttackTypes": this.order.data.prefAttackTypes });
return true;
}
Index: binaries/data/mods/public/simulation/components/tests/test_Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Attack.js
+++ binaries/data/mods/public/simulation/components/tests/test_Attack.js
@@ -8,6 +8,7 @@
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/Formation.js");
Engine.LoadComponentScript("interfaces/Health.js");
+Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Engine.LoadComponentScript("Attack.js");
@@ -25,8 +26,11 @@
});
AddMock(playerEnt1, IID_Player, {
- "GetPlayerID": () => 1,
- "IsEnemy": () => isEnemy
+ "GetPlayerID": () => 1
+ });
+
+ AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
+ "DistanceToTarget": (ent, target) => 10
});
}
@@ -34,6 +38,7 @@
AddMock(attacker, IID_Position, {
"IsInWorld": () => true,
+ "GetTurretParent": () => INVALID_ENTITY,
"GetHeightOffset": () => 5,
"GetPosition2D": () => new Vector2D(1, 2)
});
@@ -43,7 +48,8 @@
});
let cmpAttack = ConstructComponent(attacker, "Attack", {
- "Melee": {
+ "Spear": {
+ "Form": "Melee",
"Damage": {
"Hack": 11,
"Pierce": 5,
@@ -51,6 +57,8 @@
},
"MinRange": 3,
"MaxRange": 5,
+ "PrepareTime": 0,
+ "RepeatTime": 1000,
"PreferredClasses": {
"_string": "FemaleCitizen"
},
@@ -65,7 +73,8 @@
}
}
},
- "Ranged": {
+ "Arrow": {
+ "Form": "Ranged",
"Damage": {
"Hack": 0,
"Pierce": 10,
@@ -105,11 +114,28 @@
}
},
"Capture": {
+ "Form": "Capture",
"Capture": 8,
"MaxRange": 10,
+ "PrepareTime": 0,
+ "RepeatTime": 1000
+ },
+ "Slaughter": {
+ "Form": "Slaughter",
+ "Damage": {
+ "Hack": 100,
+ "Pierce": 0,
+ "Crush": 0
+ },
+ "MaxRange": 5,
+ "PrepareTime": 0,
+ "RepeatTime": 1000,
+ "RestrictedClasses": {
+ "_string": "!Domestic"
+ },
},
- "Slaughter": {},
"StatusEffect": {
+ "Form": "Ranged",
"ApplyStatus": {
"StatusInternalName": {
"StatusName": "StatusShownName",
@@ -131,7 +157,12 @@
}
},
"MinRange": "10",
- "MaxRange": "80"
+ "MaxRange": "80",
+ "PrepareTime": 0,
+ "RepeatTime": 1000,
+ "RestrictedClasses": {
+ "_string": "Elephant"
+ },
}
});
@@ -152,7 +183,20 @@
});
AddMock(defender, IID_Health, {
- "GetHitpoints": () => 100
+ "GetHitpoints": () => 100,
+ "GetRelativeDamage": (effectData, effectType) => {
+ let strength = 0
+ for (let type in effectData)
+ strength += effectData[type];
+ return strength / 100
+ },
+
+ "CanAttack": () => isEnemy || defenderClass == "Domestic"
+ });
+
+ AddMock(defender, IID_StatusEffectsReceiver, {
+ "CanTakeEffect": () => true,
+ "GetRelativeEffect": () => 0
});
test_function(attacker, cmpAttack, defender);
@@ -161,23 +205,23 @@
// Validate template getter functions
attackComponentTest(undefined, true, (attacker, cmpAttack, defender) => {
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(), ["Melee", "Ranged", "Capture"]);
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes([]), ["Melee", "Ranged", "Capture"]);
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Melee", "Ranged", "Capture"]), ["Melee", "Ranged", "Capture"]);
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Melee", "Ranged"]), ["Melee", "Ranged"]);
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(), ["Spear", "Arrow", "Capture", "Slaughter", "StatusEffect"]);
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes([]), ["Spear", "Arrow", "Capture", "Slaughter", "StatusEffect"]);
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Spear", "Arrow", "Capture"]), ["Spear", "Arrow", "Capture"]);
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Spear", "Arrow"]), ["Spear", "Arrow"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Capture"]), ["Capture"]);
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Melee", "!Melee"]), []);
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["!Melee"]), ["Ranged", "Capture"]);
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["!Melee", "!Ranged"]), ["Capture"]);
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Capture", "!Ranged"]), ["Capture"]);
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Capture", "Melee", "!Ranged"]), ["Melee", "Capture"]);
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Spear", "!Spear"]), []);
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["!Spear"]), ["Arrow", "Capture", "Slaughter", "StatusEffect"]);
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["!Spear", "!Arrow"]), ["Capture", "Slaughter", "StatusEffect"]);
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Capture", "!Arrow"]), ["Capture"]);
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Capture", "Spear", "!Arrow"]), ["Spear", "Capture"]);
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetPreferredClasses("Melee"), ["FemaleCitizen"]);
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRestrictedClasses("Melee"), ["Elephant", "Archer"]);
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetPreferredClasses("Spear"), ["FemaleCitizen"]);
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRestrictedClasses("Spear"), ["Elephant", "Archer"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetFullAttackRange(), { "min": 0, "max": 80 });
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Capture"), { "Capture": 8 });
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Ranged"), {
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Arrow"), {
"Damage": {
"Hack": 0,
"Pierce": 10,
@@ -185,7 +229,7 @@
}
});
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Ranged", true), {
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Arrow", true), {
"Damage": {
"Hack": 0.0,
"Pierce": 15.0,
@@ -223,13 +267,13 @@
}
});
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetTimers("Ranged"), {
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetTimers("Arrow"), {
"prepare": 300,
"repeat": 500
});
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRepeatTime("Ranged"), 500);
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRepeatTime("Arrow"), 500);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetTimers("Capture"), {
"prepare": 0,
@@ -238,7 +282,14 @@
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRepeatTime("Capture"), 1000);
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetSplashData("Ranged"), {
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetTimers("StatusEffect"), {
+ "prepare": 0,
+ "repeat": 1000
+ });
+
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRepeatTime("StatusEffect"), 1000);
+
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetSplashData("Arrow"), {
"attackData": {
"Damage": {
"Hack": 0,
@@ -261,14 +312,14 @@
for (let className of ["Infantry", "Cavalry"])
attackComponentTest(className, true, (attacker, cmpAttack, defender) => {
- TS_ASSERT_EQUALS(cmpAttack.GetAttackEffectsData("Melee").Bonuses.BonusCav.Multiplier, 2);
+ TS_ASSERT_EQUALS(cmpAttack.GetAttackEffectsData("Spear").Bonuses.BonusCav.Multiplier, 2);
TS_ASSERT_EQUALS(cmpAttack.GetAttackEffectsData("Capture").Bonuses || null, null);
let getAttackBonus = (s, t, e, splash) => GetAttackBonus(s, e, t, cmpAttack.GetAttackEffectsData(t, splash).Bonuses || null);
- TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Melee", defender), className == "Cavalry" ? 2 : 1);
- TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Ranged", defender), 1);
- TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Ranged", defender, true), className == "Cavalry" ? 3 : 1);
+ TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Spear", defender), className == "Cavalry" ? 2 : 1);
+ TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Arrow", defender), 1);
+ TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Arrow", defender, true), className == "Cavalry" ? 3 : 1);
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Capture", defender), 1);
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Slaughter", defender), 1);
});
@@ -278,7 +329,7 @@
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), false);
});
-function testGetBestAttackAgainst(defenderClass, bestAttack, bestAllyAttack, isBuilding = false)
+function testGetBestAttackAgainst(defenderClass, bestAttack, isBuilding = false)
{
attackComponentTest(defenderClass, true, (attacker, cmpAttack, defender) => {
@@ -287,25 +338,34 @@
"CanCapture": playerID => {
TS_ASSERT_EQUALS(playerID, 1);
return true;
- }
+ },
+ "GetRelativeCapture": (effectData, effectType) => effectData / 10
});
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), true);
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, []), true);
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Ranged"]), true);
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Melee"]), true);
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Arrow"]), true);
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Spear"]), true);
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Capture"]), isBuilding);
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Melee", "Capture"]), defenderClass != "Archer");
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Ranged", "Capture"]), true);
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Ranged", "!Melee"]), isBuilding || defenderClass == "Domestic");
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Melee", "!Melee"]), false);
-
- let allowCapturing = [true];
- if (!isBuilding)
- allowCapturing.push(false);
-
- for (let ac of allowCapturing)
- TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ac), bestAttack);
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Slaughter"]), defenderClass == "Domestic");
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Slaughter"]), true);
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Spear", "Capture"]), defenderClass != "Archer");
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Arrow", "Capture"]), true);
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Arrow", "!Spear", "!StatusEffect"]), isBuilding || defenderClass == "Domestic");
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Spear", "!Spear"]), false);
+
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender), bestAttack);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, []), bestAttack);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Slaughter"]), bestAttack);
+
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["!Slaughter"]), bestAttack == "Slaughter" ? "Arrow" : bestAttack);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Ranged"]), "Arrow");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["!Melee"]), bestAttack);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Capture"]), bestAttack);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Melee", "Capture"]), isBuilding ? "Capture" : defenderClass == "Archer" ? "Arrow" : "Spear");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Ranged", "Capture"]), isBuilding ? "Capture" : "Arrow");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["!Ranged", "!Melee"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : bestAttack);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Melee", "!Melee"]), bestAttack);
});
attackComponentTest(defenderClass, false, (attacker, cmpAttack, defender) => {
@@ -315,33 +375,45 @@
"CanCapture": playerID => {
TS_ASSERT_EQUALS(playerID, 1);
return true;
- }
+ },
+ "GetRelativeCapture": (effectData, effectType) => effectData / 10
});
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), true);
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, []), true);
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), isBuilding || defenderClass == "Domestic");
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, []), isBuilding || defenderClass == "Domestic");
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Ranged"]), false);
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Melee"]), isBuilding || defenderClass == "Domestic");
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Capture"]), isBuilding);
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Melee", "Capture"]), isBuilding);
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Ranged", "Capture"]), isBuilding);
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Ranged", "!Melee"]), isBuilding || defenderClass == "Domestic");
- TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Melee", "!Melee"]), false);
-
- let allowCapturing = [true];
- if (!isBuilding)
- allowCapturing.push(false);
-
- for (let ac of allowCapturing)
- TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ac), bestAllyAttack);
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!StatusEffect"]), isBuilding || defenderClass == "Domestic");
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!StatusEffect"]), isBuilding || defenderClass == "Domestic");
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Arrow", "!StatusEffect"]), defenderClass == "Domestic");
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Spear", "!StatusEffect"]), isBuilding || defenderClass == "Domestic");
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Capture", "!StatusEffect"]), isBuilding);
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Spear", "Capture", "!StatusEffect"]), isBuilding || defenderClass == "Domestic");
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Arrow", "Capture", "!StatusEffect"]), isBuilding || defenderClass == "Domestic");
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Slaughter", "!StatusEffect"]), defenderClass == "Domestic");
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Arrow", "!Spear", "!StatusEffect"]), isBuilding || defenderClass == "Domestic");
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Spear", "!Spear"]), false);
+
+
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : "StatusEffect");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, []), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : "StatusEffect");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["!StatusEffect"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : "StatusEffect");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Slaughter"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : "StatusEffect");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["!Slaughter"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Arrow" : "StatusEffect");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Ranged"]), defenderClass == "Domestic" ? "Arrow" : "StatusEffect");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Melee"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Spear" : "StatusEffect");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["!Melee"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : "StatusEffect");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Capture"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : "StatusEffect");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Melee", "Capture"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Spear" : "StatusEffect");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Ranged", "Capture"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Arrow" : "StatusEffect");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["!Ranged", "!Melee"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : "StatusEffect");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Melee", "!Melee"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : "StatusEffect");
});
}
-testGetBestAttackAgainst("FemaleCitizen", "Melee", undefined);
-testGetBestAttackAgainst("Archer", "Ranged", undefined);
-testGetBestAttackAgainst("Domestic", "Slaughter", "Slaughter");
-testGetBestAttackAgainst("Structure", "Capture", "Capture", true);
-testGetBestAttackAgainst("Structure", "Ranged", undefined, false);
+testGetBestAttackAgainst("FemaleCitizen", "Arrow");
+testGetBestAttackAgainst("Archer", "Arrow");
+testGetBestAttackAgainst("Domestic", "Slaughter");
+testGetBestAttackAgainst("Structure", "Capture", true);
+testGetBestAttackAgainst("Structure", "Arrow", false);
function testPredictTimeToTarget(selfPosition, horizSpeed, targetPosition, targetVelocity)
{
Index: binaries/data/mods/public/simulation/components/tests/test_Damage.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Damage.js
+++ binaries/data/mods/public/simulation/components/tests/test_Damage.js
@@ -6,12 +6,12 @@
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/AttackDetection.js");
Engine.LoadComponentScript("interfaces/DelayedDamage.js");
+Engine.LoadComponentScript("interfaces/Formation.js");
Engine.LoadComponentScript("interfaces/Resistance.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/Loot.js");
Engine.LoadComponentScript("interfaces/Player.js");
Engine.LoadComponentScript("interfaces/Promotion.js");
-Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js");
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("Attack.js");
@@ -69,7 +69,8 @@
};
AddMock(atkPlayerEntity, IID_Player, {
- "GetEnemies": () => [targetOwner]
+ "GetEnemies": () => [targetOwner],
+ "GetPlayerID": () => atkPlayerEntity
});
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
@@ -77,6 +78,10 @@
"GetAllPlayers": () => [0, 1, 2, 3, 4]
});
+ AddMock(target, IID_Identity, {
+ "GetClassesList": () => []
+ });
+
AddMock(SYSTEM_ENTITY, IID_ProjectileManager, {
"RemoveProjectile": () => {},
"LaunchProjectileAtPoint": (ent, pos, speed, gravity) => {},
@@ -87,6 +92,7 @@
"GetPreviousPosition": () => targetPos,
"GetPosition2D": () => Vector2D.From(targetPos),
"IsInWorld": () => true,
+ "GetHeightOffset": () => 0
});
AddMock(target, IID_Health, {
@@ -94,6 +100,7 @@
damageTaken = true;
return { "killed": false, "HPchange": -bonusMultiplier * effectData.Crush };
},
+ "CanAttack": () => true
});
AddMock(SYSTEM_ENTITY, IID_DelayedDamage, {
@@ -128,6 +135,8 @@
"GetPosition": () => new Vector3D(2, 0, 3),
"GetRotation": () => new Vector3D(1, 2, 3),
"IsInWorld": () => true,
+ "GetTurretParent": () => INVALID_ENTITY,
+ "GetHeightOffset": () => 0
});
function TestDamage()
Index: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
+++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
@@ -185,7 +185,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; },
@@ -344,7 +344,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
+++ binaries/data/mods/public/simulation/helpers/Commands.js
@@ -163,41 +163,35 @@
"attack-walk": function(player, cmd, data)
{
- let allowCapture = cmd.allowCapture || cmd.allowCapture == null;
-
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
- cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, allowCapture, cmd.queued);
+ cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.prefAttackForms || [], cmd.prefAttackTypes || [], cmd.queued);
});
},
"attack-walk-custom": function(player, cmd, data)
{
- let allowCapture = cmd.allowCapture || cmd.allowCapture == null;
for (let ent in data.entities)
GetFormationUnitAIs([data.entities[ent]], player).forEach(cmpUnitAI => {
- cmpUnitAI.WalkAndFight(cmd.targetPositions[ent].x, cmd.targetPositions[ent].y, cmd.targetClasses, allowCapture, cmd.queued);
+ cmpUnitAI.WalkAndFight(cmd.targetPositions[ent].x, cmd.targetPositions[ent].y, cmd.targetClasses, cmd.prefAttackForms || [], cmd.prefAttackTypes || [], cmd.queued);
});
},
"attack": function(player, cmd, data)
{
- let allowCapture = cmd.allowCapture || cmd.allowCapture == null;
-
- if (g_DebugCommands && !allowCapture &&
+ // TODO: there shouldn't be an error when we capture
+ 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));
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
- cmpUnitAI.Attack(cmd.target, allowCapture, cmd.queued);
+ cmpUnitAI.Attack(cmd.target, cmd.prefAttackForms || [], cmd.prefAttackTypes || [], cmd.queued);
});
},
"patrol": function(player, cmd, data)
{
- let allowCapture = cmd.allowCapture || cmd.allowCapture == null;
-
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI =>
- cmpUnitAI.Patrol(cmd.x, cmd.z, cmd.targetClasses, allowCapture, cmd.queued)
+ cmpUnitAI.Patrol(cmd.x, cmd.z, cmd.targetClasses, cmd.prefAttackForms || [], cmd.prefAttackTypes || [], cmd.queued)
);
},
Index: binaries/data/mods/public/simulation/templates/gaia/fauna_walrus.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/gaia/fauna_walrus.xml
+++ binaries/data/mods/public/simulation/templates/gaia/fauna_walrus.xml
@@ -7,6 +7,7 @@
+
15.0
10.0
Index: binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml
+++ binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml
@@ -12,6 +12,7 @@
+
0.0
25.0
Index: binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
+++ binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
@@ -17,6 +17,7 @@
+
0.0
12.0
Index: binaries/data/mods/public/simulation/templates/template_structure_defensive_tower.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_defensive_tower.xml
+++ binaries/data/mods/public/simulation/templates/template_structure_defensive_tower.xml
@@ -2,6 +2,7 @@
+
0
0
Index: binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_tower.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_tower.xml
+++ binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_tower.xml
@@ -2,6 +2,7 @@
+
0.0
8.0
Index: binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml
+++ binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml
@@ -7,6 +7,7 @@
+
0.0
16.0
Index: binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml
@@ -7,18 +7,22 @@
+
2
4
1000
Field Palisade SiegeWall StoneWall
+
100.0
0.0
0.0
2
+ 1000
+ !Domestic
Index: binaries/data/mods/public/simulation/templates/template_unit_cavalry_melee.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_cavalry_melee.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_cavalry_melee.xml
@@ -6,6 +6,7 @@
+
13
0
Index: binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml
@@ -2,6 +2,7 @@
+
0
9.0
Index: binaries/data/mods/public/simulation/templates/template_unit_champion.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_champion.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_champion.xml
@@ -2,6 +2,7 @@
+
5
4
1000
Index: binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_archer.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_archer.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_archer.xml
@@ -2,6 +2,7 @@
+
0
14.0
Index: binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml
@@ -2,6 +2,7 @@
+
0.0
36.0
Index: binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_spearman.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_spearman.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_spearman.xml
@@ -6,6 +6,7 @@
+
12.0
10.0
Index: binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_swordsman.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_swordsman.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_swordsman.xml
@@ -6,6 +6,7 @@
+
13.0
0
Index: binaries/data/mods/public/simulation/templates/template_unit_champion_elephant_melee.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_champion_elephant_melee.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_champion_elephant_melee.xml
@@ -2,6 +2,7 @@
+
20
0
Index: binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml
@@ -2,6 +2,7 @@
+
0
6.5
Index: binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml
@@ -2,6 +2,7 @@
+
0
26.0
Index: binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_maceman.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_maceman.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_maceman.xml
@@ -6,6 +6,7 @@
+
0
0
Index: binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_pikeman.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_pikeman.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_pikeman.xml
@@ -7,6 +7,7 @@
+
2.0
6.0
Index: binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_spearman.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_spearman.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_spearman.xml
@@ -6,6 +6,7 @@
+
6.0
5.0
Index: binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_swordsman.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_swordsman.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_swordsman.xml
@@ -6,6 +6,7 @@
+
11.0
0
Index: binaries/data/mods/public/simulation/templates/template_unit_dog.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_dog.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_dog.xml
@@ -7,6 +7,7 @@
+
7
2
Index: binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_aggressive.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_aggressive.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_aggressive.xml
@@ -2,6 +2,7 @@
+
1
1
Index: binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_defensive_bull.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_defensive_bull.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_defensive_bull.xml
@@ -2,6 +2,7 @@
+
1
5
Index: binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_defensive_elephant.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_defensive_elephant.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_defensive_elephant.xml
@@ -7,6 +7,7 @@
+
25.0
10.0
Index: binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_violent.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_violent.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_violent.xml
@@ -2,6 +2,7 @@
+
1
1
Index: binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_aggressive.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_aggressive.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_aggressive.xml
@@ -2,6 +2,7 @@
+
1
1
Index: binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_defensive_fox.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_defensive_fox.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_defensive_fox.xml
@@ -2,6 +2,7 @@
+
2.5
5.0
Index: binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_violent.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_violent.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_violent.xml
@@ -2,6 +2,7 @@
+
1
1
Index: binaries/data/mods/public/simulation/templates/template_unit_hero.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_hero.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_hero.xml
@@ -7,6 +7,7 @@
+
15
4
1000
Index: binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml
@@ -2,6 +2,7 @@
+
0
35
Index: binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_javelinist.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_javelinist.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_javelinist.xml
@@ -2,6 +2,7 @@
+
0.0
60.0
Index: binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_spearman.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_spearman.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_spearman.xml
@@ -6,6 +6,7 @@
+
24.0
20.0
Index: binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_swordsman.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_swordsman.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_swordsman.xml
@@ -6,6 +6,7 @@
+
26.0
0.0
Index: binaries/data/mods/public/simulation/templates/template_unit_hero_elephant_melee.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_hero_elephant_melee.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_hero_elephant_melee.xml
@@ -7,6 +7,7 @@
+
20.0
0
Index: binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml
@@ -2,6 +2,7 @@
+
0
8.0
Index: binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelinist.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelinist.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelinist.xml
@@ -2,6 +2,7 @@
+
0
50.0
Index: binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_pikeman.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_pikeman.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_pikeman.xml
@@ -6,6 +6,7 @@
+
4.0
20.0
Index: binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_spearman.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_spearman.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_spearman.xml
@@ -6,6 +6,7 @@
+
12.0
10.0
Index: binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_swordsman.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_swordsman.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_swordsman.xml
@@ -6,6 +6,7 @@
+
22.0
0
Index: binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
@@ -7,18 +7,22 @@
+
2
4
1000
Field Palisade SiegeWall StoneWall
+
50.0
0.0
0.0
2
+ 1000
+ !Domestic
Index: binaries/data/mods/public/simulation/templates/template_unit_infantry_melee.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_infantry_melee.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_infantry_melee.xml
@@ -2,6 +2,7 @@
+
1
0
Index: binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml
@@ -7,6 +7,7 @@
+
0
1.5
Index: binaries/data/mods/public/simulation/templates/template_unit_ship_bireme.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_ship_bireme.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_ship_bireme.xml
@@ -2,6 +2,7 @@
+
0.0
35.0
Index: binaries/data/mods/public/simulation/templates/template_unit_ship_fire.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_ship_fire.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_ship_fire.xml
@@ -2,6 +2,7 @@
+
10.0
10.0
Index: binaries/data/mods/public/simulation/templates/template_unit_ship_fishing.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_ship_fishing.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_ship_fishing.xml
@@ -7,6 +7,7 @@
+
10.0
0.0
Index: binaries/data/mods/public/simulation/templates/template_unit_ship_quinquereme.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_ship_quinquereme.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_ship_quinquereme.xml
@@ -2,6 +2,7 @@
+
0.0
10.0
Index: binaries/data/mods/public/simulation/templates/template_unit_ship_trireme.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_ship_trireme.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_ship_trireme.xml
@@ -2,6 +2,7 @@
+
0.0
35.0
Index: binaries/data/mods/public/simulation/templates/template_unit_siege_boltshooter.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_siege_boltshooter.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_siege_boltshooter.xml
@@ -2,6 +2,7 @@
+
0.0
150.0
Index: binaries/data/mods/public/simulation/templates/template_unit_siege_ram.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_siege_ram.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_siege_ram.xml
@@ -2,6 +2,7 @@
+
0.0
0.0
Index: binaries/data/mods/public/simulation/templates/template_unit_siege_stonethrower.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_siege_stonethrower.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_siege_stonethrower.xml
@@ -2,6 +2,7 @@
+
0.0
10.0
Index: binaries/data/mods/public/simulation/templates/template_unit_siege_tower.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_siege_tower.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_siege_tower.xml
@@ -2,6 +2,7 @@
+
0.0
12.0
Index: binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml
@@ -2,6 +2,7 @@
+
2.0
0
@@ -12,12 +13,15 @@
1000
+
25.0
0.0
0.0
2
+ 1000
+ !Domestic
Index: binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml
+++ binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml
@@ -1,5 +1,26 @@
+
+
+
+
+ 0
+ 6.0
+ 0
+
+ 72.0
+ 0.0
+ 600
+ 1000
+
+ 75.0
+ 3.0
+ false
+ 9.81
+
+
+
+
pers
Immortal
Index: binaries/data/mods/public/simulation/templates/units/plane.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/units/plane.xml
+++ binaries/data/mods/public/simulation/templates/units/plane.xml
@@ -2,6 +2,7 @@
+
0.0
100.0
Index: binaries/data/mods/public/simulation/templates/units/theb_siege_fireraiser.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/units/theb_siege_fireraiser.xml
+++ binaries/data/mods/public/simulation/templates/units/theb_siege_fireraiser.xml
@@ -2,6 +2,7 @@
+
50.0
0.0
Index: source/simulation2/components/ICmpAttack.h
===================================================================
--- source/simulation2/components/ICmpAttack.h
+++ source/simulation2/components/ICmpAttack.h
@@ -24,6 +24,7 @@
{
public:
virtual float GetRepeatTime(const std::string& type) const = 0;
+ virtual std::vector GetAttackTypes() const = 0;
DECLARE_INTERFACE_TYPE(Attack)
};
Index: source/simulation2/components/ICmpAttack.cpp
===================================================================
--- source/simulation2/components/ICmpAttack.cpp
+++ source/simulation2/components/ICmpAttack.cpp
@@ -31,10 +31,15 @@
public:
DEFAULT_SCRIPT_WRAPPER(AttackScripted)
- virtual float GetRepeatTime(const std::string& type) const
- {
- return m_Script.Call("GetRepeatTime", type);
- }
+ virtual float GetRepeatTime(const std::string& type) const
+ {
+ return m_Script.Call("GetRepeatTime", type);
+ }
+
+ virtual std::vector GetAttackTypes() const
+ {
+ return m_Script.Call>("GetAttackTypes");
+ }
};
REGISTER_COMPONENT_SCRIPT_WRAPPER(AttackScripted)
Index: source/tools/atlas/GameInterface/ActorViewer.h
===================================================================
--- source/tools/atlas/GameInterface/ActorViewer.h
+++ source/tools/atlas/GameInterface/ActorViewer.h
@@ -52,6 +52,7 @@
private:
ActorViewerImpl& m;
+ std::vector GetAttackTypes() const;
float GetRepeatTimeByAttackType(const std::string& type) const;
};
Index: source/tools/atlas/GameInterface/ActorViewer.cpp
===================================================================
--- source/tools/atlas/GameInterface/ActorViewer.cpp
+++ source/tools/atlas/GameInterface/ActorViewer.cpp
@@ -58,6 +58,8 @@
#include "simulation2/components/ICmpWaterManager.h"
#include "simulation2/helpers/Render.h"
+#include
+
struct ActorViewerImpl : public Scene
{
NONCOPYABLE(ActorViewerImpl);
@@ -403,14 +405,10 @@
m.CurrentSpeed = speed;
}
- else if (anim == "attack_melee")
- repeattime = GetRepeatTimeByAttackType("Melee");
- else if (anim == "attack_ranged")
- repeattime = GetRepeatTimeByAttackType("Ranged");
- else if (anim == "attack_slaughter")
- repeattime = GetRepeatTimeByAttackType("Slaughter");
- else if (anim == "attack_capture")
- repeattime = GetRepeatTimeByAttackType("Capture");
+ else
+ for (std::string& type : GetAttackTypes())
+ if (anim == "attack_" + boost::algorithm::to_lower_copy(type))
+ repeattime = GetRepeatTimeByAttackType(type);
CmpPtr cmpVisual(m.Simulation2, m.Entity);
if (cmpVisual)
@@ -482,6 +480,15 @@
g_ProfileViewer.ShowTable("");
}
+std::vector ActorViewer::GetAttackTypes() const
+{
+ CmpPtr cmpAttack(m.Simulation2, m.Entity);
+ if (cmpAttack)
+ return cmpAttack->GetAttackTypes();
+
+ return {};
+}
+
float ActorViewer::GetRepeatTimeByAttackType(const std::string& type) const
{
CmpPtr cmpAttack(m.Simulation2, m.Entity);