diff --git a/OpenRA.Mods.Common/Graphics/IsometricSelectionBarsAnnotationRenderable.cs b/OpenRA.Mods.Common/Graphics/IsometricSelectionBarsAnnotationRenderable.cs new file mode 100644 index 0000000000..ed720dca6f --- /dev/null +++ b/OpenRA.Mods.Common/Graphics/IsometricSelectionBarsAnnotationRenderable.cs @@ -0,0 +1,168 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using OpenRA.Graphics; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Graphics +{ + public struct IsometricSelectionBarsAnnotationRenderable : IRenderable, IFinalizedRenderable + { + const int BarWidth = 3; + const int BarHeight = 4; + const int BarStride = 5; + + static readonly Color EmptyColor = Color.FromArgb(160, 30, 30, 30); + static readonly Color DarkEmptyColor = Color.FromArgb(160, 15, 15, 15); + static readonly Color DarkenColor = Color.FromArgb(24, 0, 0, 0); + static readonly Color LightenColor = Color.FromArgb(24, 255, 255, 255); + + readonly WPos pos; + readonly Actor actor; + readonly bool displayHealth; + readonly bool displayExtra; + readonly Polygon bounds; + + public IsometricSelectionBarsAnnotationRenderable(Actor actor, Polygon bounds, bool displayHealth, bool displayExtra) + : this(actor.CenterPosition, actor, bounds) + { + this.displayHealth = displayHealth; + this.displayExtra = displayExtra; + } + + public IsometricSelectionBarsAnnotationRenderable(WPos pos, Actor actor, Polygon bounds) + : this() + { + this.pos = pos; + this.actor = actor; + this.bounds = bounds; + } + + public WPos Pos { get { return pos; } } + public bool DisplayHealth { get { return displayHealth; } } + public bool DisplayExtra { get { return displayExtra; } } + + public PaletteReference Palette { get { return null; } } + public int ZOffset { get { return 0; } } + public bool IsDecoration { get { return true; } } + + public IRenderable WithPalette(PaletteReference newPalette) { return this; } + public IRenderable WithZOffset(int newOffset) { return this; } + public IRenderable OffsetBy(WVec vec) { return new IsometricSelectionBarsAnnotationRenderable(pos + vec, actor, bounds); } + public IRenderable AsDecoration() { return this; } + + void DrawExtraBars(WorldRenderer wr) + { + var i = 1; + foreach (var extraBar in actor.TraitsImplementing()) + { + var value = extraBar.GetValue(); + if (value != 0 || extraBar.DisplayWhenEmpty) + DrawBar(wr, extraBar.GetValue(), extraBar.GetColor(), i++); + } + } + + void DrawBar(WorldRenderer wr, float value, Color barColor, int barNum, float? secondValue = null, Color? secondColor = null) + { + var darkColor = Color.FromArgb(barColor.A, barColor.R / 2, barColor.G / 2, barColor.B / 2); + var barAspect = new float2(1f, 0.5f); + var stepAspect = new float2(1f, -0.5f); + + var offset = barNum * BarStride * barAspect - new float2(0, BarHeight + 1); + var start = wr.Viewport.WorldToViewPx(bounds.Vertices[1]).ToFloat2() + offset; + var end = wr.Viewport.WorldToViewPx(bounds.Vertices[0]).ToFloat2() + offset; + + // HACK: Work around rounding errors that may cause a few-px offset in the end relative to the start + // Force the bar to take a 45 degree angle + end = new float2(end.X, start.Y - (end.X - start.X) / 2); + + // Round the cut point to the nearest pixel to avoid potential off-by-one pixel offsets distorting the bar + var cutX = (int)(float2.Lerp(start.X, end.X, value) + 0.5f); + var cut = new float2(cutX, start.Y - (cutX - start.X) / 2); + + var cr = Game.Renderer.RgbaColorRenderer; + var da = BarWidth * barAspect; + var db = new int2(0, BarHeight); + var dc = da + db; + + // Filled bar + cr.FillRect(start + da, start + dc, cut + dc, cut + da, darkColor); + cr.FillRect(start, start + da, start + dc, start + db, darkColor); + cr.FillRect(start, start + da, cut + da, cut, barColor); + + // Faint marks to break the monotony of the solid bar + var dx = BarWidth; + while (dx < cut.X - start.X) + { + var step = start + dx * stepAspect; + cr.DrawLine(step, step + da, 1, DarkenColor); + cr.DrawLine(step + da, step + dc, 1, LightenColor); + dx += BarWidth; + } + + // Second bar (e.g. applied damage) + if (secondValue.HasValue && secondColor.HasValue) + { + var secondCutX = (int)(float2.Lerp(start.X, end.X, secondValue.Value) + 0.5f); + var secondCut = new float2(secondCutX, start.Y - (secondCutX - start.X) / 2); + var darkSecond = Color.FromArgb(secondColor.Value.A, secondColor.Value.R / 2, secondColor.Value.G / 2, secondColor.Value.B / 2); + + cr.FillRect(cut + da, cut + dc, secondCut + dc, secondCut + da, darkSecond); + cr.FillRect(cut, cut + da, secondCut + da, secondCut, secondColor.Value); + + value = secondValue.Value; + cut = secondCut; + } + + // Empty bar + if (value < 1) + { + cr.FillRect(cut + da, cut + dc, end + dc, end + da, DarkEmptyColor); + cr.FillRect(cut, cut + da, end + da, end, EmptyColor); + } + } + + Color GetHealthColor(IHealth health) + { + if (Game.Settings.Game.UsePlayerStanceColors) + return actor.Owner.PlayerStanceColor(actor); + + return health.DamageState == DamageState.Critical ? Color.Red : + health.DamageState == DamageState.Heavy ? Color.Yellow : Color.LimeGreen; + } + + public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; } + public void Render(WorldRenderer wr) + { + if (!actor.IsInWorld || actor.IsDead) + return; + + var health = actor.TraitOrDefault(); + + if (DisplayHealth) + { + if (health == null || health.IsDead) + return; + + var displayValue = health.DisplayHP != health.HP ? (float?)health.DisplayHP / health.MaxHP : null; + DrawBar(wr, (float)health.HP / health.MaxHP, GetHealthColor(health), 0, displayValue, Color.OrangeRed); + } + + if (DisplayExtra) + DrawExtraBars(wr); + } + + public void RenderDebugGeometry(WorldRenderer wr) { } + public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; } + } +} diff --git a/OpenRA.Mods.Common/Graphics/IsometricSelectionBoxAnnotationRenderable.cs b/OpenRA.Mods.Common/Graphics/IsometricSelectionBoxAnnotationRenderable.cs new file mode 100644 index 0000000000..253ece1ba9 --- /dev/null +++ b/OpenRA.Mods.Common/Graphics/IsometricSelectionBoxAnnotationRenderable.cs @@ -0,0 +1,83 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Primitives; + +namespace OpenRA.Mods.Common.Graphics +{ + public struct IsometricSelectionBoxAnnotationRenderable : IRenderable, IFinalizedRenderable + { + static readonly float2 TLOffset = new float2(-12, -6); + static readonly float2 TROffset = new float2(12, -6); + static readonly float2 TOffset = new float2(0, -13); + static readonly float2[] Offsets = + { + -TROffset, -TLOffset, -TOffset, + TROffset, -TOffset, -TLOffset, + -TLOffset, TOffset, TROffset, + TLOffset, TROffset, TOffset, + -TROffset, TOffset, TLOffset, + TLOffset, -TOffset, -TROffset + }; + + readonly WPos pos; + readonly Polygon bounds; + readonly Color color; + + public IsometricSelectionBoxAnnotationRenderable(Actor actor, Polygon bounds, Color color) + { + pos = actor.CenterPosition; + this.bounds = bounds; + this.color = color; + } + + public IsometricSelectionBoxAnnotationRenderable(WPos pos, Polygon bounds, Color color) + { + this.pos = pos; + this.bounds = bounds; + this.color = color; + } + + public WPos Pos { get { return pos; } } + + public PaletteReference Palette { get { return null; } } + public int ZOffset { get { return 0; } } + public bool IsDecoration { get { return true; } } + + public IRenderable WithPalette(PaletteReference newPalette) { return this; } + public IRenderable WithZOffset(int newOffset) { return this; } + public IRenderable OffsetBy(WVec vec) { return new IsometricSelectionBoxAnnotationRenderable(pos + vec, bounds, color); } + public IRenderable AsDecoration() { return this; } + + public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; } + + public void Render(WorldRenderer wr) + { + var screen = bounds.Vertices.Select(v => wr.Viewport.WorldToViewPx(v).ToFloat2()).ToArray(); + + var tl = new float2(-12, -6); + var tr = new float2(12, -6); + var t = new float2(0, -13); + + var cr = Game.Renderer.RgbaColorRenderer; + for (var i = 0; i < 6; i++) + { + cr.DrawLine(new float3[] { screen[i] + Offsets[3 * i], screen[i], screen[i] + Offsets[3 * i + 1] }, 1, color, true); + cr.DrawLine(new float3[] { screen[i], screen[i] + Offsets[3 * i + 2] }, 1, color, true); + } + } + + public void RenderDebugGeometry(WorldRenderer wr) { } + public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; } + } +} diff --git a/OpenRA.Mods.Common/Traits/IsometricSelectable.cs b/OpenRA.Mods.Common/Traits/IsometricSelectable.cs new file mode 100644 index 0000000000..66815be527 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/IsometricSelectable.cs @@ -0,0 +1,142 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("This actor is selectable. Defines bounds of selectable area, selection class, selection priority and selection priority modifiers.")] + public class IsometricSelectableInfo : ITraitInfo, IMouseBoundsInfo, ISelectableInfo, IRulesetLoaded, Requires + { + [Desc("Defines a custom rectangle for mouse interaction with the actor.", + "If null, the engine will guess an appropriate size based on the building's footprint.", + "The first two numbers define the width and depth of the footprint rectangle.", + "The (optional) second two numbers define an x and y offset from the actor center.")] + public readonly int[] Bounds = null; + + [Desc("Height above the footprint for the top of the interaction rectangle.")] + public readonly int Height = 24; + + [Desc("Defines a custom rectangle for Decorations (e.g. the selection box).", + "If null, Bounds will be used instead.")] + public readonly int[] DecorationBounds = null; + + [Desc("Defines a custom height for Decorations (e.g. the selection box).", + "If < 0, Height will be used instead.", + "If Height is 0, this must be defined with a value greater than 0.")] + public readonly int DecorationHeight = -1; + + public readonly int Priority = 10; + + [Desc("Allow selection priority to be modified using a hotkey.", + "Valid values are None (priority is not affected by modifiers)", + "Ctrl (priority is raised when Ctrl pressed) and", + "Alt (priority is raised when Alt pressed).")] + public readonly SelectionPriorityModifiers PriorityModifiers = SelectionPriorityModifiers.None; + + [Desc("All units having the same selection class specified will be selected with select-by-type commands (e.g. double-click). ", + "Defaults to the actor name when not defined or inherited.")] + public readonly string Class = null; + + [VoiceReference] + public readonly string Voice = "Select"; + + public object Create(ActorInitializer init) { return new IsometricSelectable(init.Self, this); } + + int ISelectableInfo.Priority { get { return Priority; } } + SelectionPriorityModifiers ISelectableInfo.PriorityModifiers { get { return PriorityModifiers; } } + string ISelectableInfo.Voice { get { return Voice; } } + + public virtual void RulesetLoaded(Ruleset rules, ActorInfo ai) + { + var grid = Game.ModData.Manifest.Get(); + if (grid.Type != MapGridType.RectangularIsometric) + throw new YamlException("IsometricSelectable can only be used in mods that use the RectangularIsometric MapGrid type."); + + if (Height == 0 && DecorationHeight <= 0) + throw new YamlException("DecorationHeight must be defined and greater than 0 if Height is 0."); + } + } + + public class IsometricSelectable : IMouseBounds, ISelectable + { + readonly IsometricSelectableInfo info; + readonly string selectionClass = null; + readonly BuildingInfo buildingInfo; + + public IsometricSelectable(Actor self, IsometricSelectableInfo info) + { + this.info = info; + selectionClass = string.IsNullOrEmpty(info.Class) ? self.Info.Name : info.Class; + buildingInfo = self.Info.TraitInfo(); + } + + Polygon Bounds(Actor self, WorldRenderer wr, int[] bounds, int height) + { + int2 left, right, top, bottom; + if (bounds != null) + { + var offset = bounds.Length >= 4 ? new int2(bounds[2], bounds[3]) : int2.Zero; + var center = wr.ScreenPxPosition(self.CenterPosition) + offset; + left = center - new int2(bounds[0] / 2, 0); + right = left + new int2(bounds[0], 0); + top = center - new int2(0, bounds[1] / 2); + bottom = top + new int2(0, bounds[1]); + } + else + { + var xMin = int.MaxValue; + var xMax = int.MinValue; + var yMin = int.MaxValue; + var yMax = int.MinValue; + foreach (var c in buildingInfo.OccupiedTiles(self.Location)) + { + xMin = Math.Min(xMin, c.X); + xMax = Math.Max(xMax, c.X); + yMin = Math.Min(yMin, c.Y); + yMax = Math.Max(yMax, c.Y); + } + + left = wr.ScreenPxPosition(self.World.Map.CenterOfCell(new CPos(xMin, yMax)) - new WVec(768, 0, 0)); + right = wr.ScreenPxPosition(self.World.Map.CenterOfCell(new CPos(xMax, yMin)) + new WVec(768, 0, 0)); + top = wr.ScreenPxPosition(self.World.Map.CenterOfCell(new CPos(xMin, yMin)) - new WVec(0, 768, 0)); + bottom = wr.ScreenPxPosition(self.World.Map.CenterOfCell(new CPos(xMax, yMax)) + new WVec(0, 768, 0)); + } + + if (height == 0) + return new Polygon(new[] { top, left, bottom, right }); + + var h = new int2(0, height); + return new Polygon(new[] { top - h, left - h, left, bottom, right, right - h }); + } + + public Polygon Bounds(Actor self, WorldRenderer wr) + { + return Bounds(self, wr, info.Bounds, info.Height); + } + + public Polygon DecorationBounds(Actor self, WorldRenderer wr) + { + return Bounds(self, wr, info.DecorationBounds ?? info.Bounds, info.DecorationHeight >= 0 ? info.DecorationHeight : info.Height); + } + + Polygon IMouseBounds.MouseoverBounds(Actor self, WorldRenderer wr) + { + return Bounds(self, wr); + } + + string ISelectable.Class { get { return selectionClass; } } + } +} diff --git a/OpenRA.Mods.Common/Traits/Render/IsometricSelectionDecorations.cs b/OpenRA.Mods.Common/Traits/Render/IsometricSelectionDecorations.cs new file mode 100644 index 0000000000..acce98ed48 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Render/IsometricSelectionDecorations.cs @@ -0,0 +1,65 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.Collections.Generic; +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits.Render +{ + public class IsometricSelectionDecorationsInfo : SelectionDecorationsBaseInfo, Requires + { + public override object Create(ActorInitializer init) { return new IsometricSelectionDecorations(init.Self, this); } + } + + public class IsometricSelectionDecorations : SelectionDecorationsBase + { + readonly IsometricSelectable selectable; + + public IsometricSelectionDecorations(Actor self, IsometricSelectionDecorationsInfo info) + : base(info) + { + selectable = self.Trait(); + } + + protected override int2 GetDecorationPosition(Actor self, WorldRenderer wr, DecorationPosition pos) + { + var bounds = selectable.DecorationBounds(self, wr); + switch (pos) + { + case DecorationPosition.TopLeft: return bounds.Vertices[1]; + case DecorationPosition.TopRight: return bounds.Vertices[5]; + case DecorationPosition.BottomLeft: return bounds.Vertices[2]; + case DecorationPosition.BottomRight: return bounds.Vertices[4]; + case DecorationPosition.Top: return new int2((bounds.Vertices[1].X + bounds.Vertices[5].X) / 2, bounds.Vertices[1].Y); + default: return bounds.BoundingRect.TopLeft + new int2(bounds.BoundingRect.Size.Width / 2, bounds.BoundingRect.Size.Height / 2); + } + } + + protected override IEnumerable RenderSelectionBox(Actor self, WorldRenderer wr, Color color) + { + var bounds = selectable.DecorationBounds(self, wr); + yield return new IsometricSelectionBoxAnnotationRenderable(self, bounds, color); + } + + protected override IEnumerable RenderSelectionBars(Actor self, WorldRenderer wr, bool displayHealth, bool displayExtra) + { + if (!displayHealth && !displayExtra) + yield break; + + var bounds = selectable.DecorationBounds(self, wr); + yield return new IsometricSelectionBarsAnnotationRenderable(self, bounds, displayHealth, displayExtra); + } + } +} diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/CopyIsometricSelectableHeight.cs b/OpenRA.Mods.Common/UpdateRules/Rules/CopyIsometricSelectableHeight.cs new file mode 100644 index 0000000000..033a3a570c --- /dev/null +++ b/OpenRA.Mods.Common/UpdateRules/Rules/CopyIsometricSelectableHeight.cs @@ -0,0 +1,87 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OpenRA.Mods.Common.FileFormats; +using OpenRA.Mods.Common.Traits; + +namespace OpenRA.Mods.Common.UpdateRules.Rules +{ + public class CopyIsometricSelectableHeight : UpdateRule + { + public override string Name { get { return "Copy IsometricSelectable.Height from art*.ini definitions."; } } + public override string Description + { + get + { + return "Reads building Height entries art.ini/artfs.ini/artmd.ini from the current working directory\n" + + "and adds IsometricSelectable definitions to matching actors."; + } + } + + static readonly string[] SourceFiles = { "art.ini", "artfs.ini", "artmd.ini" }; + + readonly Dictionary selectionHeight = new Dictionary(); + + bool complete; + + public override IEnumerable BeforeUpdate(ModData modData) + { + if (complete) + yield break; + + var grid = Game.ModData.Manifest.Get(); + foreach (var filename in SourceFiles) + { + if (!File.Exists(filename)) + continue; + + var file = new IniFile(File.Open(filename, FileMode.Open)); + foreach (var section in file.Sections) + { + if (!section.Contains("Height")) + continue; + + selectionHeight[section.Name] = (int)(float.Parse(section.GetValue("Height", "1")) * grid.TileSize.Height); + } + } + } + + public override IEnumerable AfterUpdate(ModData modData) + { + // Rule only applies to the default ruleset - skip maps + complete = true; + yield break; + } + + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + { + if (complete || actorNode.LastChildMatching("IsometricSelectable") != null) + yield break; + + var height = 0; + if (!selectionHeight.TryGetValue(actorNode.Key.ToLowerInvariant(), out height)) + yield break; + + // Don't redefine the default value + if (height == 24) + yield break; + + var selection = new MiniYamlNode("IsometricSelectable", ""); + selection.AddNode("Height", FieldSaver.FormatValue(height)); + + actorNode.AddNode(selection); + } + } +} diff --git a/mods/ts/rules/bridges.yaml b/mods/ts/rules/bridges.yaml index cae795a9ff..82dcca3320 100644 --- a/mods/ts/rules/bridges.yaml +++ b/mods/ts/rules/bridges.yaml @@ -11,8 +11,10 @@ CABHUT: Palette: player Targetable: TargetTypes: C4 - -Selectable: + -IsometricSelectable: + -IsometricSelectionDecorations: Interactable: + SelectionDecorations: -Demolishable: -Explodes: -FrozenUnderFog: diff --git a/mods/ts/rules/civilian-structures.yaml b/mods/ts/rules/civilian-structures.yaml index 20d7e8a6f4..94d57f7c86 100644 --- a/mods/ts/rules/civilian-structures.yaml +++ b/mods/ts/rules/civilian-structures.yaml @@ -16,6 +16,8 @@ ABAN01: Pieces: 5, 9 ThrowsShrapnel@LARGE: Pieces: 2, 4 + IsometricSelectable: + Height: 72 ABAN02: Inherits: ^CivBuilding @@ -35,6 +37,8 @@ ABAN02: Pieces: 5, 9 ThrowsShrapnel@LARGE: Pieces: 2, 4 + IsometricSelectable: + Height: 48 ABAN03: Inherits: ^CivBuilding @@ -54,6 +58,8 @@ ABAN03: Pieces: 5, 9 ThrowsShrapnel@LARGE: Pieces: 2, 4 + IsometricSelectable: + Height: 48 ABAN04: Inherits: ^CivBuilding @@ -582,6 +588,8 @@ CA0012: Type: light Health: HP: 10000 + IsometricSelectable: + Height: 36 CA0013: Inherits: ^CivBuilding @@ -595,6 +603,8 @@ CA0013: Type: heavy Health: HP: 30000 + IsometricSelectable: + Height: 48 CA0014: Inherits: ^CivBuilding @@ -607,6 +617,8 @@ CA0014: Type: heavy Health: HP: 30000 + IsometricSelectable: + Height: 48 CA0015: Inherits: ^CivBuilding @@ -631,6 +643,8 @@ CA0016: Type: light Health: HP: 30000 + IsometricSelectable: + Height: 48 CA0017: Inherits: ^CivBuilding @@ -643,6 +657,8 @@ CA0017: Type: heavy Health: HP: 30000 + IsometricSelectable: + Height: 48 CA0018: Inherits: ^CivBuilding @@ -659,6 +675,8 @@ CA0018: Type: light Health: HP: 20000 + IsometricSelectable: + Height: 48 CA0019: Inherits: ^CivBuilding @@ -675,6 +693,8 @@ CA0019: Type: light Health: HP: 20000 + IsometricSelectable: + Height: 48 CA0020: Inherits: ^CivBuilding @@ -691,6 +711,8 @@ CA0020: Type: light Health: HP: 20000 + IsometricSelectable: + Height: 48 CA0021: Inherits: ^CivBuilding @@ -707,6 +729,8 @@ CA0021: Type: light Health: HP: 20000 + IsometricSelectable: + Height: 48 CAARAY: Inherits: ^CivBuilding @@ -734,6 +758,8 @@ CAARAY: Pieces: 5, 7 ThrowsShrapnel@LARGE: Pieces: 2, 4 + IsometricSelectable: + Height: 72 CAARMR: Inherits: ^CivBuilding @@ -807,6 +833,8 @@ CAHOSP: Pieces: 2, 4 MapEditorData: Categories: Civilian building + IsometricSelectable: + Height: 48 CAPYR01: Inherits: ^CivBuilding @@ -822,6 +850,8 @@ CAPYR01: HP: 40000 MapEditorData: ExcludeTilesets: SNOW + IsometricSelectable: + Height: 48 CAPYR02: Inherits: ^CivBuilding @@ -841,6 +871,8 @@ CAPYR02: Pieces: 6, 9 ThrowsShrapnel@LARGE: Pieces: 3, 4 + IsometricSelectable: + Height: 96 CAPYR03: Inherits: ^CivBuilding @@ -860,6 +892,8 @@ CAPYR03: Pieces: 6, 9 ThrowsShrapnel@LARGE: Pieces: 3, 4 + IsometricSelectable: + Height: 96 CITY01: Inherits: ^CivBuilding @@ -879,6 +913,8 @@ CITY01: Pieces: 5, 9 ThrowsShrapnel@LARGE: Pieces: 2, 4 + IsometricSelectable: + Height: 72 CITY02: Inherits: ^CivBuilding @@ -898,6 +934,8 @@ CITY02: Pieces: 5, 9 ThrowsShrapnel@LARGE: Pieces: 2, 4 + IsometricSelectable: + Height: 72 CITY03: Inherits: ^CivBuilding @@ -917,6 +955,8 @@ CITY03: Pieces: 5, 9 ThrowsShrapnel@LARGE: Pieces: 2, 4 + IsometricSelectable: + Height: 48 CITY04: Inherits: ^CivBuilding @@ -936,6 +976,8 @@ CITY04: Pieces: 5, 9 ThrowsShrapnel@LARGE: Pieces: 2, 4 + IsometricSelectable: + Height: 72 CITY05: Inherits: ^CivBuilding @@ -955,6 +997,8 @@ CITY05: Pieces: 5, 9 ThrowsShrapnel@LARGE: Pieces: 2, 4 + IsometricSelectable: + Height: 96 CITY06: Inherits: ^CivBuilding @@ -974,6 +1018,8 @@ CITY06: Pieces: 7, 9 ThrowsShrapnel@LARGE: Pieces: 3, 4 + IsometricSelectable: + Height: 72 CITY07: Inherits: ^CivBuilding @@ -993,6 +1039,8 @@ CITY07: Pieces: 7, 9 ThrowsShrapnel@LARGE: Pieces: 3, 4 + IsometricSelectable: + Height: 60 CITY08: Inherits: ^CivBuilding @@ -1023,6 +1071,8 @@ CITY09: HP: 40000 MapEditorData: RequireTilesets: TEMPERATE + IsometricSelectable: + Height: 48 CITY10: Inherits: ^CivBuilding @@ -1038,6 +1088,8 @@ CITY10: HP: 30000 MapEditorData: RequireTilesets: TEMPERATE + IsometricSelectable: + Height: 48 CITY11: Inherits: ^CivBuilding @@ -1053,6 +1105,8 @@ CITY11: HP: 50000 MapEditorData: RequireTilesets: TEMPERATE + IsometricSelectable: + Height: 96 CITY12: Inherits: ^CivBuilding @@ -1068,6 +1122,8 @@ CITY12: HP: 60000 MapEditorData: RequireTilesets: TEMPERATE + IsometricSelectable: + Height: 96 CITY13: Inherits: ^CivBuilding @@ -1083,6 +1139,8 @@ CITY13: HP: 50000 MapEditorData: RequireTilesets: TEMPERATE + IsometricSelectable: + Height: 96 CITY14: Inherits: ^CivBuilding @@ -1097,6 +1155,8 @@ CITY14: HP: 60000 MapEditorData: RequireTilesets: TEMPERATE + IsometricSelectable: + Height: 96 CITY15: Inherits: ^CivBuilding @@ -1135,6 +1195,8 @@ CITY16: Pieces: 7, 9 ThrowsShrapnel@LARGE: Pieces: 3, 4 + IsometricSelectable: + Height: 72 CITY17: Inherits: ^CivBuilding @@ -1154,6 +1216,8 @@ CITY17: Pieces: 7, 9 ThrowsShrapnel@LARGE: Pieces: 3, 4 + IsometricSelectable: + Height: 108 CITY18: Inherits: ^CivBuilding @@ -1173,6 +1237,8 @@ CITY18: Pieces: 8, 12 ThrowsShrapnel@LARGE: Pieces: 5, 7 + IsometricSelectable: + Height: 72 CITY19: Inherits: ^CivBuilding @@ -1231,6 +1297,8 @@ CITY22: HP: 10000 MapEditorData: RequireTilesets: TEMPERATE + IsometricSelectable: + Height: 72 CTDAM: Inherits: ^CivBuilding @@ -1278,6 +1346,8 @@ CTVEGA: Pieces: 7, 9 ThrowsShrapnel@LARGE: Pieces: 3, 4 + IsometricSelectable: + Height: 72 GAKODK: Inherits: ^CivBuilding @@ -1313,16 +1383,22 @@ GAOLDCC2: Name: Old Temple Building: Footprint: xx xX + IsometricSelectable: + Height: 36 GAOLDCC3: Inherits: ^OldBase Tooltip: Name: Old Weapons Factory + IsometricSelectable: + Height: 36 GAOLDCC4: Inherits: ^OldBase Tooltip: Name: Old Refinery + IsometricSelectable: + Height: 36 GAOLDCC5: Inherits: ^OldBase @@ -1371,9 +1447,6 @@ GASPOT: Building: Footprint: x Dimensions: 1, 1 - Selectable: - Bounds: 48, 30, 0, -4 - DecorationBounds: 48, 82, 0, -25 Power: Amount: -10 Armor: @@ -1387,6 +1460,8 @@ GASPOT: Sequence: idle-lights MapEditorData: Categories: Civilian building + IsometricSelectable: + Height: 72 GALITE: Inherits: ^Building @@ -1413,9 +1488,6 @@ GALITE: WithIdleOverlay@LIGHTING: Sequence: lighting Palette: alpha - Selectable: - Bounds: 24, 24, 0, -4 - DecorationBounds: 25, 35, 0, -12 -Cloak@EXTERNALCLOAK: -ExternalCondition@CLOAKGENERATOR: -ExternalCondition@CRATE-CLOAK: @@ -1453,6 +1525,8 @@ GAICBM: -WithDeathAnimation: MapEditorData: Categories: Vehicle + IsometricSelectable: + Height: 48 NAMNTK: Inherits: ^CivBuilding @@ -1470,6 +1544,8 @@ NAMNTK: Palette: player WithIdleOverlay@LIGHTS: Sequence: idle-lights + IsometricSelectable: + Height: 36 NTPYRA: Inherits: ^CivBuilding @@ -1493,6 +1569,8 @@ NTPYRA: Pieces: 7, 9 ThrowsShrapnel@LARGE: Pieces: 3, 5 + IsometricSelectable: + Height: 72 UFO: Inherits: ^CivBuilding @@ -1502,9 +1580,6 @@ UFO: Footprint: xxxxxx xxxxxx xxxxxx xxxxxx RenderSprites: Palette: terraindecoration - Selectable: - Bounds: 144, 72, 0, 0 - DecorationBounds: 144, 72, 0, 0 Tooltip: Name: Scrin Ship Health: @@ -1517,3 +1592,5 @@ UFO: Pieces: 9, 12 ThrowsShrapnel@LARGE: Pieces: 6, 8 + IsometricSelectable: + Height: 144 diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml index 9157072166..28490e78fe 100644 --- a/mods/ts/rules/defaults.yaml +++ b/mods/ts/rules/defaults.yaml @@ -1348,15 +1348,20 @@ green: pip-green yellow: pip-yellow -^SelectableCombatBuilding: - Inherits@selectiondecorations: ^Selectable - Selectable: - Priority: 4 - ^SelectableBuilding: - Inherits@selectiondecorations: ^Selectable - Selectable: + IsometricSelectable: Priority: 2 + IsometricSelectionDecorations: + WithTextControlGroupDecoration: + Margin: 1, 1 + DrawLineToTarget: + QueuedLineWidth: 2 + QueuedMarkerWidth: 3 + +^SelectableCombatBuilding: + Inherits@selectiondecorations: ^SelectableBuilding + IsometricSelectable: + Priority: 4 ^PrimaryBuilding: PrimaryBuilding: @@ -1366,6 +1371,5 @@ RequiresCondition: primary Position: Top RequiresSelection: true - Margin: 0, 5 Text: PRIMARY Color: E0D048 diff --git a/mods/ts/rules/gdi-structures.yaml b/mods/ts/rules/gdi-structures.yaml index 475a917830..25891c4051 100644 --- a/mods/ts/rules/gdi-structures.yaml +++ b/mods/ts/rules/gdi-structures.yaml @@ -29,9 +29,6 @@ GAPOWR: RequiresCondition: !build-incomplete PauseOnCondition: empdisable Sequence: idle-plug - Selectable: - Bounds: 90, 48, 0, -6 - DecorationBounds: 90, 84, 0, -12 Power: Amount: 100 RequiresCondition: !empdisable @@ -66,6 +63,8 @@ GAPOWR: RequiresCondition: !empdisable && powrup.b Amount: 50 ProvidesPrerequisite@buildingname: + IsometricSelectable: + Height: 48 GAPILE: Inherits: ^Building @@ -85,9 +84,6 @@ GAPILE: Building: Footprint: xx xx Dimensions: 2,2 - Selectable: - Bounds: 88, 48, 0, -8 - DecorationBounds: 88, 56, 0, -8 Health: HP: 80000 Armor: @@ -174,9 +170,6 @@ GAWEAP: Building: Footprint: xxX+ xxX+ xxX+ Dimensions: 4,3 - Selectable: - Bounds: 154, 96, -2, -12 - DecorationBounds: 154, 100, -2, -12 Health: HP: 100000 RevealsShroud: @@ -218,6 +211,8 @@ GAWEAP: Power: Amount: -30 ProvidesPrerequisite@buildingname: + IsometricSelectable: + Height: 48 GAHPAD: Inherits: ^Building @@ -269,9 +264,6 @@ GAHPAD: UseDeathTypeSuffix: false Power: Amount: -10 - Selectable: - Bounds: 88, 66, 0, -5 - DecorationBounds: 88, 66, 0, -5 ProvidesPrerequisite@buildingname: GADEPT: @@ -289,9 +281,6 @@ GADEPT: Building: Footprint: =+= x++ x+= Dimensions: 3,3 - Selectable: - Bounds: 96, 64, -6, -6 - DecorationBounds: 98, 68, -6, -6 Health: HP: 110000 RevealsShroud: @@ -355,9 +344,6 @@ GARADR: Building: Footprint: xx xx Dimensions: 2,2 - Selectable: - Bounds: 96, 48, 0, -6 - DecorationBounds: 96, 118, 0, -38 Health: HP: 100000 Armor: @@ -380,6 +366,8 @@ GARADR: Power: Amount: -50 ProvidesPrerequisite@buildingname: + IsometricSelectable: + Height: 72 GATECH: Inherits: ^Building @@ -399,9 +387,6 @@ GATECH: Building: Footprint: xxx xxx Dimensions: 3,2 - Selectable: - Bounds: 110, 60, 3, -4 - DecorationBounds: 110, 60, 3, -4 Health: HP: 50000 Armor: @@ -415,6 +400,8 @@ GATECH: Power: Amount: -150 ProvidesPrerequisite@buildingname: + IsometricSelectable: + Height: 48 GAPLUG: Inherits: ^Building @@ -424,9 +411,6 @@ GAPLUG: Cost: 1000 Tooltip: Name: GDI Upgrade Center - Selectable: - Bounds: 115,72,0,-12 - DecorationBounds: 115,104,0,-24 Buildable: BuildPaletteOrder: 170 Prerequisites: proc, gatech, ~structures.gdi, ~techlevel.superweapons @@ -524,3 +508,5 @@ GAPLUG: PauseOnCondition: disabled Sequence: idle-hunterseekerb ProvidesPrerequisite@buildingname: + IsometricSelectable: + Height: 48 diff --git a/mods/ts/rules/gdi-support.yaml b/mods/ts/rules/gdi-support.yaml index 8537225b75..ad0d0f0382 100644 --- a/mods/ts/rules/gdi-support.yaml +++ b/mods/ts/rules/gdi-support.yaml @@ -57,9 +57,6 @@ GACTWR: BuildPaletteOrder: 70 Prerequisites: gapile, ~structures.gdi, ~techlevel.low Description: Modular tower for base defenses. - Selectable: - Bounds: 48, 36, 0, -6 - DecorationBounds: 48, 48, 0, -12 Health: HP: 50000 Armor: @@ -139,6 +136,8 @@ GACTWR: ProvidesPrerequisite@buildingname: Replacement: ReplaceableTypes: GDITower + IsometricSelectable: + Height: 48 GAVULC: Inherits: ^BuildingPlug diff --git a/mods/ts/rules/nod-structures.yaml b/mods/ts/rules/nod-structures.yaml index 4e5f310fe1..2714836483 100644 --- a/mods/ts/rules/nod-structures.yaml +++ b/mods/ts/rules/nod-structures.yaml @@ -15,9 +15,6 @@ NAPOWR: Building: Footprint: xx xx Dimensions: 2,2 - Selectable: - Bounds: 88, 48, 2, -6 - DecorationBounds: 88, 80, 2, -12 Health: HP: 75000 Armor: @@ -35,6 +32,8 @@ NAPOWR: TargetTypes: Ground, Building, C4, SpyInfiltrate ScalePowerWithHealth: PowerTooltip: + IsometricSelectable: + Height: 48 NAAPWR: Inherits: ^Building @@ -53,9 +52,6 @@ NAAPWR: Building: Footprint: xxx xxx Dimensions: 2,3 - Selectable: - Bounds: 100, 54, 0, -4 - DecorationBounds: 100, 74, 0, -12 Health: HP: 75000 Armor: @@ -74,6 +70,8 @@ NAAPWR: ScalePowerWithHealth: PowerTooltip: ProvidesPrerequisite@buildingname: + IsometricSelectable: + Height: 48 NAHAND: Inherits: ^Building @@ -93,9 +91,6 @@ NAHAND: Building: Footprint: xxx xxx Dimensions: 3,2 - Selectable: - Bounds: 116, 60, 3, -6 - DecorationBounds: 116, 78, 3, -8 Health: HP: 80000 Armor: @@ -185,9 +180,6 @@ NAWEAP: Building: Footprint: xxX+ xxX+ xxX+ Dimensions: 4,3 - Selectable: - Bounds: 149, 80, -3, -10 - DecorationBounds: 149, 116, -3, -20 Health: HP: 100000 Armor: @@ -223,6 +215,8 @@ NAWEAP: Power: Amount: -30 ProvidesPrerequisite@buildingname: + IsometricSelectable: + Height: 48 NAHPAD: Inherits: ^Building @@ -274,10 +268,9 @@ NAHPAD: UseDeathTypeSuffix: false Power: Amount: -10 - Selectable: - Bounds: 78, 48, 0, -6 - DecorationBounds: 78, 54, 0, -8 ProvidesPrerequisite@buildingname: + IsometricSelectable: + Height: 36 NARADR: Inherits: ^Building @@ -297,9 +290,6 @@ NARADR: Building: Footprint: xx xx Dimensions: 2,2 - Selectable: - Bounds: 96, 48, 0, -6 - DecorationBounds: 96, 72, 0, -12 Health: HP: 100000 Armor: @@ -322,6 +312,8 @@ NARADR: Power: Amount: -40 ProvidesPrerequisite@buildingname: + IsometricSelectable: + Height: 72 NATECH: Inherits: ^Building @@ -341,9 +333,6 @@ NATECH: Building: Footprint: xx xx Dimensions: 2,2 - Selectable: - Bounds: 86, 48, 0, -4 - DecorationBounds: 86, 58, 0, -4 Health: HP: 50000 Armor: @@ -379,8 +368,6 @@ NATMPL: Dimensions: 4,3 RequiresBuildableArea: Adjacent: 3 - Selectable: - Bounds: 134, 120, 12, -12 Health: HP: 100000 Armor: @@ -407,3 +394,5 @@ NATMPL: PauseOnCondition: empdisable Exit@1: ExitsDebugOverlay: + IsometricSelectable: + Height: 60 diff --git a/mods/ts/rules/nod-support.yaml b/mods/ts/rules/nod-support.yaml index 037d8ad64e..83b6362d82 100644 --- a/mods/ts/rules/nod-support.yaml +++ b/mods/ts/rules/nod-support.yaml @@ -63,8 +63,6 @@ NAPOST: NodeTypes: laserfencenode SegmentType: nafnce SegmentsRequireNode: true - Selectable: - Bounds: 42, 44, 0, -12 LineBuildNode: Types: laserfencenode Power: @@ -156,9 +154,6 @@ NALASR: Prerequisites: nahand, ~structures.nod, ~techlevel.low BuildPaletteOrder: 90 Description: Basic base defense.\nRequires power to operate.\n Strong vs Ground units\n Weak vs Aircraft - Selectable: - Bounds: 40, 30, -8, -6 - DecorationBounds: 40, 36, -8, -8 Health: HP: 50000 Armor: @@ -202,9 +197,6 @@ NAOBEL: Building: Footprint: xx xx Dimensions: 2,2 - Selectable: - Bounds: 88, 42, 0, -6 - DecorationBounds: 88, 72, 0, -12 Health: HP: 72500 Armor: @@ -246,9 +238,6 @@ NASAM: Prerequisites: naradr, ~structures.nod, ~techlevel.medium BuildPaletteOrder: 100 Description: Nod Anti-Air base defense.\nRequires power to operate.\n Strong vs Aircraft\n Weak vs Ground units - Selectable: - Bounds: 40, 30, -3, -8 - DecorationBounds: 40, 36, -3, -8 Health: HP: 60000 Armor: @@ -312,9 +301,6 @@ NASTLH: EnableSound: cloak5.aud DisableSound: cloak5.aud AffectsParent: true - Selectable: - Bounds: 106, 48, 8, -6 - DecorationBounds: 106, 60, 8, -15 NAMISL: Inherits: ^Building @@ -335,9 +321,6 @@ NAMISL: Building: Footprint: xx xx Dimensions: 2,2 - Selectable: - Bounds: 75,48 - DecorationBounds: 75,48 Health: HP: 100000 Armor: @@ -397,9 +380,6 @@ NAWAST: Building: Footprint: Xx= xx= Xx= Dimensions: 3,3 - Selectable: - Bounds: 100, 60, 5, -5 - DecorationBounds: 100, 60, 5, -5 Health: HP: 40000 RevealsShroud: @@ -429,7 +409,9 @@ NAWAST: WithResourceStoragePipsDecoration: Position: BottomLeft RequiresSelection: true - Margin: 5, 2 + Margin: 8, 2 + FullSequence: pip-red-building + EmptySequence: pip-empty-building + PipStride: 4, 2 PipCount: 15 - FullSequence: pip-red Palette: pips diff --git a/mods/ts/rules/shared-structures.yaml b/mods/ts/rules/shared-structures.yaml index 2b97c5455e..745ac4a0f4 100644 --- a/mods/ts/rules/shared-structures.yaml +++ b/mods/ts/rules/shared-structures.yaml @@ -75,15 +75,14 @@ GACNST: RequiresCondition: !build-incomplete Power: Amount: 0 - Selectable: - Bounds: 144, 60, 0, -6 - DecorationBounds: 144, 80, 0, -12 ProvidesPrerequisite@gdi: Factions: gdi Prerequisite: structures.gdi ProvidesPrerequisite@nod: Factions: nod Prerequisite: structures.nod + IsometricSelectable: + Height: 36 PROC: Inherits: ^Building @@ -100,9 +99,6 @@ PROC: Building: Footprint: xxX+ xx++ xxX+ Dimensions: 4,3 - Selectable: - Bounds: 134, 96, 0, -12 - DecorationBounds: 134, 122, 0, -18 Health: HP: 90000 RevealsShroud: @@ -148,8 +144,11 @@ PROC: WithResourceStoragePipsDecoration: Position: BottomLeft RequiresSelection: true - Margin: 5, 2 - PipCount: 10 + Margin: 8, 2 + FullSequence: pip-green-building + EmptySequence: pip-empty-building + PipStride: 4, 2 + PipCount: 25 Palette: pips GASILO: @@ -167,9 +166,6 @@ GASILO: Building: Footprint: xx xx Dimensions: 2, 2 - Selectable: - Bounds: 80, 48, -5, 0 - DecorationBounds: 80, 48, -5, 0 -GivesBuildableArea: Health: HP: 30000 @@ -203,8 +199,11 @@ GASILO: WithResourceStoragePipsDecoration: Position: BottomLeft RequiresSelection: true - Margin: 5, 2 - PipCount: 5 + Margin: 8, 2 + FullSequence: pip-green-building + EmptySequence: pip-empty-building + PipStride: 4, 2 + PipCount: 12 Palette: pips ANYPOWER: diff --git a/mods/ts/rules/shared-support.yaml b/mods/ts/rules/shared-support.yaml index fcca6334ab..b841203425 100644 --- a/mods/ts/rules/shared-support.yaml +++ b/mods/ts/rules/shared-support.yaml @@ -14,9 +14,6 @@ NAPULS: Building: Footprint: xx xx Dimensions: 2,2 - Selectable: - Bounds: 78, 54, 0, -12 - DecorationBounds: 78, 54, 0, -12 Health: HP: 50000 Armor: