diff --git a/OpenRA.Game/Actor.cs b/OpenRA.Game/Actor.cs index 1cf4b8fcdb..89d9a4c743 100644 --- a/OpenRA.Game/Actor.cs +++ b/OpenRA.Game/Actor.cs @@ -44,6 +44,7 @@ namespace OpenRA public Rectangle VisualBounds { get; private set; } public IEffectiveOwner EffectiveOwner { get; private set; } public IOccupySpace OccupiesSpace { get; private set; } + public ITargetable[] Targetables { get; private set; } public bool IsIdle { get { return currentActivity == null; } } public bool IsDead { get { return Disposed || (health != null && health.IsDead); } } @@ -107,6 +108,7 @@ namespace OpenRA disables = TraitsImplementing().ToArray(); visibilityModifiers = TraitsImplementing().ToArray(); defaultVisibility = Trait(); + Targetables = TraitsImplementing().ToArray(); } Rectangle DetermineBounds() @@ -320,6 +322,30 @@ namespace OpenRA return defaultVisibility.IsVisible(this, player); } + public IEnumerable GetAllTargetTypes() + { + foreach (var targetable in Targetables) + foreach (var targetType in targetable.TargetTypes) + yield return targetType; + } + + public IEnumerable GetEnabledTargetTypes() + { + foreach (var targetable in Targetables) + if (targetable.IsTraitEnabled()) + foreach (var targetType in targetable.TargetTypes) + yield return targetType; + } + + public bool IsTargetableBy(Actor byActor) + { + foreach (var targetable in Targetables) + if (targetable.IsTraitEnabled() && targetable.TargetableBy(this, byActor)) + return true; + + return false; + } + #region Scripting interface Lazy luaInterface; diff --git a/OpenRA.Game/GameRules/WeaponInfo.cs b/OpenRA.Game/GameRules/WeaponInfo.cs index 133d35dc47..3e6591f985 100644 --- a/OpenRA.Game/GameRules/WeaponInfo.cs +++ b/OpenRA.Game/GameRules/WeaponInfo.cs @@ -127,8 +127,9 @@ namespace OpenRA.GameRules /// Checks if the weapon is valid against (can target) the actor. public bool IsValidAgainst(Actor victim, Actor firedBy) { - var targetable = victim.TraitsImplementing().Where(Exts.IsTraitEnabled); - if (!IsValidTarget(targetable.SelectMany(t => t.TargetTypes))) + var targetTypes = victim.GetEnabledTargetTypes(); + + if (!IsValidTarget(targetTypes)) return false; if (!Warheads.Any(w => w.IsValidAgainst(victim, firedBy))) diff --git a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs index 1e8a611385..027c3c9f75 100644 --- a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs +++ b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs @@ -59,7 +59,7 @@ namespace OpenRA.Traits CenterPosition = self.CenterPosition; Bounds = self.Bounds; - TargetTypes = self.TraitsImplementing().Where(Exts.IsTraitEnabled).SelectMany(t => t.TargetTypes).ToHashSet(); + TargetTypes = self.GetEnabledTargetTypes().ToHashSet(); UpdateVisibility(); } diff --git a/OpenRA.Game/Traits/Target.cs b/OpenRA.Game/Traits/Target.cs index d8c8e3fff3..823daa3cbd 100644 --- a/OpenRA.Game/Traits/Target.cs +++ b/OpenRA.Game/Traits/Target.cs @@ -22,7 +22,6 @@ namespace OpenRA.Traits TargetType type; Actor actor; - IEnumerable targetable; FrozenActor frozen; WPos pos; int generation; @@ -48,7 +47,6 @@ namespace OpenRA.Traits return new Target { actor = a, - targetable = a.TraitsImplementing(), type = TargetType.Actor, generation = a.Generation, }; @@ -83,8 +81,7 @@ namespace OpenRA.Traits if (targeter == null || Type == TargetType.Invalid) return false; - var targeted = this.actor; - if (targeted != null && !targetable.Any(t => t.IsTraitEnabled() && t.TargetableBy(targeted, targeter))) + if (actor != null && !actor.IsTargetableBy(targeter)) return false; return true; @@ -94,7 +91,24 @@ namespace OpenRA.Traits // TODO: either replace based on target type or put in singleton trait public bool RequiresForceFire { - get { return targetable != null && targetable.Any(Exts.IsTraitEnabled) && targetable.Where(Exts.IsTraitEnabled).All(t => t.RequiresForceFire); } + get + { + if (actor == null) + return false; + + var isTargetable = false; + foreach (var targetable in actor.Targetables) + { + if (!targetable.IsTraitEnabled()) + continue; + + isTargetable = true; + if (!targetable.RequiresForceFire) + return false; + } + + return isTargetable; + } } // Representative position - see Positions for the full set of targetable positions. @@ -126,8 +140,7 @@ namespace OpenRA.Traits switch (Type) { case TargetType.Actor: - var targetable = actor.TraitsImplementing().Where(Exts.IsTraitEnabled); - if (!targetable.Any()) + if (!actor.Targetables.Any(Exts.IsTraitEnabled)) return new[] { actor.CenterPosition }; var targetablePositions = actor.TraitOrDefault(); diff --git a/OpenRA.Mods.Common/AI/States/StateBase.cs b/OpenRA.Mods.Common/AI/States/StateBase.cs index b63d0d0cde..36f923a243 100644 --- a/OpenRA.Mods.Common/AI/States/StateBase.cs +++ b/OpenRA.Mods.Common/AI/States/StateBase.cs @@ -63,7 +63,7 @@ namespace OpenRA.Mods.Common.AI if (!a.Info.HasTraitInfo()) return false; - var targetTypes = target.TraitsImplementing().Where(Exts.IsTraitEnabled).SelectMany(t => t.TargetTypes); + var targetTypes = target.GetEnabledTargetTypes(); if (!targetTypes.Any()) return false; diff --git a/OpenRA.Mods.Common/AI/SupportPowerDecision.cs b/OpenRA.Mods.Common/AI/SupportPowerDecision.cs index c1fc4921f8..bd59c822e0 100644 --- a/OpenRA.Mods.Common/AI/SupportPowerDecision.cs +++ b/OpenRA.Mods.Common/AI/SupportPowerDecision.cs @@ -125,11 +125,10 @@ namespace OpenRA.Mods.Common.AI if (a == null) return 0; - var targetable = a.TraitsImplementing().Where(Exts.IsTraitEnabled); - if (!targetable.Any(t => t.TargetableBy(a, firedBy.PlayerActor))) + if (!a.IsTargetableBy(firedBy.PlayerActor)) return 0; - if (Types.Overlaps(targetable.SelectMany(t => t.TargetTypes))) + if (Types.Overlaps(a.GetEnabledTargetTypes())) { switch (TargetMetric) { diff --git a/OpenRA.Mods.Common/Activities/Hunt.cs b/OpenRA.Mods.Common/Activities/Hunt.cs index d101a3ede1..e9b61f37b9 100644 --- a/OpenRA.Mods.Common/Activities/Hunt.cs +++ b/OpenRA.Mods.Common/Activities/Hunt.cs @@ -25,12 +25,7 @@ namespace OpenRA.Mods.Common.Activities var attack = self.Trait(); targets = self.World.ActorsHavingTrait().Where( a => self != a && !a.IsDead && a.IsInWorld && a.AppearsHostileTo(self) - && IsTargetable(a, self) && attack.HasAnyValidWeapons(Target.FromActor(a))); - } - - bool IsTargetable(Actor self, Actor viewer) - { - return self.TraitsImplementing().Any(t => t.IsTraitEnabled() && t.TargetableBy(self, viewer)); + && a.IsTargetableBy(self) && attack.HasAnyValidWeapons(Target.FromActor(a))); } public override Activity Tick(Actor self) diff --git a/OpenRA.Mods.Common/Orders/UnitOrderTargeter.cs b/OpenRA.Mods.Common/Orders/UnitOrderTargeter.cs index f6a82fa872..8de25c8889 100644 --- a/OpenRA.Mods.Common/Orders/UnitOrderTargeter.cs +++ b/OpenRA.Mods.Common/Orders/UnitOrderTargeter.cs @@ -67,9 +67,9 @@ namespace OpenRA.Mods.Common.Orders public class TargetTypeOrderTargeter : UnitOrderTargeter { - readonly string[] targetTypes; + readonly HashSet targetTypes; - public TargetTypeOrderTargeter(string[] targetTypes, string order, int priority, string cursor, bool targetEnemyUnits, bool targetAllyUnits) + public TargetTypeOrderTargeter(HashSet targetTypes, string order, int priority, string cursor, bool targetEnemyUnits, bool targetAllyUnits) : base(order, priority, cursor, targetEnemyUnits, targetAllyUnits) { this.targetTypes = targetTypes; @@ -77,7 +77,7 @@ namespace OpenRA.Mods.Common.Orders public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor) { - return target.TraitsImplementing().Any(t => t.IsTraitEnabled() && t.TargetTypes.Overlaps(targetTypes)); + return targetTypes.Overlaps(target.GetEnabledTargetTypes()); } public override bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor) diff --git a/OpenRA.Mods.Common/Traits/Crates/DuplicateUnitCrateAction.cs b/OpenRA.Mods.Common/Traits/Crates/DuplicateUnitCrateAction.cs index 66c5343746..a33201dfaf 100644 --- a/OpenRA.Mods.Common/Traits/Crates/DuplicateUnitCrateAction.cs +++ b/OpenRA.Mods.Common/Traits/Crates/DuplicateUnitCrateAction.cs @@ -58,8 +58,7 @@ namespace OpenRA.Mods.Common.Traits if (info.ValidFactions.Any() && !info.ValidFactions.Contains(collector.Owner.Faction.InternalName)) return false; - var targetable = collector.TraitsImplementing().Where(Exts.IsTraitEnabled); - if (!info.ValidTargets.Overlaps(targetable.SelectMany(t => t.TargetTypes))) + if (!info.ValidTargets.Overlaps(collector.GetEnabledTargetTypes())) return false; var positionable = collector.TraitOrDefault(); diff --git a/OpenRA.Mods.Common/Warheads/Warhead.cs b/OpenRA.Mods.Common/Warheads/Warhead.cs index 3856771a41..2dd0eed660 100644 --- a/OpenRA.Mods.Common/Warheads/Warhead.cs +++ b/OpenRA.Mods.Common/Warheads/Warhead.cs @@ -56,8 +56,7 @@ namespace OpenRA.Mods.Common.Warheads return false; // A target type is valid if it is in the valid targets list, and not in the invalid targets list. - var targetable = victim.TraitsImplementing().Where(Exts.IsTraitEnabled); - if (!IsValidTarget(targetable.SelectMany(t => t.TargetTypes))) + if (!IsValidTarget(victim.GetEnabledTargetTypes())) return false; return true; diff --git a/OpenRA.Mods.RA/Traits/DemoTruck.cs b/OpenRA.Mods.RA/Traits/DemoTruck.cs index 9b50d545c7..93eb4e0672 100644 --- a/OpenRA.Mods.RA/Traits/DemoTruck.cs +++ b/OpenRA.Mods.RA/Traits/DemoTruck.cs @@ -44,7 +44,7 @@ namespace OpenRA.Mods.RA.Traits { get { - yield return new TargetTypeOrderTargeter(new[] { "DetonateAttack" }, "DetonateAttack", 5, "attack", true, false) { ForceAttack = false }; + yield return new TargetTypeOrderTargeter(new HashSet { "DetonateAttack" }, "DetonateAttack", 5, "attack", true, false) { ForceAttack = false }; yield return new DeployOrderTargeter("Detonate", 5); } } diff --git a/OpenRA.Mods.RA/Traits/Disguise.cs b/OpenRA.Mods.RA/Traits/Disguise.cs index 49a86156b5..05a4dab353 100644 --- a/OpenRA.Mods.RA/Traits/Disguise.cs +++ b/OpenRA.Mods.RA/Traits/Disguise.cs @@ -94,7 +94,7 @@ namespace OpenRA.Mods.RA.Traits { get { - yield return new TargetTypeOrderTargeter(new[] { "Disguise" }, "Disguise", 7, "ability", true, true) { ForceAttack = false }; + yield return new TargetTypeOrderTargeter(new HashSet { "Disguise" }, "Disguise", 7, "ability", true, true) { ForceAttack = false }; } } diff --git a/OpenRA.Mods.RA/Traits/Infiltration/Infiltrates.cs b/OpenRA.Mods.RA/Traits/Infiltration/Infiltrates.cs index 7e31df4c61..dc2fa3a2e1 100644 --- a/OpenRA.Mods.RA/Traits/Infiltration/Infiltrates.cs +++ b/OpenRA.Mods.RA/Traits/Infiltration/Infiltrates.cs @@ -77,8 +77,7 @@ namespace OpenRA.Mods.RA.Traits targetTypes = frozen.TargetTypes; } else - targetTypes = order.TargetActor.TraitsImplementing().Where(Exts.IsTraitEnabled) - .SelectMany(t => t.TargetTypes); + targetTypes = order.TargetActor.GetEnabledTargetTypes(); return Info.Types.Overlaps(targetTypes); } @@ -96,7 +95,7 @@ namespace OpenRA.Mods.RA.Traits var target = self.ResolveFrozenActorOrder(order, Color.Red); if (target.Type != TargetType.Actor - || !Info.Types.Overlaps(target.Actor.TraitsImplementing().SelectMany(t => t.TargetTypes))) + || !Info.Types.Overlaps(target.Actor.GetAllTargetTypes())) return; if (!order.Queued) @@ -124,7 +123,7 @@ namespace OpenRA.Mods.RA.Traits if (!info.ValidStances.HasStance(stance)) return false; - return target.TraitsImplementing().Any(t => t.TargetTypes.Overlaps(info.Types)); + return info.Types.Overlaps(target.GetAllTargetTypes()); } public override bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor) @@ -134,7 +133,7 @@ namespace OpenRA.Mods.RA.Traits if (!info.ValidStances.HasStance(stance)) return false; - return target.Info.TraitInfos().Any(t => info.Types.Overlaps(t.GetTargetTypes())); + return info.Types.Overlaps(target.Info.TraitInfos().SelectMany(ti => ti.GetTargetTypes())); } } } diff --git a/OpenRA.Mods.RA/Traits/MadTank.cs b/OpenRA.Mods.RA/Traits/MadTank.cs index 8c3345a692..d473d170db 100644 --- a/OpenRA.Mods.RA/Traits/MadTank.cs +++ b/OpenRA.Mods.RA/Traits/MadTank.cs @@ -98,7 +98,7 @@ namespace OpenRA.Mods.RA.Traits { get { - yield return new TargetTypeOrderTargeter(new[] { "DetonateAttack" }, "DetonateAttack", 5, "attack", true, false) { ForceAttack = false }; + yield return new TargetTypeOrderTargeter(new HashSet { "DetonateAttack" }, "DetonateAttack", 5, "attack", true, false) { ForceAttack = false }; yield return new DeployOrderTargeter("Detonate", 5); } }