diff --git a/.idea/.idea.osu/.idea/indexLayout.xml b/.idea/.idea.osu/.idea/indexLayout.xml index 27ba142e96..7b08163ceb 100644 --- a/.idea/.idea.osu/.idea/indexLayout.xml +++ b/.idea/.idea.osu/.idea/indexLayout.xml @@ -1,6 +1,6 @@ - + diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs index 9c512a01ea..536fdfc6df 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs @@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests [BackgroundDependencyLoader] private void load(GameHost host, OsuGameBase gameBase) { - OsuGame game = new OsuGame(); - game.SetHost(host); - Children = new Drawable[] { new Box @@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - game }; + + AddGame(new OsuGame()); } } } diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs index 270d906b01..3cdf44e6f1 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs @@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.Pippidon.Tests [BackgroundDependencyLoader] private void load(GameHost host, OsuGameBase gameBase) { - OsuGame game = new OsuGame(); - game.SetHost(host); - Children = new Drawable[] { new Box @@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.Pippidon.Tests RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - game }; + + AddGame(new OsuGame()); } } } diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs index aed6abb6bf..4d3f5086d9 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs @@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests [BackgroundDependencyLoader] private void load(GameHost host, OsuGameBase gameBase) { - OsuGame game = new OsuGame(); - game.SetHost(host); - Children = new Drawable[] { new Box @@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - game }; + + AddGame(new OsuGame()); } } } diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs index 270d906b01..3cdf44e6f1 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs @@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.Pippidon.Tests [BackgroundDependencyLoader] private void load(GameHost host, OsuGameBase gameBase) { - OsuGame game = new OsuGame(); - game.SetHost(host); - Children = new Drawable[] { new Box @@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.Pippidon.Tests RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - game }; + + AddGame(new OsuGame()); } } } diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.cs index dd0a20f1b4..98dba622d0 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osuTK; @@ -61,9 +62,9 @@ namespace osu.Game.Rulesets.Pippidon.UI } } - public bool OnPressed(PippidonAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case PippidonAction.MoveUp: changeLane(-1); @@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Pippidon.UI } } - public void OnReleased(PippidonAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Android.props b/osu.Android.props index 7378450c38..4859510e6c 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,11 +51,11 @@ - - + + - + diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs index 63c99dcb2b..2835ec9499 100644 --- a/osu.Game.Benchmarks/BenchmarkRuleset.cs +++ b/osu.Game.Benchmarks/BenchmarkRuleset.cs @@ -4,6 +4,7 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; using osu.Game.Online.API; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; namespace osu.Game.Benchmarks @@ -37,7 +38,25 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkGetAllMods() { - ruleset.GetAllMods().Consume(new Consumer()); + ruleset.CreateAllMods().Consume(new Consumer()); + } + + [Benchmark] + public void BenchmarkGetAllModsForReference() + { + ruleset.AllMods.Consume(new Consumer()); + } + + [Benchmark] + public void BenchmarkGetForAcronym() + { + ruleset.CreateModFromAcronym("DT"); + } + + [Benchmark] + public void BenchmarkGetForType() + { + ruleset.CreateMod(); } } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 73b60f51a4..d0a94767d1 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -44,9 +44,9 @@ namespace osu.Game.Rulesets.Catch.Mods } // disable keyboard controls - public bool OnPressed(CatchAction action) => true; + public bool OnPressed(KeyBindingPressEvent e) => true; - public void OnReleased(CatchAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index b30c3d82a4..604e878782 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Replays; @@ -144,9 +145,9 @@ namespace osu.Game.Rulesets.Catch.UI Catcher.VisualDirection = Direction.Left; } - public bool OnPressed(CatchAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case CatchAction.MoveLeft: currentDirection--; @@ -164,9 +165,9 @@ namespace osu.Game.Rulesets.Catch.UI return false; } - public void OnReleased(CatchAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - switch (action) + switch (e.Action) { case CatchAction.MoveLeft: currentDirection++; diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1-0@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1-0@2x.png new file mode 100644 index 0000000000..2db5d76e78 Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1-0@2x.png differ diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1-1@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1-1@2x.png new file mode 100644 index 0000000000..6e7aded39f Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1-1@2x.png differ diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1H-0@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1H-0@2x.png new file mode 100644 index 0000000000..f11fb4f853 Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1H-0@2x.png differ diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1H-1@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1H-1@2x.png new file mode 100644 index 0000000000..4eac5f6f2a Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1H-1@2x.png differ diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2-0@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2-0@2x.png new file mode 100644 index 0000000000..456cee5382 Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2-0@2x.png differ diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2-1@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2-1@2x.png new file mode 100644 index 0000000000..71a09cb4bb Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2-1@2x.png differ diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2H@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2H@2x.png new file mode 100644 index 0000000000..e6da7a1055 Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2H@2x.png differ diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-noteS@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-noteS@2x.png new file mode 100644 index 0000000000..c9bc23e8d9 Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-noteS@2x.png differ diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-noteSH@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-noteSH@2x.png new file mode 100644 index 0000000000..c9bc23e8d9 Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-noteSH@2x.png differ diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs index b7d7af6b8c..68cf3b67df 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { c.Add(CreateHitObject().With(h => { - h.HitObject.StartTime = START_TIME; + h.HitObject.StartTime = Time.Current + 5000; h.AccentColour.Value = Color4.Orange; })); }) @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { c.Add(CreateHitObject().With(h => { - h.HitObject.StartTime = START_TIME; + h.HitObject.StartTime = Time.Current + 5000; h.AccentColour.Value = Color4.Orange; })); }) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 1d84a2dfcb..ddfd057cd8 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning /// public abstract class ManiaSkinnableTestScene : SkinnableTestScene { - protected const double START_TIME = 1000000000; - [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); @@ -55,27 +53,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning public readonly Bindable Direction = new Bindable(); IBindable IScrollingInfo.Direction => Direction; - IBindable IScrollingInfo.TimeRange { get; } = new Bindable(1000); - IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ZeroScrollAlgorithm(); - } - - private class ZeroScrollAlgorithm : IScrollAlgorithm - { - public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength) - => double.MinValue; - - public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) - => scrollLength; - - public float PositionAt(double time, double currentTime, double timeRange, float scrollLength) - => (float)((time - START_TIME) / timeRange) * scrollLength; - - public double TimeAt(float position, double currentTime, double timeRange, float scrollLength) - => 0; - - public void Reset() - { - } + IBindable IScrollingInfo.TimeRange { get; } = new Bindable(5000); + IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ConstantScrollAlgorithm(); } } } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs index 4a6c59e297..92c95b8fde 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("Hold key", () => { clock.CurrentTime = 0; - note.OnPressed(ManiaAction.Key1); + note.OnPressed(new KeyBindingPressEvent(GetContainingInputManager().CurrentState, ManiaAction.Key1)); }); AddStep("progress time", () => clock.CurrentTime = 500); AddAssert("head is visible", () => note.Head.Alpha == 1); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs index e14ad92842..449a6ff23d 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . 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.Graphics; using osu.Framework.Allocation; @@ -13,6 +14,10 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Framework.Bindables; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania.Tests { @@ -22,14 +27,65 @@ namespace osu.Game.Rulesets.Mania.Tests [Resolved] private RulesetConfigCache configCache { get; set; } - private readonly Bindable configTimingBasedNoteColouring = new Bindable(); + private Bindable configTimingBasedNoteColouring; - protected override void LoadComplete() + private ManualClock clock; + private DrawableManiaRuleset drawableRuleset; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("setup hierarchy", () => Child = new Container + { + Clock = new FramedClock(clock = new ManualClock()), + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new[] + { + drawableRuleset = (DrawableManiaRuleset)Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap()) + } + }); + AddStep("retrieve config bindable", () => + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + configTimingBasedNoteColouring = config.GetBindable(ManiaRulesetSetting.TimingBasedNoteColouring); + }); + } + + [Test] + public void TestSimple() + { + AddStep("enable", () => configTimingBasedNoteColouring.Value = true); + AddStep("disable", () => configTimingBasedNoteColouring.Value = false); + } + + [Test] + public void TestToggleOffScreen() + { + AddStep("enable", () => configTimingBasedNoteColouring.Value = true); + + seekTo(10000); + AddStep("disable", () => configTimingBasedNoteColouring.Value = false); + seekTo(0); + AddAssert("all notes not coloured", () => this.ChildrenOfType().All(note => note.Colour == Colour4.White)); + + seekTo(10000); + AddStep("enable again", () => configTimingBasedNoteColouring.Value = true); + seekTo(0); + AddAssert("some notes coloured", () => this.ChildrenOfType().Any(note => note.Colour != Colour4.White)); + } + + private void seekTo(double time) + { + AddStep($"seek to {time}", () => clock.CurrentTime = time); + AddUntilStep("wait for seek", () => Precision.AlmostEquals(drawableRuleset.FrameStableClock.CurrentTime, time, 1)); + } + + private ManiaBeatmap createTestBeatmap() { const double beat_length = 500; - var ruleset = new ManiaRuleset(); - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }) { HitObjects = @@ -45,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Tests new Note { StartTime = beat_length } }, ControlPointInfo = new ControlPointInfo(), - BeatmapInfo = { Ruleset = ruleset.RulesetInfo }, + BeatmapInfo = { Ruleset = Ruleset.Value }, }; foreach (var note in beatmap.HitObjects) @@ -57,24 +113,7 @@ namespace osu.Game.Rulesets.Mania.Tests { BeatLength = beat_length }); - - Child = new Container - { - Clock = new FramedClock(new ManualClock()), - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new[] - { - ruleset.CreateDrawableRulesetWith(beatmap) - } - }; - - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring); - - AddStep("Enable", () => configTimingBasedNoteColouring.Value = true); - AddStep("Disable", () => configTimingBasedNoteColouring.Value = false); + return beatmap; } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 2923a2af2f..4e9781f336 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -253,12 +254,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables HoldBrokenTime = Time.Current; } - public bool OnPressed(ManiaAction action) + public bool OnPressed(KeyBindingPressEvent e) { if (AllJudged) return false; - if (action != Action.Value) + if (e.Action != Action.Value) return false; // do not run any of this logic when rewinding, as it inverts order of presses/releases. @@ -288,12 +289,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables isHitting.Value = true; } - public void OnReleased(ManiaAction action) + public void OnReleased(KeyBindingReleaseEvent e) { if (AllJudged) return; - if (action != Action.Value) + if (e.Action != Action.Value) return; // do not run any of this logic when rewinding, as it inverts order of presses/releases. diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs index 8458345998..6722ad8ab8 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Mania.Objects.Drawables @@ -43,9 +44,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // it will be hidden along with its parenting hold note when required. } - public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note + public override bool OnPressed(KeyBindingPressEvent e) => false; // Handled by the hold note - public override void OnReleased(ManiaAction action) + public override void OnReleased(KeyBindingReleaseEvent e) { } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index 18aa3f66d4..803685363c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects.Drawables @@ -68,9 +69,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }); } - public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note + public override bool OnPressed(KeyBindingPressEvent e) => false; // Handled by the hold note - public override void OnReleased(ManiaAction action) + public override void OnReleased(KeyBindingReleaseEvent e) { } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 33d872dfb6..51727908c9 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Configuration; @@ -66,6 +67,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true); } + protected override void OnApply() + { + base.OnApply(); + updateSnapColour(); + } + protected override void OnDirectionChanged(ValueChangedEvent e) { base.OnDirectionChanged(e); @@ -91,9 +98,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables ApplyResult(r => r.Type = result); } - public virtual bool OnPressed(ManiaAction action) + public virtual bool OnPressed(KeyBindingPressEvent e) { - if (action != Action.Value) + if (e.Action != Action.Value) return false; if (CheckHittable?.Invoke(this, Time.Current) == false) @@ -102,7 +109,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables return UpdateResult(true); } - public virtual void OnReleased(ManiaAction action) + public virtual void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs index 661e7f66f4..54ddcbd5fe 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -76,9 +77,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } } - public bool OnPressed(ManiaAction action) + public bool OnPressed(KeyBindingPressEvent e) { - if (action == Column.Action.Value) + if (e.Action == Column.Action.Value) { light.FadeIn(); light.ScaleTo(Vector2.One); @@ -87,12 +88,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy return false; } - public void OnReleased(ManiaAction action) + public void OnReleased(KeyBindingReleaseEvent e) { // Todo: Should be 400 * 100 / CurrentBPM const double animation_length = 250; - if (action == Column.Action.Value) + if (e.Action == Column.Action.Value) { light.FadeTo(0, animation_length); light.ScaleTo(new Vector2(1, 0), animation_length); diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteHeadPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteHeadPiece.cs index 21e5bdd5d6..1e75533442 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteHeadPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteHeadPiece.cs @@ -1,18 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Skinning.Legacy { public class LegacyHoldNoteHeadPiece : LegacyNotePiece { - protected override Texture GetTexture(ISkinSource skin) + protected override Drawable GetAnimation(ISkinSource skin) { // TODO: Should fallback to the head from default legacy skin instead of note. - return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage) - ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage); + return GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage) + ?? GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage); } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteTailPiece.cs index 232b47ae27..e6d4291d79 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteTailPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteTailPiece.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -18,12 +18,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy : new ValueChangedEvent(ScrollingDirection.Up, ScrollingDirection.Up)); } - protected override Texture GetTexture(ISkinSource skin) + protected override Drawable GetAnimation(ISkinSource skin) { // TODO: Should fallback to the head from default legacy skin instead of note. - return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteTailImage) - ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage) - ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage); + return GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteTailImage) + ?? GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage) + ?? GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage); } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs index 10319a7d4d..9c339345c4 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -86,9 +87,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } } - public bool OnPressed(ManiaAction action) + public bool OnPressed(KeyBindingPressEvent e) { - if (action == column.Action.Value) + if (e.Action == column.Action.Value) { upSprite.FadeTo(0); downSprite.FadeTo(1); @@ -97,9 +98,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy return false; } - public void OnReleased(ManiaAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - if (action == column.Action.Value) + if (e.Action == column.Action.Value) { upSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(1); downSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(0); diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs index 31279796ce..321a87f8b1 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Sprites; @@ -19,7 +21,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy private readonly IBindable direction = new Bindable(); private Container directionContainer; - private Sprite noteSprite; + + [CanBeNull] + private Drawable noteAnimation; private float? minimumColumnWidth; @@ -39,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Child = noteSprite = new Sprite { Texture = GetTexture(skin) } + Child = noteAnimation = GetAnimation(skin) ?? Empty() }; direction.BindTo(scrollingInfo.Direction); @@ -50,12 +54,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { base.Update(); - if (noteSprite.Texture != null) + Texture texture = null; + + if (noteAnimation is Sprite sprite) + texture = sprite.Texture; + else if (noteAnimation is TextureAnimation textureAnimation && textureAnimation.FrameCount > 0) + texture = textureAnimation.CurrentFrame; + + if (texture != null) { // The height is scaled to the minimum column width, if provided. float minimumWidth = minimumColumnWidth ?? DrawWidth; - - noteSprite.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), noteSprite.Texture.DisplayWidth); + noteAnimation.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), texture.DisplayWidth); } } @@ -73,9 +83,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } } - protected virtual Texture GetTexture(ISkinSource skin) => GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage); + [CanBeNull] + protected virtual Drawable GetAnimation(ISkinSource skin) => GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage); - protected Texture GetTextureFromLookup(ISkin skin, LegacyManiaSkinConfigurationLookups lookup) + [CanBeNull] + protected Drawable GetAnimationFromLookup(ISkin skin, LegacyManiaSkinConfigurationLookups lookup) { string suffix = string.Empty; @@ -93,7 +105,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy string noteImage = GetColumnSkinConfig(skin, lookup)?.Value ?? $"mania-note{FallbackColumnIndex}{suffix}"; - return skin.GetTexture(noteImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge); + return skin.GetAnimation(noteImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true); } } } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index f5e30efd91..9d060944cd 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.UI.Scrolling; @@ -122,16 +123,16 @@ namespace osu.Game.Rulesets.Mania.UI HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result))); } - public bool OnPressed(ManiaAction action) + public bool OnPressed(KeyBindingPressEvent e) { - if (action != Action.Value) + if (e.Action != Action.Value) return false; sampleTriggerSource.Play(); return true; } - public void OnReleased(ManiaAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs index 75cc351310..77ddc6fbbf 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.UI.Scrolling; using osuTK.Graphics; @@ -91,16 +92,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components direction.Value == ScrollingDirection.Up ? dimPoint : brightPoint); } - public bool OnPressed(ManiaAction action) + public bool OnPressed(KeyBindingPressEvent e) { - if (action == this.action.Value) + if (e.Action == action.Value) backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint); return false; } - public void OnReleased(ManiaAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - if (action == this.action.Value) + if (e.Action == action.Value) backgroundOverlay.FadeTo(0, 250, Easing.OutQuint); } } diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs index 4b4bc157d5..807f6a77d9 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Rulesets.UI.Scrolling; using osuTK.Graphics; @@ -74,16 +75,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components } } - public bool OnPressed(ManiaAction action) + public bool OnPressed(KeyBindingPressEvent e) { - if (action == column.Action.Value) + if (e.Action == column.Action.Value) backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint); return false; } - public void OnReleased(ManiaAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - if (action == column.Action.Value) + if (e.Action == column.Action.Value) backgroundOverlay.FadeTo(0, 250, Easing.OutQuint); } } diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index 47cb9bd45a..267ed1f5f4 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Rulesets.UI.Scrolling; using osuTK; using osuTK.Graphics; @@ -101,16 +102,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components } } - public bool OnPressed(ManiaAction action) + public bool OnPressed(KeyBindingPressEvent e) { - if (action == column.Action.Value) + if (e.Action == column.Action.Value) keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint); return false; } - public void OnReleased(ManiaAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - if (action == column.Action.Value) + if (e.Action == column.Action.Value) keyIcon.ScaleTo(1f, 125, Easing.OutQuint); } } diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 8d8387378e..19881b5c33 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.7568168283591499d, "diffcalc-test")] - [TestCase(1.0348244046058293d, "zero-length-sliders")] + [TestCase(6.6634445062299665d, "diffcalc-test")] + [TestCase(1.0414203870195022d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(8.4783236764532557d, "diffcalc-test")] - [TestCase(1.2708532136987165d, "zero-length-sliders")] + [TestCase(8.3858089051603368d, "diffcalc-test")] + [TestCase(1.2723279173428435d, "zero-length-sliders")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs new file mode 100644 index 0000000000..11b1f5b2af --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs @@ -0,0 +1,174 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Legacy; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Skinning; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneCursorParticles : TestSceneOsuPlayer + { + protected override bool Autoplay => autoplay; + protected override bool HasCustomSteps => true; + + private bool autoplay; + private IBeatmap currentBeatmap; + + [Resolved] + private SkinManager skinManager { get; set; } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentBeatmap ?? base.CreateBeatmap(ruleset); + + [Test] + public void TestLegacyBreakParticles() + { + LegacyCursorParticles cursorParticles = null; + + createLegacyTest(false, () => new Beatmap + { + Breaks = + { + new BreakPeriod(8500, 10000), + }, + HitObjects = + { + new HitCircle + { + StartTime = 8000, + Position = OsuPlayfield.BASE_SIZE / 2, + }, + new HitCircle + { + StartTime = 11000, + Position = OsuPlayfield.BASE_SIZE / 2, + }, + } + }); + + AddUntilStep("fetch cursor particles", () => + { + cursorParticles = this.ChildrenOfType().SingleOrDefault(); + return cursorParticles != null; + }); + + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre)); + + AddAssert("particles are being spawned", () => cursorParticles.Active); + + AddStep("press left mouse button", () => InputManager.PressButton(MouseButton.Left)); + AddWaitStep("wait a bit", 5); + AddStep("press right mouse button", () => InputManager.PressButton(MouseButton.Right)); + AddWaitStep("wait a bit", 5); + AddStep("release left mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); + AddWaitStep("wait a bit", 5); + AddStep("release right mouse button", () => InputManager.ReleaseButton(MouseButton.Right)); + + AddUntilStep("wait for beatmap start", () => !Player.IsBreakTime.Value); + AddAssert("particle spawning stopped", () => !cursorParticles.Active); + + AddUntilStep("wait for break", () => Player.IsBreakTime.Value); + AddAssert("particles are being spawned", () => cursorParticles.Active); + + AddUntilStep("wait for break end", () => !Player.IsBreakTime.Value); + AddAssert("particle spawning stopped", () => !cursorParticles.Active); + } + + [Test] + public void TestLegacyKiaiParticles() + { + LegacyCursorParticles cursorParticles = null; + DrawableSpinner spinner = null; + DrawableSlider slider = null; + + createLegacyTest(true, () => + { + var controlPointInfo = new ControlPointInfo(); + controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); + + return new Beatmap + { + ControlPointInfo = controlPointInfo, + HitObjects = + { + new Spinner + { + StartTime = 0, + Duration = 1000, + Position = OsuPlayfield.BASE_SIZE / 2, + }, + new Slider + { + StartTime = 2500, + RepeatCount = 0, + Position = OsuPlayfield.BASE_SIZE / 2, + Path = new SliderPath(new[] + { + new PathControlPoint(Vector2.Zero), + new PathControlPoint(new Vector2(100, 0)), + }) + }, + new HitCircle + { + StartTime = 4500, + Position = OsuPlayfield.BASE_SIZE / 2, + }, + }, + }; + } + ); + + AddUntilStep("fetch cursor particles", () => + { + cursorParticles = this.ChildrenOfType().SingleOrDefault(); + return cursorParticles != null; + }); + + AddUntilStep("wait for spinner tracking", () => + { + spinner = this.ChildrenOfType().SingleOrDefault(); + return spinner?.RotationTracker.Tracking == true; + }); + AddAssert("particles are being spawned", () => cursorParticles.Active); + + AddUntilStep("spinner tracking stopped", () => !spinner.RotationTracker.Tracking); + AddAssert("particle spawning stopped", () => !cursorParticles.Active); + + AddUntilStep("wait for slider tracking", () => + { + slider = this.ChildrenOfType().SingleOrDefault(); + return slider?.Tracking.Value == true; + }); + AddAssert("particles are being spawned", () => cursorParticles.Active); + + AddUntilStep("slider tracking stopped", () => !slider.Tracking.Value); + AddAssert("particle spawning stopped", () => !cursorParticles.Active); + } + + private void createLegacyTest(bool autoplay, Func beatmap) => CreateTest(() => + { + AddStep("set beatmap", () => + { + this.autoplay = autoplay; + currentBeatmap = beatmap(); + }); + AddStep("setup default legacy skin", () => + { + skinManager.CurrentSkinInfo.Value = skinManager.DefaultLegacySkin.SkinInfo; + }); + }); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 2326a0c391..f9dc9abd75 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; using osu.Framework.Input; +using osu.Framework.Input.Events; using osu.Framework.Testing.Input; using osu.Framework.Utils; using osu.Game.Audio; @@ -143,9 +144,9 @@ namespace osu.Game.Rulesets.Osu.Tests pressed = value; if (value) - OnPressed(OsuAction.LeftButton); + OnPressed(new KeyBindingPressEvent(GetContainingInputManager().CurrentState, OsuAction.LeftButton)); else - OnReleased(OsuAction.LeftButton); + OnReleased(new KeyBindingReleaseEvent(GetContainingInputManager().CurrentState, OsuAction.LeftButton)); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs index 1fdcd73dde..575523b168 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs @@ -4,6 +4,7 @@ using System; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Osu.Objects; @@ -97,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void scheduleHit() => AddStep("schedule action", () => { var delay = hitCircle.StartTime - hitCircle.HitWindows.WindowFor(HitResult.Great) - Time.Current; - Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(OsuAction.LeftButton), delay); + Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(new KeyBindingPressEvent(GetContainingInputManager().CurrentState, OsuAction.LeftButton)), delay); }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 662cbaee68..0f362851a9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -20,6 +20,7 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osu.Game.Storyboards; using osu.Game.Tests.Visual; @@ -86,9 +87,9 @@ namespace osu.Game.Rulesets.Osu.Tests if (firstObject == null) return false; - var skinnable = firstObject.ApproachCircle.Child as SkinnableDrawable; + var skinnable = firstObject.ApproachCircle; - if (skin == null && skinnable?.Drawable is Sprite) + if (skin == null && skinnable?.Drawable is DefaultApproachCircle) // check for default skin provider return true; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index e47f82fb39..da879cb02e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -34,7 +34,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; - double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; + + double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000; + double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000; + double basePerformance = Math.Pow(Math.Pow(baseAimPerformance, 1.1) + Math.Pow(baseSpeedPerformance, 1.1), 1 / 1.1); + double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; HitWindows hitWindows = new OsuHitWindows(); hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 6269a41350..1be9b5bf2e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -127,9 +127,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return false; } - public bool OnPressed(PlatformAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case PlatformAction.Delete: return DeleteSelected(); @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return false; } - public void OnReleased(PlatformAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 46fc8f99b2..f05aea0df4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Judgements; @@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public OsuAction? HitAction => HitArea.HitAction; protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle; - public ApproachCircle ApproachCircle { get; private set; } + public SkinnableDrawable ApproachCircle { get; private set; } public HitReceptor HitArea { get; private set; } public SkinnableDrawable CirclePiece { get; private set; } @@ -74,8 +75,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - ApproachCircle = new ApproachCircle + ApproachCircle = new ProxyableSkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ApproachCircle), _ => new DefaultApproachCircle()) { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, Alpha = 0, Scale = new Vector2(4), } @@ -88,7 +92,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); - AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue); } protected override void LoadComplete() @@ -228,15 +231,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables CornerExponent = 2; } - public bool OnPressed(OsuAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case OsuAction.LeftButton: case OsuAction.RightButton: if (IsHovered && (Hit?.Invoke() ?? false)) { - HitAction = action; + HitAction = e.Action; return true; } @@ -246,7 +249,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return false; } - public void OnReleased(OsuAction action) + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } + + private class ProxyableSkinnableDrawable : SkinnableDrawable + { + public override bool RemoveWhenNotAlive => false; + + public ProxyableSkinnableDrawable(ISkinComponent component, Func defaultImplementation = null, ConfineMode confineMode = ConfineMode.NoScaling) + : base(component, defaultImplementation, confineMode) { } } diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 46e501758b..71657ed532 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu FollowPoint, Cursor, CursorTrail, + CursorParticles, SliderScorePoint, ReverseArrow, HitCircleText, @@ -18,5 +19,6 @@ namespace osu.Game.Rulesets.Osu SliderBall, SliderBody, SpinnerBody, + ApproachCircle, } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/ApproachCircle.cs deleted file mode 100644 index 62f00a2b49..0000000000 --- a/osu.Game.Rulesets.Osu/Skinning/Default/ApproachCircle.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Textures; -using osu.Game.Skinning; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Skinning.Default -{ - public class ApproachCircle : Container - { - public override bool RemoveWhenNotAlive => false; - - public ApproachCircle() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - RelativeSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - Child = new SkinnableApproachCircle(); - } - - private class SkinnableApproachCircle : SkinnableSprite - { - public SkinnableApproachCircle() - : base("Gameplay/osu/approachcircle") - { - } - - protected override Drawable CreateDefault(ISkinComponent component) - { - var drawable = base.CreateDefault(component); - - // account for the sprite being used for the default approach circle being taken from stable, - // when hitcircles have 5px padding on each size. this should be removed if we update the sprite. - drawable.Scale = new Vector2(128 / 118f); - - return drawable; - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs new file mode 100644 index 0000000000..a522367fe6 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public class DefaultApproachCircle : SkinnableSprite + { + private readonly IBindable accentColour = new Bindable(); + + [Resolved] + private DrawableHitObject drawableObject { get; set; } + + public DefaultApproachCircle() + : base("Gameplay/osu/approachcircle") + { + } + + [BackgroundDependencyLoader] + private void load() + { + accentColour.BindTo(drawableObject.AccentColour); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + accentColour.BindValueChanged(colour => Colour = colour.NewValue, true); + } + + protected override Drawable CreateDefault(ISkinComponent component) + { + var drawable = base.CreateDefault(component); + + // Although this is a non-legacy component, osu-resources currently stores approach circle as a legacy-like texture. + // See LegacyApproachCircle for documentation as to why this is required. + drawable.Scale = new Vector2(128 / 118f); + + return drawable; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs new file mode 100644 index 0000000000..6a2cb871b1 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Legacy +{ + public class LegacyApproachCircle : SkinnableSprite + { + private readonly IBindable accentColour = new Bindable(); + + [Resolved] + private DrawableHitObject drawableObject { get; set; } + + public LegacyApproachCircle() + : base("Gameplay/osu/approachcircle") + { + } + + [BackgroundDependencyLoader] + private void load() + { + accentColour.BindTo(drawableObject.AccentColour); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + accentColour.BindValueChanged(colour => Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true); + } + + protected override Drawable CreateDefault(ISkinComponent component) + { + var drawable = base.CreateDefault(component); + + // account for the sprite being used for the default approach circle being taken from stable, + // when hitcircles have 5px padding on each size. this should be removed if we update the sprite. + drawable.Scale = new Vector2(128 / 118f); + + return drawable; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs new file mode 100644 index 0000000000..2b0dfba1dd --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -0,0 +1,256 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Play; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Legacy +{ + public class LegacyCursorParticles : CompositeDrawable, IKeyBindingHandler + { + public bool Active => breakSpewer?.Active.Value == true || kiaiSpewer?.Active.Value == true; + + private LegacyCursorParticleSpewer breakSpewer; + private LegacyCursorParticleSpewer kiaiSpewer; + + [Resolved(canBeNull: true)] + private Player player { get; set; } + + [Resolved(canBeNull: true)] + private OsuPlayfield playfield { get; set; } + + [Resolved(canBeNull: true)] + private GameplayBeatmap gameplayBeatmap { get; set; } + + [Resolved(canBeNull: true)] + private GameplayClock gameplayClock { get; set; } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, OsuColour colours) + { + var texture = skin.GetTexture("star2"); + var starBreakAdditive = skin.GetConfig(OsuSkinColour.StarBreakAdditive)?.Value ?? new Color4(255, 182, 193, 255); + + if (texture != null) + { + // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation. + texture.ScaleAdjust *= 1.6f; + } + + InternalChildren = new[] + { + breakSpewer = new LegacyCursorParticleSpewer(texture, 20) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = starBreakAdditive, + Direction = SpewDirection.None, + }, + kiaiSpewer = new LegacyCursorParticleSpewer(texture, 60) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = starBreakAdditive, + Direction = SpewDirection.None, + }, + }; + + if (player != null) + ((IBindable)breakSpewer.Active).BindTo(player.IsBreakTime); + } + + protected override void Update() + { + if (playfield == null || gameplayBeatmap == null) return; + + DrawableHitObject kiaiHitObject = null; + + // Check whether currently in a kiai section first. This is only done as an optimisation to avoid enumerating AliveObjects when not necessary. + if (gameplayBeatmap.ControlPointInfo.EffectPointAt(gameplayBeatmap.Time.Current).KiaiMode) + kiaiHitObject = playfield.HitObjectContainer.AliveObjects.FirstOrDefault(isTracking); + + kiaiSpewer.Active.Value = kiaiHitObject != null; + } + + private bool isTracking(DrawableHitObject h) + { + if (!h.HitObject.Kiai) + return false; + + switch (h) + { + case DrawableSlider slider: + return slider.Tracking.Value; + + case DrawableSpinner spinner: + return spinner.RotationTracker.Tracking; + } + + return false; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + handleInput(e.Action, true); + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + handleInput(e.Action, false); + } + + private bool leftPressed; + private bool rightPressed; + + private void handleInput(OsuAction action, bool pressed) + { + switch (action) + { + case OsuAction.LeftButton: + leftPressed = pressed; + break; + + case OsuAction.RightButton: + rightPressed = pressed; + break; + } + + if (leftPressed && rightPressed) + breakSpewer.Direction = SpewDirection.Omni; + else if (leftPressed) + breakSpewer.Direction = SpewDirection.Left; + else if (rightPressed) + breakSpewer.Direction = SpewDirection.Right; + else + breakSpewer.Direction = SpewDirection.None; + } + + private class LegacyCursorParticleSpewer : ParticleSpewer, IRequireHighFrequencyMousePosition + { + private const int particle_duration_min = 300; + private const int particle_duration_max = 1000; + + public SpewDirection Direction { get; set; } + + protected override bool CanSpawnParticles => base.CanSpawnParticles && cursorScreenPosition.HasValue; + protected override float ParticleGravity => 240; + + public LegacyCursorParticleSpewer(Texture texture, int perSecond) + : base(texture, perSecond, particle_duration_max) + { + Active.BindValueChanged(_ => resetVelocityCalculation()); + } + + private Vector2? cursorScreenPosition; + private Vector2 cursorVelocity; + + private const double max_velocity_frame_length = 15; + private double velocityFrameLength; + private Vector2 totalPosDifference; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (cursorScreenPosition == null) + { + cursorScreenPosition = e.ScreenSpaceMousePosition; + return base.OnMouseMove(e); + } + + // calculate cursor velocity. + totalPosDifference += e.ScreenSpaceMousePosition - cursorScreenPosition.Value; + cursorScreenPosition = e.ScreenSpaceMousePosition; + + velocityFrameLength += Math.Abs(Clock.ElapsedFrameTime); + + if (velocityFrameLength > max_velocity_frame_length) + { + cursorVelocity = totalPosDifference / (float)velocityFrameLength; + + totalPosDifference = Vector2.Zero; + velocityFrameLength = 0; + } + + return base.OnMouseMove(e); + } + + private void resetVelocityCalculation() + { + cursorScreenPosition = null; + totalPosDifference = Vector2.Zero; + velocityFrameLength = 0; + } + + protected override FallingParticle CreateParticle() => + new FallingParticle + { + StartPosition = ToLocalSpace(cursorScreenPosition ?? Vector2.Zero), + Duration = RNG.NextSingle(particle_duration_min, particle_duration_max), + StartAngle = (float)(RNG.NextDouble() * 4 - 2), + EndAngle = RNG.NextSingle(-2f, 2f), + EndScale = RNG.NextSingle(2f), + Velocity = getVelocity(), + }; + + private Vector2 getVelocity() + { + Vector2 velocity = Vector2.Zero; + + switch (Direction) + { + case SpewDirection.Left: + velocity = new Vector2( + RNG.NextSingle(-460f, 0), + RNG.NextSingle(-40f, 40f) + ); + break; + + case SpewDirection.Right: + velocity = new Vector2( + RNG.NextSingle(0, 460f), + RNG.NextSingle(-40f, 40f) + ); + break; + + case SpewDirection.Omni: + velocity = new Vector2( + RNG.NextSingle(-460f, 460f), + RNG.NextSingle(-160f, 160f) + ); + break; + } + + velocity += cursorVelocity * 40; + + return velocity; + } + } + + private enum SpewDirection + { + None, + Left, + Right, + Omni, + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 41b0a88f11..87dfd33a62 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -89,6 +89,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; + case OsuSkinComponents.CursorParticles: + if (GetTexture("star2") != null) + return new LegacyCursorParticles(); + + return null; + case OsuSkinComponents.HitCircleText: if (!this.HasFont(LegacyFont.HitCircle)) return null; @@ -108,6 +114,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return new LegacyOldStyleSpinner(); return null; + + case OsuSkinComponents.ApproachCircle: + return new LegacyApproachCircle(); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs index f7ba8b9fc4..24f9217a5f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs @@ -9,5 +9,6 @@ namespace osu.Game.Rulesets.Osu.Skinning SliderBorder, SliderBall, SpinnerBackground, + StarBreakAdditive, } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 5812e8cf75..83bcc88e5f 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Configuration; @@ -42,7 +43,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor InternalChild = fadeContainer = new Container { RelativeSizeAxes = Axes.Both, - Child = cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling) + Children = new[] + { + cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling), + new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorParticles), confineMode: ConfineMode.NoScaling), + } }; } @@ -115,9 +120,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor (ActiveCursor as OsuCursor)?.Contract(); } - public bool OnPressed(OsuAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case OsuAction.LeftButton: case OsuAction.RightButton: @@ -129,9 +134,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor return false; } - public void OnReleased(OsuAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - switch (action) + switch (e.Action) { case OsuAction.LeftButton: case OsuAction.RightButton: diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index c1c2ea2299..2233a547b9 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -24,6 +24,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.UI { + [Cached] public class OsuPlayfield : Playfield { private readonly PlayfieldBorder playfieldBorder; diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 27d48d1296..4d4340936d 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -89,9 +89,9 @@ namespace osu.Game.Rulesets.Osu.UI base.OnHoverLost(e); } - public bool OnPressed(OsuAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case OsuAction.LeftButton: case OsuAction.RightButton: @@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.UI return false; } - public void OnReleased(OsuAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs index f048cad18c..6d4cac0ebe 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Scoring; @@ -37,6 +38,6 @@ namespace osu.Game.Rulesets.Taiko.Tests Result.Type = Type; } - public override bool OnPressed(TaikoAction action) => false; + public override bool OnPressed(KeyBindingPressEvent e) => false; } } diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs index 829bcf34a1..ea877c9e17 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; @@ -30,6 +31,6 @@ namespace osu.Game.Rulesets.Taiko.Tests nestedStrongHit.Result.Type = hitBoth ? Type : HitResult.Miss; } - public override bool OnPressed(TaikoAction action) => false; + public override bool OnPressed(KeyBindingPressEvent e) => false; } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 1253b7c8ae..0a325f174e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -36,24 +36,27 @@ namespace osu.Game.Rulesets.Taiko.Mods public TaikoFlashlight(TaikoPlayfield taikoPlayfield) { this.taikoPlayfield = taikoPlayfield; - FlashlightSize = new Vector2(0, getSizeFor(0)); + FlashlightSize = getSizeFor(0); AddLayout(flashlightProperties); } - private float getSizeFor(int combo) + private Vector2 getSizeFor(int combo) { + float size = default_flashlight_size; + if (combo > 200) - return default_flashlight_size * 0.8f; + size *= 0.8f; else if (combo > 100) - return default_flashlight_size * 0.9f; - else - return default_flashlight_size; + size *= 0.9f; + + // Preserve flashlight size through the playfield's aspect adjustment. + return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); } protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), getSizeFor(e.NewValue), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; @@ -64,7 +67,11 @@ namespace osu.Game.Rulesets.Taiko.Mods if (!flashlightProperties.IsValid) { - FlashlightPosition = taikoPlayfield.HitTarget.ToSpaceOfOtherDrawable(taikoPlayfield.HitTarget.OriginPosition, this); + FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre); + + ClearTransforms(targetMember: nameof(FlashlightSize)); + FlashlightSize = getSizeFor(Combo.Value); + flashlightProperties.Validate(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index d066abf767..521189d36c 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -112,7 +113,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollBody), _ => new ElongatedCirclePiece()); - public override bool OnPressed(TaikoAction action) => false; + public override bool OnPressed(KeyBindingPressEvent e) => false; private void onNewResult(DrawableHitObject obj, JudgementResult result) { @@ -196,7 +197,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); } - public override bool OnPressed(TaikoAction action) => false; + public override bool OnPressed(KeyBindingPressEvent e) => false; } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 0df45c424d..dc2ed200a1 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -4,6 +4,7 @@ using System; using JetBrains.Annotations; using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Skinning; @@ -61,9 +62,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - public override bool OnPressed(TaikoAction action) + public override bool OnPressed(KeyBindingPressEvent e) { - JudgementType = action == TaikoAction.LeftRim || action == TaikoAction.RightRim ? HitType.Rim : HitType.Centre; + JudgementType = e.Action == TaikoAction.LeftRim || e.Action == TaikoAction.RightRim ? HitType.Rim : HitType.Centre; return UpdateResult(true); } @@ -91,7 +92,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); } - public override bool OnPressed(TaikoAction action) => false; + public override bool OnPressed(KeyBindingPressEvent e) => false; } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 1e9fc187eb..97adc5f197 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -8,6 +8,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; @@ -145,19 +146,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = result); } - public override bool OnPressed(TaikoAction action) + public override bool OnPressed(KeyBindingPressEvent e) { if (pressHandledThisFrame) return true; if (Judged) return false; - validActionPressed = HitActions.Contains(action); + validActionPressed = HitActions.Contains(e.Action); // Only count this as handled if the new judgement is a hit var result = UpdateResult(true); if (IsHit) - HitAction = action; + HitAction = e.Action; // Regardless of whether we've hit or not, any secondary key presses in the same frame should be discarded // E.g. hitting a non-strong centre as a strong should not fall through and perform a hit on the next note @@ -165,11 +166,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return result; } - public override void OnReleased(TaikoAction action) + public override void OnReleased(KeyBindingReleaseEvent e) { - if (action == HitAction) + if (e.Action == HitAction) HitAction = null; - base.OnReleased(action); + base.OnReleased(e); } protected override void Update() @@ -265,7 +266,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = r.Judgement.MaxResult); } - public override bool OnPressed(TaikoAction action) + public override bool OnPressed(KeyBindingPressEvent e) { // Don't process actions until the main hitobject is hit if (!ParentHitObject.IsHit) @@ -276,7 +277,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return false; // Don't handle invalid hit action presses - if (!ParentHitObject.HitActions.Contains(action)) + if (!ParentHitObject.HitActions.Contains(e.Action)) return false; return UpdateResult(true); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 888f47d341..2d19296d06 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Skinning.Default; @@ -266,13 +267,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private bool? lastWasCentre; - public override bool OnPressed(TaikoAction action) + public override bool OnPressed(KeyBindingPressEvent e) { // Don't handle keys before the swell starts if (Time.Current < HitObject.StartTime) return false; - var isCentre = action == TaikoAction.LeftCentre || action == TaikoAction.RightCentre; + var isCentre = e.Action == TaikoAction.LeftCentre || e.Action == TaikoAction.RightCentre; // Ensure alternating centre and rim hits if (lastWasCentre == isCentre) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index 47fc7e5ab3..d4ea9ed29f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Skinning; @@ -34,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { } - public override bool OnPressed(TaikoAction action) => false; + public override bool OnPressed(KeyBindingPressEvent e) => false; protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollTick), _ => new TickPiece()); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 6a8d8a611c..eb64ba72f2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; @@ -76,9 +77,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// public Drawable CreateProxiedContent() => proxiedContent.CreateProxy(); - public abstract bool OnPressed(TaikoAction action); + public abstract bool OnPressed(KeyBindingPressEvent e); - public virtual void OnReleased(TaikoAction action) + public virtual void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 9d35093591..86be40dea8 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Skinning; @@ -141,16 +142,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy Centre.Texture = skin.GetTexture(@"taiko-drum-inner"); } - public bool OnPressed(TaikoAction action) + public bool OnPressed(KeyBindingPressEvent e) { Drawable target = null; - if (action == CentreAction) + if (e.Action == CentreAction) { target = Centre; sampleTriggerSource.Play(HitType.Centre); } - else if (action == RimAction) + else if (e.Action == RimAction) { target = Rim; sampleTriggerSource.Play(HitType.Rim); @@ -173,7 +174,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy return false; } - public void OnReleased(TaikoAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index ddfaf64549..861b800038 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.UI; @@ -151,19 +152,19 @@ namespace osu.Game.Rulesets.Taiko.UI [Resolved(canBeNull: true)] private GameplayClock gameplayClock { get; set; } - public bool OnPressed(TaikoAction action) + public bool OnPressed(KeyBindingPressEvent e) { Drawable target = null; Drawable back = null; - if (action == CentreAction) + if (e.Action == CentreAction) { target = centreHit; back = centre; sampleTriggerSource.Play(HitType.Centre); } - else if (action == RimAction) + else if (e.Action == RimAction) { target = rimHit; back = rim; @@ -195,7 +196,7 @@ namespace osu.Game.Rulesets.Taiko.UI return false; } - public void OnReleased(TaikoAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 8560a36fb4..a4bf8c92e3 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -64,6 +64,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsFalse(beatmapInfo.LetterboxInBreaks); Assert.IsFalse(beatmapInfo.SpecialStyle); Assert.IsFalse(beatmapInfo.WidescreenStoryboard); + Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); Assert.AreEqual(0, beatmapInfo.CountdownOffset); } diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 5dc25d6643..b2bd60d342 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -424,14 +424,14 @@ namespace osu.Game.Tests.Beatmaps.IO checkBeatmapCount(osu, 12); checkSingleReferencedFileCount(osu, 18); - var breakTemp = TestResources.GetTestBeatmapForImport(); + var brokenTempFilename = TestResources.GetTestBeatmapForImport(); MemoryStream brokenOsu = new MemoryStream(); - MemoryStream brokenOsz = new MemoryStream(await File.ReadAllBytesAsync(breakTemp)); + MemoryStream brokenOsz = new MemoryStream(await File.ReadAllBytesAsync(brokenTempFilename)); - File.Delete(breakTemp); + File.Delete(brokenTempFilename); - using (var outStream = File.Open(breakTemp, FileMode.CreateNew)) + using (var outStream = File.Open(brokenTempFilename, FileMode.CreateNew)) using (var zip = ZipArchive.Open(brokenOsz)) { zip.AddEntry("broken.osu", brokenOsu, false); @@ -441,7 +441,7 @@ namespace osu.Game.Tests.Beatmaps.IO // this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu. try { - await manager.Import(new ImportTask(breakTemp)); + await manager.Import(new ImportTask(brokenTempFilename)); } catch { @@ -456,6 +456,8 @@ namespace osu.Game.Tests.Beatmaps.IO checkSingleReferencedFileCount(osu, 18); Assert.AreEqual(1, loggedExceptionCount); + + File.Delete(brokenTempFilename); } finally { diff --git a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs index 7a5789f01a..ce6b3a68a5 100644 --- a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs +++ b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Game.Online.API; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; namespace osu.Game.Tests.Mods @@ -11,26 +12,42 @@ namespace osu.Game.Tests.Mods public class ModSettingsEqualityComparison { [Test] - public void Test() + public void TestAPIMod() { + var apiMod1 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.25 } }); + var apiMod2 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.26 } }); + var apiMod3 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.26 } }); + + Assert.That(apiMod1, Is.Not.EqualTo(apiMod2)); + Assert.That(apiMod2, Is.EqualTo(apiMod2)); + Assert.That(apiMod2, Is.EqualTo(apiMod3)); + Assert.That(apiMod3, Is.EqualTo(apiMod2)); + } + + [Test] + public void TestMod() + { + var ruleset = new OsuRuleset(); + var mod1 = new OsuModDoubleTime { SpeedChange = { Value = 1.25 } }; var mod2 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } }; var mod3 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } }; - var apiMod1 = new APIMod(mod1); - var apiMod2 = new APIMod(mod2); - var apiMod3 = new APIMod(mod3); + + var doubleConvertedMod1 = new APIMod(mod1).ToMod(ruleset); + var doulbeConvertedMod2 = new APIMod(mod2).ToMod(ruleset); + var doulbeConvertedMod3 = new APIMod(mod3).ToMod(ruleset); Assert.That(mod1, Is.Not.EqualTo(mod2)); - Assert.That(apiMod1, Is.Not.EqualTo(apiMod2)); + Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doulbeConvertedMod2)); Assert.That(mod2, Is.EqualTo(mod2)); - Assert.That(apiMod2, Is.EqualTo(apiMod2)); + Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod2)); Assert.That(mod2, Is.EqualTo(mod3)); - Assert.That(apiMod2, Is.EqualTo(apiMod3)); + Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod3)); Assert.That(mod3, Is.EqualTo(mod2)); - Assert.That(apiMod3, Is.EqualTo(apiMod2)); + Assert.That(doulbeConvertedMod3, Is.EqualTo(doulbeConvertedMod2)); } } } diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 7170a76b8b..839366d98e 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using NUnit.Framework; using osu.Framework.IO.Stores; +using osu.Framework.Testing; namespace osu.Game.Tests.Resources { @@ -11,6 +13,8 @@ namespace osu.Game.Tests.Resources { public const double QUICK_BEATMAP_LENGTH = 10000; + private static readonly TemporaryNativeStorage temp_storage = new TemporaryNativeStorage("TestResources"); + public static DllResourceStore GetStore() => new DllResourceStore(typeof(TestResources).Assembly); public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}"); @@ -25,7 +29,7 @@ namespace osu.Game.Tests.Resources /// A path to a copy of a beatmap archive (osz). Should be deleted after use. public static string GetQuickTestBeatmapForImport() { - var tempPath = Path.GetTempFileName() + ".osz"; + var tempPath = getTempFilename(); using (var stream = OpenResource("Archives/241526 Soleily - Renatus_virtual_quick.osz")) using (var newFile = File.Create(tempPath)) stream.CopyTo(newFile); @@ -41,7 +45,7 @@ namespace osu.Game.Tests.Resources /// A path to a copy of a beatmap archive (osz). Should be deleted after use. public static string GetTestBeatmapForImport(bool virtualTrack = false) { - var tempPath = Path.GetTempFileName() + ".osz"; + var tempPath = getTempFilename(); using (var stream = GetTestBeatmapStream(virtualTrack)) using (var newFile = File.Create(tempPath)) @@ -50,5 +54,7 @@ namespace osu.Game.Tests.Resources Assert.IsTrue(File.Exists(tempPath)); return tempPath; } + + private static string getTempFilename() => temp_storage.GetFullPath(Guid.NewGuid() + ".osz"); } } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 0f3d413a7d..c81a1abfbc 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -7,28 +7,19 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Dialog; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Components.Menus; -using osu.Game.Screens.Menu; using osu.Game.Tests.Beatmaps.IO; -using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { - public class TestSceneDifficultySwitching : ScreenTestScene + public class TestSceneDifficultySwitching : EditorTestScene { - private BeatmapSetInfo importedBeatmapSet; - private Editor editor; + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); - // required for screen transitions to work properly - // (see comment in EditorLoader.LogoArriving). - [Cached] - private OsuLogo logo = new OsuLogo - { - Alpha = 0 - }; + protected override bool IsolateSavingFromDatabase => false; [Resolved] private OsuGameBase game { get; set; } @@ -36,20 +27,18 @@ namespace osu.Game.Tests.Visual.Editing [Resolved] private BeatmapManager beatmaps { get; set; } - [BackgroundDependencyLoader] - private void load() => Add(logo); + private BeatmapSetInfo importedBeatmapSet; - [SetUpSteps] - public void SetUp() + public override void SetUpSteps() { AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result); + base.SetUpSteps(); + } - AddStep("set current beatmap", () => Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First())); - AddStep("push loader", () => Stack.Push(new EditorLoader())); - - AddUntilStep("wait for editor push", () => Stack.CurrentScreen is Editor); - AddStep("store editor", () => editor = (Editor)Stack.CurrentScreen); - AddUntilStep("wait for editor to load", () => editor.IsLoaded); + protected override void LoadEditor() + { + Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First()); + base.LoadEditor(); } [Test] @@ -66,17 +55,66 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("stack empty", () => Stack.CurrentScreen == null); } + [Test] + public void TestClockPositionPreservedBetweenSwitches() + { + BeatmapInfo targetDifficulty = null; + AddStep("seek editor to 00:05:00", () => EditorClock.Seek(5000)); + + AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); + switchToDifficulty(() => targetDifficulty); + confirmEditingBeatmap(() => targetDifficulty); + AddAssert("editor clock at 00:05:00", () => EditorClock.CurrentTime == 5000); + + AddStep("exit editor", () => Stack.Exit()); + // ensure editor loader didn't resume. + AddAssert("stack empty", () => Stack.CurrentScreen == null); + } + + [Test] + public void TestClipboardPreservedAfterSwitch([Values] bool sameRuleset) + { + BeatmapInfo targetDifficulty = null; + + AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First())); + AddStep("copy object", () => Editor.Copy()); + + AddStep("set target difficulty", () => + { + targetDifficulty = sameRuleset + ? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID == Beatmap.Value.BeatmapInfo.RulesetID) + : importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID != Beatmap.Value.BeatmapInfo.RulesetID); + }); + switchToDifficulty(() => targetDifficulty); + confirmEditingBeatmap(() => targetDifficulty); + + AddAssert("no objects selected", () => !EditorBeatmap.SelectedHitObjects.Any()); + AddStep("paste object", () => Editor.Paste()); + + if (sameRuleset) + AddAssert("object was pasted", () => EditorBeatmap.SelectedHitObjects.Any()); + else + AddAssert("object was not pasted", () => !EditorBeatmap.SelectedHitObjects.Any()); + + AddStep("exit editor", () => Stack.Exit()); + + if (sameRuleset) + { + AddUntilStep("prompt for save dialog shown", () => DialogOverlay.CurrentDialog is PromptForSaveDialog); + AddStep("discard changes", () => ((PromptForSaveDialog)DialogOverlay.CurrentDialog).PerformOkAction()); + } + + // ensure editor loader didn't resume. + AddAssert("stack empty", () => Stack.CurrentScreen == null); + } + [Test] public void TestPreventSwitchDueToUnsavedChanges() { BeatmapInfo targetDifficulty = null; PromptForSaveDialog saveDialog = null; - AddStep("remove first hitobject", () => - { - var editorBeatmap = editor.ChildrenOfType().Single(); - editorBeatmap.RemoveAt(0); - }); + AddStep("remove first hitobject", () => EditorBeatmap.RemoveAt(0)); AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); switchToDifficulty(() => targetDifficulty); @@ -105,11 +143,7 @@ namespace osu.Game.Tests.Visual.Editing BeatmapInfo targetDifficulty = null; PromptForSaveDialog saveDialog = null; - AddStep("remove first hitobject", () => - { - var editorBeatmap = editor.ChildrenOfType().Single(); - editorBeatmap.RemoveAt(0); - }); + AddStep("remove first hitobject", () => EditorBeatmap.RemoveAt(0)); AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); switchToDifficulty(() => targetDifficulty); @@ -132,39 +166,12 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("stack empty", () => Stack.CurrentScreen == null); } - private void switchToDifficulty(Func difficulty) - { - AddUntilStep("wait for menubar to load", () => editor.ChildrenOfType().Any()); - AddStep("open file menu", () => - { - var menuBar = editor.ChildrenOfType().Single(); - var fileMenu = menuBar.ChildrenOfType().First(); - InputManager.MoveMouseTo(fileMenu); - InputManager.Click(MouseButton.Left); - }); - - AddStep("open difficulty menu", () => - { - var difficultySelector = - editor.ChildrenOfType().Single(item => item.Item.Text.Value.ToString().Contains("Change difficulty")); - InputManager.MoveMouseTo(difficultySelector); - }); - AddWaitStep("wait for open", 3); - - AddStep("switch to target difficulty", () => - { - var difficultyMenuItem = - editor.ChildrenOfType() - .Last(item => item.Item is DifficultyMenuItem difficultyItem && difficultyItem.Beatmap.Equals(difficulty.Invoke())); - InputManager.MoveMouseTo(difficultyMenuItem); - InputManager.Click(MouseButton.Left); - }); - } + private void switchToDifficulty(Func difficulty) => AddStep("switch to difficulty", () => Editor.SwitchToDifficulty(difficulty.Invoke())); private void confirmEditingBeatmap(Func targetDifficulty) { AddUntilStep("current beatmap is correct", () => Beatmap.Value.BeatmapInfo.Equals(targetDifficulty.Invoke())); - AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + AddUntilStep("current screen is editor", () => Stack.CurrentScreen == Editor && Editor?.IsLoaded == true); } } } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index b6ae91844a..440d66ff9f 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -11,6 +11,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; using osu.Game.Tests.Resources; using SharpCompress.Archives; @@ -55,6 +56,9 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestExitWithoutSave() { + EditorBeatmap editorBeatmap = null; + + AddStep("store editor beatmap", () => editorBeatmap = EditorBeatmap); AddStep("exit without save", () => { Editor.Exit(); @@ -62,7 +66,7 @@ namespace osu.Game.Tests.Visual.Editing }); AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen()); - AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true); + AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs index b7dcad3825..00b5c38e20 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . 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.Allocation; using osu.Game.Configuration; @@ -71,7 +70,7 @@ namespace osu.Game.Tests.Visual.Gameplay var working = CreateWorkingBeatmap(rulesetInfo); Beatmap.Value = working; - SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + SelectedMods.Value = new[] { ruleset.CreateMod() }; Player = CreatePlayer(ruleset); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs index 6de85499c5..0a39d94027 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; @@ -80,13 +81,13 @@ namespace osu.Game.Tests.Visual.Gameplay { public bool ReceivedAction; - public bool OnPressed(TestAction action) + public bool OnPressed(KeyBindingPressEvent e) { - ReceivedAction = action == TestAction.Down; + ReceivedAction = e.Action == TestAction.Down; return true; } - public void OnReleased(TestAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs new file mode 100644 index 0000000000..ce5cd629e0 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs @@ -0,0 +1,128 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [TestFixture] + public class TestSceneParticleSpewer : OsuTestScene + { + private TestParticleSpewer spewer; + + [Resolved] + private SkinManager skinManager { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + Child = spewer = createSpewer(); + + AddToggleStep("toggle spawning", value => spewer.Active.Value = value); + AddSliderStep("particle gravity", 0f, 1f, 0f, value => spewer.Gravity = value); + AddSliderStep("particle velocity", 0f, 1f, 0.5f, value => spewer.MaxVelocity = value); + AddStep("move to new location", () => + { + spewer.TransformTo(nameof(spewer.SpawnPosition), new Vector2(RNG.NextSingle(), RNG.NextSingle()), 1000, Easing.Out); + }); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create spewer", () => Child = spewer = createSpewer()); + } + + [Test] + public void TestPresence() + { + AddStep("start spewer", () => spewer.Active.Value = true); + AddAssert("is present", () => spewer.IsPresent); + + AddWaitStep("wait for some particles", 3); + AddStep("stop spewer", () => spewer.Active.Value = false); + + AddWaitStep("wait for clean screen", 8); + AddAssert("is not present", () => !spewer.IsPresent); + } + + [Test] + public void TestTimeJumps() + { + ManualClock testClock = new ManualClock(); + + AddStep("prepare clock", () => + { + testClock.CurrentTime = TestParticleSpewer.MAX_DURATION * -3; + spewer.Clock = new FramedClock(testClock); + }); + AddStep("start spewer", () => spewer.Active.Value = true); + AddAssert("spawned first particle", () => spewer.TotalCreatedParticles == 1); + + AddStep("move clock forward", () => testClock.CurrentTime = TestParticleSpewer.MAX_DURATION * 3); + AddAssert("spawned second particle", () => spewer.TotalCreatedParticles == 2); + + AddStep("move clock backwards", () => testClock.CurrentTime = TestParticleSpewer.MAX_DURATION * -1); + AddAssert("spawned third particle", () => spewer.TotalCreatedParticles == 3); + } + + private TestParticleSpewer createSpewer() => + new TestParticleSpewer(skinManager.DefaultLegacySkin.GetTexture("star2")) + { + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, + Position = new Vector2(0.5f), + Size = new Vector2(0.5f), + }; + + private class TestParticleSpewer : ParticleSpewer + { + public const int MAX_DURATION = 1500; + private const int rate = 250; + + public int TotalCreatedParticles { get; private set; } + + public float Gravity; + + public float MaxVelocity = 0.25f; + + public Vector2 SpawnPosition { get; set; } = new Vector2(0.5f); + + protected override float ParticleGravity => Gravity; + + public TestParticleSpewer(Texture texture) + : base(texture, rate, MAX_DURATION) + { + } + + protected override FallingParticle CreateParticle() + { + TotalCreatedParticles++; + + return new FallingParticle + { + Velocity = new Vector2( + RNG.NextSingle(-MaxVelocity, MaxVelocity), + RNG.NextSingle(-MaxVelocity, MaxVelocity) + ), + StartPosition = SpawnPosition, + Duration = RNG.NextSingle(MAX_DURATION), + StartAngle = RNG.NextSingle(MathF.PI * 2), + EndAngle = RNG.NextSingle(MathF.PI * 2), + EndScale = RNG.NextSingle(0.5f, 1.5f) + }; + } + } + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index b38f7a998d..0a3fedaf8e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -54,7 +54,11 @@ namespace osu.Game.Tests.Visual.Gameplay { recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = recorder = new TestReplayRecorder(new Score { Replay = replay }) + Recorder = recorder = new TestReplayRecorder(new Score + { + Replay = replay, + ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo } + }) { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), }, @@ -222,13 +226,13 @@ namespace osu.Game.Tests.Visual.Gameplay return base.OnMouseMove(e); } - public bool OnPressed(TestAction action) + public bool OnPressed(KeyBindingPressEvent e) { box.Colour = Color4.White; return true; } - public void OnReleased(TestAction action) + public void OnReleased(KeyBindingReleaseEvent e) { box.Colour = Color4.Black; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs index 6e338b7202..dfd5e2dc58 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs @@ -45,7 +45,11 @@ namespace osu.Game.Tests.Visual.Gameplay { recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = new TestReplayRecorder(new Score { Replay = replay }) + Recorder = new TestReplayRecorder(new Score + { + Replay = replay, + ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo } + }) { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos) }, @@ -155,13 +159,13 @@ namespace osu.Game.Tests.Visual.Gameplay return base.OnMouseMove(e); } - public bool OnPressed(TestAction action) + public bool OnPressed(KeyBindingPressEvent e) { box.Colour = Color4.White; return true; } - public void OnReleased(TestAction action) + public void OnReleased(KeyBindingReleaseEvent e) { box.Colour = Color4.Black; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index bb577886cc..6f5f774758 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -279,13 +279,13 @@ namespace osu.Game.Tests.Visual.Gameplay return base.OnMouseMove(e); } - public bool OnPressed(TestAction action) + public bool OnPressed(KeyBindingPressEvent e) { box.Colour = Color4.White; return true; } - public void OnReleased(TestAction action) + public void OnReleased(KeyBindingReleaseEvent e) { box.Colour = Color4.Black; } @@ -354,7 +354,7 @@ namespace osu.Game.Tests.Visual.Gameplay internal class TestReplayRecorder : ReplayRecorder { public TestReplayRecorder() - : base(new Score()) + : base(new Score { ScoreInfo = { Beatmap = new BeatmapInfo() } }) { } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs new file mode 100644 index 0000000000..5fdadfc2fb --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . 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.Graphics; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Login; + +namespace osu.Game.Tests.Visual.Menus +{ + [TestFixture] + public class TestSceneLoginPanel : OsuManualInputManagerTestScene + { + private LoginPanel loginPanel; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create login dialog", () => + { + Add(loginPanel = new LoginPanel + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + }); + }); + } + + [Test] + public void TestBasicLogin() + { + AddStep("logout", () => API.Logout()); + + AddStep("enter password", () => loginPanel.ChildrenOfType().First().Text = "password"); + AddStep("submit", () => loginPanel.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick()); + } + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs index 99f6ab1ae1..b7da31a2b5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Framework.Testing; @@ -51,6 +52,24 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("room join password correct", () => lastJoinedPassword == null); } + [Test] + public void TestPopoverHidesOnBackButton() + { + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); + AddStep("select room", () => InputManager.Key(Key.Down)); + AddStep("attempt join room", () => InputManager.Key(Key.Enter)); + + AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType().Any()); + + AddAssert("textbox has focus", () => InputManager.FocusedDrawable is OsuPasswordTextBox); + + AddStep("hit escape", () => InputManager.Key(Key.Escape)); + AddAssert("textbox lost focus", () => InputManager.FocusedDrawable is SearchTextBox); + + AddStep("hit escape", () => InputManager.Key(Key.Escape)); + AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType().Any()); + } + [Test] public void TestPopoverHidesOnLeavingScreen() { @@ -64,7 +83,41 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - public void TestJoinRoomWithPassword() + public void TestJoinRoomWithIncorrectPasswordViaButton() + { + DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; + + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); + AddStep("select room", () => InputManager.Key(Key.Down)); + AddStep("attempt join room", () => InputManager.Key(Key.Enter)); + AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); + AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "wrong"); + AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().TriggerClick()); + + AddAssert("room not joined", () => loungeScreen.IsCurrentScreen()); + AddUntilStep("password prompt still visible", () => passwordEntryPopover.State.Value == Visibility.Visible); + AddAssert("textbox still focused", () => InputManager.FocusedDrawable is OsuPasswordTextBox); + } + + [Test] + public void TestJoinRoomWithIncorrectPasswordViaEnter() + { + DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; + + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); + AddStep("select room", () => InputManager.Key(Key.Down)); + AddStep("attempt join room", () => InputManager.Key(Key.Enter)); + AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); + AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "wrong"); + AddStep("press enter", () => InputManager.Key(Key.Enter)); + + AddAssert("room not joined", () => loungeScreen.IsCurrentScreen()); + AddUntilStep("password prompt still visible", () => passwordEntryPopover.State.Value == Visibility.Visible); + AddAssert("textbox still focused", () => InputManager.FocusedDrawable is OsuPasswordTextBox); + } + + [Test] + public void TestJoinRoomWithCorrectPassword() { DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index 4e08ffef17..44a8d7b439 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; @@ -15,11 +16,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { SelectedRoom.Value = new Room(); - Child = new MultiplayerMatchFooter + Child = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Height = 50 + RelativeSizeAxes = Axes.X, + Height = 50, + Child = new MultiplayerMatchFooter() }; }); } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs index b8232837b5..e2baa82ba0 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs @@ -75,7 +75,6 @@ namespace osu.Game.Tests.Visual.Navigation typeof(FileStore), typeof(ScoreManager), typeof(BeatmapManager), - typeof(SettingsStore), typeof(RulesetConfigCache), typeof(OsuColour), typeof(IBindable), @@ -97,9 +96,6 @@ namespace osu.Game.Tests.Visual.Navigation { AddStep("create game", () => { - game = new OsuGame(); - game.SetHost(host); - Children = new Drawable[] { new Box @@ -107,8 +103,9 @@ namespace osu.Game.Tests.Visual.Navigation RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - game }; + + AddGame(game = new OsuGame()); }); AddUntilStep("wait for load", () => game.IsLoaded); diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index b536233ff0..aeb800f58a 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -15,6 +15,7 @@ using osu.Game.Overlays.Mods; using osu.Game.Overlays.Toolbar; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.Play; @@ -76,7 +77,13 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("press enter", () => InputManager.Key(Key.Enter)); - AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); + AddUntilStep("wait for player", () => + { + // dismiss any notifications that may appear (ie. muted notification). + clickMouseInCentre(); + return (player = Game.ScreenStack.CurrentScreen as Player) != null; + }); + AddAssert("retry count is 0", () => player.RestartCount == 0); AddStep("attempt to retry", () => player.ChildrenOfType().First().Action()); @@ -103,7 +110,14 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModDoubleTime { SpeedChange = { Value = 2 } } }); AddStep("press enter", () => InputManager.Key(Key.Enter)); - AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); + + AddUntilStep("wait for player", () => + { + // dismiss any notifications that may appear (ie. muted notification). + clickMouseInCentre(); + return (player = Game.ScreenStack.CurrentScreen as Player) != null; + }); + AddUntilStep("wait for track playing", () => beatmap().Track.IsRunning); AddStep("seek to near end", () => player.ChildrenOfType().First().Seek(beatmap().Beatmap.HitObjects[^1].StartTime - 1000)); AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded); @@ -130,7 +144,13 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("press enter", () => InputManager.Key(Key.Enter)); - AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); + AddUntilStep("wait for player", () => + { + // dismiss any notifications that may appear (ie. muted notification). + clickMouseInCentre(); + return (player = Game.ScreenStack.CurrentScreen as Player) != null; + }); + AddUntilStep("wait for fail", () => player.HasFailed); AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying); @@ -388,6 +408,27 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("now playing is hidden", () => nowPlayingOverlay.State.Value == Visibility.Hidden); } + [Test] + public void TestExitGameFromSongSelect() + { + PushAndConfirm(() => new TestPlaySongSelect()); + exitViaEscapeAndConfirm(); + + pushEscape(); // returns to osu! logo + + AddStep("Hold escape", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroTriangles); + AddStep("Release escape", () => InputManager.ReleaseKey(Key.Escape)); + AddUntilStep("Wait for game exit", () => Game.ScreenStack.CurrentScreen == null); + AddStep("test dispose doesn't crash", () => Game.Dispose()); + } + + private void clickMouseInCentre() + { + InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre); + InputManager.Click(MouseButton.Left); + } + private void pushEscape() => AddStep("Press escape", () => InputManager.Key(Key.Escape)); diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 5bfb676f81..963809ebe1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -17,10 +17,11 @@ using osu.Game.Overlays.BeatmapListing; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Users; +using osuTK.Input; namespace osu.Game.Tests.Visual.Online { - public class TestSceneBeatmapListingOverlay : OsuTestScene + public class TestSceneBeatmapListingOverlay : OsuManualInputManagerTestScene { private readonly List setsForResponse = new List(); @@ -28,27 +29,33 @@ namespace osu.Game.Tests.Visual.Online private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType().Single(); - [BackgroundDependencyLoader] - private void load() + [SetUpSteps] + public void SetUpSteps() { - Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } }; - - ((DummyAPIAccess)API).HandleRequest = req => + AddStep("setup overlay", () => { - if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false; - - searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse - { - BeatmapSets = setsForResponse, - }); - - return true; - }; + Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } }; + setsForResponse.Clear(); + }); AddStep("initialize dummy", () => { + var api = (DummyAPIAccess)API; + + api.HandleRequest = req => + { + if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false; + + searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse + { + BeatmapSets = setsForResponse, + }); + + return true; + }; + // non-supporter user - ((DummyAPIAccess)API).LocalUser.Value = new User + api.LocalUser.Value = new User { Username = "TestBot", Id = API.LocalUser.Value.Id + 1, @@ -56,6 +63,51 @@ namespace osu.Game.Tests.Visual.Online }); } + [Test] + public void TestHideViaBack() + { + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + AddStep("hide", () => InputManager.Key(Key.Escape)); + AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden); + } + + [Test] + public void TestHideViaBackWithSearch() + { + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + + AddStep("search something", () => overlay.ChildrenOfType().First().Text = "search"); + + AddStep("kill search", () => InputManager.Key(Key.Escape)); + + AddAssert("search textbox empty", () => string.IsNullOrEmpty(overlay.ChildrenOfType().First().Text)); + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + + AddStep("hide", () => InputManager.Key(Key.Escape)); + AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden); + } + + [Test] + public void TestHideViaBackWithScrolledSearch() + { + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + + AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, 100).ToArray())); + + AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any(d => d.IsPresent)); + + AddStep("scroll to bottom", () => overlay.ChildrenOfType().First().ScrollToEnd()); + + AddStep("kill search", () => InputManager.Key(Key.Escape)); + + AddUntilStep("search textbox empty", () => string.IsNullOrEmpty(overlay.ChildrenOfType().First().Text)); + AddUntilStep("is scrolled to top", () => overlay.ChildrenOfType().First().Current == 0); + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + + AddStep("hide", () => InputManager.Key(Key.Escape)); + AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden); + } + [Test] public void TestNoBeatmapsPlaceholder() { @@ -63,7 +115,7 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); - AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any(d => d.IsPresent)); AddStep("fetch for 0 beatmaps", () => fetchFor()); AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); @@ -193,13 +245,15 @@ namespace osu.Game.Tests.Visual.Online noPlaceholderShown(); } + private static int searchCount; + private void fetchFor(params BeatmapSetInfo[] beatmaps) { setsForResponse.Clear(); setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b))); // trigger arbitrary change for fetching. - searchControl.Query.TriggerChange(); + searchControl.Query.Value = $"search {searchCount++}"; } private void setRankAchievedFilter(ScoreRank[] ranks) @@ -229,8 +283,8 @@ namespace osu.Game.Tests.Visual.Online private void noPlaceholderShown() { AddUntilStep("no placeholder shown", () => - !overlay.ChildrenOfType().Any() - && !overlay.ChildrenOfType().Any()); + !overlay.ChildrenOfType().Any(d => d.IsPresent) + && !overlay.ChildrenOfType().Any(d => d.IsPresent)); } private class TestAPIBeatmapSet : APIBeatmapSet diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index edc1696456..f420ad976b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -21,6 +21,8 @@ namespace osu.Game.Tests.Visual.Online protected override bool UseOnlineAPI => true; + private int nextBeatmapSetId = 1; + public TestSceneBeatmapSetOverlay() { Add(overlay = new TestBeatmapSetOverlay()); @@ -240,12 +242,23 @@ namespace osu.Game.Tests.Visual.Online { AddStep("show explicit map", () => { - var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + var beatmapSet = getBeatmapSet(); beatmapSet.OnlineInfo.HasExplicitContent = true; overlay.ShowBeatmapSet(beatmapSet); }); } + [Test] + public void TestFeaturedBeatmap() + { + AddStep("show featured map", () => + { + var beatmapSet = getBeatmapSet(); + beatmapSet.OnlineInfo.TrackId = 1; + overlay.ShowBeatmapSet(beatmapSet); + }); + } + [Test] public void TestHide() { @@ -308,6 +321,14 @@ namespace osu.Game.Tests.Visual.Online }; } + private BeatmapSetInfo getBeatmapSet() + { + var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + // Make sure the overlay is reloaded (see `BeatmapSetInfo.Equals`). + beatmapSet.OnlineBeatmapSetID = nextBeatmapSetId++; + return beatmapSet; + } + private void downloadAssert(bool shown) { AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.HeaderContent.DownloadButtonsVisible == shown); diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 7cfca31167..609e637914 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -85,6 +86,22 @@ namespace osu.Game.Tests.Visual.Online case JoinChannelRequest joinChannel: joinChannel.TriggerSuccess(); return true; + + case GetUserRequest getUser: + if (getUser.Lookup.Equals("some body", StringComparison.OrdinalIgnoreCase)) + { + getUser.TriggerSuccess(new User + { + Username = "some body", + Id = 1, + }); + } + else + { + getUser.TriggerFailure(new Exception()); + } + + return true; } return false; @@ -322,6 +339,27 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Current channel is channel 1", () => currentChannel == channel1); } + [Test] + public void TestChatCommand() + { + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddStep("Open chat with user", () => channelManager.PostCommand("chat some body")); + AddAssert("PM channel is selected", () => + channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body"); + + AddStep("Open chat with non-existent user", () => channelManager.PostCommand("chat nobody")); + AddAssert("Last message is error", () => channelManager.CurrentChannel.Value.Messages.Last() is ErrorMessage); + + // Make sure no unnecessary requests are made when the PM channel is already open. + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + AddStep("Unregister request handling", () => ((DummyAPIAccess)API).HandleRequest = null); + AddStep("Open chat with user", () => channelManager.PostCommand("chat some body")); + AddAssert("PM channel is selected", () => + channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body"); + } + private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs index fd5f306e07..722010ace2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs @@ -99,16 +99,23 @@ namespace osu.Game.Tests.Visual.Online [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - var normal = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + var normal = getBeatmapSet(); normal.OnlineInfo.HasVideo = true; normal.OnlineInfo.HasStoryboard = true; var undownloadable = getUndownloadableBeatmapSet(); var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets); - var explicitMap = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + var explicitMap = getBeatmapSet(); explicitMap.OnlineInfo.HasExplicitContent = true; + var featuredMap = getBeatmapSet(); + featuredMap.OnlineInfo.TrackId = 1; + + var explicitFeaturedMap = getBeatmapSet(); + explicitFeaturedMap.OnlineInfo.HasExplicitContent = true; + explicitFeaturedMap.OnlineInfo.TrackId = 2; + Child = new BasicScrollContainer { RelativeSizeAxes = Axes.Both, @@ -125,13 +132,19 @@ namespace osu.Game.Tests.Visual.Online new GridBeatmapPanel(undownloadable), new GridBeatmapPanel(manyDifficulties), new GridBeatmapPanel(explicitMap), + new GridBeatmapPanel(featuredMap), + new GridBeatmapPanel(explicitFeaturedMap), new ListBeatmapPanel(normal), new ListBeatmapPanel(undownloadable), new ListBeatmapPanel(manyDifficulties), - new ListBeatmapPanel(explicitMap) + new ListBeatmapPanel(explicitMap), + new ListBeatmapPanel(featuredMap), + new ListBeatmapPanel(explicitFeaturedMap) }, }, }; + + BeatmapSetInfo getBeatmapSet() => CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; } } } diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 57ba051214..168d9fafcf 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.Settings { AddAssert($"Check {name} is bound to {keyName}", () => { - var firstRow = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text == name)); + var firstRow = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text.ToString() == name)); var firstButton = firstRow.ChildrenOfType().First(); return firstButton.Text.Text == keyName; @@ -247,7 +247,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep($"Scroll to {name}", () => { - var firstRow = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text == name)); + var firstRow = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text.ToString() == name)); firstButton = firstRow.ChildrenOfType().First(); panel.ChildrenOfType().First().ScrollTo(firstButton); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 40b2f66d74..dcc2111ad3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select EZ mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().Single() }; + SelectedMods.Value = new[] { ruleset.CreateMod() }; }); AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue)); @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select HR mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().Single() }; + SelectedMods.Value = new[] { ruleset.CreateMod() }; }); AddAssert("circle size bar is red", () => barIsRed(advancedStats.FirstValue)); @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select unchanged Difficulty Adjust mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - var difficultyAdjustMod = ruleset.GetAllMods().OfType().Single(); + var difficultyAdjustMod = ruleset.CreateMod(); difficultyAdjustMod.ReadFromDifficulty(advancedStats.Beatmap.BaseDifficulty); SelectedMods.Value = new[] { difficultyAdjustMod }; }); @@ -142,7 +142,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select changed Difficulty Adjust mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - var difficultyAdjustMod = ruleset.GetAllMods().OfType().Single(); + var difficultyAdjustMod = ruleset.CreateMod(); var originalDifficulty = advancedStats.Beatmap.BaseDifficulty; difficultyAdjustMod.ReadFromDifficulty(originalDifficulty); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 66ac700c51..f91d3f595b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("setup display", () => { - var randomMods = Ruleset.Value.CreateInstance().GetAllMods().OrderBy(_ => RNG.Next()).Take(5).ToList(); + var randomMods = Ruleset.Value.CreateInstance().CreateAllMods().OrderBy(_ => RNG.Next()).Take(5).ToList(); OsuLogo logo = new OsuLogo { Scale = new Vector2(0.15f) }; diff --git a/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs index c55988d1bb..7729ad0ff3 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs @@ -5,8 +5,8 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Screens; -using osu.Framework.Testing; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; using osu.Game.Screens; using osu.Game.Screens.Play; using osuTK.Graphics; @@ -18,10 +18,18 @@ namespace osu.Game.Tests.Visual { private TestOsuScreenStack stack; - [SetUpSteps] - public void SetUpSteps() + [Cached] + private MusicController musicController = new MusicController(); + + [BackgroundDependencyLoader] + private void load() { - AddStep("Create new screen stack", () => { Child = stack = new TestOsuScreenStack { RelativeSizeAxes = Axes.Both }; }); + stack = new TestOsuScreenStack { RelativeSizeAxes = Axes.Both }; + + Add(musicController); + Add(stack); + + LoadComponent(stack); } [Test] @@ -42,6 +50,44 @@ namespace osu.Game.Tests.Visual AddAssert("Parallax is off", () => stack.ParallaxAmount == 0); } + [Test] + public void AllowTrackAdjustmentsTest() + { + AddStep("push allowing screen", () => stack.Push(loadNewScreen())); + AddAssert("allows adjustments 1", () => musicController.AllowTrackAdjustments); + + AddStep("push inheriting screen", () => stack.Push(loadNewScreen())); + AddAssert("allows adjustments 2", () => musicController.AllowTrackAdjustments); + + AddStep("push disallowing screen", () => stack.Push(loadNewScreen())); + AddAssert("disallows adjustments 3", () => !musicController.AllowTrackAdjustments); + + AddStep("push inheriting screen", () => stack.Push(loadNewScreen())); + AddAssert("disallows adjustments 4", () => !musicController.AllowTrackAdjustments); + + AddStep("push inheriting screen", () => stack.Push(loadNewScreen())); + AddAssert("disallows adjustments 5", () => !musicController.AllowTrackAdjustments); + + AddStep("push allowing screen", () => stack.Push(loadNewScreen())); + AddAssert("allows adjustments 6", () => musicController.AllowTrackAdjustments); + + // Now start exiting from screens + AddStep("exit screen", () => stack.Exit()); + AddAssert("disallows adjustments 7", () => !musicController.AllowTrackAdjustments); + + AddStep("exit screen", () => stack.Exit()); + AddAssert("disallows adjustments 8", () => !musicController.AllowTrackAdjustments); + + AddStep("exit screen", () => stack.Exit()); + AddAssert("disallows adjustments 9", () => !musicController.AllowTrackAdjustments); + + AddStep("exit screen", () => stack.Exit()); + AddAssert("allows adjustments 10", () => musicController.AllowTrackAdjustments); + + AddStep("exit screen", () => stack.Exit()); + AddAssert("allows adjustments 11", () => musicController.AllowTrackAdjustments); + } + public class TestScreen : ScreenWithBeatmapBackground { private readonly string screenText; @@ -78,5 +124,26 @@ namespace osu.Game.Tests.Visual { public new float ParallaxAmount => base.ParallaxAmount; } + + private class AllowScreen : OsuScreen + { + public override bool? AllowTrackAdjustments => true; + } + + public class DisallowScreen : OsuScreen + { + public override bool? AllowTrackAdjustments => false; + } + + private class InheritScreen : OsuScreen + { + } + + private OsuScreen loadNewScreen() where T : OsuScreen, new() + { + OsuScreen screen = new T(); + LoadComponent(screen); + return screen; + } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs index 8f057c663b..10eab148de 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface Width = 200, Current = { - Value = new OsuRuleset().GetAllMods().ToArray(), + Value = new OsuRuleset().CreateAllMods().ToArray(), } }; }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs index e7fa7d9235..513eb2fafc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . 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.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; @@ -17,5 +19,16 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime())); AddStep("change mod", () => icon.Mod = new OsuModEasy()); } + + [Test] + public void TestInterfaceModType() + { + ModIcon icon = null; + + var ruleset = new OsuRuleset(); + + AddStep("create mod icon", () => Child = icon = new ModIcon(ruleset.AllMods.First(m => m.Acronym == "DT"))); + AddStep("change mod", () => icon.Mod = ruleset.AllMods.First(m => m.Acronym == "EZ")); + } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 9e253e089d..4f7aec3b67 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -158,8 +158,8 @@ namespace osu.Game.Tests.Visual.UserInterface var mania = new ManiaRuleset(); testModsWithSameBaseType( - mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModFadeIn)), - mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModHidden))); + mania.CreateMod(), + mania.CreateMod()); } [Test] diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs index b4d9fa4222..47e7ed9b61 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Tests.Components private void success(APIBeatmap apiBeatmap) { beatmap = apiBeatmap.ToBeatmap(rulesets); - var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().GetAllMods(); + var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().AllMods; foreach (var mod in mods) { diff --git a/osu.Game.Tournament/Components/TournamentModIcon.cs b/osu.Game.Tournament/Components/TournamentModIcon.cs index 43ac92d285..7c4e9c69a2 100644 --- a/osu.Game.Tournament/Components/TournamentModIcon.cs +++ b/osu.Game.Tournament/Components/TournamentModIcon.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -49,7 +48,7 @@ namespace osu.Game.Tournament.Components } var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0); - var modIcon = ruleset?.CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == modAcronym); + var modIcon = ruleset?.CreateInstance().CreateModFromAcronym(modAcronym); if (modIcon == null) return; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3eb766a667..8cb5da8083 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -93,6 +93,12 @@ namespace osu.Game.Beatmaps public bool WidescreenStoryboard { get; set; } public bool EpilepsyWarning { get; set; } + /// + /// Whether or not sound samples should change rate when playing with speed-changing mods. + /// TODO: only read/write supported for now, requires implementation in gameplay. + /// + public bool SamplesMatchPlaybackRate { get; set; } + public CountdownType Countdown { get; set; } = CountdownType.Normal; /// diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 27aa874dc9..bd85017d58 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -129,6 +129,7 @@ namespace osu.Game.Beatmaps Ruleset = ruleset, Metadata = metadata, WidescreenStoryboard = true, + SamplesMatchPlaybackRate = true, } } }; diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index 48f1f0ce68..3658dbab83 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -90,6 +90,12 @@ namespace osu.Game.Beatmaps /// The song language of this beatmap set. /// public BeatmapSetOnlineLanguage Language { get; set; } + + /// + /// The track ID of this beatmap set. + /// Non-null only if the track is linked to a featured artist track entry. + /// + public int? TrackId { get; set; } } public class BeatmapSetOnlineGenre diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index accefb2583..4b5eaafa4a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -180,6 +180,10 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1; break; + case @"SamplesMatchPlaybackRate": + beatmap.BeatmapInfo.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1; + break; + case @"Countdown": beatmap.BeatmapInfo.Countdown = (CountdownType)Enum.Parse(typeof(CountdownType), pair.Value); break; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 75d9a56f3e..aef13b8872 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -105,8 +105,8 @@ namespace osu.Game.Beatmaps.Formats if (beatmap.BeatmapInfo.RulesetID == 3) writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}")); writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}")); - // if (b.SamplesMatchPlaybackRate) - // writer.WriteLine(@"SamplesMatchPlaybackRate: 1"); + if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate) + writer.WriteLine(@"SamplesMatchPlaybackRate: 1"); } private void handleEditor(TextWriter writer) diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs deleted file mode 100644 index b3783b45a8..0000000000 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Bindables; -using osu.Framework.Configuration; -using osu.Game.Rulesets; - -namespace osu.Game.Configuration -{ - public abstract class DatabasedConfigManager : ConfigManager - where TLookup : struct, Enum - { - private readonly SettingsStore settings; - - private readonly int? variant; - - private List databasedSettings; - - private readonly RulesetInfo ruleset; - - private bool legacySettingsExist; - - protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null) - { - this.settings = settings; - this.ruleset = ruleset; - this.variant = variant; - - Load(); - - InitialiseDefaults(); - } - - protected override void PerformLoad() - { - databasedSettings = settings.Query(ruleset?.ID, variant); - legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out _)); - } - - protected override bool PerformSave() - { - lock (dirtySettings) - { - foreach (var setting in dirtySettings) - settings.Update(setting); - dirtySettings.Clear(); - } - - return true; - } - - private readonly List dirtySettings = new List(); - - protected override void AddBindable(TLookup lookup, Bindable bindable) - { - base.AddBindable(lookup, bindable); - - if (legacySettingsExist) - { - var legacySetting = databasedSettings.Find(s => s.Key == ((int)(object)lookup).ToString()); - - if (legacySetting != null) - { - bindable.Parse(legacySetting.Value); - settings.Delete(legacySetting); - } - } - - var setting = databasedSettings.Find(s => s.Key == lookup.ToString()); - - if (setting != null) - { - bindable.Parse(setting.Value); - } - else - { - settings.Update(setting = new DatabasedSetting - { - Key = lookup.ToString(), - Value = bindable.Value, - RulesetID = ruleset?.ID, - Variant = variant, - }); - - databasedSettings.Add(setting); - } - - bindable.ValueChanged += b => - { - setting.Value = b.NewValue; - - lock (dirtySettings) - { - if (!dirtySettings.Contains(setting)) - dirtySettings.Add(setting); - } - }; - } - } -} diff --git a/osu.Game/Configuration/DatabasedSetting.cs b/osu.Game/Configuration/DatabasedSetting.cs index f5c92b3029..fe1d51d57f 100644 --- a/osu.Game/Configuration/DatabasedSetting.cs +++ b/osu.Game/Configuration/DatabasedSetting.cs @@ -7,7 +7,7 @@ using osu.Game.Database; namespace osu.Game.Configuration { [Table("Settings")] - public class DatabasedSetting : IHasPrimaryKey + public class DatabasedSetting : IHasPrimaryKey // can be removed 20220315. { public int ID { get; set; } diff --git a/osu.Game/Configuration/RealmRulesetSetting.cs b/osu.Game/Configuration/RealmRulesetSetting.cs new file mode 100644 index 0000000000..07e56ad8dd --- /dev/null +++ b/osu.Game/Configuration/RealmRulesetSetting.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Database; +using Realms; + +#nullable enable + +namespace osu.Game.Configuration +{ + [MapTo(@"RulesetSetting")] + public class RealmRulesetSetting : RealmObject, IHasGuidPrimaryKey + { + [PrimaryKey] + public Guid ID { get; set; } = Guid.NewGuid(); + + [Indexed] + public int RulesetID { get; set; } + + [Indexed] + public int Variant { get; set; } + + [Required] + public string Key { get; set; } = string.Empty; + + [Required] + public string Value { get; set; } = string.Empty; + + public override string ToString() => $"{Key} => {Value}"; + } +} diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs index 86e84b0732..2bba20fb09 100644 --- a/osu.Game/Configuration/SettingsStore.cs +++ b/osu.Game/Configuration/SettingsStore.cs @@ -1,46 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; -using System.Linq; using osu.Game.Database; namespace osu.Game.Configuration { - public class SettingsStore : DatabaseBackedStore + public class SettingsStore { - public event Action SettingChanged; + // this class mostly exists as a wrapper to avoid breaking the ruleset API (see usage in RulesetConfigManager). + // it may cease to exist going forward, depending on how the structure of the config data layer changes. - public SettingsStore(DatabaseContextFactory contextFactory) - : base(contextFactory) + public readonly RealmContextFactory Realm; + + public SettingsStore(RealmContextFactory realmFactory) { - } - - /// - /// Retrieve s for a specified ruleset/variant content. - /// - /// The ruleset's internal ID. - /// An optional variant. - public List Query(int? rulesetId = null, int? variant = null) => - ContextFactory.Get().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); - - public void Update(DatabasedSetting setting) - { - using (ContextFactory.GetForWrite()) - { - var newValue = setting.Value; - Refresh(ref setting); - setting.Value = newValue; - } - - SettingChanged?.Invoke(); - } - - public void Delete(DatabasedSetting setting) - { - using (var usage = ContextFactory.GetForWrite()) - usage.Context.Remove(setting); + Realm = realmFactory; } } } diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 1cceb59b11..d402195f29 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -163,8 +163,11 @@ namespace osu.Game.Database public void FlushConnections() { - foreach (var context in threadContexts.Values) - context.Dispose(); + if (threadContexts != null) + { + foreach (var context in threadContexts.Values) + context.Dispose(); + } recycleThreadContexts(); } diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 68d186c65d..1d8322aadd 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -12,7 +12,6 @@ using osu.Game.Configuration; using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Scoring; -using DatabasedKeyBinding = osu.Game.Input.Bindings.DatabasedKeyBinding; using LogLevel = Microsoft.Extensions.Logging.LogLevel; using osu.Game.Skinning; @@ -24,14 +23,13 @@ namespace osu.Game.Database public DbSet BeatmapDifficulty { get; set; } public DbSet BeatmapMetadata { get; set; } public DbSet BeatmapSetInfo { get; set; } - public DbSet DatabasedSetting { get; set; } public DbSet FileInfo { get; set; } public DbSet RulesetInfo { get; set; } public DbSet SkinInfo { get; set; } public DbSet ScoreInfo { get; set; } // migrated to realm - public DbSet DatabasedKeyBinding { get; set; } + public DbSet DatabasedSetting { get; set; } private readonly string connectionString; @@ -138,11 +136,6 @@ namespace osu.Game.Database modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); modelBuilder.Entity().HasIndex(b => b.DeletePending); - modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant }); - modelBuilder.Entity().HasIndex(b => b.IntAction); - modelBuilder.Entity().Ignore(b => b.KeyCombination); - modelBuilder.Entity().Ignore(b => b.Action); - modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant }); modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 52d8230fb6..03cc345947 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -38,6 +38,33 @@ namespace osu.Game.Extensions return repeatDelegate; } + /// + /// Shakes this drawable. + /// + /// The target to shake. + /// The length of a single shake. + /// Pixels of displacement per shake. + /// The maximum length the shake should last. + public static void Shake(this Drawable target, double shakeDuration = 80, float shakeMagnitude = 8, double? maximumLength = null) + { + // if we don't have enough time, don't bother shaking. + if (maximumLength < shakeDuration * 2) + return; + + var sequence = target.MoveToX(shakeMagnitude, shakeDuration / 2, Easing.OutSine).Then() + .MoveToX(-shakeMagnitude, shakeDuration, Easing.InOutSine).Then(); + + // if we don't have enough time for the second shake, skip it. + if (!maximumLength.HasValue || maximumLength >= shakeDuration * 4) + { + sequence = sequence + .MoveToX(shakeMagnitude, shakeDuration, Easing.InOutSine).Then() + .MoveToX(-shakeMagnitude, shakeDuration, Easing.InOutSine).Then(); + } + + sequence.MoveToX(0, shakeDuration / 2, Easing.InSine); + } + /// /// Accepts a delta vector in screen-space coordinates and converts it to one which can be applied to this drawable's position. /// diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 35c48a50d0..ab8763e576 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Allocation; using System.Collections.Generic; using osu.Framework.Graphics.Batches; +using osu.Framework.Graphics.OpenGL.Buffers; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Lists; @@ -181,7 +182,10 @@ namespace osu.Game.Graphics.Backgrounds private void addTriangles(bool randomY) { - AimCount = (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio); + // limited by the maximum size of QuadVertexBuffer for safety. + const int max_triangles = QuadVertexBuffer.MAX_QUADS; + + AimCount = (int)Math.Min(max_triangles, (DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio)); for (int i = 0; i < AimCount - parts.Count; i++) parts.Add(createTriangle(randomY)); diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index b9b098df80..16ec7ab838 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -88,9 +88,9 @@ namespace osu.Game.Graphics.Containers base.OnMouseUp(e); } - public virtual bool OnPressed(GlobalAction action) + public virtual bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.Back: Hide(); @@ -103,7 +103,7 @@ namespace osu.Game.Graphics.Containers return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Graphics/Containers/ShakeContainer.cs b/osu.Game/Graphics/Containers/ShakeContainer.cs index dca9df1e98..8a0ce287db 100644 --- a/osu.Game/Graphics/Containers/ShakeContainer.cs +++ b/osu.Game/Graphics/Containers/ShakeContainer.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Extensions; namespace osu.Game.Graphics.Containers { @@ -16,40 +16,10 @@ namespace osu.Game.Graphics.Containers /// public float ShakeDuration = 80; - /// - /// Total number of shakes. May be shortened if possible. - /// - public float TotalShakes = 4; - - /// - /// Pixels of displacement per shake. - /// - public float ShakeMagnitude = 8; - /// /// Shake the contents of this container. /// /// The maximum length the shake should last. - public void Shake(double? maximumLength = null) - { - const float shake_amount = 8; - - // if we don't have enough time, don't bother shaking. - if (maximumLength < ShakeDuration * 2) - return; - - var sequence = this.MoveToX(shake_amount, ShakeDuration / 2, Easing.OutSine).Then() - .MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then(); - - // if we don't have enough time for the second shake, skip it. - if (!maximumLength.HasValue || maximumLength >= ShakeDuration * 4) - { - sequence = sequence - .MoveToX(shake_amount, ShakeDuration, Easing.InOutSine).Then() - .MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then(); - } - - sequence.MoveToX(0, ShakeDuration / 2, Easing.InSine); - } + public void Shake(double? maximumLength = null) => this.Shake(ShakeDuration, maximumLength: maximumLength); } } diff --git a/osu.Game/Graphics/ParticleSpewer.cs b/osu.Game/Graphics/ParticleSpewer.cs new file mode 100644 index 0000000000..466bf04369 --- /dev/null +++ b/osu.Game/Graphics/ParticleSpewer.cs @@ -0,0 +1,198 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Utils; +using osuTK; + +namespace osu.Game.Graphics +{ + public abstract class ParticleSpewer : Sprite + { + private readonly FallingParticle[] particles; + private int currentIndex; + private double lastParticleAdded; + + private readonly double cooldown; + private readonly double maxDuration; + + /// + /// Determines whether particles are being spawned. + /// + public readonly BindableBool Active = new BindableBool(); + + public override bool IsPresent => base.IsPresent && hasActiveParticles; + + protected virtual bool CanSpawnParticles => true; + protected virtual float ParticleGravity => 0; + + private bool hasActiveParticles => Active.Value || (lastParticleAdded + maxDuration) > Time.Current; + + protected ParticleSpewer(Texture texture, int perSecond, double maxDuration) + { + Texture = texture; + Blending = BlendingParameters.Additive; + + particles = new FallingParticle[perSecond * (int)Math.Ceiling(maxDuration / 1000)]; + + cooldown = 1000f / perSecond; + this.maxDuration = maxDuration; + } + + protected override void Update() + { + base.Update(); + + if (Active.Value && CanSpawnParticles && Math.Abs(Time.Current - lastParticleAdded) > cooldown) + { + var newParticle = CreateParticle(); + newParticle.StartTime = (float)Time.Current; + + particles[currentIndex] = newParticle; + + currentIndex = (currentIndex + 1) % particles.Length; + lastParticleAdded = Time.Current; + } + + Invalidate(Invalidation.DrawNode); + } + + /// + /// Called each time a new particle should be spawned. + /// + protected virtual FallingParticle CreateParticle() => new FallingParticle(); + + protected override DrawNode CreateDrawNode() => new ParticleSpewerDrawNode(this); + + # region DrawNode + + private class ParticleSpewerDrawNode : SpriteDrawNode + { + private readonly FallingParticle[] particles; + + protected new ParticleSpewer Source => (ParticleSpewer)base.Source; + + private readonly float maxDuration; + + private float currentTime; + private float gravity; + private Axes relativePositionAxes; + private Vector2 sourceSize; + + public ParticleSpewerDrawNode(ParticleSpewer source) + : base(source) + { + particles = new FallingParticle[Source.particles.Length]; + maxDuration = (float)Source.maxDuration; + } + + public override void ApplyState() + { + base.ApplyState(); + + Source.particles.CopyTo(particles, 0); + + currentTime = (float)Source.Time.Current; + gravity = Source.ParticleGravity; + relativePositionAxes = Source.RelativePositionAxes; + sourceSize = Source.DrawSize; + } + + protected override void Blit(Action vertexAction) + { + foreach (var p in particles) + { + var timeSinceStart = currentTime - p.StartTime; + + // ignore particles from the future. + // these can appear when seeking in replays. + if (timeSinceStart < 0) continue; + + var alpha = p.AlphaAtTime(timeSinceStart); + if (alpha <= 0) continue; + + var pos = p.PositionAtTime(timeSinceStart, gravity, maxDuration); + var scale = p.ScaleAtTime(timeSinceStart); + var angle = p.AngleAtTime(timeSinceStart); + + var rect = createDrawRect(pos, scale); + + var quad = new Quad( + transformPosition(rect.TopLeft, rect.Centre, angle), + transformPosition(rect.TopRight, rect.Centre, angle), + transformPosition(rect.BottomLeft, rect.Centre, angle), + transformPosition(rect.BottomRight, rect.Centre, angle) + ); + + DrawQuad(Texture, quad, DrawColourInfo.Colour.MultiplyAlpha(alpha), null, vertexAction, + new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height), + null, TextureCoords); + } + } + + private RectangleF createDrawRect(Vector2 position, float scale) + { + var width = Texture.DisplayWidth * scale; + var height = Texture.DisplayHeight * scale; + + if (relativePositionAxes.HasFlagFast(Axes.X)) + position.X *= sourceSize.X; + if (relativePositionAxes.HasFlagFast(Axes.Y)) + position.Y *= sourceSize.Y; + + return new RectangleF( + position.X - width / 2, + position.Y - height / 2, + width, + height); + } + + private Vector2 transformPosition(Vector2 pos, Vector2 centre, float angle) + { + float cos = MathF.Cos(angle); + float sin = MathF.Sin(angle); + + float x = centre.X + (pos.X - centre.X) * cos + (pos.Y - centre.Y) * sin; + float y = centre.Y + (pos.Y - centre.Y) * cos - (pos.X - centre.X) * sin; + + return Vector2Extensions.Transform(new Vector2(x, y), DrawInfo.Matrix); + } + } + + #endregion + + protected struct FallingParticle + { + public float StartTime; + public Vector2 StartPosition; + public Vector2 Velocity; + public float Duration; + public float StartAngle; + public float EndAngle; + public float EndScale; + + public float AlphaAtTime(float timeSinceStart) => 1 - progressAtTime(timeSinceStart); + + public float ScaleAtTime(float timeSinceStart) => (float)Interpolation.Lerp(1, EndScale, progressAtTime(timeSinceStart)); + + public float AngleAtTime(float timeSinceStart) => (float)Interpolation.Lerp(StartAngle, EndAngle, progressAtTime(timeSinceStart)); + + public Vector2 PositionAtTime(float timeSinceStart, float gravity, float maxDuration) + { + var progress = progressAtTime(timeSinceStart); + var currentGravity = new Vector2(0, gravity * Duration / maxDuration * progress); + + return StartPosition + (Velocity + currentGravity) * timeSinceStart / maxDuration; + } + + private float progressAtTime(float timeSinceStart) => Math.Clamp(timeSinceStart / Duration, 0, 1); + } + } +} diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index fb7fe4947b..9cd403f409 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -12,6 +12,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Configuration; @@ -57,9 +58,9 @@ namespace osu.Game.Graphics shutter = audio.Samples.Get("UI/shutter"); } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.TakeScreenshot: shutter.Play(); @@ -70,7 +71,7 @@ namespace osu.Game.Graphics return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs index 1607762908..c965fbcf45 100644 --- a/osu.Game/Graphics/UserInterface/BackButton.cs +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Input.Bindings; namespace osu.Game.Graphics.UserInterface @@ -61,9 +62,9 @@ namespace osu.Game.Graphics.UserInterface { public Action OnBackPressed; - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.Back: OnBackPressed?.Invoke(); @@ -73,7 +74,7 @@ namespace osu.Game.Graphics.UserInterface return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } } diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index ed9f0710b0..ceea9620c8 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -70,11 +70,11 @@ namespace osu.Game.Graphics.UserInterface return base.OnKeyDown(e); } - public bool OnPressed(GlobalAction action) + public virtual bool OnPressed(KeyBindingPressEvent e) { if (!HasFocus) return false; - if (action == GlobalAction.Back) + if (e.Action == GlobalAction.Back) { if (Text.Length > 0) { @@ -86,7 +86,7 @@ namespace osu.Game.Graphics.UserInterface return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 664f32b083..6807d007bb 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -46,7 +46,11 @@ namespace osu.Game.Graphics.UserInterface }, }; - Current.ValueChanged += filled => fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint); + Current.ValueChanged += filled => + { + fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint); + this.TransformTo(nameof(BorderThickness), filled.NewValue ? 8.5f : border_width, 200, Easing.OutQuint); + }; } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index 4a91741ce6..6937782be6 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -30,9 +30,9 @@ namespace osu.Game.Graphics.UserInterface PlaceholderText = "type to search"; } - public override bool OnPressed(PlatformAction action) + public override bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case PlatformAction.MoveBackwardLine: case PlatformAction.MoveForwardLine: @@ -43,7 +43,7 @@ namespace osu.Game.Graphics.UserInterface return false; } - return base.OnPressed(action); + return base.OnPressed(e); } protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index c07a5de1e4..226c39c030 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -4,14 +4,18 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Input.Bindings; using osu.Game.Overlays; using osuTK; namespace osu.Game.Graphics.UserInterfaceV2 { - public class OsuPopover : Popover + public class OsuPopover : Popover, IKeyBindingHandler { private const float fade_duration = 250; private const double scale_duration = 500; @@ -51,5 +55,23 @@ namespace osu.Game.Graphics.UserInterfaceV2 this.ScaleTo(0.7f, scale_duration, Easing.OutQuint); this.FadeOut(fade_duration, Easing.OutQuint); } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (State.Value == Visibility.Hidden) + return false; + + if (e.Action == GlobalAction.Back) + { + Hide(); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } } } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs deleted file mode 100644 index ad3493d0fc..0000000000 --- a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.ComponentModel.DataAnnotations.Schema; -using osu.Framework.Input.Bindings; -using osu.Game.Database; - -namespace osu.Game.Input.Bindings -{ - [Table("KeyBinding")] - public class DatabasedKeyBinding : IKeyBinding, IHasPrimaryKey - { - public int ID { get; set; } - - public int? RulesetID { get; set; } - - public int? Variant { get; set; } - - [Column("Keys")] - public string KeysString { get; set; } - - [Column("Action")] - public int IntAction { get; set; } - - [NotMapped] - public KeyCombination KeyCombination - { - get => KeysString; - set => KeysString = value.ToString(); - } - - [NotMapped] - public object Action - { - get => IntAction; - set => IntAction = (int)value; - } - } -} diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 0176a00e9d..f62131e2d7 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -2,11 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Localisation; +using osu.Game.Localisation; namespace osu.Game.Input.Bindings { @@ -137,152 +138,152 @@ namespace osu.Game.Input.Bindings public enum GlobalAction { - [Description("Toggle chat overlay")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleChat))] ToggleChat, - [Description("Toggle social overlay")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleSocial))] ToggleSocial, - [Description("Reset input settings")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ResetInputSettings))] ResetInputSettings, - [Description("Toggle toolbar")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleToolbar))] ToggleToolbar, - [Description("Toggle settings")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleSettings))] ToggleSettings, - [Description("Toggle beatmap listing")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleBeatmapListing))] ToggleBeatmapListing, - [Description("Increase volume")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseVolume))] IncreaseVolume, - [Description("Decrease volume")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseVolume))] DecreaseVolume, - [Description("Toggle mute")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleMute))] ToggleMute, // In-Game Keybindings - [Description("Skip cutscene")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SkipCutscene))] SkipCutscene, - [Description("Quick retry (hold)")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.QuickRetry))] QuickRetry, - [Description("Take screenshot")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.TakeScreenshot))] TakeScreenshot, - [Description("Toggle gameplay mouse buttons")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleGameplayMouseButtons))] ToggleGameplayMouseButtons, - [Description("Back")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.Back))] Back, - [Description("Increase scroll speed")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseScrollSpeed))] IncreaseScrollSpeed, - [Description("Decrease scroll speed")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseScrollSpeed))] DecreaseScrollSpeed, - [Description("Select")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.Select))] Select, - [Description("Quick exit (hold)")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.QuickExit))] QuickExit, // Game-wide beatmap music controller keybindings - [Description("Next track")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.MusicNext))] MusicNext, - [Description("Previous track")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.MusicPrev))] MusicPrev, - [Description("Play / pause")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.MusicPlay))] MusicPlay, - [Description("Toggle now playing overlay")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleNowPlaying))] ToggleNowPlaying, - [Description("Previous selection")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SelectPrevious))] SelectPrevious, - [Description("Next selection")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SelectNext))] SelectNext, - [Description("Home")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.Home))] Home, - [Description("Toggle notifications")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleNotifications))] ToggleNotifications, - [Description("Pause gameplay")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.PauseGameplay))] PauseGameplay, // Editor - [Description("Setup mode")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSetupMode))] EditorSetupMode, - [Description("Compose mode")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorComposeMode))] EditorComposeMode, - [Description("Design mode")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorDesignMode))] EditorDesignMode, - [Description("Timing mode")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTimingMode))] EditorTimingMode, - [Description("Hold for HUD")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.HoldForHUD))] HoldForHUD, - [Description("Random skin")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.RandomSkin))] RandomSkin, - [Description("Pause / resume replay")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.TogglePauseReplay))] TogglePauseReplay, - [Description("Toggle in-game interface")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameInterface))] ToggleInGameInterface, // Song select keybindings - [Description("Toggle Mod Select")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleModSelection))] ToggleModSelection, - [Description("Random")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SelectNextRandom))] SelectNextRandom, - [Description("Rewind")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SelectPreviousRandom))] SelectPreviousRandom, - [Description("Beatmap Options")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleBeatmapOptions))] ToggleBeatmapOptions, - [Description("Verify mode")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorVerifyMode))] EditorVerifyMode, - [Description("Nudge selection left")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorNudgeLeft))] EditorNudgeLeft, - [Description("Nudge selection right")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorNudgeRight))] EditorNudgeRight, - [Description("Toggle skin editor")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleSkinEditor))] ToggleSkinEditor, - [Description("Previous volume meter")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.PreviousVolumeMeter))] PreviousVolumeMeter, - [Description("Next volume meter")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.NextVolumeMeter))] NextVolumeMeter, - [Description("Seek replay forward")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SeekReplayForward))] SeekReplayForward, - [Description("Seek replay backward")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SeekReplayBackward))] SeekReplayBackward, - [Description("Toggle chat focus")] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleChatFocus))] ToggleChatFocus } } diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index f3d531cf6c..bfe21f650a 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -55,13 +55,13 @@ namespace osu.Game.Input isIdle.Value = TimeSpentIdle > timeToIdle && AllowIdle; } - public bool OnPressed(PlatformAction action) => updateLastInteractionTime(); + public bool OnPressed(KeyBindingPressEvent e) => updateLastInteractionTime(); - public void OnReleased(PlatformAction action) => updateLastInteractionTime(); + public void OnReleased(KeyBindingReleaseEvent e) => updateLastInteractionTime(); - public bool OnPressed(GlobalAction action) => updateLastInteractionTime(); + public bool OnPressed(KeyBindingPressEvent e) => updateLastInteractionTime(); - public void OnReleased(GlobalAction action) => updateLastInteractionTime(); + public void OnReleased(KeyBindingReleaseEvent e) => updateLastInteractionTime(); protected override bool Handle(UIEvent e) { diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs new file mode 100644 index 0000000000..14159f0d34 --- /dev/null +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -0,0 +1,254 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class GlobalActionKeyBindingStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.GlobalActionKeyBinding"; + + /// + /// "Toggle chat overlay" + /// + public static LocalisableString ToggleChat => new TranslatableString(getKey(@"toggle_chat"), @"Toggle chat overlay"); + + /// + /// "Toggle social overlay" + /// + public static LocalisableString ToggleSocial => new TranslatableString(getKey(@"toggle_social"), @"Toggle social overlay"); + + /// + /// "Reset input settings" + /// + public static LocalisableString ResetInputSettings => new TranslatableString(getKey(@"reset_input_settings"), @"Reset input settings"); + + /// + /// "Toggle toolbar" + /// + public static LocalisableString ToggleToolbar => new TranslatableString(getKey(@"toggle_toolbar"), @"Toggle toolbar"); + + /// + /// "Toggle settings" + /// + public static LocalisableString ToggleSettings => new TranslatableString(getKey(@"toggle_settings"), @"Toggle settings"); + + /// + /// "Toggle beatmap listing" + /// + public static LocalisableString ToggleBeatmapListing => new TranslatableString(getKey(@"toggle_beatmap_listing"), @"Toggle beatmap listing"); + + /// + /// "Increase volume" + /// + public static LocalisableString IncreaseVolume => new TranslatableString(getKey(@"increase_volume"), @"Increase volume"); + + /// + /// "Decrease volume" + /// + public static LocalisableString DecreaseVolume => new TranslatableString(getKey(@"decrease_volume"), @"Decrease volume"); + + /// + /// "Toggle mute" + /// + public static LocalisableString ToggleMute => new TranslatableString(getKey(@"toggle_mute"), @"Toggle mute"); + + /// + /// "Skip cutscene" + /// + public static LocalisableString SkipCutscene => new TranslatableString(getKey(@"skip_cutscene"), @"Skip cutscene"); + + /// + /// "Quick retry (hold)" + /// + public static LocalisableString QuickRetry => new TranslatableString(getKey(@"quick_retry"), @"Quick retry (hold)"); + + /// + /// "Take screenshot" + /// + public static LocalisableString TakeScreenshot => new TranslatableString(getKey(@"take_screenshot"), @"Take screenshot"); + + /// + /// "Toggle gameplay mouse buttons" + /// + public static LocalisableString ToggleGameplayMouseButtons => new TranslatableString(getKey(@"toggle_gameplay_mouse_buttons"), @"Toggle gameplay mouse buttons"); + + /// + /// "Back" + /// + public static LocalisableString Back => new TranslatableString(getKey(@"back"), @"Back"); + + /// + /// "Increase scroll speed" + /// + public static LocalisableString IncreaseScrollSpeed => new TranslatableString(getKey(@"increase_scroll_speed"), @"Increase scroll speed"); + + /// + /// "Decrease scroll speed" + /// + public static LocalisableString DecreaseScrollSpeed => new TranslatableString(getKey(@"decrease_scroll_speed"), @"Decrease scroll speed"); + + /// + /// "Select" + /// + public static LocalisableString Select => new TranslatableString(getKey(@"select"), @"Select"); + + /// + /// "Quick exit (hold)" + /// + public static LocalisableString QuickExit => new TranslatableString(getKey(@"quick_exit"), @"Quick exit (hold)"); + + /// + /// "Next track" + /// + public static LocalisableString MusicNext => new TranslatableString(getKey(@"music_next"), @"Next track"); + + /// + /// "Previous track" + /// + public static LocalisableString MusicPrev => new TranslatableString(getKey(@"music_prev"), @"Previous track"); + + /// + /// "Play / pause" + /// + public static LocalisableString MusicPlay => new TranslatableString(getKey(@"music_play"), @"Play / pause"); + + /// + /// "Toggle now playing overlay" + /// + public static LocalisableString ToggleNowPlaying => new TranslatableString(getKey(@"toggle_now_playing"), @"Toggle now playing overlay"); + + /// + /// "Previous selection" + /// + public static LocalisableString SelectPrevious => new TranslatableString(getKey(@"select_previous"), @"Previous selection"); + + /// + /// "Next selection" + /// + public static LocalisableString SelectNext => new TranslatableString(getKey(@"select_next"), @"Next selection"); + + /// + /// "Home" + /// + public static LocalisableString Home => new TranslatableString(getKey(@"home"), @"Home"); + + /// + /// "Toggle notifications" + /// + public static LocalisableString ToggleNotifications => new TranslatableString(getKey(@"toggle_notifications"), @"Toggle notifications"); + + /// + /// "Pause gameplay" + /// + public static LocalisableString PauseGameplay => new TranslatableString(getKey(@"pause_gameplay"), @"Pause gameplay"); + + /// + /// "Setup mode" + /// + public static LocalisableString EditorSetupMode => new TranslatableString(getKey(@"editor_setup_mode"), @"Setup mode"); + + /// + /// "Compose mode" + /// + public static LocalisableString EditorComposeMode => new TranslatableString(getKey(@"editor_compose_mode"), @"Compose mode"); + + /// + /// "Design mode" + /// + public static LocalisableString EditorDesignMode => new TranslatableString(getKey(@"editor_design_mode"), @"Design mode"); + + /// + /// "Timing mode" + /// + public static LocalisableString EditorTimingMode => new TranslatableString(getKey(@"editor_timing_mode"), @"Timing mode"); + + /// + /// "Hold for HUD" + /// + public static LocalisableString HoldForHUD => new TranslatableString(getKey(@"hold_for_hud"), @"Hold for HUD"); + + /// + /// "Random skin" + /// + public static LocalisableString RandomSkin => new TranslatableString(getKey(@"random_skin"), @"Random skin"); + + /// + /// "Pause / resume replay" + /// + public static LocalisableString TogglePauseReplay => new TranslatableString(getKey(@"toggle_pause_replay"), @"Pause / resume replay"); + + /// + /// "Toggle in-game interface" + /// + public static LocalisableString ToggleInGameInterface => new TranslatableString(getKey(@"toggle_in_game_interface"), @"Toggle in-game interface"); + + /// + /// "Toggle Mod Select" + /// + public static LocalisableString ToggleModSelection => new TranslatableString(getKey(@"toggle_mod_selection"), @"Toggle Mod Select"); + + /// + /// "Random" + /// + public static LocalisableString SelectNextRandom => new TranslatableString(getKey(@"select_next_random"), @"Random"); + + /// + /// "Rewind" + /// + public static LocalisableString SelectPreviousRandom => new TranslatableString(getKey(@"select_previous_random"), @"Rewind"); + + /// + /// "Beatmap Options" + /// + public static LocalisableString ToggleBeatmapOptions => new TranslatableString(getKey(@"toggle_beatmap_options"), @"Beatmap Options"); + + /// + /// "Verify mode" + /// + public static LocalisableString EditorVerifyMode => new TranslatableString(getKey(@"editor_verify_mode"), @"Verify mode"); + + /// + /// "Nudge selection left" + /// + public static LocalisableString EditorNudgeLeft => new TranslatableString(getKey(@"editor_nudge_left"), @"Nudge selection left"); + + /// + /// "Nudge selection right" + /// + public static LocalisableString EditorNudgeRight => new TranslatableString(getKey(@"editor_nudge_right"), @"Nudge selection right"); + + /// + /// "Toggle skin editor" + /// + public static LocalisableString ToggleSkinEditor => new TranslatableString(getKey(@"toggle_skin_editor"), @"Toggle skin editor"); + + /// + /// "Previous volume meter" + /// + public static LocalisableString PreviousVolumeMeter => new TranslatableString(getKey(@"previous_volume_meter"), @"Previous volume meter"); + + /// + /// "Next volume meter" + /// + public static LocalisableString NextVolumeMeter => new TranslatableString(getKey(@"next_volume_meter"), @"Next volume meter"); + + /// + /// "Seek replay forward" + /// + public static LocalisableString SeekReplayForward => new TranslatableString(getKey(@"seek_replay_forward"), @"Seek replay forward"); + + /// + /// "Seek replay backward" + /// + public static LocalisableString SeekReplayBackward => new TranslatableString(getKey(@"seek_replay_backward"), @"Seek replay backward"); + + /// + /// "Toggle chat focus" + /// + public static LocalisableString ToggleChatFocus => new TranslatableString(getKey(@"toggle_chat_focus"), @"Toggle chat focus"); + + private static string getKey(string key) => $"{prefix}:{key}"; + } +} diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs new file mode 100644 index 0000000000..6e53d7fae0 --- /dev/null +++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs @@ -0,0 +1,515 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210912144011_AddSamplesMatchPlaybackRate")] + partial class AddSamplesMatchPlaybackRate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("CountdownOffset"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SamplesMatchPlaybackRate"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorID") + .HasColumnName("AuthorID"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs new file mode 100644 index 0000000000..bf3f855d5f --- /dev/null +++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSamplesMatchPlaybackRate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SamplesMatchPlaybackRate", + table: "BeatmapInfo", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SamplesMatchPlaybackRate", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 470907ada6..036c26cb0a 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -81,6 +81,8 @@ namespace osu.Game.Migrations b.Property("RulesetID"); + b.Property("SamplesMatchPlaybackRate"); + b.Property("SpecialStyle"); b.Property("StackLeniency"); diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 596d567480..62f9976c0f 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -16,7 +16,7 @@ using osu.Game.Utils; namespace osu.Game.Online.API { [MessagePackObject] - public class APIMod : IMod, IEquatable + public class APIMod : IEquatable { [JsonProperty("acronym")] [Key(0)] @@ -48,7 +48,7 @@ namespace osu.Game.Online.API public Mod ToMod(Ruleset ruleset) { - Mod resultMod = ruleset.GetAllMods().FirstOrDefault(m => m.Acronym == Acronym); + Mod resultMod = ruleset.CreateModFromAcronym(Acronym); if (resultMod == null) throw new InvalidOperationException($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}."); @@ -67,15 +67,12 @@ namespace osu.Game.Online.API return resultMod; } - public bool Equals(IMod other) => other is APIMod them && Equals(them); - public bool Equals(APIMod other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Acronym == other.Acronym && - Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default); + return Acronym == other.Acronym && Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default); } public override string ToString() diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index bf3441d2a0..b4e0e44b2c 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -18,9 +18,9 @@ namespace osu.Game.Online.API.Requests private readonly BeatmapInfo beatmap; private readonly BeatmapLeaderboardScope scope; private readonly RulesetInfo ruleset; - private readonly IEnumerable mods; + private readonly IEnumerable mods; - public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null) + public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null) { if (!beatmap.OnlineBeatmapID.HasValue) throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); @@ -31,7 +31,7 @@ namespace osu.Game.Online.API.Requests this.beatmap = beatmap; this.scope = scope; this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset)); - this.mods = mods ?? Array.Empty(); + this.mods = mods ?? Array.Empty(); Success += onSuccess; } diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index e49c4ab298..730e4e02ed 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -8,7 +8,7 @@ namespace osu.Game.Online.API.Requests { public class GetUserRequest : APIRequest { - private readonly string lookup; + public readonly string Lookup; public readonly RulesetInfo Ruleset; private readonly LookupType lookupType; @@ -26,7 +26,7 @@ namespace osu.Game.Online.API.Requests /// The ruleset to get the user's info for. public GetUserRequest(long? userId = null, RulesetInfo ruleset = null) { - lookup = userId.ToString(); + Lookup = userId.ToString(); lookupType = LookupType.Id; Ruleset = ruleset; } @@ -38,12 +38,12 @@ namespace osu.Game.Online.API.Requests /// The ruleset to get the user's info for. public GetUserRequest(string username = null, RulesetInfo ruleset = null) { - lookup = username; + Lookup = username; lookupType = LookupType.Username; Ruleset = ruleset; } - protected override string Target => lookup != null ? $@"users/{lookup}/{Ruleset?.ShortName}?k={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}"; + protected override string Target => Lookup != null ? $@"users/{Lookup}/{Ruleset?.ShortName}?key={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}"; private enum LookupType { diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 45d9c9405f..f653a654ca 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -63,6 +63,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"ratings")] private int[] ratings { get; set; } + [JsonProperty(@"track_id")] + private int? trackId { get; set; } + [JsonProperty(@"user_id")] private int creatorId { @@ -106,7 +109,8 @@ namespace osu.Game.Online.API.Requests.Responses Availability = availability, HasFavourited = hasFavourited, Genre = genre, - Language = language + Language = language, + TrackId = trackId }, }; diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index 1b394185fd..567df524b1 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -23,10 +23,10 @@ namespace osu.Game.Online.API.Requests.Responses var rulesetInstance = ruleset.CreateInstance(); - var mods = Mods != null ? rulesetInstance.GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty(); + var mods = Mods != null ? Mods.Select(acronym => rulesetInstance.CreateModFromAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty(); // all API scores provided by this class are considered to be legacy. - mods = mods.Append(rulesetInstance.GetAllMods().OfType().Single()).ToArray(); + mods = mods.Append(rulesetInstance.CreateMod()).ToArray(); var scoreInfo = new ScoreInfo { diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 1937019ef6..47d5955fb0 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -256,8 +256,36 @@ namespace osu.Game.Online.Chat JoinChannel(channel); break; + case "chat": + case "msg": + case "query": + if (string.IsNullOrWhiteSpace(content)) + { + target.AddNewMessages(new ErrorMessage($"Usage: /{command} [user]")); + break; + } + + // Check if the user has joined the requested channel already. + // This uses the channel name for comparison as the PM user's username is unavailable after a restart. + var privateChannel = JoinedChannels.FirstOrDefault( + c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Name.Equals(content, StringComparison.OrdinalIgnoreCase)); + + if (privateChannel != null) + { + CurrentChannel.Value = privateChannel; + break; + } + + var request = new GetUserRequest(content); + request.Success += OpenPrivateChannel; + request.Failure += e => target.AddNewMessages( + new ErrorMessage(e.InnerException?.Message == @"NotFound" ? $"User '{content}' was not found." : $"Could not fetch user '{content}'.")); + + api.Queue(request); + break; + case "help": - target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /np")); + target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /chat [user], /np")); break; default: diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index d964060f10..5f71b4be4a 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -179,11 +179,7 @@ namespace osu.Game.Online.Rooms if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value) Status.Value = new RoomStatusEnded(); - // Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended, - // and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room. - // More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room. - if (!(Status.Value is RoomStatusEnded)) - other.Playlist.RemoveAll(i => i.Expired); + other.RemoveExpiredPlaylistItems(); if (!Playlist.SequenceEqual(other.Playlist)) { @@ -200,6 +196,15 @@ namespace osu.Game.Online.Rooms Position.Value = other.Position.Value; } + public void RemoveExpiredPlaylistItems() + { + // Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended, + // and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room. + // More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room. + if (!(Status.Value is RoomStatusEnded)) + Playlist.RemoveAll(i => i.Expired); + } + public bool ShouldSerializeRoomID() => false; public bool ShouldSerializeHost() => false; public bool ShouldSerializeEndDate() => false; diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 2546374b21..8c617784b9 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -15,8 +15,6 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Replays.Legacy; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Scoring; @@ -46,15 +44,8 @@ namespace osu.Game.Online.Spectator private readonly BindableDictionary playingUserStates = new BindableDictionary(); private IBeatmap? currentBeatmap; - private Score? currentScore; - [Resolved] - private IBindable currentRuleset { get; set; } = null!; - - [Resolved] - private IBindable> currentMods { get; set; } = null!; - private readonly SpectatorState currentState = new SpectatorState(); /// @@ -153,9 +144,9 @@ namespace osu.Game.Online.Spectator IsPlaying = true; // transfer state at point of beginning play - currentState.BeatmapID = beatmap.BeatmapInfo.OnlineBeatmapID; - currentState.RulesetID = currentRuleset.Value.ID; - currentState.Mods = currentMods.Value.Select(m => new APIMod(m)); + currentState.BeatmapID = score.ScoreInfo.Beatmap.OnlineBeatmapID; + currentState.RulesetID = score.ScoreInfo.RulesetID; + currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); currentBeatmap = beatmap.PlayableBeatmap; currentScore = score; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2107b3a0e9..0e55a65aec 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -27,6 +27,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Collections; @@ -968,11 +969,11 @@ namespace osu.Game return component; } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { if (introScreen == null) return false; - switch (action) + switch (e.Action) { case GlobalAction.ResetInputSettings: Host.ResetInputHandlers(); @@ -1006,7 +1007,7 @@ namespace osu.Game #endregion - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } @@ -1075,8 +1076,6 @@ namespace osu.Game OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); API.Activity.BindTo(newOsuScreen.Activity); - MusicController.AllowTrackAdjustments = newOsuScreen.AllowTrackAdjustments; - if (newOsuScreen.HideOverlaysOnEnter) CloseAllOverlays(); else diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f4db0f2603..7aa460981a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -140,8 +140,6 @@ namespace osu.Game private FileStore fileStore; - private SettingsStore settingsStore; - private RulesetConfigCache rulesetConfigCache; private SpectatorClient spectatorClient; @@ -279,8 +277,7 @@ namespace osu.Game migrateDataToRealm(); - dependencies.Cache(settingsStore = new SettingsStore(contextFactory)); - dependencies.Cache(rulesetConfigCache = new RulesetConfigCache(settingsStore)); + dependencies.Cache(rulesetConfigCache = new RulesetConfigCache(realmFactory, RulesetStore)); var powerStatus = CreateBatteryInfo(); if (powerStatus != null) @@ -453,24 +450,27 @@ namespace osu.Game using (var db = contextFactory.GetForWrite()) using (var usage = realmFactory.GetForWrite()) { - var existingBindings = db.Context.DatabasedKeyBinding; + // migrate ruleset settings. can be removed 20220315. + var existingSettings = db.Context.DatabasedSetting; // only migrate data if the realm database is empty. - if (!usage.Realm.All().Any()) + if (!usage.Realm.All().Any()) { - foreach (var dkb in existingBindings) + foreach (var dkb in existingSettings) { - usage.Realm.Add(new RealmKeyBinding + if (dkb.RulesetID == null) continue; + + usage.Realm.Add(new RealmRulesetSetting { - KeyCombinationString = dkb.KeyCombination.ToString(), - ActionInt = (int)dkb.Action, - RulesetID = dkb.RulesetID, - Variant = dkb.Variant + Key = dkb.Key, + Value = dkb.StringValue, + RulesetID = dkb.RulesetID.Value, + Variant = dkb.Variant ?? 0, }); } } - db.Context.RemoveRange(existingBindings); + db.Context.RemoveRange(existingSettings); usage.Commit(); } @@ -527,7 +527,7 @@ namespace osu.Game BeatmapManager?.Dispose(); LocalConfig?.Dispose(); - contextFactory.FlushConnections(); + contextFactory?.FlushConnections(); } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index d1e2ac38df..bbde03aa10 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -3,21 +3,22 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osuTK; -using osu.Framework.Bindables; using osu.Framework.Input.Events; -using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Resources.Localisation.Web; -using osuTK.Graphics; using osu.Game.Rulesets; using osu.Game.Scoring; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapListing { @@ -117,7 +118,7 @@ namespace osu.Game.Overlays.BeatmapListing textBox = new BeatmapSearchTextBox { RelativeSizeAxes = Axes.X, - TypingStarted = () => TypingStarted?.Invoke(), + TextChanged = () => TypingStarted?.Invoke(), }, new ReverseChildIDFillFlowContainer { @@ -167,7 +168,7 @@ namespace osu.Game.Overlays.BeatmapListing /// /// Any time the text box receives key events (even while masked). /// - public Action TypingStarted; + public Action TextChanged; protected override Color4 SelectionColour => Color4.Gray; @@ -181,7 +182,16 @@ namespace osu.Game.Overlays.BeatmapListing if (!base.OnKeyDown(e)) return false; - TypingStarted?.Invoke(); + TextChanged?.Invoke(); + return true; + } + + public override bool OnPressed(KeyBindingPressEvent e) + { + if (!base.OnPressed(e)) + return false; + + TextChanged?.Invoke(); return true; } } diff --git a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs index 4d5c387c4a..c078127353 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels private const float horizontal_padding = 10; private const float vertical_padding = 5; - private FillFlowContainer bottomPanel, statusContainer, titleContainer; + private FillFlowContainer bottomPanel, statusContainer, titleContainer, artistContainer; private PlayButton playButton; private Box progressBar; @@ -89,11 +89,19 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }, } }, - new OsuSpriteText + artistContainer = new FillFlowContainer { - Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), - Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) - }, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) + } + } + } }, }, new Container @@ -213,6 +221,16 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }); } + if (SetInfo.OnlineInfo?.TrackId != null) + { + artistContainer.Add(new FeaturedArtistBeatmapPill + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 10f, Top = 2f }, + }); + } + if (SetInfo.OnlineInfo?.HasVideo ?? false) { statusContainer.Add(new IconPill(FontAwesome.Solid.Film)); diff --git a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs index 00ffd168c1..5011749c5f 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels private const float vertical_padding = 5; private const float height = 70; - private FillFlowContainer statusContainer, titleContainer; + private FillFlowContainer statusContainer, titleContainer, artistContainer; protected BeatmapPanelDownloadButton DownloadButton; private PlayButton playButton; private Box progressBar; @@ -112,10 +112,18 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }, } }, - new OsuSpriteText + artistContainer = new FillFlowContainer { - Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), - Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + new OsuSpriteText + { + Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) + }, + }, }, } }, @@ -227,6 +235,16 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }); } + if (SetInfo.OnlineInfo?.TrackId != null) + { + artistContainer.Add(new FeaturedArtistBeatmapPill + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 10f, Top = 2f }, + }); + } + if (SetInfo.OnlineInfo?.HasVideo ?? false) { statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 6861d17f26..935a89b99b 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -75,6 +75,7 @@ namespace osu.Game.Overlays { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + Masking = true, Padding = new MarginPadding { Horizontal = 20 }, Children = new Drawable[] { @@ -186,21 +187,16 @@ namespace osu.Game.Overlays if (lastContent != null) { - var transform = lastContent.FadeOut(100, Easing.OutQuint); + lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent || lastContent == supporterRequiredContent) - { - // the placeholders may be used multiple times, so don't expire/dispose them. - transform.Schedule(() => panelTarget.Remove(lastContent)); - } - else - { - // Consider the case when the new content is smaller than the last content. - // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird. - // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0. - // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so. - lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y).Then().Schedule(() => lastContent.Expire()); - } + // Consider the case when the new content is smaller than the last content. + // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird. + // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0. + // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so. + var sequence = lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y); + + if (lastContent != notFoundContent && lastContent != supporterRequiredContent) + sequence.Then().Schedule(() => lastContent.Expire()); } if (!content.IsAlive) @@ -208,6 +204,9 @@ namespace osu.Game.Overlays content.FadeInFromZero(200, Easing.OutQuint); currentContent = content; + // currentContent may be one of the placeholders, and still have BypassAutoSizeAxes set to Y from the last fade-out. + // restore to the initial state. + currentContent.BypassAutoSizeAxes = Axes.None; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index a61640a02e..c3b6444a24 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -37,6 +37,7 @@ namespace osu.Game.Overlays.BeatmapSet private readonly OsuSpriteText title, artist; private readonly AuthorInfo author; private readonly ExplicitContentBeatmapPill explicitContentPill; + private readonly FeaturedArtistBeatmapPill featuredArtistPill; private readonly FillFlowContainer downloadButtonsContainer; private readonly BeatmapAvailability beatmapAvailability; private readonly BeatmapSetOnlineStatusPill onlineStatusPill; @@ -129,10 +130,25 @@ namespace osu.Game.Overlays.BeatmapSet } } }, - artist = new OsuSpriteText + new FillFlowContainer { - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true), - Margin = new MarginPadding { Bottom = 20 } + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = 20 }, + Children = new Drawable[] + { + artist = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true), + }, + featuredArtistPill = new FeaturedArtistBeatmapPill + { + Alpha = 0f, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Left = 10 } + } + } }, new Container { @@ -233,6 +249,7 @@ namespace osu.Game.Overlays.BeatmapSet artist.Text = new RomanisableString(setInfo.NewValue.Metadata.ArtistUnicode, setInfo.NewValue.Metadata.Artist); explicitContentPill.Alpha = setInfo.NewValue.OnlineInfo.HasExplicitContent ? 1 : 0; + featuredArtistPill.Alpha = setInfo.NewValue.OnlineInfo.TrackId != null ? 1 : 0; onlineStatusPill.FadeIn(500, Easing.OutQuint); onlineStatusPill.Status = setInfo.NewValue.OnlineInfo.Status; diff --git a/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs new file mode 100644 index 0000000000..fdee0799ff --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class FeaturedArtistBeatmapPill : CompositeDrawable + { + public FeaturedArtistBeatmapPill() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, OverlayColourProvider colourProvider) + { + InternalChild = new CircularContainer + { + Masking = true, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider?.Background5 ?? colours.Gray2, + }, + new OsuSpriteText + { + Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f }, + Text = BeatmapsetsStrings.FeaturedArtistBadgeLabel.ToUpper(), + Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), + Colour = OverlayColourProvider.Blue.Colour1, + } + } + }; + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 5b903372fd..6349f115cb 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.BeatmapSet { public class LeaderboardModSelector : CompositeDrawable { - public readonly BindableList SelectedMods = new BindableList(); + public readonly BindableList SelectedMods = new BindableList(); public readonly Bindable Ruleset = new Bindable(); private readonly FillFlowContainer modsContainer; @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.BeatmapSet return; modsContainer.Add(new ModButton(new ModNoMod())); - modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.UserPlayable).Select(m => new ModButton(m))); + modsContainer.AddRange(ruleset.NewValue.CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m))); modsContainer.ForEach(button => { @@ -76,7 +76,7 @@ namespace osu.Game.Overlays.BeatmapSet updateHighlighted(); } - private void selectionChanged(Mod mod, bool selected) + private void selectionChanged(IMod mod, bool selected) { if (selected) SelectedMods.Add(mod); @@ -101,9 +101,9 @@ namespace osu.Game.Overlays.BeatmapSet private const int duration = 200; public readonly BindableBool Highlighted = new BindableBool(); - public Action OnSelectionChanged; + public Action OnSelectionChanged; - public ModButton(Mod mod) + public ModButton(IMod mod) : base(mod) { Scale = new Vector2(0.4f); diff --git a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs index c2b855a0f8..1b0a62dc4a 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs @@ -26,9 +26,6 @@ namespace osu.Game.Overlays.Changelog private const float image_container_width = 164; private const float heart_size = 75; - private readonly FillFlowContainer textContainer; - private readonly Container imageContainer; - public ChangelogSupporterPromo() { RelativeSizeAxes = Axes.X; @@ -38,6 +35,12 @@ namespace osu.Game.Overlays.Changelog Vertical = 20, Horizontal = 50, }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour, TextureStore textures, OverlayColourProvider colourProvider) + { + SupporterPromoLinkFlowContainer supportLinkText; InternalChildren = new Drawable[] { @@ -59,7 +62,7 @@ namespace osu.Game.Overlays.Changelog new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.3f), + Colour = colourProvider.Background5, }, new Container { @@ -68,7 +71,7 @@ namespace osu.Game.Overlays.Changelog Padding = new MarginPadding { Horizontal = 75 }, Children = new Drawable[] { - textContainer = new FillFlowContainer + new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -76,91 +79,84 @@ namespace osu.Game.Overlays.Changelog Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Padding = new MarginPadding { Right = 50 + image_container_width }, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = ChangelogStrings.SupportHeading, + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light), + Margin = new MarginPadding { Bottom = 20 }, + }, + supportLinkText = new SupporterPromoLinkFlowContainer(t => + { + t.Font = t.Font.With(size: 14); + t.Colour = colour.PinkLighter; + }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + new OsuTextFlowContainer(t => + { + t.Font = t.Font.With(size: 12); + t.Colour = colour.PinkLighter; + }) + { + Text = ChangelogStrings.SupportText2.ToString(), + Margin = new MarginPadding { Top = 10 }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + }, }, - imageContainer = new Container + new Container { RelativeSizeAxes = Axes.Y, Width = image_container_width, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, + Children = new Drawable[] + { + new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Bottom = 28 }, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + Texture = textures.Get(@"Online/supporter-pippi"), + }, + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(heart_size), + Margin = new MarginPadding { Top = 70 }, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = colour.Pink, + Radius = 10, + Roundness = heart_size / 2, + }, + Child = new Sprite + { + Size = new Vector2(heart_size), + Texture = textures.Get(@"Online/supporter-heart"), + }, + }, + } } } }, } }, }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colour, TextureStore textures) - { - SupporterPromoLinkFlowContainer supportLinkText; - textContainer.Children = new Drawable[] - { - new OsuSpriteText - { - Text = ChangelogStrings.SupportHeading, - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light), - Margin = new MarginPadding { Bottom = 20 }, - }, - supportLinkText = new SupporterPromoLinkFlowContainer(t => - { - t.Font = t.Font.With(size: 14); - t.Colour = colour.PinkLighter; - }) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, - new OsuTextFlowContainer(t => - { - t.Font = t.Font.With(size: 12); - t.Colour = colour.PinkLighter; - }) - { - Text = ChangelogStrings.SupportText2.ToString(), - Margin = new MarginPadding { Top = 10 }, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - } - }; supportLinkText.AddText("Support further development of osu! and "); supportLinkText.AddLink("become an osu!supporter", @"https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold)); supportLinkText.AddText(" today!"); - - imageContainer.Children = new Drawable[] - { - new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Bottom = 28 }, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - Texture = textures.Get(@"Online/supporter-pippi"), - }, - new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Size = new Vector2(heart_size), - Margin = new MarginPadding { Top = 70 }, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = colour.Pink, - Radius = 10, - Roundness = heart_size / 2, - }, - Child = new Sprite - { - Size = new Vector2(heart_size), - Texture = textures.Get(@"Online/supporter-heart"), - }, - }, - }; } private class SupporterPromoLinkFlowContainer : LinkFlowContainer diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index a8f2e654d7..ce12e9554d 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -91,9 +92,9 @@ namespace osu.Game.Overlays Show(); } - public override bool OnPressed(GlobalAction action) + public override bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.Back: if (Current.Value == null) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 0445c63eb4..25c5154d4a 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -372,9 +372,9 @@ namespace osu.Game.Overlays return base.OnKeyDown(e); } - public bool OnPressed(PlatformAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case PlatformAction.TabNew: ChannelTabControl.SelectChannelSelectorTab(); @@ -392,7 +392,7 @@ namespace osu.Game.Overlays return false; } - public void OnReleased(PlatformAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 43ef42a809..d5d31343f2 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -7,6 +7,7 @@ using osu.Game.Overlays.Dialog; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using System.Linq; +using osu.Framework.Input.Events; namespace osu.Game.Overlays { @@ -83,16 +84,16 @@ namespace osu.Game.Overlays this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine); } - public override bool OnPressed(GlobalAction action) + public override bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.Select: CurrentDialog?.Buttons.OfType().FirstOrDefault()?.TriggerClick(); return true; } - return base.OnPressed(action); + return base.OnPressed(e); } } } diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs new file mode 100644 index 0000000000..e43b84d52a --- /dev/null +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -0,0 +1,111 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Configuration; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Overlays.Settings; +using osuTK; + +namespace osu.Game.Overlays.Login +{ + public class LoginForm : FillFlowContainer + { + private TextBox username; + private TextBox password; + private ShakeContainer shakeSignIn; + + [Resolved(CanBeNull = true)] + private IAPIProvider api { get; set; } + + public Action RequestHide; + + private void performLogin() + { + if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text)) + api?.Login(username.Text, password.Text); + else + shakeSignIn.Shake(); + } + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(OsuConfigManager config, AccountCreationOverlay accountCreation) + { + Direction = FillDirection.Vertical; + Spacing = new Vector2(0, 5); + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + Children = new Drawable[] + { + username = new OsuTextBox + { + PlaceholderText = "username", + RelativeSizeAxes = Axes.X, + Text = api?.ProvidedUsername ?? string.Empty, + TabbableContentContainer = this + }, + password = new OsuPasswordTextBox + { + PlaceholderText = "password", + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + }, + new SettingsCheckbox + { + LabelText = "Remember username", + Current = config.GetBindable(OsuSetting.SaveUsername), + }, + new SettingsCheckbox + { + LabelText = "Stay signed in", + Current = config.GetBindable(OsuSetting.SavePassword), + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + shakeSignIn = new ShakeContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new SettingsButton + { + Text = "Sign in", + Action = performLogin + }, + } + } + }, + new SettingsButton + { + Text = "Register", + Action = () => + { + RequestHide(); + accountCreation.Show(); + } + } + }; + + password.OnCommit += (sender, newText) => performLogin(); + } + + public override bool AcceptsFocus => true; + + protected override bool OnClick(ClickEvent e) => true; + + protected override void OnFocus(FocusEvent e) + { + Schedule(() => { GetContainingInputManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs new file mode 100644 index 0000000000..d1e5bfe809 --- /dev/null +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -0,0 +1,199 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Users; +using osuTK; +using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; +using Container = osu.Framework.Graphics.Containers.Container; + +namespace osu.Game.Overlays.Login +{ + public class LoginPanel : FillFlowContainer + { + private bool bounding = true; + private LoginForm form; + + [Resolved] + private OsuColour colours { get; set; } + + private UserGridPanel panel; + private UserDropdown dropdown; + + /// + /// Called to request a hide of a parent displaying this container. + /// + public Action RequestHide; + + private readonly IBindable apiState = new Bindable(); + + [Resolved] + private IAPIProvider api { get; set; } + + public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty; + + public bool Bounding + { + get => bounding; + set + { + bounding = value; + Invalidate(Invalidation.MiscGeometry); + } + } + + public LoginPanel() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Spacing = new Vector2(0f, 5f); + } + + [BackgroundDependencyLoader] + private void load() + { + apiState.BindTo(api.State); + apiState.BindValueChanged(onlineStateChanged, true); + } + + private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => + { + form = null; + + switch (state.NewValue) + { + case APIState.Offline: + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "ACCOUNT", + Margin = new MarginPadding { Bottom = 5 }, + Font = OsuFont.GetFont(weight: FontWeight.Bold), + }, + form = new LoginForm + { + RequestHide = RequestHide + } + }; + break; + + case APIState.Failing: + case APIState.Connecting: + LinkFlowContainer linkFlow; + + Children = new Drawable[] + { + new LoadingSpinner + { + State = { Value = Visibility.Visible }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + linkFlow = new LinkFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + TextAnchor = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Text = state.NewValue == APIState.Failing ? "Connection is failing, will attempt to reconnect... " : "Attempting to connect... ", + Margin = new MarginPadding { Top = 10, Bottom = 10 }, + }, + }; + + linkFlow.AddLink("cancel", api.Logout, string.Empty); + break; + + case APIState.Online: + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = 20, Right = 20 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 10f), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Signed in", + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold), + Margin = new MarginPadding { Top = 5, Bottom = 5 }, + }, + }, + }, + panel = new UserGridPanel(api.LocalUser.Value) + { + RelativeSizeAxes = Axes.X, + Action = RequestHide + }, + dropdown = new UserDropdown { RelativeSizeAxes = Axes.X }, + }, + }, + }; + + panel.Status.BindTo(api.LocalUser.Value.Status); + panel.Activity.BindTo(api.LocalUser.Value.Activity); + + dropdown.Current.BindValueChanged(action => + { + switch (action.NewValue) + { + case UserAction.Online: + api.LocalUser.Value.Status.Value = new UserStatusOnline(); + dropdown.StatusColour = colours.Green; + break; + + case UserAction.DoNotDisturb: + api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb(); + dropdown.StatusColour = colours.Red; + break; + + case UserAction.AppearOffline: + api.LocalUser.Value.Status.Value = new UserStatusOffline(); + dropdown.StatusColour = colours.Gray7; + break; + + case UserAction.SignOut: + api.Logout(); + break; + } + }, true); + break; + } + + if (form != null) GetContainingInputManager()?.ChangeFocus(form); + }); + + public override bool AcceptsFocus => true; + + protected override bool OnClick(ClickEvent e) => true; + + protected override void OnFocus(FocusEvent e) + { + if (form != null) GetContainingInputManager().ChangeFocus(form); + base.OnFocus(e); + } + } +} diff --git a/osu.Game/Overlays/Login/UserAction.cs b/osu.Game/Overlays/Login/UserAction.cs new file mode 100644 index 0000000000..07b6b4bf7e --- /dev/null +++ b/osu.Game/Overlays/Login/UserAction.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Overlays.Login +{ + public enum UserAction + { + Online, + + [Description(@"Do not disturb")] + DoNotDisturb, + + [Description(@"Appear offline")] + AppearOffline, + + [Description(@"Sign out")] + SignOut, + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Login/UserDropdown.cs b/osu.Game/Overlays/Login/UserDropdown.cs new file mode 100644 index 0000000000..ac4e7f8eda --- /dev/null +++ b/osu.Game/Overlays/Login/UserDropdown.cs @@ -0,0 +1,124 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Login +{ + public class UserDropdown : OsuEnumDropdown + { + protected override DropdownHeader CreateHeader() => new UserDropdownHeader(); + + protected override DropdownMenu CreateMenu() => new UserDropdownMenu(); + + public Color4 StatusColour + { + set + { + if (Header is UserDropdownHeader h) + h.StatusColour = value; + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Gray5; + } + + protected class UserDropdownMenu : OsuDropdownMenu + { + public UserDropdownMenu() + { + Masking = true; + CornerRadius = 5; + + Margin = new MarginPadding { Bottom = 5 }; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Radius = 4, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Gray3; + } + + protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new DrawableUserDropdownMenuItem(item); + + private class DrawableUserDropdownMenuItem : DrawableOsuDropdownMenuItem + { + public DrawableUserDropdownMenuItem(MenuItem item) + : base(item) + { + Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 5 }; + CornerRadius = 5; + } + + protected override Drawable CreateContent() => new Content + { + Label = { Margin = new MarginPadding { Left = UserDropdownHeader.LABEL_LEFT_MARGIN - 11 } } + }; + } + } + + private class UserDropdownHeader : OsuDropdownHeader + { + public const float LABEL_LEFT_MARGIN = 20; + + private readonly SpriteIcon statusIcon; + + public Color4 StatusColour + { + set => statusIcon.FadeColour(value, 500, Easing.OutQuint); + } + + public UserDropdownHeader() + { + Foreground.Padding = new MarginPadding { Left = 10, Right = 10 }; + Margin = new MarginPadding { Bottom = 5 }; + Masking = true; + CornerRadius = 5; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Radius = 4, + }; + + Icon.Size = new Vector2(14); + Icon.Margin = new MarginPadding(0); + + Foreground.Add(statusIcon = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Regular.Circle, + Size = new Vector2(14), + }); + + Text.Margin = new MarginPadding { Left = LABEL_LEFT_MARGIN }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Gray3; + } + } + } +} diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index e7caaa3aca..f3562aa6d9 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -5,17 +5,17 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -using osu.Game.Overlays.Settings.Sections.General; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; +using osu.Game.Overlays.Login; namespace osu.Game.Overlays { public class LoginOverlay : OsuFocusedOverlayContainer { - private LoginSettings settingsSection; + private LoginPanel panel; private const float transition_time = 400; @@ -50,7 +50,7 @@ namespace osu.Game.Overlays AutoSizeEasing = Easing.OutQuint, Children = new Drawable[] { - settingsSection = new LoginSettings + panel = new LoginPanel { Padding = new MarginPadding(10), RequestHide = Hide, @@ -75,17 +75,17 @@ namespace osu.Game.Overlays { base.PopIn(); - settingsSection.Bounding = true; + panel.Bounding = true; this.FadeIn(transition_time, Easing.OutQuint); - GetContainingInputManager().ChangeFocus(settingsSection); + GetContainingInputManager().ChangeFocus(panel); } protected override void PopOut() { base.PopOut(); - settingsSection.Bounding = false; + panel.Bounding = false; this.FadeOut(transition_time); } } diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs index c8e44ee159..0f51439252 100644 --- a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs @@ -107,9 +107,9 @@ namespace osu.Game.Overlays.Mods var incompatibleTypes = mod.IncompatibleMods; - var allMods = ruleset.Value.CreateInstance().GetAllMods(); + var allMods = ruleset.Value.CreateInstance().AllMods; - incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).ToList(); + incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).Select(m => m.CreateInstance()).ToList(); incompatibleText.Text = incompatibleMods.Value.Any() ? "Incompatible with:" : "Compatible with all mods"; } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index fdef48d556..ec7e49920c 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -416,7 +416,7 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(e); } - public override bool OnPressed(GlobalAction action) => false; // handled by back button + public override bool OnPressed(KeyBindingPressEvent e) => false; // handled by back button private void updateAvailableMods() { diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index f06e02e5e1..dba4bf926f 100644 --- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Input.Bindings; @@ -26,23 +27,23 @@ namespace osu.Game.Overlays.Music [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { if (beatmap.Disabled) return false; - switch (action) + switch (e.Action) { case GlobalAction.MusicPlay: // use previous state as TogglePause may not update the track's state immediately (state update is run on the audio thread see https://github.com/ppy/osu/issues/9880#issuecomment-674668842) bool wasPlaying = musicController.IsPlaying; if (musicController.TogglePause()) - onScreenDisplay?.Display(new MusicActionToast(wasPlaying ? "Pause track" : "Play track", action)); + onScreenDisplay?.Display(new MusicActionToast(wasPlaying ? "Pause track" : "Play track", e.Action)); return true; case GlobalAction.MusicNext: - musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast("Next track", action))); + musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast("Next track", e.Action))); return true; @@ -52,11 +53,11 @@ namespace osu.Game.Overlays.Music switch (res) { case PreviousTrackResult.Restart: - onScreenDisplay?.Display(new MusicActionToast("Restart track", action)); + onScreenDisplay?.Display(new MusicActionToast("Restart track", e.Action)); break; case PreviousTrackResult.Previous: - onScreenDisplay?.Display(new MusicActionToast("Previous track", action)); + onScreenDisplay?.Display(new MusicActionToast("Previous track", e.Action)); break; } }); @@ -67,7 +68,7 @@ namespace osu.Game.Overlays.Music return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index cf930e985c..f5720cffb0 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Profile.Header Origin = Anchor.CentreLeft, Children = new Drawable[] { - avatar = new UpdateableAvatar(openOnClick: false, showGuestOnNull: false) + avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false) { Size = new Vector2(avatar_size), Masking = true, diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs deleted file mode 100644 index 8f757f7a36..0000000000 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ /dev/null @@ -1,421 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Configuration; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osuTK; -using osu.Game.Users; -using System.ComponentModel; -using osu.Framework.Bindables; -using osu.Game.Graphics; -using osuTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; -using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; -using Container = osu.Framework.Graphics.Containers.Container; - -namespace osu.Game.Overlays.Settings.Sections.General -{ - public class LoginSettings : FillFlowContainer - { - private bool bounding = true; - private LoginForm form; - - [Resolved] - private OsuColour colours { get; set; } - - private UserGridPanel panel; - private UserDropdown dropdown; - - /// - /// Called to request a hide of a parent displaying this container. - /// - public Action RequestHide; - - private readonly IBindable apiState = new Bindable(); - - [Resolved] - private IAPIProvider api { get; set; } - - public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty; - - public bool Bounding - { - get => bounding; - set - { - bounding = value; - Invalidate(Invalidation.MiscGeometry); - } - } - - public LoginSettings() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Direction = FillDirection.Vertical; - Spacing = new Vector2(0f, 5f); - } - - [BackgroundDependencyLoader] - private void load() - { - apiState.BindTo(api.State); - apiState.BindValueChanged(onlineStateChanged, true); - } - - private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => - { - form = null; - - switch (state.NewValue) - { - case APIState.Offline: - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "ACCOUNT", - Margin = new MarginPadding { Bottom = 5 }, - Font = OsuFont.GetFont(weight: FontWeight.Bold), - }, - form = new LoginForm - { - RequestHide = RequestHide - } - }; - break; - - case APIState.Failing: - case APIState.Connecting: - LinkFlowContainer linkFlow; - - Children = new Drawable[] - { - new LoadingSpinner - { - State = { Value = Visibility.Visible }, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, - linkFlow = new LinkFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - TextAnchor = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Text = state.NewValue == APIState.Failing ? "Connection is failing, will attempt to reconnect... " : "Attempting to connect... ", - Margin = new MarginPadding { Top = 10, Bottom = 10 }, - }, - }; - - linkFlow.AddLink("cancel", api.Logout, string.Empty); - break; - - case APIState.Online: - Children = new Drawable[] - { - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = 20, Right = 20 }, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0f, 10f), - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new[] - { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Signed in", - Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold), - Margin = new MarginPadding { Top = 5, Bottom = 5 }, - }, - }, - }, - panel = new UserGridPanel(api.LocalUser.Value) - { - RelativeSizeAxes = Axes.X, - Action = RequestHide - }, - dropdown = new UserDropdown { RelativeSizeAxes = Axes.X }, - }, - }, - }; - - panel.Status.BindTo(api.LocalUser.Value.Status); - panel.Activity.BindTo(api.LocalUser.Value.Activity); - - dropdown.Current.BindValueChanged(action => - { - switch (action.NewValue) - { - case UserAction.Online: - api.LocalUser.Value.Status.Value = new UserStatusOnline(); - dropdown.StatusColour = colours.Green; - break; - - case UserAction.DoNotDisturb: - api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb(); - dropdown.StatusColour = colours.Red; - break; - - case UserAction.AppearOffline: - api.LocalUser.Value.Status.Value = new UserStatusOffline(); - dropdown.StatusColour = colours.Gray7; - break; - - case UserAction.SignOut: - api.Logout(); - break; - } - }, true); - break; - } - - if (form != null) GetContainingInputManager()?.ChangeFocus(form); - }); - - public override bool AcceptsFocus => true; - - protected override bool OnClick(ClickEvent e) => true; - - protected override void OnFocus(FocusEvent e) - { - if (form != null) GetContainingInputManager().ChangeFocus(form); - base.OnFocus(e); - } - - private class LoginForm : FillFlowContainer - { - private TextBox username; - private TextBox password; - private ShakeContainer shakeSignIn; - - [Resolved(CanBeNull = true)] - private IAPIProvider api { get; set; } - - public Action RequestHide; - - private void performLogin() - { - if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text)) - api?.Login(username.Text, password.Text); - else - shakeSignIn.Shake(); - } - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuConfigManager config, AccountCreationOverlay accountCreation) - { - Direction = FillDirection.Vertical; - Spacing = new Vector2(0, 5); - AutoSizeAxes = Axes.Y; - RelativeSizeAxes = Axes.X; - Children = new Drawable[] - { - username = new OsuTextBox - { - PlaceholderText = "username", - RelativeSizeAxes = Axes.X, - Text = api?.ProvidedUsername ?? string.Empty, - TabbableContentContainer = this - }, - password = new OsuPasswordTextBox - { - PlaceholderText = "password", - RelativeSizeAxes = Axes.X, - TabbableContentContainer = this, - }, - new SettingsCheckbox - { - LabelText = "Remember username", - Current = config.GetBindable(OsuSetting.SaveUsername), - }, - new SettingsCheckbox - { - LabelText = "Stay signed in", - Current = config.GetBindable(OsuSetting.SavePassword), - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - shakeSignIn = new ShakeContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = new SettingsButton - { - Text = "Sign in", - Action = performLogin - }, - } - } - }, - new SettingsButton - { - Text = "Register", - Action = () => - { - RequestHide(); - accountCreation.Show(); - } - } - }; - - password.OnCommit += (sender, newText) => performLogin(); - } - - public override bool AcceptsFocus => true; - - protected override bool OnClick(ClickEvent e) => true; - - protected override void OnFocus(FocusEvent e) - { - Schedule(() => { GetContainingInputManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); - } - } - - private class UserDropdown : OsuEnumDropdown - { - protected override DropdownHeader CreateHeader() => new UserDropdownHeader(); - - protected override DropdownMenu CreateMenu() => new UserDropdownMenu(); - - public Color4 StatusColour - { - set - { - if (Header is UserDropdownHeader h) - h.StatusColour = value; - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AccentColour = colours.Gray5; - } - - private class UserDropdownMenu : OsuDropdownMenu - { - public UserDropdownMenu() - { - Masking = true; - CornerRadius = 5; - - Margin = new MarginPadding { Bottom = 5 }; - - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Radius = 4, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = colours.Gray3; - } - - protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new DrawableUserDropdownMenuItem(item); - - private class DrawableUserDropdownMenuItem : DrawableOsuDropdownMenuItem - { - public DrawableUserDropdownMenuItem(MenuItem item) - : base(item) - { - Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 5 }; - CornerRadius = 5; - } - - protected override Drawable CreateContent() => new Content - { - Label = { Margin = new MarginPadding { Left = UserDropdownHeader.LABEL_LEFT_MARGIN - 11 } } - }; - } - } - - private class UserDropdownHeader : OsuDropdownHeader - { - public const float LABEL_LEFT_MARGIN = 20; - - private readonly SpriteIcon statusIcon; - - public Color4 StatusColour - { - set => statusIcon.FadeColour(value, 500, Easing.OutQuint); - } - - public UserDropdownHeader() - { - Foreground.Padding = new MarginPadding { Left = 10, Right = 10 }; - Margin = new MarginPadding { Bottom = 5 }; - Masking = true; - CornerRadius = 5; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Radius = 4, - }; - - Icon.Size = new Vector2(14); - Icon.Margin = new MarginPadding(0); - - Foreground.Add(statusIcon = new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Regular.Circle, - Size = new Vector2(14), - }); - - Text.Margin = new MarginPadding { Left = LABEL_LEFT_MARGIN }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = colours.Gray3; - } - } - } - - private enum UserAction - { - Online, - - [Description(@"Do not disturb")] - DoNotDisturb, - - [Description(@"Appear offline")] - AppearOffline, - - [Description(@"Sign out")] - SignOut, - } - } -} diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index c38c516f21..85d88c96f8 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -115,7 +115,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, text = new OsuSpriteText { - Text = action.GetDescription(), + Text = action.GetLocalisableDescription(), Margin = new MarginPadding(padding), }, buttons = new FillFlowContainer diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index ef5ccae1a0..fae0318359 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { Text = InputSettingsStrings.ResetSectionButton; RelativeSizeAxes = Axes.X; - Width = 0.5f; + Width = 0.8f; Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; Margin = new MarginPadding { Top = 15 }; diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 7481cfdbf5..dc0b06b255 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -178,12 +178,12 @@ namespace osu.Game.Overlays.Toolbar this.FadeOut(transition_time, Easing.InQuint); } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { if (OverlayActivationMode.Value == OverlayActivation.Disabled) return false; - switch (action) + switch (e.Action) { case GlobalAction.ToggleToolbar: hiddenByUser = State.Value == Visibility.Visible; // set before toggling to allow the operation to always succeed. @@ -194,7 +194,7 @@ namespace osu.Game.Overlays.Toolbar return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 6da41b2b5f..dd554200ca 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -184,9 +184,9 @@ namespace osu.Game.Overlays.Toolbar tooltipContainer.FadeOut(100); } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - if (action == Hotkey) + if (e.Action == Hotkey) { TriggerClick(); return true; @@ -195,7 +195,7 @@ namespace osu.Game.Overlays.Toolbar return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 165c095514..5d4430caa2 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Toolbar Add(new OpaqueBackground { Depth = 1 }); - Flow.Add(avatar = new UpdateableAvatar(openOnClick: false) + Flow.Add(avatar = new UpdateableAvatar(isInteractive: false) { Masking = true, Size = new Vector2(32), diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index b24214ff3d..4129b46ce3 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -19,27 +19,27 @@ namespace osu.Game.Overlays.Volume private ScheduledDelegate keyRepeat; - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.DecreaseVolume: case GlobalAction.IncreaseVolume: keyRepeat?.Cancel(); - keyRepeat = this.BeginKeyRepeat(Scheduler, () => ActionRequested?.Invoke(action), 150); + keyRepeat = this.BeginKeyRepeat(Scheduler, () => ActionRequested?.Invoke(e.Action), 150); return true; case GlobalAction.ToggleMute: case GlobalAction.NextVolumeMeter: case GlobalAction.PreviousVolumeMeter: - ActionRequested?.Invoke(action); + ActionRequested?.Invoke(e.Action); return true; } return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { keyRepeat?.Cancel(); } @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Volume return true; } - public bool OnScroll(GlobalAction action, float amount, bool isPrecise) => - ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false; + public bool OnScroll(KeyBindingScrollEvent e) => + ScrollActionRequested?.Invoke(e.Action, e.ScrollAmount, e.IsPrecise) ?? false; } } diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 7249dd77e5..ff28b45ebb 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -365,12 +365,12 @@ namespace osu.Game.Overlays.Volume { } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { if (!IsHovered) return false; - switch (action) + switch (e.Action) { case GlobalAction.SelectPrevious: State = SelectionState.Selected; @@ -386,7 +386,7 @@ namespace osu.Game.Overlays.Volume return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index 3fb0aa450e..c9ee2cbfd5 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Containers; using HtmlAgilityPack; using osu.Framework.Allocation; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Wiki { @@ -56,12 +56,12 @@ namespace osu.Game.Overlays.Wiki { Vertical = 30, }, - Child = new OsuSpriteText + Child = new OsuTextFlowContainer(t => t.Font = OsuFont.GetFont(Typeface.Inter, size: 12, weight: FontWeight.Light)) { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Text = blurbNode.InnerText, - Font = OsuFont.GetFont(Typeface.Inter, size: 12, weight: FontWeight.Light), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + TextAnchor = Anchor.TopCentre, } }; } diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 0ff3455f00..a0ec8e3e0e 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -2,16 +2,86 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Configuration; using osu.Game.Configuration; +using osu.Game.Database; namespace osu.Game.Rulesets.Configuration { - public abstract class RulesetConfigManager : DatabasedConfigManager, IRulesetConfigManager + public abstract class RulesetConfigManager : ConfigManager, IRulesetConfigManager where TLookup : struct, Enum { - protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) - : base(settings, ruleset, variant) + private readonly RealmContextFactory realmFactory; + + private readonly int variant; + + private List databasedSettings = new List(); + + private readonly int rulesetId; + + protected RulesetConfigManager(SettingsStore store, RulesetInfo ruleset, int? variant = null) { + realmFactory = store?.Realm; + + if (realmFactory != null && !ruleset.ID.HasValue) + throw new InvalidOperationException("Attempted to add databased settings for a non-databased ruleset"); + + rulesetId = ruleset.ID ?? -1; + + this.variant = variant ?? 0; + + Load(); + + InitialiseDefaults(); + } + + protected override void PerformLoad() + { + if (realmFactory != null) + { + // As long as RulesetConfigCache exists, there is no need to subscribe to realm events. + databasedSettings = realmFactory.Context.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); + } + } + + protected override bool PerformSave() + { + // do nothing, realm saves immediately + return true; + } + + protected override void AddBindable(TLookup lookup, Bindable bindable) + { + base.AddBindable(lookup, bindable); + + var setting = databasedSettings.Find(s => s.Key == lookup.ToString()); + + if (setting != null) + { + bindable.Parse(setting.Value); + } + else + { + setting = new RealmRulesetSetting + { + Key = lookup.ToString(), + Value = bindable.Value.ToString(), + RulesetID = rulesetId, + Variant = variant, + }; + + realmFactory?.Context.Write(() => realmFactory.Context.Add(setting)); + + databasedSettings.Add(setting); + } + + bindable.ValueChanged += b => + { + realmFactory?.Context.Write(() => setting.Value = b.NewValue.ToString()); + }; } } } diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index a5e19f293c..d5d1de91de 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Newtonsoft.Json; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Mods { @@ -11,7 +11,37 @@ namespace osu.Game.Rulesets.Mods /// /// The shortened name of this mod. /// - [JsonProperty("acronym")] string Acronym { get; } + + /// + /// The name of this mod. + /// + string Name { get; } + + /// + /// The user readable description of this mod. + /// + string Description { get; } + + /// + /// The type of this mod. + /// + ModType Type { get; } + + /// + /// The icon of this mod. + /// + IconUsage? Icon { get; } + + /// + /// Whether this mod is playable by an end user. + /// Should be false for cases where the user is not interacting with the game (so it can be excluded from multiplayer selection, for example). + /// + bool UserPlayable { get; } + + /// + /// Create a fresh instance based on this mod. + /// + Mod CreateInstance() => (Mod)Activator.CreateInstance(GetType()); } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 1199d8a956..7136795461 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -22,32 +22,17 @@ namespace osu.Game.Rulesets.Mods [ExcludeFromDynamicCompile] public abstract class Mod : IMod, IEquatable, IDeepCloneable { - /// - /// The name of this mod. - /// [JsonIgnore] public abstract string Name { get; } - /// - /// The shortened name of this mod. - /// public abstract string Acronym { get; } - /// - /// The icon of this mod. - /// [JsonIgnore] public virtual IconUsage? Icon => null; - /// - /// The type of this mod. - /// [JsonIgnore] public virtual ModType Type => ModType.Fun; - /// - /// The user readable description of this mod. - /// [JsonIgnore] public abstract string Description { get; } @@ -106,10 +91,6 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool HasImplementation => this is IApplicableMod; - /// - /// Whether this mod is playable by an end user. - /// Should be false for cases where the user is not interacting with the game (so it can be excluded from mutliplayer selection, for example). - /// [JsonIgnore] public virtual bool UserPlayable => true; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 7abae71273..a77a83b36c 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -69,9 +69,11 @@ namespace osu.Game.Rulesets.Mods public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { var flashlight = CreateFlashlight(); - flashlight.Combo = Combo; + flashlight.RelativeSizeAxes = Axes.Both; flashlight.Colour = Color4.Black; + + flashlight.Combo.BindTo(Combo); drawableRuleset.KeyBindingInputManager.Add(flashlight); flashlight.Breaks = drawableRuleset.Beatmap.Breaks; @@ -81,7 +83,8 @@ namespace osu.Game.Rulesets.Mods public abstract class Flashlight : Drawable { - internal BindableInt Combo; + public readonly BindableInt Combo = new BindableInt(); + private IShader shader; protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(this); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index de62cf8d33..b0c3836774 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; @@ -38,13 +39,58 @@ namespace osu.Game.Rulesets { public RulesetInfo RulesetInfo { get; internal set; } - public IEnumerable GetAllMods() => Enum.GetValues(typeof(ModType)).Cast() - // Confine all mods of each mod type into a single IEnumerable - .SelectMany(GetModsFor) - // Filter out all null mods - .Where(mod => mod != null) - // Resolve MultiMods as their .Mods property - .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); + private static readonly ConcurrentDictionary mod_reference_cache = new ConcurrentDictionary(); + + /// + /// A queryable source containing all available mods. + /// Call for consumption purposes. + /// + public IEnumerable AllMods + { + get + { + if (!(RulesetInfo.ID is int id)) + return CreateAllMods(); + + if (!mod_reference_cache.TryGetValue(id, out var mods)) + mod_reference_cache[id] = mods = CreateAllMods().Cast().ToArray(); + + return mods; + } + } + + /// + /// Returns fresh instances of all mods. + /// + /// + /// This comes with considerable allocation overhead. If only accessing for reference purposes (ie. not changing bindables / settings) + /// use instead. + /// + public IEnumerable CreateAllMods() => Enum.GetValues(typeof(ModType)).Cast() + // Confine all mods of each mod type into a single IEnumerable + .SelectMany(GetModsFor) + // Filter out all null mods + .Where(mod => mod != null) + // Resolve MultiMods as their .Mods property + .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); + + /// + /// Returns a fresh instance of the mod matching the specified acronym. + /// + /// The acronym to query for . + public Mod CreateModFromAcronym(string acronym) + { + return AllMods.FirstOrDefault(m => m.Acronym == acronym)?.CreateInstance(); + } + + /// + /// Returns a fresh instance of the mod matching the specified type. + /// + public T CreateMod() + where T : Mod + { + return AllMods.FirstOrDefault(m => m is T)?.CreateInstance() as T; + } public abstract IEnumerable GetModsFor(ModType type); @@ -126,7 +172,7 @@ namespace osu.Game.Rulesets } [CanBeNull] - public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().FirstOrDefault(); + public ModAutoplay GetAutoplayMod() => CreateMod(); public virtual ISkin CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null; diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index d42428638c..aeac052673 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -2,9 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Concurrent; +using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Rulesets.Configuration; namespace osu.Game.Rulesets @@ -15,12 +16,31 @@ namespace osu.Game.Rulesets /// public class RulesetConfigCache : Component { - private readonly ConcurrentDictionary configCache = new ConcurrentDictionary(); - private readonly SettingsStore settingsStore; + private readonly RealmContextFactory realmFactory; + private readonly RulesetStore rulesets; - public RulesetConfigCache(SettingsStore settingsStore) + private readonly Dictionary configCache = new Dictionary(); + + public RulesetConfigCache(RealmContextFactory realmFactory, RulesetStore rulesets) { - this.settingsStore = settingsStore; + this.realmFactory = realmFactory; + this.rulesets = rulesets; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + var settingsStore = new SettingsStore(realmFactory); + + // let's keep things simple for now and just retrieve all the required configs at startup.. + foreach (var ruleset in rulesets.AvailableRulesets) + { + if (ruleset.ID == null) + continue; + + configCache[ruleset.ID.Value] = ruleset.CreateInstance().CreateConfig(settingsStore); + } } /// @@ -34,7 +54,12 @@ namespace osu.Game.Rulesets if (ruleset.RulesetInfo.ID == null) return null; - return configCache.GetOrAdd(ruleset.RulesetInfo.ID.Value, _ => ruleset.CreateConfig(settingsStore)); + if (!configCache.TryGetValue(ruleset.RulesetInfo.ID.Value, out var config)) + // any ruleset request which wasn't initialised on startup should not be stored to realm. + // this should only be used by tests. + return ruleset.CreateConfig(null); + + return config; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 725cfa9c26..79bada0490 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -30,12 +30,12 @@ namespace osu.Game.Rulesets.UI private const float size = 80; - public virtual LocalisableString TooltipText => showTooltip ? mod.IconTooltip : null; + public virtual LocalisableString TooltipText => showTooltip ? ((mod as Mod)?.IconTooltip ?? mod.Name) : null; - private Mod mod; + private IMod mod; private readonly bool showTooltip; - public Mod Mod + public IMod Mod { get => mod; set @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.UI /// /// The mod to be displayed /// Whether a tooltip describing the mod should display on hover. - public ModIcon(Mod mod, bool showTooltip = true) + public ModIcon(IMod mod, bool showTooltip = true) { this.mod = mod ?? throw new ArgumentNullException(nameof(mod)); this.showTooltip = showTooltip; @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI updateMod(mod); } - private void updateMod(Mod value) + private void updateMod(IMod value) { modAcronym.Text = value.Acronym; modIcon.Icon = value.Icon ?? FontAwesome.Solid.Question; diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index d18e0f9541..b57c224059 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -70,16 +70,16 @@ namespace osu.Game.Rulesets.UI return base.OnMouseMove(e); } - public bool OnPressed(T action) + public bool OnPressed(KeyBindingPressEvent e) { - pressedActions.Add(action); + pressedActions.Add(e.Action); recordFrame(true); return false; } - public void OnReleased(T action) + public void OnReleased(KeyBindingReleaseEvent e) { - pressedActions.Remove(action); + pressedActions.Remove(e.Action); recordFrame(true); } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index e6cd2aa3dc..a3f311c7a6 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -152,12 +152,12 @@ namespace osu.Game.Rulesets.UI { } - public bool OnPressed(T action) => Target.Children.OfType>().Any(c => c.OnPressed(action, Clock.Rate >= 0)); + public bool OnPressed(KeyBindingPressEvent e) => Target.Children.OfType>().Any(c => c.OnPressed(e.Action, Clock.Rate >= 0)); - public void OnReleased(T action) + public void OnReleased(KeyBindingReleaseEvent e) { foreach (var c in Target.Children.OfType>()) - c.OnReleased(action, Clock.Rate >= 0); + c.OnReleased(e.Action, Clock.Rate >= 0); } } diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index f8d5a6c5a9..7b30bb9574 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Lists; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -188,12 +189,12 @@ namespace osu.Game.Rulesets.UI.Scrolling /// The amount to adjust by. Greater than 0 if the scroll speed should be increased, less than 0 if it should be decreased. protected virtual void AdjustScrollSpeed(int amount) => this.TransformBindableTo(TimeRange, TimeRange.Value - amount * time_span_step, 200, Easing.OutQuint); - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { if (!UserScrollSpeedAdjustment) return false; - switch (action) + switch (e.Action) { case GlobalAction.IncreaseScrollSpeed: scheduleScrollSpeedAdjustment(1); @@ -209,7 +210,7 @@ namespace osu.Game.Rulesets.UI.Scrolling private ScheduledDelegate scheduledScrollSpeedAdjustment; - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { scheduledScrollSpeedAdjustment?.Cancel(); scheduledScrollSpeedAdjustment = null; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 2f17167297..2e1a29372d 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -67,7 +67,7 @@ namespace osu.Game.Scoring.Legacy // lazer replays get a really high version number. if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION) - scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.GetAllMods().OfType().Single()).ToArray(); + scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.CreateMod()).ToArray(); currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods); scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index b99dacbd4a..75d4d13f94 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -228,9 +228,9 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; } - public bool OnPressed(PlatformAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case PlatformAction.SelectAll: SelectAll(); @@ -240,7 +240,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; } - public void OnReleased(PlatformAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 1d1d95890f..44eb062db8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -137,9 +137,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any items could be reversed. public virtual bool HandleReverse() => false; - public bool OnPressed(PlatformAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case PlatformAction.Delete: DeleteSelected(); @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; } - public void OnReleased(PlatformAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs index 354013a5fd..c43e554b85 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs @@ -4,6 +4,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Objects; using osuTK; @@ -20,9 +21,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.EditorNudgeLeft: nudgeSelection(-1); @@ -36,7 +37,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 62b3d33069..926a2ad4e0 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -77,15 +78,15 @@ namespace osu.Game.Screens.Edit.Compose #region Input Handling - public bool OnPressed(PlatformAction action) + public bool OnPressed(KeyBindingPressEvent e) { - if (action == PlatformAction.Copy) - host.GetClipboard().SetText(formatSelectionAsString()); + if (e.Action == PlatformAction.Copy) + host.GetClipboard()?.SetText(formatSelectionAsString()); return false; } - public void OnReleased(PlatformAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 1b9a94da58..2ff0101dc0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Edit public override bool DisallowExternalBeatmapRulesetChanges => true; - public override bool AllowTrackAdjustments => false; + public override bool? AllowTrackAdjustments => false; protected bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash; @@ -317,6 +317,16 @@ namespace osu.Game.Screens.Edit /// public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track); + /// + /// Restore the editor to a provided state. + /// + /// The state to restore. + public void RestoreState([NotNull] EditorState state) => Schedule(() => + { + clock.Seek(state.Time); + clipboard.Value = state.ClipboardContent; + }); + protected void Save() { // no longer new after first user-triggered save. @@ -337,9 +347,9 @@ namespace osu.Game.Screens.Edit clock.ProcessFrame(); } - public bool OnPressed(PlatformAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case PlatformAction.Cut: Cut(); @@ -369,7 +379,7 @@ namespace osu.Game.Screens.Edit return false; } - public void OnReleased(PlatformAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } @@ -424,9 +434,9 @@ namespace osu.Game.Screens.Edit return true; } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.Back: // as we don't want to display the back button, manual handling of exit action is required. @@ -458,7 +468,7 @@ namespace osu.Game.Screens.Edit } } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } @@ -737,10 +747,14 @@ namespace osu.Game.Screens.Edit private DifficultyMenuItem createDifficultyMenuItem(BeatmapInfo beatmapInfo) { bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(beatmapInfo); - return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, switchToDifficulty); + return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty); } - private void switchToDifficulty(BeatmapInfo beatmapInfo) => loader?.ScheduleDifficultySwitch(beatmapInfo); + protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState + { + Time = clock.CurrentTimeAccurate, + ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? clipboard.Value : string.Empty + }); private void cancelExit() => loader?.CancelPendingDifficultySwitch(); diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index aec7d32939..2a01a5b6b2 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -20,6 +20,13 @@ namespace osu.Game.Screens.Edit /// public class EditorLoader : ScreenWithBeatmapBackground { + /// + /// The stored state from the last editor opened. + /// This will be read by the next editor instance to be opened to restore any relevant previous state. + /// + [CanBeNull] + private EditorState state; + public override float BackgroundParallaxAmount => 0.1f; public override bool AllowBackButton => false; @@ -34,6 +41,20 @@ namespace osu.Game.Screens.Edit [CanBeNull] private ScheduledDelegate scheduledDifficultySwitch; + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new Drawable[] + { + new LoadingSpinner(true) + { + State = { Value = Visibility.Visible }, + } + }); + } + + protected virtual Editor CreateEditor() => new Editor(this); + protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); @@ -47,19 +68,7 @@ namespace osu.Game.Screens.Edit } } - [BackgroundDependencyLoader] - private void load() - { - AddRangeInternal(new Drawable[] - { - new LoadingSpinner(true) - { - State = { Value = Visibility.Visible }, - } - }); - } - - public void ScheduleDifficultySwitch(BeatmapInfo beatmapInfo) + public void ScheduleDifficultySwitch(BeatmapInfo nextBeatmap, EditorState editorState) { scheduledDifficultySwitch?.Cancel(); ValidForResume = true; @@ -68,7 +77,8 @@ namespace osu.Game.Screens.Edit scheduledDifficultySwitch = Schedule(() => { - Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); + Beatmap.Value = beatmapManager.GetWorkingBeatmap(nextBeatmap); + state = editorState; // This screen is a weird exception to the rule that nothing after song select changes the global beatmap. // Because of this, we need to update the background stack's beatmap to match. @@ -81,7 +91,13 @@ namespace osu.Game.Screens.Edit private void pushEditor() { - this.Push(new Editor(this)); + var editor = CreateEditor(); + + this.Push(editor); + + if (state != null) + editor.RestoreState(state); + ValidForResume = false; } diff --git a/osu.Game/Screens/Edit/EditorState.cs b/osu.Game/Screens/Edit/EditorState.cs new file mode 100644 index 0000000000..4690074e3d --- /dev/null +++ b/osu.Game/Screens/Edit/EditorState.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +namespace osu.Game.Screens.Edit +{ + /// + /// Structure used to convey the general state of an instance. + /// + public class EditorState + { + /// + /// The current audio time. + /// + public double Time { get; set; } + + /// + /// The editor clipboard content. + /// + public string ClipboardContent { get; set; } = string.Empty; + } +} diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index 90f95a668e..d5d93db050 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -25,6 +25,7 @@ namespace osu.Game.Screens.Edit.Setup private LabelledSwitchButton widescreenSupport; private LabelledSwitchButton epilepsyWarning; private LabelledSwitchButton letterboxDuringBreaks; + private LabelledSwitchButton samplesMatchPlaybackRate; public override LocalisableString Title => "Design"; @@ -79,6 +80,12 @@ namespace osu.Game.Screens.Edit.Setup Label = "Letterbox during breaks", Description = "Adds horizontal letterboxing to give a cinematic look during breaks.", Current = { Value = Beatmap.BeatmapInfo.LetterboxInBreaks } + }, + samplesMatchPlaybackRate = new LabelledSwitchButton + { + Label = "Samples match playback rate", + Description = "When enabled, all samples will speed up or slow down when rate-changing mods are enabled.", + Current = { Value = Beatmap.BeatmapInfo.SamplesMatchPlaybackRate } } }; } @@ -96,6 +103,7 @@ namespace osu.Game.Screens.Edit.Setup widescreenSupport.Current.BindValueChanged(_ => updateBeatmap()); epilepsyWarning.Current.BindValueChanged(_ => updateBeatmap()); letterboxDuringBreaks.Current.BindValueChanged(_ => updateBeatmap()); + samplesMatchPlaybackRate.Current.BindValueChanged(_ => updateBeatmap()); } private void updateCountdownSettingsVisibility() => CountdownSettings.FadeTo(EnableCountdown.Current.Value ? 1 : 0); @@ -115,6 +123,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.BeatmapInfo.WidescreenStoryboard = widescreenSupport.Current.Value; Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value; Beatmap.BeatmapInfo.LetterboxInBreaks = letterboxDuringBreaks.Current.Value; + Beatmap.BeatmapInfo.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value; } } } diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index 05a8fdd26d..b38a62d838 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; @@ -52,7 +53,7 @@ namespace osu.Game.Screens.Edit.Verify if (issue.Time != null) { clock.Seek(issue.Time.Value); - editor.OnPressed(GlobalAction.EditorComposeMode); + editor.OnPressed(new KeyBindingPressEvent(GetContainingInputManager().CurrentState, GlobalAction.EditorComposeMode)); } if (!issue.HitObjects.Any()) diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index 17384c161c..910a0c7d61 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -59,9 +59,10 @@ namespace osu.Game.Screens Bindable Ruleset { get; } /// - /// Whether mod track adjustments are allowed to be applied. + /// Whether mod track adjustments should be applied on entering this screen. + /// A value means that the parent screen's value of this setting will be used. /// - bool AllowTrackAdjustments { get; } + bool? AllowTrackAdjustments { get; } /// /// Invoked when the back button has been pressed to close any overlays before exiting this . diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 6c712e9d5b..5f76176aab 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -218,9 +218,9 @@ namespace osu.Game.Screens.Menu return base.OnKeyDown(e); } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.Back: return goBack(); @@ -234,7 +234,7 @@ namespace osu.Game.Screens.Menu } } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs index a491283e5f..364da2f887 100644 --- a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs +++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -18,9 +19,9 @@ namespace osu.Game.Screens.Menu { } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - if (action == GlobalAction.Back) + if (e.Action == GlobalAction.Back) { BeginConfirm(); return true; @@ -29,9 +30,9 @@ namespace osu.Game.Screens.Menu return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - if (action == GlobalAction.Back) + if (e.Action == GlobalAction.Back) { if (!Fired) AbortConfirm(); diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 07a94fb97e..cfe14eab92 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -115,7 +115,9 @@ namespace osu.Game.Screens.Menu if (setInfo == null) return false; - return (initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0])) != null; + initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); + + return UsingThemedIntro = initialBeatmap != null; } } @@ -165,7 +167,7 @@ namespace osu.Game.Screens.Menu protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); - protected void StartTrack() + protected virtual void StartTrack() { // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. if (UsingThemedIntro) @@ -184,7 +186,6 @@ namespace osu.Game.Screens.Menu { beatmap.Value = initialBeatmap; Track = initialBeatmap.Track; - UsingThemedIntro = !initialBeatmap.Track.IsDummyDevice; // ensure the track starts at maximum volume musicController.CurrentTrack.FinishTransforms(); diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 0ea83fe5e7..a8ca17cec1 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -41,6 +41,9 @@ namespace osu.Game.Screens.Menu private Sample welcome; + private DecoupleableInterpolatingFramedClock decoupledClock; + private TrianglesIntroSequence intro; + [BackgroundDependencyLoader] private void load() { @@ -56,10 +59,18 @@ namespace osu.Game.Screens.Menu { PrepareMenuLoad(); - LoadComponentAsync(new TrianglesIntroSequence(logo, background) + decoupledClock = new DecoupleableInterpolatingFramedClock + { + IsCoupled = false + }; + + if (UsingThemedIntro) + decoupledClock.ChangeSource(Track); + + LoadComponentAsync(intro = new TrianglesIntroSequence(logo, background) { RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(UsingThemedIntro ? Track : null), + Clock = decoupledClock, LoadMenu = LoadMenu }, t => { @@ -72,12 +83,25 @@ namespace osu.Game.Screens.Menu } } + public override void OnSuspending(IScreen next) + { + base.OnSuspending(next); + + // important as there is a clock attached to a track which will likely be disposed before returning to this screen. + intro.Expire(); + } + public override void OnResuming(IScreen last) { base.OnResuming(last); background.FadeOut(100); } + protected override void StartTrack() + { + decoupledClock.Start(); + } + private class TrianglesIntroSequence : CompositeDrawable { private readonly OsuLogo logo; diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 8b2ec43e3e..221b31f855 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -36,8 +36,6 @@ namespace osu.Game.Screens.Menu public override bool AllowExternalScreenChange => true; - public override bool AllowTrackAdjustments => false; - private Screen songSelect; private MenuSideFlashes sideFlashes; diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index daac6a66cd..fcf7767958 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -57,7 +57,10 @@ namespace osu.Game.Screens.OnlinePlay.Components } foreach (var incoming in result) + { + incoming.RemoveExpiredPlaylistItems(); RoomManager.AddOrUpdateRoom(incoming); + } initialRoomsReceived.Value = true; tcs.SetResult(true); diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 3b6c1c8be0..a64d89b699 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -87,9 +87,10 @@ namespace osu.Game.Screens.OnlinePlay.Components currentJoinRoomRequest.Failure += exception => { - if (!(exception is OperationCanceledException)) - Logger.Log($"Failed to join room: {exception}", level: LogLevel.Important); - onError?.Invoke(exception.ToString()); + if (exception is OperationCanceledException) + return; + + onError?.Invoke(exception.Message); }; api.Queue(currentJoinRoomRequest); diff --git a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs index 0769d0747b..b9d2bdf23e 100644 --- a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs @@ -39,6 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Components pollReq.Success += result => { + result.RemoveExpiredPlaylistItems(); RoomManager.AddOrUpdateRoom(result); tcs.SetResult(true); }; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 76cb02199b..907b7e308a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -141,29 +141,29 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components #region Key selection logic (shared with BeatmapCarousel) - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.SelectNext: - beginRepeatSelection(() => selectNext(1), action); + beginRepeatSelection(() => selectNext(1), e.Action); return true; case GlobalAction.SelectPrevious: - beginRepeatSelection(() => selectNext(-1), action); + beginRepeatSelection(() => selectNext(-1), e.Action); return true; } return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.SelectNext: case GlobalAction.SelectPrevious: - endRepeatSelection(action); + endRepeatSelection(e.Action); break; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index a13d67a0c9..fe7c7cc364 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -15,6 +14,9 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Extensions; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input.Bindings; @@ -120,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge } } - public Popover GetPopover() => new PasswordEntryPopover(Room) { JoinRequested = lounge.Join }; + public Popover GetPopover() => new PasswordEntryPopover(Room); public MenuItem[] ContextMenuItems => new MenuItem[] { @@ -130,12 +132,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge }) }; - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { if (SelectedRoom.Value != Room) return false; - switch (action) + switch (e.Action) { case GlobalAction.Select: TriggerClick(); @@ -145,7 +147,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } @@ -176,7 +178,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { private readonly Room room; - public Action JoinRequested; + [Resolved(canBeNull: true)] + private LoungeSubScreen lounge { get; set; } public PasswordEntryPopover(Room room) { @@ -184,30 +187,68 @@ namespace osu.Game.Screens.OnlinePlay.Lounge } private OsuPasswordTextBox passwordTextbox; + private TriangleButton joinButton; + private OsuSpriteText errorText; + private Sample sampleJoinFail; [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours, AudioManager audio) { Child = new FillFlowContainer { Margin = new MarginPadding(10), Spacing = new Vector2(5), AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, + Direction = FillDirection.Vertical, Children = new Drawable[] { - passwordTextbox = new OsuPasswordTextBox + new FillFlowContainer { - Width = 200, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + passwordTextbox = new OsuPasswordTextBox + { + Width = 200, + PlaceholderText = "password", + }, + joinButton = new TriangleButton + { + Width = 80, + Text = "Join Room", + } + } }, - new TriangleButton + errorText = new OsuSpriteText { - Width = 80, - Text = "Join Room", - Action = () => JoinRequested?.Invoke(room, passwordTextbox.Text) - } + Colour = colours.Red, + }, } }; + + sampleJoinFail = audio.Samples.Get(@"UI/password-fail"); + + joinButton.Action = () => lounge?.Join(room, passwordTextbox.Text, null, joinFailed); + } + + private void joinFailed(string error) + { + passwordTextbox.Text = string.Empty; + + GetContainingInputManager().ChangeFocus(passwordTextbox); + + errorText.Text = error; + errorText + .FadeIn() + .FlashColour(Color4.White, 200) + .Delay(1000) + .FadeOutFromOne(1000, Easing.In); + + Body.Shake(); + + sampleJoinFail?.Play(); } protected override void LoadComplete() @@ -215,7 +256,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge base.LoadComplete(); Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox)); - passwordTextbox.OnCommit += (_, __) => JoinRequested?.Invoke(room, passwordTextbox.Text); + passwordTextbox.OnCommit += (_, __) => lounge?.Join(room, passwordTextbox.Text, null, joinFailed); } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index cca1394b6d..08bdd0487a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -290,7 +290,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge popoverContainer.HidePopover(); } - public void Join(Room room, string password) => Schedule(() => + public void Join(Room room, string password, Action onSuccess = null, Action onFailure = null) => Schedule(() => { if (joiningRoomOperation != null) return; @@ -302,10 +302,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge Open(room); joiningRoomOperation?.Dispose(); joiningRoomOperation = null; - }, _ => + onSuccess?.Invoke(room); + }, error => { joiningRoomOperation?.Dispose(); joiningRoomOperation = null; + onFailure?.Invoke(error); }); }); diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs b/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs index 3801463095..53131ab90e 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; namespace osu.Game.Screens.OnlinePlay.Match.Components { @@ -16,12 +17,12 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components Triangles.TriangleScale = 1.5f; } - public bool OnPressed(PlatformAction action) + public bool OnPressed(KeyBindingPressEvent e) { if (!Enabled.Value) return false; - switch (action) + switch (e.Action) { case PlatformAction.DocumentNew: // might as well also handle new tab. it's a bit of an undefined flow on this screen. @@ -33,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components return false; } - public void OnReleased(PlatformAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } } diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs index 7a8839cdad..a6cdde14f6 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -63,9 +64,9 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components Settings.Delay(TRANSITION_DURATION / 2).FadeOut(TRANSITION_DURATION / 2); } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.Select: if (IsLoading) @@ -86,7 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 9095b78eb4..bcb793062b 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -29,6 +29,8 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached(typeof(IBindable))] protected readonly Bindable SelectedItem = new Bindable(); + public override bool? AllowTrackAdjustments => true; + protected override BackgroundScreen CreateBackground() => new RoomBackgroundScreen(Room.Playlist.FirstOrDefault()) { SelectedItem = { BindTarget = SelectedItem } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index 3af72fa25a..af0c50a848 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs @@ -71,9 +71,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }, true); } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.ToggleChatFocus: if (Textbox.HasFocus) @@ -94,7 +94,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs index 351b9b3673..833fbd6605 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs @@ -3,6 +3,8 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -22,6 +24,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants private Drawable box; + private Sample sampleTeamSwap; + [Resolved] private OsuColour colours { get; set; } @@ -39,7 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { box = new Container { @@ -72,6 +76,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants { InternalChild = box; } + + sampleTeamSwap = audio.Samples.Get(@"Multiplayer/team-swap"); } private void changeTeam() @@ -99,6 +105,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants if (newTeam == displayedTeam) return; + // only play the sample if an already valid team changes to another valid team. + // this avoids playing a sound for each user if the match type is changed to/from a team mode. + if (newTeam != null && displayedTeam != null) + sampleTeamSwap?.Play(); + displayedTeam = newTeam; if (displayedTeam != null) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index bf7c738882..c45e3a79da 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public override bool DisallowExternalBeatmapRulesetChanges => true; // We are managing our own adjustments. For now, this happens inside the Player instances themselves. - public override bool AllowTrackAdjustments => false; + public override bool? AllowTrackAdjustments => false; /// /// Whether all spectating players have finished loading. diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 9aec2a5c19..ccc891d3bf 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -11,11 +11,11 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using osu.Game.Screens.Menu; using osu.Game.Overlays; -using osu.Game.Users; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Menu; +using osu.Game.Users; namespace osu.Game.Screens { @@ -81,7 +81,10 @@ namespace osu.Game.Screens public virtual float BackgroundParallaxAmount => 1; - public virtual bool AllowTrackAdjustments => true; + [Resolved] + private MusicController musicController { get; set; } + + public virtual bool? AllowTrackAdjustments => null; public Bindable Beatmap { get; private set; } @@ -91,6 +94,8 @@ namespace osu.Game.Screens private OsuScreenDependencies screenDependencies; + private bool? trackAdjustmentStateAtSuspend; + internal void CreateLeasedDependencies(IReadOnlyDependencyContainer dependencies) => createDependencies(dependencies); protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -170,8 +175,14 @@ namespace osu.Game.Screens { if (PlayResumeSound) sampleExit?.Play(); + applyArrivingDefaults(true); + // it's feasible to resume to a screen if the target screen never loaded successfully. + // in such a case there's no need to restore this value. + if (trackAdjustmentStateAtSuspend != null) + musicController.AllowTrackAdjustments = trackAdjustmentStateAtSuspend.Value; + base.OnResuming(last); } @@ -179,6 +190,8 @@ namespace osu.Game.Screens { base.OnSuspending(next); + trackAdjustmentStateAtSuspend = musicController.AllowTrackAdjustments; + onSuspendingLogo(); } @@ -186,6 +199,9 @@ namespace osu.Game.Screens { applyArrivingDefaults(false); + if (AllowTrackAdjustments != null) + musicController.AllowTrackAdjustments = AllowTrackAdjustments.Value; + if (backgroundStack?.Push(ownedBackground = CreateBackground()) != true) { // If the constructed instance was not actually pushed to the background stack, we don't want to track it unnecessarily. diff --git a/osu.Game/Screens/OsuScreenStack.cs b/osu.Game/Screens/OsuScreenStack.cs index e2a0414df7..ebbcbd7650 100644 --- a/osu.Game/Screens/OsuScreenStack.cs +++ b/osu.Game/Screens/OsuScreenStack.cs @@ -51,6 +51,6 @@ namespace osu.Game.Screens } private void setParallax(IScreen next) => - parallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * ((IOsuScreen)next)?.BackgroundParallaxAmount ?? 1.0f; + parallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * (((IOsuScreen)next)?.BackgroundParallaxAmount ?? 1.0f); } } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 0efa66bac0..9e3400b09d 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -187,9 +187,9 @@ namespace osu.Game.Screens.Play InternalButtons.Add(button); } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.SelectPrevious: InternalButtons.SelectPrevious(); @@ -211,7 +211,7 @@ namespace osu.Game.Screens.Play return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 39dafaffad..fb49dedce7 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -33,6 +33,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private const float chevron_size = 8; private SpriteIcon arrow; + private SpriteIcon iconEarly; + private SpriteIcon iconLate; private Container colourBarsEarly; private Container colourBarsLate; @@ -97,25 +99,21 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters RelativeSizeAxes = Axes.Both, Height = 0.5f, }, - new SpriteIcon + iconEarly = new SpriteIcon { Y = -10, Size = new Vector2(10), Icon = FontAwesome.Solid.ShippingFast, Anchor = Anchor.TopCentre, Origin = Anchor.Centre, - // undo any layout rotation to display the icon the correct orientation - Rotation = -Rotation, }, - new SpriteIcon + iconLate = new SpriteIcon { Y = 10, Size = new Vector2(10), Icon = FontAwesome.Solid.Bicycle, Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, - // undo any layout rotation to display the icon the correct orientation - Rotation = -Rotation, } } }, @@ -143,6 +141,15 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters arrow.Delay(200).FadeInFromZero(600); } + protected override void Update() + { + base.Update(); + + // undo any layout rotation to display icons in the correct orientation + iconEarly.Rotation = -Rotation; + iconLate.Rotation = -Rotation; + } + private void createColourBars(OsuColour colours) { var windows = HitWindows.GetAllAvailableWindows().ToArray(); diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 284ac899ed..850543136c 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -206,9 +206,9 @@ namespace osu.Game.Screens.Play.HUD base.OnHoverLost(e); } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.Back: case GlobalAction.PauseGameplay: // in the future this behaviour will differ for replays etc. @@ -220,9 +220,9 @@ namespace osu.Game.Screens.Play.HUD return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.Back: case GlobalAction.PauseGameplay: diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 13df9fefa7..54c74a7177 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -11,6 +11,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -280,9 +281,9 @@ namespace osu.Game.Screens.Play protected PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.HoldForHUD: holdingForHUD = true; @@ -311,9 +312,9 @@ namespace osu.Game.Screens.Play return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.HoldForHUD: holdingForHUD = false; diff --git a/osu.Game/Screens/Play/HotkeyExitOverlay.cs b/osu.Game/Screens/Play/HotkeyExitOverlay.cs index 8d7e2481bf..13b72ffaf6 100644 --- a/osu.Game/Screens/Play/HotkeyExitOverlay.cs +++ b/osu.Game/Screens/Play/HotkeyExitOverlay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -9,17 +10,17 @@ namespace osu.Game.Screens.Play { public class HotkeyExitOverlay : HoldToConfirmOverlay, IKeyBindingHandler { - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - if (action != GlobalAction.QuickExit) return false; + if (e.Action != GlobalAction.QuickExit) return false; BeginConfirm(); return true; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - if (action != GlobalAction.QuickExit) return; + if (e.Action != GlobalAction.QuickExit) return; AbortConfirm(); } diff --git a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs index 58fd941f36..308befe372 100644 --- a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs +++ b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -9,17 +10,17 @@ namespace osu.Game.Screens.Play { public class HotkeyRetryOverlay : HoldToConfirmOverlay, IKeyBindingHandler { - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - if (action != GlobalAction.QuickRetry) return false; + if (e.Action != GlobalAction.QuickRetry) return false; BeginConfirm(); return true; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - if (action != GlobalAction.QuickRetry) return; + if (e.Action != GlobalAction.QuickRetry) return; AbortConfirm(); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e8a2790c94..9927467bd6 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Play protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; // We are managing our own adjustments (see OnEntering/OnExiting). - public override bool AllowTrackAdjustments => false; + public override bool? AllowTrackAdjustments => false; private readonly IBindable gameActive = new Bindable(true); diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index adbb5a53f6..0c6f1ed911 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -49,11 +50,11 @@ namespace osu.Game.Screens.Play private ScheduledDelegate keyboardSeekDelegate; - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { const double keyboard_seek_amount = 5000; - switch (action) + switch (e.Action) { case GlobalAction.SeekReplayBackward: keyboardSeekDelegate?.Cancel(); @@ -83,9 +84,9 @@ namespace osu.Game.Screens.Play } } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.SeekReplayBackward: case GlobalAction.SeekReplayForward: diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index a2145b7014..b04fcba0c6 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -144,9 +144,9 @@ namespace osu.Game.Screens.Play return base.OnMouseMove(e); } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.SkipCutscene: if (!button.Enabled.Value) @@ -159,7 +159,7 @@ namespace osu.Game.Screens.Play return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 822ee1cf90..af60296344 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -327,9 +328,9 @@ namespace osu.Game.Screens.Ranking } } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.Select: statisticsPanel.ToggleVisibility(); @@ -339,7 +340,7 @@ namespace osu.Game.Screens.Ranking return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b05b7aeb32..5eceae3c6e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -514,29 +514,29 @@ namespace osu.Game.Screens.Select base.OnKeyUp(e); } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.SelectNext: - beginRepeatSelection(() => SelectNext(1, false), action); + beginRepeatSelection(() => SelectNext(1, false), e.Action); return true; case GlobalAction.SelectPrevious: - beginRepeatSelection(() => SelectNext(-1, false), action); + beginRepeatSelection(() => SelectNext(-1, false), e.Action); return true; } return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.SelectNext: case GlobalAction.SelectPrevious: - endRepeatSelection(action); + endRepeatSelection(e.Action); break; } } diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 9c0a68133c..8d2ea47757 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -172,9 +172,9 @@ namespace osu.Game.Screens.Select return base.OnClick(e); } - public virtual bool OnPressed(GlobalAction action) + public virtual bool OnPressed(KeyBindingPressEvent e) { - if (action == Hotkey) + if (e.Action == Hotkey) { TriggerClick(); return true; @@ -183,6 +183,6 @@ namespace osu.Game.Screens.Select return false; } - public virtual void OnReleased(GlobalAction action) { } + public virtual void OnReleased(KeyBindingReleaseEvent e) { } } } diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index 1eaf2c591e..1d4722cf5d 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; @@ -58,11 +59,11 @@ namespace osu.Game.Screens.Select }; } - public override bool OnPressed(GlobalAction action) + public override bool OnPressed(KeyBindingPressEvent e) { - rewindSearch = action == GlobalAction.SelectPreviousRandom; + rewindSearch = e.Action == GlobalAction.SelectPreviousRandom; - if (action != GlobalAction.SelectNextRandom && action != GlobalAction.SelectPreviousRandom) + if (e.Action != GlobalAction.SelectNextRandom && e.Action != GlobalAction.SelectPreviousRandom) { return false; } @@ -71,9 +72,9 @@ namespace osu.Game.Screens.Select return true; } - public override void OnReleased(GlobalAction action) + public override void OnReleased(KeyBindingReleaseEvent e) { - if (action == GlobalAction.SelectPreviousRandom) + if (e.Action == GlobalAction.SelectPreviousRandom) { rewindSearch = false; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index f11f9fd614..9801098952 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -53,6 +53,8 @@ namespace osu.Game.Screens.Select protected virtual bool DisplayStableImportPrompt => stableImportManager?.SupportsImportFromStable == true; + public override bool? AllowTrackAdjustments => true; + /// /// Can be null if is false. /// @@ -810,11 +812,11 @@ namespace osu.Game.Screens.Select Schedule(() => BeatmapDetails.Refresh()))); } - public virtual bool OnPressed(GlobalAction action) + public virtual bool OnPressed(KeyBindingPressEvent e) { if (!this.IsCurrentScreen()) return false; - switch (action) + switch (e.Action) { case GlobalAction.Select: FinaliseSelection(); @@ -824,7 +826,7 @@ namespace osu.Game.Screens.Select return false; } - public void OnReleased(GlobalAction action) + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Screens/StartupScreen.cs b/osu.Game/Screens/StartupScreen.cs index 15f75d7cff..be217d6b1f 100644 --- a/osu.Game/Screens/StartupScreen.cs +++ b/osu.Game/Screens/StartupScreen.cs @@ -16,8 +16,6 @@ namespace osu.Game.Screens public override bool CursorVisible => false; - public override bool AllowTrackAdjustments => false; - protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; } } diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 2562e9c57c..a892ec1ca3 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; @@ -32,9 +33,9 @@ namespace osu.Game.Skinning.Editor RelativeSizeAxes = Axes.Both; } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { - switch (action) + switch (e.Action) { case GlobalAction.Back: if (skinEditor?.State.Value != Visibility.Visible) @@ -81,7 +82,7 @@ namespace osu.Game.Skinning.Editor { if (visibility.NewValue == Visibility.Visible) { - target.Masking = true; + updateMasking(); target.AllowScaling = false; target.RelativePositionAxes = Axes.Both; @@ -92,12 +93,15 @@ namespace osu.Game.Skinning.Editor { target.AllowScaling = true; - target.ScaleTo(1, SkinEditor.TRANSITION_DURATION, Easing.OutQuint).OnComplete(_ => target.Masking = false); + target.ScaleTo(1, SkinEditor.TRANSITION_DURATION, Easing.OutQuint).OnComplete(_ => updateMasking()); target.MoveToX(0f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint); } } - public void OnReleased(GlobalAction action) + private void updateMasking() => + target.Masking = skinEditor.State.Value == Visibility.Visible; + + public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index ec25268be4..fd1f905868 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -17,10 +18,12 @@ namespace osu.Game.Skinning { public static class LegacySkinExtensions { + [CanBeNull] public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-", bool startAtCurrentTime = true, double? frameLength = null) => source.GetAnimation(componentName, default, default, animatable, looping, applyConfigFrameRate, animationSeparator, startAtCurrentTime, frameLength); + [CanBeNull] public static Drawable GetAnimation(this ISkin source, string componentName, WrapMode wrapModeS, WrapMode wrapModeT, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-", bool startAtCurrentTime = true, double? frameLength = null) diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 7895fcccca..8fc6cbde7d 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -56,7 +56,7 @@ namespace osu.Game.Skinning if (texture == null) return null; - return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, null), texture, 1f / texture.ScaleAdjust); + return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 1f / texture.ScaleAdjust); } private static string getLookupName(char character) diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 851d71f914..2bf8668ec6 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using osu.Framework.Extensions.ObjectExtensions; -using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; @@ -39,8 +38,6 @@ namespace osu.Game.Skinning public List Files { get; set; } = new List(); - public List Settings { get; set; } - public bool DeletePending { get; set; } public static SkinInfo Default { get; } = new SkinInfo diff --git a/osu.Game/Skinning/SkinStore.cs b/osu.Game/Skinning/SkinStore.cs index 153eeda130..31cadb0a24 100644 --- a/osu.Game/Skinning/SkinStore.cs +++ b/osu.Game/Skinning/SkinStore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; -using Microsoft.EntityFrameworkCore; using osu.Framework.Platform; using osu.Game.Database; @@ -14,9 +12,5 @@ namespace osu.Game.Skinning : base(contextFactory, storage) { } - - protected override IQueryable AddIncludesForDeletion(IQueryable query) => - base.AddIncludesForDeletion(query) - .Include(s => s.Settings); // don't include FileInfo. these are handled by the FileStore itself. } } diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index 76f229a799..fdb3e1d465 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -23,7 +23,10 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } protected void Test(double expected, string name, params Mod[] mods) - => Assert.AreEqual(expected, CreateDifficultyCalculator(getBeatmap(name)).Calculate(mods).StarRating); + { + // Platform-dependent math functions (Pow, Cbrt, Exp, etc) may result in minute differences. + Assert.That(CreateDifficultyCalculator(getBeatmap(name)).Calculate(mods).StarRating, Is.EqualTo(expected).Within(0.00001)); + } private WorkingBeatmap getBeatmap(string name) { diff --git a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs index 54a83f4305..b7803f3420 100644 --- a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps protected void TestToLegacy(LegacyMods expectedLegacyMods, Type[] providedModTypes) { var ruleset = CreateRuleset(); - var modInstances = ruleset.GetAllMods() + var modInstances = ruleset.CreateAllMods() .Where(mod => providedModTypes.Contains(mod.GetType())) .ToArray(); var actualLegacyMods = ruleset.ConvertToLegacyMods(modInstances); diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs index 8ce71ace69..5ce6aae647 100644 --- a/osu.Game/Tests/TestScoreInfo.cs +++ b/osu.Game/Tests/TestScoreInfo.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests RulesetID = ruleset.ID ?? 0; Mods = excessMods - ? ruleset.CreateInstance().GetAllMods().ToArray() + ? ruleset.CreateInstance().CreateAllMods().ToArray() : new Mod[] { new TestModHardRock(), new TestModDoubleTime() }; TotalScore = 2845370; diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index a393802309..1e26036116 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -16,26 +16,38 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Screens.Menu; using osu.Game.Skinning; namespace osu.Game.Tests.Visual { public abstract class EditorTestScene : ScreenTestScene { - protected EditorBeatmap EditorBeatmap; + private TestEditorLoader editorLoader; - protected TestEditor Editor { get; private set; } + protected TestEditor Editor => editorLoader.Editor; - protected EditorClock EditorClock { get; private set; } + protected EditorBeatmap EditorBeatmap => Editor.ChildrenOfType().Single(); + protected EditorClock EditorClock => Editor.ChildrenOfType().Single(); /// /// Whether any saves performed by the editor should be isolate (and not persist) to the underlying . /// protected virtual bool IsolateSavingFromDatabase => true; + // required for screen transitions to work properly + // (see comment in EditorLoader.LogoArriving). + [Cached] + private OsuLogo logo = new OsuLogo + { + Alpha = 0 + }; + [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio, RulesetStore rulesets) { + Add(logo); + var working = CreateWorkingBeatmap(Ruleset.Value); Beatmap.Value = working; @@ -53,13 +65,11 @@ namespace osu.Game.Tests.Visual AddStep("load editor", LoadEditor); AddUntilStep("wait for editor to load", () => EditorComponentsReady); - AddStep("get beatmap", () => EditorBeatmap = Editor.ChildrenOfType().Single()); - AddStep("get clock", () => EditorClock = Editor.ChildrenOfType().Single()); } protected virtual void LoadEditor() { - LoadScreen(Editor = CreateEditor()); + LoadScreen(editorLoader = new TestEditorLoader()); } /// @@ -70,7 +80,14 @@ namespace osu.Game.Tests.Visual protected sealed override Ruleset CreateRuleset() => CreateEditorRuleset(); - protected virtual TestEditor CreateEditor() => new TestEditor(); + protected class TestEditorLoader : EditorLoader + { + public TestEditor Editor { get; private set; } + + protected sealed override Editor CreateEditor() => Editor = CreateTestEditor(this); + + protected virtual TestEditor CreateTestEditor(EditorLoader loader) => new TestEditor(loader); + } protected class TestEditor : Editor { @@ -86,7 +103,14 @@ namespace osu.Game.Tests.Visual public new void Paste() => base.Paste(); + public new void SwitchToDifficulty(BeatmapInfo beatmapInfo) => base.SwitchToDifficulty(beatmapInfo); + public new bool HasUnsavedChanges => base.HasUnsavedChanges; + + public TestEditor(EditorLoader loader = null) + : base(loader) + { + } } private class TestBeatmapManager : BeatmapManager diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index f38aaa9358..881c4bab02 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -30,8 +30,6 @@ namespace osu.Game.Tests.Visual /// public abstract class OsuGameTestScene : OsuManualInputManagerTestScene { - private GameHost host; - protected TestOsuGame Game; protected override bool UseFreshStoragePerRun => true; @@ -39,10 +37,8 @@ namespace osu.Game.Tests.Visual protected override bool CreateNestedActionContainer => false; [BackgroundDependencyLoader] - private void load(GameHost host) + private void load() { - this.host = host; - Child = new Box { RelativeSizeAxes = Axes.Both, @@ -55,7 +51,7 @@ namespace osu.Game.Tests.Visual { AddStep("Create new game instance", () => { - if (Game != null) + if (Game?.Parent != null) { Remove(Game); Game.Dispose(); @@ -81,10 +77,7 @@ namespace osu.Game.Tests.Visual protected void CreateGame() { - Game = new TestOsuGame(LocalStorage, API); - Game.SetHost(host); - - Add(Game); + AddGame(Game = new TestOsuGame(LocalStorage, API)); } protected void PushAndConfirm(Func newScreen) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 93491c800f..b34f7e2d5f 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual if (!AllowFail) { - var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail); + var noFailMod = ruleset.CreateMod(); if (noFailMod != null) SelectedMods.Value = new[] { noFailMod }; } diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index df724404e9..6d48104131 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -44,7 +44,7 @@ namespace osu.Game.Users.Drawables protected override double LoadDelay => 200; - private readonly bool openOnClick; + private readonly bool isInteractive; private readonly bool showUsernameTooltip; private readonly bool showGuestOnNull; @@ -52,12 +52,12 @@ namespace osu.Game.Users.Drawables /// Construct a new UpdateableAvatar. /// /// The initial user to display. - /// Whether to open the user's profile when clicked. - /// Whether to show the username rather than "view profile" on the tooltip. + /// If set to true, hover/click sounds will play and clicking the avatar will open the user's profile. + /// Whether to show the username rather than "view profile" on the tooltip. (note: this only applies if is also true) /// Whether to show a default guest representation on null user (as opposed to nothing). - public UpdateableAvatar(User user = null, bool openOnClick = true, bool showUsernameTooltip = false, bool showGuestOnNull = true) + public UpdateableAvatar(User user = null, bool isInteractive = true, bool showUsernameTooltip = false, bool showGuestOnNull = true) { - this.openOnClick = openOnClick; + this.isInteractive = isInteractive; this.showUsernameTooltip = showUsernameTooltip; this.showGuestOnNull = showGuestOnNull; @@ -69,14 +69,22 @@ namespace osu.Game.Users.Drawables if (user == null && !showGuestOnNull) return null; - var avatar = new ClickableAvatar(user) + if (isInteractive) { - OpenOnClick = openOnClick, - ShowUsernameTooltip = showUsernameTooltip, - RelativeSizeAxes = Axes.Both, - }; - - return avatar; + return new ClickableAvatar(user) + { + OpenOnClick = true, + ShowUsernameTooltip = showUsernameTooltip, + RelativeSizeAxes = Axes.Both, + }; + } + else + { + return new DrawableAvatar(user) + { + RelativeSizeAxes = Axes.Both, + }; + } } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d80dd075ee..0460de6d72 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,9 +35,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + diff --git a/osu.iOS.props b/osu.iOS.props index 8ce757974e..51ca381b63 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,8 +70,8 @@ - - + + @@ -93,12 +93,12 @@ - + - +