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

Sprite origin fixes (and more) #884

Merged
merged 12 commits into from
May 4, 2022
137 changes: 103 additions & 34 deletions UndertaleModLib/Models/UndertaleRoom.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,20 +166,26 @@ public enum RoomEntryFlags : uint
/// </summary>
public UndertaleSimpleList<UndertaleResourceById<UndertaleSequence, UndertaleChunkSEQN>> Sequences { get; private set; } = new UndertaleSimpleList<UndertaleResourceById<UndertaleSequence, UndertaleChunkSEQN>>();

private Layer GetBGColorLayer()
{
return _layers?.Where(l => l.LayerType is LayerType.Background
&& l.BackgroundData.Sprite is null
&& l.BackgroundData.Color != 0)
.OrderBy(l => l?.LayerDepth ?? 0)
.FirstOrDefault();
}
public void UpdateBGColorLayer() => OnPropertyChanged("BGColorLayer");

/// <summary>
/// The layer containing the background color.
/// The layer containing the background color.<br/>
/// </summary>
public Layer BGColorLayer => GetBGColorLayer();
/// <remarks>
/// Used by "BGColorConverter" of the UndertaleModTool room editor.
/// This attribute is UMT-only and does not exist in GameMaker.
/// </remarks>
public Layer BGColorLayer
{
get
{
return _layers?.Where(l => l.LayerType is LayerType.Background
&& l.BackgroundData.Sprite is null
&& l.BackgroundData.Color != 0)
.OrderBy(l => l?.LayerDepth ?? 0)
.FirstOrDefault();
}
}

public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
Expand Down Expand Up @@ -423,10 +429,27 @@ public class Background : UndertaleObject, INotifyPropertyChanged
/// <summary>
/// The room parent this background belongs to.
/// </summary>
/// <remarks>
/// This attribute is UMT-only and does not exist in GameMaker.
/// </remarks>
public UndertaleRoom ParentRoom { get => _ParentRoom; set { _ParentRoom = value; OnPropertyChanged(); UpdateStretch(); } }

//TODO:
/// <summary>
/// The calculated horizontal render scale for the background texture.
/// </summary>
/// <remarks>
/// Used in the room editor.<br/>
/// This attribute is UMT-only and does not exist in GameMaker.
/// </remarks>
public float CalcScaleX { get; set; } = 1;

/// <summary>
/// The calculated vertical render scale for the background texture.
/// </summary>
/// <remarks>
/// Used in the room editor.<br/>
/// This attribute is UMT-only and does not exist in GameMaker.
/// </remarks>
public float CalcScaleY { get; set; } = 1;

/// <summary>
Expand Down Expand Up @@ -477,25 +500,31 @@ public class Background : UndertaleObject, INotifyPropertyChanged
public bool Stretch { get => _Stretch; set { _Stretch = value; OnPropertyChanged(); UpdateStretch(); } }

/// <summary>
/// Whether this background is tiled horizontally.
/// Indicates whether this background is tiled horizontally.
/// </summary>
/// <remarks>
/// This attribute is UMT-only and does not exist in GameMaker.
/// </remarks>
public bool TiledHorizontally { get => TileX > 0; set { TileX = value ? 1 : 0; OnPropertyChanged(); } }

/// <summary>
/// Whether this background is tiled vertically.
/// Indicates whether this background is tiled vertically.
/// </summary>
/// <remarks>
/// This attribute is UMT-only and does not exist in GameMaker.
/// </remarks>
public bool TiledVertically { get => TileY > 0; set { TileY = value ? 1 : 0; OnPropertyChanged(); } }

/// <summary>
/// A horizontal offset used for proper background display in the UndertaleModTool room editor.
/// A horizontal offset used for proper background display in the room editor.
/// </summary>
/// <remarks>
/// This attribute is UMT-only and does not exist in GameMaker.
/// </remarks>
public int XOffset => X + (BackgroundDefinition?.Texture?.TargetX ?? 0);

/// <summary>
/// A vertical offset used for proper background display in the UndertaleModTool room editor.
/// A vertical offset used for proper background display in the room editor.
/// </summary>
/// <remarks>
/// This attribute is UMT-only and does not exist in GameMaker.
Expand Down Expand Up @@ -758,27 +787,47 @@ protected void OnPropertyChanged([CallerMemberName] string name = null)
/// <summary>
/// The opposite angle of the current rotation.
/// </summary>
/// <remarks>
/// This attribute is UMT-only and does not exist in GameMaker.
/// </remarks>
public float OppositeRotation => 360F - (Rotation % 360);

