Files
OpenRA/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs
Paul Chote 6f5d035e79 Introduce IMouseBounds and split/rework mouse rectangles.
The render bounds for an actor now include the area covered
by bibs, shadows, and any other widgets. In many cases this
area is much larger than we really want to consider for
tooltips and mouse selection.

An optional Margin is added to Selectable to support cases
like infantry, where we want the mouse area of the actor
to be larger than the drawn selection box.
2017-12-11 19:45:07 +01:00

295 lines
8.4 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2017 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits.Render
{
public interface IRenderActorPreviewSpritesInfo : ITraitInfo
{
IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p);
}
[Desc("Render trait fundament that won't work without additional With* render traits.")]
public class RenderSpritesInfo : IRenderActorPreviewInfo, ITraitInfo
{
[Desc("The sequence name that defines the actor sprites. Defaults to the actor name.")]
public readonly string Image = null;
[Desc("A dictionary of faction-specific image overrides.")]
public readonly Dictionary<string, string> FactionImages = null;
[Desc("Custom palette name")]
[PaletteReference] public readonly string Palette = null;
[Desc("Custom PlayerColorPalette: BaseName")]
[PaletteReference(true)] public readonly string PlayerPalette = "player";
[Desc("Change the sprite image size.")]
public readonly float Scale = 1f;
public virtual object Create(ActorInitializer init) { return new RenderSprites(init, this); }
public IEnumerable<IActorPreview> RenderPreview(ActorPreviewInitializer init)
{
var sequenceProvider = init.World.Map.Rules.Sequences;
var faction = init.Get<FactionInit, string>();
var ownerName = init.Get<OwnerInit>().PlayerName;
var image = GetImage(init.Actor, sequenceProvider, faction);
var palette = init.WorldRenderer.Palette(Palette ?? PlayerPalette + ownerName);
var facings = 0;
var body = init.Actor.TraitInfoOrDefault<BodyOrientationInfo>();
if (body != null)
{
facings = body.QuantizedFacings;
if (facings == -1)
{
var qbo = init.Actor.TraitInfoOrDefault<IQuantizeBodyOrientationInfo>();
facings = qbo != null ? qbo.QuantizedBodyFacings(init.Actor, sequenceProvider, faction) : 1;
}
}
foreach (var spi in init.Actor.TraitInfos<IRenderActorPreviewSpritesInfo>())
foreach (var preview in spi.RenderPreviewSprites(init, this, image, facings, palette))
yield return preview;
}
public string GetImage(ActorInfo actor, SequenceProvider sequenceProvider, string faction)
{
if (FactionImages != null && !string.IsNullOrEmpty(faction))
{
string factionImage = null;
if (FactionImages.TryGetValue(faction, out factionImage) && sequenceProvider.HasSequence(factionImage))
return factionImage;
}
return (Image ?? actor.Name).ToLowerInvariant();
}
}
public class RenderSprites : IRender, ITick, INotifyOwnerChanged, INotifyEffectiveOwnerChanged, IActorPreviewInitModifier
{
static readonly Pair<DamageState, string>[] DamagePrefixes =
{
Pair.New(DamageState.Critical, "critical-"),
Pair.New(DamageState.Heavy, "damaged-"),
Pair.New(DamageState.Medium, "scratched-"),
Pair.New(DamageState.Light, "scuffed-")
};
class AnimationWrapper
{
public readonly AnimationWithOffset Animation;
public readonly string Palette;
public readonly bool IsPlayerPalette;
public PaletteReference PaletteReference { get; private set; }
bool cachedVisible;
WVec cachedOffset;
ISpriteSequence cachedSequence;
public AnimationWrapper(AnimationWithOffset animation, string palette, bool isPlayerPalette)
{
Animation = animation;
Palette = palette;
IsPlayerPalette = isPlayerPalette;
}
public void CachePalette(WorldRenderer wr, Player owner)
{
PaletteReference = wr.Palette(IsPlayerPalette ? Palette + owner.InternalName : Palette);
}
public void OwnerChanged()
{
// Update the palette reference next time we draw
if (IsPlayerPalette)
PaletteReference = null;
}
public bool IsVisible
{
get
{
return Animation.DisableFunc == null || !Animation.DisableFunc();
}
}
public bool Tick()
{
// Tick the animation
Animation.Animation.Tick();
// Return to the caller whether the renderable position or size has changed
var visible = IsVisible;
var offset = Animation.OffsetFunc != null ? Animation.OffsetFunc() : WVec.Zero;
var sequence = Animation.Animation.CurrentSequence;
var updated = visible != cachedVisible || offset != cachedOffset || sequence != cachedSequence;
cachedVisible = visible;
cachedOffset = offset;
cachedSequence = sequence;
return updated;
}
}
public readonly RenderSpritesInfo Info;
readonly string faction;
readonly List<AnimationWrapper> anims = new List<AnimationWrapper>();
string cachedImage;
public static Func<int> MakeFacingFunc(Actor self)
{
var facing = self.TraitOrDefault<IFacing>();
if (facing == null) return () => 0;
return () => facing.Facing;
}
public RenderSprites(ActorInitializer init, RenderSpritesInfo info)
{
Info = info;
faction = init.Contains<FactionInit>() ? init.Get<FactionInit, string>() : init.Self.Owner.Faction.InternalName;
}
public string GetImage(Actor self)
{
if (cachedImage != null)
return cachedImage;
return cachedImage = Info.GetImage(self.Info, self.World.Map.Rules.Sequences, faction);
}
public void UpdatePalette()
{
foreach (var anim in anims)
anim.OwnerChanged();
}
public virtual void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) { UpdatePalette(); }
public void OnEffectiveOwnerChanged(Actor self, Player oldEffectiveOwner, Player newEffectiveOwner) { UpdatePalette(); }
public virtual IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr)
{
foreach (var a in anims)
{
if (!a.IsVisible)
continue;
if (a.PaletteReference == null)
{
var owner = self.EffectiveOwner != null && self.EffectiveOwner.Disguised ? self.EffectiveOwner.Owner : self.Owner;
a.CachePalette(wr, owner);
}
foreach (var r in a.Animation.Render(self, wr, a.PaletteReference, Info.Scale))
yield return r;
}
}
public virtual IEnumerable<Rectangle> ScreenBounds(Actor self, WorldRenderer wr)
{
foreach (var a in anims)
if (a.IsVisible)
yield return a.Animation.ScreenBounds(self, wr, Info.Scale);
}
void ITick.Tick(Actor self)
{
Tick(self);
}
protected virtual void Tick(Actor self)
{
var updated = false;
foreach (var a in anims)
updated |= a.Tick();
if (updated)
self.World.ScreenMap.AddOrUpdate(self);
}
public void Add(AnimationWithOffset anim, string palette = null, bool isPlayerPalette = false)
{
// Use defaults
if (palette == null)
{
palette = Info.Palette ?? Info.PlayerPalette;
isPlayerPalette = Info.Palette == null;
}
anims.Add(new AnimationWrapper(anim, palette, isPlayerPalette));
}
public void Remove(AnimationWithOffset anim)
{
anims.RemoveAll(a => a.Animation == anim);
}
public static string UnnormalizeSequence(string sequence)
{
// Remove existing damage prefix
foreach (var s in DamagePrefixes)
{
if (sequence.StartsWith(s.Second, StringComparison.Ordinal))
{
sequence = sequence.Substring(s.Second.Length);
break;
}
}
return sequence;
}
public static string NormalizeSequence(Animation anim, DamageState state, string sequence)
{
// Remove any existing damage prefix
sequence = UnnormalizeSequence(sequence);
foreach (var s in DamagePrefixes)
if (state >= s.First && anim.HasSequence(s.Second + sequence))
return s.Second + sequence;
return sequence;
}
// Required by WithSpriteBody and WithInfantryBody
public int2 AutoSelectionSize(Actor self)
{
return AutoRenderSize(self);
}
// Required by WithSpriteBody and WithInfantryBody
public int2 AutoRenderSize(Actor self)
{
return anims.Where(b => b.IsVisible
&& b.Animation.Animation.CurrentSequence != null)
.Select(a => (a.Animation.Animation.Image.Size.XY * Info.Scale).ToInt2())
.FirstOrDefault();
}
void IActorPreviewInitModifier.ModifyActorPreviewInit(Actor self, TypeDictionary inits)
{
if (!inits.Contains<FactionInit>())
inits.Add(new FactionInit(faction));
}
}
}