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.Collections.Generic; 13 using System.Linq; 14 using OpenRA.Activities; 15 using OpenRA.Mods.Common.Activities; 16 using OpenRA.Mods.Common.Orders; 17 using OpenRA.Primitives; 18 using OpenRA.Support; 19 using OpenRA.Traits; 20 21 namespace OpenRA.Mods.Common.Traits 22 { 23 [Desc("This actor can be sent to a structure for repairs.")] 24 public class RepairableInfo : ITraitInfo, Requires<IHealthInfo>, Requires<IMoveInfo>, IObservesVariablesInfo 25 { 26 [ActorReference] 27 [FieldLoader.Require] 28 public readonly HashSet<string> RepairActors = new HashSet<string> { }; 29 30 [VoiceReference] 31 public readonly string Voice = "Action"; 32 33 [Desc("The amount the unit will be repaired at each step. Use -1 for fallback behavior where HpPerStep from RepairsUnits trait will be used.")] 34 public readonly int HpPerStep = -1; 35 36 [ConsumedConditionReference] 37 [Desc("Boolean expression defining the condition under which the regular (non-force) enter cursor is disabled.")] 38 public readonly BooleanExpression RequireForceMoveCondition = null; 39 Create(ActorInitializer init)40 public virtual object Create(ActorInitializer init) { return new Repairable(init.Self, this); } 41 } 42 43 public class Repairable : IIssueOrder, IResolveOrder, IOrderVoice, INotifyCreated, IObservesVariables 44 { 45 public readonly RepairableInfo Info; 46 readonly IHealth health; 47 Rearmable rearmable; 48 bool requireForceMove; 49 bool isAircraft; 50 Repairable(Actor self, RepairableInfo info)51 public Repairable(Actor self, RepairableInfo info) 52 { 53 Info = info; 54 health = self.Trait<IHealth>(); 55 } 56 INotifyCreated.Created(Actor self)57 void INotifyCreated.Created(Actor self) 58 { 59 rearmable = self.TraitOrDefault<Rearmable>(); 60 isAircraft = self.Info.HasTraitInfo<AircraftInfo>(); 61 } 62 63 IEnumerable<IOrderTargeter> IIssueOrder.Orders 64 { 65 get 66 { 67 if (!isAircraft) 68 yield return new EnterAlliedActorTargeter<BuildingInfo>("Repair", 5, CanRepairAt, _ => CanRepair() || CanRearm()); 69 } 70 } 71 IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)72 Order IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued) 73 { 74 if (order.OrderID == "Repair") 75 return new Order(order.OrderID, self, target, queued); 76 77 return null; 78 } 79 CanRepairAt(Actor target)80 bool CanRepairAt(Actor target) 81 { 82 return Info.RepairActors.Contains(target.Info.Name); 83 } 84 CanRepairAt(Actor target, TargetModifiers modifiers)85 bool CanRepairAt(Actor target, TargetModifiers modifiers) 86 { 87 if (requireForceMove && !modifiers.HasModifier(TargetModifiers.ForceMove)) 88 return false; 89 90 return Info.RepairActors.Contains(target.Info.Name); 91 } 92 CanRepair()93 bool CanRepair() 94 { 95 return health.DamageState > DamageState.Undamaged; 96 } 97 CanRearm()98 bool CanRearm() 99 { 100 return rearmable != null && rearmable.RearmableAmmoPools.Any(p => !p.HasFullAmmo); 101 } 102 IOrderVoice.VoicePhraseForOrder(Actor self, Order order)103 string IOrderVoice.VoicePhraseForOrder(Actor self, Order order) 104 { 105 return order.OrderString == "Repair" && (CanRepair() || CanRearm()) ? Info.Voice : null; 106 } 107 IResolveOrder.ResolveOrder(Actor self, Order order)108 void IResolveOrder.ResolveOrder(Actor self, Order order) 109 { 110 if (order.OrderString != "Repair") 111 return; 112 113 // Repair orders are only valid for own/allied actors, 114 // which are guaranteed to never be frozen. 115 if (order.Target.Type != TargetType.Actor) 116 return; 117 118 // Aircraft handle Repair orders directly in the Aircraft trait 119 // TODO: Move the order handling of both this trait and Aircraft to a generalistic DockManager 120 if (isAircraft) 121 return; 122 123 if (!CanRepairAt(order.Target.Actor) || (!CanRepair() && !CanRearm())) 124 return; 125 126 self.QueueActivity(order.Queued, new Resupply(self, order.Target.Actor, new WDist(512))); 127 self.ShowTargetLines(); 128 } 129 IObservesVariables.GetVariableObservers()130 IEnumerable<VariableObserver> IObservesVariables.GetVariableObservers() 131 { 132 if (Info.RequireForceMoveCondition != null) 133 yield return new VariableObserver(RequireForceMoveConditionChanged, Info.RequireForceMoveCondition.Variables); 134 } 135 RequireForceMoveConditionChanged(Actor self, IReadOnlyDictionary<string, int> conditions)136 void RequireForceMoveConditionChanged(Actor self, IReadOnlyDictionary<string, int> conditions) 137 { 138 requireForceMove = Info.RequireForceMoveCondition.Evaluate(conditions); 139 } 140 FindRepairBuilding(Actor self)141 public Actor FindRepairBuilding(Actor self) 142 { 143 var repairBuilding = self.World.ActorsWithTrait<RepairsUnits>() 144 .Where(a => !a.Actor.IsDead && a.Actor.IsInWorld 145 && a.Actor.Owner.IsAlliedWith(self.Owner) && 146 Info.RepairActors.Contains(a.Actor.Info.Name)) 147 .OrderBy(a => a.Actor.Owner == self.Owner ? 0 : 1) 148 .ThenBy(p => (self.Location - p.Actor.Location).LengthSquared); 149 150 // Worst case FirstOrDefault() will return a TraitPair<null, null>, which is OK. 151 return repairBuilding.FirstOrDefault().Actor; 152 } 153 } 154 } 155