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