Merge pull request #10216 from pchote/remove-tags

Reimplement Primary and Fake tags using WithDecoration.
This commit is contained in:
Oliver Brakmann
2015-12-27 19:43:57 +01:00
31 changed files with 712 additions and 247 deletions

View File

@@ -25,16 +25,25 @@ namespace OpenRA.Mods.Common.Traits
}
[Desc("Used together with ClassicProductionQueue.")]
public class PrimaryBuildingInfo : TraitInfo<PrimaryBuilding> { }
public class PrimaryBuilding : IIssueOrder, IResolveOrder, ITags
public class PrimaryBuildingInfo : ITraitInfo, Requires<UpgradeManagerInfo>
{
bool isPrimary = false;
public bool IsPrimary { get { return isPrimary; } }
[UpgradeGrantedReference, Desc("The upgrades to grant while the primary building.")]
public readonly string[] Upgrades = { "primary" };
public IEnumerable<TagType> GetTags()
public object Create(ActorInitializer init) { return new PrimaryBuilding(init.Self, this); }
}
public class PrimaryBuilding : IIssueOrder, IResolveOrder
{
readonly PrimaryBuildingInfo info;
readonly UpgradeManager manager;
public bool IsPrimary { get; private set; }
public PrimaryBuilding(Actor self, PrimaryBuildingInfo info)
{
yield return isPrimary ? TagType.Primary : TagType.None;
this.info = info;
manager = self.Trait<UpgradeManager>();
}
public IEnumerable<IOrderTargeter> Orders
@@ -53,14 +62,16 @@ namespace OpenRA.Mods.Common.Traits
public void ResolveOrder(Actor self, Order order)
{
if (order.OrderString == "PrimaryProducer")
SetPrimaryProducer(self, !isPrimary);
SetPrimaryProducer(self, !IsPrimary);
}
public void SetPrimaryProducer(Actor self, bool state)
{
if (state == false)
{
isPrimary = false;
IsPrimary = false;
foreach (var up in info.Upgrades)
manager.RevokeUpgrade(self, up, this);
return;
}
@@ -78,7 +89,9 @@ namespace OpenRA.Mods.Common.Traits
b.Trait.SetPrimaryProducer(b.Actor, false);
}
isPrimary = true;
IsPrimary = true;
foreach (var up in info.Upgrades)
manager.GrantUpgrade(self, up, this);
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", "PrimaryBuildingSelected", self.Owner.Faction.InternalName);
}

View File

@@ -42,7 +42,6 @@ namespace OpenRA.Mods.Common.Traits
{
// 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" };
static readonly string[] TagStrings = { "", "tag-fake", "tag-primary" };
public readonly SelectionDecorationsInfo Info;
readonly Actor self;
@@ -90,16 +89,12 @@ namespace OpenRA.Mods.Common.Traits
var pos = wr.ScreenPxPosition(self.CenterPosition);
var tl = wr.Viewport.WorldToViewPx(pos + new int2(b.Left, b.Top));
var bl = wr.Viewport.WorldToViewPx(pos + new int2(b.Left, b.Bottom));
var tm = wr.Viewport.WorldToViewPx(pos + new int2((b.Left + b.Right) / 2, b.Top));
foreach (var r in DrawControlGroup(wr, self, tl))
yield return r;
foreach (var r in DrawPips(wr, self, bl))
yield return r;
foreach (var r in DrawTags(wr, self, tm))
yield return r;
}
IEnumerable<IRenderable> DrawControlGroup(WorldRenderer wr, Actor self, int2 basePosition)
@@ -114,7 +109,7 @@ namespace OpenRA.Mods.Common.Traits
pipImages.Tick();
var pos = basePosition - (0.5f * pipImages.Image.Size).ToInt2() + new int2(9, 5);
yield return new UISpriteRenderable(pipImages.Image, pos, 0, pal, 1f);
yield return new UISpriteRenderable(pipImages.Image, self.CenterPosition, pos, 0, pal, 1f);
}
IEnumerable<IRenderable> DrawPips(WorldRenderer wr, Actor self, int2 basePosition)
@@ -146,35 +141,12 @@ namespace OpenRA.Mods.Common.Traits
pipImages.PlayRepeating(PipStrings[(int)pip]);
pipxyOffset += new int2(pipSize.X, 0);
yield return new UISpriteRenderable(pipImages.Image, pipxyBase + pipxyOffset, 0, pal, 1f);
yield return new UISpriteRenderable(pipImages.Image, self.CenterPosition, pipxyBase + pipxyOffset, 0, pal, 1f);
}
// Increment row
pipxyOffset = new int2(0, pipxyOffset.Y - (pipSize.Y + 1));
}
}
IEnumerable<IRenderable> DrawTags(WorldRenderer wr, Actor self, int2 basePosition)
{
var tagImages = new Animation(self.World, "pips");
var pal = wr.Palette(Info.Palette);
var tagxyOffset = new int2(0, 6);
foreach (var tags in self.TraitsImplementing<ITags>())
{
foreach (var tag in tags.GetTags())
{
if (tag == TagType.None)
continue;
tagImages.PlayRepeating(TagStrings[(int)tag]);
var pos = basePosition + tagxyOffset - (0.5f * tagImages.Image.Size).ToInt2();
yield return new UISpriteRenderable(tagImages.Image, pos, 0, pal, 1f);
// Increment row
tagxyOffset = tagxyOffset.WithY(tagxyOffset.Y + 8);
}
}
}
}
}

