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