Ticket #252: t252_secondattack_21.2.diff
File t252_secondattack_21.2.diff, 54.9 KB (added by , 8 years ago) |
---|
-
binaries/data/config/default.cfg
280 280 kill = Delete ; Destroy selected units 281 281 stop = "H" ; Stop the current action 282 282 unload = "U" ; Unload garrisoned units when a building/mechanical unit is selected 283 attack = Ctrl ; Modifier to attack instead of another action (eg capture)284 attackmove = Ctrl ; Modifier to attackmove when clicking on a point285 attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point (should contain the attackmove keys)283 attack = Ctrl ; Modifier to primary attack instead of another action (eg capture) 284 attackmove = Ctrl ; Modifier to primary attackmove when clicking on a point 285 attackmoveUnit = "Ctrl+Q" ; Modifier to primary attackmove targeting only units when clicking on a point (should contain the attackmove keys) 286 286 garrison = Ctrl ; Modifier to garrison when clicking on building 287 287 autorallypoint = Ctrl ; Modifier to set the rally point on the building itself 288 meleeattack = Alt ; Modifier to melee attack instead of another action 289 rangedattack = Space ; Modifier to ranged attack instead of another action 288 290 guard = "G" ; Modifier to escort/guard when clicking on unit/building 289 291 repair = "J" ; Modifier to repair when clicking on building/mechanical unit 290 292 queue = Shift ; Modifier to queue unit orders instead of replacing -
binaries/data/mods/public/art/actors/units/persians/champion_unit_1.xml
42 42 <group> 43 43 <variant frequency="1" name="Idle"/> 44 44 <variant file="biped/attack_capture.xml"/> 45 <variant file="biped/attack_ranged_archer.xml"> 46 <props> 47 <prop attachpoint="r_hand"/> 48 <prop actor="props/units/weapons/bow_recurve.xml" attachpoint="l_hand"/> 49 </props> 50 </variant> 45 51 </group> 46 52 <material>player_trans.xml</material> 47 53 </actor> -
binaries/data/mods/public/art/actors/units/persians/infantry_archer_a.xml
36 36 <group> 37 37 <variant frequency="100" name="Idle"/> 38 38 <variant file="biped/attack_ranged_archer.xml"/> 39 <variant file="biped/attack_melee_ranged.xml"/> 39 40 <variant file="biped/attack_capture.xml"/> 40 41 <variant file="biped/attack_slaughter.xml"/> 41 42 <variant file="biped/gather_tree.xml"/> -
binaries/data/mods/public/art/actors/units/persians/infantry_archer_b.xml
24 24 <group> 25 25 <variant frequency="100" name="Idle"/> 26 26 <variant file="biped/attack_ranged_archer.xml"/> 27 <variant file="biped/attack_melee_ranged.xml"/> 27 28 <variant file="biped/attack_capture.xml"/> 28 29 <variant file="biped/attack_slaughter.xml"/> 29 30 <variant file="biped/gather_tree.xml"/> -
binaries/data/mods/public/art/actors/units/persians/infantry_archer_e.xml
37 37 <group> 38 38 <variant frequency="100" name="Idle"/> 39 39 <variant file="biped/attack_ranged_archer.xml"/> 40 <variant file="biped/attack_melee_ranged.xml"/> 40 41 <variant file="biped/attack_capture.xml"/> 41 42 <variant file="biped/attack_slaughter.xml"/> 42 43 <variant file="biped/gather_tree.xml"/> -
binaries/data/mods/public/art/textures/cursors/action-melee-attack.txt
1 1 1 -
binaries/data/mods/public/art/textures/cursors/action-ranged-attack.txt
1 1 1 -
binaries/data/mods/public/art/variants/biped/attack_melee_ranged.xml
1 <variant name="attack_melee"> 2 <animations> 3 <animation event="0.5" file="infantry/sword/attack/isw_s_off_05.psa" name="attack_melee" speed="100"/> 4 </animations> 5 <props> 6 <prop actor="props/units/weapons/knife.xml" attachpoint="r_hand"/> 7 </props> 8 </variant> -
binaries/data/mods/public/globalscripts/Templates.js
66 66 } 67 67 68 68 /** 69 * Check if entity has all needed attack types. 70 */ 71 function HasNeededAttackTypes(attack, neededAttackTypes) 72 { 73 if (!neededAttackTypes) 74 return true; 75 // transform the string to an array 76 if (typeof neededAttackTypes == "string") 77 neededAttackTypes = neededAttackTypes.split(/\s+/); 78 79 for (let type of neededAttackTypes) 80 if (!attack || attack.constructor === Object && !attack[type] || 81 attack.constructor === Array && attack.indexOf(type) == -1) 82 return false; 83 return true; 84 } 85 86 /** 69 87 * Get information about a template with or without technology modifications. 70 88 * 71 89 * NOTICE: The data returned here should have the same structure as -
binaries/data/mods/public/gui/manual/intro.txt
94 94 Right Click with a building/buildings selected: sets a rally point for units created/ungarrisoned from that building. 95 95 Ctrl + Right Click with units selected: 96 96 - If the cursor is over an allied structure: Garrison 97 - If the cursor is over a non-allied unit or building: Attack (instead of capture or gather) 98 - Otherwise: Attack move (by default all enemy units and structures along the way are targeted, use Ctrl + Q + Right Click to target only units). 97 - If the cursor is over a non-allied unit or building: Primary Attack (instead of capture, gather) 98 - Otherwise: Attack move (by default all enemy units and structures along the way are targeted, use Ctrl + Q + Right Click to target only units). 99 Alt + Right Click on non-allied unit or building: Melee attack 100 Space + Right Click on non-allied unit or building: Ranged attack 99 101 100 102 [font="sans-bold-14"]Overlays 101 103 [font="sans-14"]Alt + G: Hide/show the GUI -
binaries/data/mods/public/gui/session/input.js
228 228 if (unitActions[action] && unitActions[action].getActionInfo) 229 229 { 230 230 var r = unitActions[action].getActionInfo(entState, targetState, simState); 231 if (r ) // return true if it's possible for one of the entities231 if (r && r.possible) // return true if it's possible for one of the entities 232 232 return r; 233 233 } 234 234 } -
binaries/data/mods/public/gui/session/unit_actions.js
58 58 59 59 return { "type": "move" }; 60 60 }, 61 "specificness": 1 2,61 "specificness": 13, 62 62 }, 63 63 64 64 "attack-move": … … 77 77 "x": target.x, 78 78 "z": target.z, 79 79 "targetClasses": targetClasses, 80 "queued": queued 80 "queued": queued, 81 "prefAttackType": "noCapture" 81 82 }); 82 83 83 84 Engine.GuiInterfaceCall("PlaySound", { … … 114 115 "type": "attack", 115 116 "entities": selection, 116 117 "target": action.target, 117 " allowCapture": true,118 " queued": queued118 "queued": queued, 119 "prefAttackType": "Capture" 119 120 }); 120 121 121 122 Engine.GuiInterfaceCall("PlaySound", { … … 130 131 if (!entState.attack || !targetState.hitpoints) 131 132 return false; 132 133 133 return { 134 "possible": Engine.GuiInterfaceCall("CanCapture", { 134 return { "possible": Engine.GuiInterfaceCall("CanCapture", { 135 135 "entity": entState.id, 136 136 "target": targetState.id 137 137 }) … … 148 148 "target": target 149 149 }; 150 150 }, 151 "specificness": 9,151 "specificness": 7, 152 152 }, 153 153 154 154 "attack": … … 160 160 "entities": selection, 161 161 "target": action.target, 162 162 "queued": queued, 163 " allowCapture": false163 "prefAttackType": "noCapture" 164 164 }); 165 165 166 166 Engine.GuiInterfaceCall("PlaySound", { … … 205 205 "target": target 206 206 }; 207 207 }, 208 "specificness": 8, 209 }, 210 211 "melee-attack": 212 { 213 "execute": function(target, action, selection, queued) 214 { 215 Engine.PostNetworkCommand({ 216 "type": "attack", 217 "entities": selection, 218 "target": action.target, 219 "queued": queued, 220 "prefAttackType": "Melee" 221 }); 222 223 Engine.GuiInterfaceCall("PlaySound", { 224 "name": "order_attack", 225 "entity": selection[0] 226 }); 227 228 return true; 229 }, 230 "getActionInfo": function(entState, targetState) 231 { 232 if (!entState.attack || !targetState.hitpoints) 233 return false; 234 235 return { "possible": Engine.GuiInterfaceCall("CanAttack", { 236 "entity": entState.id, 237 "target": targetState.id, 238 "type": "Melee" 239 }) }; 240 }, 241 "hotkeyActionCheck": function(target) 242 { 243 if (!Engine.HotkeyIsPressed("session.meleeattack") || !getActionInfo("melee-attack", target).possible) 244 return false; 245 246 return { 247 "type": "melee-attack", 248 "cursor": "action-melee-attack", 249 "target": target 250 }; 251 }, 252 "actionCheck": function(target) 253 { 254 if (!getActionInfo("melee-attack", target).possible) 255 return false; 256 257 return { 258 "type": "melee-attack", 259 "cursor": "action-melee-attack", 260 "target": target 261 }; 262 }, 263 "specificness": 9, 264 }, 265 266 "ranged-attack": 267 { 268 "execute": function(target, action, selection, queued) 269 { 270 Engine.PostNetworkCommand({ 271 "type": "attack", 272 "entities": selection, 273 "target": action.target, 274 "queued": queued, 275 "prefAttackType": "Ranged" 276 }); 277 278 Engine.GuiInterfaceCall("PlaySound", { 279 "name": "order_attack", 280 "entity": selection[0] 281 }); 282 283 return true; 284 }, 285 "getActionInfo": function(entState, targetState) 286 { 287 if (!entState.attack || !targetState.hitpoints) 288 return false; 289 290 return { "possible": Engine.GuiInterfaceCall("CanAttack", { 291 "entity": entState.id, 292 "target": targetState.id, 293 "type": "Ranged" 294 }) }; 295 }, 296 "hotkeyActionCheck": function(target, selection) 297 { 298 if (!Engine.HotkeyIsPressed("session.rangedattack") || !getActionInfo("ranged-attack", target).possible) 299 return false; 300 301 return { 302 "type": "ranged-attack", 303 "cursor": "action-ranged-attack", 304 "target": target 305 }; 306 }, 307 "actionCheck": function(target, selection) 308 { 309 if (!getActionInfo("ranged-attack", target).possible) 310 return false; 311 312 return { 313 "type": "ranged-attack", 314 "cursor": "action-ranged-attack", 315 "target": target 316 }; 317 }, 208 318 "specificness": 10, 209 319 }, 210 320 … … 255 365 "target": target 256 366 }; 257 367 }, 258 "specificness": 7,368 "specificness": 6, 259 369 }, 260 370 261 371 "build": … … 368 478 "target": target 369 479 }; 370 480 }, 371 "specificness": 1 1,481 "specificness": 12, 372 482 }, 373 483 374 484 "gather": … … 389 499 390 500 return true; 391 501 }, 392 "getActionInfo": 502 "getActionInfo": function(entState, targetState) 393 503 { 394 504 if (!targetState.resourceSupply) 395 505 return false; … … 608 718 if (targetState.garrisonHolder.garrisonedEntitiesCount + extraCount >= targetState.garrisonHolder.capacity) 609 719 tooltip = "[color=\"orange\"]" + tooltip + "[/color]"; 610 720 611 if (!MatchesClassList(entState.identity.classes, targetState.garrisonHolder.allowedClasses)) 612 return false; 721 if (!MatchesClassList(entState.identity.classes, targetState.garrisonHolder.allowedClasses) || 722 !HasNeededAttackTypes(entState.attack, targetState.garrisonHolder.neededAttackTypes)) 723 return { "possible": false }; 613 724 614 725 return { 615 726 "possible": true, … … 922 1033 "position": actionInfo.position 923 1034 }; 924 1035 }, 925 "specificness": 6,1036 "specificness": 5, 926 1037 }, 927 1038 928 1039 "unset-rallypoint": … … 1005 1116 unloadAll(); 1006 1117 }, 1007 1118 }, 1119 1008 1120 "delete": { 1009 1121 "getInfo": function(entState) 1010 1122 { … … 1043 1155 openDeleteDialog(selection); 1044 1156 }, 1045 1157 }, 1158 1046 1159 "stop": { 1047 1160 "getInfo": function(entState) 1048 1161 { -
binaries/data/mods/public/simulation/ai/common-api/entity.js
742 742 return false; 743 743 }, 744 744 745 isCapturable: function() { return this.get("Capturable") !== undefined; }, 745 746 isGarrisonHolder: function() { return this.get("GarrisonHolder") !== undefined; }, 746 747 garrisoned: function() { return this._entity.garrisoned; }, 747 748 canGarrisonInside: function() { return this._entity.garrisoned.length < this.garrisonMax(); }, … … 756 757 return this; 757 758 }, 758 759 759 attackMove: function(x, z, targetClasses, queued = false) {760 Engine.PostCommand(PlayerID,{ "type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "targetClasses": targetClasses, "queued": queued });760 attackMove: function(x, z, targetClasses, prefAttackType = "undefined", queued = false) { 761 Engine.PostCommand(PlayerID,{ "type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "targetClasses": targetClasses, "prefAttackType": prefAttackType, "queued": queued }); 761 762 return this; 762 763 }, 763 764 … … 791 792 return this; 792 793 }, 793 794 794 attack: function(unitId, allowCapture = true, queued = false) {795 Engine.PostCommand(PlayerID,{ "type": "attack", "entities": [this.id()], "target": unitId, "allowCapture": allowCapture, "queued": queued});795 attack: function(unitId, prefAttackType = undefined, queued = false) { 796 Engine.PostCommand(PlayerID,{ "type": "attack", "entities": [this.id()], "target": unitId, "prefAttackType": prefAttackType, "queued": queued }); 796 797 return this; 797 798 }, 798 799 -
binaries/data/mods/public/simulation/ai/common-api/entitycollection.js
156 156 m.EntityCollection.prototype.attackMove = function(x, z, targetClasses, queued) 157 157 { 158 158 queued = queued || false; 159 Engine.PostCommand(PlayerID, {"type": "attack-walk", "entities": this.toIdArray(), "x": x, "z": z, "targetClasses": targetClasses, "queued": queued});159 Engine.PostCommand(PlayerID, { "type": "attack-walk", "entities": this.toIdArray(), "x": x, "z": z, "targetClasses": targetClasses, "queued": queued, "prefAttackType": undefined }); 160 160 return this; 161 161 }; 162 162 … … 182 182 183 183 m.EntityCollection.prototype.attack = function(unitId) 184 184 { 185 Engine.PostCommand(PlayerID,{ "type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false});185 Engine.PostCommand(PlayerID,{ "type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false, "prefAttackType": undefined }); 186 186 return this; 187 187 }; 188 188 -
binaries/data/mods/public/simulation/ai/petra/attackPlan.js
1237 1237 { 1238 1238 if (this.isSiegeUnit(gameState, ent)) // needed as mauryan elephants are not filtered out 1239 1239 continue; 1240 ent.attack(attacker.id(), !this.noCapture.has(attacker.id()));1240 ent.attack(attacker.id(), m.getPrefAttackType(ent, attacker, this.noCapture)); 1241 1241 ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); 1242 1242 } 1243 1243 // And if this attacker is a non-ranged siege unit and our unit also, attack it 1244 1244 if (this.isSiegeUnit(gameState, attacker) && attacker.hasClass("Melee") && ourUnit.hasClass("Melee")) 1245 1245 { 1246 ourUnit.attack(attacker.id(), false);1246 ourUnit.attack(attacker.id(), "Melee"); 1247 1247 ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); 1248 1248 } 1249 1249 } … … 1260 1260 let collec = this.unitCollection.filter(API3.Filters.byClass("Melee")).filterNearest(ourUnit.position(), 5); 1261 1261 for (let ent of collec.values()) 1262 1262 { 1263 ent.attack(attacker.id(), false);1263 ent.attack(attacker.id(), m.getPrefAttackType(ent, attacker, this.noCapture)); 1264 1264 ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); 1265 1265 } 1266 1266 } … … 1279 1279 continue; 1280 1280 } 1281 1281 } 1282 ourUnit.attack(attacker.id(), !this.noCapture.has(attacker.id()));1282 ourUnit.attack(attacker.id(), m.getPrefAttackType(ourUnit, attacker, this.noCapture)); 1283 1283 ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); 1284 1284 } 1285 1285 } … … 1459 1459 return valb - vala; 1460 1460 }); 1461 1461 if (mStruct[0].hasClass("Gates")) 1462 ent.attack(mStruct[0].id(), false);1462 ent.attack(mStruct[0].id(), m.getPrefAttackType(ent, mStruct[0], this.noCapture)); 1463 1463 else 1464 1464 { 1465 1465 let rand = Math.floor(Math.random() * mStruct.length * 0.2); 1466 let newTarget Id = mStruct[rand].id();1467 ent.attack(newTarget Id, !this.noCapture.has(newTargetId));1466 let newTarget = mStruct[rand]; 1467 ent.attack(newTarget.id(), m.getPrefAttackType(ent, newTarget, this.noCapture)); 1468 1468 } 1469 1469 } 1470 1470 else … … 1522 1522 return valb - vala; 1523 1523 }); 1524 1524 let rand = Math.floor(Math.random() * mUnit.length * 0.1); 1525 let newTarget Id = mUnit[rand].id();1526 ent.attack(newTarget Id, !this.noCapture.has(newTargetId));1525 let newTarget = mUnit[rand]; 1526 ent.attack(newTarget.id(), m.getPrefAttackType(ent, newTarget, this.noCapture)); 1527 1527 } 1528 1528 else if (this.isBlocked) 1529 1529 ent.attack(this.target.id(), false); … … 1570 1570 return valb - vala; 1571 1571 }); 1572 1572 if (mStruct[0].hasClass("Gates")) 1573 ent.attack(mStruct[0].id(), false);1573 ent.attack(mStruct[0].id(), m.getPrefAttackType(ent, mStruct[0], this.noCapture)); 1574 1574 else 1575 1575 { 1576 1576 let rand = Math.floor(Math.random() * mStruct.length * 0.2); 1577 let newTarget Id = mStruct[rand].id();1578 ent.attack(newTarget Id, !this.noCapture.has(newTargetId));1577 let newTarget = mStruct[rand]; 1578 ent.attack(newTarget.id(), m.getPrefAttackType(ent, newTarget, this.noCapture)); 1579 1579 } 1580 1580 } 1581 1581 else if (needsUpdate) // really nothing let's try to help our nearest unit … … 1582 1582 { 1583 1583 let distmin = Math.min(); 1584 1584 let attackerId; 1585 let attacker; 1585 1586 this.unitCollection.forEach( function (unit) { 1586 1587 if (!unit.position()) 1587 1588 return; … … 1591 1592 let dist = API3.SquareVectorDistance(unit.position(), ent.position()); 1592 1593 if (dist > distmin) 1593 1594 return; 1595 if (!gameState.getEntityById(unit.unitAIOrderData()[0].target)) 1596 return; 1594 1597 distmin = dist; 1595 1598 attackerId = unit.unitAIOrderData()[0].target; 1599 attacker = gameState.getEntityById(attackerId); 1596 1600 }); 1597 1601 if (attackerId) 1598 ent.attack(attackerId, !this.noCapture.has(attackerId));1602 ent.attack(attackerId, m.getPrefAttackType(ent, attacker, this.noCapture)); 1599 1603 } 1600 1604 } 1601 1605 } … … 1648 1652 continue; 1649 1653 if (!ent.isIdle()) 1650 1654 continue; 1651 ent.attack(attacker.id(), !this.noCapture.has(attacker.id() ));1655 ent.attack(attacker.id(), !this.noCapture.has(attacker.id(), this.noCapture)); 1652 1656 } 1653 1657 break; 1654 1658 } … … 2006 2010 2007 2011 if (this.noCapture.has(targetId)) 2008 2012 { 2009 ent.attack(targetId, false);2013 ent.attack(targetId, m.getPrefAttackType(ent, target, this.noCapture)); 2010 2014 return true; 2011 2015 } 2012 2016 … … 2018 2022 if (target.hasClass("Siege") && target.hasClass("Melee")) 2019 2023 { 2020 2024 this.noCapture.add(targetId); 2021 ent.attack(targetId, false);2025 ent.attack(targetId, m.getPrefAttackType(ent, target, this.noCapture)); 2022 2026 return true; 2023 2027 } 2024 2028 … … 2035 2039 if (antiCapture >= this.captureStrength) 2036 2040 { 2037 2041 this.noCapture.add(targetId); 2038 ent.attack(targetId, false);2042 ent.attack(targetId, "noCapture"); 2039 2043 return true; 2040 2044 } 2041 2045 … … 2044 2048 this.unitCollection.length < 2*target.garrisoned().length) 2045 2049 { 2046 2050 this.noCapture.add(targetId); 2047 ent.attack(targetId, false);2051 ent.attack(targetId, "noCapture"); 2048 2052 return true; 2049 2053 } 2050 2054 -
binaries/data/mods/public/simulation/ai/petra/defenseArmy.js
77 77 { 78 78 this.assignedTo[entID] = idFoe; 79 79 this.assignedAgainst[idFoe].push(entID); 80 ent.attack(idFoe, m. allowCapture(ent, foeEnt), queued);80 ent.attack(idFoe, m.getPrefAttackType(ent, foeEnt), queued); 81 81 } 82 82 else 83 83 gameState.ai.HQ.navalManager.requireTransport(gameState, ent, ownIndex, foeIndex, foePosition); … … 115 115 else if (orderData.length && orderData[0].target && orderData[0].attackType && orderData[0].attackType === "Capture") 116 116 { 117 117 let target = gameState.getEntityById(orderData[0].target); 118 if (target && !m.allowCapture(ent, target)) 119 ent.attack(orderData[0].target, false); 118 if (target) 119 { 120 let prefAttackType = m.getPrefAttackType(ent, target); 121 if (prefAttackType !== "Capture") 122 ent.attack(orderData[0].target, prefAttackType); 123 } 120 124 } 121 125 } 122 126 -
binaries/data/mods/public/simulation/ai/petra/entityExtend.js
81 81 return strength * ent.maxHitpoints() / 100.0; 82 82 }; 83 83 84 /** Decide if we should try to capture or destroy */ 85 m.allowCapture = function(ent, target) 84 // Decide if we should try to capture, melee or range attack 85 // TODO make this function less hacky 86 m.getPrefAttackType = function(ent, target, noCapture) 86 87 { 87 return !target.hasClass("Siege") || !ent.hasClass("Melee") || 88 !target.isGarrisonHolder() || !target.garrisoned().length; 88 if (target.hasClass("Siege") || (noCapture && noCapture.has(target.id()))) 89 return ent.hasClass("Melee") ? "Melee" : "Range"; // Don't capture for now 90 91 if (target.isCapturable() && (!target.isGarrisonHolder() || !target.garrisoned().length)) 92 return "Capture"; 93 return "noCapture"; 89 94 }; 90 95 91 96 /** Makes the worker deposit the currently carried resources at the closest accessible dropsite */ -
binaries/data/mods/public/simulation/components/Armour.js
51 51 if (this.invulnerable) 52 52 return { "killed": false, "change": 0 }; 53 53 54 // Adjust damage values based on armour; exponential armour: damage = attack * 0.9^armour 55 var armourStrengths = this.GetArmourStrengths(); 56 var adjHack = hack * Math.pow(0.9, armourStrengths.hack); 57 var adjPierce = pierce * Math.pow(0.9, armourStrengths.pierce); 58 var adjCrush = crush * Math.pow(0.9, armourStrengths.crush); 54 let damage = this.GetDamage(hack, pierce, crush); 59 55 60 // Total is sum of individual damages61 // Don't bother rounding, since HP is no longer integral.62 var total = adjHack + adjPierce + adjCrush;63 64 56 // Reduce health 65 varcmpHealth = Engine.QueryInterface(this.entity, IID_Health);66 return cmpHealth.Reduce( total);57 let cmpHealth = Engine.QueryInterface(this.entity, IID_Health); 58 return cmpHealth.Reduce(damage); 67 59 }; 68 60 69 61 Armour.prototype.GetArmourStrengths = function() 70 62 { 71 63 // Work out the armour values with technology effects 72 varapplyMods = (type, foundation) => {73 varstrength;64 let applyMods = (type, foundation) => { 65 let strength; 74 66 if (foundation) 75 67 { 76 68 strength = +this.template.Foundation[type]; … … 82 74 return ApplyValueModificationsToEntity("Armour/" + type, strength, this.entity); 83 75 }; 84 76 85 varfoundation = Engine.QueryInterface(this.entity, IID_Foundation) && this.template.Foundation;77 let foundation = Engine.QueryInterface(this.entity, IID_Foundation) && this.template.Foundation; 86 78 87 79 return { 88 80 "hack": applyMods("Hack", foundation), … … 91 83 }; 92 84 }; 93 85 86 Armour.prototype.GetDamage = function(hack, pierce, crush) 87 { 88 // Adjust damage values based on armour; exponential armour: damage = attack * 0.9^armour 89 let armourStrengths = this.GetArmourStrengths(); 90 let adjHack = hack * Math.pow(0.9, armourStrengths.hack); 91 let adjPierce = pierce * Math.pow(0.9, armourStrengths.pierce); 92 let adjCrush = crush * Math.pow(0.9, armourStrengths.crush); 93 94 // Total is sum of individual damages 95 // Don't bother rounding, since HP is no longer integral. 96 return adjHack + adjPierce + adjCrush; 97 }; 98 94 99 Engine.RegisterComponentType(IID_DamageReceiver, "Armour", Armour); -
binaries/data/mods/public/simulation/components/Attack.js
208 208 return []; 209 209 }; 210 210 211 Attack.prototype.CanAttack = function(target )211 Attack.prototype.CanAttack = function(target, wantedType) 212 212 { 213 213 let cmpFormation = Engine.QueryInterface(target, IID_Formation); 214 214 if (cmpFormation) 215 215 return true; 216 216 217 if (wantedType && !this.template[wantedType]) 218 return false; 219 217 220 let cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position); 218 221 let cmpTargetPosition = Engine.QueryInterface(target, IID_Position); 219 222 if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld()) 220 223 return false; 221 224 222 // Check if the relative height difference is larger than the attack range223 // If the relative height is bigger, it means they will never be able to224 // reach each other, no matter how close they come.225 let heightDiff = Math.abs(cmpThisPosition.GetHeightOffset() - cmpTargetPosition.GetHeightOffset());226 227 225 const cmpIdentity = Engine.QueryInterface(target, IID_Identity); 228 226 if (!cmpIdentity) 229 227 return undefined; 230 228 229 let cmpEntityPlayer = QueryOwnerInterface(this.entity); 230 let cmpTargetPlayer = QueryOwnerInterface(target); 231 if (!cmpTargetPlayer || !cmpEntityPlayer) 232 return false; 233 231 234 const targetClasses = cmpIdentity.GetClassesList(); 235 let cmpCapturable = QueryMiragedInterface(target, IID_Capturable); 236 if (targetClasses.indexOf("Domestic") == -1 && !cmpEntityPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID()) && 237 (!cmpCapturable || !cmpCapturable.CanCapture(cmpEntityPlayer.GetPlayerID()))) 238 return false; 232 239 240 // Check if the relative height difference is larger than the attack range 241 // If the relative height is bigger, it means they will never be able to 242 // reach each other, no matter how close they come. 243 let heightDiff = Math.abs(cmpThisPosition.GetHeightOffset() - cmpTargetPosition.GetHeightOffset()); 244 233 245 for (let type of this.GetAttackTypes()) 234 246 { 235 if ( type == "Capture" && !QueryMiragedInterface(target, IID_Capturable))247 if (wantedType && type != wantedType) 236 248 continue; 237 249 250 if (type == "Capture" && !cmpCapturable) 251 continue; 252 238 253 if (heightDiff > this.GetRange(type).max) 239 254 continue; 240 255 … … 291 306 return ret; 292 307 }; 293 308 294 Attack.prototype.GetBestAttackAgainst = function(target, allowCapture)309 Attack.prototype.GetBestAttackAgainst = function(target, prefAttackType) 295 310 { 296 311 let cmpFormation = Engine.QueryInterface(target, IID_Formation); 297 312 if (cmpFormation) … … 298 313 { 299 314 // TODO: Formation against formation needs review 300 315 let types = this.GetAttackTypes(); 301 return ["Ranged", "Melee", "Capture"].find(attack => types.indexOf(attack) != -1); 316 return ["Ranged", "Melee", "Capture"].find(attack => types.indexOf(attack) != -1 317 && (!prefAttackType || "no" + attack != prefAttackType)); 302 318 } 303 319 304 320 let cmpIdentity = Engine.QueryInterface(target, IID_Identity); … … 305 321 if (!cmpIdentity) 306 322 return undefined; 307 323 324 let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); 325 if (!cmpPosition || !cmpPosition.IsInWorld()) 326 return undefined; 327 328 // If we are visisble garrisoned always do ranged attack if we can. 329 if (cmpPosition.GetTurretParent() != INVALID_ENTITY) 330 return this.template.Ranged && "Ranged"; 331 308 332 let targetClasses = cmpIdentity.GetClassesList(); 309 333 let isTargetClass = className => targetClasses.indexOf(className) != -1; 310 334 … … 313 337 return "Slaughter"; 314 338 315 339 let attack = this; 340 let isAllowed = function (type) { return !attack.GetRestrictedClasses(type).some(isTargetClass) 341 && (!prefAttackType || "no" + type != prefAttackType); }; 342 316 343 let types = this.GetAttackTypes().filter(type => !attack.GetRestrictedClasses(type).some(isTargetClass)); 344 if (!types.length) 345 return undefined; 317 346 318 // check if the target is capturable319 let captureIndex = types.indexOf("Capture");320 if (captureIndex != -1)347 if (prefAttackType && prefAttackType != "Capture" && types.indexOf(prefAttackType) != -1) 348 return prefAttackType; 349 else if (!prefAttackType || prefAttackType == "Capture") 321 350 { 322 let cmpCapturable = QueryMiragedInterface(target, IID_Capturable); 323 324 let cmpPlayer = QueryOwnerInterface(this.entity); 325 if (allowCapture && cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID())) 326 return "Capture"; 327 // not captureable, so remove this attack 328 types.splice(captureIndex, 1); 351 // check if the target is capturable 352 let captureIndex = types.indexOf("Capture"); 353 if (captureIndex != -1) 354 { 355 let cmpCapturable = QueryMiragedInterface(target, IID_Capturable); 356 let cmpPlayer = QueryOwnerInterface(this.entity); 357 if (cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID())) 358 return "Capture"; 359 // not captureable, so remove this attack 360 types.splice(captureIndex, 1); 361 } 329 362 } 330 363 331 let isPreferred = className => attack.GetPreferredClasses(className).some(isTargetClass); 364 if (!types.length) 365 return undefined; 332 366 333 return types.sort((a, b) => 334 (types.indexOf(a) + (isPreferred(a) ? types.length : 0)) - 335 (types.indexOf(b) + (isPreferred(b) ? types.length : 0))).pop(); 367 // assume ranged and/or melee attack left 368 // TODO stop assuming that? 369 let meleeIndex = types.indexOf("Melee"); 370 let rangedIndex = types.indexOf("Ranged"); 371 if (meleeIndex != -1 && rangedIndex != -1) 372 { 373 let selfPosition = cmpPosition.GetPosition2D(); 374 let cmpTargetPosition = Engine.QueryInterface(target, IID_Position); 375 if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) 376 return undefined; 377 let targetPosition = cmpTargetPosition.GetPosition2D(); 378 let distanceToSquared = targetPosition.distanceToSquared(selfPosition); 379 if (distanceToSquared <= Math.pow(this.GetRange("Ranged").min, 2)) 380 return "Melee"; 381 382 let cmpDamageReceiver = Engine.QueryInterface(target, IID_DamageReceiver); 383 if (!cmpDamageReceiver) 384 return undefined; 385 386 let attackStrengthsMelee = this.GetAttackStrengths("Melee"); 387 let attackBonusMelee = this.GetAttackBonus("Melee", target); 388 let DPSMelee = cmpDamageReceiver.GetDamage( 389 attackStrengthsMelee.hack * attackBonusMelee, 390 attackStrengthsMelee.pierce * attackBonusMelee, 391 attackStrengthsMelee.crush * attackBonusMelee 392 ) / this.GetTimers("Melee").repeat; 393 394 let attackStrengthsRanged = this.GetAttackStrengths("Ranged"); 395 let attackBonusRanged = this.GetAttackBonus("Ranged", target); 396 let DPSRanged = cmpDamageReceiver.GetDamage( 397 attackStrengthsRanged.hack * attackBonusRanged, 398 attackStrengthsRanged.pierce * attackBonusRanged, 399 attackStrengthsRanged.crush * attackBonusRanged 400 ) / this.GetTimers("Ranged").repeat; 401 402 // When we are out of range do DPS only else take distance into account 403 let maxRange = this.GetFullAttackRange().max; 404 if (distanceToSquared > 1.2 * Math.pow(maxRange, 2)) 405 { 406 if (DPSRanged > DPSMelee) 407 return "Ranged"; 408 return "Melee"; 409 } 410 411 let strenghtRanged = DPSRanged * distanceToSquared / Math.pow(maxRange, 2); 412 let strenghtMelee = DPSMelee * (1 - distanceToSquared / Math.pow(maxRange, 2)); 413 414 if (strenghtRanged > strenghtMelee) 415 return "Ranged"; 416 return "Melee"; 417 } 418 else if (meleeIndex != -1) 419 return "Melee"; 420 else if (rangedIndex != -1) 421 return "Ranged"; 422 return types[0]; 336 423 }; 337 424 338 425 Attack.prototype.CompareEntitiesByPreference = function(a, b) … … 496 583 497 584 let horizDistance = targetPosition.horizDistanceTo(selfPosition); 498 585 499 // This is an approximation of the time otthe target, it assumes that the target has a constant radial586 // This is an approximation of the time to the target, it assumes that the target has a constant radial 500 587 // velocity, but since units move in straight lines this is not true. The exact value would be more 501 588 // difficult to calculate and I think this is sufficiently accurate. (I tested and for cavalry it was 502 589 // about 5% of the units radius out in the worst case) … … 572 659 "target": target, 573 660 "attacker": this.entity, 574 661 "multiplier": this.GetAttackBonus(type, target), 575 "type": type662 "type": type 576 663 }); 577 664 } 578 665 }; -
binaries/data/mods/public/simulation/components/GarrisonHolder.js
10 10 "</attribute>" + 11 11 "<text/>" + 12 12 "</element>" + 13 "<optional>" + 14 "<element name='NeededAttackTypes' a:help='AttackType which are needed to garrison inside this holder (from Identity)'>" + 15 "<attribute name='datatype'>" + 16 "<value>tokens</value>" + 17 "</attribute>" + 18 "<text/>" + 19 "</element>" + 20 "</optional>" + 13 21 "<element name='EjectHealth' a:help='Percentage of maximum health below which this holder no longer allows garrisoning'>" + 14 22 "<ref name='nonNegativeDecimal'/>" + 15 23 "</element>" + … … 119 127 }; 120 128 121 129 /** 130 * Returns an array of attack types which the unit garrisoned inside this 131 * particualar entity needs to have. Obtained from the entity's template 132 */ 133 GarrisonHolder.prototype.GetNeededAttackTypes = function() 134 { 135 return this.template.NeededAttackTypes ? this.template.NeededAttackTypes._string : undefined; 136 }; 137 138 /** 122 139 * Get Maximum pop which can be garrisoned 123 140 */ 124 141 GarrisonHolder.prototype.GetCapacity = function() -
binaries/data/mods/public/simulation/components/GuiInterface.js
359 359 "entities": cmpGarrisonHolder.GetEntities(), 360 360 "buffHeal": cmpGarrisonHolder.GetHealRate(), 361 361 "allowedClasses": cmpGarrisonHolder.GetAllowedClasses(), 362 "neededAttackTypes": cmpGarrisonHolder.GetNeededAttackTypes(), 362 363 "capacity": cmpGarrisonHolder.GetCapacity(), 363 364 "garrisonedEntitiesCount": cmpGarrisonHolder.GetGarrisonedEntitiesCount() 364 365 }; … … 440 441 let types = cmpAttack.GetAttackTypes(); 441 442 if (types.length) 442 443 ret.attack = {}; 444 443 445 for (let type of types) 444 446 { 445 447 ret.attack[type] = cmpAttack.GetAttackStrengths(type); … … 1829 1831 if (!cmpAttack) 1830 1832 return false; 1831 1833 1832 let cmpEntityPlayer = QueryOwnerInterface(data.entity, IID_Player); 1833 let cmpTargetPlayer = QueryOwnerInterface(data.target, IID_Player); 1834 if (!cmpEntityPlayer || !cmpTargetPlayer) 1835 return false; 1836 1837 // if the owner is an enemy, it's up to the attack component to decide 1838 if (cmpEntityPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID())) 1839 return cmpAttack.CanAttack(data.target); 1840 1841 return false; 1834 let attackType = data.type || undefined; 1835 return cmpAttack.CanAttack(data.target, attackType); 1842 1836 }; 1843 1837 1844 1838 /* -
binaries/data/mods/public/simulation/components/UnitAI.js
420 420 } 421 421 422 422 // Work out how to attack the given target 423 var type = this.GetBestAttackAgainst(this.order.data.target, this.order.data.allowCapture);423 let type = this.GetBestAttackAgainst(this.order.data.target, this.order.data.prefAttackType); 424 424 if (!type) 425 425 { 426 426 // Oops, we can't attack at all … … 563 563 if (this.MustKillGatherTarget(this.order.data.target)) 564 564 { 565 565 // Make sure we can attack the target, else we'll get very stuck 566 if (!this.GetBestAttackAgainst(this.order.data.target , false))566 if (!this.GetBestAttackAgainst(this.order.data.target)) 567 567 { 568 568 // Oops, we can't attack at all - give up 569 569 // TODO: should do something so the player knows why this failed … … 587 587 return; 588 588 } 589 589 590 this.PushOrderFront("Attack", { "target": this.order.data.target, "force": false, "hunting": true, " allowCapture": false});590 this.PushOrderFront("Attack", { "target": this.order.data.target, "force": false, "hunting": true, "prefAttackType": undefined }); 591 591 return; 592 592 } 593 593 … … 842 842 }, 843 843 844 844 "Order.Attack": function(msg) { 845 vartarget = msg.data.target;846 var allowCapture = msg.data.allowCapture;847 varcmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI);845 let target = msg.data.target; 846 let prefAttackType = msg.data.prefAttackType; 847 let cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI); 848 848 if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember()) 849 849 target = cmpTargetUnitAI.GetFormationController(); 850 850 … … 863 863 this.FinishOrder(); 864 864 return; 865 865 } 866 this.CallMemberFunction("Attack", [target, false, allowCapture]);866 this.CallMemberFunction("Attack", [target, false, prefAttackType]); 867 867 if (cmpAttack.CanAttackAsFormation()) 868 868 this.SetNextState("COMBAT.ATTACKING"); 869 869 else … … 918 918 return; 919 919 } 920 920 921 this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, " allowCapture": false});921 this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "prefAttackType": undefined }); 922 922 return; 923 923 } 924 924 … … 1155 1155 1156 1156 "MoveCompleted": function(msg) { 1157 1157 var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 1158 this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data. allowCapture]);1158 this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data.prefAttackType]); 1159 1159 if (cmpAttack.CanAttackAsFormation()) 1160 1160 this.SetNextState("COMBAT.ATTACKING"); 1161 1161 else … … 1166 1166 "ATTACKING": { 1167 1167 // Wait for individual members to finish 1168 1168 "enter": function(msg) { 1169 vartarget = this.order.data.target;1170 var allowCapture = this.order.data.allowCapture;1169 let target = this.order.data.target; 1170 let prefAttackType = this.order.data.prefAttackType; 1171 1171 // Check if we are already in range, otherwise walk there 1172 1172 if (!this.CheckTargetAttackRange(target, target)) 1173 1173 { … … 1174 1174 if (this.TargetIsAlive(target) && this.CheckTargetVisible(target)) 1175 1175 { 1176 1176 this.FinishOrder(); 1177 this.PushOrderFront("Attack", { "target": target, "force": false, " allowCapture": allowCapture });1177 this.PushOrderFront("Attack", { "target": target, "force": false, "prefAttackType": prefAttackType }); 1178 1178 return true; 1179 1179 } 1180 1180 this.FinishOrder(); … … 1190 1190 }, 1191 1191 1192 1192 "Timer": function(msg) { 1193 vartarget = this.order.data.target;1194 var allowCapture = this.order.data.allowCapture;1193 let target = this.order.data.target; 1194 let prefAttackType = this.order.data.prefAttackType; 1195 1195 // Check if we are already in range, otherwise walk there 1196 1196 if (!this.CheckTargetAttackRange(target, target)) 1197 1197 { … … 1198 1198 if (this.TargetIsAlive(target) && this.CheckTargetVisible(target)) 1199 1199 { 1200 1200 this.FinishOrder(); 1201 this.PushOrderFront("Attack", { "target": target, "force": false, " allowCapture": allowCapture });1201 this.PushOrderFront("Attack", { "target": target, "force": false, "prefAttackType": prefAttackType }); 1202 1202 return; 1203 1203 } 1204 1204 this.FinishOrder(); … … 1410 1410 1411 1411 // target the unit 1412 1412 if (this.CheckTargetVisible(msg.data.attacker)) 1413 this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, " allowCapture": true});1413 this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "prefAttackType": undefined }); 1414 1414 else 1415 1415 { 1416 1416 var cmpPosition = Engine.QueryInterface(msg.data.attacker, IID_Position); … … 1805 1805 // Can't reach it - try to chase after it 1806 1806 if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) 1807 1807 { 1808 if (!this.order.data.force) 1809 this.order.data.attackType = this.GetBestAttackAgainst(target); 1808 1810 if (this.MoveToTargetAttackRange(target, this.order.data.attackType)) 1809 1811 { 1810 1812 this.SetNextState("COMBAT.CHASING"); … … 1916 1918 // Can't reach it - try to chase after it 1917 1919 if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) 1918 1920 { 1921 if (!this.order.data.force) 1922 { 1923 let type = this.GetBestAttackAgainst(target); 1924 if (type) 1925 this.order.data.attackType = type; 1926 } 1919 1927 if (this.MoveToTargetRange(target, IID_Attack, this.order.data.attackType)) 1920 1928 { 1921 1929 this.SetNextState("COMBAT.CHASING"); … … 4549 4557 return distance < range; 4550 4558 }; 4551 4559 4552 UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture)4560 UnitAI.prototype.GetBestAttackAgainst = function(target, prefAttackType) 4553 4561 { 4554 4562 var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 4555 4563 if (!cmpAttack) 4556 4564 return undefined; 4557 return cmpAttack.GetBestAttackAgainst(target, allowCapture);4565 return cmpAttack.GetBestAttackAgainst(target, prefAttackType); 4558 4566 }; 4559 4567 4560 4568 UnitAI.prototype.GetAttackBonus = function(type, target) … … 4572 4580 */ 4573 4581 UnitAI.prototype.AttackVisibleEntity = function(ents, forceResponse) 4574 4582 { 4575 vartarget = ents.find(target => this.CanAttack(target, forceResponse));4583 let target = ents.find(target => this.CanAttack(target, forceResponse)); 4576 4584 if (!target) 4577 4585 return false; 4578 4586 4579 this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true }); 4587 let prefAttackType = this.GetStance().respondStandGround && "Ranged"; 4588 this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "prefAttackType": prefAttackType }); 4580 4589 return true; 4581 4590 }; 4582 4591 … … 4589 4598 { 4590 4599 var target = ents.find(target => 4591 4600 this.CanAttack(target, forceResponse) 4592 && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target , true))4601 && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target)) 4593 4602 && (this.GetStance().respondChaseBeyondVision || this.CheckTargetIsInVisionRange(target)) 4594 4603 ); 4595 4604 if (!target) 4596 4605 return false; 4597 4606 4598 this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, " allowCapture": true});4607 this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "prefAttackType": undefined }); 4599 4608 return true; 4600 4609 }; 4601 4610 … … 4986 4995 * to a player order, and so is forced. 4987 4996 * If targetClasses is given, only entities matching the targetClasses can be attacked. 4988 4997 */ 4989 UnitAI.prototype.WalkAndFight = function(x, z, targetClasses, queued )4998 UnitAI.prototype.WalkAndFight = function(x, z, targetClasses, queued, prefAttackType) 4990 4999 { 4991 this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "force": true }, queued);5000 this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "force": true, "prefAttackType": prefAttackType }, queued); 4992 5001 }; 4993 5002 4994 5003 /** … … 5009 5018 /** 5010 5019 * Adds attack order to the queue, forced by the player. 5011 5020 */ 5012 UnitAI.prototype.Attack = function(target, queued, allowCapture)5021 UnitAI.prototype.Attack = function(target, queued, prefAttackType) 5013 5022 { 5014 5023 if (!this.CanAttack(target)) 5015 5024 { … … 5021 5030 this.WalkToTarget(target, queued); 5022 5031 return; 5023 5032 } 5024 this.AddOrder("Attack", { "target": target, "force": true, " allowCapture": allowCapture}, queued);5033 this.AddOrder("Attack", { "target": target, "force": true, "prefAttackType": prefAttackType }, queued); 5025 5034 }; 5026 5035 5027 5036 /** … … 5450 5459 if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ]) 5451 5460 continue; 5452 5461 } 5453 this.PushOrderFront("Attack", { "target": targ, "force": true, " allowCapture": true});5462 this.PushOrderFront("Attack", { "target": targ, "force": true, "prefAttackType": undefined }); 5454 5463 return true; 5455 5464 } 5456 5465 } … … 5476 5485 if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ]) 5477 5486 continue; 5478 5487 } 5479 this.PushOrderFront("Attack", { "target": targ, "force": true, " allowCapture": true});5488 this.PushOrderFront("Attack", { "target": targ, "force": true, "prefAttackType": undefined }); 5480 5489 return true; 5481 5490 } 5482 5491 … … 5676 5685 if (this.IsFormationController()) 5677 5686 return true; 5678 5687 5679 varcmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);5688 let cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder); 5680 5689 if (!cmpGarrisonHolder) 5681 5690 return false; 5682 5691 5683 5692 // Verify that the target is owned by this entity's player or a mutual ally of this player 5684 varcmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);5693 let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 5685 5694 if (!cmpOwnership || !(IsOwnedByPlayer(cmpOwnership.GetOwner(), target) || IsOwnedByMutualAllyOfPlayer(cmpOwnership.GetOwner(), target))) 5686 5695 return false; 5687 5696 … … 5691 5700 if (this.IsAnimal()) 5692 5701 return false; 5693 5702 5703 let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity) 5704 if (!cmpIdentity) 5705 return false; 5706 5707 let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack) 5708 let attackTypes = cmpAttack ? cmpAttack.GetAttackTypes() : []; 5709 if (!MatchesClassList(cmpIdentity.GetClassesList(), cmpGarrisonHolder.GetAllowedClasses()) || 5710 !HasNeededAttackTypes(attackTypes, cmpGarrisonHolder.GetNeededAttackTypes())) 5711 return false; 5694 5712 return true; 5695 5713 }; 5696 5714 -
binaries/data/mods/public/simulation/components/tests/test_Attack.js
22 22 }); 23 23 24 24 AddMock(playerEnt1, IID_Player, { 25 "GetPlayerID": () => 1 25 "GetPlayerID": () => 1, 26 "IsEnemy": () => true 26 27 }); 27 28 } 28 29 … … 30 31 31 32 AddMock(attacker, IID_Position, { 32 33 "IsInWorld": () => true, 34 "GetTurretParent": () => INVALID_ENTITY, 33 35 "GetHeightOffset": () => 5 34 36 }); 35 37 … … 87 89 "HasClass": className => className == defenderClass 88 90 }); 89 91 92 AddMock(defender, IID_Ownership, { 93 "GetOwner": () => 1 94 }); 95 90 96 AddMock(defender, IID_Position, { 91 97 "IsInWorld": () => true, 98 "GetTurretParent": () => INVALID_ENTITY, 92 99 "GetHeightOffset": () => 0 93 100 }); 94 101 … … 148 155 149 156 TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), true); 150 157 151 let allowCapturing = [true];158 let prefAttackType = "Capture"; 152 159 if (!isBuilding) 153 allowCapturing.push(false);160 prefAttackType = bestAttack; 154 161 155 for (let allowCapturing of allowCapturing) 156 TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, allowCapturing), bestAttack); 162 TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, prefAttackType), bestAttack); 157 163 }); 158 164 } 159 165 -
binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
97 97 AddMock(unit, IID_Attack, { 98 98 GetRange: function() { return { "max": 10, "min": 0}; }, 99 99 GetFullAttackRange: function() { return { "max": 40, "min": 0}; }, 100 GetBestAttackAgainst: function(t) { return " melee"; },100 GetBestAttackAgainst: function(t) { return "Melee"; }, 101 101 GetPreference: function(t) { return 0; }, 102 102 GetTimers: function() { return { "prepare": 500, "repeat": 1000 }; }, 103 103 CanAttack: function(v) { return true; }, … … 247 247 AddMock(unit + i, IID_Attack, { 248 248 GetRange: function() { return {"max":10, "min": 0}; }, 249 249 GetFullAttackRange: function() { return { "max": 40, "min": 0}; }, 250 GetBestAttackAgainst: function(t) { return " melee"; },250 GetBestAttackAgainst: function(t) { return "Melee"; }, 251 251 GetTimers: function() { return { "prepare": 500, "repeat": 1000 }; }, 252 252 CanAttack: function(v) { return true; }, 253 253 CompareEntitiesByPreference: function(a, b) { return 0; }, -
binaries/data/mods/public/simulation/helpers/Commands.js
160 160 "attack-walk": function(player, cmd, data) 161 161 { 162 162 GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { 163 cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.queued );163 cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.queued, cmd.prefAttackType); 164 164 }); 165 165 }, 166 166 … … 169 169 if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target))) 170 170 warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd)); 171 171 172 let allowCapture = cmd.allowCapture || cmd.allowCapture == null;173 174 172 GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { 175 cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture);173 cmpUnitAI.Attack(cmd.target, cmd.queued, cmd.prefAttackType); 176 174 }); 177 175 }, 178 176 -
binaries/data/mods/public/simulation/templates/template_structure_defense_wall_long.xml
12 12 </Health> 13 13 <GarrisonHolder> 14 14 <Max>5</Max> 15 <List datatype="tokens">Ranged+Infantry</List> 15 <List datatype="tokens">Infantry</List> 16 <NeededAttackTypes datatype="tokens">Ranged</NeededAttackTypes> 16 17 <EjectHealth>0.1</EjectHealth> 17 18 <EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy> 18 19 <BuffHeal>0</BuffHeal> -
binaries/data/mods/public/simulation/templates/template_structure_defense_wall_medium.xml
9 9 </Cost> 10 10 <GarrisonHolder> 11 11 <Max>3</Max> 12 <List datatype="tokens">Ranged+Infantry</List> 12 <List datatype="tokens">Infantry</List> 13 <NeededAttackTypes datatype="tokens">Ranged</NeededAttackTypes> 13 14 <EjectHealth>0.1</EjectHealth> 14 15 <EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy> 15 16 <BuffHeal>0</BuffHeal> -
binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml
6 6 <Crush>10</Crush> 7 7 </Armour> 8 8 <Attack> 9 <Melee> 10 <Hack>3</Hack> 11 <Pierce>0</Pierce> 12 <Crush>0</Crush> 13 <MaxRange>4.0</MaxRange> 14 <RepeatTime>1000</RepeatTime> 15 <PreferredClasses datatype="tokens">Human</PreferredClasses> 16 </Melee> 9 17 <Ranged> 10 18 <Hack>0</Hack> 11 19 <Pierce>1.5</Pierce> -
binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_archer.xml
10 10 <Pierce>6.0</Pierce> 11 11 <Crush>0</Crush> 12 12 <MaxRange>72.0</MaxRange> 13 <MinRange> 0.0</MinRange>13 <MinRange>10.0</MinRange> 14 14 <ProjectileSpeed>120.0</ProjectileSpeed> 15 15 <PrepareTime>1000</PrepareTime> 16 16 <RepeatTime>1000</RepeatTime> -
binaries/data/mods/public/simulation/templates/units/pers_champion_infantry.xml
1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 <Entity parent="template_unit_champion_infantry_spearman"> 3 <Attack> 4 <Ranged> 5 <Hack>0</Hack> 6 <Pierce>6.0</Pierce> 7 <Crush>0</Crush> 8 <MaxRange>65.0</MaxRange> 9 <MinRange>10.0</MinRange> 10 <ProjectileSpeed>120.0</ProjectileSpeed> 11 <PrepareTime>1000</PrepareTime> 12 <RepeatTime>1000</RepeatTime> 13 <Spread>3.0</Spread> 14 </Ranged> 15 </Attack> 3 16 <Identity> 4 17 <Civ>pers</Civ> 5 18 <GenericName>Persian Immortal</GenericName>