Skip to content

Commit

Permalink
Replace ITintableRenderable with IModifyableRenderable.
Browse files Browse the repository at this point in the history
  • Loading branch information
pchote committed Jan 1, 2021
1 parent b35c33d commit 3484add
Show file tree
Hide file tree
Showing 16 changed files with 118 additions and 57 deletions.
5 changes: 3 additions & 2 deletions OpenRA.Game/Graphics/Animation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,13 @@ public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> p

public IRenderable[] Render(WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale)
{
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration, CurrentSequence.IgnoreWorldTint);
var tintModifiers = CurrentSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None;
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration, tintModifiers);

if (CurrentSequence.ShadowStart >= 0)
{
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true, CurrentSequence.IgnoreWorldTint);
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true, tintModifiers);
return new IRenderable[] { shadowRenderable, imageRenderable };
}

Expand Down
18 changes: 16 additions & 2 deletions OpenRA.Game/Graphics/Renderable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/
#endregion

using System;
using OpenRA.Primitives;

namespace OpenRA.Graphics
Expand All @@ -32,9 +33,22 @@ public interface IPalettedRenderable : IRenderable
IPalettedRenderable WithPalette(PaletteReference newPalette);
}

public interface ITintableRenderable
[Flags]
public enum TintModifiers
{
IRenderable WithTint(in float3 newTint);
None = 0,
IgnoreWorldTint = 1,
ReplaceColor = 2
}

public interface IModifyableRenderable : IRenderable
{
float Alpha { get; }
float3 Tint { get; }
TintModifiers TintModifiers { get; }

IModifyableRenderable WithAlpha(float newAlpha);
IModifyableRenderable WithTint(in float3 newTint, TintModifiers newTintModifiers);
}

public interface IFinalizedRenderable
Expand Down
58 changes: 36 additions & 22 deletions OpenRA.Game/Graphics/SpriteRenderable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

namespace OpenRA.Graphics
{
public struct SpriteRenderable : IPalettedRenderable, ITintableRenderable, IFinalizedRenderable
public struct SpriteRenderable : IPalettedRenderable, IModifyableRenderable, IFinalizedRenderable
{
public static readonly IEnumerable<IRenderable> None = new IRenderable[0];

Expand All @@ -25,16 +25,17 @@ public struct SpriteRenderable : IPalettedRenderable, ITintableRenderable, IFina
readonly PaletteReference palette;
readonly float scale;
readonly float3 tint;
readonly TintModifiers tintModifiers;
readonly float alpha;
readonly bool isDecoration;
readonly bool ignoreWorldTint;

public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration)
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, false) { }
: this(sprite, pos, offset, zOffset, palette, scale, 1f, float3.Ones, TintModifiers.None, isDecoration) { }

public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration, bool ignoreWorldTint)
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, ignoreWorldTint) { }
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration, TintModifiers tintModifiers)
: this(sprite, pos, offset, zOffset, palette, scale, 1f, float3.Ones, tintModifiers, isDecoration) { }

public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float3 tint, bool isDecoration, bool ignoreWorldTint)
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float alpha, float3 tint, TintModifiers tintModifiers, bool isDecoration)
{
this.sprite = sprite;
this.pos = pos;
Expand All @@ -44,7 +45,8 @@ public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, Palet
this.scale = scale;
this.tint = tint;
this.isDecoration = isDecoration;
this.ignoreWorldTint = ignoreWorldTint;
this.tintModifiers = tintModifiers;
this.alpha = alpha;
}

public WPos Pos { get { return pos + offset; } }
Expand All @@ -53,12 +55,24 @@ public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, Palet
public int ZOffset { get { return zOffset; } }
public bool IsDecoration { get { return isDecoration; } }

public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, tint, isDecoration, ignoreWorldTint); }
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
public IRenderable OffsetBy(WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, tint, true, ignoreWorldTint); }
public float Alpha { get { return alpha; } }
public float3 Tint { get { return tint; } }
public TintModifiers TintModifiers { get { return tintModifiers; } }

