Merge pull request #6110 from pchote/unit-upgrades
Add an actor upgrade system (+ overhauls veterancy and crates)
This commit is contained in:
@@ -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); }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
78
OpenRA.Mods.RA/GainsStatUpgrades.cs
Normal file
78
OpenRA.Mods.RA/GainsStatUpgrades.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
62
OpenRA.Mods.RA/GlobalUpgradable.cs
Normal file
62
OpenRA.Mods.RA/GlobalUpgradable.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
94
OpenRA.Mods.RA/Player/GlobalUpgradeManager.cs
Normal file
94
OpenRA.Mods.RA/Player/GlobalUpgradeManager.cs
Normal 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) { }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,7 +563,7 @@ Rules:
|
||||
-GiveMcvCrateAction:
|
||||
-GiveCashCrateAction:
|
||||
-ExplodeCrateAction@fire:
|
||||
-CloakCrateAction:
|
||||
-UnitUpgradeCrateAction@cloak:
|
||||
ScriptTriggers:
|
||||
|
||||
Sequences:
|
||||
|
||||
@@ -645,7 +645,7 @@ Rules:
|
||||
-GiveMcvCrateAction:
|
||||
-GiveCashCrateAction:
|
||||
-ExplodeCrateAction@fire:
|
||||
-CloakCrateAction:
|
||||
-UnitUpgradeCrateAction@cloak:
|
||||
|
||||
Sequences:
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ Rules:
|
||||
-GiveMcvCrateAction:
|
||||
-RevealMapCrateAction:
|
||||
-HideMapCrateAction:
|
||||
-CloakCrateAction:
|
||||
-UnitUpgradeCrateAction@cloak:
|
||||
-ExplodeCrateAction@nuke:
|
||||
-ExplodeCrateAction@boom:
|
||||
-ExplodeCrateAction@fire:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -531,7 +531,7 @@ STNK:
|
||||
RevealsShroud:
|
||||
Range: 7c0
|
||||
Cloak:
|
||||
RequiresCrate: false
|
||||
RequiresUpgrade: cloak
|
||||
InitialDelay: 90
|
||||
CloakDelay: 90
|
||||
CloakSound: trans1.aud
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user