View File

@@ -20,16 +20,14 @@ namespace OpenRA.Mods.Common.Traits
[Flags]
public enum ReferencePoints
{
Top = 0,
VCenter = 1,
Center = 0,
Top = 1,
Bottom = 2,
Left = 0 << 2,
HCenter = 1 << 2,
Right = 2 << 2,
Left = 4,
Right = 8,
}
[Desc("Displays a custom animation if conditions are satisfied.")]
[Desc("Displays a custom UI overlay relative to the selection box.")]
public class WithDecorationInfo : UpgradableTraitInfo
{
[Desc("Image used for this decoration. Defaults to the actor's type.")]
@@ -41,131 +39,96 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Palette to render the sprite in. Reference the world actor's PaletteFrom* traits.")]
[PaletteReference] public readonly string Palette = "chrome";
[Desc("Point in the actor's bounding box used as reference for offsetting the decoration image. " +
"Possible values are any combination of Top, VCenter, Bottom and Left, HCenter, Right separated by a comma.")]
[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("Pixel offset relative to the actor's bounding box' reference point.")]
public readonly int2 Offset = int2.Zero;
[Desc("The Z offset to apply when rendering this decoration.")]
public readonly int ZOffset = 1;
[Desc("Visual scale of the image.")]
public readonly float Scale = 1f;
[Desc("Should this be visible to allied players?")]
public readonly bool ShowToAllies = true;
[Desc("Should this be visible to enemy players?")]
public readonly bool ShowToEnemies = false;
[Desc("Player stances who can view the decoration.")]
public readonly Stance Stances = Stance.Ally;
[Desc("Should this be visible only when selected?")]
public readonly bool SelectionDecoration = false;
public readonly bool RequiresSelection = false;
public override object Create(ActorInitializer init) { return new WithDecoration(init.Self, this); }
}
public class WithDecoration : UpgradableTrait<WithDecorationInfo>, IRender, IPostRenderSelection
public class WithDecoration : UpgradableTrait<WithDecorationInfo>, ITick, IRender, IPostRenderSelection
{
readonly WithDecorationInfo info;
protected readonly Animation Anim;
readonly string image;
readonly Animation anim;
readonly Actor self;
public WithDecoration(Actor self, WithDecorationInfo info)
: base(info)
{
this.info = info;
this.self = self;
image = info.Image ?? self.Info.Name;
anim = new Animation(self.World, image, () => self.World.Paused);
anim.PlayRepeating(info.Sequence);
}
public void PlaySingleFrame(int frame)
{
anim.PlayFetchIndex(info.Sequence, () => frame);
Anim = new Animation(self.World, image, () => self.World.Paused);
Anim.PlayRepeating(info.Sequence);
}
public virtual bool ShouldRender(Actor self) { return true; }
public IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr)
{
return !info.SelectionDecoration ? RenderInner(self, wr, self.Bounds) : Enumerable.Empty<IRenderable>();
return !Info.RequiresSelection ? RenderInner(self, wr) : Enumerable.Empty<IRenderable>();
}
public IEnumerable<IRenderable> RenderAfterWorld(WorldRenderer wr)
{
return info.SelectionDecoration ? RenderInner(self, wr, self.VisualBounds) : Enumerable.Empty<IRenderable>();
return Info.RequiresSelection ? RenderInner(self, wr) : Enumerable.Empty<IRenderable>();
}
IEnumerable<IRenderable> RenderInner(Actor self, WorldRenderer wr, Rectangle actorBounds)
IEnumerable<IRenderable> RenderInner(Actor self, WorldRenderer wr)
{
if (IsTraitDisabled)
if (IsTraitDisabled || self.IsDead || !self.IsInWorld || Anim == null)
return Enumerable.Empty<IRenderable>();
if (self.IsDead || !self.IsInWorld)
return Enumerable.Empty<IRenderable>();
if (anim == null)
return Enumerable.Empty<IRenderable>();
var allied = self.Owner.IsAlliedWith(self.World.RenderPlayer);
if (!allied && !info.ShowToEnemies)
return Enumerable.Empty<IRenderable>();
if (allied && !info.ShowToAllies)
return Enumerable.Empty<IRenderable>();
if (!ShouldRender(self))
return Enumerable.Empty<IRenderable>();
if (self.World.FogObscures(self))
return Enumerable.Empty<IRenderable>();
var pxPos = wr.ScreenPxPosition(self.CenterPosition);
actorBounds.Offset(pxPos.X, pxPos.Y);
var img = anim.Image;
var imgSize = img.Size.ToInt2();
switch (info.ReferencePoint & (ReferencePoints)3)
if (self.World.RenderPlayer != null)
{
case ReferencePoints.Top:
pxPos = pxPos.WithY(actorBounds.Top + imgSize.Y / 2);
break;
case ReferencePoints.VCenter:
pxPos = pxPos.WithY((actorBounds.Top + actorBounds.Bottom) / 2);
break;
case ReferencePoints.Bottom:
pxPos = pxPos.WithY(actorBounds.Bottom - imgSize.Y / 2);
break;
var stance = self.Owner.Stances[self.World.RenderPlayer];
if (!Info.Stances.HasStance(stance))
return Enumerable.Empty<IRenderable>();
}
switch (info.ReferencePoint & (ReferencePoints)(3 << 2))
if (!ShouldRender(self) || self.World.FogObscures(self))
return Enumerable.Empty<IRenderable>();
var bounds = self.VisualBounds;
var halfSize = (0.5f * Anim.Image.Size).ToInt2();
var boundsOffset = new int2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom) / 2;
var sizeOffset = -halfSize;
if (Info.ReferencePoint.HasFlag(ReferencePoints.Top))
{
case ReferencePoints.Left:
pxPos = pxPos.WithX(actorBounds.Left + imgSize.X / 2);
break;
case ReferencePoints.HCenter:
pxPos = pxPos.WithX((actorBounds.Left + actorBounds.Right) / 2);
break;
case ReferencePoints.Right:
pxPos = pxPos.WithX(actorBounds.Right - imgSize.X / 2);
break;
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);
}
pxPos += info.Offset;
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);
}
// HACK: Because WorldRenderer.Position() does not care about terrain height at the location
var renderPos = wr.ProjectedPosition(pxPos);
renderPos = new WPos(renderPos.X, renderPos.Y + self.CenterPosition.Z, self.CenterPosition.Z);
anim.Tick();
return new IRenderable[] { new SpriteRenderable(img, renderPos, WVec.Zero, info.ZOffset, wr.Palette(info.Palette), info.Scale, true) };
var pxPos = wr.Viewport.WorldToViewPx(wr.ScreenPxPosition(self.CenterPosition) + boundsOffset) + sizeOffset;
return new IRenderable[] { new UISpriteRenderable(Anim.Image, self.CenterPosition, pxPos, Info.ZOffset, wr.Palette(Info.Palette), 1f) };
}
public void Tick(Actor self) { Anim.Tick(); }
}
}

View File

@@ -21,7 +21,7 @@ namespace OpenRA.Mods.Common.Traits.Render
protected override void UpgradeLevelChanged(Actor self, int oldLevel, int newLevel)
{
PlaySingleFrame(newLevel - 1);
Anim.PlayFetchIndex(Info.Sequence, () => newLevel - 1);
}
}
}

View File

@@ -30,15 +30,9 @@ namespace OpenRA.Mods.Common.Traits
[PaletteReference] public readonly string Palette = "chrome";
[Desc("Point on the production icon's used as reference for offsetting the overlay. ",
"Possible values are any combination of Top, VCenter, Bottom and Left, HCenter, Right separated by a comma.")]
"Possible values are combinations of Center, Top, Bottom, Left, Right.")]
public readonly ReferencePoints ReferencePoint = ReferencePoints.Top | ReferencePoints.Left;
[Desc("Pixel offset relative to the icon's reference point.")]
public readonly int2 Offset = int2.Zero;
[Desc("Visual scale of the overlay.")]
public readonly float Scale = 1f;
public object Create(ActorInitializer init) { return new VeteranProductionIconOverlay(init, this); }
}
@@ -76,55 +70,30 @@ namespace OpenRA.Mods.Common.Traits
}
}
public Sprite Sprite()
Sprite IProductionIconOverlay.Sprite { get { return sprite; } }
string IProductionIconOverlay.Palette { get { return info.Palette; } }
float2 IProductionIconOverlay.Offset(float2 iconSize)
{
return sprite;
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;
return new float2(x, y);
}
public string Palette()
{
return info.Palette;
}
public float Scale()
{
return info.Scale;
}
public float2 Offset(float2 iconSize)
{
float offsetX = 0, offsetY = 0;
switch (info.ReferencePoint & (ReferencePoints)3)
{
case ReferencePoints.Top:
offsetY = (-iconSize.Y + sprite.Size.Y) / 2;
break;
case ReferencePoints.VCenter:
break;
case ReferencePoints.Bottom:
offsetY = (iconSize.Y - sprite.Size.Y) / 2;
break;
}
switch (info.ReferencePoint & (ReferencePoints)(3 << 2))
{
case ReferencePoints.Left:
offsetX = (-iconSize.X + sprite.Size.X) / 2;
break;
case ReferencePoints.HCenter:
break;
case ReferencePoints.Right:
offsetX = (iconSize.X - sprite.Size.X) / 2;
break;
}
return new float2(offsetX, offsetY) + info.Offset;
}
public bool IsOverlayActive(ActorInfo ai)
bool IProductionIconOverlay.IsOverlayActive(ActorInfo ai)
{
bool isActive;
overlayActive.TryGetValue(ai, out isActive);
if (!overlayActive.TryGetValue(ai, out isActive))
return false;
return isActive;
}

View File

@@ -77,9 +77,8 @@ namespace OpenRA.Mods.Common.Traits
public interface IProductionIconOverlay
{
Sprite Sprite();
string Palette();
float Scale();
Sprite Sprite { get; }
string Palette { get; }
float2 Offset(float2 iconSize);
bool IsOverlayActive(ActorInfo ai);
}

View File

@@ -14,6 +14,7 @@ using System.Drawing;
using System.Globalization;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.UtilityCommands
{
@@ -2702,6 +2703,67 @@ namespace OpenRA.Mods.Common.UtilityCommands
node.Value.Nodes.Add(new MiniYamlNode("ParticleSize", "1, 1"));
}
// Overhauled the actor decorations traits
if (engineVersion < 20151226)
{
if (depth == 1 && (node.Key.StartsWith("WithDecoration") || node.Key.StartsWith("WithRankDecoration")))
{
node.Value.Nodes.RemoveAll(n => n.Key == "Scale");
node.Value.Nodes.RemoveAll(n => n.Key == "Offset");
var sd = node.Value.Nodes.FirstOrDefault(n => n.Key == "SelectionDecoration");
if (sd != null)
sd.Key = "RequiresSelection";
var reference = node.Value.Nodes.FirstOrDefault(n => n.Key == "ReferencePoint");
if (reference != null)
{
var values = FieldLoader.GetValue<string[]>("ReferencePoint", reference.Value.Value);
values = values.Where(v => v != "HCenter" && v != "VCenter").ToArray();
if (values.Length == 0)
values = new[] { "Center" };
reference.Value.Value = FieldSaver.FormatValue(values);
}
var stance = Stance.Ally;
var showToAllies = node.Value.Nodes.FirstOrDefault(n => n.Key == "ShowToAllies");
if (showToAllies != null && !FieldLoader.GetValue<bool>("ShowToAllies", showToAllies.Value.Value))
stance ^= Stance.Ally;
var showToEnemies = node.Value.Nodes.FirstOrDefault(n => n.Key == "ShowToEnemies");
if (showToEnemies != null && FieldLoader.GetValue<bool>("ShowToEnemies", showToEnemies.Value.Value))
stance |= Stance.Enemy;
if (stance != Stance.Ally)
node.Value.Nodes.Add(new MiniYamlNode("Stance", FieldSaver.FormatValue(stance)));
node.Value.Nodes.RemoveAll(n => n.Key == "ShowToAllies");
node.Value.Nodes.RemoveAll(n => n.Key == "ShowToEnemies");
}
if (depth == 1 && node.Key == "Fake")
{
node.Key = "WithDecoration@fake";
node.Value.Nodes.Add(new MiniYamlNode("RequiresSelection", "true"));
node.Value.Nodes.Add(new MiniYamlNode("Image", "pips"));
node.Value.Nodes.Add(new MiniYamlNode("Sequence", "tag-fake"));
node.Value.Nodes.Add(new MiniYamlNode("ReferencePoint", "Top"));
node.Value.Nodes.Add(new MiniYamlNode("ZOffset", "256"));
}
if (depth == 0 && node.Value.Nodes.Any(n => n.Key.StartsWith("PrimaryBuilding")))
{
var decNodes = new List<MiniYamlNode>();
decNodes.Add(new MiniYamlNode("RequiresSelection", "true"));
decNodes.Add(new MiniYamlNode("Image", "pips"));
decNodes.Add(new MiniYamlNode("Sequence", "tag-primary"));
decNodes.Add(new MiniYamlNode("ReferencePoint", "Top"));
decNodes.Add(new MiniYamlNode("ZOffset", "256"));
decNodes.Add(new MiniYamlNode("UpgradeTypes", "primary"));
decNodes.Add(new MiniYamlNode("UpgradeMinEnabledLevel", "1"));
node.Value.Nodes.Add(new MiniYamlNode("WithDecoration@primary", new MiniYaml("", decNodes)));
}
}
UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1);
}
}

