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