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