From 2f1a6e88073742ac795ac997a1c4f67bc1fb2a42 Mon Sep 17 00:00:00 2001 From: reaperrr Date: Thu, 16 Jun 2016 15:22:38 +0200 Subject: [PATCH 1/4] Add maximum and minimum range vs. target checks This adresses the issue that actors would ignore the validity of weapons when deciding the attack distance versus a target. This could result in actors staying out of range of a weapon valid versus target if they carried a weapon with higher range but invalid versus target. --- .../Activities/Air/HeliAttack.cs | 4 +- .../Traits/Attack/AttackBase.cs | 48 ++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs b/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs index c389c2f4ad..1cfc95144d 100644 --- a/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs +++ b/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs @@ -82,12 +82,12 @@ namespace OpenRA.Mods.Common.Activities // 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/Attack/AttackBase.cs b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs index a50e61a309..bbceaae98c 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs @@ -230,6 +230,52 @@ namespace OpenRA.Mods.Common.Traits 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.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.Weapon.IsValidAgainst(target, self.World, self)) + continue; + + var range = armament.MaxRange(); + if (max < range) + max = range; + } + + return max; + } + // Enumerates all armaments, that this actor possesses, that can be used against Target t public IEnumerable ChooseArmamentsForTarget(Target t, bool forceAttack) { @@ -282,7 +328,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() From 4f55b088eb7a342445cf57bbd64326f7244152b5 Mon Sep 17 00:00:00 2001 From: reaperrr Date: Fri, 17 Jun 2016 13:14:15 +0200 Subject: [PATCH 2/4] Check if weapon without self-reloading is out of ammo when deciding validity This should prevent attacking helicopters (or other actors) staying at the range to target of a weapon that is out of ammo and needs manual reloading at a RearmBuilding or via upgrades. --- OpenRA.Mods.Common/Activities/Air/HeliAttack.cs | 3 --- OpenRA.Mods.Common/Traits/Armament.cs | 1 + OpenRA.Mods.Common/Traits/Attack/AttackBase.cs | 16 +++++++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs b/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs index 1cfc95144d..9f245000bf 100644 --- a/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs +++ b/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs @@ -80,13 +80,10 @@ 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.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.GetMinimumRangeVersusTarget(target))) { // Facing 0 doesn't work with the following position change 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 bbceaae98c..e570cfbbd6 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,10 @@ namespace OpenRA.Mods.Common.Traits { if (armament.IsTraitDisabled) continue; + + if (armament.OutOfAmmo) + continue; + var range = armament.MaxRange(); if (max < range) max = range; @@ -242,6 +250,9 @@ namespace OpenRA.Mods.Common.Traits if (armament.IsTraitDisabled) continue; + if (armament.OutOfAmmo) + continue; + if (!armament.Weapon.IsValidAgainst(target, self.World, self)) continue; @@ -265,6 +276,9 @@ namespace OpenRA.Mods.Common.Traits if (armament.IsTraitDisabled) continue; + if (armament.OutOfAmmo) + continue; + if (!armament.Weapon.IsValidAgainst(target, self.World, self)) continue; From 40e5dfbedb4d4248ad3200ee45146c233e5bc6d7 Mon Sep 17 00:00:00 2001 From: reaperrr Date: Fri, 17 Jun 2016 13:16:34 +0200 Subject: [PATCH 3/4] Fix helicopters not returning when all valid weapons are out of ammo They now return if all weapons are invalid (which includes those that are otherwise valid, but out of ammo) and at least one AmmoPool needs reloading at RearmBuilding. --- OpenRA.Mods.Common/Activities/Air/HeliAttack.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs b/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs index 9f245000bf..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; From 27993729be2fe9ff80578337d97ef3e43bead559 Mon Sep 17 00:00:00 2001 From: reaperrr Date: Thu, 23 Jun 2016 14:46:37 +0200 Subject: [PATCH 4/4] Make attack cursor smarter Rather than simply taking the first valid armament, regardless of available ammo and regardless of which valid armament has the highest range, the attack cursor is now chosen a) by whether the armament has ammo and b) by which valid armament has the highest range. --- .../Traits/Attack/AttackBase.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs index e570cfbbd6..924f74f89a 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs @@ -387,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; @@ -414,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;