/// <summary>
/// A horizontal offset used for proper game object display in the UndertaleModTool room editor.
/// A horizontal offset relative to top-left corner of the game object.
/// </summary>
/// <remarks>
/// Used for proper game object rotation display in the room editor and in <see cref="XOffset"/>.<br/>
/// This attribute is UMT-only and does not exist in GameMaker.
/// </remarks>
public int XOffset => ObjectDefinition?.Sprite != null
? X - ObjectDefinition.Sprite.OriginX + (ObjectDefinition.Sprite.Textures.ElementAtOrDefault(ImageIndex)?.Texture?.TargetX ?? 0)
: X;
public int SpriteXOffset => ObjectDefinition?.Sprite != null
? (-1 * ObjectDefinition.Sprite.OriginXWrapper) + (ObjectDefinition.Sprite.Textures.ElementAtOrDefault(ImageIndex)?.Texture?.TargetX ?? 0)
: 0;

/// <summary>
/// A vertical offset used for proper game object display in the UndertaleModTool room editor.
/// A vertical offset relative to top-left corner of the game object.
/// </summary>
/// <remarks>
/// Used for proper game object rotation display in the room editor and in <see cref="YOffset"/>.<br/>
/// This attribute is UMT-only and does not exist in GameMaker.
/// </remarks>
public int YOffset => ObjectDefinition?.Sprite != null
? Y - ObjectDefinition.Sprite.OriginY + (ObjectDefinition.Sprite.Textures.ElementAtOrDefault(ImageIndex)?.Texture?.TargetY ?? 0)
: Y;
public int SpriteYOffset => ObjectDefinition?.Sprite != null
? (-1 * ObjectDefinition.Sprite.OriginYWrapper) + (ObjectDefinition.Sprite.Textures.ElementAtOrDefault(ImageIndex)?.Texture?.TargetY ?? 0)
: 0;
/// <summary>
/// A horizontal offset used for proper game object position display in the room editor.
/// </summary>
/// <remarks>
/// This attribute is UMT-only and does not exist in GameMaker.
/// </remarks>
public int XOffset => X + SpriteXOffset;

/// <summary>
/// A vertical offset used for proper game object display in the room editor.
/// </summary>
/// <remarks>
/// This attribute is UMT-only and does not exist in GameMaker.
/// </remarks>
public int YOffset => Y + SpriteYOffset;

