Ticket #252: t252_secondattack_2.diff
File t252_secondattack_2.diff, 27.6 KB (added by , 8 years ago) |
---|
-
binaries/data/config/default.cfg
274 274 [hotkey.session] 275 275 kill = Delete ; Destroy selected units 276 276 stop = "H" ; Stop the current action 277 attack = Ctrl ; Modifier to attack instead of another action (eg capture)278 attackmove = Ctrl ; Modifier to attackmove when clicking on a point279 attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point (should contain the attackmove keys)277 attack = Ctrl ; Modifier to primary attack instead of another action (eg capture) 278 attackmove = Ctrl ; Modifier to primary attackmove when clicking on a point 279 attackmoveUnit = "Ctrl+Q" ; Modifier to primary attackmove targeting only units when clicking on a point (should contain the attackmove keys) 280 280 garrison = Ctrl ; Modifier to garrison when clicking on building 281 281 autorallypoint = Ctrl ; Modifier to set the rally point on the building itself 282 secondattack = Alt ; Modifier to secondary attack instead of another action 283 secondattackmove = Alt ; Modifier to secondary attackmove when clicking on a point 284 secondattackmoveUnit = "Alt+Q"; Modifier to secondary attackmove targeting only units when clicking on a point 282 285 guard = "G" ; Modifier to escort/guard when clicking on unit/building 283 286 queue = Shift ; Modifier to queue unit orders instead of replacing 284 287 batchtrain = Shift ; Modifier to train units in batches -
binaries/data/mods/public/art/actors/units/persians/champion_unit_1.xml
14 14 <animation event="0.5" file="infantry/sword/attack/isw_s_def_06.psa" name="attack_melee" speed="80"/> 15 15 <animation event="0.5" file="infantry/sword/attack/isw_s_def_06.psa" name="attack_melee" speed="80"/> 16 16 <animation event="0.5" file="infantry/sword/attack/isw_s_off_05.psa" name="attack_melee" speed="80"/> 17 <animation event="0.84" file="biped/inf_arch_atk_a.psa" load="0.16" name="attack_ranged" speed="90"/> 17 18 <animation file="infantry/sword/move/run/isw_s_off_01.psa" name="Run" speed="25"/> 18 19 <animation file="infantry/sword/move/run/isw_s_def_02.psa" name="Run" speed="30"/> 19 20 <animation file="infantry/sword/move/run/isw_s_em_03.psa" name="Run" speed="30"/> … … 28 29 <props> 29 30 <prop actor="props/units/heads/head_pers_tiara.xml" attachpoint="head"/> 30 31 <prop actor="props/units/heads/pers_kidaris_tied.xml" attachpoint="helmet"/> 31 <prop actor="props/units/shields/gerron_b.xml" attachpoint="shield"/>32 <prop actor="props/units/weapons/spear_ball.xml" attachpoint="r_hand"/>33 32 <prop actor="props/units/pers_quiver_back.xml" attachpoint="back"/> 34 33 </props> 35 34 </variant> … … 39 38 <textures><texture file="skeletal/pers_su1_anusiya_iron.dds" name="baseTex"/></textures> 40 39 </variant> 41 40 </group> 41 <group> 42 <variant name="attack_melee"> 43 <props> 44 <prop actor="props/units/shields/gerron_b.xml" attachpoint="shield"/> 45 <prop actor="props/units/weapons/spear_ball.xml" attachpoint="r_hand"/> 46 </props> 47 </variant> 48 <variant name="attack_ranged"> 49 <props> 50 <prop actor="props/units/weapons/bow_recurve.xml" attachpoint="l_hand"/> 51 <prop actor="props/units/weapons/arrow_back.xml" attachpoint="loaded-r_hand"/> 52 <prop actor="props/units/weapons/arrow_front.xml" attachpoint="projectile"/> 53 </props> 54 </variant> 55 </group> 42 56 <material>player_trans.xml</material> 43 57 </actor> -
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 209 if (Engine.HotkeyIsPressed("session.secondattackmove")) 210 { 211 data.command = "attack-walk"; 212 data.targetClasses = Engine.HotkeyIsPressed("session.secondattackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] }; 213 cursor = "action-attack-move"; //TODO another cursor 214 } 208 215 return { "possible": true, "data": data, "cursor": cursor }; 209 216 } 210 217 -
binaries/data/mods/public/gui/session/unit_actions.js
87 87 "specificness": 30, 88 88 }, 89 89 90 "second-attack-move": // TODO is this one needed? 91 { 92 "execute": function(target, action, selection, queued) 93 { 94 if (Engine.HotkeyIsPressed("session.secondattackmoveUnit")) 95 var targetClasses = { "secondattack": ["Unit"] }; 96 else 97 var targetClasses = { "secondattack": ["Unit", "Structure"] }; 98 99 Engine.PostNetworkCommand({"type": "second-attack-walk", "entities": selection, "x": target.x, "z": target.z, "targetClasses": targetClasses, "queued": queued}); 100 Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] }); 101 return true; 102 }, 103 "getActionInfo": function(entState, targetState) 104 { 105 if (!entState.attack || !targetState.hitpoints) 106 return false; 107 return {"possible": Engine.GuiInterfaceCall("CanSecondAttack", {"entity": entState.id, "target": targetState.id})}; 108 }, 109 "hotkeyActionCheck": function(target, selection) 110 { 111 // Work out whether at least part of the selection have UnitAI 112 var haveUnitAI = selection.some(function(ent) { 113 var entState = GetEntityState(ent); 114 return entState && entState.unitAI; 115 }); 116 if (haveUnitAI && Engine.HotkeyIsPressed("session.secondattackmove") && getActionInfo("second-attack-move", target).possible) 117 return {"type": "second-attack-move", "cursor": "action-attack-move"}; // TODO another cursor 118 return false; 119 }, 120 "specificness": 32, 121 }, 122 90 123 "capture": 91 124 { 92 125 "execute": function(target, action, selection, queued) … … 139 172 "specificness": 10, 140 173 }, 141 174 175 "second-attack": 176 { 177 "execute": function(target, action, selection, queued) 178 { 179 Engine.PostNetworkCommand({"type": "second-attack", "entities": selection, "target": action.target, "queued": queued, "allowCapture": false}); 180 Engine.GuiInterfaceCall("PlaySound", { "name": "order_secondattack", "entity": selection[0] }); 181 return true; 182 }, 183 "getActionInfo": function(entState, targetState) 184 { 185 if (!entState.attack || !targetState.hitpoints) 186 return false; 187 return {"possible": Engine.GuiInterfaceCall("CanSecondAttack", {"entity": entState.id, "target": targetState.id})}; 188 }, 189 "hotkeyActionCheck": function(target) 190 { 191 if (Engine.HotkeyIsPressed("session.secondattack") && getActionInfo("second-attack", target).possible) 192 return {"type": "second-attack", "cursor": "action-attack", "target": target}; // TODO another cursor 193 return false; 194 }, 195 "actionCheck": function(target) 196 { 197 if (getActionInfo("second-attack", target).possible) 198 return {"type": "second-attack", "cursor": "action-attack", "target": target}; // TODO another cursor 199 return false; 200 }, 201 "specificness": 15, 202 }, 203 142 204 "heal": 143 205 { 144 206 "execute": function(target, action, selection, queued) … … 499 561 data.targetClasses = targetClasses; 500 562 cursor = "action-attack-move"; 501 563 } 564 if (Engine.HotkeyIsPressed("session.secondattackmove")) 565 { 566 if (Engine.HotkeyIsPressed("session.secondattackmoveUnit")) 567 var targetClasses = { "second-attack": ["Unit"] }; 568 else 569 var targetClasses = { "second-attack": ["Unit", "Structure"] }; 570 data.command = "second-attack-walk"; 571 data.targetClasses = targetClasses; 572 cursor = "action-attack-move"; //TODO another cursor 573 } 502 574 503 575 if (targetState.garrisonHolder && playerCheck(entState, targetState, ["Player", "MutualAlly"])) 504 576 { -
binaries/data/mods/public/simulation/components/Attack.js
42 42 "<a:help>Controls the attack abilities and strengths of the unit.</a:help>" + 43 43 "<a:example>" + 44 44 "<Melee>" + 45 "<AttackOrder>primary</AttackOrder>" + 45 46 "<Hack>10.0</Hack>" + 46 47 "<Pierce>0.0</Pierce>" + 47 48 "<Crush>5.0</Crush>" + … … 62 63 "<PreferredClasses datatype=\"tokens\">Cavalry Infantry</PreferredClasses>" + 63 64 "</Melee>" + 64 65 "<Ranged>" + 66 "<AttackOrder>secondary</AttackOrder>" + 65 67 "<Hack>0.0</Hack>" + 66 68 "<Pierce>10.0</Pierce>" + 67 69 "<Crush>0.0</Crush>" + … … 103 105 "</Slaughter>" + 104 106 "</a:example>" + 105 107 "<optional>" + 108 "<element name='ChangeDistance' a:help='Distance to change between Melee and Ranged attack'>" + 109 "<ref name='nonNegativeDecimal'/>" + 110 "</element>" + 111 "</optional>" + 112 "<optional>" + 106 113 "<element name='Melee'>" + 114 "<optional>" + 115 "<element name='AttackOrder' a:help='primary or secondary'><text/></element>" + 116 "</optional>" + 107 117 "<interleave>" + 108 118 "<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" + 109 119 "<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" + … … 120 130 "</optional>" + 121 131 "<optional>" + 122 132 "<element name='Ranged'>" + 133 "<optional>" + 134 "<element name='AttackOrder' a:help='primary or secondary'><text/></element>" + 135 "</optional>" + 123 136 "<interleave>" + 124 137 "<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" + 125 138 "<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" + … … 218 231 219 232 Attack.prototype.GetPreferredClasses = function(type) 220 233 { 221 if (this.template[type] && this.template[type].PreferredClasses && 222 this.template[type].PreferredClasses._string) 223 { 234 if (this.template[type] && this.template[type].PreferredClasses && this.template[type].PreferredClasses._string) 224 235 return this.template[type].PreferredClasses._string.split(/\s+/); 225 }226 236 return []; 227 237 }; 228 238 229 239 Attack.prototype.GetRestrictedClasses = function(type) 230 240 { 231 if (this.template[type] && this.template[type].RestrictedClasses && 232 this.template[type].RestrictedClasses._string) 233 { 241 if (this.template[type] && this.template[type].RestrictedClasses && this.template[type].RestrictedClasses._string) 234 242 return this.template[type].RestrictedClasses._string.split(/\s+/); 235 }236 243 return []; 237 244 }; 238 245 … … 288 295 return false; 289 296 }; 290 297 298 Attack.prototype.CanSecondAttack = function(target) 299 { 300 for (let type of this.GetAttackTypes()) 301 if (this.template[type].AttackOrder && this.template[type].AttackOrder == "secondary") 302 return this.CanAttack(target); 303 return false; 304 }; 291 305 /** 292 306 * Returns null if we have no preference or the lowest index of a preferred class. 293 307 */ … … 327 341 if (type == "Slaughter") 328 342 continue; 329 343 let range = this.GetRange(type); 330 if (range.min < ret.min) 331 ret.min = range.min; 332 if (range.max > ret.max) 333 ret.max = range.max; 344 ret.min = Math.min(ret.min, range.min) 345 ret.max = Math.max(ret.max, range.max) 334 346 } 335 347 return ret; 336 348 }; 337 349 338 Attack.prototype.GetBestAttackAgainst = function(target, allowCapture )350 Attack.prototype.GetBestAttackAgainst = function(target, allowCapture, prefType) 339 351 { 340 352 let cmpFormation = Engine.QueryInterface(target, IID_Formation); 341 353 if (cmpFormation) … … 365 377 366 378 let types = this.GetAttackTypes().filter(isAllowed); 367 379 380 if (prefType) 381 { 382 if (types[prefType]) 383 return preftype 384 prefType = this.GetPrefAttack(types, prefType); 385 if (types.indexOf(prefType)) 386 return prefType; 387 } 388 368 389 // check if the target is capturable 369 390 let captureIndex = types.indexOf("Capture"); 370 391 if (captureIndex != -1) … … 378 399 types.splice(captureIndex, 1); 379 400 } 380 401 381 let isPreferred = function (className) { return attack.GetPreferredClasses(className).some(isTargetClass); }; 382 let byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); }; 402 // only ranged and/or melee attack left 403 // if one attacktype left choose this one 404 if (types.indexOf("Melee") == -1 || types.indexOf("Ranged") == -1) 405 return types[0]; 383 406 384 return types.sort(byPreference).pop(); 407 if (this.HasPreferredClasses(types)) 408 { 409 let isPreferred = function (className) { return attack.GetPreferredClasses(className).some(isTargetClass); }; 410 let byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); }; 411 412 return types.sort(byPreference).pop(); 413 } 414 // assume ranged and melee attack 415 // TODO stop assuming that? 416 let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); 417 if (!cmpPosition || !cmpPosition.IsInWorld()) 418 return undefined; 419 let selfPosition = cmpPosition.GetPosition(); 420 let cmpTargetPosition = Engine.QueryInterface(target, IID_Position); 421 if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) 422 return undefined; 423 let targetPosition = cmpTargetPosition.GetPosition(); 424 let horizDistance = targetPosition.horizDistanceTo(selfPosition); 425 if (horizDistance <= this.template.ChangeDistance) 426 return "Melee"; 427 return "Ranged" 428 385 429 }; 386 430 387 431 Attack.prototype.CompareEntitiesByPreference = function(a, b) … … 478 522 return attackBonus; 479 523 }; 480 524 525 // Returns preferred attack type if exists 526 Attack.prototype.GetPrefAttack = function(types, pref) 527 { 528 for (let type of types) 529 if (this.template[type].AttackOrder && pref == this.template[type].AttackOrder) 530 return type; 531 return types[0]; 532 }; 533 534 Attack.prototype.HasPreferredClasses = function(types) 535 { 536 for (let type of types) 537 if (this.template[type].PreferredClasses) 538 return true; 539 return false 540 }; 481 541 // Returns a 2d random distribution scaled for a spread of scale 1. 482 542 // The current implementation is a 2d gaussian with sigma = 1 483 543 Attack.prototype.GetNormalDistribution = function(){ … … 486 546 let a = Math.random(); 487 547 let b = Math.random(); 488 548 489 let c = Math.sqrt(-2 *Math.log(a)) * Math.cos(2*Math.PI*b);490 let d = Math.sqrt(-2 *Math.log(a)) * Math.sin(2*Math.PI*b);549 let c = Math.sqrt(-2 * Math.log(a)) * Math.cos(2 * Math.PI * b); 550 let d = Math.sqrt(-2 * Math.log(a)) * Math.sin(2 * Math.PI * b); 491 551 492 552 return [c, d]; 493 553 }; … … 503 563 if (type == "Ranged") 504 564 { 505 565 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 506 let turnLength = cmpTimer.GetLatestTurnLength() /1000;566 let turnLength = cmpTimer.GetLatestTurnLength() / 1000; 507 567 // In the future this could be extended: 508 568 // * Obstacles like trees could reduce the probability of the target being hit 509 569 // * Obstacles like walls should block projectiles entirely … … 620 680 Attack.prototype.InterpolatedLocation = function(ent, lateness) 621 681 { 622 682 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 623 let turnLength = cmpTimer.GetLatestTurnLength() /1000;683 let turnLength = cmpTimer.GetLatestTurnLength() / 1000; 624 684 let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position); 625 685 if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly 626 686 return undefined; … … 658 718 let d = Vector3D.sub(point, targetPosition); 659 719 d = Vector2D.from3D(d).rotate(-angle); 660 720 661 return d.x < Math.abs(targetShape.width /2) && d.y < Math.abs(targetShape.depth/2);721 return d.x < Math.abs(targetShape.width / 2) && d.y < Math.abs(targetShape.depth / 2); 662 722 } 663 723 }; 664 724 … … 740 800 return; 741 801 742 802 for (let type of this.GetAttackTypes()) 743 if (msg.valueNames.indexOf("Attack/" +type+"/MaxRange") !== -1)803 if (msg.valueNames.indexOf("Attack/" + type + "/MaxRange") !== -1) 744 804 { 745 805 cmpUnitAI.UpdateRangeQueries(); 746 806 return; -
binaries/data/mods/public/simulation/components/GuiInterface.js
1728 1728 return false; 1729 1729 }; 1730 1730 1731 GuiInterface.prototype.CanSecondAttack = function(player, data) 1732 { 1733 let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack); 1734 if (!cmpAttack) 1735 return false; 1736 1737 return cmpAttack.CanSecondAttack(data.target) 1738 }; 1739 1731 1740 /* 1732 1741 * Returns batch build time. 1733 1742 */ … … 1871 1880 "GetTradingDetails": 1, 1872 1881 "CanCapture": 1, 1873 1882 "CanAttack": 1, 1883 "CanSecondAttack": 1, 1874 1884 "GetBatchTime": 1, 1875 1885 1876 1886 "IsMapRevealed": 1, -
binaries/data/mods/public/simulation/components/UnitAI.js
416 416 } 417 417 418 418 // Work out how to attack the given target 419 var type = this.GetBestAttackAgainst(this.order.data.target, this.order.data.allowCapture);419 let type = this.GetBestAttackAgainst(this.order.data.target, this.order.data.allowCapture, this.order.data.prefType); 420 420 if (!type) 421 421 { 422 422 // Oops, we can't attack at all … … 583 583 return; 584 584 } 585 585 586 this.PushOrderFront("Attack", { "target": this.order.data.target, "force": false, "hunting": true, "allowCapture": false });586 this.PushOrderFront("Attack", { "target": this.order.data.target, "force": false, "hunting": true, "allowCapture": false, "prefType": undefined }); 587 587 return; 588 588 } 589 589 … … 838 838 }, 839 839 840 840 "Order.Attack": function(msg) { 841 var target = msg.data.target; 842 var allowCapture = msg.data.allowCapture; 843 var cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI); 841 let target = msg.data.target; 842 let allowCapture = msg.data.allowCapture; 843 let prefType = msg.data.prefType; 844 let cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI); 844 845 if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember()) 845 846 target = cmpTargetUnitAI.GetFormationController(); 846 847 … … 859 860 this.FinishOrder(); 860 861 return; 861 862 } 862 this.CallMemberFunction("Attack", [target, false, allowCapture ]);863 this.CallMemberFunction("Attack", [target, false, allowCapture, prefType]); 863 864 if (cmpAttack.CanAttackAsFormation()) 864 865 this.SetNextState("COMBAT.ATTACKING"); 865 866 else … … 914 915 return; 915 916 } 916 917 917 this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "allowCapture": false });918 this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "allowCapture": false, "prefType": undefined }); 918 919 return; 919 920 } 920 921 … … 1151 1152 1152 1153 "MoveCompleted": function(msg) { 1153 1154 var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 1154 this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data.allowCapture ]);1155 this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data.allowCapture, this.order.data.prefType]); 1155 1156 if (cmpAttack.CanAttackAsFormation()) 1156 1157 this.SetNextState("COMBAT.ATTACKING"); 1157 1158 else … … 1162 1163 "ATTACKING": { 1163 1164 // Wait for individual members to finish 1164 1165 "enter": function(msg) { 1165 var target = this.order.data.target; 1166 var allowCapture = this.order.data.allowCapture; 1166 let target = this.order.data.target; 1167 let allowCapture = this.order.data.allowCapture; 1168 let prefType = this.order.data.prefType; 1167 1169 // Check if we are already in range, otherwise walk there 1168 1170 if (!this.CheckTargetAttackRange(target, target)) 1169 1171 { … … 1170 1172 if (this.TargetIsAlive(target) && this.CheckTargetVisible(target)) 1171 1173 { 1172 1174 this.FinishOrder(); 1173 this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture });1175 this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture, "prefType": prefType }); 1174 1176 return true; 1175 1177 } 1176 1178 this.FinishOrder(); … … 1186 1188 }, 1187 1189 1188 1190 "Timer": function(msg) { 1189 var target = this.order.data.target; 1190 var allowCapture = this.order.data.allowCapture; 1191 let target = this.order.data.target; 1192 let allowCapture = this.order.data.allowCapture; 1193 let prefType = this.order.data.prefType; 1191 1194 // Check if we are already in range, otherwise walk there 1192 1195 if (!this.CheckTargetAttackRange(target, target)) 1193 1196 { … … 1194 1197 if (this.TargetIsAlive(target) && this.CheckTargetVisible(target)) 1195 1198 { 1196 1199 this.FinishOrder(); 1197 this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture });1200 this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture, "prefType": prefType }); 1198 1201 return; 1199 1202 } 1200 1203 this.FinishOrder(); … … 1406 1409 1407 1410 // target the unit 1408 1411 if (this.CheckTargetVisible(msg.data.attacker)) 1409 this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "allowCapture": true });1412 this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "allowCapture": true, "prefType": undefined }); 1410 1413 else 1411 1414 { 1412 1415 var cmpPosition = Engine.QueryInterface(msg.data.attacker, IID_Position); … … 4520 4523 return distance < range; 4521 4524 }; 4522 4525 4523 UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture )4526 UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture, prefType) 4524 4527 { 4525 4528 var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 4526 4529 if (!cmpAttack) 4527 4530 return undefined; 4528 return cmpAttack.GetBestAttackAgainst(target, allowCapture );4531 return cmpAttack.GetBestAttackAgainst(target, allowCapture, prefType); 4529 4532 }; 4530 4533 4531 4534 UnitAI.prototype.GetAttackBonus = function(type, target) … … 4547 4550 if (!target) 4548 4551 return false; 4549 4552 4550 this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true });4553 this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true, "prefType": undefined }); 4551 4554 return true; 4552 4555 }; 4553 4556 … … 4560 4563 { 4561 4564 var target = ents.find(target => 4562 4565 this.CanAttack(target, forceResponse) 4563 && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true ))4566 && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true, undefined)) 4564 4567 && (this.GetStance().respondChaseBeyondVision || this.CheckTargetIsInVisionRange(target)) 4565 4568 ); 4566 4569 if (!target) 4567 4570 return false; 4568 4571 4569 this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true });4572 this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true, "prefType": undefined }); 4570 4573 return true; 4571 4574 }; 4572 4575 … … 4986 4989 /** 4987 4990 * Adds attack order to the queue, forced by the player. 4988 4991 */ 4989 UnitAI.prototype.Attack = function(target, queued, allowCapture )4992 UnitAI.prototype.Attack = function(target, queued, allowCapture, prefType) 4990 4993 { 4991 4994 if (!this.CanAttack(target)) 4992 4995 { … … 4998 5001 this.WalkToTarget(target, queued); 4999 5002 return; 5000 5003 } 5001 this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture }, queued);5004 this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture, "prefType": prefType }, queued); 5002 5005 }; 5003 5006 5004 5007 /** … … 5414 5417 if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ]) 5415 5418 continue; 5416 5419 } 5417 this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });5420 this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true, "prefType": undefined }); 5418 5421 return true; 5419 5422 } 5420 5423 } … … 5440 5443 if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ]) 5441 5444 continue; 5442 5445 } 5443 this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });5446 this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true, "prefType": undefined }); 5444 5447 return true; 5445 5448 } 5446 5449 return false; -
binaries/data/mods/public/simulation/helpers/Commands.js
156 156 }); 157 157 }, 158 158 159 "second-attack-walk": function(player, cmd, data) 160 { 161 GetFormationUnitAIs(data.entities, player).forEach(function(cmpUnitAI) { 162 cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.queued); 163 }); 164 }, 165 159 166 "attack": function(player, cmd, data) 160 167 { 161 168 if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target))) 162 169 warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd)); 163 170 171 let prefType = "primary" 164 172 let allowCapture = cmd.allowCapture || cmd.allowCapture == null; 173 if (allowCapture) 174 prefType = "Capture"; 165 175 // See UnitAI.CanAttack for target checks 166 176 GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { 167 cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture );177 cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture, prefType); 168 178 }); 169 179 }, 170 180 181 "second-attack": function(player, cmd, data) 182 { 183 if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target))) 184 { 185 // This check is for debugging only! 186 warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd)); 187 } 188 189 let prefType = "secondary" 190 let allowCapture = cmd.allowCapture || cmd.allowCapture == null; 191 // See UnitAI.CanAttack for target checks 192 GetFormationUnitAIs(data.entities, player).forEach(function(cmpUnitAI) { 193 cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture, prefType); 194 }); 195 }, 196 171 197 "heal": function(player, cmd, data) 172 198 { 173 199 if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByAllyOfPlayer(player, cmd.target))) -
binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_spearman.xml
5 5 <Pierce>10</Pierce> 6 6 </Armour> 7 7 <Attack> 8 <ChangeDistance>20</ChangeDistance> 8 9 <Melee> 10 <AttackOrder>primary</AttackOrder> 9 11 <Hack>6.0</Hack> 10 12 <Pierce>5.0</Pierce> 11 13 <Crush>0.0</Crush> … … 18 20 </BonusCavMelee> 19 21 </Bonuses> 20 22 </Melee> 23 <Ranged> 24 <AttackOrder>secondary</AttackOrder> 25 <Hack>0</Hack> 26 <Pierce>6.0</Pierce> 27 <Crush>0</Crush> 28 <MaxRange>72.0</MaxRange> 29 <MinRange>0.0</MinRange> 30 <ProjectileSpeed>120.0</ProjectileSpeed> 31 <PrepareTime>1000</PrepareTime> 32 <RepeatTime>1000</RepeatTime> 33 <Spread>2.0</Spread> 34 </Ranged> 21 35 <Charge> 22 36 <Hack>15.0</Hack> 23 37 <Pierce>40.0</Pierce>