diff --git a/OpenRA.Mods.Common/Traits/AutoTarget.cs b/OpenRA.Mods.Common/Traits/AutoTarget.cs index 1f4c856e4b..1ec70432ed 100644 --- a/OpenRA.Mods.Common/Traits/AutoTarget.cs +++ b/OpenRA.Mods.Common/Traits/AutoTarget.cs @@ -17,7 +17,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("The actor will automatically engage the enemy when it is in range.")] - public class AutoTargetInfo : ITraitInfo, Requires, UsesInit + public class AutoTargetInfo : UpgradableTraitInfo, Requires, UsesInit { [Desc("It will try to hunt down the enemy if it is not set to defend.")] public readonly bool AllowMovement = true; @@ -45,16 +45,15 @@ namespace OpenRA.Mods.Common.Traits public readonly bool TargetWhenDamaged = true; - public object Create(ActorInitializer init) { return new AutoTarget(init, this); } + public override object Create(ActorInitializer init) { return new AutoTarget(init, this); } } public enum UnitStance { HoldFire, ReturnFire, Defend, AttackAnything } - public class AutoTarget : INotifyIdle, INotifyDamage, ITick, IResolveOrder, ISync + public class AutoTarget : UpgradableTrait, INotifyIdle, INotifyDamage, ITick, IResolveOrder, ISync { - readonly AutoTargetInfo info; - readonly AttackBase attack; - readonly AttackFollow at; + readonly AttackBase[] attackBases; + readonly AttackFollow[] attackFollows; [Sync] int nextScanTime = 0; public UnitStance Stance; @@ -65,10 +64,10 @@ namespace OpenRA.Mods.Common.Traits public UnitStance PredictedStance; public AutoTarget(ActorInitializer init, AutoTargetInfo info) + : base(info) { var self = init.Self; - this.info = info; - attack = self.Trait(); + attackBases = self.TraitsImplementing().ToArray(); if (init.Contains()) Stance = init.Get(); @@ -76,18 +75,21 @@ namespace OpenRA.Mods.Common.Traits Stance = self.Owner.IsBot || !self.Owner.Playable ? info.InitialStanceAI : info.InitialStance; PredictedStance = Stance; - at = self.TraitOrDefault(); + attackFollows = self.TraitsImplementing().ToArray(); } public void ResolveOrder(Actor self, Order order) { - if (order.OrderString == "SetUnitStance" && info.EnableStances) + if (order.OrderString == "SetUnitStance" && Info.EnableStances) Stance = (UnitStance)order.ExtraData; } public void Damaged(Actor self, AttackInfo e) { - if (!self.IsIdle || !info.TargetWhenDamaged) + if (IsTraitDisabled) + return; + + if (!self.IsIdle || !Info.TargetWhenDamaged) return; var attacker = e.Attacker; @@ -103,7 +105,7 @@ namespace OpenRA.Mods.Common.Traits } // not a lot we can do about things we can't hurt... although maybe we should automatically run away? - if (!attack.HasAnyValidWeapons(Target.FromActor(attacker))) + if (attackBases.All(a => a.IsTraitDisabled || !a.HasAnyValidWeapons(Target.FromActor(attacker)))) return; // don't retaliate against own units force-firing on us. It's usually not what the player wanted. @@ -115,39 +117,51 @@ namespace OpenRA.Mods.Common.Traits return; Aggressor = attacker; - var allowMove = info.AllowMovement && Stance != UnitStance.Defend; - if (at == null || !at.IsReachableTarget(at.Target, allowMove)) + var allowMove = Info.AllowMovement && Stance != UnitStance.Defend; + if (attackFollows.All(a => a.IsTraitDisabled || !a.IsReachableTarget(a.Target, allowMove))) Attack(self, Aggressor, allowMove); } public void TickIdle(Actor self) { - if (Stance < UnitStance.Defend || !info.TargetWhenIdle) + if (IsTraitDisabled) return; - var allowMove = info.AllowMovement && Stance != UnitStance.Defend; - if (at == null || !at.IsReachableTarget(at.Target, allowMove)) + if (Stance < UnitStance.Defend || !Info.TargetWhenIdle) + return; + + var allowMove = Info.AllowMovement && Stance != UnitStance.Defend; + if (attackFollows.All(a => a.IsTraitDisabled || !a.IsReachableTarget(a.Target, allowMove))) ScanAndAttack(self, allowMove); } public void Tick(Actor self) { + if (IsTraitDisabled) + return; + if (nextScanTime > 0) --nextScanTime; } public Actor ScanForTarget(Actor self, bool allowMove) { - if (nextScanTime <= 0) + var activeAttackBases = attackBases.Where(Exts.IsTraitEnabled); + if (activeAttackBases.Any() && nextScanTime <= 0) { - nextScanTime = self.World.SharedRandom.Next(info.MinimumScanTimeInterval, info.MaximumScanTimeInterval); + nextScanTime = self.World.SharedRandom.Next(Info.MinimumScanTimeInterval, Info.MaximumScanTimeInterval); - // If we can't attack right now, there's no need to try and find a target. - var attackStances = attack.UnforcedAttackTargetStances(); - if (attackStances != OpenRA.Traits.Stance.None) + foreach (var ab in activeAttackBases) { - var range = info.ScanRadius > 0 ? WDist.FromCells(info.ScanRadius) : attack.GetMaximumRange(); - return ChooseTarget(self, attackStances, range, allowMove); + // If we can't attack right now, there's no need to try and find a target. + var attackStances = ab.UnforcedAttackTargetStances(); + if (attackStances != OpenRA.Traits.Stance.None) + { + var range = Info.ScanRadius > 0 ? WDist.FromCells(Info.ScanRadius) : ab.GetMaximumRange(); + return ChooseTarget(self, ab, attackStances, range, allowMove); + } + + continue; } } @@ -166,10 +180,13 @@ namespace OpenRA.Mods.Common.Traits TargetedActor = targetActor; var target = Target.FromActor(targetActor); self.SetTargetLine(target, Color.Red, false); - attack.AttackTarget(target, false, allowMove); + + var activeAttackBases = attackBases.Where(Exts.IsTraitEnabled); + foreach (var ab in activeAttackBases) + ab.AttackTarget(target, false, allowMove); } - Actor ChooseTarget(Actor self, Stance attackStances, WDist range, bool allowMove) + Actor ChooseTarget(Actor self, AttackBase ab, Stance attackStances, WDist range, bool allowMove) { var actorsByArmament = new Dictionary>(); var actorsInRange = self.World.FindActorsInCircle(self.CenterPosition, range); @@ -189,7 +206,7 @@ namespace OpenRA.Mods.Common.Traits // it will be thanks to the first armament anyways, since that is the first selection // criterion var target = Target.FromActor(actor); - var armaments = attack.ChooseArmamentsForTarget(target, false); + var armaments = ab.ChooseArmamentsForTarget(target, false); if (!allowMove) armaments = armaments.Where(arm => target.IsInRange(self.CenterPosition, arm.MaxRange()) && @@ -211,7 +228,7 @@ namespace OpenRA.Mods.Common.Traits // And then according to distance from actor // This enables preferential treatment of certain armaments // (e.g. tesla trooper's tesla zap should have precedence over tesla charge) - foreach (var arm in attack.Armaments) + foreach (var arm in ab.Armaments) { List actors; if (actorsByArmament.TryGetValue(arm, out actors)) diff --git a/mods/ts/rules/nod-vehicles.yaml b/mods/ts/rules/nod-vehicles.yaml index 3141529ed8..8d4ec54367 100644 --- a/mods/ts/rules/nod-vehicles.yaml +++ b/mods/ts/rules/nod-vehicles.yaml @@ -162,6 +162,7 @@ TTNK: Type: Concrete UpgradeTypes: deployed UpgradeMinEnabledLevel: 1 + AutoTarget: ART2: Inherits: ^VoxelTank