diff --git a/OpenRA.Game/Traits/Target.cs b/OpenRA.Game/Traits/Target.cs index b501221396..dffbc0b73d 100644 --- a/OpenRA.Game/Traits/Target.cs +++ b/OpenRA.Game/Traits/Target.cs @@ -145,12 +145,11 @@ namespace OpenRA.Traits if (!actor.Targetables.Any(Exts.IsTraitEnabled)) return new[] { actor.CenterPosition }; - var targetablePositions = actor.TraitOrDefault(); - if (targetablePositions != null) + var targetablePositions = actor.TraitsImplementing().Where(Exts.IsTraitEnabled); + if (targetablePositions.Any()) { - var positions = targetablePositions.TargetablePositions(actor); - if (positions.Any()) - return positions; + var target = this; + return targetablePositions.SelectMany(tp => tp.TargetablePositions(target.actor)); } return new[] { actor.CenterPosition }; diff --git a/OpenRA.Mods.Common/Traits/Buildings/Building.cs b/OpenRA.Mods.Common/Traits/Buildings/Building.cs index 077f27bfd1..15442cb447 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/Building.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/Building.cs @@ -135,7 +135,7 @@ namespace OpenRA.Mods.Common.Traits } } - public class Building : IOccupySpace, INotifySold, INotifyTransform, ISync, INotifyCreated, INotifyAddedToWorld, INotifyRemovedFromWorld, ITargetablePositions + public class Building : IOccupySpace, INotifySold, INotifyTransform, ISync, INotifyCreated, INotifyAddedToWorld, INotifyRemovedFromWorld { public readonly BuildingInfo Info; public bool BuildComplete { get; private set; } @@ -143,6 +143,8 @@ namespace OpenRA.Mods.Common.Traits readonly Actor self; public readonly bool SkipMakeAnimation; + Pair[] occupiedCells; + // Shared activity lock: undeploy, sell, capture, etc. [Sync] public bool Locked = true; @@ -173,14 +175,8 @@ namespace OpenRA.Mods.Common.Traits SkipMakeAnimation = init.Contains(); } - Pair[] occupiedCells; public IEnumerable> OccupiedCells() { return occupiedCells; } - public IEnumerable TargetablePositions(Actor self) - { - return OccupiedCells().Select(c => self.World.Map.CenterOfCell(c.First)); - } - void INotifyCreated.Created(Actor self) { if (SkipMakeAnimation || !self.Info.HasTraitInfo()) diff --git a/OpenRA.Mods.Common/Traits/CombatDebugOverlay.cs b/OpenRA.Mods.Common/Traits/CombatDebugOverlay.cs index 6d8a31f852..e6ae8614c9 100644 --- a/OpenRA.Mods.Common/Traits/CombatDebugOverlay.cs +++ b/OpenRA.Mods.Common/Traits/CombatDebugOverlay.cs @@ -36,7 +36,6 @@ namespace OpenRA.Mods.Common.Traits HitShape[] shapes; IBlocksProjectiles[] allBlockers; - ITargetablePositions[] targetablePositions; public CombatDebugOverlay(Actor self) { @@ -51,7 +50,6 @@ namespace OpenRA.Mods.Common.Traits { shapes = self.TraitsImplementing().ToArray(); allBlockers = self.TraitsImplementing().ToArray(); - targetablePositions = self.TraitsImplementing().ToArray(); } void IRenderAboveWorld.RenderAboveWorld(Actor self, WorldRenderer wr) @@ -79,8 +77,7 @@ namespace OpenRA.Mods.Common.Traits s.Info.Type.DrawCombatOverlay(wr, wcr, self); var tc = Color.Lime; - var enabledPositions = targetablePositions.Where(Exts.IsTraitEnabled); - var positions = enabledPositions.SelectMany(tp => tp.TargetablePositions(self)); + var positions = activeShapes.SelectMany(tp => tp.TargetablePositions(self)); foreach (var p in positions) { var center = wr.Screen3DPosition(p); diff --git a/OpenRA.Mods.Common/Traits/HitShape.cs b/OpenRA.Mods.Common/Traits/HitShape.cs index 6778dcdc16..9d1313a953 100644 --- a/OpenRA.Mods.Common/Traits/HitShape.cs +++ b/OpenRA.Mods.Common/Traits/HitShape.cs @@ -16,9 +16,15 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { - [Desc("Shape of actor for damage calculations.")] - public class HitShapeInfo : ConditionalTraitInfo + [Desc("Shape of actor for targeting and damage calculations.")] + public class HitShapeInfo : ConditionalTraitInfo, Requires { + [Desc("Create a targetable position for each offset listed here (relative to CenterPosition).")] + public readonly WVec[] TargetableOffsets = { WVec.Zero }; + + [Desc("Create a targetable position at the center of each occupied cell. Stacks with TargetableOffsets.")] + public readonly bool UseOccupiedCellsOffsets = false; + [FieldLoader.LoadUsing("LoadShape")] public readonly IHitShape Type; @@ -51,9 +57,36 @@ namespace OpenRA.Mods.Common.Traits public override object Create(ActorInitializer init) { return new HitShape(init.Self, this); } } - public class HitShape : ConditionalTrait + public class HitShape : ConditionalTrait, ITargetablePositions { + BodyOrientation orientation; + IOccupySpace occupy; + public HitShape(Actor self, HitShapeInfo info) : base(info) { } + + protected override void Created(Actor self) + { + orientation = self.Trait(); + occupy = self.TraitOrDefault(); + + base.Created(self); + } + + public IEnumerable TargetablePositions(Actor self) + { + if (IsTraitDisabled) + yield break; + + if (Info.UseOccupiedCellsOffsets && occupy != null) + foreach (var c in occupy.OccupiedCells()) + yield return self.World.Map.CenterOfCell(c.First); + + foreach (var o in Info.TargetableOffsets) + { + var offset = orientation.LocalToWorld(o.Rotate(orientation.QuantizeOrientation(self, self.Orientation))); + yield return self.CenterPosition + offset; + } + } } } diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs index 446fdd2217..12681dd863 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs @@ -699,6 +699,15 @@ namespace OpenRA.Mods.Common.UtilityCommands else node.Value.Nodes.Add(hitShapeNode); } + + // Moved ITargetablePositions from Building to HitShape + var building = node.Value.Nodes.FirstOrDefault(n => n.Key == "Building"); + var hitShape = node.Value.Nodes.FirstOrDefault(n => n.Key == "HitShape"); + if (building != null && hitShape == null) + { + hitShapeNode.Value.Nodes.Add(new MiniYamlNode("UseOccupiedCellsOffsets", "true")); + node.Value.Nodes.Add(hitShapeNode); + } } UpgradeActorRules(modData, engineVersion, ref node.Value.Nodes, node, depth + 1); diff --git a/mods/cnc/rules/defaults.yaml b/mods/cnc/rules/defaults.yaml index e1678edf86..4ab6481b49 100644 --- a/mods/cnc/rules/defaults.yaml +++ b/mods/cnc/rules/defaults.yaml @@ -565,6 +565,8 @@ Priority: 3 Targetable: TargetTypes: Ground, C4, Structure + HitShape: + UseOccupiedCellsOffsets: true Armor: Type: Wood Building: @@ -878,6 +880,7 @@ DamagedSounds: xplos.aud DestroyedSounds: xplobig4.aud ScriptTriggers: + BodyOrientation: HitShape: ^Crate: diff --git a/mods/d2k/rules/defaults.yaml b/mods/d2k/rules/defaults.yaml index e4aa3d0384..15676c8a43 100644 --- a/mods/d2k/rules/defaults.yaml +++ b/mods/d2k/rules/defaults.yaml @@ -336,6 +336,8 @@ VisibilityType: CenterPosition Targetable: TargetTypes: Ground, C4, Structure + HitShape: + UseOccupiedCellsOffsets: true Building: Dimensions: 1,1 Footprint: x diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml index 1c2511ed59..ed1d4f048c 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -497,6 +497,8 @@ Priority: 3 Targetable: TargetTypes: Ground, C4, DetonateAttack, Structure + HitShape: + UseOccupiedCellsOffsets: true Building: Dimensions: 1,1 Footprint: x @@ -853,6 +855,7 @@ Type: Concrete AutoTargetIgnore: ScriptTriggers: + BodyOrientation: HitShape: ^Rock: diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml index 33dd549cce..0c14989af8 100644 --- a/mods/ts/rules/defaults.yaml +++ b/mods/ts/rules/defaults.yaml @@ -135,6 +135,8 @@ Priority: 3 Targetable: TargetTypes: Ground, Building, C4 + HitShape: + UseOccupiedCellsOffsets: true Building: Dimensions: 1,1 Footprint: x