Rework decoration renderable traits:
- Removed implicit pip definitions and IPips interface. New decoration traits have been added to render them. Pip types are no longer hardcoded in OpenRA.Game. - Decoration rendering is now managed by SelectionDecorations(Base), which allows us to remove assumptions about the selection box geometry from the decoration traits. - RenderNameTag has been replaced by WithNameTagDecoration, which is an otherwise normal decoration trait. - Unify the configuration and reduce duplication between traits. - Removed hardcoded references to specific selection box renderables. - Remove legacy cruft.
This commit is contained in:
@@ -30,15 +30,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Initial ammo the actor is created with. Defaults to Ammo.")]
|
||||
public readonly int InitialAmmo = -1;
|
||||
|
||||
[Desc("Defaults to value in Ammo. 0 means no visible pips.")]
|
||||
public readonly int PipCount = -1;
|
||||
|
||||
[Desc("PipType to use for loaded ammo.")]
|
||||
public readonly PipType PipType = PipType.Green;
|
||||
|
||||
[Desc("PipType to use for empty ammo.")]
|
||||
public readonly PipType PipTypeEmpty = PipType.Transparent;
|
||||
|
||||
[Desc("How much ammo is reloaded after a certain period.")]
|
||||
public readonly int ReloadCount = 1;
|
||||
|
||||
@@ -56,7 +47,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public object Create(ActorInitializer init) { return new AmmoPool(init.Self, this); }
|
||||
}
|
||||
|
||||
public class AmmoPool : INotifyCreated, INotifyAttack, IPips, ISync
|
||||
public class AmmoPool : INotifyCreated, INotifyAttack, ISync
|
||||
{
|
||||
public readonly AmmoPoolInfo Info;
|
||||
readonly Stack<int> tokens = new Stack<int>();
|
||||
@@ -126,14 +117,5 @@ namespace OpenRA.Mods.Common.Traits
|
||||
while (CurrentAmmoCount < tokens.Count && tokens.Count > 0)
|
||||
conditionManager.RevokeCondition(self, tokens.Pop());
|
||||
}
|
||||
|
||||
public IEnumerable<PipType> GetPips(Actor self)
|
||||
{
|
||||
var pips = Info.PipCount >= 0 ? Info.PipCount : Info.Ammo;
|
||||
|
||||
return Enumerable.Range(0, pips).Select(i =>
|
||||
(CurrentAmmoCount * pips) / Info.Ammo > i ?
|
||||
Info.PipType : Info.PipTypeEmpty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("The maximum sum of Passenger.Weight that this actor can support.")]
|
||||
public readonly int MaxWeight = 0;
|
||||
|
||||
[Desc("Number of pips to display when this actor is selected.")]
|
||||
public readonly int PipCount = 0;
|
||||
|
||||
[Desc("`Passenger.CargoType`s that can be loaded into this actor.")]
|
||||
public readonly HashSet<string> Types = new HashSet<string>();
|
||||
|
||||
@@ -88,7 +85,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public object Create(ActorInitializer init) { return new Cargo(init, this); }
|
||||
}
|
||||
|
||||
public class Cargo : IPips, IIssueOrder, IResolveOrder, IOrderVoice, INotifyCreated, INotifyKilled,
|
||||
public class Cargo : IIssueOrder, IResolveOrder, IOrderVoice, INotifyCreated, INotifyKilled,
|
||||
INotifyOwnerChanged, INotifySold, INotifyActorDisposing, IIssueDeployOrder,
|
||||
ITransformActorInitModifier
|
||||
{
|
||||
@@ -379,30 +376,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
t.TurretFacing = facing.Value.Facing + Info.PassengerFacing;
|
||||
}
|
||||
|
||||
public IEnumerable<PipType> GetPips(Actor self)
|
||||
{
|
||||
var numPips = Info.PipCount;
|
||||
|
||||
for (var i = 0; i < numPips; i++)
|
||||
yield return GetPipAt(i);
|
||||
}
|
||||
|
||||
PipType GetPipAt(int i)
|
||||
{
|
||||
var n = i * Info.MaxWeight / Info.PipCount;
|
||||
|
||||
foreach (var c in cargo)
|
||||
{
|
||||
var pi = c.Info.TraitInfo<PassengerInfo>();
|
||||
if (n < pi.Weight)
|
||||
return pi.PipType;
|
||||
else
|
||||
n -= pi.Weight;
|
||||
}
|
||||
|
||||
return PipType.Transparent;
|
||||
}
|
||||
|
||||
public void Load(Actor self, Actor a)
|
||||
{
|
||||
cargo.Add(a);
|
||||
|
||||
@@ -42,9 +42,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("How many bales can it dump at once.")]
|
||||
public readonly int BaleUnloadAmount = 1;
|
||||
|
||||
[Desc("How many squares to show the fill level.")]
|
||||
public readonly int PipCount = 7;
|
||||
|
||||
public readonly int HarvestFacings = 0;
|
||||
|
||||
[Desc("Which resources it can harvest.")]
|
||||
@@ -90,15 +87,16 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public object Create(ActorInitializer init) { return new Harvester(init.Self, this); }
|
||||
}
|
||||
|
||||
public class Harvester : IIssueOrder, IResolveOrder, IPips, IOrderVoice,
|
||||
public class Harvester : IIssueOrder, IResolveOrder, IOrderVoice,
|
||||
ISpeedModifier, ISync, INotifyCreated
|
||||
{
|
||||
public readonly HarvesterInfo Info;
|
||||
public readonly IReadOnlyDictionary<ResourceTypeInfo, int> Contents;
|
||||
|
||||
readonly Mobile mobile;
|
||||
readonly ResourceLayer resLayer;
|
||||
readonly ResourceClaimLayer claimLayer;
|
||||
readonly Dictionary<ResourceTypeInfo, int> contents = new Dictionary<ResourceTypeInfo, int>();
|
||||
INotifyHarvesterAction[] notifyHarvesterAction;
|
||||
ConditionManager conditionManager;
|
||||
int conditionToken = ConditionManager.InvalidConditionToken;
|
||||
HarvesterResourceMultiplier[] resourceMultipliers;
|
||||
@@ -127,6 +125,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public Harvester(Actor self, HarvesterInfo info)
|
||||
{
|
||||
Info = info;
|
||||
Contents = new ReadOnlyDictionary<ResourceTypeInfo, int>(contents);
|
||||
|
||||
mobile = self.Trait<Mobile>();
|
||||
resLayer = self.World.WorldActor.Trait<ResourceLayer>();
|
||||
claimLayer = self.World.WorldActor.Trait<ResourceClaimLayer>();
|
||||
@@ -134,7 +134,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
notifyHarvesterAction = self.TraitsImplementing<INotifyHarvesterAction>().ToArray();
|
||||
resourceMultipliers = self.TraitsImplementing<HarvesterResourceMultiplier>().ToArray();
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
UpdateCondition(self);
|
||||
@@ -348,27 +347,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
}
|
||||
|
||||
PipType GetPipAt(int i)
|
||||
{
|
||||
var n = i * Info.Capacity / Info.PipCount;
|
||||
|
||||
foreach (var rt in contents)
|
||||
if (n < rt.Value)
|
||||
return rt.Key.PipColor;
|
||||
else
|
||||
n -= rt.Value;
|
||||
|
||||
return PipType.Transparent;
|
||||
}
|
||||
|
||||
IEnumerable<PipType> IPips.GetPips(Actor self)
|
||||
{
|
||||
var numPips = Info.PipCount;
|
||||
|
||||
for (var i = 0; i < numPips; i++)
|
||||
yield return GetPipAt(i);
|
||||
}
|
||||
|
||||
int ISpeedModifier.GetSpeedModifier()
|
||||
{
|
||||
return 100 - (100 - Info.FullyLoadedSpeed) * contents.Values.Sum() / Info.Capacity;
|
||||
|
||||
@@ -17,7 +17,7 @@ using OpenRA.Traits;
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Used to enable mouse interaction on actors that are not Selectable.")]
|
||||
public class InteractableInfo : ITraitInfo, IMouseBoundsInfo, IDecorationBoundsInfo
|
||||
public class InteractableInfo : ITraitInfo, IMouseBoundsInfo
|
||||
{
|
||||
[Desc("Defines a custom rectangle for mouse interaction with the actor.",
|
||||
"If null, the engine will guess an appropriate size based on the With*Body trait.",
|
||||
@@ -32,7 +32,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public virtual object Create(ActorInitializer init) { return new Interactable(this); }
|
||||
}
|
||||
|
||||
public class Interactable : INotifyCreated, IMouseBounds, IDecorationBounds
|
||||
public class Interactable : INotifyCreated, IMouseBounds
|
||||
{
|
||||
readonly InteractableInfo info;
|
||||
IAutoMouseBounds[] autoBounds;
|
||||
@@ -72,7 +72,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return Bounds(self, wr, info.Bounds);
|
||||
}
|
||||
|
||||
Rectangle IDecorationBounds.DecorationBounds(Actor self, WorldRenderer wr)
|
||||
public Rectangle DecorationBounds(Actor self, WorldRenderer wr)
|
||||
{
|
||||
return Bounds(self, wr, info.DecorationBounds ?? info.Bounds);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public class PassengerInfo : ITraitInfo, IObservesVariablesInfo
|
||||
{
|
||||
public readonly string CargoType = null;
|
||||
public readonly PipType PipType = PipType.Green;
|
||||
|
||||
[Desc("If defined, use a custom pip type defined on the transport's WithCargoPipsDecoration.CustomPipSequences list.")]
|
||||
public readonly string CustomPipType = null;
|
||||
|
||||
public readonly int Weight = 1;
|
||||
|
||||
[GrantedConditionReference]
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
#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
|
||||
{
|
||||
[Desc("Displays the player name above the unit")]
|
||||
class RenderNameTagInfo : ITraitInfo, Requires<IDecorationBoundsInfo>
|
||||
{
|
||||
public readonly int MaxLength = 10;
|
||||
|
||||
public readonly string Font = "TinyBold";
|
||||
|
||||
public object Create(ActorInitializer init) { return new RenderNameTag(init.Self, this); }
|
||||
}
|
||||
|
||||
class RenderNameTag : IRenderAnnotations
|
||||
{
|
||||
readonly SpriteFont font;
|
||||
readonly Color color;
|
||||
readonly string name;
|
||||
readonly IDecorationBounds[] decorationBounds;
|
||||
|
||||
public RenderNameTag(Actor self, RenderNameTagInfo info)
|
||||
{
|
||||
font = Game.Renderer.Fonts[info.Font];
|
||||
color = self.Owner.Color;
|
||||
|
||||
if (self.Owner.PlayerName.Length > info.MaxLength)
|
||||
name = self.Owner.PlayerName.Substring(0, info.MaxLength);
|
||||
else
|
||||
name = self.Owner.PlayerName;
|
||||
|
||||
decorationBounds = self.TraitsImplementing<IDecorationBounds>().ToArray();
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IRenderAnnotations.RenderAnnotations(Actor self, WorldRenderer wr)
|
||||
{
|
||||
if (self.World.FogObscures(self))
|
||||
yield break;
|
||||
|
||||
var bounds = decorationBounds.FirstNonEmptyBounds(self, wr);
|
||||
var spaceBuffer = (int)(10 / wr.Viewport.Zoom);
|
||||
var effectPos = wr.ProjectedPosition(new int2((bounds.Left + bounds.Right) / 2, bounds.Y - spaceBuffer));
|
||||
|
||||
yield return new TextAnnotationRenderable(font, effectPos, 0, color, name);
|
||||
}
|
||||
|
||||
bool IRenderAnnotations.SpatiallyPartitionable { get { return false; } }
|
||||
}
|
||||
}
|
||||
@@ -18,160 +18,49 @@ using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits.Render
|
||||
{
|
||||
public class SelectionDecorationsInfo : ITraitInfo, Requires<IDecorationBoundsInfo>
|
||||
public class SelectionDecorationsInfo : SelectionDecorationsBaseInfo, Requires<InteractableInfo>
|
||||
{
|
||||
[PaletteReference]
|
||||
public readonly string Palette = "chrome";
|
||||
|
||||
[Desc("Health bar, production progress bar etc.")]
|
||||
public readonly bool RenderSelectionBars = true;
|
||||
|
||||
public readonly bool RenderSelectionBox = true;
|
||||
|
||||
public readonly Color SelectionBoxColor = Color.White;
|
||||
|
||||
public readonly string Image = "pips";
|
||||
|
||||
public object Create(ActorInitializer init) { return new SelectionDecorations(init.Self, this); }
|
||||
public override object Create(ActorInitializer init) { return new SelectionDecorations(init.Self, this); }
|
||||
}
|
||||
|
||||
public class SelectionDecorations : ISelectionDecorations, IRenderAnnotations, INotifyCreated, ITick
|
||||
public class SelectionDecorations : SelectionDecorationsBase
|
||||
{
|
||||
// depends on the order of pips in TraitsInterfaces.cs!
|
||||
static readonly string[] PipStrings = { "pip-empty", "pip-green", "pip-yellow", "pip-red", "pip-gray", "pip-blue", "pip-ammo", "pip-ammoempty" };
|
||||
|
||||
public readonly SelectionDecorationsInfo Info;
|
||||
|
||||
readonly IDecorationBounds[] decorationBounds;
|
||||
readonly Animation pipImages;
|
||||
IPips[] pipSources;
|
||||
readonly Interactable interactable;
|
||||
|
||||
public SelectionDecorations(Actor self, SelectionDecorationsInfo info)
|
||||
: base(info)
|
||||
{
|
||||
Info = info;
|
||||
|
||||
decorationBounds = self.TraitsImplementing<IDecorationBounds>().ToArray();
|
||||
pipImages = new Animation(self.World, Info.Image);
|
||||
interactable = self.Trait<Interactable>();
|
||||
}
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
protected override int2 GetDecorationPosition(Actor self, WorldRenderer wr, DecorationPosition pos)
|
||||
{
|
||||
pipSources = self.TraitsImplementing<IPips>().ToArray();
|
||||
}
|
||||
|
||||
IEnumerable<WPos> ActivityTargetPath(Actor self)
|
||||
{
|
||||
if (!self.IsInWorld || self.IsDead)
|
||||
yield break;
|
||||
|
||||
var activity = self.CurrentActivity;
|
||||
if (activity != null)
|
||||
var bounds = interactable.DecorationBounds(self, wr);
|
||||
switch (pos)
|
||||
{
|
||||
var targets = activity.GetTargets(self);
|
||||
yield return self.CenterPosition;
|
||||
|
||||
foreach (var t in targets.Where(t => t.Type != TargetType.Invalid))
|
||||
yield return t.CenterPosition;
|
||||
case DecorationPosition.TopLeft: return bounds.TopLeft;
|
||||
case DecorationPosition.TopRight: return bounds.TopRight;
|
||||
case DecorationPosition.BottomLeft: return bounds.BottomLeft;
|
||||
case DecorationPosition.BottomRight: return bounds.BottomRight;
|
||||
case DecorationPosition.Top: return new int2(bounds.Left + bounds.Size.Width / 2, bounds.Top);
|
||||
default: return bounds.TopLeft + new int2(bounds.Size.Width / 2, bounds.Size.Height / 2);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IRenderAnnotations.RenderAnnotations(Actor self, WorldRenderer wr)
|
||||
protected override IEnumerable<IRenderable> RenderSelectionBox(Actor self, WorldRenderer wr, Color color)
|
||||
{
|
||||
if (self.World.FogObscures(self))
|
||||
return Enumerable.Empty<IRenderable>();
|
||||
|
||||
return DrawDecorations(self, wr);
|
||||
var bounds = interactable.DecorationBounds(self, wr);
|
||||
yield return new SelectionBoxAnnotationRenderable(self, bounds, color);
|
||||
}
|
||||
|
||||
bool IRenderAnnotations.SpatiallyPartitionable { get { return true; } }
|
||||
|
||||
IEnumerable<IRenderable> DrawDecorations(Actor self, WorldRenderer wr)
|
||||
protected override IEnumerable<IRenderable> RenderSelectionBars(Actor self, WorldRenderer wr, bool displayHealth, bool displayExtra)
|
||||
{
|
||||
var selected = self.World.Selection.Contains(self);
|
||||
var regularWorld = self.World.Type == WorldType.Regular;
|
||||
var statusBars = Game.Settings.Game.StatusBars;
|
||||
var bounds = decorationBounds.FirstNonEmptyBounds(self, wr);
|
||||
|
||||
// Health bars are shown when:
|
||||
// * actor is selected
|
||||
// * status bar preference is set to "always show"
|
||||
// * status bar preference is set to "when damaged" and actor is damaged
|
||||
var displayHealth = selected || (regularWorld && statusBars == StatusBarsType.AlwaysShow)
|
||||
|| (regularWorld && statusBars == StatusBarsType.DamageShow && self.GetDamageState() != DamageState.Undamaged);
|
||||
|
||||
// Extra bars are shown when:
|
||||
// * actor is selected
|
||||
// * status bar preference is set to "always show"
|
||||
// * status bar preference is set to "when damaged"
|
||||
var displayExtra = selected || (regularWorld && statusBars != StatusBarsType.Standard);
|
||||
|
||||
if (Info.RenderSelectionBox && selected)
|
||||
yield return new SelectionBoxAnnotationRenderable(self, bounds, Info.SelectionBoxColor);
|
||||
|
||||
if (Info.RenderSelectionBars && (displayHealth || displayExtra))
|
||||
yield return new SelectionBarsAnnotationRenderable(self, bounds, displayHealth, displayExtra);
|
||||
|
||||
// Target lines and pips are always only displayed for selected allied actors
|
||||
if (!selected || !self.Owner.IsAlliedWith(wr.World.RenderPlayer))
|
||||
// Don't render the selection bars for non-selectable actors
|
||||
if (!(interactable is Selectable) || (!displayHealth && !displayExtra))
|
||||
yield break;
|
||||
|
||||
if (self.World.LocalPlayer != null && self.World.LocalPlayer.PlayerActor.Trait<DeveloperMode>().PathDebug)
|
||||
yield return new TargetLineRenderable(ActivityTargetPath(self), Color.Green);
|
||||
|
||||
foreach (var r in DrawPips(self, bounds, wr))
|
||||
yield return r;
|
||||
}
|
||||
|
||||
public void DrawRollover(Actor self, WorldRenderer worldRenderer)
|
||||
{
|
||||
var bounds = decorationBounds.FirstNonEmptyBounds(self, worldRenderer);
|
||||
new SelectionBarsAnnotationRenderable(self, bounds, true, true).Render(worldRenderer);
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> DrawPips(Actor self, Rectangle bounds, WorldRenderer wr)
|
||||
{
|
||||
if (pipSources.Length == 0)
|
||||
return Enumerable.Empty<IRenderable>();
|
||||
|
||||
return DrawPipsInner(self, bounds, wr);
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> DrawPipsInner(Actor self, Rectangle bounds, WorldRenderer wr)
|
||||
{
|
||||
pipImages.PlayRepeating(PipStrings[0]);
|
||||
|
||||
var palette = wr.Palette(Info.Palette);
|
||||
var basePosition = wr.Viewport.WorldToViewPx(new int2(bounds.Left, bounds.Bottom));
|
||||
var pipSize = pipImages.Image.Size.XY.ToInt2();
|
||||
var pipxyBase = basePosition + new int2(1 - pipSize.X / 2, -(3 + pipSize.Y / 2));
|
||||
var pipxyOffset = new int2(0, 0);
|
||||
var width = bounds.Width;
|
||||
|
||||
foreach (var pips in pipSources)
|
||||
{
|
||||
var thisRow = pips.GetPips(self);
|
||||
if (thisRow == null)
|
||||
continue;
|
||||
|
||||
foreach (var pip in thisRow)
|
||||
{
|
||||
if (pipxyOffset.X + pipSize.X >= width)
|
||||
pipxyOffset = new int2(0, pipxyOffset.Y - pipSize.Y);
|
||||
|
||||
pipImages.PlayRepeating(PipStrings[(int)pip]);
|
||||
pipxyOffset += new int2(pipSize.X, 0);
|
||||
|
||||
yield return new UISpriteRenderable(pipImages.Image, self.CenterPosition, pipxyBase + pipxyOffset, 0, palette, 1f);
|
||||
}
|
||||
|
||||
// Increment row
|
||||
pipxyOffset = new int2(0, pipxyOffset.Y - (pipSize.Y + 1));
|
||||
}
|
||||
}
|
||||
|
||||
void ITick.Tick(Actor self)
|
||||
{
|
||||
pipImages.Tick();
|
||||
var bounds = interactable.DecorationBounds(self, wr);
|
||||
yield return new SelectionBarsAnnotationRenderable(self, bounds, displayHealth, displayExtra);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
145
OpenRA.Mods.Common/Traits/Render/SelectionDecorationsBase.cs
Normal file
145
OpenRA.Mods.Common/Traits/Render/SelectionDecorationsBase.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
#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.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits.Render
|
||||
{
|
||||
public abstract class SelectionDecorationsBaseInfo : ITraitInfo
|
||||
{
|
||||
public readonly Color SelectionBoxColor = Color.White;
|
||||
|
||||
public abstract object Create(ActorInitializer init);
|
||||
}
|
||||
|
||||
public abstract class SelectionDecorationsBase : ISelectionDecorations, IRenderAnnotations, INotifyCreated
|
||||
{
|
||||
Dictionary<DecorationPosition, IDecoration[]> decorations;
|
||||
Dictionary<DecorationPosition, IDecoration[]> selectedDecorations;
|
||||
|
||||
protected readonly SelectionDecorationsBaseInfo info;
|
||||
|
||||
public SelectionDecorationsBase(SelectionDecorationsBaseInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
var groupedDecorations = new Dictionary<DecorationPosition, List<IDecoration>>();
|
||||
var groupedSelectionDecorations = new Dictionary<DecorationPosition, List<IDecoration>>();
|
||||
foreach (var d in self.TraitsImplementing<IDecoration>())
|
||||
{
|
||||
groupedSelectionDecorations.GetOrAdd(d.Position).Add(d);
|
||||
if (!d.RequiresSelection)
|
||||
groupedDecorations.GetOrAdd(d.Position).Add(d);
|
||||
}
|
||||
|
||||
decorations = groupedDecorations.ToDictionary(
|
||||
d => d.Key,
|
||||
d => d.Value.ToArray());
|
||||
|
||||
selectedDecorations = groupedSelectionDecorations.ToDictionary(
|
||||
d => d.Key,
|
||||
d => d.Value.ToArray());
|
||||
}
|
||||
|
||||
IEnumerable<WPos> ActivityTargetPath(Actor self)
|
||||
{
|
||||
if (!self.IsInWorld || self.IsDead)
|
||||
yield break;
|
||||
|
||||
var activity = self.CurrentActivity;
|
||||
if (activity != null)
|
||||
{
|
||||
var targets = activity.GetTargets(self);
|
||||
yield return self.CenterPosition;
|
||||
|
||||
foreach (var t in targets.Where(t => t.Type != TargetType.Invalid))
|
||||
yield return t.CenterPosition;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IRenderAnnotations.RenderAnnotations(Actor self, WorldRenderer wr)
|
||||
{
|
||||
if (self.World.FogObscures(self))
|
||||
return Enumerable.Empty<IRenderable>();
|
||||
|
||||
return DrawDecorations(self, wr);
|
||||
}
|
||||
|
||||
bool IRenderAnnotations.SpatiallyPartitionable { get { return true; } }
|
||||
|
||||
IEnumerable<IRenderable> DrawDecorations(Actor self, WorldRenderer wr)
|
||||
{
|
||||
var selected = self.World.Selection.Contains(self);
|
||||
var regularWorld = self.World.Type == WorldType.Regular;
|
||||
var statusBars = Game.Settings.Game.StatusBars;
|
||||
|
||||
// Health bars are shown when:
|
||||
// * actor is selected
|
||||
// * status bar preference is set to "always show"
|
||||
// * status bar preference is set to "when damaged" and actor is damaged
|
||||
var displayHealth = selected || (regularWorld && statusBars == StatusBarsType.AlwaysShow)
|
||||
|| (regularWorld && statusBars == StatusBarsType.DamageShow && self.GetDamageState() != DamageState.Undamaged);
|
||||
|
||||
// Extra bars are shown when:
|
||||
// * actor is selected
|
||||
// * status bar preference is set to "always show" or "when damaged"
|
||||
var displayExtra = selected || (regularWorld && statusBars != StatusBarsType.Standard);
|
||||
|
||||
if (selected)
|
||||
foreach (var r in RenderSelectionBox(self, wr, info.SelectionBoxColor))
|
||||
yield return r;
|
||||
|
||||
if (displayHealth || displayExtra)
|
||||
foreach (var r in RenderSelectionBars(self, wr, displayHealth, displayExtra))
|
||||
yield return r;
|
||||
|
||||
var renderDecorations = self.World.Selection.Contains(self) ? selectedDecorations : decorations;
|
||||
foreach (var kv in renderDecorations)
|
||||
{
|
||||
var pos = GetDecorationPosition(self, wr, kv.Key);
|
||||
foreach (var r in kv.Value)
|
||||
foreach (var rr in r.RenderDecoration(self, wr, pos))
|
||||
yield return rr;
|
||||
}
|
||||
|
||||
// Target lines and pips are always only displayed for selected allied actors
|
||||
if (!selected || !self.Owner.IsAlliedWith(wr.World.RenderPlayer))
|
||||
yield break;
|
||||
|
||||
if (self.World.LocalPlayer != null && self.World.LocalPlayer.PlayerActor.Trait<DeveloperMode>().PathDebug)
|
||||
yield return new TargetLineRenderable(ActivityTargetPath(self), Color.Green);
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> ISelectionDecorations.RenderRolloverAnnotations(Actor self, WorldRenderer worldRenderer)
|
||||
{
|
||||
if (self.World.Selection.Contains(self))
|
||||
return Enumerable.Empty<IRenderable>();
|
||||
|
||||
return RenderSelectionBars(self, worldRenderer, true, true);
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> ISelectionDecorations.RenderSelectionAnnotations(Actor self, WorldRenderer worldRenderer, Color color)
|
||||
{
|
||||
return RenderSelectionBox(self, worldRenderer, color);
|
||||
}
|
||||
|
||||
protected abstract int2 GetDecorationPosition(Actor self, WorldRenderer wr, DecorationPosition pos);
|
||||
protected abstract IEnumerable<IRenderable> RenderSelectionBox(Actor self, WorldRenderer wr, Color color);
|
||||
protected abstract IEnumerable<IRenderable> RenderSelectionBars(Actor self, WorldRenderer wr, bool displayHealth, bool displayExtra);
|
||||
}
|
||||
}
|
||||
@@ -32,10 +32,6 @@ namespace OpenRA.Mods.Common.Traits.Render
|
||||
[Desc("Palette to render the sprite in. Reference the world actor's PaletteFrom* traits.")]
|
||||
public readonly string Palette = "chrome";
|
||||
|
||||
[Desc("Point on the production icon's used as reference for offsetting the overlay. ",
|
||||
"Possible values are combinations of Center, Top, Bottom, Left, Right.")]
|
||||
public readonly ReferencePoints ReferencePoint = ReferencePoints.Top | ReferencePoints.Left;
|
||||
|
||||
public object Create(ActorInitializer init) { return new VeteranProductionIconOverlay(init, this); }
|
||||
}
|
||||
|
||||
@@ -77,18 +73,8 @@ namespace OpenRA.Mods.Common.Traits.Render
|
||||
string IProductionIconOverlay.Palette { get { return info.Palette; } }
|
||||
float2 IProductionIconOverlay.Offset(float2 iconSize)
|
||||
{
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
if (info.ReferencePoint.HasFlag(ReferencePoints.Top))
|
||||
y -= iconSize.Y / 2 - sprite.Size.Y / 2;
|
||||
else if (info.ReferencePoint.HasFlag(ReferencePoints.Bottom))
|
||||
y += iconSize.Y / 2 - sprite.Size.Y / 2;
|
||||
|
||||
if (info.ReferencePoint.HasFlag(ReferencePoints.Left))
|
||||
x -= iconSize.X / 2 - sprite.Size.X / 2;
|
||||
else if (info.ReferencePoint.HasFlag(ReferencePoints.Right))
|
||||
x += iconSize.X / 2 - sprite.Size.X / 2;
|
||||
|
||||
var x = (sprite.Size.X - iconSize.X) / 2;
|
||||
var y = (sprite.Size.Y - iconSize.Y) / 2;
|
||||
return new float2(x, y);
|
||||
}
|
||||
|
||||
|
||||
83
OpenRA.Mods.Common/Traits/Render/WithAmmoPipsDecoration.cs
Normal file
83
OpenRA.Mods.Common/Traits/Render/WithAmmoPipsDecoration.cs
Normal file
@@ -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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits.Render
|
||||
{
|
||||
public class WithAmmoPipsDecorationInfo : WithDecorationBaseInfo, Requires<AmmoPoolInfo>
|
||||
{
|
||||
[Desc("Number of pips to display. Defaults to the sum of the enabled AmmoPool.Ammo.")]
|
||||
public readonly int PipCount = -1;
|
||||
|
||||
[Desc("If non-zero, override the spacing between adjacent pips.")]
|
||||
public readonly int2 PipStride = int2.Zero;
|
||||
|
||||
[Desc("Image that defines the pip sequences.")]
|
||||
public readonly string Image = "pips";
|
||||
|
||||
[SequenceReference("Image")]
|
||||
[Desc("Sequence used for empty pips.")]
|
||||
public readonly string EmptySequence = "pip-empty";
|
||||
|
||||
[SequenceReference("Image")]
|
||||
[Desc("Sequence used for full pips.")]
|
||||
public readonly string FullSequence = "pip-green";
|
||||
|
||||
[PaletteReference]
|
||||
public readonly string Palette = "chrome";
|
||||
|
||||
public override object Create(ActorInitializer init) { return new WithAmmoPipsDecoration(init.Self, this); }
|
||||
}
|
||||
|
||||
public class WithAmmoPipsDecoration : WithDecorationBase<WithAmmoPipsDecorationInfo>
|
||||
{
|
||||
readonly AmmoPool[] ammo;
|
||||
readonly Animation pips;
|
||||
|
||||
public WithAmmoPipsDecoration(Actor self, WithAmmoPipsDecorationInfo info)
|
||||
: base(self, info)
|
||||
{
|
||||
ammo = self.TraitsImplementing<AmmoPool>().ToArray();
|
||||
pips = new Animation(self.World, info.Image);
|
||||
}
|
||||
|
||||
protected override IEnumerable<IRenderable> RenderDecoration(Actor self, WorldRenderer wr, int2 screenPos)
|
||||
{
|
||||
pips.PlayRepeating(Info.EmptySequence);
|
||||
|
||||
var palette = wr.Palette(Info.Palette);
|
||||
var pipSize = pips.Image.Size.XY.ToInt2();
|
||||
var pipStride = Info.PipStride != int2.Zero ? Info.PipStride : new int2(pipSize.X, 0);
|
||||
screenPos -= pipSize / 2;
|
||||
|
||||
var currentAmmo = 0;
|
||||
var totalAmmo = 0;
|
||||
foreach (var a in ammo)
|
||||
{
|
||||
currentAmmo += a.CurrentAmmoCount;
|
||||
totalAmmo += a.Info.Ammo;
|
||||
}
|
||||
|
||||
var pipCount = Info.PipCount > 0 ? Info.PipCount : totalAmmo;
|
||||
for (var i = 0; i < pipCount; i++)
|
||||
{
|
||||
pips.PlayRepeating(currentAmmo * pipCount > i * totalAmmo ? Info.FullSequence : Info.EmptySequence);
|
||||
yield return new UISpriteRenderable(pips.Image, self.CenterPosition, screenPos, 0, palette, 1f);
|
||||
|
||||
screenPos += pipStride;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
101
OpenRA.Mods.Common/Traits/Render/WithCargoPipsDecoration.cs
Normal file
101
OpenRA.Mods.Common/Traits/Render/WithCargoPipsDecoration.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
#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 OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits.Render
|
||||
{
|
||||
public class WithCargoPipsDecorationInfo : WithDecorationBaseInfo, Requires<CargoInfo>
|
||||
{
|
||||
[Desc("Number of pips to display. Defaults to Cargo.MaxWeight.")]
|
||||
public readonly int PipCount = -1;
|
||||
|
||||
[Desc("If non-zero, override the spacing between adjacent pips.")]
|
||||
public readonly int2 PipStride = int2.Zero;
|
||||
|
||||
[Desc("Image that defines the pip sequences.")]
|
||||
public readonly string Image = "pips";
|
||||
|
||||
[SequenceReference("Image")]
|
||||
[Desc("Sequence used for empty pips.")]
|
||||
public readonly string EmptySequence = "pip-empty";
|
||||
|
||||
[SequenceReference("Image")]
|
||||
[Desc("Sequence used for full pips that aren't defined in CustomPipSequences.")]
|
||||
public readonly string FullSequence = "pip-green";
|
||||
|
||||
// TODO: [SequenceReference] isn't smart enough to use Dictionaries.
|
||||
[Desc("Pip sequence to use for specific passenger actors.")]
|
||||
public readonly Dictionary<string, string> CustomPipSequences = new Dictionary<string, string>();
|
||||
|
||||
[PaletteReference]
|
||||
public readonly string Palette = "chrome";
|
||||
|
||||
public override object Create(ActorInitializer init) { return new WithCargoPipsDecoration(init.Self, this); }
|
||||
}
|
||||
|
||||
public class WithCargoPipsDecoration : WithDecorationBase<WithCargoPipsDecorationInfo>
|
||||
{
|
||||
readonly Cargo cargo;
|
||||
readonly Animation pips;
|
||||
readonly int pipCount;
|
||||
|
||||
public WithCargoPipsDecoration(Actor self, WithCargoPipsDecorationInfo info)
|
||||
: base(self, info)
|
||||
{
|
||||
cargo = self.Trait<Cargo>();
|
||||
pipCount = info.PipCount > 0 ? info.PipCount : cargo.Info.MaxWeight;
|
||||
pips = new Animation(self.World, info.Image);
|
||||
}
|
||||
|
||||
string GetPipSequence(int i)
|
||||
{
|
||||
var n = i * cargo.Info.MaxWeight / pipCount;
|
||||
|
||||
foreach (var c in cargo.Passengers)
|
||||
{
|
||||
var pi = c.Info.TraitInfo<PassengerInfo>();
|
||||
if (n < pi.Weight)
|
||||
{
|
||||
var sequence = Info.FullSequence;
|
||||
if (pi.CustomPipType != null && !Info.CustomPipSequences.TryGetValue(pi.CustomPipType, out sequence))
|
||||
Log.Write("debug", "Actor type {0} defines a custom pip type {1} that is not defined for actor type {2}".F(c.Info.Name, pi.CustomPipType, self.Info.Name));
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
n -= pi.Weight;
|
||||
}
|
||||
|
||||
return Info.EmptySequence;
|
||||
}
|
||||
|
||||
protected override IEnumerable<IRenderable> RenderDecoration(Actor self, WorldRenderer wr, int2 screenPos)
|
||||
{
|
||||
pips.PlayRepeating(Info.EmptySequence);
|
||||
|
||||
var palette = wr.Palette(Info.Palette);
|
||||
var pipSize = pips.Image.Size.XY.ToInt2();
|
||||
var pipStride = Info.PipStride != int2.Zero ? Info.PipStride : new int2(pipSize.X, 0);
|
||||
|
||||
screenPos -= pipSize / 2;
|
||||
for (var i = 0; i < pipCount; i++)
|
||||
{
|
||||
pips.PlayRepeating(GetPipSequence(i));
|
||||
yield return new UISpriteRenderable(pips.Image, self.CenterPosition, screenPos, 0, palette, 1f);
|
||||
|
||||
screenPos += pipStride;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,24 +18,15 @@ using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits.Render
|
||||
{
|
||||
[Flags]
|
||||
public enum ReferencePoints
|
||||
{
|
||||
Center = 0,
|
||||
Top = 1,
|
||||
Bottom = 2,
|
||||
Left = 4,
|
||||
Right = 8,
|
||||
}
|
||||
|
||||
public enum BlinkState { Off, On }
|
||||
|
||||
[Desc("Displays a custom UI overlay relative to the actor's mouseover bounds.")]
|
||||
public class WithDecorationInfo : ConditionalTraitInfo, Requires<IDecorationBoundsInfo>
|
||||
public class WithDecorationInfo : WithDecorationBaseInfo
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[Desc("Image used for this decoration. Defaults to the actor's type.")]
|
||||
public readonly string Image = null;
|
||||
|
||||
[FieldLoader.Require]
|
||||
[SequenceReference("Image")]
|
||||
[Desc("Sequence used for this decoration (can be animated).")]
|
||||
public readonly string Sequence = null;
|
||||
|
||||
@@ -46,77 +37,20 @@ namespace OpenRA.Mods.Common.Traits.Render
|
||||
[Desc("Custom palette is a player palette BaseName")]
|
||||
public readonly bool IsPlayerPalette = false;
|
||||
|
||||
[Desc("Point in the actor's selection box used as reference for offsetting the decoration image. " +
|
||||
"Possible values are combinations of Center, Top, Bottom, Left, Right.")]
|
||||
public readonly ReferencePoints ReferencePoint = ReferencePoints.Top | ReferencePoints.Left;
|
||||
|
||||
[Desc("The Z offset to apply when rendering this decoration.")]
|
||||
public readonly int ZOffset = 1;
|
||||
|
||||
[Desc("Player stances who can view the decoration.")]
|
||||
public readonly Stance ValidStances = Stance.Ally;
|
||||
|
||||
[Desc("Should this be visible only when selected?")]
|
||||
public readonly bool RequiresSelection = false;
|
||||
|
||||
[Desc("Screen-space offsets to apply when defined conditions are enabled.",
|
||||
"A dictionary of [condition string]: [x, y offset].")]
|
||||
public readonly Dictionary<BooleanExpression, int2> Offsets = new Dictionary<BooleanExpression, int2>();
|
||||
|
||||
[Desc("The number of ticks that each step in the blink pattern in active.")]
|
||||
public readonly int BlinkInterval = 5;
|
||||
|
||||
[Desc("A pattern of ticks (BlinkInterval long) where the decoration is visible or hidden.")]
|
||||
public readonly BlinkState[] BlinkPattern = { };
|
||||
|
||||
[Desc("Override blink conditions to use when defined conditions are enabled.",
|
||||
"A dictionary of [condition string]: [pattern].")]
|
||||
public readonly Dictionary<BooleanExpression, BlinkState[]> BlinkPatterns = new Dictionary<BooleanExpression, BlinkState[]>();
|
||||
|
||||
[ConsumedConditionReference]
|
||||
public IEnumerable<string> ConsumedConditions
|
||||
{
|
||||
get { return Offsets.Keys.Concat(BlinkPatterns.Keys).SelectMany(r => r.Variables).Distinct(); }
|
||||
}
|
||||
|
||||
public override object Create(ActorInitializer init) { return new WithDecoration(init.Self, this); }
|
||||
}
|
||||
|
||||
public class WithDecoration : ConditionalTrait<WithDecorationInfo>, ITick, IRenderAnnotations, IRenderAnnotationsWhenSelected
|
||||
public class WithDecoration : WithDecorationBase<WithDecorationInfo>, ITick
|
||||
{
|
||||
protected Animation anim;
|
||||
readonly IDecorationBounds[] decorationBounds;
|
||||
readonly string image;
|
||||
int2 conditionalOffset;
|
||||
BlinkState[] blinkPattern;
|
||||
|
||||
public WithDecoration(Actor self, WithDecorationInfo info)
|
||||
: base(info)
|
||||
: base(self, info)
|
||||
{
|
||||
image = info.Image ?? self.Info.Name;
|
||||
anim = new Animation(self.World, image, () => self.World.Paused);
|
||||
anim.PlayRepeating(info.Sequence);
|
||||
decorationBounds = self.TraitsImplementing<IDecorationBounds>().ToArray();
|
||||
blinkPattern = info.BlinkPattern;
|
||||
}
|
||||
|
||||
protected virtual bool ShouldRender(Actor self)
|
||||
{
|
||||
if (blinkPattern != null && blinkPattern.Any())
|
||||
{
|
||||
var i = (self.World.WorldTick / Info.BlinkInterval) % blinkPattern.Length;
|
||||
if (blinkPattern[i] != BlinkState.On)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (self.World.RenderPlayer != null)
|
||||
{
|
||||
var stance = self.Owner.Stances[self.World.RenderPlayer];
|
||||
if (!Info.ValidStances.HasStance(stance))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual PaletteReference GetPalette(Actor self, WorldRenderer wr)
|
||||
@@ -124,99 +58,17 @@ namespace OpenRA.Mods.Common.Traits.Render
|
||||
return wr.Palette(Info.Palette + (Info.IsPlayerPalette ? self.Owner.InternalName : ""));
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IRenderAnnotations.RenderAnnotations(Actor self, WorldRenderer wr)
|
||||
protected override IEnumerable<IRenderable> RenderDecoration(Actor self, WorldRenderer wr, int2 screenPos)
|
||||
{
|
||||
return !Info.RequiresSelection ? RenderInner(self, wr) : SpriteRenderable.None;
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IRenderAnnotationsWhenSelected.RenderAnnotations(Actor self, WorldRenderer wr)
|
||||
{
|
||||
return Info.RequiresSelection ? RenderInner(self, wr) : SpriteRenderable.None;
|
||||
}
|
||||
|
||||
bool IRenderAnnotations.SpatiallyPartitionable { get { return true; } }
|
||||
bool IRenderAnnotationsWhenSelected.SpatiallyPartitionable { get { return true; } }
|
||||
|
||||
IEnumerable<IRenderable> RenderInner(Actor self, WorldRenderer wr)
|
||||
{
|
||||
if (IsTraitDisabled || self.IsDead || !self.IsInWorld || anim == null)
|
||||
if (anim == null)
|
||||
return Enumerable.Empty<IRenderable>();
|
||||
|
||||
if (!ShouldRender(self) || self.World.FogObscures(self))
|
||||
return Enumerable.Empty<IRenderable>();
|
||||
|
||||
var bounds = decorationBounds.FirstNonEmptyBounds(self, wr);
|
||||
var halfSize = (0.5f * anim.Image.Size.XY).ToInt2();
|
||||
|
||||
var boundsOffset = new int2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom) / 2;
|
||||
var sizeOffset = -halfSize;
|
||||
if (Info.ReferencePoint.HasFlag(ReferencePoints.Top))
|
||||
{
|
||||
boundsOffset -= new int2(0, bounds.Height / 2);
|
||||
sizeOffset += new int2(0, halfSize.Y);
|
||||
}
|
||||
else if (Info.ReferencePoint.HasFlag(ReferencePoints.Bottom))
|
||||
{
|
||||
boundsOffset += new int2(0, bounds.Height / 2);
|
||||
sizeOffset -= new int2(0, halfSize.Y);
|
||||
}
|
||||
|
||||
if (Info.ReferencePoint.HasFlag(ReferencePoints.Left))
|
||||
{
|
||||
boundsOffset -= new int2(bounds.Width / 2, 0);
|
||||
sizeOffset += new int2(halfSize.X, 0);
|
||||
}
|
||||
else if (Info.ReferencePoint.HasFlag(ReferencePoints.Right))
|
||||
{
|
||||
boundsOffset += new int2(bounds.Width / 2, 0);
|
||||
sizeOffset -= new int2(halfSize.X, 0);
|
||||
}
|
||||
|
||||
var pxPos = wr.Viewport.WorldToViewPx(boundsOffset) + sizeOffset + conditionalOffset;
|
||||
return new IRenderable[]
|
||||
{
|
||||
new UISpriteRenderable(anim.Image, self.CenterPosition, pxPos, Info.ZOffset, GetPalette(self, wr), 1f)
|
||||
new UISpriteRenderable(anim.Image, self.CenterPosition, screenPos - (0.5f * anim.Image.Size.XY).ToInt2(), 0, GetPalette(self, wr), 1f)
|
||||
};
|
||||
}
|
||||
|
||||
void ITick.Tick(Actor self) { anim.Tick(); }
|
||||
|
||||
public override IEnumerable<VariableObserver> GetVariableObservers()
|
||||
{
|
||||
foreach (var observer in base.GetVariableObservers())
|
||||
yield return observer;
|
||||
|
||||
foreach (var condition in Info.Offsets.Keys)
|
||||
yield return new VariableObserver(OffsetConditionChanged, condition.Variables);
|
||||
|
||||
foreach (var condition in Info.BlinkPatterns.Keys)
|
||||
yield return new VariableObserver(BlinkConditionsChanged, condition.Variables);
|
||||
}
|
||||
|
||||
void OffsetConditionChanged(Actor self, IReadOnlyDictionary<string, int> conditions)
|
||||
{
|
||||
conditionalOffset = int2.Zero;
|
||||
foreach (var kv in Info.Offsets)
|
||||
{
|
||||
if (kv.Key.Evaluate(conditions))
|
||||
{
|
||||
conditionalOffset = kv.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlinkConditionsChanged(Actor self, IReadOnlyDictionary<string, int> conditions)
|
||||
{
|
||||
blinkPattern = Info.BlinkPattern;
|
||||
foreach (var kv in Info.BlinkPatterns)
|
||||
{
|
||||
if (kv.Key.Evaluate(conditions))
|
||||
{
|
||||
blinkPattern = kv.Value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
147
OpenRA.Mods.Common/Traits/Render/WithDecorationBase.cs
Normal file
147
OpenRA.Mods.Common/Traits/Render/WithDecorationBase.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
#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.Support;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits.Render
|
||||
{
|
||||
public enum BlinkState { Off, On }
|
||||
|
||||
public abstract class WithDecorationBaseInfo : ConditionalTraitInfo
|
||||
{
|
||||
[Desc("Position in the actor's selection box to draw the decoration.")]
|
||||
public readonly DecorationPosition Position = DecorationPosition.TopLeft;
|
||||
|
||||
[Desc("Player stances who can view the decoration.")]
|
||||
public readonly Stance ValidStances = Stance.Ally;
|
||||
|
||||
[Desc("Should this be visible only when selected?")]
|
||||
public readonly bool RequiresSelection = false;
|
||||
|
||||
[Desc("Offset sprite center position from the selection box edge.")]
|
||||
public readonly int2 Margin = int2.Zero;
|
||||
|
||||
[Desc("Screen-space offsets to apply when defined conditions are enabled.",
|
||||
"A dictionary of [condition string]: [x, y offset].")]
|
||||
public readonly Dictionary<BooleanExpression, int2> Offsets = new Dictionary<BooleanExpression, int2>();
|
||||
|
||||
[Desc("The number of ticks that each step in the blink pattern in active.")]
|
||||
public readonly int BlinkInterval = 5;
|
||||
|
||||
[Desc("A pattern of ticks (BlinkInterval long) where the decoration is visible or hidden.")]
|
||||
public readonly BlinkState[] BlinkPattern = { };
|
||||
|
||||
[Desc("Override blink conditions to use when defined conditions are enabled.",
|
||||
"A dictionary of [condition string]: [pattern].")]
|
||||
public readonly Dictionary<BooleanExpression, BlinkState[]> BlinkPatterns = new Dictionary<BooleanExpression, BlinkState[]>();
|
||||
|
||||
[ConsumedConditionReference]
|
||||
public IEnumerable<string> ConsumedConditions
|
||||
{
|
||||
get { return Offsets.Keys.Concat(BlinkPatterns.Keys).SelectMany(r => r.Variables).Distinct(); }
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class WithDecorationBase<InfoType> : ConditionalTrait<InfoType>, IDecoration where InfoType : WithDecorationBaseInfo
|
||||
{
|
||||
protected readonly Actor self;
|
||||
int2 conditionalOffset;
|
||||
BlinkState[] blinkPattern;
|
||||
|
||||
public WithDecorationBase(Actor self, InfoType info)
|
||||
: base(info)
|
||||
{
|
||||
this.self = self;
|
||||
blinkPattern = info.BlinkPattern;
|
||||
}
|
||||
|
||||
protected virtual bool ShouldRender(Actor self)
|
||||
{
|
||||
if (self.World.FogObscures(self))
|
||||
return false;
|
||||
|
||||
if (blinkPattern != null && blinkPattern.Any())
|
||||
{
|
||||
var i = (self.World.WorldTick / Info.BlinkInterval) % blinkPattern.Length;
|
||||
if (blinkPattern[i] != BlinkState.On)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (self.World.RenderPlayer != null)
|
||||
{
|
||||
var stance = self.Owner.Stances[self.World.RenderPlayer];
|
||||
if (!Info.ValidStances.HasStance(stance))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DecorationPosition IDecoration.Position { get { return Info.Position; } }
|
||||
|
||||
bool IDecoration.Enabled { get { return !IsTraitDisabled && self.IsInWorld && ShouldRender(self); } }
|
||||
|
||||
bool IDecoration.RequiresSelection { get { return Info.RequiresSelection; } }
|
||||
|
||||
protected abstract IEnumerable<IRenderable> RenderDecoration(Actor self, WorldRenderer wr, int2 pos);
|
||||
|
||||
IEnumerable<IRenderable> IDecoration.RenderDecoration(Actor self, WorldRenderer wr, int2 pos)
|
||||
{
|
||||
if (IsTraitDisabled || self.IsDead || !self.IsInWorld || !ShouldRender(self))
|
||||
return Enumerable.Empty<IRenderable>();
|
||||
|
||||
var screenPos = wr.Viewport.WorldToViewPx(pos) + Info.Position.CreateMargin(Info.Margin) + conditionalOffset;
|
||||
return RenderDecoration(self, wr, screenPos);
|
||||
}
|
||||
|
||||
public override IEnumerable<VariableObserver> GetVariableObservers()
|
||||
{
|
||||
foreach (var observer in base.GetVariableObservers())
|
||||
yield return observer;
|
||||
|
||||
foreach (var condition in Info.Offsets.Keys)
|
||||
yield return new VariableObserver(OffsetConditionChanged, condition.Variables);
|
||||
|
||||
foreach (var condition in Info.BlinkPatterns.Keys)
|
||||
yield return new VariableObserver(BlinkConditionsChanged, condition.Variables);
|
||||
}
|
||||
|
||||
void OffsetConditionChanged(Actor self, IReadOnlyDictionary<string, int> conditions)
|
||||
{
|
||||
conditionalOffset = int2.Zero;
|
||||
foreach (var kv in Info.Offsets)
|
||||
{
|
||||
if (kv.Key.Evaluate(conditions))
|
||||
{
|
||||
conditionalOffset = kv.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlinkConditionsChanged(Actor self, IReadOnlyDictionary<string, int> conditions)
|
||||
{
|
||||
blinkPattern = Info.BlinkPattern;
|
||||
foreach (var kv in Info.BlinkPatterns)
|
||||
{
|
||||
if (kv.Key.Evaluate(conditions))
|
||||
{
|
||||
blinkPattern = kv.Value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#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 OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits.Render
|
||||
{
|
||||
public class WithHarvesterPipsDecorationInfo : WithDecorationBaseInfo, Requires<HarvesterInfo>
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[Desc("Number of pips to display how filled unit is.")]
|
||||
public readonly int PipCount = 0;
|
||||
|
||||
[Desc("If non-zero, override the spacing between adjacent pips.")]
|
||||
public readonly int2 PipStride = int2.Zero;
|
||||
|
||||
[Desc("Image that defines the pip sequences.")]
|
||||
public readonly string Image = "pips";
|
||||
|
||||
[SequenceReference("Image")]
|
||||
[Desc("Sequence used for empty pips.")]
|
||||
public readonly string EmptySequence = "pip-empty";
|
||||
|
||||
[SequenceReference("Image")]
|
||||
[Desc("Sequence used for full pips that aren't defined in ResourceSequences.")]
|
||||
public readonly string FullSequence = "pip-green";
|
||||
|
||||
// TODO: [SequenceReference] isn't smart enough to use Dictionaries.
|
||||
[Desc("Pip sequence to use for specific resource types.")]
|
||||
public readonly Dictionary<string, string> ResourceSequences = new Dictionary<string, string>();
|
||||
|
||||
[PaletteReference]
|
||||
public readonly string Palette = "chrome";
|
||||
|
||||
public override object Create(ActorInitializer init) { return new WithHarvesterPipsDecoration(init.Self, this); }
|
||||
}
|
||||
|
||||
public class WithHarvesterPipsDecoration : WithDecorationBase<WithHarvesterPipsDecorationInfo>
|
||||
{
|
||||
readonly Harvester harvester;
|
||||
readonly Animation pips;
|
||||
|
||||
public WithHarvesterPipsDecoration(Actor self, WithHarvesterPipsDecorationInfo info)
|
||||
: base(self, info)
|
||||
{
|
||||
harvester = self.Trait<Harvester>();
|
||||
pips = new Animation(self.World, info.Image);
|
||||
}
|
||||
|
||||
string GetPipSequence(int i)
|
||||
{
|
||||
var n = i * harvester.Info.Capacity / Info.PipCount;
|
||||
|
||||
string sequence;
|
||||
foreach (var rt in harvester.Contents)
|
||||
{
|
||||
if (n < rt.Value)
|
||||
{
|
||||
if (!Info.ResourceSequences.TryGetValue(rt.Key.Type, out sequence))
|
||||
sequence = Info.FullSequence;
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
n -= rt.Value;
|
||||
}
|
||||
|
||||
return Info.EmptySequence;
|
||||
}
|
||||
|
||||
protected override IEnumerable<IRenderable> RenderDecoration(Actor self, WorldRenderer wr, int2 screenPos)
|
||||
{
|
||||
pips.PlayRepeating(Info.EmptySequence);
|
||||
|
||||
var palette = wr.Palette(Info.Palette);
|
||||
var pipSize = pips.Image.Size.XY.ToInt2();
|
||||
var pipStride = Info.PipStride != int2.Zero ? Info.PipStride : new int2(pipSize.X, 0);
|
||||
|
||||
screenPos -= pipSize / 2;
|
||||
for (var i = 0; i < Info.PipCount; i++)
|
||||
{
|
||||
pips.PlayRepeating(GetPipSequence(i));
|
||||
yield return new UISpriteRenderable(pips.Image, self.CenterPosition, screenPos, 0, palette, 1f);
|
||||
|
||||
screenPos += pipStride;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
OpenRA.Mods.Common/Traits/Render/WithNameTagDecoration.cs
Normal file
84
OpenRA.Mods.Common/Traits/Render/WithNameTagDecoration.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
#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
|
||||
{
|
||||
[Desc("Displays the player name above the unit")]
|
||||
public class WithNameTagDecorationInfo : WithDecorationBaseInfo
|
||||
{
|
||||
public readonly int MaxLength = 10;
|
||||
|
||||
public readonly string Font = "TinyBold";
|
||||
|
||||
[Desc("Display in this color when not using the player color.")]
|
||||
public readonly Color Color = Color.White;
|
||||
|
||||
[Desc("Use the player color of the current owner.")]
|
||||
public readonly bool UsePlayerColor = false;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new WithNameTagDecoration(init.Self, this); }
|
||||
|
||||
public override void RulesetLoaded(Ruleset rules, ActorInfo ai)
|
||||
{
|
||||
if (!Game.ModData.Manifest.Get<Fonts>().FontList.ContainsKey(Font))
|
||||
throw new YamlException("Font '{0}' is not listed in the mod.yaml's Fonts section".F(Font));
|
||||
|
||||
base.RulesetLoaded(rules, ai);
|
||||
}
|
||||
}
|
||||
|
||||
public class WithNameTagDecoration : WithDecorationBase<WithNameTagDecorationInfo>, INotifyOwnerChanged
|
||||
{
|
||||
readonly SpriteFont font;
|
||||
string name;
|
||||
Color color;
|
||||
|
||||
public WithNameTagDecoration(Actor self, WithNameTagDecorationInfo info)
|
||||
: base(self, info)
|
||||
{
|
||||
font = Game.Renderer.Fonts[info.Font];
|
||||
color = info.UsePlayerColor ? self.Owner.Color : info.Color;
|
||||
|
||||
name = self.Owner.PlayerName;
|
||||
if (name.Length > info.MaxLength)
|
||||
name = name.Substring(0, info.MaxLength);
|
||||
}
|
||||
|
||||
protected override IEnumerable<IRenderable> RenderDecoration(Actor self, WorldRenderer wr, int2 screenPos)
|
||||
{
|
||||
if (IsTraitDisabled || self.IsDead || !self.IsInWorld || !ShouldRender(self))
|
||||
return Enumerable.Empty<IRenderable>();
|
||||
|
||||
var size = font.Measure(name);
|
||||
return new IRenderable[]
|
||||
{
|
||||
new UITextRenderable(font, self.CenterPosition, screenPos - size / 2, 0, color, name)
|
||||
};
|
||||
}
|
||||
|
||||
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
{
|
||||
if (Info.UsePlayerColor)
|
||||
color = newOwner.Color;
|
||||
|
||||
name = self.Owner.PlayerName;
|
||||
if (name.Length > Info.MaxLength)
|
||||
name = name.Substring(0, Info.MaxLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits.Render
|
||||
{
|
||||
public class WithResourceStoragePipsDecorationInfo : WithDecorationBaseInfo
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[Desc("Number of pips to display how filled unit is.")]
|
||||
public readonly int PipCount = 0;
|
||||
|
||||
[Desc("If non-zero, override the spacing between adjacing pips.")]
|
||||
public readonly int2 PipStride = int2.Zero;
|
||||
|
||||
[Desc("Image that defines the pip sequences.")]
|
||||
public readonly string Image = "pips";
|
||||
|
||||
[SequenceReference("Image")]
|
||||
[Desc("Sequence used for empty pips.")]
|
||||
public readonly string EmptySequence = "pip-empty";
|
||||
|
||||
[SequenceReference("Image")]
|
||||
[Desc("Sequence used for full pips.")]
|
||||
public readonly string FullSequence = "pip-green";
|
||||
|
||||
[PaletteReference]
|
||||
public readonly string Palette = "chrome";
|
||||
|
||||
public override object Create(ActorInitializer init) { return new WithResourceStoragePipsDecoration(init.Self, this); }
|
||||
}
|
||||
|
||||
public class WithResourceStoragePipsDecoration : WithDecorationBase<WithResourceStoragePipsDecorationInfo>, INotifyOwnerChanged
|
||||
{
|
||||
readonly Animation pips;
|
||||
PlayerResources player;
|
||||
|
||||
public WithResourceStoragePipsDecoration(Actor self, WithResourceStoragePipsDecorationInfo info)
|
||||
: base(self, info)
|
||||
{
|
||||
player = self.Owner.PlayerActor.Trait<PlayerResources>();
|
||||
pips = new Animation(self.World, info.Image);
|
||||
}
|
||||
|
||||
protected override IEnumerable<IRenderable> RenderDecoration(Actor self, WorldRenderer wr, int2 screenPos)
|
||||
{
|
||||
pips.PlayRepeating(Info.EmptySequence);
|
||||
|
||||
var palette = wr.Palette(Info.Palette);
|
||||
var pipSize = pips.Image.Size.XY.ToInt2();
|
||||
var pipStride = Info.PipStride != int2.Zero ? Info.PipStride : new int2(pipSize.X, 0);
|
||||
|
||||
screenPos -= pipSize / 2;
|
||||
for (var i = 0; i < Info.PipCount; i++)
|
||||
{
|
||||
pips.PlayRepeating(player.Resources * Info.PipCount > i * player.ResourceCapacity ? Info.FullSequence : Info.EmptySequence);
|
||||
yield return new UISpriteRenderable(pips.Image, self.CenterPosition, screenPos, 0, palette, 1f);
|
||||
|
||||
screenPos += pipStride;
|
||||
}
|
||||
}
|
||||
|
||||
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
{
|
||||
player = newOwner.PlayerActor.Trait<PlayerResources>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ using OpenRA.Traits;
|
||||
namespace OpenRA.Mods.Common.Traits.Render
|
||||
{
|
||||
[Desc("Renders Ctrl groups using pixel art.")]
|
||||
public class WithSpriteControlGroupDecorationInfo : ITraitInfo, Requires<IDecorationBoundsInfo>
|
||||
public class WithSpriteControlGroupDecorationInfo : ITraitInfo
|
||||
{
|
||||
[PaletteReference]
|
||||
public readonly string Palette = "chrome";
|
||||
@@ -28,66 +28,49 @@ namespace OpenRA.Mods.Common.Traits.Render
|
||||
[Desc("Sprite sequence used to render the control group 0-9 numbers.")]
|
||||
public readonly string GroupSequence = "groups";
|
||||
|
||||
[Desc("Point in the actor's selection box used as reference for offsetting the decoration image. " +
|
||||
"Possible values are combinations of Center, Top, Bottom, Left, Right.")]
|
||||
public readonly ReferencePoints ReferencePoint = ReferencePoints.Top | ReferencePoints.Left;
|
||||
[Desc("Position in the actor's selection box to draw the decoration.")]
|
||||
public readonly DecorationPosition Position = DecorationPosition.TopLeft;
|
||||
|
||||
[Desc("Offset sprite center position from the selection box edge.")]
|
||||
public readonly int2 Margin = int2.Zero;
|
||||
|
||||
public object Create(ActorInitializer init) { return new WithSpriteControlGroupDecoration(init.Self, this); }
|
||||
}
|
||||
|
||||
public class WithSpriteControlGroupDecoration : IRenderAnnotationsWhenSelected
|
||||
public class WithSpriteControlGroupDecoration : IDecoration
|
||||
{
|
||||
public readonly WithSpriteControlGroupDecorationInfo Info;
|
||||
readonly IDecorationBounds[] decorationBounds;
|
||||
readonly Animation pipImages;
|
||||
readonly Actor self;
|
||||
readonly Animation anim;
|
||||
|
||||
public WithSpriteControlGroupDecoration(Actor self, WithSpriteControlGroupDecorationInfo info)
|
||||
{
|
||||
Info = info;
|
||||
this.self = self;
|
||||
|
||||
decorationBounds = self.TraitsImplementing<IDecorationBounds>().ToArray();
|
||||
pipImages = new Animation(self.World, Info.Image);
|
||||
anim = new Animation(self.World, Info.Image);
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IRenderAnnotationsWhenSelected.RenderAnnotations(Actor self, WorldRenderer wr)
|
||||
{
|
||||
if (self.Owner != wr.World.LocalPlayer)
|
||||
yield break;
|
||||
DecorationPosition IDecoration.Position { get { return Info.Position; } }
|
||||
|
||||
if (self.World.FogObscures(self))
|
||||
yield break;
|
||||
bool IDecoration.Enabled { get { return self.Owner == self.World.LocalPlayer && self.World.Selection.GetControlGroupForActor(self) != null; } }
|
||||
|
||||
var pal = wr.Palette(Info.Palette);
|
||||
foreach (var r in DrawControlGroup(self, wr, pal))
|
||||
yield return r;
|
||||
}
|
||||
bool IDecoration.RequiresSelection { get { return true; } }
|
||||
|
||||
bool IRenderAnnotationsWhenSelected.SpatiallyPartitionable { get { return true; } }
|
||||
|
||||
IEnumerable<IRenderable> DrawControlGroup(Actor self, WorldRenderer wr, PaletteReference palette)
|
||||
IEnumerable<IRenderable> IDecoration.RenderDecoration(Actor self, WorldRenderer wr, int2 pos)
|
||||
{
|
||||
var group = self.World.Selection.GetControlGroupForActor(self);
|
||||
if (group == null)
|
||||
yield break;
|
||||
return Enumerable.Empty<IRenderable>();
|
||||
|
||||
pipImages.PlayFetchIndex(Info.GroupSequence, () => (int)group);
|
||||
anim.PlayFetchIndex(Info.GroupSequence, () => (int)group);
|
||||
|
||||
var bounds = decorationBounds.FirstNonEmptyBounds(self, wr);
|
||||
var boundsOffset = 0.5f * new float2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom);
|
||||
if (Info.ReferencePoint.HasFlag(ReferencePoints.Top))
|
||||
boundsOffset -= new float2(0, 0.5f * bounds.Height);
|
||||
|
||||
if (Info.ReferencePoint.HasFlag(ReferencePoints.Bottom))
|
||||
boundsOffset += new float2(0, 0.5f * bounds.Height);
|
||||
|
||||
if (Info.ReferencePoint.HasFlag(ReferencePoints.Left))
|
||||
boundsOffset -= new float2(0.5f * bounds.Width, 0);
|
||||
|
||||
if (Info.ReferencePoint.HasFlag(ReferencePoints.Right))
|
||||
boundsOffset += new float2(0.5f * bounds.Width, 0);
|
||||
|
||||
var pxPos = wr.Viewport.WorldToViewPx(boundsOffset.ToInt2()) - (0.5f * pipImages.Image.Size.XY).ToInt2();
|
||||
yield return new UISpriteRenderable(pipImages.Image, self.CenterPosition, pxPos, 0, palette, 1f);
|
||||
var screenPos = wr.Viewport.WorldToViewPx(pos) + Info.Position.CreateMargin(Info.Margin) - (0.5f * anim.Image.Size.XY).ToInt2();
|
||||
var palette = wr.Palette(Info.Palette);
|
||||
return new IRenderable[]
|
||||
{
|
||||
new UISpriteRenderable(anim.Image, self.CenterPosition, screenPos, 0, palette, 1f)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,14 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
using OpenRA.Mods.Common.Widgets;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits.Render
|
||||
{
|
||||
[Desc("Renders Ctrl groups using typeface.")]
|
||||
public class WithTextControlGroupDecorationInfo : ITraitInfo, IRulesetLoaded, Requires<IDecorationBoundsInfo>
|
||||
public class WithTextControlGroupDecorationInfo : ITraitInfo, IRulesetLoaded
|
||||
{
|
||||
public readonly string Font = "TinyBold";
|
||||
|
||||
@@ -29,15 +30,11 @@ namespace OpenRA.Mods.Common.Traits.Render
|
||||
[Desc("Use the player color of the current owner.")]
|
||||
public readonly bool UsePlayerColor = false;
|
||||
|
||||
[Desc("The Z offset to apply when rendering this decoration.")]
|
||||
public readonly int ZOffset = 1;
|
||||
[Desc("Position in the actor's selection box to draw the decoration.")]
|
||||
public readonly DecorationPosition Position = DecorationPosition.TopLeft;
|
||||
|
||||
[Desc("Point in the actor's selection box used as reference for offsetting the decoration image. " +
|
||||
"Possible values are combinations of Center, Top, Bottom, Left, Right.")]
|
||||
public readonly ReferencePoints ReferencePoint = ReferencePoints.Bottom | ReferencePoints.Left;
|
||||
|
||||
[Desc("Manual offset in screen pixel.")]
|
||||
public readonly int2 ScreenOffset = new int2(2, -2);
|
||||
[Desc("Offset text center position from the selection box edge.")]
|
||||
public readonly int2 Margin = int2.Zero;
|
||||
|
||||
void IRulesetLoaded<ActorInfo>.RulesetLoaded(Ruleset rules, ActorInfo info)
|
||||
{
|
||||
@@ -48,76 +45,42 @@ namespace OpenRA.Mods.Common.Traits.Render
|
||||
public object Create(ActorInitializer init) { return new WithTextControlGroupDecoration(init.Self, this); }
|
||||
}
|
||||
|
||||
public class WithTextControlGroupDecoration : IRenderAnnotationsWhenSelected, INotifyOwnerChanged
|
||||
public class WithTextControlGroupDecoration : IDecoration, INotifyOwnerChanged
|
||||
{
|
||||
readonly WithTextControlGroupDecorationInfo info;
|
||||
readonly IDecorationBounds[] decorationBounds;
|
||||
readonly SpriteFont font;
|
||||
readonly Actor self;
|
||||
readonly CachedTransform<int, string> label;
|
||||
|
||||
Color color;
|
||||
|
||||
public WithTextControlGroupDecoration(Actor self, WithTextControlGroupDecorationInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
|
||||
if (!Game.Renderer.Fonts.TryGetValue(info.Font, out font))
|
||||
throw new YamlException("Font '{0}' is not listed in the mod.yaml's Fonts section".F(info.Font));
|
||||
|
||||
decorationBounds = self.TraitsImplementing<IDecorationBounds>().ToArray();
|
||||
this.self = self;
|
||||
font = Game.Renderer.Fonts[info.Font];
|
||||
color = info.UsePlayerColor ? self.Owner.Color : info.Color;
|
||||
label = new CachedTransform<int, string>(g => g.ToString());
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IRenderAnnotationsWhenSelected.RenderAnnotations(Actor self, WorldRenderer wr)
|
||||
{
|
||||
if (self.Owner != wr.World.LocalPlayer)
|
||||
yield break;
|
||||
DecorationPosition IDecoration.Position { get { return info.Position; } }
|
||||
|
||||
if (self.World.FogObscures(self))
|
||||
yield break;
|
||||
bool IDecoration.Enabled { get { return self.Owner == self.World.LocalPlayer && self.World.Selection.GetControlGroupForActor(self) != null; } }
|
||||
|
||||
foreach (var r in DrawControlGroup(self, wr))
|
||||
yield return r;
|
||||
}
|
||||
bool IDecoration.RequiresSelection { get { return true; } }
|
||||
|
||||
bool IRenderAnnotationsWhenSelected.SpatiallyPartitionable { get { return true; } }
|
||||
|
||||
IEnumerable<IRenderable> DrawControlGroup(Actor self, WorldRenderer wr)
|
||||
IEnumerable<IRenderable> IDecoration.RenderDecoration(Actor self, WorldRenderer wr, int2 pos)
|
||||
{
|
||||
var group = self.World.Selection.GetControlGroupForActor(self);
|
||||
if (group == null)
|
||||
yield break;
|
||||
return Enumerable.Empty<IRenderable>();
|
||||
|
||||
var bounds = decorationBounds.FirstNonEmptyBounds(self, wr);
|
||||
var number = group.Value.ToString();
|
||||
var halfSize = font.Measure(number) / 2;
|
||||
|
||||
var boundsOffset = new int2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom) / 2;
|
||||
var sizeOffset = int2.Zero;
|
||||
if (info.ReferencePoint.HasFlag(ReferencePoints.Top))
|
||||
var text = label.Update(group.Value);
|
||||
var screenPos = wr.Viewport.WorldToViewPx(pos) + info.Position.CreateMargin(info.Margin);
|
||||
return new IRenderable[]
|
||||
{
|
||||
boundsOffset -= new int2(0, bounds.Height / 2);
|
||||
sizeOffset += new int2(0, halfSize.Y);
|
||||
}
|
||||
else if (info.ReferencePoint.HasFlag(ReferencePoints.Bottom))
|
||||
{
|
||||
boundsOffset += new int2(0, bounds.Height / 2);
|
||||
sizeOffset -= new int2(0, halfSize.Y);
|
||||
}
|
||||
|
||||
if (info.ReferencePoint.HasFlag(ReferencePoints.Left))
|
||||
{
|
||||
boundsOffset -= new int2(bounds.Width / 2, 0);
|
||||
sizeOffset += new int2(halfSize.X, 0);
|
||||
}
|
||||
else if (info.ReferencePoint.HasFlag(ReferencePoints.Right))
|
||||
{
|
||||
boundsOffset += new int2(bounds.Width / 2, 0);
|
||||
sizeOffset -= new int2(halfSize.X, 0);
|
||||
}
|
||||
|
||||
var screenPos = boundsOffset + sizeOffset + info.ScreenOffset;
|
||||
|
||||
yield return new TextAnnotationRenderable(font, wr.ProjectedPosition(screenPos), info.ZOffset, color, number);
|
||||
new UITextRenderable(font, self.CenterPosition, screenPos, 0, color, text)
|
||||
};
|
||||
}
|
||||
|
||||
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
|
||||
@@ -19,7 +19,7 @@ using OpenRA.Traits;
|
||||
namespace OpenRA.Mods.Common.Traits.Render
|
||||
{
|
||||
[Desc("Displays a text overlay relative to the selection box.")]
|
||||
public class WithTextDecorationInfo : ConditionalTraitInfo, Requires<IDecorationBoundsInfo>
|
||||
public class WithTextDecorationInfo : WithDecorationBaseInfo
|
||||
{
|
||||
[Translate]
|
||||
[FieldLoader.Require]
|
||||
@@ -33,19 +33,6 @@ namespace OpenRA.Mods.Common.Traits.Render
|
||||
[Desc("Use the player color of the current owner.")]
|
||||
public readonly bool UsePlayerColor = false;
|
||||
|
||||
[Desc("Point in the actor's selection box used as reference for offsetting the decoration image. " +
|
||||
"Possible values are combinations of Center, Top, Bottom, Left, Right.")]
|
||||
public readonly ReferencePoints ReferencePoint = ReferencePoints.Top | ReferencePoints.Left;
|
||||
|
||||
[Desc("The Z offset to apply when rendering this decoration.")]
|
||||
public readonly int ZOffset = 1;
|
||||
|
||||
[Desc("Player stances who can view the decoration.")]
|
||||
public readonly Stance ValidStances = Stance.Ally;
|
||||
|
||||
[Desc("Should this be visible only when selected?")]
|
||||
public readonly bool RequiresSelection = false;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new WithTextDecoration(init.Self, this); }
|
||||
|
||||
public override void RulesetLoaded(Ruleset rules, ActorInfo ai)
|
||||
@@ -57,79 +44,28 @@ namespace OpenRA.Mods.Common.Traits.Render
|
||||
}
|
||||
}
|
||||
|
||||
public class WithTextDecoration : ConditionalTrait<WithTextDecorationInfo>, IRenderAnnotations, IRenderAnnotationsWhenSelected, INotifyOwnerChanged
|
||||
public class WithTextDecoration : WithDecorationBase<WithTextDecorationInfo>, INotifyOwnerChanged
|
||||
{
|
||||
readonly SpriteFont font;
|
||||
readonly IDecorationBounds[] decorationBounds;
|
||||
Color color;
|
||||
|
||||
public WithTextDecoration(Actor self, WithTextDecorationInfo info)
|
||||
: base(info)
|
||||
: base(self, info)
|
||||
{
|
||||
font = Game.Renderer.Fonts[info.Font];
|
||||
decorationBounds = self.TraitsImplementing<IDecorationBounds>().ToArray();
|
||||
color = Info.UsePlayerColor ? self.Owner.Color : Info.Color;
|
||||
color = info.UsePlayerColor ? self.Owner.Color : info.Color;
|
||||
}
|
||||
|
||||
public virtual bool ShouldRender(Actor self) { return true; }
|
||||
|
||||
IEnumerable<IRenderable> IRenderAnnotations.RenderAnnotations(Actor self, WorldRenderer wr)
|
||||
protected override IEnumerable<IRenderable> RenderDecoration(Actor self, WorldRenderer wr, int2 screenPos)
|
||||
{
|
||||
return !Info.RequiresSelection ? RenderInner(self, wr) : SpriteRenderable.None;
|
||||
}
|
||||
|
||||
public bool SpatiallyPartitionable { get { return true; } }
|
||||
|
||||
IEnumerable<IRenderable> IRenderAnnotationsWhenSelected.RenderAnnotations(Actor self, WorldRenderer wr)
|
||||
{
|
||||
return Info.RequiresSelection ? RenderInner(self, wr) : SpriteRenderable.None;
|
||||
}
|
||||
|
||||
bool IRenderAnnotationsWhenSelected.SpatiallyPartitionable { get { return true; } }
|
||||
|
||||
IEnumerable<IRenderable> RenderInner(Actor self, WorldRenderer wr)
|
||||
{
|
||||
if (IsTraitDisabled || self.IsDead || !self.IsInWorld)
|
||||
if (IsTraitDisabled || self.IsDead || !self.IsInWorld || !ShouldRender(self))
|
||||
return Enumerable.Empty<IRenderable>();
|
||||
|
||||
if (self.World.RenderPlayer != null)
|
||||
var size = font.Measure(Info.Text);
|
||||
return new IRenderable[]
|
||||
{
|
||||
var stance = self.Owner.Stances[self.World.RenderPlayer];
|
||||
if (!Info.ValidStances.HasStance(stance))
|
||||
return Enumerable.Empty<IRenderable>();
|
||||
}
|
||||
|
||||
if (!ShouldRender(self) || self.World.FogObscures(self))
|
||||
return Enumerable.Empty<IRenderable>();
|
||||
|
||||
var bounds = decorationBounds.FirstNonEmptyBounds(self, wr);
|
||||
var halfSize = font.Measure(Info.Text) / 2;
|
||||
|
||||
var boundsOffset = new int2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom) / 2;
|
||||
var sizeOffset = int2.Zero;
|
||||
if (Info.ReferencePoint.HasFlag(ReferencePoints.Top))
|
||||
{
|
||||
boundsOffset -= new int2(0, bounds.Height / 2);
|
||||
sizeOffset += new int2(0, halfSize.Y);
|
||||
}
|
||||
else if (Info.ReferencePoint.HasFlag(ReferencePoints.Bottom))
|
||||
{
|
||||
boundsOffset += new int2(0, bounds.Height / 2);
|
||||
sizeOffset -= new int2(0, halfSize.Y);
|
||||
}
|
||||
|
||||
if (Info.ReferencePoint.HasFlag(ReferencePoints.Left))
|
||||
{
|
||||
boundsOffset -= new int2(bounds.Width / 2, 0);
|
||||
sizeOffset += new int2(halfSize.X, 0);
|
||||
}
|
||||
else if (Info.ReferencePoint.HasFlag(ReferencePoints.Right))
|
||||
{
|
||||
boundsOffset += new int2(bounds.Width / 2, 0);
|
||||
sizeOffset -= new int2(halfSize.X, 0);
|
||||
}
|
||||
|
||||
return new IRenderable[] { new TextAnnotationRenderable(font, wr.ProjectedPosition(boundsOffset + sizeOffset), Info.ZOffset, color, Info.Text) };
|
||||
new UITextRenderable(font, self.CenterPosition, screenPos - size / 2, 0, color, Info.Text)
|
||||
};
|
||||
}
|
||||
|
||||
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
|
||||
@@ -22,16 +22,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[FieldLoader.Require]
|
||||
public readonly int Capacity = 0;
|
||||
|
||||
[FieldLoader.Require]
|
||||
[Desc("Number of little squares used to display how filled unit is.")]
|
||||
public readonly int PipCount = 0;
|
||||
|
||||
public readonly PipType PipColor = PipType.Yellow;
|
||||
|
||||
public object Create(ActorInitializer init) { return new StoresResources(init.Self, this); }
|
||||
}
|
||||
|
||||
public class StoresResources : IPips, INotifyOwnerChanged, INotifyCapture, IStoreResources, ISync, INotifyKilled, INotifyAddedToWorld, INotifyRemovedFromWorld
|
||||
public class StoresResources : INotifyOwnerChanged, INotifyCapture, IStoreResources, ISync, INotifyKilled, INotifyAddedToWorld, INotifyRemovedFromWorld
|
||||
{
|
||||
readonly StoresResourcesInfo info;
|
||||
PlayerResources player;
|
||||
@@ -65,13 +59,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
player.TakeResources(Stored);
|
||||
}
|
||||
|
||||
IEnumerable<PipType> IPips.GetPips(Actor self)
|
||||
{
|
||||
return Enumerable.Range(0, info.PipCount).Select(i =>
|
||||
player.Resources * info.PipCount > i * player.ResourceCapacity
|
||||
? info.PipColor : PipType.Transparent);
|
||||
}
|
||||
|
||||
void INotifyAddedToWorld.AddedToWorld(Actor self)
|
||||
{
|
||||
player.AddStorage(info.Capacity);
|
||||
|
||||
@@ -146,8 +146,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
var xy = wr.Viewport.ViewToWorld(Viewport.LastMousePos);
|
||||
foreach (var unit in power.UnitsInRange(xy))
|
||||
{
|
||||
var bounds = unit.TraitsImplementing<IDecorationBounds>().FirstNonEmptyBounds(unit, wr);
|
||||
yield return new SelectionBoxAnnotationRenderable(unit, bounds, Color.Red);
|
||||
var decorations = unit.TraitsImplementing<ISelectionDecorations>().FirstEnabledTraitOrDefault();
|
||||
foreach (var d in decorations.RenderSelectionAnnotations(unit, wr, Color.Red))
|
||||
yield return d;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,9 +63,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Allow resource to spawn on ramp tiles.")]
|
||||
public readonly bool AllowOnRamps = false;
|
||||
|
||||
[Desc("Harvester content pip color.")]
|
||||
public PipType PipColor = PipType.Yellow;
|
||||
|
||||
void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(Map map, ActorInfo ai, ActorReference s, List<Pair<MPos, Color>> destinationBuffer)
|
||||
{
|
||||
var tileSet = map.Rules.TileSet;
|
||||
|
||||
Reference in New Issue
Block a user