diff --git a/UndertaleModLib/Models/UndertaleRoom.cs b/UndertaleModLib/Models/UndertaleRoom.cs index 44cc68d4f..5ebeb32bf 100644 --- a/UndertaleModLib/Models/UndertaleRoom.cs +++ b/UndertaleModLib/Models/UndertaleRoom.cs @@ -2069,7 +2069,7 @@ public void Unserialize(UndertaleReader reader) if (reader.undertaleData.IsNonLTSVersionAtLeast(2023, 2)) ParticleSystems = reader.ReadUndertaleObjectPointer>(); if (firstPointerTarget > reader.AbsPosition && !reader.undertaleData.IsVersionAtLeast(2024, 6)) - reader.undertaleData.SetGMS2Version(2024, 6); // there's more data before legacy tiles, so must be 2024.6+ + reader.undertaleData.SetGMS2Version(2024, 6); // There's more data before legacy tiles, so must be 2024.6+ if (reader.undertaleData.IsVersionAtLeast(2024, 6)) TextItems = reader.ReadUndertaleObjectPointer>(); } @@ -2106,7 +2106,7 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) if (reader.undertaleData.IsNonLTSVersionAtLeast(2023, 2)) partSystemsPtr = reader.ReadUInt32(); if (legacyTilesPtr > reader.AbsPosition && !reader.undertaleData.IsVersionAtLeast(2024, 6)) - reader.undertaleData.SetGMS2Version(2024, 6); // there's more data before legacy tiles, so must be 2024.6+ + reader.undertaleData.SetGMS2Version(2024, 6); // There's more data before legacy tiles, so must be 2024.6+ if (reader.undertaleData.IsVersionAtLeast(2024, 6)) textItemsPtr = reader.ReadUInt32(); } @@ -2388,6 +2388,9 @@ public void Unserialize(UndertaleReader reader) Rotation = reader.ReadSingle(); } + /// + /// Generates a random name for this instance, as a utility for room editing. + /// //TODO: rework this method slightly. public static UndertaleString GenerateRandomName(UndertaleData data) { @@ -2550,6 +2553,9 @@ public void Unserialize(UndertaleReader reader) Rotation = reader.ReadSingle(); } + /// + /// Generates a random name for this instance, as a utility for room editing. + /// public static UndertaleString GenerateRandomName(UndertaleData data) { return data.Strings.MakeString("particle_" + ((uint)Random.Shared.Next(-Int32.MaxValue, Int32.MaxValue)).ToString("X8")); @@ -2587,6 +2593,7 @@ protected void OnPropertyChanged([CallerMemberName] string name = null) private UndertaleResourceById _font = new(); + // TODO: document these fields; some are self-explanatory but unsure on the behavior of all of them public UndertaleString Name { get; set; } public int X { get; set; } public int Y { get; set; } @@ -2657,6 +2664,9 @@ public void Unserialize(UndertaleReader reader) Wrap = reader.ReadBoolean(); } + /// + /// Generates a random name for this instance, as a utility for room editing. + /// public static UndertaleString GenerateRandomName(UndertaleData data) { return data.Strings.MakeString("textitem_" + ((uint)Random.Shared.Next(-Int32.MaxValue, Int32.MaxValue)).ToString("X8")); @@ -2665,7 +2675,7 @@ public static UndertaleString GenerateRandomName(UndertaleData data) /// public override string ToString() { - return "Text item " + Name?.Content + " with text \"" + (Text?.Content ?? "?") + "\""; + return $"Text item {Name?.Content} with text \"{Text?.Content ?? "?"}\""; } /// diff --git a/UndertaleModLib/Models/UndertaleSprite.cs b/UndertaleModLib/Models/UndertaleSprite.cs index 8d298c5c1..a43b76416 100644 --- a/UndertaleModLib/Models/UndertaleSprite.cs +++ b/UndertaleModLib/Models/UndertaleSprite.cs @@ -806,6 +806,9 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) return count; } + /// + /// Returns the width and height of the collision mask for this sprite, which changes depending on GameMaker version. + /// public (uint Width, uint Height) CalculateMaskDimensions(UndertaleData data) { if (data.IsVersionAtLeast(2024, 6)) @@ -815,11 +818,22 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) return CalculateFullMaskDimensions(Width, Height); } + /// + /// Calculates the width and height of a collision mask from the given margin/bounding box. + /// This method is used to calculate collision mask dimensions in GameMaker 2024.6 and above. + /// public static (uint Width, uint Height) CalculateBboxMaskDimensions(int marginRight, int marginLeft, int marginBottom, int marginTop) { return ((uint)(marginRight - marginLeft + 1), (uint)(marginBottom - marginTop + 1)); } + /// + /// Calculates the width and height of a collision mask from a given sprite's full width and height. + /// This method is used to calculate collision mask dimensions prior to GameMaker 2024.6. + /// + /// + /// This simply returns the width and height supplied, but is intended for clarity in the code. + /// public static (uint Width, uint Height) CalculateFullMaskDimensions(uint width, uint height) { return (width, height); diff --git a/UndertaleModLib/UndertaleChunks.cs b/UndertaleModLib/UndertaleChunks.cs index cf54ca60a..4bbfc116c 100644 --- a/UndertaleModLib/UndertaleChunks.cs +++ b/UndertaleModLib/UndertaleChunks.cs @@ -453,7 +453,7 @@ private void CheckForGM2024_6(UndertaleReader reader) uint nextSpritePtr = 0; if ((i + 1) < spriteCount) nextSpritePtr = reader.ReadUInt32(); - reader.AbsPosition = spritePtr + 4 /* skip name */; + reader.AbsPosition = spritePtr + 4; // Skip past "Name" // Check if bbox size differs from width/height uint width = reader.ReadUInt32(); @@ -472,106 +472,101 @@ private void CheckForGM2024_6(UndertaleReader reader) reader.Position += 28; - if (reader.ReadInt32() == -1) + if (reader.ReadInt32() != -1) { - uint sVersion = reader.ReadUInt32(); - UndertaleSprite.SpriteType sSpriteType = (UndertaleSprite.SpriteType)reader.ReadUInt32(); - - if (sSpriteType != UndertaleSprite.SpriteType.Normal) - { - // We can't determine anything from this sprite - continue; - } + throw new IOException("Expected special sprite type"); + } - reader.Position += 8; // playback speed values + uint sVersion = reader.ReadUInt32(); + UndertaleSprite.SpriteType sSpriteType = (UndertaleSprite.SpriteType)reader.ReadUInt32(); - if (sVersion != 3) - { - throw new IOException("Expected sprite version 3"); - } - uint sequenceOffset = reader.ReadUInt32(); - uint nineSliceOffset = reader.ReadUInt32(); + if (sSpriteType != UndertaleSprite.SpriteType.Normal) + { + // We can't determine anything from this sprite + continue; + } - // Skip past texture pointers - uint textureCount = reader.ReadUInt32(); - reader.Position += textureCount * 4; + reader.Position += 8; // Playback speed values - // Calculate how much space the "full" and "bbox" mask data take up - uint maskCount = reader.ReadUInt32(); - if (maskCount == 0) - { - // We can't determine anything from this sprite - continue; - } - uint fullLength = (normalWidth + 7) / 8 * normalHeight; - fullLength *= maskCount; - if ((fullLength % 4) != 0) - fullLength += (4 - (fullLength % 4)); - uint bboxLength = (bboxWidth + 7) / 8 * bboxHeight; - bboxLength *= maskCount; - if ((bboxLength % 4) != 0) - bboxLength += (4 - (bboxLength % 4)); - - // Calculate expected end offset - long expectedEndOffset; - bool endOffsetLenient = false; - if (sequenceOffset != 0) - { - expectedEndOffset = sequenceOffset; - } - else if (nineSliceOffset != 0) - { - expectedEndOffset = nineSliceOffset; - } - else if (nextSpritePtr != 0) - { - expectedEndOffset = nextSpritePtr; - } - else - { - // Use chunk length, and be lenient with it (due to chunk padding) - endOffsetLenient = true; - expectedEndOffset = chunkStartPos + Length; - } + if (sVersion != 3) + { + throw new IOException("Expected sprite version 3"); + } + uint sequenceOffset = reader.ReadUInt32(); + uint nineSliceOffset = reader.ReadUInt32(); - // If the "full" mask data runs past the expected end offset, and the "bbox" mask data does not, then this is 2024.6. - // Otherwise, stop processing and assume this is not 2024.6. - long fullEndPos = (reader.AbsPosition + fullLength); - if (fullEndPos != expectedEndOffset) - { - if (endOffsetLenient && (fullEndPos % 16) != 0 && fullEndPos + (16 - (fullEndPos % 16)) == expectedEndOffset) - { - // "Full" mask data doesn't exactly line up, but works if rounded up to the next chunk padding - break; - } + // Skip past texture pointers + uint textureCount = reader.ReadUInt32(); + reader.Position += textureCount * 4; - long bboxEndPos = (reader.AbsPosition + bboxLength); - if (bboxEndPos == expectedEndOffset) - { - // "Bbox" mask data is valid - reader.undertaleData.SetGMS2Version(2024, 6); - } - else if (endOffsetLenient && (bboxEndPos % 16) != 0 && bboxEndPos + (16 - (bboxEndPos % 16)) == expectedEndOffset) - { - // "Bbox" mask data doesn't exactly line up, but works if rounded up to the next chunk padding - reader.undertaleData.SetGMS2Version(2024, 6); - } - else - { - // Neither option seems to have worked... - throw new IOException("Failed to detect mask type in 2024.6 detection"); - } - } - else - { - // "Full" mask data is valid - break; - } + // Calculate how much space the "full" and "bbox" mask data take up + uint maskCount = reader.ReadUInt32(); + if (maskCount == 0) + { + // We can't determine anything from this sprite + continue; + } + uint fullLength = (normalWidth + 7) / 8 * normalHeight; + fullLength *= maskCount; + if ((fullLength % 4) != 0) + fullLength += (4 - (fullLength % 4)); + uint bboxLength = (bboxWidth + 7) / 8 * bboxHeight; + bboxLength *= maskCount; + if ((bboxLength % 4) != 0) + bboxLength += (4 - (bboxLength % 4)); + + // Calculate expected end offset + long expectedEndOffset; + bool endOffsetLenient = false; + if (sequenceOffset != 0) + { + expectedEndOffset = sequenceOffset; + } + else if (nineSliceOffset != 0) + { + expectedEndOffset = nineSliceOffset; + } + else if (nextSpritePtr != 0) + { + expectedEndOffset = nextSpritePtr; } else { - throw new IOException("Expected special sprite type"); + // Use chunk length, and be lenient with it (due to chunk padding) + endOffsetLenient = true; + expectedEndOffset = chunkStartPos + Length; } + + // If the "full" mask data runs past the expected end offset, and the "bbox" mask data does not, then this is 2024.6. + // Otherwise, stop processing and assume this is not 2024.6. + long fullEndPos = (reader.AbsPosition + fullLength); + if (fullEndPos == expectedEndOffset) + { + // "Full" mask data is valid + break; + } + if (endOffsetLenient && (fullEndPos % 16) != 0 && fullEndPos + (16 - (fullEndPos % 16)) == expectedEndOffset) + { + // "Full" mask data doesn't exactly line up, but works if rounded up to the next chunk padding + break; + } + + long bboxEndPos = (reader.AbsPosition + bboxLength); + if (bboxEndPos == expectedEndOffset) + { + // "Bbox" mask data is valid + reader.undertaleData.SetGMS2Version(2024, 6); + break; + } + if (endOffsetLenient && (bboxEndPos % 16) != 0 && bboxEndPos + (16 - (bboxEndPos % 16)) == expectedEndOffset) + { + // "Bbox" mask data doesn't exactly line up, but works if rounded up to the next chunk padding + reader.undertaleData.SetGMS2Version(2024, 6); + break; + } + + // Neither option seems to have worked... + throw new IOException("Failed to detect mask type in 2024.6 detection"); } reader.Position = returnTo;