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