diff --git a/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs b/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs index c389c2f4ad..c8dd30e49a 100644 --- a/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs +++ b/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs @@ -65,9 +65,8 @@ namespace OpenRA.Mods.Common.Activities return ActivityUtils.SequenceActivities(new HeliFly(self, newTarget)); } - // If all ammo pools are depleted and none reload automatically, return to helipad to reload and then move to next activity - // TODO: This should check whether there is ammo left that is actually suitable for the target - if (ammoPools.All(x => !x.Info.SelfReloads && !x.HasAmmo())) + // If any AmmoPool is depleted and no weapon is valid against target, return to helipad to reload and then move to next activity + if (ammoPools.Any(x => !x.Info.SelfReloads && !x.HasAmmo()) && !attackHeli.HasAnyValidWeapons(target)) return ActivityUtils.SequenceActivities(new HeliReturnToBase(self), NextActivity); var dist = target.CenterPosition - self.CenterPosition; @@ -80,14 +79,11 @@ namespace OpenRA.Mods.Common.Activities return this; // Fly towards the target - // TODO: Fix that the helicopter won't do anything if it has multiple weapons with different ranges - // and the weapon with the longest range is out of ammo - if (!target.IsInRange(self.CenterPosition, attackHeli.GetMaximumRange())) + if (!target.IsInRange(self.CenterPosition, attackHeli.GetMaximumRangeVersusTarget(target))) helicopter.SetPosition(self, helicopter.CenterPosition + helicopter.FlyStep(desiredFacing)); // Fly backwards from the target - // TODO: Same problem as with MaximumRange - if (target.IsInRange(self.CenterPosition, attackHeli.GetMinimumRange())) + if (target.IsInRange(self.CenterPosition, attackHeli.GetMinimumRangeVersusTarget(target))) { // Facing 0 doesn't work with the following position change var facing = 1; diff --git a/OpenRA.Mods.Common/Traits/Armament.cs b/OpenRA.Mods.Common/Traits/Armament.cs index a424763389..9ca212cef2 100644 --- a/OpenRA.Mods.Common/Traits/Armament.cs +++ b/OpenRA.Mods.Common/Traits/Armament.cs @@ -259,6 +259,7 @@ namespace OpenRA.Mods.Common.Traits public bool IsReloading { get { return FireDelay > 0 || IsTraitDisabled; } } public bool ShouldExplode(Actor self) { return !IsReloading; } + public bool OutOfAmmo { get { return ammoPool != null && !ammoPool.Info.SelfReloads && !ammoPool.HasAmmo(); } } public WVec MuzzleOffset(Actor self, Barrel b) { diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs index a50e61a309..924f74f89a 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs @@ -186,7 +186,7 @@ namespace OpenRA.Mods.Common.Traits // PERF: Avoid LINQ. foreach (var armament in Armaments) - if (armament.Weapon.IsValidAgainst(t, self.World, self)) + if (!armament.OutOfAmmo && armament.Weapon.IsValidAgainst(t, self.World, self)) return true; return false; @@ -203,6 +203,10 @@ namespace OpenRA.Mods.Common.Traits { if (armament.IsTraitDisabled) continue; + + if (armament.OutOfAmmo) + continue; + var range = armament.Weapon.MinRange; if (min > range) min = range; @@ -222,6 +226,62 @@ namespace OpenRA.Mods.Common.Traits { if (armament.IsTraitDisabled) continue; + + if (armament.OutOfAmmo) + continue; + + var range = armament.MaxRange(); + if (max < range) + max = range; + } + + return max; + } + + public WDist GetMinimumRangeVersusTarget(Target target) + { + if (IsTraitDisabled) + return WDist.Zero; + + // PERF: Avoid LINQ. + var min = WDist.MaxValue; + foreach (var armament in Armaments) + { + if (armament.IsTraitDisabled) + continue; + + if (armament.OutOfAmmo) + continue; + + if (!armament.Weapon.IsValidAgainst(target, self.World, self)) + continue; + + var range = armament.Weapon.MinRange; + if (min > range) + min = range; + } + + return min != WDist.MaxValue ? min : WDist.Zero; + } + + public WDist GetMaximumRangeVersusTarget(Target target) + { + if (IsTraitDisabled) + return WDist.Zero; + + // PERF: Avoid LINQ. + var max = WDist.Zero; + foreach (var armament in Armaments) + { + if (armament.IsTraitDisabled) + continue; + + if (armament.OutOfAmmo) + continue; + + if (!armament.Weapon.IsValidAgainst(target, self.World, self)) + continue; + var range = armament.MaxRange(); if (max < range) max = range; @@ -282,7 +342,7 @@ namespace OpenRA.Mods.Common.Traits public bool IsReachableTarget(Target target, bool allowMove) { return HasAnyValidWeapons(target) - && (target.IsInRange(self.CenterPosition, GetMaximumRange()) || (allowMove && self.Info.HasTraitInfo())); + && (target.IsInRange(self.CenterPosition, GetMaximumRangeVersusTarget(target)) || (allowMove && self.Info.HasTraitInfo())); } public Stance UnforcedAttackTargetStances() @@ -327,10 +387,17 @@ namespace OpenRA.Mods.Common.Traits modifiers |= TargetModifiers.ForceAttack; var forceAttack = modifiers.HasModifier(TargetModifiers.ForceAttack); - var a = ab.ChooseArmamentsForTarget(target, forceAttack).FirstOrDefault(); - if (a == null) + var armaments = ab.ChooseArmamentsForTarget(target, forceAttack); + if (!armaments.Any()) return false; + // Use valid armament with highest range out of those that have ammo + // If all are out of ammo, just use valid armament with highest range + armaments = armaments.OrderByDescending(x => x.MaxRange()); + var a = armaments.FirstOrDefault(x => !x.OutOfAmmo); + if (a == null) + a = armaments.First(); + cursor = !target.IsInRange(self.CenterPosition, a.MaxRange()) ? ab.Info.OutsideRangeCursor ?? a.Info.OutsideRangeCursor : ab.Info.Cursor ?? a.Info.Cursor; @@ -354,10 +421,17 @@ namespace OpenRA.Mods.Common.Traits return false; var target = Target.FromCell(self.World, location); - var a = ab.ChooseArmamentsForTarget(target, true).FirstOrDefault(); - if (a == null) + var armaments = ab.ChooseArmamentsForTarget(target, true); + if (!armaments.Any()) return false; + // Use valid armament with highest range out of those that have ammo + // If all are out of ammo, just use valid armament with highest range + armaments = armaments.OrderByDescending(x => x.MaxRange()); + var a = armaments.FirstOrDefault(x => !x.OutOfAmmo); + if (a == null) + a = armaments.First(); + cursor = !target.IsInRange(self.CenterPosition, a.MaxRange()) ? ab.Info.OutsideRangeCursor ?? a.Info.OutsideRangeCursor : ab.Info.Cursor ?? a.Info.Cursor;