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