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.Mods.Common.Effects; 15 using OpenRA.Traits; 16 17 namespace OpenRA.Mods.Common.Traits 18 { 19 [Desc("Used to waypoint units after production or repair is finished.")] 20 public class RallyPointInfo : ITraitInfo 21 { 22 public readonly string Image = "rallypoint"; 23 24 [Desc("Width (in pixels) of the rallypoint line.")] 25 public readonly int LineWidth = 1; 26 27 [SequenceReference("Image")] 28 public readonly string FlagSequence = "flag"; 29 30 [SequenceReference("Image")] 31 public readonly string CirclesSequence = "circles"; 32 33 public readonly string Cursor = "ability"; 34 35 [PaletteReference("IsPlayerPalette")] 36 [Desc("Custom indicator palette name")] 37 public readonly string Palette = "player"; 38 39 [Desc("Custom palette is a player palette BaseName")] 40 public readonly bool IsPlayerPalette = true; 41 42 [Desc("A list of 0 or more offsets defining the initial rally point path.")] 43 public readonly CVec[] Path = { }; 44 45 [NotificationReference("Speech")] 46 [Desc("The speech notification to play when setting a new rallypoint.")] 47 public readonly string Notification = null; 48 Create(ActorInitializer init)49 public object Create(ActorInitializer init) { return new RallyPoint(init.Self, this); } 50 } 51 52 public class RallyPoint : IIssueOrder, IResolveOrder, INotifyOwnerChanged, INotifyCreated 53 { 54 const string OrderID = "SetRallyPoint"; 55 56 public List<CPos> Path; 57 58 public RallyPointInfo Info; 59 public string PaletteName { get; private set; } 60 61 const uint ForceSet = 1; 62 ResetPath(Actor self)63 public void ResetPath(Actor self) 64 { 65 Path = Info.Path.Select(p => self.Location + p).ToList(); 66 } 67 RallyPoint(Actor self, RallyPointInfo info)68 public RallyPoint(Actor self, RallyPointInfo info) 69 { 70 Info = info; 71 ResetPath(self); 72 PaletteName = info.IsPlayerPalette ? info.Palette + self.Owner.InternalName : info.Palette; 73 } 74 INotifyCreated.Created(Actor self)75 void INotifyCreated.Created(Actor self) 76 { 77 // Display only the first level of priority 78 var priorityExits = self.Info.TraitInfos<ExitInfo>() 79 .GroupBy(e => e.Priority) 80 .FirstOrDefault(); 81 82 var exits = priorityExits != null ? priorityExits.ToArray() : new ExitInfo[0]; 83 self.World.Add(new RallyPointIndicator(self, this, exits)); 84 } 85 OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)86 public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) 87 { 88 if (Info.IsPlayerPalette) 89 PaletteName = Info.Palette + newOwner.InternalName; 90 91 ResetPath(self); 92 } 93 94 public IEnumerable<IOrderTargeter> Orders 95 { 96 get { yield return new RallyPointOrderTargeter(Info.Cursor); } 97 } 98 IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)99 public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued) 100 { 101 if (order.OrderID == OrderID) 102 { 103 Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", Info.Notification, self.Owner.Faction.InternalName); 104 105 return new Order(order.OrderID, self, target, queued) 106 { 107 SuppressVisualFeedback = true, 108 ExtraData = ((RallyPointOrderTargeter)order).ForceSet ? ForceSet : 0 109 }; 110 } 111 112 return null; 113 } 114 ResolveOrder(Actor self, Order order)115 public void ResolveOrder(Actor self, Order order) 116 { 117 if (order.OrderString != OrderID) 118 return; 119 120 if (!order.Queued) 121 Path.Clear(); 122 123 Path.Add(self.World.Map.CellContaining(order.Target.CenterPosition)); 124 } 125 IsForceSet(Order order)126 public static bool IsForceSet(Order order) 127 { 128 return order.OrderString == OrderID && order.ExtraData == ForceSet; 129 } 130 131 class RallyPointOrderTargeter : IOrderTargeter 132 { 133 readonly string cursor; 134 RallyPointOrderTargeter(string cursor)135 public RallyPointOrderTargeter(string cursor) 136 { 137 this.cursor = cursor; 138 } 139 140 public string OrderID { get { return "SetRallyPoint"; } } 141 public int OrderPriority { get { return 0; } } TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers)142 public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; } 143 public bool ForceSet { get; private set; } 144 public bool IsQueued { get; protected set; } 145 CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)146 public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor) 147 { 148 if (target.Type != TargetType.Terrain) 149 return false; 150 151 IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); 152 153 var location = self.World.Map.CellContaining(target.CenterPosition); 154 if (self.World.Map.Contains(location)) 155 { 156 cursor = this.cursor; 157 158 // Notify force-set 'RallyPoint' order watchers with Ctrl and only if this is the only building of its type selected 159 if (modifiers.HasModifier(TargetModifiers.ForceAttack)) 160 { 161 var selfName = self.Info.Name; 162 if (!self.World.Selection.Actors.Any(a => a.Info.Name == selfName && a.ActorID != self.ActorID)) 163 ForceSet = true; 164 } 165 166 return true; 167 } 168 169 return false; 170 } 171 } 172 } 173 } 174