Ticket #252: t252_secondattack_20.diff
File t252_secondattack_20.diff, 53.5 KB (added by , 8 years ago) |
---|
-
binaries/data/config/default.cfg
278 278 [hotkey.session] 279 279 kill = Delete ; Destroy selected units 280 280 stop = "H" ; Stop the current action 281 attack = Ctrl ; Modifier to attack instead of another action (eg capture)282 attackmove = Ctrl ; Modifier to attackmove when clicking on a point283 attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point (should contain the attackmove keys)281 attack = Ctrl ; Modifier to primary attack instead of another action (eg capture) 282 attackmove = Ctrl ; Modifier to primary attackmove when clicking on a point 283 attackmoveUnit = "Ctrl+Q" ; Modifier to primary attackmove targeting only units when clicking on a point (should contain the attackmove keys) 284 284 garrison = Ctrl ; Modifier to garrison when clicking on building 285 285 autorallypoint = Ctrl ; Modifier to set the rally point on the building itself 286 meleeattack = Alt ; Modifier to melee attack instead of another action 287 rangedattack = space ; Modifier to ranged attack instead of another action 286 288 guard = "G" ; Modifier to escort/guard when clicking on unit/building 287 289 queue = Shift ; Modifier to queue unit orders instead of replacing 288 290 batchtrain = Shift ; Modifier to train units in batches -
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/session/input.js
205 205 data.targetClasses = Engine.HotkeyIsPressed("session.attackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] }; 206 206 cursor = "action-attack-move"; 207 207 } 208 208 209 return { "possible": true, "data": data, "cursor": cursor }; 209 210 } 210 211 … … 219 220 // Check if the target entity is a resource, dropsite, foundation, or enemy unit. 220 221 // Check if any entities in the selection can gather the requested resource, 221 222 // can return to the dropsite, can build the foundation, or can attack the enemy 222 for each (var entityID inselection)223 for (let entityID of selection) 223 224 { 224 varentState = GetExtendedEntityState(entityID);225 let entState = GetExtendedEntityState(entityID); 225 226 if (!entState) 226 227 continue; 227 228 … … 228 229 if (unitActions[action] && unitActions[action].getActionInfo) 229 230 { 230 231 var r = unitActions[action].getActionInfo(entState, targetState, simState); 231 if (r ) // return true if it's possible for one of the entities232 if (r && r.possible) // return true if it's possible for one of the entities 232 233 return r; 233 234 } 234 235 } -
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": … … 355 465 "target": target 356 466 }; 357 467 }, 358 "specificness": 1 1,468 "specificness": 12, 359 469 }, 360 470 361 471 "gather": … … 376 486 377 487 return true; 378 488 }, 379 "getActionInfo": 489 "getActionInfo": function(entState, targetState) 380 490 { 381 491 if (!targetState.resourceSupply) 382 492 return false; … … 595 705 if (targetState.garrisonHolder.garrisonedEntitiesCount + extraCount >= targetState.garrisonHolder.capacity) 596 706 tooltip = "[color=\"orange\"]" + tooltip + "[/color]"; 597 707 598 if (!MatchesClassList(entState.identity.classes, targetState.garrisonHolder.allowedClasses)) 599 return false; 708 if (!MatchesClassList(entState.identity.classes, targetState.garrisonHolder.allowedClasses) || 709 !HasNeededAttackTypes(entState.attack, targetState.garrisonHolder.neededAttackTypes)) 710 return { "possible": false }; 600 711 601 712 return { 602 713 "possible": true, … … 901 1012 "position": actionInfo.position 902 1013 }; 903 1014 }, 904 "specificness": 6,1015 "specificness": 5, 905 1016 }, 906 1017 907 1018 "unset-rallypoint": … … 983 1094 unloadAll(); 984 1095 }, 985 1096 }, 1097 986 1098 "delete": { 987 1099 "getInfo": function(entState) 988 1100 { … … 1021 1133 openDeleteDialog(selection); 1022 1134 }, 1023 1135 }, 1136 1024 1137 "stop": { 1025 1138 "getInfo": function(entState) 1026 1139 { -
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(); }, … … 752 753 }, 753 754 754 755 moveToRange: function(x, z, min, max, queued = false) { 755 Engine.PostCommand(PlayerID,{"type": "walk-to-range", "entities": [this.id()], "x": x, "z": z, "min": min, "max": max, "queued": queued 756 Engine.PostCommand(PlayerID,{"type": "walk-to-range", "entities": [this.id()], "x": x, "z": z, "min": min, "max": max, "queued": queued}); 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
229 229 return []; 230 230 }; 231 231 232 Attack.prototype.CanAttack = function(target )232 Attack.prototype.CanAttack = function(target, wantedType) 233 233 { 234 234 let cmpFormation = Engine.QueryInterface(target, IID_Formation); 235 235 if (cmpFormation) 236 236 return true; 237 237 238 if (wantedType && !this.template[wantedType]) 239 return false; 240 238 241 let cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position); 239 242 let cmpTargetPosition = Engine.QueryInterface(target, IID_Position); 240 243 if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld()) 241 244 return false; 242 245 243 // Check if the relative height difference is larger than the attack range244 // If the relative height is bigger, it means they will never be able to245 // reach each other, no matter how close they come.246 let heightDiff = Math.abs(cmpThisPosition.GetHeightOffset() - cmpTargetPosition.GetHeightOffset());247 248 246 const cmpIdentity = Engine.QueryInterface(target, IID_Identity); 249 247 if (!cmpIdentity) 250 248 return undefined; 251 249 250 let cmpEntityPlayer = QueryOwnerInterface(this.entity); 251 let cmpTargetPlayer = QueryOwnerInterface(target); 252 if (!cmpTargetPlayer || !cmpEntityPlayer) 253 return false; 254 252 255 const targetClasses = cmpIdentity.GetClassesList(); 256 let cmpCapturable = QueryMiragedInterface(target, IID_Capturable); 257 if (targetClasses.indexOf("Domestic") == -1 && !cmpEntityPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID()) && 258 (!cmpCapturable || !cmpCapturable.CanCapture(cmpEntityPlayer.GetPlayerID()))) 259 return false; 253 260 261 // Check if the relative height difference is larger than the attack range 262 // If the relative height is bigger, it means they will never be able to 263 // reach each other, no matter how close they come. 264 let heightDiff = Math.abs(cmpThisPosition.GetHeightOffset() - cmpTargetPosition.GetHeightOffset()); 265 254 266 for (let type of this.GetAttackTypes()) 255 267 { 256 if ( type == "Capture" && !QueryMiragedInterface(target, IID_Capturable))268 if (wantedType && type != wantedType) 257 269 continue; 258 270 271 if (type == "Capture" && !cmpCapturable) 272 continue; 273 259 274 if (heightDiff > this.GetRange(type).max) 260 275 continue; 261 276 … … 312 327 return ret; 313 328 }; 314 329 315 Attack.prototype.GetBestAttackAgainst = function(target, allowCapture)330 Attack.prototype.GetBestAttackAgainst = function(target, prefAttackType) 316 331 { 317 332 let cmpFormation = Engine.QueryInterface(target, IID_Formation); 318 333 if (cmpFormation) … … 319 334 { 320 335 // TODO: Formation against formation needs review 321 336 let types = this.GetAttackTypes(); 322 return ["Ranged", "Melee", "Capture"].find(attack => types.indexOf(attack) != -1); 337 return ["Ranged", "Melee", "Capture"].find(attack => types.indexOf(attack) != -1 338 && (!prefAttackType || "no" + attack != prefAttackType)); 323 339 } 324 340 325 341 let cmpIdentity = Engine.QueryInterface(target, IID_Identity); … … 326 342 if (!cmpIdentity) 327 343 return undefined; 328 344 345 // If we are visisble garrisoned always do ranged attack if we can. 346 let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); 347 if (cmpPosition && cmpPosition.GetTurretParent() != INVALID_ENTITY) 348 return this.template.Ranged && "Ranged"; 349 329 350 let targetClasses = cmpIdentity.GetClassesList(); 330 351 let isTargetClass = className => targetClasses.indexOf(className) != -1; 331 352 … … 334 355 return "Slaughter"; 335 356 336 357 let attack = this; 358 let isAllowed = function (type) { return !attack.GetRestrictedClasses(type).some(isTargetClass) 359 && (!prefAttackType || "no" + type != prefAttackType); }; 360 337 361 let types = this.GetAttackTypes().filter(type => !attack.GetRestrictedClasses(type).some(isTargetClass)); 362 if (!types.length) 363 return undefined; 338 364 339 // check if the target is capturable340 let captureIndex = types.indexOf("Capture");341 if (captureIndex != -1)365 if (prefAttackType && prefAttackType != "Capture" && types.indexOf(prefAttackType) != -1) 366 return prefAttackType; 367 else if (!prefAttackType || prefAttackType == "Capture") 342 368 { 343 let cmpCapturable = QueryMiragedInterface(target, IID_Capturable); 344 345 let cmpPlayer = QueryOwnerInterface(this.entity); 346 if (allowCapture && cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID())) 347 return "Capture"; 348 // not captureable, so remove this attack 349 types.splice(captureIndex, 1); 369 // check if the target is capturable 370 let captureIndex = types.indexOf("Capture"); 371 if (captureIndex != -1) 372 { 373 let cmpCapturable = QueryMiragedInterface(target, IID_Capturable); 374 let cmpPlayer = QueryOwnerInterface(this.entity); 375 if (cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID())) 376 return "Capture"; 377 // not captureable, so remove this attack 378 types.splice(captureIndex, 1); 379 } 350 380 } 351 381 352 let isPreferred = className => attack.GetPreferredClasses(className).some(isTargetClass); 382 // ignore charges for now: TODO implement these 383 let chargeIndex = types.indexOf("Charge"); 384 if (chargeIndex != -1) 385 types.splice(chargeIndex, 1); 353 386 354 return types.sort((a, b) => 355 (types.indexOf(a) + (isPreferred(a) ? types.length : 0)) - 356 (types.indexOf(b) + (isPreferred(b) ? types.length : 0))).pop(); 387 if (!types.length) 388 return undefined; 389 390 // assume ranged and/or melee attack left 391 // TODO stop assuming that? 392 let meleeIndex = types.indexOf("Melee"); 393 let rangedIndex = types.indexOf("Ranged"); 394 if (meleeIndex != -1 && rangedIndex != -1) 395 { 396 if (!cmpPosition.IsInWorld()) 397 return undefined; 398 let selfPosition = cmpPosition.GetPosition2D(); 399 let cmpTargetPosition = Engine.QueryInterface(target, IID_Position); 400 if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) 401 return undefined; 402 let targetPosition = cmpTargetPosition.GetPosition2D(); 403 let distanceToSquared = targetPosition.distanceToSquared(selfPosition); 404 if (distanceToSquared <= Math.pow(this.GetRange("Ranged").min, 2)) 405 return "Melee"; 406 407 let cmpDamageReceiver = Engine.QueryInterface(target, IID_DamageReceiver); 408 if (!cmpDamageReceiver) 409 return undefined; 410 411 let attackStrengthsMelee = this.GetAttackStrengths("Melee"); 412 let attackBonusMelee = this.GetAttackBonus("Melee", target); 413 let DPSMelee = cmpDamageReceiver.GetDamage( 414 attackStrengthsMelee.hack * attackBonusMelee, 415 attackStrengthsMelee.pierce * attackBonusMelee, 416 attackStrengthsMelee.crush * attackBonusMelee 417 ) / this.GetTimers("Melee").repeat; 418 419 let attackStrengthsRanged = this.GetAttackStrengths("Ranged"); 420 let attackBonusRanged = this.GetAttackBonus("Ranged", target); 421 let DPSRanged = cmpDamageReceiver.GetDamage( 422 attackStrengthsRanged.hack * attackBonusRanged, 423 attackStrengthsRanged.pierce * attackBonusRanged, 424 attackStrengthsRanged.crush * attackBonusRanged 425 ) / this.GetTimers("Ranged").repeat; 426 427 // When we are out of range do DPS only else take distance into account 428 let maxRange = this.GetFullAttackRange().max; 429 if (distanceToSquared > 1.2 * Math.pow(maxRange, 2)) 430 { 431 if (DPSRanged > DPSMelee) 432 return "Ranged"; 433 return "Melee"; 434 } 435 436 let strenghtRanged = DPSRanged * distanceToSquared / Math.pow(maxRange, 2); 437 let strenghtMelee = DPSMelee * (1 - distanceToSquared / Math.pow(maxRange, 2)); 438 439 if (strenghtRanged > strenghtMelee) 440 return "Ranged"; 441 return "Melee"; 442 } 443 else if (meleeIndex != -1) 444 return "Melee"; 445 else if (rangedIndex != -1) 446 return "Ranged"; 447 return types[0]; 357 448 }; 358 449 359 450 Attack.prototype.CompareEntitiesByPreference = function(a, b) … … 517 608 518 609 let horizDistance = targetPosition.horizDistanceTo(selfPosition); 519 610 520 // This is an approximation of the time otthe target, it assumes that the target has a constant radial611 // This is an approximation of the time to the target, it assumes that the target has a constant radial 521 612 // velocity, but since units move in straight lines this is not true. The exact value would be more 522 613 // difficult to calculate and I think this is sufficiently accurate. (I tested and for cavalry it was 523 614 // about 5% of the units radius out in the worst case) … … 593 684 "target": target, 594 685 "attacker": this.entity, 595 686 "multiplier": this.GetAttackBonus(type, target), 596 "type": type687 "type": type 597 688 }); 598 689 } 599 690 // TODO: charge attacks (need to design how they work) -
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
357 357 ret.garrisonHolder = { 358 358 "entities": cmpGarrisonHolder.GetEntities(), 359 359 "allowedClasses": cmpGarrisonHolder.GetAllowedClasses(), 360 "neededAttackTypes": cmpGarrisonHolder.GetNeededAttackTypes(), 360 361 "capacity": cmpGarrisonHolder.GetCapacity(), 361 362 "garrisonedEntitiesCount": cmpGarrisonHolder.GetGarrisonedEntitiesCount() 362 363 }; … … 438 439 let types = cmpAttack.GetAttackTypes(); 439 440 if (types.length) 440 441 ret.attack = {}; 442 441 443 for (let type of types) 442 444 { 443 445 ret.attack[type] = cmpAttack.GetAttackStrengths(type); … … 1827 1829 if (!cmpAttack) 1828 1830 return false; 1829 1831 1830 let cmpEntityPlayer = QueryOwnerInterface(data.entity, IID_Player); 1831 let cmpTargetPlayer = QueryOwnerInterface(data.target, IID_Player); 1832 if (!cmpEntityPlayer || !cmpTargetPlayer) 1833 return false; 1834 1835 // if the owner is an enemy, it's up to the attack component to decide 1836 if (cmpEntityPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID())) 1837 return cmpAttack.CanAttack(data.target); 1838 1839 return false; 1832 let attackType = data.type || undefined; 1833 return cmpAttack.CanAttack(data.target, attackType); 1840 1834 }; 1841 1835 1842 1836 /* -
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_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>