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