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.Primitives;
16 using OpenRA.Traits;
17 
18 namespace OpenRA.Mods.Common.Traits
19 {
20 	[Desc("Manages build limits and pre-requisites.", " Attach this to the player actor.")]
21 	public class TechTreeInfo : ITraitInfo
22 	{
Create(ActorInitializer init)23 		public object Create(ActorInitializer init) { return new TechTree(init); }
24 	}
25 
26 	public class TechTree
27 	{
28 		readonly List<Watcher> watchers = new List<Watcher>();
29 		readonly Player player;
30 
TechTree(ActorInitializer init)31 		public TechTree(ActorInitializer init)
32 		{
33 			player = init.Self.Owner;
34 			init.World.ActorAdded += ActorChanged;
35 			init.World.ActorRemoved += ActorChanged;
36 		}
37 
ActorChanged(Actor a)38 		public void ActorChanged(Actor a)
39 		{
40 			var bi = a.Info.TraitInfoOrDefault<BuildableInfo>();
41 			if (a.Owner == player && (a.Info.HasTraitInfo<ITechTreePrerequisiteInfo>() || (bi != null && bi.BuildLimit > 0)))
42 				Update();
43 		}
44 
Update()45 		public void Update()
46 		{
47 			var ownedPrerequisites = GatherOwnedPrerequisites(player);
48 			foreach (var w in watchers)
49 				w.Update(ownedPrerequisites);
50 		}
51 
Add(string key, string[] prerequisites, int limit, ITechTreeElement tte)52 		public void Add(string key, string[] prerequisites, int limit, ITechTreeElement tte)
53 		{
54 			watchers.Add(new Watcher(key, prerequisites, limit, tte));
55 		}
56 
Remove(string key)57 		public void Remove(string key)
58 		{
59 			watchers.RemoveAll(x => x.Key == key);
60 		}
61 
Remove(ITechTreeElement tte)62 		public void Remove(ITechTreeElement tte)
63 		{
64 			watchers.RemoveAll(x => x.RegisteredBy == tte);
65 		}
66 
HasPrerequisites(IEnumerable<string> prerequisites)67 		public bool HasPrerequisites(IEnumerable<string> prerequisites)
68 		{
69 			var ownedPrereqs = GatherOwnedPrerequisites(player);
70 			return prerequisites.All(p => !(p.Replace("~", "").StartsWith("!", StringComparison.Ordinal)
71 					^ !ownedPrereqs.ContainsKey(p.Replace("!", "").Replace("~", ""))));
72 		}
73 
GatherOwnedPrerequisites(Player player)74 		static Cache<string, List<Actor>> GatherOwnedPrerequisites(Player player)
75 		{
76 			var ret = new Cache<string, List<Actor>>(x => new List<Actor>());
77 			if (player == null)
78 				return ret;
79 
80 			// Add all actors that provide prerequisites
81 			var prerequisites = player.World.ActorsWithTrait<ITechTreePrerequisite>()
82 				.Where(a => a.Actor.Owner == player && a.Actor.IsInWorld && !a.Actor.IsDead);
83 
84 			foreach (var b in prerequisites)
85 			{
86 				foreach (var p in b.Trait.ProvidesPrerequisites)
87 				{
88 					// Ignore bogus prerequisites
89 					if (p == null)
90 						continue;
91 
92 					ret[p].Add(b.Actor);
93 				}
94 			}
95 
96 			// Add buildables that have a build limit set and are not already in the list
97 			player.World.ActorsWithTrait<Buildable>()
98 				  .Where(a =>
99 					  a.Actor.Owner == player &&
100 					  a.Actor.IsInWorld &&
101 					  !a.Actor.IsDead &&
102 					  !ret.ContainsKey(a.Actor.Info.Name) &&
103 					  a.Actor.Info.TraitInfo<BuildableInfo>().BuildLimit > 0)
104 				  .Do(b => ret[b.Actor.Info.Name].Add(b.Actor));
105 
106 			return ret;
107 		}
108 
109 		class Watcher
110 		{
111 			public readonly string Key;
112 			public ITechTreeElement RegisteredBy { get { return watcher; } }
113 
114 			// Strings may be either actor type, or "alternate name" key
115 			readonly string[] prerequisites;
116 			readonly ITechTreeElement watcher;
117 			bool hasPrerequisites;
118 			int limit;
119 			bool hidden;
120 			bool initialized = false;
121 
Watcher(string key, string[] prerequisites, int limit, ITechTreeElement watcher)122 			public Watcher(string key, string[] prerequisites, int limit, ITechTreeElement watcher)
123 			{
124 				Key = key;
125 				this.prerequisites = prerequisites;
126 				this.watcher = watcher;
127 				hasPrerequisites = false;
128 				this.limit = limit;
129 				hidden = false;
130 			}
131 
HasPrerequisites(Cache<string, List<Actor>> ownedPrerequisites)132 			bool HasPrerequisites(Cache<string, List<Actor>> ownedPrerequisites)
133 			{
134 				// PERF: Avoid LINQ.
135 				foreach (var prereq in prerequisites)
136 				{
137 					var withoutTilde = prereq.Replace("~", "");
138 					if (withoutTilde.StartsWith("!", StringComparison.Ordinal) ^ !ownedPrerequisites.ContainsKey(withoutTilde.Replace("!", "")))
139 						return false;
140 				}
141 
142 				return true;
143 			}
144 
IsHidden(Cache<string, List<Actor>> ownedPrerequisites)145 			bool IsHidden(Cache<string, List<Actor>> ownedPrerequisites)
146 			{
147 				// PERF: Avoid LINQ.
148 				foreach (var prereq in prerequisites)
149 				{
150 					if (!prereq.StartsWith("~", StringComparison.Ordinal))
151 						continue;
152 					var withoutTilde = prereq.Replace("~", "");
153 					if (withoutTilde.StartsWith("!", StringComparison.Ordinal) ^ !ownedPrerequisites.ContainsKey(withoutTilde.Replace("!", "")))
154 						return true;
155 				}
156 
157 				return false;
158 			}
159 
Update(Cache<string, List<Actor>> ownedPrerequisites)160 			public void Update(Cache<string, List<Actor>> ownedPrerequisites)
161 			{
162 				var hasReachedLimit = limit > 0 && ownedPrerequisites.ContainsKey(Key) && ownedPrerequisites[Key].Count >= limit;
163 
164 				// The '!' annotation inverts prerequisites: "I'm buildable if this prerequisite *isn't* met"
165 				var nowHasPrerequisites = HasPrerequisites(ownedPrerequisites) && !hasReachedLimit;
166 				var nowHidden = IsHidden(ownedPrerequisites);
167 
168 				if (initialized == false)
169 				{
170 					initialized = true;
171 					hasPrerequisites = !nowHasPrerequisites;
172 					hidden = !nowHidden;
173 				}
174 
175 				// Hide the item from the UI if a prereq annotated with '~' is not met.
176 				if (nowHidden && !hidden)
177 					watcher.PrerequisitesItemHidden(Key);
178 
179 				if (!nowHidden && hidden)
180 					watcher.PrerequisitesItemVisible(Key);
181 
182 				if (nowHasPrerequisites && !hasPrerequisites)
183 					watcher.PrerequisitesAvailable(Key);
184 
185 				if (!nowHasPrerequisites && hasPrerequisites)
186 					watcher.PrerequisitesUnavailable(Key);
187 
188 				hidden = nowHidden;
189 				hasPrerequisites = nowHasPrerequisites;
190 			}
191 		}
192 	}
193 }
194