mirror of
https://github.com/ppy/osu.git
synced 2025-01-14 19:22:56 +08:00
Merge branch 'master' into playlist-overlay-optimisations
This commit is contained in:
commit
cdca1285ab
@ -1,91 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Editor
|
||||
{
|
||||
public class TestSceneEditorSaving : OsuGameTestScene
|
||||
{
|
||||
private Screens.Edit.Editor editor => Game.ChildrenOfType<Screens.Edit.Editor>().FirstOrDefault();
|
||||
|
||||
private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap));
|
||||
|
||||
/// <summary>
|
||||
/// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select.
|
||||
/// Emphasis is placed on <see cref="BeatmapDifficulty.SliderMultiplier"/>, since taiko has special handling for it to keep compatibility with stable.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestNewBeatmapSaveThenLoad()
|
||||
{
|
||||
AddStep("set default beatmap", () => Game.Beatmap.SetDefault());
|
||||
AddStep("set taiko ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
|
||||
|
||||
PushAndConfirm(() => new EditorLoader());
|
||||
|
||||
AddUntilStep("wait for editor load", () => editor?.IsLoaded == true);
|
||||
|
||||
AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType<MetadataSection>().FirstOrDefault()?.IsLoaded == true);
|
||||
|
||||
// We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten.
|
||||
|
||||
AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
|
||||
AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true);
|
||||
|
||||
AddStep("Set slider multiplier", () => editorBeatmap.Difficulty.SliderMultiplier = 2);
|
||||
AddStep("Set artist and title", () =>
|
||||
{
|
||||
editorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
|
||||
editorBeatmap.BeatmapInfo.Metadata.Title = "title";
|
||||
});
|
||||
AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty");
|
||||
|
||||
checkMutations();
|
||||
|
||||
AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
|
||||
|
||||
checkMutations();
|
||||
|
||||
AddStep("Exit", () => InputManager.Key(Key.Escape));
|
||||
|
||||
AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||
|
||||
PushAndConfirm(() => new PlaySongSelect());
|
||||
|
||||
AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault);
|
||||
AddStep("Open options", () => InputManager.Key(Key.F3));
|
||||
AddStep("Enter editor", () => InputManager.Key(Key.Number5));
|
||||
|
||||
AddUntilStep("Wait for editor load", () => editor != null);
|
||||
|
||||
checkMutations();
|
||||
}
|
||||
|
||||
private void checkMutations()
|
||||
{
|
||||
AddAssert("Beatmap has correct slider multiplier", () =>
|
||||
{
|
||||
// we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use.
|
||||
// therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct.
|
||||
var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty();
|
||||
taikoDifficulty.CopyFrom(editorBeatmap.Difficulty);
|
||||
return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2);
|
||||
});
|
||||
AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title");
|
||||
AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Editor
|
||||
{
|
||||
public class TestSceneTaikoEditorSaving : EditorSavingTestScene
|
||||
{
|
||||
protected override Ruleset CreateRuleset() => new TaikoRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestTaikoSliderMultiplier()
|
||||
{
|
||||
AddStep("Set slider multiplier", () => EditorBeatmap.Difficulty.SliderMultiplier = 2);
|
||||
|
||||
SaveEditor();
|
||||
|
||||
AddAssert("Beatmap has correct slider multiplier", assertTaikoSliderMulitplier);
|
||||
|
||||
ReloadEditorToSameBeatmap();
|
||||
|
||||
AddAssert("Beatmap still has correct slider multiplier", assertTaikoSliderMulitplier);
|
||||
|
||||
bool assertTaikoSliderMulitplier()
|
||||
{
|
||||
// we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use.
|
||||
// therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct.
|
||||
var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty();
|
||||
taikoDifficulty.CopyFrom(EditorBeatmap.Difficulty);
|
||||
return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -26,12 +26,16 @@ namespace osu.Game.Tests.NonVisual
|
||||
|
||||
score.Statistics[HitResult.Good]++;
|
||||
score.Rank = ScoreRank.X;
|
||||
score.RealmUser.Username = "test";
|
||||
|
||||
Assert.That(scoreCopy.Statistics[HitResult.Good], Is.EqualTo(10));
|
||||
Assert.That(score.Statistics[HitResult.Good], Is.EqualTo(11));
|
||||
|
||||
Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B));
|
||||
Assert.That(score.Rank, Is.EqualTo(ScoreRank.X));
|
||||
|
||||
Assert.That(scoreCopy.RealmUser.Username, Is.Empty);
|
||||
Assert.That(score.RealmUser.Username, Is.EqualTo("test"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -4,97 +4,117 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public class TestSceneEditorSaving : OsuGameTestScene
|
||||
public class TestSceneEditorSaving : EditorSavingTestScene
|
||||
{
|
||||
private Editor editor => Game.ChildrenOfType<Editor>().FirstOrDefault();
|
||||
|
||||
private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap));
|
||||
|
||||
/// <summary>
|
||||
/// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestNewBeatmapSaveThenLoad()
|
||||
public void TestMetadata()
|
||||
{
|
||||
AddStep("set default beatmap", () => Game.Beatmap.SetDefault());
|
||||
|
||||
PushAndConfirm(() => new EditorLoader());
|
||||
|
||||
AddUntilStep("wait for editor load", () => editor?.IsLoaded == true);
|
||||
|
||||
AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType<MetadataSection>().FirstOrDefault()?.IsLoaded == true);
|
||||
|
||||
// We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten.
|
||||
|
||||
AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
|
||||
AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true);
|
||||
|
||||
AddStep("Set beat divisor", () => editor.Dependencies.Get<BindableBeatDivisor>().Value = 16);
|
||||
AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7);
|
||||
AddStep("Set artist and title", () =>
|
||||
{
|
||||
editorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
|
||||
editorBeatmap.BeatmapInfo.Metadata.Title = "title";
|
||||
EditorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
|
||||
EditorBeatmap.BeatmapInfo.Metadata.Title = "title";
|
||||
});
|
||||
AddStep("Set author", () => editorBeatmap.BeatmapInfo.Metadata.Author.Username = "author");
|
||||
AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty");
|
||||
AddStep("Set author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username = "author");
|
||||
AddStep("Set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = "difficulty");
|
||||
|
||||
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
||||
SaveEditor();
|
||||
|
||||
AddAssert("Beatmap has correct metadata", () => EditorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && EditorBeatmap.BeatmapInfo.Metadata.Title == "title");
|
||||
AddAssert("Beatmap has correct author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username == "author");
|
||||
AddAssert("Beatmap has correct difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
|
||||
AddAssert("Beatmap has correct .osu file path", () => EditorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu");
|
||||
|
||||
ReloadEditorToSameBeatmap();
|
||||
|
||||
AddAssert("Beatmap still has correct metadata", () => EditorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && EditorBeatmap.BeatmapInfo.Metadata.Title == "title");
|
||||
AddAssert("Beatmap still has correct author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username == "author");
|
||||
AddAssert("Beatmap still has correct difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
|
||||
AddAssert("Beatmap still has correct .osu file path", () => EditorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestConfiguration()
|
||||
{
|
||||
double originalTimelineZoom = 0;
|
||||
double changedTimelineZoom = 0;
|
||||
|
||||
AddStep("Set beat divisor", () => Editor.Dependencies.Get<BindableBeatDivisor>().Value = 16);
|
||||
AddStep("Set timeline zoom", () =>
|
||||
{
|
||||
originalTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom;
|
||||
|
||||
var timeline = Editor.ChildrenOfType<Timeline>().Single();
|
||||
InputManager.MoveMouseTo(timeline);
|
||||
InputManager.PressKey(Key.AltLeft);
|
||||
InputManager.ScrollVerticalBy(15f);
|
||||
InputManager.ReleaseKey(Key.AltLeft);
|
||||
});
|
||||
|
||||
AddAssert("Ensure timeline zoom changed", () =>
|
||||
{
|
||||
changedTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom;
|
||||
return !Precision.AlmostEquals(changedTimelineZoom, originalTimelineZoom);
|
||||
});
|
||||
|
||||
SaveEditor();
|
||||
|
||||
AddAssert("Beatmap has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16);
|
||||
AddAssert("Beatmap has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom);
|
||||
|
||||
ReloadEditorToSameBeatmap();
|
||||
|
||||
AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16);
|
||||
AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDifficulty()
|
||||
{
|
||||
AddStep("Set overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty = 7);
|
||||
|
||||
SaveEditor();
|
||||
|
||||
AddAssert("Beatmap has correct overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty == 7);
|
||||
|
||||
ReloadEditorToSameBeatmap();
|
||||
|
||||
AddAssert("Beatmap still has correct overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty == 7);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHitObjectPlacement()
|
||||
{
|
||||
AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(500, new TimingControlPoint()));
|
||||
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
|
||||
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
|
||||
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
checkMutations();
|
||||
SaveEditor();
|
||||
|
||||
AddAssert("Beatmap has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500);
|
||||
|
||||
// After placement these must be non-default as defaults are read-only.
|
||||
AddAssert("Placed object has non-default control points", () =>
|
||||
editorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
|
||||
editorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
|
||||
EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
|
||||
EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
|
||||
|
||||
AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
|
||||
ReloadEditorToSameBeatmap();
|
||||
|
||||
checkMutations();
|
||||
AddAssert("Beatmap has correct .osu file path", () => editorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu");
|
||||
AddAssert("Beatmap still has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500);
|
||||
|
||||
AddStep("Exit", () => InputManager.Key(Key.Escape));
|
||||
|
||||
AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new PlaySongSelect());
|
||||
AddUntilStep("wait for carousel load", () => songSelect.BeatmapSetsLoaded);
|
||||
|
||||
AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault);
|
||||
AddStep("Open options", () => InputManager.Key(Key.F3));
|
||||
AddStep("Enter editor", () => InputManager.Key(Key.Number5));
|
||||
|
||||
AddUntilStep("Wait for editor load", () => editor != null);
|
||||
|
||||
checkMutations();
|
||||
}
|
||||
|
||||
private void checkMutations()
|
||||
{
|
||||
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
|
||||
AddAssert("Beatmap has correct beat divisor", () => editorBeatmap.BeatmapInfo.BeatDivisor == 16);
|
||||
AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7);
|
||||
AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title");
|
||||
AddAssert("Beatmap has correct author", () => editorBeatmap.BeatmapInfo.Metadata.Author.Username == "author");
|
||||
AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
|
||||
// After placement these must be non-default as defaults are read-only.
|
||||
AddAssert("Placed object still has non-default control points", () =>
|
||||
EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
|
||||
EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,10 +50,20 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddStep("close settings", () => Game.Settings.Hide());
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
|
||||
PushAndConfirm(() => new PlaySongSelect());
|
||||
|
||||
AddUntilStep("wait for selection", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
AddStep("enter gameplay", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("wait for player", () =>
|
||||
{
|
||||
// dismiss any notifications that may appear (ie. muted notification).
|
||||
clickMouseInCentre();
|
||||
return player != null;
|
||||
});
|
||||
|
||||
AddUntilStep("wait for gameplay", () => player?.IsBreakTime.Value == false);
|
||||
|
||||
AddStep("press 'z'", () => InputManager.Key(Key.Z));
|
||||
@ -63,6 +73,12 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("key counter did increase", () => keyCounter.CountPresses == 1);
|
||||
}
|
||||
|
||||
private void clickMouseInCentre()
|
||||
{
|
||||
InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
}
|
||||
|
||||
private KeyBindingsSubsection osuBindingSubsection => keyBindingPanel
|
||||
.ChildrenOfType<VariantBindingsSubsection>()
|
||||
.FirstOrDefault(s => s.Ruleset.ShortName == "osu");
|
||||
|
@ -64,13 +64,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("delete all beatmaps", () =>
|
||||
AddStep("reset defaults", () =>
|
||||
{
|
||||
Ruleset.Value = new OsuRuleset().RulesetInfo;
|
||||
manager?.Delete(manager.GetAllUsableBeatmapSets());
|
||||
|
||||
Beatmap.SetDefault();
|
||||
});
|
||||
|
||||
AddStep("delete all beatmaps", () => manager?.Delete());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -111,7 +111,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public int GridSize { get; set; }
|
||||
|
||||
public double TimelineZoom { get; set; }
|
||||
public double TimelineZoom { get; set; } = 1.0;
|
||||
|
||||
[Ignored]
|
||||
public CountdownType Countdown { get; set; } = CountdownType.Normal;
|
||||
|
@ -286,6 +286,7 @@ namespace osu.Game.Database
|
||||
|
||||
var transaction = r.BeginWrite();
|
||||
int written = 0;
|
||||
int missing = 0;
|
||||
|
||||
try
|
||||
{
|
||||
@ -300,6 +301,13 @@ namespace osu.Game.Database
|
||||
|
||||
var beatmap = r.All<BeatmapInfo>().First(b => b.Hash == score.BeatmapInfo.Hash);
|
||||
var ruleset = r.Find<RulesetInfo>(score.Ruleset.ShortName);
|
||||
|
||||
if (ruleset == null)
|
||||
{
|
||||
log($"Skipping {++missing} scores with missing ruleset");
|
||||
continue;
|
||||
}
|
||||
|
||||
var user = new RealmUser
|
||||
{
|
||||
OnlineID = score.User.OnlineID,
|
||||
|
@ -216,12 +216,6 @@ namespace osu.Game.Database
|
||||
return new RealmLiveUnmanaged<T>(realmObject);
|
||||
}
|
||||
|
||||
public static List<Live<T>> ToLive<T>(this IEnumerable<T> realmList, RealmAccess realm)
|
||||
where T : RealmObject, IHasGuidPrimaryKey
|
||||
{
|
||||
return realmList.Select(l => new RealmLive<T>(l, realm)).Cast<Live<T>>().ToList();
|
||||
}
|
||||
|
||||
public static Live<T> ToLive<T>(this T realmObject, RealmAccess realm)
|
||||
where T : RealmObject, IHasGuidPrimaryKey
|
||||
{
|
||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Models
|
||||
{
|
||||
public int OnlineID { get; set; } = 1;
|
||||
|
||||
public string Username { get; set; }
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
public bool IsBot => false;
|
||||
|
||||
|
@ -18,6 +18,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Skinning.Editor;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections
|
||||
{
|
||||
@ -41,7 +42,7 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
Name = "<Random Skin>",
|
||||
}.ToLiveUnmanaged();
|
||||
|
||||
private List<Live<SkinInfo>> skinItems;
|
||||
private readonly List<Live<SkinInfo>> dropdownItems = new List<Live<SkinInfo>>();
|
||||
|
||||
[Resolved]
|
||||
private SkinManager skins { get; set; }
|
||||
@ -51,12 +52,6 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
|
||||
private IDisposable realmSubscription;
|
||||
|
||||
private IQueryable<SkinInfo> queryRealmSkins() =>
|
||||
realm.Realm.All<SkinInfo>()
|
||||
.Where(s => !s.DeletePending)
|
||||
.OrderByDescending(s => s.Protected) // protected skins should be at the top.
|
||||
.ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
[BackgroundDependencyLoader(permitNulls: true)]
|
||||
private void load(OsuConfigManager config, [CanBeNull] SkinEditorOverlay skinEditor)
|
||||
{
|
||||
@ -83,37 +78,58 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
|
||||
skinDropdown.Current = dropdownBindable;
|
||||
|
||||
realmSubscription = realm.RegisterForNotifications(r => queryRealmSkins(), (sender, changes, error) =>
|
||||
{
|
||||
// The first fire of this is a bit redundant due to the call below,
|
||||
// but this is safest in case the subscription is restored after a context recycle.
|
||||
updateItems();
|
||||
});
|
||||
|
||||
updateItems();
|
||||
realmSubscription = realm.RegisterForNotifications(r => realm.Realm.All<SkinInfo>()
|
||||
.Where(s => !s.DeletePending)
|
||||
.OrderByDescending(s => s.Protected) // protected skins should be at the top.
|
||||
.ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase), skinsChanged);
|
||||
|
||||
configBindable.BindValueChanged(id => Scheduler.AddOnce(updateSelectedSkinFromConfig));
|
||||
updateSelectedSkinFromConfig();
|
||||
|
||||
dropdownBindable.BindValueChanged(skin =>
|
||||
dropdownBindable.BindValueChanged(dropdownSelectionChanged);
|
||||
}
|
||||
|
||||
private void dropdownSelectionChanged(ValueChangedEvent<Live<SkinInfo>> skin)
|
||||
{
|
||||
// Only handle cases where it's clear the user has intent to change skins.
|
||||
if (skin.OldValue == null) return;
|
||||
|
||||
if (skin.NewValue.Equals(random_skin_info))
|
||||
{
|
||||
if (skin.NewValue.Equals(random_skin_info))
|
||||
var skinBefore = skins.CurrentSkinInfo.Value;
|
||||
|
||||
skins.SelectRandomSkin();
|
||||
|
||||
if (skinBefore == skins.CurrentSkinInfo.Value)
|
||||
{
|
||||
var skinBefore = skins.CurrentSkinInfo.Value;
|
||||
|
||||
skins.SelectRandomSkin();
|
||||
|
||||
if (skinBefore == skins.CurrentSkinInfo.Value)
|
||||
{
|
||||
// the random selection didn't change the skin, so we should manually update the dropdown to match.
|
||||
dropdownBindable.Value = skins.CurrentSkinInfo.Value;
|
||||
}
|
||||
|
||||
return;
|
||||
// the random selection didn't change the skin, so we should manually update the dropdown to match.
|
||||
dropdownBindable.Value = skins.CurrentSkinInfo.Value;
|
||||
}
|
||||
|
||||
configBindable.Value = skin.NewValue.ID.ToString();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
configBindable.Value = skin.NewValue.ID.ToString();
|
||||
}
|
||||
|
||||
private void skinsChanged(IRealmCollection<SkinInfo> sender, ChangeSet changes, Exception error)
|
||||
{
|
||||
// This can only mean that realm is recycling, else we would see the protected skins.
|
||||
// Because we are using `Live<>` in this class, we don't need to worry about this scenario too much.
|
||||
if (!sender.Any())
|
||||
return;
|
||||
|
||||
int protectedCount = sender.Count(s => s.Protected);
|
||||
|
||||
// For simplicity repopulate the full list.
|
||||
// In the future we should change this to properly handle ChangeSet events.
|
||||
dropdownItems.Clear();
|
||||
foreach (var skin in sender)
|
||||
dropdownItems.Add(skin.ToLive(realm));
|
||||
dropdownItems.Insert(protectedCount, random_skin_info);
|
||||
|
||||
skinDropdown.Items = dropdownItems;
|
||||
|
||||
updateSelectedSkinFromConfig();
|
||||
}
|
||||
|
||||
private void updateSelectedSkinFromConfig()
|
||||
@ -126,17 +142,6 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
dropdownBindable.Value = skin ?? skinDropdown.Items.First();
|
||||
}
|
||||
|
||||
private void updateItems()
|
||||
{
|
||||
int protectedCount = queryRealmSkins().Count(s => s.Protected);
|
||||
|
||||
skinItems = queryRealmSkins().ToLive(realm);
|
||||
|
||||
skinItems.Insert(protectedCount, random_skin_info);
|
||||
|
||||
skinDropdown.Items = skinItems;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
@ -28,21 +28,15 @@ namespace osu.Game.Rulesets
|
||||
public Ruleset CreateInstance()
|
||||
{
|
||||
if (!Available)
|
||||
throw new RulesetLoadException(@"Ruleset not available");
|
||||
return null;
|
||||
|
||||
var type = Type.GetType(InstantiationInfo);
|
||||
|
||||
if (type == null)
|
||||
throw new RulesetLoadException(@"Type lookup failure");
|
||||
return null;
|
||||
|
||||
var ruleset = Activator.CreateInstance(type) as Ruleset;
|
||||
|
||||
if (ruleset == null)
|
||||
throw new RulesetLoadException(@"Instantiation failure");
|
||||
|
||||
// overwrite the pre-populated RulesetInfo with a potentially database attached copy.
|
||||
// ruleset.RulesetInfo = this;
|
||||
|
||||
return ruleset;
|
||||
}
|
||||
|
||||
|
@ -133,6 +133,11 @@ namespace osu.Game.Scoring
|
||||
var clone = (ScoreInfo)this.Detach().MemberwiseClone();
|
||||
|
||||
clone.Statistics = new Dictionary<HitResult, int>(clone.Statistics);
|
||||
clone.RealmUser = new RealmUser
|
||||
{
|
||||
OnlineID = RealmUser.OnlineID,
|
||||
Username = RealmUser.Username,
|
||||
};
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
67
osu.Game/Tests/Visual/EditorSavingTestScene.cs
Normal file
67
osu.Game/Tests/Visual/EditorSavingTestScene.cs
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Select;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select.
|
||||
/// </summary>
|
||||
public class EditorSavingTestScene : OsuGameTestScene
|
||||
{
|
||||
protected Editor Editor => Game.ChildrenOfType<Editor>().FirstOrDefault();
|
||||
|
||||
protected EditorBeatmap EditorBeatmap => (EditorBeatmap)Editor.Dependencies.Get(typeof(EditorBeatmap));
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("set default beatmap", () => Game.Beatmap.SetDefault());
|
||||
|
||||
PushAndConfirm(() => new EditorLoader());
|
||||
|
||||
AddUntilStep("wait for editor load", () => Editor?.IsLoaded == true);
|
||||
|
||||
AddUntilStep("wait for metadata screen load", () => Editor.ChildrenOfType<MetadataSection>().FirstOrDefault()?.IsLoaded == true);
|
||||
|
||||
// We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten.
|
||||
|
||||
AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
|
||||
AddUntilStep("Wait for compose mode load", () => Editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true);
|
||||
}
|
||||
|
||||
protected void SaveEditor()
|
||||
{
|
||||
AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
|
||||
}
|
||||
|
||||
protected void ReloadEditorToSameBeatmap()
|
||||
{
|
||||
AddStep("Exit", () => InputManager.Key(Key.Escape));
|
||||
|
||||
AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||
|
||||
SongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new PlaySongSelect());
|
||||
AddUntilStep("wait for carousel load", () => songSelect.BeatmapSetsLoaded);
|
||||
|
||||
AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault);
|
||||
AddStep("Open options", () => InputManager.Key(Key.F3));
|
||||
AddStep("Enter editor", () => InputManager.Key(Key.Number5));
|
||||
|
||||
AddUntilStep("Wait for editor load", () => Editor != null);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user