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..0e60a5a99e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,11 +51,11 @@ - - + + - + 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 265a7ccd21..4b11a907c9 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.6915334809485199d, "diffcalc-test")] - [TestCase(1.0366129190339499d, "zero-length-sliders")] + [TestCase(6.5942751255134722d, "diffcalc-test")] + [TestCase(1.0421872184658874d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(8.3966904501824473d, "diffcalc-test")] - [TestCase(1.2732783892964523d, "zero-length-sliders")] + [TestCase(8.2925466073736125d, "diffcalc-test")] + [TestCase(1.2735460729578572d, "zero-length-sliders")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); 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..59fe353bd2 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -18,5 +18,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/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 41b0a88f11..190195f0a6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -108,6 +108,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/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 5812e8cf75..8c76f8a128 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; @@ -115,9 +116,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 +130,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/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/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/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/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/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/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs index 61565c88f4..4e2a91af78 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; @@ -82,7 +83,23 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - public void TestJoinRoomWithPassword() + public void TestJoinRoomWithIncorrectPassword() + { + 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); + } + + [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 cc64d37116..aeb800f58a 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -77,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()); @@ -104,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); @@ -131,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); @@ -399,7 +418,15 @@ namespace osu.Game.Tests.Visual.Navigation 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() => 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/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/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/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/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/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 6c8238a1b8..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 virtual 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 2cb696be0a..226c39c030 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -8,6 +8,7 @@ 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; @@ -55,12 +56,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 this.FadeOut(fade_duration, Easing.OutQuint); } - public bool OnPressed(GlobalAction action) + public bool OnPressed(KeyBindingPressEvent e) { if (State.Value == Visibility.Hidden) return false; - if (action == GlobalAction.Back) + if (e.Action == GlobalAction.Back) { Hide(); return true; @@ -69,7 +70,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 return false; } - public void OnReleased(GlobalAction action) + 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/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/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 07a0cfb57f..bbde03aa10 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -186,9 +186,9 @@ namespace osu.Game.Overlays.BeatmapListing return true; } - public override bool OnPressed(GlobalAction action) + public override bool OnPressed(KeyBindingPressEvent e) { - if (!base.OnPressed(action)) + if (!base.OnPressed(e)) return false; TextChanged?.Invoke(); 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/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/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/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/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/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/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..d55cccd414 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,61 @@ namespace osu.Game.Screens.OnlinePlay.Lounge } private OsuPasswordTextBox passwordTextbox; + private TriangleButton joinButton; + private OsuSpriteText errorText; [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { 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, + }, } }; + + joinButton.Action = () => lounge?.Join(room, passwordTextbox.Text, null, joinFailed); + } + + private void joinFailed(string error) + { + passwordTextbox.Text = string.Empty; + + errorText.Text = error; + errorText + .FadeIn() + .FlashColour(Color4.White, 200) + .Delay(1000) + .FadeOutFromOne(1000, Easing.In); + + Body.Shake(); } protected override void LoadComplete() @@ -215,7 +249,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/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 89f61785e8..7562df5a3b 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/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/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..390b026497 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..2158772b83 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,8 +70,8 @@ - - + + @@ -93,12 +93,12 @@ - + - +