public void Serialize(UndertaleWriter writer)
{
Expand Down Expand Up @@ -838,9 +887,14 @@ public override string ToString()
public class Tile : UndertaleObject, RoomObject, INotifyPropertyChanged
{
/// <summary>
/// Whether this tile is from an asset layer. Game Maker Studio: 2 exclusive.
/// Whether this tile is from an asset layer.<br/>
/// Equals true if it's Game Maker Studio: 2.
/// </summary>
/// <remarks>
/// This attribute is UMT-only and does not exist in GameMaker.
/// </remarks>
public bool spriteMode = false;

private UndertaleResourceById<UndertaleBackground, UndertaleChunkBGND> _backgroundDefinition = new();
private UndertaleResourceById<UndertaleSprite, UndertaleChunkSPRT> _spriteDefinition = new();

Expand All @@ -865,8 +919,8 @@ public class Tile : UndertaleObject, RoomObject, INotifyPropertyChanged
public UndertaleSprite SpriteDefinition { get => _spriteDefinition.Resource; set { _spriteDefinition.Resource = value; OnPropertyChanged(); OnPropertyChanged("ObjectDefinition"); } }

/// <summary>
/// From which object this tile stems from.
/// Will return a <see cref="UndertaleBackground"/> if <see cref="spriteMode"/> is disabled, a <see cref="UndertaleSprite"/> if it's enabled.
/// From which object this tile stems from.<br/>
/// Will return a <see cref="UndertaleBackground"/> if <see cref="spriteMode"/> is <see langword="true"/>, a <see cref="UndertaleSprite"/> if it's <see langword="false"/>.
/// </summary>
public UndertaleNamedResource ObjectDefinition { get => spriteMode ? SpriteDefinition : BackgroundDefinition; set { if (spriteMode) SpriteDefinition = (UndertaleSprite)value; else BackgroundDefinition = (UndertaleBackground)value; } }

Expand Down Expand Up @@ -913,6 +967,12 @@ public class Tile : UndertaleObject, RoomObject, INotifyPropertyChanged
//TODO?
public uint Color { get; set; } = 0xFFFFFFFF;

/// <summary>
/// Returns the texture page item of the tile.
/// </summary>
/// <remarks>
/// This attribute is UMT-only and does not exist in GameMaker.
/// </remarks>
public UndertaleTexturePageItem Tpag => spriteMode ? SpriteDefinition?.Textures.FirstOrDefault()?.Texture : BackgroundDefinition?.Texture; // TODO: what happens on sprites with multiple textures?

public event PropertyChangedEventHandler PropertyChanged;
Expand Down Expand Up @@ -1238,8 +1298,14 @@ public class LayerBackgroundData : LayerData, INotifyPropertyChanged
public float AnimationSpeed { get; set; }
public AnimationSpeedType AnimationSpeedType { get; set; }

public float XOffset => (ParentLayer?.XOffset ?? 0) + (Sprite?.Textures.FirstOrDefault()?.Texture?.TargetX ?? 0);
public float YOffset => (ParentLayer?.YOffset ?? 0) + (Sprite?.Textures.FirstOrDefault()?.Texture?.TargetY ?? 0);
public float XOffset => (ParentLayer?.XOffset ?? 0) +
(Sprite is not null
? (Sprite.Textures.FirstOrDefault()?.Texture?.TargetX ?? 0) - Sprite.OriginXWrapper
: 0);
public float YOffset => (ParentLayer?.YOffset ?? 0) +
(Sprite is not null
? (Sprite.Textures.FirstOrDefault()?.Texture?.TargetY ?? 0) - Sprite.OriginYWrapper
: 0);

public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
Expand Down Expand Up @@ -1415,12 +1481,15 @@ public int WrappedFrameIndex
}
public float Rotation { get; set; }
public float OppositeRotation => 360F - Rotation;
public int XOffset => Sprite is not null
? X - Sprite.OriginX + (Sprite.Textures.ElementAtOrDefault((int)FrameIndex)?.Texture?.TargetX ?? 0)
: X;
public int YOffset => Sprite is not null
? Y - Sprite.OriginY + (Sprite.Textures.ElementAtOrDefault((int)FrameIndex)?.Texture?.TargetY ?? 0)
: Y;

public int SpriteXOffset => Sprite != null
? (-1 * Sprite.OriginXWrapper) + (Sprite.Textures.ElementAtOrDefault(WrappedFrameIndex)?.Texture?.TargetX ?? 0)
: 0;
public int SpriteYOffset => Sprite != null
? (-1 * Sprite.OriginYWrapper) + (Sprite.Textures.ElementAtOrDefault(WrappedFrameIndex)?.Texture?.TargetY ?? 0)
: 0;
public int XOffset => X + SpriteXOffset;
public int YOffset => Y + SpriteYOffset;

public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
Expand Down
38 changes: 37 additions & 1 deletion UndertaleModLib/Models/UndertaleSprite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,42 @@ public class UndertaleSprite : UndertaleNamedResource, PrePaddedObject, INotifyP
/// </summary>
public int OriginY { get; set; }

/// <summary>
/// A <see cref="OriginX"/> wrapper than also sets horizontal origin of <see cref="V2Sequence"/>.
/// </summary>
/// <remarks>
/// This attribute is used only in UndertaleModTool and doesn't exist in GameMaker.
/// </remarks>
public int OriginXWrapper
{
get => OriginX;
set
{
OriginX = value;

if (IsSpecialType && SVersion > 1 && V2Sequence is not null)
V2Sequence.OriginX = value;
}
}

/// <summary>
/// A <see cref="OriginY"/> wrapper than also sets vertical origin of <see cref="V2Sequence"/>.
/// </summary>
/// <remarks>
/// This attribute is used only in UndertaleModTool and doesn't exist in GameMaker.
/// </remarks>
public int OriginYWrapper
{
get => OriginY;
set
{
OriginY = value;

if (IsSpecialType && SVersion > 1 && V2Sequence is not null)
V2Sequence.OriginY = value;
}
}

/// <summary>
/// The frames of the sprite.
/// </summary>
Expand Down Expand Up @@ -441,7 +477,7 @@ public void Unserialize(UndertaleReader reader)
break;
case SpriteType.SWF:
{
//// ATTENTION: This code does not work all the time for some reason. ////
//// TODO: This code does not work all the time for some reason. ////

SWFVersion = reader.ReadInt32();
Util.DebugUtil.Assert(SWFVersion == 8 || SWFVersion == 7, "Invalid SWF sprite format, expected 7 or 8, got " + SWFVersion);
Expand Down
17 changes: 13 additions & 4 deletions UndertaleModTool/Controls/UndertaleRoomRenderer.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<local:TileLayerTemplateSelector x:Key="TileLayerTemplateSelector"/>
<local:TileRectanglesConverter x:Key="TileRectanglesConverter"/>
<local:CachedImageLoaderWithIndex x:Key="CachedImageLoaderWithIndex"/>
<local:NegateNumberConverter x:Key="NegateNumberConverter"/>
<CompositeCollection x:Key="AllObjectsGMS1">
<CollectionContainer Collection="{Binding Source={x:Reference RoomRenderer}, Path=DataContext.Backgrounds}"/>
<CollectionContainer Collection="{Binding Source={x:Reference RoomRenderer}, Path=DataContext.Tiles}"/>
Expand Down Expand Up @@ -140,8 +141,12 @@
Opacity="{Binding Color, Mode=OneTime, Converter={StaticResource ColorToOpacityConverter}}">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="transform1_0" ScaleX="{Binding ScaleX, Mode=OneTime}" ScaleY="{Binding ScaleY, Mode=OneTime}"/>
<RotateTransform x:Name="transform1_1" CenterX="{Binding X, Mode=OneTime}" CenterY="{Binding Y, Mode=OneTime}" Angle="{Binding OppositeRotation, Mode=OneTime}"/>
<ScaleTransform x:Name="transform1_0" ScaleX="{Binding ScaleX, Mode=OneTime}" ScaleY="{Binding ScaleY, Mode=OneTime}"
CenterX="{Binding SpriteXOffset, Converter={StaticResource NegateNumberConverter}, Mode=OneTime}"
CenterY="{Binding SpriteYOffset, Converter={StaticResource NegateNumberConverter}, Mode=OneTime}"/>
<RotateTransform x:Name="transform1_1" Angle="{Binding OppositeRotation, Mode=OneTime}"
CenterX="{Binding SpriteXOffset, Converter={StaticResource NegateNumberConverter}, Mode=OneTime}"
CenterY="{Binding SpriteYOffset, Converter={StaticResource NegateNumberConverter}, Mode=OneTime}"/>
<TranslateTransform x:Name="transform1_2" X="{Binding XOffset, Mode=OneTime}" Y="{Binding YOffset, Mode=OneTime}"/>
</TransformGroup>
</Rectangle.RenderTransform>
Expand Down Expand Up @@ -179,8 +184,12 @@
Opacity="{Binding Color, Mode=OneTime, Converter={StaticResource ColorToOpacityConverter}}">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="transform4_0" ScaleX="{Binding ScaleX, Mode=OneTime}" ScaleY="{Binding ScaleY, Mode=OneTime}"/>
<RotateTransform x:Name="transform4_1" CenterX="{Binding X, Mode=OneTime}" CenterY="{Binding Y, Mode=OneTime}" Angle="{Binding OppositeRotation, Mode=OneTime}"/>
<ScaleTransform x:Name="transform4_0" ScaleX="{Binding ScaleX, Mode=OneTime}" ScaleY="{Binding ScaleY, Mode=OneTime}"
CenterX="{Binding SpriteXOffset, Converter={StaticResource NegateNumberConverter}, Mode=OneTime}"
CenterY="{Binding SpriteYOffset, Converter={StaticResource NegateNumberConverter}, Mode=OneTime}"/>
<RotateTransform x:Name="transform4_1" Angle="{Binding OppositeRotation, Mode=OneTime}"
CenterX="{Binding SpriteXOffset, Converter={StaticResource NegateNumberConverter}, Mode=OneTime}"
CenterY="{Binding SpriteYOffset, Converter={StaticResource NegateNumberConverter}, Mode=OneTime}"/>
<TranslateTransform x:Name="transform4_2" X="{Binding XOffset, Mode=OneTime}" Y="{Binding YOffset, Mode=OneTime}"/>
</TransformGroup>
</Rectangle.RenderTransform>
Expand Down
Loading