Files
OpenRA/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs
2023-03-10 20:11:33 +02:00

285 lines
8.0 KiB
C#

#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* 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.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits.Render
{
public interface IRenderActorPreviewSpritesInfo : ITraitInfoInterface
{
IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, string image, int facings, PaletteReference p);
}
[Desc("Render trait fundament that won't work without additional With* render traits.")]
public class RenderSpritesInfo : TraitInfo, IRenderActorPreviewInfo
{
[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;
[PaletteReference]
[Desc("Custom palette name")]
public readonly string Palette = null;
[PaletteReference(true)]
[Desc("Custom PlayerColorPalette: BaseName")]
public readonly string PlayerPalette = "player";
public override object Create(ActorInitializer init) { return new RenderSprites(init, this); }
public IEnumerable<IActorPreview> RenderPreview(ActorPreviewInitializer init)
{
var sequences = init.World.Map.Sequences;
var faction = init.GetValue<FactionInit, string>(this);
var ownerName = init.Get<OwnerInit>().InternalName;
var image = GetImage(init.Actor, 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?.QuantizedBodyFacings(init.Actor, sequences, faction) ?? 1;
}
}
foreach (var spi in init.Actor.TraitInfos<IRenderActorPreviewSpritesInfo>())
foreach (var preview in spi.RenderPreviewSprites(init, image, facings, palette))
yield return preview;
}
public string GetImage(ActorInfo actor, string faction)
{
if (FactionImages != null && !string.IsNullOrEmpty(faction) && FactionImages.TryGetValue(faction, out var factionImage))
return factionImage.ToLowerInvariant();
return (Image ?? actor.Name).ToLowerInvariant();
}
}
public class RenderSprites : IRender, ITick, INotifyOwnerChanged, INotifyEffectiveOwnerChanged, IActorPreviewInitModifier
{
static readonly (DamageState DamageState, string Prefix)[] DamagePrefixes =
{
(DamageState.Critical, "critical-"),
(DamageState.Heavy, "damaged-"),
(DamageState.Medium, "scratched-"),
(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 => 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?.Invoke() ?? 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<WAngle> MakeFacingFunc(Actor self)
{
var facing = self.TraitOrDefault<IFacing>();
if (facing == null)
return () => WAngle.Zero;
return () => facing.Facing;
}
public RenderSprites(ActorInitializer init, RenderSpritesInfo info)
{
Info = info;
faction = init.GetValue<FactionInit, string>(init.Self.Owner.Faction.InternalName);
}
public string GetImage(Actor self)
{
if (cachedImage != null)
return cachedImage;
return cachedImage = Info.GetImage(self.Info, 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, a.PaletteReference))
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);
}
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.Prefix, StringComparison.Ordinal))
{
sequence = sequence.Substring(s.Prefix.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.DamageState && anim.HasSequence(s.Prefix + sequence))
return s.Prefix + sequence;
return sequence;
}
// Required by WithSpriteBody and WithInfantryBody
public int2 AutoSelectionSize()
{
return AutoRenderSize();
}
// Required by WithSpriteBody and WithInfantryBody
public int2 AutoRenderSize()
{
return anims.Where(b => b.IsVisible
&& b.Animation.Animation.CurrentSequence != null)
.Select(a => (a.Animation.Animation.Image.Size.XY * a.Animation.Animation.CurrentSequence.Scale).ToInt2())
.FirstOrDefault();
}
void IActorPreviewInitModifier.ModifyActorPreviewInit(Actor self, TypeDictionary inits)
{
if (!inits.Contains<FactionInit>())
inits.Add(new FactionInit(faction));
}
}
}