diff --git a/OpenRA.Mods.Common/Activities/Attack.cs b/OpenRA.Mods.Common/Activities/Attack.cs index d92e2b487e..2ec721b818 100644 --- a/OpenRA.Mods.Common/Activities/Attack.cs +++ b/OpenRA.Mods.Common/Activities/Attack.cs @@ -8,6 +8,7 @@ */ #endregion +using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Traits; using OpenRA.Traits; @@ -21,15 +22,17 @@ namespace OpenRA.Mods.Common.Activities readonly AttackBase attack; readonly IMove move; readonly IFacing facing; - readonly Armament armament; - readonly WDist minRange; readonly IPositionable positionable; + readonly bool forceAttack; - public Attack(Actor self, Target target, Armament armament, bool allowMovement) + WDist minRange; + WDist maxRange; + + public Attack(Actor self, Target target, bool allowMovement, bool forceAttack) { Target = target; - this.minRange = armament.Weapon.MinRange; - this.armament = armament; + + this.forceAttack = forceAttack; attack = self.Trait(); facing = self.Trait(); @@ -64,8 +67,16 @@ namespace OpenRA.Mods.Common.Activities if (!attack.Info.IgnoresVisibility && type == TargetType.Actor && !Target.Actor.Info.HasTraitInfo() && !self.Owner.CanTargetActor(Target.Actor)) return NextActivity; + // Drop the target once none of the weapons are effective against it + var armaments = attack.ChooseArmamentsForTarget(Target, forceAttack); + if (!armaments.Any()) + return NextActivity; + + // Update ranges + minRange = armaments.Max(a => a.Weapon.MinRange); + maxRange = armaments.Min(a => a.MaxRange()); + // 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); @@ -73,7 +84,7 @@ namespace OpenRA.Mods.Common.Activities if (facing.Facing != desiredFacing) return Util.SequenceActivities(new Turn(self, desiredFacing), this); - attack.DoAttack(self, Target); + attack.DoAttack(self, Target, armaments); return this; } diff --git a/OpenRA.Mods.Common/Activities/Heal.cs b/OpenRA.Mods.Common/Activities/Heal.cs deleted file mode 100644 index 157dea71ab..0000000000 --- a/OpenRA.Mods.Common/Activities/Heal.cs +++ /dev/null @@ -1,33 +0,0 @@ -#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 OpenRA.Activities; -using OpenRA.Mods.Common.Traits; -using OpenRA.Traits; - -namespace OpenRA.Mods.Common.Activities -{ - public class Heal : Attack - { - public Heal(Actor self, Target target, Armament armament, bool allowMovement) - : base(self, target, armament, allowMovement) { } - - protected override Activity InnerTick(Actor self, AttackBase attack) - { - if (!Target.IsValidFor(self) || !self.Owner.IsAlliedWith(Target.Actor.Owner)) - return NextActivity; - - if (Target.Type == TargetType.Actor && Target.Actor.GetDamageState() == DamageState.Undamaged) - return NextActivity; - - return base.InnerTick(self, attack); - } - } -} diff --git a/OpenRA.Mods.Common/Activities/Move/AttackMoveActivity.cs b/OpenRA.Mods.Common/Activities/Move/AttackMoveActivity.cs index e76f667e02..f8fe4b0dbb 100644 --- a/OpenRA.Mods.Common/Activities/Move/AttackMoveActivity.cs +++ b/OpenRA.Mods.Common/Activities/Move/AttackMoveActivity.cs @@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.Activities { if (autoTarget != null && --scanTicks <= 0) { - autoTarget.ScanAndAttack(self); + autoTarget.ScanAndAttack(self, true); scanTicks = ScanInterval; } diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 19a1a42820..636ca30b10 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -117,7 +117,6 @@ - @@ -268,11 +267,9 @@ - - diff --git a/OpenRA.Mods.Common/Traits/Air/AttackBomber.cs b/OpenRA.Mods.Common/Traits/Air/AttackBomber.cs index 3bdc737c13..3a4fdd9c93 100644 --- a/OpenRA.Mods.Common/Traits/Air/AttackBomber.cs +++ b/OpenRA.Mods.Common/Traits/Air/AttackBomber.cs @@ -99,7 +99,7 @@ namespace OpenRA.Mods.Common.Traits OnRemovedFromWorld(self); } - public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) + public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack) { throw new NotImplementedException("AttackBomber requires a scripted target"); } diff --git a/OpenRA.Mods.Common/Traits/Air/AttackHeli.cs b/OpenRA.Mods.Common/Traits/Air/AttackHeli.cs index 42c10ac2dc..a31c48a056 100644 --- a/OpenRA.Mods.Common/Traits/Air/AttackHeli.cs +++ b/OpenRA.Mods.Common/Traits/Air/AttackHeli.cs @@ -24,7 +24,7 @@ namespace OpenRA.Mods.Common.Traits public AttackHeli(Actor self, AttackHeliInfo info) : base(self, info) { } - public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) + public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack) { return new HeliAttack(self, newTarget); } diff --git a/OpenRA.Mods.Common/Traits/Air/AttackPlane.cs b/OpenRA.Mods.Common/Traits/Air/AttackPlane.cs index 9a29479cbc..c70de07101 100644 --- a/OpenRA.Mods.Common/Traits/Air/AttackPlane.cs +++ b/OpenRA.Mods.Common/Traits/Air/AttackPlane.cs @@ -32,7 +32,7 @@ namespace OpenRA.Mods.Common.Traits AttackPlaneInfo = info; } - public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) + public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack) { return new FlyAttack(self, newTarget); } diff --git a/OpenRA.Mods.Common/Traits/Armament.cs b/OpenRA.Mods.Common/Traits/Armament.cs index b65250ea2a..4b86abe678 100644 --- a/OpenRA.Mods.Common/Traits/Armament.cs +++ b/OpenRA.Mods.Common/Traits/Armament.cs @@ -65,6 +65,17 @@ namespace OpenRA.Mods.Common.Traits public WeaponInfo WeaponInfo { get; private set; } public WDist ModifiedRange { get; private set; } + public readonly Stance TargetStances = Stance.Enemy; + public readonly Stance ForceTargetStances = Stance.Enemy | Stance.Neutral | Stance.Ally; + + // TODO: instead of having multiple Armaments and unique AttackBase, + // an actor should be able to have multiple AttackBases with + // a single corresponding Armament each + public readonly string Cursor = "attack"; + + // TODO: same as above + public readonly string OutsideRangeCursor = "attackoutsiderange"; + public override object Create(ActorInitializer init) { return new Armament(init.Self, this); } public void RulesetLoaded(Ruleset rules, ActorInfo ai) diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs index 24d2a14f50..187e323bb9 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs @@ -23,8 +23,9 @@ namespace OpenRA.Mods.Common.Traits [Desc("Armament names")] public readonly string[] Armaments = { "primary", "secondary" }; - public readonly string Cursor = "attack"; - public readonly string OutsideRangeCursor = "attackoutsiderange"; + public readonly string Cursor = null; + + public readonly string OutsideRangeCursor = null; [Desc("Does the attack type require the attacker to enter the target's cell?")] public readonly bool AttackRequiresEnteringCell = false; @@ -39,6 +40,9 @@ namespace OpenRA.Mods.Common.Traits public abstract class AttackBase : UpgradableTrait, IIssueOrder, IResolveOrder, IOrderVoice, ISync { + readonly string attackOrderName = "Attack"; + readonly string forceAttackOrderName = "ForceAttack"; + [Sync] public bool IsAttacking { get; internal set; } public IEnumerable Armaments { get { return getArmaments(); } } @@ -88,12 +92,12 @@ namespace OpenRA.Mods.Common.Traits return true; } - public virtual void DoAttack(Actor self, Target target) + public virtual void DoAttack(Actor self, Target target, IEnumerable armaments = null) { - if (!CanAttack(self, target)) + if (armaments == null && !CanAttack(self, target)) return; - foreach (var a in Armaments) + foreach (var a in armaments ?? Armaments) a.CheckFire(self, facing.Value, target); } @@ -109,7 +113,7 @@ namespace OpenRA.Mods.Common.Traits yield break; var negativeDamage = (armament.Weapon.Warheads.FirstOrDefault(w => (w is DamageWarhead)) as DamageWarhead).Damage < 0; - yield return new AttackOrderTargeter(this, "Attack", 6, negativeDamage); + yield return new AttackOrderTargeter(this, 6, negativeDamage); } } @@ -120,11 +124,11 @@ namespace OpenRA.Mods.Common.Traits switch (target.Type) { case TargetType.Actor: - return new Order("Attack", self, queued) { TargetActor = target.Actor }; + return new Order(order.OrderID, self, queued) { TargetActor = target.Actor }; case TargetType.FrozenActor: - return new Order("Attack", self, queued) { ExtraData = target.FrozenActor.ID }; + return new Order(order.OrderID, self, queued) { ExtraData = target.FrozenActor.ID }; case TargetType.Terrain: - return new Order("Attack", self, queued) { TargetLocation = self.World.Map.CellContaining(target.CenterPosition) }; + return new Order(order.OrderID, self, queued) { TargetLocation = self.World.Map.CellContaining(target.CenterPosition) }; } } @@ -133,23 +137,46 @@ namespace OpenRA.Mods.Common.Traits public virtual void ResolveOrder(Actor self, Order order) { - if (order.OrderString == "Attack") + var forceAttack = order.OrderString == forceAttackOrderName; + if (forceAttack || order.OrderString == attackOrderName) { var target = self.ResolveFrozenActorOrder(order, Color.Red); if (!target.IsValidFor(self)) return; self.SetTargetLine(target, Color.Red); - AttackTarget(target, order.Queued, true); + AttackTarget(target, order.Queued, true, forceAttack); } } + static Target TargetFromOrder(Actor self, Order order) + { + // Not targeting a frozen actor + if (order.ExtraData == 0) + return Target.FromOrder(self.World, order); + + // Targeted an actor under the fog + var frozenLayer = self.Owner.PlayerActor.TraitOrDefault(); + if (frozenLayer == null) + return Target.Invalid; + + var frozen = frozenLayer.FromID(order.ExtraData); + if (frozen == null) + return Target.Invalid; + + // Target is still alive - resolve the real order + if (frozen.Actor != null && frozen.Actor.IsInWorld) + return Target.FromActor(frozen.Actor); + + return Target.Invalid; + } + public string VoicePhraseForOrder(Actor self, Order order) { - return order.OrderString == "Attack" ? Info.Voice : null; + return order.OrderString == attackOrderName || order.OrderString == forceAttackOrderName ? Info.Voice : null; } - public abstract Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove); + public abstract Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack); public bool HasAnyValidWeapons(Target t) { @@ -183,9 +210,30 @@ namespace OpenRA.Mods.Common.Traits .Append(WDist.Zero).Max(); } - public Armament ChooseArmamentForTarget(Target t) { return Armaments.FirstOrDefault(a => a.Weapon.IsValidAgainst(t, self.World, self)); } + // Enumerates all armaments, that this actor possesses, that can be used against Target t + public IEnumerable ChooseArmamentsForTarget(Target t, bool forceAttack, bool onlyEnabled = true) + { + // If force-fire is not used, and the target requires force-firing or the target is + // terrain or invalid, no armaments can be used + if (!forceAttack && (t.RequiresForceFire || t.Type == TargetType.Terrain || t.Type == TargetType.Invalid)) + return Enumerable.Empty(); - public void AttackTarget(Target target, bool queued, bool allowMove) + // Get target's owner; in case of terrain or invalid target there will be no problems + // with owner == null since forceFire will have to be true in this part of the method + // (short-circuiting in the logical expression below) + var owner = null as Player; + if (t.Type == TargetType.FrozenActor) + owner = t.FrozenActor.Owner; + else if (t.Type == TargetType.Actor) + owner = t.Actor.Owner; + + return Armaments.Where(a => (!a.IsTraitDisabled || !onlyEnabled) && + a.Weapon.IsValidAgainst(t, self.World, self) && + (owner == null || (forceAttack ? a.Info.ForceTargetStances : a.Info.TargetStances) + .HasStance(self.Owner.Stances[owner]))); + } + + public void AttackTarget(Target target, bool queued, bool allowMove, bool forceAttack = false) { if (self.IsDisabled() || IsTraitDisabled) return; @@ -196,7 +244,7 @@ namespace OpenRA.Mods.Common.Traits if (!queued) self.CancelActivity(); - self.QueueActivity(GetAttackActivity(self, target, allowMove)); + self.QueueActivity(GetAttackActivity(self, target, allowMove, forceAttack)); } public bool IsReachableTarget(Target target, bool allowMove) @@ -207,15 +255,13 @@ namespace OpenRA.Mods.Common.Traits class AttackOrderTargeter : IOrderTargeter { - readonly bool negativeDamage; readonly AttackBase ab; - public AttackOrderTargeter(AttackBase ab, string order, int priority, bool negativeDamage) + public AttackOrderTargeter(AttackBase ab, int priority, bool negativeDamage) { this.ab = ab; - this.OrderID = order; + this.OrderID = ab.attackOrderName; this.OrderPriority = priority; - this.negativeDamage = negativeDamage; } public string OrderID { get; private set; } @@ -226,30 +272,23 @@ namespace OpenRA.Mods.Common.Traits { IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); - var a = ab.ChooseArmamentForTarget(target); - cursor = a != null && !target.IsInRange(self.CenterPosition, a.MaxRange()) - ? ab.Info.OutsideRangeCursor - : ab.Info.Cursor; - - if (target.Type == TargetType.Actor && target.Actor == self) - return false; - - if (!ab.HasAnyValidWeapons(target)) - return false; - - if (modifiers.HasModifier(TargetModifiers.ForceAttack)) - return true; - if (modifiers.HasModifier(TargetModifiers.ForceMove)) return false; - if (target.RequiresForceFire) + var forceAttack = modifiers.HasModifier(TargetModifiers.ForceAttack); + var a = ab.ChooseArmamentsForTarget(target, forceAttack).FirstOrDefault(); + if (a == null) return false; - var targetableRelationship = negativeDamage ? Stance.Ally : Stance.Enemy; + cursor = !target.IsInRange(self.CenterPosition, a.MaxRange()) + ? ab.Info.OutsideRangeCursor ?? a.Info.OutsideRangeCursor + : ab.Info.Cursor ?? a.Info.Cursor; - var owner = target.Type == TargetType.FrozenActor ? target.FrozenActor.Owner : target.Actor.Owner; - return self.Owner.Stances[owner] == targetableRelationship; + if (!forceAttack) + return true; + + OrderID = ab.forceAttackOrderName; + return true; } bool CanTargetLocation(Actor self, CPos location, List actorsAtLocation, TargetModifiers modifiers, ref string cursor) @@ -259,24 +298,21 @@ namespace OpenRA.Mods.Common.Traits IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); - cursor = ab.Info.Cursor; - - if (negativeDamage) + // Targeting the terrain is only possible with force-attack modifier + if (modifiers.HasModifier(TargetModifiers.ForceMove) || !modifiers.HasModifier(TargetModifiers.ForceAttack)) return false; - if (!ab.HasAnyValidWeapons(Target.FromCell(self.World, location))) + var target = Target.FromCell(self.World, location); + var a = ab.ChooseArmamentsForTarget(target, true).FirstOrDefault(); + if (a == null) return false; - if (modifiers.HasModifier(TargetModifiers.ForceAttack)) - { - var targetRange = (self.World.Map.CenterOfCell(location) - self.CenterPosition).HorizontalLengthSquared; - if (targetRange > ab.GetMaximumRange().LengthSquared) - cursor = ab.Info.OutsideRangeCursor; + cursor = !target.IsInRange(self.CenterPosition, a.MaxRange()) + ? ab.Info.OutsideRangeCursor ?? a.Info.OutsideRangeCursor + : ab.Info.Cursor ?? a.Info.Cursor; - return true; - } - - return false; + OrderID = ab.forceAttackOrderName; + return true; } public bool CanTarget(Actor self, Target target, List othersAtTarget, TargetModifiers modifiers, ref string cursor) diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackCharge.cs b/OpenRA.Mods.Common/Traits/Attack/AttackCharge.cs index 24100c3d9a..b50adaf8a2 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackCharge.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackCharge.cs @@ -69,7 +69,7 @@ namespace OpenRA.Mods.Common.Traits timeToRecharge = info.ReloadTime; } - public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) + public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack) { return new ChargeAttack(this, newTarget); } diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs b/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs index 2ac3e2372d..1984a212d2 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs @@ -9,6 +9,7 @@ #endregion using System; +using System.Linq; using OpenRA.Activities; using OpenRA.Traits; @@ -33,9 +34,9 @@ namespace OpenRA.Mods.Common.Traits IsAttacking = Target.IsValidFor(self); } - public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) + public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack) { - return new AttackActivity(self, newTarget, allowMove); + return new AttackActivity(self, newTarget, allowMove, forceAttack); } public override void ResolveOrder(Actor self, Order order) @@ -56,8 +57,9 @@ namespace OpenRA.Mods.Common.Traits readonly AttackFollow attack; readonly IMove move; readonly Target target; + readonly bool forceAttack; - public AttackActivity(Actor self, Target target, bool allowMove) + public AttackActivity(Actor self, Target target, bool allowMove, bool forceAttack) { attack = self.Trait(); move = allowMove ? self.TraitOrDefault() : null; @@ -68,6 +70,7 @@ namespace OpenRA.Mods.Common.Traits move = null; this.target = target; + this.forceAttack = forceAttack; } public override Activity Tick(Actor self) @@ -78,7 +81,7 @@ namespace OpenRA.Mods.Common.Traits if (self.IsDisabled()) return this; - var weapon = attack.ChooseArmamentForTarget(target); + var weapon = attack.ChooseArmamentsForTarget(target, forceAttack).FirstOrDefault(); if (weapon != null) { var targetIsMobile = (target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo()) diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackFrontal.cs b/OpenRA.Mods.Common/Traits/Attack/AttackFrontal.cs index b9ce88c635..8f02f38d0b 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackFrontal.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackFrontal.cs @@ -46,13 +46,9 @@ namespace OpenRA.Mods.Common.Traits return true; } - public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) + public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack) { - var a = ChooseArmamentForTarget(newTarget); - if (a == null) - return null; - - return new Activities.Attack(self, newTarget, a, allowMove); + return new Activities.Attack(self, newTarget, allowMove, forceAttack); } } } diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackGarrisoned.cs b/OpenRA.Mods.Common/Traits/Attack/AttackGarrisoned.cs index ce6d6c0f11..296ee83443 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackGarrisoned.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackGarrisoned.cs @@ -133,7 +133,7 @@ namespace OpenRA.Mods.Common.Traits return coords.Value.LocalToWorld(p.Offset.Rotate(bodyOrientation)); } - public override void DoAttack(Actor self, Target target) + public override void DoAttack(Actor self, Target target, IEnumerable armaments = null) { if (!CanAttack(self, target)) return; diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackMedic.cs b/OpenRA.Mods.Common/Traits/Attack/AttackMedic.cs deleted file mode 100644 index 89795550d1..0000000000 --- a/OpenRA.Mods.Common/Traits/Attack/AttackMedic.cs +++ /dev/null @@ -1,38 +0,0 @@ -#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 OpenRA.Activities; -using OpenRA.Traits; - -namespace OpenRA.Mods.Common.Traits -{ - [Desc("Give the unit a \"heal-weapon\" that attacks friendly targets if they are damaged.", - "It conflicts with any other weapon or Attack*: trait because it will hurt friendlies during the", - "heal process then. It also won't work with buildings (use RepairsUnits: for them)")] - public class AttackMedicInfo : AttackFrontalInfo - { - public override object Create(ActorInitializer init) { return new AttackMedic(init.Self, this); } - } - - public class AttackMedic : AttackFrontal - { - public AttackMedic(Actor self, AttackMedicInfo info) - : base(self, info) { } - - public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) - { - var a = ChooseArmamentForTarget(newTarget); - if (a == null) - return null; - - return new Activities.Heal(self, newTarget, a, allowMove); - } - } -} diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackOmni.cs b/OpenRA.Mods.Common/Traits/Attack/AttackOmni.cs index 10fc528170..a5cae38a92 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackOmni.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackOmni.cs @@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Traits public AttackOmni(Actor self, AttackOmniInfo info) : base(self, info) { } - public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) + public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack) { return new SetTarget(this, newTarget); } diff --git a/OpenRA.Mods.Common/Traits/AutoHeal.cs b/OpenRA.Mods.Common/Traits/AutoHeal.cs deleted file mode 100644 index fbb3b43993..0000000000 --- a/OpenRA.Mods.Common/Traits/AutoHeal.cs +++ /dev/null @@ -1,37 +0,0 @@ -#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.Linq; -using OpenRA.Traits; - -namespace OpenRA.Mods.Common.Traits -{ - [Desc("Used together with AttackMedic: to make the healer do it's job automatically to nearby units.")] - class AutoHealInfo : TraitInfo, Requires { } - - class AutoHeal : INotifyIdle - { - public void TickIdle(Actor self) - { - var attack = self.Trait(); - var inRange = self.World.FindActorsInCircle(self.CenterPosition, attack.GetMaximumRange()); - - var target = inRange - .Where(a => a != self && a.AppearsFriendlyTo(self)) - .Where(a => a.IsInWorld && !a.IsDead) - .Where(a => a.GetDamageState() > DamageState.Undamaged) - .Where(a => attack.HasAnyValidWeapons(Target.FromActor(a))) - .ClosestTo(self); - - if (target != null) - self.QueueActivity(attack.GetAttackActivity(self, Target.FromActor(target), false)); - } - } -} diff --git a/OpenRA.Mods.Common/Traits/AutoTarget.cs b/OpenRA.Mods.Common/Traits/AutoTarget.cs index e63f549414..5f5feb8836 100644 --- a/OpenRA.Mods.Common/Traits/AutoTarget.cs +++ b/OpenRA.Mods.Common/Traits/AutoTarget.cs @@ -8,6 +8,7 @@ */ #endregion +using System.Collections.Generic; using System.Drawing; using System.Linq; using OpenRA.Traits; @@ -19,20 +20,24 @@ namespace OpenRA.Mods.Common.Traits { [Desc("It will try to hunt down the enemy if it is not set to defend.")] public readonly bool AllowMovement = true; + [Desc("Set to a value >1 to override weapons maximum range for this.")] public readonly int ScanRadius = -1; [Desc("Possible values are HoldFire, ReturnFire, Defend and AttackAnything.")] public readonly UnitStance InitialStance = UnitStance.AttackAnything; + [Desc("Allow the player to change the unit stance.")] public readonly bool EnableStances = true; [Desc("Ticks to wait until next AutoTarget: attempt.")] public readonly int MinimumScanTimeInterval = 3; + [Desc("Ticks to wait until next AutoTarget: attempt.")] public readonly int MaximumScanTimeInterval = 8; public readonly bool TargetWhenIdle = true; + public readonly bool TargetWhenDamaged = true; public object Create(ActorInitializer init) { return new AutoTarget(init.Self, this); } @@ -110,7 +115,7 @@ namespace OpenRA.Mods.Common.Traits var allowMovement = info.AllowMovement && Stance != UnitStance.Defend; if (at == null || !at.IsReachableTarget(at.Target, allowMovement)) - ScanAndAttack(self); + ScanAndAttack(self, allowMovement); } public void Tick(Actor self) @@ -119,21 +124,24 @@ namespace OpenRA.Mods.Common.Traits --nextScanTime; } - public Actor ScanForTarget(Actor self, Actor currentTarget) + public Actor ScanForTarget(Actor self, Actor currentTarget, bool allowMove) { if (nextScanTime <= 0) { var range = info.ScanRadius > 0 ? WDist.FromCells(info.ScanRadius) : attack.GetMaximumRange(); if (self.IsIdle || currentTarget == null || !Target.FromActor(currentTarget).IsInRange(self.CenterPosition, range)) - return ChooseTarget(self, range); + { + nextScanTime = self.World.SharedRandom.Next(info.MinimumScanTimeInterval, info.MaximumScanTimeInterval); + return ChooseTarget(self, range, allowMove); + } } return currentTarget; } - public void ScanAndAttack(Actor self) + public void ScanAndAttack(Actor self, bool allowMove) { - var targetActor = ScanForTarget(self, null); + var targetActor = ScanForTarget(self, null, allowMove); if (targetActor != null) Attack(self, targetActor); } @@ -146,18 +154,37 @@ namespace OpenRA.Mods.Common.Traits attack.AttackTarget(target, false, info.AllowMovement && Stance != UnitStance.Defend); } - Actor ChooseTarget(Actor self, WDist range) + Actor ChooseTarget(Actor self, WDist range, bool allowMove) { - nextScanTime = self.World.SharedRandom.Next(info.MinimumScanTimeInterval, info.MaximumScanTimeInterval); var inRange = self.World.FindActorsInCircle(self.CenterPosition, range); - return inRange - .Where(a => - a.AppearsHostileTo(self) && - !a.Info.HasTraitInfo() && - attack.HasAnyValidWeapons(Target.FromActor(a)) && - self.Owner.CanTargetActor(a)) - .ClosestTo(self); + // Armaments are enumerated in attack.Armaments in construct order + // When autotargeting, first choose targets according to the used armament construct order + // 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) + var actrarms = inRange + + // Select only the first compatible armament for each actor: if this actor is selected + // it will be thanks to the first armament anyways, since that is the first selection + // criterion + .Select(a => new KeyValuePair( + attack.ChooseArmamentsForTarget(Target.FromActor(a), false) + .Where(arm => allowMove || arm.MaxRange().Length > (a.CenterPosition - self.CenterPosition).Length) + .FirstOrDefault(), a)) + + .Where(kv => kv.Key != null && kv.Value.TraitOrDefault() == null) + .GroupBy(kv => kv.Key, kv => kv.Value) + .ToDictionary(kv => kv.Key, kv => kv.ClosestTo(self)); + + foreach (var arm in attack.Armaments) + { + Actor actor; + if (actrarms.TryGetValue(arm, out actor)) + return actor; + } + + return null; } } diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs index 3bbdafd2e5..3bca6a0490 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs @@ -2198,6 +2198,52 @@ namespace OpenRA.Mods.Common.UtilityCommands node.Key = "-WithSpriteRotorOverlay"; } + if (engineVersion < 20151005) + { + // Move certain properties from Parachutable to new WithParachute trait + // Add dependency traits to actors implementing Parachutable + // Make otherwise targetable parachuting actors untargetable + var heal = node.Value.Nodes.FirstOrDefault(n => n.Key.StartsWith("AutoHeal")); + if (heal != null) + { + var otherNodes = nodes; + var inherits = new Func(traitName => node.Value.Nodes.Where(n => n.Key.StartsWith("Inherits")) + .Any(inh => otherNodes.First(n => n.Key.StartsWith(inh.Value.Value)).Value.Nodes.Any(n => n.Key.StartsWith(traitName)))); + + var target = node.Value.Nodes.FirstOrDefault(n => n.Key.StartsWith("-AutoTarget")); + if (target != null) + node.Value.Nodes.Remove(target); + else if (!inherits("AutoTarget")) + node.Value.Nodes.Add(new MiniYamlNode("AutoTarget", "")); + + node.Value.Nodes.Remove(heal); + } + + var atkmedic = node.Value.Nodes.FirstOrDefault(n => n.Key.StartsWith("AttackMedic")); + if (atkmedic != null) + { + var crsr = atkmedic.Value.Nodes.FirstOrDefault(n => n.Key.StartsWith("Cursor")); + var hasorcrsr = atkmedic.Value.Nodes.FirstOrDefault(n => n.Key.StartsWith("OutsideRangeCursor")); + foreach (var armmnt in node.Value.Nodes.Where(n => n.Key.StartsWith("Armament"))) + { + if (crsr != null) + armmnt.Value.Nodes.Add(new MiniYamlNode("Cursor", crsr.Value)); + + if (hasorcrsr != null) + armmnt.Value.Nodes.Add(new MiniYamlNode("OutsideRangeCursor", hasorcrsr.Value)); + armmnt.Value.Nodes.Add(new MiniYamlNode("TargetStances", "Ally")); + armmnt.Value.Nodes.Add(new MiniYamlNode("ForceTargetStances", "None")); + } + + if (crsr != null) + atkmedic.Value.Nodes.Remove(crsr); + if (hasorcrsr != null) + atkmedic.Value.Nodes.Remove(hasorcrsr); + + atkmedic.Key = "AttackFrontal"; + } + } + UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1); } } diff --git a/OpenRA.Mods.Common/Warheads/DamageWarhead.cs b/OpenRA.Mods.Common/Warheads/DamageWarhead.cs index 4624861606..6e00e11229 100644 --- a/OpenRA.Mods.Common/Warheads/DamageWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/DamageWarhead.cs @@ -27,6 +27,14 @@ namespace OpenRA.Mods.Common.Warheads [Desc("Damage percentage versus each armortype.")] public readonly Dictionary Versus; + public override bool IsValidAgainst(Actor victim, Actor firedBy) + { + if (Damage < 0 && victim.GetDamageState() == DamageState.Undamaged) + return false; + + return base.IsValidAgainst(victim, firedBy); + } + public static object LoadVersus(MiniYaml yaml) { var nd = yaml.ToDictionary(); diff --git a/OpenRA.Mods.Common/Warheads/Warhead.cs b/OpenRA.Mods.Common/Warheads/Warhead.cs index d336b38ebd..8073a75a13 100644 --- a/OpenRA.Mods.Common/Warheads/Warhead.cs +++ b/OpenRA.Mods.Common/Warheads/Warhead.cs @@ -42,7 +42,7 @@ namespace OpenRA.Mods.Common.Warheads public abstract void DoImpact(Target target, Actor firedBy, IEnumerable damageModifiers); /// Checks if the warhead is valid against (can do something to) the actor. - public bool IsValidAgainst(Actor victim, Actor firedBy) + public virtual bool IsValidAgainst(Actor victim, Actor firedBy) { if (!AffectsParent && victim == firedBy) return false; diff --git a/OpenRA.Mods.D2k/Traits/AttackSwallow.cs b/OpenRA.Mods.D2k/Traits/AttackSwallow.cs index 931c37b7db..090996630d 100644 --- a/OpenRA.Mods.D2k/Traits/AttackSwallow.cs +++ b/OpenRA.Mods.D2k/Traits/AttackSwallow.cs @@ -8,6 +8,8 @@ */ #endregion +using System.Collections.Generic; +using System.Linq; using OpenRA.Mods.Common.Traits; using OpenRA.Mods.D2k.Activities; using OpenRA.Traits; @@ -24,6 +26,7 @@ namespace OpenRA.Mods.D2k.Traits public readonly int AttackTime = 30; public readonly string WormAttackSound = "Worm.wav"; + public readonly string WormAttackNotification = "WormAttack"; public override object Create(ActorInitializer init) { return new AttackSwallow(init.Self, this); } @@ -39,7 +42,7 @@ namespace OpenRA.Mods.D2k.Traits Info = info; } - public override void DoAttack(Actor self, Target target) + public override void DoAttack(Actor self, Target target, IEnumerable armaments = null) { // This is so that the worm does not launch an attack against a target that has reached solid rock if (target.Type != TargetType.Actor || !CanAttack(self, target)) @@ -48,7 +51,7 @@ namespace OpenRA.Mods.D2k.Traits return; } - var a = ChooseArmamentForTarget(target); + var a = ChooseArmamentsForTarget(target, true).FirstOrDefault(); if (a == null) return; diff --git a/OpenRA.Mods.RA/Traits/Attack/AttackLeap.cs b/OpenRA.Mods.RA/Traits/Attack/AttackLeap.cs index f615d5000f..56c4b07a7f 100644 --- a/OpenRA.Mods.RA/Traits/Attack/AttackLeap.cs +++ b/OpenRA.Mods.RA/Traits/Attack/AttackLeap.cs @@ -8,6 +8,8 @@ */ #endregion +using System.Collections.Generic; +using System.Linq; using OpenRA.Mods.Common.Traits; using OpenRA.Mods.RA.Activities; using OpenRA.Traits; @@ -19,6 +21,7 @@ namespace OpenRA.Mods.RA.Traits { [Desc("Leap speed (in units/tick).")] public readonly WDist Speed = new WDist(426); + public readonly WAngle Angle = WAngle.FromDegrees(20); public override object Create(ActorInitializer init) { return new AttackLeap(init.Self, this); } @@ -34,12 +37,12 @@ namespace OpenRA.Mods.RA.Traits this.info = info; } - public override void DoAttack(Actor self, Target target) + public override void DoAttack(Actor self, Target target, IEnumerable armaments = null) { if (target.Type != TargetType.Actor || !CanAttack(self, target)) return; - var a = ChooseArmamentForTarget(target); + var a = ChooseArmamentsForTarget(target, true).FirstOrDefault(); if (a == null) return;