1 #region Copyright & License Information 2 /* 3 * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) 4 * This file is part of OpenRA, which is free software. It is made 5 * available to you under the terms of the GNU General Public License 6 * as published by the Free Software Foundation, either version 3 of 7 * the License, or (at your option) any later version. For more 8 * information, see COPYING. 9 */ 10 #endregion 11 12 using OpenRA.Traits; 13 14 namespace OpenRA.Mods.Common.Traits 15 { 16 [Desc("Wanders around aimlessly while idle.")] 17 public class WandersInfo : ConditionalTraitInfo, Requires<IMoveInfo> 18 { 19 public readonly int WanderMoveRadius = 1; 20 21 [Desc("Number of ticks to wait before decreasing the effective move radius.")] 22 public readonly int ReduceMoveRadiusDelay = 5; 23 24 [Desc("Minimum amount of ticks the actor will sit idly before starting to wander.")] 25 public readonly int MinMoveDelay = 0; 26 27 [Desc("Maximum amount of ticks the actor will sit idly before starting to wander.")] 28 public readonly int MaxMoveDelay = 0; 29 Create(ActorInitializer init)30 public override object Create(ActorInitializer init) { return new Wanders(init.Self, this); } 31 } 32 33 public class Wanders : ConditionalTrait<WandersInfo>, INotifyIdle, INotifyBecomingIdle 34 { 35 readonly Actor self; 36 readonly WandersInfo info; 37 IResolveOrder move; 38 39 int countdown; 40 int ticksIdle; 41 int effectiveMoveRadius; 42 Wanders(Actor self, WandersInfo info)43 public Wanders(Actor self, WandersInfo info) 44 : base(info) 45 { 46 this.self = self; 47 this.info = info; 48 effectiveMoveRadius = info.WanderMoveRadius; 49 countdown = self.World.SharedRandom.Next(info.MinMoveDelay, info.MaxMoveDelay); 50 } 51 Created(Actor self)52 protected override void Created(Actor self) 53 { 54 move = self.Trait<IMove>() as IResolveOrder; 55 56 base.Created(self); 57 } 58 OnBecomingIdle(Actor self)59 protected virtual void OnBecomingIdle(Actor self) 60 { 61 countdown = self.World.SharedRandom.Next(info.MinMoveDelay, info.MaxMoveDelay); 62 } 63 INotifyBecomingIdle.OnBecomingIdle(Actor self)64 void INotifyBecomingIdle.OnBecomingIdle(Actor self) 65 { 66 OnBecomingIdle(self); 67 } 68 TickIdle(Actor self)69 protected virtual void TickIdle(Actor self) 70 { 71 if (IsTraitDisabled) 72 return; 73 74 if (--countdown > 0) 75 return; 76 77 var targetCell = PickTargetLocation(); 78 if (targetCell != CPos.Zero) 79 DoAction(self, targetCell); 80 } 81 INotifyIdle.TickIdle(Actor self)82 void INotifyIdle.TickIdle(Actor self) 83 { 84 TickIdle(self); 85 } 86 PickTargetLocation()87 CPos PickTargetLocation() 88 { 89 var target = self.CenterPosition + new WVec(0, -1024 * effectiveMoveRadius, 0).Rotate(WRot.FromFacing(self.World.SharedRandom.Next(255))); 90 var targetCell = self.World.Map.CellContaining(target); 91 92 if (!self.World.Map.Contains(targetCell)) 93 { 94 // If MoveRadius is too big there might not be a valid cell to order the attack to (if actor is on a small island and can't leave) 95 if (++ticksIdle % info.ReduceMoveRadiusDelay == 0) 96 effectiveMoveRadius--; 97 98 return CPos.Zero; // We'll be back the next tick; better to sit idle for a few seconds than prolong this tick indefinitely with a loop 99 } 100 101 ticksIdle = 0; 102 effectiveMoveRadius = info.WanderMoveRadius; 103 104 return targetCell; 105 } 106 DoAction(Actor self, CPos targetCell)107 public virtual void DoAction(Actor self, CPos targetCell) 108 { 109 move.ResolveOrder(self, new Order("Move", self, Target.FromCell(self.World, targetCell), false)); 110 } 111 } 112 } 113