public IRenderable WithTint(in float3 newTint) { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newTint, isDecoration, ignoreWorldTint); }
public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, alpha, tint, tintModifiers, isDecoration); }
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, alpha, tint, tintModifiers, isDecoration); }
public IRenderable OffsetBy(WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, alpha, tint, tintModifiers, isDecoration); }
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, alpha, tint, tintModifiers, true); }

public IModifyableRenderable WithAlpha(float newAlpha)
{
return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newAlpha, tint, tintModifiers, isDecoration);
}

public IModifyableRenderable WithTint(in float3 newTint, TintModifiers newTintModifiers)
{
return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, alpha, newTint, newTintModifiers, isDecoration);
}

float3 ScreenPosition(WorldRenderer wr)
{
Expand All @@ -72,16 +86,16 @@ float3 ScreenPosition(WorldRenderer wr)
public void Render(WorldRenderer wr)
{
var wsr = Game.Renderer.WorldSpriteRenderer;
if (ignoreWorldTint)
wsr.DrawSprite(sprite, ScreenPosition(wr), palette, scale * sprite.Size);
else
{
var t = tint;
if (wr.TerrainLighting != null)
t *= wr.TerrainLighting.TintAt(pos);

wsr.DrawSprite(sprite, ScreenPosition(wr), palette, scale * sprite.Size, t, 1f);
}
var t = alpha * tint;
if (wr.TerrainLighting != null && (tintModifiers & TintModifiers.IgnoreWorldTint) == 0)
t *= wr.TerrainLighting.TintAt(pos);

// Shader interprets negative alpha as a flag to use the tint colour directly instead of multiplying the sprite colour
var a = alpha;
if ((tintModifiers & TintModifiers.ReplaceColor) != 0)
a *= -1;

wsr.DrawSprite(sprite, ScreenPosition(wr), palette, scale * sprite.Size, t, a);
}

public void RenderDebugGeometry(WorldRenderer wr)
Expand Down
3 changes: 2 additions & 1 deletion OpenRA.Mods.Cnc/Graphics/TeslaZapRenderable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ static IEnumerable<IFinalizedRenderable> DrawZap(WorldRenderer wr, float2 from,
.MinBy(t => Math.Abs(float2.Dot(z + new float2(t[0], t[1]), q) + c));

var pos = wr.ProjectedPosition((z + new float2(step[2], step[3])).ToInt2());
rs.Add(new SpriteRenderable(s.GetSprite(step[4]), pos, WVec.Zero, 0, pal, 1f, true, s.IgnoreWorldTint).PrepareRender(wr));
var tintModifiers = s.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None;
rs.Add(new SpriteRenderable(s.GetSprite(step[4]), pos, WVec.Zero, 0, pal, 1f, true, tintModifiers).PrepareRender(wr));

z += new float2(step[0], step[1]);
if (rs.Count >= 1000)
Expand Down
2 changes: 1 addition & 1 deletion OpenRA.Mods.Cnc/Traits/Minelayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ protected override IEnumerable<IRenderable> RenderAboveShroud(WorldRenderer wr,
|| !movement.CanEnterCell(c, null, BlockedByActor.Immovable) || (mobile != null && !mobile.CanStayInCell(c)))
tile = tileBlocked;

yield return new SpriteRenderable(tile, world.Map.CenterOfCell(c), WVec.Zero, -511, pal, 1f, true, true);
yield return new SpriteRenderable(tile, world.Map.CenterOfCell(c), WVec.Zero, -511, pal, 1f, true, TintModifiers.IgnoreWorldTint);
}
}

Expand Down
8 changes: 4 additions & 4 deletions OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ protected override IEnumerable<IRenderable> Render(WorldRenderer wr, World world
var tiles = power.CellsMatching(xy, footprint, dimensions);
var palette = wr.Palette(((ChronoshiftPowerInfo)power.Info).TargetOverlayPalette);
foreach (var t in tiles)
yield return new SpriteRenderable(tile, wr.World.Map.CenterOfCell(t), WVec.Zero, -511, palette, 1f, true, true);
yield return new SpriteRenderable(tile, wr.World.Map.CenterOfCell(t), WVec.Zero, -511, palette, 1f, true, TintModifiers.IgnoreWorldTint);
}

protected override string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)
Expand Down Expand Up @@ -288,7 +288,7 @@ protected override IEnumerable<IRenderable> RenderAboveShroud(WorldRenderer wr,
foreach (var t in power.CellsMatching(sourceLocation, footprint, dimensions))
{
var tile = manager.Self.Owner.Shroud.IsExplored(t + delta) ? validTile : invalidTile;
yield return new SpriteRenderable(tile, wr.World.Map.CenterOfCell(t + delta), WVec.Zero, -511, palette, 1f, true, true);
yield return new SpriteRenderable(tile, wr.World.Map.CenterOfCell(t + delta), WVec.Zero, -511, palette, 1f, true, TintModifiers.IgnoreWorldTint);
}

// Unit previews
Expand All @@ -300,7 +300,7 @@ protected override IEnumerable<IRenderable> RenderAboveShroud(WorldRenderer wr,
var canEnter = manager.Self.Owner.Shroud.IsExplored(targetCell) &&
unit.Trait<Chronoshiftable>().CanChronoshiftTo(unit, targetCell);
var tile = canEnter ? validTile : invalidTile;
yield return new SpriteRenderable(tile, wr.World.Map.CenterOfCell(targetCell), WVec.Zero, -511, palette, 1f, true, true);
yield return new SpriteRenderable(tile, wr.World.Map.CenterOfCell(targetCell), WVec.Zero, -511, palette, 1f, true, TintModifiers.IgnoreWorldTint);
}

var offset = world.Map.CenterOfCell(xy) - world.Map.CenterOfCell(sourceLocation);
Expand Down Expand Up @@ -330,7 +330,7 @@ protected override IEnumerable<IRenderable> Render(WorldRenderer wr, World world

// Source tiles
foreach (var t in power.CellsMatching(sourceLocation, footprint, dimensions))
yield return new SpriteRenderable(sourceTile, wr.World.Map.CenterOfCell(t), WVec.Zero, -511, palette, 1f, true, true);
yield return new SpriteRenderable(sourceTile, wr.World.Map.CenterOfCell(t), WVec.Zero, -511, palette, 1f, true, TintModifiers.IgnoreWorldTint);
}

bool IsValidTarget(CPos xy)
Expand Down
45 changes: 33 additions & 12 deletions OpenRA.Mods.Common/Graphics/ModelRenderable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

namespace OpenRA.Mods.Common.Graphics
{
public struct ModelRenderable : IPalettedRenderable, ITintableRenderable
public struct ModelRenderable : IPalettedRenderable, IModifyableRenderable
{
readonly IEnumerable<ModelAnimation> models;
readonly WPos pos;
Expand All @@ -30,22 +30,24 @@ public struct ModelRenderable : IPalettedRenderable, ITintableRenderable
readonly PaletteReference normalsPalette;
readonly PaletteReference shadowPalette;
readonly float scale;
readonly float alpha;
readonly float3 tint;
readonly TintModifiers tintModifiers;

public ModelRenderable(
IEnumerable<ModelAnimation> models, WPos pos, int zOffset, in WRot camera, float scale,
in WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
PaletteReference color, PaletteReference normals, PaletteReference shadow)
: this(models, pos, zOffset, camera, scale,
lightSource, lightAmbientColor, lightDiffuseColor,
color, normals, shadow,
float3.Ones) { }
color, normals, shadow, 1f,
float3.Ones, TintModifiers.None) { }

public ModelRenderable(
IEnumerable<ModelAnimation> models, WPos pos, int zOffset, in WRot camera, float scale,
in WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
PaletteReference color, PaletteReference normals, PaletteReference shadow,
in float3 tint)
float alpha, in float3 tint, TintModifiers tintModifiers)
{
this.models = models;
this.pos = pos;
Expand All @@ -58,46 +60,60 @@ public struct ModelRenderable : IPalettedRenderable, ITintableRenderable
palette = color;
normalsPalette = normals;
shadowPalette = shadow;
this.alpha = alpha;
this.tint = tint;
this.tintModifiers = tintModifiers;
}

public WPos Pos { get { return pos; } }
public PaletteReference Palette { get { return palette; } }
public int ZOffset { get { return zOffset; } }
public bool IsDecoration { get { return false; } }

public float Alpha { get { return alpha; } }
public float3 Tint { get { return tint; } }
public TintModifiers TintModifiers { get { return tintModifiers; } }

public IPalettedRenderable WithPalette(PaletteReference newPalette)
{
return new ModelRenderable(
models, pos, zOffset, camera, scale,
lightSource, lightAmbientColor, lightDiffuseColor,
newPalette, normalsPalette, shadowPalette, tint);
newPalette, normalsPalette, shadowPalette, alpha, tint, tintModifiers);
}

public IRenderable WithZOffset(int newOffset)
{
return new ModelRenderable(
models, pos, newOffset, camera, scale,
lightSource, lightAmbientColor, lightDiffuseColor,
palette, normalsPalette, shadowPalette, tint);
palette, normalsPalette, shadowPalette, alpha, tint, tintModifiers);
}

public IRenderable OffsetBy(WVec vec)
{
return new ModelRenderable(
models, pos + vec, zOffset, camera, scale,
lightSource, lightAmbientColor, lightDiffuseColor,
palette, normalsPalette, shadowPalette, tint);
palette, normalsPalette, shadowPalette, alpha, tint, tintModifiers);
}

public IRenderable AsDecoration() { return this; }

public IRenderable WithTint(in float3 newTint)
public IModifyableRenderable WithAlpha(float newAlpha)
{
return new ModelRenderable(
models, pos, zOffset, camera, scale,
lightSource, lightAmbientColor, lightDiffuseColor,
palette, normalsPalette, shadowPalette, newAlpha, tint, tintModifiers);
}

public IModifyableRenderable WithTint(in float3 newTint, TintModifiers newTintModifiers)
{
return new ModelRenderable(
models, pos, zOffset, camera, scale,
lightSource, lightAmbientColor, lightDiffuseColor,
palette, normalsPalette, shadowPalette, newTint);
palette, normalsPalette, shadowPalette, alpha, newTint, newTintModifiers);
}

// This will need generalizing once we support TS/RA2 terrain
Expand Down Expand Up @@ -145,11 +161,16 @@ public void Render(WorldRenderer wr)

var wrsr = Game.Renderer.WorldRgbaSpriteRenderer;
var t = model.tint;
if (wr.TerrainLighting != null)
if (wr.TerrainLighting != null && (model.tintModifiers & TintModifiers.IgnoreWorldTint) == 0)
t *= wr.TerrainLighting.TintAt(model.pos);

wrsr.DrawSprite(renderProxy.ShadowSprite, sa, sb, sc, sd, t, 1f);
wrsr.DrawSprite(renderProxy.Sprite, pxOrigin - 0.5f * renderProxy.Sprite.Size, renderProxy.Sprite.Size, t, 1f);
// Shader interprets negative alpha as a flag to use the tint colour directly instead of multiplying the sprite colour
var a = model.alpha;
if ((model.tintModifiers & TintModifiers.ReplaceColor) != 0)
a *= -1;

wrsr.DrawSprite(renderProxy.ShadowSprite, sa, sb, sc, sd, t, a);
wrsr.DrawSprite(renderProxy.Sprite, pxOrigin - 0.5f * renderProxy.Sprite.Size, renderProxy.Sprite.Size, t, a);
}

public void RenderDebugGeometry(WorldRenderer wr)
Expand Down
2 changes: 1 addition & 1 deletion OpenRA.Mods.Common/Traits/Buildings/Bridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ IRenderable[] TemplateRenderables(WorldRenderer wr, PaletteReference palette, us

return footprint.Select(c => (IRenderable)(new SpriteRenderable(
wr.Theater.TileSprite(new TerrainTile(template, c.Value)),
wr.World.Map.CenterOfCell(c.Key), WVec.Zero, -offset, palette, 1f, true, false))).ToArray();
wr.World.Map.CenterOfCell(c.Key), WVec.Zero, -offset, palette, 1f, true))).ToArray();
}

bool initialized;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public FootprintPlaceBuildingPreviewPreview(WorldRenderer wr, ActorInfo ai, Foot
var pal = HasFlag(c.Value, PlaceBuildingCellType.LineBuild) ? linePalette : cellPalette;
var pos = wr.World.Map.CenterOfCell(c.Key);
var offset = new WVec(0, 0, topLeftPos.Z - pos.Z);
yield return new SpriteRenderable(tile, pos, offset, -511, pal, 1f, true, true);
yield return new SpriteRenderable(tile, pos, offset, -511, pal, 1f, true, TintModifiers.IgnoreWorldTint);
}
}

Expand Down
2 changes: 1 addition & 1 deletion OpenRA.Mods.Common/Traits/Render/DrawLineToTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ IEnumerable<IRenderable> RenderAboveShroud(Actor self, WorldRenderer wr)
if (!a.IsCanceling)
foreach (var n in a.TargetLineNodes(self))
if (n.Tile != null && n.Target.Type != TargetType.Invalid)
yield return new SpriteRenderable(n.Tile, n.Target.CenterPosition, WVec.Zero, -511, pal, 1f, true, true);
yield return new SpriteRenderable(n.Tile, n.Target.CenterPosition, WVec.Zero, -511, pal, 1f, true, TintModifiers.IgnoreWorldTint);
}

bool IRenderAboveShroud.SpatiallyPartitionable { get { return false; } }
Expand Down
3 changes: 2 additions & 1 deletion OpenRA.Mods.Common/Traits/Render/WithParachute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ IEnumerable<IRenderable> IRender.Render(Actor self, WorldRenderer wr)
var dat = self.World.Map.DistanceAboveTerrain(self.CenterPosition);
var pos = self.CenterPosition - new WVec(0, 0, dat.Length);
var palette = wr.Palette(info.ShadowPalette);
return new IRenderable[] { new SpriteRenderable(shadow.Image, pos, info.ShadowOffset, info.ShadowZOffset, palette, 1, true, shadow.CurrentSequence.IgnoreWorldTint) };
var tintModifiers = shadow.CurrentSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None;
return new IRenderable[] { new SpriteRenderable(shadow.Image, pos, info.ShadowOffset, info.ShadowZOffset, palette, 1, true, tintModifiers) };
}

IEnumerable<Rectangle> IRender.ScreenBounds(Actor self, WorldRenderer wr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ protected override IEnumerable<IRenderable> Render(WorldRenderer wr, World world
var pal = wr.Palette(TileSet.TerrainPaletteInternalName);

foreach (var t in power.CellsMatching(xy, footprint, dimensions))
yield return new SpriteRenderable(tile, wr.World.Map.CenterOfCell(t), WVec.Zero, -511, pal, 1f, true, true);
yield return new SpriteRenderable(tile, wr.World.Map.CenterOfCell(t), WVec.Zero, -511, pal, 1f, true, TintModifiers.IgnoreWorldTint);
}

protected override string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)
Expand Down
5 changes: 3 additions & 2 deletions OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ void ITickRender.TickRender(WorldRenderer wr, Actor self)
var offset = world.Map.Offset(new CVec(x, y), tileInfo.Height);
var palette = wr.Palette(TerrainTemplate.Palette ?? TileSet.TerrainPaletteInternalName);

terrainOrResourcePreview.Add(new SpriteRenderable(sprite, pos, offset, 0, palette, 1, false, false));
terrainOrResourcePreview.Add(new SpriteRenderable(sprite, pos, offset, 0, palette, 1, false));
}
}
}
Expand All @@ -96,7 +96,8 @@ void ITickRender.TickRender(WorldRenderer wr, Actor self)
var sprite = sequence.GetSprite(Resource.MaxDensity - 1);
var palette = wr.Palette(Resource.Palette);

terrainOrResourcePreview.Add(new SpriteRenderable(sprite, pos, WVec.Zero, 0, palette, 1, false, sequence.IgnoreWorldTint));
var tintModifiers = sequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None;
terrainOrResourcePreview.Add(new SpriteRenderable(sprite, pos, WVec.Zero, 0, palette, 1, false, tintModifiers));
}
}
}
Expand Down
Loading

0 comments on commit 3484add

Please sign in to comment.