diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 3e2bcd7b43cb..8abd0e344eab 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -216,6 +216,25 @@ public void TestOnlyEightComboColoursEncoded() Assert.That(decodedAfterEncode.skin.Configuration.CustomComboColours, Has.Count.EqualTo(8)); } + [Test] + public void TestBackgroundOffsetsRoundTrip() + { + var beatmap = new Beatmap(); + var meta = beatmap.BeatmapInfo.Metadata; + meta.BackgroundFile = "test bg.jpg"; + meta.BackgroundOffsetX = 10.25f; + meta.BackgroundOffsetY = -42f; + + var encoded = EncodeToLegacy((beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty))); + encoded.Position = 0; + + var decoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(new LineBufferedReader(encoded)); + + Assert.That(decoded.BeatmapInfo.Metadata.BackgroundFile, Is.EqualTo(meta.BackgroundFile)); + Assert.That(decoded.BeatmapInfo.Metadata.BackgroundOffsetX, Is.EqualTo(meta.BackgroundOffsetX)); + Assert.That(decoded.BeatmapInfo.Metadata.BackgroundOffsetY, Is.EqualTo(meta.BackgroundOffsetY)); + } + [Test] public void TestEncodeStabilityOfSliderWithFractionalCoordinates() { diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index f80c4de4ea6e..ea616e2d4999 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -433,6 +433,8 @@ private List createBeatmapDifficulties(BeatmapSetInfo beatmapSet, R PreviewTime = decoded.Metadata.PreviewTime, AudioFile = decoded.Metadata.AudioFile, BackgroundFile = decoded.Metadata.BackgroundFile, + BackgroundOffsetX = decoded.Metadata.BackgroundOffsetX, + BackgroundOffsetY = decoded.Metadata.BackgroundOffsetY, }; var beatmap = new BeatmapInfo(ruleset, difficulty, metadata) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index d8bf2c752bc7..d37664661566 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -61,6 +61,16 @@ public class BeatmapMetadata : RealmObject, IBeatmapMetadataInfo, IDeepCloneable public string AudioFile { get; set; } = string.Empty; public string BackgroundFile { get; set; } = string.Empty; + /// + /// Horizontal offset in pixels of the legacy Background event (fourth column in [Events]). + /// + public float BackgroundOffsetX { get; set; } + + /// + /// Vertical offset in pixels of the legacy Background event (fifth column in [Events]). + /// + public float BackgroundOffsetY { get; set; } + public BeatmapMetadata(RealmUser? user = null) { Author = user ?? new RealmUser(); @@ -85,7 +95,9 @@ private BeatmapMetadata() Tags = Tags, PreviewTime = PreviewTime, AudioFile = AudioFile, - BackgroundFile = BackgroundFile + BackgroundFile = BackgroundFile, + BackgroundOffsetX = BackgroundOffsetX, + BackgroundOffsetY = BackgroundOffsetY }; } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index caf8dc048a17..48b027206c52 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -465,9 +465,16 @@ private void handleEvent(string line) break; case LegacyEventType.Background: - beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]); + { + var metadata = beatmap.BeatmapInfo.Metadata; + metadata.BackgroundFile = CleanFilename(split[2]); + if (split.Length > 3) + metadata.BackgroundOffsetX = Parsing.ParseFloat(split[3], Parsing.MAX_COORDINATE_VALUE); + if (split.Length > 4) + metadata.BackgroundOffsetY = Parsing.ParseFloat(split[4], Parsing.MAX_COORDINATE_VALUE); lineSupportedByEncoder = true; break; + } case LegacyEventType.Break: double start = getOffsetTime(Parsing.ParseDouble(split[1])); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index c5749b9ce806..67ecb792cbe9 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -152,7 +152,10 @@ private void handleEvents(TextWriter writer) writer.WriteLine("[Events]"); if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile)) - writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Background},0,\"{beatmap.BeatmapInfo.Metadata.BackgroundFile}\",0,0")); + { + var metadata = beatmap.BeatmapInfo.Metadata; + writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Background},0,\"{metadata.BackgroundFile}\",{metadata.BackgroundOffsetX},{metadata.BackgroundOffsetY}")); + } foreach (var b in beatmap.Breaks) writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}")); diff --git a/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs b/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs index 9c96aba85b93..797835b3750c 100644 --- a/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs @@ -62,6 +62,16 @@ public interface IBeatmapMetadataInfo : IEquatable /// string BackgroundFile { get; } + /// + /// Horizontal offset in pixels of the legacy Background event (fourth column in [Events]). + /// + float BackgroundOffsetX { get; } + + /// + /// Vertical offset in pixels of the legacy Background event (fifth column in [Events]). + /// + float BackgroundOffsetY { get; } + bool IEquatable.Equals(IBeatmapMetadataInfo? other) { if (other == null) @@ -76,7 +86,9 @@ bool IEquatable.Equals(IBeatmapMetadataInfo? other) && Tags == other.Tags && PreviewTime == other.PreviewTime && AudioFile == other.AudioFile - && BackgroundFile == other.BackgroundFile; + && BackgroundFile == other.BackgroundFile + && BackgroundOffsetX.Equals(other.BackgroundOffsetX) + && BackgroundOffsetY.Equals(other.BackgroundOffsetY); } } } diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 379366b7e024..72ec3c346a9b 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -101,8 +101,9 @@ public class RealmAccess : IDisposable /// 49 2025-06-10 Reset the LegacyOnlineID to -1 for all scores that have it set to 0 (which is semantically the same) for consistency of handling with OnlineID. /// 50 2025-07-11 Add UserTags to BeatmapMetadata. /// 51 2025-07-22 Add ScoreInfo.Pauses. + /// 52 2026-04-21 Add Background offset fields to BeatmapMetadata. /// - private const int schema_version = 51; + private const int schema_version = 52; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods.