Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an asset editor #21393

Draft
wants to merge 6 commits into
base: bleed
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions OpenRA.Game/FieldSaver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ public static MiniYamlNode SaveField(object o, string field)
return new MiniYamlNode(field, FormatValue(o, o.GetType().GetField(field)));
}

public static MiniYamlNode SaveField(object o, FieldInfo field)
{
return new MiniYamlNode(field.Name, FormatValue(o, field));
}

public static string FormatValue(object v)
{
if (v == null)
Expand Down
16 changes: 11 additions & 5 deletions OpenRA.Game/Graphics/Animation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,23 @@ public class Animation
Action tickFunc;

public Animation(World world, string name)
: this(world, name, () => WAngle.Zero) { }
: this(world.Map.Sequences, name, () => WAngle.Zero) { }

public Animation(World world, string name, Func<WAngle> facingFunc)
: this(world, name, facingFunc, null) { }
: this(world.Map.Sequences, name, facingFunc, null) { }

public Animation(World world, string name, Func<bool> paused)
: this(world, name, () => WAngle.Zero, paused) { }
: this(world.Map.Sequences, name, () => WAngle.Zero, paused) { }

public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
public Animation(SequenceSet sequences, string name)
: this(sequences, name, () => WAngle.Zero) { }

public Animation(SequenceSet sequences, string name, Func<WAngle> facingFunc)
: this(sequences, name, facingFunc, null) { }

public Animation(SequenceSet sequences, string name, Func<WAngle> facingFunc, Func<bool> paused)
{
sequences = world.Map.Sequences;
this.sequences = sequences;
Name = name.ToLowerInvariant();
this.facingFunc = facingFunc;
this.paused = paused;
Expand Down
3 changes: 3 additions & 0 deletions OpenRA.Game/Graphics/Sprite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

using System;
using OpenRA.Primitives;
using OpenRA.Traits;

namespace OpenRA.Graphics
{
Expand All @@ -22,6 +23,8 @@ public class Sprite
public readonly TextureChannel Channel;
public readonly float ZRamp;
public readonly float3 Size;

[AssetEditor]
public readonly float3 Offset;
public readonly float Top, Left, Bottom, Right;

Expand Down
7 changes: 7 additions & 0 deletions OpenRA.Game/Traits/TraitsInterfaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ namespace OpenRA.Traits
[AttributeUsage(AttributeTargets.Interface)]
public sealed class RequireExplicitImplementationAttribute : Attribute { }

[AttributeUsage(AttributeTargets.Field)]
public sealed class AssetEditorAttribute : Attribute
{
public readonly string[] EditInsideMembers;
public AssetEditorAttribute(string[] editInsideMembers = null) { EditInsideMembers = editInsideMembers; }
}

[Flags]
public enum DamageState
{
Expand Down
2 changes: 1 addition & 1 deletion OpenRA.Mods.Cnc/Traits/Render/WithBuildingBib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer i
for (var i = 0; i < rows * width; i++)
{
var index = i;
var anim = new Animation(init.World, image);
var anim = new Animation(init.Sequences, image);
var cellOffset = new CVec(i % width, i / width + bibOffset);
var cell = location + cellOffset;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public override IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInit
var wsb = init.Actor.TraitInfos<WithSpriteBodyInfo>().First();

// Show the correct turret facing
var anim = new Animation(init.World, image, t.WorldFacingFromInit(init));
var anim = new Animation(init.Sequences, image, t.WorldFacingFromInit(init));
anim.PlayRepeating(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), wsb.Sequence));

yield return new SpriteActorPreview(anim, () => WVec.Zero, () => 0, p);
Expand Down
15 changes: 8 additions & 7 deletions OpenRA.Mods.Common/Graphics/ActorPreview.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,24 @@ public class ActorPreviewInitializer : IActorInitializer
{
public readonly ActorInfo Actor;
public readonly WorldRenderer WorldRenderer;
public readonly SequenceSet Sequences;
public World World => WorldRenderer.World;

readonly ActorReference reference;

public ActorPreviewInitializer(ActorInfo actor, WorldRenderer worldRenderer, TypeDictionary dict)
public ActorPreviewInitializer(ActorInfo actor, ActorReference reference, WorldRenderer worldRenderer, SequenceSet sequences = null)
{
Actor = actor;
Sequences = sequences ?? worldRenderer.World.Map.Sequences;
this.reference = reference;
WorldRenderer = worldRenderer;
reference = new ActorReference(actor.Name.ToLowerInvariant(), dict);
}

public ActorPreviewInitializer(ActorInfo actor, WorldRenderer worldRenderer, TypeDictionary dict)
: this(actor, new ActorReference(actor.Name.ToLowerInvariant(), dict), worldRenderer) { }

public ActorPreviewInitializer(ActorReference actor, WorldRenderer worldRenderer)
{
Actor = worldRenderer.World.Map.Rules.Actors[actor.Type.ToLowerInvariant()];
reference = actor;
WorldRenderer = worldRenderer;
}
: this(worldRenderer.World.Map.Rules.Actors[actor.Type.ToLowerInvariant()], actor, worldRenderer) { }

// Forward IActorInitializer queries to the actor reference
// ActorReference can't reference a World instance, which prevents it from implementing this directly.
Expand Down
25 changes: 16 additions & 9 deletions OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;

namespace OpenRA.Mods.Common.Graphics
{
Expand Down Expand Up @@ -206,8 +207,9 @@ public ReservationInfo(string filename, List<int> loadFrames, int[] frames, Mini

protected readonly ISpriteSequenceLoader Loader;

protected string image;
public readonly string Image;
protected List<SpriteReservation> spritesToLoad = new();

protected Sprite[] sprites;
protected Sprite[] shadowSprites;
protected bool reverseFacings;
Expand All @@ -222,6 +224,11 @@ public ReservationInfo(string filename, List<int> loadFrames, int[] frames, Mini
protected int facings;
protected int? interpolatedFacings;
protected int tick;

[AssetEditor(new string[] { nameof(sprites), nameof(shadowSprites) })]
protected float3 offset;

[AssetEditor]
protected int zOffset;
protected int shadowZOffset;
protected bool ignoreWorldTint;
Expand All @@ -236,7 +243,7 @@ public ReservationInfo(string filename, List<int> loadFrames, int[] frames, Mini
protected void ThrowIfUnresolved()
{
if (bounds == null)
throw new InvalidOperationException($"Unable to query unresolved sequence {image}.{Name}.");
throw new InvalidOperationException($"Unable to query unresolved sequence {Image}.{Name}.");
}

int ISpriteSequence.Length
Expand Down Expand Up @@ -367,7 +374,7 @@ protected virtual IEnumerable<ReservationInfo> ParseCombineFilenames(ModData mod

public DefaultSpriteSequence(SpriteCache cache, ISpriteSequenceLoader loader, string image, string sequence, MiniYaml data, MiniYaml defaults)
{
this.image = image;
Image = image;
Name = sequence;
Loader = loader;

Expand Down Expand Up @@ -432,7 +439,7 @@ public virtual void ReserveSprites(ModData modData, string tileset, SpriteCache
var flipX = LoadField(FlipX, data, defaults);
var flipY = LoadField(FlipY, data, defaults);
var zRamp = LoadField(ZRamp, data, defaults);
var offset = LoadField(Offset, data, defaults);
offset = LoadField(Offset, data, defaults);
var blendMode = LoadField(BlendMode, data, defaults);

var combineNode = data.NodeWithKeyOrDefault(Combine.Key);
Expand Down Expand Up @@ -522,7 +529,7 @@ public virtual void ResolveSprites(SpriteCache cache)
if (alpha.Length == 1)
alpha = Exts.MakeArray(length.Value, _ => alpha[0]);
else if (alpha.Length != length.Value)
throw new YamlException($"Sequence {image}.{Name} must define either 1 or {length.Value} Alpha values.");
throw new YamlException($"Sequence {Image}.{Name} must define either 1 or {length.Value} Alpha values.");
}
else if (alphaFade)
alpha = Exts.MakeArray(length.Value, i => float2.Lerp(1f, 0f, i / (length.Value - 1f)));
Expand All @@ -536,12 +543,12 @@ public virtual void ResolveSprites(SpriteCache cache)
}

if (index.Count == 0)
throw new YamlException($"Sequence {image}.{Name} does not define any frames.");
throw new YamlException($"Sequence {Image}.{Name} does not define any frames.");

var minIndex = index.Min();
var maxIndex = index.Max();
if (minIndex < 0 || maxIndex >= allSprites.Length)
throw new YamlException($"Sequence {image}.{Name} uses frames between {minIndex}..{maxIndex}, but only 0..{allSprites.Length - 1} exist.");
throw new YamlException($"Sequence {Image}.{Name} uses frames between {minIndex}..{maxIndex}, but only 0..{allSprites.Length - 1} exist.");

sprites = index.Select(f => allSprites[f]).ToArray();
if (shadowStart >= 0)
Expand Down Expand Up @@ -583,7 +590,7 @@ public Sprite GetShadow(int frame, WAngle facing)
var index = GetFacingFrameOffset(facing) * length.Value + frame % length.Value;
var sprite = shadowSprites[index];
if (sprite == null)
throw new InvalidOperationException($"Attempted to query unloaded shadow sprite from {image}.{Name} frame={frame} facing={facing}.");
throw new InvalidOperationException($"Attempted to query unloaded shadow sprite from {Image}.{Name} frame={frame} facing={facing}.");

return sprite;
}
Expand All @@ -594,7 +601,7 @@ public virtual Sprite GetSprite(int frame, WAngle facing)
var index = GetFacingFrameOffset(facing) * length.Value + frame % length.Value;
var sprite = sprites[index];
if (sprite == null)
throw new InvalidOperationException($"Attempted to query unloaded sprite from {image}.{Name} frame={frame} facing={facing}.");
throw new InvalidOperationException($"Attempted to query unloaded sprite from {Image}.{Name} frame={frame} facing={facing}.");

return sprite;
}
Expand Down
12 changes: 6 additions & 6 deletions OpenRA.Mods.Common/Graphics/SpriteActorPreview.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,34 @@ namespace OpenRA.Mods.Common.Graphics
{
public class SpriteActorPreview : IActorPreview
{
readonly Animation animation;
public readonly Animation Animation;
readonly Func<WVec> offset;
readonly Func<int> zOffset;
readonly PaletteReference pr;

public SpriteActorPreview(Animation animation, Func<WVec> offset, Func<int> zOffset, PaletteReference pr)
{
this.animation = animation;
Animation = animation;
this.offset = offset;
this.zOffset = zOffset;
this.pr = pr;
}

void IActorPreview.Tick() { animation.Tick(); }
void IActorPreview.Tick() { Animation.Tick(); }

IEnumerable<IRenderable> IActorPreview.RenderUI(WorldRenderer wr, int2 pos, float scale)
{
return animation.RenderUI(wr, pos, offset(), zOffset(), pr, scale);
return Animation.RenderUI(wr, pos, offset(), zOffset(), pr, scale);
}

IEnumerable<IRenderable> IActorPreview.Render(WorldRenderer wr, WPos pos)
{
return animation.Render(pos, offset(), zOffset(), pr);
return Animation.Render(pos, offset(), zOffset(), pr);
}

IEnumerable<Rectangle> IActorPreview.ScreenBounds(WorldRenderer wr, WPos pos)
{
yield return animation.ScreenBounds(wr, pos, offset());
yield return Animation.ScreenBounds(wr, pos, offset());
}
}
}
1 change: 1 addition & 0 deletions OpenRA.Mods.Common/Traits/Armament.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class ArmamentInfo : PausableConditionalTraitInfo, Requires<AttackBaseInf
[Desc("Time (in frames) until the weapon can fire again.")]
public readonly int FireDelay = 0;

[AssetEditor]
[Desc("Muzzle position relative to turret or body, (forward, right, up) triples.",
"If weapon Burst = 1, it cycles through all listed offsets, otherwise the offset corresponding to current burst is used.")]
public readonly WVec[] LocalOffset = Array.Empty<WVec>();
Expand Down
3 changes: 2 additions & 1 deletion OpenRA.Mods.Common/Traits/AutoTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ IEnumerable<EditorActorOption> IEditorActorOptions.ActorOptions(ActorInfo ai, Wo
var stance = init?.Value ?? InitialStance;
return stances[(int)stance];
},
(actor, value) => actor.ReplaceInit(new StanceInit(this, (UnitStance)stances.IndexOf(value))));
(actor, value) => actor.ReplaceInit(new StanceInit(this, (UnitStance)stances.IndexOf(value))),
true);
}
}

Expand Down
2 changes: 2 additions & 0 deletions OpenRA.Mods.Common/Traits/Buildings/Exit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Where the unit should leave the building. Multiples are allowed if IDs are added: Exit@2, ...")]
public class ExitInfo : ConditionalTraitInfo, Requires<IOccupySpaceInfo>
{
[AssetEditor]
[Desc("Offset at which that the exiting actor is spawned relative to the center of the producing actor.")]
public readonly WVec SpawnOffset = WVec.Zero;

[AssetEditor]
[Desc("Cell offset where the exiting actor enters the ActorMap relative to the topleft cell of the producing actor.")]
public readonly CVec ExitCell = CVec.Zero;
public readonly WAngle? Facing = null;
Expand Down
2 changes: 1 addition & 1 deletion OpenRA.Mods.Common/Traits/Buildings/FreeActor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ IEnumerable<EditorActorOption> IEditorActorOptions.ActorOptions(ActorInfo ai, Wo

return true;
},
(actor, value) => actor.ReplaceInit(new FreeActorInit(this, value), this));
(actor, value) => actor.ReplaceInit(new FreeActorInit(this, value), this), true);
}

public override object Create(ActorInitializer init) { return new FreeActor(init, this); }
Expand Down
1 change: 1 addition & 0 deletions OpenRA.Mods.Common/Traits/HitShape.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class HitShapeInfo : ConditionalTraitInfo, Requires<BodyOrientationInfo>
[Desc("Name of turret this shape is linked to. Leave empty to link shape to body.")]
public readonly string Turret = null;

[AssetEditor]
[Desc("Create a targetable position for each offset listed here (relative to CenterPosition).")]
public readonly WVec[] TargetableOffsets = { WVec.Zero };

Expand Down
2 changes: 2 additions & 0 deletions OpenRA.Mods.Common/Traits/Interactable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Used to enable mouse interaction on actors that are not Selectable.")]
public class InteractableInfo : TraitInfo, IMouseBoundsInfo
{
[AssetEditor]
[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.",
"The first two numbers define the width and height of the rectangle as a world distance.",
"The (optional) second two numbers define an x and y offset from the actor center.")]
public readonly WDist[] Bounds = null;

[AssetEditor]
[Desc("Defines a custom rectangle for Decorations (e.g. the selection box).",
"If null, Bounds will be used instead")]
public readonly WDist[] DecorationBounds = null;
Expand Down
2 changes: 1 addition & 1 deletion OpenRA.Mods.Common/Traits/Pluggable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ IEnumerable<EditorActorOption> IEditorActorOptions.ActorOptions(ActorInfo ai, Wo
actor.RemoveInit<PlugInit>(this);
else
actor.ReplaceInit(new PlugInit(this, value), this);
});
}, true);
}

public override object Create(ActorInitializer init) { return new Pluggable(init, this); }
Expand Down
2 changes: 1 addition & 1 deletion OpenRA.Mods.Common/Traits/Render/WithBridgeSpriteBody.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public override IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInit
if (!EnabledByDefault)
yield break;

var anim = new Animation(init.World, image);
var anim = new Animation(init.Sequences, image);
anim.PlayFetchIndex(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), Sequences[0]), () => 0);

yield return new SpriteActorPreview(anim, () => WVec.Zero, () => 0, p);
Expand Down
2 changes: 1 addition & 1 deletion OpenRA.Mods.Common/Traits/Render/WithChargeSpriteBody.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public override IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInit
if (!EnabledByDefault)
yield break;

var anim = new Animation(init.World, image);
var anim = new Animation(init.Sequences, image);
anim.PlayFetchIndex(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), Sequence), () => 0);

yield return new SpriteActorPreview(anim, () => WVec.Zero, () => 0, p);
Expand Down
2 changes: 1 addition & 1 deletion OpenRA.Mods.Common/Traits/Render/WithCrateBody.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ sealed class WithCrateBodyInfo : TraitInfo, Requires<RenderSpritesInfo>, IRender

public IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, string image, int facings, PaletteReference p)
{
var anim = new Animation(init.World, image);
var anim = new Animation(init.Sequences, image);
anim.PlayRepeating(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), IdleSequence));
yield return new SpriteActorPreview(anim, () => WVec.Zero, () => 0, p);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public override IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInit
if (!EnabledByDefault)
yield break;

var anim = new Animation(init.World, image);
var anim = new Animation(init.Sequences, image);
var sequence = init.World.Type == WorldType.Editor ? EditorSequence : Sequence;
var palette = init.World.Type == WorldType.Editor ? init.WorldRenderer.Palette(EditorPalette) : p;
anim.PlayFetchIndex(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), sequence), () => 0);
Expand Down
2 changes: 1 addition & 1 deletion OpenRA.Mods.Common/Traits/Render/WithFacingSpriteBody.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public override IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInit
if (!EnabledByDefault)
yield break;

var anim = new Animation(init.World, image, init.GetFacing());
var anim = new Animation(init.Sequences, image, init.GetFacing());
anim.PlayRepeating(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), Sequence));

yield return new SpriteActorPreview(anim, () => WVec.Zero, () => 0, p);
Expand Down
2 changes: 1 addition & 1 deletion OpenRA.Mods.Common/Traits/Render/WithGateSpriteBody.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public override IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInit
if (!EnabledByDefault)
yield break;

var anim = new Animation(init.World, image);
var anim = new Animation(init.Sequences, image);
anim.PlayFetchIndex(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), Sequence), () => 0);

yield return new SpriteActorPreview(anim, () => WVec.Zero, () => 0, p);
Expand Down