diff --git a/OpenRA.Game/Traits/Health.cs b/OpenRA.Game/Traits/Health.cs index 9955ec78ed..6566068d21 100755 --- a/OpenRA.Game/Traits/Health.cs +++ b/OpenRA.Game/Traits/Health.cs @@ -100,16 +100,21 @@ namespace OpenRA.Traits public void InflictDamage(Actor self, Actor attacker, int damage, DamageWarhead warhead, bool ignoreModifiers) { - if (IsDead) return; /* overkill! don't count extra hits as more kills! */ + // Overkill! don't count extra hits as more kills! + if (IsDead) + return; var oldState = this.DamageState; - /* apply the damage modifiers, if we have any. */ - var modifier = self.TraitsImplementing() - .Concat(self.Owner.PlayerActor.TraitsImplementing()) - .Select(t => t.GetDamageModifier(attacker, warhead)).Product(); - if (!ignoreModifiers) - damage = damage > 0 ? (int)(damage * modifier) : damage; + // Apply any damage modifiers + if (!ignoreModifiers && damage > 0) + { + var modifiers = self.TraitsImplementing() + .Concat(self.Owner.PlayerActor.TraitsImplementing()) + .Select(t => t.GetDamageModifier(attacker, warhead)); + + damage = Util.ApplyPercentageModifiers(damage, modifiers); + } hp = Exts.Clamp(hp - damage, 0, MaxHP); diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 3ea4d94368..be008b25ba 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -167,9 +167,9 @@ namespace OpenRA.Traits } public interface IRenderModifier { IEnumerable ModifyRender(Actor self, WorldRenderer wr, IEnumerable r); } - public interface IDamageModifier { float GetDamageModifier(Actor attacker, DamageWarhead warhead); } - public interface ISpeedModifier { decimal GetSpeedModifier(); } - public interface IFirepowerModifier { float GetFirepowerModifier(); } + public interface IDamageModifier { int GetDamageModifier(Actor attacker, DamageWarhead warhead); } + public interface ISpeedModifier { int GetSpeedModifier(); } + public interface IFirepowerModifier { int GetFirepowerModifier(); } public interface ILoadsPalettes { void LoadPalettes(WorldRenderer wr); } public interface IPaletteModifier { void AdjustPalette(IReadOnlyDictionary b); } public interface IPips { IEnumerable GetPips(Actor self); } diff --git a/OpenRA.Game/Traits/Util.cs b/OpenRA.Game/Traits/Util.cs index 6268bcf1d5..0a6a999f82 100644 --- a/OpenRA.Game/Traits/Util.cs +++ b/OpenRA.Game/Traits/Util.cs @@ -138,5 +138,15 @@ namespace OpenRA.Traits var cells = target.Positions.Select(p => w.Map.CellContaining(p)).Distinct(); return ExpandFootprint(cells, true); } + + public static int ApplyPercentageModifiers(int number, IEnumerable percentages) + { + // See the comments of PR#6079 for a faster algorithm if this becomes a performance bottleneck + var a = (decimal)number; + foreach (var p in percentages) + a *= p / 100m; + + return (int)a; + } } } diff --git a/OpenRA.Mods.D2k/ThrowsShrapnel.cs b/OpenRA.Mods.D2k/ThrowsShrapnel.cs index 6a219bd97b..32e963acad 100644 --- a/OpenRA.Mods.D2k/ThrowsShrapnel.cs +++ b/OpenRA.Mods.D2k/ThrowsShrapnel.cs @@ -48,8 +48,10 @@ namespace OpenRA.Mods.D2k { Weapon = wep, Facing = self.World.SharedRandom.Next(-1, 255), + + // TODO: Convert to ints FirepowerModifier = self.TraitsImplementing() - .Select(a => a.GetFirepowerModifier()) + .Select(a => a.GetFirepowerModifier() / 100f) .Product(), Source = self.CenterPosition, diff --git a/OpenRA.Mods.RA/Activities/Demolish.cs b/OpenRA.Mods.RA/Activities/Demolish.cs index 97f9a97924..045eccbea0 100644 --- a/OpenRA.Mods.RA/Activities/Demolish.cs +++ b/OpenRA.Mods.RA/Activities/Demolish.cs @@ -50,16 +50,17 @@ namespace OpenRA.Mods.RA.Activities if (target.Type != TargetType.Actor) return; - // Invulnerable actors can't be demolished - var modifier = (float)target.Actor.TraitsImplementing() - .Concat(self.Owner.PlayerActor.TraitsImplementing()) - .Select(t => t.GetDamageModifier(self, null)).Product(); + var demolishable = target.Actor.TraitOrDefault(); if (demolishable == null || !demolishable.IsValidTarget(target.Actor, self)) return; - if (modifier > 0) + var modifiers = target.Actor.TraitsImplementing() + .Concat(self.Owner.PlayerActor.TraitsImplementing()) + .Select(t => t.GetDamageModifier(self, null)); + + if (Util.ApplyPercentageModifiers(100, modifiers) > 0) demolishable.Demolish(target.Actor, self); })); }); diff --git a/OpenRA.Mods.RA/Air/Aircraft.cs b/OpenRA.Mods.RA/Air/Aircraft.cs index 55964bdfee..61fef40569 100644 --- a/OpenRA.Mods.RA/Air/Aircraft.cs +++ b/OpenRA.Mods.RA/Air/Aircraft.cs @@ -203,10 +203,9 @@ namespace OpenRA.Mods.RA.Air { get { - decimal ret = info.Speed; - foreach (var t in self.TraitsImplementing()) - ret *= t.GetSpeedModifier(); - return (int)ret; + var modifiers = self.TraitsImplementing() + .Select(m => m.GetSpeedModifier()); + return Util.ApplyPercentageModifiers(info.Speed, modifiers); } } diff --git a/OpenRA.Mods.RA/Armament.cs b/OpenRA.Mods.RA/Armament.cs index aee2b84e2f..176488022e 100644 --- a/OpenRA.Mods.RA/Armament.cs +++ b/OpenRA.Mods.RA/Armament.cs @@ -153,8 +153,10 @@ namespace OpenRA.Mods.RA { Weapon = Weapon, Facing = legacyFacing, + + // TODO: Convert to ints FirepowerModifier = self.TraitsImplementing() - .Select(a => a.GetFirepowerModifier()) + .Select(a => a.GetFirepowerModifier() / 100f) .Product(), Source = muzzlePosition, diff --git a/OpenRA.Mods.RA/Attack/AttackPopupTurreted.cs b/OpenRA.Mods.RA/Attack/AttackPopupTurreted.cs index a4a66b8a92..85a36478fc 100644 --- a/OpenRA.Mods.RA/Attack/AttackPopupTurreted.cs +++ b/OpenRA.Mods.RA/Attack/AttackPopupTurreted.cs @@ -23,8 +23,8 @@ namespace OpenRA.Mods.RA public int CloseDelay = 125; public int DefaultFacing = 0; - [Desc("The factor damage received is multiplied by while this actor is closed.")] - public float ClosedDamageMultiplier = 0.5f; + [Desc("The percentage of damage that is received while this actor is closed.")] + public int ClosedDamageMultiplier = 50; public override object Create(ActorInitializer init) { return new AttackPopupTurreted(init, this); } } @@ -105,9 +105,9 @@ namespace OpenRA.Mods.RA } } - public float GetDamageModifier(Actor attacker, DamageWarhead warhead) + public int GetDamageModifier(Actor attacker, DamageWarhead warhead) { - return state == PopupState.Closed ? info.ClosedDamageMultiplier : 1f; + return state == PopupState.Closed ? info.ClosedDamageMultiplier : 100; } } } diff --git a/OpenRA.Mods.RA/GainsStatUpgrades.cs b/OpenRA.Mods.RA/GainsStatUpgrades.cs index f7889e1446..2e1527ed70 100644 --- a/OpenRA.Mods.RA/GainsStatUpgrades.cs +++ b/OpenRA.Mods.RA/GainsStatUpgrades.cs @@ -19,13 +19,13 @@ namespace OpenRA.Mods.RA public class GainsStatUpgradesInfo : ITraitInfo { public readonly string FirepowerUpgrade = "firepower"; - public readonly float[] FirepowerModifier = { 1.1f, 1.15f, 1.2f, 1.5f }; + public readonly int[] FirepowerModifier = { 110, 115, 120, 150 }; public readonly string ArmorUpgrade = "armor"; - public readonly float[] ArmorModifier = { 1.1f, 1.2f, 1.3f, 1.5f }; + public readonly int[] ArmorModifier = { 110, 120, 130, 150 }; public readonly string SpeedUpgrade = "speed"; - public readonly decimal[] SpeedModifier = { 1.1m, 1.15m, 1.2m, 1.5m }; + public readonly int[] SpeedModifier = { 110, 115, 120, 150 }; public object Create(ActorInitializer init) { return new GainsStatUpgrades(this); } } @@ -60,19 +60,19 @@ namespace OpenRA.Mods.RA speedLevel = (speedLevel + mod).Clamp(0, info.SpeedModifier.Length); } - public float GetDamageModifier(Actor attacker, DamageWarhead warhead) + public int GetDamageModifier(Actor attacker, DamageWarhead warhead) { - return armorLevel > 0 ? 1 / info.ArmorModifier[armorLevel - 1] : 1; + return armorLevel > 0 ? 1 / info.ArmorModifier[armorLevel - 1] : 100; } - public float GetFirepowerModifier() + public int GetFirepowerModifier() { - return firepowerLevel > 0 ? info.FirepowerModifier[firepowerLevel - 1] : 1; + return firepowerLevel > 0 ? info.FirepowerModifier[firepowerLevel - 1] : 100; } - public decimal GetSpeedModifier() + public int GetSpeedModifier() { - return speedLevel > 0 ? info.SpeedModifier[speedLevel - 1] : 1m; + return speedLevel > 0 ? info.SpeedModifier[speedLevel - 1] : 100; } } } diff --git a/OpenRA.Mods.RA/Harvester.cs b/OpenRA.Mods.RA/Harvester.cs index a18a53e208..1a06bd35e3 100644 --- a/OpenRA.Mods.RA/Harvester.cs +++ b/OpenRA.Mods.RA/Harvester.cs @@ -31,8 +31,8 @@ namespace OpenRA.Mods.RA public readonly int HarvestFacings = 0; [Desc("Which resources it can harvest.")] public readonly string[] Resources = { }; - [Desc("How much it is slowed down when returning to the refinery.")] - public readonly decimal FullyLoadedSpeed = .85m; + [Desc("Percentage of maximum speed when fully loaded.")] + public readonly int FullyLoadedSpeed = 85; [Desc("Initial search radius (in cells) from the refinery that created us.")] public readonly int SearchFromProcRadius = 24; [Desc("Search radius (in cells) from the last harvest order location to find more resources.")] @@ -418,9 +418,9 @@ namespace OpenRA.Mods.RA public bool ShouldExplode(Actor self) { return !IsEmpty; } - public decimal GetSpeedModifier() + public int GetSpeedModifier() { - return 1m - (1m - Info.FullyLoadedSpeed) * contents.Values.Sum() / Info.Capacity; + return 100 - (100 - Info.FullyLoadedSpeed) * contents.Values.Sum() / Info.Capacity; } class HarvestOrderTargeter : IOrderTargeter diff --git a/OpenRA.Mods.RA/Invulnerable.cs b/OpenRA.Mods.RA/Invulnerable.cs index b05950fed7..b4407d3377 100644 --- a/OpenRA.Mods.RA/Invulnerable.cs +++ b/OpenRA.Mods.RA/Invulnerable.cs @@ -18,6 +18,6 @@ namespace OpenRA.Mods.RA class Invulnerable : IDamageModifier { - public float GetDamageModifier(Actor attacker, DamageWarhead warhead) { return 0.0f; } + public int GetDamageModifier(Actor attacker, DamageWarhead warhead) { return 0; } } } diff --git a/OpenRA.Mods.RA/IronCurtainable.cs b/OpenRA.Mods.RA/IronCurtainable.cs index 0c8db3f3ab..dfe50c7dc2 100644 --- a/OpenRA.Mods.RA/IronCurtainable.cs +++ b/OpenRA.Mods.RA/IronCurtainable.cs @@ -28,9 +28,9 @@ namespace OpenRA.Mods.RA RemainingTicks--; } - public float GetDamageModifier(Actor attacker, DamageWarhead warhead) + public int GetDamageModifier(Actor attacker, DamageWarhead warhead) { - return (RemainingTicks > 0) ? 0.0f : 1.0f; + return RemainingTicks > 0 ? 0 : 100; } public void Activate(Actor self, int duration) diff --git a/OpenRA.Mods.RA/Move/Mobile.cs b/OpenRA.Mods.RA/Move/Mobile.cs index b83ea123cc..0a8b94f030 100755 --- a/OpenRA.Mods.RA/Move/Mobile.cs +++ b/OpenRA.Mods.RA/Move/Mobile.cs @@ -487,13 +487,15 @@ namespace OpenRA.Mods.RA.Move if (index == -1) return 0; + // TODO: Convert to integers var speed = Info.TilesetTerrainInfo[self.World.TileSet][index].Speed; if (speed == decimal.Zero) return 0; speed *= Info.Speed; foreach (var t in self.TraitsImplementing()) - speed *= t.GetSpeedModifier(); + speed *= t.GetSpeedModifier() / 100m; + return (int)(speed / 100); } diff --git a/OpenRA.Mods.RA/ScaredyCat.cs b/OpenRA.Mods.RA/ScaredyCat.cs index 01904e1de2..0e5a526818 100644 --- a/OpenRA.Mods.RA/ScaredyCat.cs +++ b/OpenRA.Mods.RA/ScaredyCat.cs @@ -17,7 +17,7 @@ namespace OpenRA.Mods.RA class ScaredyCatInfo : ITraitInfo { public readonly int PanicLength = 25 * 10; - public readonly decimal PanicSpeedModifier = 2; + public readonly int PanicSpeedModifier = 200; public readonly int AttackPanicChance = 20; public object Create(ActorInitializer init) { return new ScaredyCat(init.self, this); } @@ -74,9 +74,9 @@ namespace OpenRA.Mods.RA Panic(); } - public decimal GetSpeedModifier() + public int GetSpeedModifier() { - return Panicking ? Info.PanicSpeedModifier : 1; + return Panicking ? Info.PanicSpeedModifier : 100; } } } diff --git a/OpenRA.Mods.RA/Scripting/ScriptInvulnerable.cs b/OpenRA.Mods.RA/Scripting/ScriptInvulnerable.cs index eebcf35013..f2c8e5f067 100644 --- a/OpenRA.Mods.RA/Scripting/ScriptInvulnerable.cs +++ b/OpenRA.Mods.RA/Scripting/ScriptInvulnerable.cs @@ -20,9 +20,9 @@ namespace OpenRA.Mods.RA { public bool Invulnerable = false; - public float GetDamageModifier(Actor attacker, DamageWarhead warhead) + public int GetDamageModifier(Actor attacker, DamageWarhead warhead) { - return Invulnerable ? 0.0f : 1.0f; + return Invulnerable ? 0 : 100; } } } diff --git a/OpenRA.Mods.RA/TakeCover.cs b/OpenRA.Mods.RA/TakeCover.cs index 6d64de1782..3334ef30b6 100644 --- a/OpenRA.Mods.RA/TakeCover.cs +++ b/OpenRA.Mods.RA/TakeCover.cs @@ -21,8 +21,8 @@ namespace OpenRA.Mods.RA "Measured in game ticks. Default is 4 seconds.")] public readonly int ProneTime = 100; - [Desc("How quickly we should go from standing to prone.")] - public readonly decimal ProneSpeed = .5m; + [Desc("Prone movement speed as a percentage of the normal speed.")] + public readonly int SpeedModifier = 50; public readonly WVec ProneOffset = new WVec(85, 0, -171); @@ -61,14 +61,14 @@ namespace OpenRA.Mods.RA LocalOffset = WVec.Zero; } - public float GetDamageModifier(Actor attacker, DamageWarhead warhead) + public int GetDamageModifier(Actor attacker, DamageWarhead warhead) { - return IsProne && warhead != null ? warhead.ProneModifier / 100f : 1f; + return IsProne && warhead != null ? warhead.ProneModifier : 100; } - public decimal GetSpeedModifier() + public int GetSpeedModifier() { - return IsProne ? Info.ProneSpeed : 1m; + return IsProne ? Info.SpeedModifier : 100; } } diff --git a/OpenRA.Utility/UpgradeRules.cs b/OpenRA.Utility/UpgradeRules.cs index bb6ef61b7c..cffba0a7ca 100644 --- a/OpenRA.Utility/UpgradeRules.cs +++ b/OpenRA.Utility/UpgradeRules.cs @@ -26,6 +26,12 @@ namespace OpenRA.Utility input = "{0}c{1}".F(cells, subcells); } + static void ConvertFloatArrayToPercentArray(ref string input) + { + input = string.Join(", ", input.Split(',') + .Select(s => ((int)(float.Parse(s) * 100)).ToString())); + } + static void ConvertPxToRange(ref string input) { ConvertPxToRange(ref input, 1, 1); @@ -402,6 +408,34 @@ namespace OpenRA.Utility } } + // Modifiers were changed to integer percentages + if (engineVersion < 20140812) + { + if (depth == 2 && node.Key == "ClosedDamageMultiplier" && parentKey == "AttackPopupTurreted") + ConvertFloatArrayToPercentArray(ref node.Value.Value); + + if (depth == 2 && node.Key == "ArmorModifier" && parentKey == "GainsStatUpgrades") + ConvertFloatArrayToPercentArray(ref node.Value.Value); + + if (depth == 2 && node.Key == "FullyLoadedSpeed" && parentKey == "Harvester") + ConvertFloatArrayToPercentArray(ref node.Value.Value); + + if (depth == 2 && node.Key == "PanicSpeedModifier" && parentKey == "ScaredyCat") + ConvertFloatArrayToPercentArray(ref node.Value.Value); + + if (depth == 2 && node.Key == "ProneSpeed" && parentKey == "TakeCover") + { + node.Key = "SpeedModifier"; + ConvertFloatArrayToPercentArray(ref node.Value.Value); + } + + if (depth == 2 && node.Key == "SpeedModifier" && parentKey == "GainsStatUpgrades") + ConvertFloatArrayToPercentArray(ref node.Value.Value); + + if (depth == 2 && node.Key == "FirepowerModifier" && parentKey == "GainsStatUpgrades") + ConvertFloatArrayToPercentArray(ref node.Value.Value); + } + UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1); } } diff --git a/mods/cnc/rules/defaults.yaml b/mods/cnc/rules/defaults.yaml index 036ffe7060..8081af4080 100644 --- a/mods/cnc/rules/defaults.yaml +++ b/mods/cnc/rules/defaults.yaml @@ -151,7 +151,7 @@ TargetableUnit: TargetTypes: Ground, Infantry TakeCover: - ProneSpeed: 0.6 + SpeedModifier: 60 RenderInfantryProne: AttackMove: Passenger: diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml index e867dca41a..223141f59e 100644 --- a/mods/ts/rules/defaults.yaml +++ b/mods/ts/rules/defaults.yaml @@ -117,9 +117,9 @@ 500: firepower, armor, speed 1000: firepower, armor, speed GainsStatUpgrades: - FirepowerModifier: 1.2, 1.5 - ArmorModifier: 1.2, 1.5 - SpeedModifier: 1.2, 1.5 + FirepowerModifier: 120, 150 + ArmorModifier: 120, 150 + SpeedModifier: 120, 150 GivesExperience: DrawLineToTarget: ActorLostNotification: @@ -206,9 +206,9 @@ 500: firepower, armor, speed 1000: firepower, armor, speed GainsStatUpgrades: - FirepowerModifier: 1.2, 1.5 - ArmorModifier: 1.2, 1.5 - SpeedModifier: 1.2, 1.5 + FirepowerModifier: 120, 150 + ArmorModifier: 120, 150 + SpeedModifier: 120, 150 GivesExperience: DrawLineToTarget: ActorLostNotification: @@ -258,9 +258,9 @@ 500: firepower, armor, speed 1000: firepower, armor, speed GainsStatUpgrades: - FirepowerModifier: 1.2, 1.5 - ArmorModifier: 1.2, 1.5 - SpeedModifier: 1.2, 1.5 + FirepowerModifier: 120, 150 + ArmorModifier: 120, 150 + SpeedModifier: 120, 150 GivesExperience: DrawLineToTarget: ActorLostNotification: @@ -304,9 +304,9 @@ 500: firepower, armor, speed 1000: firepower, armor, speed GainsStatUpgrades: - FirepowerModifier: 1.2, 1.5 - ArmorModifier: 1.2, 1.5 - SpeedModifier: 1.2, 1.5 + FirepowerModifier: 120, 150 + ArmorModifier: 120, 150 + SpeedModifier: 120, 150 GivesExperience: DrawLineToTarget: ActorLostNotification: