Ticket #1716: formation-order-queues_formation-atack.patch
File formation-order-queues_formation-atack.patch, 20.2 KB (added by , 11 years ago) |
---|
-
binaries/data/mods/public/simulation/components/Formation.js
50 50 } 51 51 52 52 this.inPosition.push(ent); 53 if (this.inPosition.length >= this.members.length) 53 if (this.inPosition.length >= this.members.length) { 54 54 this.Disband(); 55 } 55 56 }; 56 57 57 58 /** … … 81 82 Formation.prototype.SetMembers = function(ents) 82 83 { 83 84 this.members = ents; 84 85 85 for each (var ent in this.members) 86 86 { 87 87 var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); … … 122 122 this.ComputeMotionParameters(); 123 123 124 124 // Rearrange the remaining members 125 this.MoveMembersIntoFormation(true );125 this.MoveMembersIntoFormation(true, true); 126 126 }; 127 127 128 128 /** … … 174 174 }; 175 175 176 176 /** 177 * Call obj.functname(args) on UnitAI components of all members, 178 * and return true if all calls return true. 179 */ 180 Formation.prototype.TestAllMemberFunction = function(funcname, args) 181 { 182 for each (var ent in this.members) 183 { 184 var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); 185 if (!cmpUnitAI[funcname].apply(cmpUnitAI, args)) 186 return false; 187 } 188 return true; 189 }; 190 191 /** 177 192 * Set all members to form up into the formation shape. 178 * If moveCenter is true, the formation center will be reinitiali sed193 * If moveCenter is true, the formation center will be reinitialized 179 194 * to the center of the units. 195 * If force is true, all individual orders of the formation units are replaced, 196 * otherwise the order to walk into formation is just pushed to the front. 180 197 */ 181 Formation.prototype.MoveMembersIntoFormation = function(moveCenter )198 Formation.prototype.MoveMembersIntoFormation = function(moveCenter, force) 182 199 { 183 200 var active = []; 184 201 var positions = []; … … 213 230 var offset = offsets[i]; 214 231 215 232 var cmpUnitAI = Engine.QueryInterface(offset.ent, IID_UnitAI); 216 cmpUnitAI.ReplaceOrder("FormationWalk", { 217 "target": this.entity, 218 "x": offset.x - avgoffset.x, 219 "z": offset.z - avgoffset.z 220 }); 233 234 if (force) 235 { 236 cmpUnitAI.ReplaceOrder("FormationWalk", { 237 "target": this.entity, 238 "x": offset.x - avgoffset.x, 239 "z": offset.z - avgoffset.z 240 }); 241 } 242 else 243 { 244 cmpUnitAI.PushOrderFront("FormationWalk", { 245 "target": this.entity, 246 "x": offset.x - avgoffset.x, 247 "z": offset.z - avgoffset.z 248 }); 249 } 221 250 } 222 251 }; 223 252 … … 651 680 var walkingDistance = cmpUnitAI.ComputeWalkingDistance(); 652 681 var columnar = (walkingDistance > g_ColumnDistanceThreshold); 653 682 if (columnar != this.columnar) 654 this.MoveMembersIntoFormation(false );683 this.MoveMembersIntoFormation(false, true); 655 684 // (disable moveCenter so we can't get stuck in a loop of switching 656 685 // shape causing center to change causing shape to switch back) 657 686 }; -
binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
145 145 TS_FAIL("invalid mode"); 146 146 } 147 147 148 function TestMoveIntoFormationWhileAttacking() 149 { 150 ResetState(); 151 152 var playerEntity = 5; 153 var controller = 10; 154 var enemy = 20; 155 var unit = 30; 156 var units = []; 157 var unitCount = 8; 158 var unitAIs = []; 159 160 AddMock(SYSTEM_ENTITY, IID_Timer, { 161 SetInterval: function() { }, 162 SetTimeout: function() { }, 163 }); 164 165 166 AddMock(SYSTEM_ENTITY, IID_RangeManager, { 167 CreateActiveQuery: function(ent, minRange, maxRange, players, iid, flags) { 168 return 1; 169 }, 170 EnableActiveQuery: function(id) { }, 171 ResetActiveQuery: function(id) { return [enemy]; }, 172 DisableActiveQuery: function(id) { }, 173 GetEntityFlagMask: function(identifier) { }, 174 });; 175 176 AddMock(SYSTEM_ENTITY, IID_PlayerManager, { 177 GetPlayerByID: function(id) { return playerEntity; }, 178 GetNumPlayers: function() { return 2; }, 179 }); 180 181 AddMock(playerEntity, IID_Player, { 182 IsAlly: function() { return []; }, 183 IsEnemy: function() { return []; }, 184 }); 185 186 // create units 187 for (var i = 0; i < unitCount; i++) { 188 189 units.push(unit + i); 190 191 var unitAI = ConstructComponent(unit + i, "UnitAI", { "FormationController": "false", "DefaultStance": "aggressive" }); 192 193 AddMock(unit + i, IID_Identity, { 194 GetClassesList: function() { return []; }, 195 }); 196 197 AddMock(unit + i, IID_Ownership, { 198 GetOwner: function() { return 1; }, 199 }); 200 201 AddMock(unit + i, IID_Position, { 202 GetPosition: function() { return { "x": 0, "z": 0 }; }, 203 IsInWorld: function() { return true; }, 204 }); 205 206 AddMock(unit + i, IID_UnitMotion, { 207 GetWalkSpeed: function() { return 1; }, 208 MoveToFormationOffset: function(target, x, z) { }, 209 IsInTargetRange: function(target, min, max) { return true; }, 210 MoveToTargetRange: function(target, min, max) { }, 211 StopMoving: function() { }, 212 }); 213 214 AddMock(unit + i, IID_Vision, { 215 GetRange: function() { return 10; }, 216 }); 217 218 AddMock(unit + i, IID_Attack, { 219 GetRange: function() { return 10; }, 220 GetBestAttack: function() { return "melee"; }, 221 GetBestAttackAgainst: function(t) { return "melee"; }, 222 GetTimers: function() { return { "prepare": 500, "repeat": 1000 }; }, 223 CanAttack: function(v) { return true; }, 224 CompareEntitiesByPreference: function(a, b) { return 0; }, 225 }); 226 227 unitAI.OnCreate(); 228 229 unitAI.SetupRangeQuery(1); 230 231 unitAIs.push(unitAI); 232 } 233 234 // create enemy 235 AddMock(enemy, IID_Health, { 236 GetHitpoints: function() { return 40; }, 237 }); 238 239 var controllerFormation = ConstructComponent(controller, "Formation"); 240 var controllerAI = ConstructComponent(controller, "UnitAI", { "FormationController": "true", "DefaultStance": "aggressive" }); 241 242 AddMock(controller, IID_Position, { 243 JumpTo: function(x, z) { this.x = x; this.z = z; }, 244 GetPosition: function() { return { "x": this.x, "z": this.z }; }, 245 IsInWorld: function() { return true; }, 246 }); 247 248 AddMock(controller, IID_UnitMotion, { 249 SetUnitRadius: function(r) { }, 250 SetSpeed: function(speed) { }, 251 MoveToPointRange: function(x, z, minRange, maxRange) { }, 252 IsInTargetRange: function(target, min, max) { return true; }, 253 }); 254 255 controllerAI.OnCreate(); 256 257 controllerFormation.SetMembers(units); 258 259 controllerAI.Attack(enemy, []); 260 261 for each (var ent in unitAIs) { 262 TS_ASSERT_EQUALS(unitAI.fsmStateName, "INDIVIDUAL.COMBAT.ATTACKING"); 263 } 264 265 controllerAI.MoveIntoFormation({"name": "Circle"}); 266 267 // let all units be in position 268 for each (var ent in unitAIs) { 269 controllerFormation.SetInPosition(ent); 270 } 271 272 for each (var ent in unitAIs) { 273 TS_ASSERT_EQUALS(unitAI.fsmStateName, "INDIVIDUAL.COMBAT.ATTACKING"); 274 } 275 276 controllerFormation.Disband(); 277 } 278 148 279 TestFormationExiting(0); 149 280 TestFormationExiting(1); 150 281 TestFormationExiting(2); 282 283 TestMoveIntoFormationWhileAttacking(); 284 No newline at end of file -
binaries/data/mods/public/simulation/components/UnitAI.js
496 496 this.MoveToPoint(this.order.data.x, this.order.data.z); 497 497 this.SetNextState("WALKING"); 498 498 }, 499 500 "Order.MoveIntoFormation": function(msg) { 501 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 502 cmpFormation.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); 499 503 504 this.MoveToPoint(this.order.data.x, this.order.data.z); 505 this.SetNextState("FORMING"); 506 }, 507 500 508 // Only used by other orders to walk there in formation 501 509 "Order.WalkToTargetRange": function(msg) { 502 510 if(this.MoveToTargetRangeExplicit(this.order.data.target, this.order.data.min, this.order.data.max)) … … 512 520 }, 513 521 514 522 "Order.Attack": function(msg) { 515 // TODO: we should move in formation towards the target, 516 // then break up into individuals when close enough to it 523 // TODO on what should we base this range? 524 // Check if we are already in range, otherwise walk there 525 if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 20)) 526 { 527 if (!this.TargetIsAlive(msg.data.target)) 528 // The target was destroyed 529 this.FinishOrder(); 530 else 531 // Out of range; move there in formation 532 this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 20 }); 533 return; 534 } 517 535 518 536 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 537 // We don't want to rearrange the formation if the individual units are carrying 538 // out a task and one of the members dies/leaves the formation. 539 cmpFormation.SetRearrange(false); 519 540 cmpFormation.CallMemberFunction("Attack", [msg.data.target, false]); 520 541 521 // TODO: we should wait until the target is killed, then 522 // move on to the next queued order. 523 // Don't bother now, just disband the formation immediately. 524 cmpFormation.Disband(); 542 this.SetNextStateAlwaysEntering("MEMBER"); 525 543 }, 526 544 527 545 "Order.Heal": function(msg) { 528 // TODO: see notes in Order.Attack 546 // TODO on what should we base this range? 547 // Check if we are already in range, otherwise walk there 548 if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) 549 { 550 if (!this.TargetIsAlive(msg.data.target)) 551 // The target was destroyed 552 this.FinishOrder(); 553 else 554 // Out of range; move there in formation 555 this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); 556 return; 557 } 558 529 559 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 560 // We don't want to rearrange the formation if the individual units are carrying 561 // out a task and one of the members dies/leaves the formation. 562 cmpFormation.SetRearrange(false); 530 563 cmpFormation.CallMemberFunction("Heal", [msg.data.target, false]); 531 cmpFormation.Disband(); 564 565 this.SetNextStateAlwaysEntering("MEMBER"); 532 566 }, 533 567 534 568 "Order.Repair": function(msg) { … … 551 585 cmpFormation.SetRearrange(false); 552 586 cmpFormation.CallMemberFunction("Repair", [msg.data.target, msg.data.autocontinue, false]); 553 587 554 this.SetNextState ("REPAIR");588 this.SetNextStateAlwaysEntering("REPAIR"); 555 589 }, 556 590 557 591 "Order.Gather": function(msg) { 558 // TODO: see notes in Order.Attack 592 // TODO on what should we base this range? 593 // Check if we are already in range, otherwise walk there 594 if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) 595 { 596 if (!this.CanGather(msg.data.target)) 597 // The target isn't gatherable 598 this.FinishOrder(); 599 // TODO should be a GatherNearPosition order. 600 // else if (!this.TargetIsAlive(msg.data.target)) 601 // gather near position 602 //cmpFormation.CallMemberFunction("GatherNearPosition", [msg.data.lastPos.x, msg.data.lastPos.z, msg.data.type, msg.data.template, false]); 603 else 604 // Out of range; move there in formation 605 this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); 606 return; 607 } 559 608 560 // If the resource no longer exists, send a GatherNearPosition order561 609 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 562 if (this.CanGather(msg.data.target))563 cmpFormation.CallMemberFunction("Gather", [msg.data.target, false]);564 else565 cmpFormation.CallMemberFunction("GatherNearPosition", [msg.data.lastPos.x, msg.data.lastPos.z, msg.data.type, msg.data.template, false]);610 // We don't want to rearrange the formation if the individual units are carrying 611 // out a task and one of the members dies/leaves the formation. 612 cmpFormation.SetRearrange(false); 613 cmpFormation.CallMemberFunction("Gather", [msg.data.target, false]); 566 614 567 cmpFormation.Disband();615 this.SetNextStateAlwaysEntering("MEMBER"); 568 616 }, 569 617 570 618 "Order.GatherNearPosition": function(msg) { 571 // TODO: see notes in Order.Attack 619 // TODO on what should we base this range? 620 // Check if we are already in range, otherwise walk there 621 if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 20)) 622 { 623 if (!this.TargetIsAlive(msg.data.target)) 624 // The target was destroyed 625 this.FinishOrder(); 626 else 627 // Out of range; move there in formation 628 this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 20 }); 629 return; 630 } 631 572 632 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 633 // We don't want to rearrange the formation if the individual units are carrying 634 // out a task and one of the members dies/leaves the formation. 635 cmpFormation.SetRearrange(false); 573 636 cmpFormation.CallMemberFunction("GatherNearPosition", [msg.data.x, msg.data.z, msg.data.type, msg.data.template, false]); 574 cmpFormation.Disband(); 637 638 this.SetNextStateAlwaysEntering("MEMBER"); 575 639 }, 576 640 577 641 "Order.ReturnResource": function(msg) { 578 // TODO: see notes in Order.Attack 642 // TODO on what should we base this range? 643 // Check if we are already in range, otherwise walk there 644 if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) 645 { 646 if (!this.TargetIsAlive(msg.data.target)) 647 // The target was destroyed 648 this.FinishOrder(); 649 else 650 // Out of range; move there in formation 651 this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); 652 return; 653 } 654 579 655 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 656 // We don't want to rearrange the formation if the individual units are carrying 657 // out a task and one of the members dies/leaves the formation. 658 cmpFormation.SetRearrange(false); 580 659 cmpFormation.CallMemberFunction("ReturnResource", [msg.data.target, false]); 581 cmpFormation.Disband(); 660 661 this.SetNextStateAlwaysEntering("MEMBER"); 582 662 }, 583 663 584 664 "Order.Garrison": function(msg) { 585 // TODO: see notes in Order.Attack 665 // TODO on what should we base this range? 666 // Check if we are already in range, otherwise walk there 667 if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) 668 { 669 if (!this.TargetIsAlive(msg.data.target)) 670 // The target was destroyed 671 this.FinishOrder(); 672 else 673 // Out of range; move there in formation 674 this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); 675 return; 676 } 677 586 678 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 679 // We don't want to rearrange the formation if the individual units are carrying 680 // out a task and one of the members dies/leaves the formation. 681 cmpFormation.SetRearrange(false); 587 682 cmpFormation.CallMemberFunction("Garrison", [msg.data.target, false]); 588 cmpFormation.Disband(); 683 684 this.SetNextStateAlwaysEntering("MEMBER"); 589 685 }, 590 686 591 687 "IDLE": { … … 595 691 "MoveStarted": function(msg) { 596 692 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 597 693 cmpFormation.SetRearrange(true); 598 cmpFormation.MoveMembersIntoFormation(true );694 cmpFormation.MoveMembersIntoFormation(true, true); 599 695 }, 600 696 601 697 "MoveCompleted": function(msg) { 602 698 if (this.FinishOrder()) 603 699 return; 604 700 605 701 // If this was the last order, attempt to disband the formation. 606 702 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 607 703 cmpFormation.FindInPosition(); 608 704 }, 609 705 }, 706 707 "FORMING": { 708 "MoveStarted": function(msg) { 709 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 710 cmpFormation.SetRearrange(true); 711 cmpFormation.MoveMembersIntoFormation(true, false); 712 }, 610 713 714 "MoveCompleted": function(msg) { 715 if (this.FinishOrder()) 716 return; 717 718 // If this was the last order, attempt to disband the formation. 719 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 720 cmpFormation.FindInPosition(); 721 } 722 }, 723 611 724 "REPAIR": { 612 725 "ConstructionFinished": function(msg) { 613 726 if (msg.data.entity != this.order.data.target) … … 620 733 cmpFormation.Disband(); 621 734 }, 622 735 }, 736 737 "MEMBER": { 738 // Wait for individual members to finish 739 "enter": function(msg) { 740 this.StartTimer(1000, 1000); 741 }, 742 743 "Timer": function(msg) { 744 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 745 746 // Have all members finished the task? 747 if (!cmpFormation.TestAllMemberFunction("HasFinishedOrder", [])) 748 return; 749 750 cmpFormation.CallMemberFunction("ResetFinishOrder", []); 751 752 // Execute the next order 753 if (this.FinishOrder()) 754 return; 755 756 // No more order left. 757 cmpFormation.Disband(); 758 }, 759 760 "leave": function(msg) { 761 this.StopTimer(); 762 }, 763 }, 623 764 }, 624 765 625 766 626 767 // States for entities moving as part of a formation: 627 768 "FORMATIONMEMBER": { 628 769 "FormationLeave": function(msg) { 770 // We're not in a formation anymore, so no need to track this. 771 this.finishedOrder = false; 772 629 773 // Stop moving as soon as the formation disbands 630 774 this.StopMoving(); 631 775 … … 685 829 // is done moving. The controller is notified, and will disband the 686 830 // formation if all units are in formation and no orders remain. 687 831 "MoveCompleted": function(msg) { 832 if(this.FinishOrder()) 833 return; 688 834 var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); 689 835 cmpFormation.SetInPosition(this.entity); 690 836 }, … … 1849 1995 this.isGarrisoned = false; 1850 1996 this.isIdle = false; 1851 1997 this.lastFormationName = ""; 1998 this.finishedOrder = false; // used to find if all formation members finished the order 1852 1999 1853 2000 // For preventing increased action rate due to Stop orders or target death. 1854 2001 this.lastAttacked = undefined; … … 1862 2009 return (this.template.FormationController == "true"); 1863 2010 }; 1864 2011 2012 UnitAI.prototype.IsFormationMember = function() 2013 { 2014 return (this.formationController != INVALID_ENTITY); 2015 }; 2016 2017 UnitAI.prototype.HasFinishedOrder = function() 2018 { 2019 return this.finishedOrder; 2020 }; 2021 2022 UnitAI.prototype.ResetFinishOrder = function() 2023 { 2024 this.finishedOrder = false; 2025 }; 2026 1865 2027 UnitAI.prototype.IsAnimal = function() 1866 2028 { 1867 2029 return (this.template.NaturalBehaviour ? true : false); … … 2149 2311 else 2150 2312 { 2151 2313 this.SetNextState("IDLE"); 2314 2315 // Check if there are queued formation orders 2316 if (this.IsFormationMember()) 2317 { 2318 var cmpUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI); 2319 if (cmpUnitAI) 2320 { 2321 // Inform the formation controller that we finished this task 2322 this.finishedOrder = true; 2323 // We don't want to carry out the default order 2324 // if there are still queued formation orders left 2325 if (cmpUnitAI.GetOrders().length > 1) 2326 return true; 2327 } 2328 } 2329 2152 2330 return false; 2153 2331 } 2154 2332 }; … … 2887 3065 return this.lastFormationName; 2888 3066 }; 2889 3067 3068 UnitAI.prototype.MoveIntoFormation = function(cmd) 3069 { 3070 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 3071 if (!cmpFormation) 3072 return; 3073 cmpFormation.LoadFormation(cmd.name); 3074 var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); 3075 if (!cmpPosition || !cmpPosition.IsInWorld()) 3076 return; 3077 var pos = cmpPosition.GetPosition(); 3078 3079 // add new order to get in formation at the current position 3080 this.PushOrderFront("MoveIntoFormation", { "x": pos.x, "z": pos.z, "force": true }); 3081 }; 3082 2890 3083 /** 2891 3084 * Returns the estimated distance that this unit will travel before either 2892 3085 * finishing all of its orders, or reaching a non-walk target (attack, gather, etc). … … 2909 3102 switch (order.type) 2910 3103 { 2911 3104 case "Walk": 3105 case "MoveIntoFormation": 2912 3106 case "GatherNearPosition": 2913 3107 // Add the distance to the target point 2914 3108 var dx = order.data.x - pos.x; -
binaries/data/mods/public/simulation/helpers/Commands.js
370 370 case "formation": 371 371 var entities = FilterEntityList(cmd.entities, player, controlAllUnits); 372 372 GetFormationUnitAIs(entities, player, cmd.name).forEach(function(cmpUnitAI) { 373 var cmpFormation = Engine.QueryInterface(cmpUnitAI.entity, IID_Formation); 374 if (!cmpFormation) 375 return; 376 cmpFormation.LoadFormation(cmd.name); 377 cmpFormation.MoveMembersIntoFormation(true); 373 cmpUnitAI.MoveIntoFormation(cmd); 378 374 }); 379 375 break; 380 376