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.Mods.Common.Activities;
15 using OpenRA.Mods.Common.Orders;
16 using OpenRA.Primitives;
17 using OpenRA.Support;
18 using OpenRA.Traits;
19 
20 namespace OpenRA.Mods.Common.Traits
21 {
22 	[Desc("This actor can enter Cargo actors.")]
23 	public class PassengerInfo : ITraitInfo, IObservesVariablesInfo
24 	{
25 		public readonly string CargoType = null;
26 		public readonly PipType PipType = PipType.Green;
27 		public readonly int Weight = 1;
28 
29 		[GrantedConditionReference]
30 		[Desc("The condition to grant to when this actor is loaded inside any transport.")]
31 		public readonly string CargoCondition = null;
32 
33 		[Desc("Conditions to grant when this actor is loaded inside specified transport.",
34 			"A dictionary of [actor id]: [condition].")]
35 		public readonly Dictionary<string, string> CargoConditions = new Dictionary<string, string>();
36 
37 		[GrantedConditionReference]
38 		public IEnumerable<string> LinterCargoConditions { get { return CargoConditions.Values; } }
39 
40 		[VoiceReference]
41 		public readonly string Voice = "Action";
42 
43 		[ConsumedConditionReference]
44 		[Desc("Boolean expression defining the condition under which the regular (non-force) enter cursor is disabled.")]
45 		public readonly BooleanExpression RequireForceMoveCondition = null;
46 
Create(ActorInitializer init)47 		public object Create(ActorInitializer init) { return new Passenger(this); }
48 	}
49 
50 	public class Passenger : INotifyCreated, IIssueOrder, IResolveOrder, IOrderVoice, INotifyRemovedFromWorld, INotifyEnteredCargo, INotifyExitedCargo, INotifyKilled, IObservesVariables
51 	{
52 		public readonly PassengerInfo Info;
53 		public Actor Transport;
54 		bool requireForceMove;
55 
56 		ConditionManager conditionManager;
57 		int anyCargoToken = ConditionManager.InvalidConditionToken;
58 		int specificCargoToken = ConditionManager.InvalidConditionToken;
59 
Passenger(PassengerInfo info)60 		public Passenger(PassengerInfo info)
61 		{
62 			Info = info;
63 		}
64 
65 		public Cargo ReservedCargo { get; private set; }
66 
67 		IEnumerable<IOrderTargeter> IIssueOrder.Orders
68 		{
69 			get
70 			{
71 				yield return new EnterAlliedActorTargeter<CargoInfo>("EnterTransport", 5, IsCorrectCargoType, CanEnter);
72 			}
73 		}
74 
INotifyCreated.Created(Actor self)75 		void INotifyCreated.Created(Actor self)
76 		{
77 			conditionManager = self.TraitOrDefault<ConditionManager>();
78 		}
79 
IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)80 		public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
81 		{
82 			if (order.OrderID == "EnterTransport")
83 				return new Order(order.OrderID, self, target, queued);
84 
85 			return null;
86 		}
87 
IsCorrectCargoType(Actor target, TargetModifiers modifiers)88 		bool IsCorrectCargoType(Actor target, TargetModifiers modifiers)
89 		{
90 			if (requireForceMove && !modifiers.HasModifier(TargetModifiers.ForceMove))
91 				return false;
92 
93 			return IsCorrectCargoType(target);
94 		}
95 
IsCorrectCargoType(Actor target)96 		bool IsCorrectCargoType(Actor target)
97 		{
98 			var ci = target.Info.TraitInfo<CargoInfo>();
99 			return ci.Types.Contains(Info.CargoType);
100 		}
101 
CanEnter(Cargo cargo)102 		bool CanEnter(Cargo cargo)
103 		{
104 			return cargo != null && cargo.HasSpace(Info.Weight);
105 		}
106 
CanEnter(Actor target)107 		bool CanEnter(Actor target)
108 		{
109 			return CanEnter(target.TraitOrDefault<Cargo>());
110 		}
111 
VoicePhraseForOrder(Actor self, Order order)112 		public string VoicePhraseForOrder(Actor self, Order order)
113 		{
114 			if (order.OrderString != "EnterTransport")
115 				return null;
116 
117 			if (order.Target.Type != TargetType.Actor || !CanEnter(order.Target.Actor))
118 				return null;
119 
120 			return Info.Voice;
121 		}
122 
INotifyEnteredCargo.OnEnteredCargo(Actor self, Actor cargo)123 		void INotifyEnteredCargo.OnEnteredCargo(Actor self, Actor cargo)
124 		{
125 			string specificCargoCondition;
126 			if (conditionManager != null)
127 			{
128 				if (anyCargoToken == ConditionManager.InvalidConditionToken && !string.IsNullOrEmpty(Info.CargoCondition))
129 					anyCargoToken = conditionManager.GrantCondition(self, Info.CargoCondition);
130 
131 				if (specificCargoToken == ConditionManager.InvalidConditionToken && Info.CargoConditions.TryGetValue(cargo.Info.Name, out specificCargoCondition))
132 					specificCargoToken = conditionManager.GrantCondition(self, specificCargoCondition);
133 			}
134 
135 			// Allow scripted / initial actors to move from the unload point back into the cell grid on unload
136 			// This is handled by the RideTransport activity for player-loaded cargo
137 			if (self.IsIdle)
138 			{
139 				// IMove is not used anywhere else in this trait, there is no benefit to caching it from Created.
140 				var move = self.TraitOrDefault<IMove>();
141 				if (move != null)
142 					self.QueueActivity(move.ReturnToCell(self));
143 			}
144 		}
145 
INotifyExitedCargo.OnExitedCargo(Actor self, Actor cargo)146 		void INotifyExitedCargo.OnExitedCargo(Actor self, Actor cargo)
147 		{
148 			if (anyCargoToken != ConditionManager.InvalidConditionToken)
149 				anyCargoToken = conditionManager.RevokeCondition(self, anyCargoToken);
150 
151 			if (specificCargoToken != ConditionManager.InvalidConditionToken)
152 				specificCargoToken = conditionManager.RevokeCondition(self, specificCargoToken);
153 		}
154 
IResolveOrder.ResolveOrder(Actor self, Order order)155 		void IResolveOrder.ResolveOrder(Actor self, Order order)
156 		{
157 			if (order.OrderString != "EnterTransport")
158 				return;
159 
160 			// Enter orders are only valid for own/allied actors,
161 			// which are guaranteed to never be frozen.
162 			if (order.Target.Type != TargetType.Actor)
163 				return;
164 
165 			var targetActor = order.Target.Actor;
166 			if (!CanEnter(targetActor))
167 				return;
168 
169 			if (!IsCorrectCargoType(targetActor))
170 				return;
171 
172 			self.QueueActivity(order.Queued, new RideTransport(self, order.Target));
173 			self.ShowTargetLines();
174 		}
175 
Reserve(Actor self, Cargo cargo)176 		public bool Reserve(Actor self, Cargo cargo)
177 		{
178 			if (cargo == ReservedCargo)
179 				return true;
180 
181 			Unreserve(self);
182 			if (!cargo.ReserveSpace(self))
183 				return false;
184 
185 			ReservedCargo = cargo;
186 			return true;
187 		}
188 
INotifyRemovedFromWorld.RemovedFromWorld(Actor self)189 		void INotifyRemovedFromWorld.RemovedFromWorld(Actor self) { Unreserve(self); }
190 
Unreserve(Actor self)191 		public void Unreserve(Actor self)
192 		{
193 			if (ReservedCargo == null)
194 				return;
195 
196 			ReservedCargo.UnreserveSpace(self);
197 			ReservedCargo = null;
198 		}
199 
INotifyKilled.Killed(Actor self, AttackInfo e)200 		void INotifyKilled.Killed(Actor self, AttackInfo e)
201 		{
202 			if (Transport == null)
203 				return;
204 
205 			// Something killed us, but it wasn't our transport blowing up. Remove us from the cargo.
206 			if (!Transport.IsDead)
207 				Transport.Trait<Cargo>().Unload(Transport, self);
208 		}
209 
IObservesVariables.GetVariableObservers()210 		IEnumerable<VariableObserver> IObservesVariables.GetVariableObservers()
211 		{
212 			if (Info.RequireForceMoveCondition != null)
213 				yield return new VariableObserver(RequireForceMoveConditionChanged, Info.RequireForceMoveCondition.Variables);
214 		}
215 
RequireForceMoveConditionChanged(Actor self, IReadOnlyDictionary<string, int> conditions)216 		void RequireForceMoveConditionChanged(Actor self, IReadOnlyDictionary<string, int> conditions)
217 		{
218 			requireForceMove = Info.RequireForceMoveCondition.Evaluate(conditions);
219 		}
220 	}
221 }
222