Ticket #252: t252_secondattack_1.diff
File t252_secondattack_1.diff, 27.0 KB (added by , 8 years ago) |
---|
-
binaries/data/config/default.cfg
272 272 [hotkey.session] 273 273 kill = Delete ; Destroy selected units 274 274 stop = "H" ; Stop the current action 275 attack = Ctrl ; Modifier to attack instead of another action (eg capture)276 attackmove = Ctrl ; Modifier to attackmove when clicking on a point277 attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point (should contain the attackmove keys)275 attack = Ctrl ; Modifier to primary attack instead of another action (eg capture) 276 attackmove = Ctrl ; Modifier to primary attackmove when clicking on a point 277 attackmoveUnit = "Ctrl+Q" ; Modifier to primary attackmove targeting only units when clicking on a point (should contain the attackmove keys) 278 278 garrison = Ctrl ; Modifier to garrison when clicking on building 279 279 autorallypoint = Ctrl ; Modifier to set the rally point on the building itself 280 secondattack = Alt ; Modifier to secondary attack instead of another action 281 secondattackmove = Alt ; Modifier to secondary attackmove when clicking on a point 282 secondattackmoveUnit = "Alt+Q"; Modifier to secondary attackmove targeting only units when clicking on a point 280 283 guard = "G" ; Modifier to escort/guard when clicking on unit/building 281 284 queue = Shift ; Modifier to queue unit orders instead of replacing 282 285 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"/> … … 32 33 <prop actor="props/units/weapons/spear_ball.xml" attachpoint="r_hand"/> 33 34 <prop actor="props/units/pers_quiver_back.xml" attachpoint="back"/> 34 35 </props> 36 <textures><texture file="skeletal/pers_su1_anusiya_iron.dds" name="baseTex"/></textures> 35 37 </variant> 36 38 </group> 37 39 <group> … … 38 40 <variant frequency="10" name="Armor Iron Scales"> 39 41 <textures><texture file="skeletal/pers_su1_anusiya_iron.dds" name="baseTex"/></textures> 40 42 </variant> 43 <variant name="attack_ranged"> 44 <props> 45 <prop actor="props/units/weapons/bow_recurve.xml" attachpoint="l_hand"/> 46 <prop actor="props/units/weapons/arrow_back.xml" attachpoint="loaded-r_hand"/> 47 <prop actor="props/units/weapons/arrow_front.xml" attachpoint="projectile"/> 48 </props> 49 </variant> 41 50 </group> 42 51 <material>player_trans.xml</material> 43 52 </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", "Ally"])) 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>" + … … 104 106 "</a:example>" + 105 107 "<optional>" + 106 108 "<element name='Melee'>" + 109 "<optional>" + 110 "<element name='AttackOrder' a:help='primary or secondary'><text/></element>" + 111 "</optional>" + 107 112 "<interleave>" + 108 113 "<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" + 109 114 "<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" + … … 120 125 "</optional>" + 121 126 "<optional>" + 122 127 "<element name='Ranged'>" + 128 "<optional>" + 129 "<element name='AttackOrder' a:help='primary or secondary'><text/></element>" + 130 "</optional>" + 123 131 "<interleave>" + 124 132 "<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" + 125 133 "<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" + … … 218 226 219 227 Attack.prototype.GetPreferredClasses = function(type) 220 228 { 221 if (this.template[type] && this.template[type].PreferredClasses && 222 this.template[type].PreferredClasses._string) 223 { 229 if (this.template[type] && this.template[type].PreferredClasses && this.template[type].PreferredClasses._string) 224 230 return this.template[type].PreferredClasses._string.split(/\s+/); 225 }226 231 return []; 227 232 }; 228 233 229 234 Attack.prototype.GetRestrictedClasses = function(type) 230 235 { 231 if (this.template[type] && this.template[type].RestrictedClasses && 232 this.template[type].RestrictedClasses._string) 233 { 236 if (this.template[type] && this.template[type].RestrictedClasses && this.template[type].RestrictedClasses._string) 234 237 return this.template[type].RestrictedClasses._string.split(/\s+/); 235 }236 238 return []; 237 239 }; 238 240 … … 288 290 return false; 289 291 }; 290 292 293 Attack.prototype.CanSecondAttack = function(target) 294 { 295 for (let type of this.GetAttackTypes()) 296 if (this.template[type].AttackOrder && this.template[type].AttackOrder == "secondary") 297 return this.CanAttack(target); 298 return false; 299 }; 291 300 /** 292 301 * Returns null if we have no preference or the lowest index of a preferred class. 293 302 */ … … 327 336 if (type == "Slaughter") 328 337 continue; 329 338 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; 339 ret.min = Math.min(ret.min, Range.min) 340 ret.max = Math.max(ret.max, Range.max) 334 341 } 335 342 return ret; 336 343 }; 337 344 338 Attack.prototype.GetBestAttackAgainst = function(target, allowCapture )345 Attack.prototype.GetBestAttackAgainst = function(target, allowCapture, prefType) 339 346 { 340 347 let cmpFormation = Engine.QueryInterface(target, IID_Formation); 341 348 if (cmpFormation) … … 365 372 366 373 let types = this.GetAttackTypes().filter(isAllowed); 367 374 375 if (prefType) 376 { 377 if (types[prefType]) 378 return preftype 379 prefType = this.GetPrefAttack(types, prefType); 380 if (types.indexOf(prefType)) 381 return prefType; 382 } 383 368 384 // check if the target is capturable 369 385 let captureIndex = types.indexOf("Capture"); 370 386 if (captureIndex != -1) … … 378 394 types.splice(captureIndex, 1); 379 395 } 380 396 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) ); }; 397 // only ranged and/or melee attack left 398 // if one attacktype left choose this one 399 if (types.indexOf("Melee") == -1 || types.indexOf("Ranged") == -1) 400 return types[0]; 383 401 384 return types.sort(byPreference).pop(); 402 if (this.HasPreferredClasses(types)) 403 { 404 let isPreferred = function (className) { return attack.GetPreferredClasses(className).some(isTargetClass); }; 405 let byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); }; 406 407 return types.sort(byPreference).pop(); 408 } 409 // assume ranged and melee attack 410 // TODO stop assuming that 411 let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); 412 if (!cmpPosition || !cmpPosition.IsInWorld()) 413 return undefined; 414 let selfPosition = cmpPosition.GetPosition(); 415 let cmpTargetPosition = Engine.QueryInterface(target, IID_Position); 416 if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) 417 return undefined; 418 let targetPosition = cmpTargetPosition.GetPosition(); 419 let horizDistance = targetPosition.horizDistanceTo(selfPosition); 420 if (horizDistance <= 2 * this.GetRange("Melee").max) 421 return "Melee"; 422 return "Ranged" 423 385 424 }; 386 425 387 426 Attack.prototype.CompareEntitiesByPreference = function(a, b) … … 478 517 return attackBonus; 479 518 }; 480 519 520 // Returns preferred attack type if exists 521 Attack.prototype.GetPrefAttack = function(types, pref) 522 { 523 for (let type of types) 524 if (this.template[type].AttackOrder && pref == this.template[type].AttackOrder) 525 return type; 526 return types[0]; 527 }; 528 529 Attack.prototype.HasPreferredClasses = function(types) 530 { 531 for (let type of types) 532 if (this.template[type].PreferredClasses) 533 return true; 534 return false 535 }; 481 536 // Returns a 2d random distribution scaled for a spread of scale 1. 482 537 // The current implementation is a 2d gaussian with sigma = 1 483 538 Attack.prototype.GetNormalDistribution = function(){ … … 486 541 let a = Math.random(); 487 542 let b = Math.random(); 488 543 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);544 let c = Math.sqrt(-2 * Math.log(a)) * Math.cos(2 * Math.PI * b); 545 let d = Math.sqrt(-2 * Math.log(a)) * Math.sin(2 * Math.PI * b); 491 546 492 547 return [c, d]; 493 548 }; … … 503 558 if (type == "Ranged") 504 559 { 505 560 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 506 let turnLength = cmpTimer.GetLatestTurnLength() /1000;561 let turnLength = cmpTimer.GetLatestTurnLength() / 1000; 507 562 // In the future this could be extended: 508 563 // * Obstacles like trees could reduce the probability of the target being hit 509 564 // * Obstacles like walls should block projectiles entirely … … 620 675 Attack.prototype.InterpolatedLocation = function(ent, lateness) 621 676 { 622 677 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 623 let turnLength = cmpTimer.GetLatestTurnLength() /1000;678 let turnLength = cmpTimer.GetLatestTurnLength() / 1000; 624 679 let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position); 625 680 if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly 626 681 return undefined; … … 658 713 let d = Vector3D.sub(point, targetPosition); 659 714 d = Vector2D.from3D(d).rotate(-angle); 660 715 661 return d.x < Math.abs(targetShape.width /2) && d.y < Math.abs(targetShape.depth/2);716 return d.x < Math.abs(targetShape.width / 2) && d.y < Math.abs(targetShape.depth / 2); 662 717 } 663 718 }; 664 719 … … 740 795 return; 741 796 742 797 for (let type of this.GetAttackTypes()) 743 if (msg.valueNames.indexOf("Attack/" +type+"/MaxRange") !== -1)798 if (msg.valueNames.indexOf("Attack/" + type + "/MaxRange") !== -1) 744 799 { 745 800 cmpUnitAI.UpdateRangeQueries(); 746 801 return; -
binaries/data/mods/public/simulation/components/GuiInterface.js
1726 1726 return false; 1727 1727 }; 1728 1728 1729 GuiInterface.prototype.CanSecondAttack = function(player, data) 1730 { 1731 let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack); 1732 if (!cmpAttack) 1733 return false; 1734 1735 return cmpAttack.CanSecondAttack(data.target) 1736 }; 1737 1729 1738 /* 1730 1739 * Returns batch build time. 1731 1740 */ … … 1869 1878 "GetTradingDetails": 1, 1870 1879 "CanCapture": 1, 1871 1880 "CanAttack": 1, 1881 "CanSecondAttack": 1, 1872 1882 "GetBatchTime": 1, 1873 1883 1874 1884 "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 … … 842 842 }, 843 843 844 844 "Order.Attack": function(msg) { 845 var target = msg.data.target; 846 var allowCapture = msg.data.allowCapture; 847 var cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI); 845 let target = msg.data.target; 846 let allowCapture = msg.data.allowCapture; 847 let prefType = msg.data.prefType; 848 let cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI); 848 849 if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember()) 849 850 target = cmpTargetUnitAI.GetFormationController(); 850 851 … … 863 864 this.FinishOrder(); 864 865 return; 865 866 } 866 this.CallMemberFunction("Attack", [target, false, allowCapture ]);867 this.CallMemberFunction("Attack", [target, false, allowCapture, prefType]); 867 868 if (cmpAttack.CanAttackAsFormation()) 868 869 this.SetNextState("COMBAT.ATTACKING"); 869 870 else … … 918 919 return; 919 920 } 920 921 921 this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "allowCapture": false });922 this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "allowCapture": false, "prefType": undefined }); 922 923 return; 923 924 } 924 925 … … 1155 1156 1156 1157 "MoveCompleted": function(msg) { 1157 1158 var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 1158 this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data.allowCapture ]);1159 this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data.allowCapture, this.order.data.prefType]); 1159 1160 if (cmpAttack.CanAttackAsFormation()) 1160 1161 this.SetNextState("COMBAT.ATTACKING"); 1161 1162 else … … 1166 1167 "ATTACKING": { 1167 1168 // Wait for individual members to finish 1168 1169 "enter": function(msg) { 1169 var target = this.order.data.target; 1170 var allowCapture = this.order.data.allowCapture; 1170 let target = this.order.data.target; 1171 let allowCapture = this.order.data.allowCapture; 1172 let prefType = this.order.data.prefType; 1171 1173 // Check if we are already in range, otherwise walk there 1172 1174 if (!this.CheckTargetAttackRange(target, target)) 1173 1175 { … … 1174 1176 if (this.TargetIsAlive(target) && this.CheckTargetVisible(target)) 1175 1177 { 1176 1178 this.FinishOrder(); 1177 this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture });1179 this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture, "prefType": prefType }); 1178 1180 return true; 1179 1181 } 1180 1182 this.FinishOrder(); … … 1190 1192 }, 1191 1193 1192 1194 "Timer": function(msg) { 1193 var target = this.order.data.target; 1194 var allowCapture = this.order.data.allowCapture; 1195 let target = this.order.data.target; 1196 let allowCapture = this.order.data.allowCapture; 1197 let prefType = this.order.data.prefType; 1195 1198 // Check if we are already in range, otherwise walk there 1196 1199 if (!this.CheckTargetAttackRange(target, target)) 1197 1200 { … … 1198 1201 if (this.TargetIsAlive(target) && this.CheckTargetVisible(target)) 1199 1202 { 1200 1203 this.FinishOrder(); 1201 this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture });1204 this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture, "prefType": prefType }); 1202 1205 return; 1203 1206 } 1204 1207 this.FinishOrder(); … … 1410 1413 1411 1414 // target the unit 1412 1415 if (this.CheckTargetVisible(msg.data.attacker)) 1413 this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "allowCapture": true });1416 this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "allowCapture": true, "prefType": undefined }); 1414 1417 else 1415 1418 { 1416 1419 var cmpPosition = Engine.QueryInterface(msg.data.attacker, IID_Position); … … 4523 4526 return distance < range; 4524 4527 }; 4525 4528 4526 UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture )4529 UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture, prefType) 4527 4530 { 4528 4531 var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 4529 4532 if (!cmpAttack) 4530 4533 return undefined; 4531 return cmpAttack.GetBestAttackAgainst(target, allowCapture );4534 return cmpAttack.GetBestAttackAgainst(target, allowCapture, prefType); 4532 4535 }; 4533 4536 4534 4537 UnitAI.prototype.GetAttackBonus = function(type, target) … … 4550 4553 if (!target) 4551 4554 return false; 4552 4555 4553 this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true });4556 this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true, "prefType": undefined }); 4554 4557 return true; 4555 4558 }; 4556 4559 … … 4563 4566 { 4564 4567 var target = ents.find(target => 4565 4568 this.CanAttack(target, forceResponse) 4566 && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true ))4569 && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true, undefined)) 4567 4570 && (this.GetStance().respondChaseBeyondVision || this.CheckTargetIsInVisionRange(target)) 4568 4571 ); 4569 4572 if (!target) 4570 4573 return false; 4571 4574 4572 this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true });4575 this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true, "prefType": undefined }); 4573 4576 return true; 4574 4577 }; 4575 4578 … … 4989 4992 /** 4990 4993 * Adds attack order to the queue, forced by the player. 4991 4994 */ 4992 UnitAI.prototype.Attack = function(target, queued, allowCapture )4995 UnitAI.prototype.Attack = function(target, queued, allowCapture, prefType) 4993 4996 { 4994 4997 if (!this.CanAttack(target)) 4995 4998 { … … 5001 5004 this.WalkToTarget(target, queued); 5002 5005 return; 5003 5006 } 5004 this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture }, queued);5007 this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture, "prefType": prefType }, queued); 5005 5008 }; 5006 5009 5007 5010 /** … … 5411 5414 if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ]) 5412 5415 continue; 5413 5416 } 5414 this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });5417 this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true, "prefType": undefined }); 5415 5418 return true; 5416 5419 } 5417 5420 } … … 5437 5440 if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ]) 5438 5441 continue; 5439 5442 } 5440 this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });5443 this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true, "prefType": undefined }); 5441 5444 return true; 5442 5445 } 5443 5446 return false; -
binaries/data/mods/public/simulation/helpers/Commands.js
141 141 }); 142 142 }, 143 143 144 "second-attack-walk": function(player, cmd, data) 145 { 146 GetFormationUnitAIs(data.entities, player).forEach(function(cmpUnitAI) { 147 cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.queued); 148 }); 149 }, 150 144 151 "attack": function(player, cmd, data) 145 152 { 146 153 if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target))) … … 149 156 warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd)); 150 157 } 151 158 159 let prefType = "primary" 152 160 let allowCapture = cmd.allowCapture || cmd.allowCapture == null; 161 if (allowCapture) 162 prefType = "Capture"; 153 163 // See UnitAI.CanAttack for target checks 154 164 GetFormationUnitAIs(data.entities, player).forEach(function(cmpUnitAI) { 155 cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture );165 cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture, prefType); 156 166 }); 157 167 }, 158 168 169 "second-attack": function(player, cmd, data) 170 { 171 if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target))) 172 { 173 // This check is for debugging only! 174 warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd)); 175 } 176 177 let prefType = "secondary" 178 let allowCapture = cmd.allowCapture || cmd.allowCapture == null; 179 // See UnitAI.CanAttack for target checks 180 GetFormationUnitAIs(data.entities, player).forEach(function(cmpUnitAI) { 181 cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture, prefType); 182 }); 183 }, 184 159 185 "heal": function(player, cmd, data) 160 186 { 161 187 if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByAllyOfPlayer(player, cmd.target))) -
binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_spearman.xml
6 6 </Armour> 7 7 <Attack> 8 8 <Melee> 9 <AttackOrder>primary</AttackOrder> 9 10 <Hack>6.0</Hack> 10 11 <Pierce>5.0</Pierce> 11 12 <Crush>0.0</Crush> … … 18 19 </BonusCavMelee> 19 20 </Bonuses> 20 21 </Melee> 22 <Ranged> 23 <AttackOrder>secondary</AttackOrder> 24 <Hack>0</Hack> 25 <Pierce>6.0</Pierce> 26 <Crush>0</Crush> 27 <MaxRange>72.0</MaxRange> 28 <MinRange>0.0</MinRange> 29 <ProjectileSpeed>120.0</ProjectileSpeed> 30 <PrepareTime>1000</PrepareTime> 31 <RepeatTime>1000</RepeatTime> 32 <Spread>2.0</Spread> 33 </Ranged> 21 34 <Charge> 22 35 <Hack>15.0</Hack> 23 36 <Pierce>40.0</Pierce>