Merge pull request #6110 from pchote/unit-upgrades

Add an actor upgrade system (+ overhauls veterancy and crates)
This commit is contained in:
Curtis Shmyr
2014-08-07 07:59:57 -06:00
23 changed files with 438 additions and 272 deletions

View File

@@ -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); }

View File

@@ -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;
}
}
}

View File

@@ -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<Cloak>();
if (cloak == null || !cloak.AcceptsCloakCrate)
return 0;
return base.GetSelectionShares(collector);
}
public override void Activate(Actor collector)
{
collector.Trait<Cloak>().ReceivedCloakCrate(collector);
var inRange = self.World.FindActorsInCircle(self.CenterPosition, Info.Range);
inRange = inRange.Where(a =>
(a.Owner == collector.Owner) &&
(a != collector) &&
(a.TraitOrDefault<Cloak>() != null) &&
(a.TraitOrDefault<Cloak>().AcceptsCloakCrate));
if (inRange.Any())
{
if (Info.MaxExtraCollectors > -1)
inRange = inRange.Take(Info.MaxExtraCollectors);
if (inRange.Any())
foreach (Actor actor in inRange)
{
actor.Trait<Cloak>().ReceivedCloakCrate(actor);
}
}
base.Activate(collector);
}
}
}

View File

@@ -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<IUpgradable>()
.Any(up => Info.Upgrades.Any(u => up.AcceptsUpgrade(u)));
}
void GrantActorUpgrades(Actor a)
{
foreach (var up in a.TraitsImplementing<IUpgradable>())
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<GainsUnitUpgrades>();
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<GainsUnitUpgrades>();
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<GainsUnitUpgrades>() != null) &&
(a.TraitOrDefault<GainsUnitUpgrades>().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<GainsUnitUpgrades>();
if (gainsStatBonuses != null)
gainsStatBonuses.GiveUnitUpgrade(Info.Upgrade, Info.Levels);
});
if (!a.IsDead() && a.IsInWorld)
GrantActorUpgrades(a);
}
});
}
base.Activate(collector);

View File

@@ -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<ValuedInfo>
{
[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<int, string[]> 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<int, string[]>()
{
{ 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<int>("(key)", kv.Key),
kv => FieldLoader.GetValue<string[]>("(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<Pair<int, string[]>> nextLevel = new List<Pair<int, string[]>>();
// 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<ValuedInfo>().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<ExperienceInit>())
GiveExperience(init.Get<ExperienceInit, int>());
}
[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<IUpgradable>())
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<int>

View File

@@ -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;
}
}
}

View File

@@ -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
}
}

View File

@@ -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<GivesExperience>
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<GivesExperienceInfo>();
var valued = self.Info.Traits.GetOrDefault<ValuedInfo>();
// 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<GainsExperience>();
if (killer != null)

View File

@@ -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<GlobalUpgradeManager>();
}
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<IUpgradable>();
foreach (var u in upgrades)
{
foreach (var t in info.Upgrades)
if (u.AcceptsUpgrade(t))
u.UpgradeAvailable(self, t, available);
}
}
}
}

View File

@@ -193,7 +193,6 @@
<Compile Include="Crate.cs" />
<Compile Include="CrateAction.cs" />
<Compile Include="CrateSpawner.cs" />
<Compile Include="Crates\CloakCrateAction.cs" />
<Compile Include="Crates\ExplodeCrateAction.cs" />
<Compile Include="Crates\GiveCashCrateAction.cs" />
<Compile Include="Crates\GiveMcvCrateAction.cs" />
@@ -234,7 +233,6 @@
<Compile Include="Fake.cs" />
<Compile Include="FreeActor.cs" />
<Compile Include="GainsExperience.cs" />
<Compile Include="GainsUnitUpgrades.cs" />
<Compile Include="GivesBounty.cs" />
<Compile Include="GivesExperience.cs" />
<Compile Include="Guard.cs" />
@@ -550,6 +548,9 @@
<Compile Include="Graphics\ActorPreview.cs" />
<Compile Include="Graphics\SpriteActorPreview.cs" />
<Compile Include="Graphics\VoxelActorPreview.cs" />
<Compile Include="GlobalUpgradable.cs" />
<Compile Include="Player\GlobalUpgradeManager.cs" />
<Compile Include="GainsStatUpgrades.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">

View File

@@ -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<TechTreeInfo>
{
public object Create(ActorInitializer init) { return new GlobalUpgradeManager(init); }
}
public class GlobalUpgradeManager : ITechTreeElement
{
readonly Actor self;
readonly Dictionary<string, List<Pair<Actor, GlobalUpgradable>>> upgradables = new Dictionary<string, List<Pair<Actor, GlobalUpgradable>>>();
readonly TechTree techTree;
public GlobalUpgradeManager(ActorInitializer init)
{
self = init.self;
techTree = self.Trait<TechTree>();
}
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<Pair<Actor, GlobalUpgradable>>());
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<Pair<Actor, GlobalUpgradable>> 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<Pair<Actor, GlobalUpgradable>> 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) { }
}
}

View File

@@ -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<IFacingInfo>();
var initialFacing = exitinfo.Facing < 0 ? Util.GetFacing(to - spawn, fi.GetInitialFacing()) : exitinfo.Facing;
var fi = producee.Traits.GetOrDefault<IFacingInfo>();
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);

View File

@@ -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);
}
}

View File

@@ -563,7 +563,7 @@ Rules:
-GiveMcvCrateAction:
-GiveCashCrateAction:
-ExplodeCrateAction@fire:
-CloakCrateAction:
-UnitUpgradeCrateAction@cloak:
ScriptTriggers:
Sequences:

View File

@@ -645,7 +645,7 @@ Rules:
-GiveMcvCrateAction:
-GiveCashCrateAction:
-ExplodeCrateAction@fire:
-CloakCrateAction:
-UnitUpgradeCrateAction@cloak:
Sequences:

View File

@@ -211,7 +211,7 @@ Rules:
-GiveMcvCrateAction:
-RevealMapCrateAction:
-HideMapCrateAction:
-CloakCrateAction:
-UnitUpgradeCrateAction@cloak:
-ExplodeCrateAction@nuke:
-ExplodeCrateAction@boom:
-ExplodeCrateAction@fire:

View File

@@ -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:

View File

@@ -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

View File

@@ -531,7 +531,7 @@ STNK:
RevealsShroud:
Range: 7c0
Cloak:
RequiresCrate: false
RequiresUpgrade: cloak
InitialDelay: 90
CloakDelay: 90
CloakSound: trans1.aud

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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