From b4bc4caaef885fb96b89718a49571540ab7a2e1e Mon Sep 17 00:00:00 2001 From: dnqbob Date: Mon, 15 Jan 2024 22:32:35 +0800 Subject: [PATCH 1/4] GuardsSelection Extend: Support unit will guard ally when cannot attack target under cursor --- OpenRA.Mods.CA/Traits/GuardsSelection.cs | 169 +++++++++++++++++++---- mods/ca/rules/infantry.yaml | 6 +- mods/ca/rules/scrin.yaml | 2 +- mods/ca/rules/vehicles.yaml | 6 +- 4 files changed, 147 insertions(+), 36 deletions(-) diff --git a/OpenRA.Mods.CA/Traits/GuardsSelection.cs b/OpenRA.Mods.CA/Traits/GuardsSelection.cs index 572cd60c4b..0ecfaebf7c 100644 --- a/OpenRA.Mods.CA/Traits/GuardsSelection.cs +++ b/OpenRA.Mods.CA/Traits/GuardsSelection.cs @@ -16,54 +16,66 @@ namespace OpenRA.Mods.CA.Traits { - [Desc("Attach to support unit so that when ordered as part of a group with combat units it will guard those units.")] + [Desc("Attach to support unit so that when ordered as part of a group with combat units it will guard those units when Attack and AttackMove.")] class GuardsSelectionInfo : ConditionalTraitInfo { + [CursorReference] + [Desc("Cursor to display when hovering over a valid target.")] + public readonly string Cursor = "guard"; + + public readonly PlayerRelationship TargetRelationships = PlayerRelationship.Enemy; + public readonly PlayerRelationship ForceTargetRelationships = PlayerRelationship.Enemy | PlayerRelationship.Neutral | PlayerRelationship.Ally; + [Desc("Will only guard units with these target types.")] - public readonly BitSet ValidTargets = new BitSet("Ground", "Water"); + public readonly BitSet ValidTargetsToGuard = new("Ground", "Water"); - [Desc("Maximum number of guard orders to chain together.")] - public readonly int MaxTargets = 10; + [Desc("Will not guard units with these target types.")] + public readonly BitSet InvalidTargetsToGuard = default; - [Desc("Color to use for the target line.")] - public readonly Color TargetLineColor = Color.OrangeRed; + [Desc("Maximum number of guard orders to chain together.")] + public readonly int MaxGuardingTargets = 10; - [Desc("Maximum range that guarding actors will maintain.")] - public readonly WDist Range = WDist.FromCells(2); + [Desc("Orders to override to guard ally unit in selection. Use AttackGuards if you need override Attack/ForceAttack order.")] + public readonly HashSet OverrideOrders = new() { "AttackMove", "AssaultMove", "AttackGuards" }; - public override object Create(ActorInitializer init) { return new GuardsSelection(init, this); } + public override object Create(ActorInitializer init) { return new GuardsSelection(this); } } - class GuardsSelection : ConditionalTrait, IResolveOrder, INotifyCreated + class GuardsSelection : ConditionalTrait, IResolveOrder, INotifyCreated, IIssueOrder { - IMove move; + AttackBase[] attackBases; - public GuardsSelection(ActorInitializer init, GuardsSelectionInfo info) + public GuardsSelection(GuardsSelectionInfo info) : base(info) { } protected override void Created(Actor self) { - move = self.Trait(); + attackBases = self.TraitsImplementing().ToArray(); base.Created(self); } - void IResolveOrder.ResolveOrder(Actor self, Order order) + IEnumerable IIssueOrder.Orders { - if (IsTraitDisabled) - return; - - if (order.Target.Type == TargetType.Invalid) - return; + get + { + if (IsTraitDisabled || !Info.OverrideOrders.Contains("AttackGuards")) + yield break; - if (order.Queued) - return; + yield return new AttackGuardOrderTargeter(this, 6); + } + } - var validOrders = new HashSet { "AttackMove", "AssaultMove", "Attack", "ForceAttack", "KeepDistance" }; + Order IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, in Target target, bool queued) + { + if (order is AttackGuardOrderTargeter) + return new Order(order.OrderID, self, target, queued); - if (!validOrders.Contains(order.OrderString)) - return; + return null; + } - if (self.Owner.IsBot) + void IResolveOrder.ResolveOrder(Actor self, Order order) + { + if (IsTraitDisabled || order.Target.Type == TargetType.Invalid || order.Queued || self.Owner.IsBot || !Info.OverrideOrders.Contains(order.OrderString)) return; var world = self.World; @@ -77,7 +89,7 @@ void IResolveOrder.ResolveOrder(Actor self, Order order) && !a.IsDead && a.IsInWorld && a != self - && IsValidGuardTarget(a)) + && IsValidGuardableTarget(a)) .ToArray(); if (guardActors.Length == 0) @@ -97,17 +109,18 @@ void IResolveOrder.ResolveOrder(Actor self, Order order) guardTargets++; world.IssueOrder(new Order("Guard", self, Target.FromActor(guardActor), true, null, null)); - if (guardTargets >= Info.MaxTargets) + if (guardTargets >= Info.MaxGuardingTargets) break; } } - bool IsValidGuardTarget(Actor targetActor) + bool IsValidGuardableTarget(Actor targetActor) { - if (!Info.ValidTargets.Overlaps(targetActor.GetEnabledTargetTypes())) + var targets = targetActor.GetEnabledTargetTypes(); + if (!Info.ValidTargetsToGuard.Overlaps(targets) || Info.InvalidTargetsToGuard.Overlaps(targets)) return false; - if (!targetActor.Info.HasTraitInfo()) + if (!targetActor.Info.HasTraitInfo()) return false; var guardsSelection = targetActor.TraitsImplementing(); @@ -116,5 +129,101 @@ bool IsValidGuardTarget(Actor targetActor) return true; } + + public bool CanAttackGuard(Actor self, Target t, bool forceAttack) + { + // 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 (t.Type == TargetType.Invalid || (!forceAttack && (t.Type == TargetType.Terrain || t.RequiresForceFire))) + return false; + + // 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) + Player owner = null; + if (t.Type == TargetType.FrozenActor) + owner = t.FrozenActor.Owner; + else if (t.Type == TargetType.Actor) + owner = t.Actor.Owner; + + return (owner == null || (forceAttack ? Info.ForceTargetRelationships : Info.TargetRelationships).HasRelationship(self.Owner.RelationshipWith(owner))) + && !attackBases.Any(ab => !ab.IsTraitDisabled && !ab.IsTraitPaused && ab.Armaments.Any(a => !a.IsTraitDisabled && !a.IsTraitPaused && a.Weapon.IsValidAgainst(t, self.World, self))); + } + } + + sealed class AttackGuardOrderTargeter : IOrderTargeter + { + readonly GuardsSelection gs; + + public AttackGuardOrderTargeter(GuardsSelection gs, int priority) + { + this.gs = gs; + OrderID = "AttackGuards"; + OrderPriority = priority; + } + + public string OrderID { get; private set; } + public int OrderPriority { get; } + public bool TargetOverridesSelection(Actor self, in Target target, List actorsAt, CPos xy, TargetModifiers modifiers) { return true; } + + bool CanTargetActor(Actor self, in Target target, ref TargetModifiers modifiers, ref string cursor) + { + IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); + + if (modifiers.HasModifier(TargetModifiers.ForceMove)) + return false; + + // Disguised actors are revealed by the attack cursor + // HACK: works around limitations in the targeting code that force the + // targeting and attacking logic (which should be logically separate) + // to use the same code + if (target.Type == TargetType.Actor && target.Actor.EffectiveOwner != null && + target.Actor.EffectiveOwner.Disguised && self.Owner.RelationshipWith(target.Actor.Owner) == PlayerRelationship.Enemy) + modifiers |= TargetModifiers.ForceAttack; + + if (!gs.CanAttackGuard(self, target, modifiers.HasModifier(TargetModifiers.ForceAttack))) + return false; + + cursor = gs.Info.Cursor; + + return true; + } + + bool CanTargetLocation(Actor self, CPos location, TargetModifiers modifiers, ref string cursor) + { + if (!self.World.Map.Contains(location)) + return false; + + IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); + + // Targeting the terrain is only possible with force-attack modifier + if (modifiers.HasModifier(TargetModifiers.ForceMove)) + return false; + + var target = Target.FromCell(self.World, location); + + if (!gs.CanAttackGuard(self, target, modifiers.HasModifier(TargetModifiers.ForceAttack))) + return false; + + cursor = gs.Info.Cursor; + + return true; + } + + public bool CanTarget(Actor self, in Target target, ref TargetModifiers modifiers, ref string cursor) + { + switch (target.Type) + { + case TargetType.Actor: + case TargetType.FrozenActor: + return CanTargetActor(self, target, ref modifiers, ref cursor); + case TargetType.Terrain: + return CanTargetLocation(self, self.World.Map.CellContaining(target.CenterPosition), modifiers, ref cursor); + default: + return false; + } + } + + public bool IsQueued { get; private set; } } } diff --git a/mods/ca/rules/infantry.yaml b/mods/ca/rules/infantry.yaml index 4ec82ea427..d99946071b 100644 --- a/mods/ca/rules/infantry.yaml +++ b/mods/ca/rules/infantry.yaml @@ -904,7 +904,7 @@ MEDI: Targetable@ChaosImmune: TargetTypes: ChaosImmune GuardsSelection: - ValidTargets: Infantry + ValidTargetsToGuard: Infantry KeepsDistance: MECH: @@ -995,7 +995,7 @@ MECH: Targetable@ChaosImmune: TargetTypes: ChaosImmune GuardsSelection: - ValidTargets: Vehicle + ValidTargetsToGuard: Vehicle Convertible: SpawnActors: CMEC ReplacedInQueue: @@ -1088,7 +1088,7 @@ CMEC: Types: Veterancy Prerequisites: barracks.upgraded GuardsSelection: - ValidTargets: Vehicle + ValidTargetsToGuard: Vehicle -TakeCover: KeepsDistance: diff --git a/mods/ca/rules/scrin.yaml b/mods/ca/rules/scrin.yaml index da4945c91f..c98c0960b1 100644 --- a/mods/ca/rules/scrin.yaml +++ b/mods/ca/rules/scrin.yaml @@ -664,7 +664,7 @@ SMEDI: Targetable: TargetTypes: Ground, Infantry, ChaosImmune GuardsSelection: - ValidTargets: Infantry + ValidTargetsToGuard: Infantry KeepsDistance: GSCR: diff --git a/mods/ca/rules/vehicles.yaml b/mods/ca/rules/vehicles.yaml index ac42aeced5..98ecf4e628 100644 --- a/mods/ca/rules/vehicles.yaml +++ b/mods/ca/rules/vehicles.yaml @@ -5699,12 +5699,14 @@ IFV: Condition: cryr-upgrade Prerequisites: cryr.upgrade GuardsSelection@REPAIR: - ValidTargets: Vehicle + ValidTargetsToGuard: Vehicle RequiresCondition: engturr && !stance-attackanything GuardsSelection@MEDIC: - ValidTargets: Infantry + ValidTargetsToGuard: Infantry RequiresCondition: medturr && !stance-attackanything GuardsSelection@SPY: + ValidTargetsToGuard: Ground + InvalidTargetsToGuard: Air RequiresCondition: spyturr && !stance-attackanything WithDecoration@COMMANDOSKULL: RequiresCondition: commturr || psyturr From 942c020fa2523184d68d2699fc55002818136e55 Mon Sep 17 00:00:00 2001 From: dnqbob Date: Tue, 16 Jan 2024 19:21:42 +0800 Subject: [PATCH 2/4] Add ChooseClosestAllyRangeCells to GuardsSelection, allow closest ally to be guarded when ally closest to target is too far away. --- OpenRA.Mods.CA/Traits/GuardsSelection.cs | 47 ++++++++++++++++-------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/OpenRA.Mods.CA/Traits/GuardsSelection.cs b/OpenRA.Mods.CA/Traits/GuardsSelection.cs index 0ecfaebf7c..ecb5e9f82d 100644 --- a/OpenRA.Mods.CA/Traits/GuardsSelection.cs +++ b/OpenRA.Mods.CA/Traits/GuardsSelection.cs @@ -38,6 +38,9 @@ class GuardsSelectionInfo : ConditionalTraitInfo [Desc("Orders to override to guard ally unit in selection. Use AttackGuards if you need override Attack/ForceAttack order.")] public readonly HashSet OverrideOrders = new() { "AttackMove", "AssaultMove", "AttackGuards" }; + [Desc("Guard ally closest to target when distance between smaller than this value, otherwise choose ally closest to this actor.")] + public readonly int ChooseClosestAllyRangeCells = 7; + public override object Create(ActorInitializer init) { return new GuardsSelection(this); } } @@ -83,35 +86,49 @@ void IResolveOrder.ResolveOrder(Actor self, Order order) if (order.Target.Type == TargetType.Actor && (order.Target.Actor.Disposed || order.Target.Actor.Owner == self.Owner || !order.Target.Actor.IsInWorld || order.Target.Actor.IsDead)) return; - var guardActors = world.Selection.Actors + var guardableActors = world.Selection.Actors .Where(a => a.Owner == self.Owner && !a.Disposed && !a.IsDead && a.IsInWorld && a != self && IsValidGuardableTarget(a)) + .OrderBy(a => (a.Location - self.Location).LengthSquared) .ToArray(); - if (guardActors.Length == 0) + if (guardableActors.Length == 0) return; - var mainGuardActor = guardActors.ClosestTo(order.Target.CenterPosition); + // We find candidates that within "ChooseClosestAllyRangeCells" to guard at highest priority. + var minDest = long.MaxValue; + var candidate = 0; + for (var i = 0; i < guardableActors.Length; i++) + { + if ((guardableActors[i].Location - self.Location).LengthSquared <= Info.ChooseClosestAllyRangeCells * Info.ChooseClosestAllyRangeCells) + { + var dist = (guardableActors[i].CenterPosition - order.Target.CenterPosition).HorizontalLengthSquared; + if (dist < minDest) + { + minDest = dist; + var a = guardableActors[i]; + guardableActors[i] = guardableActors[candidate]; + guardableActors[candidate] = a; + candidate++; + } + } + } + + var mainGuardActor = guardableActors[--candidate > 0 ? candidate : candidate = 0]; if (mainGuardActor == null) return; - var mainGuardTarget = Target.FromActor(mainGuardActor); - world.IssueOrder(new Order("Guard", self, mainGuardTarget, false, null, null)); + world.IssueOrder(new Order("Guard", self, Target.FromActor(mainGuardActor), false, null, null)); - var guardTargets = 0; + for (var i = 0; i < candidate && i < Info.MaxGuardingTargets; i++) + world.IssueOrder(new Order("Guard", self, Target.FromActor(guardableActors[candidate - i - 1]), true, null, null)); - foreach (var guardActor in guardActors) - { - guardTargets++; - world.IssueOrder(new Order("Guard", self, Target.FromActor(guardActor), true, null, null)); - - if (guardTargets >= Info.MaxGuardingTargets) - break; - } + for (var i = candidate + 1; i < guardableActors.Length && i < Info.MaxGuardingTargets; i++) + world.IssueOrder(new Order("Guard", self, Target.FromActor(guardableActors[i]), true, null, null)); } bool IsValidGuardableTarget(Actor targetActor) @@ -133,7 +150,7 @@ bool IsValidGuardableTarget(Actor targetActor) public bool CanAttackGuard(Actor self, Target t, bool forceAttack) { // If force-fire is not used, and the target requires force-firing or the target is - // terrain or invalid, no armaments can be used + // terrain or invalid, we will just ignore it. if (t.Type == TargetType.Invalid || (!forceAttack && (t.Type == TargetType.Terrain || t.RequiresForceFire))) return false; From b2b51e6b6b57a98a6323b2caae2476473506bb00 Mon Sep 17 00:00:00 2001 From: dnqbob Date: Thu, 1 Feb 2024 13:47:23 +0800 Subject: [PATCH 3/4] Add GuardsSelectionLevel --- OpenRA.Mods.CA/Traits/GuardsSelection.cs | 5 ++++- mods/ca/rules/infantry.yaml | 3 +++ mods/ca/rules/scrin.yaml | 1 + mods/ca/rules/vehicles.yaml | 8 ++++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/OpenRA.Mods.CA/Traits/GuardsSelection.cs b/OpenRA.Mods.CA/Traits/GuardsSelection.cs index ecb5e9f82d..5d78a24b09 100644 --- a/OpenRA.Mods.CA/Traits/GuardsSelection.cs +++ b/OpenRA.Mods.CA/Traits/GuardsSelection.cs @@ -41,6 +41,9 @@ class GuardsSelectionInfo : ConditionalTraitInfo [Desc("Guard ally closest to target when distance between smaller than this value, otherwise choose ally closest to this actor.")] public readonly int ChooseClosestAllyRangeCells = 7; + [Desc("When there are units with " + nameof(GuardsSelection) + " in player's selection, the one with higher level will guards the one with lower level.")] + public readonly int GuardsSelectionLevel = 1; + public override object Create(ActorInitializer init) { return new GuardsSelection(this); } } @@ -141,7 +144,7 @@ bool IsValidGuardableTarget(Actor targetActor) return false; var guardsSelection = targetActor.TraitsImplementing(); - if (guardsSelection.Any(t => !t.IsTraitDisabled)) + if (guardsSelection.Any(t => !t.IsTraitDisabled && Info.GuardsSelectionLevel <= t.Info.GuardsSelectionLevel)) return false; return true; diff --git a/mods/ca/rules/infantry.yaml b/mods/ca/rules/infantry.yaml index d99946071b..f5220eb56f 100644 --- a/mods/ca/rules/infantry.yaml +++ b/mods/ca/rules/infantry.yaml @@ -905,6 +905,7 @@ MEDI: TargetTypes: ChaosImmune GuardsSelection: ValidTargetsToGuard: Infantry + GuardsSelectionLevel: 2 KeepsDistance: MECH: @@ -996,6 +997,7 @@ MECH: TargetTypes: ChaosImmune GuardsSelection: ValidTargetsToGuard: Vehicle + GuardsSelectionLevel: 2 Convertible: SpawnActors: CMEC ReplacedInQueue: @@ -1089,6 +1091,7 @@ CMEC: Prerequisites: barracks.upgraded GuardsSelection: ValidTargetsToGuard: Vehicle + GuardsSelectionLevel: 2 -TakeCover: KeepsDistance: diff --git a/mods/ca/rules/scrin.yaml b/mods/ca/rules/scrin.yaml index c98c0960b1..2e021fcbd9 100644 --- a/mods/ca/rules/scrin.yaml +++ b/mods/ca/rules/scrin.yaml @@ -665,6 +665,7 @@ SMEDI: TargetTypes: Ground, Infantry, ChaosImmune GuardsSelection: ValidTargetsToGuard: Infantry + GuardsSelectionLevel: 2 KeepsDistance: GSCR: diff --git a/mods/ca/rules/vehicles.yaml b/mods/ca/rules/vehicles.yaml index 98ecf4e628..1273456a7a 100644 --- a/mods/ca/rules/vehicles.yaml +++ b/mods/ca/rules/vehicles.yaml @@ -5700,14 +5700,22 @@ IFV: Prerequisites: cryr.upgrade GuardsSelection@REPAIR: ValidTargetsToGuard: Vehicle + GuardsSelectionLevel: 2 RequiresCondition: engturr && !stance-attackanything GuardsSelection@MEDIC: ValidTargetsToGuard: Infantry + GuardsSelectionLevel: 2 RequiresCondition: medturr && !stance-attackanything GuardsSelection@SPY: ValidTargetsToGuard: Ground InvalidTargetsToGuard: Air + GuardsSelectionLevel: 2 RequiresCondition: spyturr && !stance-attackanything + GuardsSelection@COMM: + ValidTargetsToGuard: Ground + InvalidTargetsToGuard: Air + OverrideOrders: AttackGuards + RequiresCondition: commturr && !stance-attackanything WithDecoration@COMMANDOSKULL: RequiresCondition: commturr || psyturr WithDecoration@SEALSKULL: From c9fa8e7ec61d5c47ad4247a05840e23158bc7446 Mon Sep 17 00:00:00 2001 From: dnqbob Date: Wed, 14 Feb 2024 11:00:10 +0800 Subject: [PATCH 4/4] Merge KeepsDistance into new GuardsSelection --- OpenRA.Mods.CA/Traits/GuardsSelection.cs | 22 ++++- OpenRA.Mods.CA/Traits/KeepsDistance.cs | 112 ---------------------- mods/ca/maps/ca28-illumination/rules.yaml | 4 +- mods/ca/rules/infantry.yaml | 8 +- mods/ca/rules/scrin.yaml | 1 - mods/ca/rules/vehicles.yaml | 7 +- 6 files changed, 32 insertions(+), 122 deletions(-) delete mode 100644 OpenRA.Mods.CA/Traits/KeepsDistance.cs diff --git a/OpenRA.Mods.CA/Traits/GuardsSelection.cs b/OpenRA.Mods.CA/Traits/GuardsSelection.cs index 5d78a24b09..fea6a23a51 100644 --- a/OpenRA.Mods.CA/Traits/GuardsSelection.cs +++ b/OpenRA.Mods.CA/Traits/GuardsSelection.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Linq; +using OpenRA.Mods.Common.Activities; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; using OpenRA.Traits; @@ -26,6 +27,9 @@ class GuardsSelectionInfo : ConditionalTraitInfo public readonly PlayerRelationship TargetRelationships = PlayerRelationship.Enemy; public readonly PlayerRelationship ForceTargetRelationships = PlayerRelationship.Enemy | PlayerRelationship.Neutral | PlayerRelationship.Ally; + [Desc("Cells to keep distance to target when there is no guardable.")] + public readonly WDist DistanceWhenNoGuardable = WDist.FromCells(7); + [Desc("Will only guard units with these target types.")] public readonly BitSet ValidTargetsToGuard = new("Ground", "Water"); @@ -50,6 +54,7 @@ class GuardsSelectionInfo : ConditionalTraitInfo class GuardsSelection : ConditionalTrait, IResolveOrder, INotifyCreated, IIssueOrder { AttackBase[] attackBases; + IMoveInfo moveInfo; public GuardsSelection(GuardsSelectionInfo info) : base(info) { } @@ -57,6 +62,7 @@ public GuardsSelection(GuardsSelectionInfo info) protected override void Created(Actor self) { attackBases = self.TraitsImplementing().ToArray(); + moveInfo = self.Info.TraitInfoOrDefault(); base.Created(self); } @@ -81,7 +87,7 @@ Order IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, in Target target, void IResolveOrder.ResolveOrder(Actor self, Order order) { - if (IsTraitDisabled || order.Target.Type == TargetType.Invalid || order.Queued || self.Owner.IsBot || !Info.OverrideOrders.Contains(order.OrderString)) + if (IsTraitDisabled || order.Target.Type == TargetType.Invalid || order.Queued || self.Owner.IsBot) return; var world = self.World; @@ -89,6 +95,14 @@ void IResolveOrder.ResolveOrder(Actor self, Order order) if (order.Target.Type == TargetType.Actor && (order.Target.Actor.Disposed || order.Target.Actor.Owner == self.Owner || !order.Target.Actor.IsInWorld || order.Target.Actor.IsDead)) return; + if (order.OrderString == "AttackGuardsWithinRange") + { + self.QueueActivity(order.Queued, new MoveWithinRange(self, order.Target, WDist.Zero, Info.DistanceWhenNoGuardable, targetLineColor: moveInfo.GetTargetLineColor())); + return; + } + else if (!Info.OverrideOrders.Contains(order.OrderString)) + return; + var guardableActors = world.Selection.Actors .Where(a => a.Owner == self.Owner && !a.Disposed @@ -99,8 +113,14 @@ void IResolveOrder.ResolveOrder(Actor self, Order order) .OrderBy(a => (a.Location - self.Location).LengthSquared) .ToArray(); + // When no actor can be guarded in AttackGuards, move close to target if (guardableActors.Length == 0) + { + if (order.OrderString == "AttackGuards") + world.IssueOrder(new Order("AttackGuardsWithinRange", self, order.Target, false, null, null)); + return; + } // We find candidates that within "ChooseClosestAllyRangeCells" to guard at highest priority. var minDest = long.MaxValue; diff --git a/OpenRA.Mods.CA/Traits/KeepsDistance.cs b/OpenRA.Mods.CA/Traits/KeepsDistance.cs deleted file mode 100644 index 742a794e51..0000000000 --- a/OpenRA.Mods.CA/Traits/KeepsDistance.cs +++ /dev/null @@ -1,112 +0,0 @@ -#region Copyright & License Information -/** - * Copyright (c) The OpenRA Combined Arms Developers (see CREDITS). - * This file is part of OpenRA Combined Arms, 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, either version 3 of the License, - * or (at your option) any later version. For more information, see COPYING. - */ -#endregion - -using System.Collections.Generic; -using OpenRA.Mods.Common.Traits; -using OpenRA.Traits; - -namespace OpenRA.Mods.CA.Traits -{ - [Desc("Will keep distance from enemies that the unit can't attack.")] - class KeepsDistanceInfo : ConditionalTraitInfo - { - [Desc("Cells to keep distance.")] - public readonly WDist Distance = WDist.FromCells(7); - - [CursorReference] - [Desc("Cursor to display when targeting an actor to keep distance from.")] - public readonly string Cursor = "move"; - - public override object Create(ActorInitializer init) { return new KeepsDistance(init, this); } - } - - class KeepsDistance : ConditionalTrait, IIssueOrder, IResolveOrder - { - readonly IMove move; - readonly IMoveInfo moveInfo; - - public KeepsDistance(ActorInitializer init, KeepsDistanceInfo info) - : base(info) - { - move = init.Self.TraitOrDefault(); - moveInfo = init.Self.Info.TraitInfoOrDefault(); - } - - IEnumerable IIssueOrder.Orders - { - get - { - if (IsTraitDisabled) - yield break; - - yield return new KeepDistanceOrderTargeter(Info.Cursor); - } - } - - Order IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, in Target target, bool queued) - { - if (order is KeepDistanceOrderTargeter) - return new Order(order.OrderID, self, target, queued); - - return null; - } - - void IResolveOrder.ResolveOrder(Actor self, Order order) - { - if (order.OrderString == "KeepDistance") - { - self.QueueActivity(order.Queued, move.MoveWithinRange(order.Target, Info.Distance, targetLineColor: moveInfo.GetTargetLineColor())); - self.ShowTargetLines(); - } - } - - class KeepDistanceOrderTargeter : IOrderTargeter - { - readonly string cursor; - - public KeepDistanceOrderTargeter(string cursor) - { - this.cursor = cursor; - } - - public string OrderID => "KeepDistance"; - public int OrderPriority => 5; - public bool TargetOverridesSelection(Actor self, in Target target, List actorsAt, CPos xy, TargetModifiers modifiers) { return true; } - - bool CanTargetActor(Actor self, in Target target, ref TargetModifiers modifiers, ref string cursor) - { - IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); - - if (modifiers.HasModifier(TargetModifiers.ForceMove)) - return false; - - cursor = this.cursor; - var targetOwner = target.Type == TargetType.Actor ? target.Actor.Owner : target.FrozenActor.Owner; - return self.Owner.RelationshipWith(targetOwner) == PlayerRelationship.Enemy; - } - - public bool CanTarget(Actor self, in Target target, ref TargetModifiers modifiers, ref string cursor) - { - switch (target.Type) - { - case TargetType.Actor: - case TargetType.FrozenActor: - return CanTargetActor(self, target, ref modifiers, ref cursor); - case TargetType.Terrain: - return false; - default: - return false; - } - } - - public bool IsQueued { get; protected set; } - } - } -} diff --git a/mods/ca/maps/ca28-illumination/rules.yaml b/mods/ca/maps/ca28-illumination/rules.yaml index c684aefd6e..6401414d18 100644 --- a/mods/ca/maps/ca28-illumination/rules.yaml +++ b/mods/ca/maps/ca28-illumination/rules.yaml @@ -102,7 +102,9 @@ KANE: RenderDetectionCircle: Color: ffffff20 BorderColor: 00000020 - KeepsDistance: + GuardsSelection: + ValidTargetsToGuard: Ground + InvalidTargetsToGuard: Air CaptureManager: Captures: CaptureTypes: building diff --git a/mods/ca/rules/infantry.yaml b/mods/ca/rules/infantry.yaml index f5220eb56f..f6c4c44836 100644 --- a/mods/ca/rules/infantry.yaml +++ b/mods/ca/rules/infantry.yaml @@ -906,7 +906,6 @@ MEDI: GuardsSelection: ValidTargetsToGuard: Infantry GuardsSelectionLevel: 2 - KeepsDistance: MECH: Inherits: ^Soldier @@ -1002,7 +1001,6 @@ MECH: SpawnActors: CMEC ReplacedInQueue: Actors: cmec - KeepsDistance: CMEC: Inherits: ^Cyborg @@ -1093,7 +1091,6 @@ CMEC: ValidTargetsToGuard: Vehicle GuardsSelectionLevel: 2 -TakeCover: - KeepsDistance: HACK: Inherits: ^Soldier @@ -3835,9 +3832,12 @@ YURI: Range: 13c0 Color: cc00ff77 BorderColor: 00000044 - KeepsDistance: ChangesHealth@ELITE: Delay: 50 + GuardsSelection: + ValidTargetsToGuard: Ground + InvalidTargetsToGuard: Air + OverrideOrders: AttackGuards SEAL: Inherits: ^Soldier diff --git a/mods/ca/rules/scrin.yaml b/mods/ca/rules/scrin.yaml index 2e021fcbd9..362edb030c 100644 --- a/mods/ca/rules/scrin.yaml +++ b/mods/ca/rules/scrin.yaml @@ -666,7 +666,6 @@ SMEDI: GuardsSelection: ValidTargetsToGuard: Infantry GuardsSelectionLevel: 2 - KeepsDistance: GSCR: Inherits: BRUT diff --git a/mods/ca/rules/vehicles.yaml b/mods/ca/rules/vehicles.yaml index 1273456a7a..0445f1c04c 100644 --- a/mods/ca/rules/vehicles.yaml +++ b/mods/ca/rules/vehicles.yaml @@ -5725,8 +5725,6 @@ IFV: Palette: effect Position: TopLeft ValidRelationships: Ally, Enemy, Neutral - KeepsDistance: - RequiresCondition: engturr || medturr || spyturr DamageTypeDamageMultiplier@A2GPROTECTION: RequiresCondition: !full || samturr || ggiturr AmbientSoundCA: @@ -7769,7 +7767,10 @@ MANT: Range: 8c0 DetectionTypes: AirCloak RequiresCondition: empdisable || being-warped - KeepsDistance: + GuardsSelection: + ValidTargetsToGuard: Ground + InvalidTargetsToGuard: Air + OverrideOrders: AttackGuards VIPR: Inherits: ^VehicleTD