Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg
+++ binaries/data/config/default.cfg
@@ -293,17 +293,19 @@
stop = "H" ; Stop the current action
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/art/textures/cursors/action-melee-attack.txt
===================================================================
--- binaries/data/mods/public/art/textures/cursors/action-melee-attack.txt
+++ /dev/null
@@ -1 +0,0 @@
-1 1
Index: binaries/data/mods/public/art/textures/cursors/action-ranged-attack.txt
===================================================================
--- binaries/data/mods/public/art/textures/cursors/action-ranged-attack.txt
+++ /dev/null
@@ -1 +0,0 @@
-1 1
Index: binaries/data/mods/public/globalscripts/DamageTypes.js
===================================================================
--- binaries/data/mods/public/globalscripts/DamageTypes.js
+++ binaries/data/mods/public/globalscripts/DamageTypes.js
@@ -2,13 +2,19 @@
{
// TODO: load these from files
- this.names = {
- "Hack": markForTranslationWithContext("damage type", "Hack"),
- "Pierce": markForTranslationWithContext("damage type", "Pierce"),
- "Crush": markForTranslationWithContext("damage type", "Crush"),
+ this.types = {
+ "Hack": { "name": markForTranslationWithContext("damage type", "Hack"), "receiveComponent": "health" },
+ "Pierce": { "name": markForTranslationWithContext("damage type", "Pierce"), "receiveComponent": "health" },
+ "Crush": { "name": markForTranslationWithContext("damage type", "Crush"), "receiveComponent": "health" },
+ "Capture": { "name": markForTranslationWithContext("damage type", "Capture"),"receiveComponent": "capturable" }
};
- deepfreeze(this.names);
+ this.names = {};
+ for (let type in this.types)
+ this.names[type] = this.types[type].name;
+
+ deepfreeze(this.types);
+ deepfreeze(this.names)
}
DamageTypes.prototype.GetNames = function()
@@ -18,5 +24,5 @@
DamageTypes.prototype.GetTypes = function()
{
- return Object.keys(this.names);
+ return Object.keys(this.types);
};
Index: binaries/data/mods/public/globalscripts/Templates.js
===================================================================
--- binaries/data/mods/public/globalscripts/Templates.js
+++ binaries/data/mods/public/globalscripts/Templates.js
@@ -177,25 +177,19 @@
let getAttackStat = function(stat) {
return getEntityValue("Attack/" + type + "/" + stat);
};
+ ret.attack[type] = {
+ "tooltipHeader": template.Attack[type].TooltipHeader,
+ "minRange": getAttackStat("MinRange"),
+ "maxRange": getAttackStat("MaxRange"),
+ "elevationBonus": getAttackStat("ElevationBonus"),
+ "damage": {}
+ };
+ for (let damageType of damageTypes.GetTypes())
+ ret.attack[type].damage[damageType] = getAttackStat("Damage/" + damageType);
- if (type == "Capture")
- ret.attack.Capture = {
- "value": getAttackStat("Value")
- };
- else
- {
- ret.attack[type] = {
- "minRange": getAttackStat("MinRange"),
- "maxRange": getAttackStat("MaxRange"),
- "elevationBonus": getAttackStat("ElevationBonus"),
- "damage": {}
- };
- for (let damageType of damageTypes.GetTypes())
- ret.attack[type].damage[damageType] = getAttackStat("Damage/" + damageType);
+ ret.attack[type].elevationAdaptedRange = Math.sqrt(ret.attack[type].maxRange *
+ (2 * ret.attack[type].elevationBonus + ret.attack[type].maxRange));
- ret.attack[type].elevationAdaptedRange = Math.sqrt(ret.attack[type].maxRange *
- (2 * ret.attack[type].elevationBonus + ret.attack[type].maxRange));
- }
ret.attack[type].repeatTime = getAttackStat("RepeatTime");
if (template.Attack[type].Splash)
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
@@ -8,12 +8,6 @@
"nameGeneric": { "font": "sans-bold-16" }
};
-var g_AttackTypes = {
- "Melee": translate("Melee Attack:"),
- "Ranged": translate("Ranged Attack:"),
- "Capture": translate("Capture Attack:")
-};
-
var g_DamageTypes = new DamageTypes();
var g_SplashDamageTypes = {
@@ -226,7 +220,7 @@
"armorPercentage":
'[font="sans-10"]' +
sprintf(translate("(%(armorPercentage)s)"), {
- "armorPercentage": armorLevelToPercentageString(template.armour[dmgType])
+ "armorPercentage": armorLevelToPercentageString(template.armour[dmgType] || 0) //TODO capture?
}) + '[/font]'
})
).join(commaFont(translate(", ")))
@@ -237,9 +231,8 @@
{
if (!dmg)
return '[font="sans-12"]' + translate("(None)") + '[/font]';
-
return g_DamageTypes.GetTypes().filter(
- dmgType => dmg[dmgType]).map(
+ dmgType => !!dmg[dmgType]).map(
dmgType => sprintf(translate("%(damage)s %(damageType)s"), {
"damage": dmg[dmgType].toFixed(1),
"damageType": unitFont(translateWithContext("damage type", g_DamageTypes.GetNames()[dmgType]))
@@ -254,38 +247,24 @@
let tooltips = [];
for (let type in template.attack)
{
- if (type == "Slaughter")
- continue; // Slaughter is used to kill animals, so do not show it.
+ //if (type == "Slaughter")
+ // continue; // Slaughter is used to kill animals, so do not show it.
let rate = sprintf(translate("%(label)s %(details)s"), {
"label":
headerFont(
- template.buildingAI && type == "Ranged" ?
+ template.buildingAI && template.attack[type].projectile ?
translate("Interval:") :
translate("Rate:")),
"details": attackRateDetails(template, type)
});
- let attackLabel = headerFont(g_AttackTypes[type]);
- if (type == "Capture" || type != "Ranged")
- {
- tooltips.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), {
- "attackLabel": attackLabel,
- "details":
- type == "Capture" ?
- template.attack.Capture.value :
- damageTypesToText(template.attack[type].damage),
- "rate": rate
- }));
- continue;
- }
-
let minRange = Math.round(template.attack[type].minRange);
let maxRange = Math.round(template.attack[type].maxRange);
let realRange = template.attack[type].elevationAdaptedRange;
let relativeRange = realRange ? Math.round(realRange - maxRange) : 0;
tooltips.push(sprintf(g_RangeTooltipString[relativeRange ? "relative" : "non-relative"][minRange ? "minRange" : "no-minRange"], {
- "attackLabel": attackLabel,
+ "attackLabel": headerFont(translate(template.attack[type].tooltipHeader)),
"damageTypes": damageTypesToText(template.attack[type].damage),
"rangeLabel": headerFont(translate("Range:")),
"minRange": minRange,
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
@@ -101,7 +101,7 @@
J + Right Click on building: 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
@@ -113,8 +113,10 @@
Right Click with a building/buildings selected: sets a rally point for units created/ungarrisoned from that building.
Ctrl + Right Click with units selected:
• If the cursor is over an allied structure: Garrison
- • If the cursor is over a non-allied unit or building: Attack (instead of capture or gather)
+ • If the cursor is over a non-allied unit or building: Primary Attack (instead of capture, gather)
• Otherwise: Attack move (by default all enemy units and structures along the way are targeted, use Ctrl + Q + Right Click to target only units).
+Alt + Right Click on non-allied unit or building: Melee attack
+Space + Right Click on non-allied unit or building: Ranged attack
[font="sans-bold-14"]Overlays
[font="sans-14"]Alt + G: Hide/show the GUI
Index: binaries/data/mods/public/gui/session/unit_actions.js
===================================================================
--- binaries/data/mods/public/gui/session/unit_actions.js
+++ binaries/data/mods/public/gui/session/unit_actions.js
@@ -82,7 +82,7 @@
return { "type": "move" };
},
- "specificness": 12,
+ "specificness": 13,
},
"attack-move":
@@ -101,6 +101,7 @@
"x": target.x,
"z": target.z,
"targetClasses": targetClasses,
+ "prefAttackTypes": ["!Capture"],
"queued": queued
});
@@ -132,7 +133,7 @@
"specificness": 30,
},
- "capture":
+ "attack-capture":
{
"execute": function(target, action, selection, queued)
{
@@ -140,7 +141,7 @@
"type": "attack",
"entities": selection,
"target": action.target,
- "allowCapture": true,
+ "prefAttackTypes": ["Capture"],
"queued": queued
});
@@ -166,19 +167,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)
{
@@ -186,8 +187,8 @@
"type": "attack",
"entities": selection,
"target": action.target,
- "queued": queued,
- "allowCapture": false
+ "prefAttackTypes": ["!Capture"],
+ "queued": queued
});
Engine.GuiInterfaceCall("PlaySound", {
@@ -213,23 +214,136 @@
"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-nocapture",
+ "cursor": "action-attack-melee",
+ "target": target
+ };
+ },
+ "actionCheck": function(target, selection)
+ {
+ 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",
- "cursor": "action-attack",
+ "entities": selection,
+ "target": action.target,
+ "prefAttackTypes": ["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", target, selection).possible)
+ 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,
+ "prefAttackTypes": ["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
};
},
@@ -247,8 +361,8 @@
"z": target.z,
"target": action.target,
"targetClasses": { "attack": g_PatrolTargets },
- "queued": queued,
- "allowCapture": false
+ "prefAttackTypes": ["!Capture"],
+ "queued": queued
});
DrawTargetMarker(target);
@@ -335,7 +449,7 @@
"target": target
};
},
- "specificness": 7,
+ "specificness": 6,
},
"repair":
@@ -646,6 +760,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;
@@ -915,11 +1030,12 @@
data.target = targetState.id;
cursor = "action-repair";
}
+ // TODO other attackTypes should be allowed too, maybe this needs a rewrite of the rallypoint handling
else if (playerCheck(entState, targetState, ["Enemy"]))
{
data.target = targetState.id;
- data.command = "attack";
- cursor = "action-attack";
+ 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)
@@ -957,7 +1073,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
+ "prefAttackTypes": ["!Capture"],
+ "queued": true
});
let patrolTargets = shuffleArray(this.GetTriggerPoints(triggerPointRef)).slice(0, patrolCount);
@@ -454,8 +454,8 @@
"targetClasses": {
"attack": targetClass
},
- "queued": true,
- "allowCapture": false
+ "prefAttackTypes": ["!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
+ "prefAttackTypes": ["!Capture"],
+ "queued": true
});
}
}
@@ -578,8 +578,8 @@
"targetClasses": {
"attack": spawnPointBalancing.targetClasses()
},
- "queued": true,
- "allowCapture": false
+ "prefAttackTypes": ["!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
@@ -99,6 +99,7 @@
"type": "attack",
"entities": attackers[spawnPoint],
"target": target,
+ "prefAttackTypes": ["!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,
+ "prefAttackTypes": ["!Capture"],
"queued": true
});
Index: binaries/data/mods/public/maps/skirmishes/Gallic Fields (3).js
===================================================================
--- binaries/data/mods/public/maps/skirmishes/Gallic Fields (3).js
+++ binaries/data/mods/public/maps/skirmishes/Gallic Fields (3).js
@@ -23,7 +23,7 @@
cmd.type = "attack-walk";
cmd.entities = intruders[origin];
cmd.targetClasses = { "attack": ["Unit", "Structure"] };
- cmd.allowCapture = false;
+ cmd.prefAttackTypes = ["!Capture"];
cmd.queued = true;
ProcessCommand(0, cmd);
}
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
@@ -384,7 +384,7 @@
Engine.QueryInterface(e, IID_Identity) && Engine.QueryInterface(e, IID_UnitAI) &&
Engine.QueryInterface(e, IID_Identity).HasClass("CitizenSoldier")
);
- this.attackers.forEach(e => { Engine.QueryInterface(e, IID_UnitAI).WalkAndFight(position.x, position.y, { "attack": ["Unit"] }, false); });
+ this.attackers.forEach(e => { Engine.QueryInterface(e, IID_UnitAI).WalkAndFight(position.x, position.y, { "attack": ["Unit"] }, ["!Capture"]); });
};
Trigger.prototype.IsAttackRepelled = function()
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
@@ -202,9 +202,10 @@
return undefined;
return {
- "Hack": +this.get("Armour/Hack"),
- "Pierce": +this.get("Armour/Pierce"),
- "Crush": +this.get("Armour/Crush")
+ "Hack": +(this.get("Armour/Hack") || 0),
+ "Pierce": +(this.get("Armour/Pierce") || 0),
+ "Crush": +(this.get("Armour/Crush") || 0),
+ "Capture": +(this.get("Armour/Capture") || 0)
};
},
@@ -235,17 +236,11 @@
return {
"Hack": +(this.get("Attack/" + type + "/Damage/Hack") || 0),
"Pierce": +(this.get("Attack/" + type + "/Damage/Pierce") || 0),
- "Crush": +(this.get("Attack/" + type + "/Damage/Crush") || 0)
+ "Crush": +(this.get("Attack/" + type + "/Damage/Crush") || 0),
+ "Capture": +(this.get("Attack/" + type + "/Damage/Capture") || 0)
};
},
- "captureStrength": function() {
- if (!this.get("Attack/Capture"))
- return undefined;
-
- return +this.get("Attack/Capture/Value") || 0;
- },
-
"attackTimes": function(type) {
if (!this.get("Attack/" + type +""))
return undefined;
@@ -525,7 +520,7 @@
*/
"canCapture": function(target)
{
- if (!this.get("Attack/Capture"))
+ if (!this.get("Attack/Capture")) //TODO
return false;
if (!target)
return true;
@@ -751,8 +746,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, prefAttackTypes = ["Capture"], queued = false) {
+ Engine.PostCommand(PlayerID, { "type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "targetClasses": targetClasses, "prefAttackTypes": prefAttackTypes, "queued": queued });
return this;
},
@@ -788,8 +783,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, prefAttackTypes = ["Capture"], queued = false) {
+ Engine.PostCommand(PlayerID, { "type": "attack", "entities": [this.id()], "target": unitId, "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, prefAttackTypes = ["Capture"], queued = false)
{
Engine.PostCommand(PlayerID, { "type": "attack-walk", "entities": this.toIdArray(), "x": x, "z": z,
- "targetClasses": targetClasses, "allowCapture": allowCapture, "queued": queued });
+ "targetClasses": targetClasses, "prefAttackTypes": prefAttackTypes, "queued": queued });
return this;
};
@@ -181,9 +181,9 @@
return this;
};
-m.EntityCollection.prototype.attack = function(unitId)
+m.EntityCollection.prototype.attack = function(unitId, prefAttackTypes)
{
- Engine.PostCommand(PlayerID, { "type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false });
+ Engine.PostCommand(PlayerID, { "type": "attack", "entities": this.toIdArray(), "target": unitId, "prefAttackTypes": prefAttackTypes, "queued": false });
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
@@ -224,7 +224,7 @@
attackingUnits.add(ent.id());
if (dist > range)
ent.move(x, z);
- ent.attack(struct.id(), false, dist > range);
+ ent.attack(struct.id(), ["!Capture"], 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
@@ -1334,13 +1334,13 @@
{
if (m.isSiegeUnit(ent)) // needed as mauryan elephants are not filtered out
continue;
- ent.attack(attacker.id(), m.allowCapture(gameState, ent, attacker));
+ ent.attack(attacker.id(), m.getPrefAttackTypes(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 (m.isSiegeUnit(attacker) && attacker.hasClass("Melee") && ourUnit.hasClass("Melee"))
{
- ourUnit.attack(attacker.id(), m.allowCapture(gameState, ourUnit, attacker));
+ ourUnit.attack(attacker.id(), m.getPrefAttackTypes(gameState, ourUnit, attacker));
ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
}
@@ -1357,7 +1357,7 @@
let collec = this.unitCollection.filter(API3.Filters.byClass("Melee")).filterNearest(ourUnit.position(), 5);
for (let ent of collec.values())
{
- ent.attack(attacker.id(), m.allowCapture(gameState, ent, attacker));
+ ent.attack(attacker.id(), m.getPrefAttackTypes(gameState, ent, attacker));
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
}
@@ -1378,7 +1378,7 @@
if (target && !target.hasClass("Structure") && !target.hasClass("Support"))
continue;
}
- ent.attack(attacker.id(), m.allowCapture(gameState, ent, attacker));
+ ent.attack(attacker.id(), m.getPrefAttackTypes(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
@@ -1395,7 +1395,7 @@
continue;
}
}
- ourUnit.attack(attacker.id(), m.allowCapture(gameState, ourUnit, attacker));
+ ourUnit.attack(attacker.id(), m.getPrefAttackTypes(gameState, ourUnit, attacker));
ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
}
@@ -1577,11 +1577,11 @@
return valb - vala;
});
if (mStruct[0].hasClass("Gates"))
- ent.attack(mStruct[0].id(), m.allowCapture(gameState, ent, mStruct[0]));
+ ent.attack(mStruct[0].id(), m.getPrefAttackTypes(gameState, ent, mStruct[0]));
else
{
let rand = randIntExclusive(0, mStruct.length * 0.2);
- ent.attack(mStruct[rand].id(), m.allowCapture(gameState, ent, mStruct[rand]));
+ ent.attack(mStruct[rand].id(), m.getPrefAttackTypes(gameState, ent, mStruct[rand]));
}
}
else
@@ -1639,10 +1639,10 @@
return valb - vala;
});
let rand = randIntExclusive(0, mUnit.length * 0.1);
- ent.attack(mUnit[rand].id(), m.allowCapture(gameState, ent, mUnit[rand]));
+ ent.attack(mUnit[rand].id(), m.getPrefAttackTypes(gameState, ent, mUnit[rand]));
}
else if (this.isBlocked)
- ent.attack(this.target.id(), false);
+ ent.attack(this.target.id(), ["!Capture"]);
else if (API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500)
{
let targetClasses = targetClassesUnit;
@@ -1686,11 +1686,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(), m.allowCapture(gameState, ent, mStruct[rand]));
+ ent.attack(mStruct[rand].id(), m.getPrefAttackTypes(gameState, ent, mStruct[rand]));
}
}
else if (needsUpdate) // really nothing let's try to help our nearest unit
@@ -1712,7 +1712,7 @@
attacker = gameState.getEntityById(unit.unitAIOrderData()[0].target);
});
if (attacker)
- ent.attack(attacker.id(), m.allowCapture(gameState, ent, attacker));
+ ent.attack(attacker.id(), m.getPrefAttackTypes(gameState, ent, attacker));
}
}
}
@@ -1765,7 +1765,7 @@
continue;
if (!ent.isIdle())
continue;
- ent.attack(attacker.id(), m.allowCapture(gameState, ent, attacker));
+ ent.attack(attacker.id(), m.getPrefAttackTypes(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
@@ -357,7 +357,7 @@
{
this.assignedTo[entID] = idFoe;
this.assignedAgainst[idFoe].push(entID);
- ent.attack(idFoe, m.allowCapture(gameState, ent, foeEnt), queued);
+ ent.attack(idFoe, m.getPrefAttackTypes(gameState, ent, foeEnt), queued);
}
else
gameState.ai.HQ.navalManager.requireTransport(gameState, ent, ownIndex, foeIndex, foePosition);
@@ -573,8 +573,8 @@
else if (orderData.length && orderData[0].target && orderData[0].attackType && orderData[0].attackType === "Capture")
{
let target = gameState.getEntityById(orderData[0].target);
- if (target && !m.allowCapture(gameState, ent, target))
- ent.attack(orderData[0].target, false);
+ if (target && m.getPrefAttackTypes(gameState, ent, target).indexOf("Capture") != -1) // TODO doesn't cover all cases
+ ent.attack(orderData[0].target, ["!Capture"]);
}
}
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
@@ -724,11 +724,11 @@
if (minEnt)
{
capturableTarget.ents.delete(minEnt.id());
- minEnt.attack(attacker.id(), m.allowCapture(gameState, minEnt, attacker));
+ minEnt.attack(attacker.id(), m.getPrefAttackTypes(gameState, minEnt, attacker));
}
}
}
- target.attack(attacker.id(), m.allowCapture(gameState, target, attacker));
+ target.attack(attacker.id(), m.getPrefAttackTypes(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
@@ -37,6 +37,9 @@
case "Pierce":
strength += val * 0.065 / 3;
break;
+ case "Capture":
+ // TODO implement this
+ break;
default:
API3.warn("Petra: " + str + " unknown attackStrength in getMaxStrength");
}
@@ -79,6 +82,9 @@
case "Pierce":
strength += val * 0.065 / 3;
break;
+ case "Capture":
+ // TODO implement this
+ break;
default:
API3.warn("Petra: " + str + " unknown armourStrength in getMaxStrength");
}
@@ -154,16 +160,18 @@
m.getSeaAccess(gameState, ent);
};
-/** Decide if we should try to capture (returns true) or destroy (return false) */
-m.allowCapture = function(gameState, ent, target)
+/**
+ * Decide if we should try to capture, melee or ranged attack.
+*/
+m.getPrefAttackTypes = 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())
@@ -175,7 +183,7 @@
let capturableTargets = gameState.ai.HQ.capturableTargets;
if (!capturableTargets.has(target.id()))
{
- capture = ent.captureStrength() * m.getAttackBonus(ent, target, "Capture");
+ capture = ent.attackStrengths("Capture").Capture * m.getAttackBonus(ent, target, "Capture");
capturableTargets.set(target.id(), { "strength": capture, "ents": new Set([ent.id()]) });
}
else
@@ -183,7 +191,7 @@
let capturable = capturableTargets.get(target.id());
if (!capturable.ents.has(ent.id()))
{
- capturable.strength += ent.captureStrength() * m.getAttackBonus(ent, target, "Capture");
+ capturable.strength += ent.attackStrengths("Capture").Capture * m.getAttackBonus(ent, target, "Capture");
capturable.ents.add(ent.id());
}
capture = capturable.strength;
@@ -191,8 +199,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;
+ return capture > antiCapture + sumCapturePoints/50 ? ["Capture"] : ["!Capture"];
+ return capture > antiCapture + sumCapturePoints/80 ? ["Capture"] : ["!Capture"];
};
m.getAttackBonus = function(ent, target, type)
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
@@ -2499,13 +2499,13 @@
continue;
if (!this.capturableTargets.has(targetId))
this.capturableTargets.set(targetId, {
- "strength": ent.captureStrength() * m.getAttackBonus(ent, target, "Capture"),
+ "strength": ent.attackStrengths("Capture").Capture * m.getAttackBonus(ent, target, "Capture"),
"ents": new Set([ent.id()])
});
else
{
let capturableTarget = this.capturableTargets.get(target.id());
- capturableTarget.strength += ent.captureStrength() * m.getAttackBonus(ent, target, "Capture");
+ capturableTarget.strength += ent.attackStrengths("Capture").Capture * m.getAttackBonus(ent, target, "Capture");
capturableTarget.ents.add(ent.id());
}
}
@@ -2513,17 +2513,16 @@
for (let [targetId, capturableTarget] of this.capturableTargets)
{
let target = gameState.getEntityById(targetId);
- let allowCapture;
+ let prefAttackTypes;
for (let entId of capturableTarget.ents)
{
let ent = gameState.getEntityById(entId);
- if (allowCapture === undefined)
- allowCapture = m.allowCapture(gameState, ent, target);
+ if (prefAttackTypes === undefined)
+ prefAttackTypes = m.getPrefAttackTypes(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, prefAttackTypes);
}
}
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
@@ -662,7 +662,7 @@
if (!attack)
continue;
for (let ent of attack.unitCollection.values())
- capture += ent.captureStrength() * m.getAttackBonus(ent, relic, "Capture");
+ capture += ent.attackStrengths("Capture").Capture * m.getAttackBonus(ent, relic, "Capture");
}
// No need to make a new attack if already enough units
if (capture > sumCapturePoints / 50)
@@ -692,7 +692,7 @@
let expedition = [];
for (let ent of units.values())
{
- capture += ent.captureStrength() * m.getAttackBonus(ent, relic, "Capture");
+ capture += ent.attackStrengths("Capture").Capture * m.getAttackBonus(ent, relic, "Capture");
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
@@ -184,10 +184,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 prefAttackTypes === undefined.
let target = gameState.getEntityById(orderData.target);
if (target && target.owner() > 0 && !gameState.isPlayerAlly(target.owner()))
- ent.attack(orderData.target, m.allowCapture(gameState, ent, target));
+ ent.attack(orderData.target, m.getPrefAttackTypes(gameState, ent, target));
}
}
return;
Index: binaries/data/mods/public/simulation/components/Armour.js
===================================================================
--- binaries/data/mods/public/simulation/components/Armour.js
+++ binaries/data/mods/public/simulation/components/Armour.js
@@ -6,6 +6,7 @@
"10.0" +
"0.0" +
"5.0" +
+ "0" +
"" +
DamageTypes.BuildSchema("damage protection") +
"" +
@@ -34,46 +35,49 @@
/**
* Take damage according to the entity's armor.
+ * @param {number} playerID - Owner of the attacking entity.
* @param {Object} strengths - { "hack": number, "pierce": number, "crush": number } or something like that.
- * @param {number} multiplier - the damage multiplier.
- * Returns object of the form { "killed": false, "change": -12 }.
+ * @param {number} multiplier - The damage multiplier.
+ * Returns object of the form { "killed": false, "captured": false, "healthChange": -12, "captureChange": 0 }.
*/
-Armour.prototype.TakeDamage = function(strengths, multiplier = 1)
+Armour.prototype.TakeDamage = function(playerID, strengths, multiplier = 1)
{
- if (this.invulnerable)
- return { "killed": false, "change": 0 };
+ let result = { "killed": false, "captured": false, "healthChange": 0, "captureChange": 0 }
+ let damages = this.GetDamage(strengths, multiplier);
+ for (let component in damages)
+ {
+ // TODO adapt invulnerability into something scalable with damageTypes/Components (ie also make an "uncapturable")
+ if (component == IID_Health && this.invulnerable)
+ continue;
- // Adjust damage values based on armour; exponential armour: damage = attack * 0.9^armour
- var armourStrengths = this.GetArmourStrengths();
-
- // Total is sum of individual damages
- // Don't bother rounding, since HP is no longer integral.
- var total = 0;
- for (let type in strengths)
- total += strengths[type] * multiplier * Math.pow(0.9, armourStrengths[type] || 0);
-
- // Reduce health
- var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
- return cmpHealth.Reduce(total);
+ let cmpRecieveComponent = Engine.QueryInterface(this.entity, +component);
+ if (cmpRecieveComponent)
+ {
+ let componentResult = cmpRecieveComponent.Reduce(damages[component], playerID)
+ for (let key in componentResult)
+ result[key] = result[key] || componentResult[key];
+ }
+ }
+ return result;
};
Armour.prototype.GetArmourStrengths = function()
{
// Work out the armour values with technology effects
- var applyMods = (type, foundation) => {
- var strength;
+ let applyMods = (type, foundation) => {
+ let strength;
if (foundation)
{
- strength = +this.template.Foundation[type];
+ strength = +(this.template.Foundation[type] || 0);
type = "Foundation/" + type;
}
else
- strength = +this.template[type];
+ strength = +(this.template[type] || 0);
return ApplyValueModificationsToEntity("Armour/" + type, strength, this.entity);
};
- var foundation = Engine.QueryInterface(this.entity, IID_Foundation) && this.template.Foundation;
+ let foundation = Engine.QueryInterface(this.entity, IID_Foundation) && this.template.Foundation;
let ret = {};
for (let damageType of DamageTypes.GetTypes())
@@ -82,4 +86,39 @@
return ret;
};
+Armour.prototype.GetArmourMultipliers = function()
+{
+ let armourStrength = this.GetArmourStrengths();
+ let ret = {};
+ for (let strength in armourStrength)
+ ret[strength] = Math.pow(0.9, armourStrength[strength]);
+
+ // Capture is special, let that depend on health
+ let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
+ if (cmpHealth && cmpHealth.GetHitpoints() != 0)
+ {
+ ret.capture = ret.capture || 1;
+ ret.capture *= cmpHealth.GetMaxHitpoints() / (0.1 * cmpHealth.GetMaxHitpoints() + 0.9 * cmpHealth.GetHitpoints());
+ }
+ return ret;
+};
+
+Armour.prototype.GetDamage = function(strengths, multiplier = 1)
+{
+ let armourMultipliers = this.GetArmourMultipliers();
+ let damageComponents = DamageTypes.GetComponents();
+
+ // Total is sum of individual damages per component
+ // Don't bother rounding, since HP is no longer integral.
+ let total = {};
+ for (let type in strengths)
+ {
+ if (total[damageComponents[type]] === undefined)
+ total[damageComponents[type]] = 0;
+ total[damageComponents[type]] += strengths[type] * multiplier * armourMultipliers[type];
+ }
+
+ return total;
+};
+
Engine.RegisterComponentType(IID_DamageReceiver, "Armour", Armour);
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.statusEffectsSchema =
"" +
"" +
@@ -127,130 +125,101 @@
"4.0" +
"" +
"" +
- "" +
- "" +
- "" +
- "" +
- DamageTypes.BuildSchema("damage strength") +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + // TODO: it shouldn't be stretched
- "" +
- "" +
- Attack.prototype.bonusesSchema +
- Attack.prototype.preferredClassesSchema +
- Attack.prototype.restrictedClassesSchema +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- DamageTypes.BuildSchema("damage strength") +
- "" +
- "" +
- "" +
- ""+
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
"" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- DamageTypes.BuildSchema("damage strength") +
- "" +
- Attack.prototype.bonusesSchema +
- "" +
+ "" +
+ DamageTypes.BuildSchema("damage strength") +
"" +
- "" +
- "" +
- "" +
- "" +
- "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ ""+
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
"" +
- "" +
- "" +
- "" +
+ "" +
+
+ "" +
+ "" +
+ "" +
"" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- Attack.prototype.statusEffectsSchema +
- Attack.prototype.bonusesSchema +
- Attack.prototype.preferredClassesSchema +
- Attack.prototype.restrictedClassesSchema +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + // TODO: it shouldn't be stretched
- "" +
- "" +
- Attack.prototype.bonusesSchema +
- Attack.prototype.preferredClassesSchema +
- Attack.prototype.restrictedClassesSchema +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- DamageTypes.BuildSchema("damage strength") +
- "" +
- "" + // TODO: how do these work?
- Attack.prototype.bonusesSchema +
- Attack.prototype.preferredClassesSchema +
- Attack.prototype.restrictedClassesSchema +
- "" +
- "" +
- "";
+ "" +
+ "" + // TODO: it shouldn't be stretched
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ DamageTypes.BuildSchema("damage strength") +
+ "" +
+ Attack.prototype.bonusesSchema +
+ "" +
+ "" +
+ "" +
+
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ Attack.prototype.statusEffectsSchema +
+ Attack.prototype.bonusesSchema +
+ Attack.prototype.preferredClassesSchema +
+ Attack.prototype.restrictedClassesSchema +
+ "" +
+ "" +
+ "" +
+ "";
Attack.prototype.Init = function()
{
@@ -260,7 +229,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;
@@ -289,50 +258,56 @@
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;
-
- let cmpIdentity = QueryMiragedInterface(target, IID_Identity);
- if (!cmpIdentity)
- return false;
+ return [];
+ 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 => {
+ //TODO splash?
+ if (Object.keys(this.template[type].Damage).every(damageType => {
+ if (!this.template[type].Damage[damageType])
+ return true;
+ let cmp = QueryMiragedInterface(target, DamageTypes.GetComponents()[damageType]);
+ return !cmp || !cmp.CanAttack(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)
@@ -340,9 +315,7 @@
if (!MatchesClassList(targetClasses, restrictedClasses))
return true;
- }
-
- return false;
+ });
};
/**
@@ -387,16 +360,20 @@
return ret;
};
-Attack.prototype.GetBestAttackAgainst = function(target, allowCapture)
+Attack.prototype.GetBestAttackAgainst = function(target, prefAttackTypes)
{
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);
+ return types.length ? types[0] : undefined;
}
+ let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+ if (!cmpPosition || !cmpPosition.IsInWorld())
+ return undefined;
+
let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
if (!cmpIdentity)
return undefined;
@@ -404,30 +381,53 @@
let targetClasses = cmpIdentity.GetClassesList();
let isTargetClass = className => targetClasses.indexOf(className) != -1;
- // Always slaughter domestic animals instead of using a normal attack
- if (isTargetClass("Domestic") && this.template.Slaughter)
- return "Slaughter";
-
- let types = this.GetAttackTypes().filter(type => !this.GetRestrictedClasses(type).some(isTargetClass));
-
- // check if the target is capturable
- let captureIndex = types.indexOf("Capture");
- if (captureIndex != -1)
- {
- let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
+ // Try find an allowed attack preferred by prefAttackTypes, if none exists try all attack types in the template
+ let types = this.GetAllowedAttackTypes(target, prefAttackTypes);
+ if (!types.length)
+ types = this.GetAllowedAttackTypes(target);
+ if (!types.length)
+ return undefined;
- let cmpPlayer = QueryOwnerInterface(this.entity);
- if (allowCapture && cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID()))
- return "Capture";
- // not capturable, so remove this attack
- types.splice(captureIndex, 1);
- }
+ // For performance: don't worry calculating the best when there is only one
+ if (types.length == 1)
+ return types[0];
- let isPreferred = className => this.GetPreferredClasses(className).some(isTargetClass);
+ let cmpDamageReceiver = QueryMiragedInterface(target, IID_DamageReceiver);
+ if (!cmpDamageReceiver)
+ return undefined;
- return types.sort((a, b) =>
- (types.indexOf(a) + (isPreferred(a) ? types.length : 0)) -
- (types.indexOf(b) + (isPreferred(b) ? types.length : 0))).pop();
+ 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 damage = cmpDamageReceiver.GetDamage(this.GetAttackStrengths(type), GetDamageBonus(this.entity, target, type, this.GetBonusTemplate(type)));
+ for (let cmp in damage)
+ {
+ let cmpReciever = QueryMiragedInterface(target, +cmp);
+ if (!cmpReciever)
+ continue;
+ DPSRange += damage[cmp] / cmpReciever.GetMax() / this.GetTimers(type).repeat;
+ }
+
+ // 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));
+
+ if (DPSRange > bestDPSRange)
+ {
+ bestType = type;
+ bestDPSRange = DPSRange;
+ }
+ }
+ return bestType
};
Attack.prototype.CompareEntitiesByPreference = function(a, b)
@@ -443,13 +443,10 @@
Attack.prototype.GetTimers = function(type)
{
- let prepare = +(this.template[type].PrepareTime || 0);
- prepare = ApplyValueModificationsToEntity("Attack/" + type + "/PrepareTime", prepare, this.entity);
-
- let repeat = +(this.template[type].RepeatTime || 1000);
- repeat = ApplyValueModificationsToEntity("Attack/" + type + "/RepeatTime", repeat, this.entity);
-
- return { "prepare": prepare, "repeat": repeat };
+ return {
+ "prepare": ApplyValueModificationsToEntity("Attack/" + type + "/PrepareTime", +(this.template[type].PrepareTime || 0), this.entity),
+ "repeat": ApplyValueModificationsToEntity("Attack/" + type + "/RepeatTime", +this.template[type].RepeatTime, this.entity)
+ };
};
Attack.prototype.GetAttackStrengths = function(type)
@@ -466,9 +463,6 @@
let applyMods = damageType =>
ApplyValueModificationsToEntity("Attack/" + type + splash + "/Damage/" + damageType, +(template.Damage[damageType] || 0), this.entity);
- if (type == "Capture")
- return { "value": ApplyValueModificationsToEntity("Attack/Capture/Value", +(template.Value || 0), this.entity) };
-
let ret = {};
for (let damageType of DamageTypes.GetTypes())
ret[damageType] = applyMods(damageType);
@@ -511,6 +505,16 @@
return template.Bonuses || null;
};
+Attack.prototype.GetTooltipHeader = function(type)
+{
+ return this.template[type] ? this.template[type].TooltipHeader : "";
+};
+
+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
@@ -518,13 +522,39 @@
*/
Attack.prototype.PerformAttack = function(type, target)
{
+ // Safety check, TODO find out if it is required
+ if (!this.CanAttack(target, [type]))
+ return;
+
let attackerOwner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+
+ let data = {
+ "type": type,
+ "attacker": this.entity,
+ "target": target,
+ "strengths": this.GetAttackStrengths(type),
+ "isSplash": false,
+ "attackerOwner": attackerOwner,
+ "statusEffects": this.template[type].StatusEffects
+ };
- // If this is a ranged attack, then launch a projectile
- if (type == "Ranged")
+ if (this.template[type].Splash)
{
- let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ data.friendlyFire = this.template[type].Splash.FriendlyFire != "false";
+ data.isSplash = true;
+ data.radius = +this.template[type].Splash.Range;
+ data.shape = this.template[type].Splash.Shape;
+ data.splashStrengths = this.GetAttackStrengths(type + ".Splash");
+ data.splashBonus = this.GetBonusTemplate(type + ".Splash");
+ }
+
+ // When we have a projectile, launch it.
+ if (this.template[type].Projectile)
+ {
+ data.bonus = this.GetBonusTemplate(type);
+
let turnLength = cmpTimer.GetLatestTurnLength()/1000;
// In the future this could be extended:
// * Obstacles like trees could reduce the probability of the target being hit
@@ -550,7 +580,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();
@@ -558,9 +588,11 @@
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);
+ data.direction = Vector3D.sub(realTargetPosition, selfPosition).div(realHorizDistance);
timeToTarget = realHorizDistance / horizSpeed;
let missileDirection = Vector3D.sub(realTargetPosition, selfPosition).div(realHorizDistance);
@@ -593,74 +625,36 @@
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,
- "attacker": this.entity,
- "target": target,
- "strengths": this.GetAttackStrengths(type),
- "position": realTargetPosition,
- "direction": missileDirection,
- "projectileId": id,
- "bonus": this.GetBonusTemplate(type),
- "isSplash": false,
- "attackerOwner": attackerOwner,
- "attackImpactSound": attackImpactSound,
- "statusEffects": this.template[type].StatusEffects
- };
- if (this.template[type].Splash)
- {
- data.friendlyFire = this.template[type].Splash.FriendlyFire != "false";
- data.radius = +this.template[type].Splash.Range;
- data.shape = this.template[type].Splash.Shape;
- data.isSplash = true;
- data.splashStrengths = this.GetAttackStrengths(type + ".Splash");
- data.splashBonus = this.GetBonusTemplate(type + ".Splash");
- }
- cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000 + +this.template[type].Delay, data);
+ cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000 + +(this.template[type].Delay || 0), data);
}
- else if (type == "Capture")
+ // Close attack, hurt the target immediately after this.template.Delay
+ else
{
- if (attackerOwner == INVALID_PLAYER)
- return;
+ data.multiplier = GetDamageBonus(this.entity, target, type, this.GetBonusTemplate(type));
- let multiplier = GetDamageBonus(this.entity, target, type, this.GetBonusTemplate(type));
- let cmpHealth = Engine.QueryInterface(target, IID_Health);
- if (!cmpHealth || cmpHealth.GetHitpoints() == 0)
- return;
- multiplier *= cmpHealth.GetMaxHitpoints() / (0.1 * cmpHealth.GetMaxHitpoints() + 0.9 * cmpHealth.GetHitpoints());
+ if (this.template[type].Splash)
+ {
+ 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 cmpCapturable = Engine.QueryInterface(target, IID_Capturable);
- if (!cmpCapturable || !cmpCapturable.CanCapture(attackerOwner))
- return;
+ data.position = targetPosition;
+ data.direction = new Vector3D.sub(targetPosition, selfPosition);
+ }
- let strength = this.GetAttackStrengths("Capture").value * multiplier;
- if (cmpCapturable.Reduce(strength, attackerOwner) && IsOwnedByEnemyOfPlayer(attackerOwner, target))
- Engine.PostMessage(target, MT_Attacked, {
- "attacker": this.entity,
- "target": target,
- "type": type,
- "damage": strength,
- "attackerOwner": attackerOwner
- });
- }
- else
- {
- // Melee attack - hurt the target immediately
- cmpDamage.CauseDamage({
- "strengths": this.GetAttackStrengths(type),
- "target": target,
- "attacker": this.entity,
- "multiplier": GetDamageBonus(this.entity, target, type, this.GetBonusTemplate(type)),
- "type": type,
- "attackerOwner": attackerOwner
- });
+ cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "CauseDamage", +(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
@@ -33,6 +33,9 @@
return this.maxCp;
};
+//TODO get rid of this
+Capturable.prototype.GetMax = Capturable.prototype.GetMaxCapturePoints;
+
Capturable.prototype.GetGarrisonRegenRate = function()
{
return ApplyValueModificationsToEntity("Capturable/GarrisonRegenRate", +this.template.GarrisonRegenRate, this.entity);
@@ -56,15 +59,15 @@
Capturable.prototype.Reduce = function(amount, playerID)
{
if (amount <= 0)
- return 0;
+ return { "captured": false, "captureChange": 0 };
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership || cmpOwnership.GetOwner() == INVALID_PLAYER)
- return 0;
+ return { "captured": false, "captureChange": 0 };
var cmpPlayerSource = QueryPlayerIDInterface(playerID);
if (!cmpPlayerSource)
- return 0;
+ return { "captured": false, "captureChange": 0 };
// Before changing the value, activate Fogging if necessary to hide changes
var cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
@@ -74,7 +77,7 @@
var numberOfEnemies = this.cp.filter((v, i) => v > 0 && cmpPlayerSource.IsEnemy(i)).length;
if (numberOfEnemies == 0)
- return 0;
+ return { "captured": false, "captureChange": 0 };
// Distribute the capture points over all enemies.
let distributedAmount = amount / numberOfEnemies;
@@ -106,8 +109,7 @@
this.cp[playerID] += takenCp;
this.CheckTimer();
- this.RegisterCapturePointsChanged();
- return takenCp;
+ return { "captured": this.RegisterCapturePointsChanged(), "captureChange": takenCp };
};
/**
@@ -127,23 +129,39 @@
return sourceEnemyCp > 0;
};
+// TODO stop this duplication
+Capturable.prototype.CanAttack = function(playerID)
+{
+ var cmpPlayerSource = QueryPlayerIDInterface(playerID);
+
+ if (!cmpPlayerSource)
+ warn(playerID + " has no player component defined on its id");
+ var cp = this.GetCapturePoints();
+ var sourceEnemyCp = 0;
+ for (let i in this.GetCapturePoints())
+ if (cmpPlayerSource.IsEnemy(i))
+ sourceEnemyCp += cp[i];
+ return sourceEnemyCp > 0;
+};
+
//// Private functions ////
/**
* This has to be called whenever the capture points are changed.
* It notifies other components of the change, and switches ownership when needed.
+ * return {boolean} - Whether the entity has changed owner.
*/
Capturable.prototype.RegisterCapturePointsChanged = function()
{
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership)
- return;
+ return false;
Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp });
var owner = cmpOwnership.GetOwner();
if (owner == INVALID_PLAYER || this.cp[owner] > 0)
- return;
+ return false;
// If all cp has been taken from the owner, convert it to the best player.
var bestPlayer = 0;
@@ -160,6 +178,8 @@
let cmpCapturedPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
if (cmpCapturedPlayerStatisticsTracker)
cmpCapturedPlayerStatisticsTracker.CapturedEntity(this.entity);
+
+ return true;
};
Capturable.prototype.GetRegenRate = function()
@@ -207,9 +227,9 @@
var regenRate = this.GetRegenRate();
if (regenRate < 0)
- modifiedCp += this.Reduce(-regenRate, 0);
+ modifiedCp += this.Reduce(-regenRate, 0).captureChange;
else if (regenRate > 0)
- modifiedCp += this.Reduce(regenRate, owner);
+ modifiedCp += this.Reduce(regenRate, owner).captureChange;
if (modifiedCp)
return;
Index: binaries/data/mods/public/simulation/components/Damage.js
===================================================================
--- binaries/data/mods/public/simulation/components/Damage.js
+++ binaries/data/mods/public/simulation/components/Damage.js
@@ -84,7 +84,7 @@
* @param {number} data.attacker - the entity id of the attacker.
* @param {number} data.target - the entity id of the target.
* @param {Vector2D} data.origin - the origin of the projectile hit.
- * @param {Object} data.strengths - data of the form { 'hack': number, 'pierce': number, 'crush': number }.
+ * @param {Object} data.strengths - data of the form { 'hack': number, 'pierce': number, 'crush': number, 'capture': number }.
* @param {string} data.type - the type of damage.
* @param {number} data.attackerOwner - the player id of the owner of the attacker.
* @param {boolean} data.isSplash - a flag indicating if it's splash damage.
@@ -99,7 +99,7 @@
* @param {number} data.radius - the radius of the splash damage.
* @param {string} data.shape - the shape of the splash range.
* @param {Object} data.splashBonus - the attack bonus template from the attacker.
- * @param {Object} data.splashStrengths - data of the form { 'hack': number, 'pierce': number, 'crush': number }.
+ * @param {Object} data.splashStrengths - data of the form { 'hack': number, 'pierce': number, 'crush': number, 'capture': number }.
*/
Damage.prototype.MissileHit = function(data, lateness)
{
@@ -177,7 +177,7 @@
* @param {Vector2D} data.origin - the origin of the projectile hit.
* @param {number} data.radius - the radius of the splash damage.
* @param {string} data.shape - the shape of the radius.
- * @param {Object} data.strengths - data of the form { 'hack': number, 'pierce': number, 'crush': number }.
+ * @param {Object} data.strengths - data of the form { 'hack': number, 'pierce': number, 'crush': number, 'capture': number }.
* @param {string} data.type - the type of damage.
* @param {number} data.attackerOwner - the player id of the attacker.
* @param {Vector3D} [data.direction] - the unit vector defining the direction. Needed for linear splash damage.
@@ -239,32 +239,59 @@
/**
* Causes damage on a given unit.
- * @param {Object} data - the data passed by the caller.
- * @param {Object} data.strengths - data in the form of { 'hack': number, 'pierce': number, 'crush': number }.
- * @param {number} data.target - the entity id of the target.
- * @param {number} data.attacker - the entity id of the attacker.
- * @param {number} data.multiplier - the damage multiplier.
- * @param {string} data.type - the type of damage.
- * @param {number} data.attackerOwner - the player id of the attacker.
+ * @param {Object} data - the data passed by the caller.
+ * @param {Object} data.strengths - data in the form of { 'hack': number, 'pierce': number, 'crush': number, 'capture': number }.
+ * @param {number} data.target - the entity id of the target.
+ * @param {number} data.attacker - the entity id of the attacker.
+ * @param {number} data.multiplier - the damage multiplier.
+ * @param {string} data.type - the type of damage.
+ * @param {number} data.attackerOwner - the player id of the attacker.
+ * @param {boolean} data.isSplash - a flag indicating if it's splash damage.
+ * ***When splash damage***
+ * @param {boolean} data.friendlyFire - a flag indicating if allied entities are also damaged.
+ * @param {number} data.radius - the radius of the splash damage.
+ * @param {string} data.shape - the shape of the splash range.
+ * @param {Object} data.splashBonus - the attack bonus template from the attacker.
+ * @param {Object} data.splashStrengths - data of the form { 'hack': number, 'pierce': number, 'crush': number, 'capture': number }.
+ * @param {Vector3D} data.position - the position of the target.
+ * @param {Vector3D} data.direction - The unit vector defining the direction
*/
Damage.prototype.CauseDamage = function(data)
{
+ // Do splash first in case the direct hit kills or captures the target
+ if (data.isSplash)
+ this.CauseSplashDamage({
+ "attacker": data.attacker,
+ "origin": Vector2D.from3D(data.position),
+ "radius": data.radius,
+ "shape": data.shape,
+ "strengths": data.splashStrengths,
+ "splashBonus": data.splashBonus,
+ "direction": data.direction,
+ "playersToDamage": data.friendlyFire ?
+ Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayers() :
+ QueryPlayerIDInterface(data.attackerOwner).GetEnemies(),
+ "type": data.type,
+ "attackerOwner": data.attackerOwner
+ });
+
let cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
if (!cmpDamageReceiver)
return;
- let targetState = cmpDamageReceiver.TakeDamage(data.strengths, data.multiplier);
+ let targetState = cmpDamageReceiver.TakeDamage(data.attackerOwner, data.strengths, data.multiplier);
let cmpPromotion = Engine.QueryInterface(data.attacker, IID_Promotion);
let cmpLoot = Engine.QueryInterface(data.target, IID_Loot);
let cmpHealth = Engine.QueryInterface(data.target, IID_Health);
+ //TODO capture?
if (cmpPromotion && cmpLoot && cmpLoot.GetXp() > 0)
- cmpPromotion.IncreaseXp(cmpLoot.GetXp() * -targetState.change / cmpHealth.GetMaxHitpoints());
+ cmpPromotion.IncreaseXp(cmpLoot.GetXp() * -targetState.healthChange / cmpHealth.GetMaxHitpoints());
if (targetState.killed)
this.TargetKilled(data.attacker, data.target, data.attackerOwner);
- Engine.PostMessage(data.target, MT_Attacked, { "attacker": data.attacker, "target": data.target, "type": data.type, "damage": -targetState.change, "attackerOwner": data.attackerOwner });
+ Engine.PostMessage(data.target, MT_Attacked, { "attacker": data.attacker, "target": data.target, "type": data.type, "damage": (-targetState.healthChange || 0) + (targetState.captureChange || 0), "attackerOwner": data.attackerOwner });
};
/**
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,10 @@
if (cmpCapturable)
cmpMirage.CopyCapturable(cmpCapturable);
+ var cmpDamageReceiver = Engine.QueryInterface(this.entity, IID_DamageReceiver);
+ if (cmpDamageReceiver)
+ cmpMirage.CopyDamageReceiver(cmpDamageReceiver);
+
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
@@ -389,30 +389,21 @@
for (let type of types)
{
ret.attack[type] = {};
- if (type == "Capture")
- ret.attack[type] = cmpAttack.GetAttackStrengths(type);
- else
- ret.attack[type].damage = cmpAttack.GetAttackStrengths(type);
+ ret.attack[type].tooltipHeader = cmpAttack.GetTooltipHeader(type);
+ ret.attack[type].damage = cmpAttack.GetAttackStrengths(type);
ret.attack[type].splash = cmpAttack.GetSplashDamage(type);
let range = cmpAttack.GetRange(type);
ret.attack[type].minRange = range.min;
ret.attack[type].maxRange = range.max;
+ ret.attack[type].elevationBonus = range.elevationBonus;
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;
+ ret.attack[type].projectile = cmpAttack.HasProjectile(type);
if (cmpUnitAI && cmpPosition && cmpPosition.IsInWorld())
{
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
@@ -78,6 +78,9 @@
return this.maxHitpoints;
};
+// TODO get rid of this
+Health.prototype.GetMax = Health.prototype.GetMaxHitpoints;
+
/**
* @return {boolean} Whether the units are injured. Dead units are not considered injured.
*/
@@ -107,6 +110,17 @@
this.RegisterHealthChanged(old);
};
+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;
@@ -188,7 +202,7 @@
// might get called multiple times)
// Likewise if the amount is 0.
if (!amount || !this.hitpoints)
- return { "killed": false, "change": 0 };
+ return { "killed": false, "healthChange": 0 };
// Before changing the value, activate Fogging if necessary to hide changes
let cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
@@ -202,7 +216,7 @@
this.hitpoints = 0;
this.RegisterHealthChanged(oldHitpoints);
this.HandleDeath();
- return { "killed": true, "change": -oldHitpoints };
+ return { "killed": true, "healthChange": -oldHitpoints };
}
// If we are not marked as injured, do it now
@@ -215,7 +229,7 @@
this.hitpoints -= amount;
this.RegisterHealthChanged(oldHitpoints);
- return { "killed": false, "change": this.hitpoints - oldHitpoints };
+ return { "killed": false, "healthChange": this.hitpoints - oldHitpoints };
};
/**
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
@@ -29,6 +29,8 @@
this.capturePoints = [];
this.maxCapturePoints = 0;
+ this.armourStrengths = {};
+
this.maxAmount = null;
this.amount = null;
this.type = null;
@@ -114,6 +116,7 @@
Mirage.prototype.IsRepairable = function() { return this.repairable; };
Mirage.prototype.IsInjured = function() { return this.injured; };
Mirage.prototype.IsUnhealable = function() { return this.unhealable; };
+Mirage.prototype.CanAttack = Health.prototype.CanAttack;
// Capture data
@@ -128,6 +131,18 @@
Mirage.prototype.GetCapturePoints = function() { return this.capturePoints; };
Mirage.prototype.CanCapture = Capturable.prototype.CanCapture;
+//TODO stop dupli
+Mirage.prototype.CanAttack = Capturable.prototype.CanAttack;
+
+// Armour data
+Mirage.prototype.CopyDamageReceiver = function(cmpDamageReceiver)
+{
+ this.miragedIids.add(IID_DamageReceiver);
+ this.armourStrengths = cmpDamageReceiver.GetArmourStrengths();
+};
+
+Mirage.prototype.GetArmourStrengths = function() { return this.armourStrengths };
+Mirage.prototype.GetDamage = Armour.prototype.GetDamage;
// ResourceSupply data
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
@@ -393,7 +393,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.prefAttackTypes);
if (!type)
{
// Oops, we can't attack at all
@@ -500,7 +500,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
@@ -524,7 +524,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, "prefAttackTypes": ["!Capture"] });
return;
}
@@ -704,7 +704,6 @@
"Order.Attack": function(msg) {
var target = msg.data.target;
- var allowCapture = msg.data.allowCapture;
var cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI);
if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember())
target = cmpTargetUnitAI.GetFormationController();
@@ -721,7 +720,7 @@
this.FinishOrder();
return;
}
- this.CallMemberFunction("Attack", [target, allowCapture, false]);
+ this.CallMemberFunction("Attack", [target, msg.data.prefAttackTypes, false]);
if (cmpAttack.CanAttackAsFormation())
this.SetNextState("COMBAT.ATTACKING");
else
@@ -772,7 +771,7 @@
return;
}
- this.PushOrderFront("Attack", { "target": msg.data.target, "force": !!msg.data.force, "hunting": true, "allowCapture": false });
+ this.PushOrderFront("Attack", { "target": msg.data.target, "force": !!msg.data.force, "hunting": true, "prefAttackTypes": ["!apture"] });
return;
}
@@ -951,7 +950,7 @@
{
this.patrolStartPosOrder = cmpPosition.GetPosition();
this.patrolStartPosOrder.targetClasses = this.order.data.targetClasses;
- this.patrolStartPosOrder.allowCapture = this.order.data.allowCapture;
+ this.patrolStartPosOrder.prefAttackTypes = this.order.data.prefAttackTypes;
}
if (!this.MoveTo(this.order.data))
@@ -1104,7 +1103,7 @@
"MovementUpdate": function(msg) {
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
- this.CallMemberFunction("Attack", [this.order.data.target, this.order.data.allowCapture, false]);
+ this.CallMemberFunction("Attack", [this.order.data.target, this.order.data.prefAttackTypes, false]);
if (cmpAttack.CanAttackAsFormation())
this.SetNextState("COMBAT.ATTACKING");
else
@@ -1116,14 +1115,13 @@
// Wait for individual members to finish
"enter": function(msg) {
var target = this.order.data.target;
- var allowCapture = this.order.data.allowCapture;
// Check if we are already in range, otherwise walk there
if (!this.CheckTargetAttackRange(target, 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, "prefAttackTypes": this.order.data.prefAttackTypes });
return true;
}
this.FinishOrder();
@@ -1140,14 +1138,13 @@
"Timer": function(msg) {
var target = this.order.data.target;
- var allowCapture = this.order.data.allowCapture;
// Check if we are already in range, otherwise walk there
if (!this.CheckTargetAttackRange(target, 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, "prefAttackTypes": this.order.data.prefAttackTypes });
return;
}
this.FinishOrder();
@@ -1374,7 +1371,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, "prefAttackTypes": ["Capture"] });
else
{
var cmpPosition = Engine.QueryInterface(msg.data.attacker, IID_Position);
@@ -1459,8 +1456,10 @@
},
"LosRangeUpdate": function(msg) {
+warn(this.entity)
if (this.GetStance().targetVisibleEnemies)
{
+warn(this.entity)
// Start attacking one of the newly-seen enemy (if any)
this.AttackEntitiesByPreference(msg.data.added);
}
@@ -1549,7 +1548,7 @@
{
this.patrolStartPosOrder = cmpPosition.GetPosition();
this.patrolStartPosOrder.targetClasses = this.order.data.targetClasses;
- this.patrolStartPosOrder.allowCapture = this.order.data.allowCapture;
+ this.patrolStartPosOrder.prefAttackTypes = this.order.data.prefAttackTypes;
}
this.StartTimer(0, 1000);
@@ -1963,8 +1962,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]);
},
},
@@ -4483,12 +4483,12 @@
return distance < range;
};
-UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture)
+UnitAI.prototype.GetBestAttackAgainst = function(target, prefAttackTypes)
{
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
if (!cmpAttack)
return undefined;
- return cmpAttack.GetBestAttackAgainst(target, allowCapture);
+ return cmpAttack.GetBestAttackAgainst(target, prefAttackTypes);
};
/**
@@ -4502,7 +4502,7 @@
if (!target)
return false;
- this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": true });
+ this.PushOrderFront("Attack", { "target": target, "force": false, "prefAttackTypes": ["Capture"] });
return true;
};
@@ -4515,13 +4515,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, "prefAttackTypes": ["Capture"] });
return true;
};
@@ -4915,12 +4915,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, prefAttackTypes = ["Capture"], 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, "prefAttackTypes": prefAttackTypes, "force": true }, queued);
};
-UnitAI.prototype.Patrol = function(x, z, targetClasses, allowCapture = true, queued = false)
+UnitAI.prototype.Patrol = function(x, z, targetClasses, prefAttackTypes = ["Capture"], queued = false)
{
if (!this.CanPatrol())
{
@@ -4928,7 +4928,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, "prefAttackTypes": prefAttackTypes, "force": true }, queued);
};
/**
@@ -4949,7 +4949,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, prefAttackTypes = ["Capture"], queued = false)
{
if (!this.CanAttack(target))
{
@@ -4965,7 +4965,7 @@
let order = {
"target": target,
"force": true,
- "allowCapture": allowCapture,
+ "prefAttackTypes": prefAttackTypes,
};
this.RememberTargetPosition(order);
@@ -5413,7 +5413,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, "prefAttackTypes": this.order.data.prefAttackTypes });
return true;
}
}
@@ -5439,7 +5439,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, "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
@@ -3,13 +3,16 @@
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Attack.js");
+Engine.LoadComponentScript("interfaces/DamageReceiver.js");
Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Auras.js");
Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/Formation.js");
+Engine.LoadComponentScript("interfaces/Foundation.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Engine.LoadComponentScript("Attack.js");
+Engine.LoadComponentScript("Armour.js");
let entityID = 903;
@@ -25,8 +28,11 @@
});
AddMock(playerEnt1, IID_Player, {
- "GetPlayerID": () => 1,
- "IsEnemy": () => isEnemy
+ "GetPlayerID": () => 1
+ });
+
+ AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
+ "DistanceToTarget": (ent, target) => 10
});
}
@@ -34,6 +40,7 @@
AddMock(attacker, IID_Position, {
"IsInWorld": () => true,
+ "GetTurretParent": () => INVALID_ENTITY,
"GetHeightOffset": () => 5,
"GetPosition2D": () => new Vector2D(1, 2)
});
@@ -51,6 +58,8 @@
},
"MinRange": 3,
"MaxRange": 5,
+ "PrepareTime": 0,
+ "RepeatTime": 1000,
"PreferredClasses": {
"_string": "FemaleCitizen"
},
@@ -104,10 +113,26 @@
}
},
"Capture": {
- "Value": 8,
+ "Damage": {
+ "Capture": 30
+ },
"MaxRange": 10,
+ "PrepareTime": 0,
+ "RepeatTime": 1000
},
- "Slaughter": {}
+ "Slaughter": {
+ "Damage": {
+ "Hack": 100,
+ "Pierce": 0,
+ "Crush": 0
+ },
+ "MaxRange": 5,
+ "PrepareTime": 0,
+ "RepeatTime": 1000,
+ "RestrictedClasses": {
+ "_string": "!Domestic"
+ },
+ }
});
let defender = ++entityID;
@@ -117,6 +142,14 @@
"HasClass": className => className == defenderClass
});
+ // TODO reuse the actual armour function
+ ConstructComponent(defender, "Armour", {
+ "Hack": 1,
+ "Pierce": 1,
+ "Crush": 1,
+ "Capture": 1
+ });
+
AddMock(defender, IID_Ownership, {
"GetOwner": () => 1
});
@@ -127,7 +160,10 @@
});
AddMock(defender, IID_Health, {
- "GetHitpoints": () => 100
+ "GetHitpoints": () => 100,
+ "GetMax": () => 100,
+ "GetMaxHitpoints": () => 100,
+ "CanAttack": () => isEnemy || defenderClass == "Domestic"
});
test_function(attacker, cmpAttack, defender);
@@ -136,32 +172,39 @@
// 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", "Slaughter"]);
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes([]), ["Melee", "Ranged", "Capture", "Slaughter"]);
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(["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(["!Melee"]), ["Ranged", "Capture", "Slaughter"]);
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["!Melee", "!Ranged"]), ["Capture", "Slaughter"]);
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.GetPreferredClasses("Melee"), ["FemaleCitizen"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRestrictedClasses("Melee"), ["Elephant", "Archer"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetFullAttackRange(), { "min": 0, "max": 80 });
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Capture"), { "value": 8 });
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Capture"), {
+ "Hack": 0,
+ "Pierce": 0,
+ "Crush": 0,
+ "Capture": 30
+ });
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Ranged"), {
"Hack": 0,
"Pierce": 10,
- "Crush": 0
+ "Crush": 0,
+ "Capture": 0
});
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Ranged.Splash"), {
"Hack": 0.0,
"Pierce": 15.0,
- "Crush": 35.0
+ "Crush": 35.0,
+ "Capture": 0
});
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetTimers("Ranged"), {
@@ -179,6 +222,7 @@
"Hack": 0,
"Pierce": 15,
"Crush": 35,
+ "Capture": 0
},
"friendlyFire": false,
"shape": "Circular"
@@ -209,12 +253,21 @@
{
attackComponentTest(defenderClass, true, (attacker, cmpAttack, defender) => {
+ AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
+ "DistanceToTarget": (ent, target) => 10
+ });
+
if (isBuilding)
AddMock(defender, IID_Capturable, {
"CanCapture": playerID => {
TS_ASSERT_EQUALS(playerID, 1);
return true;
- }
+ },
+ "CanAttack": playerID => {
+ TS_ASSERT_EQUALS(playerID, 1);
+ return true;
+ },
+ "GetMax": () => 100
});
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), true);
@@ -222,17 +275,25 @@
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Ranged"]), true);
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Melee"]), true);
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Capture"]), isBuilding);
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Slaughter"]), defenderClass == "Domestic");
+ TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Slaughter"]), true);
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.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" ? "Ranged" : bestAttack);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Ranged"]), "Ranged");
+ 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" ? "Ranged" : "Melee");
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Ranged", "Capture"]), isBuilding ? "Capture" : "Ranged");
+ 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) => {
@@ -242,35 +303,37 @@
"CanCapture": playerID => {
TS_ASSERT_EQUALS(playerID, 1);
return true;
+ },
+ "CanAttack": playerID => {
+ TS_ASSERT_EQUALS(playerID, 1);
+ return 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, ["Ranged"]), defenderClass == "Domestic");
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, ["Slaughter"]), defenderClass == "Domestic");
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);
-
- let attack;
- if (defenderClass == "Domestic")
- attack = "Slaughter";
- else if (defenderClass == "Structure")
- attack = "Capture";
-
- for (let ac of allowCapturing)
- TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ac), bestAttack);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : undefined);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, []), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : undefined);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Slaughter"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : undefined);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["!Slaughter"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Ranged" : undefined);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Ranged"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Ranged" : undefined);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["!Melee"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : undefined);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Capture"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : undefined);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Melee", "Capture"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Melee" : undefined);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Ranged", "Capture"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Ranged" : undefined);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["!Ranged", "!Melee"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : undefined);
+ TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ["Melee", "!Melee"]), isBuilding ? "Capture" : defenderClass == "Domestic" ? "Slaughter" : undefined);
});
}
-testGetBestAttackAgainst("FemaleCitizen", "Melee");
+testGetBestAttackAgainst("FemaleCitizen", "Ranged");
testGetBestAttackAgainst("Archer", "Ranged");
testGetBestAttackAgainst("Domestic", "Slaughter");
testGetBestAttackAgainst("Structure", "Capture", true);
Index: binaries/data/mods/public/simulation/components/tests/test_Capturable.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Capturable.js
+++ binaries/data/mods/public/simulation/components/tests/test_Capturable.js
@@ -117,7 +117,7 @@
if (iid == MT_CaptureRegenStateChanged)
TS_ASSERT_UNEVAL_EQUALS(message, { "regenerating": true, "regenRate": cmpCapturable.GetRegenRate(), "territoryDecay": 0 });
};
- TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.Reduce(100, 3), 100);
+ TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.Reduce(100, 3), { "captured": false, "captureChange": 100 });
TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.GetCapturePoints(), [0, 2000 - 100, 0, 1000 + 100]);
});
@@ -125,7 +125,7 @@
testCapturable(testData, cmpCapturable => {
cmpCapturable.SetCapturePoints([0, 2000, 0, 1000]);
cmpCapturable.CheckTimer();
- TS_ASSERT_EQUALS(cmpCapturable.Reduce(2500, 3), 2000);
+ TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.Reduce(2500, 3), { "captured": true, "captureChange": 2000 });
TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.GetCapturePoints(), [0, 0, 0, 3000]);
});
@@ -176,9 +176,10 @@
function testReduce(testData, amount, player, taken)
{
testCapturable(testData, cmpCapturable => {
- cmpCapturable.SetCapturePoints([0, 2000, 0, 1000]);
+ let capturePoints = [0, 2000, 0, 1000];
+ cmpCapturable.SetCapturePoints(clone(capturePoints));
cmpCapturable.CheckTimer();
- TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.Reduce(amount, player), taken);
+ TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.Reduce(amount, player), { "captured": capturePoints[player] + amount >= 3000, "captureChange": taken });
});
}
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,8 +6,10 @@
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/AttackDetection.js");
Engine.LoadComponentScript("interfaces/AuraManager.js");
+Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/Damage.js");
Engine.LoadComponentScript("interfaces/DamageReceiver.js");
+Engine.LoadComponentScript("interfaces/Formation.js")
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/Loot.js");
Engine.LoadComponentScript("interfaces/Player.js");
@@ -32,6 +34,9 @@
let cmpAttack = ConstructComponent(attacker, "Attack",
{
"Ranged": {
+ "Damage": {
+ "Hack": 1
+ },
"MaxRange": 50,
"MinRange": 0,
"Delay": 0,
@@ -51,7 +56,7 @@
let type = "Melee";
let damageTaken = false;
- cmpAttack.GetAttackStrengths = attackType => ({ "hack": 0, "pierce": 0, "crush": damage });
+ cmpAttack.GetAttackStrengths = attackType => ({ "hack": 0, "pierce": 0, "crush": damage, "Capture": 0 });
let data = {
"attacker": attacker,
@@ -67,6 +72,7 @@
};
AddMock(atkPlayerEntity, IID_Player, {
+ "GetPlayerID": () => atkPlayerEntity,
"GetEnemies": () => [targetOwner]
});
@@ -80,17 +86,24 @@
"LaunchProjectileAtPoint": (ent, pos, speed, gravity) => {},
});
+ AddMock(target, IID_Identity, {
+ "GetClassesList": () => []
+ });
+
AddMock(target, IID_Position, {
"GetPosition": () => targetPos,
"GetPreviousPosition": () => targetPos,
"GetPosition2D": () => Vector2D.From(targetPos),
+ "GetHeightOffset": () => 0,
"IsInWorld": () => true,
});
- AddMock(target, IID_Health, {});
+ AddMock(target, IID_Health, {
+ "CanAttack": () => true
+ });
AddMock(target, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => { damageTaken = true; return { "killed": false, "change": -multiplier * strengths.crush }; },
+ "TakeDamage": (playerID, strengths, multiplier) => { damageTaken = true; return { "killed": false, "healthChange": -multiplier * strengths.crush }; },
});
Engine.PostMessage = function(ent, iid, message)
@@ -103,13 +116,15 @@
});
AddMock(attacker, IID_Ownership, {
- "GetOwner": () => attackerOwner,
+ "GetOwner": () => attackerOwner
});
AddMock(attacker, IID_Position, {
"GetPosition": () => new Vector3D(2, 0, 3),
"GetRotation": () => new Vector3D(1, 2, 3),
"IsInWorld": () => true,
+ "GetHeightOffset": () => 0,
+ "GetTurretParent": () => INVALID_ENTITY
});
function TestDamage()
@@ -189,26 +204,26 @@
});
AddMock(60, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ "TakeDamage": (playerID, strengths, multiplier) => {
hitEnts.add(60);
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(2.2, -0.4));
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ return { "killed": false, "healthChange": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(61, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ "TakeDamage": (playerID, strengths, multiplier) => {
hitEnts.add(61);
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(0, 0));
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ return { "killed": false, "healthChange": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(62, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ "TakeDamage": (playerID, strengths, multiplier) => {
hitEnts.add(62);
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 0);
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ return { "killed": false, "healthChange": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
@@ -221,10 +236,10 @@
data.direction = new Vector3D(0.6, 747, 0.8);
AddMock(60, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ "TakeDamage": (playerID, strengths, multiplier) => {
hitEnts.add(60);
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(1, 2));
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ return { "killed": false, "healthChange": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
@@ -277,36 +292,36 @@
});
AddMock(60, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ "TakeDamage": (playerID, strengths, multiplier) => {
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(0));
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ return { "killed": false, "healthChange": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(61, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ "TakeDamage": (playerID, strengths, multiplier) => {
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(5));
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ return { "killed": false, "healthChange": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(62, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ "TakeDamage": (playerID, strengths, multiplier) => {
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(1));
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ return { "killed": false, "healthChange": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(63, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ "TakeDamage": (playerID, strengths, multiplier) => {
TS_ASSERT(false);
}
});
AddMock(64, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ "TakeDamage": (playerID, strengths, multiplier) => {
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 0);
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ return { "killed": false, "healthChange": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
@@ -375,10 +390,10 @@
AddMock(60, IID_Health, {});
AddMock(60, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ "TakeDamage": (playerID, strengths, multiplier) => {
hitEnts.add(60);
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100);
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ return { "killed": false, "healthChange": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
@@ -414,9 +429,9 @@
});
AddMock(60, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ "TakeDamage": (playerID, strengths, multiplier) => {
TS_ASSERT_EQUALS(false);
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ return { "killed": false, "healthChange": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
@@ -434,10 +449,10 @@
AddMock(61, IID_Health, {});
AddMock(61, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ "TakeDamage": (playerID, strengths, multiplier) => {
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100);
hitEnts.add(61);
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ return { "killed": false, "healthChange": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
@@ -463,10 +478,10 @@
let dealtDamage = 0;
AddMock(61, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ "TakeDamage": (playerID, strengths, multiplier) => {
dealtDamage += multiplier * (strengths.hack + strengths.pierce + strengths.crush);
hitEnts.add(61);
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ return { "killed": false, "healthChange": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
@@ -480,10 +495,10 @@
AddMock(62, IID_Health, {});
AddMock(62, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ "TakeDamage": (playerID, strengths, multiplier) => {
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 200 * 0.75);
hitEnts.add(62);
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ return { "killed": false, "healthChange": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
Index: binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
+++ binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
@@ -24,14 +24,16 @@
"Damage": {
"Hack": 0.0,
"Pierce": 15.0,
- "Crush": 35.0
+ "Crush": 35.0,
+ "Capture": 0
}
};
let modifiedDamage = {
"Hack": 0.0,
"Pierce": 215.0,
- "Crush": 35.0
+ "Crush": 35.0,
+ "Capture": 0
};
let cmpDeathDamage = ConstructComponent(deadEnt, "DeathDamage", template);
Index: binaries/data/mods/public/simulation/components/tests/test_Health.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Health.js
+++ binaries/data/mods/public/simulation/components/tests/test_Health.js
@@ -64,7 +64,7 @@
TS_ASSERT_EQUALS(injured_flag, true);
TS_ASSERT_EQUALS(change.killed, false);
-TS_ASSERT_EQUALS(change.change, -25);
+TS_ASSERT_EQUALS(change.healthChange, -25);
TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 25);
TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50);
TS_ASSERT_EQUALS(cmpHealth.IsInjured(), true);
@@ -107,7 +107,7 @@
TS_ASSERT_EQUALS(injured_flag, false);
TS_ASSERT_EQUALS(change.killed, true);
-TS_ASSERT_EQUALS(change.change, -50);
+TS_ASSERT_EQUALS(change.healthChange, -50);
TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 0);
TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50);
TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false);
@@ -122,7 +122,7 @@
// Check that we can't die twice.
change = cmpHealth.Reduce(50);
TS_ASSERT_EQUALS(change.killed, false);
-TS_ASSERT_EQUALS(change.change, 0);
+TS_ASSERT_EQUALS(change.healthChange, 0);
TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 0);
TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50);
TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false);
@@ -132,7 +132,7 @@
// Check that we still die with > Max HP of damage.
change = cmpHealth.Reduce(60);
TS_ASSERT_EQUALS(change.killed, true);
-TS_ASSERT_EQUALS(change.change, -50);
+TS_ASSERT_EQUALS(change.healthChange, -50);
TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 0);
TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50);
TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false);
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
@@ -101,7 +101,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; },
@@ -256,7 +256,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/data/auras/units/catafalques/maur_catafalque_2.json
===================================================================
--- binaries/data/mods/public/simulation/data/auras/units/catafalques/maur_catafalque_2.json
+++ binaries/data/mods/public/simulation/data/auras/units/catafalques/maur_catafalque_2.json
@@ -2,7 +2,7 @@
"type": "global",
"affects": ["Soldier"],
"modifications": [
- { "value": "Attack/Capture/Value", "multiply": 1.15 }
+ { "value": "Attack/Capture/Damage/Capture", "multiply": 1.15 }
],
"auraName": "Vamba Moriyar",
"auraDescription": "Bindusara is said to have conquered lands to the south of the empire.\n+15% unit capture rate."
Index: binaries/data/mods/public/simulation/data/auras/units/heroes/brit_hero_boudicca.json
===================================================================
--- binaries/data/mods/public/simulation/data/auras/units/heroes/brit_hero_boudicca.json
+++ binaries/data/mods/public/simulation/data/auras/units/heroes/brit_hero_boudicca.json
@@ -10,7 +10,7 @@
{ "value": "Attack/Ranged/Damage/Hack", "multiply": 1.2 },
{ "value": "Attack/Ranged/Damage/Pierce", "multiply": 1.2 },
{ "value": "Attack/Ranged/Damage/Crush", "multiply": 1.2 },
- { "value": "Attack/Capture/Value", "add": 2 }
+ { "value": "Attack/Capture/Damage/Capture", "add": 2 }
],
"auraName": "Champion Army",
"auraDescription": "+20% attack, +2 capture and +10% speed for champion units.",
Index: binaries/data/mods/public/simulation/data/auras/units/heroes/cart_hero_hannibal.json
===================================================================
--- binaries/data/mods/public/simulation/data/auras/units/heroes/cart_hero_hannibal.json
+++ binaries/data/mods/public/simulation/data/auras/units/heroes/cart_hero_hannibal.json
@@ -10,7 +10,7 @@
{ "value": "Attack/Ranged/Damage/Hack", "multiply": 1.20 },
{ "value": "Attack/Ranged/Damage/Pierce", "multiply": 1.20 },
{ "value": "Attack/Ranged/Damage/Crush", "multiply": 1.20 },
- { "value": "Attack/Capture/Value", "add": 1 }
+ { "value": "Attack/Capture/Damage/Capture", "add": 1 }
],
"auraName": "Tactician",
"auraDescription": "+20% attack and +1 capture for nearby allied soldiers and siege engines.",
Index: binaries/data/mods/public/simulation/data/auras/units/heroes/gaul_hero_vercingetorix.json
===================================================================
--- binaries/data/mods/public/simulation/data/auras/units/heroes/gaul_hero_vercingetorix.json
+++ binaries/data/mods/public/simulation/data/auras/units/heroes/gaul_hero_vercingetorix.json
@@ -9,7 +9,7 @@
{ "value": "Attack/Ranged/Damage/Hack", "multiply": 1.20 },
{ "value": "Attack/Ranged/Damage/Pierce", "multiply": 1.20 },
{ "value": "Attack/Ranged/Damage/Crush", "multiply": 1.20 },
- { "value": "Attack/Capture/Value", "add": 1 }
+ { "value": "Attack/Capture/Damage/Capture", "add": 1 }
],
"auraName": "Celtic Warlord",
"auraDescription": "+20% attack and +1 capture for soldiers and siege engines.",
Index: binaries/data/mods/public/simulation/data/auras/units/heroes/mace_hero_craterus.json
===================================================================
--- binaries/data/mods/public/simulation/data/auras/units/heroes/mace_hero_craterus.json
+++ binaries/data/mods/public/simulation/data/auras/units/heroes/mace_hero_craterus.json
@@ -6,7 +6,7 @@
{ "value": "Attack/Melee/Damage/Hack", "multiply": 1.2 },
{ "value": "Attack/Melee/Damage/Pierce", "multiply": 1.2 },
{ "value": "Attack/Melee/Damage/Crush", "multiply": 1.2 },
- { "value": "Attack/Capture/Value", "multiply": 1.2 }
+ { "value": "Attack/Capture/Damage/Capture", "multiply": 1.2 }
],
"auraName": "Taxiarchès",
"auraDescription": "+20% attack and +20% capture for pikemen.",
Index: binaries/data/mods/public/simulation/data/auras/units/heroes/mace_hero_philip.json
===================================================================
--- binaries/data/mods/public/simulation/data/auras/units/heroes/mace_hero_philip.json
+++ binaries/data/mods/public/simulation/data/auras/units/heroes/mace_hero_philip.json
@@ -9,7 +9,7 @@
{ "value": "Attack/Ranged/Damage/Hack", "multiply": 1.2 },
{ "value": "Attack/Ranged/Damage/Pierce", "multiply": 1.2 },
{ "value": "Attack/Ranged/Damage/Crush", "multiply": 1.2 },
- { "value": "Attack/Capture/Value", "add": 2 }
+ { "value": "Attack/Capture/Damage/Capture", "add": 2 }
],
"auraName": "Rise of Macedon",
"auraDescription": "+20% attack and +2 capture for champion units.",
Index: binaries/data/mods/public/simulation/data/auras/units/heroes/pers_hero_cyrus.json
===================================================================
--- binaries/data/mods/public/simulation/data/auras/units/heroes/pers_hero_cyrus.json
+++ binaries/data/mods/public/simulation/data/auras/units/heroes/pers_hero_cyrus.json
@@ -9,7 +9,7 @@
{ "value": "Attack/Ranged/Damage/Hack", "multiply": 1.2 },
{ "value": "Attack/Ranged/Damage/Pierce", "multiply": 1.2 },
{ "value": "Attack/Ranged/Damage/Crush", "multiply": 1.2 },
- { "value": "Attack/Capture/Value", "add": 1 }
+ { "value": "Attack/Capture/Damage/Capture", "add": 1 }
],
"auraName": "Forefront Leader",
"auraDescription": "+20% attack and +1 capture for cavalry soldiers.",
Index: binaries/data/mods/public/simulation/data/auras/units/heroes/rome_hero_scipio.json
===================================================================
--- binaries/data/mods/public/simulation/data/auras/units/heroes/rome_hero_scipio.json
+++ binaries/data/mods/public/simulation/data/auras/units/heroes/rome_hero_scipio.json
@@ -9,7 +9,7 @@
{ "value": "Attack/Ranged/Damage/Hack", "multiply": 1.2 },
{ "value": "Attack/Ranged/Damage/Pierce", "multiply": 1.2 },
{ "value": "Attack/Ranged/Damage/Crush", "multiply": 1.2 },
- { "value": "Attack/Capture/Value", "add": 2 }
+ { "value": "Attack/Capture/Damage/Capture", "add": 2 }
],
"auraName": "Triumph",
"auraDescription": "+20% attack and +2 capture for soldiers and siege engines.",
Index: binaries/data/mods/public/simulation/data/auras/units/heroes/spart_hero_leonidas.json
===================================================================
--- binaries/data/mods/public/simulation/data/auras/units/heroes/spart_hero_leonidas.json
+++ binaries/data/mods/public/simulation/data/auras/units/heroes/spart_hero_leonidas.json
@@ -6,7 +6,7 @@
{ "value": "Attack/Melee/Damage/Hack", "multiply": 1.25 },
{ "value": "Attack/Melee/Damage/Pierce", "multiply": 1.25 },
{ "value": "Attack/Melee/Damage/Crush", "multiply": 1.25 },
- { "value": "Attack/Capture/Value", "add": 1 }
+ { "value": "Attack/Capture/Damage/Capture", "add": 1 }
],
"auraName": "Last Stand",
"auraDescription": "+25% attack and +1 capture for spear soldiers.",
Index: binaries/data/mods/public/simulation/data/technologies/advanced_unit_bonus.json
===================================================================
--- binaries/data/mods/public/simulation/data/technologies/advanced_unit_bonus.json
+++ binaries/data/mods/public/simulation/data/technologies/advanced_unit_bonus.json
@@ -7,7 +7,7 @@
{ "value": "Armour/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 },
- { "value": "Attack/Capture/Value", "add": 0.4 },
+ { "value": "Attack/Capture/Damage/Capture", "add": 0.4 },
{ "value": "ResourceGatherer/BaseSpeed", "multiply": 0.5 },
{ "value": "UnitMotion/WalkSpeed", "add": 0.5, "affects": "Infantry" },
{ "value": "UnitMotion/WalkSpeed", "add": 1, "affects": "Cavalry" },
Index: binaries/data/mods/public/simulation/data/technologies/elite_unit_bonus.json
===================================================================
--- binaries/data/mods/public/simulation/data/technologies/elite_unit_bonus.json
+++ binaries/data/mods/public/simulation/data/technologies/elite_unit_bonus.json
@@ -7,7 +7,7 @@
{ "value": "Armour/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 },
- { "value": "Attack/Capture/Value", "add": 0.4 },
+ { "value": "Attack/Capture/Damage/Capture", "add": 0.4 },
{ "value": "ResourceGatherer/BaseSpeed", "multiply": 0.5 },
{ "value": "UnitMotion/WalkSpeed", "add": 0.5, "affects": "Infantry" },
{ "value": "UnitMotion/WalkSpeed", "add": 1, "affects": "Cavalry" },
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
@@ -173,41 +173,36 @@
"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.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.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 can 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.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.prefAttackTypes, cmd.queued)
);
},
Index: binaries/data/mods/public/simulation/helpers/DamageTypes.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/DamageTypes.js
+++ binaries/data/mods/public/simulation/helpers/DamageTypes.js
@@ -1,8 +1,20 @@
DamageTypes.prototype.BuildSchema = function(helptext = "")
{
return "" + this.GetTypes().reduce((schema, type) =>
- schema + "",
+ schema + "",
"") + "";
};
+DamageTypes.prototype.GetComponents = function()
+{
+ // TODO hack less, maybe freeze
+ // These components are expected to have a GetMax, Reduce and CanAttack function
+ let componentsToID = { "health": IID_Health, "capturable": IID_Capturable };
+ let components = {};
+ for (let type in this.types)
+ components[type] = componentsToID[this.types[type].receiveComponent];
+
+ return components;
+};
+
DamageTypes = new DamageTypes();
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 @@
+ Melee Attack:
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 @@
+ Ranged Attack:
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 @@
+ Ranged Attack:
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 @@
+ Ranged Attack:
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 @@
+ Ranged Attack:
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 @@
+ Ranged Attack:
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,24 @@
- 2
+ Capture Attack:
+
+ 2
+
4
1000
Field Palisade SiegeWall StoneWall
+ Slaughter Attack:
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 @@
+ Melee Attack:
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 @@
+ Ranged Attack:
0
9.0
Index: binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged_javelinist.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged_javelinist.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged_javelinist.xml
@@ -2,6 +2,7 @@
+ Ranged Attack:
0
18.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,7 +2,10 @@
- 5
+ Capture Attack:
+
+ 5
+
4
1000
Field Palisade SiegeWall StoneWall
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 @@
+ Ranged Attack:
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 @@
+ Ranged Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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 @@
+ Ranged Attack:
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 @@
+ Ranged Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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
@@ -6,6 +6,7 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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,7 +7,10 @@
- 15
+ Capture Attack:
+
+ 15
+
4
1000
Field Palisade SiegeWall StoneWall
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 @@
+ Ranged Attack:
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 @@
+ Ranged Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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 @@
+ Ranged Attack:
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 @@
+ Ranged Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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,24 @@
- 2
+ Capture Attack:
+
+ 2
+
4
1000
Field Palisade SiegeWall StoneWall
+ Slaughter Attack:
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 @@
+ Melee Attack:
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 @@
+ Ranged Attack:
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 @@
+ Ranged Attack:
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 @@
+ Melee Attack:
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 @@
+ Melee Attack:
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 @@
+ Ranged Attack:
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 @@
+ Ranged Attack:
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 @@
+ Ranged Attack:
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 @@
+ Melee Attack:
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 @@
+ Ranged Attack:
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 @@
+ Ranged Attack:
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 @@
+ Melee Attack:
2.0
0
@@ -12,12 +13,15 @@
1000
+ Slaughter Attack:
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,25 @@
+
+
+ Ranged Attack:
+
+ 0
+ 6.0
+ 0
+
+ 72.0
+ 0.0
+ 600
+ 1000
+
+ 75.0
+ 3.0
+ 9.81
+
+
+
+
pers
persian
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 @@
+ Ranged Attack:
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 @@
+ Ranged Attack:
50.0
0.0