Implement isometric selection boxes for TS structures.
This commit is contained in:
142
OpenRA.Mods.Common/Traits/IsometricSelectable.cs
Normal file
142
OpenRA.Mods.Common/Traits/IsometricSelectable.cs
Normal file
@@ -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<BuildingInfo>
|
||||
{
|
||||
[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<MapGrid>();
|
||||
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<BuildingInfo>();
|
||||
}
|
||||
|
||||
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; } }
|
||||
}
|
||||
}
|
||||
@@ -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<IsometricSelectableInfo>
|
||||
{
|
||||
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<IsometricSelectable>();
|
||||
}
|
||||
|
||||
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<IRenderable> RenderSelectionBox(Actor self, WorldRenderer wr, Color color)
|
||||
{
|
||||
var bounds = selectable.DecorationBounds(self, wr);
|
||||
yield return new IsometricSelectionBoxAnnotationRenderable(self, bounds, color);
|
||||
}
|
||||
|
||||
protected override IEnumerable<IRenderable> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user