diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index b88c288b52..946ad1c90d 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -31,6 +31,7 @@ namespace OpenRA public bool? AllyBuildRadius; public bool? FragileAlliances; public int? StartingCash; + public string TechLevel; public bool ConfigurableStartingUnits = true; public string[] Difficulties = { }; diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index 15908ac281..73308599b8 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -186,6 +186,7 @@ namespace OpenRA.Network public bool Fog = true; public bool AllyBuildRadius = true; public int StartingCash = 5000; + public String TechLevel = "none"; public string StartingUnitsClass = "none"; public bool AllowVersionMismatch; public string GameUid; diff --git a/OpenRA.Mods.RA/Buildable.cs b/OpenRA.Mods.RA/Buildable.cs index f02476f896..b70b64038f 100755 --- a/OpenRA.Mods.RA/Buildable.cs +++ b/OpenRA.Mods.RA/Buildable.cs @@ -18,7 +18,6 @@ namespace OpenRA.Mods.RA public readonly string[] Owner = { }; public readonly string Queue; - public readonly bool Hidden = false; public readonly int BuildLimit = 0; // TODO: UI fluff; doesn't belong here diff --git a/OpenRA.Mods.RA/Lint/LintBuildablePrerequisites.cs b/OpenRA.Mods.RA/Lint/LintBuildablePrerequisites.cs index 7f4696186a..782da84d0c 100644 --- a/OpenRA.Mods.RA/Lint/LintBuildablePrerequisites.cs +++ b/OpenRA.Mods.RA/Lint/LintBuildablePrerequisites.cs @@ -11,6 +11,7 @@ using System; using System.Linq; using OpenRA.Traits; +using OpenRA.Mods.RA.Buildings; namespace OpenRA.Mods.RA { @@ -18,10 +19,22 @@ namespace OpenRA.Mods.RA { public void Run(Action emitError, Action emitWarning, Map map) { - var providedPrereqs = map.Rules.Actors.Keys.Concat( - map.Rules.Actors.SelectMany(a => a.Value.Traits - .WithInterface() - .Select(p => p.Prerequisite))).ToArray(); + // Buildings provide their actor names as a prerequisite + var buildingPrereqs = map.Rules.Actors.Where(a => a.Value.Traits.Contains()) + .Select(a => a.Key); + + // ProvidesCustomPrerequisite allows arbitrary prereq definitions + var customPrereqs = map.Rules.Actors.SelectMany(a => a.Value.Traits + .WithInterface()) + .Select(p => p.Prerequisite); + + // ProvidesTechPrerequisite allows arbitrary prereq definitions + // (but only one group at a time during gameplay) + var techPrereqs = map.Rules.Actors.SelectMany(a => a.Value.Traits + .WithInterface()) + .SelectMany(p => p.Prerequisites); + + var providedPrereqs = buildingPrereqs.Concat(customPrereqs).Concat(techPrereqs); // TODO: this check is case insensitive while the real check in-game is not foreach (var i in map.Rules.Actors) @@ -29,9 +42,9 @@ namespace OpenRA.Mods.RA var bi = i.Value.Traits.GetOrDefault(); if (bi != null) foreach (var prereq in bi.Prerequisites) - if (!providedPrereqs.Contains(prereq.Replace("!", ""))) + if (!providedPrereqs.Contains(prereq.Replace("!", "").Replace("~", ""))) emitError("Buildable actor {0} has prereq {1} not provided by anything.".F(i.Key, prereq)); } } } -} +} \ No newline at end of file diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 683a94eec1..6fd3d80ffb 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -300,6 +300,7 @@ + diff --git a/OpenRA.Mods.RA/Player/ProductionQueue.cs b/OpenRA.Mods.RA/Player/ProductionQueue.cs index cce033dead..102a2d4e61 100644 --- a/OpenRA.Mods.RA/Player/ProductionQueue.cs +++ b/OpenRA.Mods.RA/Player/ProductionQueue.cs @@ -123,11 +123,12 @@ namespace OpenRA.Mods.RA foreach (var a in AllBuildables(Info.Type)) { var bi = a.Traits.Get(); - // Can our race build this by satisfying normal prereqs? + // Can our race build this by satisfying normal prerequisites? var buildable = bi.Owner.Contains(Race.Race); - tech.Add(a, new ProductionState { Visible = buildable && !bi.Hidden }); + // Checks if Prerequisites want to hide the Actor from buildQueue if they are false + tech.Add(a, new ProductionState { Visible = buildable }); if (buildable) - ttc.Add(a.Name, bi, this); + ttc.Add(a.Name, bi.Prerequisites, bi.BuildLimit, this); } return tech; @@ -161,6 +162,18 @@ namespace OpenRA.Mods.RA ps.Buildable = false; } + public void PrerequisitesItemHidden(string key) + { + var ps = Produceable[self.World.Map.Rules.Actors[key]]; + ps.Visible = false; + } + + public void PrerequisitesItemVisable(string key) + { + var ps = Produceable[self.World.Map.Rules.Actors[key]]; + ps.Visible = true; + } + public ProductionItem CurrentItem() { return Queue.ElementAtOrDefault(0); diff --git a/OpenRA.Mods.RA/Player/ProvidesTechPrerequisite.cs b/OpenRA.Mods.RA/Player/ProvidesTechPrerequisite.cs new file mode 100644 index 0000000000..deb9251845 --- /dev/null +++ b/OpenRA.Mods.RA/Player/ProvidesTechPrerequisite.cs @@ -0,0 +1,51 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRA.Primitives; +using OpenRA.Traits; +using OpenRA.FileSystem; + +namespace OpenRA.Mods.RA +{ + public class ProvidesTechPrerequisiteInfo : ITraitInfo + { + public readonly string Name; + public readonly string[] Prerequisites = {}; + + public object Create(ActorInitializer init) { return new ProvidesTechPrerequisite(this, init); } + } + + public class ProvidesTechPrerequisite : ITechTreePrerequisite + { + ProvidesTechPrerequisiteInfo info; + bool enabled; + static readonly string[] NoPrerequisites = new string[0]; + + public string Name { get { return info.Name; } } + + public IEnumerable ProvidesPrerequisites + { + get + { + return enabled ? info.Prerequisites : NoPrerequisites; + } + } + + public ProvidesTechPrerequisite(ProvidesTechPrerequisiteInfo info, ActorInitializer init) + { + this.info = info; + this.enabled = info.Name == init.world.LobbyInfo.GlobalSettings.TechLevel; + } + } +} diff --git a/OpenRA.Mods.RA/Player/TechTree.cs b/OpenRA.Mods.RA/Player/TechTree.cs index a90ee60602..8bbd3f04be 100755 --- a/OpenRA.Mods.RA/Player/TechTree.cs +++ b/OpenRA.Mods.RA/Player/TechTree.cs @@ -41,14 +41,14 @@ namespace OpenRA.Mods.RA public void Update() { - var buildables = GatherBuildables(player); + var ownedPrerequisites = GatherOwnedPrerequisites(player); foreach (var w in watchers) - w.Update(buildables); + w.Update(ownedPrerequisites); } - public void Add(string key, BuildableInfo info, ITechTreeElement tte) + public void Add(string key, string[] prerequisites, int limit, ITechTreeElement tte) { - watchers.Add(new Watcher(key, info, tte)); + watchers.Add(new Watcher(key, prerequisites, limit, tte)); } public void Remove(string key) @@ -56,17 +56,17 @@ namespace OpenRA.Mods.RA watchers.RemoveAll(x => x.Key == key); } - static Cache> GatherBuildables(Player player) + static Cache> GatherOwnedPrerequisites(Player player) { var ret = new Cache>(x => new List()); if (player == null) return ret; - // Add buildables that provide prerequisites - var prereqs = player.World.ActorsWithTrait() + // Add all actors that provide prerequisites + var prerequisites = player.World.ActorsWithTrait() .Where(a => a.Actor.Owner == player && !a.Actor.IsDead() && a.Actor.IsInWorld); - foreach (var b in prereqs) + foreach (var b in prerequisites) { foreach (var p in b.Trait.ProvidesPrerequisites) { @@ -91,30 +91,54 @@ namespace OpenRA.Mods.RA { public readonly string Key; - // strings may be either actor type, or "alternate name" key + // Strings may be either actor type, or "alternate name" key readonly string[] prerequisites; readonly ITechTreeElement watcher; bool hasPrerequisites; - int buildLimit; + int limit; + bool hidden; + bool initialized = false; - public Watcher(string key, BuildableInfo info, ITechTreeElement watcher) + public Watcher(string key, string[] prerequisites, int limit, ITechTreeElement watcher) { this.Key = key; - this.prerequisites = info.Prerequisites; + this.prerequisites = prerequisites; this.watcher = watcher; this.hasPrerequisites = false; - this.buildLimit = info.BuildLimit; + this.limit = limit; + this.hidden = false; } - bool HasPrerequisites(Cache> buildables) + bool HasPrerequisites(Cache> ownedPrerequisites) { - return prerequisites.All(p => !(p.StartsWith("!") ^ !buildables.Keys.Contains(p.Replace("!", "")))); + return prerequisites.All(p => !(p.Replace("~", "").StartsWith("!") ^ !ownedPrerequisites.Keys.Contains(p.Replace("!", "").Replace("~", "")))); } - public void Update(Cache> buildables) + bool IsHidden(Cache> ownedPrerequisites) { - var hasReachedBuildLimit = buildLimit > 0 && buildables.Keys.Contains(Key) && buildables[Key].Count >= buildLimit; - var nowHasPrerequisites = HasPrerequisites(buildables) && !hasReachedBuildLimit; + return prerequisites.Any(prereq => prereq.StartsWith("~") && (prereq.Replace("~", "").StartsWith("!") ^ !ownedPrerequisites.Keys.Contains(prereq.Replace("~", "").Replace("!", "")))); + } + + public void Update(Cache> ownedPrerequisites) + { + var hasReachedLimit = limit > 0 && ownedPrerequisites.Keys.Contains(Key) && ownedPrerequisites[Key].Count >= limit; + // The '!' annotation inverts prerequisites: "I'm buildable if this prerequisite *isn't* met" + var nowHasPrerequisites = HasPrerequisites(ownedPrerequisites) && !hasReachedLimit; + var nowHidden = IsHidden(ownedPrerequisites); + + if (initialized == false) + { + initialized = true; + hasPrerequisites = !nowHasPrerequisites; + hidden = !nowHidden; + } + + // Hide the item from the UI if a prereq annotated with '~' is not met. + if (nowHidden && !hidden) + watcher.PrerequisitesItemHidden(Key); + + if (!nowHidden && hidden) + watcher.PrerequisitesItemVisable(Key); if (nowHasPrerequisites && !hasPrerequisites) watcher.PrerequisitesAvailable(Key); @@ -122,6 +146,7 @@ namespace OpenRA.Mods.RA if (!nowHasPrerequisites && hasPrerequisites) watcher.PrerequisitesUnavailable(Key); + hidden = nowHidden; hasPrerequisites = nowHasPrerequisites; } } diff --git a/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs index 0451222131..49fa27ae73 100644 --- a/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs @@ -551,6 +551,25 @@ namespace OpenRA.Mods.RA.Server server.LobbyInfo.GlobalSettings.StartingCash = Exts.ParseIntegerInvariant(s); server.SyncLobbyGlobalSettings(); + return true; + }}, + { "techlevel", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can set that option"); + return true; + } + + if (server.Map.Options.TechLevel != null) + { + server.SendOrderTo(conn, "Message", "Map has disabled Tech configuration"); + return true; + } + + server.LobbyInfo.GlobalSettings.TechLevel = s; + server.SyncLobbyInfo(); return true; }}, { "kick", diff --git a/OpenRA.Mods.RA/TraitsInterfaces.cs b/OpenRA.Mods.RA/TraitsInterfaces.cs index 2715cad444..63c088db17 100755 --- a/OpenRA.Mods.RA/TraitsInterfaces.cs +++ b/OpenRA.Mods.RA/TraitsInterfaces.cs @@ -32,6 +32,8 @@ namespace OpenRA.Mods.RA { void PrerequisitesAvailable(string key); void PrerequisitesUnavailable(string key); + void PrerequisitesItemHidden(string key); + void PrerequisitesItemVisable(string key); } public interface ITechTreePrerequisite diff --git a/OpenRA.Mods.RA/Widgets/BuildPaletteWidget.cs b/OpenRA.Mods.RA/Widgets/BuildPaletteWidget.cs index 9cf2e66309..00ac5ded20 100644 --- a/OpenRA.Mods.RA/Widgets/BuildPaletteWidget.cs +++ b/OpenRA.Mods.RA/Widgets/BuildPaletteWidget.cs @@ -500,7 +500,7 @@ namespace OpenRA.Mods.RA.Widgets var prereqs = buildable.Prerequisites.Select(s => Description(world.Map.Rules, s)); if (prereqs.Any()) { - Game.Renderer.Fonts["Regular"].DrawText(RequiresText.F(prereqs.JoinWith(", ")), p.ToInt2(), Color.White); + Game.Renderer.Fonts["Regular"].DrawText(RequiresText.F(prereqs.Where(s => !s.StartsWith("~")).JoinWith(", ")), p.ToInt2(), Color.White); p += new int2(0, 8); } diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index aa17d24a48..9a144ce9fa 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -429,6 +429,34 @@ namespace OpenRA.Mods.RA.Widgets.Logic }; } + var techLevel = optionsBin.GetOrNull("TECHLEVEL_DROPDOWNBUTTON"); + if (techLevel != null) + { + var techTraits = modRules.Actors["player"].Traits.WithInterface().ToArray(); + techLevel.IsVisible = () => techTraits.Length > 0; + optionsBin.GetOrNull("TECHLEVEL_DESC").IsVisible = () => techTraits.Length > 0; + techLevel.IsDisabled = () => Map.Status != MapStatus.Available || Map.Map.Options.TechLevel != null || configurationDisabled() || techTraits.Length <= 1; + techLevel.GetText = () => Map.Status != MapStatus.Available || Map.Map.Options.TechLevel != null ? "Not Available" : "{0}".F(orderManager.LobbyInfo.GlobalSettings.TechLevel); + techLevel.OnMouseDown = _ => + { + var options = techTraits.Select(c => new DropDownOption + { + Title = "{0}".F(c.Name), + IsSelected = () => orderManager.LobbyInfo.GlobalSettings.TechLevel == c.Name, + OnClick = () => orderManager.IssueOrder(Order.Command("techlevel {0}".F(c.Name))) + }); + + Func setupItem = (option, template) => + { + var item = ScrollItemWidget.Setup(template, option.IsSelected, option.OnClick); + item.Get("LABEL").GetText = () => option.Title; + return item; + }; + + techLevel.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count() * 30, options, setupItem); + }; + } + var enableShroud = optionsBin.GetOrNull("SHROUD_CHECKBOX"); if (enableShroud != null) {