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 System;
13 using System.Collections.Generic;
14 using OpenRA.Activities;
15 using OpenRA.Mods.Common.Pathfinder;
16 using OpenRA.Mods.Common.Traits;
17 using OpenRA.Primitives;
18 using OpenRA.Traits;
19 
20 namespace OpenRA.Mods.Common.Activities
21 {
22 	public class FindAndDeliverResources : Activity
23 	{
24 		readonly Harvester harv;
25 		readonly HarvesterInfo harvInfo;
26 		readonly Mobile mobile;
27 		readonly LocomotorInfo locomotorInfo;
28 		readonly ResourceClaimLayer claimLayer;
29 		readonly IPathFinder pathFinder;
30 		readonly DomainIndex domainIndex;
31 
32 		Actor deliverActor;
33 		CPos? orderLocation;
34 		CPos? lastHarvestedCell;
35 		bool hasDeliveredLoad;
36 		bool hasHarvestedCell;
37 		bool hasWaited;
38 
39 		public bool LastSearchFailed { get; private set; }
40 
FindAndDeliverResources(Actor self, Actor deliverActor = null)41 		public FindAndDeliverResources(Actor self, Actor deliverActor = null)
42 		{
43 			harv = self.Trait<Harvester>();
44 			harvInfo = self.Info.TraitInfo<HarvesterInfo>();
45 			mobile = self.Trait<Mobile>();
46 			locomotorInfo = mobile.Info.LocomotorInfo;
47 			claimLayer = self.World.WorldActor.Trait<ResourceClaimLayer>();
48 			pathFinder = self.World.WorldActor.Trait<IPathFinder>();
49 			domainIndex = self.World.WorldActor.Trait<DomainIndex>();
50 			this.deliverActor = deliverActor;
51 		}
52 
FindAndDeliverResources(Actor self, CPos orderLocation)53 		public FindAndDeliverResources(Actor self, CPos orderLocation)
54 			: this(self, null)
55 		{
56 			this.orderLocation = orderLocation;
57 		}
58 
OnFirstRun(Actor self)59 		protected override void OnFirstRun(Actor self)
60 		{
61 			// If an explicit "harvest" order is given, direct the harvester to the ordered location instead of
62 			// the previous harvested cell for the initial search.
63 			if (orderLocation != null)
64 			{
65 				lastHarvestedCell = orderLocation;
66 
67 				// If two "harvest" orders are issued consecutively, we deliver the load first if needed.
68 				// We have to make sure the actual "harvest" order is not skipped if a third order is queued,
69 				// so we keep deliveredLoad false.
70 				if (harv.IsFull)
71 					QueueChild(new DeliverResources(self));
72 			}
73 
74 			// If an explicit "deliver" order is given, the harvester goes immediately to the refinery.
75 			if (deliverActor != null)
76 			{
77 				QueueChild(new DeliverResources(self, deliverActor));
78 				hasDeliveredLoad = true;
79 				deliverActor = null;
80 			}
81 		}
82 
Tick(Actor self)83 		public override bool Tick(Actor self)
84 		{
85 			if (IsCanceling)
86 				return true;
87 
88 			if (NextActivity != null)
89 			{
90 				// Interrupt automated harvesting after clearing the first cell.
91 				if (!harvInfo.QueueFullLoad && (hasHarvestedCell || LastSearchFailed))
92 					return true;
93 
94 				// Interrupt automated harvesting after first complete harvest cycle.
95 				if (hasDeliveredLoad || harv.IsFull)
96 					return true;
97 			}
98 
99 			// Are we full or have nothing more to gather? Deliver resources.
100 			if (harv.IsFull || (!harv.IsEmpty && LastSearchFailed))
101 			{
102 				QueueChild(new DeliverResources(self));
103 				hasDeliveredLoad = true;
104 				return false;
105 			}
106 
107 			// After a failed search, wait and sit still for a bit before searching again.
108 			if (LastSearchFailed && !hasWaited)
109 			{
110 				QueueChild(new Wait(harv.Info.WaitDuration));
111 				hasWaited = true;
112 				return false;
113 			}
114 
115 			hasWaited = false;
116 
117 			// Scan for resources. If no resources are found near the current field, search near the refinery
118 			// instead. If that doesn't help, give up for now.
119 			var closestHarvestableCell = ClosestHarvestablePos(self);
120 			if (!closestHarvestableCell.HasValue)
121 			{
122 				if (lastHarvestedCell != null)
123 				{
124 					lastHarvestedCell = null; // Forces search from backup position.
125 					closestHarvestableCell = ClosestHarvestablePos(self);
126 					LastSearchFailed = !closestHarvestableCell.HasValue;
127 				}
128 				else
129 					LastSearchFailed = true;
130 			}
131 			else
132 				LastSearchFailed = false;
133 
134 			// If no harvestable position could be found and we are at the refinery, get out of the way
135 			// of the refinery entrance.
136 			if (LastSearchFailed)
137 			{
138 				var lastproc = harv.LastLinkedProc ?? harv.LinkedProc;
139 				if (lastproc != null && !lastproc.Disposed)
140 				{
141 					var deliveryLoc = lastproc.Location + lastproc.Trait<IAcceptResources>().DeliveryOffset;
142 					if (self.Location == deliveryLoc && harv.IsEmpty)
143 					{
144 						var unblockCell = deliveryLoc + harv.Info.UnblockCell;
145 						var moveTo = mobile.NearestMoveableCell(unblockCell, 1, 5);
146 						QueueChild(mobile.MoveTo(moveTo, 1));
147 					}
148 				}
149 
150 				return false;
151 			}
152 
153 			// If we get here, our search for resources was successful. Commence harvesting.
154 			QueueChild(new HarvestResource(self, closestHarvestableCell.Value));
155 			lastHarvestedCell = closestHarvestableCell.Value;
156 			hasHarvestedCell = true;
157 			return false;
158 		}
159 
160 		/// <summary>
161 		/// Finds the closest harvestable pos between the current position of the harvester
162 		/// and the last order location
163 		/// </summary>
ClosestHarvestablePos(Actor self)164 		CPos? ClosestHarvestablePos(Actor self)
165 		{
166 			// Harvesters should respect an explicit harvest order instead of harvesting the current cell.
167 			if (orderLocation == null)
168 			{
169 				if (harv.CanHarvestCell(self, self.Location) && claimLayer.CanClaimCell(self, self.Location))
170 					return self.Location;
171 			}
172 			else
173 			{
174 				if (harv.CanHarvestCell(self, orderLocation.Value) && claimLayer.CanClaimCell(self, orderLocation.Value))
175 					return orderLocation;
176 
177 				orderLocation = null;
178 			}
179 
180 			// Determine where to search from and how far to search:
181 			var procLoc = GetSearchFromProcLocation(self);
182 			var searchFromLoc = lastHarvestedCell ?? procLoc;
183 			var searchRadius = lastHarvestedCell.HasValue ? harvInfo.SearchFromHarvesterRadius : harvInfo.SearchFromProcRadius;
184 			if (!searchFromLoc.HasValue)
185 			{
186 				searchFromLoc = self.Location;
187 				searchRadius = harvInfo.SearchFromHarvesterRadius;
188 			}
189 
190 			var searchRadiusSquared = searchRadius * searchRadius;
191 
192 			var procPos = procLoc.HasValue ? (WPos?)self.World.Map.CenterOfCell(procLoc.Value) : null;
193 			var harvPos = self.CenterPosition;
194 
195 			// Find any harvestable resources:
196 			List<CPos> path;
197 			using (var search = PathSearch.Search(self.World, mobile.Locomotor, self, BlockedByActor.Stationary, loc =>
198 					domainIndex.IsPassable(self.Location, loc, locomotorInfo) && harv.CanHarvestCell(self, loc) && claimLayer.CanClaimCell(self, loc))
199 				.WithCustomCost(loc =>
200 				{
201 					if ((loc - searchFromLoc.Value).LengthSquared > searchRadiusSquared)
202 						return int.MaxValue;
203 
204 					// Add a cost modifier to harvestable cells to prefer resources that are closer to the refinery.
205 					// This reduces the tendancy for harvesters to move in straight lines
206 					if (procPos.HasValue && harvInfo.ResourceRefineryDirectionPenalty > 0 && harv.CanHarvestCell(self, loc))
207 					{
208 						var pos = self.World.Map.CenterOfCell(loc);
209 
210 						// Calculate harv-cell-refinery angle (cosine rule)
211 						var a = harvPos - procPos.Value;
212 						var b = pos - procPos.Value;
213 						var c = pos - harvPos;
214 
215 						if (b != WVec.Zero && c != WVec.Zero)
216 						{
217 							var cosA = (int)(512 * (b.LengthSquared + c.LengthSquared - a.LengthSquared) / b.Length / c.Length);
218 
219 							// Cost modifier varies between 0 and ResourceRefineryDirectionPenalty
220 							return Math.Abs(harvInfo.ResourceRefineryDirectionPenalty / 2) + harvInfo.ResourceRefineryDirectionPenalty * cosA / 2048;
221 						}
222 					}
223 
224 					return 0;
225 				})
226 				.FromPoint(searchFromLoc.Value)
227 				.FromPoint(self.Location))
228 				path = pathFinder.FindPath(search);
229 
230 			if (path.Count > 0)
231 				return path[0];
232 
233 			return null;
234 		}
235 
GetTargets(Actor self)236 		public override IEnumerable<Target> GetTargets(Actor self)
237 		{
238 			yield return Target.FromCell(self.World, self.Location);
239 		}
240 
TargetLineNodes(Actor self)241 		public override IEnumerable<TargetLineNode> TargetLineNodes(Actor self)
242 		{
243 			if (ChildActivity != null)
244 				foreach (var n in ChildActivity.TargetLineNodes(self))
245 					yield return n;
246 
247 			if (orderLocation != null)
248 				yield return new TargetLineNode(Target.FromCell(self.World, orderLocation.Value), Color.Crimson);
249 			else if (deliverActor != null)
250 				yield return new TargetLineNode(Target.FromActor(deliverActor), Color.Green);
251 		}
252 
GetSearchFromProcLocation(Actor self)253 		CPos? GetSearchFromProcLocation(Actor self)
254 		{
255 			if (harv.LastLinkedProc != null && !harv.LastLinkedProc.IsDead && harv.LastLinkedProc.IsInWorld)
256 				return harv.LastLinkedProc.Location + harv.LastLinkedProc.Trait<IAcceptResources>().DeliveryOffset;
257 
258 			if (harv.LinkedProc != null && !harv.LinkedProc.IsDead && harv.LinkedProc.IsInWorld)
259 				return harv.LinkedProc.Location + harv.LinkedProc.Trait<IAcceptResources>().DeliveryOffset;
260 
261 			return null;
262 		}
263 	}
264 }
265