Skip to content

Commit

Permalink
Merge pull request ppy#30860 from frenzibyte/editor-multiple-backgrou…
Browse files Browse the repository at this point in the history
…nd-audio-files

Allow choosing different background/audio files for individual difficulties
  • Loading branch information
bdach authored Dec 11, 2024
2 parents 842829d + bbaa542 commit 89e3c55
Show file tree
Hide file tree
Showing 7 changed files with 512 additions and 83 deletions.
259 changes: 228 additions & 31 deletions osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
Expand All @@ -28,6 +30,7 @@
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Screens.Edit.Setup;
using osu.Game.Skinning;
using osu.Game.Storyboards;
using osu.Game.Tests.Resources;
using osuTK;
Expand Down Expand Up @@ -99,44 +102,15 @@ public void TestAddAudioTrack()
AddStep("enter compose mode", () => InputManager.Key(Key.F1));
AddUntilStep("wait for timeline load", () => Editor.ChildrenOfType<Timeline>().FirstOrDefault()?.IsLoaded == true);

AddStep("enter setup mode", () => InputManager.Key(Key.F4));
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual);
AddAssert("switch track to real track", () =>
{
var setup = Editor.ChildrenOfType<SetupScreen>().First();

string temp = TestResources.GetTestBeatmapForImport();

string extractedFolder = $"{temp}_extracted";
Directory.CreateDirectory(extractedFolder);

try
{
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);

bool success = setup.ChildrenOfType<ResourcesSection>().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")));

// ensure audio file is copied to beatmap as "audio.mp3" rather than original filename.
Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3");

return success;
}
finally
{
File.Delete(temp);
Directory.Delete(extractedFolder, true);
}
});
AddAssert("switch track to real track", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3"));

AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual);
AddUntilStep("track length changed", () => Beatmap.Value.Track.Length > 60000);

AddStep("test play", () => Editor.TestGameplay());

AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null);
AddStep("confirm save", () => InputManager.Key(Key.Number1));

AddUntilStep("wait for return to editor", () => Editor.IsCurrentScreen());

AddAssert("track is still not virtual", () => Beatmap.Value.Track is not TrackVirtual);
Expand Down Expand Up @@ -635,5 +609,228 @@ public void TestCreateNewDifficultyForInconvertibleRuleset()
return set != null && set.PerformRead(s => s.Beatmaps.Count == 3 && s.Files.Count == 3);
});
}

[Test]
public void TestSingleBackgroundFile()
{
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
AddAssert("set background", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg"));

createNewDifficulty();
createNewDifficulty();

switchToDifficulty(1);

AddAssert("set background on second diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg"));
AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg"));

switchToDifficulty(0);

AddAssert("set background on first diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (2).jpg"));
AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (2).jpg"));

AddAssert("set background on all diff", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg"));
AddAssert("all diff uses one background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.BackgroundFile == "bg.jpg"));
AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg"));
AddAssert("other files removed", () => !Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg" || f.Filename == "bg (2).jpg"));
}

[Test]
public void TestBackgroundFileChangesPreserveOnEncode()
{
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
AddAssert("set background", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg"));

createNewDifficulty();
createNewDifficulty();

switchToDifficulty(0);

AddAssert("set different background on all diff", () => setBackgroundDifferentExtension(applyToAllDifficulties: true, expected: "bg.jpeg"));
AddAssert("all diff uses one background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.BackgroundFile == "bg.jpeg"));
AddAssert("all diff encode same background", () =>
{
return Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b =>
{
var files = new RealmFileStore(Realm, Dependencies.Get<GameHost>().Storage);
using var store = new RealmBackedResourceStore<BeatmapSetInfo>(b.BeatmapSet!.ToLive(Realm), files.Store, Realm);
string[] osu = Encoding.UTF8.GetString(store.Get(b.File!.Filename)).Split(Environment.NewLine);
Assert.That(osu, Does.Contain("0,0,\"bg.jpeg\",0,0"));
return true;
});
});
}

[Test]
public void TestSingleAudioFile()
{
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
AddAssert("set audio", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3"));

createNewDifficulty();
createNewDifficulty();

switchToDifficulty(1);

AddAssert("set audio on second diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3"));
AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3"));

switchToDifficulty(0);

AddAssert("set audio on first diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (2).mp3"));
AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (2).mp3"));

AddAssert("set audio on all diff", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3"));
AddAssert("all diff uses one audio", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.AudioFile == "audio.mp3"));
AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3"));
AddAssert("other files removed", () => !Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3" || f.Filename == "audio (2).mp3"));
}

[Test]
public void TestMultipleBackgroundFiles()
{
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg"));

createNewDifficulty();

AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg");
AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg"));
AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg");

switchToDifficulty(0);

AddAssert("old difficulty uses old background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg");
AddAssert("old background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg"));
AddStep("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg"));
AddAssert("other background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg"));
}

[Test]
public void TestMultipleAudioFiles()
{
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3"));

createNewDifficulty();

AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3");
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
AddUntilStep("wait for load", () => Editor.ChildrenOfType<SetupScreen>().Any());
AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3"));
AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3");

switchToDifficulty(0);

AddAssert("old difficulty uses old audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3");
AddAssert("old audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3"));
AddStep("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3"));
AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3"));
}

private void createNewDifficulty()
{
string? currentDifficulty = null;

AddStep("save", () => Editor.Save());
AddStep("create new difficulty", () =>
{
currentDifficulty = EditorBeatmap.BeatmapInfo.DifficultyName;
Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo);
});

AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction());
AddUntilStep("wait for created", () =>
{
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
return difficultyName != null && difficultyName != currentDifficulty;
});

AddUntilStep("wait for editor load", () => Editor.IsLoaded);
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
AddUntilStep("wait for load", () => Editor.ChildrenOfType<SetupScreen>().Any());
}

private void switchToDifficulty(int index)
{
AddStep("save", () => Editor.Save());
AddStep($"switch to difficulty #{index + 1}", () =>
Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(index)));

AddUntilStep("wait for editor load", () => Editor.IsLoaded);
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
AddUntilStep("wait for load", () => Editor.ChildrenOfType<SetupScreen>().Any());
}

private bool setBackground(bool applyToAllDifficulties, string expected)
{
var setup = Editor.ChildrenOfType<SetupScreen>().First();

return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder =>
{
bool success = setup.ChildrenOfType<ResourcesSection>().First().ChangeBackgroundImage(
new FileInfo(Path.Combine(extractedFolder, @"machinetop_background.jpg")),
applyToAllDifficulties);

Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected));
return success;
});
}

private bool setBackgroundDifferentExtension(bool applyToAllDifficulties, string expected)
{
var setup = Editor.ChildrenOfType<SetupScreen>().First();

return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder =>
{
File.Move(
Path.Combine(extractedFolder, @"machinetop_background.jpg"),
Path.Combine(extractedFolder, @"machinetop_background.jpeg"));

bool success = setup.ChildrenOfType<ResourcesSection>().First().ChangeBackgroundImage(
new FileInfo(Path.Combine(extractedFolder, @"machinetop_background.jpeg")),
applyToAllDifficulties);

Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected));
return success;
});
}

private bool setAudio(bool applyToAllDifficulties, string expected)
{
var setup = Editor.ChildrenOfType<SetupScreen>().First();

return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder =>
{
bool success = setup.ChildrenOfType<ResourcesSection>().First().ChangeAudioTrack(
new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")),
applyToAllDifficulties);

Assert.That(Beatmap.Value.Metadata.AudioFile, Is.EqualTo(expected));
return success;
});
}

private bool setFile(string archivePath, Func<string, bool> func)
{
string temp = archivePath;

string extractedFolder = $"{temp}_extracted";
Directory.CreateDirectory(extractedFolder);

try
{
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);

return func(extractedFolder);
}
finally
{
File.Delete(temp);
Directory.Delete(extractedFolder, true);
}
}
}
}
10 changes: 8 additions & 2 deletions osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Localisation;
using osu.Game.Screens.Edit.Setup;
using osuTK;

namespace osu.Game.Tests.Visual.UserInterface
Expand Down Expand Up @@ -89,8 +90,13 @@ public TestSceneFormControls()
},
new FormFileSelector
{
Caption = "Audio file",
PlaceholderText = "Select an audio file",
Caption = "File selector",
PlaceholderText = "Select a file",
},
new FormBeatmapFileSelector(true)
{
Caption = "File selector with intermediate choice dialog",
PlaceholderText = "Select a file",
},
new FormColourPalette
{
Expand Down
2 changes: 1 addition & 1 deletion osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static void Register()
}

/// <summary>
/// Whether or not beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes.
/// Whether beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes.
/// </summary>
public bool ApplyOffsets = true;

Expand Down
30 changes: 25 additions & 5 deletions osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,20 +242,26 @@ Task ICanAcceptFiles.Import(params string[] paths)

Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException();

protected virtual FileChooserPopover CreatePopover(string[] handledExtensions, Bindable<FileInfo?> current, string? chooserPath) => new FileChooserPopover(handledExtensions, current, chooserPath);

public Popover GetPopover()
{
var popover = new FileChooserPopover(handledExtensions, Current, initialChooserPath);
var popover = CreatePopover(handledExtensions, Current, initialChooserPath);
popoverState.UnbindBindings();
popoverState.BindTo(popover.State);
return popover;
}

private partial class FileChooserPopover : OsuPopover
protected partial class FileChooserPopover : OsuPopover
{
protected override string PopInSampleName => "UI/overlay-big-pop-in";
protected override string PopOutSampleName => "UI/overlay-big-pop-out";

public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> currentFile, string? chooserPath)
private readonly Bindable<FileInfo?> current = new Bindable<FileInfo?>();

protected OsuFileSelector FileSelector;

public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> current, string? chooserPath)
: base(false)
{
Child = new Container
Expand All @@ -264,12 +270,13 @@ public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> curren
// simplest solution to avoid underlying text to bleed through the bottom border
// https://github.com/ppy/osu/pull/30005#issuecomment-2378884430
Padding = new MarginPadding { Bottom = 1 },
Child = new OsuFileSelector(chooserPath, handledExtensions)
Child = FileSelector = new OsuFileSelector(chooserPath, handledExtensions)
{
RelativeSizeAxes = Axes.Both,
CurrentFile = { BindTarget = currentFile }
},
};

this.current.BindTo(current);
}

[BackgroundDependencyLoader]
Expand All @@ -292,6 +299,19 @@ private void load(OverlayColourProvider colourProvider)
}
});
}

protected override void LoadComplete()
{
base.LoadComplete();

FileSelector.CurrentFile.ValueChanged += f =>
{
if (f.NewValue != null)
OnFileSelected(f.NewValue);
};
}

protected virtual void OnFileSelected(FileInfo file) => current.Value = file;
}
}
}
Loading

0 comments on commit 89e3c55

Please sign in to comment.