diff --git a/OpenRA.Game/GameRules/WeaponInfo.cs b/OpenRA.Game/GameRules/WeaponInfo.cs index 00838d70a8..e512c8c153 100644 --- a/OpenRA.Game/GameRules/WeaponInfo.cs +++ b/OpenRA.Game/GameRules/WeaponInfo.cs @@ -21,6 +21,7 @@ namespace OpenRA.GameRules public WeaponInfo Weapon; public int[] DamageModifiers; public int[] InaccuracyModifiers; + public int[] RangeModifiers; public int Facing; public WPos Source; public Actor SourceActor; diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index a66d204fbd..6e2278eb8b 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -218,6 +218,8 @@ namespace OpenRA.Traits public interface IFirepowerModifier { int GetFirepowerModifier(); } public interface IReloadModifier { int GetReloadModifier(); } public interface IInaccuracyModifier { int GetInaccuracyModifier(); } + public interface IRangeModifier { int GetRangeModifier(); } + public interface IRangeModifierInfo : ITraitInfo { int GetRangeModifierDefault(); } public interface IPowerModifier { int GetPowerModifier(); } public interface ILoadsPalettes { void LoadPalettes(WorldRenderer wr); } public interface ILoadsPlayerPalettes { void LoadPlayerPalettes(WorldRenderer wr, string playerName, HSLColor playerColor, bool replaceExisting); } diff --git a/OpenRA.Mods.Common/Activities/Attack.cs b/OpenRA.Mods.Common/Activities/Attack.cs index d48334ffea..d92e2b487e 100644 --- a/OpenRA.Mods.Common/Activities/Attack.cs +++ b/OpenRA.Mods.Common/Activities/Attack.cs @@ -21,15 +21,15 @@ namespace OpenRA.Mods.Common.Activities readonly AttackBase attack; readonly IMove move; readonly IFacing facing; + readonly Armament armament; readonly WDist minRange; - readonly WDist maxRange; readonly IPositionable positionable; public Attack(Actor self, Target target, Armament armament, bool allowMovement) { Target = target; this.minRange = armament.Weapon.MinRange; - this.maxRange = armament.Weapon.Range; + this.armament = armament; attack = self.Trait(); facing = self.Trait(); @@ -65,6 +65,7 @@ namespace OpenRA.Mods.Common.Activities return NextActivity; // Try to move within range + var maxRange = armament.MaxRange(); if (move != null && (!Target.IsInRange(self.CenterPosition, maxRange) || Target.IsInRange(self.CenterPosition, minRange))) return Util.SequenceActivities(move.MoveWithinRange(Target, minRange, maxRange), this); diff --git a/OpenRA.Mods.Common/Effects/Bullet.cs b/OpenRA.Mods.Common/Effects/Bullet.cs index ac65568ad7..92199ee377 100644 --- a/OpenRA.Mods.Common/Effects/Bullet.cs +++ b/OpenRA.Mods.Common/Effects/Bullet.cs @@ -108,7 +108,8 @@ namespace OpenRA.Mods.Common.Effects if (info.Inaccuracy.Length > 0) { var inaccuracy = OpenRA.Traits.Util.ApplyPercentageModifiers(info.Inaccuracy.Length, args.InaccuracyModifiers); - var maxOffset = inaccuracy * (target - pos).Length / args.Weapon.Range.Length; + var range = OpenRA.Traits.Util.ApplyPercentageModifiers(args.Weapon.Range.Length, args.RangeModifiers); + var maxOffset = inaccuracy * (target - pos).Length / range; target += WVec.FromPDF(world.SharedRandom, 2) * maxOffset / 1024; } diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 7f6549ee7d..b43c5df25f 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -365,6 +365,7 @@ + diff --git a/OpenRA.Mods.Common/Traits/Air/AttackBomber.cs b/OpenRA.Mods.Common/Traits/Air/AttackBomber.cs index 15350deeee..3bdc737c13 100644 --- a/OpenRA.Mods.Common/Traits/Air/AttackBomber.cs +++ b/OpenRA.Mods.Common/Traits/Air/AttackBomber.cs @@ -60,7 +60,7 @@ namespace OpenRA.Mods.Common.Traits // Bombs drop anywhere in range foreach (var a in Armaments.Where(a => a.Info.Name == info.Bombs)) { - if (!target.IsInRange(self.CenterPosition, a.Weapon.Range)) + if (!target.IsInRange(self.CenterPosition, a.MaxRange())) continue; inAttackRange = true; @@ -72,10 +72,10 @@ namespace OpenRA.Mods.Common.Traits { foreach (var a in Armaments.Where(a => a.Info.Name == info.Guns)) { - if (!target.IsInRange(self.CenterPosition, a.Weapon.Range)) + if (!target.IsInRange(self.CenterPosition, a.MaxRange())) continue; - var t = Target.FromPos(cp - new WVec(0, a.Weapon.Range.Length / 2, cp.Z).Rotate(WRot.FromFacing(f))); + var t = Target.FromPos(cp - new WVec(0, a.MaxRange().Length / 2, cp.Z).Rotate(WRot.FromFacing(f))); inAttackRange = true; a.CheckFire(self, facing.Value, t); } diff --git a/OpenRA.Mods.Common/Traits/Armament.cs b/OpenRA.Mods.Common/Traits/Armament.cs index c26feedb22..b65250ea2a 100644 --- a/OpenRA.Mods.Common/Traits/Armament.cs +++ b/OpenRA.Mods.Common/Traits/Armament.cs @@ -24,7 +24,7 @@ namespace OpenRA.Mods.Common.Traits } [Desc("Allows you to attach weapons to the unit (use @IdentifierSuffix for > 1)")] - public class ArmamentInfo : UpgradableTraitInfo, Requires + public class ArmamentInfo : UpgradableTraitInfo, IRulesetLoaded, Requires { public readonly string Name = "primary"; @@ -62,7 +62,18 @@ namespace OpenRA.Mods.Common.Traits [Desc("Use multiple muzzle images if non-zero")] public readonly int MuzzleSplitFacings = 0; + public WeaponInfo WeaponInfo { get; private set; } + public WDist ModifiedRange { get; private set; } + public override object Create(ActorInitializer init) { return new Armament(init.Self, this); } + + public void RulesetLoaded(Ruleset rules, ActorInfo ai) + { + WeaponInfo = rules.Weapons[Weapon.ToLowerInvariant()]; + ModifiedRange = new WDist(Util.ApplyPercentageModifiers( + WeaponInfo.Range.Length, + ai.TraitInfos().Select(m => m.GetRangeModifierDefault()))); + } } public class Armament : UpgradableTrait, ITick, IExplodeModifier @@ -75,6 +86,7 @@ namespace OpenRA.Mods.Common.Traits Lazy coords; Lazy ammoPool; List> delayedActions = new List>(); + Lazy> rangeModifiers; public WDist Recoil; public int FireDelay { get; private set; } @@ -89,8 +101,9 @@ namespace OpenRA.Mods.Common.Traits turret = Exts.Lazy(() => self.TraitsImplementing().FirstOrDefault(t => t.Name == info.Turret)); coords = Exts.Lazy(() => self.Trait()); ammoPool = Exts.Lazy(() => self.TraitsImplementing().FirstOrDefault(la => la.Info.Name == info.AmmoPoolName)); + rangeModifiers = Exts.Lazy(() => self.TraitsImplementing().ToArray().Select(m => m.GetRangeModifier())); - Weapon = self.World.Map.Rules.Weapons[info.Weapon.ToLowerInvariant()]; + Weapon = info.WeaponInfo; Burst = Weapon.Burst; var barrels = new List(); @@ -109,6 +122,11 @@ namespace OpenRA.Mods.Common.Traits Barrels = barrels.ToArray(); } + public WDist MaxRange() + { + return new WDist(Util.ApplyPercentageModifiers(Weapon.Range.Length, rangeModifiers.Value)); + } + public void Tick(Actor self) { if (IsTraitDisabled) @@ -148,7 +166,7 @@ namespace OpenRA.Mods.Common.Traits if (ammoPool.Value != null && !ammoPool.Value.HasAmmo()) return null; - if (!target.IsInRange(self.CenterPosition, Weapon.Range)) + if (!target.IsInRange(self.CenterPosition, MaxRange())) return null; if (Weapon.MinRange != WDist.Zero && target.IsInRange(self.CenterPosition, Weapon.MinRange)) @@ -172,6 +190,9 @@ namespace OpenRA.Mods.Common.Traits InaccuracyModifiers = self.TraitsImplementing() .Select(a => a.GetInaccuracyModifier()).ToArray(), + RangeModifiers = self.TraitsImplementing() + .Select(a => a.GetRangeModifier()).ToArray(), + Source = muzzlePosition, SourceActor = self, PassiveTarget = target.CenterPosition, diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs index ac9e214668..24d2a14f50 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs @@ -179,7 +179,7 @@ namespace OpenRA.Mods.Common.Traits return WDist.Zero; return Armaments.Where(a => !a.IsTraitDisabled) - .Select(a => a.Weapon.Range) + .Select(a => a.MaxRange()) .Append(WDist.Zero).Max(); } @@ -227,7 +227,7 @@ namespace OpenRA.Mods.Common.Traits IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); var a = ab.ChooseArmamentForTarget(target); - cursor = a != null && !target.IsInRange(self.CenterPosition, a.Weapon.Range) + cursor = a != null && !target.IsInRange(self.CenterPosition, a.MaxRange()) ? ab.Info.OutsideRangeCursor : ab.Info.Cursor; diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs b/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs index d992065111..2ac3e2372d 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs @@ -85,8 +85,9 @@ namespace OpenRA.Mods.Common.Traits || (target.Type == TargetType.FrozenActor && target.FrozenActor.Info.HasTraitInfo()); // Try and sit at least one cell closer than the max range to give some leeway if the target starts moving. - var maxRange = targetIsMobile ? new WDist(Math.Max(weapon.Weapon.MinRange.Length, weapon.Weapon.Range.Length - 1024)) - : weapon.Weapon.Range; + var modifiedRange = weapon.MaxRange(); + var maxRange = targetIsMobile ? new WDist(Math.Max(weapon.Weapon.MinRange.Length, modifiedRange.Length - 1024)) + : modifiedRange; attack.Target = target; diff --git a/OpenRA.Mods.Common/Traits/Multipliers/RangeMultiplier.cs b/OpenRA.Mods.Common/Traits/Multipliers/RangeMultiplier.cs new file mode 100644 index 0000000000..4995bd0665 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Multipliers/RangeMultiplier.cs @@ -0,0 +1,37 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 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.Linq; +using OpenRA; +using OpenRA.GameRules; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Range of this actor is multiplied based on upgrade level.")] + public class RangeMultiplierInfo : UpgradeMultiplierTraitInfo, IRangeModifierInfo + { + public override object Create(ActorInitializer init) { return new RangeMultiplier(this, init.Self.Info.Name); } + + public int GetRangeModifierDefault() + { + return BaseLevel > 0 || UpgradeTypes.Length == 0 ? 100 : Modifier[0]; + } + } + + public class RangeMultiplier : UpgradeMultiplierTrait, IRangeModifier + { + public RangeMultiplier(RangeMultiplierInfo info, string actorType) + : base(info, "RangeMultiplier", actorType) { } + + public int GetRangeModifier() { return GetModifier(); } + } +} diff --git a/OpenRA.Mods.Common/Traits/Render/RenderRangeCircle.cs b/OpenRA.Mods.Common/Traits/Render/RenderRangeCircle.cs index 5df4978fbe..d04c628042 100644 --- a/OpenRA.Mods.Common/Traits/Render/RenderRangeCircle.cs +++ b/OpenRA.Mods.Common/Traits/Render/RenderRangeCircle.cs @@ -18,22 +18,18 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Draw a circle indicating my weapon's range.")] - class RenderRangeCircleInfo : ITraitInfo, IPlaceBuildingDecorationInfo, Requires + class RenderRangeCircleInfo : ITraitInfo, IPlaceBuildingDecorationInfo, IRulesetLoaded, Requires { public readonly string RangeCircleType = null; [Desc("Range to draw if no armaments are available")] public readonly WDist FallbackRange = WDist.Zero; + // Computed range + WDist range; + public IEnumerable Render(WorldRenderer wr, World w, ActorInfo ai, WPos centerPosition) { - var armaments = ai.TraitInfos() - .Where(a => a.UpgradeMinEnabledLevel == 0); - var range = FallbackRange; - - if (armaments.Any()) - range = armaments.Select(a => w.Map.Rules.Weapons[a.Weapon.ToLowerInvariant()].Range).Max(); - if (range == WDist.Zero) yield break; @@ -52,6 +48,15 @@ namespace OpenRA.Mods.Common.Traits } public object Create(ActorInitializer init) { return new RenderRangeCircle(init.Self); } + public void RulesetLoaded(Ruleset rules, ActorInfo ai) + { + var armaments = ai.TraitInfos().Where(a => a.UpgradeMinEnabledLevel == 0); + + if (armaments.Any()) + range = armaments.Select(a => a.ModifiedRange).Max(); + else + range = FallbackRange; + } } class RenderRangeCircle : IPostRenderSelection diff --git a/OpenRA.Mods.Common/Traits/ThrowsShrapnel.cs b/OpenRA.Mods.Common/Traits/ThrowsShrapnel.cs index da862fae0a..9fb377d3b7 100644 --- a/OpenRA.Mods.Common/Traits/ThrowsShrapnel.cs +++ b/OpenRA.Mods.Common/Traits/ThrowsShrapnel.cs @@ -55,6 +55,9 @@ namespace OpenRA.Mods.Common.Traits InaccuracyModifiers = self.TraitsImplementing() .Select(a => a.GetInaccuracyModifier()).ToArray(), + RangeModifiers = self.TraitsImplementing() + .Select(a => a.GetRangeModifier()).ToArray(), + Source = self.CenterPosition, SourceActor = self, PassiveTarget = self.CenterPosition + new WVec(range, 0, 0).Rotate(rotation) diff --git a/OpenRA.Mods.D2k/Traits/AttackSwallow.cs b/OpenRA.Mods.D2k/Traits/AttackSwallow.cs index a6e8600f1e..931c37b7db 100644 --- a/OpenRA.Mods.D2k/Traits/AttackSwallow.cs +++ b/OpenRA.Mods.D2k/Traits/AttackSwallow.cs @@ -52,7 +52,7 @@ namespace OpenRA.Mods.D2k.Traits if (a == null) return; - if (!target.IsInRange(self.CenterPosition, a.Weapon.Range)) + if (!target.IsInRange(self.CenterPosition, a.MaxRange())) return; self.CancelActivity(); diff --git a/OpenRA.Mods.RA/Traits/Attack/AttackLeap.cs b/OpenRA.Mods.RA/Traits/Attack/AttackLeap.cs index 8965dd47fa..f615d5000f 100644 --- a/OpenRA.Mods.RA/Traits/Attack/AttackLeap.cs +++ b/OpenRA.Mods.RA/Traits/Attack/AttackLeap.cs @@ -43,7 +43,7 @@ namespace OpenRA.Mods.RA.Traits if (a == null) return; - if (!target.IsInRange(self.CenterPosition, a.Weapon.Range)) + if (!target.IsInRange(self.CenterPosition, a.MaxRange())) return; self.CancelActivity();