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 System.Linq; 15 using OpenRA.Activities; 16 using OpenRA.Mods.Common.Activities; 17 using OpenRA.Primitives; 18 using OpenRA.Traits; 19 20 namespace OpenRA.Mods.Common.Traits 21 { 22 [Desc("Add to a building to expose a move cursor that triggers Transforms and issues a move order to the transformed actor.")] 23 public class TransformsIntoMobileInfo : ConditionalTraitInfo, Requires<TransformsInfo> 24 { 25 [LocomotorReference] 26 [FieldLoader.Require] 27 [Desc("Locomotor used by the transformed actor. Must be defined on the World actor.")] 28 public readonly string Locomotor = null; 29 30 public readonly string Cursor = "move"; 31 public readonly string BlockedCursor = "move-blocked"; 32 33 [VoiceReference] 34 public readonly string Voice = "Action"; 35 36 [Desc("Require the force-move modifier to display the move cursor.")] 37 public readonly bool RequiresForceMove = false; 38 Create(ActorInitializer init)39 public override object Create(ActorInitializer init) { return new TransformsIntoMobile(init, this); } 40 41 public LocomotorInfo LocomotorInfo { get; private set; } 42 RulesetLoaded(Ruleset rules, ActorInfo ai)43 public override void RulesetLoaded(Ruleset rules, ActorInfo ai) 44 { 45 var locomotorInfos = rules.Actors["world"].TraitInfos<LocomotorInfo>(); 46 LocomotorInfo = locomotorInfos.FirstOrDefault(li => li.Name == Locomotor); 47 if (LocomotorInfo == null) 48 throw new YamlException("A locomotor named '{0}' doesn't exist.".F(Locomotor)); 49 else if (locomotorInfos.Count(li => li.Name == Locomotor) > 1) 50 throw new YamlException("There is more than one locomotor named '{0}'.".F(Locomotor)); 51 52 base.RulesetLoaded(rules, ai); 53 } 54 } 55 56 public class TransformsIntoMobile : ConditionalTrait<TransformsIntoMobileInfo>, IIssueOrder, IResolveOrder, IOrderVoice 57 { 58 readonly Actor self; 59 Transforms[] transforms; 60 Locomotor locomotor; 61 TransformsIntoMobile(ActorInitializer init, TransformsIntoMobileInfo info)62 public TransformsIntoMobile(ActorInitializer init, TransformsIntoMobileInfo info) 63 : base(info) 64 { 65 self = init.Self; 66 } 67 Created(Actor self)68 protected override void Created(Actor self) 69 { 70 transforms = self.TraitsImplementing<Transforms>().ToArray(); 71 locomotor = self.World.WorldActor.TraitsImplementing<Locomotor>() 72 .Single(l => l.Info.Name == Info.Locomotor); 73 base.Created(self); 74 } 75 76 IEnumerable<IOrderTargeter> IIssueOrder.Orders 77 { 78 get 79 { 80 if (!IsTraitDisabled) 81 yield return new MoveOrderTargeter(self, this); 82 } 83 } 84 85 // Note: Returns a valid order even if the unit can't move to the target IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)86 Order IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued) 87 { 88 if (order is MoveOrderTargeter) 89 return new Order("Move", self, target, queued); 90 91 return null; 92 } 93 IResolveOrder.ResolveOrder(Actor self, Order order)94 void IResolveOrder.ResolveOrder(Actor self, Order order) 95 { 96 if (IsTraitDisabled) 97 return; 98 99 if (order.OrderString == "Move") 100 { 101 var cell = self.World.Map.Clamp(this.self.World.Map.CellContaining(order.Target.CenterPosition)); 102 if (!Info.LocomotorInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell)) 103 return; 104 105 var currentTransform = self.CurrentActivity as Transform; 106 var transform = transforms.FirstOrDefault(t => !t.IsTraitDisabled && !t.IsTraitPaused); 107 if (transform == null && currentTransform == null) 108 return; 109 110 // Manually manage the inner activity queue 111 var activity = currentTransform ?? transform.GetTransformActivity(self); 112 if (!order.Queued && activity.NextActivity != null) 113 activity.NextActivity.Cancel(self); 114 115 activity.Queue(new IssueOrderAfterTransform("Move", order.Target, Color.Green)); 116 117 if (currentTransform == null) 118 self.QueueActivity(order.Queued, activity); 119 120 self.ShowTargetLines(); 121 } 122 else if (order.OrderString == "Stop") 123 { 124 // We don't want Stop orders from traits other than Mobile or Aircraft to cancel Resupply activity. 125 // Resupply is always either the main activity or a child of ReturnToBase. 126 // TODO: This should generally only cancel activities queued by this trait. 127 if (self.CurrentActivity == null || self.CurrentActivity is Resupply || self.CurrentActivity is ReturnToBase) 128 return; 129 130 self.CancelActivity(); 131 } 132 } 133 IOrderVoice.VoicePhraseForOrder(Actor self, Order order)134 string IOrderVoice.VoicePhraseForOrder(Actor self, Order order) 135 { 136 if (IsTraitDisabled) 137 return null; 138 139 switch (order.OrderString) 140 { 141 case "Move": 142 if (!Info.LocomotorInfo.MoveIntoShroud && order.Target.Type != TargetType.Invalid) 143 { 144 var cell = self.World.Map.CellContaining(order.Target.CenterPosition); 145 if (!self.Owner.Shroud.IsExplored(cell)) 146 return null; 147 } 148 149 return Info.Voice; 150 case "Stop": 151 return Info.Voice; 152 default: 153 return null; 154 } 155 } 156 157 class MoveOrderTargeter : IOrderTargeter 158 { 159 readonly TransformsIntoMobile mobile; 160 readonly bool rejectMove; TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers)161 public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) 162 { 163 // Always prioritise orders over selecting other peoples actors or own actors that are already selected 164 if (target.Type == TargetType.Actor && (target.Actor.Owner != self.Owner || self.World.Selection.Contains(target.Actor))) 165 return true; 166 167 return modifiers.HasModifier(TargetModifiers.ForceMove); 168 } 169 MoveOrderTargeter(Actor self, TransformsIntoMobile mobile)170 public MoveOrderTargeter(Actor self, TransformsIntoMobile mobile) 171 { 172 this.mobile = mobile; 173 rejectMove = !self.AcceptsOrder("Move"); 174 } 175 176 public string OrderID { get { return "Move"; } } 177 public int OrderPriority { get { return 4; } } 178 public bool IsQueued { get; protected set; } 179 CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)180 public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor) 181 { 182 if (rejectMove || target.Type != TargetType.Terrain || (mobile.Info.RequiresForceMove && !modifiers.HasModifier(TargetModifiers.ForceMove))) 183 return false; 184 185 var location = self.World.Map.CellContaining(target.CenterPosition); 186 IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); 187 188 var explored = self.Owner.Shroud.IsExplored(location); 189 cursor = self.World.Map.Contains(location) ? 190 (self.World.Map.GetTerrainInfo(location).CustomCursor ?? mobile.Info.Cursor) : mobile.Info.BlockedCursor; 191 192 if (!(self.CurrentActivity is Transform || mobile.transforms.Any(t => !t.IsTraitDisabled && !t.IsTraitPaused)) 193 || (!explored && !mobile.locomotor.Info.MoveIntoShroud) 194 || (explored && !CanEnterCell(self, location))) 195 cursor = mobile.Info.BlockedCursor; 196 197 return true; 198 } 199 CanEnterCell(Actor self, CPos cell)200 bool CanEnterCell(Actor self, CPos cell) 201 { 202 if (mobile.locomotor.MovementCostForCell(cell) == short.MaxValue) 203 return false; 204 205 return mobile.locomotor.CanMoveFreelyInto(self, cell, BlockedByActor.All, null); 206 } 207 } 208 } 209 } 210