View File

@@ -91,10 +91,11 @@ namespace OpenRA.Mods.Common.Widgets
var location = new float2(RenderBounds.Location) + new float2(queue.i * (IconWidth + IconSpacing), 0);
WidgetUtils.DrawSHPCentered(icon.Image, location + 0.5f * iconSize, worldRenderer.Palette(bi.IconPalette), 0.5f);
var pio = queue.Trait.Actor.Owner.PlayerActor.TraitsImplementing<IProductionIconOverlay>().FirstOrDefault();
if (pio != null && pio.IsOverlayActive(actor))
WidgetUtils.DrawSHPCentered(pio.Sprite(), location + 0.5f * iconSize + pio.Offset(0.5f * iconSize),
worldRenderer.Palette(pio.Palette()), 0.5f * pio.Scale());
var pio = queue.Trait.Actor.Owner.PlayerActor.TraitsImplementing<IProductionIconOverlay>()
.FirstOrDefault(p => p.IsOverlayActive(actor));
if (pio != null)
WidgetUtils.DrawSHPCentered(pio.Sprite, location + 0.5f * iconSize + pio.Offset(0.5f * iconSize),
worldRenderer.Palette(pio.Palette), 0.5f);
var clock = clocks[queue.Trait];
clock.PlayFetchIndex(ClockSequence,

View File

@@ -374,8 +374,7 @@ namespace OpenRA.Mods.Common.Widgets
var buildableItems = CurrentQueue.BuildableItems();
var pio = currentQueue.Actor.Owner.PlayerActor.TraitsImplementing<IProductionIconOverlay>().FirstOrDefault();
var pioOffset = pio != null ? pio.Offset(IconSize) : new float2(0, 0);
var pios = currentQueue.Actor.Owner.PlayerActor.TraitsImplementing<IProductionIconOverlay>();
// Icons
foreach (var icon in icons.Values)
@@ -383,8 +382,9 @@ namespace OpenRA.Mods.Common.Widgets
WidgetUtils.DrawSHPCentered(icon.Sprite, icon.Pos + iconOffset, icon.Palette);
// Draw the ProductionIconOverlay's sprite
if (pio != null && pio.IsOverlayActive(icon.Actor))
WidgetUtils.DrawSHPCentered(pio.Sprite(), icon.Pos + iconOffset + pioOffset, worldRenderer.Palette(pio.Palette()), pio.Scale());
var pio = pios.FirstOrDefault(p => p.IsOverlayActive(icon.Actor));
if (pio != null)
WidgetUtils.DrawSHPCentered(pio.Sprite, icon.Pos + iconOffset + pio.Offset(IconSize), worldRenderer.Palette(pio.Palette), 1f);
// Build progress
if (icon.Queued.Count > 0)