diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index d4fbdf7b56..780673dd7f 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -83,6 +83,12 @@ namespace OpenRA.Traits public interface INotifyHarvest { void Harvested(Actor self, ResourceType resource); } public interface INotifyInfiltrated { void Infiltrated(Actor self, Actor infiltrator); } + public interface IUpgradable + { + bool AcceptsUpgrade(string type); + void UpgradeAvailable(Actor self, string type, bool available); + } + public interface ISeedableResource { void Seed(Actor self); } public interface IDemolishableInfo { bool IsValidTarget(ActorInfo actorInfo, Actor saboteur); } diff --git a/OpenRA.Mods.RA/Cloak.cs b/OpenRA.Mods.RA/Cloak.cs index b8ccb4d671..aabb804941 100644 --- a/OpenRA.Mods.RA/Cloak.cs +++ b/OpenRA.Mods.RA/Cloak.cs @@ -29,7 +29,9 @@ namespace OpenRA.Mods.RA public readonly bool UncloakOnAttack = true; public readonly bool UncloakOnMove = false; public readonly bool UncloakOnUnload = false; - public readonly bool RequiresCrate = false; + + [Desc("Enable only if this upgrade is enabled.")] + public readonly string RequiresUpgrade = null; public readonly string CloakSound = null; public readonly string UncloakSound = null; @@ -40,11 +42,11 @@ namespace OpenRA.Mods.RA public object Create(ActorInitializer init) { return new Cloak(init.self, this); } } - public class Cloak : IRenderModifier, INotifyDamageStateChanged, INotifyAttack, ITick, IVisibilityModifier, IRadarColorModifier, ISync + public class Cloak : IUpgradable, IRenderModifier, INotifyDamageStateChanged, INotifyAttack, ITick, IVisibilityModifier, IRadarColorModifier, ISync { [Sync] int remainingTime; [Sync] bool damageDisabled; - [Sync] bool crateDisabled; + [Sync] bool disabled; Actor self; public readonly CloakInfo Info; @@ -56,7 +58,20 @@ namespace OpenRA.Mods.RA Info = info; remainingTime = info.InitialDelay; - crateDisabled = info.RequiresCrate; + + // Disable if an upgrade is required + disabled = info.RequiresUpgrade != null; + } + + public bool AcceptsUpgrade(string type) + { + return type == Info.RequiresUpgrade; + } + + public void UpgradeAvailable(Actor self, string type, bool available) + { + if (type == Info.RequiresUpgrade) + disabled = !available; } public void Uncloak() { Uncloak(Info.CloakDelay); } @@ -96,7 +111,7 @@ namespace OpenRA.Mods.RA public void Tick(Actor self) { - if (remainingTime > 0 && !crateDisabled && !damageDisabled && --remainingTime <= 0) + if (remainingTime > 0 && !disabled && !damageDisabled && --remainingTime <= 0) Sound.Play(Info.CloakSound, self.CenterPosition); if (self.IsDisabled()) @@ -131,12 +146,5 @@ namespace OpenRA.Mods.RA c = Color.FromArgb(128, c); return c; } - - public bool AcceptsCloakCrate { get { return Info.RequiresCrate && crateDisabled; } } - - public void ReceivedCloakCrate(Actor self) - { - crateDisabled = false; - } } } diff --git a/OpenRA.Mods.RA/Crates/CloakCrateAction.cs b/OpenRA.Mods.RA/Crates/CloakCrateAction.cs deleted file mode 100644 index 160a4b9c32..0000000000 --- a/OpenRA.Mods.RA/Crates/CloakCrateAction.cs +++ /dev/null @@ -1,71 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2011 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.Linq; - -namespace OpenRA.Mods.RA.Crates -{ - [Desc("Grants the collector the ability to cloak.")] - public class CloakCrateActionInfo : CrateActionInfo - { - [Desc("The range to search for extra collectors in.", "Extra collectors will also be granted the crate action.")] - public readonly WRange Range = new WRange(3); - - [Desc("The maximum number of extra collectors to grant the crate action to.")] - public readonly int MaxExtraCollectors = 4; - - public override object Create(ActorInitializer init) { return new CloakCrateAction(init.self, this); } - } - - public class CloakCrateAction : CrateAction - { - CloakCrateActionInfo Info; - - public CloakCrateAction(Actor self, CloakCrateActionInfo info) - : base(self, info) - { - Info = info; - } - - public override int GetSelectionShares(Actor collector) - { - var cloak = collector.TraitOrDefault(); - if (cloak == null || !cloak.AcceptsCloakCrate) - return 0; - - return base.GetSelectionShares(collector); - } - - public override void Activate(Actor collector) - { - collector.Trait().ReceivedCloakCrate(collector); - - var inRange = self.World.FindActorsInCircle(self.CenterPosition, Info.Range); - inRange = inRange.Where(a => - (a.Owner == collector.Owner) && - (a != collector) && - (a.TraitOrDefault() != null) && - (a.TraitOrDefault().AcceptsCloakCrate)); - if (inRange.Any()) - { - if (Info.MaxExtraCollectors > -1) - inRange = inRange.Take(Info.MaxExtraCollectors); - - if (inRange.Any()) - foreach (Actor actor in inRange) - { - actor.Trait().ReceivedCloakCrate(actor); - } - } - - base.Activate(collector); - } - } -} diff --git a/OpenRA.Mods.RA/Crates/UnitUpgradeCrateAction.cs b/OpenRA.Mods.RA/Crates/UnitUpgradeCrateAction.cs index bab5b01445..a8c2c8f14e 100644 --- a/OpenRA.Mods.RA/Crates/UnitUpgradeCrateAction.cs +++ b/OpenRA.Mods.RA/Crates/UnitUpgradeCrateAction.cs @@ -9,6 +9,7 @@ #endregion using System.Linq; +using OpenRA.Traits; namespace OpenRA.Mods.RA.Crates { @@ -16,15 +17,12 @@ namespace OpenRA.Mods.RA.Crates public class UnitUpgradeCrateActionInfo : CrateActionInfo { [Desc("The upgrade to grant.")] - public readonly UnitUpgrade? Upgrade = null; + public readonly string[] Upgrades = {}; - [Desc("The number of levels of the upgrade to grant.")] - public readonly int Levels = 1; - - [Desc("The range to search for extra collectors in.","Extra collectors will also be granted the crate action.")] + [Desc("The range to search for extra collectors in.", "Extra collectors will also be granted the crate action.")] public readonly WRange Range = new WRange(3); - [Desc("The maximum number of extra collectors to grant the crate action to.","-1 = no limit")] + [Desc("The maximum number of extra collectors to grant the crate action to.", "-1 = no limit")] public readonly int MaxExtraCollectors = 4; public override object Create(ActorInitializer init) { return new UnitUpgradeCrateAction(init.self, this); } @@ -32,7 +30,7 @@ namespace OpenRA.Mods.RA.Crates public class UnitUpgradeCrateAction : CrateAction { - UnitUpgradeCrateActionInfo Info; + readonly UnitUpgradeCrateActionInfo Info; public UnitUpgradeCrateAction(Actor self, UnitUpgradeCrateActionInfo info) : base(self, info) @@ -40,42 +38,45 @@ namespace OpenRA.Mods.RA.Crates Info = info; } + bool AcceptsUpgrade(Actor a) + { + return a.TraitsImplementing() + .Any(up => Info.Upgrades.Any(u => up.AcceptsUpgrade(u))); + } + + void GrantActorUpgrades(Actor a) + { + foreach (var up in a.TraitsImplementing()) + foreach (var u in Info.Upgrades) + if (up.AcceptsUpgrade(u)) + up.UpgradeAvailable(a, u, true); + } + public override int GetSelectionShares(Actor collector) { - var up = collector.TraitOrDefault(); - return up != null && up.CanGainUnitUpgrade(Info.Upgrade) ? info.SelectionShares : 0; + return AcceptsUpgrade(collector) ? info.SelectionShares : 0; } public override void Activate(Actor collector) { - collector.World.AddFrameEndTask(w => - { - var gainsStatBonuses = collector.TraitOrDefault(); - if (gainsStatBonuses != null) - gainsStatBonuses.GiveUnitUpgrade(Info.Upgrade, Info.Levels); - }); + collector.World.AddFrameEndTask(w => GrantActorUpgrades(collector)); - var inRange = self.World.FindActorsInCircle(self.CenterPosition, Info.Range); - inRange = inRange.Where(a => - (a.Owner == collector.Owner) && - (a != collector) && - (a.TraitOrDefault() != null) && - (a.TraitOrDefault().CanGainUnitUpgrade(Info.Upgrade))); - if (inRange.Any()) + var actorsInRange = self.World.FindActorsInCircle(self.CenterPosition, Info.Range) + .Where(a => a != self && a.Owner == collector.Owner && AcceptsUpgrade(a)); + + if (actorsInRange.Any()) { if (Info.MaxExtraCollectors > -1) - inRange = inRange.Take(Info.MaxExtraCollectors); + actorsInRange = actorsInRange.Take(Info.MaxExtraCollectors); - if (inRange.Any()) - foreach (Actor actor in inRange) + collector.World.AddFrameEndTask(w => + { + foreach (var a in actorsInRange) { - actor.World.AddFrameEndTask(w => - { - var gainsStatBonuses = actor.TraitOrDefault(); - if (gainsStatBonuses != null) - gainsStatBonuses.GiveUnitUpgrade(Info.Upgrade, Info.Levels); - }); + if (!a.IsDead() && a.IsInWorld) + GrantActorUpgrades(a); } + }); } base.Activate(collector); diff --git a/OpenRA.Mods.RA/GainsExperience.cs b/OpenRA.Mods.RA/GainsExperience.cs index 60bbad63e7..d8d179ea1c 100644 --- a/OpenRA.Mods.RA/GainsExperience.cs +++ b/OpenRA.Mods.RA/GainsExperience.cs @@ -8,9 +8,12 @@ */ #endregion +using System; +using System.Collections.Generic; using System.Linq; using OpenRA.GameRules; using OpenRA.Mods.RA.Effects; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.RA @@ -18,84 +21,105 @@ namespace OpenRA.Mods.RA [Desc("This actor's experience increases when it has killed a GivesExperience actor.")] public class GainsExperienceInfo : ITraitInfo, Requires { - [Desc("XP requirements for each level, as multiples of our own cost.")] - public readonly float[] CostThreshold = { 2, 4, 8, 16 }; - public readonly float[] FirepowerModifier = { 1.1f, 1.15f, 1.2f, 1.5f }; - public readonly float[] ArmorModifier = { 1.1f, 1.2f, 1.3f, 1.5f }; - public readonly decimal[] SpeedModifier = { 1.1m, 1.15m, 1.2m, 1.5m }; + [FieldLoader.LoadUsing("LoadUpgrades")] + [Desc("Upgrades to grant at each level", + "Key is the XP requirements for each level as a percentage of our own value.", + "Value is a list of the upgrade types to grant")] + public readonly Dictionary Upgrades = null; + + [Desc("Palette for the chevron glyph rendered in the selection box.")] public readonly string ChevronPalette = "effect"; + + [Desc("Palette for the level up sprite.")] public readonly string LevelUpPalette = "effect"; + public object Create(ActorInitializer init) { return new GainsExperience(init, this); } + + static object LoadUpgrades(MiniYaml y) + { + MiniYaml upgrades; + + if (!y.ToDictionary().TryGetValue("Upgrades", out upgrades)) + { + return new Dictionary() + { + { 200, new[] { "firepower", "armor", "speed" } }, + { 400, new[] { "firepower", "armor", "speed" } }, + { 800, new[] { "firepower", "armor", "speed" } }, + { 1600, new[] { "firepower", "armor", "speed" } } + }; + } + + return upgrades.Nodes.ToDictionary( + kv => FieldLoader.GetValue("(key)", kv.Key), + kv => FieldLoader.GetValue("(value)", kv.Value.Value)); + } } - public class GainsExperience : IFirepowerModifier, ISpeedModifier, IDamageModifier, ISync + public class GainsExperience : ISync { readonly Actor self; - readonly int[] levels; readonly GainsExperienceInfo info; + readonly List> nextLevel = new List>(); + + // Stored as a percentage of our value + [Sync] int experience = 0; + + [Sync] public int Level { get; private set; } + public readonly int MaxLevel; + public GainsExperience(ActorInitializer init, GainsExperienceInfo info) { self = init.self; this.info = info; + + MaxLevel = info.Upgrades.Count; + var cost = self.Info.Traits.Get().Cost; - levels = info.CostThreshold.Select(t => (int)(t * cost)).ToArray(); + foreach (var kv in info.Upgrades) + nextLevel.Add(Pair.New(kv.Key * cost, kv.Value)); if (init.Contains()) GiveExperience(init.Get()); } - [Sync] int experience = 0; - [Sync] public int Level { get; private set; } - - int MaxLevel { get { return levels.Length; } } public bool CanGainLevel { get { return Level < MaxLevel; } } - public void GiveOneLevel() - { - if (Level < MaxLevel) - GiveExperience(levels[Level] - experience); - } - public void GiveLevels(int numLevels) { - for (var i = 0; i < numLevels; i++) - GiveOneLevel(); + var newLevel = Math.Min(Level + numLevels, MaxLevel); + GiveExperience(nextLevel[newLevel - 1].First - experience); } public void GiveExperience(int amount) { experience += amount; - while (Level < MaxLevel && experience >= levels[Level]) + while (Level < MaxLevel && experience >= nextLevel[Level].First) { + var upgrades = nextLevel[Level].Second; + Level++; + foreach (var up in self.TraitsImplementing()) + foreach (var u in upgrades) + if (up.AcceptsUpgrade(u)) + up.UpgradeAvailable(self, u, true); + Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Sounds", "LevelUp", self.Owner.Country.Race); self.World.AddFrameEndTask(w => w.Add(new CrateEffect(self, "levelup", info.LevelUpPalette))); + if (Level == 1) + { self.World.AddFrameEndTask(w => { if (!self.IsDead()) w.Add(new Rank(self, info.ChevronPalette)); }); + } } } - - public float GetDamageModifier(Actor attacker, DamageWarhead warhead) - { - return Level > 0 ? 1 / info.ArmorModifier[Level - 1] : 1; - } - - public float GetFirepowerModifier() - { - return Level > 0 ? info.FirepowerModifier[Level - 1] : 1; - } - - public decimal GetSpeedModifier() - { - return Level > 0 ? info.SpeedModifier[Level - 1] : 1m; - } } class ExperienceInit : IActorInit diff --git a/OpenRA.Mods.RA/GainsStatUpgrades.cs b/OpenRA.Mods.RA/GainsStatUpgrades.cs new file mode 100644 index 0000000000..f7889e1446 --- /dev/null +++ b/OpenRA.Mods.RA/GainsStatUpgrades.cs @@ -0,0 +1,78 @@ +#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 OpenRA.GameRules; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + [Desc("This actor has properties that upgrade when a specific criteria is met.")] + public class GainsStatUpgradesInfo : ITraitInfo + { + public readonly string FirepowerUpgrade = "firepower"; + public readonly float[] FirepowerModifier = { 1.1f, 1.15f, 1.2f, 1.5f }; + + public readonly string ArmorUpgrade = "armor"; + public readonly float[] ArmorModifier = { 1.1f, 1.2f, 1.3f, 1.5f }; + + public readonly string SpeedUpgrade = "speed"; + public readonly decimal[] SpeedModifier = { 1.1m, 1.15m, 1.2m, 1.5m }; + + public object Create(ActorInitializer init) { return new GainsStatUpgrades(this); } + } + + public class GainsStatUpgrades : IUpgradable, IFirepowerModifier, IDamageModifier, ISpeedModifier + { + readonly GainsStatUpgradesInfo info; + [Sync] int firepowerLevel = 0; + [Sync] int speedLevel = 0; + [Sync] int armorLevel = 0; + + public GainsStatUpgrades(GainsStatUpgradesInfo info) + { + this.info = info; + } + + public bool AcceptsUpgrade(string type) + { + return (type == info.FirepowerUpgrade && firepowerLevel < info.FirepowerModifier.Length) + || (type == info.ArmorUpgrade && armorLevel < info.ArmorModifier.Length) + || (type == info.SpeedUpgrade && speedLevel < info.SpeedModifier.Length); + } + + public void UpgradeAvailable(Actor self, string type, bool available) + { + var mod = available ? 1 : -1; + if (type == info.FirepowerUpgrade) + firepowerLevel = (firepowerLevel + mod).Clamp(0, info.FirepowerModifier.Length); + else if (type == info.ArmorUpgrade) + armorLevel = (armorLevel + mod).Clamp(0, info.ArmorModifier.Length); + else if (type == info.SpeedUpgrade) + speedLevel = (speedLevel + mod).Clamp(0, info.SpeedModifier.Length); + } + + public float GetDamageModifier(Actor attacker, DamageWarhead warhead) + { + return armorLevel > 0 ? 1 / info.ArmorModifier[armorLevel - 1] : 1; + } + + public float GetFirepowerModifier() + { + return firepowerLevel > 0 ? info.FirepowerModifier[firepowerLevel - 1] : 1; + } + + public decimal GetSpeedModifier() + { + return speedLevel > 0 ? info.SpeedModifier[speedLevel - 1] : 1m; + } + } +} diff --git a/OpenRA.Mods.RA/GainsUnitUpgrades.cs b/OpenRA.Mods.RA/GainsUnitUpgrades.cs deleted file mode 100644 index 095a12cbd8..0000000000 --- a/OpenRA.Mods.RA/GainsUnitUpgrades.cs +++ /dev/null @@ -1,87 +0,0 @@ -#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 OpenRA.GameRules; -using OpenRA.Traits; - -namespace OpenRA.Mods.RA -{ - [Desc("This actor has properties that upgrade when a specific criteria is met.")] - public class GainsUnitUpgradesInfo : ITraitInfo - { - public readonly int FirepowerMaxLevel = 15; - public readonly float FirepowerModifier = .2f; - public readonly int ArmorMaxLevel = 15; - public readonly float ArmorModifier = .2f; - public readonly int SpeedMaxLevel = 15; - public readonly decimal SpeedModifier = .2m; - // TODO: weapon range, rate of fire modifiers. potentially a vision modifier. - - public object Create(ActorInitializer init) { return new GainsUnitUpgrades(this); } - } - - public class GainsUnitUpgrades : IFirepowerModifier, IDamageModifier, ISpeedModifier - { - GainsUnitUpgradesInfo info; - [Sync] public int FirepowerLevel = 0; - [Sync] public int SpeedLevel = 0; - [Sync] public int ArmorLevel = 0; - - public GainsUnitUpgrades(GainsUnitUpgradesInfo info) - { - this.info = info; - } - - public bool CanGainUnitUpgrade(UnitUpgrade? upgrade) - { - if (upgrade == UnitUpgrade.Firepower) - return FirepowerLevel < info.FirepowerMaxLevel; - if (upgrade == UnitUpgrade.Armor) - return ArmorLevel < info.ArmorMaxLevel; - if (upgrade == UnitUpgrade.Speed) - return SpeedLevel < info.SpeedMaxLevel; - - return false; - } - - public void GiveUnitUpgrade(UnitUpgrade? upgrade, int numLevels) - { - if (upgrade == UnitUpgrade.Firepower) - FirepowerLevel = Math.Min(FirepowerLevel + numLevels, info.FirepowerMaxLevel); - else if (upgrade == UnitUpgrade.Armor) - ArmorLevel = Math.Min(ArmorLevel + numLevels, info.ArmorMaxLevel); - else if (upgrade == UnitUpgrade.Speed) - SpeedLevel = Math.Min(SpeedLevel + numLevels, info.SpeedMaxLevel); - } - - public float GetFirepowerModifier() - { - return FirepowerLevel > 0 ? (1 + FirepowerLevel * info.FirepowerModifier) : 1; - } - - public float GetDamageModifier(Actor attacker, DamageWarhead warhead) - { - return ArmorLevel > 0 ? (1 / (1 + ArmorLevel * info.ArmorModifier)) : 1; - } - - public decimal GetSpeedModifier() - { - return SpeedLevel > 0 ? (1m + SpeedLevel * info.SpeedModifier) : 1m; - } - } - - public enum UnitUpgrade - { - Firepower = 0, - Armor = 1, - Speed = 2 - } -} diff --git a/OpenRA.Mods.RA/GivesExperience.cs b/OpenRA.Mods.RA/GivesExperience.cs index 947e34961a..85df386107 100644 --- a/OpenRA.Mods.RA/GivesExperience.cs +++ b/OpenRA.Mods.RA/GivesExperience.cs @@ -13,26 +13,38 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA { [Desc("This actor gives experience to a GainsExperience actor when they are killed.")] - class GivesExperienceInfo : TraitInfo + class GivesExperienceInfo : ITraitInfo { [Desc("If -1, use the value of the unit cost.")] public readonly int Experience = -1; + + [Desc("Grant experience for team-kills.")] + public readonly bool FriendlyFire = false; + + public object Create(ActorInitializer init) { return new GivesExperience(init.self, this); } } class GivesExperience : INotifyKilled { + readonly GivesExperienceInfo info; + + public GivesExperience(Actor self, GivesExperienceInfo info) + { + this.info = info; + } + public void Killed(Actor self, AttackInfo e) { // Prevent TK from giving exp - if (e.Attacker == null || e.Attacker.Destroyed || e.Attacker.Owner.Stances[self.Owner] == Stance.Ally) + if (e.Attacker == null || e.Attacker.Destroyed || (!info.FriendlyFire && e.Attacker.Owner.Stances[self.Owner] == Stance.Ally)) return; - var info = self.Info.Traits.Get(); var valued = self.Info.Traits.GetOrDefault(); + // Default experience is 100 times our value var exp = info.Experience >= 0 ? info.Experience - : valued != null ? valued.Cost : 0; + : valued != null ? valued.Cost * 100 : 0; var killer = e.Attacker.TraitOrDefault(); if (killer != null) diff --git a/OpenRA.Mods.RA/GlobalUpgradable.cs b/OpenRA.Mods.RA/GlobalUpgradable.cs new file mode 100644 index 0000000000..dbfbb36594 --- /dev/null +++ b/OpenRA.Mods.RA/GlobalUpgradable.cs @@ -0,0 +1,62 @@ +#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.Mods.RA; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + public class GlobalUpgradableInfo : ITraitInfo + { + public readonly string[] Upgrades = { }; + public readonly string[] Prerequisites = { }; + + public object Create(ActorInitializer init) { return new GlobalUpgradable(init.self, this); } + } + + public class GlobalUpgradable : INotifyAddedToWorld + { + readonly GlobalUpgradableInfo info; + readonly GlobalUpgradeManager manager; + + public GlobalUpgradable(Actor actor, GlobalUpgradableInfo info) + { + this.info = info; + manager = actor.Owner.PlayerActor.Trait(); + } + + public void AddedToWorld(Actor self) + { + if (info.Prerequisites.Any()) + manager.Register(self, this, info.Prerequisites); + } + + public void RemovedFromWorld(Actor self) + { + if (info.Prerequisites.Any()) + manager.Unregister(self, this, info.Prerequisites); + } + + public void PrerequisitesUpdated(Actor self, bool available) + { + var upgrades = self.TraitsImplementing(); + foreach (var u in upgrades) + { + foreach (var t in info.Upgrades) + if (u.AcceptsUpgrade(t)) + u.UpgradeAvailable(self, t, available); + } + } + } +} diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 35c8e104c0..6c4548cde7 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -193,7 +193,6 @@ - @@ -234,7 +233,6 @@ - @@ -550,6 +548,9 @@ + + + diff --git a/OpenRA.Mods.RA/Player/GlobalUpgradeManager.cs b/OpenRA.Mods.RA/Player/GlobalUpgradeManager.cs new file mode 100644 index 0000000000..8e07b259e0 --- /dev/null +++ b/OpenRA.Mods.RA/Player/GlobalUpgradeManager.cs @@ -0,0 +1,94 @@ +#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 OpenRA.Graphics; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + [Desc("Attach this to the player actor.")] + public class GlobalUpgradeManagerInfo : ITraitInfo, Requires + { + public object Create(ActorInitializer init) { return new GlobalUpgradeManager(init); } + } + + public class GlobalUpgradeManager : ITechTreeElement + { + readonly Actor self; + readonly Dictionary>> upgradables = new Dictionary>>(); + readonly TechTree techTree; + + public GlobalUpgradeManager(ActorInitializer init) + { + self = init.self; + techTree = self.Trait(); + } + + static string MakeKey(string[] prerequisites) + { + return "upgrade_" + string.Join("_", prerequisites.OrderBy(a => a)); + } + + public void Register(Actor actor, GlobalUpgradable u, string[] prerequisites) + { + var key = MakeKey(prerequisites); + if (!upgradables.ContainsKey(key)) + { + upgradables.Add(key, new List>()); + techTree.Add(key, prerequisites, 0, this); + } + + upgradables[key].Add(Pair.New(actor, u)); + + // Notify the current state + u.PrerequisitesUpdated(actor, techTree.HasPrerequisites(prerequisites)); + } + + public void Unregister(Actor actor, GlobalUpgradable u, string[] prerequisites) + { + var key = MakeKey(prerequisites); + var list = upgradables[key]; + + list.RemoveAll(x => x.First == actor && x.Second == u); + if (!list.Any()) + { + upgradables.Remove(key); + techTree.Remove(key); + } + } + + public void PrerequisitesAvailable(string key) + { + List> list; + if (!upgradables.TryGetValue(key, out list)) + return; + + foreach (var u in list) + u.Second.PrerequisitesUpdated(u.First, true); + } + + public void PrerequisitesUnavailable(string key) + { + List> list; + if (!upgradables.TryGetValue(key, out list)) + return; + + foreach (var u in list) + u.Second.PrerequisitesUpdated(u.First, false); + } + + public void PrerequisitesItemHidden(string key) { } + public void PrerequisitesItemVisible(string key) { } + } +} diff --git a/OpenRA.Mods.RA/Production.cs b/OpenRA.Mods.RA/Production.cs index b8004e535a..21745cf5c1 100755 --- a/OpenRA.Mods.RA/Production.cs +++ b/OpenRA.Mods.RA/Production.cs @@ -60,8 +60,8 @@ namespace OpenRA.Mods.RA var spawn = self.CenterPosition + exitinfo.SpawnOffset; var to = self.World.Map.CenterOfCell(exit); - var fi = producee.Traits.Get(); - var initialFacing = exitinfo.Facing < 0 ? Util.GetFacing(to - spawn, fi.GetInitialFacing()) : exitinfo.Facing; + var fi = producee.Traits.GetOrDefault(); + var initialFacing = exitinfo.Facing < 0 ? Util.GetFacing(to - spawn, fi == null ? 0 : fi.GetInitialFacing()) : exitinfo.Facing; var exitLocation = rp.Value != null ? rp.Value.Location : exit; var target = Target.FromCell(self.World, exitLocation); diff --git a/OpenRA.Utility/UpgradeRules.cs b/OpenRA.Utility/UpgradeRules.cs index 27386372fe..12d943e97b 100644 --- a/OpenRA.Utility/UpgradeRules.cs +++ b/OpenRA.Utility/UpgradeRules.cs @@ -364,6 +364,28 @@ namespace OpenRA.Utility } } + // Veterancy was changed to use the upgrades system + if (engineVersion < 20140807) + { + if (depth == 0 && node.Value.Nodes.Any(n => n.Key.StartsWith("GainsExperience"))) + node.Value.Nodes.Add(new MiniYamlNode("GainsStatUpgrades", new MiniYaml(""))); + + if (depth == 1 && node.Key == "-CloakCrateAction") + node.Key = "-UnitUpgradeCrateAction@cloak"; + + if (depth == 1 && node.Key == "CloakCrateAction") + { + node.Key = "UnitUpgradeCrateAction@cloak"; + node.Value.Nodes.Add(new MiniYamlNode("Upgrades", new MiniYaml("cloak"))); + } + + if (depth == 2 && node.Key == "RequiresCrate" && parentKey == "Cloak") + { + node.Key = "RequiresUpgrade"; + node.Value.Value = "cloak"; + } + } + UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1); } } diff --git a/mods/cnc/maps/gdi04a/map.yaml b/mods/cnc/maps/gdi04a/map.yaml index 95782d2f10..c0f56925fb 100644 --- a/mods/cnc/maps/gdi04a/map.yaml +++ b/mods/cnc/maps/gdi04a/map.yaml @@ -563,7 +563,7 @@ Rules: -GiveMcvCrateAction: -GiveCashCrateAction: -ExplodeCrateAction@fire: - -CloakCrateAction: + -UnitUpgradeCrateAction@cloak: ScriptTriggers: Sequences: diff --git a/mods/cnc/maps/gdi04b/map.yaml b/mods/cnc/maps/gdi04b/map.yaml index 6b5a8782cf..98e836adc3 100644 --- a/mods/cnc/maps/gdi04b/map.yaml +++ b/mods/cnc/maps/gdi04b/map.yaml @@ -645,7 +645,7 @@ Rules: -GiveMcvCrateAction: -GiveCashCrateAction: -ExplodeCrateAction@fire: - -CloakCrateAction: + -UnitUpgradeCrateAction@cloak: Sequences: diff --git a/mods/cnc/maps/the-hot-box/map.yaml b/mods/cnc/maps/the-hot-box/map.yaml index 205273c280..dc896e5f10 100644 --- a/mods/cnc/maps/the-hot-box/map.yaml +++ b/mods/cnc/maps/the-hot-box/map.yaml @@ -211,7 +211,7 @@ Rules: -GiveMcvCrateAction: -RevealMapCrateAction: -HideMapCrateAction: - -CloakCrateAction: + -UnitUpgradeCrateAction@cloak: -ExplodeCrateAction@nuke: -ExplodeCrateAction@boom: -ExplodeCrateAction@fire: diff --git a/mods/cnc/rules/defaults.yaml b/mods/cnc/rules/defaults.yaml index 51c36dacc5..036ffe7060 100644 --- a/mods/cnc/rules/defaults.yaml +++ b/mods/cnc/rules/defaults.yaml @@ -33,7 +33,7 @@ BodyOrientation: UpdatesPlayerStatistics: Cloak: - RequiresCrate: true + RequiresUpgrade: cloak InitialDelay: 15 CloakDelay: 90 CloakSound: trans1.aud @@ -41,6 +41,7 @@ Huntable: LuaScriptEvents: ScriptTriggers: + GainsStatUpgrades: ^Tank: AppearsOnRadar: @@ -80,7 +81,7 @@ BodyOrientation: UpdatesPlayerStatistics: Cloak: - RequiresCrate: true + RequiresUpgrade: cloak InitialDelay: 15 CloakDelay: 90 CloakSound: trans1.aud @@ -88,6 +89,7 @@ Huntable: LuaScriptEvents: ScriptTriggers: + GainsStatUpgrades: ^Helicopter: AppearsOnRadar: @@ -119,6 +121,7 @@ Huntable: LuaScriptEvents: ScriptTriggers: + GainsStatUpgrades: ^Infantry: AppearsOnRadar: @@ -187,6 +190,7 @@ DeathSounds@POISONED: DeathSound: Poisoned InfDeaths: 6 + GainsStatUpgrades: ^CivInfantry: Inherits: ^Infantry @@ -285,6 +289,7 @@ AttackMove: LuaScriptEvents: ScriptTriggers: + GainsStatUpgrades: ^Ship: AppearsOnRadar: @@ -311,6 +316,7 @@ Huntable: LuaScriptEvents: ScriptTriggers: + GainsStatUpgrades: ^Building: AppearsOnRadar: diff --git a/mods/cnc/rules/misc.yaml b/mods/cnc/rules/misc.yaml index c2cb588de3..040ca5dff0 100644 --- a/mods/cnc/rules/misc.yaml +++ b/mods/cnc/rules/misc.yaml @@ -14,9 +14,10 @@ CRATE: ExplodeCrateAction@fire: Weapon: Napalm.Crate SelectionShares: 5 - CloakCrateAction: + UnitUpgradeCrateAction@cloak: SelectionShares: 5 Effect: cloak + Upgrades: cloak DuplicateUnitCrateAction: SelectionShares: 10 MaxAmount: 5 diff --git a/mods/cnc/rules/vehicles.yaml b/mods/cnc/rules/vehicles.yaml index 413f988eb9..d8d9ed207c 100644 --- a/mods/cnc/rules/vehicles.yaml +++ b/mods/cnc/rules/vehicles.yaml @@ -531,7 +531,7 @@ STNK: RevealsShroud: Range: 7c0 Cloak: - RequiresCrate: false + RequiresUpgrade: cloak InitialDelay: 90 CloakDelay: 90 CloakSound: trans1.aud diff --git a/mods/d2k/rules/defaults.yaml b/mods/d2k/rules/defaults.yaml index a9eaa8b4e5..fd26a51cce 100644 --- a/mods/d2k/rules/defaults.yaml +++ b/mods/d2k/rules/defaults.yaml @@ -39,6 +39,7 @@ LuaScriptEvents: Demolishable: ScriptTriggers: + GainsStatUpgrades: ^Tank: AppearsOnRadar: @@ -81,6 +82,7 @@ LuaScriptEvents: Demolishable: ScriptTriggers: + GainsStatUpgrades: ^Husk: Health: @@ -198,6 +200,7 @@ DeathSounds: Parachutable: FallRate: 130 + GainsStatUpgrades: ^Plane: AppearsOnRadar: @@ -223,6 +226,7 @@ AttackMove: LuaScriptEvents: ScriptTriggers: + GainsStatUpgrades: ^Helicopter: Inherits: ^Plane diff --git a/mods/ra/maps/desert-shellmap/map.yaml b/mods/ra/maps/desert-shellmap/map.yaml index f2abcb15a3..9fc0a2cdfe 100644 --- a/mods/ra/maps/desert-shellmap/map.yaml +++ b/mods/ra/maps/desert-shellmap/map.yaml @@ -1293,28 +1293,19 @@ Rules: GivesBounty: Percentage: 0 GainsExperience: - CostThreshold: - FirepowerModifier: - ArmorModifier: - SpeedModifier: + Upgrades: ^Tank: ScriptInvulnerable: GivesBounty: Percentage: 0 GainsExperience: - CostThreshold: - FirepowerModifier: - ArmorModifier: - SpeedModifier: + Upgrades: ^Infantry: ScriptInvulnerable: GivesBounty: Percentage: 0 GainsExperience: - CostThreshold: - FirepowerModifier: - ArmorModifier: - SpeedModifier: + Upgrades: DeathSounds@NORMAL: VolumeMultiplier: 0.1 DeathSounds@BURNED: @@ -1326,10 +1317,7 @@ Rules: GivesBounty: Percentage: 0 GainsExperience: - CostThreshold: - FirepowerModifier: - ArmorModifier: - SpeedModifier: + Upgrades: ^Plane: ScriptInvulnerable: GivesBounty: diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml index 10730dac6e..1fe2145be0 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -54,6 +54,7 @@ CaptureNotification: Notification: UnitStolen ScriptTriggers: + GainsStatUpgrades: ^Tank: AppearsOnRadar: @@ -111,6 +112,7 @@ CaptureNotification: Notification: UnitStolen ScriptTriggers: + GainsStatUpgrades: ^Infantry: AppearsOnRadar: @@ -182,6 +184,7 @@ ShadowSequence: parach-shadow Cloneable: Types: Infantry + GainsStatUpgrades: ^Ship: AppearsOnRadar: @@ -215,6 +218,7 @@ Huntable: LuaScriptEvents: ScriptTriggers: + GainsStatUpgrades: ^Plane: AppearsOnRadar: @@ -251,6 +255,7 @@ Huntable: LuaScriptEvents: ScriptTriggers: + GainsStatUpgrades: ^Helicopter: Inherits: ^Plane diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml index 655d2adda3..e867dca41a 100644 --- a/mods/ts/rules/defaults.yaml +++ b/mods/ts/rules/defaults.yaml @@ -113,7 +113,10 @@ HiddenUnderFog: GainsExperience: ChevronPalette: ra - CostThreshold: 5, 10 + Upgrades: + 500: firepower, armor, speed + 1000: firepower, armor, speed + GainsStatUpgrades: FirepowerModifier: 1.2, 1.5 ArmorModifier: 1.2, 1.5 SpeedModifier: 1.2, 1.5 @@ -199,7 +202,10 @@ HiddenUnderFog: GainsExperience: ChevronPalette: ra - CostThreshold: 5, 10 + Upgrades: + 500: firepower, armor, speed + 1000: firepower, armor, speed + GainsStatUpgrades: FirepowerModifier: 1.2, 1.5 ArmorModifier: 1.2, 1.5 SpeedModifier: 1.2, 1.5 @@ -248,7 +254,10 @@ HiddenUnderFog: GainsExperience: ChevronPalette: ra - CostThreshold: 5, 10 + Upgrades: + 500: firepower, armor, speed + 1000: firepower, armor, speed + GainsStatUpgrades: FirepowerModifier: 1.2, 1.5 ArmorModifier: 1.2, 1.5 SpeedModifier: 1.2, 1.5 @@ -291,7 +300,10 @@ AttackMove: GainsExperience: ChevronPalette: ra - CostThreshold: 5, 10 + Upgrades: + 500: firepower, armor, speed + 1000: firepower, armor, speed + GainsStatUpgrades: FirepowerModifier: 1.2, 1.5 ArmorModifier: 1.2, 1.5 SpeedModifier: 1.2, 1.5