Ticket #1001: attack-move.diff
File attack-move.diff, 13.9 KB (added by , 13 years ago) |
---|
-
binaries/data/mods/public/gui/session/input.js
298 298 return {"type": "garrison", "cursor": "action-garrison", "target": target}; 299 299 else 300 300 return {"type": "none", "cursor": "action-garrison-disabled", "target": undefined}; 301 } 302 else if (Engine.HotkeyIsPressed("attackmove")) 303 { 304 return {"type": "attack-move"}; 301 305 } 302 306 else 303 307 { … … 909 913 Engine.PostNetworkCommand({"type": "attack", "entities": selection, "target": action.target, "queued": queued}); 910 914 Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] }); 911 915 return true; 916 917 case "attack-move": 918 var target = Engine.GetTerrainAtPoint(ev.x, ev.y); 919 Engine.PostNetworkCommand({"type": "attack-move", "entities": selection, "x": target.x, "z": target.z, "queued": queued}); 920 Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack_move", "entity": selection[0] }); 921 return true; 912 922 913 923 case "build": // (same command as repair) 914 924 case "repair": -
binaries/data/mods/public/simulation/helpers/Commands.js
52 52 }); 53 53 } 54 54 break; 55 56 case "attack-move": 57 var entities = FilterEntityList(cmd.entities, player, controlAllUnits); 58 GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) { 59 cmpUnitAI.AttackMove(cmd.x, cmd.z, cmd.queued); 60 }); 61 break; 55 62 56 63 case "repair": 57 64 // This covers both repairing damaged buildings, and constructing unfinished foundations … … 413 420 414 421 if (formedEnts.length == 0) 415 422 { 416 // No units support the fo undation - return all the others423 // No units support the formation - return all the others 417 424 return nonformedUnitAIs; 418 425 } 419 426 -
binaries/data/mods/public/simulation/components/UnitAI.js
164 164 165 165 this.SetNextState("FORMATIONMEMBER.WALKING"); 166 166 }, 167 168 // Called when being told to attack-move as part of a formation 169 "Order.FormationAttackMove": function(msg) { 170 var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 171 cmpUnitMotion.MoveToFormationOffset(msg.data.target, msg.data.x, msg.data.z); 167 172 173 this.SetNextState("FORMATIONMEMBER.ATTACKMOVING"); 174 }, 175 168 176 // Special orders: 169 177 // (these will be overridden by various states) 170 178 … … 394 402 // Don't bother now, just disband the formation immediately. 395 403 cmpFormation.Disband(); 396 404 }, 405 406 "Order.AttackMove": function(msg){ 407 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 408 // The AttackMoveAttack order can mess up the destination position in this.order, so save it. 409 this.attackMovePosition = {x: this.order.data.x, z: this.order.data.z}; 410 cmpFormation.CallMemberFunction("SetHeldPosition", [this.attackMovePosition.x, this.attackMovePosition.z]); 397 411 412 this.MoveToPoint(this.attackMovePosition.x, this.attackMovePosition.z); 413 this.SetNextState("FORMATIONCONTROLLER.ATTACKMOVE.WALK"); 414 }, 415 416 "Order.AttackMoveAttack": function(msg){ 417 // The enemy that was spotted by a formation member. 418 this.target = msg.data.target; 419 this.SetNextState("FORMATIONCONTROLLER.ATTACKMOVE.ATTACK"); 420 }, 421 398 422 "Order.Repair": function(msg) { 399 423 // TODO: see notes in Order.Attack 400 424 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); … … 439 463 cmpFormation.Disband(); 440 464 }, 441 465 }, 466 467 "ATTACKMOVE": { 468 "WALK": { 469 // Whe we enter/re-enter the walk state then set the formation moving again towards the final destination 470 "enter": function(msg){ 471 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 472 if (this.order.data.x) // The AttackMoveAttack order can mess up the destination position in this.order, so save it. 473 this.attackMovePosition = {x: this.order.data.x, z: this.order.data.z}; 474 cmpFormation.CallMemberFunction("SetHeldPosition", [this.attackMovePosition.x, this.attackMovePosition.z]); 475 476 this.MoveToPoint(this.attackMovePosition.x, this.attackMovePosition.z); 477 }, 478 479 "MoveStarted": function(msg) { 480 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 481 cmpFormation.MoveMembersIntoFormation(true, true); 482 }, 483 484 // For when we have arrived at the commanded destination, break up the formation. 485 "MoveCompleted": function(msg) { 486 if (this.FinishOrder()) 487 return; 488 489 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 490 491 cmpFormation.Disband(); 492 }, 493 }, 494 495 "ATTACK":{ 496 // Send all of the formation to attack the enemy that was spotted 497 "enter": function(msg){ 498 // When we enter the attack state stop so the formation controller just stays in one spot until 499 // the members have finished attacking stuff. 500 this.StopMoving(); 501 502 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 503 cmpFormation.CallMemberFunction("Attack", [this.order.data.target]); 504 cmpFormation.suspendFormation(); 505 506 // A timer to check when we should resume the march 507 this.StartTimer(1000, 1000); 508 }, 509 510 "Timer": function(msg){ 511 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 512 // If more than 80% of the formation is idle then resume the walk towards the destination. 513 if (cmpFormation.CountMemberFunction("IsIdle", []) >= 0.8 * cmpFormation.GetMemberCount()){ 514 cmpFormation.resumeFormation(); 515 this.SetNextState("FORMATIONCONTROLLER.ATTACKMOVE.WALK"); 516 } 517 }, 518 519 "leave": function(msg){ 520 this.StopTimer(); 521 }, 522 }, 523 }, 442 524 }, 443 525 444 526 … … 496 578 // and no longer moving, because the formation controller might 497 579 // move and we'll automatically start chasing after it again) 498 580 }, 581 582 "ATTACKMOVING": { 583 "enter": function(){ 584 this.SelectAnimation("move"); 585 // Watch for enemies 586 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 587 var ents = rangeMan.ResetActiveQuery(this.losRangeQuery); 588 }, 589 590 "LosRangeUpdate": function(msg) { 591 var cmpUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI); 592 // TODO: better target selection, e.g. ignore scouts and houses 593 if (msg.data.added[0]){ 594 // Tell the formation controller to stop walking and attack this enemy 595 // Using an order here isn't ideal because this is basically a sub-order, 596 // there doesn't seem to be an alternative though. 597 cmpUnitAI.ReplaceOrder("AttackMoveAttack", {target: msg.data.added[0]}); 598 } 599 }, 600 601 "leave": function(msg){ 602 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 603 rangeMan.DisableActiveQuery(this.losRangeQuery); 604 } 605 }, 499 606 }, 500 607 501 608 … … 2173 2280 var order = this.orderQueue[i]; 2174 2281 switch (order.type) 2175 2282 { 2283 case "AttackMove": 2176 2284 case "Walk": 2177 2285 // Add the distance to the target point 2178 2286 var dx = order.data.x - pos.x; … … 2185 2293 2186 2294 break; // and continue the loop 2187 2295 2296 case "AttackMoveAttack": 2188 2297 case "WalkToTarget": 2189 2298 case "Flee": 2190 2299 case "LeaveFoundation": … … 2258 2367 this.AddOrder("Attack", { "target": target, "force": true }, queued); 2259 2368 }; 2260 2369 2370 UnitAI.prototype.AttackMove = function(x, z, queued) 2371 { 2372 this.AddOrder("AttackMove", { "x": x, "z": z }, queued); 2373 }; 2374 2261 2375 UnitAI.prototype.Garrison = function(target, queued) 2262 2376 { 2263 2377 if (!this.CanGarrison(target)) -
binaries/data/mods/public/simulation/components/Formation.js
8 8 Formation.prototype.Init = function() 9 9 { 10 10 this.members = []; // entity IDs currently belonging to this formation 11 this.suspendedMembers = []; // entity ID's which currently belong to the formation but are being left to their own devices. 11 12 this.columnar = false; // whether we're travelling in column (vs box) formation 12 13 this.formationName = "Line Closed"; 13 14 }; 14 15 15 16 Formation.prototype.GetMemberCount = function() 16 17 { 17 return this.members.length; 18 var allMembers = this.members.concat(this.suspendedMembers); 19 return allMembers.length; 18 20 }; 19 21 20 22 /** … … 26 28 */ 27 29 Formation.prototype.GetPrimaryMember = function() 28 30 { 29 return this.members[0] ;31 return this.members[0] || this.suspendedMembers[0]; 30 32 }; 31 33 32 34 /** … … 36 38 */ 37 39 Formation.prototype.SetMembers = function(ents) 38 40 { 41 this.suspendedMembers = []; 39 42 this.members = ents; 40 43 41 44 for each (var ent in this.members) … … 57 60 Formation.prototype.RemoveMembers = function(ents) 58 61 { 59 62 this.members = this.members.filter(function(e) { return ents.indexOf(e) == -1; }); 63 this.suspendedMembers = this.suspendedMembers.filter(function(e) { return ents.indexOf(e) == -1; }); 60 64 61 65 for each (var ent in ents) 62 66 { … … 65 69 } 66 70 67 71 // If there's nobody left, destroy the formation 68 if (this.members.length == 0 )72 if (this.members.length == 0 && this.suspendedMembers.length == 0) 69 73 { 70 74 Engine.DestroyEntity(this.entity); 71 75 return; … … 82 86 */ 83 87 Formation.prototype.Disband = function() 84 88 { 85 for each (var ent in this.members) 89 var allMembers = this.members.concat(this.suspendedMembers); 90 for each (var ent in allMembers) 86 91 { 87 92 var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); 88 93 cmpUnitAI.SetFormationController(INVALID_ENTITY); 89 94 } 90 95 91 96 this.members = []; 97 this.suspendedMembers = []; 92 98 93 99 Engine.DestroyEntity(this.entity); 94 100 }; 95 101 102 // Allow the units in the formation to move freely but keep them registered to this formation 103 Formation.prototype.suspendFormation = function(){ 104 this.suspendedMembers = this.members; 105 this.members = []; 106 }; 107 108 // Make the units resume their positions in the formation 109 Formation.prototype.resumeFormation = function(){ 110 this.SetMembers(this.suspendedMembers); 111 this.suspendedMembers = []; 112 }; 113 96 114 /** 97 115 * Call obj.funcname(args) on UnitAI components of all members. 98 116 */ 99 117 Formation.prototype.CallMemberFunction = function(funcname, args) 100 118 { 101 for each (var ent in this.members) 119 var allMembers = this.members.concat(this.suspendedMembers); 120 for each (var ent in allMembers) 102 121 { 103 122 var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); 104 123 cmpUnitAI[funcname].apply(cmpUnitAI, args); … … 106 125 }; 107 126 108 127 /** 128 * Call obj.funcname(args) on UnitAI components of all members and count true responses 129 */ 130 Formation.prototype.CountMemberFunction = function(funcname, args) 131 { 132 var count = 0; 133 var allMembers = this.members.concat(this.suspendedMembers); 134 for each (var ent in allMembers) 135 { 136 var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); 137 if (cmpUnitAI[funcname].apply(cmpUnitAI, args)) 138 count ++; 139 } 140 return count; 141 }; 142 143 /** 109 144 * Set all members to form up into the formation shape. 110 145 * If moveCenter is true, the formation center will be reinitialised 111 146 * to the center of the units. 147 * If attackMove is true then the units will be given an AttackMove order 112 148 */ 113 Formation.prototype.MoveMembersIntoFormation = function(moveCenter )149 Formation.prototype.MoveMembersIntoFormation = function(moveCenter, attackMove) 114 150 { 115 151 var active = []; 116 152 var positions = []; … … 145 181 var offset = offsets[i]; 146 182 147 183 var cmpUnitAI = Engine.QueryInterface(offset.ent, IID_UnitAI); 148 cmpUnitAI.ReplaceOrder("FormationWalk", { 149 "target": this.entity, 150 "x": offset.x - avgoffset.x, 151 "z": offset.z - avgoffset.z 152 }); 184 if (attackMove){ 185 cmpUnitAI.ReplaceOrder("FormationAttackMove", { 186 "target": this.entity, 187 "x": offset.x - avgoffset.x, 188 "z": offset.z - avgoffset.z 189 }); 190 }else{ 191 cmpUnitAI.ReplaceOrder("FormationWalk", { 192 "target": this.entity, 193 "x": offset.x - avgoffset.x, 194 "z": offset.z - avgoffset.z 195 }); 196 } 153 197 } 154 198 }; 155 199 … … 594 638 { 595 639 // When an entity is captured or destroyed, it should no longer be 596 640 // controlled by this formation 641 642 var allMembers = this.members.concat(this.suspendedMembers); 597 643 598 if ( this.members.indexOf(msg.entity) != -1)644 if (allMembers.indexOf(msg.entity) != -1) 599 645 this.RemoveMembers([msg.entity]); 600 646 }; 601 647 … … 611 657 var cmpNewUnitAI = Engine.QueryInterface(msg.newentity, IID_UnitAI); 612 658 cmpNewUnitAI.SetFormationController(this.entity); 613 659 } 660 if (this.suspendedMembers.indexOf(msg.entity) != -1) 661 { 662 this.suspendedMembers[this.suspendedMembers.indexOf(msg.entity)] = msg.newentity; 663 664 var cmpOldUnitAI = Engine.QueryInterface(msg.entity, IID_UnitAI); 665 cmpOldUnitAI.SetFormationController(INVALID_ENTITY); 666 667 var cmpNewUnitAI = Engine.QueryInterface(msg.newentity, IID_UnitAI); 668 cmpNewUnitAI.SetFormationController(this.entity); 669 } 614 670 } 615 671 616 672 Formation.prototype.LoadFormation = function(formationName) 617 673 { 674 var allMembers = this.members.concat(this.suspendedMembers); 618 675 this.formationName = formationName; 619 for each (var ent in this.members)676 for each (var ent in allMembers) 620 677 { 621 678 var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); 622 679 cmpUnitAI.SetLastFormationName(this.formationName); -
binaries/data/config/default.cfg
202 202 hotkey.profile.toggle = "F11" ; Enable/disable real-time profiler 203 203 hotkey.profile.save = "Shift+F11" ; Save current profiler data to logs/profile.txt 204 204 205 hotkey.attackmove = Super 206 205 207 ; EXPERIMENTAL: joystick/gamepad settings 206 208 joystick.enable = false 207 209 joystick.deadzone = 8192